Skip to content

refactor: recursion proof crisp [skip-line-limit]#1208

Merged
cedoor merged 68 commits into
mainfrom
refactor/recursion-proof-crisp
Feb 24, 2026
Merged

refactor: recursion proof crisp [skip-line-limit]#1208
cedoor merged 68 commits into
mainfrom
refactor/recursion-proof-crisp

Conversation

@cedoor

@cedoor cedoor commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

The CRISP circuit with production params had ~6M gates, which made it impractical to generate proofs in browsers or Node.js due to memory and time limits.

Solution: Sub-circuit Splitting

The monolithic circuit is now split into several sub-circuits:

  1. user_data_encryption_ct0 (GRECO ct0) – CT0 component equation (~1.9M gates)
  2. user_data_encryption_ct1 (GRECO ct1) – CT1 component equation (~1.6M gates)
  3. user_data_encryption wrapper – Verifies ct0 and ct1 proofs and constrains both (~1.5M gates)
  4. CRISP – CRISP logic only (without user_data_encryption) (~1.85M gates)
  5. fold – Aggregates CRISP and the user_data_encryption wrapper (~1.5M gates)

The final on-chain proof is produced by the fold circuit.

Toolchain Updates

  • Noir: v1.0.0-beta.16
  • Barretenberg: 3.0.0-nightly.20260102

New BB supports noir-recursive-no-zk for non-zk proofs (reduced recursion overhead)

CRISP SDK Updates

  • Input generation worker – Circuit input generation (BFV encryption) runs in a Web Worker when available, so it does not block the main thread.
  • BB workers – Barretenberg uses BackendType.WasmWorker for proof generation.
  • Error handling – Correct cleanup of Barretenberg WASM (e.g. api.destroy()) on both success and error to avoid leaks.

Results

Proof generation for the new circuits takes around 208 seconds with secure parameters.

Diagram

CRISP recursion

Closes #1201

@vercel

vercel Bot commented Jan 26, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
crisp Ready Ready Preview, Comment Feb 24, 2026 5:04pm
enclave-docs Ready Ready Preview, Comment Feb 24, 2026 5:04pm

Request Review

@coderabbitai

coderabbitai Bot commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Splits the CRISP circuit into CRISP and GRECO binaries, introducing GRECO-based BFV verification and ciphertext commitments. Refactors the circuits library, updates SDK APIs/types (BFV keypair, decrypt), swaps verifier calls to non-ZK honk, bumps tooling/dependencies, and adjusts build scripts and tests.

Changes

