Skip to content

feat: get previous ciphertext from server [skip-line-limit]#1109

Merged
ctrlc03 merged 19 commits into
mainfrom
chore/get-previous-ciphertext
Dec 19, 2025
Merged

feat: get previous ciphertext from server [skip-line-limit]#1109
ctrlc03 merged 19 commits into
mainfrom
chore/get-previous-ciphertext

Conversation

@ctrlc03

@ctrlc03 ctrlc03 commented Dec 15, 2025

Copy link
Copy Markdown
Collaborator

Get previous ciphertext from server and add masking specific logic

fix #1127 and #1091

Summary by CodeRabbit

  • New Features

    • Vote updating and masking flow with balance-based encoding, message-hash signing, and ZERO_VOTE support.
    • New server endpoints to check slot availability and fetch previous vote ciphertexts.
    • New CrispSDK with generateVoteProof/generateMaskVoteProof, encryptVote, public-key utilities, and streamlined proof generation.
  • Tests

    • Expanded test coverage for update/masking scenarios, slot-empty handling, and previous-ciphertext flows.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel

vercel Bot commented Dec 15, 2025

Copy link
Copy Markdown

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

2 Skipped Deployments
Project Deployment Review Updated (UTC)
crisp Skipped Skipped Dec 19, 2025 6:00pm
enclave-docs Skipped Skipped Dec 19, 2025 6:00pm

@coderabbitai

coderabbitai Bot commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds previous-ciphertext & slot-empty support and masking/update vote flows across CRISP: introduces a Vote type and ZERO_VOTE, moves per-vote message hashing/signing, exposes server endpoints, adds CrispSDK orchestrator that queries slot state and obtains or derives previous ciphertext, and extends ZK inputs (GRECO→BFV) with an update path.

Changes

Cohort / File(s) Summary
Client types & hooks
examples/CRISP/client/src/context/voteManagement/VoteManagement.types.ts, examples/CRISP/client/src/hooks/voting/useVoteCasting.ts, examples/CRISP/client/src/hooks/voting/useSDKWorker.tsx
Replaced voteId with e3Id, added Vote type import; updated generateProof and worker payloads to accept e3Id, vote, balance, messageHash, isMasking; updated casting flow to produce messageHash and pass new args.
Client model
examples/CRISP/client/src/model/vote.model.ts
Added Vote interface { yes: bigint; no: bigint }.
SDK constants & exports
examples/CRISP/packages/crisp-sdk/src/constants.ts, examples/CRISP/packages/crisp-sdk/src/index.ts
Added CRISP_SERVER_PREVIOUS_CIPHERTEXT_ENDPOINT, CRISP_SERVER_IS_SLOT_EMPTY_ENDPOINT, ZERO_VOTE; re-exported CrispSDK.
SDK types
examples/CRISP/packages/crisp-sdk/src/types.ts
Promoted previousCiphertext and messageHash to required, added isFirstVote, added MaskVoteProofRequest, VoteProofRequest, Signature; adjusted proof input shapes.
SDK core & API
examples/CRISP/packages/crisp-sdk/src/vote.ts, examples/CRISP/packages/crisp-sdk/src/sdk.ts, examples/CRISP/packages/crisp-sdk/src/state.ts
Added encryptVote, generatePublicKey; refactored circuit input flows to support first-vote vs update (generate_inputs_for_update), added generateMaskVoteProof, added CrispSDK class that queries getIsSlotEmpty/getPreviousCiphertext (or uses encrypted ZERO_VOTE) and delegates to proof generators.
Worker integration
examples/CRISP/client/libs/crispSDKWorker.js
Switched worker to use CrispSDK; updated message payload to include e3Id, vote, balance, slotAddress, signature, messageHash, isMasking; conditionally call generateMaskVoteProof or generateVoteProof.
Smart contract & EVM helpers
examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol, examples/CRISP/crates/evm_helpers/src/lib.rs
Added SlotIsEmpty() error and view functions getSlotIndex(uint256,address) and isSlotEmptyByAddress(uint256,address); generalized CRISPContract<P> to support read-only provider, added new_read_only, read-only accessors (get_slot_index_from_address, get_is_slot_empty_by_address) and address() accessor.
ZK inputs & WASM
examples/CRISP/crates/zk-inputs/src/lib.rs, examples/CRISP/crates/zk-inputs-wasm/src/lib.rs
Added generate_inputs_for_update that decodes GRECO prior-ciphertext, converts to BFV, computes ciphertext addition with new vote, builds CRISP ZK inputs JSON; exposed corresponding WASM binding.
Server models, repo & routes
examples/CRISP/server/src/server/models.rs, examples/CRISP/server/src/server/repo.rs, examples/CRISP/server/src/server/routes/state.rs
Added request/response models for previous-ciphertext and is-slot-empty; repo method get_ciphertext_input(index); new /state/previous-ciphertext and /state/is-slot-empty routes that query contract and storage and return previous ciphertext or slot-empty status.
Tests & test scaffolding
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts, examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts, examples/CRISP/packages/crisp-sdk/tests/state.test.ts, examples/CRISP/packages/crisp-sdk/tests/token.test.ts
Updated tests to use ZERO_VOTE, derive previousCiphertext via encryptVote, added mocks for previous-ciphertext and slot-empty endpoints, switched some fetch mocks to mockResolvedValueOnce, and extended proof input expectations (isFirstVote, previousCiphertext, slotAddress).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (Hook / Worker)
    participant SDK as CrispSDK
    participant Server as State API
    participant Contract as CRISPProgram (on-chain)
    participant ZK as ZK Inputs & Proof

    Client->>+SDK: request generateVoteProof / generateMaskVoteProof (vote, e3Id, publicKey, slotAddress...)
    SDK->>+Server: POST getIsSlotEmpty(e3Id, slotAddress)
    Server->>+Contract: isSlotEmptyByAddress(e3Id, slotAddress)
    Contract-->>-Server: is_empty
    Server-->>-SDK: is_empty

    alt slot not empty
        SDK->>+Server: POST getPreviousCiphertext(e3Id, slotAddress)
        Server->>Server: repo.get_ciphertext_input(index)
        Server-->>-SDK: previousCiphertext
        SDK->>SDK: isFirstVote = false
    else slot empty
        SDK->>SDK: previousCiphertext = encryptVote(ZERO_VOTE, publicKey)
        SDK->>SDK: isFirstVote = true
    end

    SDK->>+ZK: generate_inputs_for_update(...) or generate_inputs(..., previousCiphertext, isFirstVote)
    ZK->>ZK: build inputs (GRECO decode, BFV convert, ciphertext add)
    ZK->>ZK: executeCircuit() / generate proof
    ZK-->>-SDK: ProofData
    SDK-->>-Client: ProofData
    Client->>Client: broadcast proof on-chain
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay extra attention to:
    • CrispSDK orchestration (packages/crisp-sdk/src/sdk.ts) — branching, error handling, previousCiphertext derivation.
    • GRECO→BFV update path & WASM binding (crates/zk-inputs/src/lib.rs, crates/zk-inputs-wasm/src/lib.rs) — ABI decoding and ciphertext arithmetic correctness.
    • Contract read-only provider changes (crates/evm_helpers/src/lib.rs) — generics and read-only constructor.
    • Server routes and repo lookup (server/src/server/routes/state.rs, server/src/server/repo.rs) — address parsing and index resolution.
    • Client signing/messageHash changes (useVoteCasting.ts, worker files) — signature vs hashed-message handling.

Possibly related PRs

Suggested reviewers

  • cedoor
  • 0xjei
  • hmzakhalid

Poem

🐇 I hopped through bytes and GRECO fields,

fetched old ciphers from hidden shields,
I hashed each vote, then tucked it tight,
the SDK guides the masked update right—
a rabbit's patch, swift in code delights.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: get previous ciphertext from server' accurately describes the main feature added—fetching previous ciphertext from server endpoints.
Linked Issues check ✅ Passed The PR addresses issue #1127 (CRISP support vote update) by adding masking logic, server endpoints for previous ciphertext and slot-empty checks, and updating vote proof generation to support vote updates.
Out of Scope Changes check ✅ Passed All changes are within scope: server endpoints for ciphertext retrieval, SDK updates for masking/vote updates, contract view functions, and Rust helper methods. No unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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 chore/get-previous-ciphertext

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.

@ctrlc03 ctrlc03 force-pushed the chore/get-previous-ciphertext branch from 744a618 to 55c02a4 Compare December 15, 2025 10:00
@vercel vercel Bot temporarily deployed to Preview – enclave-docs December 15, 2025 10:00 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs December 15, 2025 14:12 Inactive
@ctrlc03 ctrlc03 marked this pull request as ready for review December 15, 2025 14:13
@ctrlc03 ctrlc03 requested a review from cedoor December 15, 2025 14:13
@ctrlc03 ctrlc03 enabled auto-merge (squash) December 15, 2025 14:15

@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

🧹 Nitpick comments (6)
examples/CRISP/crates/zk-inputs/src/lib.rs (2)

97-115: Minor: Duplicate GrecoBounds::compute calls.

GrecoBounds::compute is invoked twice: once at line 97 (discarding crypto_params) and again at line 114. Consider computing once and reusing both values:

-        let (_, bounds) = GrecoBounds::compute(&self.bfv_params, 0)?;
-
-        let bit_pk = shared::template::calculate_bit_width(&bounds.pk_bounds[0].to_string())?;
-
-        // Compute the vectors of the GRECO inputs.
-        let greco_vectors = GrecoVectors::compute(
-            ...
-        )
-        .with_context(|| "Failed to compute vectors")?;
-
-        let (crypto_params, bounds) = GrecoBounds::compute(&self.bfv_params, 0)
-            .with_context(|| "Failed to compute bounds")?;
+        let (crypto_params, bounds) = GrecoBounds::compute(&self.bfv_params, 0)
+            .with_context(|| "Failed to compute bounds")?;
+
+        let bit_pk = shared::template::calculate_bit_width(&bounds.pk_bounds[0].to_string())?;
+
+        // Compute the vectors of the GRECO inputs.
+        let greco_vectors = GrecoVectors::compute(
+            ...
+        )
+        .with_context(|| "Failed to compute vectors")?;

Note: The same duplication exists in generate_inputs (lines 169 and 186-187).


78-139: Add test coverage for generate_inputs_for_masking.

