-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Expand file tree
/
Copy pathTrieProof.sol
More file actions
256 lines (229 loc) · 12.3 KB
/
TrieProof.sol
File metadata and controls
256 lines (229 loc) · 12.3 KB
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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.6.0) (utils/cryptography/TrieProof.sol)
pragma solidity ^0.8.26;
import {Bytes} from "../Bytes.sol";
import {Memory} from "../Memory.sol";
import {RLP} from "../RLP.sol";
/**
* @dev Library for verifying Ethereum Merkle-Patricia trie inclusion proofs.
*
* The {traverse} and {verify} functions can be used to prove the following value:
*
* * Transaction against the transactionsRoot of a block.
* * Event against receiptsRoot of a block.
* * Account details (RLP encoding of [nonce, balance, storageRoot, codeHash]) against the stateRoot of a block.
* * Storage slot (RLP encoding of the value) against the storageRoot of an account.
*
* Proving a storage slot is usually done in 3 steps:
*
* * From the stateRoot of a block, process the account proof (see `eth_getProof`) to get the account details.
* * RLP decode the account details to extract the storageRoot.
* * Use storageRoot of that account to process the storageProof (again, see `eth_getProof`).
*
* See https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie[Merkle-Patricia trie]
*
* Based on https://github.com/ethereum-optimism/optimism/blob/ef970556e668b271a152124023a8d6bb5159bacf/packages/contracts-bedrock/src/libraries/trie/MerkleTrie.sol[this implementation from optimism].
*/
library TrieProof {
using Bytes for *;
using RLP for *;
using Memory for *;
enum Prefix {
EXTENSION_EVEN, // 0 - Extension node with even length path
EXTENSION_ODD, // 1 - Extension node with odd length path
LEAF_EVEN, // 2 - Leaf node with even length path
LEAF_ODD // 3 - Leaf node with odd length path
}
enum ProofError {
NO_ERROR, // No error occurred during proof traversal
EMPTY_KEY, // The provided key is empty
INVALID_ROOT, // The validation of the root node failed
INVALID_LARGE_NODE, // The validation of a large node failed
INVALID_SHORT_NODE, // The validation of a short node failed
EMPTY_PATH, // The path in a leaf or extension node is empty
INVALID_PATH_REMAINDER, // The path remainder in a leaf or extension node is invalid
EMPTY_EXTENSION_PATH_REMAINDER, // The path remainder in an extension node is empty
INVALID_EXTRA_PROOF_ELEMENT, // A leaf value should be the last proof element
EMPTY_VALUE, // The leaf value is empty
MISMATCH_LEAF_PATH_KEY_REMAINDER, // The path remainder in a leaf node doesn't match the key remainder
UNKNOWN_NODE_PREFIX, // The node prefix is unknown
UNPARSEABLE_NODE, // The node cannot be parsed from RLP encoding
INVALID_PROOF // General failure during proof traversal
}
error TrieProofTraversalError(ProofError err);
/// @dev The radix of the Ethereum trie
uint256 internal constant EVM_TREE_RADIX = 16;
/// @dev Number of items in a branch node (16 children + 1 value)
uint256 internal constant BRANCH_NODE_LENGTH = EVM_TREE_RADIX + 1;
/// @dev Number of items in leaf or extension nodes (always 2)
uint256 internal constant LEAF_OR_EXTENSION_NODE_LENGTH = 2;
/// @dev Verifies a `proof` against a given `key`, `value`, and `root` hash.
function verify(
bytes memory value,
bytes32 root,
bytes memory key,
bytes[] memory proof
) internal pure returns (bool) {
(bytes memory processedValue, ProofError err) = tryTraverse(root, key, proof);
return processedValue.equal(value) && err == ProofError.NO_ERROR;
}
/**
* @dev Traverses a proof with a given key and returns the value.
*
* Reverts with {TrieProofTraversalError} if proof is invalid.
*/
function traverse(bytes32 root, bytes memory key, bytes[] memory proof) internal pure returns (bytes memory) {
(bytes memory value, ProofError err) = tryTraverse(root, key, proof);
require(err == ProofError.NO_ERROR, TrieProofTraversalError(err));
return value;
}
/**
* @dev Traverses a proof with a given key and returns the value and an error flag
* instead of reverting if the proof is invalid. This function may still revert if
* malformed input leads to RLP decoding errors.
*/
function tryTraverse(
bytes32 root,
bytes memory key,
bytes[] memory proof
) internal pure returns (bytes memory value, ProofError err) {
if (key.length == 0) return (_emptyBytesMemory(), ProofError.EMPTY_KEY);
// Expand the key
bytes memory keyExpanded = key.toNibbles();
bytes32 currentNodeId;
uint256 currentNodeIdLength;
// Free memory pointer cache
Memory.Pointer fmp = Memory.getFreeMemoryPointer();
// Traverse proof
uint256 keyIndex = 0;
for (uint256 i = 0; i < proof.length; ++i) {
// validates the encoded node matches the expected node id
bytes memory encoded = proof[i];
if (keyIndex == 0) {
// Root node must match root hash
if (keccak256(encoded) != root) return (_emptyBytesMemory(), ProofError.INVALID_ROOT);
} else if (encoded.length >= 32) {
// Large nodes are stored as hashes
if (currentNodeIdLength != 32 || keccak256(encoded) != currentNodeId)
return (_emptyBytesMemory(), ProofError.INVALID_LARGE_NODE);
} else {
// Short nodes must match directly
if (currentNodeIdLength != encoded.length || bytes32(encoded) != currentNodeId)
return (_emptyBytesMemory(), ProofError.INVALID_SHORT_NODE);
}
// decode the current node as an RLP list, and process it
for (Memory.Slice[] memory decoded = encoded.decodeList(); ; ) {
if (decoded.length == BRANCH_NODE_LENGTH) {
// If we've consumed the entire key, the value must be in the last slot
// Otherwise, continue down the branch specified by the next nibble in the key
if (keyIndex == keyExpanded.length) {
return _validateLastItem(decoded[EVM_TREE_RADIX], proof.length, i);
} else {
bytes1 branchKey = keyExpanded[keyIndex];
Memory.Slice childNode = decoded[uint8(branchKey)];
(currentNodeId, currentNodeIdLength) = _getNodeId(childNode);
keyIndex += 1;
if (currentNodeIdLength == 32 || _match(childNode, proof, i + 1)) {
break;
}
decoded = childNode.readList();
}
} else if (decoded.length == LEAF_OR_EXTENSION_NODE_LENGTH) {
bytes[] memory proof_ = proof;
bytes memory path = decoded[0].readBytes().toNibbles(); // expanded path
// The following is equivalent to path.length < 2 because toNibbles can't return odd-length buffers
if (path.length == 0) {
return (_emptyBytesMemory(), ProofError.EMPTY_PATH);
}
uint8 prefix = uint8(path[0]); // path encoding nibble (node type + parity), see {Prefix}
Memory.Slice keyRemainder = keyExpanded.asSlice().slice(keyIndex); // Remaining key to match
Memory.Slice pathRemainder = path.asSlice().slice(2 - (prefix % 2)); // Path after the prefix
uint256 pathRemainderLength = pathRemainder.length();
// pathRemainder must not be longer than keyRemainder and must match the start of keyRemainder
if (
pathRemainderLength > keyRemainder.length() ||
!pathRemainder.equal(keyRemainder.slice(0, pathRemainderLength))
) {
return (_emptyBytesMemory(), ProofError.INVALID_PATH_REMAINDER);
}
if (prefix <= uint8(Prefix.EXTENSION_ODD)) {
// Eq to: prefix == EXTENSION_EVEN || prefix == EXTENSION_ODD
if (pathRemainderLength == 0) {
return (_emptyBytesMemory(), ProofError.EMPTY_EXTENSION_PATH_REMAINDER);
}
// Increment keyIndex by the number of nibbles consumed and continue traversal
Memory.Slice childNode = decoded[1];
(currentNodeId, currentNodeIdLength) = _getNodeId(childNode);
keyIndex += pathRemainderLength;
if (currentNodeIdLength == 32 || _match(childNode, proof_, i + 1)) {
break;
}
decoded = childNode.readList();
} else if (prefix <= uint8(Prefix.LEAF_ODD)) {
// Eq to: prefix == LEAF_EVEN || prefix == LEAF_ODD
//
// Leaf node (terminal) - return its value if key matches completely
// we already know that pathRemainder is a prefix of keyRemainder, so checking the length sufficient
return
pathRemainderLength == keyRemainder.length()
? _validateLastItem(decoded[1], proof_.length, i)
: (_emptyBytesMemory(), ProofError.MISMATCH_LEAF_PATH_KEY_REMAINDER);
} else {
return (_emptyBytesMemory(), ProofError.UNKNOWN_NODE_PREFIX);
}
} else {
return (_emptyBytesMemory(), ProofError.UNPARSEABLE_NODE);
}
}
// Reset memory before next iteration. Deallocates `decoded` and `path`.
Memory.unsafeSetFreeMemoryPointer(fmp);
}
// If we've gone through all proof elements without finding a value, the proof is invalid
return (_emptyBytesMemory(), ProofError.INVALID_PROOF);
}
/**
* @dev Validates that we've reached a valid leaf value and this is the last proof element.
* Ensures the value is not empty and no extra proof elements exist.
*/
function _validateLastItem(
Memory.Slice item,
uint256 trieProofLength,
uint256 i
) private pure returns (bytes memory, ProofError) {
if (i != trieProofLength - 1) {
return (_emptyBytesMemory(), ProofError.INVALID_EXTRA_PROOF_ELEMENT);
}
bytes memory value = item.readBytes();
return (value, value.length == 0 ? ProofError.EMPTY_VALUE : ProofError.NO_ERROR);
}
/**
* @dev Extracts the node ID (hash or raw data based on size)
*
* For short nodes (encoded length < 32 bytes) the node ID is the node content itself,
* For larger nodes, the node ID is the hash of the encoded node data.
*
* [NOTE]
* ====
* If a 32-byte input is provided (can occur with inline child references), it is used directly (like short nodes).
* When `nodeIdLength == 32`, inline processing is skipped. The next traversal step then checks whether the next
* node is large and its hash matches those raw bytes. If that is not the case, it returns {INVALID_LARGE_NODE}.
*
* If the input is empty (e.g. when traversing a branch node whose target child slot is empty, meaning the key
* does not exist in the trie), calling this function will panic with {ARRAY_OUT_OF_BOUNDS}. In practice, this
* never occurs because {readList} always returns slices with at least 1 byte (every RLP element includes its
* prefix byte, e.g., empty string is `0x80`).
* ====
*/
function _getNodeId(Memory.Slice node) private pure returns (bytes32 nodeId, uint256 nodeIdLength) {
uint256 nodeLength = node.length();
return nodeLength < 33 ? (node.load(0), nodeLength) : (node.readBytes32(), 32);
}
function _emptyBytesMemory() private pure returns (bytes memory result) {
assembly ("memory-safe") {
result := 0x60 // mload(0x60) is always 0
}
}
function _match(Memory.Slice slice, bytes[] memory array, uint256 index) private pure returns (bool) {
return index < array.length && slice.equal(array[index].asSlice());
}
}