Cohort / File(s) Summary
CRISP & GRECO binaries
examples/CRISP/circuits/bin/crisp/Nargo.toml, examples/CRISP/circuits/bin/crisp/src/main.nr, examples/CRISP/circuits/bin/greco/Nargo.toml, examples/CRISP/circuits/bin/greco/src/main.nr
Adds GRECO binary; reworks CRISP main to accept greco_verification_key/greco_proof and commitment inputs; moves BFV verification into GRECO and returns k1/ct commitments.
Circuit library & commitments
examples/CRISP/circuits/lib/Nargo.toml, examples/CRISP/circuits/lib/src/lib.nr, .../commitments.nr, .../ciphertext_addition.nr, .../ecdsa.nr, .../utils.nr
Converts circuit lib to crisp_lib, exposes modules, adds DS_CRISP_CT/DS_CRISP_K1 and compute_ct_commitment/compute_k1_commitment. Changes ecdsa.validate_signature and utils.* to return bool; updates imports to enclave_lib.
SDK types, utils & vote flow
examples/CRISP/packages/crisp-sdk/src/types.ts, .../utils.ts, .../vote.ts, .../index.ts
Switches Vote to number[], renames CircuitInputs→CRISPCircuitInputs, adds GrecoCircuitInputs and commitment fields, introduces decryptVote/generateBFVKeys, splits executeCircuit into GRECO/CRISP paths, updates encoding/decoding helpers.
zk-inputs & wasm APIs
examples/CRISP/crates/zk-inputs/src/lib.rs, examples/CRISP/crates/zk-inputs-wasm/src/lib.rs, examples/CRISP/crates/zk-inputs/Cargo.toml
Replaces generate_public_key with generate_keys returning {secretKey, publicKey}; adds decrypt_vote; adds bincode dependency and corresponding tests/usage updates.
Verification & tooling updates
circuits/**/src/*.nr (multiple), many Nargo.toml files, crates/zk-prover/*, packages/enclave-sdk/*, .github/workflows/ci.yml
Replaces verify_ultrahonk_proof with verify_honk_proof_non_zk across circuits; updates many Nargo.toml to use compiler_version and bumps bb_proof_verification tag and Noir/barretenberg-related versions; adds --oracle_hash keccak in prover calls.
Build scripts & package changes
examples/CRISP/scripts/compile_circuits.sh, examples/CRISP/packages/crisp-sdk/package.json, examples/CRISP/package.json
Adjusts compile flow to build crisp and greco, adds compile:crisp/greco/circuits scripts, updates package scripts and dependency versions.
Tests & fixtures
crates/zk-prover/tests/fixtures/*, crates/zk-prover/tests/local_e2e_tests.rs, examples/CRISP/packages/crisp-sdk/tests/vote.test.ts, examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts
Removes outdated fixtures, updates tests to use BFV keypairs, numeric votes, decryptVote checks, and adjusts assertions for new boolean-return utilities.
Backend initialization & small Rust tweaks
packages/enclave-sdk/src/greco.ts, examples/CRISP/crates/zk-inputs/src/ciphertext_addition.rs
Initializes Barretenberg API and passes it to UltraHonkBackend; removes unnecessary mutability in some Rust bindings.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant GRECO
    participant CRISP
    participant Prover
    participant Verifier

    Client->>GRECO: submit BFV encryption polynomials + pk_commitment
    GRECO->>GRECO: execute UserDataEncryption, compute k1_commitment & ct_commitment
    GRECO-->>Client: return k1_commitment, ct_commitment, greco_proof

    Client->>CRISP: submit vote inputs, merkle proof, greco_proof, commitments
    CRISP->>CRISP: verify merkle proof, verify_honk_proof_non_zk(greco_proof), check commitments & signature
    CRISP-->>Client: ct_commitment (or validation result)

    Client->>Prover: request combined proof (CRISP + GRECO)
    Prover->>Prover: generate recursive/combined proof
    Prover-->>Client: proof

    Client->>Verifier: submit proof
    Verifier->>Verifier: verify combined proof
    Verifier-->>Client: valid / invalid
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

crisp

Suggested reviewers

  • ctrlc03

Poem

🐰 I hopped through circuits, split the load,
CRISP kept the vote, GRECO bore the code.
Commitments tidy, proofs now play nice,
A recursive dance — carrot and device. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: recursion proof crisp' is partially related to the changeset; it highlights recursion and CRISP aspects but does not explicitly convey the key architectural change of splitting GRECO and implementing recursive verification.
Linked Issues check ✅ Passed The PR successfully implements the core requirements of #1201: GRECO is split into a separate circuit (greco/bin/), CRISP circuit refactored to use GRECO verification, and recursive verification architecture established with fold circuit updates.
Out of Scope Changes check ✅ Passed All changes are in-scope with #1201: GRECO/CRISP circuit split, recursive verification integration, Barretenberg/Nargo upgrades, version field updates, and supporting infrastructure modifications are all aligned with the stated objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/recursion-proof-crisp

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.

@cedoor cedoor requested a review from ctrlc03 January 26, 2026 13:14
Comment thread examples/CRISP/circuits/bin/crisp/src/main.nr Outdated
@vercel vercel Bot temporarily deployed to Preview – enclave-docs January 26, 2026 15:05 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp January 26, 2026 15:05 Inactive
ctrlc03
ctrlc03 previously approved these changes Jan 26, 2026

@ctrlc03 ctrlc03 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@cedoor cedoor force-pushed the refactor/recursion-proof-crisp branch from 53c60e4 to 0ea4819 Compare February 16, 2026 15:54
@vercel vercel Bot temporarily deployed to Preview – crisp February 16, 2026 15:54 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs February 16, 2026 15:54 Inactive
@cedoor cedoor force-pushed the refactor/recursion-proof-crisp branch from 0ea4819 to 16e995a Compare February 16, 2026 15:54
@vercel vercel Bot temporarily deployed to Preview – enclave-docs February 16, 2026 15:54 Inactive
@cedoor cedoor marked this pull request as ready for review February 16, 2026 15:54

@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: 8

Caution

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

⚠️ Outside diff range comments (4)
examples/CRISP/crates/zk-inputs/src/ciphertext_addition.rs (1)

99-116: ⚠️ Potential issue | 🟡 Minor

Stale doc: parameter n is documented but doesn't exist in the signature.

Line 110–111 documents a parameter n (polynomial degree), but the function signature on lines 117–122 has no such parameter. This was likely left over from a previous version.

📝 Suggested fix
     /// # Arguments
     ///
     /// * `sum` - CRT polynomial of the sum ciphertext
     /// * `a` - CRT polynomial of the first ciphertext
     /// * `b` - CRT polynomial of the second ciphertext
-    /// * `n` - polynomial degree (number of coefficients per limb)
     /// * `moduli` - moduli for each CRT limb
examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts (1)

52-52: ⚠️ Potential issue | 🔴 Critical

Type mismatch: vote uses bigint[] but Vote is now number[].

Vote was changed to number[] in types.ts (line 70), but this test still uses bigint literals ([10n, 0n]). The same issue appears on line 111. This will cause TypeScript compilation errors and, at runtime, could produce incorrect circuit inputs since the encoding functions now expect number values.

Proposed fix
-      const vote = [10n, 0n]
+      const vote = [10, 0]

And on line 111:

-      const vote = [10n, 0n]
+      const vote = [10, 0]
examples/CRISP/packages/crisp-sdk/src/types.ts (1)

82-110: ⚠️ Potential issue | 🟡 Minor

Remove unused type definitions GrecoParams, GrecoCryptographicParams, and GrecoBoundParams.

These types are declared but not referenced anywhere in the codebase. They were replaced by individual fields (greco_verification_key, greco_key_hash, greco_proof, and commitment fields) in CRISPCircuitInputs and are now dead code that should be removed.

examples/CRISP/scripts/compile_circuits.sh (1)

46-59: ⚠️ Potential issue | 🟡 Minor

Fragile license header replacement — assumes exactly 2-line Apache preamble from bb.

tail -n +3 silently drops the first two lines. If bb write_solidity_verifier changes its output format (e.g., adds a pragma or extra comment line), this will corrupt the file. Consider a more robust approach, such as stripping lines matching the Apache pattern.

🛡️ More robust alternative
-# Remove the first 2 lines (Apache license and copyright) and prepend our license header
-TEMP_FILE=$(mktemp)
-{
-    echo "$LICENSE_HEADER"
-    tail -n +3 packages/crisp-contracts/contracts/CRISPVerifier.sol
-} > "$TEMP_FILE"
-mv "$TEMP_FILE" packages/crisp-contracts/contracts/CRISPVerifier.sol
+# Remove existing SPDX/Apache header lines and prepend our license header
+TEMP_FILE=$(mktemp)
+{
+    echo "$LICENSE_HEADER"
+    # Skip lines matching Apache-2.0 license or copyright before the first code line
+    sed '1,/^\/\/ SPDX/d' packages/crisp-contracts/contracts/CRISPVerifier.sol || \
+        tail -n +3 packages/crisp-contracts/contracts/CRISPVerifier.sol
+} > "$TEMP_FILE"
+mv "$TEMP_FILE" packages/crisp-contracts/contracts/CRISPVerifier.sol
🤖 Fix all issues with AI agents
In `@examples/CRISP/circuits/bin/crisp/Nargo.toml`:
- Line 9: The dependency entry bb_proof_verification = { git =
"https://github.com/AztecProtocol/aztec-packages/", tag =
"v3.0.0-nightly.20251104", directory = "barretenberg/noir/bb_proof_verification"
lacks an explanatory comment; add a short inline comment next to the
bb_proof_verification TOML line stating why this exact nightly tag is pinned
(e.g., required compatibility with Noir 1.0.0-beta.15, specific support for
non‑ZK recursive proofs, or coordination with a fixed commit that won’t be
rebased) so future maintainers understand the intentional choice. Ensure the
comment references the tag and the reason and stays brief.

In `@examples/CRISP/circuits/bin/crisp/src/main.nr`:
- Around line 55-57: The greco_key_hash input in main.nr is currently a private
Field allowing an attacker to substitute a malicious GRECO verification key;
change the declaration greco_key_hash: Field to greco_key_hash: pub Field (or
replace it with a hardcoded constant) so the value becomes a public input that
on-chain verifiers can check against the expected GRECO circuit hash; update
every occurrence of greco_key_hash (including the similar declarations around
lines 137-142) to use pub Field to ensure on-chain binding to the correct GRECO
verification key.

In `@examples/CRISP/circuits/bin/greco/Nargo.toml`:
- Around line 1-6: The package manifest's license field ("license = \"MIT\"" in
Nargo.toml) does not match the SPDX header in the source file
(SPDX-License-Identifier: LGPL-3.0-only in src/main.nr) — update one to make
them consistent; either change the Nargo.toml license value to "LGPL-3.0-only"
to match the SPDX header in src/main.nr (and the other example binaries/libs in
examples/CRISP/circuits), or update the SPDX header in each source file to "MIT"
so all SPDX headers and the Nargo.toml license field match across crisp_greco
and the sibling crisp binary and lib examples.

In `@examples/CRISP/circuits/lib/src/utils.nr`:
- Around line 27-31: The code computes segment_size = D / num_options even when
num_options may be 0; change both functions (the one setting let mut valid =
(num_options > 1) & (...) and check_coefficient_zero) to guard against
division-by-zero by early-returning false (or setting valid = false and
returning) before any division when num_options == 0 (or num_options <= 1), i.e.
check num_options first, then compute segment_size and the rest; reference the
symbols num_options, D, segment_size, MAX_OPTIONS, valid, and
check_coefficient_zero when making the change.

In `@examples/CRISP/crates/zk-inputs-wasm/src/lib.rs`:
- Around line 232-243: The current direct cast in decrypt_vote (in decrypt_vote
-> generator.decrypt_vote handling) uses "v as i64" which can overflow silently;
change the Vec<u64> -> Vec<i64> conversion to use TryFrom<i64> (or
i64::try_from) for each element, returning an Err(JsValue::from_str(...)) if any
conversion fails so failures are explicit; apply the same pattern in
encrypt_vote (and any other places doing "i64 as u64" or "u64 as i64") by using
TryFrom/try_into and mapping conversion errors to JsValue to fail gracefully
instead of wrapping.

In `@examples/CRISP/packages/crisp-sdk/src/utils.ts`:
- Around line 199-201: The helper bigInt64ArrayToNumberArray silently loses
precision by mapping BigInt values to Number; update bigInt64ArrayToNumberArray
to validate each element of the BigInt64Array against Number.MAX_SAFE_INTEGER
and Number.MIN_SAFE_INTEGER (or the equivalent negative bound) before
converting, and throw a clear RangeError (or return an explicit error) when any
value would lose precision; replace the Array.from(...).map(Number)
implementation with an explicit loop that checks each BigInt in the input and
only converts when safe to do so to prevent silent truncation.
- Around line 177-197: The function decodeBytesToNumbers currently reads u64
values with getBigUint64 but then converts them to Number, which silently loses
precision for values > Number.MAX_SAFE_INTEGER; update the implementation and
docs: either change the return type to bigint[] and keep returning the raw
BigInt array from decodeBytesToNumbers (update JSDoc to reflect bigint[]), or if
you must keep number[] add a runtime guard in decodeBytesToNumbers that checks
each BigInt against BigInt(Number.MAX_SAFE_INTEGER) and throws an Error when
exceeded (and update the JSDoc to state this runtime check), and ensure any
callers are adjusted accordingly (search references to decodeBytesToNumbers to
handle bigint[] or to catch the new error).

In `@examples/CRISP/packages/crisp-sdk/src/vote.ts`:
- Line 59: The computation of maxValue uses a 32-bit bit-shift causing overflow
for large MAX_VOTE_BITS; update the expression in vote.ts where maxValue is
computed (currently using "1 << maxBits") to use an exponentiation-based
calculation (2 ** maxBits - 1) so maxValue correctly represents a maxBits-wide
unsigned value and the subsequent bounds check (which uses maxValue and maxBits)
validates votes correctly.
🧹 Nitpick comments (10)
examples/CRISP/packages/crisp-sdk/package.json (1)

24-26: Consider parallel compilation for independent circuits.

Since compile:crisp and compile:greco are independent of each other, you could run them in parallel to speed up the build:

⚡ Suggested change
-    "compile:circuits": "pnpm compile:crisp && pnpm compile:greco",
+    "compile:circuits": "pnpm compile:crisp & pnpm compile:greco & wait",
examples/CRISP/crates/zk-inputs/src/lib.rs (1)

499-594: Solid test coverage for the new decrypt_vote method.

The tests cover the essential scenarios: basic roundtrip, multiple vote patterns, wrong-key behavior, and invalid input handling. The wrong-key test at line 549 properly handles the non-deterministic BFV behavior (may succeed with garbage or may fail).

Minor nit: at lines 554 and 564, _secret_key2 uses the underscore-prefix convention (implying unused), but it is used on line 564. Consider renaming to secret_key2.

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

156-158: Nit: Use /** for JSDoc consistency.

Line 156 uses /* instead of /**, unlike all other type doc comments in this file.

-/*
+/**
  * The inputs required for the GRECO circuit.
  */