The new method uses a different code path than generate_inputs, specifically the GRECO ciphertext decoding and conversion (abi_decode_greco_ciphertext and greco_to_bfv_ciphertext). The existing tests only cover generate_inputs. Add tests to validate the GRECO decoding and conversion functionality in generate_inputs_for_masking.

examples/CRISP/server/src/server/routes/state.rs (1)

74-84: Potential panic on slot index conversion.

The conversion .to::<u64>() on a U256 value will panic if the value exceeds u64::MAX. While slot indices are unlikely to reach such values in practice, using a safer conversion would be more robust.

Consider using a checked conversion:

     let slot_index = match contract
         .get_slot_index_from_address(U256::from(incoming.round_id), address)
         .await
     {
-        Ok(index) => index.to::<u64>(),
+        Ok(index) => {
+            if index > U256::from(u64::MAX) {
+                error!("Slot index exceeds u64 range: {:?}", index);
+                return HttpResponse::InternalServerError()
+                    .body("Slot index out of range");
+            }
+            index.to::<u64>()
+        }
         Err(e) => {
             error!("Error getting slot index from address: {:?}", e);
             return HttpResponse::InternalServerError()
                 .body("Failed to get slot index from address");
         }
     };
examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (1)

149-166: Redundant optional chaining and unclear error handling for vote updates.

Two concerns with this block:

  1. Line 156 has redundant optional chaining: previousCiphertextFromServer && previousCiphertextFromServer?.ciphertext - the ?. is unnecessary since the left side of && already ensures the value is truthy.

  2. If isVoteUpdate is true but getPreviousCiphertext fails (returns undefined), the vote proceeds without the previous ciphertext. Should this be an error condition for vote updates?

         const previousCiphertextFromServer = isVoteUpdate
           ? await getPreviousCiphertext({
               round_id: roundState.id,
               address: user.address,
             })
           : undefined

-        const previousCiphertext = previousCiphertextFromServer && previousCiphertextFromServer?.ciphertext
+        const previousCiphertext = previousCiphertextFromServer?.ciphertext
+
+        if (isVoteUpdate && !previousCiphertext) {
+          throw new Error('Failed to retrieve previous vote data for update.')
+        }

         const encodedProof = await handleProofGeneration(
examples/CRISP/packages/crisp-sdk/src/vote.ts (2)

104-111: Clarify placeholder ciphertext semantics and isFirstVote coupling

You now always pass encryptVote(zeroVote, proofInputs.publicKey) into generateInputs and set crispInputs.is_first_vote = proofInputs.isFirstVote. The comment still talks about “previous ciphertext is not provided (is_first_vote is true)”, but there’s no previous-ciphertext wiring in this path anymore.

I’d suggest:

  • Updating the comment to explicitly describe that normal-vote proofs always start from a zero-vote ciphertext and rely purely on isFirstVote for circuit branching.
  • Double‑checking that all call sites reliably set isFirstVote in line with how the circuit interprets is_first_vote, since it’s no longer derived from data availability.

Also applies to: 124-128


133-157: Tighten masking invariants around previousCiphertext and document fixed-signature / Merkle placeholders

Nice consolidation of the masking inputs. A couple of robustness points to consider:

  • For non‑first votes, silently defaulting to encryptVote(zeroVote, publicKey) when previousCiphertext is missing could make upstream bugs harder to spot. It may be safer to derive the base ciphertext from isFirstVote and throw if !isFirstVote && !previousCiphertext:
-  let crispInputs = await zkInputsGenerator.generateInputsForMasking(
-    proofInputs.previousCiphertext || encryptVote(zeroVote, proofInputs.publicKey),
-    proofInputs.publicKey,
-    encodedVote,
-  )
+  const baseCiphertext =
+    proofInputs.isFirstVote
+      ? encryptVote(zeroVote, proofInputs.publicKey)
+      : proofInputs.previousCiphertext
+
+  if (!baseCiphertext) {
+    throw new Error('Masking proof for non-first vote requires previousCiphertext')
+  }
+
+  let crispInputs = await zkInputsGenerator.generateInputsForMasking(
+    baseCiphertext,
+    proofInputs.publicKey,
+    encodedVote,
+  )
  • You hard‑code merkle_proof_length = '0' and zero‑fill merkle_proof_indices/siblings to MERKLE_TREE_MAX_DEPTH. It would be good to confirm that this depth matches the circuit’s compiled constant and that “no Merkle proof” is indeed represented exactly this way.

  • Using fixed MASK_SIGNATURE / SIGNATURE_MESSAGE_HASH while taking slot_address and balance from proofInputs decouples masking proofs from the user’s actual signature. Please confirm that the circuit / contract logic for masking flows is intentionally keyed off these constants (and/or other inputs) and does not expect a per-user signature as in the normal vote path.

📜 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 ca8f01d and dbe457f.

📒 Files selected for processing (16)
  • examples/CRISP/client/libs/crispWorker.js (2 hunks)
  • examples/CRISP/client/src/context/voteManagement/VoteManagement.types.ts (1 hunks)
  • examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts (3 hunks)
  • examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (6 hunks)
  • examples/CRISP/client/src/hooks/wasm/useWebAssembly.tsx (2 hunks)
  • examples/CRISP/client/src/model/vote.model.ts (1 hunks)
  • examples/CRISP/crates/evm_helpers/src/lib.rs (4 hunks)
  • examples/CRISP/crates/zk-inputs-wasm/src/lib.rs (2 hunks)
  • examples/CRISP/crates/zk-inputs/src/lib.rs (2 hunks)
  • examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/constants.ts (1 hunks)
  • examples/CRISP/packages/crisp-sdk/src/types.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/vote.ts (5 hunks)
  • examples/CRISP/server/src/server/models.rs (1 hunks)
  • examples/CRISP/server/src/server/repo.rs (1 hunks)
  • examples/CRISP/server/src/server/routes/state.rs (2 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2024-09-26T03:11:29.311Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 107
File: packages/ciphernode/sortition/src/distance.rs:1-1
Timestamp: 2024-09-26T03:11:29.311Z
Learning: In `packages/ciphernode/core/src/events.rs`, the import statements use the correct and updated `alloy::primitives` module.

Applied to files:

  • examples/CRISP/server/src/server/models.rs
📚 Learning: 2024-10-22T03:30:45.478Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/data/src/repository.rs:58-60
Timestamp: 2024-10-22T03:30:45.478Z
Learning: In `packages/ciphernode/data/src/repository.rs`, the `write` method in `Repository<T>` must be synchronous and should not return errors.

Applied to files:

  • examples/CRISP/server/src/server/repo.rs
📚 Learning: 2025-11-12T10:08:30.720Z
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 996
File: examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol:144-169
Timestamp: 2025-11-12T10:08:30.720Z
Learning: In the CRISP program (examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol), there are plans to move the merkle tree from the Enclave contract to the program contract itself in the future, which would allow direct calls to validateInput to be stored in the merkle tree.

Applied to files:

  • examples/CRISP/crates/evm_helpers/src/lib.rs
📚 Learning: 2024-10-22T03:17:41.617Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/router/src/hooks.rs:0-0
Timestamp: 2024-10-22T03:17:41.617Z
Learning: In `packages/ciphernode/router/src/hooks.rs`, in the `hydrate` methods of the `E3Feature` implementations, it's acceptable to return `Ok(())` when dependencies are missing, as the error is reported to the bus.

Applied to files:

  • examples/CRISP/server/src/server/routes/state.rs
📚 Learning: 2024-11-05T06:48:58.177Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 173
File: packages/ciphernode/config/src/app_config.rs:13-21
Timestamp: 2024-11-05T06:48:58.177Z
Learning: In the `packages/ciphernode/config/src/app_config.rs` file, for the `Contract` enum, the team prefers to use `String` type for `address` fields, relying on parsing to handle validation, instead of using the `Address` type.

Applied to files:

  • examples/CRISP/server/src/server/routes/state.rs
🧬 Code graph analysis (7)
examples/CRISP/crates/zk-inputs-wasm/src/lib.rs (1)
examples/CRISP/crates/zk-inputs/src/lib.rs (1)
  • generate_inputs_for_masking (78-139)
examples/CRISP/crates/zk-inputs/src/lib.rs (2)
crates/bfv-helpers/src/utils/greco.rs (2)
  • abi_decode_greco_ciphertext (125-183)
  • greco_to_bfv_ciphertext (68-103)
examples/CRISP/crates/zk-inputs/src/serialization.rs (2)
  • construct_inputs (60-261)
  • serialize_inputs_to_json (270-272)
examples/CRISP/client/libs/crispWorker.js (3)
examples/CRISP/packages/crisp-sdk/src/index.ts (3)
  • hashLeaf (10-10)
  • generateMaskVoteProof (14-14)
  • generateVoteProof (13-13)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
  • hashLeaf (20-22)
examples/CRISP/packages/crisp-sdk/src/vote.ts (2)
  • generateMaskVoteProof (203-209)
  • generateVoteProof (176-201)
examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts (2)
examples/CRISP/client/src/hooks/generic/useFetchApi.tsx (1)
  • useApi (11-33)
examples/CRISP/client/src/model/vote.model.ts (2)
  • GetPreviousCiphertextRequest (67-70)
  • GetPreviousCiphertextResponse (72-74)
examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (3)
examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts (1)
  • useEnclaveServer (35-58)
examples/CRISP/client/src/model/poll.model.ts (1)
  • Poll (32-36)
packages/enclave-sdk/src/index.ts (1)
  • generateProof (69-69)
examples/CRISP/server/src/server/routes/state.rs (2)
examples/CRISP/crates/evm_helpers/src/lib.rs (2)
  • create_read (154-159)
  • address (136-138)
crates/config/src/app_config.rs (1)
  • address (298-300)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)
examples/CRISP/packages/crisp-sdk/src/index.ts (1)
  • MaskVoteProofInputs (21-21)
⏰ 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). (9)
  • GitHub Check: crisp_unit
  • GitHub Check: integration_prebuild
  • GitHub Check: build_sdk
  • GitHub Check: test_net
  • GitHub Check: build_enclave_cli
  • GitHub Check: rust_unit
  • GitHub Check: rust_integration
  • GitHub Check: build_ciphernode_image
  • GitHub Check: Build & Push Image
🔇 Additional comments (18)
examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol (1)

240-251: LGTM!

The getSlotIndex function correctly implements the inverse of the storage logic in _processVote, properly handling the storedIndexPlusOne - 1 offset pattern. The SlotIsEmpty error provides clear feedback when querying non-existent slots.

examples/CRISP/crates/zk-inputs-wasm/src/lib.rs (1)

