Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d8f574b
feat(circuits): prototype chunked C2 circuits
0xjei Aug 5, 2026
7751668
feat(circuits): add chunked C2 accumulator
0xjei Aug 5, 2026
c938f62
feat(zk-prover): add C2 chunk fold orchestration
0xjei Aug 5, 2026
4c2c02e
feat(zk-prover): wire chunked C2 into DKG
0xjei Aug 6, 2026
52c78a0
fix(zk-prover): bind C2 terminal variants
0xjei Aug 6, 2026
fca1033
feat(zk-prover): configure C2 chunk size
0xjei Aug 6, 2026
56c07f6
test(zk-prover): cover chunked C2 node fold
0xjei Aug 6, 2026
0a4341d
update benches to validate full e2e run
0xjei Aug 6, 2026
a0fd137
feat(zk-prover): batch chunked C2 proofs
0xjei Aug 6, 2026
18af1f9
feat(zk-prover): anchor chunked C2 commitments
0xjei Aug 6, 2026
8f56a2d
benches
0xjei Aug 6, 2026
bf42847
format & lint
0xjei Aug 6, 2026
3f527ae
fix(zk-prover): validate C2 chunk artifact layout
0xjei Aug 6, 2026
e882335
fix: address CodeRabbit review findings
0xjei Aug 6, 2026
22f8dc0
fix: satisfy contract lint
0xjei Aug 6, 2026
b109f61
fix: validate C3 rows against threshold moduli
0xjei Aug 6, 2026
89bd33e
fix(zk-helpers): thread chunk-size through DKG circuits and enforce c…
0xjei Aug 7, 2026
f109c28
style: apply rustfmt import ordering
0xjei Aug 7, 2026
27948e1
fmt and micro benches
0xjei Aug 7, 2026
2156f2b
feat(benchmarks): surface chunked share-computation prove sub-steps
0xjei Aug 7, 2026
4240fc4
update benches and docs
0xjei Aug 7, 2026
57c593c
lint & format
0xjei Aug 7, 2026
42ebec2
perf(zk-prover): parallelize C2 chunk and batch proving; secure micro…
0xjei Aug 8, 2026
7306158
chore: raise do_send ratchet baseline 107->108 for chunked C2 sub-ste…
0xjei Aug 8, 2026
ac1d15e
fix(zk-prover): address CodeRabbit findings on length checks and E3Fa…
0xjei Aug 8, 2026
af3832f
chore: remove dead sequential C2 chunk pipeline
0xjei Aug 8, 2026
57f34d8
fix(zk-prover): bind recursive aggregation VKs
0xjei Aug 8, 2026
f8affd7
style(circuits): format recursive VK binding
0xjei Aug 8, 2026
268558b
chore(contracts): regenerate stale interface artifacts
0xjei Aug 9, 2026
2bff021
fix(zk-prover): harden C2 chunking guards and dkg_aggregator shape ch…
0xjei Aug 9, 2026
f657ffa
fix(zk-prover): bind C2/fold VK chains and pin C2 chunk layout
0xjei Aug 9, 2026
5402510
dependent smudging from committee
0xjei Aug 9, 2026
2e370db
removed unsused variables
0xjei Aug 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/releases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ jobs:
- name: Pull circuit artifacts
id: pull
run: |
SOURCE_HASH=$(pnpm tsx scripts/build-circuits.ts hash)
SOURCE_HASH=$(pnpm tsx scripts/build-circuits.ts hash --preset insecure-512 --committee all)
echo "source_hash=$SOURCE_HASH" >> $GITHUB_OUTPUT

if git fetch origin circuit-artifacts 2>/dev/null; then
Expand Down
12 changes: 6 additions & 6 deletions agent/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,14 @@ opentelemetry/tracing.

## Circuit map (IDs ↔ `CircuitName` in `crates/events`)

