Skip to content

feat: add merkle tree proof inputs to circuit and sdk#917

Merged
ctrlc03 merged 4 commits into
devfrom
feat/merkle-tree-proof-inputs
Oct 28, 2025
Merged

feat: add merkle tree proof inputs to circuit and sdk#917
ctrlc03 merged 4 commits into
devfrom
feat/merkle-tree-proof-inputs

Conversation

@ctrlc03

@ctrlc03 ctrlc03 commented Oct 28, 2025

Copy link
Copy Markdown
Collaborator

fix #916

Summary by CodeRabbit

  • New Features

    • Merkle root added as a public circuit input for vote verification.
    • Merkle proofs now include original length and padded bit indices; siblings are padded to a configurable max depth.
    • CRISP input generation accepts and forwards merkle proof data and voting balance (merkle_root, proof siblings/indices/length, balance).
  • Tests

    • Added Merkle-root verification test and expanded SDK tests using static sample leaves and max-depth constant.
    • Validated padded proof structure and updated input-generation tests.

@ctrlc03 ctrlc03 self-assigned this Oct 28, 2025
@vercel

vercel Bot commented Oct 28, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
crisp Skipped Skipped Oct 28, 2025 5:19pm
enclave-docs Skipped Skipped Oct 28, 2025 5:19pm

@coderabbitai

coderabbitai Bot commented Oct 28, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Circuit main now accepts a public merkle_root; SDK types, proof generation, vote input assembly, and tests were extended to include padded Merkle proof siblings, indices, proof length, merkle_root, and balance in generated circuit inputs. New tests cover padded proof behavior.

Changes

Cohort / File(s) Summary
Circuit
examples/CRISP/circuits/src/main.nr
Added merkle_root: pub Field to main signature; renamed computed root to merkle_root_calculated; added is_voter boolean via equality check against provided root.
Circuit Tests
examples/CRISP/circuits/src/merkle_tree.nr
Added test_get_merkle_root_ts_sdk exercising get_merkle_root with depth 4 and a 20-entry siblings array, asserting against a constant.
SDK Types
examples/CRISP/packages/crisp-sdk/src/types.ts
IMerkleProof gains length: number and indices: number[]; CRISPCircuitInputs adds merkle_root: string.
SDK Utilities
examples/CRISP/packages/crisp-sdk/src/utils.ts
generateMerkleProof(...) signature updated to accept maxDepth; pads proof.siblings and indices to maxDepth; returns length (original sibling count), padded indices, and padded siblings.
SDK Vote Logic
examples/CRISP/packages/crisp-sdk/src/vote.ts
generateCRISPInputs(...) signature extended to accept merkleData: IMerkleProof and balance: bigint; populates merkle_proof_length, merkle_proof_indices, merkle_proof_siblings, merkle_root, and balance in returned CRISP inputs.
SDK Test Constants
examples/CRISP/packages/crisp-sdk/tests/constants.ts
Added LEAVES: bigint[] and MAX_DEPTH: number (20).
SDK Tests
examples/CRISP/packages/crisp-sdk/tests/utils.test.ts, examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
Replaced async test data with static LEAVES/MAX_DEPTH; updated calls to pass maxDepth; assert proof.length and proof.indices.length; updated vote test to pass generated merkleProof and votingPowerLeaf and assert new merkle fields and balance.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Test
    participant SDK_Utils as "SDK Utils\n(generateMerkleProof)"
    participant Vote as "Vote Logic\n(generateCRISPInputs)"
    participant Circuit

    Test->>SDK_Utils: generateMerkleProof(threshold, balance, address, leaves, maxDepth)
    Note over SDK_Utils: compute siblings & index bits\npad siblings & indices to maxDepth
    SDK_Utils-->>Test: IMerkleProof { leaf, index, proof:{siblings}, length, indices }

    Test->>Vote: generateCRISPInputs(partialInputs, signature, message, merkleData, balance)
    Note over Vote: extract signature fields\nattach merkle_proof_siblings, indices, length, merkle_root, balance
    Vote-->>Test: CRISPCircuitInputs { ..., merkle_root, merkle_proof_siblings, merkle_proof_indices, merkle_proof_length, balance }

    Test->>Circuit: main(public inputs including merkle_root, other public inputs)
    Note over Circuit: compute merkle_root_calculated\ncompare with provided merkle_root (is_voter)
    Circuit-->>Test: verification result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Focus areas:
    • Padding logic and returned shape in examples/CRISP/packages/crisp-sdk/src/utils.ts
    • Updated signature and data mapping in examples/CRISP/packages/crisp-sdk/src/vote.ts
    • Circuit public input change and comparison in examples/CRISP/circuits/src/main.nr
    • Test updates validating proof length/indices and new CRISP inputs

