forked from ensdomains/evmgateway
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathScrollVerifierHooks.sol
executable file
·176 lines (160 loc) · 6.77 KB
/
ScrollVerifierHooks.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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IVerifierHooks, InvalidProof, NOT_A_CONTRACT, NULL_CODE_HASH} from '../IVerifierHooks.sol';
interface IPoseidon {
function poseidon(
uint256[2] memory,
uint256
) external view returns (bytes32);
}
contract ScrollVerifierHooks is IVerifierHooks {
IPoseidon immutable _poseidon;
constructor(IPoseidon poseidon) {
_poseidon = poseidon;
}
// https://github.com/scroll-tech/scroll/blob/738c85759d0248c005469972a49fc983b031ff1c/contracts/src/libraries/verifier/ZkTrieVerifier.sol#L259
// https://github.com/scroll-tech/go-ethereum/blob/staging/trie/zk_trie.go#L176
// https://github.com/scroll-tech/zktrie/blob/main/trie/zk_trie_proof.go#L30
// https://github.com/ethereum/go-ethereum/blob/master/trie/proof.go#L114
// https://github.com/scroll-tech/mpt-circuit/blob/v0.7/spec/mpt-proof.md#storage-segmenttypes
// 20240622: we ignore magic bytes (doesn't do anything)
// https://github.com/scroll-tech/zktrie/blob/23181f209e94137f74337b150179aeb80c72e7c8/trie/zk_trie_proof.go#L13
// bytes32 constant MAGIC = keccak256("THIS IS SOME MAGIC BYTES FOR SMT m1rRXgP2xpDI");
// 20241205: we ignore compressed flags (doesn't do anything)
// https://github.com/scroll-tech/zktrie/blob/23181f209e94137f74337b150179aeb80c72e7c8/trie/zk_trie_node.go#L30
uint256 constant NODE_LEAF = 4;
uint256 constant NODE_LEAF_EMPTY = 5;
uint256 constant NODE_LEAF_LEAF = 6; // XX
uint256 constant NODE_LEAF_BRANCH = 7; // XB
uint256 constant NODE_BRANCH_LEAF = 8; // BX
uint256 constant NODE_BRANCH_BRANCH = 9; // BB
// https://docs.scroll.io/en/technology/sequencer/zktrie/
uint256 constant MAX_TRIE_DEPTH = 248;
function verifyAccountState(
bytes32 stateRoot,
address account,
bytes memory encodedProof
) external view returns (bytes32 storageRoot) {
(
bytes32 keyHash,
bytes32 leafHash,
bytes memory leaf,
bool exists
) = walkTree(bytes20(account), encodedProof, stateRoot, 230); // flags = 0x05080000
if (leafHash == 0) return NOT_A_CONTRACT;
bytes32 temp;
bytes32 amount;
bytes32 codeHash;
assembly {
temp := mload(add(leaf, 69)) // nonce||codesize||0
amount := mload(add(leaf, 101))
storageRoot := mload(add(leaf, 133))
codeHash := mload(add(leaf, 165))
}
bytes32 h = poseidonHash2(storageRoot, poseidonHash1(codeHash), 1280);
h = poseidonHash2(poseidonHash2(temp, amount, 1280), h, 1280);
assembly {
temp := mload(add(leaf, 197))
}
h = poseidonHash2(h, temp, 1280);
h = poseidonHash2(keyHash, h, 4);
if (leafHash != h) revert InvalidProof(); // InvalidAccountLeafNodeHash
if (codeHash == NULL_CODE_HASH || !exists) storageRoot = NOT_A_CONTRACT;
}
function verifyStorageValue(
bytes32 storageRoot,
address /*target*/,
uint256 slot,
bytes memory encodedProof
) external view returns (bytes32 value) {
(
bytes32 keyHash,
bytes32 leafHash,
bytes memory leaf,
bool exists
) = walkTree(bytes32(slot), encodedProof, storageRoot, 102); // flags = 0x01010000
if (leafHash != 0) {
assembly {
value := mload(add(leaf, 69))
}
bytes32 h = poseidonHash2(keyHash, poseidonHash1(value), 4);
if (leafHash != h) revert InvalidProof(); // InvalidStorageLeafNodeHash
if (!exists) value = 0;
}
}
function walkTree(
bytes32 key,
bytes memory encodedProof,
bytes32 rootHash,
uint256 leafSize
)
internal
view
returns (bytes32 keyHash, bytes32 h, bytes memory v, bool exists)
{
bytes[] memory proof = abi.decode(encodedProof, (bytes[]));
keyHash = poseidonHash1(key);
h = rootHash;
for (uint256 i; ; i++) {
if (i == proof.length || i >= MAX_TRIE_DEPTH) revert InvalidProof();
v = proof[i];
if (v.length == 0) revert InvalidProof();
uint256 nodeType = uint8(v[0]);
if (nodeType == NODE_LEAF_EMPTY) {
if (h != 0) revert InvalidProof();
break;
} else if (nodeType == NODE_LEAF) {
if (v.length != leafSize) revert InvalidProof();
// NOTE: leafSize is >= 33
if (uint8(v[leafSize - 33]) != 32) revert InvalidProof(); // InvalidKeyPreimageLength
// Proof is invalid if there are un-used proof elements after this leaf node and magic bytes
if (keccak256(proof[i + 1]) != keccak256("THIS IS SOME MAGIC BYTES FOR SMT m1rRXgP2xpDI")) revert InvalidProof();
if (proof.length - 1 != i + 1) revert InvalidProof();
bytes32 temp;
assembly {
temp := mload(add(v, 33))
}
if (temp == keyHash) {
assembly {
temp := mload(add(v, leafSize))
}
if (temp != key) revert InvalidProof(); // InvalidKeyPreimage
exists = true;
} else {
// If the trie does not contain a value for key, the returned proof contains all
// nodes of the longest existing prefix of the key (at least the root node), ending
// with the node that proves the absence of the key.
bytes32 p = bytes32((1 << i) - 1); // prefix mask
if ((temp & p) != (keyHash & p)) revert InvalidProof();
// this is a proof for a different value that traverses to the same place
keyHash = temp;
}
break;
} else if (
nodeType < NODE_LEAF_LEAF ||
nodeType > NODE_BRANCH_BRANCH ||
v.length != 65
) {
revert InvalidProof(); // expected node
}
bytes32 l;
bytes32 r;
assembly {
l := mload(add(v, 33))
r := mload(add(v, 65))
}
if (h != poseidonHash2(l, r, nodeType)) revert InvalidProof();
h = uint256(keyHash >> i) & 1 == 0 ? l : r;
}
}
function poseidonHash1(bytes32 x) internal view returns (bytes32) {
return poseidonHash2(x >> 128, (x << 128) >> 128, 512);
}
function poseidonHash2(
bytes32 v0,
bytes32 v1,
uint256 domain
) internal view returns (bytes32) {
return _poseidon.poseidon([uint256(v0), uint256(v1)], domain);
}
}