75-98: LGTM!

The WASM binding correctly mirrors the pattern of generateInputs, delegating to the core generate_inputs_for_masking method with appropriate type conversions and error handling.

examples/CRISP/crates/evm_helpers/src/lib.rs (1)

37-45: LGTM!

The generic refactoring cleanly separates read-only and write capabilities. The type aliases and separate impl blocks provide a clear API boundary while the generic address() method remains available for both variants.

Also applies to: 62-65, 106-116, 134-139

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

37-38: LGTM!

The zeroVote constant provides a clean baseline for masking operations, consistent with the BigInt usage pattern established by MAXIMUM_VOTE_VALUE.

examples/CRISP/client/libs/crispWorker.js (1)

14-14: Rename from address to slotAddress aligns with SDK usage.

The destructuring correctly renames address to slotAddress and adds the new previousCiphertext, isFirstVote, and isMasking fields needed for the branching proof generation logic.

examples/CRISP/client/src/model/vote.model.ts (1)

67-74: New interfaces for previous ciphertext retrieval look correct.

The GetPreviousCiphertextRequest and GetPreviousCiphertextResponse interfaces follow the existing naming conventions and field patterns used in this file. Using Uint8Array for ciphertext is appropriate for binary data on the client side.

examples/CRISP/server/src/server/models.rs (1)

108-117: New request/response structs align with client-side types.

The PreviousCiphertextRequest and PreviousCiphertextResponse structs correctly mirror the client-side TypeScript interfaces. The response struct only deriving Serialize (not Deserialize) is appropriate for a response-only type.

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

180-180: Addition of isFirstVote to ProofInputs is consistent with the proof generation flow.

The isFirstVote boolean enables the circuit to distinguish between first votes and subsequent votes, supporting the masking functionality.


183-190: MaskVoteProofInputs updated with isFirstVote and merkleRoot.

Adding isFirstVote and merkleRoot to MaskVoteProofInputs aligns with the circuit input requirements for the masking proof pathway. The merkleRoot as a bigint is appropriate for cryptographic use.


192-201: VoteProofInputs now includes isFirstVote.

The addition maintains consistency with the other proof input types and enables proper circuit behavior for both first and subsequent votes.

examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts (1)

45-46: LGTM!

The new getPreviousCiphertext function follows the established patterns in this hook, correctly using the typed request/response interfaces and POST method consistent with other endpoints.

examples/CRISP/client/src/context/voteManagement/VoteManagement.types.ts (1)

40-49: LGTM!

The updated generateProof signature correctly places the new required boolean parameters (isFirstVote, isMasking) before the optional previousCiphertext parameter, maintaining proper TypeScript conventions.

examples/CRISP/client/src/hooks/wasm/useWebAssembly.tsx (1)

26-46: LGTM!

The updated generateProof function signature and worker message payload correctly include the new isFirstVote and isMasking parameters, maintaining consistency with the type definition and enabling the worker to conditionally select the appropriate proof generation path.

examples/CRISP/server/src/server/routes/state.rs (1)

42-98: LGTM on overall structure and error handling.

The endpoint implementation correctly validates input, handles errors with appropriate HTTP status codes (400 for bad input, 404 for missing data, 500 for internal errors), and follows the patterns established by other handlers in this file.

examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (2)

72-95: LGTM on handleProofGeneration signature update.

The callback correctly propagates isFirstVote and isMasking parameters to generateProof, maintaining consistency with the updated type definition.


243-256: LGTM on dependency array update.

The getPreviousCiphertext function is correctly added to the useCallback dependency array since it's used within castVoteWithProof.

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

10-17: New constants import is consistent with usages

All imported constants (MAXIMUM_VOTE_VALUE, HALF_LARGEST_MINIMUM_DEGREE, MASK_SIGNATURE, zeroVote, SIGNATURE_MESSAGE_HASH, MERKLE_TREE_MAX_DEPTH) are used below and match the surrounding patterns; no issues here.


203-208: Refactor of generateMaskVoteProof looks good

Delegating to generateCircuitInputsForMasking keeps all masking‑specific input wiring in one place and avoids duplicating constants / Merkle placeholder setup. This improves maintainability of the masking path.

Comment thread examples/CRISP/client/libs/crispSDKWorker.js
Comment thread examples/CRISP/crates/evm_helpers/src/lib.rs Outdated
Comment thread examples/CRISP/server/src/server/repo.rs Outdated
@ctrlc03 ctrlc03 marked this pull request as draft December 15, 2025 15:31
auto-merge was automatically disabled December 15, 2025 15:31

Pull request was converted to draft

@vercel vercel Bot temporarily deployed to Preview – enclave-docs December 15, 2025 15:37 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp December 15, 2025 15:37 Inactive
@ctrlc03 ctrlc03 force-pushed the chore/get-previous-ciphertext branch from df1962c to de8e904 Compare December 17, 2025 16:44
@vercel vercel Bot temporarily deployed to Preview – enclave-docs December 17, 2025 16:44 Inactive
@ctrlc03 ctrlc03 force-pushed the chore/get-previous-ciphertext branch from de8e904 to 26ade67 Compare December 18, 2025 18:07
@vercel vercel Bot temporarily deployed to Preview – enclave-docs December 18, 2025 18:08 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs December 19, 2025 10:58 Inactive
@ctrlc03 ctrlc03 force-pushed the chore/get-previous-ciphertext branch from af323a8 to e569105 Compare December 19, 2025 11:13
@vercel vercel Bot temporarily deployed to Preview – enclave-docs December 19, 2025 11:13 Inactive
@ctrlc03 ctrlc03 marked this pull request as ready for review December 19, 2025 11:13
@ctrlc03 ctrlc03 changed the title feat: get previous ciphertext from server feat: get previous ciphertext from server [skip-line-limit] Dec 19, 2025

@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

🧹 Nitpick comments (7)
examples/CRISP/crates/zk-inputs-wasm/src/lib.rs (1)

75-98: LGTM! New WASM binding for update input generation.

The generate_inputs_for_update method correctly mirrors the existing generate_inputs pattern and properly delegates to the core generator. The vote conversion from Vec<i64> to Vec<u64> is consistent with the existing implementation.

Consider adding a WASM test case for this new method similar to test_js_inputs_generation_with_defaults to verify the end-to-end flow with GRECO-encoded ciphertext.

examples/CRISP/crates/zk-inputs/src/lib.rs (1)

97-115: Redundant GrecoBounds::compute call.

GrecoBounds::compute is invoked twice with identical parameters (lines 97 and 114). The first call extracts only bounds for bit width calculation, but then both crypto_params and bounds are recomputed on line 114.

Consider computing once and reusing:

🔎 Suggested refactor
-        let (_, bounds) = GrecoBounds::compute(&self.bfv_params, 0)?;
-
-        let bit_pk = shared::template::calculate_bit_width(&bounds.pk_bounds[0].to_string())?;
-
-        // ... greco_vectors computation ...
-
-        let (crypto_params, bounds) = GrecoBounds::compute(&self.bfv_params, 0)
-            .with_context(|| "Failed to compute bounds")?;
+        let (crypto_params, bounds) = GrecoBounds::compute(&self.bfv_params, 0)
+            .with_context(|| "Failed to compute bounds")?;
+
+        let bit_pk = shared::template::calculate_bit_width(&bounds.pk_bounds[0].to_string())?;
examples/CRISP/client/libs/crispSDKWorker.js (1)

16-21: Note: Hardcoded merkle leaves remain.

The TODO comment indicates this is known technical debt. The first leaf correctly uses the dynamic slotAddress and balance, which is an improvement.

Would you like me to open an issue to track implementing server-side merkle leaf retrieval?

examples/CRISP/server/src/server/routes/state.rs (1)

54-71: Consider extracting common helper for contract creation and address parsing.

Both handle_get_previous_ciphertext and handle_is_slot_empty duplicate the contract creation and address parsing logic. Extracting these into helper functions would reduce duplication.

🔎 Suggested helpers
async fn get_crisp_contract() -> Result<CRISPContract<CRISPReadProvider>, HttpResponse> {
    CRISPContractFactory::create_read(&CONFIG.http_rpc_url, &CONFIG.e3_program_address)
        .await
        .map_err(|e| {
            error!("Failed to create CRISP contract: {:?}", e);
            HttpResponse::InternalServerError().body("Failed to create CRISP contract")
        })
}

fn parse_address(address_str: &str) -> Result<Address, HttpResponse> {
    Address::from_str(address_str).map_err(|e| {
        error!("Invalid address format: {:?}", e);
        HttpResponse::BadRequest().body("Invalid address format")
    })
}

Also applies to: 291-308

examples/CRISP/client/src/hooks/voting/useSDKWorker.tsx (1)

12-12: Consider validating ENCLAVE_API is defined.

If VITE_ENCLAVE_API is not set in the environment, ENCLAVE_API will be undefined, and server calls in the worker will fail with unclear errors.

🔎 Proposed validation
-const ENCLAVE_API = import.meta.env.VITE_ENCLAVE_API
+const ENCLAVE_API = import.meta.env.VITE_ENCLAVE_API
+if (!ENCLAVE_API) {
+  console.warn('VITE_ENCLAVE_API environment variable is not set')
+}
examples/CRISP/crates/evm_helpers/src/lib.rs (2)

120-131: Consider enriching error context for better diagnostics.

The current error handling preserves the underlying error, which addresses the past review concern about swallowing errors. However, adding context via .wrap_err() would provide clearer diagnostic information about which operation failed and with what parameters.

🔎 Optional enhancement with error context
-        match contract.getSlotIndex(e3_id, slot_address).call().await {
-            Ok(slot_index) => Ok(slot_index),
-            Err(e) => Err(eyre::eyre!("Failed to get slot index: {}", e)),
-        }
+        contract
+            .getSlotIndex(e3_id, slot_address)
+            .call()
+            .await
+            .wrap_err_with(|| format!(
+                "Failed to get slot index for e3_id={} slot_address={}",
+                e3_id, slot_address
+            ))

This provides richer context including the parameters that caused the failure, which aids debugging when this method is called from server endpoints.

Based on past review.


134-145: Consider adding error context for consistency.

Similar to get_slot_index_from_address, adding context via .wrap_err_with() would improve debuggability by including the parameters that caused the failure.

