-
Notifications
You must be signed in to change notification settings - Fork 34
/
Summa.sol
220 lines (198 loc) · 8.02 KB
/
Summa.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.18;
// Uncomment this line to use console.log
//import "hardhat/console.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./interfaces/IVerifier.sol";
contract Summa is Ownable {
/**
* @dev Struct representing the configuration of the Summa instance
* @param mstLevels The number of levels of the Merkle sum tree
* @param currenciesCount The number of cryptocurrencies supported by the Merkle sum tree
* @param balanceByteRange The number of bytes used to represent the balance of a cryptocurrency in the Merkle sum tree
*/
struct SummaConfig {
uint16 mstLevels;
uint16 currenciesCount;
uint8 balanceByteRange;
}
/**
* @dev Struct representing an address ownership proof submitted by the CEX
* @param cexAddress The address owned by the CEX (submitted as a string, as it can be a non-EVM address)
* @param chain The name of the chain name where the address belongs (e.g., ETH, BTC)
* @param signature The signature of the message signed by the address public key
* @param message The message signed by the address public key
*/
struct AddressOwnershipProof {
string cexAddress;
string chain;
bytes signature;
bytes message;
}
/**
* @dev Struct identifying a cryptocurrency traded on the CEX
* @param name The name of the cryptocurrency
* @param chain The name of the chain name where the cryptocurrency lives (e.g., ETH, BTC)
*/
struct Cryptocurrency {
string name;
string chain;
}
/**
* @dev Struct representing a commitment submitted by the CEX.
* @param mstRoot Merkle sum tree root of the CEX's liabilities
* @param rootBalances The total sums of the liabilities included in the tree
* @param blockchainNames The names of the blockchains where the CEX holds the cryptocurrencies included into the tree
* @param cryptocurrencyNames The names of the cryptocurrencies included into the tree
*/
struct Commitment {
uint256 mstRoot;
uint256[] rootBalances;
string[] cryptocurrencyNames;
string[] blockchainNames;
}
// Summa configuration
SummaConfig public config;
// User inclusion proof verifier
IVerifier private immutable inclusionVerifier;
// List of all address ownership proofs submitted by the CEX
AddressOwnershipProof[] public addressOwnershipProofs;
function getAddressOwnershipProof(
bytes32 addressHash
) public view returns (AddressOwnershipProof memory) {
require(
_ownershipProofByAddress[addressHash] > 0,
"Address not verified"
);
// -1 comes from the fact that 0 is reserved to distinguish the case when the proof has not yet been submitted
return
addressOwnershipProofs[_ownershipProofByAddress[addressHash] - 1];
}
// Convenience mapping to check if an address has already been verified
mapping(bytes32 => uint256) private _ownershipProofByAddress;
// Solvency commitments by timestamp submitted by the CEX
mapping(uint256 => Commitment) public commitments;
event AddressOwnershipProofSubmitted(
AddressOwnershipProof[] addressOwnershipProofs
);
event LiabilitiesCommitmentSubmitted(
uint256 indexed timestamp,
uint256 mstRoot,
uint256[] rootBalances,
Cryptocurrency[] cryptocurrencies
);
constructor(
IVerifier _inclusionVerifier,
uint16 mstLevels,
uint16 currenciesCount,
uint8 balanceByteRange
) {
inclusionVerifier = _inclusionVerifier;
config = SummaConfig(mstLevels, currenciesCount, balanceByteRange);
}
/**
* @dev Submit an optimistic proof of multiple address ownership for a CEX. The proof is subject to an off-chain verification as it's not feasible to verify the signatures of non-EVM chains in an Ethereum smart contract.
* @param _addressOwnershipProofs The list of address ownership proofs
*/
function submitProofOfAddressOwnership(
AddressOwnershipProof[] memory _addressOwnershipProofs
) public onlyOwner {
for (uint i = 0; i < _addressOwnershipProofs.length; i++) {
bytes32 addressHash = keccak256(
abi.encodePacked(_addressOwnershipProofs[i].cexAddress)
);
uint256 proofIndex = _ownershipProofByAddress[addressHash];
require(proofIndex == 0, "Address already verified");
addressOwnershipProofs.push(_addressOwnershipProofs[i]);
_ownershipProofByAddress[addressHash] = addressOwnershipProofs
.length;
require(
bytes(_addressOwnershipProofs[i].cexAddress).length != 0 &&
bytes(_addressOwnershipProofs[i].chain).length != 0 &&
_addressOwnershipProofs[i].signature.length != 0 &&
_addressOwnershipProofs[i].message.length != 0,
"Invalid proof of address ownership"
);
}
emit AddressOwnershipProofSubmitted(_addressOwnershipProofs);
}
/**
* @dev Submit commitment for a CEX
* @param mstRoot Merkle sum tree root of the CEX's liabilities
* @param rootBalances The total sums of the liabilities included into the Merkle sum tree
* @param cryptocurrencies The cryptocurrencies included into the Merkle sum tree
* @param timestamp The timestamp at which the CEX took the snapshot of its assets and liabilities
*/
function submitCommitment(
uint256 mstRoot,
uint256[] memory rootBalances,
Cryptocurrency[] memory cryptocurrencies,
uint256 timestamp
) public onlyOwner {
require(mstRoot != 0, "Invalid MST root");
require(
rootBalances.length == cryptocurrencies.length,
"Root liabilities sums and liabilities number mismatch"
);
string[] memory cryptocurrencyNames = new string[](
cryptocurrencies.length
);
string[] memory blockchainNames = new string[](cryptocurrencies.length);
for (uint i = 0; i < cryptocurrencies.length; i++) {
require(
bytes(cryptocurrencies[i].chain).length != 0 &&
bytes(cryptocurrencies[i].name).length != 0,
"Invalid cryptocurrency"
);
require(
rootBalances[i] != 0,
"All root sums should be greater than zero"
);
cryptocurrencyNames[i] = cryptocurrencies[i].name;
blockchainNames[i] = cryptocurrencies[i].chain;
}
commitments[timestamp] = Commitment(
mstRoot,
rootBalances,
cryptocurrencyNames,
blockchainNames
);
emit LiabilitiesCommitmentSubmitted(
timestamp,
mstRoot,
rootBalances,
cryptocurrencies
);
}
/**
* Verify the proof of user inclusion into the liabilities tree
* @param proof ZK proof
* @param publicInputs proof inputs
*/
function verifyInclusionProof(
bytes memory proof,
uint256[] memory publicInputs,
uint256 timestamp
) public view returns (bool) {
require(
commitments[timestamp].mstRoot == publicInputs[1],
"Invalid MST root"
);
for (uint i = 2; i < publicInputs.length; i++) {
require(
commitments[timestamp].rootBalances[i - 2] == publicInputs[i],
"Invalid root balance"
);
}
// "require" won't catch the exception thrown by the verifier, so we need to catch it manually
try inclusionVerifier.verifyProof(proof, publicInputs) returns (
bool result
) {
return result;
} catch (bytes memory /*lowLevelData*/) {
// force revert to return the error message
require(false, "Invalid inclusion proof");
return false;
}
}
}