From e00311d7f5b88a65f8ba53d550d2c5592cd9e863 Mon Sep 17 00:00:00 2001 From: okkothejawa <103260942+okkothejawa@users.noreply.github.com> Date: Fri, 29 Mar 2024 15:21:04 +0300 Subject: [PATCH] Add witness root to contract --- .../system_contracts/src/L1BlockHashList.sol | 50 +++++++++++++------ .../src/interfaces/IL1BlockHashList.sol | 4 +- .../test/L1BlockHashList.t.sol | 33 ++++++++---- .../src/evm/system_contracts/lib/forge-std | 1 + 4 files changed, 61 insertions(+), 27 deletions(-) create mode 160000 module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/forge-std diff --git a/crates/evm/src/evm/system_contracts/src/L1BlockHashList.sol b/crates/evm/src/evm/system_contracts/src/L1BlockHashList.sol index 99eacc163..e27e74b3c 100644 --- a/crates/evm/src/evm/system_contracts/src/L1BlockHashList.sol +++ b/crates/evm/src/evm/system_contracts/src/L1BlockHashList.sol @@ -9,7 +9,7 @@ import "./interfaces/IL1BlockHashList.sol"; contract L1BlockHashList is Ownable, IL1BlockHashList { mapping(uint256 => bytes32) public blockHashes; - mapping(bytes32 => bytes32) public merkleRoots; + mapping(bytes32 => bytes32) public witnessRoots; uint256 public blockNumber; event BlockInfoAdded(uint256 blockNumber, bytes32 blockHash, bytes32 merkleRoot); @@ -17,40 +17,60 @@ contract L1BlockHashList is Ownable, IL1BlockHashList { /// @notice Sets the initial value for the block number, can only be called once /// @param _blockNumber The L1 block number that is associated with the genesis block of Citrea - function initializeBlockNumber(uint256 _blockNumber) public onlyOwner { + function initializeBlockNumber(uint256 _blockNumber) external onlyOwner { require(blockNumber == 0, "Already initialized"); blockNumber = _blockNumber; } - /// @notice Sets the block hash and merkle root for a given block + /// @notice Sets the block hash and witness root for a given block /// @notice Can only be called after the initial block number is set /// @dev The block number is incremented by the contract as no block info should be overwritten or skipped /// @param _blockHash The hash of the current L1 block - /// @param _merkleRoot The merkle root of the current L1 block - function setBlockInfo(bytes32 _blockHash, bytes32 _merkleRoot) public onlyOwner { + /// @param _witnessRoot The witness root of the current L1 block + function setBlockInfo(bytes32 _blockHash, bytes32 _witnessRoot) external onlyOwner { uint256 _blockNumber = blockNumber; require(_blockNumber != 0, "Not initialized"); blockHashes[_blockNumber] = _blockHash; blockNumber = _blockNumber + 1; - merkleRoots[_blockHash] = _merkleRoot; - emit BlockInfoAdded(blockNumber, _blockHash, _merkleRoot); + witnessRoots[_blockHash] = _witnessRoot; + emit BlockInfoAdded(blockNumber, _blockHash, _witnessRoot); } /// @param _blockNumber The number of the block to get the hash for /// @return The block hash for the given block - function getBlockHash(uint256 _blockNumber) public view returns (bytes32) { + function getBlockHash(uint256 _blockNumber) external view returns (bytes32) { return blockHashes[_blockNumber]; } - /// @param _blockHash The block hash of the block to get the merkle root for - /// @return The merkle root for the given block - function getMerkleRootByHash(bytes32 _blockHash) public view returns (bytes32) { - return merkleRoots[_blockHash]; + /// @param _blockHash The block hash of the block to get the witness root for + /// @return The witness root for the given block + function getWitnessRootByHash(bytes32 _blockHash) external view returns (bytes32) { + return witnessRoots[_blockHash]; } - /// @param _blockNumber The block number of the block to get the merkle root for + /// @param _blockNumber The block number of the block to get the witness root for /// @return The merkle root for the given block - function getMerkleRootByNumber(uint256 _blockNumber) public view returns (bytes32) { - return merkleRoots[blockHashes[_blockNumber]]; + function getWitnessRootByNumber(uint256 _blockNumber) external view returns (bytes32) { + return witnessRoots[blockHashes[_blockNumber]]; + } + + function verifyInclusion(bytes32 _blockHash, bytes32 _wtxId, bytes32[] calldata _proof) external view returns (bool) { + return _verifyInclusion(_blockHash, _wtxId, _proof); + } + + function verifyInclusion(uint256 _blockNumber, bytes32 _wtxId, bytes32[] calldata _proof) external view returns (bool) { + return _verifyInclusion(blockHashes[_blockNumber], _wtxId, _proof); + } + + function _verifyInclusion(bytes32 _blockHash, bytes32 _wtxId, bytes32[] calldata _proof) internal view returns (bool) { + bytes32 result = _wtxId; + for (uint256 i = 0; i < _proof.length; i++) { + result = hash256(abi.encodePacked(result, _proof[i])); + } + return result == witnessRoots[_blockHash]; + } + + function hash256(bytes memory _bytes) internal pure returns (bytes32) { + return sha256(abi.encodePacked(sha256(_bytes))); } } diff --git a/crates/evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol b/crates/evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol index 06da7b7d7..ad5026624 100644 --- a/crates/evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol +++ b/crates/evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol @@ -5,6 +5,6 @@ interface IL1BlockHashList { function initializeBlockNumber(uint256) external; function setBlockInfo(bytes32, bytes32) external; function getBlockHash(uint256) external view returns (bytes32); - function getMerkleRootByHash(bytes32) external view returns (bytes32); - function getMerkleRootByNumber(uint256) external view returns (bytes32); + function getWitnessRootByHash(bytes32) external view returns (bytes32); + function getWitnessRootByNumber(uint256) external view returns (bytes32); } \ No newline at end of file diff --git a/crates/evm/src/evm/system_contracts/test/L1BlockHashList.t.sol b/crates/evm/src/evm/system_contracts/test/L1BlockHashList.t.sol index fa6204a8a..2b2ddbd1d 100644 --- a/crates/evm/src/evm/system_contracts/test/L1BlockHashList.t.sol +++ b/crates/evm/src/evm/system_contracts/test/L1BlockHashList.t.sol @@ -7,7 +7,7 @@ import "../src/L1BlockHashList.sol"; contract L1BlockHashListTest is Test { L1BlockHashList l1BlockHashList; bytes32 randomBlockHash = bytes32(keccak256("CITREA_TEST")); - bytes32 randomMerkleRoot = bytes32(keccak256("CITREA")); + bytes32 randomWitnessRoot = bytes32(keccak256("CITREA")); uint256 constant INITIAL_BLOCK_NUMBER = 505050; function setUp() public { @@ -16,10 +16,10 @@ contract L1BlockHashListTest is Test { function testSetBlockInfo() public { l1BlockHashList.initializeBlockNumber(INITIAL_BLOCK_NUMBER); - l1BlockHashList.setBlockInfo(randomBlockHash, randomMerkleRoot); + l1BlockHashList.setBlockInfo(randomBlockHash, randomWitnessRoot); assertEq(l1BlockHashList.getBlockHash(INITIAL_BLOCK_NUMBER), randomBlockHash); - assertEq(l1BlockHashList.getMerkleRootByHash(randomBlockHash), randomMerkleRoot); - assertEq(l1BlockHashList.getMerkleRootByNumber(INITIAL_BLOCK_NUMBER), randomMerkleRoot); + assertEq(l1BlockHashList.getWitnessRootByHash(randomBlockHash), randomWitnessRoot); + assertEq(l1BlockHashList.getWitnessRootByNumber(INITIAL_BLOCK_NUMBER), randomWitnessRoot); } function testCannotReinitalize() public { @@ -32,7 +32,7 @@ contract L1BlockHashListTest is Test { l1BlockHashList.initializeBlockNumber(INITIAL_BLOCK_NUMBER); vm.startPrank(address(0x1)); vm.expectRevert("Caller is not owner"); - l1BlockHashList.setBlockInfo(randomBlockHash, randomMerkleRoot); + l1BlockHashList.setBlockInfo(randomBlockHash, randomWitnessRoot); } function testNonOwnerCannotInitializeBlockNumber() public { @@ -43,7 +43,7 @@ contract L1BlockHashListTest is Test { function testCannotSetInfoWithoutInitialize() public { vm.expectRevert("Not initialized"); - l1BlockHashList.setBlockInfo(randomBlockHash, randomMerkleRoot); + l1BlockHashList.setBlockInfo(randomBlockHash, randomWitnessRoot); } function testBlockInfoAvailableAfterManyWrites() public { @@ -53,14 +53,27 @@ contract L1BlockHashListTest is Test { bytes32 root = keccak256(abi.encodePacked(blockHash)); l1BlockHashList.setBlockInfo(blockHash, root); assertEq(l1BlockHashList.getBlockHash(i + INITIAL_BLOCK_NUMBER), blockHash); - assertEq(l1BlockHashList.getMerkleRootByHash(blockHash), root); - assertEq(l1BlockHashList.getMerkleRootByNumber(i + INITIAL_BLOCK_NUMBER), root); + assertEq(l1BlockHashList.getWitnessRootByHash(blockHash), root); + assertEq(l1BlockHashList.getWitnessRootByNumber(i + INITIAL_BLOCK_NUMBER), root); } bytes32 zeroth_hash = keccak256(abi.encodePacked(uint(0))); bytes32 zeroth_root = keccak256(abi.encodePacked(zeroth_hash)); assertEq(l1BlockHashList.getBlockHash(INITIAL_BLOCK_NUMBER), zeroth_hash); - assertEq(l1BlockHashList.getMerkleRootByHash(zeroth_hash), zeroth_root); - assertEq(l1BlockHashList.getMerkleRootByNumber(INITIAL_BLOCK_NUMBER), zeroth_root); + assertEq(l1BlockHashList.getWitnessRootByHash(zeroth_hash), zeroth_root); + assertEq(l1BlockHashList.getWitnessRootByNumber(INITIAL_BLOCK_NUMBER), zeroth_root); + } + + function testVerifyInclusion() public { + l1BlockHashList.initializeBlockNumber(INITIAL_BLOCK_NUMBER); + // 2975efaf781b03df6a635e8f38160492413a311b083d4dc640c524782f695ccc + // 4cf9d14bae01332bbd96fb4d46731c024c0d82ffdfcc70a4fa97aa7395e38f7d + bytes32 root = hex"1603c518f01939a30b46000912288d827588d5bbc70a04098f731c0c5b978c1b"; + l1BlockHashList.setBlockInfo(randomBlockHash, root); + bytes32[] memory proof = new bytes32[](1); + proof[0] = hex"4cf9d14bae01332bbd96fb4d46731c024c0d82ffdfcc70a4fa97aa7395e38f7d"; + bytes32 wtxId = hex"0000000000000000000000000000000000000000000000000000000000000000"; + assert(l1BlockHashList.verifyInclusion(randomBlockHash, wtxId, proof)); + assert(l1BlockHashList.verifyInclusion(INITIAL_BLOCK_NUMBER, wtxId, proof)); } } \ No newline at end of file diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/forge-std b/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/forge-std new file mode 160000 index 000000000..ae570fec0 --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit ae570fec082bfe1c1f45b0acca4a2b4f84d345ce