From 1931bb7a4244859528374a88b61af3f9cf89b232 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Mon, 23 Feb 2026 18:45:22 +0100 Subject: [PATCH 01/31] add docs for C0 --- circuits/bin/dkg/pk/README.md | 45 ++++++++++++++++++++++++++++++++- circuits/lib/src/core/dkg/pk.nr | 22 +++++++++++----- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/circuits/bin/dkg/pk/README.md b/circuits/bin/dkg/pk/README.md index 3edb840650..5aa56fa350 100644 --- a/circuits/bin/dkg/pk/README.md +++ b/circuits/bin/dkg/pk/README.md @@ -1 +1,44 @@ -instantiation of correct DKG Public Key circuit (PVSS #0) +# [C0] BFV Public Key Commitment (`pk`) + +The BFV Public Key Commitment circuit (C0) is the first circuit executed in Phase 1 (Distributed Key +Generation). Each ciphernode creates a cryptographic commitment to their BFV public key, which will +be used exclusively for encrypting secret shares during DKG. + +Rather than verifying the key generation process, this circuit establishes a _binding commitment_ +that prevents key substitution attacks. The commitment acts as an immutable reference—any attempt to +use a different key in later encryption or decryption steps will be cryptographically detected. + +```mermaid +flowchart TD + %% Input from Phase 0 + Input0["P0
Configs Verification"] -.->|"verified configs"| C0 + + subgraph Focus["Circuit C0"] + C0["C0: pk
Commit to BFV public key"] + end + + %% Output to C3a and C3b + C0 -->|"commit(pk_bfv)"| Output1["→ C3a
share-encryption-sk"] + C0 -->|"commit(pk_bfv)"| Output2["→ C3b
share-encryption-e-sm"] + + style Focus fill:#E8A87C,stroke:#C97A4A,stroke-width:3px + style Input0 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Output2 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + + linkStyle 0 stroke:#808080,stroke-width:2px + linkStyle 1 stroke:#808080,stroke-width:2px + linkStyle 2 stroke:#808080,stroke-width:2px +``` + +- **Phase**: P1 (DKG). +- **Runs**: 1 x Ciphernode (at the start of key generation). +- **Requires**: [`config`](../../config) circuit from P0 (Configs Verification). +- **Output(s)**: `commit(pk_bfv)` consumed by C3a / C3b + ([`dkg/share_encryption`](../share_encryption)) +- **Data Flow**: `P0 (configs) → C0 → commit(pk_bfv) → C3a, C3b` +- **Commitment Function**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - + `compute_dkg_pk_commitment()` +- **Related Circuits**: + - C3a, C3b [`dkg/share_encryption`](../share_encryption), + - C4a, C4b [`dkg/share_decryption`](../share_decryption) diff --git a/circuits/lib/src/core/dkg/pk.nr b/circuits/lib/src/core/dkg/pk.nr index 2745b29efd..6a51ed1947 100644 --- a/circuits/lib/src/core/dkg/pk.nr +++ b/circuits/lib/src/core/dkg/pk.nr @@ -7,22 +7,32 @@ use crate::math::commitments::compute_dkg_pk_commitment; use crate::math::polynomial::Polynomial; -/// Correct DKG Public Key Circuit (Circuit 0). +/// BFV Public Key Commitment Circuit (Circuit C0). +/// +/// This circuit establishes a binding cryptographic commitment to a ciphernode's +/// BFV public key, which is used exclusively for encrypting secret shares during +/// the Distributed Key Generation (DKG) phase. pub struct Pk { - /// Correct DKG public key components - /// pk0[i] is the first component for modulus i + /// BFV public key first component for each CRT modulus. + /// pk0[i] is a degree N-1 polynomial for modulus q_i. pk0: [Polynomial; L], - /// pk1[i] is the second component for modulus i + /// BFV public key second component for each CRT modulus. + /// pk1[i] is a degree N-1 polynomial for modulus q_i. pk1: [Polynomial; L], } impl Pk { + /// Creates a new BFV public key commitment circuit instance. + /// + /// # Arguments + /// + /// * `pk0` - First component of BFV public key (one polynomial per CRT modulus) + /// * `pk1` - Second component of BFV public key (one polynomial per CRT modulus) pub fn new(pk0: [Polynomial; L], pk1: [Polynomial; L]) -> Self { Pk { pk0, pk1 } } - /// Main verification function - /// Returns commitment to correct DKG public key + /// Executes the commitment circuit and returns the binding commitment. pub fn execute(self) -> Field { compute_dkg_pk_commitment::(self.pk0, self.pk1) } From c4c301b40cb0380439432e979a63e9afbd698436 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 24 Feb 2026 11:24:15 +0100 Subject: [PATCH 02/31] add docs and readme for configs verification circuit --- circuits/bin/config/README.md | 38 +++++++++ circuits/bin/config/src/main.nr | 136 +++++++++++++++++++++++++------- 2 files changed, 147 insertions(+), 27 deletions(-) create mode 100644 circuits/bin/config/README.md diff --git a/circuits/bin/config/README.md b/circuits/bin/config/README.md new file mode 100644 index 0000000000..fefae2fcad --- /dev/null +++ b/circuits/bin/config/README.md @@ -0,0 +1,38 @@ +# Configuration Verification Circuit (Phase 0) + +The Configuration Verification circuit runs once per deployment to verify all cryptographic +parameters used across both BFV (for DKG share encryption) and Threshold BFV (for user data +encryption) schemes. It provides public proof that the mathematical foundation is correct before any +key generation or encryption occurs. + +Rather than trusting parameter configuration, this circuit verifies dozens of mathematical +relationships: CRT moduli products, error bounds, scaling factors, Reed-Solomon parity matrices, and +cross-scheme consistency. + +```mermaid +flowchart TD + subgraph P0["P0
Configuration Verification"] + ConfigCircuit["Configuration Circuit
Verify crypto configs"] + end + + %% External connections + ConfigCircuit -.->|"verified parameters"| NextPhase["→ P1-P4
(trusted configs for all circuits)"] + + style P0 fill:#2B3BD5,stroke:#2E75B6,stroke-width:3px + style NextPhase fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + linkStyle 0 stroke:#808080,stroke-width:2px +``` + +### Metadata + +- **Phase**: P0 (Configuration Verification). +- **Runs**: 1 x Ciphernode (one-time program verification). +- **Requires**: Configured parameter sets from [`configs/secure`](../../../lib/src/configs/secure). +- **Output(s)**: Single proof that all parameters are valid, consumed by all P1-P4 circuits. +- **Data Flow**: `Parameter Sets → P0 → Verified Configs → All Circuits (P1-P4)` +- **Verification Categories**: + - DKG (BFV) Parameters: [`configs/secure/dkg.nr`](../../../lib/src/configs/secure/dkg.nr) + - Threshold BFV Parameters: + [`configs/secure/threshold.nr`](../../../lib/src/configs/secure/threshold.nr) + - Cross-Configuration Consistency +- **Related Circuits**: All circuits in P1-P4 assume these parameters are correct. diff --git a/circuits/bin/config/src/main.nr b/circuits/bin/config/src/main.nr index ea55977aa9..aa0b586215 100644 --- a/circuits/bin/config/src/main.nr +++ b/circuits/bin/config/src/main.nr @@ -4,8 +4,19 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -// This circuit verifies all the configuration parameters used in the secure configs -// Only re-run if parameters change (new deployment) +/// Configuration Verification Circuit (Phase 0). +/// +/// This circuit verifies all cryptographic parameters used across both BFV (for DKG share encryption) +/// and Threshold BFV (for user data encryption) schemes. It runs once per deployment to establish +/// the mathematical foundation that all subsequent circuits depend on. +/// +/// Rather than trusting parameter configuration, this circuit provides public proof that: +/// +/// - CRT moduli products are correctly computed +/// - Error bounds satisfy security requirements +/// - Scaling factors have correct multiplicative inverses +/// - Reed-Solomon parity matrices for Shamir sharing are properly constructed +/// - BFV and Threshold BFV parameter sets are mutually consistent use lib::configs::default::{N_PARTIES, T}; use lib::configs::secure::dkg::{ @@ -30,7 +41,7 @@ use lib::configs::secure::threshold::{ PK_GENERATION_R1_BOUNDS, PK_GENERATION_R2_BOUNDS, PK_GENERATION_B_ENC, // share_decryption bounds THRESHOLD_SHARE_DECRYPTION_R1_BOUNDS, THRESHOLD_SHARE_DECRYPTION_R2_BOUNDS, - // user_data_encryption (Greco) bounds + // user_data_encryption bounds USER_DATA_ENCRYPTION_K0IS, USER_DATA_ENCRYPTION_PK_BOUNDS, USER_DATA_ENCRYPTION_E0_BOUND, USER_DATA_ENCRYPTION_E1_BOUND, USER_DATA_ENCRYPTION_U_BOUND, USER_DATA_ENCRYPTION_K1_LOW_BOUND, USER_DATA_ENCRYPTION_K1_UP_BOUND, USER_DATA_ENCRYPTION_R1_LOW_BOUNDS, @@ -40,10 +51,14 @@ use lib::configs::secure::threshold::{ use lib::math::modulo::U128::ModU128; -/// Center a value from [0, modulus) to [-modulus/2, modulus/2). -/// For odd modulus use (raw > t/2). -/// For even use (raw >= t/2). -/// Precondition: raw is in [0, modulus). +/// Centers a value from [0, modulus) to [-modulus/2, modulus/2). +/// +/// This function converts values from standard representation [0, modulus) to +/// centered representation [-modulus/2, modulus/2), which is used in BFV for +/// efficient arithmetic and correct decoding. +/// +/// - For odd modulus: center if raw > modulus/2 +/// - For even modulus: center if raw >= modulus/2 pub fn center(raw: Field, modulus: Field) -> Field { let needs_centering = if (modulus as u128 % 2) == 1 { raw as u128 > modulus as u128 / 2 @@ -58,6 +73,16 @@ pub fn center(raw: Field, modulus: Field) -> Field { } } +/// Main entry point for configuration verification circuit. +/// +/// Verifies all cryptographic parameters across four categories: +/// 1. DKG (BFV) derived values and bounds +/// 2. Threshold BFV derived values and bounds +/// 3. User data encryption bounds +/// 4. Cross-configuration consistency +/// +/// This function will panic if any verification fails, preventing deployment +/// with incorrect parameters. fn main() { // DKG (BFV) Verification verify_dkg_derived_values(); @@ -68,21 +93,27 @@ fn main() { verify_threshold_bounds(); // user_data_encryption Verification - verify_greco_bounds(); + verify_user_data_encryption_bounds(); // Cross-Config Consistency verify_cross_config_consistency(); } -//DKG Derived Values +// === DKG Derived Values === +/// Verifies DKG-specific derived values. +/// +/// Checks: +/// - Q_MOD_T computation (product of CRT moduli mod t) +/// - Q_MOD_T_CENTERED (centered representation) +/// - Reed-Solomon parity matrices for Shamir sharing fn verify_dkg_derived_values() { verify_dkg_q_mod_t(); verify_dkg_q_mod_t_centered(); verify_dkg_parity_matrix(); } -// Verifies DKG: Q_MOD_T = (product of QIS) mod t +/// Verifies DKG: Q_MOD_T = (product of QIS) mod t. fn verify_dkg_q_mod_t() { let t = DKG_PLAINTEXT_MODULUS; let m = ModU128::new(t); @@ -95,14 +126,23 @@ fn verify_dkg_q_mod_t() { assert(product == DKG_Q_MOD_T, "DKG Q_MOD_T verification failed"); } -// Verifies DKG: Q_MOD_T_CENTERED = center(Q_MOD_T) +/// Verifies DKG: Q_MOD_T_CENTERED = center(Q_MOD_T). fn verify_dkg_q_mod_t_centered() { assert( center(DKG_Q_MOD_T, DKG_PLAINTEXT_MODULUS) == DKG_Q_MOD_T_CENTERED, "DKG Q_MOD_T_CENTERED verification failed", ); } -// Verify parity matrix : H * G^T = 0 (mod q_l) for each modulus + +/// Verifies Reed-Solomon parity matrix: H * G^T = 0 (mod q_l) for each modulus. +/// +/// For Shamir Secret Sharing with threshold T, shares must satisfy the parity check +/// equation H * shares^T = 0, where H is the parity check matrix. This function +/// verifies that the precomputed PARITY_MATRIX is correct by checking it against +/// the Vandermonde generator matrix G. +/// +/// The generator matrix G is constructed as G[i][j] = j^i mod q_l (Vandermonde structure). +/// For valid Shamir shares, H * G^T must equal zero for all entries. fn verify_dkg_parity_matrix() { // For each CRT modulus for l in 0..L_THRESHOLD { @@ -139,9 +179,18 @@ fn verify_dkg_parity_matrix() { } } } -//DKG Bounds -// Verifies share_encryption bounds (Circuit 3) +// === DKG Bounds === + +/// Verifies all DKG share encryption bounds (used in circuits C3a/C3b). +/// +/// Checks bounds for: +/// - Encryption randomness u (ternary, bound = 1) +/// - Error terms e0, e1 (bound = 20) +/// - Message bound (t - 1) +/// - Public key bounds per CRT modulus +/// - Quotient bounds r1, r2, p1, p2 +/// - Scaling factors k0[i] satisfy k0[i] * t == -1 (mod q_i) fn verify_dkg_bounds() { let n: u128 = DKG_N as u128; let t: u128 = DKG_PLAINTEXT_MODULUS as u128; @@ -227,15 +276,21 @@ fn verify_dkg_bounds() { } } -//Threshold Derived Values +// === Threshold Derived Values === +/// Verifies Threshold BFV-specific derived values. +/// +/// Checks: +/// - Q_MOD_T computation +/// - Q_MOD_T_CENTERED (centered representation) +/// - Q_INVERSE_MOD_T (multiplicative inverse for decoding) fn verify_threshold_derived_values() { verify_threshold_q_mod_t(); verify_threshold_q_mod_t_centered(); verify_threshold_q_inverse_mod_t(); } -// Verifies Threshold: Q_MOD_T = (product of QIS) mod t +/// Verifies Threshold: Q_MOD_T = (product of QIS) mod t. fn verify_threshold_q_mod_t() { let t = THRESHOLD_PLAINTEXT_MODULUS; let m = ModU128::new(t); @@ -248,7 +303,7 @@ fn verify_threshold_q_mod_t() { assert(product == THRESHOLD_Q_MOD_T, "Threshold Q_MOD_T verification failed"); } -// Verifies Threshold: Q_MOD_T_CENTERED = center(Q_MOD_T) +/// Verifies Threshold: Q_MOD_T_CENTERED = center(Q_MOD_T). fn verify_threshold_q_mod_t_centered() { assert( center(THRESHOLD_Q_MOD_T, THRESHOLD_PLAINTEXT_MODULUS) == THRESHOLD_Q_MOD_T_CENTERED, @@ -256,7 +311,10 @@ fn verify_threshold_q_mod_t_centered() { ); } -// Verifies Threshold: Q * Q_INVERSE_MOD_T = 1 mod t +/// Verifies Threshold: Q * Q_INVERSE_MOD_T == 1 (mod t). +/// +/// This inverse is used in circuit C7 for final decoding: +/// message = -Q^{-1} * (t * u_global)_Q mod t fn verify_threshold_q_inverse_mod_t() { let t = THRESHOLD_PLAINTEXT_MODULUS; let m = ModU128::new(t); @@ -266,14 +324,25 @@ fn verify_threshold_q_inverse_mod_t() { assert(product == 1, "Threshold Q_INVERSE_MOD_T verification failed"); } -//Threshold Bounds +// === Threshold Bounds === +/// Verifies Threshold BFV bounds for key generation and decryption. +/// +/// Checks: +/// - Public key generation bounds +/// - Share decryption bounds fn verify_threshold_bounds() { verify_pk_generation_bounds(); verify_share_decryption_bounds(); } -// Verifies pk_generation bounds (Circuit 1) +/// Verifies pk_generation bounds (Circuit C1). +/// +/// Checks bounds for: +/// - Secret key sk (ternary, bound = 1) +/// - Error eek (bound = 20) +/// - Smudging noise e_sm (large bound for 80-bit statistical security) +/// - Quotients r1, r2 for each CRT modulus fn verify_pk_generation_bounds() { let n: u128 = THRESHOLD_N as u128; let eek: u128 = PK_GENERATION_EEK_BOUND as u128; @@ -303,7 +372,14 @@ fn verify_pk_generation_bounds() { verify_e_sm_bound(); } -// Verifies e_sm_bound (smudging noise bound) +/// Verifies e_sm_bound (smudging noise bound). +/// +/// The smudging noise bound is critical for threshold decryption security. +/// It's computed as: e_sm_bound = 2^lambda * N_CIPHERTEXTS * (b_fresh + Q_MOD_T) +/// where b_fresh = N * e_norm + b_enc + N * b_e * sk_norm and lambda = 80. +/// +/// The 2^80 factor provides 80 bits of statistical security, ensuring that +/// decryption shares don't leak secret key information. fn verify_e_sm_bound() { let n: Field = THRESHOLD_N as Field; let e_norm: Field = 20; @@ -326,7 +402,9 @@ fn verify_e_sm_bound() { assert(expected_e_sm_bound == PK_GENERATION_E_SM_BOUND, "PK_GENERATION_E_SM_BOUND mismatch"); } -// Verifies share_decryption bounds (Circuit 6) +/// Verifies share_decryption bounds (Circuit C6). +/// +/// Checks quotient bounds r1, r2 used in the threshold decryption share equation. fn verify_share_decryption_bounds() { let n: u128 = THRESHOLD_N as u128; @@ -349,11 +427,14 @@ fn verify_share_decryption_bounds() { } } -//user_data_encryption Bounds +// === User Data Encryption Bounds === -// Verifies user_data_encryption (Greco) bounds -// Uses THRESHOLD parameters -fn verify_greco_bounds() { +/// Verifies user_data_encryption bounds. +/// +/// These bounds are used in Phase 3 when users encrypt data to the aggregated +/// threshold public key. Uses Threshold BFV parameters with specific constraints +/// for the user data encryption zero-knowledge proof. +fn verify_user_data_encryption_bounds() { let n: u128 = THRESHOLD_N as u128; let t: u128 = THRESHOLD_PLAINTEXT_MODULUS as u128; let u_bound: u128 = USER_DATA_ENCRYPTION_U_BOUND as u128; @@ -454,8 +535,9 @@ fn verify_greco_bounds() { } } -//Cross-Config Consistency +// === Cross-Config Consistency === +/// Verifies consistency between BFV and Threshold BFV parameter sets. fn verify_cross_config_consistency() { // N is consistent assert(DKG_N == THRESHOLD_N, "N mismatch between DKG and Threshold configs"); From 40c4f116067a9dd4817c057c65a6ab962d288cef Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 24 Feb 2026 15:04:25 +0100 Subject: [PATCH 03/31] add docs and readme for c1 --- circuits/bin/dkg/pk/README.md | 7 +-- .../bin/threshold/pk_generation/README.md | 54 ++++++++++++++++++- circuits/lib/src/core/dkg/pk.nr | 5 +- .../lib/src/core/threshold/pk_generation.nr | 48 +++++++++++++---- 4 files changed, 98 insertions(+), 16 deletions(-) diff --git a/circuits/bin/dkg/pk/README.md b/circuits/bin/dkg/pk/README.md index 5aa56fa350..d1d431e6eb 100644 --- a/circuits/bin/dkg/pk/README.md +++ b/circuits/bin/dkg/pk/README.md @@ -13,8 +13,8 @@ flowchart TD %% Input from Phase 0 Input0["P0
Configs Verification"] -.->|"verified configs"| C0 - subgraph Focus["Circuit C0"] - C0["C0: pk
Commit to BFV public key"] + subgraph Focus["C0"] + C0["Commit to BFV public key"] end %% Output to C3a and C3b @@ -39,6 +39,3 @@ flowchart TD - **Data Flow**: `P0 (configs) → C0 → commit(pk_bfv) → C3a, C3b` - **Commitment Function**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - `compute_dkg_pk_commitment()` -- **Related Circuits**: - - C3a, C3b [`dkg/share_encryption`](../share_encryption), - - C4a, C4b [`dkg/share_decryption`](../share_decryption) diff --git a/circuits/bin/threshold/pk_generation/README.md b/circuits/bin/threshold/pk_generation/README.md index 57a498a870..46150c923e 100644 --- a/circuits/bin/threshold/pk_generation/README.md +++ b/circuits/bin/threshold/pk_generation/README.md @@ -1 +1,53 @@ -instantiation of correct Threshold Public Key Generation circuit (PVSS #1) +# [C1] Threshold Public Key Generation (`pk_generation`) + +The Threshold Public Key Generation circuit (C1) is where each ciphernode generates their +contribution to the threshold BFV key. This circuit proves that the public key `(pk0, pk1)` was +generated correctly from a secret key `sk`, error term `eek`, and smudging noise `e_sm`, without +revealing any of these secret values. + +This _commit-then-verify_ approach uses the Schwartz-Zippel lemma: rather than checking every +coefficient of the key generation equations (expensive), the circuit verifies the equations hold at +random challenge points. If they hold at these points, they hold everywhere with overwhelming +probability. + +```mermaid +flowchart TD + %% Input from Phase 0 + Input0["P0
Configs Verification"] -.->|"verified configs"| C1 + + subgraph Focus["C1"] + C1["Generate TRBFV key pair"] + end + + %% Outputs to C2a, C2b, and C5 + C1 -->|"commit(sk)"| Output1["→ C2a
share-computation-sk"] + C1 -->|"commit(e_sm)"| Output2["→ C2b
share-computation-e-sm"] + C1 -->|"commit(pk_trbfv)"| Output3["→ C5
pk-aggregation"] + + style Focus fill:#5B9BD5,stroke:#2E75B6,stroke-width:3px + style Input0 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Output2 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Output3 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + + linkStyle 0 stroke:#808080,stroke-width:2px + linkStyle 1 stroke:#808080,stroke-width:2px + linkStyle 2 stroke:#808080,stroke-width:2px + linkStyle 3 stroke:#808080,stroke-width:2px +``` + +### Metadata + +- **Phase**: P1 (DKG). +- **Runs**: 1 × Ciphernode (after BFV key commitment). +- **Requires**: [`config`](../../config) circuit from P0 (Configs Verification). +- **Output(s)**: + - `commit(sk)` → C2a ([`dkg/share_computation`](../../dkg/sk_share_computation)) + - `commit(e_sm)` → C2b ([`dkg/share_computation`](../../dkg/e_sm_share_computation)) + - `commit(pk_trbfv)` → C5 ([`threshold/pk_aggregation`](../../threshold/pk_aggregation/)) +- **Data Flow**: `P0 → C1 → {commit(sk) → C2a, commit(pk_trbfv) → C5, commit(e_sm) → C2b}` +- **Challenge Generation**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - + `compute_threshold_pk_challenge()` +- **Commitment Functions**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - + `compute_share_computation_sk_commitment()`, `compute_share_computation_e_sm_commitment()`, + `compute_threshold_pk_commitment()` diff --git a/circuits/lib/src/core/dkg/pk.nr b/circuits/lib/src/core/dkg/pk.nr index 6a51ed1947..0ccd88e0f9 100644 --- a/circuits/lib/src/core/dkg/pk.nr +++ b/circuits/lib/src/core/dkg/pk.nr @@ -7,11 +7,14 @@ use crate::math::commitments::compute_dkg_pk_commitment; use crate::math::polynomial::Polynomial; -/// BFV Public Key Commitment Circuit (Circuit C0). +/// BFV Public Key Commitment Circuit (C0). /// /// This circuit establishes a binding cryptographic commitment to a ciphernode's /// BFV public key, which is used exclusively for encrypting secret shares during /// the Distributed Key Generation (DKG) phase. +/// +/// Outputs: +/// - commit(pk0, pk1) -> C3a / C3b (share encryption) pub struct Pk { /// BFV public key first component for each CRT modulus. /// pk0[i] is a degree N-1 polynomial for modulus q_i. diff --git a/circuits/lib/src/core/threshold/pk_generation.nr b/circuits/lib/src/core/threshold/pk_generation.nr index a8d38711d8..565804acb6 100644 --- a/circuits/lib/src/core/threshold/pk_generation.nr +++ b/circuits/lib/src/core/threshold/pk_generation.nr @@ -40,7 +40,12 @@ impl Configs { } } -/// Correct Threshold Public Key Generation Circuit (Circuit 1). +/// Correct Threshold Public Key Contribution Generation Circuit (C1). +/// +/// This circuit proves that a contribution to the threshold BFV public key was generated correctly +/// from secret values without revealing those secrets. Each ciphernode generates +/// their own key contribution, and this circuit ensures the generation followed +/// the BFV key generation equations. /// /// Verifies: /// 1. Range checks on all secret witnesses (secret key, error, smudging noise, quotients) @@ -48,9 +53,9 @@ impl Configs { /// (pk1 is a_i from the hardcoded CRS `a`, not a separate witness) /// /// Outputs: -/// - commit(threshold_sk) -/// - commit(threshold_pk) -/// - commit(e_sm) +/// - commit(sk) -> C2a (secret key share verification) +/// - commit(e_sm) -> C2b (smudging noise share verification) +/// - commit(pk_trbfv) -> C5 (public key aggregation) pub struct PkGeneration { /// Cryptographic parameters including bounds, moduli, and constants. configs: Configs, @@ -95,7 +100,10 @@ impl Vec { let mut inputs = Vec::new(); @@ -116,30 +124,44 @@ impl C2a (secret key share verification) + /// - commit(e_sm) -> C2b (smudging noise share verification) + /// - commit(pk_trbfv) -> C5 (public key aggregation) pub fn execute(self) -> (Field, Field, Field) { + // 1. Perform range checks on all secret witness values self.perform_range_checks(); + // 2. Compute commitments let sk_commitment = compute_share_computation_sk_commitment::(self.sk); let e_sm_commitment = compute_share_computation_e_sm_commitment::(self.e_sm); let pk_commitment = compute_threshold_pk_commitment::(self.pk0, self.a); + // 3. Generate Fiat-Shamir challenges (one per CRT modulus) let gamma = self.generate_challenge(sk_commitment, pk_commitment); self.verify_evaluations(gamma); + // 4. Return all commitments (sk_commitment, pk_commitment, e_sm_commitment) } - /// Generates Fiat-Shamir challenge values using the SAFE cryptographic sponge + /// Generates Fiat-Shamir challenge values using the SAFE cryptographic sponge. + /// + /// Returns a single challenge point `gamma` used to verify key generation + /// equations for all CRT moduli via the Schwartz-Zippel lemma. fn generate_challenge(self, sk_commitment: Field, pk_commitment: Field) -> Field { let inputs = self.payload(sk_commitment, pk_commitment); compute_threshold_pk_challenge(inputs) } - /// Performs range checks on all secret witness values + /// Performs range checks on all secret witness values. + /// + /// These bounds are critical for BFV security-large coefficients would + /// break the scheme's hardness assumptions (Ring-LWE). fn perform_range_checks(self) { // Check that error polynomial has small coefficients self.eek.range_check_2bounds::(self.configs.eek_bound, self.configs.eek_bound); @@ -166,6 +188,14 @@ impl Date: Tue, 24 Feb 2026 16:11:44 +0100 Subject: [PATCH 04/31] add docs for c3 --- circuits/bin/dkg/pk/README.md | 2 +- circuits/bin/dkg/share_encryption/README.md | 65 ++++++++++- .../bin/threshold/pk_generation/README.md | 2 +- circuits/lib/src/core/dkg/share_encryption.nr | 110 ++++++++++++------ 4 files changed, 142 insertions(+), 37 deletions(-) diff --git a/circuits/bin/dkg/pk/README.md b/circuits/bin/dkg/pk/README.md index d1d431e6eb..ae7572d95a 100644 --- a/circuits/bin/dkg/pk/README.md +++ b/circuits/bin/dkg/pk/README.md @@ -32,7 +32,7 @@ flowchart TD ``` - **Phase**: P1 (DKG). -- **Runs**: 1 x Ciphernode (at the start of key generation). +- **Runs**: N_PARTIES = 1 x Ciphernode (at the start of key generation). - **Requires**: [`config`](../../config) circuit from P0 (Configs Verification). - **Output(s)**: `commit(pk_bfv)` consumed by C3a / C3b ([`dkg/share_encryption`](../share_encryption)) diff --git a/circuits/bin/dkg/share_encryption/README.md b/circuits/bin/dkg/share_encryption/README.md index 0040c1cec8..1a37898cf0 100644 --- a/circuits/bin/dkg/share_encryption/README.md +++ b/circuits/bin/dkg/share_encryption/README.md @@ -1 +1,64 @@ -instantiation of Smudging Noise Share Encryption circuit (PVSS #3b) +# [C3a & C3b] Share Encryption (`share_encryption`) + +The Share Encryption circuit verifies that each ciphernode correctly encrypted a secret share under +the recipient's BFV public key. After shares are verified in _C2a/C2b_, they must be encrypted for +secure peer-to-peer transmission — this circuit proves the encryption was formed correctly without +revealing the plaintext share or the encryption randomness. + +This is a single circuit used for both variants: **C3a** encrypts secret key (`sk`) shares, taking +its message commitment from _C2a_; **C3b** encrypts smudging noise (`e_sm`) shares, taking its +message commitment from _C2b_. The verification logic and all input types are identical — only the +value of `expected_message_commitment` differs between the two instantiations. + +```mermaid +flowchart TD + Input0["C0
pk"] -.->|"commit(pk_bfv)"| C3 + Input2a["C2a
share-computation-sk"] -.->|"commit(sk_share[i][j])"| C3 + Input2b["C2b
share-computation-e-sm"] -.->|"commit(e_sm_share[i][j])"| C3 + + subgraph Focus["C3a & C3b"] + C3["Encrypt share under BFV pk"] + end + + C3 -->|"ct(sk_share)"| Output1["→ C4a
share-decryption-sk"] + C3 -->|"ct(e_sm_share)"| Output2["→ C4b
share-decryption-e-sm"] + + style Focus fill:#5B9BD5,stroke:#2E75B6,stroke-width:3px + style Input0 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Input2a fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Input2b fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Output2 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + + linkStyle 0 stroke:#808080,stroke-width:2px + linkStyle 1 stroke:#808080,stroke-width:2px + linkStyle 2 stroke:#808080,stroke-width:2px + linkStyle 3 stroke:#808080,stroke-width:2px + linkStyle 4 stroke:#808080,stroke-width:2px +``` + +### Metadata + +- **Phase**: P1 (DKG). +- **Runs**: (N_PARTIES - 1) × Ciphernode per variant (once per recipient per share type). +- **Requires**: + - `commit(pk_bfv)` from C0 ([`dkg/pk`](../pk)) + - C3a: `commit(sk_share[party_idx][mod_idx])` from C2a + ([`dkg/sk_share_computation`](../sk_share_computation)) + - C3b: `commit(e_sm_share[party_idx][mod_idx])` from C2b + ([`dkg/e_sm_share_computation`](../e_sm_share_computation)) +- **Output(s)**: + - C3a: encrypted SK share ciphertexts `ct(sk_share)` → C4a + ([`dkg/share_decryption`](../share_decryption)) + - C3b: encrypted noise share ciphertexts `ct(e_sm_share)` → C4b + ([`dkg/share_decryption`](../share_decryption)) +- **Data Flow**: `C0 + C2a → C3a → ct(sk_share) → C4a` and `C0 + C2b → C3b → ct(e_sm_share) → C4b` +- **Challenge Generation**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - + `compute_share_encryption_challenge()` +- **Commitment Functions**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - + `compute_dkg_pk_commitment()`, `compute_share_encryption_commitment_from_message()` +- **Related Circuits**: + - C0 [`dkg/pk`](../pk) + - C2a [`dkg/sk_share_computation`](../sk_share_computation) + - C2b [`dkg/e_sm_share_computation`](../e_sm_share_computation) + - C4a, C4b [`dkg/share_decryption`](../share_decryption) diff --git a/circuits/bin/threshold/pk_generation/README.md b/circuits/bin/threshold/pk_generation/README.md index 46150c923e..c9e0cd87a6 100644 --- a/circuits/bin/threshold/pk_generation/README.md +++ b/circuits/bin/threshold/pk_generation/README.md @@ -39,7 +39,7 @@ flowchart TD ### Metadata - **Phase**: P1 (DKG). -- **Runs**: 1 × Ciphernode (after BFV key commitment). +- **Runs**: N_PARTIES = 1 x Ciphernode (after BFV key commitment). - **Requires**: [`config`](../../config) circuit from P0 (Configs Verification). - **Output(s)**: - `commit(sk)` → C2a ([`dkg/share_computation`](../../dkg/sk_share_computation)) diff --git a/circuits/lib/src/core/dkg/share_encryption.nr b/circuits/lib/src/core/dkg/share_encryption.nr index 1f48c41d53..c4662c1977 100644 --- a/circuits/lib/src/core/dkg/share_encryption.nr +++ b/circuits/lib/src/core/dkg/share_encryption.nr @@ -16,31 +16,31 @@ use crate::math::polynomial::Polynomial; pub struct Configs { /// Plaintext modulus t pub t: Field, - /// Q mod t (for scaling message) + /// Q mod t: product of all CRT moduli modulo the plaintext modulus, used for message scaling pub q_mod_t: Field, - /// CRT moduli for each basis: [q_0, q_1, ..., q_{L-1}] + /// CRT moduli: [q_0, q_1, ..., q_{L-1}] pub qis: [Field; L], - /// Scaling factors for each basis: [k0_0, k0_1, ..., k0_{L-1}] + /// Scaling factors k0[i] satisfying `k0[i] * t == -1 (mod q_i)` for each CRT modulus pub k0is: [Field; L], - /// Bounds for public key polynomials for each CRT basis + /// Coefficient bounds for public key polynomials per CRT modulus: (q_i - 1) / 2 pub pk_bounds: [Field; L], - /// Bounds for error polynomials (e0) + /// Coefficient bound for the global error polynomial e0 pub e0_bound: Field, - /// Bounds for error polynomials (e1) + /// Coefficient bound for the ternary randomness polynomial u (= 1) pub e1_bound: Field, - /// Bound for secret polynomial u (ternary distribution) + /// Coefficient bound for the ternary randomness polynomial u (= 1) pub u_bound: Field, - /// Lower bounds for r1 polynomials (modulus switching quotients) + /// Lower bounds for r1 quotient polynomials per CRT modulus pub r1_low_bounds: [Field; L], - /// Upper bounds for r1 polynomials (modulus switching quotients) + /// Coefficient bounds for r2 cyclotomic reduction quotients per CRT modulus pub r1_up_bounds: [Field; L], - /// Bounds for r2 polynomials (cyclotomic reduction quotients) + /// Coefficient bounds for r2 cyclotomic reduction quotients per CRT modulus pub r2_bounds: [Field; L], - /// Bounds for p1 polynomials (modulus switching quotients) + /// Coefficient bounds for p1 modulus switching quotients per CRT modulus pub p1_bounds: [Field; L], - /// Bounds for p2 polynomials (cyclotomic reduction quotients) + /// Coefficient bounds for p2 cyclotomic reduction quotients per CRT modulus pub p2_bounds: [Field; L], - /// Bound for message polynomial (m) + /// Bound for the raw message (share) polynomial coefficients: t - 1 pub msg_bound: Field, } @@ -80,20 +80,28 @@ impl Configs { } } -/// DKG Share Encryption Circuit (Circuit 3). +/// DKG Share Encryption Circuit (C3a / C3b). /// -/// Verifies: -/// 1. Public key commitment matches expected (from Circuit 0) -/// 2. Message commitment matches expected (from SK shares circuit) -/// 3. Correct DKG share encryption: ct0[l] = pk0[l] * u + e0[l] + k1 * k0[l] + r1[l] * q[l] + r2[l] * (X^N + 1) -/// and ct1[l] = pk1[l] * u + e1 + p2[l] * (X^N + 1) + p1[l] * q[l] +/// Verifies that a secret share was correctly encrypted under a recipient's BFV public key. +/// This circuit runs twice in parallel per ciphernode per recipient: +/// - C3a: encrypts a secret key (sk) share +/// - C3b: encrypts a smudging noise (e_sm) share +/// +/// Both variants use identical verification logic. The plaintext being encrypted +/// is the respective share committed in C2a (for C3a) or C2b (for C3b). +/// +/// Outputs: +/// - None +/// The output ciphertexts (ct0, ct1) are broadcasted to the intended recipient for decryption in C4a/C4b. pub struct ShareEncryption { /// Circuit parameters configs: Configs, - /// Expected commitment to public key (from Circuit 0) + /// Expected commitment to the BFV public key (from C0: pk). /// (public witness) expected_pk_commitment: Field, - /// Expected commitment to message (from SK shares verification circuit) + /// Expected commitment to the message (share) being encrypted. + /// commit(sk_share[party_idx][mod_idx]) from C2a, or + /// commit(e_sm_share[party_idx][mod_idx]) from C2b. /// (public witness) expected_message_commitment: Field, /// Public key component 0 for each CRT basis (committed witnesses) @@ -167,7 +175,10 @@ impl(self.pk0is, self.pk1is) @@ -176,7 +187,10 @@ impl(self.message) @@ -185,8 +199,14 @@ impl Polynomial { let t = self.configs.t; let t_mod = ModU128::new(t); @@ -217,7 +237,11 @@ impl) -> Vec { let mut inputs = Vec::new(); @@ -247,7 +271,13 @@ impl(self.configs.u_bound, self.configs.u_bound); self.e0.range_check_2bounds::(self.configs.e0_bound, self.configs.e0_bound); @@ -323,14 +353,26 @@ impl) -> Vec { let inputs = self.payload(k1); compute_share_encryption_challenge::(inputs) } - /// Verifies DKG encryption constraints using Fiat-Shamir challenges and the Schwartz-Zippel lemma + /// Verifies BFV encryption equations using Schwartz-Zippel with batched modulus verification. + /// + /// For each CRT modulus l, checks at the primary challenge point gamma: + /// ct0[l](gamma) = pk0[l](gamma)*u(gamma) + e0[l](gamma) + k1(gamma)*k0[l] + r1[l](gamma)*q_l + r2[l](gamma)*(gamma^N + 1) + /// ct1[l](gamma) = pk1[l](gamma)*u(gamma) + e1(gamma) + p2[l](gamma)*(gamma^N + 1) + p1[l](gamma)*q_l + /// + /// Rather than asserting each equation independently, both sides are accumulated + /// into a weighted batch sum using additional challenge values as weights. + /// A deviation in any single equation will cause the accumulated sums to diverge + /// with overwhelming probability (Schwartz-Zippel soundness). fn verify_evaluations(self, gammas: Vec, k1: Polynomial) { let gamma = gammas.get(0); let cyclo_at_gamma = gamma.pow_32(N as Field) + 1; @@ -345,14 +387,14 @@ impl Date: Tue, 24 Feb 2026 16:44:27 +0100 Subject: [PATCH 05/31] add docs for c4 and fix nits --- circuits/bin/dkg/share_decryption/README.md | 55 ++++++++++++++++++- circuits/bin/dkg/share_encryption/README.md | 6 +- circuits/lib/src/core/dkg/share_decryption.nr | 51 +++++++++++++---- circuits/lib/src/core/dkg/share_encryption.nr | 33 +++++------ 4 files changed, 113 insertions(+), 32 deletions(-) diff --git a/circuits/bin/dkg/share_decryption/README.md b/circuits/bin/dkg/share_decryption/README.md index 6cbed3aa86..fbb637d35d 100644 --- a/circuits/bin/dkg/share_decryption/README.md +++ b/circuits/bin/dkg/share_decryption/README.md @@ -1 +1,54 @@ -instantiation of correct Smudging Noise Share Decryption circuit (PVSS #4b) +# [C4a & C4b] Share Decryption & Aggregation (`share_decryption`) + +The Share Decryption circuit verifies that each ciphernode correctly decrypted the shares they +received, and that those shares were honestly aggregated into a single combined value. This closes +the DKG loop: shares were committed in _C2_, encrypted in _C3_, and are now verified upon decryption +and aggregated here. + +This is a single circuit used for both variants: **C4a** handles secret key (`sk`) shares, consuming +commitments from _C2a_; **C4b** handles smudging noise (`e_sm`) shares, consuming commitments from +_C2b_. The verification logic and all input types are identical — only the source of +`expected_commitments` differs between the two instantiations. + +```mermaid +flowchart TD + Input2a["C2a
share-computation-sk"] -.->|"commit(sk_share[i][j])"| C4 + Input2b["C2b
share-computation-e-sm"] -.->|"commit(e_sm_share[i][j])"| C4 + + subgraph Focus["C4a & C4b"] + C4["Verify decrypted shares & aggregate"] + end + + C4 -->|"commit(agg_sk)"| Output1["→ C6
threshold-share-decryption"] + C4 -->|"commit(agg_e_sm)"| Output1 + + style Focus fill:#5B9BD5,stroke:#2E75B6,stroke-width:3px + style Input2a fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Input2b fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + + linkStyle 0 stroke:#808080,stroke-width:2px + linkStyle 1 stroke:#808080,stroke-width:2px + linkStyle 2 stroke:#808080,stroke-width:2px + linkStyle 3 stroke:#808080,stroke-width:2px +``` + +### Metadata + +- **Phase**: P1 (DKG). +- **Runs**: (N_PARTIES - 1) × Ciphernode per variant (once per recipient per share type). +- **Requires**: + - C4a: `commit(sk_share[party_idx][mod_idx])` from C2a + ([`dkg/sk_share_computation`](../sk_share_computation)) + - C4b: `commit(e_sm_share[party_idx][mod_idx])` from C2b + ([`dkg/e_sm_share_computation`](../e_sm_share_computation)) +- **Output(s)**: + - C4a: `commit(agg_sk)` → C6 ([`threshold/share_decryption`](../../threshold/share_decryption)) + - C4b: `commit(agg_e_sm)` → C6 ([`threshold/share_decryption`](../../threshold/share_decryption)) +- **Data Flow**: `C2a → C4a → commit(agg_sk) → C6` and `C2b → C4b → commit(agg_e_sm) → C6` +- **Commitment Functions**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - + `compute_share_encryption_commitment_from_message()`, `compute_aggregated_shares_commitment()` +- **Related Circuits**: + - C2a [`dkg/sk_share_computation`](../sk_share_computation) + - C2b [`dkg/e_sm_share_computation`](../e_sm_share_computation) + - C6 [`threshold/share_decryption`](../../threshold/share_decryption) diff --git a/circuits/bin/dkg/share_encryption/README.md b/circuits/bin/dkg/share_encryption/README.md index 1a37898cf0..c87e37a262 100644 --- a/circuits/bin/dkg/share_encryption/README.md +++ b/circuits/bin/dkg/share_encryption/README.md @@ -1,9 +1,9 @@ # [C3a & C3b] Share Encryption (`share_encryption`) The Share Encryption circuit verifies that each ciphernode correctly encrypted a secret share under -the recipient's BFV public key. After shares are verified in _C2a/C2b_, they must be encrypted for -secure peer-to-peer transmission — this circuit proves the encryption was formed correctly without -revealing the plaintext share or the encryption randomness. +the recipient's BFV public key. After shares are verified and committed in _C2a/C2b_, they must be +encrypted for secure peer-to-peer transmission — this circuit proves the encryption was formed +correctly without revealing the plaintext share or the encryption randomness. This is a single circuit used for both variants: **C3a** encrypts secret key (`sk`) shares, taking its message commitment from _C2a_; **C3b** encrypts smudging noise (`e_sm`) shares, taking its diff --git a/circuits/lib/src/core/dkg/share_decryption.nr b/circuits/lib/src/core/dkg/share_decryption.nr index 1ffe3a09a0..59238bd992 100644 --- a/circuits/lib/src/core/dkg/share_decryption.nr +++ b/circuits/lib/src/core/dkg/share_decryption.nr @@ -9,18 +9,30 @@ use crate::math::commitments::{ }; use crate::math::polynomial::Polynomial; -/// Share Decryption Commitment Verification (Circuit 4). +/// DKG Share Decryption & Aggregation Circuit (C4a / C4b). /// -/// Verifies: -/// 1. Each decrypted share from H honest parties matches its commitment from Circuit 3 -/// 2. Computes sum of all shares -/// 3. Returns commitment to aggregated shares +/// Verifies that each ciphernode correctly decrypted the shares addressed to them +/// and honestly aggregated all honest parties' contributions into a single combined value. +/// +/// This circuit runs twice in parallel: +/// - C4a: verifies and aggregates secret key (sk) shares, consuming commit(sk_share) from C2a +/// - C4b: verifies and aggregates smudging noise (e_sm) shares, consuming commit(e_sm_share) from C2b +/// +/// Both variants use identical verification logic. The source of `expected_commitments` +/// is the only difference between the two instantiations. +/// +/// Outputs: +/// - C4a: `commit(agg_sk)` -> C6 ([`threshold/share_decryption`](../../threshold/share_decryption)) +/// - C4b: `commit(agg_e_sm)` -> C6 ([`threshold/share_decryption`](../../threshold/share_decryption)) pub struct ShareDecryption { - /// Expected commitments from Circuit 3 for H honest parties: [party_idx][mod_idx] - /// (public witness) + /// Expected commitments to the share polynomials, produced in C2a (for C4a) or C2b (for C4b) + /// via commit_to_party_shares. Organised as [party_idx][mod_idx], covering all H honest + /// parties and L CRT moduli. + /// (public witnesses) expected_commitments: [[Field; L]; H], - /// Decrypted shares from H honest parties: [party_idx][mod_idx] + /// Decrypted share polynomials from H honest parties. + /// Organised as [party_idx][mod_idx]. /// (secret witnesses) decrypted_shares: [[Polynomial; L]; H], } @@ -33,7 +45,12 @@ impl ShareDecryption ShareDecryption [Polynomial; L] { let mut sum: [Polynomial; L] = [Polynomial::new([0; N]); L]; @@ -68,8 +92,11 @@ impl ShareDecryption Field { // Step 1: Verify all commitments match self.verify_commitments(); diff --git a/circuits/lib/src/core/dkg/share_encryption.nr b/circuits/lib/src/core/dkg/share_encryption.nr index c4662c1977..7837682e84 100644 --- a/circuits/lib/src/core/dkg/share_encryption.nr +++ b/circuits/lib/src/core/dkg/share_encryption.nr @@ -84,24 +84,26 @@ impl Configs { /// /// Verifies that a secret share was correctly encrypted under a recipient's BFV public key. /// This circuit runs twice in parallel per ciphernode per recipient: -/// - C3a: encrypts a secret key (sk) share -/// - C3b: encrypts a smudging noise (e_sm) share +/// - C3a: encrypts a secret key (sk) share, consuming commit(sk_share) from C2a +/// - C3b: encrypts a smudging noise (e_sm) share, consuming commit(e_sm_share) from C2b /// -/// Both variants use identical verification logic. The plaintext being encrypted -/// is the respective share committed in C2a (for C3a) or C2b (for C3b). +/// The key insight is that the commitment to the plaintext being encrypted must equal +/// the commitment to the share produced in C2a/C2b. This binds the encryption to the +/// previously verified share, we know the ciphertext contains the correct share without +/// ever seeing it in plaintext. /// /// Outputs: -/// - None -/// The output ciphertexts (ct0, ct1) are broadcasted to the intended recipient for decryption in C4a/C4b. +/// No new commitments are produced. The output ciphertexts (ct0, ct1) are broadcast +/// to the intended recipient for decryption in C4a/C4b. pub struct ShareEncryption { /// Circuit parameters configs: Configs, /// Expected commitment to the BFV public key (from C0: pk). /// (public witness) expected_pk_commitment: Field, - /// Expected commitment to the message (share) being encrypted. - /// commit(sk_share[party_idx][mod_idx]) from C2a, or - /// commit(e_sm_share[party_idx][mod_idx]) from C2b. + /// Expected commitment to the share (message) being encrypted. + /// - C3a: commit(sk_share[party_idx][mod_idx]) produced by C2a + /// - C3b: commit(e_sm_share[party_idx][mod_idx]) produced by C2b /// (public witness) expected_message_commitment: Field, /// Public key component 0 for each CRT basis (committed witnesses) @@ -177,7 +179,7 @@ impl(self.message) @@ -204,9 +208,6 @@ impl Polynomial { let t = self.configs.t; let t_mod = ModU128::new(t); From b21967b924deba8b201a935f69962e20d374481b Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 24 Feb 2026 17:05:11 +0100 Subject: [PATCH 06/31] add docs for c5 --- .../bin/threshold/pk_aggregation/README.md | 40 ++++++++++++++- .../lib/src/core/threshold/pk_aggregation.nr | 49 ++++++++++++------- 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/circuits/bin/threshold/pk_aggregation/README.md b/circuits/bin/threshold/pk_aggregation/README.md index 7644350ebc..d458d035b5 100644 --- a/circuits/bin/threshold/pk_aggregation/README.md +++ b/circuits/bin/threshold/pk_aggregation/README.md @@ -1 +1,39 @@ -instantiation of Threshold Public Key Aggregation circuit (PVSS #5) +# [C5] Public Key Aggregation (`pk_aggregation`) + +The Public Key Aggregation circuit combines the threshold BFV public key shares from all honest +ciphernodes into a single aggregated public key that users will encrypt to. It verifies both that +the individual shares match their commitments from _C1_ and that the aggregation was computed +correctly, before committing to the result. + +```mermaid +flowchart TD + Input1["C1
pk-generation (×H)"] -.->|"commit(pk_trbfv[h])"| C5 + + subgraph Focus["C5"] + C5["Verify pk shares & aggregate"] + end + + C5 -->|"commit(pk_agg)"| Output1["→ P3
User Encryption"] + + style Focus fill:#70AD47,stroke:#548235,stroke-width:3px + style Input1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + + linkStyle 0 stroke:#808080,stroke-width:2px + linkStyle 1 stroke:#808080,stroke-width:2px +``` + +### Metadata + +- **Phase**: P2 (Aggregation). +- **Runs**: 1 × Aggregator (once after all honest parties' pk shares are collected). +- **Requires**: `commit(pk_trbfv[h])` from C1 ([`threshold/pk_generation`](../pk_generation)) for + each honest party `h ∈ H`. +- **Output(s)**: `commit(pk_agg)` → user-data-encryption + ([`threshold/user_data_encryption`](../user_data_encryption)) +- **Data Flow**: `C1 (×H) → C5 → commit(pk_agg) → P3` +- **Commitment Functions**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - + `compute_pk_aggregation_commitment()` +- **Related Circuits**: + - C1 [`threshold/pk_generation`](../pk_generation) + - user-data-encryption [`threshold/user_data_encryption`](../user_data_encryption) diff --git a/circuits/lib/src/core/threshold/pk_aggregation.nr b/circuits/lib/src/core/threshold/pk_aggregation.nr index 91efaf8326..6d76eb7b9c 100644 --- a/circuits/lib/src/core/threshold/pk_aggregation.nr +++ b/circuits/lib/src/core/threshold/pk_aggregation.nr @@ -20,7 +20,7 @@ impl Configs { } } -/// Public Key Aggregation (Circuit 5). +/// Public Key Aggregation Circuit (C5). /// /// Verifies that for each CRT basis l and each coefficient i: /// - pk0_agg[l][i] = sum_h(pk0[h][l][i]) mod q_l (witnesses use centered coefficients; aggregation @@ -30,25 +30,27 @@ pub struct PkAggregation { /// Circuit parameters including CRT moduli configs: Configs, - /// Expected commitments to threshold public key (from C1) - /// We need one commitment from each honest party (H). - /// (public witness) + /// Expected commitments to each honest party's threshold public key share. + /// commit(pk_trbfv[h]) produced by C1 for each h in H. + /// (public witnesses) expected_threshold_pk_commitments: [Field; H], - /// Individual public keys from H honest parties - /// pk0[party_idx][basis_idx] - first component of public key for each party and CRT basis + /// Individual threshold public key first components from H honest parties. + /// pk0[party_idx][basis_idx] for each party and CRT basis. /// (committed witnesses) pk0: [[Polynomial; L]; H], - /// pk1[party_idx][basis_idx] - second component of public key for each party and CRT basis + /// Individual threshold public key second components from H honest parties. + /// pk1[party_idx][basis_idx] for each party and CRT basis. /// (committed witnesses) pk1: [[Polynomial; L]; H], - /// Claimed aggregated public key - /// pk0_agg[basis_idx] - first component of aggregated public key for each CRT basis - /// (committed witnesses) + /// Claimed aggregated public key first component for each CRT basis. + /// Must equal sum(pk0[h][l]) mod q_l for each basis l. + /// (committed witness) pk0_agg: [Polynomial; L], - /// pk1_agg[basis_idx] - second component of aggregated public key for each CRT basis - /// (committed witnesses) + /// Claimed aggregated public key second component for each CRT basis. + /// Must equal sum(pk1[h][l]) mod q_l for each basis l. + /// (committed witness) pk1_agg: [Polynomial; L], } @@ -64,7 +66,11 @@ impl PkAggregation PkAggregation; L]; H], pk_agg: [Polynomial; L], @@ -114,15 +127,17 @@ impl PkAggregation Field { // 0. Verify pk commitments self.verify_pk_commitments(); // 1. Verify pk0 aggregation (sum) and pk1 (all equal to a, pk1_agg = a) for basis_idx in 0..L { - self.verify_pk0_for_basis(self.pk0, self.pk0_agg, basis_idx); + self.verify_pk_for_basis(self.pk0, self.pk0_agg, basis_idx); self.verify_pk1(basis_idx); } From bbc0b994531fbe3e8e59fecd73d303f615698f9d Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 24 Feb 2026 17:35:47 +0100 Subject: [PATCH 07/31] add docs for c6 --- .../bin/threshold/share_decryption/README.md | 53 ++++++++- .../src/core/threshold/share_decryption.nr | 102 +++++++++++------- 2 files changed, 116 insertions(+), 39 deletions(-) diff --git a/circuits/bin/threshold/share_decryption/README.md b/circuits/bin/threshold/share_decryption/README.md index 07caf5bb80..48df342975 100644 --- a/circuits/bin/threshold/share_decryption/README.md +++ b/circuits/bin/threshold/share_decryption/README.md @@ -1 +1,52 @@ -instantiation of Threshold Share Decryption circuit (PVSS #6) +# [C6] Decryption Share (`share_decryption`) + +The Decryption Share circuit proves that a ciphernode correctly computed its decryption share from +the homomorphic result ciphertext, using the aggregated secret key share and smudging noise produced +in P1. It runs once per ciphernode per decryption request; T+1 valid proofs are required before C7 +can proceed. + +```mermaid +flowchart TD + Input1["P1
C4a / C4b"] -.->|"commit(agg_sk)
commit(agg_e_sm)"| C6 + Input2["P3
homomorphic result"] -.->|"(ct0, ct1)"| C6 + + subgraph Focus["C6"] + C6["Verify sk & e_sm commitments,
verify decryption share computation
"] + end + + C6 -->|"d[l] (public)"| Output1["→ C7
dec-result-trbfv"] + + style Focus fill:#E06666,stroke:#C00000,stroke-width:3px + style Input1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Input2 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 + + linkStyle 0 stroke:#808080,stroke-width:2px + linkStyle 1 stroke:#808080,stroke-width:2px + linkStyle 2 stroke:#808080,stroke-width:2px +``` + +**Phase:** P4 (Decryption) + +**Runs:** 1 × Ciphernode (once per ciphernode per decryption request; T+1 valid proofs required) + +**Requires:** + +- `commit(agg_sk)` from C4a (`threshold/dec-bfv-sk`) +- `commit(agg_e_sm)` from C4b (`threshold/dec-bfv-e-sm`) +- `(ct0, ct1)` — homomorphic result ciphertext from P3 + +**Output(s):** + +- `d[l]` — public decryption share polynomial per CRT basis, consumed by C7 + +**Data Flow:** C4a/C4b + P3 result → C6 → `d` → C7 + +**Commitment Functions:** `math/commitments.nr` — `compute_aggregated_shares_commitment()`, +`compute_threshold_share_decryption_challenge()` + +**Related Circuits:** + +- C4a `threshold/dec-bfv-sk` +- C4b `threshold/dec-bfv-e-sm` +- C7 `threshold/dec-result-trbfv` diff --git a/circuits/lib/src/core/threshold/share_decryption.nr b/circuits/lib/src/core/threshold/share_decryption.nr index 5771f8196c..bf4f96c35a 100644 --- a/circuits/lib/src/core/threshold/share_decryption.nr +++ b/circuits/lib/src/core/threshold/share_decryption.nr @@ -15,9 +15,9 @@ use crate::math::polynomial::Polynomial; pub struct Configs { /// CRT moduli: [q_0, q_1, ..., q_{L-1}] pub qis: [Field; L], - /// Bounds for r1 polynomials (modulus switching quotients) for each CRT basis + /// Coefficient bounds for r1 polynomials (modulus switching quotients, per CRT basis) pub r1_bounds: [Field; L], - /// Bounds for r2 polynomials (cyclotomic reduction quotients) for each CRT basis + /// Coefficient bounds for r2 polynomials (cyclotomic reduction quotients, per CRT basis) pub r2_bounds: [Field; L], } @@ -27,8 +27,34 @@ impl Configs { } } -/// Threshold Share Decryption (Circuit 6). +/// Decryption Share Circuit (C6). /// +/// Proves that a ciphernode correctly computed its decryption share from the homomorphic +/// result ciphertext, using the aggregated secret key share and smudging noise committed +/// during P1 (DKG). Runs once per ciphernode; T+1 valid proofs are required before +/// C7 can reconstruct the plaintext. +/// +/// # Decryption Share Formula +/// +/// For each CRT basis l: +/// d[l] = ct0[l] + ct1[l] * sk[l] + e_sm[l] + r2[l] * (X^N + 1) + r1[l] * q_l +/// +/// where: +/// - sk is the ciphernode's aggregated secret key share (sum of received shares, from C4a) +/// - e_sm is the ciphernode's aggregated smudging noise share (from C4b) +/// - r1, r2 are quotient polynomials for modular reduction (secret witnesses) +/// +/// Smudging: +/// +/// The decryption share d is public but reveals nothing about sk. The smudging noise +/// e_sm is chosen large enough to statistically mask the ct1 * sk term, so an adversary +/// observing all T+1 decryption shares cannot extract information about any party's +/// secret key share. +/// +/// Outputs: +/// +/// d[l] -> public decryption share per CRT basis, consumed by C7 (dec-result-trbfv). +pub struct ShareDecryption { /// Verifies: /// 1. Commitment to sk matches expected (from DKG decryption circuit) /// 2. Commitment to e_sm matches expected (from DKG decryption circuit) @@ -37,29 +63,40 @@ pub struct ShareDecryption, - /// Expected commitment to aggregated sk shares (from DKG decryption circuit) + /// Expected commitment to the aggregated secret key share, from C4a. + /// Binds this proof to the sk committed during DKG. /// (public witness) expected_sk_commitment: Field, - /// Expected commitment to aggregated e_sm shares (from DKG decryption circuit) + /// Expected commitment to the aggregated smudging noise share, from C4b. + /// Binds this proof to the e_sm committed during DKG. /// (public witness) expected_e_sm_commitment: Field, - /// Ciphertext components (public witnesses) - /// ct0 components for each CRT basis (degree N-1 polynomials with N coefficients) + /// First ciphertext component per CRT basis. + /// (public witnesses) ct0: [Polynomial; L], - /// ct1 components for each CRT basis (degree N-1 polynomials with N coefficients) + + /// Second ciphertext component per CRT basis. + /// (public witnesses) ct1: [Polynomial; L], - /// Aggregated sum of sk shares (secret witness) + /// Aggregated secret key share: sum of all received sk shares (from C4a). + /// Verified against expected_sk_commitment; range checks handled by DKG circuits. + /// (secret witness) sk: [Polynomial; L], - /// Aggregated sum of e_sm shares (secret witness, direct input) - /// e_sm[basis] - sum of e_sm shares for each CRT basis (degree N-1 with N coefficients) + /// Aggregated smudging noise share: sum of all received e_sm shares (from C4b). + /// Verified against expected_e_sm_commitment; range checks handled by DKG circuits. + /// (secret witness) e_sm: [Polynomial; L], - /// Quotient polynomials for lifting to Z (secret witnesses) + /// Modulus switching quotient polynomials, per CRT basis. + /// (secret witnesses) r1: [Polynomial<2 * N - 1>; L], + + /// Cyclotomic reduction quotient polynomials, per CRT basis. + /// (secret witnesses) r2: [Polynomial; L], /// Party's computed decryption share (secret witness) @@ -94,7 +131,10 @@ impl(self.sk) @@ -103,7 +143,10 @@ impl(self.e_sm) @@ -147,9 +190,7 @@ impl Vec { let mut inputs = Vec::new(); @@ -232,7 +273,7 @@ impl(inputs) } - /// Verifies the lifted decryption share computation formula for a specific CRT basis using the Schwartz-Zippel lemma. + /// Verifies the decryption share equation at the Fiat-Shamir challenge point for one CRT basis. /// - /// This function verifies that the decryption share for basis `i` satisfies: - /// `d_i(gamma) = c_0i(gamma) + c_1i(gamma) * s_i(gamma) + e_i(gamma) + r_2i(gamma) * cyclo(gamma) + r_1i(gamma) * q_i` + /// Checks that the claimed d[basis_idx] satisfies: + /// d(gamma) = ct0(gamma) + ct1(gamma) * sk(gamma) + e_sm(gamma) + r2(gamma) * (gamma^N + 1) + r1(gamma) * q_l /// - /// Where: - /// - `c_0i`, `c_1i` are ciphertext components for basis i - /// - `s_i` is the aggregated secret key shares for basis i - /// - `e_i` is the aggregated noise shares for basis i - /// - `r_1i`, `r_2i` are quotient witnesses for basis i - /// - `cyclo(gamma) = gamma^N + 1` is the cyclotomic polynomial evaluated at gamma - /// - `q_i` is the CRT modulus for basis i - /// - /// The Schwartz-Zippel lemma ensures that if this equation holds at a random point - /// `gamma`, then the polynomials are identical with high probability. - /// - /// # Arguments - /// * `basis_idx` - The index of the CRT basis to verify (0 <= basis_idx < L) - /// * `gamma` - The Fiat-Shamir challenge value used as the evaluation point - /// - /// # Panics - /// The circuit will fail if the decryption share computation formula doesn't hold for the specified basis. + /// By the Schwartz-Zippel lemma, if this holds at a random gamma, the underlying + /// polynomial equation holds with high probability. fn verify_decryption_share_computation(self, basis_idx: u32, gamma: Field) { // Evaluate ciphertext components at gamma let c_0_at_gamma = self.ct0[basis_idx].eval(gamma); From 0e390effa2b24fcc4e9a7a1ee4e0d26cb61def1b Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 24 Feb 2026 17:52:28 +0100 Subject: [PATCH 08/31] add docs for C7 --- .../threshold/decrypted_shares_aggregation.nr | 78 ++++++++++++------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr index abdd450d36..3c45c5cb1e 100644 --- a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr +++ b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr @@ -7,9 +7,9 @@ use crate::math::commitments::compute_threshold_decryption_share_commitment; use crate::math::modulo::U128::ModU128; use crate::math::polynomial::Polynomial; -use dep::bignum::BigNum; -use dep::bignum::bignum::to_field; -use dep::bignum::SecureThreshold8192; +use ::bignum::BigNum; +use ::bignum::bignum::to_field; +use ::bignum::SecureThreshold8192; /// Cryptographic parameters for decryption share aggregation circuit. pub struct Configs { @@ -17,7 +17,8 @@ pub struct Configs { pub qis: [Field; L], /// Plaintext modulus (typically denoted as `t`) pub plaintext_modulus: Field, - /// Precomputed value: `-Q^(-1) mod t` where Q is the product of all CRT moduli + /// Precomputed value: -Q^{-1} mod t, where Q = q_0 * q_1 * ... * q_{L-1}. + /// Used in the final decoding step to recover the message from u_global. pub q_inverse_mod_t: Field, } @@ -45,17 +46,24 @@ pub struct DecryptedSharesAggregation; L]; T + 1], - /// Party IDs (x-coordinates) for interpolation (public witnesses) - /// Note: Must be in strictly increasing order for correct Lagrange sign computation + /// Shamir x-coordinates for the T+1 participating parties. + /// Must be in strictly increasing order, the sign computation in Lagrange + /// interpolation depends on this ordering. + /// (public witnesses) party_ids: [Field; T + 1], - /// Message polynomial m(x) (public witness) + /// Claimed output plaintext polynomial. + /// (public witness) message: Polynomial, - /// Global u polynomial (secret witness) + /// Global decryption value lifted from CRT representation. + /// Verified against u^{(l)} via CRT reconstruction check. + /// (secret witness) u_global: Polynomial, - /// CRT quotient polynomials (secret witnesses) + /// Quotient polynomials witnessing the CRT lift: + /// u^{(l)} + crt_quotients[l] * q_l == u_global for each basis l. + /// (secret witnesses) crt_quotients: [Polynomial; L], } @@ -111,7 +119,14 @@ impl= Q/2, the value is treated + /// as negative, flipping the sign of the -Q^{-1} multiplication. Only non-zero + /// message coefficients are checked, zero coefficients are unconstrained. fn verify_decoding(self) { // Compute Q as product of all CRT moduli let mut q_modulus = 1 as Field; @@ -138,16 +153,15 @@ impl q_half_bn; let computed_message = if needs_centering { - // When (t*u) mod Q >= Q/2: treat as negative in centered form - // Centered value is conceptually negative: (t_times_u_mod_q - Q) - // -Q^{-1} * (negative) = positive result + // (t*u) mod Q >= Q/2: centered value is negative + // -Q^{-1} * (Q - (t*u) mod Q) mod t let centered_positive = q_bn - t_times_u_bn_q; let centered_positive_mod_t = centered_positive.umod(t_bn); let centered_field = to_field(centered_positive_mod_t); m.mul_mod(self.configs.q_inverse_mod_t, centered_field) } else { - // When (t*u) mod Q < Q/2: stays positive in centered form - // -Q^{-1} * (positive) = negative result = t - result + // (t*u) mod Q < Q/2: centered value is positive + // t - (-Q^{-1} * (t*u) mod Q mod t) let t_times_u_bn_t = t_times_u_bn_q.umod(t_bn); let t_times_u_field = to_field(t_times_u_bn_t); let product = m.mul_mod(self.configs.q_inverse_mod_t, t_times_u_field); @@ -173,7 +187,7 @@ pub fn compute_all_lagrange_coeffs( ) -> [[Field; T + 1]; L] { let mut lagrange_coeffs = [[0 as Field; T + 1]; L]; - // Step 1: Cache |x_i - x_j| factors for all party pairs + // Cache pairwise differences: diffs[i][j] = |x_j - x_i| for i < j let mut diffs = [[0 as Field; T + 1]; T + 1]; for i in 0..(T + 1) { for j in (i + 1)..(T + 1) { @@ -183,26 +197,25 @@ pub fn compute_all_lagrange_coeffs( } } - // Step 2: Determine signs (same for all parties within a basis) + // Numerator sign: PROD_{j=0..T} (-x_j) contributes (-1)^{T+1} overall; + // negative when T is odd let numerator_sign_negative = (T % 2) == 1; - // Step 3: For each CRT basis, compute Lagrange coefficients for basis_idx in 0..L { let q_l = qis[basis_idx]; let m = ModU128::new(q_l); - // Compute product of all party IDs: PRODUCT(j=0..T) x_j mod q_l + // Compute PROD(j=0..T) x_j mod q_l once; divide by x_i per party let mut product_x = 1 as Field; for j in 0..(T + 1) { product_x = m.mul_mod(product_x, party_ids[j]); } - // For each party i, compute L_i(0) mod q_l for party_idx in 0..(T + 1) { - // Numerator (absolute value): PRODUCT(j!=party_idx) x_j + // |numerator| = PROD_{j!=i} x_j = product_x / x_i let numerator_abs = m.div_mod(product_x, party_ids[party_idx]); - // Denominator (absolute value): PRODUCT(j!=party_idx) |x_party_idx - x_j| + // |denominator| = PROD_{j!=i} |x_i - x_j| let mut denominator_abs = 1 as Field; for j in 0..(T + 1) { if j != party_idx { @@ -210,14 +223,13 @@ pub fn compute_all_lagrange_coeffs( } } - // Determine denominator sign + // Denominator sign: negative when the number of j > party_idx is odd let num_greater = T - party_idx; let denom_sign_negative = (num_greater % 2) == 1; - // Compute unsigned result: |numerator| / |denominator| mod q_l let result_abs = m.div_mod(numerator_abs, denominator_abs); - // Apply combined sign + // XOR signs: negate if exactly one of numerator/denominator is negative let should_negate = numerator_sign_negative != denom_sign_negative; let result = if should_negate { m.reduce_mod(q_l - result_abs) @@ -232,7 +244,13 @@ pub fn compute_all_lagrange_coeffs( lagrange_coeffs } -/// Computes u^{(l)} for each CRT basis via Lagrange interpolation +/// Computes u^{(l)} for each CRT basis via Lagrange interpolation. +/// +/// For each basis l and each coefficient position k: +/// u^{(l)}[k] = SUM_{i=0..T} d_i^{(l)}[k] * L_i(0) mod q_l +/// +/// This reconstructs the threshold decryption value at zero for each CRT basis +/// independently, coefficient by coefficient. pub fn compute_crt_components( qis: [Field; L], decryption_shares: [[Polynomial; L]; T + 1], @@ -268,7 +286,13 @@ pub fn compute_crt_components( qis: [Field; L], u_global: Polynomial, From f4594b90e2d5124b70a780bee97466561136a634 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Feb 2026 17:55:04 +0100 Subject: [PATCH 09/31] fix docs --- circuits/bin/dkg/pk/README.md | 8 ++++---- circuits/bin/dkg/share_decryption/README.md | 8 ++++---- circuits/bin/dkg/share_encryption/README.md | 4 ++-- circuits/bin/threshold/share_decryption/README.md | 13 +++++++------ 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/circuits/bin/dkg/pk/README.md b/circuits/bin/dkg/pk/README.md index ae7572d95a..de2ea81b7d 100644 --- a/circuits/bin/dkg/pk/README.md +++ b/circuits/bin/dkg/pk/README.md @@ -18,8 +18,8 @@ flowchart TD end %% Output to C3a and C3b - C0 -->|"commit(pk_bfv)"| Output1["→ C3a
share-encryption-sk"] - C0 -->|"commit(pk_bfv)"| Output2["→ C3b
share-encryption-e-sm"] + C0 -->|"commit(pk_dkg)"| Output1["→ C3a
share-encryption-sk"] + C0 -->|"commit(pk_dkg)"| Output2["→ C3b
share-encryption-e-sm"] style Focus fill:#E8A87C,stroke:#C97A4A,stroke-width:3px style Input0 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 @@ -34,8 +34,8 @@ flowchart TD - **Phase**: P1 (DKG). - **Runs**: N_PARTIES = 1 x Ciphernode (at the start of key generation). - **Requires**: [`config`](../../config) circuit from P0 (Configs Verification). -- **Output(s)**: `commit(pk_bfv)` consumed by C3a / C3b +- **Output(s)**: `commit(pk_dkg)` consumed by C3a / C3b ([`dkg/share_encryption`](../share_encryption)) -- **Data Flow**: `P0 (configs) → C0 → commit(pk_bfv) → C3a, C3b` +- **Data Flow**: `P0 (configs) → C0 → commit(pk_dkg) → C3a, C3b` - **Commitment Function**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - `compute_dkg_pk_commitment()` diff --git a/circuits/bin/dkg/share_decryption/README.md b/circuits/bin/dkg/share_decryption/README.md index fbb637d35d..e1265ddf61 100644 --- a/circuits/bin/dkg/share_decryption/README.md +++ b/circuits/bin/dkg/share_decryption/README.md @@ -12,8 +12,8 @@ _C2b_. The verification logic and all input types are identical — only the sou ```mermaid flowchart TD - Input2a["C2a
share-computation-sk"] -.->|"commit(sk_share[i][j])"| C4 - Input2b["C2b
share-computation-e-sm"] -.->|"commit(e_sm_share[i][j])"| C4 + Input2a["C2a
share-computation-sk"] -.->|"commit(sk_share)"| C4 + Input2b["C2b
share-computation-e-sm"] -.->|"commit(e_sm_share)"| C4 subgraph Focus["C4a & C4b"] C4["Verify decrypted shares & aggregate"] @@ -38,9 +38,9 @@ flowchart TD - **Phase**: P1 (DKG). - **Runs**: (N_PARTIES - 1) × Ciphernode per variant (once per recipient per share type). - **Requires**: - - C4a: `commit(sk_share[party_idx][mod_idx])` from C2a + - C4a: `commit(sk_share)` from C2a ([`dkg/sk_share_computation`](../sk_share_computation)) - - C4b: `commit(e_sm_share[party_idx][mod_idx])` from C2b + - C4b: `commit(e_sm_share)` from C2b ([`dkg/e_sm_share_computation`](../e_sm_share_computation)) - **Output(s)**: - C4a: `commit(agg_sk)` → C6 ([`threshold/share_decryption`](../../threshold/share_decryption)) diff --git a/circuits/bin/dkg/share_encryption/README.md b/circuits/bin/dkg/share_encryption/README.md index c87e37a262..03bac019da 100644 --- a/circuits/bin/dkg/share_encryption/README.md +++ b/circuits/bin/dkg/share_encryption/README.md @@ -12,7 +12,7 @@ value of `expected_message_commitment` differs between the two instantiations. ```mermaid flowchart TD - Input0["C0
pk"] -.->|"commit(pk_bfv)"| C3 + Input0["C0
pk"] -.->|"commit(pk_dkg)"| C3 Input2a["C2a
share-computation-sk"] -.->|"commit(sk_share[i][j])"| C3 Input2b["C2b
share-computation-e-sm"] -.->|"commit(e_sm_share[i][j])"| C3 @@ -42,7 +42,7 @@ flowchart TD - **Phase**: P1 (DKG). - **Runs**: (N_PARTIES - 1) × Ciphernode per variant (once per recipient per share type). - **Requires**: - - `commit(pk_bfv)` from C0 ([`dkg/pk`](../pk)) + - `commit(pk_dkg)` from C0 ([`dkg/pk`](../pk)) - C3a: `commit(sk_share[party_idx][mod_idx])` from C2a ([`dkg/sk_share_computation`](../sk_share_computation)) - C3b: `commit(e_sm_share[party_idx][mod_idx])` from C2b diff --git a/circuits/bin/threshold/share_decryption/README.md b/circuits/bin/threshold/share_decryption/README.md index 48df342975..f0be331c88 100644 --- a/circuits/bin/threshold/share_decryption/README.md +++ b/circuits/bin/threshold/share_decryption/README.md @@ -14,7 +14,7 @@ flowchart TD C6["Verify sk & e_sm commitments,
verify decryption share computation
"] end - C6 -->|"d[l] (public)"| Output1["→ C7
dec-result-trbfv"] + C6 -->|"d[l] (public)"| Output1["→ C7
decrypted-shares-agg"] style Focus fill:#E06666,stroke:#C00000,stroke-width:3px style Input1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 @@ -32,8 +32,8 @@ flowchart TD **Requires:** -- `commit(agg_sk)` from C4a (`threshold/dec-bfv-sk`) -- `commit(agg_e_sm)` from C4b (`threshold/dec-bfv-e-sm`) +- `commit(agg_sk)` from C4a ([`dkg/share_decryption`](../../dkg/share_decryption)) +- `commit(agg_e_sm)` from C4b ([`dkg/share_decryption`](../../dkg/share_decryption)) - `(ct0, ct1)` — homomorphic result ciphertext from P3 **Output(s):** @@ -47,6 +47,7 @@ flowchart TD **Related Circuits:** -- C4a `threshold/dec-bfv-sk` -- C4b `threshold/dec-bfv-e-sm` -- C7 `threshold/dec-result-trbfv` +- C4a [`dkg/share_decryption`](../../dkg/share_decryption) +- C4b [`dkg/share_decryption`](../../dkg/share_decryption) +- C7 [`threshold/decrypted_shares_aggregation_bn`](../decrypted_shares_aggregation_bn) / + [`threshold/decrypted_shares_aggregation_mod`](../decrypted_shares_aggregation_mod) From 3f7c4459e4e9ee9785eb82495246d7ace3c51061 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Feb 2026 18:07:34 +0100 Subject: [PATCH 10/31] fix code comments --- circuits/lib/src/core/dkg/pk.nr | 2 +- circuits/lib/src/core/dkg/share_decryption.nr | 6 +++++- circuits/lib/src/core/threshold/pk_generation.nr | 8 ++++---- circuits/lib/src/core/threshold/share_decryption.nr | 3 ++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/circuits/lib/src/core/dkg/pk.nr b/circuits/lib/src/core/dkg/pk.nr index 0ccd88e0f9..83fdd3a6dd 100644 --- a/circuits/lib/src/core/dkg/pk.nr +++ b/circuits/lib/src/core/dkg/pk.nr @@ -14,7 +14,7 @@ use crate::math::polynomial::Polynomial; /// the Distributed Key Generation (DKG) phase. /// /// Outputs: -/// - commit(pk0, pk1) -> C3a / C3b (share encryption) +/// - commit(pk_dkg) -> C3a / C3b (share encryption) pub struct Pk { /// BFV public key first component for each CRT modulus. /// pk0[i] is a degree N-1 polynomial for modulus q_i. diff --git a/circuits/lib/src/core/dkg/share_decryption.nr b/circuits/lib/src/core/dkg/share_decryption.nr index 59238bd992..4fc87a0d4c 100644 --- a/circuits/lib/src/core/dkg/share_decryption.nr +++ b/circuits/lib/src/core/dkg/share_decryption.nr @@ -68,9 +68,13 @@ impl ShareDecryption [Polynomial; L] { diff --git a/circuits/lib/src/core/threshold/pk_generation.nr b/circuits/lib/src/core/threshold/pk_generation.nr index 565804acb6..126f27b6fe 100644 --- a/circuits/lib/src/core/threshold/pk_generation.nr +++ b/circuits/lib/src/core/threshold/pk_generation.nr @@ -52,10 +52,10 @@ impl Configs { /// 2. Correct public key generation: pk0_i = -a_i * sk + eek + r2_i * (X^N + 1) + r1_i * q_i /// (pk1 is a_i from the hardcoded CRS `a`, not a separate witness) /// -/// Outputs: +/// Outputs (in return order): /// - commit(sk) -> C2a (secret key share verification) -/// - commit(e_sm) -> C2b (smudging noise share verification) /// - commit(pk_trbfv) -> C5 (public key aggregation) +/// - commit(e_sm) -> C2b (smudging noise share verification) pub struct PkGeneration { /// Cryptographic parameters including bounds, moduli, and constants. configs: Configs, @@ -126,10 +126,10 @@ impl C2a (secret key share verification) - /// - commit(e_sm) -> C2b (smudging noise share verification) /// - commit(pk_trbfv) -> C5 (public key aggregation) + /// - commit(e_sm) -> C2b (smudging noise share verification) pub fn execute(self) -> (Field, Field, Field) { // 1. Perform range checks on all secret witness values self.perform_range_checks(); diff --git a/circuits/lib/src/core/threshold/share_decryption.nr b/circuits/lib/src/core/threshold/share_decryption.nr index bf4f96c35a..dc8c80de89 100644 --- a/circuits/lib/src/core/threshold/share_decryption.nr +++ b/circuits/lib/src/core/threshold/share_decryption.nr @@ -53,7 +53,8 @@ impl Configs { /// /// Outputs: /// -/// d[l] -> public decryption share per CRT basis, consumed by C7 (dec-result-trbfv). +/// d[l] -> public decryption share per CRT basis, consumed by C7 +/// (decrypted_shares_aggregation_bn or decrypted_shares_aggregation_mod). pub struct ShareDecryption { /// Verifies: /// 1. Commitment to sk matches expected (from DKG decryption circuit) From 6e2f87a32debf6b8931b30337fd5a1a7797aafd2 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Feb 2026 18:21:55 +0100 Subject: [PATCH 11/31] remove unused BIT_NOISE from c7 --- .../decrypted_shares_aggregation/src/main.nr | 9 +- .../lib/src/configs/insecure/threshold.nr | 3 - circuits/lib/src/configs/secure/threshold.nr | 3 - .../threshold/decrypted_shares_aggregation.nr | 162 +++++++++++++++++- .../decrypted_shares_aggregation/codegen.rs | 20 --- .../computation.rs | 33 +--- 6 files changed, 160 insertions(+), 70 deletions(-) diff --git a/circuits/bin/threshold/decrypted_shares_aggregation/src/main.nr b/circuits/bin/threshold/decrypted_shares_aggregation/src/main.nr index f77cb65ff8..18ad4c423e 100644 --- a/circuits/bin/threshold/decrypted_shares_aggregation/src/main.nr +++ b/circuits/bin/threshold/decrypted_shares_aggregation/src/main.nr @@ -5,11 +5,8 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use lib::configs::default::{MAX_MSG_NON_ZERO_COEFFS, T}; -use lib::configs::default::threshold::{ - DECRYPTED_SHARES_AGGREGATION_BIT_D, DECRYPTED_SHARES_AGGREGATION_BIT_NOISE, - DECRYPTED_SHARES_AGGREGATION_CONFIGS, L, -}; -use lib::core::threshold::decrypted_shares_aggregation::DecryptedSharesAggregation; +use lib::configs::default::threshold::{DECRYPTED_SHARES_AGGREGATION_CONFIGS, L}; +use lib::core::threshold::decrypted_shares_aggregation::DecryptedSharesAggregationBigNum; use lib::math::polynomial::Polynomial; fn main( @@ -20,7 +17,7 @@ fn main( u_global: Polynomial, crt_quotients: [Polynomial; L], ) { - let decrypted_shares_aggregation: DecryptedSharesAggregation = DecryptedSharesAggregation::new( + let decrypted_shares_aggregation: DecryptedSharesAggregationBigNum = DecryptedSharesAggregationBigNum::new( DECRYPTED_SHARES_AGGREGATION_CONFIGS, expected_d_commitments, decryption_shares, diff --git a/circuits/lib/src/configs/insecure/threshold.nr b/circuits/lib/src/configs/insecure/threshold.nr index 67a646f9ad..332c1ee75d 100644 --- a/circuits/lib/src/configs/insecure/threshold.nr +++ b/circuits/lib/src/configs/insecure/threshold.nr @@ -1182,8 +1182,5 @@ decrypted_shares_aggregation (CIRCUIT 7) ------------------------------------- ************************************/ -pub global DECRYPTED_SHARES_AGGREGATION_BIT_NOISE: u32 = 65; -pub global DECRYPTED_SHARES_AGGREGATION_BIT_D: u32 = 35; - pub global DECRYPTED_SHARES_AGGREGATION_CONFIGS: DecryptedSharesAggregationConfigs = DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T); diff --git a/circuits/lib/src/configs/secure/threshold.nr b/circuits/lib/src/configs/secure/threshold.nr index 9e16c89d0d..fd43cce95d 100644 --- a/circuits/lib/src/configs/secure/threshold.nr +++ b/circuits/lib/src/configs/secure/threshold.nr @@ -32932,8 +32932,5 @@ decrypted_shares_aggregation (CIRCUIT 7) ------------------------------------- ************************************/ -pub global DECRYPTED_SHARES_AGGREGATION_BIT_NOISE: u32 = 200; -pub global DECRYPTED_SHARES_AGGREGATION_BIT_D: u32 = 52; - pub global DECRYPTED_SHARES_AGGREGATION_CONFIGS: DecryptedSharesAggregationConfigs = DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T); diff --git a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr index 3c45c5cb1e..5edfe87ddc 100644 --- a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr +++ b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr @@ -31,13 +31,14 @@ impl Configs { /// Decrypted Shares Aggregation Circuit (Circuit 7). /// Uses BigNum for Q values (works for both insecure and secure parameter sets). /// -/// Verifies: -/// 0. Each party's decryption share matches the corresponding C6 public `d` commitment -/// 1. Lagrange interpolation to compute u^{(l)} for each CRT basis -/// 2. CRT reconstruction: u^{(l)} + r^{(l)} * q_l = u_global -/// 3. Decoding verification: message = -Q^{-1} * (t * u_global)_Q mod t -pub struct DecryptedSharesAggregation { - /// Circuit parameters including crypto constants +/// Combines T+1 public decryption shares (from C6) into the final plaintext via +/// Lagrange interpolation, CRT reconstruction, and BFV decoding. This is the last +/// circuit in the Enclave protocol. +/// +/// Use this variant when Q (the product of all CRT moduli) exceeds 128 bits, +/// as is the case for cryptographically secure parameter sets. For smaller parameter +/// sets where Q fits within 128 bits, use DecryptedSharesAggregationModular instead. +pub struct DecryptedSharesAggregationBigNum { configs: Configs, /// Public `d` commitments from circuit 6 (one per reconstructing party), same order as `decryption_shares` @@ -67,7 +68,43 @@ pub struct DecryptedSharesAggregation; L], } -impl DecryptedSharesAggregation { +/// Decryption Share Aggregation Circuit - Modular variant (C7). +/// +/// Identical verification logic to DecryptedSharesAggregationBigNum, but uses +/// ModU128 arithmetic for the decoding step instead of BigNum. More efficient +/// when Q (the product of all CRT moduli) fits within 128 bits, e.g. for smaller +/// or non-production parameter sets. For cryptographically secure parameter sets +/// where Q exceeds 128 bits, use DecryptedSharesAggregationBigNum instead. +pub struct DecryptedSharesAggregationModular { + configs: Configs, + + /// Public decryption shares from T+1 ciphernodes, as produced by C6. + /// decryption_shares[party_idx][basis_idx] is party i's share for CRT basis l. + /// (public witnesses) + decryption_shares: [[Polynomial; L]; T + 1], + + /// Shamir x-coordinates for the T+1 participating parties. + /// Must be in strictly increasing order, the sign computation in Lagrange + /// interpolation depends on this ordering. + /// (public witnesses) + party_ids: [Field; T + 1], + + /// Claimed output plaintext polynomial. + /// (public witness) + message: Polynomial, + + /// Global decryption value lifted from CRT representation. + /// Verified against u^{(l)} via CRT reconstruction check. + /// (secret witness) + u_global: Polynomial, + + /// Quotient polynomials witnessing the CRT lift: + /// u^{(l)} + crt_quotients[l] * q_l == u_global for each basis l. + /// (secret witnesses) + crt_quotients: [Polynomial; L], +} + +impl DecryptedSharesAggregationBigNum { pub fn new( configs: Configs, expected_d_commitments: [Field; T + 1], @@ -180,7 +217,114 @@ impl DecryptedSharesAggregationModular { + pub fn new( + configs: Configs, + decryption_shares: [[Polynomial; L]; T + 1], + party_ids: [Field; T + 1], + message: Polynomial, + u_global: Polynomial, + crt_quotients: [Polynomial; L], + ) -> Self { + DecryptedSharesAggregationModular { + configs, + decryption_shares, + party_ids, + message, + u_global, + crt_quotients, + } + } + + /// Executes the decryption share aggregation circuit. + /// + /// Runs all four verification steps in order: Lagrange coefficient computation, + /// interpolation, CRT reconstruction, and decoding. + pub fn execute(self) { + // Step 1: Compute Lagrange coefficients in-circuit + let lagrange_coeffs = compute_all_lagrange_coeffs::(self.configs.qis, self.party_ids); + + // Step 2: Compute u^{(l)} for each CRT basis via Lagrange interpolation + let u_crts = compute_crt_components::( + self.configs.qis, + self.decryption_shares, + lagrange_coeffs, + ); + + // Step 3: Verify CRT reconstruction: u^{(l)} + r^{(l)} * q_l = u_global + verify_crt_reconstruction::( + self.configs.qis, + self.u_global, + self.crt_quotients, + u_crts, + ); + // Step 4: Verify decoding + self.verify_decoding(); + } + + /// Verifies the BFV decoding step using ModU128 arithmetic. + /// + /// For each coefficient, computes message = -Q^{-1} * (t * u_global)_Q mod t + /// using u128 modular arithmetic. Only valid when Q fits within 128 bits. + /// + /// Centered representation: if (t * u_global) mod Q >= Q/2, the value is treated + /// as negative, flipping the sign of the -Q^{-1} multiplication. Only non-zero + /// message coefficients are checked, zero coefficients are unconstrained. + fn verify_decoding(self) { + let t: Field = self.configs.plaintext_modulus; + // Compute Q as product of all CRT moduli + let mut q_modulus = 1; + for l in 0..L { + q_modulus *= self.configs.qis[l]; + } + + // For centered arithmetic + let q_half = q_modulus as u128 / 2; + + for coeff_idx in 0..MAX_MSG_NON_ZERO_COEFFS { + // Compute (t * u_global) mod Q using ModU128 + let q_mod = ModU128::new(q_modulus); + let t_mod = ModU128::new(t); + + // Compute (t * u_global) mod Q using modular arithmetic functions + let t_times_u_mod_q = q_mod.mul_mod(t, self.u_global.coefficients[coeff_idx]); + let needs_centering = (t_times_u_mod_q as u128) > q_half; + + let computed_message = if needs_centering { + // (t*u) mod Q >= Q/2: centered value is negative + // -Q^{-1} * (Q - (t*u) mod Q) mod t + let centered_positive = q_modulus - t_times_u_mod_q; + let centered_positive_mod_t = t_mod.reduce_mod(centered_positive); + + t_mod.mul_mod(self.configs.q_inverse_mod_t, centered_positive_mod_t) + } else { + // (t*u) mod Q < Q/2: centered value is positive + // t - (-Q^{-1} * (t*u) mod Q mod t) + let t_times_u_mod_t = t_mod.reduce_mod(t_times_u_mod_q); + let product = t_mod.mul_mod(self.configs.q_inverse_mod_t, t_times_u_mod_t); + if product == 0 { + 0 + } else { + t - product + } + }; + + // Verify: only check non-zero coefficients + if self.message.coefficients[coeff_idx] != 0 { + assert(computed_message == self.message.coefficients[coeff_idx]); + } + } + } +} + +/// Computes all Lagrange basis coefficients L_i(0) for each CRT basis. +/// +/// For each CRT basis l and each party i, computes: +/// L_i(0) = PROD_{j!=i} (-x_j) / (x_i - x_j) mod q_l +/// +/// IMPORTANT: party_ids must be in strictly increasing order. The sign of each +/// Lagrange coefficient depends on this ordering, out-of-order IDs will silently +/// produce incorrect signs and produce wrong decryption results. pub fn compute_all_lagrange_coeffs( qis: [Field; L], party_ids: [Field; T + 1], diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs index b689d9862d..cec0af9f87 100644 --- a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs @@ -71,9 +71,6 @@ decrypted_shares_aggregation (CIRCUIT 7) ------------------------------------- ************************************/ -pub global {}_BIT_NOISE: u32 = {}; -pub global {}_BIT_D: u32 = {}; - pub global {}_CONFIGS: DecryptedSharesAggregationConfigs = DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T); "#, @@ -84,10 +81,6 @@ pub global {}_CONFIGS: DecryptedSharesAggregationConfigs = configs.q_mod_t_centered, configs.q_inverse_mod_t, prefix, - configs.bits.noise_bit, - prefix, - configs.bits.d_bit, - prefix, ) } @@ -105,13 +98,6 @@ mod tests { let codegen_configs = generate_configs(preset, &configs); assert!(codegen_configs.contains("decrypted_shares_aggregation")); - assert!(codegen_configs.contains(&format!( - "{}_BIT_NOISE: u32 = {}", - prefix, configs.bits.noise_bit - ))); - assert!( - codegen_configs.contains(&format!("{}_BIT_D: u32 = {}", prefix, configs.bits.d_bit)) - ); assert!(codegen_configs.contains(&format!("{}_CONFIGS:", prefix))); assert!(codegen_configs.contains( "DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T)" @@ -129,12 +115,6 @@ mod tests { let artifacts = circuit.codegen(preset, &input).unwrap(); assert!(!artifacts.toml.is_empty()); - assert!(artifacts - .configs - .contains("DECRYPTED_SHARES_AGGREGATION_BIT_NOISE")); - assert!(artifacts - .configs - .contains("DECRYPTED_SHARES_AGGREGATION_BIT_D")); assert!(artifacts .configs .contains("DECRYPTED_SHARES_AGGREGATION_CONFIGS")); diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs index eb33a6bf7e..cdf7a991e9 100644 --- a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs @@ -11,11 +11,6 @@ //! with [`e3_polynomial::CrtPolynomial::reduce`]; all input coefficients are reduced to //! [0, zkp_modulus) with [`e3_polynomial::reduce`] inside [`Inputs::compute`]. -/// Max message coefficients in the C7 circuit (matches Noir's MAX_MSG_NON_ZERO_COEFFS). -pub const MAX_MSG_NON_ZERO_COEFFS: usize = 100; - -use crate::calculate_bit_width; -use crate::circuits::commitments::compute_threshold_decryption_share_commitment; use crate::compute_q_mod_t; use crate::compute_q_mod_t_centered; use crate::get_zkp_modulus; @@ -66,13 +61,9 @@ pub struct Bounds { pub delta_half: BigUint, } -/// Bit widths used by the circuit (e.g. noise bit for range checks). +/// Bit widths used by the circuit. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Bits { - pub noise_bit: u32, - /// Coefficient bit width for decryption-share commitments (aligned with threshold share_decryption BIT_D). - pub d_bit: u32, -} +pub struct Bits {} /// Circuit config: moduli count, plaintext modulus, q_inverse_mod_t, bits, bounds, and message polynomial length. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -131,19 +122,8 @@ impl Computation for Bits { type Data = Bounds; type Error = CircuitsErrors; - fn compute(preset: Self::Preset, data: &Self::Data) -> Result { - let noise_bit = calculate_bit_width(BigInt::from(data.delta_half.clone())); - let (threshold_params, _) = - build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Other(e.to_string()))?; - let ctx = threshold_params - .ctx_at_level(0) - .map_err(|e| CircuitsErrors::Other(format!("ctx_at_level: {:?}", e)))?; - let mut d_bit = 0u32; - for qi in ctx.moduli_operators() { - let qi_bound = (BigInt::from(qi.modulus()) - 1) / 2; - d_bit = d_bit.max(calculate_bit_width(qi_bound)); - } - Ok(Bits { noise_bit, d_bit }) + fn compute(_: Self::Preset, _: &Self::Data) -> Result { + Ok(Bits {}) } } @@ -385,13 +365,10 @@ mod tests { fn test_bounds_and_bits_consistency() { let preset = BfvPreset::InsecureThreshold512; let bounds = Bounds::compute(preset, &()).unwrap(); - let bits = Bits::compute(preset, &bounds).unwrap(); assert!(!bounds.delta.is_zero()); assert!(!bounds.delta_half.is_zero()); assert!(bounds.delta_half < bounds.delta); - assert!(bits.noise_bit > 0); - assert!(bits.d_bit > 0); } #[test] @@ -433,7 +410,5 @@ mod tests { out.inputs.crt_quotients.limb(0).coefficients().len(), configs.max_msg_non_zero_coeffs ); - assert!(out.bits.noise_bit > 0); - assert!(out.bits.d_bit > 0); } } From 286a0b07d8821bcf79a21c172fcbfdcb99c872cd Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Feb 2026 18:44:36 +0100 Subject: [PATCH 12/31] add readme for lib --- circuits/lib/src/README.md | 173 +++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 circuits/lib/src/README.md diff --git a/circuits/lib/src/README.md b/circuits/lib/src/README.md new file mode 100644 index 0000000000..405c22a137 --- /dev/null +++ b/circuits/lib/src/README.md @@ -0,0 +1,173 @@ +# Enclave ZK Library + +Noir library shared by all circuits in the Enclave protocol. It provides the mathematical +primitives, cryptographic circuit logic, and parameter configurations that the binary circuits +(`circuits/bin/`) compose and instantiate. + +``` +lib/src/ +├── math/ — polynomial arithmetic, hashing, modular arithmetic +├── core/ — circuit logic for DKG and threshold protocols +└── configs/ — cryptographic parameter presets +``` + +```mermaid +graph LR + math["math primitives"] --> core["core circuits logic"] + configs["configs & parameters"] --> core + core --> dkg["dkg (C0 · C2a/b · C3a/b · C4a/b)"] + core --> threshold["threshold (C1 · C5 · P3 · C6 · C7)"] +``` + +## math + +Low-level building blocks used throughout all circuits. + +### polynomial + +Fixed-degree polynomial with `N` coefficients (degree `N-1`), stored in descending order +`[a_{N-1}, ..., a_0]`. Core operations: + +| Method | Description | +| ------------------------------------ | --------------------------------------------------------- | +| `eval(x)` | Evaluate at point `x` via Horner's method | +| `eval_mod(x, m)` | Evaluate with modular reduction to prevent field overflow | +| `add(other)` / `mul_scalar(c)` | Coefficient-wise arithmetic | +| `range_check_2bounds::(lo, hi)` | Constrain coefficients to `[-lo, hi]` | +| `range_check_standard::(bound)` | Constrain coefficients to `[0, bound)` | + +### safe + +Full implementation of the [SAFE](https://hackmd.io/@7dpNYqjKQGeYC7wMlPxHtQ/ByIbpfX9c) sponge +framework on top of Poseidon2. Rate 3, capacity 1. Used for all commitment and challenge +computations, providing domain separation between circuit types. + +### helpers + +Utilities for preparing witness data for hashing: + +| Function | Description | +| ------------------------------------- | ------------------------------------------------------- | +| `flatten(inputs, polys)` | Pack `L` polynomials into field carriers for the sponge | +| `pack(values, ...)` | Pack raw coefficient arrays with deterministic padding | +| `compute_safe(inputs, n_squeeze, ds)` | One-shot absorb → squeeze → finish wrapper | + +### ModU128 + +Fully constrained modular arithmetic over `u128` values, used wherever field arithmetic alone is +insufficient (e.g. Lagrange reconstruction, Reed-Solomon parity checks). + +| Method | Description | +| ------------------------- | ---------------------------------------------- | +| `reduce_mod(v)` | `v mod m` | +| `mul_mod(a, b)` | `a * b mod m` | +| `div_mod(a, b)` | `a * b⁻¹ mod m` — `b` must be coprime with `m` | +| `add(a, b)` / `sub(a, b)` | Modular add / sub with underflow handling | + +Unconstrained oracle helpers live in `modulo/unconstrained_U128.nr` and are called internally. + +### commitments + +Domain separators and typed wrappers over `SafeSponge`. Each circuit family has its own domain +separator constant (`DS_PK`, `DS_SHARE_ENCRYPTION`, `DS_CIPHERTEXT`, …) ensuring that commitments +produced by different circuits are never interchangeable. High-level functions: + +| Function | Used by | +| ----------------------------------------------------------- | --------------------- | +| `compute_dkg_pk_commitment()` | C0 | +| `compute_threshold_pk_commitment()` | C1 | +| `compute_share_computation_sk/e_sm_commitment()` | C1 → C2a/b | +| `compute_share_encryption_commitment_from_shares/message()` | C2 ↔ C3, C4 | +| `compute_aggregated_shares_commitment()` | C4 → C6 | +| `compute_threshold_pk/share_decryption_challenge()` | Fiat-Shamir in C1, C6 | + +## core + +Circuit structs and verification logic. Each struct maps 1-to-1 to a binary circuit in +`circuits/bin/`. Circuits call `execute()` which returns commitments consumed by downstream +circuits. + +### DKG + +Circuits for the Distributed Key Generation phase (P1). + +| File | Circuit | Role | +| ---------------------- | --------- | ------------------------------------------------------------------- | +| `pk.nr` | C0 | Commit to a ciphernode's BFV public key | +| `share_computation.nr` | C2a / C2b | Verify Shamir shares of `sk` / `e_sm` via Reed-Solomon parity check | +| `share_encryption.nr` | C3a / C3b | Verify BFV encryption of each share for its recipient | +| `share_decryption.nr` | C4a / C4b | Verify share decryption and aggregate across honest parties | + +**C2 share matrix layout** — `y[coeff_idx][mod_idx][party_idx]`: + +- `y[i][j][0]` — the secret itself (evaluation at 0), exempt from range checks (validated via C1 + commitment) +- `y[i][j][k]` for `k = 1..N_PARTIES` — shares distributed to each party, range-checked to + `[0, q_j)` + +### Threshold + +Circuits for threshold key generation (P1/P2) and threshold decryption (P4), plus user data +encryption (P3). + +| File | Circuit | Role | +| --------------------------------- | ------- | --------------------------------------------------------------------------- | +| `pk_generation.nr` | C1 | Prove correct BFV threshold key contribution + smudging noise generation | +| `pk_aggregation.nr` | C5 | Verify aggregation of honest parties' public key contributions | +| `user_data_encryption_ct0/ct1.nr` | P3 | Prove correct BFV encryption of user data (GRECO) | +| `share_decryption.nr` | C6 | Prove correct decryption share `d[l] = ct0[l] + ct1[l]·sk[l] + e_sm[l] + …` | +| `decrypted_shares_aggregation.nr` | C7 | Lagrange interpolation + CRT lift + BFV decode to recover plaintext | + +**C7 variants** — two implementations of the final decoding step: + +| Struct | When to use | +| ----------------------------------- | ---------------------------------------------------- | +| `DecryptedSharesAggregationBigNum` | `Q` exceeds 128 bits — production parameter sets | +| `DecryptedSharesAggregationModular` | `Q` fits in 128 bits — smaller / test parameter sets | + +## configs + +Cryptographic parameter presets. All binary circuits import from `configs::default`, which is the +single place to switch between parameter sets. + +``` +configs/ +├── default/mod.nr ← change this to switch preset +├── committee/ +│ └── small.nr N_PARTIES = 5 · T = 2 · H = 5 +├── insecure/ +│ ├── dkg.nr N = 512 · L = 1 · q = 2251799813554177 +│ └── threshold.nr +└── secure/ + ├── dkg.nr production-grade parameters + └── threshold.nr +``` + +### Switching presets + +Edit `configs/default/mod.nr`: + +```rust +// switch to production parameters: +pub use super::secure::dkg; +pub use super::secure::threshold; +``` + +### Parameters + +Each preset file defines bit-width constants and `Configs` struct instances for each circuit, +organised by circuit: + +| Category | Examples | +| ------------------- | ----------------------------------------------------------------------- | +| Polynomial degree | `N`, `L` | +| CRT moduli | `QIS: [Field; L]` | +| Plaintext modulus | `PLAINTEXT_MODULUS`, `Q_MOD_T`, `Q_MOD_T_CENTERED` | +| Bit bounds | `PK_BIT_PK`, `SHARE_COMPUTATION_BIT_SHARE`, `SHARE_ENCRYPTION_BIT_*`, … | +| Quotient bounds | `R1_BOUNDS`, `R2_BOUNDS`, `P1_BOUNDS`, `P2_BOUNDS` | +| Reed-Solomon matrix | `PARITY_MATRIX: [[[Field; N_PARTIES+1]; N_PARTIES-T]; L]` | +| Circuit configs | `SHARE_COMPUTATION_SK_CONFIGS`, `SHARE_ENCRYPTION_CONFIGS`, … | + +The `MAX_MSG_NON_ZERO_COEFFS` is defined in `default/mod.nr` (currently `80`). Controls the maximum +number of non-zero coefficients in the plaintext polynomial accepted by C7. Shared across all +parameter presets. From 9d929e6b33d038a4c069e83e898d27843e398ef5 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Feb 2026 18:49:02 +0100 Subject: [PATCH 13/31] readmes for recursive aggregation --- .../bin/recursive_aggregation/fold/README.md | 47 ++++++++++++++++ .../recursive_aggregation/wrapper/README.md | 54 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 circuits/bin/recursive_aggregation/fold/README.md create mode 100644 circuits/bin/recursive_aggregation/wrapper/README.md diff --git a/circuits/bin/recursive_aggregation/fold/README.md b/circuits/bin/recursive_aggregation/fold/README.md new file mode 100644 index 0000000000..e9244f6ecb --- /dev/null +++ b/circuits/bin/recursive_aggregation/fold/README.md @@ -0,0 +1,47 @@ +# Fold + +Aggregates two non-ZK UltraHonk proofs into a single recursive commitment. + +This sits at the top of the aggregation stack: it takes two wrapper proofs (stripped of ZK +randomness) and combines their public commitments into one field element that a verifier can check +cheaply in place of the original pair. + +## Inputs + +| Name | Visibility | Type | Description | +| ------------------ | ---------- | -------------------------- | ---------------------------------------- | +| `verification_key` | private | `UltraHonkVerificationKey` | Shared VK for both proofs | +| `proofs` | private | `[UltraHonkProof; 2]` | The two non-ZK proofs to aggregate | +| `commitments` | public | `[Field; 2]` | Public commitment output from each proof | +| `key_hash` | private | `Field` | Hash of the verification key | + +## Output + +`pub Field` — aggregated recursive commitment computed over both input commitments. + +## Verification + +1. Verify `proofs[0]` non-ZK against `verification_key` with public input `commitments[0]`. +2. Verify `proofs[1]` non-ZK against `verification_key` with public input `commitments[1]`. +3. Return `compute_recursive_aggregation_commitment([commitments[0], commitments[1]])`. + +Both proofs must share the same verification key. + +## Data Flow + +```mermaid +flowchart LR + W0["wrapper proof 0 (pub: commitment)"] --> F["fold"] + W1["wrapper proof 1 (pub: commitment)"] --> F + F -->|"aggregated commitment"| Out["verifier"] +``` + +## Notes + +- Uses **non-ZK** proof verification (`verify_honk_proof_non_zk`) — the ZK layer is handled inside + the wrapper circuits that feed into this one. +- Hardcoded to aggregate exactly **2** proofs per invocation. + +## Related + +- [../wrapper/](../wrapper/README.md) — produces the wrapper proofs consumed here diff --git a/circuits/bin/recursive_aggregation/wrapper/README.md b/circuits/bin/recursive_aggregation/wrapper/README.md new file mode 100644 index 0000000000..6d684eb0ba --- /dev/null +++ b/circuits/bin/recursive_aggregation/wrapper/README.md @@ -0,0 +1,54 @@ +# Wrapper Circuits + +Each wrapper circuit takes one or more ZK UltraHonk proofs from a base circuit, re-verifies them +in-circuit, and computes a single recursive aggregation commitment over all public inputs. This +converts a ZK proof with many public inputs into a single `Field` that downstream aggregation steps +(fold, or an on-chain verifier) can process cheaply. + +## Common Pattern + +Every wrapper follows the same structure. What varies between circuits: + +- `N_PROOFS` — how many proofs the wrapper verifies in one invocation +- `N_PUBLIC_INPUTS` — how many public field elements each proof exposes, derived from config + parameters resolved at compile time from `lib::configs::default` + +## Circuit Index + +| Circuit | `N_PROOFS` | `N_PUBLIC_INPUTS` | +| ---------------------------------------- | ---------- | --------------------------------------------------------------------------- | +| `dkg/pk` | 1 | `1` | +| `dkg/share_computation` | 2 | `(L_THRESHOLD × N_PARTIES) + 1` | +| `dkg/share_encryption` | 2 | `(2 × L × N) + 2` | +| `dkg/share_decryption` | 2 | `(H × L_THRESHOLD) + 1` | +| `threshold/pk_generation` | 1 | `(L × N) + 3` | +| `threshold/pk_aggregation` | 1 | `H + 1` | +| `threshold/share_decryption` | 1 | `2 + (3 × L × N)` | +| `threshold/decrypted_shares_aggregation` | 1 | `((T+1) × L × MAX_MSG_NON_ZERO_COEFFS) + (T + 1 + MAX_MSG_NON_ZERO_COEFFS)` | +| `threshold/user_data_encryption` | — | — (see below) | + +## Special Case: `threshold/user_data_encryption` + +This wrapper departs from the standard pattern in three ways: + +1. **Non-ZK verification** — uses `verify_honk_proof_non_zk`; the ct0 and ct1 base circuits are + verified without the ZK layer, unlike all other wrappers. +2. **Cross-proof constraint** — asserts that the `u_commitment` is identical across the ct0 and ct1 + proofs, binding the two ciphertexts to the same encryption randomness. +3. **Tuple output** — returns `(Field, Field, Field)` instead of a single commitment, carrying the + ciphertext commitment, the public-key commitment, and the aggregation commitment separately. + +## Data Flow + +```mermaid +flowchart LR + Base["base circuit proof (ZK UltraHonk)"] --> W["wrapper"] + W -->|"pub: commitment (Field)"| F["fold"] + F -->|"aggregated commitment"| Out["verifier"] +``` + +## Related + +- [../fold/](../fold/README.md) — aggregates two wrapper outputs into a single commitment +- [../../../../lib/src/math/commitments.nr](../../../../lib/src/math/commitments.nr) — + `compute_recursive_aggregation_commitment` implementation From e95e022c63d0d7bbc1b7eade632fc041212981e4 Mon Sep 17 00:00:00 2001 From: Giacomo Date: Fri, 27 Feb 2026 18:58:32 +0100 Subject: [PATCH 14/31] Update circuits/bin/config/README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- circuits/bin/config/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuits/bin/config/README.md b/circuits/bin/config/README.md index fefae2fcad..44a39d36e1 100644 --- a/circuits/bin/config/README.md +++ b/circuits/bin/config/README.md @@ -23,7 +23,7 @@ flowchart TD linkStyle 0 stroke:#808080,stroke-width:2px ``` -### Metadata +## Metadata - **Phase**: P0 (Configuration Verification). - **Runs**: 1 x Ciphernode (one-time program verification). From c0dde40ab7e73c77e886af8b08ad4a67f4d0607b Mon Sep 17 00:00:00 2001 From: Giacomo Date: Fri, 27 Feb 2026 18:58:44 +0100 Subject: [PATCH 15/31] Update circuits/bin/dkg/share_decryption/README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- circuits/bin/dkg/share_decryption/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuits/bin/dkg/share_decryption/README.md b/circuits/bin/dkg/share_decryption/README.md index e1265ddf61..d4bc5be7e0 100644 --- a/circuits/bin/dkg/share_decryption/README.md +++ b/circuits/bin/dkg/share_decryption/README.md @@ -33,7 +33,7 @@ flowchart TD linkStyle 3 stroke:#808080,stroke-width:2px ``` -### Metadata +## Metadata - **Phase**: P1 (DKG). - **Runs**: (N_PARTIES - 1) × Ciphernode per variant (once per recipient per share type). From 506e47c1c3c721aaf8822ba6a29a540ffb91a18e Mon Sep 17 00:00:00 2001 From: Giacomo Date: Fri, 27 Feb 2026 18:59:07 +0100 Subject: [PATCH 16/31] Update circuits/bin/dkg/share_encryption/README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- circuits/bin/dkg/share_encryption/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuits/bin/dkg/share_encryption/README.md b/circuits/bin/dkg/share_encryption/README.md index 03bac019da..68a673e74a 100644 --- a/circuits/bin/dkg/share_encryption/README.md +++ b/circuits/bin/dkg/share_encryption/README.md @@ -37,7 +37,7 @@ flowchart TD linkStyle 4 stroke:#808080,stroke-width:2px ``` -### Metadata +## Metadata - **Phase**: P1 (DKG). - **Runs**: (N_PARTIES - 1) × Ciphernode per variant (once per recipient per share type). From 19598f09f4e9b6c7135db22595fc525b0d3a0719 Mon Sep 17 00:00:00 2001 From: Giacomo Date: Fri, 27 Feb 2026 18:59:54 +0100 Subject: [PATCH 17/31] Update circuits/bin/threshold/pk_aggregation/README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- circuits/bin/threshold/pk_aggregation/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuits/bin/threshold/pk_aggregation/README.md b/circuits/bin/threshold/pk_aggregation/README.md index d458d035b5..136cff6a0b 100644 --- a/circuits/bin/threshold/pk_aggregation/README.md +++ b/circuits/bin/threshold/pk_aggregation/README.md @@ -23,7 +23,7 @@ flowchart TD linkStyle 1 stroke:#808080,stroke-width:2px ``` -### Metadata +## Metadata - **Phase**: P2 (Aggregation). - **Runs**: 1 × Aggregator (once after all honest parties' pk shares are collected). From cdd936d69e26e22d1eea288ec9c39ad55bcfa886 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Feb 2026 19:07:10 +0100 Subject: [PATCH 18/31] fixes --- circuits/bin/recursive_aggregation/wrapper/README.md | 2 +- circuits/lib/src/README.md | 4 ++-- circuits/lib/src/core/dkg/share_encryption.nr | 11 ++++++----- circuits/lib/src/core/threshold/pk_generation.nr | 6 +++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/circuits/bin/recursive_aggregation/wrapper/README.md b/circuits/bin/recursive_aggregation/wrapper/README.md index 6d684eb0ba..403f4190aa 100644 --- a/circuits/bin/recursive_aggregation/wrapper/README.md +++ b/circuits/bin/recursive_aggregation/wrapper/README.md @@ -25,7 +25,7 @@ Every wrapper follows the same structure. What varies between circuits: | `threshold/pk_aggregation` | 1 | `H + 1` | | `threshold/share_decryption` | 1 | `2 + (3 × L × N)` | | `threshold/decrypted_shares_aggregation` | 1 | `((T+1) × L × MAX_MSG_NON_ZERO_COEFFS) + (T + 1 + MAX_MSG_NON_ZERO_COEFFS)` | -| `threshold/user_data_encryption` | — | — (see below) | +| `threshold/user_data_encryption` | 2 | 4 (ct0) · 3 (ct1) — asymmetric, see below | ## Special Case: `threshold/user_data_encryption` diff --git a/circuits/lib/src/README.md b/circuits/lib/src/README.md index 405c22a137..c93a6eb740 100644 --- a/circuits/lib/src/README.md +++ b/circuits/lib/src/README.md @@ -4,7 +4,7 @@ Noir library shared by all circuits in the Enclave protocol. It provides the mat primitives, cryptographic circuit logic, and parameter configurations that the binary circuits (`circuits/bin/`) compose and instantiate. -``` +```text lib/src/ ├── math/ — polynomial arithmetic, hashing, modular arithmetic ├── core/ — circuit logic for DKG and threshold protocols @@ -130,7 +130,7 @@ encryption (P3). Cryptographic parameter presets. All binary circuits import from `configs::default`, which is the single place to switch between parameter sets. -``` +```text configs/ ├── default/mod.nr ← change this to switch preset ├── committee/ diff --git a/circuits/lib/src/core/dkg/share_encryption.nr b/circuits/lib/src/core/dkg/share_encryption.nr index 7837682e84..da51b94a25 100644 --- a/circuits/lib/src/core/dkg/share_encryption.nr +++ b/circuits/lib/src/core/dkg/share_encryption.nr @@ -26,13 +26,13 @@ pub struct Configs { pub pk_bounds: [Field; L], /// Coefficient bound for the global error polynomial e0 pub e0_bound: Field, - /// Coefficient bound for the ternary randomness polynomial u (= 1) + /// Coefficient bound for the error polynomial e1 pub e1_bound: Field, /// Coefficient bound for the ternary randomness polynomial u (= 1) pub u_bound: Field, - /// Lower bounds for r1 quotient polynomials per CRT modulus + /// Lower bounds for r1 modulus switching quotient coefficients per CRT modulus pub r1_low_bounds: [Field; L], - /// Coefficient bounds for r2 cyclotomic reduction quotients per CRT modulus + /// Upper bounds for r1 modulus switching quotient coefficients per CRT modulus pub r1_up_bounds: [Field; L], /// Coefficient bounds for r2 cyclotomic reduction quotients per CRT modulus pub r2_bounds: [Field; L], @@ -356,8 +356,9 @@ impl) -> Vec { let inputs = self.payload(k1); diff --git a/circuits/lib/src/core/threshold/pk_generation.nr b/circuits/lib/src/core/threshold/pk_generation.nr index 126f27b6fe..b0ebec1f88 100644 --- a/circuits/lib/src/core/threshold/pk_generation.nr +++ b/circuits/lib/src/core/threshold/pk_generation.nr @@ -102,8 +102,8 @@ impl Vec { let mut inputs = Vec::new(); @@ -140,7 +140,7 @@ impl(self.e_sm); let pk_commitment = compute_threshold_pk_commitment::(self.pk0, self.a); - // 3. Generate Fiat-Shamir challenges (one per CRT modulus) + // 3. Generate a single Fiat-Shamir challenge point (gamma) let gamma = self.generate_challenge(sk_commitment, pk_commitment); self.verify_evaluations(gamma); From dd3bcd21bbafe8e5076dad1fd2bd66297dc4ebd9 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Feb 2026 19:10:03 +0100 Subject: [PATCH 19/31] more coderabbit fixes --- .../lib/src/core/threshold/decrypted_shares_aggregation.nr | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr index 5edfe87ddc..408a5270de 100644 --- a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr +++ b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr @@ -17,8 +17,10 @@ pub struct Configs { pub qis: [Field; L], /// Plaintext modulus (typically denoted as `t`) pub plaintext_modulus: Field, - /// Precomputed value: -Q^{-1} mod t, where Q = q_0 * q_1 * ... * q_{L-1}. - /// Used in the final decoding step to recover the message from u_global. + /// Precomputed value: Q^{-1} mod t (the positive modular inverse of Q modulo t), + /// where Q = q_0 * q_1 * ... * q_{L-1}. The negation required by the BFV decoding + /// formula (-Q^{-1}) is applied separately in verify_decoding: via `t - product` + /// in the non-centering branch and via `Q - (t*u mod Q)` in the centering branch. pub q_inverse_mod_t: Field, } From 4bc6dde08ec00abe48d5cf132efe31565d0015fa Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Mar 2026 15:40:13 +0100 Subject: [PATCH 20/31] update runs x circuit --- circuits/bin/dkg/pk/README.md | 2 +- circuits/bin/dkg/share_decryption/README.md | 9 ++++----- circuits/bin/dkg/share_encryption/README.md | 7 ++++++- circuits/bin/threshold/pk_generation/README.md | 2 +- circuits/bin/threshold/share_decryption/README.md | 3 ++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/circuits/bin/dkg/pk/README.md b/circuits/bin/dkg/pk/README.md index de2ea81b7d..05c3546a4c 100644 --- a/circuits/bin/dkg/pk/README.md +++ b/circuits/bin/dkg/pk/README.md @@ -32,7 +32,7 @@ flowchart TD ``` - **Phase**: P1 (DKG). -- **Runs**: N_PARTIES = 1 x Ciphernode (at the start of key generation). +- **Runs**: N_PARTIES (once per ciphernode at the start of key generation). - **Requires**: [`config`](../../config) circuit from P0 (Configs Verification). - **Output(s)**: `commit(pk_dkg)` consumed by C3a / C3b ([`dkg/share_encryption`](../share_encryption)) diff --git a/circuits/bin/dkg/share_decryption/README.md b/circuits/bin/dkg/share_decryption/README.md index d4bc5be7e0..6dfbfc4bbc 100644 --- a/circuits/bin/dkg/share_decryption/README.md +++ b/circuits/bin/dkg/share_decryption/README.md @@ -36,12 +36,11 @@ flowchart TD ## Metadata - **Phase**: P1 (DKG). -- **Runs**: (N_PARTIES - 1) × Ciphernode per variant (once per recipient per share type). +- **Runs**: N_PARTIES × Ciphernode x variant (`e_sm` && `sk`) (once per ciphernode; each instance + aggregates all received shares). - **Requires**: - - C4a: `commit(sk_share)` from C2a - ([`dkg/sk_share_computation`](../sk_share_computation)) - - C4b: `commit(e_sm_share)` from C2b - ([`dkg/e_sm_share_computation`](../e_sm_share_computation)) + - C4a: `commit(sk_share)` from C2a ([`dkg/sk_share_computation`](../sk_share_computation)) + - C4b: `commit(e_sm_share)` from C2b ([`dkg/e_sm_share_computation`](../e_sm_share_computation)) - **Output(s)**: - C4a: `commit(agg_sk)` → C6 ([`threshold/share_decryption`](../../threshold/share_decryption)) - C4b: `commit(agg_e_sm)` → C6 ([`threshold/share_decryption`](../../threshold/share_decryption)) diff --git a/circuits/bin/dkg/share_encryption/README.md b/circuits/bin/dkg/share_encryption/README.md index 68a673e74a..23a9863c71 100644 --- a/circuits/bin/dkg/share_encryption/README.md +++ b/circuits/bin/dkg/share_encryption/README.md @@ -40,7 +40,8 @@ flowchart TD ## Metadata - **Phase**: P1 (DKG). -- **Runs**: (N_PARTIES - 1) × Ciphernode per variant (once per recipient per share type). +- **Runs**: N_PARTIES × (N_PARTIES - 1) x variant (`e_sm` && `sk`) (each ciphernode encrypts one + share for each of the other N_PARTIES - 1 recipients). - **Requires**: - `commit(pk_dkg)` from C0 ([`dkg/pk`](../pk)) - C3a: `commit(sk_share[party_idx][mod_idx])` from C2a @@ -57,6 +58,10 @@ flowchart TD `compute_share_encryption_challenge()` - **Commitment Functions**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - `compute_dkg_pk_commitment()`, `compute_share_encryption_commitment_from_message()` +- **Note**: C3 is structurally similar to the + [GRECO](https://blog.enclave.gg/enclave-cryptography-greco-fhe-zk/) circuit (P3). The same + decomposition approach used for GRECO could be applied here if further circuit splitting is + desired. - **Related Circuits**: - C0 [`dkg/pk`](../pk) - C2a [`dkg/sk_share_computation`](../sk_share_computation) diff --git a/circuits/bin/threshold/pk_generation/README.md b/circuits/bin/threshold/pk_generation/README.md index c9e0cd87a6..09c703b62c 100644 --- a/circuits/bin/threshold/pk_generation/README.md +++ b/circuits/bin/threshold/pk_generation/README.md @@ -39,7 +39,7 @@ flowchart TD ### Metadata - **Phase**: P1 (DKG). -- **Runs**: N_PARTIES = 1 x Ciphernode (after BFV key commitment). +- **Runs**: N_PARTIES (once per ciphernode after BFV key commitment). - **Requires**: [`config`](../../config) circuit from P0 (Configs Verification). - **Output(s)**: - `commit(sk)` → C2a ([`dkg/share_computation`](../../dkg/sk_share_computation)) diff --git a/circuits/bin/threshold/share_decryption/README.md b/circuits/bin/threshold/share_decryption/README.md index f0be331c88..785f9062da 100644 --- a/circuits/bin/threshold/share_decryption/README.md +++ b/circuits/bin/threshold/share_decryption/README.md @@ -28,7 +28,8 @@ flowchart TD **Phase:** P4 (Decryption) -**Runs:** 1 × Ciphernode (once per ciphernode per decryption request; T+1 valid proofs required) +**Runs:** H (once per honest ciphernode per decryption request; T+1 valid proofs required before C7 +can proceed) **Requires:** From 94da8229bbc10cd7337994440c85e0ad61f855ad Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Mar 2026 16:49:27 +0100 Subject: [PATCH 21/31] fix readmes based on latest changes for aggregation --- .../bin/recursive_aggregation/fold/README.md | 67 ++++++++++++++----- .../recursive_aggregation/wrapper/README.md | 23 +++---- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/circuits/bin/recursive_aggregation/fold/README.md b/circuits/bin/recursive_aggregation/fold/README.md index e9244f6ecb..1e78722f92 100644 --- a/circuits/bin/recursive_aggregation/fold/README.md +++ b/circuits/bin/recursive_aggregation/fold/README.md @@ -1,45 +1,78 @@ # Fold -Aggregates two non-ZK UltraHonk proofs into a single recursive commitment. +Aggregates two non-ZK UltraHonk proofs into a single recursive commitment and a proof-genealogy +fingerprint. This sits at the top of the aggregation stack: it takes two wrapper proofs (stripped of ZK -randomness) and combines their public commitments into one field element that a verifier can check -cheaply in place of the original pair. +randomness), verifies them under their respective verification keys, and folds their public outputs +into a single `(key_hash, commitment)` tuple that a verifier can check cheaply in place of the +original pair. ## Inputs -| Name | Visibility | Type | Description | -| ------------------ | ---------- | -------------------------- | ---------------------------------------- | -| `verification_key` | private | `UltraHonkVerificationKey` | Shared VK for both proofs | -| `proofs` | private | `[UltraHonkProof; 2]` | The two non-ZK proofs to aggregate | -| `commitments` | public | `[Field; 2]` | Public commitment output from each proof | -| `key_hash` | private | `Field` | Hash of the verification key | +All parameters are private witnesses. + +| Name | Type | Description | +| ------------------------- | -------------------------- | -------------------------------------------- | +| `proof1_verification_key` | `UltraHonkVerificationKey` | VK for proof 1 | +| `proof1_proof` | `UltraHonkProof` | First non-ZK proof to aggregate | +| `proof1_public_inputs` | `[Field; 2]` | `[key_hash, commitment]` attested by proof 1 | +| `proof1_key_hash` | `Field` | Hash of the VK that verified proof 1 | +| `proof2_verification_key` | `UltraHonkVerificationKey` | VK for proof 2 | +| `proof2_proof` | `UltraHonkProof` | Second non-ZK proof to aggregate | +| `proof2_public_inputs` | `[Field; 2]` | `[key_hash, commitment]` attested by proof 2 | +| `proof2_key_hash` | `Field` | Hash of the VK that verified proof 2 | + +`proof*_public_inputs[0]` carries the key_hash propagated upward by the inner proof (encoding its +own circuit genealogy); `proof*_public_inputs[1]` carries its aggregation commitment. ## Output -`pub Field` — aggregated recursive commitment computed over both input commitments. +`pub (Field, Field)` — `(key_hash, commitment)` where: + +- `key_hash` is a single fingerprint encoding the full proof genealogy (see below). +- `commitment` is + `compute_recursive_aggregation_commitment([proof1_public_inputs[1], proof2_public_inputs[1]])`. ## Verification -1. Verify `proofs[0]` non-ZK against `verification_key` with public input `commitments[0]`. -2. Verify `proofs[1]` non-ZK against `verification_key` with public input `commitments[1]`. -3. Return `compute_recursive_aggregation_commitment([commitments[0], commitments[1]])`. +1. `verify_honk_proof_non_zk(proof1_verification_key, proof1_proof, proof1_public_inputs, proof1_key_hash)` +2. `verify_honk_proof_non_zk(proof2_verification_key, proof2_proof, proof2_public_inputs, proof2_key_hash)` +3. Compute + `commitment = compute_recursive_aggregation_commitment([proof1_public_inputs[1], proof2_public_inputs[1]])`. +4. Compute + `key_hash = compute_vk_hash([proof1_public_inputs[0], proof2_public_inputs[0], proof1_key_hash, proof2_key_hash])`. +5. Return `(key_hash, commitment)`. + +### Key genealogy + +`key_hash` is computed by hashing four values in order: + +| Position | Value | Meaning | +| -------- | ------------------------- | --------------------------------------------------- | +| 0 | `proof1_public_inputs[0]` | key_hash attested by proof 1 (from its inner folds) | +| 1 | `proof2_public_inputs[0]` | key_hash attested by proof 2 (from its inner folds) | +| 2 | `proof1_key_hash` | VK hash of the circuit that produced proof 1 | +| 3 | `proof2_key_hash` | VK hash of the circuit that produced proof 2 | -Both proofs must share the same verification key. +This combined fingerprint lets the verifier check the entire proof genealogy: which circuits were +folded and which VK verified each level, without re-running any inner proof. ## Data Flow ```mermaid flowchart LR - W0["wrapper proof 0 (pub: commitment)"] --> F["fold"] - W1["wrapper proof 1 (pub: commitment)"] --> F - F -->|"aggregated commitment"| Out["verifier"] + W0["wrapper proof 1\npub_inputs: [key_hash₁, commitment₁]"] --> F["fold"] + W1["wrapper proof 2\npub_inputs: [key_hash₂, commitment₂]"] --> F + F -->|"key_hash (genealogy)"| Out["verifier"] + F -->|"commitment (folded)"| Out ``` ## Notes - Uses **non-ZK** proof verification (`verify_honk_proof_non_zk`) — the ZK layer is handled inside the wrapper circuits that feed into this one. +- Each proof has its **own** verification key; there is no shared VK constraint between the two. - Hardcoded to aggregate exactly **2** proofs per invocation. ## Related diff --git a/circuits/bin/recursive_aggregation/wrapper/README.md b/circuits/bin/recursive_aggregation/wrapper/README.md index 403f4190aa..3047378f02 100644 --- a/circuits/bin/recursive_aggregation/wrapper/README.md +++ b/circuits/bin/recursive_aggregation/wrapper/README.md @@ -1,9 +1,10 @@ # Wrapper Circuits -Each wrapper circuit takes one or more ZK UltraHonk proofs from a base circuit, re-verifies them -in-circuit, and computes a single recursive aggregation commitment over all public inputs. This -converts a ZK proof with many public inputs into a single `Field` that downstream aggregation steps -(fold, or an on-chain verifier) can process cheaply. +Each wrapper circuit takes one or more UltraHonk proofs from a base circuit, re-verifies them +in-circuit with `verify_honk_proof_non_zk`, and computes a single recursive aggregation commitment +over all public inputs (or a tuple for the user_data_encryption wrapper). This converts a proof with +many public inputs into one or a few `Field` values that downstream aggregation steps (fold, or an +on-chain verifier) can process cheaply. ## Common Pattern @@ -25,24 +26,22 @@ Every wrapper follows the same structure. What varies between circuits: | `threshold/pk_aggregation` | 1 | `H + 1` | | `threshold/share_decryption` | 1 | `2 + (3 × L × N)` | | `threshold/decrypted_shares_aggregation` | 1 | `((T+1) × L × MAX_MSG_NON_ZERO_COEFFS) + (T + 1 + MAX_MSG_NON_ZERO_COEFFS)` | -| `threshold/user_data_encryption` | 2 | 4 (ct0) · 3 (ct1) — asymmetric, see below | +| `threshold/user_data_encryption` | 2 | 4 (ct0) · 3 (ct1) — asymmetric, see below | ## Special Case: `threshold/user_data_encryption` -This wrapper departs from the standard pattern in three ways: +This wrapper departs from the standard pattern in two ways: -1. **Non-ZK verification** — uses `verify_honk_proof_non_zk`; the ct0 and ct1 base circuits are - verified without the ZK layer, unlike all other wrappers. -2. **Cross-proof constraint** — asserts that the `u_commitment` is identical across the ct0 and ct1 +1. **Cross-proof constraint** — asserts that the `u_commitment` is identical across the ct0 and ct1 proofs, binding the two ciphertexts to the same encryption randomness. -3. **Tuple output** — returns `(Field, Field, Field)` instead of a single commitment, carrying the - ciphertext commitment, the public-key commitment, and the aggregation commitment separately. +2. **Tuple output** — returns `(Field, Field, Field)` instead of a single commitment: the public-key + commitment, the ciphertext commitment, and the k1_commitment (in that order). ## Data Flow ```mermaid flowchart LR - Base["base circuit proof (ZK UltraHonk)"] --> W["wrapper"] + Base["base circuit proof (UltraHonk)"] --> W["wrapper"] W -->|"pub: commitment (Field)"| F["fold"] F -->|"aggregated commitment"| Out["verifier"] ``` From 03e0d4226cd52b24e7a1fc1d60cb8f3ed1798d32 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 6 Mar 2026 17:22:12 +0100 Subject: [PATCH 22/31] adapt to last version of the article --- circuits/bin/config/README.md | 10 +++++----- circuits/bin/config/src/main.nr | 2 +- circuits/bin/dkg/pk/README.md | 14 +++++++------- circuits/bin/dkg/share_decryption/README.md | 4 ++-- circuits/bin/dkg/share_encryption/README.md | 4 ++-- circuits/bin/threshold/pk_generation/README.md | 10 +++++----- circuits/lib/src/README.md | 6 +++--- circuits/lib/src/core/dkg/pk.nr | 14 +++++++------- circuits/lib/src/core/dkg/share_encryption.nr | 6 +++--- 9 files changed, 35 insertions(+), 35 deletions(-) diff --git a/circuits/bin/config/README.md b/circuits/bin/config/README.md index 44a39d36e1..1972ae6084 100644 --- a/circuits/bin/config/README.md +++ b/circuits/bin/config/README.md @@ -1,4 +1,4 @@ -# Configuration Verification Circuit (Phase 0) +# Configuration Verification Circuit The Configuration Verification circuit runs once per deployment to verify all cryptographic parameters used across both BFV (for DKG share encryption) and Threshold BFV (for user data @@ -11,7 +11,7 @@ cross-scheme consistency. ```mermaid flowchart TD - subgraph P0["P0
Configuration Verification"] + subgraph P0["Configuration Verification"] ConfigCircuit["Configuration Circuit
Verify crypto configs"] end @@ -25,11 +25,11 @@ flowchart TD ## Metadata -- **Phase**: P0 (Configuration Verification). -- **Runs**: 1 x Ciphernode (one-time program verification). +- **Phase**: Pre-deployment (one-time configuration verification). +- **Runs**: 1 (once per deployment, before any P1–P4 circuits run). - **Requires**: Configured parameter sets from [`configs/secure`](../../../lib/src/configs/secure). - **Output(s)**: Single proof that all parameters are valid, consumed by all P1-P4 circuits. -- **Data Flow**: `Parameter Sets → P0 → Verified Configs → All Circuits (P1-P4)` +- **Data Flow**: `Parameter Sets → Config Circuit → Verified Configs → All Circuits (P1-P4)` - **Verification Categories**: - DKG (BFV) Parameters: [`configs/secure/dkg.nr`](../../../lib/src/configs/secure/dkg.nr) - Threshold BFV Parameters: diff --git a/circuits/bin/config/src/main.nr b/circuits/bin/config/src/main.nr index aa0b586215..25146bdd19 100644 --- a/circuits/bin/config/src/main.nr +++ b/circuits/bin/config/src/main.nr @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -/// Configuration Verification Circuit (Phase 0). +/// Configuration Verification Circuit (pre-deployment). /// /// This circuit verifies all cryptographic parameters used across both BFV (for DKG share encryption) /// and Threshold BFV (for user data encryption) schemes. It runs once per deployment to establish diff --git a/circuits/bin/dkg/pk/README.md b/circuits/bin/dkg/pk/README.md index 05c3546a4c..cfcda52af2 100644 --- a/circuits/bin/dkg/pk/README.md +++ b/circuits/bin/dkg/pk/README.md @@ -1,8 +1,8 @@ # [C0] BFV Public Key Commitment (`pk`) The BFV Public Key Commitment circuit (C0) is the first circuit executed in Phase 1 (Distributed Key -Generation). Each ciphernode creates a cryptographic commitment to their BFV public key, which will -be used exclusively for encrypting secret shares during DKG. +Generation). Each ciphernode creates a cryptographic commitment to their DKG public key, which will +be used exclusively for encrypting secret shares during the PVDKG phase. Rather than verifying the key generation process, this circuit establishes a _binding commitment_ that prevents key substitution attacks. The commitment acts as an immutable reference—any attempt to @@ -10,11 +10,11 @@ use a different key in later encryption or decryption steps will be cryptographi ```mermaid flowchart TD - %% Input from Phase 0 - Input0["P0
Configs Verification"] -.->|"verified configs"| C0 + %% Input from config circuit + Input0["Config
Verification"] -.->|"verified configs"| C0 subgraph Focus["C0"] - C0["Commit to BFV public key"] + C0["Commit to DKG public key"] end %% Output to C3a and C3b @@ -33,9 +33,9 @@ flowchart TD - **Phase**: P1 (DKG). - **Runs**: N_PARTIES (once per ciphernode at the start of key generation). -- **Requires**: [`config`](../../config) circuit from P0 (Configs Verification). +- **Requires**: [`config`](../../config) circuit (pre-deployment parameter verification). - **Output(s)**: `commit(pk_dkg)` consumed by C3a / C3b ([`dkg/share_encryption`](../share_encryption)) -- **Data Flow**: `P0 (configs) → C0 → commit(pk_dkg) → C3a, C3b` +- **Data Flow**: `Config → C0 → commit(pk_dkg) → C3a, C3b` - **Commitment Function**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - `compute_dkg_pk_commitment()` diff --git a/circuits/bin/dkg/share_decryption/README.md b/circuits/bin/dkg/share_decryption/README.md index 6dfbfc4bbc..5f298a54e4 100644 --- a/circuits/bin/dkg/share_decryption/README.md +++ b/circuits/bin/dkg/share_decryption/README.md @@ -36,8 +36,8 @@ flowchart TD ## Metadata - **Phase**: P1 (DKG). -- **Runs**: N_PARTIES × Ciphernode x variant (`e_sm` && `sk`) (once per ciphernode; each instance - aggregates all received shares). +- **Runs**: N_PARTIES per variant (once per ciphernode; each instance aggregates all shares received + from other ciphernodes). - **Requires**: - C4a: `commit(sk_share)` from C2a ([`dkg/sk_share_computation`](../sk_share_computation)) - C4b: `commit(e_sm_share)` from C2b ([`dkg/e_sm_share_computation`](../e_sm_share_computation)) diff --git a/circuits/bin/dkg/share_encryption/README.md b/circuits/bin/dkg/share_encryption/README.md index 23a9863c71..33db540672 100644 --- a/circuits/bin/dkg/share_encryption/README.md +++ b/circuits/bin/dkg/share_encryption/README.md @@ -1,7 +1,7 @@ # [C3a & C3b] Share Encryption (`share_encryption`) The Share Encryption circuit verifies that each ciphernode correctly encrypted a secret share under -the recipient's BFV public key. After shares are verified and committed in _C2a/C2b_, they must be +the recipient's DKG public key. After shares are verified and committed in _C2a/C2b_, they must be encrypted for secure peer-to-peer transmission — this circuit proves the encryption was formed correctly without revealing the plaintext share or the encryption randomness. @@ -17,7 +17,7 @@ flowchart TD Input2b["C2b
share-computation-e-sm"] -.->|"commit(e_sm_share[i][j])"| C3 subgraph Focus["C3a & C3b"] - C3["Encrypt share under BFV pk"] + C3["Encrypt share under DKG pk"] end C3 -->|"ct(sk_share)"| Output1["→ C4a
share-decryption-sk"] diff --git a/circuits/bin/threshold/pk_generation/README.md b/circuits/bin/threshold/pk_generation/README.md index 09c703b62c..34d7e7cb99 100644 --- a/circuits/bin/threshold/pk_generation/README.md +++ b/circuits/bin/threshold/pk_generation/README.md @@ -12,8 +12,8 @@ probability. ```mermaid flowchart TD - %% Input from Phase 0 - Input0["P0
Configs Verification"] -.->|"verified configs"| C1 + %% Input from config circuit + Input0["Config
Verification"] -.->|"verified configs"| C1 subgraph Focus["C1"] C1["Generate TRBFV key pair"] @@ -39,13 +39,13 @@ flowchart TD ### Metadata - **Phase**: P1 (DKG). -- **Runs**: N_PARTIES (once per ciphernode after BFV key commitment). -- **Requires**: [`config`](../../config) circuit from P0 (Configs Verification). +- **Runs**: N_PARTIES (once per ciphernode after DKG key commitment). +- **Requires**: [`config`](../../config) circuit (pre-deployment parameter verification). - **Output(s)**: - `commit(sk)` → C2a ([`dkg/share_computation`](../../dkg/sk_share_computation)) - `commit(e_sm)` → C2b ([`dkg/share_computation`](../../dkg/e_sm_share_computation)) - `commit(pk_trbfv)` → C5 ([`threshold/pk_aggregation`](../../threshold/pk_aggregation/)) -- **Data Flow**: `P0 → C1 → {commit(sk) → C2a, commit(pk_trbfv) → C5, commit(e_sm) → C2b}` +- **Data Flow**: `Config → C1 → {commit(sk) → C2a, commit(pk_trbfv) → C5, commit(e_sm) → C2b}` - **Challenge Generation**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - `compute_threshold_pk_challenge()` - **Commitment Functions**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - diff --git a/circuits/lib/src/README.md b/circuits/lib/src/README.md index c93a6eb740..90217088f4 100644 --- a/circuits/lib/src/README.md +++ b/circuits/lib/src/README.md @@ -16,7 +16,7 @@ graph LR math["math primitives"] --> core["core circuits logic"] configs["configs & parameters"] --> core core --> dkg["dkg (C0 · C2a/b · C3a/b · C4a/b)"] - core --> threshold["threshold (C1 · C5 · P3 · C6 · C7)"] + core --> threshold["threshold (C1 · C5 · UDE · C6 · C7)"] ``` ## math @@ -93,9 +93,9 @@ Circuits for the Distributed Key Generation phase (P1). | File | Circuit | Role | | ---------------------- | --------- | ------------------------------------------------------------------- | -| `pk.nr` | C0 | Commit to a ciphernode's BFV public key | +| `pk.nr` | C0 | Commit to a ciphernode's DKG public key | | `share_computation.nr` | C2a / C2b | Verify Shamir shares of `sk` / `e_sm` via Reed-Solomon parity check | -| `share_encryption.nr` | C3a / C3b | Verify BFV encryption of each share for its recipient | +| `share_encryption.nr` | C3a / C3b | Verify DKG BFV encryption of each share for its recipient | | `share_decryption.nr` | C4a / C4b | Verify share decryption and aggregate across honest parties | **C2 share matrix layout** — `y[coeff_idx][mod_idx][party_idx]`: diff --git a/circuits/lib/src/core/dkg/pk.nr b/circuits/lib/src/core/dkg/pk.nr index 83fdd3a6dd..907151093a 100644 --- a/circuits/lib/src/core/dkg/pk.nr +++ b/circuits/lib/src/core/dkg/pk.nr @@ -7,30 +7,30 @@ use crate::math::commitments::compute_dkg_pk_commitment; use crate::math::polynomial::Polynomial; -/// BFV Public Key Commitment Circuit (C0). +/// DKG Public Key Commitment Circuit (C0). /// /// This circuit establishes a binding cryptographic commitment to a ciphernode's -/// BFV public key, which is used exclusively for encrypting secret shares during +/// DKG public key, which is used exclusively for encrypting secret shares during /// the Distributed Key Generation (DKG) phase. /// /// Outputs: /// - commit(pk_dkg) -> C3a / C3b (share encryption) pub struct Pk { - /// BFV public key first component for each CRT modulus. + /// DKG public key first component for each CRT modulus. /// pk0[i] is a degree N-1 polynomial for modulus q_i. pk0: [Polynomial; L], - /// BFV public key second component for each CRT modulus. + /// DKG public key second component for each CRT modulus. /// pk1[i] is a degree N-1 polynomial for modulus q_i. pk1: [Polynomial; L], } impl Pk { - /// Creates a new BFV public key commitment circuit instance. + /// Creates a new DKG public key commitment circuit instance. /// /// # Arguments /// - /// * `pk0` - First component of BFV public key (one polynomial per CRT modulus) - /// * `pk1` - Second component of BFV public key (one polynomial per CRT modulus) + /// * `pk0` - First component of DKG public key (one polynomial per CRT modulus) + /// * `pk1` - Second component of DKG public key (one polynomial per CRT modulus) pub fn new(pk0: [Polynomial; L], pk1: [Polynomial; L]) -> Self { Pk { pk0, pk1 } } diff --git a/circuits/lib/src/core/dkg/share_encryption.nr b/circuits/lib/src/core/dkg/share_encryption.nr index da51b94a25..39f9584ea5 100644 --- a/circuits/lib/src/core/dkg/share_encryption.nr +++ b/circuits/lib/src/core/dkg/share_encryption.nr @@ -82,7 +82,7 @@ impl Configs { /// DKG Share Encryption Circuit (C3a / C3b). /// -/// Verifies that a secret share was correctly encrypted under a recipient's BFV public key. +/// Verifies that a secret share was correctly encrypted under a recipient's DKG public key. /// This circuit runs twice in parallel per ciphernode per recipient: /// - C3a: encrypts a secret key (sk) share, consuming commit(sk_share) from C2a /// - C3b: encrypts a smudging noise (e_sm) share, consuming commit(e_sm_share) from C2b @@ -98,7 +98,7 @@ impl Configs { pub struct ShareEncryption { /// Circuit parameters configs: Configs, - /// Expected commitment to the BFV public key (from C0: pk). + /// Expected commitment to the DKG public key (from C0: pk). /// (public witness) expected_pk_commitment: Field, /// Expected commitment to the share (message) being encrypted. @@ -177,7 +177,7 @@ impl Date: Mon, 9 Mar 2026 15:03:29 +0100 Subject: [PATCH 23/31] fix wrong comment --- .../lib/src/core/threshold/decrypted_shares_aggregation.nr | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr index 408a5270de..cf84d09e27 100644 --- a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr +++ b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr @@ -343,22 +343,20 @@ pub fn compute_all_lagrange_coeffs( } } - // Numerator sign: PROD_{j=0..T} (-x_j) contributes (-1)^{T+1} overall; - // negative when T is odd + // PROD_{j!=i}(-x_j) has T factors -> sign (-1)^T; numerator_sign_negative = (T % 2) == 1. let numerator_sign_negative = (T % 2) == 1; for basis_idx in 0..L { let q_l = qis[basis_idx]; let m = ModU128::new(q_l); - // Compute PROD(j=0..T) x_j mod q_l once; divide by x_i per party + // product_x = PROD_{j=0..T} x_j; numerator_abs = product_x / x_i = PROD_{j!=i} x_j let mut product_x = 1 as Field; for j in 0..(T + 1) { product_x = m.mul_mod(product_x, party_ids[j]); } for party_idx in 0..(T + 1) { - // |numerator| = PROD_{j!=i} x_j = product_x / x_i let numerator_abs = m.div_mod(product_x, party_ids[party_idx]); // |denominator| = PROD_{j!=i} |x_i - x_j| From 013a874be37ca2d8fe7e1007de43b1a6d383ce35 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Mon, 9 Mar 2026 15:41:59 +0100 Subject: [PATCH 24/31] small nit on docs --- circuits/bin/config/src/main.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuits/bin/config/src/main.nr b/circuits/bin/config/src/main.nr index 25146bdd19..91a7b9c58e 100644 --- a/circuits/bin/config/src/main.nr +++ b/circuits/bin/config/src/main.nr @@ -7,7 +7,7 @@ /// Configuration Verification Circuit (pre-deployment). /// /// This circuit verifies all cryptographic parameters used across both BFV (for DKG share encryption) -/// and Threshold BFV (for user data encryption) schemes. It runs once per deployment to establish +/// and Threshold BFV (for user data encryption and decryption) schemes. It runs once per deployment to establish /// the mathematical foundation that all subsequent circuits depend on. /// /// Rather than trusting parameter configuration, this circuit provides public proof that: From eb2d639bd86582e73cb05255ba1846ee4442b1fb Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Mar 2026 11:52:33 +0100 Subject: [PATCH 25/31] fix wrong merge conflicts resolution --- .../decrypted_shares_aggregation/src/main.nr | 9 +- .../lib/src/configs/insecure/threshold.nr | 2 + circuits/lib/src/configs/secure/threshold.nr | 2 + .../threshold/decrypted_shares_aggregation.nr | 240 +++--------------- .../src/core/threshold/share_decryption.nr | 103 +++----- .../computation.rs | 33 ++- 6 files changed, 113 insertions(+), 276 deletions(-) diff --git a/circuits/bin/threshold/decrypted_shares_aggregation/src/main.nr b/circuits/bin/threshold/decrypted_shares_aggregation/src/main.nr index 18ad4c423e..f77cb65ff8 100644 --- a/circuits/bin/threshold/decrypted_shares_aggregation/src/main.nr +++ b/circuits/bin/threshold/decrypted_shares_aggregation/src/main.nr @@ -5,8 +5,11 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use lib::configs::default::{MAX_MSG_NON_ZERO_COEFFS, T}; -use lib::configs::default::threshold::{DECRYPTED_SHARES_AGGREGATION_CONFIGS, L}; -use lib::core::threshold::decrypted_shares_aggregation::DecryptedSharesAggregationBigNum; +use lib::configs::default::threshold::{ + DECRYPTED_SHARES_AGGREGATION_BIT_D, DECRYPTED_SHARES_AGGREGATION_BIT_NOISE, + DECRYPTED_SHARES_AGGREGATION_CONFIGS, L, +}; +use lib::core::threshold::decrypted_shares_aggregation::DecryptedSharesAggregation; use lib::math::polynomial::Polynomial; fn main( @@ -17,7 +20,7 @@ fn main( u_global: Polynomial, crt_quotients: [Polynomial; L], ) { - let decrypted_shares_aggregation: DecryptedSharesAggregationBigNum = DecryptedSharesAggregationBigNum::new( + let decrypted_shares_aggregation: DecryptedSharesAggregation = DecryptedSharesAggregation::new( DECRYPTED_SHARES_AGGREGATION_CONFIGS, expected_d_commitments, decryption_shares, diff --git a/circuits/lib/src/configs/insecure/threshold.nr b/circuits/lib/src/configs/insecure/threshold.nr index 332c1ee75d..36e5c6a4df 100644 --- a/circuits/lib/src/configs/insecure/threshold.nr +++ b/circuits/lib/src/configs/insecure/threshold.nr @@ -1181,6 +1181,8 @@ pub global THRESHOLD_SHARE_DECRYPTION_CONFIGS: ShareDecryptionConfigs = Share decrypted_shares_aggregation (CIRCUIT 7) ------------------------------------- ************************************/ +pub global DECRYPTED_SHARES_AGGREGATION_BIT_NOISE: u32 = 65; +pub global DECRYPTED_SHARES_AGGREGATION_BIT_D: u32 = 35; pub global DECRYPTED_SHARES_AGGREGATION_CONFIGS: DecryptedSharesAggregationConfigs = DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T); diff --git a/circuits/lib/src/configs/secure/threshold.nr b/circuits/lib/src/configs/secure/threshold.nr index fd43cce95d..91c45600ee 100644 --- a/circuits/lib/src/configs/secure/threshold.nr +++ b/circuits/lib/src/configs/secure/threshold.nr @@ -32931,6 +32931,8 @@ pub global THRESHOLD_SHARE_DECRYPTION_CONFIGS: ShareDecryptionConfigs = Share decrypted_shares_aggregation (CIRCUIT 7) ------------------------------------- ************************************/ +pub global DECRYPTED_SHARES_AGGREGATION_BIT_NOISE: u32 = 200; +pub global DECRYPTED_SHARES_AGGREGATION_BIT_D: u32 = 52; pub global DECRYPTED_SHARES_AGGREGATION_CONFIGS: DecryptedSharesAggregationConfigs = DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T); diff --git a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr index cf84d09e27..abdd450d36 100644 --- a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr +++ b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr @@ -7,9 +7,9 @@ use crate::math::commitments::compute_threshold_decryption_share_commitment; use crate::math::modulo::U128::ModU128; use crate::math::polynomial::Polynomial; -use ::bignum::BigNum; -use ::bignum::bignum::to_field; -use ::bignum::SecureThreshold8192; +use dep::bignum::BigNum; +use dep::bignum::bignum::to_field; +use dep::bignum::SecureThreshold8192; /// Cryptographic parameters for decryption share aggregation circuit. pub struct Configs { @@ -17,10 +17,7 @@ pub struct Configs { pub qis: [Field; L], /// Plaintext modulus (typically denoted as `t`) pub plaintext_modulus: Field, - /// Precomputed value: Q^{-1} mod t (the positive modular inverse of Q modulo t), - /// where Q = q_0 * q_1 * ... * q_{L-1}. The negation required by the BFV decoding - /// formula (-Q^{-1}) is applied separately in verify_decoding: via `t - product` - /// in the non-centering branch and via `Q - (t*u mod Q)` in the centering branch. + /// Precomputed value: `-Q^(-1) mod t` where Q is the product of all CRT moduli pub q_inverse_mod_t: Field, } @@ -33,14 +30,13 @@ impl Configs { /// Decrypted Shares Aggregation Circuit (Circuit 7). /// Uses BigNum for Q values (works for both insecure and secure parameter sets). /// -/// Combines T+1 public decryption shares (from C6) into the final plaintext via -/// Lagrange interpolation, CRT reconstruction, and BFV decoding. This is the last -/// circuit in the Enclave protocol. -/// -/// Use this variant when Q (the product of all CRT moduli) exceeds 128 bits, -/// as is the case for cryptographically secure parameter sets. For smaller parameter -/// sets where Q fits within 128 bits, use DecryptedSharesAggregationModular instead. -pub struct DecryptedSharesAggregationBigNum { +/// Verifies: +/// 0. Each party's decryption share matches the corresponding C6 public `d` commitment +/// 1. Lagrange interpolation to compute u^{(l)} for each CRT basis +/// 2. CRT reconstruction: u^{(l)} + r^{(l)} * q_l = u_global +/// 3. Decoding verification: message = -Q^{-1} * (t * u_global)_Q mod t +pub struct DecryptedSharesAggregation { + /// Circuit parameters including crypto constants configs: Configs, /// Public `d` commitments from circuit 6 (one per reconstructing party), same order as `decryption_shares` @@ -49,64 +45,21 @@ pub struct DecryptedSharesAggregationBigNum; L]; T + 1], - /// Shamir x-coordinates for the T+1 participating parties. - /// Must be in strictly increasing order, the sign computation in Lagrange - /// interpolation depends on this ordering. - /// (public witnesses) - party_ids: [Field; T + 1], - - /// Claimed output plaintext polynomial. - /// (public witness) - message: Polynomial, - - /// Global decryption value lifted from CRT representation. - /// Verified against u^{(l)} via CRT reconstruction check. - /// (secret witness) - u_global: Polynomial, - - /// Quotient polynomials witnessing the CRT lift: - /// u^{(l)} + crt_quotients[l] * q_l == u_global for each basis l. - /// (secret witnesses) - crt_quotients: [Polynomial; L], -} - -/// Decryption Share Aggregation Circuit - Modular variant (C7). -/// -/// Identical verification logic to DecryptedSharesAggregationBigNum, but uses -/// ModU128 arithmetic for the decoding step instead of BigNum. More efficient -/// when Q (the product of all CRT moduli) fits within 128 bits, e.g. for smaller -/// or non-production parameter sets. For cryptographically secure parameter sets -/// where Q exceeds 128 bits, use DecryptedSharesAggregationBigNum instead. -pub struct DecryptedSharesAggregationModular { - configs: Configs, - - /// Public decryption shares from T+1 ciphernodes, as produced by C6. - /// decryption_shares[party_idx][basis_idx] is party i's share for CRT basis l. - /// (public witnesses) - decryption_shares: [[Polynomial; L]; T + 1], - - /// Shamir x-coordinates for the T+1 participating parties. - /// Must be in strictly increasing order, the sign computation in Lagrange - /// interpolation depends on this ordering. - /// (public witnesses) + /// Party IDs (x-coordinates) for interpolation (public witnesses) + /// Note: Must be in strictly increasing order for correct Lagrange sign computation party_ids: [Field; T + 1], - /// Claimed output plaintext polynomial. - /// (public witness) + /// Message polynomial m(x) (public witness) message: Polynomial, - /// Global decryption value lifted from CRT representation. - /// Verified against u^{(l)} via CRT reconstruction check. - /// (secret witness) + /// Global u polynomial (secret witness) u_global: Polynomial, - /// Quotient polynomials witnessing the CRT lift: - /// u^{(l)} + crt_quotients[l] * q_l == u_global for each basis l. - /// (secret witnesses) + /// CRT quotient polynomials (secret witnesses) crt_quotients: [Polynomial; L], } -impl DecryptedSharesAggregationBigNum { +impl DecryptedSharesAggregation { pub fn new( configs: Configs, expected_d_commitments: [Field; T + 1], @@ -158,14 +111,7 @@ impl DecryptedSharesAg self.verify_decoding(); } - /// Verifies the BFV decoding step using BigNum arithmetic. - /// - /// For each coefficient, computes message = -Q^{-1} * (t * u_global)_Q mod t - /// using SecureThreshold8192 to handle Q values that exceed 128 bits. - /// - /// Centered representation: if (t * u_global) mod Q >= Q/2, the value is treated - /// as negative, flipping the sign of the -Q^{-1} multiplication. Only non-zero - /// message coefficients are checked, zero coefficients are unconstrained. + /// Verifies decoding using BigNum for large Q values fn verify_decoding(self) { // Compute Q as product of all CRT moduli let mut q_modulus = 1 as Field; @@ -192,15 +138,16 @@ impl DecryptedSharesAg let needs_centering = t_times_u_bn_q > q_half_bn; let computed_message = if needs_centering { - // (t*u) mod Q >= Q/2: centered value is negative - // -Q^{-1} * (Q - (t*u) mod Q) mod t + // When (t*u) mod Q >= Q/2: treat as negative in centered form + // Centered value is conceptually negative: (t_times_u_mod_q - Q) + // -Q^{-1} * (negative) = positive result let centered_positive = q_bn - t_times_u_bn_q; let centered_positive_mod_t = centered_positive.umod(t_bn); let centered_field = to_field(centered_positive_mod_t); m.mul_mod(self.configs.q_inverse_mod_t, centered_field) } else { - // (t*u) mod Q < Q/2: centered value is positive - // t - (-Q^{-1} * (t*u) mod Q mod t) + // When (t*u) mod Q < Q/2: stays positive in centered form + // -Q^{-1} * (positive) = negative result = t - result let t_times_u_bn_t = t_times_u_bn_q.umod(t_bn); let t_times_u_field = to_field(t_times_u_bn_t); let product = m.mul_mod(self.configs.q_inverse_mod_t, t_times_u_field); @@ -219,121 +166,14 @@ impl DecryptedSharesAg } } -impl DecryptedSharesAggregationModular { - pub fn new( - configs: Configs, - decryption_shares: [[Polynomial; L]; T + 1], - party_ids: [Field; T + 1], - message: Polynomial, - u_global: Polynomial, - crt_quotients: [Polynomial; L], - ) -> Self { - DecryptedSharesAggregationModular { - configs, - decryption_shares, - party_ids, - message, - u_global, - crt_quotients, - } - } - - /// Executes the decryption share aggregation circuit. - /// - /// Runs all four verification steps in order: Lagrange coefficient computation, - /// interpolation, CRT reconstruction, and decoding. - pub fn execute(self) { - // Step 1: Compute Lagrange coefficients in-circuit - let lagrange_coeffs = compute_all_lagrange_coeffs::(self.configs.qis, self.party_ids); - - // Step 2: Compute u^{(l)} for each CRT basis via Lagrange interpolation - let u_crts = compute_crt_components::( - self.configs.qis, - self.decryption_shares, - lagrange_coeffs, - ); - - // Step 3: Verify CRT reconstruction: u^{(l)} + r^{(l)} * q_l = u_global - verify_crt_reconstruction::( - self.configs.qis, - self.u_global, - self.crt_quotients, - u_crts, - ); - // Step 4: Verify decoding - self.verify_decoding(); - } - - /// Verifies the BFV decoding step using ModU128 arithmetic. - /// - /// For each coefficient, computes message = -Q^{-1} * (t * u_global)_Q mod t - /// using u128 modular arithmetic. Only valid when Q fits within 128 bits. - /// - /// Centered representation: if (t * u_global) mod Q >= Q/2, the value is treated - /// as negative, flipping the sign of the -Q^{-1} multiplication. Only non-zero - /// message coefficients are checked, zero coefficients are unconstrained. - fn verify_decoding(self) { - let t: Field = self.configs.plaintext_modulus; - // Compute Q as product of all CRT moduli - let mut q_modulus = 1; - for l in 0..L { - q_modulus *= self.configs.qis[l]; - } - - // For centered arithmetic - let q_half = q_modulus as u128 / 2; - - for coeff_idx in 0..MAX_MSG_NON_ZERO_COEFFS { - // Compute (t * u_global) mod Q using ModU128 - let q_mod = ModU128::new(q_modulus); - let t_mod = ModU128::new(t); - - // Compute (t * u_global) mod Q using modular arithmetic functions - let t_times_u_mod_q = q_mod.mul_mod(t, self.u_global.coefficients[coeff_idx]); - let needs_centering = (t_times_u_mod_q as u128) > q_half; - - let computed_message = if needs_centering { - // (t*u) mod Q >= Q/2: centered value is negative - // -Q^{-1} * (Q - (t*u) mod Q) mod t - let centered_positive = q_modulus - t_times_u_mod_q; - let centered_positive_mod_t = t_mod.reduce_mod(centered_positive); - - t_mod.mul_mod(self.configs.q_inverse_mod_t, centered_positive_mod_t) - } else { - // (t*u) mod Q < Q/2: centered value is positive - // t - (-Q^{-1} * (t*u) mod Q mod t) - let t_times_u_mod_t = t_mod.reduce_mod(t_times_u_mod_q); - let product = t_mod.mul_mod(self.configs.q_inverse_mod_t, t_times_u_mod_t); - if product == 0 { - 0 - } else { - t - product - } - }; - - // Verify: only check non-zero coefficients - if self.message.coefficients[coeff_idx] != 0 { - assert(computed_message == self.message.coefficients[coeff_idx]); - } - } - } -} - -/// Computes all Lagrange basis coefficients L_i(0) for each CRT basis. -/// -/// For each CRT basis l and each party i, computes: -/// L_i(0) = PROD_{j!=i} (-x_j) / (x_i - x_j) mod q_l -/// -/// IMPORTANT: party_ids must be in strictly increasing order. The sign of each -/// Lagrange coefficient depends on this ordering, out-of-order IDs will silently -/// produce incorrect signs and produce wrong decryption results. +/// Computes all Lagrange coefficients using optimized modular arithmetic pub fn compute_all_lagrange_coeffs( qis: [Field; L], party_ids: [Field; T + 1], ) -> [[Field; T + 1]; L] { let mut lagrange_coeffs = [[0 as Field; T + 1]; L]; - // Cache pairwise differences: diffs[i][j] = |x_j - x_i| for i < j + // Step 1: Cache |x_i - x_j| factors for all party pairs let mut diffs = [[0 as Field; T + 1]; T + 1]; for i in 0..(T + 1) { for j in (i + 1)..(T + 1) { @@ -343,23 +183,26 @@ pub fn compute_all_lagrange_coeffs( } } - // PROD_{j!=i}(-x_j) has T factors -> sign (-1)^T; numerator_sign_negative = (T % 2) == 1. + // Step 2: Determine signs (same for all parties within a basis) let numerator_sign_negative = (T % 2) == 1; + // Step 3: For each CRT basis, compute Lagrange coefficients for basis_idx in 0..L { let q_l = qis[basis_idx]; let m = ModU128::new(q_l); - // product_x = PROD_{j=0..T} x_j; numerator_abs = product_x / x_i = PROD_{j!=i} x_j + // Compute product of all party IDs: PRODUCT(j=0..T) x_j mod q_l let mut product_x = 1 as Field; for j in 0..(T + 1) { product_x = m.mul_mod(product_x, party_ids[j]); } + // For each party i, compute L_i(0) mod q_l for party_idx in 0..(T + 1) { + // Numerator (absolute value): PRODUCT(j!=party_idx) x_j let numerator_abs = m.div_mod(product_x, party_ids[party_idx]); - // |denominator| = PROD_{j!=i} |x_i - x_j| + // Denominator (absolute value): PRODUCT(j!=party_idx) |x_party_idx - x_j| let mut denominator_abs = 1 as Field; for j in 0..(T + 1) { if j != party_idx { @@ -367,13 +210,14 @@ pub fn compute_all_lagrange_coeffs( } } - // Denominator sign: negative when the number of j > party_idx is odd + // Determine denominator sign let num_greater = T - party_idx; let denom_sign_negative = (num_greater % 2) == 1; + // Compute unsigned result: |numerator| / |denominator| mod q_l let result_abs = m.div_mod(numerator_abs, denominator_abs); - // XOR signs: negate if exactly one of numerator/denominator is negative + // Apply combined sign let should_negate = numerator_sign_negative != denom_sign_negative; let result = if should_negate { m.reduce_mod(q_l - result_abs) @@ -388,13 +232,7 @@ pub fn compute_all_lagrange_coeffs( lagrange_coeffs } -/// Computes u^{(l)} for each CRT basis via Lagrange interpolation. -/// -/// For each basis l and each coefficient position k: -/// u^{(l)}[k] = SUM_{i=0..T} d_i^{(l)}[k] * L_i(0) mod q_l -/// -/// This reconstructs the threshold decryption value at zero for each CRT basis -/// independently, coefficient by coefficient. +/// Computes u^{(l)} for each CRT basis via Lagrange interpolation pub fn compute_crt_components( qis: [Field; L], decryption_shares: [[Polynomial; L]; T + 1], @@ -430,13 +268,7 @@ pub fn compute_crt_components( qis: [Field; L], u_global: Polynomial, diff --git a/circuits/lib/src/core/threshold/share_decryption.nr b/circuits/lib/src/core/threshold/share_decryption.nr index dc8c80de89..5771f8196c 100644 --- a/circuits/lib/src/core/threshold/share_decryption.nr +++ b/circuits/lib/src/core/threshold/share_decryption.nr @@ -15,9 +15,9 @@ use crate::math::polynomial::Polynomial; pub struct Configs { /// CRT moduli: [q_0, q_1, ..., q_{L-1}] pub qis: [Field; L], - /// Coefficient bounds for r1 polynomials (modulus switching quotients, per CRT basis) + /// Bounds for r1 polynomials (modulus switching quotients) for each CRT basis pub r1_bounds: [Field; L], - /// Coefficient bounds for r2 polynomials (cyclotomic reduction quotients, per CRT basis) + /// Bounds for r2 polynomials (cyclotomic reduction quotients) for each CRT basis pub r2_bounds: [Field; L], } @@ -27,35 +27,8 @@ impl Configs { } } -/// Decryption Share Circuit (C6). +/// Threshold Share Decryption (Circuit 6). /// -/// Proves that a ciphernode correctly computed its decryption share from the homomorphic -/// result ciphertext, using the aggregated secret key share and smudging noise committed -/// during P1 (DKG). Runs once per ciphernode; T+1 valid proofs are required before -/// C7 can reconstruct the plaintext. -/// -/// # Decryption Share Formula -/// -/// For each CRT basis l: -/// d[l] = ct0[l] + ct1[l] * sk[l] + e_sm[l] + r2[l] * (X^N + 1) + r1[l] * q_l -/// -/// where: -/// - sk is the ciphernode's aggregated secret key share (sum of received shares, from C4a) -/// - e_sm is the ciphernode's aggregated smudging noise share (from C4b) -/// - r1, r2 are quotient polynomials for modular reduction (secret witnesses) -/// -/// Smudging: -/// -/// The decryption share d is public but reveals nothing about sk. The smudging noise -/// e_sm is chosen large enough to statistically mask the ct1 * sk term, so an adversary -/// observing all T+1 decryption shares cannot extract information about any party's -/// secret key share. -/// -/// Outputs: -/// -/// d[l] -> public decryption share per CRT basis, consumed by C7 -/// (decrypted_shares_aggregation_bn or decrypted_shares_aggregation_mod). -pub struct ShareDecryption { /// Verifies: /// 1. Commitment to sk matches expected (from DKG decryption circuit) /// 2. Commitment to e_sm matches expected (from DKG decryption circuit) @@ -64,40 +37,29 @@ pub struct ShareDecryption, - /// Expected commitment to the aggregated secret key share, from C4a. - /// Binds this proof to the sk committed during DKG. + /// Expected commitment to aggregated sk shares (from DKG decryption circuit) /// (public witness) expected_sk_commitment: Field, - /// Expected commitment to the aggregated smudging noise share, from C4b. - /// Binds this proof to the e_sm committed during DKG. + /// Expected commitment to aggregated e_sm shares (from DKG decryption circuit) /// (public witness) expected_e_sm_commitment: Field, - /// First ciphertext component per CRT basis. - /// (public witnesses) + /// Ciphertext components (public witnesses) + /// ct0 components for each CRT basis (degree N-1 polynomials with N coefficients) ct0: [Polynomial; L], - - /// Second ciphertext component per CRT basis. - /// (public witnesses) + /// ct1 components for each CRT basis (degree N-1 polynomials with N coefficients) ct1: [Polynomial; L], - /// Aggregated secret key share: sum of all received sk shares (from C4a). - /// Verified against expected_sk_commitment; range checks handled by DKG circuits. - /// (secret witness) + /// Aggregated sum of sk shares (secret witness) sk: [Polynomial; L], - /// Aggregated smudging noise share: sum of all received e_sm shares (from C4b). - /// Verified against expected_e_sm_commitment; range checks handled by DKG circuits. - /// (secret witness) + /// Aggregated sum of e_sm shares (secret witness, direct input) + /// e_sm[basis] - sum of e_sm shares for each CRT basis (degree N-1 with N coefficients) e_sm: [Polynomial; L], - /// Modulus switching quotient polynomials, per CRT basis. - /// (secret witnesses) + /// Quotient polynomials for lifting to Z (secret witnesses) r1: [Polynomial<2 * N - 1>; L], - - /// Cyclotomic reduction quotient polynomials, per CRT basis. - /// (secret witnesses) r2: [Polynomial; L], /// Party's computed decryption share (secret witness) @@ -132,10 +94,7 @@ impl(self.sk) @@ -144,10 +103,7 @@ impl(self.e_sm) @@ -191,7 +147,9 @@ impl Vec { let mut inputs = Vec::new(); @@ -274,7 +232,7 @@ impl(inputs) } - /// Verifies the decryption share equation at the Fiat-Shamir challenge point for one CRT basis. + /// Verifies the lifted decryption share computation formula for a specific CRT basis using the Schwartz-Zippel lemma. /// - /// Checks that the claimed d[basis_idx] satisfies: - /// d(gamma) = ct0(gamma) + ct1(gamma) * sk(gamma) + e_sm(gamma) + r2(gamma) * (gamma^N + 1) + r1(gamma) * q_l + /// This function verifies that the decryption share for basis `i` satisfies: + /// `d_i(gamma) = c_0i(gamma) + c_1i(gamma) * s_i(gamma) + e_i(gamma) + r_2i(gamma) * cyclo(gamma) + r_1i(gamma) * q_i` /// - /// By the Schwartz-Zippel lemma, if this holds at a random gamma, the underlying - /// polynomial equation holds with high probability. + /// Where: + /// - `c_0i`, `c_1i` are ciphertext components for basis i + /// - `s_i` is the aggregated secret key shares for basis i + /// - `e_i` is the aggregated noise shares for basis i + /// - `r_1i`, `r_2i` are quotient witnesses for basis i + /// - `cyclo(gamma) = gamma^N + 1` is the cyclotomic polynomial evaluated at gamma + /// - `q_i` is the CRT modulus for basis i + /// + /// The Schwartz-Zippel lemma ensures that if this equation holds at a random point + /// `gamma`, then the polynomials are identical with high probability. + /// + /// # Arguments + /// * `basis_idx` - The index of the CRT basis to verify (0 <= basis_idx < L) + /// * `gamma` - The Fiat-Shamir challenge value used as the evaluation point + /// + /// # Panics + /// The circuit will fail if the decryption share computation formula doesn't hold for the specified basis. fn verify_decryption_share_computation(self, basis_idx: u32, gamma: Field) { // Evaluate ciphertext components at gamma let c_0_at_gamma = self.ct0[basis_idx].eval(gamma); diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs index cdf7a991e9..eb33a6bf7e 100644 --- a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs @@ -11,6 +11,11 @@ //! with [`e3_polynomial::CrtPolynomial::reduce`]; all input coefficients are reduced to //! [0, zkp_modulus) with [`e3_polynomial::reduce`] inside [`Inputs::compute`]. +/// Max message coefficients in the C7 circuit (matches Noir's MAX_MSG_NON_ZERO_COEFFS). +pub const MAX_MSG_NON_ZERO_COEFFS: usize = 100; + +use crate::calculate_bit_width; +use crate::circuits::commitments::compute_threshold_decryption_share_commitment; use crate::compute_q_mod_t; use crate::compute_q_mod_t_centered; use crate::get_zkp_modulus; @@ -61,9 +66,13 @@ pub struct Bounds { pub delta_half: BigUint, } -/// Bit widths used by the circuit. +/// Bit widths used by the circuit (e.g. noise bit for range checks). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Bits {} +pub struct Bits { + pub noise_bit: u32, + /// Coefficient bit width for decryption-share commitments (aligned with threshold share_decryption BIT_D). + pub d_bit: u32, +} /// Circuit config: moduli count, plaintext modulus, q_inverse_mod_t, bits, bounds, and message polynomial length. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -122,8 +131,19 @@ impl Computation for Bits { type Data = Bounds; type Error = CircuitsErrors; - fn compute(_: Self::Preset, _: &Self::Data) -> Result { - Ok(Bits {}) + fn compute(preset: Self::Preset, data: &Self::Data) -> Result { + let noise_bit = calculate_bit_width(BigInt::from(data.delta_half.clone())); + let (threshold_params, _) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Other(e.to_string()))?; + let ctx = threshold_params + .ctx_at_level(0) + .map_err(|e| CircuitsErrors::Other(format!("ctx_at_level: {:?}", e)))?; + let mut d_bit = 0u32; + for qi in ctx.moduli_operators() { + let qi_bound = (BigInt::from(qi.modulus()) - 1) / 2; + d_bit = d_bit.max(calculate_bit_width(qi_bound)); + } + Ok(Bits { noise_bit, d_bit }) } } @@ -365,10 +385,13 @@ mod tests { fn test_bounds_and_bits_consistency() { let preset = BfvPreset::InsecureThreshold512; let bounds = Bounds::compute(preset, &()).unwrap(); + let bits = Bits::compute(preset, &bounds).unwrap(); assert!(!bounds.delta.is_zero()); assert!(!bounds.delta_half.is_zero()); assert!(bounds.delta_half < bounds.delta); + assert!(bits.noise_bit > 0); + assert!(bits.d_bit > 0); } #[test] @@ -410,5 +433,7 @@ mod tests { out.inputs.crt_quotients.limb(0).coefficients().len(), configs.max_msg_non_zero_coeffs ); + assert!(out.bits.noise_bit > 0); + assert!(out.bits.d_bit > 0); } } From 019c1cc26c7718b1e1150f618a8630af39e94027 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Mar 2026 11:54:53 +0100 Subject: [PATCH 26/31] remove unused to make the lint pass --- .../test/Pricing/Pricing.spec.ts | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/packages/enclave-contracts/test/Pricing/Pricing.spec.ts b/packages/enclave-contracts/test/Pricing/Pricing.spec.ts index 18910e562c..c773286b65 100644 --- a/packages/enclave-contracts/test/Pricing/Pricing.spec.ts +++ b/packages/enclave-contracts/test/Pricing/Pricing.spec.ts @@ -25,8 +25,6 @@ import { Enclave__factory as EnclaveFactory, MockUSDC__factory as MockUSDCFactory, } from "../../types"; -import type { Enclave } from "../../types/contracts/Enclave"; -import type { MockUSDC } from "../../types/contracts/test/MockStableToken.sol/MockUSDC"; import { encodePkProof } from "../fixtures"; const { ethers, ignition, networkHelpers } = await network.connect(); @@ -346,20 +344,6 @@ describe("E3 Pricing", function () { }; }; - // Helper to make a request - const _makeRequest = async ( - enclave: Enclave, - usdcToken: MockUSDC, - requestParams: Parameters[0], - signer?: Signer, - ) => { - const fee = await enclave.getE3Quote(requestParams); - const tokenContract = signer ? usdcToken.connect(signer) : usdcToken; - const enclaveContract = signer ? enclave.connect(signer) : enclave; - await tokenContract.approve(await enclave.getAddress(), fee); - return enclaveContract.request(requestParams); - }; - // ────────────────────────────────────────────────────────────────────────── // getE3Quote() — Parametric Fee Calculation // ────────────────────────────────────────────────────────────────────────── @@ -784,16 +768,7 @@ describe("E3 Pricing", function () { describe("End-to-end request with parametric pricing", function () { it("charges the computed fee and completes successfully", async function () { - const { - enclave, - usdcToken, - request, - // ciphernodeRegistryContract, - // operator1, - // operator2, - // operator3, - owner, - } = await loadFixture(setup); + const { enclave, usdcToken, request, owner } = await loadFixture(setup); const fee = await enclave.getE3Quote(request); const ownerAddr = await owner.getAddress(); From 1c299bbc373c0af972b6dfa542529c7ea35568dc Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Mar 2026 16:27:07 +0100 Subject: [PATCH 27/31] update docs with cryptography --- docs/next.config.js | 1 + docs/package.json | 1 + docs/pages/_app.tsx | 1 + docs/pages/_meta.json | 3 + docs/pages/architecture-overview.mdx | 5 + docs/pages/computation-flow.mdx | 3 + docs/pages/cryptography.mdx | 396 +++++++++++++++++++++++++++ docs/pages/introduction.mdx | 4 + docs/pages/noir-circuits.mdx | 155 +++++------ docs/pages/what-is-e3.mdx | 3 + 10 files changed, 483 insertions(+), 89 deletions(-) create mode 100644 docs/pages/cryptography.mdx diff --git a/docs/next.config.js b/docs/next.config.js index d4e87fa2dd..4f3ac8b89f 100644 --- a/docs/next.config.js +++ b/docs/next.config.js @@ -10,6 +10,7 @@ const nextra = require('nextra') const withNextra = nextra({ theme: 'nextra-theme-docs', themeConfig: './theme.config.jsx', + latex: true, }) module.exports = withNextra({ diff --git a/docs/package.json b/docs/package.json index fe7f08adb4..164ac761a7 100644 --- a/docs/package.json +++ b/docs/package.json @@ -10,6 +10,7 @@ "start": "next start" }, "dependencies": { + "katex": "^0.16.44", "next": "^14.2.1", "nextra": "^2.13.4", "nextra-theme-docs": "^2.13.4", diff --git a/docs/pages/_app.tsx b/docs/pages/_app.tsx index f57275885b..6873a1c71e 100644 --- a/docs/pages/_app.tsx +++ b/docs/pages/_app.tsx @@ -6,6 +6,7 @@ import React from 'react' import type { AppProps } from 'next/app' +import 'katex/dist/katex.min.css' import '../styles/globals.css' function App({ Component, pageProps }: AppProps) { diff --git a/docs/pages/_meta.json b/docs/pages/_meta.json index 94b37ba41d..959fcfcd09 100644 --- a/docs/pages/_meta.json +++ b/docs/pages/_meta.json @@ -19,6 +19,9 @@ "computation-flow": { "title": "E3 Computation Flow" }, + "cryptography": { + "title": "Cryptography" + }, "use-cases": { "title": "Use Cases" }, diff --git a/docs/pages/architecture-overview.mdx b/docs/pages/architecture-overview.mdx index fdf8951914..3065af7c03 100644 --- a/docs/pages/architecture-overview.mdx +++ b/docs/pages/architecture-overview.mdx @@ -157,3 +157,8 @@ As a developer, you'll interact with: - **Interfold smart contracts**: To submit computation requests and retrieve results. - **Compute Providers**: To run your E3P using verifiable or oracle-based systems. - **E3 Smart Contracts**: To verify the inputs and computation result. + +For how DKG, threshold BFV, and ZK circuits (**C0–C7**) fit the protocol story, see +[Cryptography](/cryptography). For the `circuits/` tree, scripts, and toolchain, see +[Noir Circuits](/noir-circuits) and the repo’s +[`circuits/README.md`](https://github.com/gnosisguild/enclave/blob/main/circuits/README.md). diff --git a/docs/pages/computation-flow.mdx b/docs/pages/computation-flow.mdx index 0e467994de..34d0181c68 100644 --- a/docs/pages/computation-flow.mdx +++ b/docs/pages/computation-flow.mdx @@ -118,6 +118,9 @@ event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput, byte Upon successful decryption, rewards are distributed to the active committee members. +**See also:** [Cryptography](/cryptography) (threshold BFV, phases P1–P4, circuit IDs) and +[Noir Circuits](/noir-circuits) (build, lint, `enclave noir`, Rust prover integration). + ### Failure Handling If any phase exceeds its deadline or a fault is detected, the E3 transitions to the `Failed` stage. diff --git a/docs/pages/cryptography.mdx b/docs/pages/cryptography.mdx new file mode 100644 index 0000000000..907a50f8a4 --- /dev/null +++ b/docs/pages/cryptography.mdx @@ -0,0 +1,396 @@ +--- +title: 'Cryptography' +description: + 'Threshold BFV, zero-knowledge circuits, and how they map to the Interfold implementation' +--- + +## Cryptography + +This page is a guided tour of the **cryptographic foundations** behind the Interfold: how +**threshold Brakerski–Fan–Vercauteren (BFV)** encryption, **distributed key generation (DKG)**, and +**zero-knowledge proofs** fit together so that ciphernodes can run an **E3** without any single +party holding the full decryption key, while still allowing **anyone** to check that keys, shares, +and decryptions were produced honestly. + +You do not need to read every section in order. If the execution model is still fuzzy, start with +[What is an E3?](/what-is-e3). When you are ready to compile circuits or inspect the tree, the +[Noir Circuits](./noir-circuits) page collects toolchain notes and repository layout. + +## Overview + +### Why threshold cryptography needs more than “honest majority” + +When many parties jointly generate keys or decrypt, classical threshold protocols spread trust +across participants: if enough of them behave, the protocol succeeds. That works well inside a +closed group, but it leaves **observers** with little to verify—they mostly assume the right nodes +ran the right steps. The Interfold design pushes in a different direction, often summarised as the +**coordination trilemma**: keep individual data and intermediate secrets confidential, avoid giving +any one party full control over decryption, and still make each cryptographic step **publicly +checkable** without re-running the whole computation. + +The protocol leans on **zero-knowledge proofs** for the “checkable” part and on **economics** (for +example slashing) so that detected misbehaviour is costly as well as visible. The aim is not to +replace engineering discipline, but to ensure that correctness rests on verifiable mathematics +rather than on blind trust in operators. + +### One execution, end to end + +Each confidential workload is an FHE program running inside an **E3**. After someone requests such a +computation, a committee of **N** [ciphernodes](https://blog.theinterfold.com/ciphernodes/) is drawn +using **sortition**, so membership is tied to verifiable randomness rather than to a central picker. +There is **no trusted dealer** handing out key material: every ciphernode contributes its own share +of the work and takes part in **PVDKG** (publicly verifiable DKG), producing proofs alongside +messages so that deviating parties can be identified and excluded without exposing other nodes’ +secrets. + +Write **A** for the **authorised** committee for that E3—initially all **N** members, then shrinking +as proofs fail and bad actors drop out of the active path. When DKG finishes, the network publishes +a **threshold public key** that serves as the reference for everyone who will encrypt inputs. The +numeric threshold **T** (how many partial decryptions you need later) is fixed at **program** level; +this page does not pin a single formula, because deployments can tune it. + +From there the story is easier to tell in order. Users encrypt under the threshold key. A **compute +provider** evaluates the FHE program on those ciphertexts; in practice that step usually ships with +its own **correct execution** story (for example attested FHE inside a zkVM), separate from the Noir +circuits that handle keys and decryptions. When the job is done, at least **T+1** honest members of +**A** publish **partial decryptions**, and an **aggregator** combines them into plaintext +**without** ever reassembling the full secret key in one place. + +### How the product phases line up + +It helps to name four **product phases**, P1 through P4, which reappear throughout the docs and in +the implementation. Think of them as chapters in the same book: DKG establishes the committee’s +keys, aggregation turns many contributions into one public key, users encrypt, then the system +decrypts after homomorphic evaluation. + +- **P1 — Distributed key generation** is the heavy chapter. Each ciphernode builds its **secret key + contribution** and matching **public key share**, splits the sensitive parts into **Shamir** + shares, and sends those shares encrypted under each peer’s **individual** BFV public key—because + shares must not travel in the clear. Parallel **smudging noise** material follows the same + pattern, since decryption later relies on noise that is also shared and proved. Proofs accompany + generation, share computation, encryption, and the local decrypt-and-aggregate step; as they fail + or succeed, the set **A** updates so that only honest nodes remain on the critical path. + +- **P2 — Public key aggregation** is deliberately short: someone acting as aggregator sums the + surviving public key shares into the single **threshold public key**. Circuit + [**C5**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_aggregation) + checks that this sum matches what earlier proofs committed to. + +- **P3 — User encryption** is where data providers enter. They encrypt under the aggregated + threshold key; the in-tree packages + [`user_data_encryption_ct0`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/user_data_encryption_ct0), + [`user_data_encryption_ct1`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/user_data_encryption_ct1), + and + [`user_data_encryption`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/user_data_encryption) + implement a **GRECO**-style proof pattern: show that the ciphertext is a valid BFV encryption + without revealing the message or the randomness. + +- **P4 — Threshold decryption** closes the loop after homomorphic evaluation. Ciphernodes in **A** + produce partial decryptions; circuits + [**C6**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/share_decryption) + and + [**C7**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/decrypted_shares_aggregation) + prove the shares and the final recombination. The FHE evaluation itself sits **between** P3 and + P4: it consumes user ciphertexts and yields the output ciphertext that **C6** and **C7** reason + about. Those evaluation proofs are **not** the same Noir binaries as DKG or decryption; what you + get depends on the FHE backend you plug in. + +### Wiring phases to packages in the repository + +Before any of this runs in production, a one-shot **`config`** proof (see +[`circuits/bin/config`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/config)) +checks that parameter presets—CRT limbs, noise bounds, Reed–Solomon parity matrices, and the +like—match across the deployment. After that, the table below is the map most people keep open while +reading the code: it connects the narrative phases P1–P4 to the **C0–C7** labels used in issues, +logs, and the +[`circuits/README.md`](https://github.com/gnosisguild/enclave/blob/main/circuits/README.md) index. + +| Phase | Circuits | What you are looking at | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | +| **Config** | [`config`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/config) | One-time consistency of presets before any E3. | +| **P1 — DKG** | [**C0**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/pk), [**C1**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_generation), [**C2**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_computation), [**C3**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_encryption), [**C4**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_decryption) | Individual pk; TrBFV contribution; Shamir + parity (recursive **C2**); encrypt shares; decrypt and aggregate. | +| **P2 — Aggregation** | [**C5**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_aggregation) | Sum honest public key shares into the threshold pk. | +| **P3 — User encryption** | [`user_data_encryption_*`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/user_data_encryption_ct0) | Valid BFV encryption under the aggregated key. | +| **P4 — Decryption** | [**C6**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/share_decryption), [**C7**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/decrypted_shares_aggregation) | Partial decryptions; Lagrange combination, CRT lift, decode to plaintext. | + +### Sequence diagram + +The diagram below is the same story as a swimlane sketch: committee work, aggregation, users, +compute, then threshold decryption. It is not a substitute for the precise package list, but it +gives a sense of ordering when you read events on-chain or in logs. + +```mermaid +sequenceDiagram + participant CMT as Committee + participant AGG as Aggregator + participant USR as Users + participant CP as Compute Provider + participant BC as Blockchain + + Note over CMT,AGG: P1 — Distributed Key Generation + loop Each authorised node + CMT->>CMT: Individual sk/pk + proof (C0) + CMT->>CMT: Contribution + smudging + proofs (C1, C2) + CMT->>CMT: Encrypt and exchange shares + proofs (C3) + CMT->>CMT: Decrypt shares + proofs (C4) + end + + Note over CMT,AGG: P2 — Public Key Aggregation + CMT-->>AGG: pk shares + proofs + AGG-->>BC: Threshold public key + proof (C5) + + Note over USR,BC: P3 — User Encryption + USR-->>BC: Encrypted inputs + proofs + + BC-->>CP: Encrypted inputs + Note over CP: FHE program (off these circuits) + CP-->>BC: Encrypted output + execution attestation + + Note over CMT,AGG: P4 — Threshold Decryption + BC-->>CMT: Encrypted output + loop At least T+1 shares + CMT->>CMT: Partial decryption + proof (C6) + CMT-->>AGG: Share + proof + end + AGG-->>BC: Plaintext + proof (C7) +``` + +## Publicly verifiable threshold BFV (PV-TBFV) + +**PV-TBFV** means **publicly verifiable threshold Ring-BFV**: every sensitive step you would worry +about in a centralised system—generating key material, moving it under encryption, aggregating +public keys, taking user ciphertexts, emitting decryption shares—is backed by a statement that +verifiers can check **without** seeing the private witnesses. The DKG slice of that story is what we +call **PVDKG**; in the repository it lands in **C0** through **C4** (with +[**C1**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_generation) +living under +[`circuits/bin/threshold`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold) +and the rest of the DKG chain under +[`circuits/bin/dkg`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg)). + +Taken together, the design is aiming for a few properties that reinforce each other. **Threshold +security** means decrypting user data should require at least **T+1** cooperating honest shares for +the chosen **T**. **Public verifiability** means observers can validate each proof without +participating in the protocol. **Homomorphic evaluation** is what lets ciphertexts leave P3 and +enter the FHE engine before P4. **Robustness** means bad behaviour should show up as failing proofs, +so the protocol can update **A** and the economic layer can apply penalties when appropriate. + +### Commitments and why polynomials do not land on-chain whole + +BFV keys and intermediate polynomials are enormous under post-quantum parameters. Publishing them in +full for every step would be impractical on-chain, so each circuit hashes the relevant outputs into +a short **commitment** using the **SAFE** sponge (see +[`circuits/lib/src/math/safe.nr`](https://github.com/gnosisguild/enclave/blob/main/circuits/lib/src/math/safe.nr)), +built from **Poseidon2** and **Keccak256**. The story that emerges is a **chain of accountability**: +later circuits take earlier commitments as **public inputs**, re-hash the private witnesses inside +the proof, and check equality. That is how +[**C1**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_generation) +connects to +[**C5**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_aggregation), +and how [**C4**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_decryption) +hands off to +[**C6**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/share_decryption), +without ever dumping raw polynomials into calldata. Domain labels (`DS_*` in +[`commitments.nr`](https://github.com/gnosisguild/enclave/blob/main/circuits/lib/src/math/commitments.nr)) +keep different commitment kinds from being confused with one another. + +## Key terminology + +The same **BFV** ring arithmetic appears twice in the system: once for **individual** keys that +exist only to protect share traffic during DKG, and once for the **threshold** key that users and +decryption actually rely on. Mixing those two notions is the most common source of confusion, so the +table below keeps the vocabulary straight. Treat it as a glossary you can return to while reading +logs or Noir sources. + +| Term | Meaning | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Individual key pair** | Per-node BFV keys used **only** to encrypt **Shamir shares** in transit during DKG. [**C0**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/pk) commits the individual public key; [**C3**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_encryption) binds encryption to that commitment. | +| **Secret key contribution** | One node’s additive piece of the threshold secret **before** Shamir splitting in the usual DKG story. | +| **Public key share** | The TrBFV public material for that contribution; [**C1**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_generation) proves BFV keygen relations; [**C5**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_aggregation) sums these shares. | +| **Secret key share** | After P1, a node’s **Shamir** share of the threshold secret (not the same as “contribution”). | +| **Threshold public key** | The BFV key users encrypt to after [**C5**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_aggregation); no single party holds the full matching secret. | +| **Smudging noise** | Noise shared across parties so decryption shares do not leak **sk**; **e_sm** in Noir; proved in [**C1**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_generation)–[**C4**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_decryption) and used in [**C6**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/share_decryption). | +| **Smudging noise contribution / share** | Per-node piece of smudging material and its Shamir shares, parallel to the secret-key track ([**C2**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_computation) / [**C3**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_encryption) / [**C4**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_decryption) smudging track). | + +In Noir, +[**C1**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_generation) +names the BFV encryption-error polynomial **`eek`**. Ring dimension **N**, CRT limbs **L**, +plaintext modulus **t**, and threshold **T** are fixed per deployment; the **PARITY_MATRIX** that +constrains Shamir structure in +[**C2**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_computation) ships +inside the secure preset that +[`config`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/config) checks. + +## Phases & Circuits + +The phase table in **Wiring phases to packages** is the map; this section walks the same path with +implementation detail. Names and paths match +[`circuits/README.md`](https://github.com/gnosisguild/enclave/blob/main/circuits/README.md). + +### P1 — Distributed Key Generation + +The DKG story begins by pinning down each node’s **individual** public key: circuit +[**C0**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/pk) (`dkg/pk`) commits +that key, and later +[**C3**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_encryption) proves +that share ciphertexts use exactly the key material promised in that commitment. + +Next, +[**C1**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_generation) +(`threshold/pk_generation`) proves the TrBFV contribution satisfies BFV key generation—bounded +coefficients, Fiat–Shamir challenge points, Schwartz–Zippel style checks—and emits commitments that +feed both the share pipeline +([**C2**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_computation), for +secret and smudging tracks) and aggregation +([**C5**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_aggregation)). +At one Fiat–Shamir challenge point γ (per CRT limb), the TrBFV public key legs are checked in the +shape below (the implementation names the error polynomial **eek**): + +```math +\begin{aligned} +\mathrm{pk}_0^{[\ell]}(\gamma) &= -a^{[\ell]}(\gamma)\,\mathrm{sk}(\gamma) + \mathrm{eek}(\gamma) + r_2^{[\ell]}(\gamma)(\gamma^N+1) + r_1^{[\ell]}(\gamma)\,q_\ell +\end{aligned} +``` + +(`pk_generation.nr`: **eek** is a **single** polynomial; **r1**, **r2** are per CRT limb; the second +public-key leg is the CRS polynomial **a**, so **pk1**[\ell] = **a**[\ell] by construction—the +circuit’s `verify_evaluations` only evaluates the **pk0** identity at γ.) + +[**C2**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_computation) is not +a single monolithic proof: under +[`dkg/`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg) you will find the +recursive chain—base, chunk, batch, wrapper—that proves Shamir-style sharing and Reed–Solomon parity +on the contribution tensors while keeping proof size manageable. Intuitively, for each CRT limb and +coefficient column, the shares laid out as a vector **y** must satisfy parity with a fixed matrix +**H** so that only low-degree Shamir-style codewords pass (the exact **PARITY_MATRIX** is preset and +checked by `config`): + +```math +\mathbf{H}_j\,\mathbf{y}_{i,j}^{\mathsf{T}} \equiv \mathbf{0} \pmod{q_j} +``` + +[**C3**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_encryption) +(`dkg/share_encryption`) then encrypts each share under the recipient’s individual key; in a fully +connected committee the proof count scales like **|A| × (|A| − 1)** per track. At a Fiat–Shamir +challenge point, the BFV encryption algebra it checks matches the usual two-leg ciphertext (per +limb), schematically: + +```math +\begin{aligned} +\mathsf{ct}_0^{[\ell]}(\gamma) &= \mathsf{pk}_0^{[\ell]}(\gamma)\,u(\gamma) + e_0^{[\ell]}(\gamma) + k_1(\gamma)\,k_0^{[\ell]} + r_1^{[\ell]}(\gamma)\,q_\ell + r_2^{[\ell]}(\gamma)(\gamma^N+1) \\ +\mathsf{ct}_1^{[\ell]}(\gamma) &= \mathsf{pk}_1^{[\ell]}(\gamma)\,u(\gamma) + e_1(\gamma) + p_1^{[\ell]}(\gamma)\,q_\ell + p_2^{[\ell]}(\gamma)(\gamma^N+1) +\end{aligned} +``` + +(`share_encryption.nr`: **k0**[\ell] are fixed scalars per limb; **e1** is one polynomial whose +evaluation is reused for every **ℓ** in the **ct1** leg, matching the witness layout in Noir.) + +Finally, +[**C4**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_decryption) +(`dkg/share_decryption`) runs after local decryption: it proves the opened values match **C2**’s +commitments, aggregates per modulus, and forwards commitments toward threshold decryption +([**C6**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/share_decryption)). +Because the threshold secret is the sum of contributions, each node combines the decrypted shares +into per-limb aggregates that line up with that design: + +```math +\mathrm{agg}[\ell][i] = \sum_{h \in \mathcal{H}} \mathsf{share}[h][\ell][i] \pmod{q_\ell} +``` + +### P2 — Public key aggregation + +[**C5**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/pk_aggregation) +(`threshold/pk_aggregation`) is the bridge from many committed contributions to one threshold public +key: it re-checks each contribution against **C1**, then aggregates. The **first** leg is summed +across honest parties; the **second** leg is the same CRS polynomial **a** for every party, so +**pk1_agg** is **not** a sum—it must equal each party’s **pk1** (see `verify_pk_for_basis` vs +`verify_pk1` in `pk_aggregation.nr`): + +```math +\begin{aligned} +\mathrm{pk}^{\mathrm{agg}}_{0}[\ell][i] &= \sum_{h \in \mathcal{H}} \mathrm{pk}_{0}[h][\ell][i] \pmod{q_\ell} \\ +\mathrm{pk}^{\mathrm{agg}}_{1}[\ell] &= \mathrm{pk}_{1}[h][\ell] = a^{[\ell]} \quad \forall\, h \in \mathcal{H} +\end{aligned} +``` + +### P3 — User encryption + +The user-encryption family proves that ciphertexts are valid BFV encryptions under the aggregated +key—implemented across +[`user_data_encryption_ct0`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/user_data_encryption_ct0), +[`user_data_encryption_ct1`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/user_data_encryption_ct1), +and +[`user_data_encryption`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/user_data_encryption)—following +a GRECO-style encryption proof pattern. Algebraically it is the same BFV encryption statement as in +**C3**, but with **pk0**, **pk1** taken from the aggregated threshold key committed by **C5**; the +split across `ct0` / `ct1` packages exists to keep witness sizes and verifier constraints +manageable. + +### P4 — Threshold decryption + +[**C6**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/share_decryption) +ties each partial decryption to **C4**’s aggregated material and to the homomorphic output +ciphertext. +[**C7**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/decrypted_shares_aggregation) +collects at least **T+1** such shares, applies Lagrange weights, reconstructs across CRT, and +decodes modulo **t**; it runs once at the aggregator after enough valid **C6** proofs are available. + +**C6** proves a partial decryption share **d** built from the output ciphertext, the node’s Shamir +shares of the threshold secret and smudging noise (from **C4**), and reduction +quotients—schematically per CRT limb (see `verify_decryption_share_computation` in +`share_decryption.nr`): + +```math +d^{[\ell]}(\gamma) = \mathrm{ct}_0^{[\ell]}(\gamma) + \mathrm{ct}_1^{[\ell]}(\gamma)\,\mathrm{sk}^{[\ell]}(\gamma) + e_{\mathrm{sm}}^{[\ell]}(\gamma) + r_2^{[\ell]}(\gamma)(\gamma^N+1) + r_1^{[\ell]}(\gamma)\,q_\ell +``` + +**C7** first recombines partials with Lagrange coefficients at zero for the participating party +indices **x_i**, then stitches limbs and decodes to the plaintext ring. In short: + +```math +\begin{aligned} +L_i(0) &= \prod_{j \neq i} \frac{-x_j}{x_i - x_j} \pmod{q_\ell} \\ +u^{(\ell)} &= \sum_i d_i^{(\ell)}\,L_i(0) \pmod{q_\ell} +\end{aligned} +``` + +(`decrypted_shares_aggregation.nr` computes the same **L_i(0)** values with an equivalent +product–quotient form and explicit sign handling; **u**[\ell] is then formed coefficient-wise per +`compute_crt_components`.) + +CRT reconstruction checks **u**[\ell] + **r**[\ell] **q_ℓ** = `u_global` coefficient-wise, then +decoding uses the map in `verify_decoding` (BigNum mod **Q**, plaintext modulus **t**, and +`q_inverse_mod_t` as in `Configs`). + +## Footnotes + +Across the stack you will see the same few ideas reused in different algebraic costumes. +**Fiat–Shamir** turns interactive polynomial checks into single challenge points; +**Schwartz–Zippel** lets the circuits test identities at random points instead of +coefficient-by-coefficient. **Reed–Solomon parity** via **PARITY_MATRIX** enforces valid Shamir +structure inside +[**C2**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg/share_computation) +without reconstructing secrets in the clear. **CRT / RNS** arithmetic shows up everywhere in BFV, +with explicit reconstruction where the statement needs a single ring element—notably in +[**C7**](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/threshold/decrypted_shares_aggregation). +Finally, **range checks** keep coefficients inside the bounds that make RLWE-style assumptions +meaningful inside a proof. + +Some statements, especially the large **C2** pipeline, do not fit in one proof: the repository uses +inner UltraHonk proofs, wrapper circuits, and the folding machinery under +[`recursive_aggregation/`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/recursive_aggregation); +see [Noir Circuits](./noir-circuits) for how to build and verify them. + +Formal security definitions and a fuller academic treatment of PV-TBFV will appear separately; when +that reference exists, this page will link to it. + +## Where to go next + +If you are integrating proofs from Rust or following on-chain events, these entry points complement +this page: the +[`circuits/README.md`](https://github.com/gnosisguild/enclave/blob/main/circuits/README.md) index +for package names and **C0–C7** paths; +[`agent/circuits/REFERENCE.md`](https://github.com/gnosisguild/enclave/blob/main/agent/circuits/REFERENCE.md) +for `ProofType`, `CircuitName`, and prover wiring; and the flow-trace chapter +[DKG & computation](https://github.com/gnosisguild/enclave/blob/main/agent/flow-trace/04_DKG_AND_COMPUTATION.md) +for actors, gossip, and proof ordering in a running node. diff --git a/docs/pages/introduction.mdx b/docs/pages/introduction.mdx index 3dc845fe4b..83c20596a0 100644 --- a/docs/pages/introduction.mdx +++ b/docs/pages/introduction.mdx @@ -55,6 +55,10 @@ The Interfold coordinates encrypted execution using a combination of Fully Homom These components enable computation on encrypted inputs, verification of correct execution, and distributed control of decryption through the network of ciphernodes. +For a dedicated walkthrough of threshold BFV, PV-TBFV, and the circuit phases (C0–C7), see +[Cryptography](/cryptography). To compile and lint the Noir workspace, run `enclave noir`, and wire the +prover from Rust, see [Noir Circuits](/noir-circuits). + ### What You'll Learn This documentation covers everything you need to get started with the Interfold, including: diff --git a/docs/pages/noir-circuits.mdx b/docs/pages/noir-circuits.mdx index debf9381c0..e9e1a4e42a 100644 --- a/docs/pages/noir-circuits.mdx +++ b/docs/pages/noir-circuits.mdx @@ -1,128 +1,107 @@ --- title: 'Noir Circuits' -description: 'Build and reuse Noir circuits for Interfold programs' +description: + 'Toolchain, repo layout, compile/lint scripts, and integration hooks for Interfold Noir circuits' --- -# Noir Circuits & Libraries +# Noir Circuits -The Interfold ships a dedicated Noir workspace under `circuits/` plus reusable libraries (SAFE -sponge API, polynomial utilities, modular arithmetic, PVSS circuit primitives, etc.). Use these -circuits as-is or vendor them into your own E3 programs. +This page is for **building and integrating** the Noir workspace under `circuits/`: where packages +live, which scripts to run, and how they connect to the Rust prover and on-chain verifiers. + +For **what** the circuits prove (PV-TBFV, phases **P1–P4**, **C0–C7**, commitments), read +[Cryptography](./cryptography) first. For **Rust types and events** (`ProofType`, `CircuitName`, +public inputs), use the repo’s +[`agent/circuits/REFERENCE.md`](https://github.com/gnosisguild/enclave/blob/main/agent/circuits/REFERENCE.md). +For **runtime ordering** (actors, gossip, proof pipeline), see +[`agent/flow-trace/04_DKG_AND_COMPUTATION.md`](https://github.com/gnosisguild/enclave/blob/main/agent/flow-trace/04_DKG_AND_COMPUTATION.md). + +The canonical **package index** (paths, **CircuitName**, C0–C7 IDs) is +[`circuits/README.md`](https://github.com/gnosisguild/enclave/blob/main/circuits/README.md) in the +open-source tree—Noir sources are the source of truth; treat docs as orientation only. ## Workspace layout -``` +```text circuits/ -├── lib/ -│ ├── Nargo.toml # library manifest +├── lib/ # Shared library (all `bin/` packages depend on this) │ └── src/ -│ ├── lib.nr # top-level library entry -│ ├── configs/ # BFV parameter configurations (default, insecure, secure, committee) -│ ├── core/ # DKG and threshold PVSS circuit implementations -│ │ ├── dkg/ # pk, share_computation, share_encryption, share_decryption -│ │ └── threshold/ # pk_generation, pk_aggregation, share_decryption, -│ │ # decrypted_shares_aggregation, user_data_encryption_ct0/ct1 -│ └── math/ # polynomial, SAFE sponge, modular arithmetic (U128), commitments +│ ├── lib.nr +│ ├── configs/ # BFV presets (default / secure / insecure / committee) +│ ├── core/dkg/ # C0, C2–C4 (core logic) +│ ├── core/threshold/ # C1, C5, P3, C6, C7 (core logic) +│ └── math/ # Polynomials, SAFE sponge, ModU128, commitments ├── bin/ -│ ├── config/ # configuration validation circuit -│ ├── dkg/ # DKG sub-circuits (pk, sk_share_computation, e_sm_share_computation, -│ │ # share_encryption, share_decryption) -│ ├── threshold/ # threshold sub-circuits (pk_generation, pk_aggregation, -│ │ # share_decryption, user_data_encryption_ct0/ct1, -│ │ # decrypted_shares_aggregation) -│ ├── insecure/ # legacy insecure-parameter circuits for development -│ ├── production/ # production-parameter circuits (placeholder) -│ └── recursive_aggregation/ # recursive proof aggregation -│ ├── fold/ # fold circuit (uses Aztec bb_proof_verification) -│ └── wrapper/ # recursive wrappers for DKG and threshold circuits -│ ├── dkg/ # pk, share_computation, share_encryption, share_decryption -│ └── threshold/ # pk_generation, pk_aggregation, share_decryption, -│ # decrypted_shares_aggregation, user_data_encryption -├── benchmarks/ # benchmark scripts and results -└── .gitignore +│ ├── config/ # Pre-deployment preset consistency (`config` circuit) +│ ├── dkg/ # C0, C2 pipeline, C3, C4 +│ ├── threshold/ # C1, C5, user_data_encryption*, C6, C7 +│ └── recursive_aggregation/ # fold/ + wrapper/{dkg,threshold}/ +└── benchmarks/ # Timing scripts and reports ``` -The core library at `circuits/lib/` contains reusable modules for polynomial arithmetic, the SAFE -sponge API, modular reduction helpers (including `U128` large-integer operations), and BFV -DKG/threshold PVSS primitives. Binary circuits under `circuits/bin/` implement specific proof -programs (DKG key generation, share encryption/decryption, threshold aggregation, recursive folding, -etc.). The library depends on `poseidon`, `keccak256` (from noir-lang), and `bignum` (from -gnosisguild/noir-bignum). +Binary packages map to **C0–C7** and operational phases as on the [Cryptography](./cryptography) +page (**Phases & Circuits** section). Large statements (especially **C2**) use **inner** proofs plus +**wrapper** + **fold** under `recursive_aggregation/`. -To reference the library from an external project: +## Using `circuits/lib` from another Noir project + +Add a path or git dependency in your `Nargo.toml` (pin a **tag or rev** that matches your prover): ```toml [dependencies] enclave_lib = { git = "https://github.com/gnosisguild/enclave", tag = "v0.1.15", directory = "circuits/lib" } ``` -## Tooling requirements +Check `crates/zk-prover/Cargo.toml` / `versions.json` in the same release for compatible **nargo** +and **bb** versions. + +## Toolchain -Install the Noir toolchain: +Install Noir (or rely on CI / Docker): ```bash -curl -L https://noir-lang.org/install | bash # installs nargo + bb -nargo --version # v1.0.0-beta.16+ (see zk-prover Cargo.toml for exact tag) -bb --version # v3.0.0-nightly.20260102+ (see crates/zk-prover/versions.json) +curl -L https://noir-lang.org/install | bash # nargo + bb +nargo --version # match zk-prover / versions.json +bb --version ``` -> **Tip:** Use `enclave noir setup` instead of manual installation — it downloads and verifies the -> exact versions of `bb` and pre-compiled circuit artifacts needed by the Enclave ZK prover. - -### CLI ZK Prover Setup - -The Enclave CLI can manage the ZK prover toolchain (Barretenberg and circuits) for you: +**Recommended:** use the Enclave CLI so **bb** and artifacts match the prover: ```bash -# Check current ZK prover status (installed versions, paths) enclave noir status - -# Install or update the ZK prover components (bb binary, circuit artifacts) enclave noir setup - -# Force reinstall all components -enclave noir setup --force +enclave noir setup --force # reinstall ``` -`enclave noir status` shows installation paths, versions, and whether all components are present. -`enclave noir setup` downloads and installs the correct versions of `bb` and pre-compiled circuit -artifacts needed for proof generation. +## Compile, format, test -## Formatting & compilation - -Run the helper scripts from the repo root or reproduce them in your project: +From the **repository root**: ```bash -pnpm tsx scripts/build-circuits.ts # compile circuits, generate VKs, prepare release artifacts -./scripts/lint-circuits.sh # nargo fmt --check && nargo check across all circuit directories -./scripts/test-circuits.sh # run circuit unit tests (nargo test on circuits/lib) +pnpm tsx scripts/build-circuits.ts # compile, VKs, release artifacts (see script for scope) +./scripts/lint-circuits.sh # nargo fmt --check && nargo check (lib + bin areas) +./scripts/test-circuits.sh # nargo test on circuits/lib ``` -`lint-circuits.sh` checks formatting and validates circuits across `lib`, `bin/config`, -`bin/recursive_aggregation`, `bin/dkg`, and `bin/threshold`. Both shell scripts exit early (without -failing the whole build) if `nargo` is not installed, which makes CI happy when Noir is optional. +`lint-circuits.sh` and `test-circuits.sh` exit successfully when **nargo** is missing so +optional-Noir CI stays green. -## Exporting verifiers +## Solidity verifiers (example: CRISP) -Projects such as CRISP compile Noir circuits into Solidity verifiers using `bb`: +Projects such as CRISP compile circuits with **bb** and emit a verifier contract. Example +entrypoint: ```bash cd examples/CRISP ./scripts/compile_circuits.sh ``` -The script performs: - -1. Compiles enclave dependency circuits (`user_data_encryption_ct0`, `user_data_encryption_ct1`, and - the recursive wrapper for `user_data_encryption`) -2. Compiles the CRISP-specific circuit and fold circuit (recursive aggregation) -3. `bb write_vk` with `--oracle_hash keccak` on the fold circuit to emit the verification key -4. `bb write_solidity_verifier` to produce `CRISPVerifier.sol` from the fold VK -5. Copies the generated verifier into `packages/crisp-contracts/contracts/` -6. Replaces the license header and formats with Prettier +That flow typically: compile dependency circuits (`user_data_encryption_ct0` / `ct1` / wrapper), +compile the app-specific and fold circuits, `bb write_vk` (e.g. `--oracle_hash keccak` on the fold +target), `bb write_solidity_verifier`, then copy/format into the package contracts. Adapt paths for +your own fold root and output dir. -Adapt the script for your own circuits by changing the circuit paths and output directory. On-chain -verification uses the `ICircuitVerifier` interface: +On-chain verification matches the usual pattern: ```solidity interface ICircuitVerifier { @@ -133,14 +112,12 @@ interface ICircuitVerifier { } ``` -## Integrating with E3 programs +## Rust integration -- Import the library in your circuit's `Nargo.toml` (see the dependency snippet above) -- Generate Solidity verifiers and deploy them as `ICircuitVerifier` implementations -- Reference the compiled artifacts when deploying via the template or CRISP scripts -- For Rust-side proof generation, the `e3-zk-prover` crate handles witness generation, proof - generation (via `bb prove`), and verification (via `bb verify`) automatically +- **`e3-zk-prover`** (`crates/zk-prover`) — witness wiring, `bb prove` / `bb verify`, artifact paths + aligned with `enclave noir setup`. +- **Events / API** — `CircuitName`, `ProofType`, and request payloads are defined next to the node; + see `agent/circuits/REFERENCE.md`. -When adding new circuits, remember to update `package.json` scripts (e.g., `pnpm dev:all`) so they -call your compile script before spinning up the dev environment. This keeps your verifiers in sync -with the Noir source. +When you add or rename a circuit package, update root **`package.json`** / CI scripts that call +`build-circuits.ts` or `lint-circuits.sh` so dev and deployment stay in sync with Noir sources. diff --git a/docs/pages/what-is-e3.mdx b/docs/pages/what-is-e3.mdx index ad24fa301e..19947be596 100644 --- a/docs/pages/what-is-e3.mdx +++ b/docs/pages/what-is-e3.mdx @@ -15,6 +15,9 @@ confidentiality and support correct execution: Together, these components make it possible to compute across private inputs without exposing the underlying data. +The docs site goes deeper on how the ciphernode stack implements this in practice (BFV, DKG, proofs +**C0–C7**) in [Cryptography](/cryptography); tooling lives under [Noir Circuits](/noir-circuits). + ### When to Use E3s E3s are useful when you need to: From 17823f4379c75ca55ee555e7310a7637949889f2 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Mar 2026 16:47:12 +0100 Subject: [PATCH 28/31] update readmes --- circuits/README.md | 129 ++++++++++++ circuits/benchmarks/README.md | 62 +----- circuits/bin/config/README.md | 44 +--- .../dkg/e_sm_share_computation_base/README.md | 9 + circuits/bin/dkg/pk/README.md | 46 +--- circuits/bin/dkg/share_computation/README.md | 12 ++ .../bin/dkg/share_computation_chunk/README.md | 9 + .../share_computation_chunk_batch/README.md | 10 + circuits/bin/dkg/share_decryption/README.md | 59 +----- circuits/bin/dkg/share_encryption/README.md | 75 +------ .../dkg/sk_share_computation_base/README.md | 9 + .../bin/recursive_aggregation/fold/README.md | 87 +------- .../recursive_aggregation/wrapper/README.md | 74 +++---- .../decrypted_shares_aggregation/README.md | 10 + .../bin/threshold/pk_aggregation/README.md | 45 +--- .../bin/threshold/pk_generation/README.md | 59 +----- .../bin/threshold/share_decryption/README.md | 60 +----- .../threshold/user_data_encryption/README.md | 9 + .../user_data_encryption_ct0/README.md | 10 + .../user_data_encryption_ct1/README.md | 9 + circuits/lib/src/README.md | 199 ++++-------------- circuits/lib/src/lib.nr | 14 +- docs/pages/cryptography.mdx | 3 - pnpm-lock.yaml | 33 +-- 24 files changed, 394 insertions(+), 682 deletions(-) create mode 100644 circuits/README.md create mode 100644 circuits/bin/dkg/e_sm_share_computation_base/README.md create mode 100644 circuits/bin/dkg/share_computation/README.md create mode 100644 circuits/bin/dkg/share_computation_chunk/README.md create mode 100644 circuits/bin/dkg/share_computation_chunk_batch/README.md create mode 100644 circuits/bin/dkg/sk_share_computation_base/README.md create mode 100644 circuits/bin/threshold/decrypted_shares_aggregation/README.md create mode 100644 circuits/bin/threshold/user_data_encryption/README.md create mode 100644 circuits/bin/threshold/user_data_encryption_ct0/README.md create mode 100644 circuits/bin/threshold/user_data_encryption_ct1/README.md diff --git a/circuits/README.md b/circuits/README.md new file mode 100644 index 0000000000..d967f2e620 --- /dev/null +++ b/circuits/README.md @@ -0,0 +1,129 @@ +# Circuits + +This directory holds the **Noir** implementation of Interfold’s zero-knowledge circuits: distributed +key generation and encrypted share handling (**BFV**), threshold key generation, user encryption, +and threshold decryption (**TrBFV**), together with recursive proof aggregation. + +The Noir sources and tests in this tree are authoritative for constraints and public inputs. +Everything else—docs, diagrams, comments—is there to help you navigate; when in doubt, trust the +code. + +```text +circuits/ +├── lib/ +│ └── src/ +│ ├── configs/ # BFV / CRT parameter presets +│ ├── core/dkg/ # Shared logic: C0, C2–C4 +│ ├── core/threshold/ # Shared logic: C1, C5, P3, C6, C7 +│ └── math/ # Polynomials, SAFE, commitments, modular arithmetic +├── bin/ +│ ├── config/ # Deployment-time consistency checks on presets +│ ├── dkg/ # DKG packages and C2 proof pipeline +│ ├── threshold/ # TrBFV, user encryption, threshold decryption +│ └── recursive_aggregation/ +│ ├── fold/ +│ └── wrapper/ +│ ├── dkg/ +│ └── threshold/ +└── benchmarks/ +``` + +The shared library is the Nargo package **`lib`** (`lib/Nargo.toml`). All packages under `bin/` +depend on it; module structure is documented in [`lib/src/README.md`](lib/src/README.md). + +Packages under `bin/` with a `Nargo.toml` are build targets. Directory names align with the +**`CircuitName`** enum in `crates/events` via `CircuitName::group()` and `CircuitName::dir_path()`. +Workspace manifests also exist at `dkg/` and `threshold/` for grouped builds. + +## Circuit package index + +The tables below map **`circuits/bin/` paths** to **circuit labels** (C0–C7) and **`CircuitName`** +values used in Rust. Phases **P1–P4** are a product-level grouping of the same protocol steps; for +how phases, commitments, and circuit IDs line up end to end, read +[Cryptography](https://docs.theinterfold.com/cryptography) (source: +[`docs/pages/cryptography.mdx`](../docs/pages/cryptography.mdx)). + +**C2** is implemented as a **pipeline** of packages (base, chunk, batch, final `share_computation`), +not a single crate. + +### DKG (`bin/dkg/`) + +| Path | ID | `CircuitName` | Role | +| ------------------------------- | -------- | ---------------------------- | --------------------------------------------- | +| `pk` | C0 | `PkBfv` | Commit to individual BFV public key | +| `sk_share_computation_base` | C2 inner | `SkShareComputationBase` | Shamir tensor for secret contribution | +| `e_sm_share_computation_base` | C2 inner | `ESmShareComputationBase` | Shamir tensor for smudging noise | +| `share_computation_chunk` | C2 inner | `ShareComputationChunk` | Reed–Solomon parity on a coefficient slice | +| `share_computation_chunk_batch` | C2 inner | `ShareComputationChunkBatch` | Binds base proof to a batch of chunk proofs | +| `share_computation` | **C2** | `ShareComputation` | Final C2 step; aggregates inner proofs | +| `share_encryption` | C3 | `ShareEncryption` | BFV encryption of shares under recipient keys | +| `share_decryption` | C4 | `DkgShareDecryption` | Decrypt shares; aggregate; commitments for P4 | + +### Threshold (`bin/threshold/`) + +| Path | ID | `CircuitName` | Role | +| ------------------------------ | ---------- | ---------------------------- | ------------------------------------------------- | +| `pk_generation` | C1 | `PkGeneration` | Threshold public-key contribution | +| `pk_aggregation` | C5 | `PkAggregation` | Aggregate contributions into threshold public key | +| `user_data_encryption_ct0` | P3 | — | User ciphertext (first leg) | +| `user_data_encryption_ct1` | P3 | — | User ciphertext (second leg) | +| `user_data_encryption` | P3 wrapper | — | Wrapper: ct0, ct1, shared randomness | +| `share_decryption` | C6 | `ThresholdShareDecryption` | Partial decryption share | +| `decrypted_shares_aggregation` | C7 | `DecryptedSharesAggregation` | Combine shares; CRT; decode | + +### Recursive aggregation (`bin/recursive_aggregation/`) + +| Path | `CircuitName` | Role | +| --------------------- | ------------- | --------------------------------------------------------- | +| `fold` | `Fold` | Fold two wrapper outputs | +| `wrapper/dkg/*` | — | Verifies inner DKG proofs; compresses public inputs | +| `wrapper/threshold/*` | — | Verifies inner threshold proofs; compresses public inputs | + +Wrapper parameters are documented in +[`wrapper/README.md`](bin/recursive_aggregation/wrapper/README.md). + +### Configuration + +| Path | Role | +| -------- | ----------------------------------------------------------------------- | +| `config` | Validates secure preset constants (CRT moduli, bounds, parity matrices) | + +### Per-package READMEs (`bin/**/README.md`) + +Many packages include a **short** README for navigation in the file tree. Keep them that way: one or +two sentences on what this binary proves, then a small table—**do not** duplicate +[Cryptography](https://docs.theinterfold.com/cryptography) or the full package index. + +| Row | Purpose | +| --- | ------- | +| **Core** (or **Source**) | Link to the shared implementation in [`lib/src/`](lib/src/README.md), or to [`src/main.nr`](bin/dkg/pk/src/main.nr) when the crate is self-contained. | +| **Index** | Link to [**Circuit package index**](#circuit-package-index) in this file. Use the right number of `../` so the path reaches `circuits/README.md` (depends on folder depth; all current READMEs are already consistent). | +| **Docs** | [Noir Circuits](https://docs.theinterfold.com/noir-circuits) for toolchain and layout, plus the repo [source](../docs/pages/noir-circuits.mdx). | + +Optional extras (only when they save a click): **Wrappers** (recursive aggregation), or a second +sentence naming the paired package (e.g. P3 `ct0` / `ct1`). + +## Build and test + +From the repository root: + +```bash +pnpm tsx scripts/build-circuits.ts # compile circuits, verification keys, artifacts +./scripts/lint-circuits.sh # nargo fmt --check; nargo check (skipped if nargo absent) +./scripts/test-circuits.sh # unit tests in circuits/lib +``` + +Pin **nargo** and **bb** to the versions in `crates/zk-prover` and `versions.json`. For local work, +**`enclave noir setup`** installs a toolchain that lines up with the prover and the artifacts CI +produces. Install options and CLI flags are on the +[Noir Circuits](https://docs.theinterfold.com/noir-circuits) page +([`docs/pages/noir-circuits.mdx`](../docs/pages/noir-circuits.mdx)). + +## Related documentation + +| Topic | Location | +| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| Cryptographic model (PV-TBFV, phases P1–P4, circuit identifiers C0–C7) | [Cryptography](https://docs.theinterfold.com/cryptography) · [source](../docs/pages/cryptography.mdx) | +| Toolchain, repository layout, `enclave noir`, compilation | [Noir Circuits](https://docs.theinterfold.com/noir-circuits) · [source](../docs/pages/noir-circuits.mdx) | +| Rust types (`ProofType`, `CircuitName`) | [`signed_proof.rs`](../crates/events/src/enclave_event/signed_proof.rs) · [`proof.rs`](../crates/events/src/enclave_event/proof.rs) | +| Protocol execution (actors, events, proof ordering) | [`agent/flow-trace/04_DKG_AND_COMPUTATION.md`](../agent/flow-trace/04_DKG_AND_COMPUTATION.md) | diff --git a/circuits/benchmarks/README.md b/circuits/benchmarks/README.md index 398bf97d23..2fa47fddab 100644 --- a/circuits/benchmarks/README.md +++ b/circuits/benchmarks/README.md @@ -1,62 +1,20 @@ -# Circuit benchmarks +# Benchmarks -Benchmark the ZK circuits. Configuration is in `config.json` (circuit list, mode, oracles, metrics). +Scripts to compile and time Nargo packages listed in `config.json` (`results_*/report.md`). -## How to run +| | | +| --------------------- | --------------------------------------------------- | +| **Circuits overview** | [README](../README.md) | +| **Docs** | [Noir Circuits](../../docs/pages/noir-circuits.mdx) | -From the **benchmarks** directory: +## Run -```bash -./run_benchmarks.sh [options] -``` - -Or from **scripts**: - -```bash -./scripts/run_benchmarks.sh [options] -``` - -Both use `config.json` in the benchmarks directory by default. - -### Options - -| Option | Description | -| --------------------------- | ----------------------------------------------------------------------------------------------------------- | -| `--mode insecure \| secure` | Run in insecure (default) or secure mode. Overrides `config.json`’s `mode`. | -| `--circuit ` | Run only this circuit (e.g. `dkg/pk` or `config`). If not in `config.json`, runs anyway if the path exists. | -| `--config ` | Use a different config file instead of `config.json`. | -| `--skip-compile` | Reuse existing build artifacts; skip compilation. | -| `--clean` | Remove circuit `target/` directories after the run. | - -### Examples +From this directory: ```bash -# Default: insecure mode, all circuits from config (config circuit is skipped) ./run_benchmarks.sh - -# Secure mode (includes the config circuit) -./run_benchmarks.sh --mode secure - -# Single circuit -./run_benchmarks.sh --circuit threshold/pk_generation -./run_benchmarks.sh --mode secure --circuit config - -# Re-run without recompiling +./run_benchmarks.sh --mode secure --circuit dkg/pk ./run_benchmarks.sh --skip-compile ``` -### Results - -Output goes under `results_/` (e.g. `results_insecure/`, `results_secure/`). A Markdown report -is written to `results_/report.md`. Raw JSON is kept in `results_/raw/` so that a run -with `--circuit` only overwrites that circuit’s file and the report is regenerated from all data -(existing + updated). View the report with `cat results_/report.md` or -`open results_/report.md` (macOS). - -## Secure-only circuits - -The **config** circuit (validates secure configs only) is listed in `config.json` with -`"modes": ["secure"]` so it is only run in secure mode. With the default `"mode": "insecure"` it is -skipped. The script `scripts/run_benchmarks.sh` respects this by filtering circuits by the `modes` -field in `config.json` before running; see the “Circuit-specific modes” comment and the loop that -builds `RUN_CIRCUITS` there. +Options and secure-only **config** circuit behavior are documented in the script and `config.json`. diff --git a/circuits/bin/config/README.md b/circuits/bin/config/README.md index 1972ae6084..f84ec18629 100644 --- a/circuits/bin/config/README.md +++ b/circuits/bin/config/README.md @@ -1,38 +1,10 @@ -# Configuration Verification Circuit +# `config` -The Configuration Verification circuit runs once per deployment to verify all cryptographic -parameters used across both BFV (for DKG share encryption) and Threshold BFV (for user data -encryption) schemes. It provides public proof that the mathematical foundation is correct before any -key generation or encryption occurs. +Pre-deployment circuit: checks **secure** DKG + threshold config constants (CRT, bounds, +Reed–Solomon parity matrices, cross-file consistency). -Rather than trusting parameter configuration, this circuit verifies dozens of mathematical -relationships: CRT moduli products, error bounds, scaling factors, Reed-Solomon parity matrices, and -cross-scheme consistency. - -```mermaid -flowchart TD - subgraph P0["Configuration Verification"] - ConfigCircuit["Configuration Circuit
Verify crypto configs"] - end - - %% External connections - ConfigCircuit -.->|"verified parameters"| NextPhase["→ P1-P4
(trusted configs for all circuits)"] - - style P0 fill:#2B3BD5,stroke:#2E75B6,stroke-width:3px - style NextPhase fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - linkStyle 0 stroke:#808080,stroke-width:2px -``` - -## Metadata - -- **Phase**: Pre-deployment (one-time configuration verification). -- **Runs**: 1 (once per deployment, before any P1–P4 circuits run). -- **Requires**: Configured parameter sets from [`configs/secure`](../../../lib/src/configs/secure). -- **Output(s)**: Single proof that all parameters are valid, consumed by all P1-P4 circuits. -- **Data Flow**: `Parameter Sets → Config Circuit → Verified Configs → All Circuits (P1-P4)` -- **Verification Categories**: - - DKG (BFV) Parameters: [`configs/secure/dkg.nr`](../../../lib/src/configs/secure/dkg.nr) - - Threshold BFV Parameters: - [`configs/secure/threshold.nr`](../../../lib/src/configs/secure/threshold.nr) - - Cross-Configuration Consistency -- **Related Circuits**: All circuits in P1-P4 assume these parameters are correct. +| | | +| ---------- | -------------------------------------------------------------- | +| **Source** | [`src/main.nr`](src/main.nr) (uses `lib/src/configs/secure/`) | +| **Index** | [Circuit package index](../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/dkg/e_sm_share_computation_base/README.md b/circuits/bin/dkg/e_sm_share_computation_base/README.md new file mode 100644 index 0000000000..fe62ff9d44 --- /dev/null +++ b/circuits/bin/dkg/e_sm_share_computation_base/README.md @@ -0,0 +1,9 @@ +# `e_sm_share_computation_base` — C2 (inner) + +Base proof for the **smudging noise** Shamir tensor, bound to C1’s `e_sm` commitment. + +| | | +| --------- | --------------------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/dkg/share_computation/base.nr`](../../../lib/src/core/dkg/share_computation/base.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/dkg/pk/README.md b/circuits/bin/dkg/pk/README.md index cfcda52af2..9f2c6795cc 100644 --- a/circuits/bin/dkg/pk/README.md +++ b/circuits/bin/dkg/pk/README.md @@ -1,41 +1,9 @@ -# [C0] BFV Public Key Commitment (`pk`) +# `pk` — C0 -The BFV Public Key Commitment circuit (C0) is the first circuit executed in Phase 1 (Distributed Key -Generation). Each ciphernode creates a cryptographic commitment to their DKG public key, which will -be used exclusively for encrypting secret shares during the PVDKG phase. +BFV **individual** public key commitment: binds the ciphernode’s share-encryption key used in C3. -Rather than verifying the key generation process, this circuit establishes a _binding commitment_ -that prevents key substitution attacks. The commitment acts as an immutable reference—any attempt to -use a different key in later encryption or decryption steps will be cryptographically detected. - -```mermaid -flowchart TD - %% Input from config circuit - Input0["Config
Verification"] -.->|"verified configs"| C0 - - subgraph Focus["C0"] - C0["Commit to DKG public key"] - end - - %% Output to C3a and C3b - C0 -->|"commit(pk_dkg)"| Output1["→ C3a
share-encryption-sk"] - C0 -->|"commit(pk_dkg)"| Output2["→ C3b
share-encryption-e-sm"] - - style Focus fill:#E8A87C,stroke:#C97A4A,stroke-width:3px - style Input0 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Output2 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - - linkStyle 0 stroke:#808080,stroke-width:2px - linkStyle 1 stroke:#808080,stroke-width:2px - linkStyle 2 stroke:#808080,stroke-width:2px -``` - -- **Phase**: P1 (DKG). -- **Runs**: N_PARTIES (once per ciphernode at the start of key generation). -- **Requires**: [`config`](../../config) circuit (pre-deployment parameter verification). -- **Output(s)**: `commit(pk_dkg)` consumed by C3a / C3b - ([`dkg/share_encryption`](../share_encryption)) -- **Data Flow**: `Config → C0 → commit(pk_dkg) → C3a, C3b` -- **Commitment Function**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - - `compute_dkg_pk_commitment()` +| | | +| --------- | ----------------------------------------------------------------- | +| **Core** | [`lib/src/core/dkg/pk.nr`](../../../lib/src/core/dkg/pk.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/dkg/share_computation/README.md b/circuits/bin/dkg/share_computation/README.md new file mode 100644 index 0000000000..ccf09f5519 --- /dev/null +++ b/circuits/bin/dkg/share_computation/README.md @@ -0,0 +1,12 @@ +# `share_computation` — C2 (final wrapper) + +Verifies **`N_BATCHES`** inner batch proofs (non-ZK UltraHonk verify), folds their commitments, and +checks the VK genealogy (`key_hash`). This is the **ProofType C2a / C2b** surface circuit — upstream +packages are `sk_share_computation_base` / `e_sm_share_computation_base`, `share_computation_chunk`, +`share_computation_chunk_batch`. + +| | | +| --------- | ------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/dkg/share_computation/`](../../../lib/src/core/dkg/share_computation/) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/dkg/share_computation_chunk/README.md b/circuits/bin/dkg/share_computation_chunk/README.md new file mode 100644 index 0000000000..0560eb2cab --- /dev/null +++ b/circuits/bin/dkg/share_computation_chunk/README.md @@ -0,0 +1,9 @@ +# `share_computation_chunk` — C2 (inner) + +Reed–Solomon parity checks on a **slice** of the public `y` tensor (see `PARITY_MATRIX` in configs). + +| | | +| --------- | ----------------------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/dkg/share_computation/chunk.nr`](../../../lib/src/core/dkg/share_computation/chunk.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/dkg/share_computation_chunk_batch/README.md b/circuits/bin/dkg/share_computation_chunk_batch/README.md new file mode 100644 index 0000000000..91485d7f4f --- /dev/null +++ b/circuits/bin/dkg/share_computation_chunk_batch/README.md @@ -0,0 +1,10 @@ +# `share_computation_chunk_batch` — C2 (inner) + +Verifies the **base** proof plus a batch of **chunk** proofs and enforces consistency of the `y` +slice across them. + +| | | +| --------- | ----------------------------------------------------------------- | +| **Core** | (invokes base + chunk layouts; configs in `lib`) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/dkg/share_decryption/README.md b/circuits/bin/dkg/share_decryption/README.md index 5f298a54e4..df2972edef 100644 --- a/circuits/bin/dkg/share_decryption/README.md +++ b/circuits/bin/dkg/share_decryption/README.md @@ -1,53 +1,10 @@ -# [C4a & C4b] Share Decryption & Aggregation (`share_decryption`) +# `share_decryption` — C4a / C4b -The Share Decryption circuit verifies that each ciphernode correctly decrypted the shares they -received, and that those shares were honestly aggregated into a single combined value. This closes -the DKG loop: shares were committed in _C2_, encrypted in _C3_, and are now verified upon decryption -and aggregated here. +Decrypts incoming shares and aggregates them; outputs commitments consumed by **C6** (threshold +decryption). -This is a single circuit used for both variants: **C4a** handles secret key (`sk`) shares, consuming -commitments from _C2a_; **C4b** handles smudging noise (`e_sm`) shares, consuming commitments from -_C2b_. The verification logic and all input types are identical — only the source of -`expected_commitments` differs between the two instantiations. - -```mermaid -flowchart TD - Input2a["C2a
share-computation-sk"] -.->|"commit(sk_share)"| C4 - Input2b["C2b
share-computation-e-sm"] -.->|"commit(e_sm_share)"| C4 - - subgraph Focus["C4a & C4b"] - C4["Verify decrypted shares & aggregate"] - end - - C4 -->|"commit(agg_sk)"| Output1["→ C6
threshold-share-decryption"] - C4 -->|"commit(agg_e_sm)"| Output1 - - style Focus fill:#5B9BD5,stroke:#2E75B6,stroke-width:3px - style Input2a fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Input2b fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - - linkStyle 0 stroke:#808080,stroke-width:2px - linkStyle 1 stroke:#808080,stroke-width:2px - linkStyle 2 stroke:#808080,stroke-width:2px - linkStyle 3 stroke:#808080,stroke-width:2px -``` - -## Metadata - -- **Phase**: P1 (DKG). -- **Runs**: N_PARTIES per variant (once per ciphernode; each instance aggregates all shares received - from other ciphernodes). -- **Requires**: - - C4a: `commit(sk_share)` from C2a ([`dkg/sk_share_computation`](../sk_share_computation)) - - C4b: `commit(e_sm_share)` from C2b ([`dkg/e_sm_share_computation`](../e_sm_share_computation)) -- **Output(s)**: - - C4a: `commit(agg_sk)` → C6 ([`threshold/share_decryption`](../../threshold/share_decryption)) - - C4b: `commit(agg_e_sm)` → C6 ([`threshold/share_decryption`](../../threshold/share_decryption)) -- **Data Flow**: `C2a → C4a → commit(agg_sk) → C6` and `C2b → C4b → commit(agg_e_sm) → C6` -- **Commitment Functions**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - - `compute_share_encryption_commitment_from_message()`, `compute_aggregated_shares_commitment()` -- **Related Circuits**: - - C2a [`dkg/sk_share_computation`](../sk_share_computation) - - C2b [`dkg/e_sm_share_computation`](../e_sm_share_computation) - - C6 [`threshold/share_decryption`](../../threshold/share_decryption) +| | | +| --------- | --------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/dkg/share_decryption.nr`](../../../lib/src/core/dkg/share_decryption.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/dkg/share_encryption/README.md b/circuits/bin/dkg/share_encryption/README.md index 33db540672..e5d9367483 100644 --- a/circuits/bin/dkg/share_encryption/README.md +++ b/circuits/bin/dkg/share_encryption/README.md @@ -1,69 +1,10 @@ -# [C3a & C3b] Share Encryption (`share_encryption`) +# `share_encryption` — C3a / C3b -The Share Encryption circuit verifies that each ciphernode correctly encrypted a secret share under -the recipient's DKG public key. After shares are verified and committed in _C2a/C2b_, they must be -encrypted for secure peer-to-peer transmission — this circuit proves the encryption was formed -correctly without revealing the plaintext share or the encryption randomness. +BFV-encrypts each Shamir share under the recipient’s **individual** public key. Same Nargo package +for both variants; witnesses differ (`expected_message_commitment` from C2a vs C2b). -This is a single circuit used for both variants: **C3a** encrypts secret key (`sk`) shares, taking -its message commitment from _C2a_; **C3b** encrypts smudging noise (`e_sm`) shares, taking its -message commitment from _C2b_. The verification logic and all input types are identical — only the -value of `expected_message_commitment` differs between the two instantiations. - -```mermaid -flowchart TD - Input0["C0
pk"] -.->|"commit(pk_dkg)"| C3 - Input2a["C2a
share-computation-sk"] -.->|"commit(sk_share[i][j])"| C3 - Input2b["C2b
share-computation-e-sm"] -.->|"commit(e_sm_share[i][j])"| C3 - - subgraph Focus["C3a & C3b"] - C3["Encrypt share under DKG pk"] - end - - C3 -->|"ct(sk_share)"| Output1["→ C4a
share-decryption-sk"] - C3 -->|"ct(e_sm_share)"| Output2["→ C4b
share-decryption-e-sm"] - - style Focus fill:#5B9BD5,stroke:#2E75B6,stroke-width:3px - style Input0 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Input2a fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Input2b fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Output2 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - - linkStyle 0 stroke:#808080,stroke-width:2px - linkStyle 1 stroke:#808080,stroke-width:2px - linkStyle 2 stroke:#808080,stroke-width:2px - linkStyle 3 stroke:#808080,stroke-width:2px - linkStyle 4 stroke:#808080,stroke-width:2px -``` - -## Metadata - -- **Phase**: P1 (DKG). -- **Runs**: N_PARTIES × (N_PARTIES - 1) x variant (`e_sm` && `sk`) (each ciphernode encrypts one - share for each of the other N_PARTIES - 1 recipients). -- **Requires**: - - `commit(pk_dkg)` from C0 ([`dkg/pk`](../pk)) - - C3a: `commit(sk_share[party_idx][mod_idx])` from C2a - ([`dkg/sk_share_computation`](../sk_share_computation)) - - C3b: `commit(e_sm_share[party_idx][mod_idx])` from C2b - ([`dkg/e_sm_share_computation`](../e_sm_share_computation)) -- **Output(s)**: - - C3a: encrypted SK share ciphertexts `ct(sk_share)` → C4a - ([`dkg/share_decryption`](../share_decryption)) - - C3b: encrypted noise share ciphertexts `ct(e_sm_share)` → C4b - ([`dkg/share_decryption`](../share_decryption)) -- **Data Flow**: `C0 + C2a → C3a → ct(sk_share) → C4a` and `C0 + C2b → C3b → ct(e_sm_share) → C4b` -- **Challenge Generation**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - - `compute_share_encryption_challenge()` -- **Commitment Functions**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - - `compute_dkg_pk_commitment()`, `compute_share_encryption_commitment_from_message()` -- **Note**: C3 is structurally similar to the - [GRECO](https://blog.enclave.gg/enclave-cryptography-greco-fhe-zk/) circuit (P3). The same - decomposition approach used for GRECO could be applied here if further circuit splitting is - desired. -- **Related Circuits**: - - C0 [`dkg/pk`](../pk) - - C2a [`dkg/sk_share_computation`](../sk_share_computation) - - C2b [`dkg/e_sm_share_computation`](../e_sm_share_computation) - - C4a, C4b [`dkg/share_decryption`](../share_decryption) +| | | +| --------- | --------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/dkg/share_encryption.nr`](../../../lib/src/core/dkg/share_encryption.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/dkg/sk_share_computation_base/README.md b/circuits/bin/dkg/sk_share_computation_base/README.md new file mode 100644 index 0000000000..de4851c346 --- /dev/null +++ b/circuits/bin/dkg/sk_share_computation_base/README.md @@ -0,0 +1,9 @@ +# `sk_share_computation_base` — C2 (inner) + +Base proof for the **secret key contribution** Shamir tensor `y`, bound to C1’s `sk` commitment. + +| | | +| --------- | --------------------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/dkg/share_computation/base.nr`](../../../lib/src/core/dkg/share_computation/base.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/recursive_aggregation/fold/README.md b/circuits/bin/recursive_aggregation/fold/README.md index 1e78722f92..5ce4c28170 100644 --- a/circuits/bin/recursive_aggregation/fold/README.md +++ b/circuits/bin/recursive_aggregation/fold/README.md @@ -1,80 +1,13 @@ -# Fold +# `fold` -Aggregates two non-ZK UltraHonk proofs into a single recursive commitment and a proof-genealogy -fingerprint. +Pairwise aggregation of **two** non-ZK UltraHonk proofs: verifies each under its VK, merges +commitments, and computes a **genealogy** `key_hash` over inner key hashes and VK hashes. -This sits at the top of the aggregation stack: it takes two wrapper proofs (stripped of ZK -randomness), verifies them under their respective verification keys, and folds their public outputs -into a single `(key_hash, commitment)` tuple that a verifier can check cheaply in place of the -original pair. +| | | +| ------------ | ----------------------------------------------------------------- | +| **Source** | [`src/main.nr`](src/main.nr) | +| **Wrappers** | [../wrapper/README.md](../wrapper/README.md) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | -## Inputs - -All parameters are private witnesses. - -| Name | Type | Description | -| ------------------------- | -------------------------- | -------------------------------------------- | -| `proof1_verification_key` | `UltraHonkVerificationKey` | VK for proof 1 | -| `proof1_proof` | `UltraHonkProof` | First non-ZK proof to aggregate | -| `proof1_public_inputs` | `[Field; 2]` | `[key_hash, commitment]` attested by proof 1 | -| `proof1_key_hash` | `Field` | Hash of the VK that verified proof 1 | -| `proof2_verification_key` | `UltraHonkVerificationKey` | VK for proof 2 | -| `proof2_proof` | `UltraHonkProof` | Second non-ZK proof to aggregate | -| `proof2_public_inputs` | `[Field; 2]` | `[key_hash, commitment]` attested by proof 2 | -| `proof2_key_hash` | `Field` | Hash of the VK that verified proof 2 | - -`proof*_public_inputs[0]` carries the key_hash propagated upward by the inner proof (encoding its -own circuit genealogy); `proof*_public_inputs[1]` carries its aggregation commitment. - -## Output - -`pub (Field, Field)` — `(key_hash, commitment)` where: - -- `key_hash` is a single fingerprint encoding the full proof genealogy (see below). -- `commitment` is - `compute_recursive_aggregation_commitment([proof1_public_inputs[1], proof2_public_inputs[1]])`. - -## Verification - -1. `verify_honk_proof_non_zk(proof1_verification_key, proof1_proof, proof1_public_inputs, proof1_key_hash)` -2. `verify_honk_proof_non_zk(proof2_verification_key, proof2_proof, proof2_public_inputs, proof2_key_hash)` -3. Compute - `commitment = compute_recursive_aggregation_commitment([proof1_public_inputs[1], proof2_public_inputs[1]])`. -4. Compute - `key_hash = compute_vk_hash([proof1_public_inputs[0], proof2_public_inputs[0], proof1_key_hash, proof2_key_hash])`. -5. Return `(key_hash, commitment)`. - -### Key genealogy - -`key_hash` is computed by hashing four values in order: - -| Position | Value | Meaning | -| -------- | ------------------------- | --------------------------------------------------- | -| 0 | `proof1_public_inputs[0]` | key_hash attested by proof 1 (from its inner folds) | -| 1 | `proof2_public_inputs[0]` | key_hash attested by proof 2 (from its inner folds) | -| 2 | `proof1_key_hash` | VK hash of the circuit that produced proof 1 | -| 3 | `proof2_key_hash` | VK hash of the circuit that produced proof 2 | - -This combined fingerprint lets the verifier check the entire proof genealogy: which circuits were -folded and which VK verified each level, without re-running any inner proof. - -## Data Flow - -```mermaid -flowchart LR - W0["wrapper proof 1\npub_inputs: [key_hash₁, commitment₁]"] --> F["fold"] - W1["wrapper proof 2\npub_inputs: [key_hash₂, commitment₂]"] --> F - F -->|"key_hash (genealogy)"| Out["verifier"] - F -->|"commitment (folded)"| Out -``` - -## Notes - -- Uses **non-ZK** proof verification (`verify_honk_proof_non_zk`) — the ZK layer is handled inside - the wrapper circuits that feed into this one. -- Each proof has its **own** verification key; there is no shared VK constraint between the two. -- Hardcoded to aggregate exactly **2** proofs per invocation. - -## Related - -- [../wrapper/](../wrapper/README.md) — produces the wrapper proofs consumed here +Output: `pub (Field, Field)` = `(key_hash, commitment)`. diff --git a/circuits/bin/recursive_aggregation/wrapper/README.md b/circuits/bin/recursive_aggregation/wrapper/README.md index 3047378f02..1ad9ea0daa 100644 --- a/circuits/bin/recursive_aggregation/wrapper/README.md +++ b/circuits/bin/recursive_aggregation/wrapper/README.md @@ -1,53 +1,45 @@ -# Wrapper Circuits +# Wrapper circuits -Each wrapper circuit takes one or more UltraHonk proofs from a base circuit, re-verifies them -in-circuit with `verify_honk_proof_non_zk`, and computes a single recursive aggregation commitment -over all public inputs (or a tuple for the user_data_encryption wrapper). This converts a proof with -many public inputs into one or a few `Field` values that downstream aggregation steps (fold, or an -on-chain verifier) can process cheaply. +Re-verify **UltraHonk** proofs inside Noir (`verify_honk_proof` / `verify_honk_proof_non_zk`) and +compress public inputs to a **recursive aggregation commitment** (and usually a **key hash** chain) +for folding or cheap verification. -## Common Pattern +## Dimensions -Every wrapper follows the same structure. What varies between circuits: +Each subdirectory under `wrapper/dkg/` and `wrapper/threshold/` sets `N_PROOFS` and +`N_PUBLIC_INPUTS` in its `src/main.nr`. Values below match the sources as of this tree; symbols come +from `lib::configs::default` (`L`, `N`, `H`, `T`, `L_THRESHOLD`, `MAX_MSG_NON_ZERO_COEFFS`, etc.). -- `N_PROOFS` — how many proofs the wrapper verifies in one invocation -- `N_PUBLIC_INPUTS` — how many public field elements each proof exposes, derived from config - parameters resolved at compile time from `lib::configs::default` +| Wrapper path | `N_PROOFS` | `N_PUBLIC_INPUTS` (per proof) | +| ---------------------------------------- | ---------- | --------------------------------------------------------------------------------------------- | +| `dkg/pk` | 1 | `1` | +| `dkg/share_computation` | 1 | `3` (batch key hash + inner proof’s public outputs; final C2 proof wrapped **one at a time**) | +| `dkg/share_encryption` | 1 | `(2 × L × N) + 2` | +| `dkg/share_decryption` | 1 | `(H × L_THRESHOLD) + 1` | +| `threshold/pk_generation` | 1 | `3` (`sk_commitment`, `pk_commitment`, `e_sm_commitment`) | +| `threshold/pk_aggregation` | 1 | `H + 1` | +| `threshold/share_decryption` | 1 | `2 + 2 × L × N + 1` | +| `threshold/decrypted_shares_aggregation` | 1 | `(T + 1) + MAX_MSG_NON_ZERO_COEFFS + (T + 1)` | -## Circuit Index +### P3 user encryption (different path) -| Circuit | `N_PROOFS` | `N_PUBLIC_INPUTS` | -| ---------------------------------------- | ---------- | --------------------------------------------------------------------------- | -| `dkg/pk` | 1 | `1` | -| `dkg/share_computation` | 2 | `(L_THRESHOLD × N_PARTIES) + 1` | -| `dkg/share_encryption` | 2 | `(2 × L × N) + 2` | -| `dkg/share_decryption` | 2 | `(H × L_THRESHOLD) + 1` | -| `threshold/pk_generation` | 1 | `(L × N) + 3` | -| `threshold/pk_aggregation` | 1 | `H + 1` | -| `threshold/share_decryption` | 1 | `2 + (3 × L × N)` | -| `threshold/decrypted_shares_aggregation` | 1 | `((T+1) × L × MAX_MSG_NON_ZERO_COEFFS) + (T + 1 + MAX_MSG_NON_ZERO_COEFFS)` | -| `threshold/user_data_encryption` | 2 | 4 (ct0) · 3 (ct1) — asymmetric, see below | +The user-encryption wrapper (ct0 + ct1, shared `u_commitment`) is **not** under +`recursive_aggregation/wrapper/`. It lives at +[`bin/threshold/user_data_encryption`](../../threshold/user_data_encryption): two inner proofs with +**4** public inputs (ct0) and **3** (ct1), `verify_honk_proof_non_zk`, and a **three-field** public +return tuple. See that crate’s `src/main.nr`. -## Special Case: `threshold/user_data_encryption` - -This wrapper departs from the standard pattern in two ways: - -1. **Cross-proof constraint** — asserts that the `u_commitment` is identical across the ct0 and ct1 - proofs, binding the two ciphertexts to the same encryption randomness. -2. **Tuple output** — returns `(Field, Field, Field)` instead of a single commitment: the public-key - commitment, the ciphertext commitment, and the k1_commitment (in that order). - -## Data Flow +## Flow ```mermaid flowchart LR - Base["base circuit proof (UltraHonk)"] --> W["wrapper"] - W -->|"pub: commitment (Field)"| F["fold"] - F -->|"aggregated commitment"| Out["verifier"] + Base["base UltraHonk proof"] --> W["wrapper"] + W --> Fold["fold"] ``` -## Related - -- [../fold/](../fold/README.md) — aggregates two wrapper outputs into a single commitment -- [../../../../lib/src/math/commitments.nr](../../../../lib/src/math/commitments.nr) — - `compute_recursive_aggregation_commitment` implementation +| | | +| --------------- | --------------------------------------------------------------------- | +| **Fold** | [../fold/README.md](../fold/README.md) | +| **Commitments** | [`lib/src/math/commitments.nr`](../../../lib/src/math/commitments.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/threshold/decrypted_shares_aggregation/README.md b/circuits/bin/threshold/decrypted_shares_aggregation/README.md new file mode 100644 index 0000000000..efdc80cc87 --- /dev/null +++ b/circuits/bin/threshold/decrypted_shares_aggregation/README.md @@ -0,0 +1,10 @@ +# `decrypted_shares_aggregation` — C7 + +Combines **T+1** decryption shares (commitments from C6), Lagrange weights, and CRT data to recover +the plaintext polynomial / message. + +| | | +| --------- | --------------------------------------------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/threshold/decrypted_shares_aggregation.nr`](../../../lib/src/core/threshold/decrypted_shares_aggregation.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/threshold/pk_aggregation/README.md b/circuits/bin/threshold/pk_aggregation/README.md index 136cff6a0b..d7567218ed 100644 --- a/circuits/bin/threshold/pk_aggregation/README.md +++ b/circuits/bin/threshold/pk_aggregation/README.md @@ -1,39 +1,10 @@ -# [C5] Public Key Aggregation (`pk_aggregation`) +# `pk_aggregation` — C5 -The Public Key Aggregation circuit combines the threshold BFV public key shares from all honest -ciphernodes into a single aggregated public key that users will encrypt to. It verifies both that -the individual shares match their commitments from _C1_ and that the aggregation was computed -correctly, before committing to the result. +Aggregates honest parties’ threshold **public key** contributions and checks consistency with C1 +commitments. -```mermaid -flowchart TD - Input1["C1
pk-generation (×H)"] -.->|"commit(pk_trbfv[h])"| C5 - - subgraph Focus["C5"] - C5["Verify pk shares & aggregate"] - end - - C5 -->|"commit(pk_agg)"| Output1["→ P3
User Encryption"] - - style Focus fill:#70AD47,stroke:#548235,stroke-width:3px - style Input1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - - linkStyle 0 stroke:#808080,stroke-width:2px - linkStyle 1 stroke:#808080,stroke-width:2px -``` - -## Metadata - -- **Phase**: P2 (Aggregation). -- **Runs**: 1 × Aggregator (once after all honest parties' pk shares are collected). -- **Requires**: `commit(pk_trbfv[h])` from C1 ([`threshold/pk_generation`](../pk_generation)) for - each honest party `h ∈ H`. -- **Output(s)**: `commit(pk_agg)` → user-data-encryption - ([`threshold/user_data_encryption`](../user_data_encryption)) -- **Data Flow**: `C1 (×H) → C5 → commit(pk_agg) → P3` -- **Commitment Functions**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - - `compute_pk_aggregation_commitment()` -- **Related Circuits**: - - C1 [`threshold/pk_generation`](../pk_generation) - - user-data-encryption [`threshold/user_data_encryption`](../user_data_encryption) +| | | +| --------- | ----------------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/threshold/pk_aggregation.nr`](../../../lib/src/core/threshold/pk_aggregation.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/threshold/pk_generation/README.md b/circuits/bin/threshold/pk_generation/README.md index 34d7e7cb99..1a3709a24a 100644 --- a/circuits/bin/threshold/pk_generation/README.md +++ b/circuits/bin/threshold/pk_generation/README.md @@ -1,53 +1,10 @@ -# [C1] Threshold Public Key Generation (`pk_generation`) +# `pk_generation` — C1 -The Threshold Public Key Generation circuit (C1) is where each ciphernode generates their -contribution to the threshold BFV key. This circuit proves that the public key `(pk0, pk1)` was -generated correctly from a secret key `sk`, error term `eek`, and smudging noise `e_sm`, without -revealing any of these secret values. +TrBFV **threshold public key** contribution: proves correct generation of `pk` share, `sk`, and +smudging noise commitments (Schwartz–Zippel style checks). -This _commit-then-verify_ approach uses the Schwartz-Zippel lemma: rather than checking every -coefficient of the key generation equations (expensive), the circuit verifies the equations hold at -random challenge points. If they hold at these points, they hold everywhere with overwhelming -probability. - -```mermaid -flowchart TD - %% Input from config circuit - Input0["Config
Verification"] -.->|"verified configs"| C1 - - subgraph Focus["C1"] - C1["Generate TRBFV key pair"] - end - - %% Outputs to C2a, C2b, and C5 - C1 -->|"commit(sk)"| Output1["→ C2a
share-computation-sk"] - C1 -->|"commit(e_sm)"| Output2["→ C2b
share-computation-e-sm"] - C1 -->|"commit(pk_trbfv)"| Output3["→ C5
pk-aggregation"] - - style Focus fill:#5B9BD5,stroke:#2E75B6,stroke-width:3px - style Input0 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Output2 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Output3 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - - linkStyle 0 stroke:#808080,stroke-width:2px - linkStyle 1 stroke:#808080,stroke-width:2px - linkStyle 2 stroke:#808080,stroke-width:2px - linkStyle 3 stroke:#808080,stroke-width:2px -``` - -### Metadata - -- **Phase**: P1 (DKG). -- **Runs**: N_PARTIES (once per ciphernode after DKG key commitment). -- **Requires**: [`config`](../../config) circuit (pre-deployment parameter verification). -- **Output(s)**: - - `commit(sk)` → C2a ([`dkg/share_computation`](../../dkg/sk_share_computation)) - - `commit(e_sm)` → C2b ([`dkg/share_computation`](../../dkg/e_sm_share_computation)) - - `commit(pk_trbfv)` → C5 ([`threshold/pk_aggregation`](../../threshold/pk_aggregation/)) -- **Data Flow**: `Config → C1 → {commit(sk) → C2a, commit(pk_trbfv) → C5, commit(e_sm) → C2b}` -- **Challenge Generation**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - - `compute_threshold_pk_challenge()` -- **Commitment Functions**: [`math/commitments.nr`](../../../lib/src/math/commitments.nr) - - `compute_share_computation_sk_commitment()`, `compute_share_computation_e_sm_commitment()`, - `compute_threshold_pk_commitment()` +| | | +| --------- | --------------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/threshold/pk_generation.nr`](../../../lib/src/core/threshold/pk_generation.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/threshold/share_decryption/README.md b/circuits/bin/threshold/share_decryption/README.md index 785f9062da..5ca5265889 100644 --- a/circuits/bin/threshold/share_decryption/README.md +++ b/circuits/bin/threshold/share_decryption/README.md @@ -1,54 +1,10 @@ -# [C6] Decryption Share (`share_decryption`) +# `share_decryption` — C6 -The Decryption Share circuit proves that a ciphernode correctly computed its decryption share from -the homomorphic result ciphertext, using the aggregated secret key share and smudging noise produced -in P1. It runs once per ciphernode per decryption request; T+1 valid proofs are required before C7 -can proceed. +Threshold **decryption share** for the homomorphic result ciphertext, using aggregated `sk` / `e_sm` +commitments from C4. -```mermaid -flowchart TD - Input1["P1
C4a / C4b"] -.->|"commit(agg_sk)
commit(agg_e_sm)"| C6 - Input2["P3
homomorphic result"] -.->|"(ct0, ct1)"| C6 - - subgraph Focus["C6"] - C6["Verify sk & e_sm commitments,
verify decryption share computation
"] - end - - C6 -->|"d[l] (public)"| Output1["→ C7
decrypted-shares-agg"] - - style Focus fill:#E06666,stroke:#C00000,stroke-width:3px - style Input1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Input2 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - style Output1 fill:#000000,stroke:#999,stroke-width:2px,stroke-dasharray: 5 5 - - linkStyle 0 stroke:#808080,stroke-width:2px - linkStyle 1 stroke:#808080,stroke-width:2px - linkStyle 2 stroke:#808080,stroke-width:2px -``` - -**Phase:** P4 (Decryption) - -**Runs:** H (once per honest ciphernode per decryption request; T+1 valid proofs required before C7 -can proceed) - -**Requires:** - -- `commit(agg_sk)` from C4a ([`dkg/share_decryption`](../../dkg/share_decryption)) -- `commit(agg_e_sm)` from C4b ([`dkg/share_decryption`](../../dkg/share_decryption)) -- `(ct0, ct1)` — homomorphic result ciphertext from P3 - -**Output(s):** - -- `d[l]` — public decryption share polynomial per CRT basis, consumed by C7 - -**Data Flow:** C4a/C4b + P3 result → C6 → `d` → C7 - -**Commitment Functions:** `math/commitments.nr` — `compute_aggregated_shares_commitment()`, -`compute_threshold_share_decryption_challenge()` - -**Related Circuits:** - -- C4a [`dkg/share_decryption`](../../dkg/share_decryption) -- C4b [`dkg/share_decryption`](../../dkg/share_decryption) -- C7 [`threshold/decrypted_shares_aggregation_bn`](../decrypted_shares_aggregation_bn) / - [`threshold/decrypted_shares_aggregation_mod`](../decrypted_shares_aggregation_mod) +| | | +| --------- | --------------------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/threshold/share_decryption.nr`](../../../lib/src/core/threshold/share_decryption.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/threshold/user_data_encryption/README.md b/circuits/bin/threshold/user_data_encryption/README.md new file mode 100644 index 0000000000..ba7eecc146 --- /dev/null +++ b/circuits/bin/threshold/user_data_encryption/README.md @@ -0,0 +1,9 @@ +# `user_data_encryption` — P3 (wrapper) + +Re-verifies **ct0** and **ct1** proofs, asserts identical **`u_commitment`**, and outputs aggregated +pk / ciphertext / k1 commitments for downstream use. + +| | | +| --------- | ----------------------------------------------------------------- | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/threshold/user_data_encryption_ct0/README.md b/circuits/bin/threshold/user_data_encryption_ct0/README.md new file mode 100644 index 0000000000..a57f9f0991 --- /dev/null +++ b/circuits/bin/threshold/user_data_encryption_ct0/README.md @@ -0,0 +1,10 @@ +# `user_data_encryption_ct0` — P3 (inner) + +First leg of user BFV encryption under the **aggregated** threshold public key (paired with +`user_data_encryption_ct1`). + +| | | +| --------- | ------------------------------------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/threshold/user_data_encryption_ct0.nr`](../../../lib/src/core/threshold/user_data_encryption_ct0.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/bin/threshold/user_data_encryption_ct1/README.md b/circuits/bin/threshold/user_data_encryption_ct1/README.md new file mode 100644 index 0000000000..ef423eef5b --- /dev/null +++ b/circuits/bin/threshold/user_data_encryption_ct1/README.md @@ -0,0 +1,9 @@ +# `user_data_encryption_ct1` — P3 (inner) + +Second leg of user BFV encryption (paired with `user_data_encryption_ct0`). + +| | | +| --------- | ------------------------------------------------------------------------------------------------------------------- | +| **Core** | [`lib/src/core/threshold/user_data_encryption_ct1.nr`](../../../lib/src/core/threshold/user_data_encryption_ct1.nr) | +| **Index** | [Circuit package index](../../../README.md#circuit-package-index) | +| **Docs** | [Noir Circuits](../../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/lib/src/README.md b/circuits/lib/src/README.md index 90217088f4..a945270634 100644 --- a/circuits/lib/src/README.md +++ b/circuits/lib/src/README.md @@ -1,173 +1,64 @@ -# Enclave ZK Library +# Noir library (`circuits/lib/src/`) -Noir library shared by all circuits in the Enclave protocol. It provides the mathematical -primitives, cryptographic circuit logic, and parameter configurations that the binary circuits -(`circuits/bin/`) compose and instantiate. +Every Nargo package under `circuits/bin/` depends on this library containing the shared **PVSS / +BFV** constraint logic: polynomials, commitments, SAFE hashing, modular arithmetic, and the +`core/dkg` and `core/threshold` circuit bodies that binaries wrap. + +For **which** binary package maps to **C0–C7** and **`CircuitName`**, see the +[**circuit package index**](../../README.md#circuit-package-index) in +[`circuits/README.md`](../../README.md); for protocol phases and the PV-TBFV picture, read +[Cryptography](https://docs.theinterfold.com/cryptography) +([`docs/pages/cryptography.mdx`](../../../docs/pages/cryptography.mdx)). ```text lib/src/ -├── math/ — polynomial arithmetic, hashing, modular arithmetic -├── core/ — circuit logic for DKG and threshold protocols -└── configs/ — cryptographic parameter presets +├── math/ # polynomials, SAFE sponge, helpers, ModU128, commitments +├── core/ # dkg/ and threshold/ circuit structs (`execute()` entry points) +└── configs/ # BFV / CRT presets; wired via `configs::default` (see `default/mod.nr`) ``` ```mermaid -graph LR - math["math primitives"] --> core["core circuits logic"] - configs["configs & parameters"] --> core - core --> dkg["dkg (C0 · C2a/b · C3a/b · C4a/b)"] - core --> threshold["threshold (C1 · C5 · UDE · C6 · C7)"] +flowchart LR + math["math"] --> core["core"] + configs["configs"] --> core + core --> dkg["dkg"] + core --> threshold["threshold"] ``` ## math -Low-level building blocks used throughout all circuits. - -### polynomial - -Fixed-degree polynomial with `N` coefficients (degree `N-1`), stored in descending order -`[a_{N-1}, ..., a_0]`. Core operations: - -| Method | Description | -| ------------------------------------ | --------------------------------------------------------- | -| `eval(x)` | Evaluate at point `x` via Horner's method | -| `eval_mod(x, m)` | Evaluate with modular reduction to prevent field overflow | -| `add(other)` / `mul_scalar(c)` | Coefficient-wise arithmetic | -| `range_check_2bounds::(lo, hi)` | Constrain coefficients to `[-lo, hi]` | -| `range_check_standard::(bound)` | Constrain coefficients to `[0, bound)` | - -### safe - -Full implementation of the [SAFE](https://hackmd.io/@7dpNYqjKQGeYC7wMlPxHtQ/ByIbpfX9c) sponge -framework on top of Poseidon2. Rate 3, capacity 1. Used for all commitment and challenge -computations, providing domain separation between circuit types. - -### helpers - -Utilities for preparing witness data for hashing: - -| Function | Description | -| ------------------------------------- | ------------------------------------------------------- | -| `flatten(inputs, polys)` | Pack `L` polynomials into field carriers for the sponge | -| `pack(values, ...)` | Pack raw coefficient arrays with deterministic padding | -| `compute_safe(inputs, n_squeeze, ds)` | One-shot absorb → squeeze → finish wrapper | - -### ModU128 - -Fully constrained modular arithmetic over `u128` values, used wherever field arithmetic alone is -insufficient (e.g. Lagrange reconstruction, Reed-Solomon parity checks). - -| Method | Description | -| ------------------------- | ---------------------------------------------- | -| `reduce_mod(v)` | `v mod m` | -| `mul_mod(a, b)` | `a * b mod m` | -| `div_mod(a, b)` | `a * b⁻¹ mod m` — `b` must be coprime with `m` | -| `add(a, b)` / `sub(a, b)` | Modular add / sub with underflow handling | - -Unconstrained oracle helpers live in `modulo/unconstrained_U128.nr` and are called internally. - -### commitments - -Domain separators and typed wrappers over `SafeSponge`. Each circuit family has its own domain -separator constant (`DS_PK`, `DS_SHARE_ENCRYPTION`, `DS_CIPHERTEXT`, …) ensuring that commitments -produced by different circuits are never interchangeable. High-level functions: - -| Function | Used by | -| ----------------------------------------------------------- | --------------------- | -| `compute_dkg_pk_commitment()` | C0 | -| `compute_threshold_pk_commitment()` | C1 | -| `compute_share_computation_sk/e_sm_commitment()` | C1 → C2a/b | -| `compute_share_encryption_commitment_from_shares/message()` | C2 ↔ C3, C4 | -| `compute_aggregated_shares_commitment()` | C4 → C6 | -| `compute_threshold_pk/share_decryption_challenge()` | Fiat-Shamir in C1, C6 | +| Area | Contents | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **polynomial** | Coefficients in descending order; `eval` / `eval_mod`, range checks | +| **safe** | [SAFE](https://hackmd.io/@7dpNYqjKQGeYC7wMlPxHtQ/ByIbpfX9c) sponge in `safe.nr`: **Poseidon2** permutation + **Keccak256** as required by the construction; commitments & challenges | +| **helpers** | `flatten`, `pack`, `compute_safe` for witness hashing | +| **modulo** | `ModU128` constrained modular arithmetic | +| **commitments** | Domain-separated `DS_*`; `compute_dkg_pk_commitment`, `compute_threshold_pk_commitment`, share encryption/computation helpers, etc. | ## core -Circuit structs and verification logic. Each struct maps 1-to-1 to a binary circuit in -`circuits/bin/`. Circuits call `execute()` which returns commitments consumed by downstream -circuits. - -### DKG - -Circuits for the Distributed Key Generation phase (P1). - -| File | Circuit | Role | -| ---------------------- | --------- | ------------------------------------------------------------------- | -| `pk.nr` | C0 | Commit to a ciphernode's DKG public key | -| `share_computation.nr` | C2a / C2b | Verify Shamir shares of `sk` / `e_sm` via Reed-Solomon parity check | -| `share_encryption.nr` | C3a / C3b | Verify DKG BFV encryption of each share for its recipient | -| `share_decryption.nr` | C4a / C4b | Verify share decryption and aggregate across honest parties | - -**C2 share matrix layout** — `y[coeff_idx][mod_idx][party_idx]`: - -- `y[i][j][0]` — the secret itself (evaluation at 0), exempt from range checks (validated via C1 - commitment) -- `y[i][j][k]` for `k = 1..N_PARTIES` — shares distributed to each party, range-checked to - `[0, q_j)` - -### Threshold - -Circuits for threshold key generation (P1/P2) and threshold decryption (P4), plus user data -encryption (P3). - -| File | Circuit | Role | -| --------------------------------- | ------- | --------------------------------------------------------------------------- | -| `pk_generation.nr` | C1 | Prove correct BFV threshold key contribution + smudging noise generation | -| `pk_aggregation.nr` | C5 | Verify aggregation of honest parties' public key contributions | -| `user_data_encryption_ct0/ct1.nr` | P3 | Prove correct BFV encryption of user data (GRECO) | -| `share_decryption.nr` | C6 | Prove correct decryption share `d[l] = ct0[l] + ct1[l]·sk[l] + e_sm[l] + …` | -| `decrypted_shares_aggregation.nr` | C7 | Lagrange interpolation + CRT lift + BFV decode to recover plaintext | - -**C7 variants** — two implementations of the final decoding step: - -| Struct | When to use | -| ----------------------------------- | ---------------------------------------------------- | -| `DecryptedSharesAggregationBigNum` | `Q` exceeds 128 bits — production parameter sets | -| `DecryptedSharesAggregationModular` | `Q` fits in 128 bits — smaller / test parameter sets | +| Module | Circuits | Role | +| -------------------------------------------- | -------- | ------------------------------------------ | +| `dkg/pk` | C0 | Individual pk commitment | +| `dkg/share_computation/` | C2 | Base + chunk + parity; `execute()` layouts | +| `dkg/share_encryption` | C3 | Encrypt share under recipient pk | +| `dkg/share_decryption` | C4 | Decrypt and aggregate | +| `threshold/pk_generation` | C1 | TrBFV contribution | +| `threshold/pk_aggregation` | C5 | Aggregate pk shares | +| `threshold/user_data_encryption_ct0` / `ct1` | P3 | User encryption legs | +| `threshold/share_decryption` | C6 | Threshold decryption share | +| `threshold/decrypted_shares_aggregation` | C7 | Final plaintext | ## configs -Cryptographic parameter presets. All binary circuits import from `configs::default`, which is the -single place to switch between parameter sets. - -```text -configs/ -├── default/mod.nr ← change this to switch preset -├── committee/ -│ └── small.nr N_PARTIES = 5 · T = 2 · H = 5 -├── insecure/ -│ ├── dkg.nr N = 512 · L = 1 · q = 2251799813554177 -│ └── threshold.nr -└── secure/ - ├── dkg.nr production-grade parameters - └── threshold.nr -``` - -### Switching presets - -Edit `configs/default/mod.nr`: - -```rust -// switch to production parameters: -pub use super::secure::dkg; -pub use super::secure::threshold; -``` - -### Parameters - -Each preset file defines bit-width constants and `Configs` struct instances for each circuit, -organised by circuit: +Switch presets in `configs/default/mod.nr` (`pub use super::secure::dkg` / `threshold`). Each preset +defines `N`, `L`, `QIS`, bounds, `PARITY_MATRIX`, per-circuit `Configs`, and +`MAX_MSG_NON_ZERO_COEFFS` (C7 plaintext sparsity). -| Category | Examples | -| ------------------- | ----------------------------------------------------------------------- | -| Polynomial degree | `N`, `L` | -| CRT moduli | `QIS: [Field; L]` | -| Plaintext modulus | `PLAINTEXT_MODULUS`, `Q_MOD_T`, `Q_MOD_T_CENTERED` | -| Bit bounds | `PK_BIT_PK`, `SHARE_COMPUTATION_BIT_SHARE`, `SHARE_ENCRYPTION_BIT_*`, … | -| Quotient bounds | `R1_BOUNDS`, `R2_BOUNDS`, `P1_BOUNDS`, `P2_BOUNDS` | -| Reed-Solomon matrix | `PARITY_MATRIX: [[[Field; N_PARTIES+1]; N_PARTIES-T]; L]` | -| Circuit configs | `SHARE_COMPUTATION_SK_CONFIGS`, `SHARE_ENCRYPTION_CONFIGS`, … | +## Related documentation -The `MAX_MSG_NON_ZERO_COEFFS` is defined in `default/mod.nr` (currently `80`). Controls the maximum -number of non-zero coefficients in the plaintext polynomial accepted by C7. Shared across all -parameter presets. +| Topic | Location | +| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| Binary packages, `CircuitName`, build and test | [`circuits/README.md`](../../README.md) | +| Phases, PV-TBFV, circuit identifiers | [Cryptography](https://docs.theinterfold.com/cryptography) · [source](../../../docs/pages/cryptography.mdx) | +| Toolchain, `enclave noir`, compile scripts | [Noir Circuits](https://docs.theinterfold.com/noir-circuits) · [source](../../../docs/pages/noir-circuits.mdx) | diff --git a/circuits/lib/src/lib.nr b/circuits/lib/src/lib.nr index 68f7aa2a96..32aadd2763 100644 --- a/circuits/lib/src/lib.nr +++ b/circuits/lib/src/lib.nr @@ -4,13 +4,13 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! # lib - Enclave Zero-Knowledge Cryptographic Library -//! - **`core`**: Core functions and structs for Public Verifiable -//! Secret Sharing (PVSS) circuits implementations. -//! - **`math`**: Mathematical utilities including polynomial operations, SAFE sponge -//! API implementation, and helper functions for circuit construction. -//! - **`configs`**: Cryptographic parameter configurations for different security -//! levels (insecure, production) and different circuit variants (DKG, Threshold). +//! # lib - Interfold / Enclave ZK library +//! - **`core`**: DKG and threshold TrBFV circuit logic (PV-TBFV / publicly verifiable +//! threshold BFV). +//! - **`math`**: Polynomials, SAFE sponge (Poseidon2 + Keccak per `safe.nr`), helpers, +//! `ModU128`, commitments. +//! - **`configs`**: Parameter presets (default -> secure / insecure / committee) for DKG +//! and threshold packages under `circuits/bin/`. pub mod math; pub mod core; diff --git a/docs/pages/cryptography.mdx b/docs/pages/cryptography.mdx index 907a50f8a4..bc156738f2 100644 --- a/docs/pages/cryptography.mdx +++ b/docs/pages/cryptography.mdx @@ -381,9 +381,6 @@ inner UltraHonk proofs, wrapper circuits, and the folding machinery under [`recursive_aggregation/`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/recursive_aggregation); see [Noir Circuits](./noir-circuits) for how to build and verify them. -Formal security definitions and a fuller academic treatment of PV-TBFV will appear separately; when -that reference exists, this page will link to it. - ## Where to go next If you are integrating proofs from Rust or following on-chain events, these entry points complement diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1cd2bacd24..881873a5a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,6 +72,9 @@ importers: docs: dependencies: + katex: + specifier: ^0.16.44 + version: 0.16.44 next: specifier: ^14.2.1 version: 14.2.33(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -728,7 +731,7 @@ importers: version: 5.3.0 '@risc0/ethereum': specifier: file:lib/risc0-ethereum - version: risc0-ethereum@file:templates/default/lib/risc0-ethereum + version: file:templates/default/lib/risc0-ethereum '@types/chai': specifier: ^4.2.0 version: 4.3.20 @@ -3100,6 +3103,9 @@ packages: '@reown/appkit@1.7.8': resolution: {integrity: sha512-51kTleozhA618T1UvMghkhKfaPcc9JlKwLJ5uV+riHyvSoWPKPRIa5A6M1Wano5puNyW0s3fwywhyqTHSilkaA==} + '@risc0/ethereum@file:templates/default/lib/risc0-ethereum': + resolution: {directory: templates/default/lib/risc0-ethereum, type: directory} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -7013,8 +7019,8 @@ packages: jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} - katex@0.16.25: - resolution: {integrity: sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==} + katex@0.16.44: + resolution: {integrity: sha512-EkxoDTk8ufHqHlf9QxGwcxeLkWRR3iOuYfRpfORgYfqc8s13bgb+YtRY59NK5ZpRaCwq1kqA6a5lpX8C/eLphQ==} hasBin: true keccak@3.0.4: @@ -8783,9 +8789,6 @@ packages: resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} engines: {node: '>= 0.8'} - risc0-ethereum@file:templates/default/lib/risc0-ethereum: - resolution: {directory: templates/default/lib/risc0-ethereum, type: directory} - robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} @@ -13241,6 +13244,8 @@ snapshots: - utf-8-validate - zod + '@risc0/ethereum@file:templates/default/lib/risc0-ethereum': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/plugin-inject@5.0.5(rollup@4.52.5)': @@ -18683,7 +18688,7 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 - katex@0.16.25: + katex@0.16.44: dependencies: commander: 8.3.0 @@ -19180,7 +19185,7 @@ snapshots: dayjs: 1.11.19 dompurify: 3.3.0 elkjs: 0.9.3 - katex: 0.16.25 + katex: 0.16.44 khroma: 2.1.0 lodash-es: 4.17.21 mdast-util-from-markdown: 1.3.1 @@ -19303,7 +19308,7 @@ snapshots: micromark-extension-math@2.1.2: dependencies: '@types/katex': 0.16.7 - katex: 0.16.25 + katex: 0.16.44 micromark-factory-space: 1.1.0 micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 @@ -19879,7 +19884,7 @@ snapshots: github-slugger: 2.0.0 graceful-fs: 4.2.11 gray-matter: 4.0.3 - katex: 0.16.25 + katex: 0.16.44 lodash.get: 4.4.2 next: 14.2.33(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-mdx-remote: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -20112,7 +20117,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.1.1(typescript@5.8.3)(zod@3.22.4) + abitype: 1.1.1(typescript@5.8.3)(zod@3.25.76) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.8.3 @@ -20809,7 +20814,7 @@ snapshots: '@types/katex': 0.16.7 hast-util-from-html-isomorphic: 2.0.0 hast-util-to-text: 4.0.2 - katex: 0.16.25 + katex: 0.16.44 unist-util-visit-parents: 6.0.2 vfile: 6.0.3 @@ -20966,8 +20971,6 @@ snapshots: hash-base: 3.1.2 inherits: 2.0.4 - risc0-ethereum@file:templates/default/lib/risc0-ethereum: {} - robust-predicates@3.0.2: {} rollup@4.52.5: @@ -22168,7 +22171,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.8.3)(zod@3.22.4) + abitype: 1.1.0(typescript@5.8.3)(zod@3.25.76) isows: 1.0.7(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)) ox: 0.9.6(typescript@5.8.3) ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) From b703532bed196dde797e84b24b6a44d965eaaae4 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Mar 2026 16:57:48 +0100 Subject: [PATCH 29/31] update circuits readmes --- circuits/README.md | 19 +-------- .../dkg/e_sm_share_computation_base/README.md | 2 +- .../bin/dkg/share_computation/src/main.nr | 2 +- .../bin/dkg/share_computation_chunk/README.md | 2 +- .../share_computation_chunk_batch/src/main.nr | 10 ++--- .../dkg/sk_share_computation_base/README.md | 2 +- .../recursive_aggregation/fold/src/main.nr | 2 +- .../wrapper/dkg/share_computation/src/main.nr | 4 +- .../user_data_encryption/src/main.nr | 6 +-- circuits/lib/src/core/dkg/pk.nr | 10 ++--- .../src/core/dkg/share_computation/base.nr | 40 +++++++++++++------ .../src/core/dkg/share_computation/chunk.nr | 14 +++++-- .../lib/src/core/dkg/share_computation/mod.nr | 3 ++ circuits/lib/src/core/dkg/share_decryption.nr | 25 +++++------- circuits/lib/src/core/dkg/share_encryption.nr | 19 +++------ .../threshold/decrypted_shares_aggregation.nr | 18 ++++----- .../lib/src/core/threshold/pk_aggregation.nr | 28 +++++++------ .../lib/src/core/threshold/pk_generation.nr | 36 +++++++---------- .../src/core/threshold/share_decryption.nr | 35 ++++++++-------- .../threshold/user_data_encryption_ct0.nr | 22 +++++++--- .../threshold/user_data_encryption_ct1.nr | 18 +++++++-- circuits/lib/src/lib.nr | 2 +- docs/pages/cryptography.mdx | 2 +- 23 files changed, 169 insertions(+), 152 deletions(-) diff --git a/circuits/README.md b/circuits/README.md index d967f2e620..5c1dc4205d 100644 --- a/circuits/README.md +++ b/circuits/README.md @@ -51,8 +51,8 @@ not a single crate. | Path | ID | `CircuitName` | Role | | ------------------------------- | -------- | ---------------------------- | --------------------------------------------- | | `pk` | C0 | `PkBfv` | Commit to individual BFV public key | -| `sk_share_computation_base` | C2 inner | `SkShareComputationBase` | Shamir tensor for secret contribution | -| `e_sm_share_computation_base` | C2 inner | `ESmShareComputationBase` | Shamir tensor for smudging noise | +| `sk_share_computation_base` | C2 inner | `SkShareComputationBase` | Shamir shares (`y`) for secret contribution | +| `e_sm_share_computation_base` | C2 inner | `ESmShareComputationBase` | Shamir shares (`y`) for smudging noise | | `share_computation_chunk` | C2 inner | `ShareComputationChunk` | Reed–Solomon parity on a coefficient slice | | `share_computation_chunk_batch` | C2 inner | `ShareComputationChunkBatch` | Binds base proof to a batch of chunk proofs | | `share_computation` | **C2** | `ShareComputation` | Final C2 step; aggregates inner proofs | @@ -88,21 +88,6 @@ Wrapper parameters are documented in | -------- | ----------------------------------------------------------------------- | | `config` | Validates secure preset constants (CRT moduli, bounds, parity matrices) | -### Per-package READMEs (`bin/**/README.md`) - -Many packages include a **short** README for navigation in the file tree. Keep them that way: one or -two sentences on what this binary proves, then a small table—**do not** duplicate -[Cryptography](https://docs.theinterfold.com/cryptography) or the full package index. - -| Row | Purpose | -| --- | ------- | -| **Core** (or **Source**) | Link to the shared implementation in [`lib/src/`](lib/src/README.md), or to [`src/main.nr`](bin/dkg/pk/src/main.nr) when the crate is self-contained. | -| **Index** | Link to [**Circuit package index**](#circuit-package-index) in this file. Use the right number of `../` so the path reaches `circuits/README.md` (depends on folder depth; all current READMEs are already consistent). | -| **Docs** | [Noir Circuits](https://docs.theinterfold.com/noir-circuits) for toolchain and layout, plus the repo [source](../docs/pages/noir-circuits.mdx). | - -Optional extras (only when they save a click): **Wrappers** (recursive aggregation), or a second -sentence naming the paired package (e.g. P3 `ct0` / `ct1`). - ## Build and test From the repository root: diff --git a/circuits/bin/dkg/e_sm_share_computation_base/README.md b/circuits/bin/dkg/e_sm_share_computation_base/README.md index fe62ff9d44..ab070b84f1 100644 --- a/circuits/bin/dkg/e_sm_share_computation_base/README.md +++ b/circuits/bin/dkg/e_sm_share_computation_base/README.md @@ -1,6 +1,6 @@ # `e_sm_share_computation_base` — C2 (inner) -Base proof for the **smudging noise** Shamir tensor, bound to C1’s `e_sm` commitment. +Base proof for the **smudging noise** Shamir share array `y`, bound to C1’s `e_sm` commitment. | | | | --------- | --------------------------------------------------------------------------------------------------- | diff --git a/circuits/bin/dkg/share_computation/src/main.nr b/circuits/bin/dkg/share_computation/src/main.nr index 8a1cc20ef1..a2fef6379d 100644 --- a/circuits/bin/dkg/share_computation/src/main.nr +++ b/circuits/bin/dkg/share_computation/src/main.nr @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -// Level 2: final_wrapper +// Level 2: final C2 wrapper (after chunk_batch); folds batches and checks VK genealogy. use bb_proof_verification::{UltraHonkProof, UltraHonkVerificationKey, verify_honk_proof_non_zk}; use lib::configs::default::dkg::{ SHARE_COMPUTATION_EXPECTED_VK_HASH_ESM, SHARE_COMPUTATION_EXPECTED_VK_HASH_SK, diff --git a/circuits/bin/dkg/share_computation_chunk/README.md b/circuits/bin/dkg/share_computation_chunk/README.md index 0560eb2cab..8f7e30542d 100644 --- a/circuits/bin/dkg/share_computation_chunk/README.md +++ b/circuits/bin/dkg/share_computation_chunk/README.md @@ -1,6 +1,6 @@ # `share_computation_chunk` — C2 (inner) -Reed–Solomon parity checks on a **slice** of the public `y` tensor (see `PARITY_MATRIX` in configs). +Reed-Solomon parity checks on a **slice** of the public share array `y` (see `PARITY_MATRIX` in configs). | | | | --------- | ----------------------------------------------------------------------------------------------------- | diff --git a/circuits/bin/dkg/share_computation_chunk_batch/src/main.nr b/circuits/bin/dkg/share_computation_chunk_batch/src/main.nr index 6cc2a23bd3..1556c6cb9f 100644 --- a/circuits/bin/dkg/share_computation_chunk_batch/src/main.nr +++ b/circuits/bin/dkg/share_computation_chunk_batch/src/main.nr @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -// Level 1: chunk_batch_wrapper +// Level 1: chunk_batch wrapper (verifies base + per-batch chunk proofs). use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; use lib::configs::default::dkg::{ @@ -19,10 +19,10 @@ pub global BASE_PUBLIC_INPUTS: u32 = pub global CHUNK_PUBLIC_INPUTS: u32 = SHARE_COMPUTATION_CHUNK_SIZE * L_THRESHOLD * (N_PARTIES + 1); -// Each batch wrapper takes: -// - base proof (for y consistency) -// - CHUNKS_PER_BATCH chunk proofs -// - CHUNK_BATCH_IDX to know which y slice to check +// Each batch proof bundles: +// - one base proof (`y` share-array layout for consistency), +// - CHUNKS_PER_BATCH chunk proofs, +// - batch_idx (which batch along the chunk sequence). fn main( base_verification_key: UltraHonkVerificationKey, diff --git a/circuits/bin/dkg/sk_share_computation_base/README.md b/circuits/bin/dkg/sk_share_computation_base/README.md index de4851c346..0b0150ce32 100644 --- a/circuits/bin/dkg/sk_share_computation_base/README.md +++ b/circuits/bin/dkg/sk_share_computation_base/README.md @@ -1,6 +1,6 @@ # `sk_share_computation_base` — C2 (inner) -Base proof for the **secret key contribution** Shamir tensor `y`, bound to C1’s `sk` commitment. +Base proof for the **secret key contribution** Shamir share array `y`, bound to C1’s `sk` commitment. | | | | --------- | --------------------------------------------------------------------------------------------------- | diff --git a/circuits/bin/recursive_aggregation/fold/src/main.nr b/circuits/bin/recursive_aggregation/fold/src/main.nr index 957e439f28..3f79183aa7 100644 --- a/circuits/bin/recursive_aggregation/fold/src/main.nr +++ b/circuits/bin/recursive_aggregation/fold/src/main.nr @@ -30,7 +30,7 @@ fn main( proof2_key_hash, ); - // Hash the two commitments with Poseidon so the verifier can check the folded proof used the expected public inputs. + // Combine the two inner commitments with SAFE (`compute_recursive_aggregation_commitment`). let mut commitments_vec = Vec::new(); commitments_vec.push(proof1_public_inputs[1]); commitments_vec.push(proof2_public_inputs[1]); diff --git a/circuits/bin/recursive_aggregation/wrapper/dkg/share_computation/src/main.nr b/circuits/bin/recursive_aggregation/wrapper/dkg/share_computation/src/main.nr index 727d156e17..dd456d87ec 100644 --- a/circuits/bin/recursive_aggregation/wrapper/dkg/share_computation/src/main.nr +++ b/circuits/bin/recursive_aggregation/wrapper/dkg/share_computation/src/main.nr @@ -7,9 +7,9 @@ use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; use lib::math::commitments::compute_recursive_aggregation_commitment; -// Each SK/ESM final C2 proof is wrapped individually after the two-level pipeline. +// Each SK/ESM final C2 proof is wrapped individually after the base -> chunk -> batch pipeline. pub global N_PROOFS: u32 = 1; -// The final share_computation circuit exposes 3 public outputs: +// The final share_computation wrapper exposes 3 public outputs: // batch_key_hash (pub param) + (key_hash, commitment) return tuple. pub global N_PUBLIC_INPUTS: u32 = 3; diff --git a/circuits/bin/threshold/user_data_encryption/src/main.nr b/circuits/bin/threshold/user_data_encryption/src/main.nr index b13d5ff72a..0e570310e5 100644 --- a/circuits/bin/threshold/user_data_encryption/src/main.nr +++ b/circuits/bin/threshold/user_data_encryption/src/main.nr @@ -8,12 +8,12 @@ use bb_proof_verification::{UltraHonkProof, UltraHonkVerificationKey, verify_hon use lib::math::commitments::{compute_commitment, DS_CIPHERTEXT, DS_PK_AGGREGATION}; fn main( - // User Data Encryption ct0 Section. + // P3: ct0 inner proof. ct0_verification_key: UltraHonkVerificationKey, ct0_proof: UltraHonkProof, ct0_public_inputs: [Field; 4], // pk0_commitment, ct0_commitment, k1_commitment, u_commitment ct0_key_hash: pub Field, - // User Data Encryption ct1 Section. + // P3: ct1 inner proof. ct1_verification_key: UltraHonkVerificationKey, ct1_proof: UltraHonkProof, ct1_public_inputs: [Field; 3], // pk1_commitment, ct1_commitment, u_commitment @@ -41,7 +41,7 @@ fn main( ct_inputs.push(ct1_public_inputs[1]); let ct_commitment = compute_commitment(ct_inputs, DS_CIPHERTEXT); - // Computes the public key aggregation commitment. + // Threshold PK aggregation commitment (pk0 and pk1 limbs). let mut pk_inputs = Vec::new(); pk_inputs.push(ct0_public_inputs[0]); pk_inputs.push(ct1_public_inputs[0]); diff --git a/circuits/lib/src/core/dkg/pk.nr b/circuits/lib/src/core/dkg/pk.nr index 907151093a..81fdd762e2 100644 --- a/circuits/lib/src/core/dkg/pk.nr +++ b/circuits/lib/src/core/dkg/pk.nr @@ -7,14 +7,12 @@ use crate::math::commitments::compute_dkg_pk_commitment; use crate::math::polynomial::Polynomial; -/// DKG Public Key Commitment Circuit (C0). +/// DKG public key commitment (C0). /// -/// This circuit establishes a binding cryptographic commitment to a ciphernode's -/// DKG public key, which is used exclusively for encrypting secret shares during -/// the Distributed Key Generation (DKG) phase. +/// **Role:** Bind the ciphernode DKG public key used to encrypt outgoing shares in DKG. /// -/// Outputs: -/// - commit(pk_dkg) -> C3a / C3b (share encryption) +/// **Produces:** +/// - `commit(pk_dkg)` -> C3 (share encryption). pub struct Pk { /// DKG public key first component for each CRT modulus. /// pk0[i] is a degree N-1 polynomial for modulus q_i. diff --git a/circuits/lib/src/core/dkg/share_computation/base.nr b/circuits/lib/src/core/dkg/share_computation/base.nr index 44420f2082..b70f30d94e 100644 --- a/circuits/lib/src/core/dkg/share_computation/base.nr +++ b/circuits/lib/src/core/dkg/share_computation/base.nr @@ -4,10 +4,8 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -// Base circuit for C2 (SecretKeyShareComputation and SmudgingNoiseShareComputation). -// Verifies secret commitment and consistency, and outputs party commitments for -// downstream use in C3/C4. y is public to allow the wrapper to enforce consistency -// with chunk circuits without hashing overhead. +//! C2 base circuits - SK and e_sm share-array verification (`SecretKeyShareComputationBase`, +//! `SmudgingNoiseShareComputationBase`). use crate::math::commitments::{ compute_share_computation_e_sm_commitment, compute_share_computation_sk_commitment, @@ -15,15 +13,21 @@ use crate::math::commitments::{ }; use crate::math::polynomial::Polynomial; -// ===== BASE CIRCUIT FOR SK ===== +// --- C2a: SK secret shares --- +/// C2a base - bind `sk_secret` and public share array `y` to C1; emit per-party commitments for C3. +/// +/// **Role:** `y[coeff][mod][0]` matches `sk_secret`; party columns hash to C3-facing commitments. +/// +/// **Consumes:** `expected_secret_commitment` from C1 (sk branch). +/// +/// **Produces:** `[[Field; L]; N_PARTIES]` party commitments for C3. pub struct SecretKeyShareComputationBase { /// Expected commitment to sk secret (from C1, public input) expected_secret_commitment: Field, /// Secret key polynomial sk_secret: Polynomial, - /// Full shares array y[coeff_idx][mod_idx][party_idx] - /// Public so wrapper can enforce consistency with chunk circuits + /// Public share array `y[coeff][mod][party]`; wrappers/chunks enforce equality. y: [[[Field; N_PARTIES + 1]; L]; N], } @@ -72,23 +76,32 @@ impl [[Field; L]; N_PARTIES] { + // Step 1: Bind `sk_secret` to C1. self.verify_secret_commitment(); + // Step 2: Align `y[..][..][0]` with `sk_secret`. self.verify_secret_consistency(); + // Step 3: Hash party columns for C3. self.compute_party_commitments() } } -// ===== BASE CIRCUIT FOR ESM ===== +// --- C2b: e_sm shares --- +/// C2b base - bind `e_sm_secret` and public share array `y` to C1; emit per-party commitments for C3. +/// +/// **Role:** Same layout as C2a, for the smudging-noise branch. +/// +/// **Consumes:** `expected_secret_commitment` from C1 (e_sm branch). +/// +/// **Produces:** `[[Field; L]; N_PARTIES]` party commitments for C3. pub struct SmudgingNoiseShareComputationBase { /// Expected commitment to e_sm secret (from C1, public input) expected_secret_commitment: Field, /// Smudging noise polynomial per modulus e_sm_secret: [Polynomial; L], - /// Full shares array y[coeff_idx][mod_idx][party_idx] - /// Public so wrapper can enforce consistency with chunk circuits + /// Public share array `y[coeff][mod][party]`; wrappers/chunks enforce equality. y: [[[Field; N_PARTIES + 1]; L]; N], } @@ -137,10 +150,13 @@ impl [[Field; L]; N_PARTIES] { + // Step 1: Bind `e_sm_secret` to C1. self.verify_secret_commitment(); + // Step 2: Align `y[..][..][0]` with `e_sm_secret` per modulus. self.verify_secret_consistency(); + // Step 3: Hash party columns for C3. self.compute_party_commitments() } } diff --git a/circuits/lib/src/core/dkg/share_computation/chunk.nr b/circuits/lib/src/core/dkg/share_computation/chunk.nr index 5dc4722ea7..26a2c65a4d 100644 --- a/circuits/lib/src/core/dkg/share_computation/chunk.nr +++ b/circuits/lib/src/core/dkg/share_computation/chunk.nr @@ -4,13 +4,12 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -// Chunk circuit for C2. Verifies range checks and parity check for a chunk of -// CHUNK_SIZE coefficients. y_chunk is public so the wrapper can enforce consistency -// with the base circuit without hashing overhead in either circuit. +//! C2 chunk circuit - range + parity on a slice of the public share array `y`. use crate::math::modulo::U128::ModU128; use crate::math::polynomial::Polynomial; +/// Parameters for share computation chunk (C2). pub struct Configs { pub qis: [Field; L], } @@ -21,6 +20,13 @@ impl Configs { } } +/// Share computation chunk (C2). +/// +/// **Role:** For a slice of coefficients, range-check Shamir shares and Reed-Solomon parity (`H`). +/// +/// **Consumes:** Public `y_chunk` aligned with the C2 base `y` array. +/// +/// **Produces:** None (assert-only); wrappers glue chunks to the base proof. pub struct ShareComputationChunk { configs: Configs, /// Slice of y for this chunk, public so wrapper can enforce @@ -72,7 +78,9 @@ impl C6 ([`threshold/share_decryption`](../../threshold/share_decryption)) -/// - C4b: `commit(agg_e_sm)` -> C6 ([`threshold/share_decryption`](../../threshold/share_decryption)) +/// **Produces:** +/// - C4a: `commit(agg_sk)` -> C6 (threshold share decryption). +/// - C4b: `commit(agg_e_sm)` -> C6 (threshold share decryption). pub struct ShareDecryption { /// Expected commitments to the share polynomials, produced in C2a (for C4a) or C2b (for C4b) /// via commit_to_party_shares. Organised as [party_idx][mod_idx], covering all H honest @@ -102,13 +97,13 @@ impl ShareDecryption Field { - // Step 1: Verify all commitments match + // Step 1: Verify decrypted shares match expected C2 commitments. self.verify_commitments(); - // Step 2: Compute aggregated shares + // Step 2: Sum shares coefficient-wise per CRT limb. let aggregated = self.compute_aggregated_shares(); - // Step 3: Return commitment to aggregated shares + // Step 3: Publish commitment to the aggregate (C6 input). compute_aggregated_shares_commitment::(aggregated) } } diff --git a/circuits/lib/src/core/dkg/share_encryption.nr b/circuits/lib/src/core/dkg/share_encryption.nr index 39f9584ea5..6410f17c08 100644 --- a/circuits/lib/src/core/dkg/share_encryption.nr +++ b/circuits/lib/src/core/dkg/share_encryption.nr @@ -12,7 +12,7 @@ use crate::math::helpers::flatten; use crate::math::modulo::U128::ModU128; use crate::math::polynomial::Polynomial; -/// Cryptographic parameters for DKG share encryption circuit. +/// Parameters for DKG share encryption (C3). pub struct Configs { /// Plaintext modulus t pub t: Field, @@ -80,21 +80,14 @@ impl Configs { } } -/// DKG Share Encryption Circuit (C3a / C3b). +/// DKG share encryption (C3a / C3b). /// -/// Verifies that a secret share was correctly encrypted under a recipient's DKG public key. -/// This circuit runs twice in parallel per ciphernode per recipient: -/// - C3a: encrypts a secret key (sk) share, consuming commit(sk_share) from C2a -/// - C3b: encrypts a smudging noise (e_sm) share, consuming commit(e_sm_share) from C2b +/// **Role:** Prove BFV encryption of a share under the recipient DKG public key. Run twice per +/// recipient (sk share and e_sm share). /// -/// The key insight is that the commitment to the plaintext being encrypted must equal -/// the commitment to the share produced in C2a/C2b. This binds the encryption to the -/// previously verified share, we know the ciphertext contains the correct share without -/// ever seeing it in plaintext. +/// **Consumes:** `expected_pk_commitment` (C0); `expected_message_commitment` (C2 sk or C2 e_sm). /// -/// Outputs: -/// No new commitments are produced. The output ciphertexts (ct0, ct1) are broadcast -/// to the intended recipient for decryption in C4a/C4b. +/// **Produces:** Ciphertexts `ct0`, `ct1` for C4 (no new standalone commitment artifact). pub struct ShareEncryption { /// Circuit parameters configs: Configs, diff --git a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr index abdd450d36..ec0e468bba 100644 --- a/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr +++ b/circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr @@ -11,7 +11,7 @@ use dep::bignum::BigNum; use dep::bignum::bignum::to_field; use dep::bignum::SecureThreshold8192; -/// Cryptographic parameters for decryption share aggregation circuit. +/// Parameters for decrypted shares aggregation (C7). pub struct Configs { /// CRT moduli: [q_0, q_1, ..., q_{L-1}] pub qis: [Field; L], @@ -27,14 +27,14 @@ impl Configs { } } -/// Decrypted Shares Aggregation Circuit (Circuit 7). -/// Uses BigNum for Q values (works for both insecure and secure parameter sets). +/// Decrypted shares aggregation (C7). /// -/// Verifies: -/// 0. Each party's decryption share matches the corresponding C6 public `d` commitment -/// 1. Lagrange interpolation to compute u^{(l)} for each CRT basis -/// 2. CRT reconstruction: u^{(l)} + r^{(l)} * q_l = u_global -/// 3. Decoding verification: message = -Q^{-1} * (t * u_global)_Q mod t +/// **Role:** Combine partial decryption shares into the plaintext message (CRT + decode). Uses +/// BigNum where the product of CRT moduli exceeds the field. +/// +/// **Consumes:** C6 `d` commitments for each reconstructing party (same order as shares). +/// +/// **Verifies:** Share binding to C6; Lagrange combine per limb; CRT glue; decode mod `t`. pub struct DecryptedSharesAggregation { /// Circuit parameters including crypto constants configs: Configs, @@ -80,7 +80,7 @@ impl( diff --git a/circuits/lib/src/core/threshold/pk_aggregation.nr b/circuits/lib/src/core/threshold/pk_aggregation.nr index 6d76eb7b9c..7c93ef11e1 100644 --- a/circuits/lib/src/core/threshold/pk_aggregation.nr +++ b/circuits/lib/src/core/threshold/pk_aggregation.nr @@ -8,7 +8,7 @@ use crate::math::commitments::{compute_pk_aggregation_commitment, compute_thresh use crate::math::modulo::U128::ModU128; use crate::math::polynomial::Polynomial; -/// Cryptographic parameters for Threshold public key aggregation circuit. +/// Parameters for threshold public key aggregation (C5). pub struct Configs { /// CRT moduli for each basis: [q_0, q_1, ..., q_{L-1}] pub qis: [Field; L], @@ -20,12 +20,17 @@ impl Configs { } } -/// Public Key Aggregation Circuit (C5). +/// Threshold public key aggregation (C5). /// -/// Verifies that for each CRT basis l and each coefficient i: -/// - pk0_agg[l][i] = sum_h(pk0[h][l][i]) mod q_l (witnesses use centered coefficients; aggregation -/// uses a half-q shift so [`ModU128::reduce_mod`] sees small non-negative integers) -/// - pk1_agg[l][i] = pk1[0][l][i] (all parties share pk1 = a, aggregated pk1 = a) +/// **Role:** Sum honest parties' threshold PK limbs into `pk0_agg`, check `pk1_agg` matches CRS `a`, +/// and commit the aggregated key for P3. +/// +/// **Consumes:** `commit(pk_trbfv[h])` from C1 per honest party `h`. +/// +/// **Produces:** `commit(pk_agg)` for user-data encryption (P3). +/// +/// **Verifies:** For each limb and coefficient, `pk0_agg` matches the mod-`q_l` sum of party `pk0` +/// (centered coefficients, `ModU128::reduce_mod` with half-`q` shift); `pk1_agg` equals shared CRS `a`. pub struct PkAggregation { /// Circuit parameters including CRT moduli configs: Configs, @@ -127,21 +132,18 @@ impl PkAggregation Field { - // 0. Verify pk commitments + // Step 1: Bind each party PK to its C1 commitment. self.verify_pk_commitments(); - // 1. Verify pk0 aggregation (sum) and pk1 (all equal to a, pk1_agg = a) + // Step 2: Check `pk0_agg` sums party limbs and `pk1_agg` matches CRS `a` (per CRT basis). for basis_idx in 0..L { self.verify_pk_for_basis(self.pk0, self.pk0_agg, basis_idx); self.verify_pk1(basis_idx); } - // 2. Commit to aggregated threshold public key + // Step 3: Commit to the aggregated threshold public key. compute_pk_aggregation_commitment::(self.pk0_agg, self.pk1_agg) } } diff --git a/circuits/lib/src/core/threshold/pk_generation.nr b/circuits/lib/src/core/threshold/pk_generation.nr index b0ebec1f88..aa2b0de233 100644 --- a/circuits/lib/src/core/threshold/pk_generation.nr +++ b/circuits/lib/src/core/threshold/pk_generation.nr @@ -11,7 +11,7 @@ use crate::math::commitments::{ use crate::math::helpers::flatten; use crate::math::polynomial::Polynomial; -/// Cryptographic parameters for threshold public key generation circuit. +/// Parameters for threshold public key generation (C1). pub struct Configs { /// CRT moduli: [q_0, q_1, ..., q_{L-1}] pub qis: [Field; L], @@ -40,22 +40,17 @@ impl Configs { } } -/// Correct Threshold Public Key Contribution Generation Circuit (C1). +/// Threshold public key contribution (C1). /// -/// This circuit proves that a contribution to the threshold BFV public key was generated correctly -/// from secret values without revealing those secrets. Each ciphernode generates -/// their own key contribution, and this circuit ensures the generation followed -/// the BFV key generation equations. +/// **Role:** Prove one party's TrBFV public-key contribution from hidden `sk`, `eek`, `e_sm`, and +/// quotients (`pk1` limb is the CRS `a`). /// -/// Verifies: -/// 1. Range checks on all secret witnesses (secret key, error, smudging noise, quotients) -/// 2. Correct public key generation: pk0_i = -a_i * sk + eek + r2_i * (X^N + 1) + r1_i * q_i -/// (pk1 is a_i from the hardcoded CRS `a`, not a separate witness) +/// **Verifies:** Range checks on witnesses; evaluation form of `pk0 = -a*sk + eek + ...` at the FS challenge. /// -/// Outputs (in return order): -/// - commit(sk) -> C2a (secret key share verification) -/// - commit(pk_trbfv) -> C5 (public key aggregation) -/// - commit(e_sm) -> C2b (smudging noise share verification) +/// **Produces (tuple order):** +/// - `commit(sk)` -> C2a (share computation). +/// - `commit(pk_trbfv)` -> C5 (pk aggregation). +/// - `commit(e_sm)` -> C2b (share computation). pub struct PkGeneration { /// Cryptographic parameters including bounds, moduli, and constants. configs: Configs, @@ -126,25 +121,22 @@ impl C2a (secret key share verification) - /// - commit(pk_trbfv) -> C5 (public key aggregation) - /// - commit(e_sm) -> C2b (smudging noise share verification) + /// Returns `(commit(sk), commit(pk_trbfv), commit(e_sm))` as documented on the struct. pub fn execute(self) -> (Field, Field, Field) { - // 1. Perform range checks on all secret witness values + // Step 1: Range-check secret witnesses (sk, eek, e_sm, quotients). self.perform_range_checks(); - // 2. Compute commitments + // Step 2: Derive commitments for the FS transcript. let sk_commitment = compute_share_computation_sk_commitment::(self.sk); let e_sm_commitment = compute_share_computation_e_sm_commitment::(self.e_sm); let pk_commitment = compute_threshold_pk_commitment::(self.pk0, self.a); - // 3. Generate a single Fiat-Shamir challenge point (gamma) + // Step 3: Fiat-Shamir challenge and per-modulus evaluation checks. let gamma = self.generate_challenge(sk_commitment, pk_commitment); self.verify_evaluations(gamma); - // 4. Return all commitments + // Step 4: Return commitments for downstream circuits. (sk_commitment, pk_commitment, e_sm_commitment) } diff --git a/circuits/lib/src/core/threshold/share_decryption.nr b/circuits/lib/src/core/threshold/share_decryption.nr index 5771f8196c..222dd03cf6 100644 --- a/circuits/lib/src/core/threshold/share_decryption.nr +++ b/circuits/lib/src/core/threshold/share_decryption.nr @@ -11,7 +11,7 @@ use crate::math::commitments::{ use crate::math::helpers::flatten; use crate::math::polynomial::Polynomial; -/// Cryptographic parameters for Threshold decryption share circuit. +/// Parameters for threshold share decryption (C6). pub struct Configs { /// CRT moduli: [q_0, q_1, ..., q_{L-1}] pub qis: [Field; L], @@ -27,21 +27,23 @@ impl Configs { } } -/// Threshold Share Decryption (Circuit 6). +/// Threshold share decryption (C6). /// -/// Verifies: -/// 1. Commitment to sk matches expected (from DKG decryption circuit) -/// 2. Commitment to e_sm matches expected (from DKG decryption circuit) -/// 3. Correct computation: d_i = c_0i + c_1i * s_i + e_i + r_2i * (X^N + 1) + r_1i * q_i +/// **Role:** Prove one party's partial decryption share `d` is consistent with the ciphertext and +/// with aggregate `sk` / `e_sm` commitments from C4. +/// +/// **Consumes:** `expected_sk_commitment`, `expected_e_sm_commitment` (C4); ciphertext limbs `ct0`, `ct1`. +/// +/// **Produces:** Truncated-`d` commitment for C7 (`decrypted_shares_aggregation`). pub struct ShareDecryption { /// Circuit parameters including bounds and cryptographic constants configs: Configs, - /// Expected commitment to aggregated sk shares (from DKG decryption circuit) + /// Expected commitment to aggregated sk shares (from C4 / protocol transcript) /// (public witness) expected_sk_commitment: Field, - /// Expected commitment to aggregated e_sm shares (from DKG decryption circuit) + /// Expected commitment to aggregated e_sm shares (from C4 / protocol transcript) /// (public witness) expected_e_sm_commitment: Field, @@ -171,8 +173,7 @@ impl Field { // Step 1: Verify sk commitment matches expected self.verify_agg_sk_commitment(); @@ -180,9 +181,9 @@ impl { pub qis: [Field; L], pub k0is: [Field; L], @@ -55,9 +56,17 @@ impl Configs { } } +/// User-data encryption ct0 (P3). +/// +/// **Role:** Prove the first ciphertext leg and publish `pk0`, `ct0`, `k1`, and `u` commitments +/// for the wrapper and ct1 circuit. +/// +/// **Consumes:** Threshold PK from C5 (`pk0is` witness path). +/// +/// **Produces:** `(pk0_commitment, ct0_commitment, k1_commitment, u_commitment)` public outputs. pub struct UserDataEncryptionCt0 { configs: Configs, - /// Public input from PkAggregation + /// Aggregated threshold `pk0` limb (from C5). pk0is: [Polynomial; L], ct0is: [Polynomial; L], u: Polynomial, @@ -182,14 +191,17 @@ impl (Field, Field, Field, Field) { + // Step 1: Witness bounds and CRT consistency of `e0`. self.check_range_bounds(); self.check_e0_crt_consistency(); + // Step 2: Domain-separated commitments for the FS transcript. let (pk0_commitment, ct0_commitment, k1_commitment, u_commitment) = self.generate_commitments(); + // Step 3: Fiat-Shamir challenge and batched evaluation check of ct0 equations. let gammas = self.generate_challenge(pk0_commitment, ct0_commitment, k1_commitment, u_commitment); diff --git a/circuits/lib/src/core/threshold/user_data_encryption_ct1.nr b/circuits/lib/src/core/threshold/user_data_encryption_ct1.nr index 2c84f1595d..4565d4e225 100644 --- a/circuits/lib/src/core/threshold/user_data_encryption_ct1.nr +++ b/circuits/lib/src/core/threshold/user_data_encryption_ct1.nr @@ -4,7 +4,10 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//user data encryption for ct1- the second part of the ciphertext +//! User-data encryption - ct1 leg (P3). +//! +//! See `UserDataEncryptionCt1` for the circuit contract. + use crate::math::commitments::compute_multiple_polynomial_commitment; use crate::math::commitments::compute_single_polynomial_commitment; use crate::math::commitments::compute_user_data_encryption_ct1_challenge; @@ -14,6 +17,7 @@ use crate::math::commitments::DS_USER_DATA_ENCRYPTION_COMMITMENT; use crate::math::helpers::flatten; use crate::math::polynomial::Polynomial; +/// Parameters for user-data encryption ct1 (P3). pub struct Configs { pub qis: [Field; L], pub e1_bound: Field, @@ -33,11 +37,16 @@ impl Configs { } } +/// User-data encryption ct1 (P3). +/// +/// **Role:** Second ciphertext leg; `u` must match the ct0 `u_commitment`. +/// +/// **Produces:** `(pk1_commitment, ct1_commitment, u_commitment)` public outputs. pub struct UserDataEncryptionCt1 { configs: Configs, pk1is: [Polynomial; L], ct1is: [Polynomial; L], - /// Re-witnessed privately, checked against commit_u from Circuit A + /// Re-witnessed privately; checked against `u_commitment` from ct0 (same P3 flow). u: Polynomial, e1: Polynomial, p1is: [Polynomial<(2 * N) - 1>; L], @@ -130,12 +139,15 @@ impl (Field, Field, Field) { + // Step 1: Witness bounds. self.check_range_bounds(); + // Step 2: Commitments for the FS transcript. let (pk1_commitment, ct1_commitment, u_commitment) = self.generate_commitments(); + // Step 3: Fiat-Shamir challenge and evaluation check of ct1 equations. let gammas = self.generate_challenge(pk1_commitment, ct1_commitment, u_commitment); assert(self.verify_evaluations(gammas), "ct1 encryption check failed"); diff --git a/circuits/lib/src/lib.nr b/circuits/lib/src/lib.nr index 32aadd2763..aa4289ca99 100644 --- a/circuits/lib/src/lib.nr +++ b/circuits/lib/src/lib.nr @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! # lib - Interfold / Enclave ZK library +//! # lib - Interfold ZK library //! - **`core`**: DKG and threshold TrBFV circuit logic (PV-TBFV / publicly verifiable //! threshold BFV). //! - **`math`**: Polynomials, SAFE sponge (Poseidon2 + Keccak per `safe.nr`), helpers, diff --git a/docs/pages/cryptography.mdx b/docs/pages/cryptography.mdx index bc156738f2..b63a84cff7 100644 --- a/docs/pages/cryptography.mdx +++ b/docs/pages/cryptography.mdx @@ -260,7 +260,7 @@ circuit’s `verify_evaluations` only evaluates the **pk0** identity at γ.) a single monolithic proof: under [`dkg/`](https://github.com/gnosisguild/enclave/tree/main/circuits/bin/dkg) you will find the recursive chain—base, chunk, batch, wrapper—that proves Shamir-style sharing and Reed–Solomon parity -on the contribution tensors while keeping proof size manageable. Intuitively, for each CRT limb and +on the contribution arrays while keeping proof size manageable. Intuitively, for each CRT limb and coefficient column, the shares laid out as a vector **y** must satisfy parity with a fixed matrix **H** so that only low-degree Shamir-style codewords pass (the exact **PARITY_MATRIX** is preset and checked by `config`): From 81003499daf2f2a068e0cbd76187739e3ad61245 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 27 Mar 2026 17:00:39 +0100 Subject: [PATCH 30/31] fix wrong conflicts merge --- .../decrypted_shares_aggregation/codegen.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs index cec0af9f87..b689d9862d 100644 --- a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs @@ -71,6 +71,9 @@ decrypted_shares_aggregation (CIRCUIT 7) ------------------------------------- ************************************/ +pub global {}_BIT_NOISE: u32 = {}; +pub global {}_BIT_D: u32 = {}; + pub global {}_CONFIGS: DecryptedSharesAggregationConfigs = DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T); "#, @@ -81,6 +84,10 @@ pub global {}_CONFIGS: DecryptedSharesAggregationConfigs = configs.q_mod_t_centered, configs.q_inverse_mod_t, prefix, + configs.bits.noise_bit, + prefix, + configs.bits.d_bit, + prefix, ) } @@ -98,6 +105,13 @@ mod tests { let codegen_configs = generate_configs(preset, &configs); assert!(codegen_configs.contains("decrypted_shares_aggregation")); + assert!(codegen_configs.contains(&format!( + "{}_BIT_NOISE: u32 = {}", + prefix, configs.bits.noise_bit + ))); + assert!( + codegen_configs.contains(&format!("{}_BIT_D: u32 = {}", prefix, configs.bits.d_bit)) + ); assert!(codegen_configs.contains(&format!("{}_CONFIGS:", prefix))); assert!(codegen_configs.contains( "DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T)" @@ -115,6 +129,12 @@ mod tests { let artifacts = circuit.codegen(preset, &input).unwrap(); assert!(!artifacts.toml.is_empty()); + assert!(artifacts + .configs + .contains("DECRYPTED_SHARES_AGGREGATION_BIT_NOISE")); + assert!(artifacts + .configs + .contains("DECRYPTED_SHARES_AGGREGATION_BIT_D")); assert!(artifacts .configs .contains("DECRYPTED_SHARES_AGGREGATION_CONFIGS")); From dceba8dc98439de3e7a86761b35a6552bbd0d0b3 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Mon, 30 Mar 2026 11:25:30 +0200 Subject: [PATCH 31/31] remove dead link --- docs/pages/cryptography.mdx | 2 -- docs/pages/noir-circuits.mdx | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/pages/cryptography.mdx b/docs/pages/cryptography.mdx index b63a84cff7..56cee522ce 100644 --- a/docs/pages/cryptography.mdx +++ b/docs/pages/cryptography.mdx @@ -387,7 +387,5 @@ If you are integrating proofs from Rust or following on-chain events, these entr this page: the [`circuits/README.md`](https://github.com/gnosisguild/enclave/blob/main/circuits/README.md) index for package names and **C0–C7** paths; -[`agent/circuits/REFERENCE.md`](https://github.com/gnosisguild/enclave/blob/main/agent/circuits/REFERENCE.md) -for `ProofType`, `CircuitName`, and prover wiring; and the flow-trace chapter [DKG & computation](https://github.com/gnosisguild/enclave/blob/main/agent/flow-trace/04_DKG_AND_COMPUTATION.md) for actors, gossip, and proof ordering in a running node. diff --git a/docs/pages/noir-circuits.mdx b/docs/pages/noir-circuits.mdx index e9e1a4e42a..0b59363f34 100644 --- a/docs/pages/noir-circuits.mdx +++ b/docs/pages/noir-circuits.mdx @@ -12,8 +12,6 @@ live, which scripts to run, and how they connect to the Rust prover and on-chain For **what** the circuits prove (PV-TBFV, phases **P1–P4**, **C0–C7**, commitments), read [Cryptography](./cryptography) first. For **Rust types and events** (`ProofType`, `CircuitName`, public inputs), use the repo’s -[`agent/circuits/REFERENCE.md`](https://github.com/gnosisguild/enclave/blob/main/agent/circuits/REFERENCE.md). -For **runtime ordering** (actors, gossip, proof pipeline), see [`agent/flow-trace/04_DKG_AND_COMPUTATION.md`](https://github.com/gnosisguild/enclave/blob/main/agent/flow-trace/04_DKG_AND_COMPUTATION.md). The canonical **package index** (paths, **CircuitName**, C0–C7 IDs) is @@ -116,8 +114,7 @@ interface ICircuitVerifier { - **`e3-zk-prover`** (`crates/zk-prover`) — witness wiring, `bb prove` / `bb verify`, artifact paths aligned with `enclave noir setup`. -- **Events / API** — `CircuitName`, `ProofType`, and request payloads are defined next to the node; - see `agent/circuits/REFERENCE.md`. +- **Events / API** — `CircuitName`, `ProofType`, and request payloads are defined next to the node. When you add or rename a circuit package, update root **`package.json`** / CI scripts that call `build-circuits.ts` or `lint-circuits.sh` so dev and deployment stay in sync with Noir sources.