🔎 Optional enhancement with error context
-        match contract.isSlotEmptyByAddress(e3_id, slot_address).call().await {
-            Ok(is_empty) => Ok(is_empty),
-            Err(e) => Err(eyre::eyre!("Failed to check if slot is empty: {}", e)),
-        }
+        contract
+            .isSlotEmptyByAddress(e3_id, slot_address)
+            .call()
+            .await
+            .wrap_err_with(|| format!(
+                "Failed to check if slot is empty for e3_id={} slot_address={}",
+                e3_id, slot_address
+            ))
📜 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 dbe457f and e569105.

📒 Files selected for processing (17)
  • examples/CRISP/client/libs/crispSDKWorker.js (1 hunks)
  • examples/CRISP/client/src/context/voteManagement/VoteManagement.types.ts (2 hunks)
  • examples/CRISP/client/src/hooks/voting/useSDKWorker.tsx (3 hunks)
  • examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (5 hunks)
  • examples/CRISP/client/src/model/vote.model.ts (1 hunks)
  • examples/CRISP/crates/evm_helpers/src/lib.rs (4 hunks)
  • examples/CRISP/crates/zk-inputs-wasm/src/lib.rs (2 hunks)
  • examples/CRISP/crates/zk-inputs/src/lib.rs (2 hunks)
  • examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/constants.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/state.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/types.ts (1 hunks)
  • examples/CRISP/packages/crisp-sdk/src/vote.ts (5 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (9 hunks)
  • examples/CRISP/server/src/server/models.rs (1 hunks)
  • examples/CRISP/server/src/server/repo.rs (1 hunks)
  • examples/CRISP/server/src/server/routes/state.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • examples/CRISP/client/src/model/vote.model.ts
  • examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol
  • examples/CRISP/server/src/server/repo.rs
  • examples/CRISP/server/src/server/models.rs
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2024-10-22T03:17:41.617Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/router/src/hooks.rs:0-0
Timestamp: 2024-10-22T03:17:41.617Z
Learning: In `packages/ciphernode/router/src/hooks.rs`, in the `hydrate` methods of the `E3Feature` implementations, it's acceptable to return `Ok(())` when dependencies are missing, as the error is reported to the bus.

Applied to files:

  • examples/CRISP/server/src/server/routes/state.rs
  • examples/CRISP/crates/evm_helpers/src/lib.rs
📚 Learning: 2024-11-05T06:48:58.177Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 173
File: packages/ciphernode/config/src/app_config.rs:13-21
Timestamp: 2024-11-05T06:48:58.177Z
Learning: In the `packages/ciphernode/config/src/app_config.rs` file, for the `Contract` enum, the team prefers to use `String` type for `address` fields, relying on parsing to handle validation, instead of using the `Address` type.

Applied to files:

  • examples/CRISP/server/src/server/routes/state.rs
📚 Learning: 2024-10-22T03:47:04.014Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/router/src/e3_request_router.rs:202-235
Timestamp: 2024-10-22T03:47:04.014Z
Learning: In `packages/ciphernode/router/src/e3_request_router.rs`, within the `E3RequestRouter::from_snapshot` method, errors during context restoration propagate upwards due to the `?` operator, and skipping contexts when `repositories.context(&e3_id).read().await?` returns `Ok(None)` is acceptable, as missing snapshots are expected.

Applied to files:

  • examples/CRISP/crates/evm_helpers/src/lib.rs
📚 Learning: 2024-10-12T10:24:07.572Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 139
File: packages/ciphernode/evm/src/ciphernode_registry_sol.rs:133-143
Timestamp: 2024-10-12T10:24:07.572Z
Learning: In `packages/ciphernode/evm/src/ciphernode_registry_sol.rs`, the function `helpers::stream_from_evm` in Rust returns `()`, not a `Result`, so error handling with `if let Err(e) = ...` is not applicable.

Applied to files:

  • examples/CRISP/crates/evm_helpers/src/lib.rs
📚 Learning: 2024-11-05T06:49:46.285Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 173
File: packages/ciphernode/enclave_node/src/datastore.rs:14-16
Timestamp: 2024-11-05T06:49:46.285Z
Learning: In `packages/ciphernode/enclave_node/src/datastore.rs`, for internal functions like `get_in_mem_store`, adding extensive documentation and error handling may not be necessary if they are not client-facing.

Applied to files:

  • examples/CRISP/crates/evm_helpers/src/lib.rs
📚 Learning: 2025-11-12T10:08:30.720Z
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 996
File: examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol:144-169
Timestamp: 2025-11-12T10:08:30.720Z
Learning: In the CRISP program (examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol), there are plans to move the merkle tree from the Enclave contract to the program contract itself in the future, which would allow direct calls to validateInput to be stored in the merkle tree.

Applied to files:

  • examples/CRISP/crates/evm_helpers/src/lib.rs
🧬 Code graph analysis (11)
examples/CRISP/packages/crisp-sdk/src/state.ts (1)
examples/CRISP/packages/crisp-sdk/src/constants.ts (2)
  • CRISP_SERVER_PREVIOUS_CIPHERTEXT_ENDPOINT (11-11)
  • CRISP_SERVER_IS_SLOT_EMPTY_ENDPOINT (12-12)
examples/CRISP/crates/zk-inputs-wasm/src/lib.rs (1)
examples/CRISP/crates/zk-inputs/src/lib.rs (1)
  • generate_inputs_for_update (78-139)
examples/CRISP/client/src/context/voteManagement/VoteManagement.types.ts (3)
examples/CRISP/client/src/model/vote.model.ts (1)
  • Vote (59-62)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)
  • Vote (70-79)
examples/CRISP/packages/crisp-sdk/src/index.ts (1)
  • Vote (21-21)
examples/CRISP/server/src/server/routes/state.rs (2)
examples/CRISP/crates/evm_helpers/src/lib.rs (2)
  • create_read (168-173)
  • address (150-152)
crates/config/src/app_config.rs (1)
  • address (298-300)
examples/CRISP/crates/zk-inputs/src/lib.rs (2)
crates/bfv-helpers/src/utils/greco.rs (2)
  • abi_decode_greco_ciphertext (125-183)
  • greco_to_bfv_ciphertext (68-103)
examples/CRISP/crates/zk-inputs/src/serialization.rs (2)
  • construct_inputs (60-261)
  • serialize_inputs_to_json (270-272)
examples/CRISP/client/src/hooks/voting/useSDKWorker.tsx (2)
examples/CRISP/client/src/model/vote.model.ts (1)
  • Vote (59-62)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)
  • Vote (70-79)
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (7)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)
  • Signature (204-209)
examples/CRISP/packages/crisp-sdk/src/constants.ts (3)
  • SIGNATURE_MESSAGE (33-33)
  • SIGNATURE_MESSAGE_HASH (34-34)
  • zeroVote (40-40)
examples/CRISP/packages/crisp-sdk/src/index.ts (5)
  • SIGNATURE_MESSAGE (9-9)
  • SIGNATURE_MESSAGE_HASH (9-9)
  • encryptVote (17-17)
  • generateVoteProof (13-13)
  • generateMaskVoteProof (14-14)
examples/CRISP/packages/crisp-sdk/tests/constants.ts (3)
  • ECDSA_PRIVATE_KEY (8-8)
  • LEAVES (9-17)
  • CRISP_SERVER_URL (7-7)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
  • extractSignatureComponents (89-118)
examples/CRISP/packages/crisp-sdk/src/vote.ts (4)
  • encryptVote (126-130)
  • generateCircuitInputs (146-180)
  • generateVoteProof (214-255)
  • generateMaskVoteProof (262-292)
examples/CRISP/crates/evm_helpers/src/lib.rs (1)
  • address (150-152)
examples/CRISP/client/libs/crispSDKWorker.js (2)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
  • hashLeaf (20-22)
examples/CRISP/packages/crisp-sdk/src/vote.ts (2)
  • generateMaskVoteProof (262-292)
  • generateVoteProof (214-255)
examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (5)
examples/CRISP/client/src/model/vote.model.ts (1)
  • Vote (59-62)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)
  • Vote (70-79)
examples/CRISP/packages/crisp-sdk/src/vote.ts (1)
  • generateProof (198-207)
packages/enclave-sdk/src/greco.ts (1)
  • generateProof (122-159)
examples/CRISP/client/src/model/poll.model.ts (1)
  • Poll (32-36)
examples/CRISP/packages/crisp-sdk/src/vote.ts (4)
examples/CRISP/packages/crisp-sdk/src/types.ts (2)
  • Vote (70-79)
  • ProofInputs (172-181)
examples/CRISP/packages/crisp-sdk/src/constants.ts (3)
  • zeroVote (40-40)
  • MASK_SIGNATURE (37-38)
  • SIGNATURE_MESSAGE_HASH (34-34)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
  • extractSignatureComponents (89-118)
examples/CRISP/packages/crisp-sdk/src/state.ts (2)
  • getIsSlotEmpty (93-109)
  • getPreviousCiphertext (68-84)
examples/CRISP/packages/crisp-sdk/src/types.ts (2)
examples/CRISP/client/src/model/vote.model.ts (1)
  • Vote (59-62)
examples/CRISP/packages/crisp-sdk/src/index.ts (3)
  • Vote (21-21)
  • MaskVoteProofInputs (21-21)
  • VoteProofInputs (21-21)
⏰ 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_sdk
  • GitHub Check: build_crisp_sdk
  • GitHub Check: integration_prebuild
  • GitHub Check: rust_unit
  • GitHub Check: rust_integration
  • GitHub Check: test_net
  • GitHub Check: Build & Push Image
🔇 Additional comments (24)
examples/CRISP/packages/crisp-sdk/src/constants.ts (1)

11-12: LGTM!

The new endpoint constants and zeroVote follow existing conventions. The zeroVote correctly uses bigint values consistent with the SDK's Vote type definition.

Also applies to: 39-40

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

68-84: Good addition of error handling for HTTP responses.

The new functions properly check response.ok before processing, which is an improvement over getRoundDetails. The consistent request structure (POST with JSON body) aligns well with existing patterns.

Also applies to: 93-109

examples/CRISP/client/libs/crispSDKWorker.js (1)

25-46: LGTM! Clean separation of masking vs regular vote proof generation.

The conditional logic correctly routes to generateMaskVoteProof or generateVoteProof based on the isMasking flag. The SDK functions now handle previousCiphertext fetching internally (via getIsSlotEmpty and getPreviousCiphertext), which addresses the previous review concern about undefined previousCiphertext validation—this is no longer the worker's responsibility.

examples/CRISP/server/src/server/routes/state.rs (2)

48-97: LGTM! Well-structured endpoint with proper error handling.