Possibly related PRs

Suggested reviewers

  • cedoor

Poem

🐰 I padded siblings, hopped in line,

indices set, a root to find.
From SDK burrow to circuit glen,
the proof arrived — checked once, checked again.
Hooray for leaves and roots aligned!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "add merkle tree proof inputs to circuit and sdk" accurately and clearly describes the primary changes across the codebase. The changeset includes additions of Merkle root as a public input parameter in the circuit, type extensions for IMerkleProof and CRISPCircuitInputs in the SDK, updates to generateMerkleProof with maxDepth and proof padding logic, and integration of Merkle proof data into the SDK's vote generation workflow. The title is concise, specific, and directly reflects the main objective of integrating Merkle tree proof inputs into both the zk circuit and the SDK.
Linked Issues Check ✅ Passed The code changes comprehensively implement the objective stated in issue #916 to add Merkle tree proof inputs to both the circuit and SDK. The circuit changes add merkle_root as a public input parameter [#916]. The SDK type changes extend IMerkleProof with length and indices metadata, and CRISPCircuitInputs with merkle_root field. The utils changes update generateMerkleProof to accept maxDepth and return properly padded proof data. The vote.ts changes integrate merkleData and balance parameters into generateCRISPInputs, enabling full circuit input generation. Supporting test updates ensure the new functionality is properly validated. All changes align with the stated objective.
Out of Scope Changes Check ✅ Passed All changes in this pull request are directly in scope and support the stated objective of adding Merkle tree proof inputs to the circuit and SDK. The circuit modifications add merkle_root parameter support, type extensions provide necessary infrastructure for Merkle proof metadata, utility updates implement proof generation with maxDepth padding, integration into vote.ts connects the pieces together, and test updates including new test constants validate the new functionality. No unrelated refactoring, bug fixes for unrelated issues, or extraneous features are present.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/merkle-tree-proof-inputs

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)

56-67: Consider validating maxDepth against actual proof depth.

The padding logic is correct, but if maxDepth < proof.siblings.length, Array(maxDepth - proof.siblings.length) would create a negative-length array, causing a runtime error.

Consider adding validation:

  const proof = tree.generateProof(index)

+ if (maxDepth < proof.siblings.length) {
+   throw new Error(`maxDepth (${maxDepth}) must be >= actual proof depth (${proof.siblings.length})`)
+ }
+
  // Pad siblings with zeros
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31ad834 and ff45df1.

