Skip to content

Ithaca account#62

Closed
Dargon789 wants to merge 12 commits into
Legionfrom
IthacaAccount
Closed

Ithaca account#62
Dargon789 wants to merge 12 commits into
Legionfrom
IthacaAccount

Conversation

@Dargon789

@Dargon789 Dargon789 commented Apr 11, 2026

Copy link
Copy Markdown
Owner

Summary by Sourcery

Add Merkle tree–based signature support to IthacaAccount and update tests and helpers to exercise the new wrapped signature format and Merkle verification, including new benchmark coverage for ERC20 transfers via the orchestrator with passkeys.

New Features:

  • Support Merkle proof–based signature validation in IthacaAccount via a new wrapped signature format that can encode Merkle-backed digests.
  • Introduce a benchmark scenario for ERC20 transfers executed through the orchestrator using passkey-based authorization.

Enhancements:

  • Extend unwrapAndValidateSignature to handle an additional merkle flag in the wrapped signature encoding and delegate to Merkle verification when set.
  • Bump the IthacaAccount EIP-712 version string from 0.5.10 to 0.5.11.

Tests:

  • Add fuzz tests for Merkle-based signatures on accounts, covering valid proofs, digests not in the tree, tampered proofs, and incorrect roots.
  • Update existing test helpers and orchestrator/simulation tests to use the new wrapped signature layout with an explicit Merkle flag byte.

Chores:

  • Add IntelliJ IDEA project configuration files to the repository.

github-actions Bot and others added 12 commits August 5, 2025 22:58
Add testERC20TransferViaPortoOrchestratorWithPasskey() benchmark to isolate
passkey authentication costs from spend limit enforcement costs.

- Uses secp256k1 passkey for transaction signing
- Sets execution permissions for ERC20 transfers
- Requires spend limits (set to max) for passkey operations
- Gas cost: 116,094 (vs 97,030 without passkey, 117,083 with restrictive limits)
- Provides clean measurement of passkey overhead (~19k gas)

Resolves ithacaxyz#272

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Tanishk Goyal <legion2002@users.noreply.github.com>
@sourcery-ai

sourcery-ai Bot commented Apr 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds Merkle tree–based signature support to IthacaAccount, updates the wrapped signature format to include a merkle flag, and introduces tests (including fuzz tests and benchmarks) for Merkle signatures and passkey-based orchestrator flows, along with minor EIP712 domain and versioning adjustments.

Sequence diagram for Merkle-based signature verification in IthacaAccount

sequenceDiagram
    actor Caller
    participant IthacaAccount
    participant MerkleProofLib
    participant ECDSA

    Caller->>IthacaAccount: unwrapAndValidateSignature(digest, wrappedSignature)
    activate IthacaAccount
    IthacaAccount->>IthacaAccount: parse keyHash, prehash flag, merkle flag
    alt merkle flag is set
        IthacaAccount->>IthacaAccount: _verifyMerkleSig(digest, innerMerkleSignature)
        activate IthacaAccount
        IthacaAccount->>MerkleProofLib: verifyCalldata(proof, root, digest)
        activate MerkleProofLib
        MerkleProofLib-->>IthacaAccount: isProofValid
        deactivate MerkleProofLib
        alt isProofValid
            IthacaAccount->>IthacaAccount: unwrapAndValidateSignature(root, rootSig)
            activate IthacaAccount
            IthacaAccount->>ECDSA: recoverCalldata(root, rootSig)
            activate ECDSA
            ECDSA-->>IthacaAccount: signerAddress
            deactivate ECDSA
            IthacaAccount-->>IthacaAccount: validate signerAddress
            deactivate IthacaAccount
            IthacaAccount-->>Caller: (isValid, keyHash)
        else invalid proof
            IthacaAccount-->>Caller: (false, 0x0)
        end
        deactivate IthacaAccount
    else merkle flag not set
        IthacaAccount->>ECDSA: recoverCalldata(digestOrPrehashed, innerSignature)
        activate ECDSA
        ECDSA-->>IthacaAccount: signerAddress
        deactivate ECDSA
        IthacaAccount-->>Caller: (isValid, keyHash)
    end
    deactivate IthacaAccount
Loading

Class diagram for updated IthacaAccount signature handling

