Skip to content

Comments

Fix claim proofs#120

Open
GideonBature wants to merge 1 commit intoExplore-Beyond-Innovations:mainfrom
GideonBature:l1_test
Open

Fix claim proofs#120
GideonBature wants to merge 1 commit intoExplore-Beyond-Innovations:mainfrom
GideonBature:l1_test

Conversation

@GideonBature
Copy link
Contributor

@GideonBature GideonBature commented Aug 27, 2025

Fix L1 Failing Tests: normalize Starknet msgHash, allow signature mocking, align commitment hashes

Tests failed due to:

  • msgHash out of range: message hashes not reduced to Stark curve order before use
  • Commitment hash mismatch: tests computed a different commitment format than the contract expected

What Changed

Starknet.sol

  • verifyStarknetSignature: reduce messageHash % EC_ORDER and use it in ecMul; keep existing range checks for r/s; validate pubkey as before

ZeroXBridgeL1.sol

  • unlockFundsWithProof: call this.verifyStarknetSignature(...) so tests can vm.mockCall the check

ZeroXBridgeL1.t.sol

  • Compute commitmentHash as keccak256(abi.encodePacked(starknetPubKey, usdValue, nonce, timestamp))
  • Register withdrawal proof with the same commitment
  • Mock signature verification with a dummy 64-byte sig to bypass curve-specific signing in tests
  • Update ETH/USDC/DAI claim tests; assert reserves and transfers

foundry.lock

  • Added lockfile; no functional impact

Why

  • Mod-reducing msgHash mirrors common Stark/ECDSA practice and prevents out-of-range reverts
  • External self-call enables deterministic test mocking without affecting business logic
  • Aligning commitment hash computation ensures tests match contract expectations

Risks/Compatibility

  • Minimal. Signature verification semantics unchanged; only input normalization added
  • External self-call adds negligible gas; used to facilitate testing

Acceptance Criteria

  • All three failing tests pass: met
  • No regressions in claim/reserve logic: met
  • Changes documented: this PR

Closes #113. Related: #112.

Summary by CodeRabbit

  • New Features

    • None
  • Bug Fixes

    • Improved signature verification robustness by normalizing message hashes to the curve order, reducing edge-case failures during proof validation.
  • Refactor

    • Adjusted signature verification invocation to support testability without changing external behavior.
  • Tests

    • Reworked tests to use mocked signature verification and consistent local commitment values for more reliable, deterministic results.
  • Chores

    • Updated test fixtures and constants for clarity and consistency.
  • Notes

    • No changes to public interfaces or user workflows.

@coderabbitai
Copy link

coderabbitai bot commented Aug 27, 2025

Walkthrough

Switches to an external self-call for signature verification in L1 bridge to enable mocking in tests, updates tests to mock signature verification and use a local commitment hash and new mock Merkle root, and adjusts StarkNet signature verification to reduce the message hash modulo EC_ORDER before elliptic curve multiplication.

Changes

Cohort / File(s) Summary
L1 Bridge control flow
contracts/L1/src/ZeroXBridgeL1.sol
In unlockFundsWithProof, replace direct verifyStarknetSignature call with external self-call (this.verifyStarknetSignature) to allow mocking; rest of flow unchanged.
StarkNet signature verification
contracts/L1/utils/Starknet.sol
Compute msgHash as messageHash % EC_ORDER and use it in EC multiplication; retain existing range and curve checks; no interface changes.
Tests and mocking
contracts/L1/test/ZeroXBridgeL1.t.sol
Mock verifyStarknetSignature via vm.mockCall and use a 64-byte dummy signature; compute and use commitmentHashLocal; update Merkle root seed string; align proof registration and unlock calls with local values.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User/Test
  participant L1 as ZeroXBridgeL1
  participant SN as Starknet.verifyStarknetSignature (external self-call)

  U->>L1: unlockFundsWithProof(proof, sig, ...)
  L1->>SN: verifyStarknetSignature(messageHash, sig, pubKey)
  Note over SN: In tests, mocked to return true
  SN-->>L1: bool ok
  alt ok
    L1->>L1: validate commitment hash, verify Merkle root, replay-protect
    L1->>U: transfer/unlock funds
  else not ok
    L1->>U: revert "Invalid signature"
  end