The handle_get_previous_ciphertext endpoint correctly:

  1. Creates a read-only contract instance
  2. Validates the address format
  3. Resolves the slot index from address
  4. Fetches the ciphertext from storage with appropriate 404 handling

310-322: LGTM! Clean implementation for slot-empty check.

The handle_is_slot_empty endpoint properly queries the contract and returns the result with consistent error handling.

examples/CRISP/client/src/context/voteManagement/VoteManagement.types.ts (2)

40-49: Signature updated to support server-driven masking flow.

The updated generateProof signature properly reflects the new architecture where:

  • previousCiphertext is removed (now fetched internally by SDK)
  • isMasking flag enables conditional proof generation
  • vote and balance are passed for proof inputs

Note: The voteId type change from bigint to number is a breaking change for existing callers.


9-9: Type mismatch: client Vote uses number, SDK Vote uses bigint.

The client's Vote type (from @/model/vote.model) has { yes: number, no: number }, but the SDK expects { yes: bigint, no: bigint }. Additionally, balance is passed as number instead of bigint to SDK functions. BigInt values cannot be mixed with Number values in operations; they must be coerced to the same type. Convert both the Vote properties and balance to bigint before passing to SDK functions, or align the type definitions.

⛔ Skipped due to learnings
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 665
File: packages/enclave-sdk/src/contract-client.ts:110-118
Timestamp: 2025-08-28T08:58:17.434Z
Learning: viem's TypeScript ABI inference maps uint32[2] to [number, number], not [bigint, bigint]. The library uses number type for uint32 values to align with traditional database patterns and avoid unnecessary BigInt conversion.
examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (2)

119-124: Good improvement: Round-specific message signing.

Using a dynamic message with the round ID (Vote for round ${roundState.id}) and hashing it for proof generation is a sound approach that prevents cross-round replay of signatures.


68-83: LGTM!

The handleProofGeneration function correctly integrates the new parameters (vote, balance, messageHash, isMasking) and passes them to the proof generation call.

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

146-180: LGTM!

The generateCircuitInputs function correctly handles both first-vote and update paths. The defensive check for previousCiphertext on non-first votes (line 161-163) is good practice even though callers should always provide it.


235-252: Verify the slotAddress usage is intentional.

The function uses voteProofInputs.slotAddress for server calls (lines 236, 240) but then overrides it with address (derived from signature) in the circuit inputs (line 247). This means the server is queried for one address's slot, but the circuit proves for the signer's address.

If this is intentional (e.g., the input slotAddress is expected to match the derived address), consider adding a validation check. Otherwise, consider using the derived address consistently for both server calls and circuit inputs.


262-292: LGTM!

The generateMaskVoteProof function correctly implements the masking flow:

  • Uses the fixed MASK_SIGNATURE for signature components
  • Fetches previous ciphertext from server when slot is not empty
  • Passes zeroVote to ensure no voting power is added
examples/CRISP/client/src/hooks/voting/useSDKWorker.tsx (1)

31-74: LGTM!

The generateProof function correctly passes all new parameters to the worker and handles responses appropriately. The early return when the worker is not initialized prevents runtime errors.

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

172-209: LGTM!

The type definitions are well-structured:

  • ProofInputs correctly makes previousCiphertext required and uses the new Signature type
  • MaskVoteProofInputs and VoteProofInputs appropriately include server-related fields (serverUrl, e3Id)
  • The Signature type cleanly encapsulates all signature components
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3)

37-55: Good test setup with proper mock lifecycle management.

The mock setup correctly uses vi.clearAllMocks() in beforeEach and vi.restoreAllMocks() in afterEach to ensure test isolation. The mock response objects are well-defined for both slot empty and previous ciphertext scenarios.


274-303: LGTM!

This test correctly validates the mask vote proof generation when there's a previous vote in the slot. The chained mock setup (mockResolvedValueOnce twice) properly simulates the sequential server calls for isSlotEmpty and getPreviousCiphertext.


147-179: LGTM!

This test correctly validates that when using a previous ciphertext with a zero vote (masking scenario), the circuit outputs the ciphertext addition. The intentional use of an invalid leaf (merkleProof.leaf = 0n) tests the circuit's behavior when authentication fails, which is expected in masking operations.

examples/CRISP/crates/evm_helpers/src/lib.rs (7)

29-30: LGTM! Contract interface additions are well-formed.

The new view functions getSlotIndex and isSlotEmptyByAddress are properly declared with appropriate types and modifiers.


38-46: LGTM! Read-only provider type is correctly composed.

The CRISPReadProvider properly excludes the WalletFiller while maintaining the necessary fillers for read operations (gas estimation, nonce, chain ID).


63-66: LGTM! Generic design enables read/write flexibility.

The default type parameter P = CRISPWriteProvider maintains backward compatibility while allowing read-only instances via CRISPContract<CRISPReadProvider>.


68-87: LGTM! Write-capable constructor is properly scoped.

The implementation correctly restricts wallet-requiring operations to CRISPContract<CRISPWriteProvider>.


107-117: LGTM! Read-only constructor is correctly implemented.

The method appropriately creates a provider without wallet authentication, enabling contract read operations without requiring a private key.


148-153: LGTM! Generic address getter is well-implemented.

The method appropriately works for both read and write contract instances, providing access to the contract address without unnecessary cloning.


168-173: LGTM! Factory method completes the read/write separation.

The create_read method provides a convenient factory interface for read-only contract instances, maintaining consistency with the existing create_write pattern.

Comment thread examples/CRISP/client/src/hooks/voting/useVoteCasting.ts Outdated
Comment thread examples/CRISP/crates/zk-inputs/src/lib.rs
Comment thread examples/CRISP/packages/crisp-sdk/src/state.ts Outdated
Comment thread examples/CRISP/packages/crisp-sdk/tests/vote.test.ts Outdated
Comment thread examples/CRISP/server/src/server/routes/state.rs Outdated
@vercel vercel Bot temporarily deployed to Preview – enclave-docs December 19, 2025 11:27 Inactive
@ctrlc03 ctrlc03 force-pushed the chore/get-previous-ciphertext branch from 79c50db to d3a7fc3 Compare December 19, 2025 17:50
@vercel vercel Bot temporarily deployed to Preview – enclave-docs December 19, 2025 17:50 Inactive
@github-actions

Copy link
Copy Markdown
Contributor

License Header Check Failed

Some files are missing the required SPDX license header. Please add the following header to the beginning of all .js, .jsx, .nr, .rs, .sol, .ts, and .tsx files:

// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

You can run ./scripts/check-license-headers.sh --fix locally to automatically add missing headers, then commit the changes.

Or run ./scripts/check-license-headers.sh to see which files need headers.

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

♻️ Duplicate comments (2)
examples/CRISP/crates/zk-inputs/src/lib.rs (1)

117-120: GRECO decoding functions can panic on invalid input.

Per the past review comment, abi_decode_greco_ciphertext and greco_to_bfv_ciphertext panic on invalid input or unexpected shape. The relevant code snippets show multiple panic! and .expect() calls in these functions (crates/bfv-helpers/src/utils/greco.rs). This could cause the WASM module to abort unexpectedly when invalid prev_ciphertext is provided.

Based on learnings, the team has accepted panics in some utility functions as a temporary solution pending refactoring to proper Result-based error handling. However, this remains a critical issue for production use.

Consider wrapping these calls with proper error handling to return a Result instead of allowing panics to propagate through the WASM boundary.

examples/CRISP/server/src/server/routes/state.rs (1)

47-47: Typo still present from previous review.

The word "sucessfull" should be "successful" in the doc comment.

🧹 Nitpick comments (3)
examples/CRISP/crates/zk-inputs/src/lib.rs (1)

78-139: Missing test coverage for generate_inputs_for_update.

The new generate_inputs_for_update method lacks test coverage. The test section (lines 254-471) only tests the existing generate_inputs method.

Consider adding tests similar to the existing ones but using GRECO ABI-encoded ciphertexts. You'll need a helper to create GRECO-encoded ciphertexts for testing:

#[test]
fn test_inputs_generation_for_update_with_defaults() {
    let generator = ZKInputsGenerator::with_defaults();
    let public_key = generator.generate_public_key().expect("failed to generate public key");
    let vote = create_vote_vector();
    
    // Generate a GRECO ABI-encoded ciphertext
    let greco_encoded_ct = /* helper to create GRECO-encoded ciphertext */;
    
    let result = generator.generate_inputs_for_update(&greco_encoded_ct, &public_key, vote);
    
    assert!(result.is_ok());
    let json_output = result.unwrap();
    assert!(json_output.contains("params"));
    assert!(json_output.contains("pk0is"));
}
examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts (1)

23-23: Consider using ZERO_VOTE constant for consistency.

The inline { yes: 0n, no: 0n } could be replaced with the ZERO_VOTE constant from @crisp-e3/sdk to align with the SDK's defined constants.

🔎 Proposed fix
 import {
   hashLeaf,
   generatePublicKey,
   SIGNATURE_MESSAGE,
   generateVoteProof,
   getAddressFromSignature,
   encodeSolidityProof,
   generateMerkleTree,
   SIGNATURE_MESSAGE_HASH,
   encryptVote,
+  ZERO_VOTE,
 } from '@crisp-e3/sdk'
-const previousCiphertext = encryptVote({ yes: 0n, no: 0n }, publicKey)
+const previousCiphertext = encryptVote(ZERO_VOTE, publicKey)
examples/CRISP/client/libs/crispSDKWorker.js (1)

17-21: Reminder: Address the TODO for fetching merkle leaves from server.

The comment indicates the leaves should be fetched from the server. Currently, only the first leaf is dynamically computed from slotAddress and balance, while the other two are hardcoded test values.

Would you like me to open an issue to track this TODO?

📜 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 d0a65a4 and d3a7fc3.