- **DKG** (`circuits/bin/dkg/`): C0 `pk` (PkBfv) · C2a `sk_share_computation` · C2b
`e_sm_share_computation` · C3 `share_encryption` · C4 `share_decryption`
- **DKG** (`circuits/bin/dkg/`): C0 `pk` (PkBfv) · C2a `sk_share_computation_chunk` · C2b
`esm_share_computation_chunk` · C3 `share_encryption` · C4 `share_decryption`
- **Threshold** (`circuits/bin/threshold/`): C1 `pk_generation` · C5 `pk_aggregation` · P3
`user_data_encryption_ct0/ct1` (+ wrapper) · C6 `share_decryption` · C7
`decrypted_shares_aggregation`
- **Recursive aggregation** (`circuits/bin/recursive_aggregation/`): fold kernels (`c2ab_fold`,
`c3_fold`, `c6_fold`, `node_fold`, `nodes_fold`, …) and the top-level `dkg_aggregator` /
`decryption_aggregator`, which produce the on-chain Honk verifiers (committed only for
`(insecure-512, minimum)`).
- **Recursive aggregation** (`circuits/bin/recursive_aggregation/`): fold kernels
(`c2ab_chunk_fold`, `c3_fold`, `c6_fold`, `node_fold`, `nodes_fold`, …) and the top-level
`dkg_aggregator` / `decryption_aggregator`, which produce the on-chain Honk verifiers (committed
only for `(insecure-512, minimum)`).
- `config` circuit validates preset constants (CRT moduli, bounds, parity matrices). Parity matrices
are generated by the Rust `generate_parity_matrices` binary — never hand-edit.
18 changes: 16 additions & 2 deletions agent/INVARIANTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ skip-proof feature containment (`pnpm check:invariants`, baselines in
public-input layout and must be redeployed on committee change.
- Parity matrices (`parity_{insecure,secure}.nr`) are derived artifacts regenerated from preset
`QIS` + committee `(N, T)`; hand-edits are caught by regenerate-and-diff.
- Smudging constants (`smudging.nr`) are derived artifacts regenerated from the BFV preset and
committee size. C1 and C2b use the same generated bit width.

### Noir / Barretenberg compatibility

Expand All @@ -183,8 +185,10 @@ skip-proof feature containment (`pnpm check:invariants`, baselines in
### DKG / threshold structure

- SK splits into N shares; any **M+1** reconstruct/decrypt. — `flow-trace/04`
- `party_id` derives from the finalized committee normalized by ascending address; 1-indexed,
strictly increasing. Active aggregator = lowest non-expelled `party_id`. — `ARCHITECTURE.md`;
- DKG runtime and NodeFold `party_id` derives from the finalized committee normalized by ascending
address; it is zero-indexed and strictly increasing. Active aggregator = lowest non-expelled
`party_id`. Decryption-aggregator Shamir coordinates are a separate 1-indexed circuit format and
translate to zero-indexed registry slots at the wrapper boundary. — `ARCHITECTURE.md`;
`flow-trace/04`
- DKG aggregation receives **exactly H** canonical honest NodeFold proofs (unique in-range party
IDs) and **exactly N** ordered committee addresses; every preset has `H < N` — never assert
Expand Down Expand Up @@ -222,6 +226,16 @@ skip-proof feature containment (`pnpm check:invariants`, baselines in
- **No proof-disabled bypass (C-02):** both final verifier calls are mandatory in production;
`skip_proof_aggregation` works only under the `test-only-skip-proof-aggregation` Cargo feature;
production verifiers reject placeholder C5/C7 proofs. — INDEX concern #32
- **Complete DKG recursive VK binding:** the DKG proof carries the canonical `NodeFold` VK hash and
a recursive VK manifest. The manifest binds the C0/C1, C2 chunk/batch/finalizer/C2AB, C3
leaf/fold/kernel/C3AB, C4 leaf/C4AB, and NodesFold kernel VK hashes. NodesFold and C3Fold bind
both the current accumulator VK and each prior accumulator's expected kernel or fold VK hash, and
every fold step asserts the accumulator's child-VK hash equals the inner proof's VK hash (IF-005);
a substituted inner VK fails witness generation. C2 terminal proofs
(`SkC2ChunkFinalize`/`ESmC2ChunkFinalize`) are bound to the deployment-time chunk-leaf and
`C2ChunkBatch` VK anchors before generic verification (IF-006). `BfvPkVerifier` checks these
values against deployment-time anchors before it calls the Honk verifier. — `dkg_aggregator`,
`BfvPkVerifier`
- Circuit soundness fixes to preserve: `ModU64::div_mod` verifies
`result*divisor == dividend (mod modulus)` (IF-001); C7 compares **every** decoded coefficient,
including zeros, to the claimed message (IF-002).
Expand Down
2 changes: 2 additions & 0 deletions agent/flow-trace/00_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ _Found during source-code cross-referencing of these trace documents._
| **IF-002 — conditional C7 decoding equality** | Fixed | C7 compares every decoded coefficient with the claimed message, including zero coefficients; a focused regression test rejects a nonzero decoded value claimed as zero. |
| **IF-003 — decryption proof phase and party binding** | Fixed | `decryption_aggregator` requires 1-indexed, strictly increasing party IDs, while `BfvDecryptionVerifier` checks the surfaced SK/ESM commitments against the E3's registry-backed DKG anchors. |
| **IF-004 — ciphertext commitment binding** | Fixed | The off-chain SAFE commitment is stored at ciphertext publication, propagated as a final decryption-proof public input, and compared on-chain without attempting BFV decoding or Poseidon2 execution in Solidity. |
| **IF-005 — fold-step child VK binding** | Fixed | Every `c3_fold`/`nodes_fold` step asserts the accumulator's child-VK hash equals the verifying key hash of the inner proof folded that step. A substituted (even canonical) inner VK now fails witness generation instead of passing silently. Regression-covered by `fold_vk_binding_adversarial_tests`. |
| **IF-006 — C2 terminal VK binding at ingress** | Fixed | Before generic (bb) verification, each `SkC2ChunkFinalize`/`ESmC2ChunkFinalize` proof is checked against the deployment-time anchors: the child field equals the canonical chunk-leaf VK hash for that proof type, the final field equals the canonical `C2ChunkBatch` VK hash, the public-signal length matches the frozen committee and preset, and every field is canonical. A mismatch marks the signed proof invalid. Covered by `validate_c2_terminal_proof`. |

### Protocol Design Concerns

Expand Down
86 changes: 69 additions & 17 deletions agent/flow-trace/04_DKG_AND_COMPUTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,11 @@ ProofRequestActor receives ThresholdSharePending
│ ├─ C2a: ComputeRequest::zk(ZkRequest::ShareComputation { kind: SK })
│ ├─ C2b: ComputeRequest::zk(ZkRequest::ShareComputation { kind: ESM })
│ ├─ C3a[i]: ComputeRequest::zk(ZkRequest::ShareEncryption { recipient, row })
│ │ → One per recipient party × modulus row
│ │ → One per recipient party × threshold-secret modulus row; the paired DKG parameters
│ │ define the ciphertext CRT limbs
│ └─ C3b[i]: ComputeRequest::zk(ZkRequest::ShareEncryption { esi_idx, recipient, row })
│ → One per ESI × recipient party × modulus row
│ → One per ESI × recipient party × threshold-secret modulus row; the paired DKG
│ parameters define the ciphertext CRT limbs
├─ 3. ZkActor generates proofs via bb binary (in parallel via multithread):
│ → Each proof takes 1-10 seconds depending on circuit complexity
Expand Down Expand Up @@ -305,16 +307,63 @@ ProofRequestActor receives ThresholdSharePending
→ Ensures no incomplete data is gossiped
```

**C2 proofs:** For each C2a/C2b request, the prover builds a **recursive** proof for
`sk_share_computation` / `e_sm_share_computation`. That `Proof` is what `PendingThresholdProofs`
stores and what gets ECDSA-signed for gossip (`ProofType::C2aSkShareComputation` /
`C2bESmShareComputation`). The old generic `recursive_aggregation/wrapper/*` circuits and two-proof
`recursive_aggregation/fold` were removed; aggregation is done by ad-hoc Noir bins under
`circuits/bin/recursive_aggregation/` (e.g. `c2ab_fold`, `c3ab_fold`, `c6_fold`, `node_fold`,
`nodes_fold`, `dkg_aggregator`, `decryption_aggregator` — `nodes_fold` chains `H` `node_fold` proofs
for `dkg_aggregator`; `decryption_aggregator` folds C6 via non-ZK `c6_fold` then checks C7 with ZK).
The per-circuit `wrapper/` Noir step was removed; aggregator response structs no longer carry a
`wrapped_proof` field — the inner recursive proof itself is what flows between stages.
**C2 proofs:** For each C2a/C2b request, the prover builds a **recursive** proof per coefficient
chunk via `sk_share_computation_chunk` / `esm_share_computation_chunk`. The chunk proofs are batched
(`C2ChunkBatch`) and verified in the type-bound terminal `SkC2ChunkFinalize` / `ESmC2ChunkFinalize`.
That terminal `Proof` is what `PendingThresholdProofs` stores and what gets ECDSA-signed for gossip
(`ProofType::C2aSkShareComputation` / `C2bESmShareComputation`). The old generic
`recursive_aggregation/wrapper/*` circuits and two-proof `recursive_aggregation/fold` were removed;
aggregation is done by ad-hoc Noir bins under `circuits/bin/recursive_aggregation/` (e.g.
`c2ab_chunk_fold`, `c3ab_fold`, `c6_fold`, `node_fold`, `nodes_fold`, `dkg_aggregator`,
`decryption_aggregator` — `nodes_fold` chains `H` `node_fold` proofs for `dkg_aggregator`;
`decryption_aggregator` folds C6 via non-ZK `c6_fold` then checks C7 with ZK). The per-circuit
`wrapper/` Noir step was removed; aggregator response structs no longer carry a `wrapped_proof`
field — the inner recursive proof itself is what flows between stages.

Every `c3_fold`/`nodes_fold` step asserts the accumulator's child-VK hash (`acc_public_inputs[0]`)
equals the verifying key hash of the inner proof folded that step (IF-005), so a substituted inner
VK fails witness generation. Fold accumulators start from the canonical leaf/kernel proof, whose VK
hash is committed by that circuit's public inputs.

The chunked C2 path keeps the same signed proof multiplicity. For each C2a/C2b request, Rust
generates one type-bound recursive proof per chunk. The chunk size is pinned to 512 coefficients;
the `zk_cli --chunk-size` option rejects any other value. The production multithread path uses the
compiled default chunk size. Rust groups the chunk proofs into fixed recursive batches and verifies
all batches in a type-bound terminal circuit. The terminal circuits reconstruct a root commitment
for the secret and for each recipient share. The signed response contains only the type-bound
terminal `SkC2ChunkFinalize` or `ESmC2ChunkFinalize` proof. `C2ChunkBatch` binds the ordered chunk
indices and chunk commitments.

The chunk size is one value across the DKG pipeline: it threads from the sample into the C2 (share
computation), C3 (share encryption), and C4 (share decryption) witness computation and into the
generated `configs.nr` values (`SHARE_COMPUTATION_CHUNK_SIZE` / `SHARE_COMPUTATION_N_CHUNKS`), so
the witness always matches the circuit parameters the artifacts were generated against. The
generated `configs.nr` `N` and `L` values come from the same parameter object that drives the
witness computation. The compiled circuits and committed `configs.nr` defaults use chunk size 512;
`--chunk-size` accepts only 512, so generated artifacts always match the compiled C2/C3/C4 circuits.
The chunk layout (chunk/batch counts) is derived from a single `C2ChunkLayout` in `e3-zk-prover`,
never from independent runtime constants.

C1 and C2b import their smudging-noise bit width from the active committee's generated
`smudging.nr`. C1 also imports the matching bound. The build uses the same Rust calculation as
witness generation, so a committee change cannot leave either circuit on another committee's range.

Before the generic recursive (bb) verification of a received C2 proof, the node validates the
`SkC2ChunkFinalize`/`ESmC2ChunkFinalize` public signals against the deployment-time anchors
(IF-006): the child field must equal the canonical chunk-leaf VK hash for that proof type, the final
field must equal the canonical `C2ChunkBatch` VK hash, the signal length must match the frozen
committee and preset, and every field must be canonical. A mismatch marks the signed proof invalid.

C1, normal C2, C3, C4 per-share checks, and `NodeFold` now use the same root commitment scheme. C3
fold steps bind each inner proof's recipient and modulus indices to its accumulator slot, including
the first genesis step. C3Fold and NodesFold also bind the current accumulator VK and the prior
step's expected kernel or fold VK hash. C4 binds every decrypted row to the recipient party's
zero-based C2 commitment domain. C4 aggregate commitments remain on the legacy aggregate scheme at
the C4-to-C6 boundary until that boundary is migrated. The terminal C2 proofs surface the canonical
SK/ESM chunk VK hashes through C2AB, NodeFold, and DkgAggregator. Each recursive fold also
propagates a VK manifest for the child proofs that it verifies. The final DKG proof carries the
`NodeFold` VK hash and this complete manifest; `BfvPkVerifier` compares them with its
deployment-time anchors before it accepts the final proof.

**Ciphernode / aggregator integration:** `ZkRequest::FoldProofs` was removed. The multithread actor
implements `ZkRequest::NodeDkgFold` (full per-node pipeline to a `NodeFold` proof),
Expand Down Expand Up @@ -816,17 +865,20 @@ Compute provider runs computation on encrypted data:
│ │ → Can only publish once │
│ │ 5. require(activeCount >= threshold[0]) │
│ │ → The request-time committee is still viable │
│ │ 6. Save output hash and SAFE commitment
│ │ Set stage and decryption deadline
│ │ → A later revert restores all prior state
│ │ 6. Require no ciphertext publication is in progress
│ │ Set the per-E3 publication guard
│ │ → Reentrant publication attempts revert │
│ │ 7. schemeVerifier.verify(...) │
│ │ → Checks the protocol fields in the compute receipt│
│ │ → Must return true │
│ │ 8. e3Program.verify(...) │
│ │ → Checks the application fields in the same receipt│
│ │ → Must return true │
│ │ 9. Emit CiphertextOutputPublished(...) │
│ │ 10. Emit E3StageChanged(CiphertextReady) │
│ │ 9. Save output hash and SAFE commitment │
│ │ Set stage and decryption deadline │
│ │ Clear the publication guard │
│ │ 10. Emit CiphertextOutputPublished(...) │
│ │ 11. Emit E3StageChanged(CiphertextReady) │
│ │ } │
│ └─────────────────────────────────────────────────────────┘
```
Expand Down
2 changes: 1 addition & 1 deletion agent/flow-trace/05_FAILURE_REFUND_SLASHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ REQUESTER claims:
├─ Transfer requesterAmount in the per-E3 fee token
└─ Emit RefundClaimed(e3Id, requester, amount)

HONEST NODE'S BOND OWNER claims:
FROZEN REWARD RECIPIENT claims an honest-node reward:
E3RefundManager.claimHonestNodeReward(e3Id, operator)
├─ require(distribution calculated)
Expand Down
Loading
Loading