Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raunak/sig verifier test #2

Merged
merged 1 commit into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions contracts/libs/ReceiptParser.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
6 changes: 6 additions & 0 deletions test/PolymerProofLib.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "forge-std/Test.sol";

contract PolymerProofLibTest is Test {}
52 changes: 52 additions & 0 deletions test/SequencerVerifier.t.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
1 change: 1 addition & 0 deletions test/payload/l1_block_0x4df537.hex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000604d00f3add4dfa340f263a29c611ca08de042d195efd8fab8ec18a102d5c6f63c00000000000000000000000000000000000000000000000000000000004df53700000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000005c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000068000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000076000000000000000000000000000000000000000000000000000000000000007a000000000000000000000000000000000000000000000000000000000000007e00000000000000000000000000000000000000000000000000000000000000021a07b7f1cd3400204ab518b143360ac525502f4bb581d427429f196fe1918ac6024000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015946a7aa9b882d50bb7bc5da1a244719c99f12f06a300000000000000000000000000000000000000000000000000000000000000000000000000000000000021a04d00f3add4dfa340f263a29c611ca08de042d195efd8fab8ec18a102d5c6f63c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021a0055b2c032685cb2a61d961c90c07959746fed2d28645ffa111a4ff89ad43ccd7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021a0a04f47b60169ec4e651dd932ffe8d66b33de8c8548af12657eefda8895217593000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103b901000d70308f1a5cf2a1a33174d28ca50572c6f8000c4294a84d0ad019e92109402ac20a588289bf2aabd8298a2ac42486b3500d0e12318b57484511d09a12365a4d3209ad824fe56a70e1102a8b886da09486115c8409a9023583000a26406060203ff681dc075dc25d84a040774c32a995da8c2ec045248421aa9a8c1e308b0f148230ca093578200027fb11318ad072101be1b44b184780e579a7580f264018510e8ca70a1af0104b426901d26850b059d49230104a1a8c85604027a0256200b20381bb23122cd2504309e809691612d90475162002740a2640a6aa331207212918f809b700f112c12b8a3650a3a4211ea66964300a410301e70b1e8502d27a9d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004834df5370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058401c9c380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000584013577f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058465a94dd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a99d883010d08846765746888676f312e32302e31856c696e75780000000000000000000000000000000000000000000000000000000000000000000000000021a021c3d44a811c2c122d61ca3aef8847c6d6f6defe5f02ff74a96b41ee38c8497500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000988000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006850cbade162400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021a0ab226847f9728d2c1b0fd37a4431c938a65a196304dddc4e84615862767aa2ca00000000000000000000000000000000000000000000000000000000000000
21 changes: 21 additions & 0 deletions test/payload/l2_block_0x4b0.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
69 changes: 69 additions & 0 deletions test/utils/Signing.base.t.sol
Original file line number Diff line number Diff line change
@@ -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));
}
Comment on lines +32 to +56
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor constructor to reduce complexity

The constructor has too many responsibilities. Consider breaking it down into smaller, focused functions:

  • initializeAddresses()
  • loadL1Header()
  • loadPeptideState()
  • computeHashes()


// 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));
}
Comment on lines +59 to +62
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for JSON parsing

The JSON parsing functions should include error handling for:

  • Missing files
  • Invalid JSON format
  • Missing properties
  • Invalid property types

Example implementation:

 function readBytes32FromJson(string memory fileName, string memory property) public view returns (bytes32) {
+    if (bytes(fileName).length == 0 || bytes(property).length == 0) {
+        revert("Invalid file name or property");
+    }
     string memory blockJson = vm.readFile(string.concat(rootDir, fileName));
+    if (bytes(blockJson).length == 0) {
+        revert("File not found or empty");
+    }
     return abi.decode(blockJson.parseRaw(property), (bytes32));
 }

Also applies to: 65-68


// 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));
}
}
Loading