Skip to content

feat: enable full e2e proof agg and slashing [skip-line-limit]#1550

Merged
0xjei merged 55 commits into
mainfrom
feat/1549
May 24, 2026
Merged

feat: enable full e2e proof agg and slashing [skip-line-limit]#1550
0xjei merged 55 commits into
mainfrom
feat/1549

Conversation

@0xjei

@0xjei 0xjei commented May 21, 2026

Copy link
Copy Markdown
Contributor

Several reviewers (and review LLMs) have flagged a concern that the committee-hash binding is set-level rather than per-party, and that "there is no cryptographic chain from 'fold proof at row i is bad' to 'address topNodes[i] produced it.'"

That chain is on this branch, in the Phase B fold-attestation work. For each honest party, DkgFoldAttestationVerifier enforces on-chain:

  1. isCommitteeMemberActive(e3Id, node) for the claimed operator at that partyId.
  2. ecrecover(EIP-712 digest of (chainId, e3Id, partyId, skAggCommit, esmAggCommit), signature) == node.
  3. The signed skAggCommit / esmAggCommit equal the proof's public inputs at that party's slot.

Plus the bundle must contain exactly h attestations (derived from the proof shape) in strict ascending partyId order — so an aggregator holding fold proofs from all members can neither reassign attribution nor pick an H-subset.

Per-party commitments are persisted to dkgPartyIds, dkgSkAggCommits, dkgEsmAggCommits so downstream slashing can attribute a bad fold at row i to topNodes[dkgPartyIds[e3Id][i]].

Files to audit:

  • packages/enclave-contracts/contracts/verifiers/DkgFoldAttestationVerifier.sol
  • packages/enclave-contracts/contracts/lib/DkgFoldAttestationLib.sol
  • _verifyAndStoreDkgAnchors in packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol

Docs:

  • docs/pages/cryptography.mdx — "Per-node attestations and topNodes binding" subsection
  • docs/pages/internals/dkg.mdx — "Proof Aggregation and On-Chain Anchoring" section

Summary by CodeRabbit

  • New Features

    • On-chain proof aggregation flow with per-node fold-attestation bundles and EIP‑712 attestations.
    • Configurable proof-aggregation mode and multithread benchmarking (CLI/env flags).
    • Integration: CRISP dev/deploy helpers, env sync, and improved local deploy/test workflows.
  • Documentation

    • Expanded docs on proof aggregation, fold-attestations, verifier syncing, and benchmarking.

Review Change Stack

@vercel

vercel Bot commented May 21, 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 May 24, 2026 1:32pm
enclave-docs Ready Ready Preview, Comment May 24, 2026 1:32pm
enclave-enclave-dashboard Ready Ready Preview, Comment May 24, 2026 1:32pm

Request Review

@coderabbitai

coderabbitai Bot commented May 21, 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
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

This PR adds DKG fold attestation support across Rust and Solidity flows, updates committee ordering and slashing evidence handling, expands benchmark generation and reporting, and refreshes integration, template, and CRISP local-development wiring to support proof-aggregation and non-aggregation modes.

Changes

End-to-end DKG fold attestations, committee publication, and slashing evidence