📒 Files selected for processing (8)
  • examples/CRISP/circuits/src/main.nr (2 hunks)
  • examples/CRISP/circuits/src/merkle_tree.nr (1 hunks)
  • examples/CRISP/packages/crisp-sdk/src/types.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/utils.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/vote.ts (4 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/constants.ts (1 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/utils.test.ts (3 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
examples/CRISP/packages/crisp-sdk/tests/utils.test.ts (2)
examples/CRISP/packages/crisp-sdk/src/utils.ts (3)
  • generateMerkleTree (27-29)
  • generateMerkleProof (39-80)
  • hashLeaf (18-20)
examples/CRISP/packages/crisp-sdk/tests/constants.ts (2)
  • LEAVES (14-25)
  • MAX_DEPTH (27-27)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)
  • IMerkleProof (59-65)
examples/CRISP/packages/crisp-sdk/src/vote.ts (2)
examples/CRISP/packages/crisp-sdk/src/types.ts (2)
  • CRISPCircuitInputs (148-172)
  • IMerkleProof (59-65)
examples/CRISP/packages/crisp-sdk/src/index.ts (2)
  • CRISPCircuitInputs (21-21)
  • IMerkleProof (19-19)
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
  • generateMerkleProof (39-80)
examples/CRISP/packages/crisp-sdk/tests/constants.ts (2)
  • LEAVES (14-25)
  • MAX_DEPTH (27-27)
examples/CRISP/packages/crisp-sdk/src/vote.ts (3)
  • encodeVote (60-86)
  • encryptVoteAndGenerateCRISPInputs (154-183)
  • generateCRISPInputs (194-215)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: build_enclave_cli
  • GitHub Check: build_e3_support_risc0
  • GitHub Check: build_sdk
  • GitHub Check: test_contracts
  • GitHub Check: integration_prebuild
  • GitHub Check: rust_unit
  • GitHub Check: rust_integration
  • GitHub Check: test_net
🔇 Additional comments (12)
examples/CRISP/circuits/src/merkle_tree.nr (1)

46-79: LGTM! Well-structured test for deeper Merkle tree.

The test validates get_merkle_root with a depth of 4 and hardcoded siblings that align with SDK-generated data. This provides good coverage for the extended Merkle proof functionality.

examples/CRISP/circuits/src/main.nr (2)

36-36: LGTM! Public input added for on-chain verification.

Adding merkle_root as a public input enables on-chain verification of voter eligibility through Merkle proof validation.


48-48: Good refactor to avoid shadowing.

Renaming the local computed result to merkle_root_calculated prevents shadowing the parameter and improves code clarity.

examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (1)

147-172: LGTM! Test properly validates Merkle proof integration.

The test correctly generates a Merkle proof with the new maxDepth parameter and validates all expected fields in the CRISP inputs, including merkle_proof_indices, merkle_proof_siblings, merkle_proof_length, merkle_root, and balance.

examples/CRISP/packages/crisp-sdk/tests/constants.ts (1)

14-27: LGTM! Well-defined test constants.

The LEAVES array and MAX_DEPTH constant provide consistent test data for Merkle tree operations. A max depth of 20 supports up to ~1M leaves, which is reasonable for testing.

examples/CRISP/packages/crisp-sdk/src/types.ts (2)

63-64: LGTM! Well-designed type extension.

Adding length (original proof length) and indices (path through tree) to IMerkleProof provides essential metadata for circuit verification with padded proofs.


168-168: LGTM! Aligns with circuit public input.

Adding merkle_root to CRISPCircuitInputs matches the new public input in the circuit (examples/CRISP/circuits/src/main.nr, line 36).

examples/CRISP/packages/crisp-sdk/tests/utils.test.ts (2)

22-22: LGTM! Cleaner test with shared constants.

Using LEAVES from constants improves test maintainability and consistency across test files.


31-35: LGTM! Tests validate new proof structure.

The test correctly validates:

  • proof.length equals 4 (actual tree depth for 10 leaves)
  • proof.indices.length equals MAX_DEPTH (20), confirming padding is applied
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)

69-79: LGTM! Well-structured proof with padding metadata.

The return structure correctly includes:

  • Padded siblings array
  • length (original proof depth before padding)
  • indices (bit path through tree, padded to maxDepth)

This design enables circuits to verify proofs of varying depths with a fixed-size input.

examples/CRISP/packages/crisp-sdk/src/vote.ts (2)

178-178: LGTM! Correct initialization for partial inputs.

Initializing merkle_root to '0' in the partial inputs is appropriate, as it will be populated later by generateCRISPInputs.


194-214: LGTM! Function correctly integrates Merkle proof data.

The updated signature adds merkleData and balance parameters, and the implementation correctly populates all Merkle-related fields in the CRISP inputs:

  • merkle_proof_length, merkle_proof_indices, merkle_proof_siblings from merkleData
  • merkle_root from merkleData.proof.root
  • balance as a string

Note: This is a breaking change to the public API, as two new required parameters were added.

Comment thread examples/CRISP/circuits/src/main.nr
Comment thread examples/CRISP/packages/crisp-sdk/tests/vote.test.ts Outdated
@vercel vercel Bot temporarily deployed to Preview – enclave-docs October 28, 2025 14:21 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp October 28, 2025 14:21 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (1)

166-169: Consider strengthening the merkle proof assertions.

The current assertions only check that the fields are defined. Given the known structure from IMerkleProof and the implementation, you could add more specific checks:

  • Verify merkle_proof_indices is an array of length MAX_DEPTH (20)
  • Verify merkle_proof_siblings is an array of length MAX_DEPTH (20)
  • Verify merkle_proof_length is a numeric string within expected range
  • Verify merkle_root is a non-empty string

Example strengthened assertions:

-      expect(crispInputs.merkle_proof_indices).toBeDefined()
-      expect(crispInputs.merkle_proof_siblings).toBeDefined()
-      expect(crispInputs.merkle_proof_length).toBeDefined() 
-      expect(crispInputs.merkle_root).toBeDefined()
+      expect(crispInputs.merkle_proof_indices).toBeInstanceOf(Array)
+      expect(crispInputs.merkle_proof_indices).toHaveLength(MAX_DEPTH)
+      expect(crispInputs.merkle_proof_siblings).toBeInstanceOf(Array)
+      expect(crispInputs.merkle_proof_siblings).toHaveLength(MAX_DEPTH)
+      expect(crispInputs.merkle_proof_length).toMatch(/^\d+$/)
+      expect(crispInputs.merkle_root).toBeTruthy()
       expect(crispInputs.balance).toBe(votingPowerLeaf.toString())
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff45df1 and 6a323eb.

📒 Files selected for processing (1)
  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
  • generateMerkleProof (39-80)
examples/CRISP/packages/crisp-sdk/tests/constants.ts (5)
  • LEAVES (14-25)
  • MAX_DEPTH (27-27)
  • VOTE (12-12)
  • SIGNATURE (10-11)
  • MESSAGE (9-9)
examples/CRISP/packages/crisp-sdk/src/vote.ts (2)
  • encryptVoteAndGenerateCRISPInputs (154-183)
  • generateCRISPInputs (194-215)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: build_e3_support_dev
  • GitHub Check: test_contracts
  • GitHub Check: integration_prebuild
  • GitHub Check: rust_integration
  • GitHub Check: build_enclave_cli
  • GitHub Check: build_sdk
  • GitHub Check: test_net
  • GitHub Check: rust_unit
🔇 Additional comments (3)
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3)

