11---
2- title : ' DKG & Threshold Cryptography '
2+ title : ' PVDKG & ZK Proof Pipeline '
33description :
4- ' How ciphernodes collectively generate a threshold public key without any party learning the full
5- secret — BFV keypairs, Shamir sharing, TrBFV, and the DKG state machine '
4+ ' How ciphernodes collectively generate a threshold public key, prove every step with
5+ zero-knowledge circuits (C0–C7), and verify proofs across the network '
66---
77
8- # DKG & Threshold Cryptography — Technical Deep Dive
8+ # PVDKG & ZK Proof Pipeline
99
1010After [ sortition] ( ./sortition ) selects a committee, the selected ciphernodes run a Publicly
1111Verifiable Distributed Key Generation (PVDKG) protocol to produce a ** threshold public key** that
12- users encrypt to. No single party ever holds the full secret key — instead, each node holds a Shamir
12+ users encrypt to. No single party ever holds the full secret key, instead, each node holds a Shamir
1313share, and any ` T+1 ` of ` N ` committee members can collaboratively decrypt (where ` T ` is the
1414threshold parameter, configurable per E3 program).
1515
16- The scheme is built on ** Threshold Ring-BFV** (TrBFV), which extends the standard BFV
17- fully-homomorphic encryption scheme to a threshold setting.
16+ Every step of the protocol is accompanied by a zero-knowledge proof. These proofs let nodes verify
17+ each other's work without trusting anyone, and allow on-chain contracts to verify correctness
18+ without seeing secret data. The pipeline uses ** Noir** circuits compiled to ** UltraHonk** proofs via
19+ [ Barretenberg] ( https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg ) (` bb ` ).
20+
21+ The scheme is built on ** threshold BFV** , which extends the standard BFV fully-homomorphic
22+ encryption scheme to a threshold setting where no single party holds the full secret key. For the
23+ full cryptographic specification, see the
24+ [ PVDKG article] ( https://blog.theinterfold.com/verifiability-in-the-coordination-trilemma/ ) .
1825
1926---
2027
2128## Terminology
2229
23- Following the conventions from the PVDKG specification:
24-
2530| Term | Definition |
2631| ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
2732| ** Individual key pair** | A BFV key pair each ciphernode holds independently, used to encrypt/decrypt shares exchanged during DKG |
@@ -36,14 +41,53 @@ Following the conventions from the PVDKG specification:
3641
3742---
3843
44+ ## Circuit Map
45+
46+ | ID | Circuit | Phase | Proves | Source → Target |
47+ | ------- | ---------------------------- | ------ | -------------------------------------------------------------------------------------------- | -------------------------- |
48+ | ** C0** | ` PkBfv ` | P1 DKG | Commits to individual BFV public key (binding for C3) | Each node → all peers |
49+ | ** C1** | ` PkGeneration ` | P1 DKG | Public key share correctly derived from secret key contribution (Schwartz-Zippel on BFV eq.) | Each node → all peers |
50+ | ** C2a** | ` SkShareComputation ` | P1 DKG | Secret key Shamir shares consistent with C1 commitment and satisfy Reed-Solomon parity | Each node → all peers |
51+ | ** C2b** | ` ESmShareComputation ` | P1 DKG | Smudging noise Shamir shares consistent with C1 commitment and satisfy Reed-Solomon parity | Each node → all peers |
52+ | ** C3a** | ` ShareEncryption ` (sk) | P1 DKG | BFV encryption of secret key share uses correct share (C2) and correct recipient pk (C0) | Per sender → per recipient |
53+ | ** C3b** | ` ShareEncryption ` (e_sm) | P1 DKG | BFV encryption of smudging noise share uses correct share (C2) and correct recipient pk (C0) | Per sender → per recipient |
54+ | ** C4a** | ` DkgShareDecryption ` (sk) | P1 DKG | Decrypted shares match C2 commitments; aggregated into local secret key share | Each node → all peers |
55+ | ** C4b** | ` DkgShareDecryption ` (e_sm) | P1 DKG | Decrypted shares match C2 commitments; aggregated into local smudging noise share | Each node → all peers |
56+ | ** C5** | ` PkAggregation ` | P2 Agg | Sum of all pk shares (verified against C1 commitments) equals published threshold public key | Aggregator → on-chain |
57+ | ** C6** | ` ThresholdShareDecryption ` | P4 Dec | Partial decryption uses correct secret key share and smudging noise share from C4 | Each node → aggregator |
58+ | ** C7** | ` DecryptedSharesAggregation ` | P4 Dec | Lagrange interpolation + CRT reconstruction + decoding is correct | Aggregator → on-chain |
59+
60+ ### Proof Count Per E3
61+
62+ For a committee of ** N** nodes with ** L** CRT moduli:
63+
64+ | Circuit | Count per node | Total across committee |
65+ | ------- | ------------------- | ---------------------- |
66+ | C0 | 1 | N |
67+ | C1 | 1 | N |
68+ | C2a | 1 | N |
69+ | C2b | 1 | N |
70+ | C3a | (N-1) × L | N × (N-1) × L |
71+ | C3b | (N-1) × L × ESI | N × (N-1) × L × ESI |
72+ | C4a | 1 | N |
73+ | C4b | 1 | N |
74+ | C5 | 1 (aggregator only) | 1 |
75+ | C6 | 1 | N |
76+ | C7 | 1 (aggregator only) | 1 |
77+
78+ C3 is by far the most proof-intensive circuit. With N=3 and L=4 (secure params), each node generates
79+ 8 C3a proofs alone. This is why proof generation runs on a multithread pool.
80+
81+ ---
82+
3983## Two Key Hierarchies
4084
4185There are ** two separate key pairs** in the protocol — a common source of confusion:
4286
43- | Key type | Scheme | Purpose | Lifetime |
44- | ------------------ | ------------ | -------------------------------------------------------------------- | ------------------ |
45- | ** Individual key** | Standard BFV | Encrypt Shamir shares in transit during DKG (like a secure channel) | One DKG session |
46- | ** Threshold key** | TrBFV | The collective key users encrypt to; used for homomorphic operations | One E3 computation |
87+ | Key type | Scheme | Purpose | Lifetime |
88+ | ------------------ | ------------- | -------------------------------------------------------------------- | ------------------ |
89+ | ** Individual key** | Standard BFV | Encrypt Shamir shares in transit during DKG (like a secure channel) | One DKG session |
90+ | ** Threshold key** | Threshold BFV | The collective key users encrypt to; used for homomorphic operations | One E3 computation |
4791
4892The individual public key is committed in ** C0** . The threshold key contributions are proved correct
4993in ** C1** . They live in different polynomial rings — the individual key uses a smaller "DKG"
@@ -88,7 +132,9 @@ authorised set **A** is updated.
88132
89133---
90134
91- ## Phase 1: Individual Key Commitment (C0)
135+ ## P1: Distributed Key Generation
136+
137+ ### Individual Key Commitment (C0)
92138
93139```
94140CiphernodeSelected event arrives at ThresholdKeyshare
@@ -113,14 +159,10 @@ moduli) and produces a cryptographic **commitment**. This commitment is consumed
113159committed in C0. C0 itself does not prove key generation correctness — its role is binding the
114160individual public key for later verification.
115161
116- ---
117-
118- ## Phase 2: Threshold Share Generation (C1, C2, C3)
162+ ### Threshold Share Generation (C1)
119163
120164Once all N individual public keys are collected, each node generates its threshold contribution.
121165
122- ### Step 1 — Generation (C1)
123-
124166Each ciphernode generates its secret key contribution, the corresponding public key share, and its
125167smudging noise contribution. ** C1** proves that the public key share was correctly derived from the
126168secret key contribution using the BFV key generation equation:
@@ -144,7 +186,7 @@ circuits:
1441862 . ` commit(e_sm_contribution) ` → consumed by ** C2b**
1451873 . ` commit(pk_share) ` → consumed by ** C5** in P2
146188
147- ### Step 2 — Share Computation (C2a / C2b)
189+ ### Share Computation (C2a / C2b)
148190
149191C2 runs separately for the secret key shares (C2a) and smudging noise shares (C2b). Each C2 circuit
150192verifies two properties:
@@ -159,7 +201,7 @@ The circuit outputs a commitment for each party's share per CRT modulus — an `
159201commitment values. These commitments are the bridge to C4: the recipient verifies the decrypted
160202shares match what was committed here.
161203
162- ### Step 3 — Share Encryption (C3a / C3b)
204+ ### Share Encryption (C3a / C3b)
163205
164206Each share must be encrypted before transmission. C3a handles secret key shares and C3b handles
165207smudging noise shares. The circuit verifies two commitment links simultaneously:
@@ -186,9 +228,7 @@ complete does the node broadcast its `ThresholdShareCreated` message containing:
186228This atomic publication prevents partial data from reaching peers. Any party whose proofs fail
187229verification is excluded from the authorised set ** A** .
188230
189- ---
190-
191- ## Phase 3: Share Decryption and Aggregation (C4)
231+ ### Share Decryption and Aggregation (C4)
192232
193233Each node decrypts the encrypted shares it received from all other authorised parties using its
194234individual BFV secret key. C4 then:
@@ -288,6 +328,101 @@ aggregator.
288328
289329---
290330
331+ ## Proof Lifecycle
332+
333+ ### Generation
334+
335+ The ` ProofRequestActor ` dispatches ZK requests to the ` ZkActor ` :
336+
337+ ```
338+ ProofRequestActor receives event (e.g. EncryptionKeyPending)
339+ │
340+ ├─ Builds witness data from event payload
341+ ├─ Dispatches ComputeRequest::zk(ZkRequest::PkBfv { ... })
342+ │
343+ ▼
344+ ZkActor (IO layer):
345+ ├─ Writes witness to temp directory
346+ ├─ Spawns: bb prove -b circuit.json -w witness.gz -o proof/
347+ └─ Returns: Proof { data, public_signals }
348+ ```
349+
350+ ### Signing
351+
352+ Every proof is ECDSA-signed by the generating node before broadcast:
353+
354+ ```
355+ digest = keccak256(abi.encode(
356+ PROOF_PAYLOAD_TYPEHASH,
357+ chainId,
358+ e3Id,
359+ proofType, // e.g. C0, C2a, C3b
360+ keccak256(proof.data),
361+ keccak256(proof.public_signals)
362+ ))
363+
364+ signature = ecSign(digest, operator_private_key) // 65-byte r||s||v
365+ ```
366+
367+ This binds the proof to a specific E3, chain, and proof type — preventing replay attacks. The
368+ signature also identifies the prover, so verifiers know which committee member produced the proof.
369+
370+ ### Broadcast
371+
372+ Signed proofs are wrapped in protocol events and broadcast via ** libp2p gossip** :
373+
374+ - ** C0** : Published as ` EncryptionKeyCreated ` — all peers receive and verify
375+ - ** C1–C3** : Bundled into ` ThresholdShareCreated ` — atomic publication of all DKG proofs
376+ - ** C6** : Published as ` DecryptionShareCreated ` — aggregator collects
377+
378+ A node does ** not** publish partial results. All proofs for a given phase complete before anything
379+ is broadcast.
380+
381+ ### Verification
382+
383+ On receiving a signed proof, the ` ProofVerificationActor ` :
384+
385+ ```
386+ ProofVerificationActor receives signed proof from gossip
387+ │
388+ ├─ Recover ECDSA signer address from signature
389+ ├─ Validate signer is a known committee member
390+ │
391+ ├─ Dispatch to ZkActor: bb verify -k vk -p proof.data
392+ │ (uses the inner circuit's verification key)
393+ │
394+ ├─ If PASS:
395+ │ ├─ Publish ProofVerificationPassed (cached by CommitmentConsistencyChecker)
396+ │ └─ Continue protocol
397+ │
398+ └─ If FAIL:
399+ └─ Publish SignedProofFailed → triggers accusation pipeline
400+ ```
401+
402+ ### Share Verification (Three-Phase Pipeline)
403+
404+ For DKG shares (C1, C2, C3 together), there's a more sophisticated pipeline in
405+ ` ShareVerificationActor ` :
406+
407+ ** Phase 1 — Lightweight ECDSA** (inline, fast):
408+
409+ - Verify signature, recover signer, check consistency (all proofs from same address)
410+ - Filter obviously invalid submissions
411+
412+ ** Phase 2 — Commitment Consistency** (dispatched to per-E3 checker):
413+
414+ - Evaluate all registered [ commitment links] ( ./commitment-consistency )
415+ - Exclude parties whose commitments are inconsistent across circuits
416+ - This catches dishonest behavior ** before** expensive ZK verification
417+
418+ ** Phase 3 — Heavy ZK Verification** (multithread):
419+
420+ - Only consistency-passing parties reach this stage
421+ - Each proof verified independently via ` bb verify `
422+ - Results aggregated into ` ShareVerificationComplete { dishonest_parties } `
423+
424+ ---
425+
291426## Commitment Chain
292427
293428Circuits are not isolated — they are linked through cryptographic commitments using
@@ -298,8 +433,24 @@ circuit, and verify that it matches. This is how, for example, C1 connects to C5
298433commitment produced by C1 without the raw polynomials ever appearing on-chain — only the compact
299434commitment is published.
300435
301- See [ Commitment Consistency] ( ./commitment-consistency ) for how these cross-circuit links are
302- enforced at runtime.
436+ The full dependency graph:
437+
438+ ```
439+ C0 ──────────────────────────────────────→ C3 (pk_commitment verified)
440+ C1 ──→ C2a (sk_commitment)
441+ C1 ──→ C2b (e_sm_commitment)
442+ C1 ──→ C5 (pk_commitment)
443+ C2a ─→ C3a (share_commitment)
444+ C2b ─→ C3b (share_commitment)
445+ C2a ─→ C4a (expected_commitments for all L moduli)
446+ C2b ─→ C4b (expected_commitments for all L moduli)
447+ C4a ─→ C6 (sk_commitment)
448+ C4b ─→ C6 (e_sm_commitment)
449+ C6 ─→ C7 (d_commitment)
450+ ```
451+
452+ These dependencies are enforced by the [ commitment consistency checker] ( ./commitment-consistency ) ,
453+ which evaluates 12 cross-circuit links to detect any party submitting inconsistent values.
303454
304455---
305456
@@ -323,7 +474,9 @@ separately for each preset, so both the prover and verifier must use matching pa
323474
324475## Related
325476
326- - [ ZK Proof Pipeline] ( ./zk-proofs ) — how each circuit is proved, verified, and broadcast
327477- [ Commitment Consistency] ( ./commitment-consistency ) — how cross-circuit commitments are enforced
328478- [ Sortition] ( ./sortition ) — how the committee is selected before DKG begins
329479- [ Cryptography] ( /cryptography ) — mathematical foundations (user-facing overview)
480+ - [ Noir Circuits] ( /noir-circuits ) — toolchain setup and circuit compilation
481+ - [ Verifiability in the Coordination Trilemma] ( https://blog.theinterfold.com/verifiability-in-the-coordination-trilemma/ )
482+ — full cryptographic specification
0 commit comments