Layer / File(s) Summary
Attestation payloads and committee ordering
crates/events/src/enclave_event/dkg_fold_attestation.rs, crates/events/src/enclave_event/dkg_recursive_aggregation_complete.rs, crates/events/src/enclave_event/publickey_aggregated.rs, crates/aggregator/src/committee.rs, crates/zk-prover/src/node_fold_public.rs, crates/zk-prover/src/actors/node_proof_aggregator.rs
Adds signed fold-attestation types, extracts NodeFold public commits, carries attestations in recursive aggregation events, and derives committee addresses in party order.
Aggregator to registry publish path
crates/aggregator/src/publickey_aggregator.rs, crates/aggregator/src/ext.rs, crates/evm/src/dkg_attestation_bundle.rs, crates/evm/src/ciphernode_registry_sol.rs, packages/enclave-contracts/contracts/interfaces/ICiphernodeRegistry.sol, packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol, packages/enclave-contracts/contracts/verifiers/DkgFoldAttestationVerifier.sol
Verifies received fold attestations, encodes attestation bundles, forwards them in publishCommittee, stores DKG anchors, and exposes canonical committee-node and verifier/vote-validity reads.
Slashing evidence and accusation vote updates
crates/events/src/enclave_event/accusation_vote.rs, crates/events/src/enclave_event/accusation_quorum_reached.rs, crates/events/src/enclave_event/commitment_consistency.rs, crates/events/src/enclave_event/proof_verification_passed.rs, crates/zk-prover/src/actors/accusation_manager.rs, crates/evm/src/slashing_manager_sol_writer.rs, packages/enclave-contracts/contracts/slashing/SlashingManager.sol
Changes accusation votes to EIP-712 deadlines, carries encoded evidence through verification and quorum events, and updates slash-proof encoding and contract constants.
Benchmark and integration harness updates
circuits/benchmarks/scripts/*, circuits/benchmarks/results_*, crates/tests/tests/integration.rs, tests/integration/*, crates/ciphernode-builder/src/ciphernode_builder.rs, crates/config/src/app_config.rs, crates/entrypoint/src/start/start.rs, crates/multithread/src/multithread.rs
Adds proof-aggregation benchmark modes, run metadata, metric-aware reports, integration fixture staging, configurable multithread settings, and updated integration execution paths.
Contracts deployment, templates, and CRISP workflow
packages/enclave-contracts/scripts/*, packages/enclave-contracts/tasks/*, templates/default/*, examples/CRISP/*
Adds verifier deployment and wiring helpers, local deployment cleanup/sync utilities, template path and localhost updates, and CRISP env, deploy, CLI, server, and test changes for local proof-aggregation setup.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

ciphernode, crisp, documentation

Suggested reviewers

  • hmzakhalid

Poem

🐇 I thumped through folds of proof tonight,
with party slots all lined up right.
I packed attestations in a bundle neat,
and left fresh benchmarks at your feet.
In burrows of scripts and docs anew,
this clever patch now hops on through.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/1549

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (6)
circuits/benchmarks/scripts/generate_report.sh (2)

548-551: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard jq reads when integration blob is absent.

Lines 549–551 run jq on $blob even when it’s empty; under set -e this can terminate report generation in no-summary runs.

Suggested fix
-        local nodes_spawned network_model testmode_harness
-        nodes_spawned=$(jq -r '.benchmark_config.nodes_spawned // empty' <<<"$blob")
-        network_model=$(jq -r '.benchmark_config.network_model // empty' <<<"$blob")
-        testmode_harness=$(jq -r 'if .benchmark_config.testmode_harness != null then .benchmark_config.testmode_harness else empty end' <<<"$blob")
+        local nodes_spawned network_model testmode_harness
+        nodes_spawned=""
+        network_model=""
+        testmode_harness=""
+        if [ -n "$blob" ]; then
+            nodes_spawned=$(jq -r '.benchmark_config.nodes_spawned // empty' <<<"$blob" 2>/dev/null || true)
+            network_model=$(jq -r '.benchmark_config.network_model // empty' <<<"$blob" 2>/dev/null || true)
+            testmode_harness=$(jq -r 'if .benchmark_config.testmode_harness != null then .benchmark_config.testmode_harness else empty end' <<<"$blob" 2>/dev/null || true)
+        fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@circuits/benchmarks/scripts/generate_report.sh` around lines 548 - 551, The
jq reads on $blob (populating nodes_spawned, network_model, testmode_harness)
must be guarded so they don't run when $blob is empty; modify generate_report.sh
to check if $blob is non-empty (e.g., if [ -n "$blob" ] or similar) before
calling jq and otherwise set nodes_spawned, network_model, and testmode_harness
to empty/default values, ensuring under set -e the script won't exit when there
is no integration blob.

503-503: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use phase_timings fallback for “Entire Test” extraction.

Line 503 only reads .timings_seconds; for current summaries that use .phase_timings, the run-config wall-clock row can be silently omitted.

Suggested fix
-        entire_test_sec=$(jq -r '.timings_seconds[]? | select(.label == "Entire Test") | .seconds' <<<"$blob" | head -1)
+        entire_test_sec=$(jq -r '
+          ((.phase_timings // .timings_seconds // [])[]
+            | select(.label == "Entire Test")
+            | if type == "object" then .seconds else . end) // empty
+        ' <<<"$blob" | head -1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@circuits/benchmarks/scripts/generate_report.sh` at line 503, The extraction
for entire_test_sec only looks at .timings_seconds and misses summaries that use
.phase_timings; update the logic that computes entire_test_sec to fall back to
.phase_timings when the jq query on .timings_seconds returns empty (i.e., check
.timings_seconds[]? | select(.label == "Entire Test") | .seconds first, and if
not present run the same select against .phase_timings[]?); reference the
existing variable name entire_test_sec and the jq invocation that queries
".timings_seconds[]? | select(.label == \"Entire Test\") | .seconds" and ensure
the final value is set from either source.
circuits/benchmarks/scripts/replay_folded_verify_gas.sh (1)

63-64: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject empty folded proof hex in preflight validation.

Line 63 uses jq truthiness, so empty strings can pass the check and cause invalid replay input.

Suggested fix
-if ! jq -e '.folded_artifacts.dkg_aggregator.proof_hex and .folded_artifacts.decryption_aggregator.proof_hex' "$SUMMARY_JSON" >/dev/null 2>&1; then
+if ! jq -e '
+  (.folded_artifacts.dkg_aggregator.proof_hex? | type == "string" and length > 2) and
+  (.folded_artifacts.decryption_aggregator.proof_hex? | type == "string" and length > 2)
+' "$SUMMARY_JSON" >/dev/null 2>&1; then
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@circuits/benchmarks/scripts/replay_folded_verify_gas.sh` around lines 63 -
64, The preflight jq check using SUMMARY_JSON currently only tests truthiness so
empty strings for .folded_artifacts.dkg_aggregator.proof_hex or
.folded_artifacts.decryption_aggregator.proof_hex can pass; change the jq
expression used in the if to explicitly reject empty strings (e.g. test that
both fields exist and are not "" or that their length > 0) so the script echoes
the error and exits when either proof_hex is empty; update the condition that
references SUMMARY_JSON and the jq path
.folded_artifacts.dkg_aggregator.proof_hex /
.folded_artifacts.decryption_aggregator.proof_hex accordingly.
crates/zk-prover/src/actors/accusation_manager.rs (2)

765-920: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bound incoming accusation deadlines to the configured validity window.

verify_accusation_signature proves who picked deadline, but it never checks that the value respects vote_validity_secs. A malicious accuser can choose an arbitrarily distant deadline, every honest voter will reuse it, and the on-chain block.timestamp <= deadline check will keep the slash evidence live far longer than governance intended.

Possible guard
+        let now = self.clock.unix_now_secs();
+        let max_deadline = now.saturating_add(self.vote_validity_secs);
+        if accusation.deadline < now || accusation.deadline > max_deadline {
+            warn!(
+                "Ignoring accusation from {} with out-of-window deadline {}",
+                accusation.accuser, accusation.deadline
+            );
+            return;
+        }
+
         let accusation_id = Self::accusation_id(&accusation);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/zk-prover/src/actors/accusation_manager.rs` around lines 765 - 920,
The accusation handler currently trusts Accusation.deadline from the signer
(verify_accusation_signature only checks signer, not range), allowing a
malicious accuser to set arbitrarily distant deadlines; update the logic that
accepts an accusation (after verify_accusation_signature and before inserting
PendingAccusation / creating AccusationVote) to enforce vote_validity_secs:
compute a max_deadline = now + self.vote_validity_secs and reject or clamp any
accusation.deadline > max_deadline (preferably reject and log a warning), and
ensure the created AccusationVote.deadline uses the bounded value; apply the
same bounding when handling forwarded C3a/C3b branches where you "Adopt the
accuser's deadline" so votes and pending state never carry an out-of-range
deadline.

800-889: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't trust accused_party_id from a forwarded accusation.

In the C3a/C3b branch, sender_party_id comes straight from the accusation even though only the forwarded payload's signer is authenticated. A malicious accuser can pair a valid payload from accused with a different party id and make honest peers re-verify against the wrong slot, turning a valid proof into a false agreement vote.

Suggested validation
+            if self
+                .committee
+                .get(accusation.accused_party_id as usize)
+                .copied()
+                != Some(accusation.accused)
+            {
+                warn!(
+                    "Forwarded accusation party_id {} does not match accused {}",
+                    accusation.accused_party_id, accusation.accused
+                );
+                return;
+            }
+
             let data_hash = Self::compute_payload_hash(forwarded);
packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol (1)

366-380: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add array-length consistency guard before persisting DKG anchors.

partyIds, skAgg, and esmAgg are persisted without checking equal lengths. A mismatched return can corrupt per-party attribution used for slashing evidence.

🔧 Suggested fix
         (
             uint256[] memory partyIds,
             bytes32[] memory skAgg,
             bytes32[] memory esmAgg
         ) = dkgFoldAttestationVerifier.verify(
                 address(this),
                 block.chainid,
                 e3Id,
                 proof,
                 dkgAttestationBundle
             );
+        require(
+            partyIds.length == skAgg.length && skAgg.length == esmAgg.length,
+            AttestationBindingCountMismatch()
+        );

         dkgPartyIds[e3Id] = partyIds;
         dkgSkAggCommits[e3Id] = skAgg;
         dkgEsmAggCommits[e3Id] = esmAgg;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol`
around lines 366 - 380, The three arrays returned from
dkgFoldAttestationVerifier.verify (partyIds, skAgg, esmAgg) are stored into
dkgPartyIds[e3Id], dkgSkAggCommits[e3Id], and dkgEsmAggCommits[e3Id] without
checking lengths; add a guard after the verify call that requires
partyIds.length == skAgg.length && partyIds.length == esmAgg.length and revert
with a clear error string (e.g., "DKG anchor length mismatch") if not, before
assigning to the storage mappings, to prevent inconsistent per-party attribution
and slashing corruption.
♻️ Duplicate comments (3)
circuits/benchmarks/scripts/extract_crisp_verify_gas.sh (1)

166-167: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Tighten folded-proof presence check before replay.

Line 166 still treats missing keys as valid (null != ""), so EVM replay can execute with invalid folded artifacts.

Suggested fix
-elif [ ! -s "$TMP_JSON_FOLDED" ] || ! jq -e '(.dkg_aggregator.proof_hex != "") and (.decryption_aggregator.proof_hex != "")' "$TMP_JSON_FOLDED" >/dev/null 2>&1; then
+elif [ ! -s "$TMP_JSON_FOLDED" ] || ! jq -e '
+  (.dkg_aggregator.proof_hex? | type == "string" and length > 2) and
+  (.decryption_aggregator.proof_hex? | type == "string" and length > 2)
+' "$TMP_JSON_FOLDED" >/dev/null 2>&1; then
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@circuits/benchmarks/scripts/extract_crisp_verify_gas.sh` around lines 166 -
167, The current jq check treats missing/null keys as valid because null != ""
passes; update the condition that inspects TMP_JSON_FOLDED so it verifies both
.dkg_aggregator.proof_hex and .decryption_aggregator.proof_hex exist and are
non-empty strings (e.g., ensure has/type and != "" or coalesce null to "" before
comparing) so the shell branch skips EVM replay when folded proofs are missing
or empty; modify the jq expression used in the if condition that references
TMP_JSON_FOLDED and the fields .dkg_aggregator.proof_hex and
.decryption_aggregator.proof_hex accordingly.
crates/tests/tests/integration.rs (1)

271-275: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the local-build fallback preset-verified.

Checking only for .build-stamp.json presence still lets a stale secure stamp authorize an insecure circuits/bin build, or vice versa, so this path can still silently mix BFV presets and circuit artifacts. Parse the stamp and require it to match preset_subdir before staging local artifacts; otherwise fall back to ensure_installed(). Based on learnings: In Rust tests, ensure that test suites consistently use either insecure BFV parameter presets or production/secure presets during transitions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tests/tests/integration.rs` around lines 271 - 275, The current check
only tests for preset_build_stamp.exists() which can allow mismatched
secure/insecure artifacts; update the logic around dist_marker /
circuits_bin_marker / preset_build_stamp so you parse the JSON in
preset_build_stamp (use the same structure produced elsewhere and check the
preserved preset_subdir field) and only call copy_dir_recursive(&dist_preset,
&preset_out) when the parsed stamp's preset_subdir equals the current
preset_subdir; if it does not match (or parse fails) fall back to calling
ensure_installed() instead of staging local artifacts; apply this check where
circuits_bin_marker and preset_build_stamp are evaluated so tests consistently
use either insecure BFV presets or secure presets during transitions.
examples/CRISP/client/.env.example (1)

5-5: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a placeholder for VITE_CRISP_TOKEN in the example env.

Hardcoding a concrete token address here is likely to drift and can cause confusing local setup failures. Prefer an explicit placeholder (or empty value) so users must set their environment-specific token.

Suggested fix
-VITE_CRISP_TOKEN=0x5081a39b8A5f0E35a8D959395a630b68B74Dd30f
+VITE_CRISP_TOKEN=<YOUR_CRISP_TOKEN_ADDRESS>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/CRISP/client/.env.example` at line 5, The example env file currently
hardcodes a concrete token value for VITE_CRISP_TOKEN; replace that concrete
token with a clear placeholder or empty value so users must supply their own
token (update the VITE_CRISP_TOKEN entry in examples/CRISP/client/.env.example
to something like VITE_CRISP_TOKEN=YOUR_CRISP_TOKEN or VITE_CRISP_TOKEN=).
Ensure the placeholder is obvious and consistent with other example env entries.
🧹 Nitpick comments (1)
packages/enclave-contracts/scripts/benchmarkGasFromRaw.ts (1)

59-68: ⚡ Quick win

Shell command injection risk via preset variable.

The preset value originates from BENCHMARK_PRESET env var or .active-preset.json and is interpolated directly into a shell command string. Malicious input like insecure-512; rm -rf / could execute arbitrary commands.

Consider using execFileSync with an argument array or sanitizing/validating the preset value.

♻️ Option 1: Validate preset format
 function readBenchmarkPreset(): string {
   const fromEnv = process.env.BENCHMARK_PRESET?.trim();
-  if (fromEnv) return fromEnv;
+  if (fromEnv) {
+    if (!/^[a-zA-Z0-9_-]+$/.test(fromEnv)) {
+      throw new Error(`Invalid BENCHMARK_PRESET format: ${fromEnv}`);
+    }
+    return fromEnv;
+  }
   const activePath = path.join(REPO_ROOT, "circuits/bin/.active-preset.json");
♻️ Option 2: Use spawnSync with args array (preferred)
+import { spawnSync } from "node:child_process";

-  execSync(
-    [
-      "pnpm generate:verifiers",
-      "--circuits dkg_aggregator,decryption_aggregator",
-      "--no-compile",
-      "--write",
-      `--preset ${preset}`,
-      `--output-dir ${benchDir}`,
-    ].join(" "),
-    { cwd: REPO_ROOT, stdio: "inherit" },
-  );
+  const result = spawnSync(
+    "pnpm",
+    [
+      "generate:verifiers",
+      "--circuits", "dkg_aggregator,decryption_aggregator",
+      "--no-compile",
+      "--write",
+      "--preset", preset,
+      "--output-dir", benchDir,
+    ],
+    { cwd: REPO_ROOT, stdio: "inherit" },
+  );
+  if (result.status !== 0) {
+    throw new Error(`generate:verifiers failed with status ${result.status}`);
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/enclave-contracts/scripts/benchmarkGasFromRaw.ts` around lines 59 -
68, The code invokes execSync to run "pnpm generate:verifiers" with a backfilled
`--preset ${preset}` which allows shell injection via the preset variable;
update the call in benchmarkGasFromRaw.ts (the execSync call that builds the
command string for "pnpm generate:verifiers" with benchDir and REPO_ROOT) to
avoid string interpolation: either validate/sanitize `preset` against an
allowlist/regex (e.g., only alphanumerics, dashes, underscores) before using it,
or replace execSync with execFileSync/spawnSync and pass the command and
arguments as an array (e.g., command "pnpm" and args
["generate:verifiers","--circuits","dkg_aggregator,decryption_aggregator","--no-compile","--write",`--preset`,
preset, `--output-dir`, benchDir]) so the preset cannot inject shell
metacharacters.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/tests/tests/integration.rs`:
- Around line 1162-1170: The test mutates the process-global env var
BENCHMARK_DKG_FOLD_ATTESTATION_VERIFIER without restoring it; wrap the set_var
call in the same RAII helper you used for the timeout env vars so the original
value is restored at scope exit: when benchmark_proof_aggregation_enabled() &&
std::env::var("BENCHMARK_DKG_FOLD_ATTESTATION_VERIFIER").is_err() create a
Scoped/guard (the same RAII pattern used for the timeout env vars) that sets
BENCHMARK_DKG_FOLD_ATTESTATION_VERIFIER to
"0x7969c5eD335650692Bc04293B07F5BF2e7A673C0" and drops at the end of the test to
restore the prior value (or unset it) instead of calling std::env::set_var
directly.

---

Outside diff comments:
In `@circuits/benchmarks/scripts/generate_report.sh`:
- Around line 548-551: The jq reads on $blob (populating nodes_spawned,
network_model, testmode_harness) must be guarded so they don't run when $blob is
empty; modify generate_report.sh to check if $blob is non-empty (e.g., if [ -n
"$blob" ] or similar) before calling jq and otherwise set nodes_spawned,
network_model, and testmode_harness to empty/default values, ensuring under set
-e the script won't exit when there is no integration blob.
- Line 503: The extraction for entire_test_sec only looks at .timings_seconds
and misses summaries that use .phase_timings; update the logic that computes
entire_test_sec to fall back to .phase_timings when the jq query on
.timings_seconds returns empty (i.e., check .timings_seconds[]? | select(.label
== "Entire Test") | .seconds first, and if not present run the same select
against .phase_timings[]?); reference the existing variable name entire_test_sec
and the jq invocation that queries ".timings_seconds[]? | select(.label ==
\"Entire Test\") | .seconds" and ensure the final value is set from either
source.

In `@circuits/benchmarks/scripts/replay_folded_verify_gas.sh`:
- Around line 63-64: The preflight jq check using SUMMARY_JSON currently only
tests truthiness so empty strings for .folded_artifacts.dkg_aggregator.proof_hex
or .folded_artifacts.decryption_aggregator.proof_hex can pass; change the jq
expression used in the if to explicitly reject empty strings (e.g. test that
both fields exist and are not "" or that their length > 0) so the script echoes
the error and exits when either proof_hex is empty; update the condition that
references SUMMARY_JSON and the jq path
.folded_artifacts.dkg_aggregator.proof_hex /
.folded_artifacts.decryption_aggregator.proof_hex accordingly.

In `@crates/zk-prover/src/actors/accusation_manager.rs`:
- Around line 765-920: The accusation handler currently trusts
Accusation.deadline from the signer (verify_accusation_signature only checks
signer, not range), allowing a malicious accuser to set arbitrarily distant
deadlines; update the logic that accepts an accusation (after
verify_accusation_signature and before inserting PendingAccusation / creating
AccusationVote) to enforce vote_validity_secs: compute a max_deadline = now +
self.vote_validity_secs and reject or clamp any accusation.deadline >
max_deadline (preferably reject and log a warning), and ensure the created
AccusationVote.deadline uses the bounded value; apply the same bounding when
handling forwarded C3a/C3b branches where you "Adopt the accuser's deadline" so
votes and pending state never carry an out-of-range deadline.

In `@packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol`:
- Around line 366-380: The three arrays returned from
dkgFoldAttestationVerifier.verify (partyIds, skAgg, esmAgg) are stored into
dkgPartyIds[e3Id], dkgSkAggCommits[e3Id], and dkgEsmAggCommits[e3Id] without
checking lengths; add a guard after the verify call that requires
partyIds.length == skAgg.length && partyIds.length == esmAgg.length and revert
with a clear error string (e.g., "DKG anchor length mismatch") if not, before
assigning to the storage mappings, to prevent inconsistent per-party attribution
and slashing corruption.

---

Duplicate comments:
In `@circuits/benchmarks/scripts/extract_crisp_verify_gas.sh`:
- Around line 166-167: The current jq check treats missing/null keys as valid
because null != "" passes; update the condition that inspects TMP_JSON_FOLDED so
it verifies both .dkg_aggregator.proof_hex and .decryption_aggregator.proof_hex
exist and are non-empty strings (e.g., ensure has/type and != "" or coalesce
null to "" before comparing) so the shell branch skips EVM replay when folded
proofs are missing or empty; modify the jq expression used in the if condition
that references TMP_JSON_FOLDED and the fields .dkg_aggregator.proof_hex and
.decryption_aggregator.proof_hex accordingly.

In `@crates/tests/tests/integration.rs`:
- Around line 271-275: The current check only tests for
preset_build_stamp.exists() which can allow mismatched secure/insecure
artifacts; update the logic around dist_marker / circuits_bin_marker /
preset_build_stamp so you parse the JSON in preset_build_stamp (use the same
structure produced elsewhere and check the preserved preset_subdir field) and
only call copy_dir_recursive(&dist_preset, &preset_out) when the parsed stamp's
preset_subdir equals the current preset_subdir; if it does not match (or parse
fails) fall back to calling ensure_installed() instead of staging local
artifacts; apply this check where circuits_bin_marker and preset_build_stamp are
evaluated so tests consistently use either insecure BFV presets or secure
presets during transitions.

In `@examples/CRISP/client/.env.example`:
- Line 5: The example env file currently hardcodes a concrete token value for
VITE_CRISP_TOKEN; replace that concrete token with a clear placeholder or empty
value so users must supply their own token (update the VITE_CRISP_TOKEN entry in
examples/CRISP/client/.env.example to something like
VITE_CRISP_TOKEN=YOUR_CRISP_TOKEN or VITE_CRISP_TOKEN=). Ensure the placeholder
is obvious and consistent with other example env entries.

---

Nitpick comments:
In `@packages/enclave-contracts/scripts/benchmarkGasFromRaw.ts`:
- Around line 59-68: The code invokes execSync to run "pnpm generate:verifiers"
with a backfilled `--preset ${preset}` which allows shell injection via the
preset variable; update the call in benchmarkGasFromRaw.ts (the execSync call
that builds the command string for "pnpm generate:verifiers" with benchDir and
REPO_ROOT) to avoid string interpolation: either validate/sanitize `preset`
against an allowlist/regex (e.g., only alphanumerics, dashes, underscores)
before using it, or replace execSync with execFileSync/spawnSync and pass the
command and arguments as an array (e.g., command "pnpm" and args
["generate:verifiers","--circuits","dkg_aggregator,decryption_aggregator","--no-compile","--write",`--preset`,
preset, `--output-dir`, benchDir]) so the preset cannot inject shell
metacharacters.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a8ce00c8-9da9-4572-9cc9-47095f9689fb

📥 Commits

Reviewing files that changed from the base of the PR and between 4c0d561 and 6d3fbb8.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (101)
  • circuits/.gitignore
  • circuits/benchmarks/README.md
  • circuits/benchmarks/results_insecure_agg/benchmark_run_meta.json
  • circuits/benchmarks/results_insecure_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_insecure_agg/integration_summary.json
  • circuits/benchmarks/results_insecure_agg/report.md
  • circuits/benchmarks/results_insecure_no_agg/benchmark_run_meta.json
  • circuits/benchmarks/results_insecure_no_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_insecure_no_agg/integration_summary.json
  • circuits/benchmarks/results_insecure_no_agg/report.md
  • circuits/benchmarks/results_secure_agg/benchmark_run_meta.json
  • circuits/benchmarks/results_secure_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_secure_agg/integration_summary.json
  • circuits/benchmarks/results_secure_agg/report.md
  • circuits/benchmarks/results_secure_no_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_secure_no_agg/integration_summary.json
  • circuits/benchmarks/results_secure_no_agg/report.md
  • circuits/benchmarks/scripts/check_circuit_preset_artifacts.sh
  • circuits/benchmarks/scripts/extract_crisp_verify_gas.sh
  • circuits/benchmarks/scripts/generate_report.sh
  • circuits/benchmarks/scripts/replay_folded_verify_gas.sh
  • circuits/benchmarks/scripts/run_benchmarks.sh
  • crates/Dockerfile
  • crates/ciphernode-builder/src/ciphernode_builder.rs
  • crates/config/src/app_config.rs
  • crates/entrypoint/src/start/start.rs
  • crates/events/src/enclave_event/accusation_quorum_reached.rs
  • crates/events/src/enclave_event/accusation_vote.rs
  • crates/events/src/enclave_event/proof_failure_accusation.rs
  • crates/evm/src/ciphernode_registry_sol.rs
  • crates/evm/src/lib.rs
  • crates/evm/src/slashing_manager_sol_writer.rs
  • crates/keyshare/src/threshold_keyshare.rs
  • crates/multithread/src/multithread.rs
  • crates/test-helpers/src/lib.rs
  • crates/test-helpers/src/usecase_helpers.rs
  • crates/tests/tests/integration.rs
  • crates/trbfv/src/gen_esi_sss.rs
  • crates/trbfv/src/gen_pk_share_and_sk_sss.rs
  • crates/zk-prover/Cargo.toml
  • crates/zk-prover/src/actors/accusation_manager.rs
  • crates/zk-prover/src/actors/accusation_manager_ext.rs
  • crates/zk-prover/src/actors/mod.rs
  • crates/zk-prover/src/actors/node_proof_aggregator.rs
  • crates/zk-prover/src/actors/proof_request.rs
  • crates/zk-prover/src/actors/proof_verification.rs
  • crates/zk-prover/src/prover.rs
  • crates/zk-prover/tests/slashing_integration_tests.rs
  • docs/pages/internals/dkg.mdx
  • examples/CRISP/Readme.md
  • examples/CRISP/client/.env.example
  • examples/CRISP/client/src/components/Cards/PollCardResult.tsx
  • examples/CRISP/crisp.dev.env.example
  • examples/CRISP/docs/PROOF_AGGREGATION_AND_ZK.md
  • examples/CRISP/enclave.config.yaml
  • examples/CRISP/packages/crisp-contracts/.env.example
  • examples/CRISP/packages/crisp-contracts/README.md
  • examples/CRISP/packages/crisp-contracts/deploy/deploy.ts
  • examples/CRISP/packages/crisp-contracts/deploy/syncCrispEnv.ts
  • examples/CRISP/packages/crisp-contracts/deployed_contracts.json
  • examples/CRISP/scripts/crisp_deploy.sh
  • examples/CRISP/scripts/lib/dev_config.sh
  • examples/CRISP/scripts/setup.sh
  • examples/CRISP/server/.env.example
  • examples/CRISP/test/crisp.spec.ts
  • packages/enclave-contracts/.gitignore
  • packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json
  • packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json
  • packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json
  • packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json
  • packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json
  • packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json
  • packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json
  • packages/enclave-contracts/contracts/interfaces/ICiphernodeRegistry.sol
  • packages/enclave-contracts/contracts/interfaces/ISlashingManager.sol
  • packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol
  • packages/enclave-contracts/contracts/slashing/SlashingManager.sol
  • packages/enclave-contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol
  • packages/enclave-contracts/deployed_contracts.json
  • packages/enclave-contracts/hardhat.config.ts
  • packages/enclave-contracts/package.json
  • packages/enclave-contracts/scripts/benchmarkGasFromRaw.ts
  • packages/enclave-contracts/scripts/deployAndSave/enclaveToken.ts
  • packages/enclave-contracts/scripts/deployAndSave/slashingManager.ts
  • packages/enclave-contracts/scripts/deployEnclave.ts
  • packages/enclave-contracts/tasks/ciphernode.ts
  • packages/enclave-contracts/test/E3Lifecycle/E3Integration.spec.ts
  • packages/enclave-contracts/test/Enclave.spec.ts
  • packages/enclave-contracts/test/Pricing/DustRotation.spec.ts
  • packages/enclave-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts
  • packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts
  • packages/enclave-contracts/test/Slashing/CommitteeExpulsion.spec.ts
  • packages/enclave-contracts/test/Slashing/SlashingManager.spec.ts
  • packages/enclave-contracts/test/fixtures/bfv_vk_binding/folded_artifacts.json
  • packages/enclave-contracts/test/fixtures/helpers.ts
  • scripts/build-circuits.ts
  • scripts/generate-verifiers.ts
  • templates/default/deployed_contracts.json
  • templates/default/enclave.config.yaml
  • templates/default/hardhat.config.ts
  • tests/integration/enclave.config.yaml
💤 Files with no reviewable changes (5)
  • packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json
  • circuits/benchmarks/results_secure_agg/integration_summary.json
  • circuits/benchmarks/results_insecure_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_secure_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_insecure_agg/integration_summary.json
✅ Files skipped from review due to trivial changes (24)
  • packages/enclave-contracts/.gitignore
  • packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json
  • examples/CRISP/crisp.dev.env.example
  • examples/CRISP/packages/crisp-contracts/.env.example
  • crates/Dockerfile
  • examples/CRISP/Readme.md
  • circuits/benchmarks/results_secure_agg/benchmark_run_meta.json
  • packages/enclave-contracts/contracts/interfaces/ISlashingManager.sol
  • circuits/benchmarks/results_secure_no_agg/report.md
  • circuits/benchmarks/results_insecure_agg/benchmark_run_meta.json
  • packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json
  • examples/CRISP/docs/PROOF_AGGREGATION_AND_ZK.md
  • circuits/benchmarks/results_secure_no_agg/crisp_verify_gas.json
  • packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json
  • circuits/benchmarks/results_insecure_no_agg/benchmark_run_meta.json
  • examples/CRISP/packages/crisp-contracts/deployed_contracts.json
  • circuits/benchmarks/results_secure_no_agg/integration_summary.json
  • circuits/benchmarks/results_insecure_no_agg/integration_summary.json
  • circuits/benchmarks/results_insecure_agg/report.md
  • packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json
  • circuits/benchmarks/results_insecure_no_agg/report.md
  • circuits/benchmarks/results_secure_agg/report.md
  • docs/pages/internals/dkg.mdx
  • packages/enclave-contracts/deployed_contracts.json

Comment thread crates/tests/tests/integration.rs Outdated
0xjei added 5 commits May 23, 2026 23:22
Diagnostic console.error calls + test-side log buffer added in 142034a
to identify the post-reload cast-vote race. CI confirmed the race was
in waitForWalletSession (other agent's fix) — actor-signed signatures
recover cleanly, end-to-end is green. Restore the three touched files
to their pre-instrumentation state.

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

Caution

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

⚠️ Outside diff range comments (4)
crates/keyshare/src/threshold_keyshare.rs (1)

499-511: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only clear the deferred flag after KeyshareCreated publishes successfully.

If publish_keyshare_created() errors here, pending_keyshare_publish has already been reset, so the deferred publish is dropped permanently.

Proposed fix
-        self.pending_keyshare_publish = false;
-        self.publish_keyshare_created(ec)
+        let result = self.publish_keyshare_created(ec);
+        if result.is_ok() {
+            self.pending_keyshare_publish = false;
+        }
+        result
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/keyshare/src/threshold_keyshare.rs` around lines 499 - 511, In
try_finish_deferred_keyshare_publish, don't clear self.pending_keyshare_publish
before calling publish_keyshare_created; instead, call
publish_keyshare_created(ec) first and only set self.pending_keyshare_publish =
false after that call returns Ok (or set it in the success arm), propagating any
Err from publish_keyshare_created so the deferred flag remains set on failure;
refer to the try_finish_deferred_keyshare_publish method, the
pending_keyshare_publish field, and the publish_keyshare_created(...) call to
locate and update the logic accordingly.
crates/evm/src/ciphernode_registry_sol.rs (1)

692-695: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject aggregated DKG publishes that are missing the attestation bundle.

When dkg_aggregator_proof is present, sending Bytes::new() here just pushes an invalid proof-aggregation publish on-chain. This should fail fast locally instead of attempting a revert-prone publishCommittee call.

Proposed fix
+    if dkg_aggregator_proof.is_some() != dkg_attestation_bundle.is_some() {
+        anyhow::bail!(
+            "aggregated DKG publish requires both the aggregator proof and the attestation bundle"
+        );
+    }
+
     let attestation_bundle: Bytes = match dkg_attestation_bundle {
         Some(b) => Bytes::copy_from_slice(b),
         None => Bytes::new(),
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/evm/src/ciphernode_registry_sol.rs` around lines 692 - 695, The code
currently constructs attestation_bundle as Bytes::new() when
dkg_attestation_bundle is None, allowing an invalid aggregated DKG publish to be
sent if dkg_aggregator_proof is present; change the logic to early-fail when an
aggregator proof is provided but the attestation bundle is missing: check
dkg_aggregator_proof and dkg_attestation_bundle together and return an error (or
Err variant / early return) if dkg_aggregator_proof.is_some() &&
dkg_attestation_bundle.is_none(), otherwise construct attestation_bundle only
from Some(b); ensure this prevents calling publishCommittee with an empty
attestation_bundle and surfaces a clear error from the surrounding function
(reference variables: dkg_attestation_bundle, dkg_aggregator_proof,
attestation_bundle, and the publishCommittee call).
circuits/benchmarks/scripts/generate_report.sh (1)

548-551: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard these jq reads when no integration blob is available.

emit_audit_warnings() explicitly supports raw-only runs, but these assignments still parse "$blob" unconditionally. With an empty blob, jq exits non-zero under set -e, so report generation can abort before the script reaches the intended “No integration summary” fallback.

Suggested fix
-        local nodes_spawned network_model testmode_harness
-        nodes_spawned=$(jq -r '.benchmark_config.nodes_spawned // empty' <<<"$blob")
-        network_model=$(jq -r '.benchmark_config.network_model // empty' <<<"$blob")
-        testmode_harness=$(jq -r 'if .benchmark_config.testmode_harness != null then .benchmark_config.testmode_harness else empty end' <<<"$blob")
+        local nodes_spawned="" network_model="" testmode_harness=""
+        if [ -n "$blob" ]; then
+            nodes_spawned=$(jq -r '.benchmark_config.nodes_spawned // empty' <<<"$blob")
+            network_model=$(jq -r '.benchmark_config.network_model // empty' <<<"$blob")
+            testmode_harness=$(jq -r 'if .benchmark_config.testmode_harness != null then .benchmark_config.testmode_harness else empty end' <<<"$blob")
+        fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@circuits/benchmarks/scripts/generate_report.sh` around lines 548 - 551, The
jq reads for nodes_spawned, network_model and testmode_harness must be skipped
when there is no integration blob to avoid jq failing under set -e; update
generate_report.sh so that before executing the jq assignments for
nodes_spawned, network_model and testmode_harness you check that blob is
non-empty (e.g. if [[ -n "$blob" ]]; then ... fi) and only run the three jq
commands inside that branch, leaving the variables empty otherwise so
emit_audit_warnings() and the “No integration summary” fallback proceed safely.
crates/zk-prover/src/actors/accusation_manager.rs (1)

781-920: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate incoming accusation deadlines before re-signing them.

on_accusation_received() reuses accusation.deadline verbatim for our vote, but never checks whether that deadline is still live or even within the local vote_validity_secs window. A malicious accuser can therefore gather signatures on already-expired evidence that will always fail on chain, or on arbitrarily long-lived votes that bypass the freshness bound this PR is introducing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/zk-prover/src/actors/accusation_manager.rs` around lines 781 - 920,
The code in on_accusation_received reuses accusation.deadline directly when
building AccusationVote (voter signature) without checking freshness; before
creating/signing the AccusationVote validate that accusation.deadline is not
already expired and is within our allowed freshness window
(self.vote_validity_secs) relative to current time (use the same clock source
used elsewhere, e.g., actor ctx or system time). If the deadline has already
passed or exceeds now + self.vote_validity_secs, abstain (log and return) or
clamp the deadline to now + self.vote_validity_secs (prefer abstain for expired,
clamp for too-far future) and only then proceed to build/sign the
AccusationVote; update references around AccusationVote, pending insertion, and
any call to on_vote_timeout to use the validated/clamped deadline.
♻️ Duplicate comments (3)
examples/CRISP/client/.env.example (1)

7-7: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a placeholder instead of a fixed CRISP token value.

Line 7 still hardcodes a deployment-specific token address; this can become stale and cause incorrect local setup defaults.

Suggested fix
-VITE_CRISP_TOKEN=0x5081a39b8A5f0E35a8D959395a630b68B74Dd30f
+VITE_CRISP_TOKEN=<YOUR_CRISP_TOKEN_HERE>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/CRISP/client/.env.example` at line 7, The .env example currently
hardcodes a real token value for VITE_CRISP_TOKEN which can be stale or leak
secrets; replace the fixed value with a clear placeholder (e.g.,
VITE_CRISP_TOKEN=<YOUR_CRISP_TOKEN_HERE>) and update any README instructions to
tell users to insert their own token for VITE_CRISP_TOKEN when setting up
locally, ensuring no real token remains in examples.
circuits/benchmarks/scripts/extract_crisp_verify_gas.sh (1)

166-168: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat missing proof_hex the same as empty before replay.

null != "" is truthy in jq, so this guard still passes when either proof_hex field is missing. That lets the replay branch run with incomplete folded artifacts and can skew the reported Pi_DKG / Pi_dec gas numbers.

♻️ Minimal fix
-elif [ ! -s "$TMP_JSON_FOLDED" ] || ! jq -e '(.dkg_aggregator.proof_hex != "") and (.decryption_aggregator.proof_hex != "")' "$TMP_JSON_FOLDED" >/dev/null 2>&1; then
+elif [ ! -s "$TMP_JSON_FOLDED" ] || ! jq -e '
+  ((.dkg_aggregator.proof_hex // "") | length > 2) and
+  ((.decryption_aggregator.proof_hex // "") | length > 2)
+' "$TMP_JSON_FOLDED" >/dev/null 2>&1; then
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@circuits/benchmarks/scripts/extract_crisp_verify_gas.sh` around lines 166 -
168, The jq guard currently treats missing proof_hex as non-empty because null
!= "" is true; update the condition used against TMP_JSON_FOLDED so missing and
empty are equivalent by normalizing with a fallback or explicit existence check
(e.g., use the null-coalescing pattern or check for has + not null) for both
.dkg_aggregator.proof_hex and .decryption_aggregator.proof_hex, and keep the
rest of the branch that echoes the skip message and writes '{}' to
TMP_JSON_ENCLAVE unchanged; target the jq invocation that currently reads
TMP_JSON_FOLDED in the elif line to apply this change.
crates/tests/tests/integration.rs (1)

299-315: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Existence-only .build-stamp.json checking can still mix circuit presets.

This branch trusts circuits/bin for preset_subdir when dist/circuits/<preset>/.build-stamp.json merely exists, but it never reads the stamp to confirm that the current local build actually targeted that preset. A stale secure stamp plus a later insecure local build still satisfies this check and stages the wrong artifacts into preset_out, which reintroduces mixed-parameter runs.

Based on learnings: In Rust tests, ensure that test suites consistently use either insecure BFV parameter presets or production/secure presets during transitions. Do not mix insecure and production parameters within the same test run or across tests during the transition window.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tests/tests/integration.rs` around lines 299 - 315, The code currently
treats the mere existence of preset_build_stamp as proof the local circuits
match preset_subdir, allowing stale stamps to mix presets; change the logic to
open and parse the .build-stamp.json (preset_build_stamp) and validate its
recorded preset/target field (or checksum/commit field) equals the current
preset_subdir (and/or matches the circuits/bin identity) before trusting
circuits_bin_marker. If the stamp's preset does not match, behave as if the
stamp is missing (call ZkBackend::ensure_installed to download fresh circuits)
and avoid populating preset_out from the local circuits/bin. Reference symbols:
preset_build_stamp, circuits_bin_marker, preset_subdir, preset_out, and
ZkBackend::ensure_installed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ciphernode-builder/src/ciphernode_builder.rs`:
- Around line 586-618: The code currently computes a single
dkg_fold_verifier_addr and accusation_vote_validity_secs at startup; change
these to be keyed maps by chain id (use e3_id.chain_id()) instead of globals so
each enabled chain can have its own settings. Specifically, replace the
single-valued dkg_fold_verifier_addr and accusation_vote_validity_secs with
HashMap<ChainId, ...>, populate them by calling
Self::fetch_dkg_fold_attestation_verifier_from_registry and
Self::fetch_accusation_vote_validity_from_registry per chain (with the same
BENCHMARK_* env-var fallback applied per chain), and update places that
construct AccusationManagerExtension and any E3-building logic to look up values
by e3_id.chain_id() rather than using the global variables; reference functions
fetch_dkg_fold_attestation_verifier_from_registry,
fetch_accusation_vote_validity_from_registry and the identifiers
dkg_fold_verifier_addr and accusation_vote_validity_secs when making the
changes.

In `@crates/multithread/src/multithread.rs`:
- Around line 537-539: The current code locks the shared RNG (rng.lock(),
rng_guard, rng_mut) before the match causing unrelated branches to serialize;
remove the pre-match lock and instead acquire the RNG mutex only inside the
branches that actually need randomness (GenPkShareAndSkSss and GenEsiSss), e.g.
call rng.lock().expect(...) and bind the mutable RNG reference only within those
branch scopes where it is used; ensure the lock is dropped promptly (do not hold
it across awaits or long-running work) and update uses of rng_mut in those
branches accordingly.

In `@docs/pages/internals/dkg.mdx`:
- Around line 567-573: The text overstates that accusationVoteValidity = 0 is an
absolute hard stop; update the description to say it "practically disables"
slashing or describes it as a zero-second window instead, and mention that
because the on-chain check uses block.timestamp <= deadline a vote with deadline
= now can still succeed if included in the same block; reference the terms
accusationVoteValidity, deadline, proposeSlash and quorum M in the paragraph so
readers know the exact parameters affected and the on-chain race condition.

In `@examples/CRISP/client/src/utils/methods.ts`:
- Around line 25-28: getChain currently only special-cases 31337 and otherwise
ignores explicit VITE_CHAIN_ID values by falling back to DEV, so update getChain
to honor VITE_CHAIN_ID for all configured networks: read chainId from
import.meta.env.VITE_CHAIN_ID, normalize it to a string/number, then return
anvil when it matches 31337 (string or number) and return sepolia when it
matches the sepolia identifier(s) you support (e.g., 'sepolia' or its chainId
like '11155111'), and only if chainId is missing/empty fall back to
import.meta.env.DEV ? anvil : sepolia; reference the getChain function, the
chainId const, and the anvil/sepolia symbols when making the change.

In `@examples/CRISP/packages/crisp-contracts/deploy/syncCrispEnv.ts`:
- Around line 112-123: The loop over mockMappings only sets
serverUpdates[envKey] when deploymentAddress(contractName, chain) returns an
addr, leaving stale MOCK_* entries in server/.env; update the loop in
syncCrispEnv.ts so that for each [envKey, contractName] you call
deploymentAddress(contractName, chain) and if addr exists set
serverUpdates[envKey] = addr, otherwise remove any previous value by deleting
serverUpdates[envKey] (or setting it to an empty string) so old addresses aren’t
left behind when a deployment is absent.

In `@examples/CRISP/scripts/lib/dev_config.sh`:
- Around line 79-80: The script currently runs cd "${REPO_ROOT}" then executes
pnpm build:circuits without checking cd succeeded; update the dev_config.sh so
the directory change is verified before running pnpm (e.g., check the exit
status of cd "${REPO_ROOT}" and exit non‑zero on failure, or chain the commands
so pnpm build:circuits --preset "${CRISP_BFV_PRESET}" --skip-if-built only runs
if cd succeeded), referencing the existing cd "${REPO_ROOT}" and the pnpm
build:circuits invocation.

In `@examples/CRISP/scripts/setup.sh`:
- Around line 7-16: The remaining setup steps still run relative to the caller
cwd; update each command after load_crisp_dev_config so they are executed from
REPO_ROOT (or SCRIPT_DIR as appropriate) — e.g., wrap the pnpm install, pnpm
build:ts and the subsequent commands referenced in the review to run with an
explicit (cd "${REPO_ROOT}" && ...) or use pushd/popd so every invocation is
path-safe; locate the commands using the SCRIPT_DIR and REPO_ROOT variables in
this script and convert them to use the explicit cd pattern so the script is
cwd-independent.

In `@packages/enclave-contracts/scripts/benchmarkGasFromRaw.ts`:
- Around line 29-44: readBenchmarkPreset() currently prefers BENCHMARK_PRESET or
local .active-preset.json and ignores the preset embedded in the replayed proof
artifacts (folded input), causing non-reproducible verifier selection; update
the logic so that wherever readBenchmarkPreset() is used (and specifically at
the points that load foldedPath), you first attempt to extract the producing
benchmark preset from the folded proof metadata (the object loaded from
foldedPath) and return that if present, only falling back to BENCHMARK_PRESET /
.active-preset.json when the artifact has no preset; modify
readBenchmarkPreset() (or add an overload/readFromArtifact helper) to accept the
parsed folded artifact (or read it prior to calling) and prefer artifact.preset
before existing fallbacks, and apply the same change at the other locations
referenced (around the blocks at the earlier and later call sites).
- Around line 50-75: The code builds shell command strings with execSync in
ensureHonkVerifierContractDir and interpolates preset directly; replace those
execSync string-invocations with child_process.execFileSync calls that pass the
executable and an args array so preset is a separate argument (e.g.
execFileSync('pnpm',
['generate:verifiers','--circuits','dkg_aggregator,decryption_aggregator','--no-compile','--write','--preset',
preset,'--output-dir', benchDir], {cwd: REPO_ROOT, stdio: 'inherit'}) ) and
likewise call execFileSync('pnpm', ['hardhat','compile'], { cwd:
path.join(REPO_ROOT,'packages/enclave-contracts'), stdio: 'inherit' }); this
removes shell interpolation risk while preserving behavior in
ensureHonkVerifierContractDir.

---

Outside diff comments:
In `@circuits/benchmarks/scripts/generate_report.sh`:
- Around line 548-551: The jq reads for nodes_spawned, network_model and
testmode_harness must be skipped when there is no integration blob to avoid jq
failing under set -e; update generate_report.sh so that before executing the jq
assignments for nodes_spawned, network_model and testmode_harness you check that
blob is non-empty (e.g. if [[ -n "$blob" ]]; then ... fi) and only run the three
jq commands inside that branch, leaving the variables empty otherwise so
emit_audit_warnings() and the “No integration summary” fallback proceed safely.

In `@crates/evm/src/ciphernode_registry_sol.rs`:
- Around line 692-695: The code currently constructs attestation_bundle as
Bytes::new() when dkg_attestation_bundle is None, allowing an invalid aggregated
DKG publish to be sent if dkg_aggregator_proof is present; change the logic to
early-fail when an aggregator proof is provided but the attestation bundle is
missing: check dkg_aggregator_proof and dkg_attestation_bundle together and
return an error (or Err variant / early return) if
dkg_aggregator_proof.is_some() && dkg_attestation_bundle.is_none(), otherwise
construct attestation_bundle only from Some(b); ensure this prevents calling
publishCommittee with an empty attestation_bundle and surfaces a clear error
from the surrounding function (reference variables: dkg_attestation_bundle,
dkg_aggregator_proof, attestation_bundle, and the publishCommittee call).

In `@crates/keyshare/src/threshold_keyshare.rs`:
- Around line 499-511: In try_finish_deferred_keyshare_publish, don't clear
self.pending_keyshare_publish before calling publish_keyshare_created; instead,
call publish_keyshare_created(ec) first and only set
self.pending_keyshare_publish = false after that call returns Ok (or set it in
the success arm), propagating any Err from publish_keyshare_created so the
deferred flag remains set on failure; refer to the
try_finish_deferred_keyshare_publish method, the pending_keyshare_publish field,
and the publish_keyshare_created(...) call to locate and update the logic
accordingly.

In `@crates/zk-prover/src/actors/accusation_manager.rs`:
- Around line 781-920: The code in on_accusation_received reuses
accusation.deadline directly when building AccusationVote (voter signature)
without checking freshness; before creating/signing the AccusationVote validate
that accusation.deadline is not already expired and is within our allowed
freshness window (self.vote_validity_secs) relative to current time (use the
same clock source used elsewhere, e.g., actor ctx or system time). If the
deadline has already passed or exceeds now + self.vote_validity_secs, abstain
(log and return) or clamp the deadline to now + self.vote_validity_secs (prefer
abstain for expired, clamp for too-far future) and only then proceed to
build/sign the AccusationVote; update references around AccusationVote, pending
insertion, and any call to on_vote_timeout to use the validated/clamped
deadline.

---

Duplicate comments:
In `@circuits/benchmarks/scripts/extract_crisp_verify_gas.sh`:
- Around line 166-168: The jq guard currently treats missing proof_hex as
non-empty because null != "" is true; update the condition used against
TMP_JSON_FOLDED so missing and empty are equivalent by normalizing with a
fallback or explicit existence check (e.g., use the null-coalescing pattern or
check for has + not null) for both .dkg_aggregator.proof_hex and
.decryption_aggregator.proof_hex, and keep the rest of the branch that echoes
the skip message and writes '{}' to TMP_JSON_ENCLAVE unchanged; target the jq
invocation that currently reads TMP_JSON_FOLDED in the elif line to apply this
change.

In `@crates/tests/tests/integration.rs`:
- Around line 299-315: The code currently treats the mere existence of
preset_build_stamp as proof the local circuits match preset_subdir, allowing
stale stamps to mix presets; change the logic to open and parse the
.build-stamp.json (preset_build_stamp) and validate its recorded preset/target
field (or checksum/commit field) equals the current preset_subdir (and/or
matches the circuits/bin identity) before trusting circuits_bin_marker. If the
stamp's preset does not match, behave as if the stamp is missing (call
ZkBackend::ensure_installed to download fresh circuits) and avoid populating
preset_out from the local circuits/bin. Reference symbols: preset_build_stamp,
circuits_bin_marker, preset_subdir, preset_out, and ZkBackend::ensure_installed.

In `@examples/CRISP/client/.env.example`:
- Line 7: The .env example currently hardcodes a real token value for
VITE_CRISP_TOKEN which can be stale or leak secrets; replace the fixed value
with a clear placeholder (e.g., VITE_CRISP_TOKEN=<YOUR_CRISP_TOKEN_HERE>) and
update any README instructions to tell users to insert their own token for
VITE_CRISP_TOKEN when setting up locally, ensuring no real token remains in
examples.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 346781cf-77fd-4236-af7a-81cf2b9e0c7d

📥 Commits

Reviewing files that changed from the base of the PR and between 4c0d561 and defde70.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • examples/CRISP/client/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (107)
  • circuits/.gitignore
  • circuits/benchmarks/README.md
  • circuits/benchmarks/results_insecure_agg/benchmark_run_meta.json
  • circuits/benchmarks/results_insecure_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_insecure_agg/integration_summary.json
  • circuits/benchmarks/results_insecure_agg/report.md
  • circuits/benchmarks/results_insecure_no_agg/benchmark_run_meta.json
  • circuits/benchmarks/results_insecure_no_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_insecure_no_agg/integration_summary.json
  • circuits/benchmarks/results_insecure_no_agg/report.md
  • circuits/benchmarks/results_secure_agg/benchmark_run_meta.json
  • circuits/benchmarks/results_secure_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_secure_agg/integration_summary.json
  • circuits/benchmarks/results_secure_agg/report.md
  • circuits/benchmarks/results_secure_no_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_secure_no_agg/integration_summary.json
  • circuits/benchmarks/results_secure_no_agg/report.md
  • circuits/benchmarks/scripts/check_circuit_preset_artifacts.sh
  • circuits/benchmarks/scripts/extract_crisp_verify_gas.sh
  • circuits/benchmarks/scripts/generate_report.sh
  • circuits/benchmarks/scripts/replay_folded_verify_gas.sh
  • circuits/benchmarks/scripts/run_benchmarks.sh
  • crates/Dockerfile
  • crates/ciphernode-builder/src/ciphernode_builder.rs
  • crates/config/src/app_config.rs
  • crates/entrypoint/src/start/start.rs
  • crates/events/src/enclave_event/accusation_quorum_reached.rs
  • crates/events/src/enclave_event/accusation_vote.rs
  • crates/events/src/enclave_event/proof_failure_accusation.rs
  • crates/evm/src/ciphernode_registry_sol.rs
  • crates/evm/src/lib.rs
  • crates/evm/src/slashing_manager_sol_writer.rs
  • crates/keyshare/src/threshold_keyshare.rs
  • crates/multithread/src/multithread.rs
  • crates/test-helpers/src/lib.rs
  • crates/test-helpers/src/usecase_helpers.rs
  • crates/tests/tests/integration.rs
  • crates/trbfv/src/gen_esi_sss.rs
  • crates/trbfv/src/gen_pk_share_and_sk_sss.rs
  • crates/zk-prover/Cargo.toml
  • crates/zk-prover/src/actors/accusation_manager.rs
  • crates/zk-prover/src/actors/accusation_manager_ext.rs
  • crates/zk-prover/src/actors/mod.rs
  • crates/zk-prover/src/actors/node_proof_aggregator.rs
  • crates/zk-prover/src/actors/proof_request.rs
  • crates/zk-prover/src/actors/proof_verification.rs
  • crates/zk-prover/src/prover.rs
  • crates/zk-prover/tests/slashing_integration_tests.rs
  • docs/pages/cryptography.mdx
  • docs/pages/internals/dkg.mdx
  • examples/CRISP/Readme.md
  • examples/CRISP/client/.env.example
  • examples/CRISP/client/.npmrc
  • examples/CRISP/client/src/components/Cards/PollCardResult.tsx
  • examples/CRISP/client/src/providers/Web3Provider.tsx
  • examples/CRISP/client/src/utils/methods.ts
  • examples/CRISP/client/vercel.json
  • examples/CRISP/crisp.dev.env.example
  • examples/CRISP/docs/PROOF_AGGREGATION_AND_ZK.md
  • examples/CRISP/enclave.config.yaml
  • examples/CRISP/packages/crisp-contracts/.env.example
  • examples/CRISP/packages/crisp-contracts/README.md
  • examples/CRISP/packages/crisp-contracts/deploy/deploy.ts
  • examples/CRISP/packages/crisp-contracts/deploy/syncCrispEnv.ts
  • examples/CRISP/packages/crisp-contracts/deployed_contracts.json
  • examples/CRISP/scripts/crisp_deploy.sh
  • examples/CRISP/scripts/lib/dev_config.sh
  • examples/CRISP/scripts/setup.sh
  • examples/CRISP/server/.env.example
  • examples/CRISP/test/crisp.spec.ts
  • examples/CRISP/test/wallet-setup/basic.setup.ts
  • packages/enclave-contracts/.gitignore
  • packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json
  • packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json
  • packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json
  • packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json
  • packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json
  • packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json
  • packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json
  • packages/enclave-contracts/contracts/interfaces/ICiphernodeRegistry.sol
  • packages/enclave-contracts/contracts/interfaces/ISlashingManager.sol
  • packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol
  • packages/enclave-contracts/contracts/slashing/SlashingManager.sol
  • packages/enclave-contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol
  • packages/enclave-contracts/deployed_contracts.json
  • packages/enclave-contracts/hardhat.config.ts
  • packages/enclave-contracts/package.json
  • packages/enclave-contracts/scripts/benchmarkGasFromRaw.ts
  • packages/enclave-contracts/scripts/deployAndSave/enclaveToken.ts
  • packages/enclave-contracts/scripts/deployAndSave/slashingManager.ts
  • packages/enclave-contracts/scripts/deployEnclave.ts
  • packages/enclave-contracts/tasks/ciphernode.ts
  • packages/enclave-contracts/test/E3Lifecycle/E3Integration.spec.ts
  • packages/enclave-contracts/test/Enclave.spec.ts
  • packages/enclave-contracts/test/Pricing/DustRotation.spec.ts
  • packages/enclave-contracts/test/Pricing/PullPaymentsAndAllowlist.spec.ts
  • packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts
  • packages/enclave-contracts/test/Slashing/CommitteeExpulsion.spec.ts
  • packages/enclave-contracts/test/Slashing/SlashingManager.spec.ts
  • packages/enclave-contracts/test/fixtures/bfv_vk_binding/folded_artifacts.json
  • packages/enclave-contracts/test/fixtures/helpers.ts
  • scripts/build-circuits.ts
  • scripts/generate-verifiers.ts
  • templates/default/deployed_contracts.json
  • templates/default/enclave.config.yaml
  • templates/default/hardhat.config.ts
  • tests/integration/enclave.config.yaml
💤 Files with no reviewable changes (5)
  • packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json
  • circuits/benchmarks/results_secure_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_insecure_agg/integration_summary.json
  • circuits/benchmarks/results_secure_agg/integration_summary.json
  • circuits/benchmarks/results_insecure_agg/crisp_verify_gas.json
✅ Files skipped from review due to trivial changes (20)
  • packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json
  • circuits/benchmarks/results_insecure_no_agg/benchmark_run_meta.json
  • examples/CRISP/client/.npmrc
  • circuits/.gitignore
  • packages/enclave-contracts/.gitignore
  • packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json
  • examples/CRISP/packages/crisp-contracts/.env.example
  • examples/CRISP/crisp.dev.env.example
  • circuits/benchmarks/results_secure_no_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_insecure_no_agg/integration_summary.json
  • packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json
  • circuits/benchmarks/results_secure_no_agg/report.md
  • examples/CRISP/packages/crisp-contracts/README.md
  • circuits/benchmarks/results_insecure_no_agg/crisp_verify_gas.json
  • circuits/benchmarks/results_insecure_agg/report.md
  • circuits/benchmarks/results_secure_agg/report.md
  • circuits/benchmarks/results_insecure_no_agg/report.md
  • examples/CRISP/docs/PROOF_AGGREGATION_AND_ZK.md
  • circuits/benchmarks/results_secure_no_agg/integration_summary.json
  • examples/CRISP/packages/crisp-contracts/deployed_contracts.json

Comment thread crates/ciphernode-builder/src/ciphernode_builder.rs Outdated
Comment thread crates/multithread/src/multithread.rs Outdated
Comment thread docs/pages/internals/dkg.mdx Outdated
Comment thread examples/CRISP/client/src/utils/methods.ts
Comment thread examples/CRISP/packages/crisp-contracts/deploy/syncCrispEnv.ts
Comment thread examples/CRISP/scripts/lib/dev_config.sh Outdated
Comment thread examples/CRISP/scripts/setup.sh
Comment thread packages/enclave-contracts/scripts/benchmarkGasFromRaw.ts Outdated
Comment thread packages/enclave-contracts/scripts/benchmarkGasFromRaw.ts

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

utACK

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.

Set-level binding different from per-party attribution

3 participants