19-21: LGTM!

The new imports are necessary for the merkle proof test setup and align with the PR objectives.


151-151: LGTM!

The function call correctly passes the new merkle proof parameters in the expected order.


146-147: Test data is valid—no issues found. The same address and balance are intentionally tested in utils.test.ts and expected to succeed.

The review comment raises concern that the test data might fail at runtime. However, the codebase reveals that utils.test.ts explicitly uses the identical address (0x1234567890123456789012345678901234567890) and balance (1000), and its test expects generateMerkleProof to succeed with this data. The utils.test.ts test validates the leaf computation and proves the data exists in the LEAVES array. Since the same address/balance combination is reused across two test files and tested to work in utils.test.ts, the vote.test.ts usage is valid and will not fail at runtime.

Likely an incorrect or invalid review comment.

@ctrlc03 ctrlc03 linked an issue Oct 28, 2025 that may be closed by this pull request
@ctrlc03 ctrlc03 requested a review from cedoor October 28, 2025 16:28
@ctrlc03 ctrlc03 force-pushed the feat/merkle-tree-proof-inputs branch from 6a323eb to 02c3fd0 Compare October 28, 2025 16:28
@vercel vercel Bot temporarily deployed to Preview – enclave-docs October 28, 2025 16:28 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp October 28, 2025 16:28 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
examples/CRISP/circuits/src/main.nr (1)