examples/CRISP/packages/crisp-sdk/src/vote.ts (2)

182-182: circuitInputs: any defeats type safety across the proof pipeline.

Both generateCircuitInputs (line 182) and generateProof (line 258) use any for the circuit inputs, meaning any typo or missing field will only surface at circuit execution time. Consider defining a union or intersection type that captures the combined GRECO + CRISP fields returned by the WASM generator, so the compiler catches mismatches early.

Also applies to: 258-258


62-63: Nit: dead branch — choiceIdx < vote.length is always true.

The loop condition is choiceIdx < n where n = vote.length, so the ternary guard on line 63 can never take the 0 branch.

-    const value = choiceIdx < vote.length ? vote[choiceIdx] : 0
+    const value = vote[choiceIdx]
examples/CRISP/circuits/lib/src/ecdsa.nr (1)

155-181: Slightly misleading test comment.

The comment on lines 157–159 says the test "will fail (assertion failure) when validate_signature is called with an invalid signature." Since validate_signature now returns false rather than panicking, the actual failure comes from assert(false) on line 180, not from within validate_signature itself. Consider updating the comment for clarity.

📝 Suggested comment update
-    // This test verifies that invalid signatures cause validation to fail.
-    // The test itself will fail (assertion failure) when validate_signature is called
-    // with an invalid signature, which demonstrates that invalid signatures are properly rejected.
+    // This test verifies that validate_signature returns false for an invalid signature.
+    // assert(false) triggers the expected panic, satisfying #[test(should_fail)].
examples/CRISP/circuits/lib/src/commitments.nr (1)

38-41: Nit: payload doesn't need mut in compute_k1_commitment.

Unlike compute_ct_commitment where payload is reassigned on line 33, here payload is only assigned once on line 39.

📝 Proposed fix
 pub fn compute_k1_commitment<let N: u32, let BIT_K1: u32>(k1: Polynomial<N>) -> Field {
-    let mut payload = single_polynomial_payload::<N, BIT_K1>(Vec::new(), k1);
+    let payload = single_polynomial_payload::<N, BIT_K1>(Vec::new(), k1);
     compute_commitments(payload, DS_CRISP_K1, [0x80000000 | payload.len(), 1]).get(0)
 }
examples/CRISP/scripts/compile_circuits.sh (1)

1-10: Script assumes a specific working directory.

All paths are relative (e.g., circuits/bin/crisp), implying the script must be run from examples/CRISP/. Consider adding a cd to the script's directory or documenting the expected invocation context.

📝 Proposed addition at the top of the script
 set -euo pipefail
 
+# Ensure we're in the examples/CRISP directory
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$SCRIPT_DIR/.."
+
 echo "noir"
examples/CRISP/circuits/lib/src/ciphertext_addition.nr (1)

151-160: Pre-existing: execute() mixes panic (range checks) with bool return (evaluations).

check_range_bounds() (line 153) asserts internally and will panic on failure, while verify_evaluations() (line 159) returns a bool. This means callers get inconsistent failure modes — a panic for out-of-range coefficients vs. false for incorrect addition. Not introduced in this PR, but worth harmonizing as a follow-up if the broader refactor aims for consistent bool-based validation.

examples/CRISP/circuits/bin/greco/src/main.nr (1)

34-51: The long generic parameter list is correct but consider a type alias for readability.

The UserDataEncryption instantiation with 12 generic parameters is faithful to the type definition but quite verbose. This is a minor readability concern — a type alias could make this more maintainable. That said, given this is a circuit entry point that's unlikely to be duplicated, this is acceptable as-is.

Comment thread examples/CRISP/circuits/bin/crisp/Nargo.toml Outdated
Comment thread examples/CRISP/circuits/bin/crisp/src/main.nr Outdated
Comment thread examples/CRISP/circuits/bin/greco/Nargo.toml Outdated
Comment thread examples/CRISP/circuits/lib/src/utils.nr
Comment thread examples/CRISP/crates/zk-inputs-wasm/src/lib.rs
Comment thread examples/CRISP/packages/crisp-sdk/src/utils.ts
Comment thread examples/CRISP/packages/crisp-sdk/src/utils.ts
Comment thread examples/CRISP/packages/crisp-sdk/src/vote.ts Outdated

@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: 6

Caution

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

⚠️ Outside diff range comments (2)
examples/CRISP/packages/crisp-sdk/src/vote.ts (2)

