From 88317da97d3cb28f8de5d50fc86fb7e008c8e770 Mon Sep 17 00:00:00 2001 From: RnkSngh Date: Mon, 13 Jan 2025 12:07:28 -0500 Subject: [PATCH] add sequencer sig verifier tests --- contracts/libs/ReceiptParser.sol | 8 ++++ test/PolymerProofLib.t.sol | 6 +++ test/SequencerVerifier.t.sol | 52 ++++++++++++++++++++++ test/payload/l1_block_0x4df537.hex | 1 + test/payload/l2_block_0x4b0.json | 21 +++++++++ test/utils/Signing.base.t.sol | 69 ++++++++++++++++++++++++++++++ 6 files changed, 157 insertions(+) create mode 100644 test/PolymerProofLib.t.sol create mode 100644 test/SequencerVerifier.t.sol create mode 100644 test/payload/l1_block_0x4df537.hex create mode 100644 test/payload/l2_block_0x4b0.json create mode 100644 test/utils/Signing.base.t.sol diff --git a/contracts/libs/ReceiptParser.sol b/contracts/libs/ReceiptParser.sol index b8a2261..6407b35 100644 --- a/contracts/libs/ReceiptParser.sol +++ b/contracts/libs/ReceiptParser.sol @@ -41,6 +41,14 @@ struct Ics23Proof { uint256 height; } +// This is the proof we use to verify the apphash (state) updates. +struct OpL2StateProof { + bytes[] accountProof; + bytes[] outputRootProof; + bytes32 l2OutputProposalKey; + bytes32 l2BlockHash; +} + /** * A library for helpers for proving peptide state */ diff --git a/test/PolymerProofLib.t.sol b/test/PolymerProofLib.t.sol new file mode 100644 index 0000000..fd95e12 --- /dev/null +++ b/test/PolymerProofLib.t.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; + +contract PolymerProofLibTest is Test {} diff --git a/test/SequencerVerifier.t.sol b/test/SequencerVerifier.t.sol new file mode 100644 index 0000000..72c1408 --- /dev/null +++ b/test/SequencerVerifier.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "./utils/Signing.base.t.sol"; + +contract SequencerProofVerifierStateUpdate is SigningBase { + function test_verify_signature_state_update_sucess() public view { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(sequencerPkey, hashToSign); + + bytes memory signature = abi.encodePacked(r, s, v); // Annoyingly, v, r, s are in a different order than those + // returned from vm.sign + + // This call should not revert as long as it's a valid sequencer signature + sigVerifier.verifyStateUpdate(peptideBlockNumber, peptideAppHash, l1BlockHash, signature); + } + + // Call should revert if invalid signature values are passed + function test_verify_state_update_invalid_signature() public { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(sequencerPkey, hashToSign); + + bytes memory signatureTooShort = abi.encodePacked(r, s); + bytes memory signatureTooLong = abi.encodePacked(r, s, v, uint256(123)); + + // This call should not revert as long as it's a valid sequencer signature + vm.expectRevert("ECDSA: invalid signature length"); + sigVerifier.verifyStateUpdate(peptideBlockNumber, peptideAppHash, l1BlockHash, signatureTooShort); + vm.expectRevert("ECDSA: invalid signature length"); + sigVerifier.verifyStateUpdate(peptideBlockNumber, peptideAppHash, l1BlockHash, signatureTooLong); + } + + // Valid signature but from the wrong signer should revert + function test_verify_state_update_not_signer() public { + (, uint256 notSequencerPkey) = makeAddrAndKey(unicode"😳"); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(notSequencerPkey, hashToSign); + + bytes memory wrongSignerSignature = abi.encodePacked(r, s, v); + + vm.expectRevert(abi.encodeWithSelector(ISignatureVerifier.InvalidSequencerSignature.selector)); + sigVerifier.verifyStateUpdate(peptideBlockNumber, peptideAppHash, l1BlockHash, wrongSignerSignature); + } + + // Valid signature but on the wrong header should revert + function test_verify_state_update_incorrect_header() public { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(sequencerPkey, hashToSign); + + bytes memory signature = abi.encodePacked(r, s, v); + + vm.expectRevert(abi.encodeWithSelector(ISignatureVerifier.InvalidSequencerSignature.selector)); + sigVerifier.verifyStateUpdate(peptideBlockNumber + 1, peptideAppHash, l1BlockHash, signature); + } +} diff --git a/test/payload/l1_block_0x4df537.hex b/test/payload/l1_block_0x4df537.hex new file mode 100644 index 0000000..f0d199b --- /dev/null +++ b/test/payload/l1_block_0x4df537.hex @@ -0,0 +1 @@ +0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000604d00f3add4dfa340f263a29c611ca08de042d195efd8fab8ec18a102d5c6f63c00000000000000000000000000000000000000000000000000000000004df53700000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000005c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000068000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000076000000000000000000000000000000000000000000000000000000000000007a000000000000000000000000000000000000000000000000000000000000007e00000000000000000000000000000000000000000000000000000000000000021a07b7f1cd3400204ab518b143360ac525502f4bb581d427429f196fe1918ac6024000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015946a7aa9b882d50bb7bc5da1a244719c99f12f06a300000000000000000000000000000000000000000000000000000000000000000000000000000000000021a04d00f3add4dfa340f263a29c611ca08de042d195efd8fab8ec18a102d5c6f63c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021a0055b2c032685cb2a61d961c90c07959746fed2d28645ffa111a4ff89ad43ccd7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021a0a04f47b60169ec4e651dd932ffe8d66b33de8c8548af12657eefda8895217593000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103b901000d70308f1a5cf2a1a33174d28ca50572c6f8000c4294a84d0ad019e92109402ac20a588289bf2aabd8298a2ac42486b3500d0e12318b57484511d09a12365a4d3209ad824fe56a70e1102a8b886da09486115c8409a9023583000a26406060203ff681dc075dc25d84a040774c32a995da8c2ec045248421aa9a8c1e308b0f148230ca093578200027fb11318ad072101be1b44b184780e579a7580f264018510e8ca70a1af0104b426901d26850b059d49230104a1a8c85604027a0256200b20381bb23122cd2504309e809691612d90475162002740a2640a6aa331207212918f809b700f112c12b8a3650a3a4211ea66964300a410301e70b1e8502d27a9d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004834df5370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058401c9c380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000584013577f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058465a94dd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a99d883010d08846765746888676f312e32302e31856c696e75780000000000000000000000000000000000000000000000000000000000000000000000000021a021c3d44a811c2c122d61ca3aef8847c6d6f6defe5f02ff74a96b41ee38c8497500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000988000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006850cbade162400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021a0ab226847f9728d2c1b0fd37a4431c938a65a196304dddc4e84615862767aa2ca00000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/test/payload/l2_block_0x4b0.json b/test/payload/l2_block_0x4b0.json new file mode 100644 index 0000000..3675ee7 --- /dev/null +++ b/test/payload/l2_block_0x4b0.json @@ -0,0 +1,21 @@ +{ + "jsonrpc": "2.0", + "id": "1", + "result": { + "baseFeePerGas": "0x0", + "difficulty": "0x0", + "extraData": "", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "hash": "0xf51d6bb7b2578b14d98849963eab7433110e4ad7fdac1636d03bcc9f9a852721", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "mixHash": "0x13368de1a039f35df18b6363569284e944460f3e408fabe7ce998c2c5d8f46d0", + "number": "0x4b0", + "parentHash": "0xb1c7530229b8155c745feaa0da2d9018f3c873fd8e4a8f226a08b8b1c8bc894e", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "stateRoot": "0x6cc4ecfce2112e8b6100e023edac48d81f554c1d3869bac8140b0609959586af", + "timestamp": "0x657ba6ba", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + } +} diff --git a/test/utils/Signing.base.t.sol b/test/utils/Signing.base.t.sol new file mode 100644 index 0000000..fd70ae3 --- /dev/null +++ b/test/utils/Signing.base.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "../../contracts/core/SequencerSignatureVerifier.sol"; +import {RLPWriter} from "optimism/libraries/rlp/RLPWriter.sol"; +import {L1Header} from "../../contracts/interfaces/IAppStateVerifier.sol"; + +contract SigningBase is Test { + using stdJson for string; + + string rootDir = vm.projectRoot(); + + bytes32 l1BlockHash; + bytes32 peptideAppHash; + uint256 public peptideBlockNumber; + uint256 public sequencerPkey; + uint256 public notSequencerPkey; + address public sequencer; + bytes32 hashToSign; + bytes32 domain; // Domain will be empty so we can leave it as initialized to default 0x 32 bytes + + SequencerSignatureVerifier public sigVerifier; + bytes32 PEPTIDE_CHAIN_ID = bytes32(uint256(444)); + + L1Header childl1Block; // Child block, represents the l1 origin of dest chain when peptide catches up to ancestor L1 + // block + L1Header ancestorL1Block; // Ancestor block, represents the l1 origin of dest chain when peptide wants to submit a + + // client update but is behind, so we need to checkpoint this block + + constructor() { + (sequencer, sequencerPkey) = makeAddrAndKey("alice"); + (, notSequencerPkey) = makeAddrAndKey(unicode"bob😈"); + sigVerifier = new SequencerSignatureVerifier(sequencer, PEPTIDE_CHAIN_ID); + + // generate the channel_proof.hex file with the following command: + // cd test-data-generator && go run ./cmd/ --type l1 > ../test/payload/l1_block_0x4df537.hex + // this is the "rlp" half-encoded header that would be sent by the relayer. this was produced + // by the test-data-generator tool. + L1Header memory l1header = abi.decode( + vm.parseBytes(vm.readFile(string.concat(rootDir, "/test/payload/l1_block_0x4df537.hex"))), (L1Header) + ); + + l1BlockHash = keccak256(RLPWriter.writeList(l1header.header)); // Blockhash that will be signed by sequencer + + peptideBlockNumber = 101; + + // this happens to be the polymer height when the L2OO was updated with the output proposal + // we are using in the test + string memory l2BlockJson = vm.readFile(string.concat(rootDir, "/test/payload/l2_block_0x4b0.json")); + peptideAppHash = abi.decode(l2BlockJson.parseRaw(".result.stateRoot"), (bytes32)); + + bytes32 payloadHash = keccak256(abi.encodePacked(peptideBlockNumber, peptideAppHash, l1BlockHash)); + hashToSign = keccak256(bytes.concat(domain, PEPTIDE_CHAIN_ID, payloadHash)); + } + + // Read a specific bytes32 json property from a json file + function readBytes32FromJson(string memory fileName, string memory property) public view returns (bytes32) { + string memory blockJson = vm.readFile(string.concat(rootDir, fileName)); + return abi.decode(blockJson.parseRaw(property), (bytes32)); + } + + // Read a specific uint64 json property from a json file + function readUint64FromJson(string memory fileName, string memory property) public view returns (uint64) { + string memory blockJson = vm.readFile(string.concat(rootDir, fileName)); + return abi.decode(blockJson.parseRaw(property), (uint64)); + } +}