56-61: Enforce membership: assert computed root equals public merkle_root; remove dead is_voter.

Currently no constraint ties merkle_root_calculated to the public merkle_root; the proof doesn’t enforce set membership. Replace the block with an assertion and drop is_voter.

-    // compare the merkle root with the param to confirm whether it's the voter
-    // or a masker
-    let mut is_voter = false;
-    if (merkle_root_calculated == merkle_root) {
-        is_voter = true;
-    }
+    // Enforce Merkle membership against the public root
+    assert(merkle_root_calculated == merkle_root);

(If you truly need a voter/masker flag later, derive it as a constrained boolean from this equality and gate behavior with it.) Based on past review comments.

🧹 Nitpick comments (3)
examples/CRISP/circuits/src/main.nr (1)

48-55: Guard Merkle proof shape: bound length and validate padding to avoid unconstrained inputs.

Add a check that merkle_proof_length ≤ 20 and (recommended) require padded indices/siblings to be zero beyond length. This prevents OOB usage in get_merkle_root and avoids unconstrained witness slots.

Example (adjust to your DSL):

     let merkle_root_calculated = get_merkle_root(
         address,
         balance,
         merkle_proof_length,
         merkle_proof_indices,
         merkle_proof_siblings,
     );
+    assert(merkle_proof_length <= 20);
+    // Optional: ensure padding is canonical
+    // for (let i = merkle_proof_length; i < 20; i++) {
+    //   assert(merkle_proof_indices[i] == 0u1);
+    //   assert(merkle_proof_siblings[i] == 0);
+    // }

If SDK guarantees zero-padding to max depth, add a single assert here to lock that invariant in the circuit.

examples/CRISP/packages/crisp-sdk/src/vote.ts (2)

178-182: Placeholders are fine but rely on overwrite.

These defaults are OK if generateCRISPInputs is always called next to set real values. If encrypt-only flows can skip that step, consider explicit guards to prevent a '0' root reaching the circuit.


209-214: Validate Merkle proof lengths before mapping; ensure max-depth padding.

Add guards so indices/siblings match the circuit’s 20-depth expectation and contain only 0/1 for indices.

   return {
     ...partialInputs,
     hashed_message: Array.from(hashed_message).map((b) => b.toString()),
     public_key_x: Array.from(pub_key_x).map((b) => b.toString()),
     public_key_y: Array.from(pub_key_y).map((b) => b.toString()),
     signature: Array.from(extractedSignature).map((b) => b.toString()),
+    // Runtime guards (SDK should already pad to max depth)
+    // If you have MAX_DEPTH in ./constants, compare to it instead of 20
+    ...( (() => {
+      if (merkleData.indices.length !== 20 || merkleData.proof.siblings.length !== 20) {
+        throw new Error('Invalid Merkle proof: arrays must be padded to depth 20')
+      }
+      if (merkleData.indices.some((i) => i !== 0 && i !== 1)) {
+        throw new Error('Invalid Merkle proof: indices must be 0/1')
+      }
+      return {}
+    })() ),
     merkle_proof_length: merkleData.length.toString(),
     merkle_proof_indices: merkleData.indices.map((i) => i.toString()),
     merkle_proof_siblings: merkleData.proof.siblings.map((s) => s.toString()),
     merkle_root: merkleData.proof.root.toString(),
     balance: balance.toString(),
   }