📒 Files selected for processing (22)
  • examples/CRISP/client/libs/crispSDKWorker.js (1 hunks)
  • examples/CRISP/client/src/context/voteManagement/VoteManagement.types.ts (2 hunks)
  • examples/CRISP/client/src/hooks/voting/useSDKWorker.tsx (3 hunks)
  • examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (5 hunks)
  • examples/CRISP/client/src/model/vote.model.ts (1 hunks)
  • examples/CRISP/crates/evm_helpers/src/lib.rs (4 hunks)
  • examples/CRISP/crates/zk-inputs-wasm/src/lib.rs (2 hunks)
  • examples/CRISP/crates/zk-inputs/src/lib.rs (2 hunks)
  • examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol (2 hunks)
  • examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts (3 hunks)
  • examples/CRISP/packages/crisp-sdk/src/constants.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/index.ts (1 hunks)
  • examples/CRISP/packages/crisp-sdk/src/sdk.ts (1 hunks)
  • examples/CRISP/packages/crisp-sdk/src/state.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/types.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/src/vote.ts (5 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/state.test.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/token.test.ts (1 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (8 hunks)
  • examples/CRISP/server/src/server/models.rs (1 hunks)
  • examples/CRISP/server/src/server/repo.rs (1 hunks)
  • examples/CRISP/server/src/server/routes/state.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • examples/CRISP/server/src/server/repo.rs
  • examples/CRISP/client/src/model/vote.model.ts
  • examples/CRISP/packages/crisp-sdk/src/state.ts
🧰 Additional context used
🧠 Learnings (22)
📓 Common learnings
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 1109
File: examples/CRISP/server/src/server/routes/state.rs:33-37
Timestamp: 2025-12-19T11:35:43.204Z
Learning: In the CRISP voting system (examples/CRISP), ciphertext data and slot states are public and stored on-chain in the CRISPProgram smart contract. The server endpoints `/previous-ciphertext` and `/is-slot-empty` expose public blockchain data that can already be accessed by anyone through the contract's external view functions (getSlotIndex, isSlotEmptyByAddress) or by reading contract state directly. Authentication is not needed for these endpoints since they only expose data that is already publicly available on the blockchain.
📚 Learning: 2024-10-08T19:45:18.209Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 107
File: packages/ciphernode/sortition/src/distance.rs:1-1
Timestamp: 2024-10-08T19:45:18.209Z
Learning: In `packages/ciphernode/core/src/events.rs`, the import statements use the correct and updated `alloy::primitives` module.

Applied to files:

  • examples/CRISP/server/src/server/models.rs
📚 Learning: 2025-08-25T10:28:56.174Z
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 657
File: Cargo.toml:32-34
Timestamp: 2025-08-25T10:28:56.174Z
Learning: The examples/CRISP directory has its own Cargo.toml workspace configuration with members like "server", "wasm-crypto", "program/core", "program/client", etc. The root workspace intentionally excludes "examples/CRISP/server", "examples/CRISP/program", and "examples/CRISP/wasm-crypto" to prevent double workspace membership, which is the correct approach for self-contained example workspaces.

Applied to files:

  • examples/CRISP/server/src/server/models.rs
📚 Learning: 2025-12-19T11:35:43.204Z
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 1109
File: examples/CRISP/server/src/server/routes/state.rs:33-37
Timestamp: 2025-12-19T11:35:43.204Z
Learning: In the CRISP voting system (examples/CRISP), ciphertext data and slot states are public and stored on-chain in the CRISPProgram smart contract. The server endpoints `/previous-ciphertext` and `/is-slot-empty` expose public blockchain data that can already be accessed by anyone through the contract's external view functions (getSlotIndex, isSlotEmptyByAddress) or by reading contract state directly. Authentication is not needed for these endpoints since they only expose data that is already publicly available on the blockchain.

Applied to files:

  • examples/CRISP/client/libs/crispSDKWorker.js
  • examples/CRISP/packages/crisp-sdk/src/sdk.ts
  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
  • examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts
  • examples/CRISP/packages/crisp-sdk/src/constants.ts
  • examples/CRISP/crates/evm_helpers/src/lib.rs
  • examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol
  • examples/CRISP/server/src/server/routes/state.rs
📚 Learning: 2025-11-05T14:12:57.814Z
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 963
File: examples/CRISP/client/package.json:25-25
Timestamp: 2025-11-05T14:12:57.814Z
Learning: In the Enclave/CRISP codebase, `enclave-e3/sdk` and `crisp-e3/sdk` are different packages: `enclave-e3/sdk` is the general Enclave SDK, while `crisp-e3/sdk` is the CRISP-specific SDK. The CRISP client (`examples/CRISP/client`) intentionally depends on `enclave-e3/sdk`, not `crisp-e3/sdk`.

Applied to files:

  • examples/CRISP/packages/crisp-sdk/src/sdk.ts
  • examples/CRISP/packages/crisp-sdk/src/index.ts
📚 Learning: 2024-10-22T02:10:34.864Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs:82-83
Timestamp: 2024-10-22T02:10:34.864Z
Learning: In the file `packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs`, when reviewing test functions like `generate_pk_share`, minor performance optimizations (e.g., minimizing mutex locks) are not a priority.

Applied to files:

  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
  • examples/CRISP/crates/zk-inputs/src/lib.rs
📚 Learning: 2024-09-26T04:12:09.345Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 107
File: tests/basic_integration/test.sh:103-114
Timestamp: 2024-09-26T04:12:09.345Z
Learning: In `tests/basic_integration/test.sh`, the user prefers not to refactor the ciphernode addition section to reduce duplication.

Applied to files:

  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
📚 Learning: 2025-11-08T01:31:47.505Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 969
File: packages/enclave-sdk/src/enclave-sdk.ts:101-103
Timestamp: 2025-11-08T01:31:47.505Z
Learning: In packages/enclave-sdk/src/enclave-sdk.ts, the TRBFV protocol parameter setup is intentionally incomplete as of PR #969. The constructor sets BFV_THRESHOLD parameters for TRBFV, but the encryption methods (encryptNumber, encryptVector, encryptNumberAndGenInputs, encryptVectorAndGenInputs) intentionally only support BFV and will throw "Protocol not supported" for TRBFV until future implementation is completed.

Applied to files:

  • examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts
📚 Learning: 2025-11-12T10:08:30.720Z
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 996
File: examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol:144-169
Timestamp: 2025-11-12T10:08:30.720Z
Learning: In the CRISP program (examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol), there are plans to move the merkle tree from the Enclave contract to the program contract itself in the future, which would allow direct calls to validateInput to be stored in the merkle tree.

Applied to files:

  • examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts
  • examples/CRISP/crates/evm_helpers/src/lib.rs
  • examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol
📚 Learning: 2025-05-07T15:18:20.056Z
Learnt from: 0xjei
Repo: gnosisguild/enclave PR: 388
File: packages/commons/src/bfv/mod.rs:38-48
Timestamp: 2025-05-07T15:18:20.056Z
Learning: The `build_bfv_params` and related functions in the commons BFV utilities intentionally use panic for error handling as a temporary solution. The team plans to refactor these to use proper Result-based error handling in the future.

Applied to files:

  • examples/CRISP/crates/zk-inputs/src/lib.rs
📚 Learning: 2024-10-28T12:04:26.578Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 156
File: packages/ciphernode/enclave_node/src/ciphernode.rs:26-28
Timestamp: 2024-10-28T12:04:26.578Z
Learning: In the `setup_ciphernode` function in `packages/ciphernode/enclave_node/src/ciphernode.rs`, panicking on errors during setup is acceptable.

Applied to files:

  • examples/CRISP/crates/zk-inputs/src/lib.rs
📚 Learning: 2025-09-22T15:08:29.814Z
Learnt from: ozgurarmanc
Repo: gnosisguild/enclave PR: 734
File: packages/circuits/crates/libs/polynomial/src/lib.nr:140-155
Timestamp: 2025-09-22T15:08:29.814Z
Learning: Greco (packages/circuits/crates/libs/greco/src/lib.nr) performs range_check_1bound/2bounds on all polynomials (u, e0/e1, k1, pk*, r*, p*) before serialization; packer/flatten rely on these bounds, so per-limb asserts inside packer are unnecessary in this crate’s flow.

Applied to files:

  • examples/CRISP/crates/zk-inputs/src/lib.rs
📚 Learning: 2024-10-22T03:30:21.818Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/enclave_node/src/shutdown.rs:12-12
Timestamp: 2024-10-22T03:30:21.818Z
Learning: In shutdown code (e.g., in `packages/ciphernode/enclave_node/src/shutdown.rs`), if signal creation fails, it's acceptable to panic rather than handle the error gracefully.

Applied to files:

  • examples/CRISP/crates/zk-inputs/src/lib.rs
📚 Learning: 2024-11-05T06:49:46.285Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 173
File: packages/ciphernode/enclave_node/src/datastore.rs:14-16
Timestamp: 2024-11-05T06:49:46.285Z
Learning: In `packages/ciphernode/enclave_node/src/datastore.rs`, for internal functions like `get_in_mem_store`, adding extensive documentation and error handling may not be necessary if they are not client-facing.

Applied to files:

  • examples/CRISP/crates/zk-inputs/src/lib.rs
  • examples/CRISP/crates/evm_helpers/src/lib.rs
  • examples/CRISP/server/src/server/routes/state.rs
📚 Learning: 2024-10-22T03:47:04.014Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/router/src/e3_request_router.rs:202-235
Timestamp: 2024-10-22T03:47:04.014Z
Learning: In `packages/ciphernode/router/src/e3_request_router.rs`, within the `E3RequestRouter::from_snapshot` method, errors during context restoration propagate upwards due to the `?` operator, and skipping contexts when `repositories.context(&e3_id).read().await?` returns `Ok(None)` is acceptable, as missing snapshots are expected.

Applied to files:

  • examples/CRISP/crates/evm_helpers/src/lib.rs
📚 Learning: 2024-10-12T10:24:07.572Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 139
File: packages/ciphernode/evm/src/ciphernode_registry_sol.rs:133-143
Timestamp: 2024-10-12T10:24:07.572Z
Learning: In `packages/ciphernode/evm/src/ciphernode_registry_sol.rs`, the function `helpers::stream_from_evm` in Rust returns `()`, not a `Result`, so error handling with `if let Err(e) = ...` is not applicable.

Applied to files:

  • examples/CRISP/crates/evm_helpers/src/lib.rs
📚 Learning: 2024-10-22T03:17:41.617Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/router/src/hooks.rs:0-0
Timestamp: 2024-10-22T03:17:41.617Z
Learning: In `packages/ciphernode/router/src/hooks.rs`, in the `hydrate` methods of the `E3Feature` implementations, it's acceptable to return `Ok(())` when dependencies are missing, as the error is reported to the bus.

Applied to files:

  • examples/CRISP/crates/evm_helpers/src/lib.rs
  • examples/CRISP/server/src/server/routes/state.rs
📚 Learning: 2025-04-26T12:15:26.610Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 344
File: packages/ciphernode/enclave_core/src/nodes/server.rs:63-75
Timestamp: 2025-04-26T12:15:26.610Z
Learning: Authentication is not considered necessary for the internal server implementation in `packages/ciphernode/enclave_core/src/nodes/server.rs` as it likely binds to localhost and is for internal use only.

Applied to files:

  • examples/CRISP/server/src/server/routes/state.rs
📚 Learning: 2024-10-29T01:03:50.414Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 156
File: packages/ciphernode/config/src/app_config.rs:21-26
Timestamp: 2024-10-29T01:03:50.414Z
Learning: In `packages/ciphernode/config/src/app_config.rs`, the `rpc_url` field in the `ChainConfig` struct is not considered sensitive and does not need to be encrypted.

Applied to files:

  • examples/CRISP/server/src/server/routes/state.rs
📚 Learning: 2024-10-23T01:59:42.967Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 156
File: packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs:274-274
Timestamp: 2024-10-23T01:59:42.967Z
Learning: In the `packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs` file and other test files within this project, hardcoding `CIPHERNODE_SECRET` is acceptable for testing purposes.

Applied to files:

  • examples/CRISP/server/src/server/routes/state.rs
📚 Learning: 2024-10-23T02:35:07.689Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 156
File: packages/ciphernode/keyshare/src/keyshare.rs:58-65
Timestamp: 2024-10-23T02:35:07.689Z
Learning: In `packages/ciphernode/keyshare/src/keyshare.rs`, the data being decrypted in the `get_secret` method of the `Keyshare` struct is not sensitive, so wrapping it with `Zeroizing` is unnecessary.

Applied to files:

  • examples/CRISP/server/src/server/routes/state.rs
📚 Learning: 2024-11-05T06:48:58.177Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 173
File: packages/ciphernode/config/src/app_config.rs:13-21
Timestamp: 2024-11-05T06:48:58.177Z
Learning: In the `packages/ciphernode/config/src/app_config.rs` file, for the `Contract` enum, the team prefers to use `String` type for `address` fields, relying on parsing to handle validation, instead of using the `Address` type.

Applied to files:

  • examples/CRISP/server/src/server/routes/state.rs
🧬 Code graph analysis (7)
examples/CRISP/client/src/hooks/voting/useSDKWorker.tsx (2)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)
  • Vote (70-79)
examples/CRISP/client/src/model/vote.model.ts (1)
  • Vote (59-62)
examples/CRISP/client/libs/crispSDKWorker.js (2)
examples/CRISP/packages/crisp-sdk/src/index.ts (2)
  • hashLeaf (10-10)
  • CrispSDK (20-20)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
  • hashLeaf (20-22)
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3)
examples/CRISP/packages/crisp-sdk/src/sdk.ts (1)
  • CrispSDK (11-69)