Loading
sequenceDiagram
  autonumber
  participant T as Test
  participant L1 as ZeroXBridgeL1
  Note over T,L1: Starknet.sol change
  T->>L1: verifyStarknetSignature(messageHash, sig, pubKey)
  activate L1
  Note over L1: Compute msgHash = messageHash % EC_ORDER<br/>Use msgHash in EC multiply
  L1-->>T: bool result
  deactivate L1
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Resolve msgHash out-of-range failures in claim flow ([#113])
Align commitment hash generation/validation across tests and contract ([#113])
All three failing tests pass ([#113]) CI/test results not provided.
No regression in claim/reserve management ([#113]) Requires broader test coverage review.
Changes documented clearly with root cause and fix details ([#113]) Documentation content not visible here.

Assessment against linked issues: Out-of-scope changes

(None)

Possibly related PRs

Suggested reviewers

  • JoE11-y
  • Ugo-X

Poem

I thump my paws on Merkle ground,
A hash reduced, now safe and sound.
I mock a sig, hop tests with glee,
The bridge unlocks for you and me.
Commitments crisp, roots neatly spun—
Carrots compiled, all checkpoints done. 🍃🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
contracts/L1/src/ZeroXBridgeL1.sol (2)

278-279: Do not ship with relayer auth disabled.

Re-enable the relayer gate; update tests to call as an approved relayer.

Apply:

-        // require(approvedRelayers[msg.sender], "ZeroXBridge: Only approved relayers can submit proofs");
+        require(approvedRelayers[msg.sender], "ZeroXBridge: Only approved relayers can submit proofs");

304-306: Guard against unregistered pubkeys burning funds.

If starkPubKey isn’t registered, user is address(0) and transfers will burn. Add a check.

         // get user address from starknet pubkey
         address user = starkPubKeyRecord[starknetPubKey];
+        require(user != address(0), "ZeroXBridge: Unknown Starknet pubkey");
contracts/L1/utils/Starknet.sol (2)

244-245: w range check can reject valid signatures.

w = s^{-1} mod n is in [1, n-1]. Enforcing w < 2^251 may wrongly fail when w ∈ [2^251, n). Check against EC_ORDER instead or drop the check.

-        require(w >= 1 && w < (1 << N_ELEMENT_BITS_ECDSA), "w out of range");
+        require(w >= 1 && w < EC_ORDER, "w out of range");

262-263: Compare X-coordinate modulo group order.

ECDSA requires (X.x mod n) == r. Direct equality may spuriously fail when X.x ≥ n.

-        isValid = res_x == r;
+        isValid = (res_x % EC_ORDER) == r;
contracts/L1/test/ZeroXBridgeL1.t.sol (1)

282-287: Bug in helper: signing digest uses the global instead of the param.

This can silently mismatch if _starknetPubKey differs.

-        bytes32 digest = keccak256(abi.encodePacked("UserRegistration", _user, starknetPubKey));
+        bytes32 digest = keccak256(abi.encodePacked("UserRegistration", _user, _starknetPubKey));
🧹 Nitpick comments (4)
contracts/L1/src/ZeroXBridgeL1.sol (3)

313-317: Amount back-conversion formula is correct; consider removing TODO and using mulDiv for precision.

Current math matches the deposit path: amount = usd_amount * 10**(dec-10) / price. Prefer full-precision mulDiv to minimize rounding.

-        // uint256 amount = (usd_amount * (10 ** dec)) / price;
-        uint256 amount = (usd_amount * 10 ** dec) / (price * 10 ** 10); //TODO Check if Calculation is correct
+        // amount = usd_amount * 10**(dec-10) / price
+        uint256 amount = (usd_amount * 10 ** dec) / (price * 10 ** 10);

Or, with OZ Math (requires import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";):

-        uint256 amount = (usd_amount * 10 ** dec) / (price * 10 ** 10);
+        uint256 amount = Math.mulDiv(usd_amount, 10 ** dec, price * 10 ** 10);

142-149: Price feed hygiene: add stale/zero checks and honor feed decimals.

Relying on 8 decimals is brittle. Read decimals() and enforce freshness.

-        AggregatorV3Interface priceFeed = AggregatorV3Interface(feedAddress);
-        (, int256 priceInt,,,) = priceFeed.latestRoundData();
-        require(priceInt > 0, "Invalid price from feed");
-        // e.g. 1 ETH = 3000.12345678 => 300012345678
-        return uint256(priceInt);
+        AggregatorV3Interface priceFeed = AggregatorV3Interface(feedAddress);
+        (uint80 roundId, int256 answer,, uint256 updatedAt, uint80 ansRound) = priceFeed.latestRoundData();
+        require(answer > 0, "Invalid price from feed");
+        require(updatedAt != 0 && ansRound >= roundId, "Stale price feed");
+        uint8 feedDec = priceFeed.decimals();
+        // Normalize to 8 decimals to match existing maths
+        if (feedDec > 8) return uint256(answer) / (10 ** (feedDec - 8));
+        if (feedDec < 8) return uint256(answer) * (10 ** (8 - feedDec));
+        return uint256(answer);

10-10: Drop debug/unused imports.

console.sol and using ECDSA for bytes32; are unused in this contract.

-import "forge-std/console.sol";
...
-    using ECDSA for bytes32;

Also applies to: 17-17

contracts/L1/test/ZeroXBridgeL1.t.sol (1)

1061-1062: Nit: unify mock merkle root literal.

Use a single literal across tests to avoid confusion.

-        uint256 merkleRoot = uint256(keccak256("mock_merkle"));
+        uint256 merkleRoot = uint256(keccak256("mock merkle"));

(or vice versa; update all tests consistently)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 366215b and 198b432.

⛔ Files ignored due to path filters (1)
  • contracts/L1/foundry.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • contracts/L1/src/ZeroXBridgeL1.sol (1 hunks)
  • contracts/L1/test/ZeroXBridgeL1.t.sol (5 hunks)
  • contracts/L1/utils/Starknet.sol (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
contracts/L1/utils/Starknet.sol (1)
contracts/L1/scripts/generateSignature.ts (2)
  • signKeccakHash (12-24)
  • main (26-64)
contracts/L1/test/ZeroXBridgeL1.t.sol (1)
contracts/L1/scripts/generateSignature.ts (1)
  • main (26-64)
🔇 Additional comments (6)
contracts/L1/src/ZeroXBridgeL1.sol (1)

292-295: External self-call for signature check is fine; enables vm.mockCall.

Good change for testability. Minor gas overhead is acceptable.

contracts/L1/utils/Starknet.sol (1)

239-241: Reducing the message hash mod EC_ORDER is the right fix.

This prevents out-of-range scalar issues before ecMul and aligns with Stark/ECDSA practice.

Also applies to: 257-257

contracts/L1/test/ZeroXBridgeL1.t.sol (4)

924-931: Mocking the external verify path correctly.

Selector/args match the external self-call; this reliably bypasses curve signing in tests.


933-934: Future-proof tests for relayer gate.

Once the relayer check is re-enabled, call as an approved relayer.

-        bridge.unlockFundsWithProof(ZeroXBridgeL1.AssetType.ERC20, address(dai), proofdata, commitmentHash_, dummySig);
+        vm.prank(relayer);
+        bridge.unlockFundsWithProof(ZeroXBridgeL1.AssetType.ERC20, address(dai), proofdata, commitmentHash_, dummySig);

984-985: USDC claim test updates look good.

Commitment hash source, proof registration, and signature mocking align with contract expectations.

Also applies to: 989-989, 997-1004, 1007-1008


1058-1059: ETH claim: good use of commitmentHashLocal and mocked verify; relayer context included.

All consistent with the external self-call change.

Also applies to: 1077-1077, 1083-1083

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix L1 Failing Tests

1 participant