340-370: ⚠️ Potential issue | 🟡 Minor

Type mismatch in validateVote: reduce initial value 0 vs bigint elements.

Line 365: vote.reduce((sum, v) => sum + v, 0) — if Vote is bigint[], the initial value should be 0n. With 0 (number), the first addition produces 0 + bigint which coerces to bigint in JS but TypeScript will flag this as a type error with strict settings. Similarly, line 360's comparison votedAmount > balance mixes bigint | number with bigint.

If Vote is updated to number[] (per the earlier comment), these become consistent. Otherwise, use 0n and ensure consistent types.


92-123: ⚠️ Potential issue | 🟡 Minor

decodeTally returns Vote (bigint[]) but pushes number values.

Line 113 initializes value as 0 (number), and line 119 pushes it into results: Vote (which is bigint[]). This is a type inconsistency — the array will contain number values at runtime while the type signature promises bigint[].

This is another symptom of the Vote type needing to be updated to number[] to match the PR's direction.

🤖 Fix all issues with AI agents
In `@circuits/bin/recursive_aggregation/wrapper/dkg/share_computation/Nargo.toml`:
- Around line 5-9: Update the Nargo compiler version string in the Nargo.toml by
changing the compiler_version entry from "1.0.0" to "1.0.0-beta.18" so it
matches the NOIR_TOOLCHAIN used in CI; locate the compiler_version key in the
Nargo.toml (e.g., the line with compiler_version = "1.0.0") and replace its
value with "1.0.0-beta.18" across this file (and any other Nargo.toml files if
present) to ensure the declared toolchain matches the installed Nargo release.

In `@circuits/bin/threshold/user_data_encryption/Nargo.toml`:
- Line 5: The Nargo.toml files set compiler_version = "1.0.0" which mismatches
the CI NOIR_TOOLCHAIN pre-release v1.0.0-beta.18; update each Nargo.toml's
compiler_version entry to "1.0.0-beta.18" (or alternatively change the CI
NOIR_TOOLCHAIN to v1.0.0) so the nargo check uses the same toolchain; locate the
compiler_version key in the Nargo.toml files (e.g., the compiler_version field
in circuits/bin/threshold/user_data_encryption/Nargo.toml) and make the
consistent change across all 23 manifests.

In `@circuits/lib/src/math/safe.nr`:
- Line 346: The Vec::from_vector calls are inconsistent: one site uses
Vec::from_vector(&[1, 2, 3]) while other call sites use array literals directly;
standardize all Vec::from_vector invocations to the explicit slice form used in
the docs by changing the occurrences at the other call sites to use the &[...]
form (i.e., update each Vec::from_vector([...]) to Vec::from_vector(&[...]) so
all calls use the same & slice syntax).

In `@examples/CRISP/packages/crisp-sdk/src/utils.ts`:
- Around line 161-166: The getMaxVoteValue function uses a 32-bit left shift (1
<< effectiveBits) which overflows for effectiveBits > 31; update getMaxVoteValue
to compute the mask using an exponentiation-based or BigInt approach (e.g., use
2 ** effectiveBits - 1 or BigInt(1) << BigInt(effectiveBits) minus 1) instead of
the << operator so it returns correct values for up to MAX_VOTE_BITS; change the
expression in getMaxVoteValue accordingly.

In `@examples/CRISP/packages/crisp-sdk/src/vote.ts`:
- Around line 423-432: The verifyProof function currently instantiates
Barretenberg via Barretenberg.new and returns after calling
UltraHonkBackend.verifyProof but may skip api.destroy() if an exception occurs;
wrap the usage of the api and crispBackend.verifyProof call in a try/finally so
api.destroy() is always invoked (e.g., create api, then try { const isValid =
await crispBackend.verifyProof(...); return isValid } finally { api.destroy();
}), ensuring the cleanup runs even on errors while preserving the verifyProof
return value.
- Around line 259-333: The Barretenberg WASM instance created by
Barretenberg.new in generateProof can leak if any later step throws; wrap the
logic from after Barretenberg.new(...) through proof creation (everything that
uses the api, grecoBackend, crispBackend, etc.) in a try/finally block and call
api.destroy() in the finally so destroy runs on error or success, guarding the
destroy call so it only runs when api is defined; ensure generateProof still
rethrows errors after cleanup.
🧹 Nitpick comments (3)
examples/CRISP/packages/crisp-sdk/src/vote.ts (1)

183-183: circuitInputs: any loses type safety across the proof pipeline.

generateCircuitInputs returns any, generateProof accepts any — property access typos or missing fields won't be caught at compile time. Consider defining a union or intersection type (e.g., CRISPCircuitInputs & GrecoCircuitInputs & SignatureInputs) or at least a Record<string, unknown> with runtime validation at the boundary.

Also applies to: 188-188, 259-259

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

207-215: Use bytesToHex from viem instead of Buffer.from().toString('hex') for browser compatibility.

This SDK builds for ESM with bundler resolution (not Node.js-specific), so using Buffer without a polyfill will cause runtime errors in browsers. bytesToHex from viem (already a project dependency) is already used in vote.ts for the same conversion and is the idiomatic solution here.

Proposed fix
-import { publicKeyToAddress } from 'viem/utils'
+import { publicKeyToAddress, bytesToHex } from 'viem/utils'
 import { hexToBytes, recoverPublicKey } from 'viem'

 export const proofToFields = (proof: Uint8Array): string[] => {
   const fields: string[] = []
   for (let i = 0; i < proof.length; i += 32) {
     const chunk = proof.slice(i, i + 32)
-    fields.push('0x' + Buffer.from(chunk).toString('hex'))
+    fields.push(bytesToHex(chunk))
   }
   return fields
 }
examples/CRISP/circuits/lib/Nargo.toml (1)

9-11: Consider upgrading to newer compatible versions of external Noir dependencies.

The current dependencies are compatible with Noir 1.0.0 (as indicated by the explicit compiler_version = "1.0.0" setting in Nargo.toml and Noir tests in CI), but newer versions are available:

  • keccak256: v0.1.3 (currently v0.1.0)
  • poseidon: v0.2.3 (currently v0.1.1; note v0.2.x may have API changes)
  • binary_merkle_root: v0.0.1 (no newer version available)

Evaluate upgrading keccak256 and poseidon to their latest patch/minor versions to benefit from potential improvements and bug fixes.

Comment thread circuits/bin/recursive_aggregation/wrapper/dkg/share_computation/Nargo.toml Outdated
Comment thread circuits/bin/threshold/user_data_encryption/Nargo.toml Outdated
Comment thread circuits/lib/src/math/safe.nr Outdated
Comment thread examples/CRISP/packages/crisp-sdk/src/utils.ts
Comment thread examples/CRISP/packages/crisp-sdk/src/vote.ts
Comment thread examples/CRISP/packages/crisp-sdk/src/vote.ts

@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: 5

Caution

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

⚠️ Outside diff range comments (1)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)

82-110: ⚠️ Potential issue | 🟡 Minor

Remove unused type definitions GrecoCryptographicParams, GrecoBoundParams, and GrecoParams.

