Skip to content

Commit

Permalink
Add witness root to contract
Browse files Browse the repository at this point in the history
  • Loading branch information
okkothejawa committed Mar 29, 2024
1 parent 7fe2f9e commit e00311d
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 27 deletions.
50 changes: 35 additions & 15 deletions crates/evm/src/evm/system_contracts/src/L1BlockHashList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,68 @@ 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);
constructor() Ownable(){ }

/// @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)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
33 changes: 23 additions & 10 deletions crates/evm/src/evm/system_contracts/test/L1BlockHashList.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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));
}
}
Submodule forge-std added at ae570f

0 comments on commit e00311d

Please sign in to comment.