examples/CRISP/packages/crisp-sdk/src/vote.ts (2)
  • encryptVote (125-129)
  • generateCircuitInputs (145-181)
examples/CRISP/packages/crisp-sdk/src/constants.ts (3)
  • ZERO_VOTE (40-40)
  • SIGNATURE_MESSAGE_HASH (34-34)
  • MASK_SIGNATURE (37-38)
examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts (1)
examples/CRISP/packages/crisp-sdk/src/index.ts (2)
  • generatePublicKey (16-16)
  • encryptVote (17-17)
examples/CRISP/crates/zk-inputs/src/lib.rs (3)
crates/bfv-helpers/src/utils/greco.rs (2)
  • abi_decode_greco_ciphertext (125-183)
  • greco_to_bfv_ciphertext (68-103)
examples/CRISP/crates/zk-inputs/src/ciphertext_addition.rs (1)
  • compute (63-230)
examples/CRISP/crates/zk-inputs/src/serialization.rs (2)
  • construct_inputs (60-261)
  • serialize_inputs_to_json (270-272)
examples/CRISP/packages/crisp-sdk/src/vote.ts (3)
examples/CRISP/packages/crisp-sdk/src/types.ts (2)
  • Vote (70-79)
  • ProofInputs (172-182)
examples/CRISP/packages/crisp-sdk/src/constants.ts (2)
  • ZERO_VOTE (40-40)
  • SIGNATURE_MESSAGE_HASH (34-34)
examples/CRISP/packages/crisp-sdk/src/utils.ts (1)
  • extractSignatureComponents (89-118)
examples/CRISP/packages/crisp-sdk/src/types.ts (2)
examples/CRISP/packages/crisp-sdk/src/index.ts (2)
  • Vote (22-22)
  • MaskVoteProofInputs (22-22)
examples/CRISP/client/src/model/vote.model.ts (1)
  • Vote (59-62)
🪛 GitHub Actions: License Header Check
examples/CRISP/packages/crisp-sdk/src/sdk.ts

[error] 1-1: Missing license header. Files missing license headers: 1. Expected header format: // SPDX-License-Identifier: LGPL-3.0-only

⏰ 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). (7)
  • GitHub Check: build_crisp_sdk
  • GitHub Check: rust_unit
  • GitHub Check: build_sdk
  • GitHub Check: build_enclave_cli
  • GitHub Check: rust_integration
  • GitHub Check: integration_prebuild
  • GitHub Check: test_net
🔇 Additional comments (27)
examples/CRISP/crates/zk-inputs-wasm/src/lib.rs (1)

75-98: LGTM - Consistent wrapper implementation.

The new generate_inputs_for_update method follows the same pattern as generate_inputs, with consistent type conversions (i64→u64), delegation to the core generator, and error handling.

examples/CRISP/crates/zk-inputs/src/lib.rs (1)

12-15: LGTM - Required imports for GRECO decoding.

The new imports for abi_decode_greco_ciphertext and greco_to_bfv_ciphertext are necessary for the GRECO ABI-encoded ciphertext handling in generate_inputs_for_update.

examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol (2)

55-55: LGTM!

The custom error SlotIsEmpty() follows Solidity best practices for gas-efficient error handling.


182-200: LGTM!

Both view functions correctly implement slot state queries:

  • getSlotIndex properly handles the +1 encoding and reverts with a clear error when the slot is empty
  • isSlotEmptyByAddress provides a non-reverting check for slot emptiness

The implementations are clean, well-documented, and correctly expose public on-chain data for masking operations.

examples/CRISP/server/src/server/routes/state.rs (4)

7-19: LGTM!

The new imports are necessary and correctly structured for the previousCiphertext and slot-emptiness query functionality.


32-37: LGTM!

The new routes are properly registered and align with the existing endpoint structure.


48-97: LGTM!

The handle_get_previous_ciphertext implementation is well-structured with proper error handling:

  • Validates address format with clear error messages
  • Uses read-only contract for querying public on-chain data
  • Returns appropriate HTTP status codes (400 for invalid input, 404 for not found, 500 for internal errors)

Based on learnings, authentication is not required as this endpoint exposes public blockchain data.


283-323: LGTM!

The handle_is_slot_empty implementation follows the same solid pattern as handle_get_previous_ciphertext:

  • Proper address validation
  • Read-only contract usage for public data queries
  • Appropriate error handling with clear status codes
examples/CRISP/server/src/server/models.rs (1)

108-128: LGTM!

The new model structs are well-defined with appropriate traits and field types:

  • Request types properly implement Debug, Deserialize, and Serialize
  • Response types implement Serialize for JSON serialization
  • Field types match the requirements for previousCiphertext and slot-emptiness queries
examples/CRISP/crates/evm_helpers/src/lib.rs (5)

29-30: LGTM!

The sol! interface correctly declares the new view functions matching the CRISPProgram contract.


38-66: LGTM!

The generic provider design cleanly separates read and write capabilities:

  • CRISPReadProvider omits the wallet filler for read-only operations
  • Generic CRISPContract<P> with default CRISPWriteProvider maintains backward compatibility
  • Using Arc<P> is appropriate for shared provider instances

107-146: LGTM!

The read-only implementation is well-structured:

  • new_read_only correctly creates a provider without requiring a private key
  • Both query methods (get_slot_index_from_address and get_is_slot_empty_by_address) properly preserve error context by matching on the Result instead of using .ok()
  • Error messages are descriptive and helpful for debugging

This addresses the error handling concern from the previous review.


148-153: LGTM!

The generic address() accessor provides a clean way to retrieve the contract address for both read and write instances.


164-173: LGTM!

The factory methods are well-structured:

  • create_write explicitly returns CRISPContract<CRISPWriteProvider>
  • create_read provides a convenient way to create read-only instances
  • Both methods follow the factory pattern consistently
examples/CRISP/packages/crisp-sdk/src/index.ts (1)

20-20: LGTM!

The CrispSDK export cleanly extends the public API surface, aligning with the existing export patterns.

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

29-29: LGTM!

Using mockResolvedValueOnce is the correct approach for a test that expects exactly one fetch call.

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

49-49: LGTM!

Consistent use of mockResolvedValueOnce for the single expected fetch call.


90-90: LGTM!

Consistent with the mock pattern in getRoundDetails test.

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

64-66: LGTM!

The test correctly passes the new required parameters (isFirstVote, previousCiphertext, slotAddress) for the updated generateVoteProof signature.


97-99: LGTM!

Consistent with the first test case - correctly exercises the verifier and encoder with the new proof input model.

examples/CRISP/client/src/hooks/voting/useSDKWorker.tsx (1)

31-51: LGTM!

The updated generateProof function signature aligns with the new SDK API, properly passes all required parameters to the worker, and handles the uninitialized worker case gracefully.

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

11-12: LGTM!

The new endpoint constants follow the established naming convention and are correctly placed with the other server endpoint definitions.


39-40: LGTM!

The ZERO_VOTE constant is well-named following SCREAMING_SNAKE_CASE convention and provides a reusable zero-vote representation for first-vote scenarios.

examples/CRISP/client/src/context/voteManagement/VoteManagement.types.ts (1)

40-49: LGTM!

The updated generateProof type signature correctly reflects the new API contract, with properly typed parameters for vote masking support.

examples/CRISP/client/libs/crispSDKWorker.js (2)

27-48: Masking logic edge cases are properly handled by the SDK.

The SDK's generateMaskVoteProof and generateVoteProof methods correctly handle edge cases: they check slot status to determine if previousCiphertext should be fetched from the server (vote-update scenarios) or initialized as a zero vote (first vote), and properly set the isFirstVote flag accordingly. The client code correctly delegates to these SDK methods.


23-48: > Likely an incorrect or invalid review comment.

examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (1)

