From f5e7ee14439c46f7322a0fb9c2cb11f21498b0ca Mon Sep 17 00:00:00 2001 From: Daniel Wang <99078276+dantaik@users.noreply.github.com> Date: Wed, 31 Jan 2024 09:10:20 +0800 Subject: [PATCH] feat(protocol): adopt optimism new trie codebase (#15608) --- .../contracts/L1/verifiers/PseZkVerifier.sol | 10 +- .../L1/verifiers/SgxAndZkVerifier.sol | 7 +- .../contracts/L1/verifiers/SgxVerifier.sol | 8 +- .../contracts/signal/SignalService.sol | 24 +- .../contracts/thirdparty/LibBytesUtils.sol | 128 ------ .../contracts/thirdparty/LibMerkleTrie.sol | 317 -------------- .../contracts/thirdparty/LibRLPReader.sol | 394 ------------------ .../thirdparty/LibSecureMerkleTrie.sol | 101 ----- .../protocol/contracts/thirdparty/README.md | 1 + .../contracts/thirdparty/optimism/Bytes.sol | 152 +++++++ .../thirdparty/optimism/rlp/RLPReader.sol | 303 ++++++++++++++ .../thirdparty/optimism/trie/MerkleTrie.sol | 250 +++++++++++ .../optimism/trie/SecureMerkleTrie.sol | 57 +++ 13 files changed, 794 insertions(+), 958 deletions(-) delete mode 100644 packages/protocol/contracts/thirdparty/LibBytesUtils.sol delete mode 100644 packages/protocol/contracts/thirdparty/LibMerkleTrie.sol delete mode 100644 packages/protocol/contracts/thirdparty/LibRLPReader.sol delete mode 100644 packages/protocol/contracts/thirdparty/LibSecureMerkleTrie.sol create mode 100644 packages/protocol/contracts/thirdparty/README.md create mode 100644 packages/protocol/contracts/thirdparty/optimism/Bytes.sol create mode 100644 packages/protocol/contracts/thirdparty/optimism/rlp/RLPReader.sol create mode 100644 packages/protocol/contracts/thirdparty/optimism/trie/MerkleTrie.sol create mode 100644 packages/protocol/contracts/thirdparty/optimism/trie/SecureMerkleTrie.sol diff --git a/packages/protocol/contracts/L1/verifiers/PseZkVerifier.sol b/packages/protocol/contracts/L1/verifiers/PseZkVerifier.sol index de23171bc55..a3fe4c32cb3 100644 --- a/packages/protocol/contracts/L1/verifiers/PseZkVerifier.sol +++ b/packages/protocol/contracts/L1/verifiers/PseZkVerifier.sol @@ -16,7 +16,7 @@ pragma solidity 0.8.24; import "../../4844/Lib4844.sol"; import "../../common/EssentialContract.sol"; -import "../../thirdparty/LibBytesUtils.sol"; +import "../../thirdparty/optimism/Bytes.sol"; import "../TaikoData.sol"; import "./IVerifier.sol"; @@ -92,14 +92,14 @@ contract PseZkVerifier is EssentialContract, IVerifier { } // Validate the instance using bytes utilities. - bool verified = LibBytesUtils.equal( - LibBytesUtils.slice(zkProof.zkp, 0, 32), bytes.concat(bytes16(0), bytes16(instance)) + bool verified = Bytes.equal( + Bytes.slice(zkProof.zkp, 0, 32), bytes.concat(bytes16(0), bytes16(instance)) ); if (!verified) revert L1_INVALID_PROOF(); - verified = LibBytesUtils.equal( - LibBytesUtils.slice(zkProof.zkp, 32, 32), + verified = Bytes.equal( + Bytes.slice(zkProof.zkp, 32, 32), bytes.concat(bytes16(0), bytes16(uint128(uint256(instance)))) ); if (!verified) revert L1_INVALID_PROOF(); diff --git a/packages/protocol/contracts/L1/verifiers/SgxAndZkVerifier.sol b/packages/protocol/contracts/L1/verifiers/SgxAndZkVerifier.sol index f7bab4fbb58..5f6dd407d96 100644 --- a/packages/protocol/contracts/L1/verifiers/SgxAndZkVerifier.sol +++ b/packages/protocol/contracts/L1/verifiers/SgxAndZkVerifier.sol @@ -15,7 +15,7 @@ pragma solidity 0.8.24; import "../../common/EssentialContract.sol"; -import "../../thirdparty/LibBytesUtils.sol"; +import "../../thirdparty/optimism/Bytes.sol"; import "../TaikoData.sol"; import "./IVerifier.sol"; @@ -44,12 +44,11 @@ contract SgxAndZkVerifier is EssentialContract, IVerifier { _proof.tier = proof.tier; // Verify the SGX part - _proof.data = LibBytesUtils.slice(proof.data, 0, SGX_PROOF_SIZE); + _proof.data = Bytes.slice(proof.data, 0, SGX_PROOF_SIZE); IVerifier(resolve("tier_sgx", false)).verifyProof(ctx, tran, _proof); // Verify the ZK part - _proof.data = - LibBytesUtils.slice(proof.data, SGX_PROOF_SIZE, (proof.data.length - SGX_PROOF_SIZE)); + _proof.data = Bytes.slice(proof.data, SGX_PROOF_SIZE, (proof.data.length - SGX_PROOF_SIZE)); IVerifier(resolve("tier_pse_zkevm", false)).verifyProof(ctx, tran, _proof); } } diff --git a/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol b/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol index 8e8531aa5dc..1f1846050f0 100644 --- a/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol +++ b/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol @@ -16,7 +16,7 @@ pragma solidity 0.8.24; import "lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; import "../../common/EssentialContract.sol"; -import "../../thirdparty/LibBytesUtils.sol"; +import "../../thirdparty/optimism/Bytes.sol"; import "../ITaikoL1.sol"; import "./IVerifier.sol"; @@ -126,9 +126,9 @@ contract SgxVerifier is EssentialContract, IVerifier { // 4 bytes + 20 bytes + 65 bytes (signature) = 89 if (proof.data.length != 89) revert SGX_INVALID_PROOF(); - uint32 id = uint32(bytes4(LibBytesUtils.slice(proof.data, 0, 4))); - address newInstance = address(bytes20(LibBytesUtils.slice(proof.data, 4, 20))); - bytes memory signature = LibBytesUtils.slice(proof.data, 24); + uint32 id = uint32(bytes4(Bytes.slice(proof.data, 0, 4))); + address newInstance = address(bytes20(Bytes.slice(proof.data, 4, 20))); + bytes memory signature = Bytes.slice(proof.data, 24); address oldInstance = ECDSA.recover(getSignedHash(tran, newInstance, ctx.prover, ctx.metaHash), signature); diff --git a/packages/protocol/contracts/signal/SignalService.sol b/packages/protocol/contracts/signal/SignalService.sol index c126a016726..36201d1498c 100644 --- a/packages/protocol/contracts/signal/SignalService.sol +++ b/packages/protocol/contracts/signal/SignalService.sol @@ -17,7 +17,8 @@ pragma solidity 0.8.24; import "lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol"; import "../common/AuthorizableContract.sol"; import "../common/ICrossChainSync.sol"; -import "../thirdparty/LibSecureMerkleTrie.sol"; +import "../thirdparty/optimism/trie/SecureMerkleTrie.sol"; +import "../thirdparty/optimism/rlp/RLPReader.sol"; import "./ISignalService.sol"; /// @title SignalService @@ -87,6 +88,7 @@ contract SignalService is AuthorizableContract, ISignalService { ) public view + virtual returns (bool) { if (skipProofCheck()) return true; @@ -130,18 +132,19 @@ contract SignalService is AuthorizableContract, ISignalService { hop.signalRootRelay, hop.signalRoot // as a signal ); - bool verified = LibSecureMerkleTrie.verifyInclusionProof( - bytes.concat(slot), hex"01", hop.storageProof, signalRoot + + bool verified = SecureMerkleTrie.verifyInclusionProof( + bytes.concat(slot), hex"01", _transcode(hop.storageProof), signalRoot ); if (!verified) return false; signalRoot = hop.signalRoot; } - return LibSecureMerkleTrie.verifyInclusionProof( + return SecureMerkleTrie.verifyInclusionProof( bytes.concat(getSignalSlot(srcChainId, app, signal)), hex"01", - p.storageProof, + _transcode(p.storageProof), signalRoot ); } @@ -170,6 +173,17 @@ contract SignalService is AuthorizableContract, ISignalService { return false; } + /// @notice Translate a RLP-encoded list of RLP-encoded TrieNodes into a list of LP-encoded + /// TrieNodes. + function _transcode(bytes memory proof) internal pure returns (bytes[] memory proofs) { + RLPReader.RLPItem[] memory nodes = RLPReader.readList(proof); + proofs = new bytes[](nodes.length); + + for (uint256 i; i < nodes.length; ++i) { + proofs[i] = RLPReader.readBytes(nodes[i]); + } + } + function _authorizePause(address) internal pure override { revert SS_UNSUPPORTED(); } diff --git a/packages/protocol/contracts/thirdparty/LibBytesUtils.sol b/packages/protocol/contracts/thirdparty/LibBytesUtils.sol deleted file mode 100644 index 12081842ab7..00000000000 --- a/packages/protocol/contracts/thirdparty/LibBytesUtils.sol +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: MIT -// Taken from -// https://github.com/ethereum-optimism/optimism-legacy/blob/develop/packages/contracts/contracts/libraries/utils/Lib_BytesUtils.sol -// (The MIT License) -// -// Copyright 2020-2021 Optimism -// Copyright 2022-2023 Taiko Labs -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -pragma solidity 0.8.24; - -/** - * @title LibBytesUtils - */ -library LibBytesUtils { - function slice( - bytes memory _bytes, - uint256 _start, - uint256 _length - ) - internal - pure - returns (bytes memory) - { - require(_length + 31 >= _length, "slice_overflow"); - require(_start + _length >= _start, "slice_overflow"); - require(_bytes.length >= _start + _length, "slice_outOfBounds"); - - bytes memory tempBytes; - - assembly { - switch iszero(_length) - case 0 { - // Get a location of some free memory and store it in tempBytes - // as - // Solidity does for memory variables. - tempBytes := mload(0x40) - - // The first word of the slice result is potentially a partial - // word read from the original array. To read it, we calculate - // the length of that partial word and start copying that many - // bytes into the array. The first word we copy will start with - // data we don't care about, but the last `lengthmod` bytes will - // land at the beginning of the contents of the new array. When - // we're done copying, we overwrite the full first word with - // the actual length of the slice. - let lengthmod := and(_length, 31) - - // The multiplication in the next line is necessary - // because when slicing multiples of 32 bytes (lengthmod == 0) - // the following copy loop was copying the origin's length - // and then ending prematurely not copying everything it should. - let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) - let end := add(mc, _length) - - for { - // The multiplication in the next line has the same exact - // purpose - // as the one above. - let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) - } lt(mc, end) { - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { mstore(mc, mload(cc)) } - - mstore(tempBytes, _length) - - // update free-memory pointer allocating the array padded to 32 - // bytes like the compiler does now - mstore(0x40, and(add(mc, 31), not(31))) - } - // if we want a zero-length slice let's just return a zero-length - // array - default { - tempBytes := mload(0x40) - - // zero out the 32 bytes slice we are about to return - // we need to do it because Solidity does not garbage collect - mstore(tempBytes, 0) - - mstore(0x40, add(tempBytes, 0x20)) - } - } - - return tempBytes; - } - - function slice(bytes memory _bytes, uint256 _start) internal pure returns (bytes memory) { - if (_start >= _bytes.length) { - return bytes(""); - } - - return slice(_bytes, _start, _bytes.length - _start); - } - - function toNibbles(bytes memory _bytes) internal pure returns (bytes memory) { - bytes memory nibbles = new bytes(_bytes.length * 2); - - for (uint256 i; i < _bytes.length; ++i) { - nibbles[i * 2] = _bytes[i] >> 4; - nibbles[i * 2 + 1] = bytes1(uint8(_bytes[i]) % 16); - } - - return nibbles; - } - - function equal(bytes memory _bytes, bytes memory _other) internal pure returns (bool) { - return keccak256(_bytes) == keccak256(_other); - } -} diff --git a/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol b/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol deleted file mode 100644 index eb0c9b0d118..00000000000 --- a/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol +++ /dev/null @@ -1,317 +0,0 @@ -// SPDX-License-Identifier: MIT -// Taken from -// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/libraries/trie/LibMerkleTrie.sol -// (The MIT License) -// -// Copyright 2020-2021 Optimism -// Copyright 2022-2023 Taiko Labs -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -pragma solidity 0.8.24; - -/* Library Imports */ -import "./LibBytesUtils.sol"; -import "./LibRLPReader.sol"; - -/** - * @title LibMerkleTrie - */ -library LibMerkleTrie { - enum NodeType { - BranchNode, - ExtensionNode, - LeafNode - } - - struct TrieNode { - LibRLPReader.RLPItem[] decoded; - bytes encoded; - } - - // TREE_RADIX determines the number of elements per branch node. - uint8 private constant TREE_RADIX = 16; - // Branch nodes have TREE_RADIX elements plus an additional `value` slot. - uint8 private constant BRANCH_NODE_LENGTH = TREE_RADIX + 1; - // Leaf nodes and extension nodes always have two elements, a `path` and a - // `value`. - uint8 private constant LEAF_OR_EXTENSION_NODE_LENGTH = 2; - - // Prefixes are prepended to the `path` within a leaf or extension node and - // allow us to differentiate between the two node types. `ODD` or `EVEN` is - // determined by the number of nibbles within the unprefixed `path`. If the - // number of nibbles if even, we need to insert an extra padding nibble so - // the resulting prefixed `path` has an even number of nibbles. - uint8 private constant PREFIX_EXTENSION_EVEN = 0; - uint8 private constant PREFIX_EXTENSION_ODD = 1; - uint8 private constant PREFIX_LEAF_EVEN = 2; - uint8 private constant PREFIX_LEAF_ODD = 3; - - // Just a utility constant. RLP represents `NULL` as 0x80. - bytes1 private constant RLP_NULL = bytes1(0x80); - - /** - * @notice Verifies a proof that a given key/value pair is present in the - * Merkle 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 _verified `true` if the k/v pair exists in the trie, `false` - * otherwise. - */ - function verifyInclusionProof( - bytes memory _key, - bytes memory _value, - bytes memory _proof, - bytes32 _root - ) - internal - pure - returns (bool _verified) - { - (bool exists, bytes memory value) = get(_key, _proof, _root); - - return (exists && LibBytesUtils.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 _exists Whether or not the key exists. - * @return _value Value of the key if it exists. - */ - function get( - bytes memory _key, - bytes memory _proof, - bytes32 _root - ) - internal - pure - returns (bool _exists, bytes memory _value) - { - TrieNode[] memory proof = _parseProof(_proof); - (uint256 pathLength, bytes memory keyRemainder, bool isFinalNode) = - _walkNodePath(proof, _key, _root); - - bool exists = keyRemainder.length == 0; - - require(exists || isFinalNode, "Provided proof is invalid."); - - bytes memory value = exists ? _getNodeValue(proof[pathLength - 1]) : bytes(""); - - return (exists, 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 _pathLength Length of the final path - * @return _keyRemainder Portion of the key remaining after the walk. - * @return _isFinalNode Whether or not we've hit a dead end. - */ - function _walkNodePath( - TrieNode[] memory _proof, - bytes memory _key, - bytes32 _root - ) - private - pure - returns (uint256 _pathLength, bytes memory _keyRemainder, bool _isFinalNode) - { - uint256 pathLength; - bytes memory key = LibBytesUtils.toNibbles(_key); - - bytes32 currentNodeID = _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; 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(keccak256(currentNode.encoded) == currentNodeID, "Invalid root hash"); - } else if (currentNode.encoded.length >= 32) { - // Nodes 32 bytes or larger are hashed inside branch nodes. - require( - keccak256(currentNode.encoded) == currentNodeID, "Invalid large internal hash" - ); - } else { - // Nodes smaller than 31 bytes aren't hashed. - require(bytes32(currentNode.encoded) == currentNodeID, "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. - 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]); - LibRLPReader.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 = LibBytesUtils.slice(path, offset); - bytes memory keyRemainder = LibBytesUtils.slice(key, currentKeyIndex); - uint256 sharedNibbleLength = _getSharedNibbleLength(pathRemainder, keyRemainder); - - 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 = bytes32(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 = bytes32(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("Received a node with an unknown prefix"); - } - } else { - revert("Received an unparseable node."); - } - } - - // If our node ID is NULL, then we're at a dead end. - bool isFinalNode = currentNodeID == bytes32(RLP_NULL); - return (pathLength, LibBytesUtils.slice(key, currentKeyIndex), isFinalNode); - } - - /** - * @notice Parses an RLP-encoded proof into something more useful. - * @param _proof RLP-encoded proof to parse. - * @return _parsed Proof parsed into easily accessible structs. - */ - function _parseProof(bytes memory _proof) private pure returns (TrieNode[] memory _parsed) { - LibRLPReader.RLPItem[] memory nodes = LibRLPReader.readList(_proof); - TrieNode[] memory proof = new TrieNode[](nodes.length); - - for (uint256 i; i < nodes.length; ++i) { - bytes memory encoded = LibRLPReader.readBytes(nodes[i]); - proof[i] = TrieNode({ encoded: encoded, decoded: LibRLPReader.readList(encoded) }); - } - - 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 _nodeID ID for the node, depending on the size of its contents. - */ - function _getNodeID(LibRLPReader.RLPItem memory _node) private pure returns (bytes32 _nodeID) { - bytes memory nodeID; - - if (_node.length < 32) { - // Nodes smaller than 32 bytes are RLP encoded. - nodeID = LibRLPReader.readRawBytes(_node); - } else { - // Nodes 32 bytes or larger are hashed. - nodeID = LibRLPReader.readBytes(_node); - } - - return bytes32(nodeID); - } - - /** - * @notice Gets the path for a leaf or extension node. - * @param _node Node to get a path for. - * @return _path Node path, converted to an array of nibbles. - */ - function _getNodePath(TrieNode memory _node) private pure returns (bytes memory _path) { - return LibBytesUtils.toNibbles(LibRLPReader.readBytes(_node.decoded[0])); - } - - /** - * @notice Gets the path for a node. - * @param _node Node to get a value for. - * @return _value Node value, as hex bytes. - */ - function _getNodeValue(TrieNode memory _node) private pure returns (bytes memory _value) { - return LibRLPReader.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 _shared Number of shared nibbles. - */ - function _getSharedNibbleLength( - bytes memory _a, - bytes memory _b - ) - private - pure - returns (uint256 _shared) - { - uint256 i; - while (_a.length > i && _b.length > i && _a[i] == _b[i]) { - ++i; - } - return i; - } -} diff --git a/packages/protocol/contracts/thirdparty/LibRLPReader.sol b/packages/protocol/contracts/thirdparty/LibRLPReader.sol deleted file mode 100644 index 965034d5977..00000000000 --- a/packages/protocol/contracts/thirdparty/LibRLPReader.sol +++ /dev/null @@ -1,394 +0,0 @@ -// SPDX-License-Identifier: MIT -// Taken from -// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/libraries/rlp/LibRLPReader.sol -// (The MIT License) -// -// Copyright 2020-2021 Optimism -// Copyright 2022-2023 Taiko Labs -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -pragma solidity 0.8.24; - -/** - * @title LibRLPReader - * @dev Adapted from "RLPReader" by Hamdi Allam (hamdi.allam97@gmail.com). - */ -library LibRLPReader { - uint256 internal constant MAX_LIST_LENGTH = 32; - - enum RLPItemType { - DATA_ITEM, - LIST_ITEM - } - - struct RLPItem { - uint256 length; - uint256 ptr; - } - - /** - * Converts bytes to a reference to memory position and length. - * @param _in Input bytes to convert. - * @return Output memory reference. - */ - function toRLPItem(bytes memory _in) internal pure returns (RLPItem memory) { - uint256 ptr; - assembly { - ptr := add(_in, 32) - } - - return RLPItem({ length: _in.length, ptr: ptr }); - } - - /** - * Reads an RLP list value into a list of RLP items. - * @param _in RLP list value. - * @return Decoded RLP list items. - */ - function readList(RLPItem memory _in) internal pure returns (RLPItem[] memory) { - (uint256 listOffset,, RLPItemType itemType) = _decodeLength(_in); - - require(itemType == RLPItemType.LIST_ITEM, "Invalid RLP list value."); - - // Solidity in-memory arrays can't be increased in size, but *can* be - // decreased in size by - // writing to the length. Since we can't know the number of RLP items - // without looping over - // the entire input, we'd have to loop twice to accurately size this - // array. It's easier to - // simply set a reasonable maximum list length and decrease the size - // before we finish. - RLPItem[] memory out = new RLPItem[](MAX_LIST_LENGTH); - - uint256 itemCount; - uint256 offset = listOffset; - while (offset < _in.length) { - require(itemCount < MAX_LIST_LENGTH, "Provided RLP list exceeds max list length."); - - (uint256 itemOffset, uint256 itemLength,) = - _decodeLength(RLPItem({ length: _in.length - offset, ptr: _in.ptr + offset })); - - out[itemCount] = RLPItem({ length: itemLength + itemOffset, ptr: _in.ptr + offset }); - - itemCount += 1; - offset += itemOffset + itemLength; - } - - // Decrease the array size to match the actual item count. - assembly { - mstore(out, itemCount) - } - - return out; - } - - /** - * Reads an RLP list value into a list of RLP items. - * @param _in RLP list value. - * @return Decoded RLP list items. - */ - function readList(bytes memory _in) internal pure returns (RLPItem[] memory) { - return readList(toRLPItem(_in)); - } - - /** - * Reads an RLP bytes value into bytes. - * @param _in RLP bytes value. - * @return Decoded bytes. - */ - function readBytes(RLPItem memory _in) internal pure returns (bytes memory) { - (uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in); - - require(itemType == RLPItemType.DATA_ITEM, "Invalid RLP bytes value."); - - return _copy(_in.ptr, itemOffset, itemLength); - } - - /** - * Reads an RLP bytes value into bytes. - * @param _in RLP bytes value. - * @return Decoded bytes. - */ - function readBytes(bytes memory _in) internal pure returns (bytes memory) { - return readBytes(toRLPItem(_in)); - } - - /** - * Reads an RLP string value into a string. - * @param _in RLP string value. - * @return Decoded string. - */ - function readString(RLPItem memory _in) internal pure returns (string memory) { - return string(readBytes(_in)); - } - - /** - * Reads an RLP string value into a string. - * @param _in RLP string value. - * @return Decoded string. - */ - function readString(bytes memory _in) internal pure returns (string memory) { - return readString(toRLPItem(_in)); - } - - /** - * Reads an RLP bytes32 value into a bytes32. - * @param _in RLP bytes32 value. - * @return Decoded bytes32. - */ - function readBytes32(RLPItem memory _in) internal pure returns (bytes32) { - require(_in.length <= 33, "Invalid RLP bytes32 value."); - - (uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in); - - require(itemType == RLPItemType.DATA_ITEM, "Invalid RLP bytes32 value."); - - uint256 ptr = _in.ptr + itemOffset; - bytes32 out; - assembly { - out := mload(ptr) - - // Shift the bytes over to match the item size. - // Note: Will align to the right for an input smaller than 32 bytes. - if lt(itemLength, 32) { out := div(out, exp(256, sub(32, itemLength))) } - } - - return out; - } - - /** - * Reads an RLP bytes32 value into a bytes32. - * @param _in RLP bytes32 value. - * @return Decoded bytes32. - */ - function readBytes32(bytes memory _in) internal pure returns (bytes32) { - return readBytes32(toRLPItem(_in)); - } - - /** - * Reads an RLP uint256 value into a uint256. - * @param _in RLP uint256 value. - * @return Decoded uint256. - */ - function readUint256(RLPItem memory _in) internal pure returns (uint256) { - return uint256(readBytes32(_in)); - } - - /** - * Reads an RLP uint256 value into a uint256. - * @param _in RLP uint256 value. - * @return Decoded uint256. - */ - function readUint256(bytes memory _in) internal pure returns (uint256) { - return readUint256(toRLPItem(_in)); - } - - /** - * Reads an RLP bool value into a bool. - * @param _in RLP bool value. - * @return Decoded bool. - */ - function readBool(RLPItem memory _in) internal pure returns (bool) { - require(_in.length == 1, "Invalid RLP boolean value."); - - uint256 ptr = _in.ptr; - uint256 out; - assembly { - out := byte(0, mload(ptr)) - } - - require(out == 0 || out == 1, "LibRLPReader: Invalid RLP boolean value, must be 0 or 1"); - - return out != 0; - } - - /** - * Reads an RLP bool value into a bool. - * @param _in RLP bool value. - * @return Decoded bool. - */ - function readBool(bytes memory _in) internal pure returns (bool) { - return readBool(toRLPItem(_in)); - } - - /** - * Reads an RLP address value into a address. - * @param _in RLP address value. - * @return Decoded address. - */ - function readAddress(RLPItem memory _in) internal pure returns (address) { - if (_in.length == 1) { - // Note: regardless of value of _in, it returns address(0) if length is 1. - return address(0); - } - - require(_in.length == 21, "Invalid RLP address value."); - - return address(uint160(readUint256(_in))); - } - - /** - * Reads an RLP address value into a address. - * @param _in RLP address value. - * @return Decoded address. - */ - function readAddress(bytes memory _in) internal pure returns (address) { - return readAddress(toRLPItem(_in)); - } - - /** - * Reads the raw bytes of an RLP item. - * @param _in RLP item to read. - * @return Raw RLP bytes. - */ - function readRawBytes(RLPItem memory _in) internal pure returns (bytes memory) { - return _copy(_in); - } - - /** - * Decodes the length of an RLP item. - * @param _in RLP item to decode. - * @return Offset of the encoded data. - * @return Length of the encoded data. - * @return RLP item type (LIST_ITEM or DATA_ITEM). - */ - function _decodeLength(RLPItem memory _in) - private - pure - returns (uint256, uint256, RLPItemType) - { - require(_in.length > 0, "RLP item cannot be null."); - - uint256 ptr = _in.ptr; - uint256 prefix; - assembly { - prefix := byte(0, mload(ptr)) - } - - if (prefix <= 0x7f) { - // Single byte. - - return (0, 1, RLPItemType.DATA_ITEM); - } else if (prefix <= 0xb7) { - // Short string. - - // slither-disable-next-line variable-scope - uint256 strLen = prefix - 0x80; - - require(_in.length > strLen, "Invalid RLP short string."); - - return (1, strLen, RLPItemType.DATA_ITEM); - } else if (prefix <= 0xbf) { - // Long string. - uint256 lenOfStrLen = prefix - 0xb7; - - require(_in.length > lenOfStrLen, "Invalid RLP long string length."); - - uint256 strLen; - assembly { - // Pick out the string length. - strLen := div(mload(add(ptr, 1)), exp(256, sub(32, lenOfStrLen))) - } - - require(_in.length > lenOfStrLen + strLen, "Invalid RLP long string."); - - return (1 + lenOfStrLen, strLen, RLPItemType.DATA_ITEM); - } else if (prefix <= 0xf7) { - // Short list. - // slither-disable-next-line variable-scope - uint256 listLen = prefix - 0xc0; - - require(_in.length > listLen, "Invalid RLP short list."); - - return (1, listLen, RLPItemType.LIST_ITEM); - } else { - // Long list. - uint256 lenOfListLen = prefix - 0xf7; - - require(_in.length > lenOfListLen, "Invalid RLP long list length."); - - uint256 listLen; - assembly { - // Pick out the list length. - listLen := div(mload(add(ptr, 1)), exp(256, sub(32, lenOfListLen))) - } - - require(_in.length > lenOfListLen + listLen, "Invalid RLP long list."); - - return (1 + lenOfListLen, listLen, RLPItemType.LIST_ITEM); - } - } - - /** - * Copies the bytes from a memory location. - * @param _src Pointer to the location to read from. - * @param _offset Offset to start reading from. - * @param _length Number of bytes to read. - * @return Copied bytes. - */ - function _copy( - uint256 _src, - uint256 _offset, - uint256 _length - ) - internal - pure - returns (bytes memory) - { - bytes memory result = new bytes(_length); - if (result.length == 0) { - return result; - } - - bytes memory src; - bytes memory dst; - assembly { - src := add(_src, _offset) - - dst := add(result, 32) - - for { let i := 0 } lt(i, _length) { i := add(i, 32) } { - mstore(add(dst, i), mload(add(src, i))) - } - } - - // Pick out the remaining bytes. - uint256 mask; - unchecked { - mask = 256 ** (32 - (_length % 32)) - 1; - } - - assembly { - mstore(dst, or(and(mload(src), not(mask)), and(mload(dst), mask))) - } - - return result; - } - - /** - * Copies an RLP item into bytes. - * @param _in RLP item to copy. - * @return Copied bytes. - */ - function _copy(RLPItem memory _in) private pure returns (bytes memory) { - return _copy(_in.ptr, 0, _in.length); - } -} diff --git a/packages/protocol/contracts/thirdparty/LibSecureMerkleTrie.sol b/packages/protocol/contracts/thirdparty/LibSecureMerkleTrie.sol deleted file mode 100644 index 52a27c0645f..00000000000 --- a/packages/protocol/contracts/thirdparty/LibSecureMerkleTrie.sol +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: MIT -// Taken from -// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/libraries/trie/LibSecureMerkleTrie.sol -// (The MIT License) -// -// Copyright 2020-2021 Optimism -// Copyright 2022-2023 Taiko Labs -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -pragma solidity 0.8.24; - -/* Library Imports */ -import "./LibMerkleTrie.sol"; - -/** - * @title LibSecureMerkleTrie - */ -library LibSecureMerkleTrie { - /*////////////////////////////////////////////////////////////// - INTERNAL FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Verifies a proof that a given key/value pair is present in the - * Merkle 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 _verified `true` if the k/v pair exists in the trie, `false` - * otherwise. - */ - function verifyInclusionProof( - bytes memory _key, - bytes memory _value, - bytes memory _proof, - bytes32 _root - ) - internal - pure - returns (bool _verified) - { - bytes memory key = _getSecureKey(_key); - return LibMerkleTrie.verifyInclusionProof(key, _value, _proof, _root); - } - - /** - * @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 _exists Whether or not the key exists. - * @return _value Value of the key if it exists. - */ - function get( - bytes memory _key, - bytes memory _proof, - bytes32 _root - ) - internal - pure - returns (bool _exists, bytes memory _value) - { - bytes memory key = _getSecureKey(_key); - return LibMerkleTrie.get(key, _proof, _root); - } - - /*////////////////////////////////////////////////////////////// - PRIVATE FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /** - * Computes the secure counterpart to a key. - * @param _key Key to get a secure key from. - * @return _secureKey Secure version of the key. - */ - function _getSecureKey(bytes memory _key) private pure returns (bytes memory _secureKey) { - return bytes.concat(keccak256(_key)); - } -} diff --git a/packages/protocol/contracts/thirdparty/README.md b/packages/protocol/contracts/thirdparty/README.md new file mode 100644 index 00000000000..bb94bcd3f5e --- /dev/null +++ b/packages/protocol/contracts/thirdparty/README.md @@ -0,0 +1 @@ +- /optimism: code copied from https://github.com/ethereum-optimism/optimism/releases/tag/op-batcher%2Fv1.4.3 as-is with only solidity pragma changed. diff --git a/packages/protocol/contracts/thirdparty/optimism/Bytes.sol b/packages/protocol/contracts/thirdparty/optimism/Bytes.sol new file mode 100644 index 00000000000..d0f8c8d5d84 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/optimism/Bytes.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title Bytes +/// @notice Bytes is a library for manipulating byte arrays. +library Bytes { + /// @custom:attribution https://github.com/GNSPS/solidity-bytes-utils + /// @notice Slices a byte array with a given starting index and length. Returns a new byte array + /// as opposed to a pointer to the original array. Will throw if trying to slice more + /// bytes than exist in the array. + /// @param _bytes Byte array to slice. + /// @param _start Starting index of the slice. + /// @param _length Length of the slice. + /// @return Slice of the input byte array. + function slice( + bytes memory _bytes, + uint256 _start, + uint256 _length + ) + internal + pure + returns (bytes memory) + { + unchecked { + require(_length + 31 >= _length, "slice_overflow"); + require(_start + _length >= _start, "slice_overflow"); + require(_bytes.length >= _start + _length, "slice_outOfBounds"); + } + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { mstore(mc, mload(cc)) } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + + //zero out the 32 bytes slice we are about to return + //we need to do it because Solidity does not garbage collect + mstore(tempBytes, 0) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + + /// @notice Slices a byte array with a given starting index up to the end of the original byte + /// array. Returns a new array rathern than a pointer to the original. + /// @param _bytes Byte array to slice. + /// @param _start Starting index of the slice. + /// @return Slice of the input byte array. + function slice(bytes memory _bytes, uint256 _start) internal pure returns (bytes memory) { + if (_start >= _bytes.length) { + return bytes(""); + } + return slice(_bytes, _start, _bytes.length - _start); + } + + /// @notice Converts a byte array into a nibble array by splitting each byte into two nibbles. + /// Resulting nibble array will be exactly twice as long as the input byte array. + /// @param _bytes Input byte array to convert. + /// @return Resulting nibble array. + function toNibbles(bytes memory _bytes) internal pure returns (bytes memory) { + bytes memory _nibbles; + assembly { + // Grab a free memory offset for the new array + _nibbles := mload(0x40) + + // Load the length of the passed bytes array from memory + let bytesLength := mload(_bytes) + + // Calculate the length of the new nibble array + // This is the length of the input array times 2 + let nibblesLength := shl(0x01, bytesLength) + + // Update the free memory pointer to allocate memory for the new array. + // To do this, we add the length of the new array + 32 bytes for the array length + // rounded up to the nearest 32 byte boundary to the current free memory pointer. + mstore(0x40, add(_nibbles, and(not(0x1F), add(nibblesLength, 0x3F)))) + + // Store the length of the new array in memory + mstore(_nibbles, nibblesLength) + + // Store the memory offset of the _bytes array's contents on the stack + let bytesStart := add(_bytes, 0x20) + + // Store the memory offset of the nibbles array's contents on the stack + let nibblesStart := add(_nibbles, 0x20) + + // Loop through each byte in the input array + for { let i := 0x00 } lt(i, bytesLength) { i := add(i, 0x01) } { + // Get the starting offset of the next 2 bytes in the nibbles array + let offset := add(nibblesStart, shl(0x01, i)) + // Load the byte at the current index within the `_bytes` array + let b := byte(0x00, mload(add(bytesStart, i))) + + // Pull out the first nibble and store it in the new array + mstore8(offset, shr(0x04, b)) + // Pull out the second nibble and store it in the new array + mstore8(add(offset, 0x01), and(b, 0x0F)) + } + } + return _nibbles; + } + + /// @notice Compares two byte arrays by comparing their keccak256 hashes. + /// @param _bytes First byte array to compare. + /// @param _other Second byte array to compare. + /// @return True if the two byte arrays are equal, false otherwise. + function equal(bytes memory _bytes, bytes memory _other) internal pure returns (bool) { + return keccak256(_bytes) == keccak256(_other); + } +} diff --git a/packages/protocol/contracts/thirdparty/optimism/rlp/RLPReader.sol b/packages/protocol/contracts/thirdparty/optimism/rlp/RLPReader.sol new file mode 100644 index 00000000000..6c5d73b7036 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/optimism/rlp/RLPReader.sol @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @custom:attribution https://github.com/hamdiallam/Solidity-RLP +/// @title RLPReader +/// @notice RLPReader is a library for parsing RLP-encoded byte arrays into Solidity types. Adapted +/// from Solidity-RLP (https://github.com/hamdiallam/Solidity-RLP) by Hamdi Allam with +/// various tweaks to improve readability. +library RLPReader { + /// @notice Custom pointer type to avoid confusion between pointers and uint256s. + type MemoryPointer is uint256; + + /// @notice RLP item types. + /// @custom:value DATA_ITEM Represents an RLP data item (NOT a list). + /// @custom:value LIST_ITEM Represents an RLP list item. + enum RLPItemType { + DATA_ITEM, + LIST_ITEM + } + + /// @notice Struct representing an RLP item. + /// @custom:field length Length of the RLP item. + /// @custom:field ptr Pointer to the RLP item in memory. + struct RLPItem { + uint256 length; + MemoryPointer ptr; + } + + /// @notice Max list length that this library will accept. + uint256 internal constant MAX_LIST_LENGTH = 32; + + /// @notice Converts bytes to a reference to memory position and length. + /// @param _in Input bytes to convert. + /// @return out_ Output memory reference. + function toRLPItem(bytes memory _in) internal pure returns (RLPItem memory out_) { + // Empty arrays are not RLP items. + require( + _in.length > 0, + "RLPReader: length of an RLP item must be greater than zero to be decodable" + ); + + MemoryPointer ptr; + assembly { + ptr := add(_in, 32) + } + + out_ = RLPItem({ length: _in.length, ptr: ptr }); + } + + /// @notice Reads an RLP list value into a list of RLP items. + /// @param _in RLP list value. + /// @return out_ Decoded RLP list items. + function readList(RLPItem memory _in) internal pure returns (RLPItem[] memory out_) { + (uint256 listOffset, uint256 listLength, RLPItemType itemType) = _decodeLength(_in); + + require( + itemType == RLPItemType.LIST_ITEM, + "RLPReader: decoded item type for list is not a list item" + ); + + require( + listOffset + listLength == _in.length, + "RLPReader: list item has an invalid data remainder" + ); + + // Solidity in-memory arrays can't be increased in size, but *can* be decreased in size by + // writing to the length. Since we can't know the number of RLP items without looping over + // the entire input, we'd have to loop twice to accurately size this array. It's easier to + // simply set a reasonable maximum list length and decrease the size before we finish. + out_ = new RLPItem[](MAX_LIST_LENGTH); + + uint256 itemCount = 0; + uint256 offset = listOffset; + while (offset < _in.length) { + (uint256 itemOffset, uint256 itemLength,) = _decodeLength( + RLPItem({ + length: _in.length - offset, + ptr: MemoryPointer.wrap(MemoryPointer.unwrap(_in.ptr) + offset) + }) + ); + + // We don't need to check itemCount < out.length explicitly because Solidity already + // handles this check on our behalf, we'd just be wasting gas. + out_[itemCount] = RLPItem({ + length: itemLength + itemOffset, + ptr: MemoryPointer.wrap(MemoryPointer.unwrap(_in.ptr) + offset) + }); + + itemCount += 1; + offset += itemOffset + itemLength; + } + + // Decrease the array size to match the actual item count. + assembly { + mstore(out_, itemCount) + } + } + + /// @notice Reads an RLP list value into a list of RLP items. + /// @param _in RLP list value. + /// @return out_ Decoded RLP list items. + function readList(bytes memory _in) internal pure returns (RLPItem[] memory out_) { + out_ = readList(toRLPItem(_in)); + } + + /// @notice Reads an RLP bytes value into bytes. + /// @param _in RLP bytes value. + /// @return out_ Decoded bytes. + function readBytes(RLPItem memory _in) internal pure returns (bytes memory out_) { + (uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in); + + require( + itemType == RLPItemType.DATA_ITEM, + "RLPReader: decoded item type for bytes is not a data item" + ); + + require( + _in.length == itemOffset + itemLength, + "RLPReader: bytes value contains an invalid remainder" + ); + + out_ = _copy(_in.ptr, itemOffset, itemLength); + } + + /// @notice Reads an RLP bytes value into bytes. + /// @param _in RLP bytes value. + /// @return out_ Decoded bytes. + function readBytes(bytes memory _in) internal pure returns (bytes memory out_) { + out_ = readBytes(toRLPItem(_in)); + } + + /// @notice Reads the raw bytes of an RLP item. + /// @param _in RLP item to read. + /// @return out_ Raw RLP bytes. + function readRawBytes(RLPItem memory _in) internal pure returns (bytes memory out_) { + out_ = _copy(_in.ptr, 0, _in.length); + } + + /// @notice Decodes the length of an RLP item. + /// @param _in RLP item to decode. + /// @return offset_ Offset of the encoded data. + /// @return length_ Length of the encoded data. + /// @return type_ RLP item type (LIST_ITEM or DATA_ITEM). + function _decodeLength(RLPItem memory _in) + private + pure + returns (uint256 offset_, uint256 length_, RLPItemType type_) + { + // Short-circuit if there's nothing to decode, note that we perform this check when + // the user creates an RLP item via toRLPItem, but it's always possible for them to bypass + // that function and create an RLP item directly. So we need to check this anyway. + require( + _in.length > 0, + "RLPReader: length of an RLP item must be greater than zero to be decodable" + ); + + MemoryPointer ptr = _in.ptr; + uint256 prefix; + assembly { + prefix := byte(0, mload(ptr)) + } + + if (prefix <= 0x7f) { + // Single byte. + return (0, 1, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xb7) { + // Short string. + + // slither-disable-next-line variable-scope + uint256 strLen = prefix - 0x80; + + require( + _in.length > strLen, + "RLPReader: length of content must be greater than string length (short string)" + ); + + bytes1 firstByteOfContent; + assembly { + firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff)) + } + + require( + strLen != 1 || firstByteOfContent >= 0x80, + "RLPReader: invalid prefix, single byte < 0x80 are not prefixed (short string)" + ); + + return (1, strLen, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xbf) { + // Long string. + uint256 lenOfStrLen = prefix - 0xb7; + + require( + _in.length > lenOfStrLen, + "RLPReader: length of content must be > than length of string length (long string)" + ); + + bytes1 firstByteOfContent; + assembly { + firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff)) + } + + require( + firstByteOfContent != 0x00, + "RLPReader: length of content must not have any leading zeros (long string)" + ); + + uint256 strLen; + assembly { + strLen := shr(sub(256, mul(8, lenOfStrLen)), mload(add(ptr, 1))) + } + + require( + strLen > 55, + "RLPReader: length of content must be greater than 55 bytes (long string)" + ); + + require( + _in.length > lenOfStrLen + strLen, + "RLPReader: length of content must be greater than total length (long string)" + ); + + return (1 + lenOfStrLen, strLen, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xf7) { + // Short list. + // slither-disable-next-line variable-scope + uint256 listLen = prefix - 0xc0; + + require( + _in.length > listLen, + "RLPReader: length of content must be greater than list length (short list)" + ); + + return (1, listLen, RLPItemType.LIST_ITEM); + } else { + // Long list. + uint256 lenOfListLen = prefix - 0xf7; + + require( + _in.length > lenOfListLen, + "RLPReader: length of content must be > than length of list length (long list)" + ); + + bytes1 firstByteOfContent; + assembly { + firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff)) + } + + require( + firstByteOfContent != 0x00, + "RLPReader: length of content must not have any leading zeros (long list)" + ); + + uint256 listLen; + assembly { + listLen := shr(sub(256, mul(8, lenOfListLen)), mload(add(ptr, 1))) + } + + require( + listLen > 55, + "RLPReader: length of content must be greater than 55 bytes (long list)" + ); + + require( + _in.length > lenOfListLen + listLen, + "RLPReader: length of content must be greater than total length (long list)" + ); + + return (1 + lenOfListLen, listLen, RLPItemType.LIST_ITEM); + } + } + + /// @notice Copies the bytes from a memory location. + /// @param _src Pointer to the location to read from. + /// @param _offset Offset to start reading from. + /// @param _length Number of bytes to read. + /// @return out_ Copied bytes. + function _copy( + MemoryPointer _src, + uint256 _offset, + uint256 _length + ) + private + pure + returns (bytes memory out_) + { + out_ = new bytes(_length); + if (_length == 0) { + return out_; + } + + // Mostly based on Solidity's copy_memory_to_memory: + // solhint-disable max-line-length + // https://github.com/ethereum/solidity/blob/34dd30d71b4da730488be72ff6af7083cf2a91f6/libsolidity/codegen/YulUtilFunctions.cpp#L102-L114 + uint256 src = MemoryPointer.unwrap(_src) + _offset; + assembly { + let dest := add(out_, 32) + let i := 0 + for { } lt(i, _length) { i := add(i, 32) } { mstore(add(dest, i), mload(add(src, i))) } + + if gt(i, _length) { mstore(add(dest, _length), 0) } + } + } +} diff --git a/packages/protocol/contracts/thirdparty/optimism/trie/MerkleTrie.sol b/packages/protocol/contracts/thirdparty/optimism/trie/MerkleTrie.sol new file mode 100644 index 00000000000..f961c591ce2 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/optimism/trie/MerkleTrie.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { Bytes } from "../Bytes.sol"; +import { RLPReader } from "../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. + /// @custom:field encoded The RLP-encoded node. + /// @custom:field decoded The RLP-decoded node. + 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 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 valid_ Whether or not the proof is valid. + function verifyInclusionProof( + bytes memory _key, + bytes memory _value, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bool valid_) + { + valid_ = Bytes.equal(_value, get(_key, _proof, _root)); + } + + /// @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 value_ Value of the key if it exists. + function get( + bytes memory _key, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bytes memory value_) + { + require(_key.length > 0, "MerkleTrie: empty key"); + + TrieNode[] memory proof = _parseProof(_proof); + bytes memory key = Bytes.toNibbles(_key); + bytes memory currentNodeID = abi.encodePacked(_root); + uint256 currentKeyIndex = 0; + + // Proof is top-down, so we start at the first element (root). + for (uint256 i = 0; i < proof.length; i++) { + TrieNode memory currentNode = proof[i]; + + // Key index should never exceed total key length or we'll be out of bounds. + require(currentKeyIndex <= key.length, "MerkleTrie: key index exceeds total key length"); + + 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) { + // Value is the last element of the decoded list (for branch nodes). There's + // some ambiguity in the Merkle trie specification because bytes(0) is a + // valid value to place into the trie, but for branch nodes bytes(0) can exist + // even when the value wasn't explicitly placed there. Geth treats a value of + // bytes(0) as "key does not exist" and so we do the same. + value_ = RLPReader.readBytes(currentNode.decoded[TREE_RADIX]); + require( + value_.length > 0, + "MerkleTrie: value length must be greater than zero (branch)" + ); + + // Extra proof elements are not allowed. + require( + i == proof.length - 1, + "MerkleTrie: value node must be last node in proof (branch)" + ); + + return value_; + } 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); + currentKeyIndex += 1; + } + } 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); + + // Whether this is a leaf node or an extension node, the path remainder MUST be a + // prefix of the key remainder (or be equal to the key remainder) or the proof is + // considered invalid. + require( + pathRemainder.length == sharedNibbleLength, + "MerkleTrie: path remainder must share all nibbles with key" + ); + + if (prefix == PREFIX_LEAF_EVEN || prefix == PREFIX_LEAF_ODD) { + // Prefix of 2 or 3 means this is a leaf node. For the leaf node to be valid, + // the key remainder must be exactly equal to the path remainder. We already + // did the necessary byte comparison, so it's more efficient here to check that + // the key remainder length equals the shared nibble length, which implies + // equality with the path remainder (since we already did the same check with + // the path remainder and the shared nibble length). + require( + keyRemainder.length == sharedNibbleLength, + "MerkleTrie: key remainder must be identical to path remainder" + ); + + // Our Merkle Trie is designed specifically for the purposes of the Ethereum + // state trie. Empty values are not allowed in the state trie, so we can safely + // say that if the value is empty, the key should not exist and the proof is + // invalid. + value_ = RLPReader.readBytes(currentNode.decoded[1]); + require( + value_.length > 0, + "MerkleTrie: value length must be greater than zero (leaf)" + ); + + // Extra proof elements are not allowed. + require( + i == proof.length - 1, + "MerkleTrie: value node must be last node in proof (leaf)" + ); + + return value_; + } else if (prefix == PREFIX_EXTENSION_EVEN || prefix == PREFIX_EXTENSION_ODD) { + // Prefix of 0 or 1 means this is an extension node. We move onto the next node + // in the proof and increment the key index by the length of the path remainder + // which is equal to the shared nibble length. + currentNodeID = _getNodeID(currentNode.decoded[1]); + currentKeyIndex += sharedNibbleLength; + } else { + revert("MerkleTrie: received a node with an unknown prefix"); + } + } else { + revert("MerkleTrie: received an unparseable node"); + } + } + + revert("MerkleTrie: ran out of proof elements"); + } + + /// @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_ Proof parsed into easily accessible structs. + function _parseProof(bytes[] memory _proof) private pure returns (TrieNode[] memory proof_) { + uint256 length = _proof.length; + proof_ = new TrieNode[](length); + for (uint256 i = 0; i < length;) { + proof_[i] = TrieNode({ encoded: _proof[i], decoded: RLPReader.readList(_proof[i]) }); + unchecked { + ++i; + } + } + } + + /// @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_ ID for the node, depending on the size of its contents. + function _getNodeID(RLPReader.RLPItem memory _node) private pure returns (bytes memory id_) { + id_ = _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 nibbles_ Node path, converted to an array of nibbles. + function _getNodePath(TrieNode memory _node) private pure returns (bytes memory nibbles_) { + nibbles_ = Bytes.toNibbles(RLPReader.readBytes(_node.decoded[0])); + } + + /// @notice Utility; determines the number of nibbles shared between two nibble arrays. + /// @param _a First nibble array. + /// @param _b Second nibble array. + /// @return shared_ Number of shared nibbles. + function _getSharedNibbleLength( + bytes memory _a, + bytes memory _b + ) + private + pure + returns (uint256 shared_) + { + uint256 max = (_a.length < _b.length) ? _a.length : _b.length; + for (; shared_ < max && _a[shared_] == _b[shared_];) { + unchecked { + ++shared_; + } + } + } +} diff --git a/packages/protocol/contracts/thirdparty/optimism/trie/SecureMerkleTrie.sol b/packages/protocol/contracts/thirdparty/optimism/trie/SecureMerkleTrie.sol new file mode 100644 index 00000000000..01808436903 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/optimism/trie/SecureMerkleTrie.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { MerkleTrie } from "./MerkleTrie.sol"; + +/// @title SecureMerkleTrie +/// @notice SecureMerkleTrie is a thin wrapper around the MerkleTrie library that hashes the input +/// keys. Ethereum's state trie hashes input keys before storing them. +library SecureMerkleTrie { + /// @notice Verifies a proof that a given key/value pair is present in the Merkle 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 valid_ Whether or not the proof is valid. + function verifyInclusionProof( + bytes memory _key, + bytes memory _value, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bool valid_) + { + bytes memory key = _getSecureKey(_key); + valid_ = MerkleTrie.verifyInclusionProof(key, _value, _proof, _root); + } + + /// @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 value_ Value of the key if it exists. + function get( + bytes memory _key, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bytes memory value_) + { + bytes memory key = _getSecureKey(_key); + value_ = MerkleTrie.get(key, _proof, _root); + } + + /// @notice Computes the hashed version of the input key. + /// @param _key Key to hash. + /// @return hash_ Hashed version of the key. + function _getSecureKey(bytes memory _key) private pure returns (bytes memory hash_) { + hash_ = abi.encodePacked(keccak256(_key)); + } +}