Skip to content

Commit f17b0b6

Browse files
committed
chore: pr comments
1 parent 5c008c5 commit f17b0b6

6 files changed

Lines changed: 196 additions & 246 deletions

File tree

docs/pages/cryptography.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,11 @@ sequenceDiagram
157157

158158
## Publicly verifiable threshold BFV (PV-TBFV)
159159

160-
**PV-TBFV** means **publicly verifiable threshold Ring-BFV**: every sensitive step you would worry
161-
about in a centralised system—generating key material, moving it under encryption, aggregating
162-
public keys, taking user ciphertexts, emitting decryption shares—is backed by a statement that
163-
verifiers can check **without** seeing the private witnesses. The DKG slice of that story is what we
164-
call **PVDKG**; in the repository it lands in **C0** through **C4** (with
160+
**PV-TBFV** means **publicly verifiable threshold BFV**: every sensitive step you would worry about
161+
in a centralised system—generating key material, moving it under encryption, aggregating public
162+
keys, taking user ciphertexts, emitting decryption shares—is backed by a statement that verifiers
163+
can check **without** seeing the private witnesses. The DKG slice of that story is what we call
164+
**PVDKG**; in the repository it lands in **C0** through **C4** (with
165165
[**C1**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_generation)
166166
living under
167167
[`circuits/bin/threshold`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold)

docs/pages/internals/_meta.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
"title": "Sortition"
44
},
55
"dkg": {
6-
"title": "DKG & Threshold Cryptography"
7-
},
8-
"zk-proofs": {
9-
"title": "ZK Proof Pipeline"
6+
"title": "PVDKG & ZK Proofs"
107
},
118
"commitment-consistency": {
129
"title": "Commitment Consistency"

docs/pages/internals/commitment-consistency.mdx

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
title: 'Commitment Consistency'
33
description:
44
'How the Interfold protocol detects dishonest ciphernodes by cross-checking commitment values
5-
across ZK circuit proofs — link types, scoping rules, and the C2→C4 check'
5+
across ZK circuit proofs.'
66
---
77

8-
# Commitment Consistency — Technical Deep Dive
8+
# Commitment Consistency
99

10-
The [ZK proof pipeline](./zk-proofs) ensures each individual circuit execution is correct. But a
11-
dishonest node could still cheat by using **different inputs** across circuits — for example,
12-
committing to one set of shares in C2 but using different shares in C4. Each proof would verify in
13-
isolation, but the overall protocol output would be wrong.
10+
The [ZK proof pipeline](./dkg) ensures each individual circuit execution is correct. But a dishonest
11+
node could still cheat by using **different inputs** across circuits — for example, committing to
12+
one set of shares in C2 but using different shares in C4. Each proof would verify in isolation, but
13+
the overall protocol output would be wrong.
1414

1515
The **commitment consistency checker** closes this gap by cross-referencing public signals across
1616
circuit proofs. If a commitment value produced by one circuit doesn't match the expected value in
@@ -28,7 +28,7 @@ The checker is a per-E3 actor (`CommitmentConsistencyChecker`) that:
2828

2929
It operates in two modes:
3030

31-
### Pre-ZK Gating (fast path)
31+
**Pre-ZK Gating (fast path)**
3232

3333
Before expensive ZK verification, the `ShareVerificationActor` sends all received proofs to the
3434
checker in a batch via `CommitmentConsistencyCheckRequested`. The checker evaluates all links and
@@ -37,7 +37,7 @@ returns a set of `inconsistent_parties` that are **excluded from ZK verification
3737
This is a significant optimization — a dishonest party caught by commitment checks never wastes
3838
resources on ZK verification.
3939

40-
### Post-ZK Cross-Check (defense in depth)
40+
**Post-ZK Cross-Check (defense in depth)**
4141

4242
After ZK verification passes, the checker receives `ProofVerificationPassed` events and re-evaluates
4343
links. This catches cases where a proof from one phase arrives after the initial batch, or where a
@@ -147,21 +147,6 @@ Given C2 sender `X` (party_id) and C4 recipient `R` (party_id):
147147

148148
If any modulus commitment differs, sender X is accused.
149149

150-
### The L Parameter
151-
152-
The block size `L` (number of CRT moduli) varies by BFV preset:
153-
154-
| Preset | L |
155-
| ------------------------ | --- |
156-
| `INSECURE_THRESHOLD_512` | 2 |
157-
| `SECURE_THRESHOLD_8192` | 4 |
158-
159-
The `L` used in the check **must match the actual preset** for the E3 — otherwise every index
160-
calculation is wrong and the check produces false positives. This is why the checker reads `L` from
161-
`E3Meta.params_preset` at E3 startup rather than using a hardcoded default.
162-
163-
---
164-
165150
## Deduplication
166151

167152
The same proof can arrive via two paths:
@@ -204,6 +189,6 @@ response, preventing them from consuming ZK verification resources.
204189

205190
## Related
206191

207-
- [ZK Proof Pipeline](./zk-proofs) — the circuits whose commitments are being cross-checked
192+
- [ZK Proof Pipeline](./dkg) — the circuits whose commitments are being cross-checked
208193
- [DKG & Threshold Cryptography](./dkg) — the protocol that generates the proofs
209194
- [Sortition](./sortition) — committee selection before any of this begins

docs/pages/internals/dkg.mdx

Lines changed: 180 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
---
2-
title: 'DKG & Threshold Cryptography'
2+
title: 'PVDKG & ZK Proof Pipeline'
33
description:
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

1010
After [sortition](./sortition) selects a committee, the selected ciphernodes run a Publicly
1111
Verifiable 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
1313
share, and any `T+1` of `N` committee members can collaboratively decrypt (where `T` is the
1414
threshold 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

4185
There 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

4892
The individual public key is committed in **C0**. The threshold key contributions are proved correct
4993
in **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
```
94140
CiphernodeSelected event arrives at ThresholdKeyshare
@@ -113,14 +159,10 @@ moduli) and produces a cryptographic **commitment**. This commitment is consumed
113159
committed in C0. C0 itself does not prove key generation correctness — its role is binding the
114160
individual public key for later verification.
115161

116-
---
117-
118-
## Phase 2: Threshold Share Generation (C1, C2, C3)
162+
### Threshold Share Generation (C1)
119163

120164
Once all N individual public keys are collected, each node generates its threshold contribution.
121165

122-
### Step 1 — Generation (C1)
123-
124166
Each ciphernode generates its secret key contribution, the corresponding public key share, and its
125167
smudging noise contribution. **C1** proves that the public key share was correctly derived from the
126168
secret key contribution using the BFV key generation equation:
@@ -144,7 +186,7 @@ circuits:
144186
2. `commit(e_sm_contribution)` → consumed by **C2b**
145187
3. `commit(pk_share)` → consumed by **C5** in P2
146188

147-
### Step 2 — Share Computation (C2a / C2b)
189+
### Share Computation (C2a / C2b)
148190

149191
C2 runs separately for the secret key shares (C2a) and smudging noise shares (C2b). Each C2 circuit
150192
verifies two properties:
@@ -159,7 +201,7 @@ The circuit outputs a commitment for each party's share per CRT modulus — an `
159201
commitment values. These commitments are the bridge to C4: the recipient verifies the decrypted
160202
shares match what was committed here.
161203

162-
### Step 3 — Share Encryption (C3a / C3b)
204+
### Share Encryption (C3a / C3b)
163205

164206
Each share must be encrypted before transmission. C3a handles secret key shares and C3b handles
165207
smudging noise shares. The circuit verifies two commitment links simultaneously:
@@ -186,9 +228,7 @@ complete does the node broadcast its `ThresholdShareCreated` message containing:
186228
This atomic publication prevents partial data from reaching peers. Any party whose proofs fail
187229
verification 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

193233
Each node decrypts the encrypted shares it received from all other authorised parties using its
194234
individual 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

293428
Circuits 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
298433
commitment produced by C1 without the raw polynomials ever appearing on-chain — only the compact
299434
commitment 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

Comments
 (0)