138-140: The balance value of 1n is intentional and represents the one-person-one-vote design of the CRISP protocol. As stated in the code comment, "vote is either 0 or 1," and the Vote structure encodes this as a binary choice: either {yes: 1n, no: 0n} or {yes: 0n, no: 1n}. This is not a placeholder for user's actual token balance; rather, it's the core voting weight for the Coercion-Resistant Impartial Selection Protocol, which uses fully homomorphic encryption for secret ballots.

Likely an incorrect or invalid review comment.

Comment thread examples/CRISP/client/src/hooks/voting/useSDKWorker.tsx
Comment thread examples/CRISP/crates/zk-inputs/src/lib.rs
Comment thread examples/CRISP/packages/crisp-sdk/src/sdk.ts
Comment thread examples/CRISP/packages/crisp-sdk/src/sdk.ts
@vercel vercel Bot temporarily deployed to Preview – enclave-docs December 19, 2025 18:00 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp December 19, 2025 18:00 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

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/tests/vote.test.ts (1)

173-204: Inconsistent vote value used for previousCiphertext.

Line 187 uses encryptVote(vote, publicKey) where vote is { yes: 10n, no: 0n } (set in beforeAll), but this test is titled "if there is no previous ciphertext" and uses vote: ZERO_VOTE at line 179 for the circuit input. For consistency and clarity, line 187 should use ZERO_VOTE instead of vote, or use the previousCiphertext that matches the test scenario.

🔎 Proposed fix
       const crispInputs = await generateCircuitInputs({
         vote: ZERO_VOTE,
         publicKey,
         signature: MASK_SIGNATURE,
         messageHash: SIGNATURE_MESSAGE_HASH,
         merkleProof,
         balance,
         slotAddress,
         isFirstVote: true,
-        previousCiphertext: encryptVote(vote, publicKey),
+        previousCiphertext: encryptVote(ZERO_VOTE, publicKey),
       })
🧹 Nitpick comments (1)
examples/CRISP/packages/crisp-sdk/src/sdk.ts (1)

35-74: Consider extracting the shared previousCiphertext logic.

Both generateMaskVoteProof and generateVoteProof contain identical logic for determining and obtaining the previousCiphertext (lines 36-44 and 59-67). Extracting this into a private helper method would reduce duplication and improve maintainability.

🔎 Proposed refactor to extract shared logic
+  private async getPreviousCiphertextForSlot(
+    e3Id: number,
+    slotAddress: string,
+    publicKey: Uint8Array
+  ): Promise<{ previousCiphertext: Uint8Array; isFirstVote: boolean }> {
+    const isSlotEmpty = await getIsSlotEmpty(this.serverUrl, e3Id, slotAddress)
+
+    let previousCiphertext: Uint8Array
+    if (!isSlotEmpty) {
+      previousCiphertext = await getPreviousCiphertext(this.serverUrl, e3Id, slotAddress)
+    } else {
+      previousCiphertext = encryptVote(ZERO_VOTE, publicKey)
+    }
+
+    return { previousCiphertext, isFirstVote: isSlotEmpty }
+  }
+
   async generateMaskVoteProof(maskProofInputs: MaskVoteProofRequest): Promise<ProofData> {
-    // check if the slot is empty first
-    const isSlotEmpty = await getIsSlotEmpty(this.serverUrl, maskProofInputs.e3Id, maskProofInputs.slotAddress)
-
-    let previousCiphertext: Uint8Array
-    if (!isSlotEmpty) {
-      previousCiphertext = await getPreviousCiphertext(this.serverUrl, maskProofInputs.e3Id, maskProofInputs.slotAddress)
-    } else {
-      previousCiphertext = encryptVote(ZERO_VOTE, maskProofInputs.publicKey)
-    }
+    const { previousCiphertext, isFirstVote } = await this.getPreviousCiphertextForSlot(
+      maskProofInputs.e3Id,
+      maskProofInputs.slotAddress,
+      maskProofInputs.publicKey
+    )

     return generateMaskVoteProof({
       ...maskProofInputs,
       previousCiphertext,
-      isFirstVote: isSlotEmpty,
+      isFirstVote,
     })
   }

   async generateVoteProof(voteProofInputs: VoteProofRequest): Promise<ProofData> {
-    // check if the slot is empty first
-    const isSlotEmpty = await getIsSlotEmpty(this.serverUrl, voteProofInputs.e3Id, voteProofInputs.slotAddress)
-
-    let previousCiphertext: Uint8Array
-    if (!isSlotEmpty) {
-      previousCiphertext = await getPreviousCiphertext(this.serverUrl, voteProofInputs.e3Id, voteProofInputs.slotAddress)
-    } else {
-      previousCiphertext = encryptVote(ZERO_VOTE, voteProofInputs.publicKey)
-    }
+    const { previousCiphertext, isFirstVote } = await this.getPreviousCiphertextForSlot(
+      voteProofInputs.e3Id,
+      voteProofInputs.slotAddress,
+      voteProofInputs.publicKey
+    )

     return generateVoteProof({
       ...voteProofInputs,
       previousCiphertext,
-      isFirstVote: isSlotEmpty,
+      isFirstVote,
     })
   }
📜 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 27958de and 821d65f.

📒 Files selected for processing (3)
  • examples/CRISP/packages/crisp-sdk/src/sdk.ts (1 hunks)
  • examples/CRISP/packages/crisp-sdk/src/types.ts (2 hunks)
  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (8 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 1109
File: examples/CRISP/server/src/server/routes/state.rs:33-37
Timestamp: 2025-12-19T11:35:43.204Z
Learning: In the CRISP voting system (examples/CRISP), ciphertext data and slot states are public and stored on-chain in the CRISPProgram smart contract. The server endpoints `/previous-ciphertext` and `/is-slot-empty` expose public blockchain data that can already be accessed by anyone through the contract's external view functions (getSlotIndex, isSlotEmptyByAddress) or by reading contract state directly. Authentication is not needed for these endpoints since they only expose data that is already publicly available on the blockchain.
📚 Learning: 2025-11-05T14:12:57.814Z
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 963
File: examples/CRISP/client/package.json:25-25
Timestamp: 2025-11-05T14:12:57.814Z
Learning: In the Enclave/CRISP codebase, `enclave-e3/sdk` and `crisp-e3/sdk` are different packages: `enclave-e3/sdk` is the general Enclave SDK, while `crisp-e3/sdk` is the CRISP-specific SDK. The CRISP client (`examples/CRISP/client`) intentionally depends on `enclave-e3/sdk`, not `crisp-e3/sdk`.

Applied to files:

  • examples/CRISP/packages/crisp-sdk/src/sdk.ts
📚 Learning: 2025-12-19T11:35:43.204Z
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 1109
File: examples/CRISP/server/src/server/routes/state.rs:33-37
Timestamp: 2025-12-19T11:35:43.204Z
Learning: In the CRISP voting system (examples/CRISP), ciphertext data and slot states are public and stored on-chain in the CRISPProgram smart contract. The server endpoints `/previous-ciphertext` and `/is-slot-empty` expose public blockchain data that can already be accessed by anyone through the contract's external view functions (getSlotIndex, isSlotEmptyByAddress) or by reading contract state directly. Authentication is not needed for these endpoints since they only expose data that is already publicly available on the blockchain.

Applied to files:

  • examples/CRISP/packages/crisp-sdk/src/sdk.ts
  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
📚 Learning: 2024-10-22T02:10:34.864Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs:82-83
Timestamp: 2024-10-22T02:10:34.864Z
Learning: In the file `packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs`, when reviewing test functions like `generate_pk_share`, minor performance optimizations (e.g., minimizing mutex locks) are not a priority.

Applied to files:

  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
📚 Learning: 2024-09-26T04:12:09.345Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 107
File: tests/basic_integration/test.sh:103-114
Timestamp: 2024-09-26T04:12:09.345Z
Learning: In `tests/basic_integration/test.sh`, the user prefers not to refactor the ciphernode addition section to reduce duplication.

Applied to files:

  • examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
🧬 Code graph analysis (3)
examples/CRISP/packages/crisp-sdk/src/sdk.ts (4)
examples/CRISP/packages/crisp-sdk/src/types.ts (2)
  • MaskVoteProofRequest (193-199)
  • VoteProofRequest (201-210)
examples/CRISP/packages/crisp-sdk/src/state.ts (2)
  • getIsSlotEmpty (93-109)
  • getPreviousCiphertext (68-84)
examples/CRISP/packages/crisp-sdk/src/vote.ts (3)
  • encryptVote (125-129)
  • generateMaskVoteProof (251-265)
  • generateVoteProof (215-244)
examples/CRISP/packages/crisp-sdk/src/constants.ts (1)
  • ZERO_VOTE (40-40)
examples/CRISP/packages/crisp-sdk/tests/vote.test.ts (3)
examples/CRISP/packages/crisp-sdk/src/sdk.ts (1)
  • CrispSDK (17-75)
examples/CRISP/packages/crisp-sdk/src/vote.ts (2)
  • encryptVote (125-129)
  • generateCircuitInputs (145-181)
examples/CRISP/packages/crisp-sdk/src/constants.ts (3)
  • ZERO_VOTE (40-40)
  • SIGNATURE_MESSAGE_HASH (34-34)
  • MASK_SIGNATURE (37-38)
examples/CRISP/packages/crisp-sdk/src/types.ts (2)
examples/CRISP/packages/crisp-sdk/src/index.ts (2)
  • Vote (22-22)
  • MaskVoteProofInputs (22-22)
examples/CRISP/client/src/model/vote.model.ts (1)
  • Vote (59-62)
⏰ 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_crisp_sdk
  • GitHub Check: build_e3_support_dev
  • GitHub Check: build_enclave_cli
  • GitHub Check: build_sdk
  • GitHub Check: rust_unit
  • GitHub Check: integration_prebuild
  • GitHub Check: test_net
  • GitHub Check: rust_integration
🔇 Additional comments (1)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)

193-210: LGTM! Well-designed API separation.

The new MaskVoteProofRequest and VoteProofRequest types cleanly separate the public API surface from internal implementation details. By excluding previousCiphertext and isFirstVote from the request types, the SDK correctly encapsulates the logic for determining these values, which improves the developer experience and reduces the chance of caller errors.

@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.

LGTM

@ctrlc03 ctrlc03 merged commit 38440ca into main Dec 19, 2025
27 checks passed
@github-actions github-actions Bot deleted the chore/get-previous-ciphertext branch December 27, 2025 02:52
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.

CRISP support vote update CRISP SDK get the previous ciphertext from server or from contract

2 participants