diff --git a/crosschain-resolver/package.json b/crosschain-resolver/package.json index 9df8988..a8e06bb 100644 --- a/crosschain-resolver/package.json +++ b/crosschain-resolver/package.json @@ -48,7 +48,7 @@ "@ensdomains/address-encoder": "^0.2.22", "@ensdomains/ens-contracts": "ensdomains/ens-contracts#feature/crosschain-resolver-with-reverse-registrar", "@ensdomains/evm-gateway": "file:.yalc/@ensdomains/evm-gateway", - "@ensdomains/evm-verifier": "^0.1.0", + "@ensdomains/evm-verifier": "file:.yalc/@ensdomains/evm-verifier", "@ensdomains/l1-verifier": "file:.yalc/@ensdomains/l1-verifier", "@eth-optimism/contracts": "^0.6.0" } diff --git a/crosschain-resolver/yalc.lock b/crosschain-resolver/yalc.lock index 8bc09f9..eefa382 100644 --- a/crosschain-resolver/yalc.lock +++ b/crosschain-resolver/yalc.lock @@ -13,6 +13,11 @@ "@ensdomains/evm-gateway": { "signature": "53d54cf29041349acb87e1322a9ad5b5", "file": true + }, + "@ensdomains/evm-verifier": { + "signature": "edf310727548ba31fa41477742fdc1b8", + "file": true, + "replaced": "^0.1.0" } } } \ No newline at end of file diff --git a/crosschain-reverse-resolver/package.json b/crosschain-reverse-resolver/package.json index eac717e..e80870e 100644 --- a/crosschain-reverse-resolver/package.json +++ b/crosschain-reverse-resolver/package.json @@ -45,7 +45,7 @@ "dependencies": { "@ensdomains/ens-contracts": "ensdomains/ens-contracts#feature/crosschain-resolver-with-reverse-registrar", "@ensdomains/evm-gateway": "file:.yalc/@ensdomains/evm-gateway", - "@ensdomains/evm-verifier": "^0.1.0", + "@ensdomains/evm-verifier": "file:.yalc/@ensdomains/evm-verifier", "@ensdomains/l1-verifier": "file:.yalc/@ensdomains/l1-verifier", "@eth-optimism/contracts": "^0.6.0" } diff --git a/crosschain-reverse-resolver/yalc.lock b/crosschain-reverse-resolver/yalc.lock index 8bc09f9..eefa382 100644 --- a/crosschain-reverse-resolver/yalc.lock +++ b/crosschain-reverse-resolver/yalc.lock @@ -13,6 +13,11 @@ "@ensdomains/evm-gateway": { "signature": "53d54cf29041349acb87e1322a9ad5b5", "file": true + }, + "@ensdomains/evm-verifier": { + "signature": "edf310727548ba31fa41477742fdc1b8", + "file": true, + "replaced": "^0.1.0" } } } \ No newline at end of file diff --git a/evm-verifier/.gitignore b/evm-verifier/.gitignore deleted file mode 100644 index 00dad77..0000000 --- a/evm-verifier/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -node_modules -.env -coverage -coverage.json -typechain -typechain-types - -# Hardhat files -cache -artifacts - diff --git a/evm-verifier/LICENSE b/evm-verifier/LICENSE deleted file mode 100644 index 92887e5..0000000 --- a/evm-verifier/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Nick Johnson - -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. \ No newline at end of file diff --git a/evm-verifier/README.md b/evm-verifier/README.md deleted file mode 100644 index 5839957..0000000 --- a/evm-verifier/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# @ensdomains/evm-verifier -A Solidity library that verifies state proofs generated by an [evm-gateway](https://github.com/ensdomains/evmgateway/tree/main/evm-gateway) instance. This library implements all the functionality required make CCIP-Read calls to an EVM gateway and verify the responses, except for verifying the root of the proof. This library is intended to be used by libraries for specific EVM-compatible chains that implement the missing functionality. - -For a detailed readme and usage instructions, see the [monorepo readme](https://github.com/ensdomains/evmgateway/tree/main). diff --git a/evm-verifier/contracts/EVMFetchTarget.sol b/evm-verifier/contracts/EVMFetchTarget.sol deleted file mode 100644 index 6ddc945..0000000 --- a/evm-verifier/contracts/EVMFetchTarget.sol +++ /dev/null @@ -1,32 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import { IEVMVerifier } from './IEVMVerifier.sol'; -import { Address } from '@openzeppelin/contracts/utils/Address.sol'; - -/** - * @dev Callback implementation for users of `EVMFetcher`. If you use `EVMFetcher`, your contract must - * inherit from this contract in order to handle callbacks correctly. - */ -abstract contract EVMFetchTarget { - using Address for address; - - error ResponseLengthMismatch(uint256 actual, uint256 expected); - - /** - * @dev Internal callback function invoked by CCIP-Read in response to a `getStorageSlots` request. - */ - function getStorageSlotsCallback(bytes calldata response, bytes calldata extradata) external { - bytes memory proof = abi.decode(response, (bytes)); - (IEVMVerifier verifier, address addr, bytes32[] memory commands, bytes[] memory constants, bytes4 callback, bytes memory callbackData) = - abi.decode(extradata, (IEVMVerifier, address, bytes32[], bytes[], bytes4, bytes)); - bytes[] memory values = verifier.getStorageValues(addr, commands, constants, proof); - if(values.length != commands.length) { - revert ResponseLengthMismatch(values.length, commands.length); - } - bytes memory ret = address(this).functionCall(abi.encodeWithSelector(callback, values, callbackData)); - assembly { - return(add(ret, 32), mload(ret)) - } - } -} diff --git a/evm-verifier/contracts/EVMFetcher.sol b/evm-verifier/contracts/EVMFetcher.sol deleted file mode 100644 index 5b56b15..0000000 --- a/evm-verifier/contracts/EVMFetcher.sol +++ /dev/null @@ -1,228 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import { IEVMVerifier } from './IEVMVerifier.sol'; -import { EVMFetchTarget } from './EVMFetchTarget.sol'; -import { Address } from '@openzeppelin/contracts/utils/Address.sol'; - -interface IEVMGateway { - function getStorageSlots(address addr, bytes32[] memory commands, bytes[] memory constants) external pure returns(bytes memory witness); -} - -uint8 constant FLAG_DYNAMIC = 0x01; -uint8 constant OP_CONSTANT = 0x00; -uint8 constant OP_BACKREF = 0x20; -uint8 constant OP_END = 0xff; - -/** - * @dev A library to facilitate requesting storage data proofs from contracts, possibly on a different chain. - * See l1-verifier/test/TestL1.sol for example usage. - */ -library EVMFetcher { - uint256 constant MAX_COMMANDS = 32; - uint256 constant MAX_CONSTANTS = 32; // Must not be greater than 32 - - using Address for address; - - error TooManyCommands(uint256 max); - error CommandTooLong(); - error InvalidReference(uint256 value, uint256 max); - error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData); - - struct EVMFetchRequest { - IEVMVerifier verifier; - address target; - bytes32[] commands; - uint256 operationIdx; - bytes[] constants; - } - - /** - * @dev Creates a request to fetch the value of multiple storage slots from a contract via CCIP-Read, possibly from - * another chain. - * Supports dynamic length values and slot numbers derived from other retrieved values. - * @param verifier An instance of a verifier contract that can provide and verify the storage slot information. - * @param target The address of the contract to fetch storage proofs for. - */ - function newFetchRequest(IEVMVerifier verifier, address target) internal pure returns (EVMFetchRequest memory) { - bytes32[] memory commands = new bytes32[](MAX_COMMANDS); - bytes[] memory constants = new bytes[](MAX_CONSTANTS); - assembly { - mstore(commands, 0) // Set current array length to 0 - mstore(constants, 0) - } - return EVMFetchRequest(verifier, target, commands, 0, constants); - } - - /** - * @dev Starts describing a new fetch request. - * Paths specify a series of hashing operations to derive the final slot ID. - * See https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_storage.html for details on how Solidity - * lays out storage variables. - * @param request The request object being operated on. - * @param baseSlot The base slot ID that forms the root of the path. - */ - function getStatic(EVMFetchRequest memory request, uint256 baseSlot) internal pure returns (EVMFetchRequest memory) { - bytes32[] memory commands = request.commands; - uint256 commandIdx = commands.length; - if(commandIdx > 0 && request.operationIdx < 32) { - // Terminate previous command - _addOperation(request, OP_END); - } - assembly { - mstore(commands, add(commandIdx, 1)) // Increment command array length - } - if(request.commands.length > MAX_COMMANDS) { - revert TooManyCommands(MAX_COMMANDS); - } - request.operationIdx = 0; - _addOperation(request, 0); - _addOperation(request, _addConstant(request, abi.encode(baseSlot))); - return request; - } - - /** - * @dev Starts describing a new fetch request. - * Paths specify a series of hashing operations to derive the final slot ID. - * See https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_storage.html for details on how Solidity - * lays out storage variables. - * @param request The request object being operated on. - * @param baseSlot The base slot ID that forms the root of the path. - */ - function getDynamic(EVMFetchRequest memory request, uint256 baseSlot) internal pure returns (EVMFetchRequest memory) { - bytes32[] memory commands = request.commands; - uint256 commandIdx = commands.length; - if(commandIdx > 0 && request.operationIdx < 32) { - // Terminate previous command - _addOperation(request, OP_END); - } - assembly { - mstore(commands, add(commandIdx, 1)) // Increment command array length - } - if(request.commands.length > MAX_COMMANDS) { - revert TooManyCommands(MAX_COMMANDS); - } - request.operationIdx = 0; - _addOperation(request, FLAG_DYNAMIC); - _addOperation(request, _addConstant(request, abi.encode(baseSlot))); - return request; - } - - /** - * @dev Adds a `uint256` element to the current path. - * @param request The request object being operated on. - * @param el The element to add. - */ - function element(EVMFetchRequest memory request, uint256 el) internal pure returns (EVMFetchRequest memory) { - if(request.operationIdx >= 32) { - revert CommandTooLong(); - } - _addOperation(request, _addConstant(request, abi.encode(el))); - return request; - } - - /** - * @dev Adds a `bytes32` element to the current path. - * @param request The request object being operated on. - * @param el The element to add. - */ - function element(EVMFetchRequest memory request, bytes32 el) internal pure returns (EVMFetchRequest memory) { - if(request.operationIdx >= 32) { - revert CommandTooLong(); - } - _addOperation(request, _addConstant(request, abi.encode(el))); - return request; - } - - /** - * @dev Adds an `address` element to the current path. - * @param request The request object being operated on. - * @param el The element to add. - */ - function element(EVMFetchRequest memory request, address el) internal pure returns (EVMFetchRequest memory) { - if(request.operationIdx >= 32) { - revert CommandTooLong(); - } - _addOperation(request, _addConstant(request, abi.encode(el))); - return request; - } - - /** - * @dev Adds a `bytes` element to the current path. - * @param request The request object being operated on. - * @param el The element to add. - */ - function element(EVMFetchRequest memory request, bytes memory el) internal pure returns (EVMFetchRequest memory) { - if(request.operationIdx >= 32) { - revert CommandTooLong(); - } - _addOperation(request, _addConstant(request, el)); - return request; - } - - /** - * @dev Adds a `string` element to the current path. - * @param request The request object being operated on. - * @param el The element to add. - */ - function element(EVMFetchRequest memory request, string memory el) internal pure returns (EVMFetchRequest memory) { - if(request.operationIdx >= 32) { - revert CommandTooLong(); - } - _addOperation(request, _addConstant(request, bytes(el))); - return request; - } - - /** - * @dev Adds a reference to a previous fetch to the current path. - * @param request The request object being operated on. - * @param idx The index of the previous fetch request, starting at 0. - */ - function ref(EVMFetchRequest memory request, uint8 idx) internal pure returns (EVMFetchRequest memory) { - if(request.operationIdx >= 32) { - revert CommandTooLong(); - } - if(idx > request.commands.length || idx > 31) { - revert InvalidReference(idx, request.commands.length); - } - _addOperation(request, OP_BACKREF | idx); - return request; - } - - /** - * @dev Initiates the fetch request. - * Calling this function terminates execution; clients that implement CCIP-Read will make a callback to - * `callback` with the results of the operation. - * @param callbackId A callback function selector on this contract that will be invoked via CCIP-Read with the result of the lookup. - * The function must have a signature matching `(bytes[] memory values, bytes callbackData)` with a return type matching the call in which - * this function was invoked. Its return data will be returned as the return value of the entire CCIP-read operation. - * @param callbackData Extra data to supply to the callback. - */ - function fetch(EVMFetchRequest memory request, bytes4 callbackId, bytes memory callbackData) internal view { - if(request.commands.length > 0 && request.operationIdx < 32) { - // Terminate last command - _addOperation(request, OP_END); - } - revert OffchainLookup( - address(this), - request.verifier.gatewayURLs(), - abi.encodeCall(IEVMGateway.getStorageSlots, (request.target, request.commands, request.constants)), - EVMFetchTarget.getStorageSlotsCallback.selector, - abi.encode(request.verifier, request.target, request.commands, request.constants, callbackId, callbackData) - ); - } - - function _addConstant(EVMFetchRequest memory request, bytes memory value) private pure returns(uint8 idx) { - bytes[] memory constants = request.constants; - idx = uint8(constants.length); - assembly { - mstore(constants, add(idx, 1)) // Increment constant array length - } - constants[idx] = value; - } - - function _addOperation(EVMFetchRequest memory request, uint8 op) private pure { - uint256 commandIdx = request.commands.length - 1; - request.commands[commandIdx] = request.commands[commandIdx] | (bytes32(bytes1(op)) >> (8 * request.operationIdx++)); - } -} diff --git a/evm-verifier/contracts/EVMProofHelper.sol b/evm-verifier/contracts/EVMProofHelper.sol deleted file mode 100644 index d91f139..0000000 --- a/evm-verifier/contracts/EVMProofHelper.sol +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {RLPReader} from "@eth-optimism/contracts-bedrock/src/libraries/rlp/RLPReader.sol"; -import {Bytes} from "@eth-optimism/contracts-bedrock/src/libraries/Bytes.sol"; -import {SecureMerkleTrie} from "./SecureMerkleTrie.sol"; - -struct StateProof { - bytes[] stateTrieWitness; // Witness proving the `storageRoot` against a state root. - bytes[][] storageProofs; // An array of proofs of individual storage elements -} - -uint8 constant OP_CONSTANT = 0x00; -uint8 constant OP_BACKREF = 0x20; -uint8 constant FLAG_DYNAMIC = 0x01; - -library EVMProofHelper { - using Bytes for bytes; - - error AccountNotFound(address); - error UnknownOpcode(uint8); - error InvalidSlotSize(uint256 size); - - /** - * @notice Get the storage root for the provided merkle proof - * @param stateRoot The state root the witness was generated against - * @param target The address we are fetching a storage root for - * @param witness A witness proving the value of the storage root for `target`. - * @return The storage root retrieved from the provided state root - */ - function getStorageRoot(bytes32 stateRoot, address target, bytes[] memory witness) private pure returns (bytes32) { - (bool exists, bytes memory encodedResolverAccount) = SecureMerkleTrie.get( - abi.encodePacked(target), - witness, - stateRoot - ); - if(!exists) { - revert AccountNotFound(target); - } - RLPReader.RLPItem[] memory accountState = RLPReader.readList(encodedResolverAccount); - return bytes32(RLPReader.readBytes(accountState[2])); - } - - /** - * @notice Prove whether the provided storage slot is part of the storageRoot - * @param storageRoot the storage root for the account that contains the storage slot - * @param slot The storage key we are fetching the value of - * @param witness the StorageProof struct containing the necessary proof data - * @return The retrieved storage proof value or 0x if the storage slot is empty - */ - function getSingleStorageProof(bytes32 storageRoot, uint256 slot, bytes[] memory witness) private pure returns (bytes memory) { - (bool exists, bytes memory retrievedValue) = SecureMerkleTrie.get( - abi.encodePacked(slot), - witness, - storageRoot - ); - if(!exists) { - // Nonexistent values are treated as zero. - return ""; - } - return RLPReader.readBytes(retrievedValue); - } - - function getFixedValue(bytes32 storageRoot, uint256 slot, bytes[] memory witness) private pure returns(bytes32) { - bytes memory value = getSingleStorageProof(storageRoot, slot, witness); - // RLP encoded storage slots are stored without leading 0 bytes. - // Casting to bytes32 appends trailing 0 bytes, so we have to bit shift to get the - // original fixed-length representation back. - return bytes32(value) >> (256 - 8 * value.length); - } - - function executeOperation(bytes1 operation, bytes[] memory constants, bytes[] memory values) private pure returns(bytes memory) { - uint8 opcode = uint8(operation) & 0xe0; - uint8 operand = uint8(operation) & 0x1f; - - if(opcode == OP_CONSTANT) { - return constants[operand]; - } else if(opcode == OP_BACKREF) { - return values[operand]; - } else { - revert UnknownOpcode(opcode); - } - } - - function computeFirstSlot(bytes32 command, bytes[] memory constants, bytes[] memory values) private pure returns(bool isDynamic, uint256 slot) { - uint8 flags = uint8(command[0]); - isDynamic = (flags & FLAG_DYNAMIC) != 0; - - bytes memory slotData = executeOperation(command[1], constants, values); - require(slotData.length == 32, "First path element must be 32 bytes"); - slot = uint256(bytes32(slotData)); - - for(uint256 j = 2; j < 32 && command[j] != 0xff; j++) { - bytes memory index = executeOperation(command[j], constants, values); - slot = uint256(keccak256(abi.encodePacked(index, slot))); - } - } - - function getDynamicValue(bytes32 storageRoot, uint256 slot, StateProof memory proof, uint256 proofIdx) private pure returns(bytes memory value, uint256 newProofIdx) { - uint256 firstValue = uint256(getFixedValue(storageRoot, slot, proof.storageProofs[proofIdx++])); - if(firstValue & 0x01 == 0x01) { - // Long value: first slot is `length * 2 + 1`, following slots are data. - uint256 length = (firstValue - 1) / 2; - value = ""; - slot = uint256(keccak256(abi.encodePacked(slot))); - // This is horribly inefficient - O(n^2). A better approach would be to build an array of words and concatenate them - // all at once, but we're trying to avoid writing new library code. - while(length > 0) { - if(length < 32) { - value = bytes.concat(value, getSingleStorageProof(storageRoot, slot++, proof.storageProofs[proofIdx++]).slice(0, length)); - length = 0; - } else { - value = bytes.concat(value, getSingleStorageProof(storageRoot, slot++, proof.storageProofs[proofIdx++])); - length -= 32; - } - } - return (value, proofIdx); - } else { - // Short value: least significant byte is `length * 2`, other bytes are data. - uint256 length = (firstValue & 0xFF) / 2; - return (abi.encode(firstValue).slice(0, length), proofIdx); - } - } - - function getStorageValues(address target, bytes32[] memory commands, bytes[] memory constants, bytes32 stateRoot, StateProof memory proof) internal pure returns(bytes[] memory values) { - bytes32 storageRoot = getStorageRoot(stateRoot, target, proof.stateTrieWitness); - uint256 proofIdx = 0; - values = new bytes[](commands.length); - for(uint256 i = 0; i < commands.length; i++) { - bytes32 command = commands[i]; - (bool isDynamic, uint256 slot) = computeFirstSlot(command, constants, values); - if(!isDynamic) { - values[i] = abi.encode(getFixedValue(storageRoot, slot, proof.storageProofs[proofIdx++])); - if(values[i].length > 32) { - revert InvalidSlotSize(values[i].length); - } - } else { - (values[i], proofIdx) = getDynamicValue(storageRoot, slot, proof, proofIdx); - } - } - } -} \ No newline at end of file diff --git a/evm-verifier/contracts/IEVMVerifier.sol b/evm-verifier/contracts/IEVMVerifier.sol deleted file mode 100644 index 24f2a3c..0000000 --- a/evm-verifier/contracts/IEVMVerifier.sol +++ /dev/null @@ -1,7 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -interface IEVMVerifier { - function gatewayURLs() external view returns(string[] memory); - function getStorageValues(address target, bytes32[] memory commands, bytes[] memory constants, bytes memory proof) external view returns(bytes[] memory values); -} diff --git a/evm-verifier/contracts/MerkleTrie.sol b/evm-verifier/contracts/MerkleTrie.sol deleted file mode 100644 index e00564a..0000000 --- a/evm-verifier/contracts/MerkleTrie.sol +++ /dev/null @@ -1,329 +0,0 @@ -// 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 "@eth-optimism/contracts-bedrock/src/libraries/Bytes.sol"; -import { RLPReader } from "@eth-optimism/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. - 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; - } -} \ No newline at end of file diff --git a/evm-verifier/contracts/SecureMerkleTrie.sol b/evm-verifier/contracts/SecureMerkleTrie.sol deleted file mode 100644 index 40211f0..0000000 --- a/evm-verifier/contracts/SecureMerkleTrie.sol +++ /dev/null @@ -1,69 +0,0 @@ -// 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; - -/* Library Imports */ -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 Whether or not the proof is valid. - */ - function verifyInclusionProof( - bytes memory _key, - bytes memory _value, - bytes[] memory _proof, - bytes32 _root - ) internal pure returns (bool) { - bytes memory key = _getSecureKey(_key); - return 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 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) { - bytes memory key = _getSecureKey(_key); - return MerkleTrie.get(key, _proof, _root); - } - - /** - * @notice Computes the hashed version of the input key. - * - * @param _key Key to hash. - * - * @return Hashed version of the key. - */ - function _getSecureKey(bytes memory _key) private pure returns (bytes memory) { - return abi.encodePacked(keccak256(_key)); - } -} \ No newline at end of file diff --git a/evm-verifier/hardhat.config.ts b/evm-verifier/hardhat.config.ts deleted file mode 100644 index c4ef851..0000000 --- a/evm-verifier/hardhat.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-toolbox"; - -const config: HardhatUserConfig = { - solidity: "0.8.19", -}; - -export default config; diff --git a/evm-verifier/package.json b/evm-verifier/package.json deleted file mode 100644 index e46b0b5..0000000 --- a/evm-verifier/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@ensdomains/evm-verifier", - "license": "MIT", - "version": "0.1.0", - "scripts": { - "build": "echo 'building evm-verifier...' && bun hardhat compile", - "test": "bun hardhat test", - "clean": "rm -fr artifacts cache node_modules typechain-types", - "lint": "exit 0" - }, - "devDependencies": { - "@foundry-rs/hardhat-anvil": "^0.1.7", - "@nomicfoundation/hardhat-toolbox": "^3.0.0", - "@types/express": "^4.17.18", - "@types/supertest": "^2.0.14", - "ethers": "^6.7.1", - "express": "^4.18.2", - "ganache": "^7.9.1", - "hardhat": "^2.17.4", - "supertest": "^6.3.3" - }, - "dependencies": { - "@eth-optimism/contracts-bedrock": "^0.16.2", - "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", - "@nomicfoundation/hardhat-ethers": "^3.0.0", - "@nomicfoundation/hardhat-network-helpers": "^1.0.0", - "@nomicfoundation/hardhat-verify": "^1.0.0", - "@nomiclabs/hardhat-ganache": "^2.0.1", - "@openzeppelin/contracts": "^4.9.3", - "@typechain/ethers-v6": "^0.4.0", - "@typechain/hardhat": "^8.0.0", - "@types/chai": "^4.2.0", - "@types/mocha": ">=9.1.0", - "chai": "^4.2.0", - "hardhat-gas-reporter": "^1.0.8", - "solidity-bytes-utils": "^0.8.0", - "solidity-coverage": "^0.8.1", - "ts-node": "^10.9.1", - "typechain": "^8.2.0", - "typescript": "^5.2.2" - } -} diff --git a/evm-verifier/scripts/deploy.ts b/evm-verifier/scripts/deploy.ts deleted file mode 100644 index 1819253..0000000 --- a/evm-verifier/scripts/deploy.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ethers } from "hardhat"; - -async function main() { - const currentTimestampInSeconds = Math.round(Date.now() / 1000); - const unlockTime = currentTimestampInSeconds + 60; - - const lockedAmount = ethers.parseEther("0.001"); - - const lock = await ethers.deployContract("Lock", [unlockTime], { - value: lockedAmount, - }); - - await lock.waitForDeployment(); - - console.log( - `Lock with ${ethers.formatEther( - lockedAmount - )}ETH and unlock timestamp ${unlockTime} deployed to ${lock.target}` - ); -} - -// We recommend this pattern to be able to use async/await everywhere -// and properly handle errors. -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/evm-verifier/tsconfig.json b/evm-verifier/tsconfig.json deleted file mode 100644 index 574e785..0000000 --- a/evm-verifier/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "commonjs", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true, - "resolveJsonModule": true - } -} diff --git a/package.json b/package.json index dfeec85..60b6031 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "type": "module", "description": "A generic gateway and smart contract library for cross-chain data retrieval", "workspaces": [ - "evm-verifier", "crosschain-reverse-resolver", "crosschain-resolver" ],