170 lines
7.3 KiB
Solidity
170 lines
7.3 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.28;
|
|
|
|
// RLP reading and Merkle Patricia trie proofs in Ethereum's layout (keccak-keyed secure trie).
|
|
// A proof is the node list eth_getProof returns, root first, in path order. Every node is
|
|
// hashed and compared with the reference that led to it. Nodes shorter than 32 bytes may be
|
|
// embedded in their parent; those are walked in place.
|
|
library Mpt {
|
|
bytes32 internal constant EMPTY_ROOT = 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421;
|
|
|
|
struct Item {
|
|
bool isList;
|
|
uint256 start; // position of the item's prefix
|
|
uint256 off; // position of the item's payload
|
|
uint256 len; // payload length
|
|
}
|
|
|
|
function item(bytes memory b, uint256 p) internal pure returns (Item memory it) {
|
|
require(p < b.length, "rlp: short");
|
|
uint8 x = uint8(b[p]);
|
|
it.start = p;
|
|
if (x < 0x80) {
|
|
it.off = p;
|
|
it.len = 1;
|
|
} else if (x < 0xb8) {
|
|
it.len = x - 0x80;
|
|
it.off = p + 1;
|
|
} else if (x < 0xc0) {
|
|
uint256 ll = x - 0xb7;
|
|
it.len = uintAt(b, p + 1, ll);
|
|
it.off = p + 1 + ll;
|
|
} else if (x < 0xf8) {
|
|
it.isList = true;
|
|
it.len = x - 0xc0;
|
|
it.off = p + 1;
|
|
} else {
|
|
uint256 ll = x - 0xf7;
|
|
it.isList = true;
|
|
it.len = uintAt(b, p + 1, ll);
|
|
it.off = p + 1 + ll;
|
|
}
|
|
require(it.off + it.len <= b.length, "rlp: overrun");
|
|
}
|
|
|
|
function uintAt(bytes memory b, uint256 p, uint256 n) internal pure returns (uint256 v) {
|
|
require(n <= 32 && p + n <= b.length, "rlp: bad length");
|
|
for (uint256 i = 0; i < n; i++) v = (v << 8) | uint8(b[p + i]);
|
|
}
|
|
|
|
function slice(bytes memory b, uint256 off, uint256 len) internal pure returns (bytes memory out) {
|
|
out = new bytes(len);
|
|
assembly { mcopy(add(out, 32), add(add(b, 32), off), len) }
|
|
}
|
|
|
|
function nibble(bytes32 key, uint256 i) private pure returns (uint8) {
|
|
uint8 x = uint8(key[i >> 1]);
|
|
return (i & 1) == 0 ? x >> 4 : x & 15;
|
|
}
|
|
|
|
// Walks the proof for a 32-byte (already hashed) key. Returns (found, value bytes).
|
|
// Reverts on any node that does not hash to its reference or is malformed.
|
|
function get(bytes32 root, bytes32 key, bytes[] calldata proof) internal pure returns (bool found, bytes memory value) {
|
|
if (proof.length == 0) {
|
|
require(root == EMPTY_ROOT, "mpt: no nodes for a non-empty root");
|
|
return (false, "");
|
|
}
|
|
bytes memory node = proof[0];
|
|
require(keccak256(node) == root, "mpt: the first node is not the root");
|
|
uint256 pi = 1;
|
|
uint256 pos = 0; // nibbles of the key consumed
|
|
uint256 np = 0; // position of the current node inside `node`
|
|
for (uint256 guard = 0; guard < 80; guard++) {
|
|
Item memory list = item(node, np);
|
|
require(list.isList, "mpt: node is not a list");
|
|
uint256[17] memory starts;
|
|
uint256 count = 0;
|
|
uint256 p = list.off;
|
|
while (p < list.off + list.len) {
|
|
require(count < 17, "mpt: too many items");
|
|
Item memory it = item(node, p);
|
|
starts[count++] = p;
|
|
p = it.off + it.len;
|
|
}
|
|
Item memory child;
|
|
if (count == 17) {
|
|
if (pos == 64) {
|
|
Item memory v = item(node, starts[16]);
|
|
if (v.len == 0) return (false, "");
|
|
return (true, slice(node, v.off, v.len));
|
|
}
|
|
child = item(node, starts[nibble(key, pos)]);
|
|
pos += 1;
|
|
} else if (count == 2) {
|
|
Item memory hp = item(node, starts[0]);
|
|
require(!hp.isList && hp.len > 0, "mpt: bad path");
|
|
uint8 flag = uint8(node[hp.off]) >> 4;
|
|
bool leaf = (flag & 2) != 0;
|
|
uint256 n = hp.len * 2 - ((flag & 1) != 0 ? 1 : 2);
|
|
for (uint256 i = 0; i < n; i++) {
|
|
uint256 k = i + ((flag & 1) != 0 ? 1 : 2);
|
|
uint8 x = uint8(node[hp.off + (k >> 1)]);
|
|
uint8 nib = (k & 1) == 0 ? x >> 4 : x & 15;
|
|
if (pos + i >= 64 || nib != nibble(key, pos + i)) return (false, ""); // the key diverges
|
|
}
|
|
pos += n;
|
|
child = item(node, starts[1]);
|
|
if (leaf) {
|
|
if (pos != 64) return (false, "");
|
|
return (true, slice(node, child.off, child.len));
|
|
}
|
|
} else {
|
|
revert("mpt: node with an odd item count");
|
|
}
|
|
if (child.isList) {
|
|
np = child.start; // an embedded node
|
|
continue;
|
|
}
|
|
if (child.len == 0) return (false, "");
|
|
require(child.len == 32, "mpt: bad reference");
|
|
bytes32 want;
|
|
assembly { want := mload(add(add(node, 32), mload(add(child, 64)))) }
|
|
require(pi < proof.length, "mpt: a referenced node is missing");
|
|
node = proof[pi++];
|
|
require(keccak256(node) == want, "mpt: node does not hash to its reference");
|
|
np = 0;
|
|
}
|
|
revert("mpt: path too deep");
|
|
}
|
|
|
|
function toUint(bytes memory b, uint256 off, uint256 len) internal pure returns (uint256 v) {
|
|
require(len <= 32, "rlp: integer too wide");
|
|
for (uint256 i = 0; i < len; i++) v = (v << 8) | uint8(b[off + i]);
|
|
}
|
|
|
|
function toBytes32(bytes memory b, uint256 off, uint256 len) internal pure returns (bytes32 v) {
|
|
require(len == 32, "rlp: not 32 bytes");
|
|
assembly { v := mload(add(add(b, 32), off)) }
|
|
}
|
|
|
|
// An eth_getProof account proof under `stateRoot`.
|
|
function account(bytes32 stateRoot, address a, bytes[] calldata proof)
|
|
internal
|
|
pure
|
|
returns (bool exists, uint256 nonce, uint256 balance, bytes32 storageRoot, bytes32 codeHash)
|
|
{
|
|
(bool found, bytes memory v) = get(stateRoot, keccak256(abi.encodePacked(a)), proof);
|
|
if (!found) return (false, 0, 0, bytes32(0), bytes32(0));
|
|
Item memory list = item(v, 0);
|
|
require(list.isList, "mpt: account is not a list");
|
|
Item memory f0 = item(v, list.off);
|
|
Item memory f1 = item(v, f0.off + f0.len);
|
|
Item memory f2 = item(v, f1.off + f1.len);
|
|
Item memory f3 = item(v, f2.off + f2.len);
|
|
require(f3.off + f3.len == list.off + list.len, "mpt: account has not four fields");
|
|
exists = true;
|
|
nonce = toUint(v, f0.off, f0.len);
|
|
balance = toUint(v, f1.off, f1.len);
|
|
storageRoot = toBytes32(v, f2.off, f2.len);
|
|
codeHash = toBytes32(v, f3.off, f3.len);
|
|
}
|
|
|
|
// An eth_getProof storage proof under `storageRoot`; a proven absence reads as zero.
|
|
function storageSlot(bytes32 storageRoot, bytes32 slot, bytes[] calldata proof) internal pure returns (bytes32) {
|
|
(bool found, bytes memory v) = get(storageRoot, keccak256(abi.encodePacked(slot)), proof);
|
|
if (!found) return bytes32(0);
|
|
Item memory it = item(v, 0);
|
|
require(!it.isList && it.off + it.len == v.length, "mpt: storage value is not a string");
|
|
return bytes32(toUint(v, it.off, it.len));
|
|
}
|
|
}
|