// Pulled from https://github.com/ethereum-optimism/optimism/blob/4d13f0afe8869faf7bba45d8339998525ebc5161/packages/contracts-bedrock/contracts/libraries/trie/MerkleTrie.sol // as this is the last version of Optimism's Merkle Trie library that supports nonexistence proofs; support was removed // in the next commit for some version. // Copyright 2020-2021 Optimism // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import { Bytes } from "../../lib/optimism/packages/contracts-bedrock/src/libraries/Bytes.sol"; import { RLPReader } from "../../lib/optimism/packages/contracts-bedrock/src/libraries/rlp/RLPReader.sol"; /** * @title MerkleTrie * @notice MerkleTrie is a small library for verifying standard Ethereum Merkle-Patricia trie * inclusion proofs. By default, this library assumes a hexary trie. One can change the * trie radix constant to support other trie radixes. */ library MerkleTrie { /** * @notice Struct representing a node in the trie. */ struct TrieNode { bytes encoded; RLPReader.RLPItem[] decoded; } /** * @notice Determines the number of elements per branch node. */ uint256 internal constant TREE_RADIX = 16; /** * @notice Branch nodes have TREE_RADIX elements and one value element. */ uint256 internal constant BRANCH_NODE_LENGTH = TREE_RADIX + 1; /** * @notice Leaf nodes and extension nodes have two elements, a `path` and a `value`. */ uint256 internal constant LEAF_OR_EXTENSION_NODE_LENGTH = 2; /** * @notice Prefix for even-nibbled extension node paths. */ uint8 internal constant PREFIX_EXTENSION_EVEN = 0; /** * @notice Prefix for odd-nibbled extension node paths. */ uint8 internal constant PREFIX_EXTENSION_ODD = 1; /** * @notice Prefix for even-nibbled leaf node paths. */ uint8 internal constant PREFIX_LEAF_EVEN = 2; /** * @notice Prefix for odd-nibbled leaf node paths. */ uint8 internal constant PREFIX_LEAF_ODD = 3; /** * @notice RLP representation of `NULL`. */ bytes internal constant RLP_NULL = hex"80"; /** * @notice Verifies a proof that a given key/value pair is present in the trie. * * @param _key Key of the node to search for, as a hex string. * @param _value Value of the node to search for, as a hex string. * @param _proof Merkle trie inclusion proof for the desired node. Unlike traditional Merkle * trees, this proof is executed top-down and consists of a list of RLP-encoded * nodes that make a path down to the target node. * @param _root Known root of the Merkle trie. Used to verify that the included proof is * correctly constructed. * * @return Whether or not the proof is valid. */ function verifyInclusionProof( bytes memory _key, bytes memory _value, bytes[] memory _proof, bytes32 _root ) internal pure returns (bool) { (bool exists, bytes memory value) = get(_key, _proof, _root); return (exists && Bytes.equal(_value, value)); } /** * @notice Retrieves the value associated with a given key. * * @param _key Key to search for, as hex bytes. * @param _proof Merkle trie inclusion proof for the key. * @param _root Known root of the Merkle trie. * * @return Whether or not the key exists. * @return Value of the key if it exists. */ function get( bytes memory _key, bytes[] memory _proof, bytes32 _root ) internal pure returns (bool, bytes memory) { TrieNode[] memory proof = _parseProof(_proof); (uint256 pathLength, bytes memory keyRemainder, bool isFinalNode) = _walkNodePath( proof, _key, _root ); bool noRemainder = keyRemainder.length == 0; require(noRemainder || isFinalNode, "MerkleTrie: provided proof is invalid"); bytes memory value = noRemainder ? _getNodeValue(proof[pathLength - 1]) : bytes(""); return (value.length > 0, value); } /** * @notice Walks through a proof using a provided key. * * @param _proof Inclusion proof to walk through. * @param _key Key to use for the walk. * @param _root Known root of the trie. * * @return Length of the final path * @return Portion of the key remaining after the walk. * @return Whether or not we've hit a dead end. */ // solhint-disable-next-line code-complexity function _walkNodePath( TrieNode[] memory _proof, bytes memory _key, bytes32 _root ) private pure returns ( uint256, bytes memory, bool ) { uint256 pathLength = 0; bytes memory key = Bytes.toNibbles(_key); bytes memory currentNodeID = abi.encodePacked(_root); uint256 currentKeyIndex = 0; uint256 currentKeyIncrement = 0; TrieNode memory currentNode; // Proof is top-down, so we start at the first element (root). for (uint256 i = 0; i < _proof.length; i++) { currentNode = _proof[i]; currentKeyIndex += currentKeyIncrement; // Keep track of the proof elements we actually need. // It's expensive to resize arrays, so this simply reduces gas costs. pathLength += 1; if (currentKeyIndex == 0) { // First proof element is always the root node. require( Bytes.equal(abi.encodePacked(keccak256(currentNode.encoded)), currentNodeID), "MerkleTrie: invalid root hash" ); } else if (currentNode.encoded.length >= 32) { // Nodes 32 bytes or larger are hashed inside branch nodes. require( Bytes.equal(abi.encodePacked(keccak256(currentNode.encoded)), currentNodeID), "MerkleTrie: invalid large internal hash" ); } else { // Nodes smaller than 32 bytes aren't hashed. require( Bytes.equal(currentNode.encoded, currentNodeID), "MerkleTrie: invalid internal node hash" ); } if (currentNode.decoded.length == BRANCH_NODE_LENGTH) { if (currentKeyIndex == key.length) { // We've hit the end of the key // meaning the value should be within this branch node. // Extra proof elements are not allowed. require(i == _proof.length - 1, "MerkleTrie: value node must be last node in proof (branch)"); break; } else { // We're not at the end of the key yet. // Figure out what the next node ID should be and continue. uint8 branchKey = uint8(key[currentKeyIndex]); RLPReader.RLPItem memory nextNode = currentNode.decoded[branchKey]; currentNodeID = _getNodeID(nextNode); currentKeyIncrement = 1; continue; } } else if (currentNode.decoded.length == LEAF_OR_EXTENSION_NODE_LENGTH) { bytes memory path = _getNodePath(currentNode); uint8 prefix = uint8(path[0]); uint8 offset = 2 - (prefix % 2); bytes memory pathRemainder = Bytes.slice(path, offset); bytes memory keyRemainder = Bytes.slice(key, currentKeyIndex); uint256 sharedNibbleLength = _getSharedNibbleLength(pathRemainder, keyRemainder); require( keyRemainder.length >= pathRemainder.length, "MerkleTrie: invalid key length for leaf or extension node" ); if (prefix == PREFIX_LEAF_EVEN || prefix == PREFIX_LEAF_ODD) { if ( pathRemainder.length == sharedNibbleLength && keyRemainder.length == sharedNibbleLength ) { // The key within this leaf matches our key exactly. // Increment the key index to reflect that we have no remainder. currentKeyIndex += sharedNibbleLength; } // We've hit a leaf node, so our next node should be NULL. currentNodeID = RLP_NULL; break; } else if (prefix == PREFIX_EXTENSION_EVEN || prefix == PREFIX_EXTENSION_ODD) { if (sharedNibbleLength != pathRemainder.length) { // Our extension node is not identical to the remainder. // We've hit the end of this path // updates will need to modify this extension. currentNodeID = RLP_NULL; break; } else { // Our extension shares some nibbles. // Carry on to the next node. currentNodeID = _getNodeID(currentNode.decoded[1]); currentKeyIncrement = sharedNibbleLength; continue; } } else { revert("MerkleTrie: received a node with an unknown prefix"); } } else { revert("MerkleTrie: received an unparseable node"); } } return ( pathLength, Bytes.slice(key, currentKeyIndex), Bytes.equal(currentNodeID, RLP_NULL) ); } /** * @notice Parses an array of proof elements into a new array that contains both the original * encoded element and the RLP-decoded element. * * @param _proof Array of proof elements to parse. * * @return Proof parsed into easily accessible structs. */ function _parseProof(bytes[] memory _proof) private pure returns (TrieNode[] memory) { uint256 length = _proof.length; TrieNode[] memory proof = new TrieNode[](length); for (uint256 i = 0; i < length; ) { proof[i] = TrieNode({ encoded: _proof[i], decoded: RLPReader.readList(_proof[i]) }); unchecked { ++i; } } return proof; } /** * @notice Picks out the ID for a node. Node ID is referred to as the "hash" within the * specification, but nodes < 32 bytes are not actually hashed. * * @param _node Node to pull an ID for. * * @return ID for the node, depending on the size of its contents. */ function _getNodeID(RLPReader.RLPItem memory _node) private pure returns (bytes memory) { return _node.length < 32 ? RLPReader.readRawBytes(_node) : RLPReader.readBytes(_node); } /** * @notice Gets the path for a leaf or extension node. * * @param _node Node to get a path for. * * @return Node path, converted to an array of nibbles. */ function _getNodePath(TrieNode memory _node) private pure returns (bytes memory) { return Bytes.toNibbles(RLPReader.readBytes(_node.decoded[0])); } /** * @notice Gets the value for a node. * * @param _node Node to get a value for. * * @return Node value, as hex bytes. */ function _getNodeValue(TrieNode memory _node) private pure returns (bytes memory) { return RLPReader.readBytes(_node.decoded[_node.decoded.length - 1]); } /** * @notice Utility; determines the number of nibbles shared between two nibble arrays. * * @param _a First nibble array. * @param _b Second nibble array. * * @return Number of shared nibbles. */ function _getSharedNibbleLength(bytes memory _a, bytes memory _b) private pure returns (uint256) { uint256 shared; uint256 max = (_a.length < _b.length) ? _a.length : _b.length; for (; shared < max && _a[shared] == _b[shared]; ) { unchecked { ++shared; } } return shared; } }