These types are not referenced anywhere in the codebase and have been replaced by the GrecoCircuitInputs type introduced in the restructured circuit flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/CRISP/packages/crisp-sdk/src/types.ts` around lines 82 - 110, Remove
the unused type definitions GrecoCryptographicParams, GrecoBoundParams, and
GrecoParams from the file; delete their declarations and any related exports so
the module only exposes currently used types (e.g., GrecoCircuitInputs) and
avoid leaving unused imports/exports behind, then run TypeScript build/tsc to
ensure no references remain and adjust any import sites if they accidentally
referenced these types.
🧹 Nitpick comments (9)
packages/enclave-sdk/package.json (1)

53-53: vitest is listed under dependencies instead of devDependencies.

This is a test runner and should be in devDependencies alongside the other dev tooling. This is pre-existing but worth cleaning up to avoid shipping unnecessary dependencies to consumers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/enclave-sdk/package.json` at line 53, The package.json currently
lists the test runner "vitest" under dependencies; move the "vitest" entry from
the top-level "dependencies" section into the "devDependencies" section in
packages/enclave-sdk/package.json so it is not shipped to consumers, then update
the lockfile by running the project's package manager install (e.g.,
npm/yarn/pnpm install) to reflect the change; ensure the unique key "vitest" is
removed from "dependencies" and added under "devDependencies".
examples/CRISP/crates/zk-inputs/src/lib.rs (1)

239-242: Custom secret key serialization bypasses SecretKey::from_bytes / SecretKeySerializer.

The SK is serialized as raw Vec<i64> coefficients via bincode::serialize(&sk.coeffs) and deserialized the same way. This is inconsistent with the SecretKey::from_bytes/SecretKeySerializer in crates/fhe/src/fhe.rs (which wraps in SecretKeyData).

This works fine as long as generate_keys and decrypt_vote are always used together, but could cause confusing failures if keys are exchanged with code using the standard serialization format. Consider adding a doc comment noting the serialization format, or using the standard SecretKeySerializer for interoperability.

Also applies to: 263-265

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/CRISP/crates/zk-inputs/src/lib.rs` around lines 239 - 242, The code
currently serializes/deserializes secret keys as raw Vec<i64> coefficients
(using bincode) and constructs keys with SecretKey::new, which bypasses the
standard SecretKey::from_bytes / SecretKeySerializer format and can break
interoperability; fix by either (A) switching to the canonical
serializer/deserializer (use SecretKeySerializer or call
SecretKey::from_bytes/from_bytes(&SecretKeyData) when loading in the code paths
around SecretKey::new, and update generate_keys/decrypt_vote to produce/consume
that format), or (B) if keeping the raw-coeff format, add a clear doc comment on
the functions (generate_keys, decrypt_vote and the load code around
SecretKey::new) that states the exact serialization format and warns about
incompatibility with SecretKeySerializer/SecretKey::from_bytes so callers know
the contract.
examples/CRISP/circuits/lib/src/commitments.nr (1)

38-41: Unnecessary mut on payload.

payload in compute_k1_commitment is never reassigned after initialization (unlike compute_ct_commitment where it's reassigned on line 33). The mut qualifier can be removed.

Proposed fix
 pub fn compute_k1_commitment<let N: u32, let BIT_K1: u32>(k1: Polynomial<N>) -> Field {
-    let mut payload = single_polynomial_payload::<N, BIT_K1>(Vec::new(), k1);
+    let payload = single_polynomial_payload::<N, BIT_K1>(Vec::new(), k1);
     compute_commitments(payload, DS_CRISP_K1, [0x80000000 | payload.len(), 1]).get(0)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/CRISP/circuits/lib/src/commitments.nr` around lines 38 - 41, In
compute_k1_commitment the local variable payload is declared mutable but never
reassigned; remove the unnecessary mut qualifier from the payload binding in
compute_k1_commitment (the call that constructs it uses
single_polynomial_payload::<N, BIT_K1>(Vec::new(), k1)) so the variable is
immutable, then keep the call to compute_commitments(payload, DS_CRISP_K1,
[0x80000000 | payload.len(), 1]).get(0) unchanged.
examples/CRISP/circuits/bin/crisp/src/main.nr (1)

181-195: CiphertextAddition::new and execute() are computed unconditionally despite only being asserted in the mask-vote non-first-vote path.

In Noir's constraint system, all computations contribute to the constraint count regardless of branching. CiphertextAddition::new and execute() will generate constraints even when is_mask_vote == false or is_first_vote == true. This is functionally correct (the result is only asserted conditionally), but it adds to the circuit's gate count — which is relevant given the PR's goal of reducing per-circuit constraints for browser WASM viability.

Is there a way to defer or skip these constraints when they're not needed, or is this an acceptable cost given the current architecture?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/CRISP/circuits/bin/crisp/src/main.nr` around lines 181 - 195,
CiphertextAddition::new and ct_add.execute() are always building constraints;
only run them when needed by guarding creation/execution behind the runtime
flags (is_mask_vote && !is_first_vote). Move the construction and execute call
for ct_add/valid_ct_add inside the conditional that checks is_mask_vote and
!is_first_vote, and in the else branch produce equivalent placeholder outputs
(e.g., zero/false witnesses) so the rest of the circuit still composes;
alternatively add a no-op constructor/factory on CiphertextAddition (e.g.,
CiphertextAddition::noop or CiphertextAddition::empty) that returns a structure
that does not allocate constraints and whose execute() returns the placeholder
result, then replace unconditional uses with the guarded/new-noop flow.
examples/CRISP/packages/crisp-sdk/src/types.ts (1)

