-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Add TrieProof library #5826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add TrieProof library #5826
Changes from 109 commits
9eb5f1c
2d397f4
2a0fb7e
a7e61c3
1aae8bb
1b2679a
d514606
14fa04e
d0d55fc
7b3cb66
95149f8
ad5d4ac
18540ef
163f27c
c484289
e0d4790
a6f9053
e2b5e4c
203d1a2
48eabc1
63ced95
f342756
23dba37
0cacca2
831e8ab
5da111f
ba2293e
9409bc6
e740dac
0332ffe
608e3cd
ac92bb4
6094bb7
6bb96d5
860e5a8
ecdb768
95907aa
124ccee
c3237df
27f0a9b
282ce39
bdd2cf1
42c79f1
5754ab8
44f0e14
05c73bd
3a6fbf6
e67e8b4
3385718
40d7922
89860bc
9b58730
77ffa8c
ce91c80
b3e3add
2f3107c
4383e01
5a44b11
d6db2d7
3847050
4fd1947
c4e0375
acb14cb
2208006
13f4d8f
502a520
aab9274
aa26e48
948f0a1
d4bfb8b
138de7f
5efeb37
00ff228
fd7d2b5
940ede8
5b93acb
48c7dcb
eaf3527
f587b88
03f42b9
67a74b0
85d638e
62f255d
4d4edb8
1286c96
9853d3d
2ef93d2
6211aed
466ecdf
0b6d207
d47d52e
fc69547
702bf9c
7020ab4
a37e305
4366235
2a9a3ec
60f5d7d
c3cacbd
1ed7f87
b3c7f16
658c9ca
8f60bec
278ce24
ea53d08
8fbcb34
ff31d24
0706595
0a3e229
6e38f3e
b5bc785
1725d92
05d05c1
926d704
47a7d2f
3e6b05f
77192fe
18e1c8e
70d76d9
13619d5
8d80329
66ddaa1
217fdf1
7a7bb97
536dc09
9603aee
39e56bb
8f7ed6a
4004388
ad5476b
19336bf
14fe8e4
8707438
486a562
bdff2ad
80149e4
0600dd4
8a04d05
b683357
86ed474
6a6a4b2
30cefc6
784ff8a
d19f5f9
2ca2a3d
bb02a8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'openzeppelin-solidity': minor | ||
| --- | ||
|
|
||
| `TrieProof`: Add library for verifying Ethereum Merkle-Patricia trie inclusion proofs. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,213 @@ | ||||||||||||||||||||||
| // SPDX-License-Identifier: MIT | ||||||||||||||||||||||
| pragma solidity ^0.8.27; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import {Math} from "../math/Math.sol"; | ||||||||||||||||||||||
| 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. | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * Ethereum's State Trie state layout is a 4-item array of `[nonce, balance, storageRoot, codeHash]` | ||||||||||||||||||||||
| * See https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie[Merkle-Patricia trie] | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| library TrieProof { | ||||||||||||||||||||||
| 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 verification | ||||||||||||||||||||||
| EMPTY_KEY, // The provided key is empty | ||||||||||||||||||||||
| INDEX_OUT_OF_BOUNDS, // Array index access is out of bounds | ||||||||||||||||||||||
| INVALID_ROOT_HASH, // The provided root hash doesn't match the proof | ||||||||||||||||||||||
| INVALID_LARGE_INTERNAL_HASH, // Internal node hash exceeds expected size | ||||||||||||||||||||||
| INVALID_INTERNAL_NODE_HASH, // Internal node hash doesn't match expected value | ||||||||||||||||||||||
| EMPTY_VALUE, // The value to verify is empty | ||||||||||||||||||||||
| INVALID_EXTRA_PROOF_ELEMENT, // Proof contains unexpected additional elements | ||||||||||||||||||||||
| MISMATCH_LEAF_PATH_KEY_REMAINDERS, // Leaf path remainder doesn't match key remainder | ||||||||||||||||||||||
| INVALID_PATH_REMAINDER, // Path remainder doesn't match expected value | ||||||||||||||||||||||
| UNKNOWN_NODE_PREFIX, // Node prefix is not recognized | ||||||||||||||||||||||
| UNPARSEABLE_NODE, // Node cannot be parsed from RLP encoding | ||||||||||||||||||||||
| INVALID_PROOF // General proof validation failure | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| struct Node { | ||||||||||||||||||||||
| bytes encoded; // Raw RLP encoded node | ||||||||||||||||||||||
| Memory.Slice[] decoded; // Decoded RLP items | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// @dev The radix of the Ethereum trie (hexadecimal = 16) | ||||||||||||||||||||||
| uint256 internal constant EVM_TREE_RADIX = 16; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// @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. | ||||||||||||||||||||||
ernestognw marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
| function verify( | ||||||||||||||||||||||
| bytes memory key, | ||||||||||||||||||||||
| bytes memory value, | ||||||||||||||||||||||
| bytes[] memory proof, | ||||||||||||||||||||||
| bytes32 root | ||||||||||||||||||||||
Amxx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||
| ) internal pure returns (bool) { | ||||||||||||||||||||||
| (bytes memory processedValue, ProofError err) = processProof(key, proof, root); | ||||||||||||||||||||||
| return Bytes.equal(processedValue, value) && err == ProofError.NO_ERROR; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// @dev Processes a proof for a given key and returns the processed value. | ||||||||||||||||||||||
| function processProof( | ||||||||||||||||||||||
Amxx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||
| bytes memory key, | ||||||||||||||||||||||
| bytes[] memory proof, | ||||||||||||||||||||||
| bytes32 root | ||||||||||||||||||||||
| ) internal pure returns (bytes memory value, ProofError err) { | ||||||||||||||||||||||
| if (key.length == 0) return ("", ProofError.EMPTY_KEY); | ||||||||||||||||||||||
Amxx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Expand the key | ||||||||||||||||||||||
| bytes memory keyExpanded = _nibbles(key); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| bytes32 currentNodeId; | ||||||||||||||||||||||
| uint256 currentNodeIdLength; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Free memory pointer cache | ||||||||||||||||||||||
| Memory.Pointer fmp = Memory.getFreeMemoryPointer(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Process proof | ||||||||||||||||||||||
| uint256 keyIndex = 0; | ||||||||||||||||||||||
| uint256 proofLength = proof.length; | ||||||||||||||||||||||
| for (uint256 i = 0; i < proofLength; ++i) { | ||||||||||||||||||||||
| Node memory node = Node(proof[i], proof[i].decodeList()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // ensure we haven't overshot the key | ||||||||||||||||||||||
| if (keyIndex > keyExpanded.length) { | ||||||||||||||||||||||
| return ("", ProofError.INDEX_OUT_OF_BOUNDS); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // validates the node hashes at different levels of the proof. | ||||||||||||||||||||||
| if (keyIndex == 0) { | ||||||||||||||||||||||
| // Root node must match root hash | ||||||||||||||||||||||
| if (keccak256(node.encoded) != root) return ("", ProofError.INVALID_ROOT_HASH); | ||||||||||||||||||||||
| } else if (node.encoded.length >= 32) { | ||||||||||||||||||||||
| // Large nodes are stored as hashes | ||||||||||||||||||||||
| if (currentNodeIdLength != 32 || keccak256(node.encoded) != currentNodeId) | ||||||||||||||||||||||
| return ("", ProofError.INVALID_LARGE_INTERNAL_HASH); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| // Small nodes must match directly | ||||||||||||||||||||||
| if (currentNodeIdLength != node.encoded.length || bytes32(node.encoded) != currentNodeId) | ||||||||||||||||||||||
| return ("", ProofError.INVALID_INTERNAL_NODE_HASH); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| uint256 nodeLength = node.decoded.length; | ||||||||||||||||||||||
| if (nodeLength == EVM_TREE_RADIX + 1) { | ||||||||||||||||||||||
| // 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(node.decoded[EVM_TREE_RADIX], proofLength, i); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| bytes1 branchKey = keyExpanded[keyIndex]; | ||||||||||||||||||||||
| (currentNodeId, currentNodeIdLength) = _getNodeId(node.decoded[uint8(branchKey)]); | ||||||||||||||||||||||
| keyIndex += 1; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } else if (nodeLength == LEAF_OR_EXTENSION_NODE_LENGTH) { | ||||||||||||||||||||||
| bytes memory path = _nibbles(node.decoded[0].readBytes()); | ||||||||||||||||||||||
| uint8 prefix = uint8(path[0]); | ||||||||||||||||||||||
| Memory.Slice pathRemainder = path.asSlice().slice(2 - (prefix % 2)); // Path after the prefix | ||||||||||||||||||||||
| Memory.Slice keyRemainder = keyExpanded.asSlice().slice(keyIndex); // Remaining key to match | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
| // pathRemainder must not be longer than keyRemainder, and it must be a prefix of it | ||||||||||||||||||||||
| if ( | ||||||||||||||||||||||
| pathRemainder.length() > keyRemainder.length() || | ||||||||||||||||||||||
| pathRemainder.getHash() != keyRemainder.slice(0, pathRemainder.length()).getHash() | ||||||||||||||||||||||
| ) { | ||||||||||||||||||||||
| return ("", ProofError.INVALID_PATH_REMAINDER); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (prefix == uint8(Prefix.EXTENSION_EVEN) || prefix == uint8(Prefix.EXTENSION_ODD)) { | ||||||||||||||||||||||
| // Increment keyIndex by the number of nibbles consumed and continue traversal | ||||||||||||||||||||||
| (currentNodeId, currentNodeIdLength) = _getNodeId(node.decoded[1]); | ||||||||||||||||||||||
| keyIndex += pathRemainder.length(); | ||||||||||||||||||||||
| } else if (prefix == uint8(Prefix.LEAF_EVEN) || prefix == uint8(Prefix.LEAF_ODD)) { | ||||||||||||||||||||||
|
||||||||||||||||||||||
| if (prefix == uint8(Prefix.EXTENSION_EVEN) || prefix == uint8(Prefix.EXTENSION_ODD)) { | |
| // Increment keyIndex by the number of nibbles consumed and continue traversal | |
| (currentNodeId, currentNodeIdLength) = _getNodeId(node.decoded[1]); | |
| keyIndex += pathRemainder.length(); | |
| } else if (prefix == uint8(Prefix.LEAF_EVEN) || prefix == uint8(Prefix.LEAF_ODD)) { | |
| if (prefix < 2) { | |
| // Extension node - Increment keyIndex by the number of nibbles consumed and continue traversal | |
| (currentNodeId, currentNodeIdLength) = _getNodeId(node.decoded[1]); | |
| keyIndex += pathRemainder.length(); | |
| } else if (prefix < 4) { |
Perhaps we should convert the enum Prefix { .. } declaration to a piece of doc/natspec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change (using one < instead of two == ) would (very slightly) increasse performance but would also decrease readability. @ernestognw WDYT ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very slightly yes. I was also thinking about smthg like:
if (prefix < uint8(Prefix.EXTENSION_ODD) + 1)
else if (prefix < uint8(Prefix.LEAF_ODD) + 1)
which I guess should be computed/hardcoded during compilation so assuming same cost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or
if (prefix <= uint8(Prefix.EXTENSION_ODD)) {
//...
} else if (prefix <= uint8(Prefix.LEAF_ODD)) {
// ...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do like the < instead of two ==
Uh oh!
There was an error while loading. Please reload this page.