classDiagram
    class IthacaAccount {
        +unwrapAndValidateSignature(digest bytes32, signature bytes) bool isValid
        +unwrapAndValidateSignature(digest bytes32, signature bytes) bytes32 keyHash
        -_verifyMerkleSig(digest bytes32, signature bytes) bool isValid
        -_verifyMerkleSig(digest bytes32, signature bytes) bytes32 keyHash
        -getKey(keyHash bytes32) Key
    }

    class Key {
        <<struct>>
    }

    class MerkleProofLib {
        <<library>>
        +verifyCalldata(proof bytes32[], root bytes32, leaf bytes32) bool
    }

    class LibBytes {
        <<library>>
        +loadCalldata(data bytes, index uint256) bytes32
        +truncatedCalldata(data bytes, newLength uint256) bytes
    }

    class EfficientHashLib {
        <<library>>
        +sha2(data bytes32) bytes32
    }

    class ECDSA {
        <<library>>
        +recoverCalldata(digest bytes32, signature bytes) address
    }

    IthacaAccount ..> MerkleProofLib : uses
    IthacaAccount ..> LibBytes : uses
    IthacaAccount ..> EfficientHashLib : uses
    IthacaAccount ..> ECDSA : uses
    IthacaAccount o--> Key : returns
Loading

File-Level Changes

Change Details Files
Introduce Merkle tree–based signature verification path in IthacaAccount and wire it into unwrapAndValidateSignature, extending the wrapped signature format.
  • Import MerkleProofLib for Merkle proof verification against a root digest
  • Add internal _verifyMerkleSig that ABI-decodes proof/root/rootSig from calldata using assembly and validates the leaf digest via MerkleProofLib.verifyCalldata, then recursively validates the root signature via unwrapAndValidateSignature
  • Extend unwrapAndValidateSignature encoding format to abi.encode(bytes(innerSignature), bytes32(keyHash), bool(prehash), bool(merkle)), adjust length math and flags extraction, and route Merkle signatures through _verifyMerkleSig before normal key lookup
  • Bump IthacaAccount EIP712 version from 0.5.10 to 0.5.11
src/IthacaAccount.sol
Add Merkle signature fuzz test and adjust EIP712 domain usage in account tests.
  • Import murky/Merkle in tests and add testMerkleSignature that builds a random Merkle tree of digests, signs the root with a super-admin key, and checks valid, invalid, tampered-proof, and wrong-root behaviors via unwrapAndValidateSignature
  • Simplify eip712Domain destructuring in testSignatureCheckerApproval to only read verifyingContract
test/Account.t.sol
Update all test helpers and call sites to the new wrapped signature encoding that includes a merkle flag byte.
  • Append an extra uint8(0) merkle flag to P-256, secp256k1, and multisig helper encodings so they match abi.encode(innerSig, keyHash, prehash, merkle)
  • Update gas-estimation and junk-signature constructions to include the additional trailing merkle flag byte in Orchestrator and simulation tests
test/Base.t.sol
test/Orchestrator.t.sol
test/SimulateExecute.t.sol
Add a benchmark covering passkey-based ERC20 transfer via Porto Orchestrator.
  • Introduce testERC20TransferViaPortoOrchestratorWithPasskey that configures a random passkey on a delegated EOA, sets execution and spend limits for the payment token, signs an Orchestrator.Intent with the passkey, executes it via oc.execute, and asserts the ERC20 transfer
  • Pause/resume gas metering around setup and assertion to focus on execution gas
test/Benchmark.t.sol
Add IDE configuration files to the repository.
  • Check in IntelliJ IDEA project/module, code style, markdown, device streaming cache, Copilot migration, and VCS configuration files under .idea
.idea/.gitignore
.idea/account.iml
.idea/caches/deviceStreaming.xml
.idea/codeStyles/Project.xml
.idea/codeStyles/codeStyleConfig.xml
.idea/copilot.data.migration.ask2agent.xml
.idea/markdown.xml
.idea/modules.xml
.idea/vcs.xml

Possibly linked issues

  • Fix tests account #30: Yes. The PR adds _verifyMerkleSig, Merkle flag handling, and test/benchmark updates exactly matching the issue’s plan.
  • Legion rouge #41: The PR implements the Merkle proof signature path, new wrapped-signature encoding, tests, and version bump described in the issue.
  • Legion rouge #41: PR implements the Merkle-based wrapped signature verification, encoding changes, and tests exactly as described in the issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In unwrapAndValidateSignature, the prehash and merkle flags are read after truncating signature to length n, so the loadCalldata(signature, n + 1) / n + 2 accesses are out-of-bounds; compute the flags before truncating, or adjust the offsets to the new length.
  • The merkle signature wrapper format has effectively changed (an extra boolean/byte at the end), but _verifyMerkleSig assumes the inner signature is a clean abi.encode(proof, root, rootSig) blob; consider validating or documenting (in-code) that no extra trailer bytes are present when merkle is set to avoid subtle encoding mismatch bugs in other callers.
  • The .idea directory and related IDE project files have been added to the repo; these are typically local-only and should be removed from version control or added to .gitignore.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `unwrapAndValidateSignature`, the `prehash` and `merkle` flags are read *after* truncating `signature` to length `n`, so the `loadCalldata(signature, n + 1)` / `n + 2` accesses are out-of-bounds; compute the flags before truncating, or adjust the offsets to the new length.