156-175: JSDoc comment uses /* instead of /**.

The comment block for GrecoCircuitInputs uses a single-star opening (/*) while other type comments in this file use the JSDoc convention (/**). This prevents documentation tools from picking it up.

Suggested fix
-/*
+/**
  * The inputs required for the GRECO circuit.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/CRISP/packages/crisp-sdk/src/types.ts` around lines 156 - 175, The
comment block above the GrecoCircuitInputs type is using a plain block comment
(/*) instead of a JSDoc comment, so documentation tools will skip it; update the
comment to use the JSDoc opening /** (i.e., change the leading /* to /**) and
ensure the comment text remains the same so GrecoCircuitInputs is picked up by
docs generators and matches the other type comments in the file.
examples/CRISP/packages/crisp-sdk/src/vote.ts (4)

183-231: circuitInputs typed as any loses type safety across the proof generation pipeline.

generateCircuitInputs returns circuitInputs: any (line 183, 188), and generateProof accepts circuitInputs: any (line 259). This means there's no compile-time verification that the WASM generator produces the correct shape or that generateProof receives all required fields (e.g., pk0is, r1is, e0, etc. for GRECO).

Consider defining a combined input type (or intersection of CRISPCircuitInputs and GrecoCircuitInputs plus any additional fields from the WASM generator) to catch mismatches at compile time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/CRISP/packages/crisp-sdk/src/vote.ts` around lines 183 - 231,
generateCircuitInputs currently returns circuitInputs: any (and generateProof
accepts any), losing type safety; define a concrete combined type (e.g., type
CombinedCircuitInputs = CRISPCircuitInputs & GrecoCircuitInputs &
WasmGeneratedFields) that includes all expected fields from the WASM generator
(pk0is, r1is, e0, etc.) and use it as the return type of generateCircuitInputs
and the parameter type for generateProof; update occurrences of circuitInputs
assignment in generateCircuitInputs to satisfy the new type (convert
array/string mappings to the typed fields) and adjust any downstream consumers
to rely on the new CombinedCircuitInputs interface so missing/incorrect fields
are caught at compile time.

440-449: Verify public input indices in encodeSolidityProof match the updated circuit.

publicInputs[3] is assumed to be slot_address and publicInputs[6] is the return value (encrypted vote commitment). Based on the circuit's pub parameter order: prev_ct_commitment(0), pk_commitment(1), merkle_root(2), slot_address(3), is_first_vote(4), num_options(5), return(6) — this appears correct. However, any future reordering of pub inputs in the circuit would silently break this.

Consider documenting the expected public input layout or using named constants for these indices.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/CRISP/packages/crisp-sdk/src/vote.ts` around lines 440 - 449, The
function encodeSolidityProof uses hard-coded publicInputs indices
(publicInputs[3] and publicInputs[6]) which can silently break if the circuit's
public input order changes; update encodeSolidityProof to replace magic indices
with descriptive constants (e.g., SLOT_ADDRESS_INDEX, RETURN_INDEX) or a small
mapped accessor (e.g., getPubInput(publicInputs, 'slot_address')) and add a
brief comment documenting the expected public input layout (prev_ct_commitment,
pk_commitment, merkle_root, slot_address, is_first_vote, num_options, return) so
future circuit reorderings are obvious and maintainers can update the constants
instead of hunting for numeric indices.

246-252: returnValue as any[] cast is fragile — consider narrowing.

The Noir execute method returns a generic type. Casting through any[] silently swallows type mismatches. If the GRECO circuit's return type changes (e.g., returns a single value instead of a tuple), this will fail at runtime with an unhelpful error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/CRISP/packages/crisp-sdk/src/vote.ts` around lines 246 - 252, The
cast "returnValue as any[]" in executeGrecoCircuit is fragile and can hide
mismatches from noir.execute; update executeGrecoCircuit to narrow and validate
returnValue before mapping: use a type guard or runtime check on the value
returned by noir.execute (referencing noir.execute and the local variable
returnValue) to confirm it's an array of hex strings (or handle the single-value
case), throw a clear error if it’s not the expected shape, and then safely map
each element to BigInt; this ensures changes to the GRECO circuit return type
are caught and handled instead of failing silently at runtime.

340-369: Mixed number > bigint comparisons in validateVote rely on implicit JS coercion.

Lines 360 and 366 compare number (from vote[i]) against bigint (balance). JavaScript supports relational operators across these types, but arithmetic mixing throws. This is subtle and could break if someone later refactors to use arithmetic (e.g., balance - total).

Consider explicitly converting for clarity:

Suggested fix
-    const votedAmount = vote.find((v) => v > 0) ?? 0
-    if (votedAmount > balance) {
+    const votedAmount = vote.find((v) => v > 0) ?? 0
+    if (BigInt(votedAmount) > balance) {
       throw new Error('Invalid vote: vote exceeds balance')
     }
   } else {
     // 3+ options: split allowed, total capped
-    const total = vote.reduce((sum, v) => sum + v, 0)
-    if (total > balance) {
+    const total = vote.reduce((sum, v) => sum + v, 0)
+    if (BigInt(total) > balance) {
       throw new Error(`Invalid vote: total votes (${total}) exceed balance (${balance})`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/CRISP/packages/crisp-sdk/src/vote.ts` around lines 340 - 369,
validateVote mixes numeric vote entries with a bigint balance causing subtle
type-coercion risks; convert balance to a numeric value up-front (e.g., const
balanceNum = Number(balance)) and use balanceNum for all comparisons and error
messages involving vote values/totals (replace checks that use balance and
interpolations that show balance with balanceNum). Update references in
validateVote for the votedAmount > balance and total > balance checks (and any
other comparisons against balance) to use balanceNum so arithmetic and
comparisons remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/ci.yml:
- Line 24: CI is pinning NOIR_TOOLCHAIN to v1.0.0-beta.18 while all Nargo.toml
files require compiler_version = "1.0.0", causing nargo check/compile failures;
update every Nargo.toml to set compiler_version = "1.0.0-beta.18" so it matches
the NOIR_TOOLCHAIN variable, verifying changes touch the compiler_version key in
each Nargo.toml and rerun CI locally to confirm nargo check passes.

In `@examples/CRISP/circuits/lib/Nargo.toml`:
- Line 8: The dependency name in the Nargo manifest is mismatched: the root file
declares enclave_lib in the dependency block but the target package's Nargo.toml
defines name = "lib"; fix by making these identifiers match — either change the
dependency key enclave_lib to lib in the dependency entry or rename the package
in circuits/lib/Nargo.toml to name = "enclave_lib" so the dependency name and
the package name are identical.

In `@examples/CRISP/packages/crisp-sdk/src/types.ts`:
- Line 70: Change the client vote type and vote construction to use number[]
instead of bigint[]: update the exported type Vote in
client/src/model/vote.model.ts (replace any "export type Vote = bigint[]" with
"export type Vote = number[]") and adjust the vote creation in
client/src/hooks/voting/useVoteCasting.ts where the variable vote is built
(replace BigInt literals like [balance, 0n] / [0n, balance] with numeric arrays
using Number(balance) or numeric literals, e.g., [Number(balance), 0] and [0,
Number(balance)]). Ensure all usages and imports referencing Vote reflect the
new number[] type.

In `@examples/CRISP/packages/crisp-sdk/src/utils.ts`:
- Around line 207-215: The proofToFields function currently calls
Buffer.from(...) which breaks in browsers; replace the Buffer usage with a
pure-TS/Web-API hex encoder: for each 32-byte chunk in proof (inside
proofToFields) convert the Uint8Array bytes to a hex string by mapping/iterating
bytes and producing two-digit hex (pad with '0' when needed) then prefix with
'0x' and push into fields; optionally factor the logic into a small helper like
byteArrayToHex to keep proofToFields concise and ensure it works in both Node
and browser environments.

In `@examples/CRISP/packages/crisp-sdk/tests/vote.test.ts`:
- Around line 219-221: The test is decrypting the local previousCiphertext
instead of the proof output; update the assertion to decrypt proof.encryptedVote
by calling decryptVote(proof.encryptedVote, secretKey, 2) and compare that
result to zeroVote so the test actually validates the proof's encryptedVote
rather than the trivially-initialized previousCiphertext.

---

Outside diff comments:
In `@examples/CRISP/packages/crisp-sdk/src/types.ts`:
- Around line 82-110: Remove the unused type definitions
GrecoCryptographicParams, GrecoBoundParams, and GrecoParams from the file;
delete their declarations and any related exports so the module only exposes
currently used types (e.g., GrecoCircuitInputs) and avoid leaving unused
imports/exports behind, then run TypeScript build/tsc to ensure no references
remain and adjust any import sites if they accidentally referenced these types.

---

Duplicate comments:
In `@circuits/bin/config/Nargo.toml`:
- Line 5: The compiler_version value in Nargo.toml is inconsistent with the CI
toolchain; update the compiler_version setting (the compiler_version entry in
Nargo.toml) to match the CI's version string (v1.0.0-beta.18) so both the
repository and CI use the exact same compiler version string.

In `@circuits/bin/dkg/pk/Nargo.toml`:
- Line 5: The compiler_version field in Nargo.toml currently reads "1.0.0" and
must be updated to match the CI toolchain; open the Nargo.toml file and change
the compiler_version value to the exact version used by CI (replace "1.0.0" with
the CI toolchain version), saving the file so the build uses the correct
compiler version.

In `@circuits/bin/dkg/share_encryption/Nargo.toml`:
- Line 5: The Nargo.toml contains a mismatched compiler_version entry; update
the compiler_version value in Nargo.toml to match the expected version used in
CI (the same version referenced in ci.yml line 24) so both places agree; locate
the compiler_version key in Nargo.toml and replace its current string with the
CI's canonical compiler version.

In `@circuits/bin/recursive_aggregation/fold/Nargo.toml`:
- Line 5: The Nargo.toml contains compiler_version = "1.0.0" which mismatches
the CI toolchain; update the compiler_version entry to the exact compiler
version string used by CI (replace "1.0.0" with the CI toolchain's compiler
version) so the compiler_version field matches the CI toolchain.

In `@circuits/bin/recursive_aggregation/wrapper/dkg/share_computation/Nargo.toml`:
- Line 5: The compiler_version field in Nargo.toml is incorrect; update the
compiler_version value (the compiler_version key) to match the
expected/consistent version used across the project (replace "1.0.0" with the
canonical compiler version used elsewhere) so all Nargo.toml files use the same
compiler_version string.

In `@circuits/bin/recursive_aggregation/wrapper/dkg/share_decryption/Nargo.toml`:
- Line 5: The Nargo.toml currently sets compiler_version = "1.0.0" which
mismatches the CI toolchain; update the compiler_version entry in Nargo.toml to
the CI-required version string (replace the "1.0.0" value) so it matches the
toolchain used in CI (ensure the compiler_version key remains unchanged and only
the value is updated).

In
`@circuits/bin/recursive_aggregation/wrapper/threshold/pk_generation/Nargo.toml`:
- Line 5: The project's Nargo.toml has an outdated compiler_version entry
("compiler_version = \"1.0.0\""); update the compiler_version field in
Nargo.toml to match the CI toolchain's required version (replace the current
value with the CI's compiler version string) so the build toolchain aligns with
CI; locate the compiler_version key in Nargo.toml and change its value
accordingly.

In
`@circuits/bin/recursive_aggregation/wrapper/threshold/share_decryption/Nargo.toml`:
- Line 5: compiler_version in Nargo.toml ("compiler_version = \"1.0.0\"")
mismatches the CI-declared compiler version; update the compiler_version key in
Nargo.toml to the exact same version string used by the project's CI
configuration (the canonical compiler_version) so both are identical and keep
them synchronized going forward.

In `@circuits/bin/threshold/decrypted_shares_aggregation_mod/Nargo.toml`:
- Line 5: The compiler_version entry in Nargo.toml (the compiler_version =
"1.0.0" line) is out of sync with the CI toolchain; update the compiler_version
value to the correct version used by CI (match the exact string used elsewhere
in the repo), and ensure other Nargo.toml files use the same version to keep
consistency across the project.

In `@circuits/bin/threshold/pk_aggregation/Nargo.toml`:
- Line 5: The compiler_version setting in Nargo.toml ("compiler_version") is
inconsistent with the version used in CI; update the compiler_version value in
the pk_aggregation/Nargo.toml (symbol: compiler_version) to match the canonical
compiler version used in the CI configuration (the same version string
referenced in ci.yml) so both files use the identical compiler version.

In `@circuits/bin/threshold/pk_generation/Nargo.toml`:
- Line 5: The TOML key compiler_version in Nargo.toml is inconsistent with the
CI config; update the compiler_version value to exactly match the compiler
version specified in the CI configuration (the value used at ci.yml line 24) so
both places use the same version string.

In `@circuits/bin/threshold/user_data_encryption/Nargo.toml`:
- Line 5: The Nargo.toml has a mismatched compiler_version value; update the
compiler_version entry in Nargo.toml to the canonical compiler version used by
the project (the same value declared in CI) so they match; locate the
compiler_version key in Nargo.toml and change its string to the project's
canonical compiler version.

In `@circuits/lib/Nargo.toml`:
- Line 5: The compiler_version field in Nargo.toml is set to "1.0.0" which will
mismatch the CI-installed Nargo toolchain; update the compiler_version value in
Nargo.toml to the exact version pinned by CI (e.g., "1.0.0-beta.18") so it
matches NOIR_TOOLCHAIN/Nargo, ensuring Nargo's version check passes during
builds.

In `@examples/CRISP/circuits/bin/crisp/src/main.nr`:
- Around line 55-63: The greco_key_hash field is currently private which lets a
prover substitute an arbitrary GRECO verification key; change the declaration of
greco_key_hash to be public (greco_key_hash: pub Field) or replace it with a
hardcoded constant bound to the expected GRECO VK so the on-chain verifier can
trust the GRECO VK; ensure references in verification logic that rely on
greco_key_hash (and related symbols UltraHonkVerificationKey, greco_proof,
k1_commitment, ct_commitment) use the public/hardcoded value so the re-derived
k1_commitment and ct_commitment cannot be forged by a malicious prover.

In `@examples/CRISP/crates/zk-inputs-wasm/src/lib.rs`:
- Around line 232-243: The current cast in decrypt_vote (function decrypt_vote
calling self.generator.decrypt_vote) uses v as i64 which silently wraps for u64
≥ 2^63; change the JS-facing API to avoid lossy casts by returning JS-friendly
values: map each u64 to a JsValue and for values > i64::MAX create a
js_sys::BigInt (js_sys::BigInt::from(u64)) and for smaller values use
JsValue::from_f64(v as f64) or JsValue::from(i64) so no wrap occurs; update the
function signature to return Result<Vec<JsValue>, JsValue> and adjust callers
accordingly (preserve error handling that converts e.to_string()).

In `@examples/CRISP/packages/crisp-sdk/src/utils.ts`:
- Around line 177-197: The function decodeBytesToNumbers currently collects
BigInts via DataView.getBigUint64 but then silently converts them to JS Numbers
with result.map(Number), which loses precision; update the implementation and
signature so it returns bigint[] (remove the final .map(Number) and return
result directly), and update the JSDoc to state it returns bigint[] (or,
alternatively, if a number[] is required, add an explicit check in
decodeBytesToNumbers that throws an error when any value exceeds
Number.MAX_SAFE_INTEGER before converting); reference the function name
decodeBytesToNumbers and the use of DataView.getBigUint64/result.map(Number)
when making the change.
- Around line 161-166: getMaxVoteValue uses bit-shift (1 << effectiveBits) which
overflows for effectiveBits > 31; replace that expression with an
exponentiation-based computation such as (2 ** effectiveBits) - 1 (or
Math.pow(2, effectiveBits) - 1) in getMaxVoteValue so effectiveBits up to
MAX_VOTE_BITS (50) are handled correctly and the function still returns a
number; keep the rest of the logic (bfvParams.degree, segmentSize,
effectiveBits) unchanged.

In `@examples/CRISP/packages/crisp-sdk/src/vote.ts`:
- Around line 423-432: The verifyProof function leaks the Barretenberg API when
crispBackend.verifyProof throws because api.destroy() is called unconditionally
after the await; wrap the usage of the Barretenberg API in a try/finally: create
the API via Barretenberg.new, construct UltraHonkBackend(crispCircuit.bytecode,
api), then call crispBackend.verifyProof inside the try block and always call
api.destroy() in the finally block so the API is cleaned up on success or error.
- Around line 259-333: generateProof leaks the Barretenberg WASM instance
because api.destroy() is only called on the happy path; wrap creation/use of the
Barretenberg API in a try/finally so api.destroy() runs even on exceptions.
Specifically, after calling Barretenberg.new(...) (in generateProof) ensure you
assign the api variable and then place all uses of api (UltraHonkBackend
construction, generateProof, generateRecursiveProofArtifacts, generateProof for
crisp) inside a try block and call api.destroy() in the finally block; keep the
existing return of the proof but rethrow any caught error if needed so behavior
is preserved.

---

Nitpick comments:
In `@examples/CRISP/circuits/bin/crisp/src/main.nr`:
- Around line 181-195: CiphertextAddition::new and ct_add.execute() are always
building constraints; only run them when needed by guarding creation/execution
behind the runtime flags (is_mask_vote && !is_first_vote). Move the construction
and execute call for ct_add/valid_ct_add inside the conditional that checks
is_mask_vote and !is_first_vote, and in the else branch produce equivalent
placeholder outputs (e.g., zero/false witnesses) so the rest of the circuit
still composes; alternatively add a no-op constructor/factory on
CiphertextAddition (e.g., CiphertextAddition::noop or CiphertextAddition::empty)
that returns a structure that does not allocate constraints and whose execute()
returns the placeholder result, then replace unconditional uses with the
guarded/new-noop flow.

In `@examples/CRISP/circuits/lib/src/commitments.nr`:
- Around line 38-41: In compute_k1_commitment the local variable payload is
declared mutable but never reassigned; remove the unnecessary mut qualifier from
the payload binding in compute_k1_commitment (the call that constructs it uses
single_polynomial_payload::<N, BIT_K1>(Vec::new(), k1)) so the variable is
immutable, then keep the call to compute_commitments(payload, DS_CRISP_K1,
[0x80000000 | payload.len(), 1]).get(0) unchanged.

In `@examples/CRISP/crates/zk-inputs/src/lib.rs`:
- Around line 239-242: The code currently serializes/deserializes secret keys as
raw Vec<i64> coefficients (using bincode) and constructs keys with
SecretKey::new, which bypasses the standard SecretKey::from_bytes /
SecretKeySerializer format and can break interoperability; fix by either (A)
switching to the canonical serializer/deserializer (use SecretKeySerializer or
call SecretKey::from_bytes/from_bytes(&SecretKeyData) when loading in the code
paths around SecretKey::new, and update generate_keys/decrypt_vote to
produce/consume that format), or (B) if keeping the raw-coeff format, add a
clear doc comment on the functions (generate_keys, decrypt_vote and the load
code around SecretKey::new) that states the exact serialization format and warns
about incompatibility with SecretKeySerializer/SecretKey::from_bytes so callers
know the contract.

In `@examples/CRISP/packages/crisp-sdk/src/types.ts`:
- Around line 156-175: The comment block above the GrecoCircuitInputs type is
using a plain block comment (/*) instead of a JSDoc comment, so documentation
tools will skip it; update the comment to use the JSDoc opening /** (i.e.,
change the leading /* to /**) and ensure the comment text remains the same so
GrecoCircuitInputs is picked up by docs generators and matches the other type
comments in the file.

In `@examples/CRISP/packages/crisp-sdk/src/vote.ts`:
- Around line 183-231: generateCircuitInputs currently returns circuitInputs:
any (and generateProof accepts any), losing type safety; define a concrete
combined type (e.g., type CombinedCircuitInputs = CRISPCircuitInputs &
GrecoCircuitInputs & WasmGeneratedFields) that includes all expected fields from
the WASM generator (pk0is, r1is, e0, etc.) and use it as the return type of
generateCircuitInputs and the parameter type for generateProof; update
occurrences of circuitInputs assignment in generateCircuitInputs to satisfy the
new type (convert array/string mappings to the typed fields) and adjust any
downstream consumers to rely on the new CombinedCircuitInputs interface so
missing/incorrect fields are caught at compile time.
- Around line 440-449: The function encodeSolidityProof uses hard-coded
publicInputs indices (publicInputs[3] and publicInputs[6]) which can silently
break if the circuit's public input order changes; update encodeSolidityProof to
replace magic indices with descriptive constants (e.g., SLOT_ADDRESS_INDEX,
RETURN_INDEX) or a small mapped accessor (e.g., getPubInput(publicInputs,
'slot_address')) and add a brief comment documenting the expected public input
layout (prev_ct_commitment, pk_commitment, merkle_root, slot_address,
is_first_vote, num_options, return) so future circuit reorderings are obvious
and maintainers can update the constants instead of hunting for numeric indices.
- Around line 246-252: The cast "returnValue as any[]" in executeGrecoCircuit is
fragile and can hide mismatches from noir.execute; update executeGrecoCircuit to
narrow and validate returnValue before mapping: use a type guard or runtime
check on the value returned by noir.execute (referencing noir.execute and the
local variable returnValue) to confirm it's an array of hex strings (or handle
the single-value case), throw a clear error if it’s not the expected shape, and
then safely map each element to BigInt; this ensures changes to the GRECO
circuit return type are caught and handled instead of failing silently at
runtime.
- Around line 340-369: validateVote mixes numeric vote entries with a bigint
balance causing subtle type-coercion risks; convert balance to a numeric value
up-front (e.g., const balanceNum = Number(balance)) and use balanceNum for all
comparisons and error messages involving vote values/totals (replace checks that
use balance and interpolations that show balance with balanceNum). Update
references in validateVote for the votedAmount > balance and total > balance
checks (and any other comparisons against balance) to use balanceNum so
arithmetic and comparisons remain consistent.

In `@packages/enclave-sdk/package.json`:
- Line 53: The package.json currently lists the test runner "vitest" under
dependencies; move the "vitest" entry from the top-level "dependencies" section
into the "devDependencies" section in packages/enclave-sdk/package.json so it is
not shipped to consumers, then update the lockfile by running the project's
package manager install (e.g., npm/yarn/pnpm install) to reflect the change;
ensure the unique key "vitest" is removed from "dependencies" and added under
"devDependencies".

Comment thread .github/workflows/ci.yml Outdated
Comment thread examples/CRISP/circuits/lib/Nargo.toml
Comment thread examples/CRISP/packages/crisp-sdk/src/types.ts
Comment thread examples/CRISP/packages/crisp-sdk/src/utils.ts
Comment thread examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
cedoor and others added 3 commits February 23, 2026 17:51
- Updated @crisp-e3/sdk to 0.6.0
- Updated @crisp-e3/contracts to 0.6.0
- Updated @crisp-e3/zk-inputs to 0.6.0
- Published to npm
0xjei
0xjei previously approved these changes Feb 23, 2026
ctrlc03 and others added 8 commits February 23, 2026 17:51
- Use chain block timestamp in CountdownTime so countdown matches on-chain end
  (block.timestamp > end_time) instead of local Date.now()
- Compute voting window from RPC timestamp in CLI and HTTP init with 20s buffer
  for tx mining before window opens; window_end = start + e3_duration
- Replace chrono Utc::now() with get_current_timestamp_rpc() in rounds route
- Make E2E WAIT derive from E3_DURATION env var instead of hardcoded 280s
- Reduce E3_DURATION in .env.example from 250 to 80 for faster iteration
- Load server .env in crisp.spec.ts for dynamic config in tests
0xjei
0xjei previously approved these changes Feb 24, 2026
@0xjei

0xjei commented Feb 24, 2026

Copy link
Copy Markdown
Contributor

it passes 😭

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.

Split CRISP circuit into 3 or more circuits with recursion to reduce gate count

5 participants