Optionally import and use MAX_DEPTH from ./constants for maintainability.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a323eb and 02c3fd0.

📒 Files selected for processing (8)
  • examples/CRISP/circuits/src/main.nr (2 hunks)
  • examples/CRISP/circuits/src/merkle_tree.nr (1 hunks)
  • examples/CRISP/packages/crisp-sdk/src/types.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/utils.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/vote.ts (4 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/constants.ts (1 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/utils.test.ts (3 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • examples/CRISP/packages/crisp-sdk/src/types.ts
  • examples/CRISP/packages/crisp-sdk/tests/constants.ts
  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
  • examples/CRISP/packages/crisp-sdk/src/utils.ts
  • examples/CRISP/circuits/src/merkle_tree.nr
  • examples/CRISP/packages/crisp-sdk/tests/utils.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
examples/CRISP/packages/crisp-sdk/src/vote.ts (1)
examples/CRISP/packages/crisp-sdk/src/types.ts (2)
  • CRISPCircuitInputs (148-172)
  • IMerkleProof (59-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: build_sdk
  • GitHub Check: build_e3_support_dev
  • GitHub Check: build_enclave_cli
  • GitHub Check: integration_prebuild
  • GitHub Check: test_contracts
  • GitHub Check: rust_integration
  • GitHub Check: test_net
  • GitHub Check: rust_unit
🔇 Additional comments (3)
examples/CRISP/circuits/src/main.nr (1)

36-36: Public merkle_root addition looks good; ensure it’s now constrained.

The new public input is fine. After adding the equality assertion (see below), this ties the public claim to the witness.

examples/CRISP/packages/crisp-sdk/src/vote.ts (2)

8-8: Import extension LGTM.

Pulling IMerkleProof into this module aligns with the new inputs.


190-192: Doc: update balance type to bigint if you adopt the change below.

Keep the JSDoc in sync when switching balance to bigint.

Comment thread examples/CRISP/packages/crisp-sdk/src/vote.ts
@ctrlc03 ctrlc03 force-pushed the feat/merkle-tree-proof-inputs branch from 02c3fd0 to 1f4df03 Compare October 28, 2025 17:01
@vercel vercel Bot temporarily deployed to Preview – crisp October 28, 2025 17:01 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs October 28, 2025 17:01 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
examples/CRISP/packages/crisp-sdk/src/vote.ts (1)

190-200: Switch to bigint balance and accept IMerkleProof — aligns with previous feedback.
API is now safe for large token amounts; signature change is appropriate.

🧹 Nitpick comments (4)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)

18-20: Normalize hash inputs to bigint for unambiguous hashing.

Poseidon inputs should be field elements; avoid passing strings. Backward-compatible overload recommended.

-export const hashLeaf = (address: string, balance: string): bigint => {
-  return poseidon2([address, balance])
-}
+export const hashLeaf = (address: string, balance: string | bigint): bigint => {
+  const addr = address.startsWith('0x') ? BigInt(address) : BigInt(`0x${address}`)
+  const bal = typeof balance === 'bigint' ? balance : BigInt(balance)
+  return poseidon2([addr, bal])
+}

And optionally pass the bigint directly here:

-const leaf = hashLeaf(address, balance.toString())
+const leaf = hashLeaf(address, balance)
examples/CRISP/packages/crisp-sdk/tests/constants.ts (1)

14-27: Assert MAX_DEPTH is sufficient for LEAVES.

Prevent silent misconfig by asserting MAX_DEPTH ≥ ceil(log2(LEAVES.length)) at import-time in tests.

 export const MAX_DEPTH = 20
+export const REQUIRED_DEPTH = Math.ceil(Math.log2(LEAVES.length))
+if (MAX_DEPTH < REQUIRED_DEPTH) {
+  throw new Error(`MAX_DEPTH (${MAX_DEPTH}) must be ≥ REQUIRED_DEPTH (${REQUIRED_DEPTH})`)
+}
examples/CRISP/packages/crisp-sdk/src/vote.ts (1)

209-214: Minor: add lightweight invariants for Merkle data (optional).

Before returning, consider asserting basic shape to catch wiring errors early.

if (partialInputs.merkle_proof_indices && merkleData) {
  if (merkleData.proof.siblings.length > merkleData.indices.length) {
    throw new Error('indices shorter than siblings')
  }
}
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (1)

166-171: Strengthen Merkle assertions (optional).
Verify depth, padding, and equality with source proof.

   expect(crispInputs.merkle_proof_indices).toBeDefined()
   expect(crispInputs.merkle_proof_siblings).toBeDefined()
   expect(crispInputs.merkle_proof_length).toBeDefined()
   expect(crispInputs.merkle_root).toBeDefined()
   expect(crispInputs.balance).toBe(votingPowerLeaf.toString())
+  expect(crispInputs.merkle_proof_indices.length).toBe(MAX_DEPTH)
+  expect(crispInputs.merkle_proof_siblings.length).toBe(MAX_DEPTH)
+  expect(crispInputs.merkle_proof_length).toBe(merkleProof.length.toString())
+  expect(crispInputs.merkle_root).toBe(merkleProof.proof.root.toString())
+  expect(crispInputs.merkle_proof_siblings.slice(merkleProof.length).every((s) => s === '0')).toBe(true)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02c3fd0 and 1f4df03.

📒 Files selected for processing (8)
  • examples/CRISP/circuits/src/main.nr (2 hunks)
  • examples/CRISP/circuits/src/merkle_tree.nr (1 hunks)
  • examples/CRISP/packages/crisp-sdk/src/types.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/utils.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/vote.ts (4 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/constants.ts (1 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/utils.test.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • examples/CRISP/packages/crisp-sdk/src/types.ts
  • examples/CRISP/packages/crisp-sdk/tests/utils.test.ts
  • examples/CRISP/circuits/src/merkle_tree.nr
  • examples/CRISP/circuits/src/main.nr
🧰 Additional context used
🧬 Code graph analysis (3)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)
  • IMerkleProof (59-65)
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
  • generateMerkleProof (39-80)
examples/CRISP/packages/crisp-sdk/tests/constants.ts (2)
  • LEAVES (14-25)
  • MAX_DEPTH (27-27)
examples/CRISP/packages/crisp-sdk/src/vote.ts (2)
  • encryptVoteAndGenerateCRISPInputs (154-183)
  • generateCRISPInputs (194-215)
examples/CRISP/packages/crisp-sdk/src/vote.ts (1)
examples/CRISP/packages/crisp-sdk/src/types.ts (2)
  • CRISPCircuitInputs (148-172)
  • IMerkleProof (59-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: build_sdk
  • GitHub Check: build_enclave_cli
  • GitHub Check: build_e3_support_dev
  • GitHub Check: rust_integration
  • GitHub Check: test_contracts
  • GitHub Check: test_net
  • GitHub Check: rust_unit
  • GitHub Check: integration_prebuild
🔇 Additional comments (2)
examples/CRISP/packages/crisp-sdk/src/vote.ts (1)

178-182: Good default initialization for new Merkle fields.
The placeholders are sane and get overwritten in generateCRISPInputs.

examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (1)

19-19: Import is valid; generateMerkleProof is properly exported from the SDK barrel.

The import resolves through the barrel file (src/index.ts), which re-exports all exports from src/utils.ts via export * from './utils', and generateMerkleProof is exported in src/utils.ts. The import will not fail.

Comment thread examples/CRISP/packages/crisp-sdk/src/utils.ts Outdated
Comment thread examples/CRISP/packages/crisp-sdk/src/utils.ts
Comment thread examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
Comment thread examples/CRISP/packages/crisp-sdk/tests/utils.test.ts Outdated
@ctrlc03 ctrlc03 requested a review from cedoor October 28, 2025 17:19
@vercel vercel Bot temporarily deployed to Preview – crisp October 28, 2025 17:19 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs October 28, 2025 17:19 Inactive
@ctrlc03 ctrlc03 enabled auto-merge (squash) October 28, 2025 17:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)

62-68: Guard against maxDepth smaller than actual proof depth (runtime RangeError).

If maxDepth < proof.siblings.length, lines 63 and 67 will attempt to create arrays with negative lengths, throwing a RangeError at runtime.

Apply this diff to add a guard:

  const proof = tree.generateProof(index)

+  if (maxDepth < proof.siblings.length) {
+    throw new RangeError(`maxDepth (${maxDepth}) must be >= proof depth (${proof.siblings.length})`)
+  }
+
  // Pad siblings with zeros
  const paddedSiblings = [...proof.siblings, ...Array(maxDepth - proof.siblings.length).fill(0n)]
🧹 Nitpick comments (1)
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (1)

166-170: Consider more specific assertions for Merkle proof fields.

The current assertions use toBeDefined() which only verifies field existence. Consider adding assertions for array lengths, value types, or expected content to strengthen test coverage.

For example:

expect(crispInputs.merkle_proof_indices).toBeInstanceOf(Array)
expect(crispInputs.merkle_proof_indices.length).toBe(MAX_DEPTH)
expect(crispInputs.merkle_proof_siblings).toBeInstanceOf(Array)
expect(crispInputs.merkle_proof_siblings.length).toBe(MAX_DEPTH)
expect(crispInputs.merkle_proof_length).toBe(merkleProof.length.toString())
expect(crispInputs.merkle_root).toBe(merkleProof.proof.root.toString())
expect(crispInputs.balance).toBe(votingPowerLeaf.toString())
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f4df03 and 67fea97.

📒 Files selected for processing (3)
  • examples/CRISP/packages/crisp-sdk/src/utils.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/utils.test.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/CRISP/packages/crisp-sdk/tests/utils.test.ts
🧰 Additional context used
🧬 Code graph analysis (2)
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
  • generateMerkleProof (39-80)
examples/CRISP/packages/crisp-sdk/tests/constants.ts (2)
  • LEAVES (14-25)
  • MAX_DEPTH (27-27)
examples/CRISP/packages/crisp-sdk/src/vote.ts (2)
  • encryptVoteAndGenerateCRISPInputs (154-183)
  • generateCRISPInputs (194-215)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)
  • IMerkleProof (59-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test_net
  • GitHub Check: build_enclave_cli
  • GitHub Check: rust_unit
  • GitHub Check: build_e3_support_dev
  • GitHub Check: rust_integration
  • GitHub Check: build_sdk
  • GitHub Check: integration_prebuild
  • GitHub Check: test_contracts
🔇 Additional comments (4)
examples/CRISP/packages/crisp-sdk/src/utils.ts (2)

37-45: LGTM! Function signature correctly uses bigint types.

The updated signature properly uses bigint for threshold and balance parameters, addressing previous type compatibility concerns.


69-80: LGTM! Return structure correctly implements IMerkleProof interface.

The return value properly includes padded siblings, original proof length, and padded indices as defined in the interface.

examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (2)

19-21: LGTM! Imports correctly updated for Merkle proof testing.

The imports appropriately include generateMerkleProof and test constants needed for Merkle proof generation and validation.


146-147: LGTM! Test setup correctly uses bigint types.

The Merkle proof generation correctly passes 0n (bigint) for the threshold parameter, addressing the previous type mismatch issue.

@cedoor cedoor left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

utACK 👍🏽

@ctrlc03 ctrlc03 merged commit ebd06c3 into dev Oct 28, 2025
23 checks passed
@ctrlc03 ctrlc03 deleted the feat/merkle-tree-proof-inputs branch October 28, 2025 17:39
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.

Add merkle tree proof inputs to the circuit and SDK

2 participants