- The merkle signature wrapper format has effectively changed (an extra boolean/byte at the end), but `_verifyMerkleSig` assumes the inner `signature` is a clean `abi.encode(proof, root, rootSig)` blob; consider validating or documenting (in-code) that no extra trailer bytes are present when `merkle` is set to avoid subtle encoding mismatch bugs in other callers.
- The `.idea` directory and related IDE project files have been added to the repo; these are typically local-only and should be removed from version control or added to `.gitignore`.

## Individual Comments

### Comment 1
<location path="src/IthacaAccount.sol" line_range="541-549" />
<code_context>
         }

+        bool merkle;
         unchecked {
-            uint256 n = signature.length - 0x21;
+            uint256 n = signature.length - 0x22;
             keyHash = LibBytes.loadCalldata(signature, n);
             signature = LibBytes.truncatedCalldata(signature, n);
             // Do the prehash if last byte is non-zero.
             if (uint256(LibBytes.loadCalldata(signature, n + 1)) & 0xff != 0) {
                 digest = EfficientHashLib.sha2(digest); // `sha256(abi.encode(digest))`.
             }
+            merkle = uint256(LibBytes.loadCalldata(signature, n + 2)) & 0xff != 0;
+        }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Reading `prehash` and `merkle` after truncating `signature` is likely out-of-bounds / incorrect.

With the new layout `[inner][keyHash(32)][prehash(1)][merkle(1)]`, `n` is the start of `keyHash`. After loading `keyHash`, you truncate `signature` to length `n`, then read `prehash` and `merkle` at `n + 1` and `n + 2` using this truncated slice. Those indices are now past `signature.length`, so these loads will be out of bounds / revert. You should read `prehash` and `merkle` from the original `signature` before truncating, or compute their positions from the original offset and keep a separate variable for the inner-sig slice.
</issue_to_address>

### Comment 2
<location path="src/IthacaAccount.sol" line_range="552-553" />
<code_context>
+            merkle = uint256(LibBytes.loadCalldata(signature, n + 2)) & 0xff != 0;
+        }
+
+        if (merkle) {
+            return _verifyMerkleSig(digest, signature);
         }

</code_context>
<issue_to_address>
**suggestion (bug_risk):** Recursive use of `unwrapAndValidateSignature` via merkle root signatures can be abused for gas griefing if nesting is unbounded.

When `merkle` is true, `_verifyMerkleSig` calls `unwrapAndValidateSignature(root, rootSig)` again, allowing arbitrarily nested merkle signatures as long as each inner `rootSig` also sets `merkle = true`. An attacker can exploit this to create very deep nesting and inflate verification gas, leading to griefing or DoS for callers that must handle arbitrary signatures.

If unbounded nesting is not a required feature, consider either enforcing a maximum nesting depth (e.g., via a depth counter) or rejecting merkle-wrapped signatures inside another merkle verification to bound worst-case gas cost.
</issue_to_address>

Fix all in Cursor


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/IthacaAccount.sol
Comment thread src/IthacaAccount.sol

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements Merkle signature verification within the IthacaAccount contract, enabling efficient validation of batched digests. The update includes a new internal verification function, modifications to the signature unwrapping logic to support a Merkle flag, and updated tests and benchmarks. Feedback identifies a potential underflow vulnerability when processing short signatures and a lack of bounds validation in the assembly-based decoding logic. Additionally, there is a documentation inconsistency regarding signature encoding, and it is advised to remove IDE-specific configuration files from the repository.

Comment thread src/IthacaAccount.sol
Comment thread .idea/.gitignore
Comment thread src/IthacaAccount.sol
Comment thread src/IthacaAccount.sol
@snyk-io

snyk-io Bot commented Apr 11, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@Dargon789 Dargon789 closed this Apr 11, 2026
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.

4 participants