From 47aefe547e8881609721f525e86943599c4279fc Mon Sep 17 00:00:00 2001 From: Zara Date: Fri, 6 Mar 2026 15:25:58 -0800 Subject: [PATCH 01/27] split C2 into base and chunk --- circuits/bin/dkg/Nargo.toml | 6 +- .../bin/dkg/e_sm_share_computation/Nargo.toml | 7 - .../bin/dkg/e_sm_share_computation/README.md | 1 - .../dkg/e_sm_share_computation/src/main.nr | 29 -- .../dkg/esm_share_computation_base/Nargo.toml | 7 + .../esm_share_computation_base/src/main.nr | 21 ++ .../dkg/share_computation_chunk/Nargo.toml | 7 + .../dkg/share_computation_chunk/src/main.nr | 21 ++ .../bin/dkg/sk_share_computation/Nargo.toml | 8 - .../bin/dkg/sk_share_computation/README.md | 1 - .../bin/dkg/sk_share_computation/src/main.nr | 29 -- .../dkg/sk_share_computation_base/Nargo.toml | 7 + .../dkg/sk_share_computation_base/src/main.nr | 21 ++ circuits/lib/src/configs/insecure/dkg.nr | 23 +- circuits/lib/src/configs/secure/dkg.nr | 27 +- .../lib/src/core/dkg/share_computation.nr | 294 ------------------ .../src/core/dkg/share_computation/base.nr | 199 ++++++++++++ .../src/core/dkg/share_computation/chunk.nr | 87 ++++++ .../lib/src/core/dkg/share_computation/mod.nr | 2 + circuits/lib/src/math/commitments.nr | 45 +++ 20 files changed, 456 insertions(+), 386 deletions(-) delete mode 100644 circuits/bin/dkg/e_sm_share_computation/Nargo.toml delete mode 100644 circuits/bin/dkg/e_sm_share_computation/README.md delete mode 100644 circuits/bin/dkg/e_sm_share_computation/src/main.nr create mode 100644 circuits/bin/dkg/esm_share_computation_base/Nargo.toml create mode 100644 circuits/bin/dkg/esm_share_computation_base/src/main.nr create mode 100644 circuits/bin/dkg/share_computation_chunk/Nargo.toml create mode 100644 circuits/bin/dkg/share_computation_chunk/src/main.nr delete mode 100644 circuits/bin/dkg/sk_share_computation/Nargo.toml delete mode 100644 circuits/bin/dkg/sk_share_computation/README.md delete mode 100644 circuits/bin/dkg/sk_share_computation/src/main.nr create mode 100644 circuits/bin/dkg/sk_share_computation_base/Nargo.toml create mode 100644 circuits/bin/dkg/sk_share_computation_base/src/main.nr delete mode 100644 circuits/lib/src/core/dkg/share_computation.nr create mode 100644 circuits/lib/src/core/dkg/share_computation/base.nr create mode 100644 circuits/lib/src/core/dkg/share_computation/chunk.nr create mode 100644 circuits/lib/src/core/dkg/share_computation/mod.nr diff --git a/circuits/bin/dkg/Nargo.toml b/circuits/bin/dkg/Nargo.toml index a99b2343e7..f13092c377 100644 --- a/circuits/bin/dkg/Nargo.toml +++ b/circuits/bin/dkg/Nargo.toml @@ -5,4 +5,8 @@ members = [ "e_sm_share_computation", "share_encryption", "share_decryption", -] \ No newline at end of file + "sk_share_computation_base", + "esm_share_computation_base", + "share_computation_chunk", +] + diff --git a/circuits/bin/dkg/e_sm_share_computation/Nargo.toml b/circuits/bin/dkg/e_sm_share_computation/Nargo.toml deleted file mode 100644 index 5af3e6395b..0000000000 --- a/circuits/bin/dkg/e_sm_share_computation/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "e_sm_share_computation" -type = "bin" -authors = ["Gnosis Guild / Enclave"] - -[dependencies] -lib = { path = "../../../lib" } diff --git a/circuits/bin/dkg/e_sm_share_computation/README.md b/circuits/bin/dkg/e_sm_share_computation/README.md deleted file mode 100644 index 4909c92d85..0000000000 --- a/circuits/bin/dkg/e_sm_share_computation/README.md +++ /dev/null @@ -1 +0,0 @@ -instantiation of correct Smudging Noise Secret Share Computation (PVSS #2b) diff --git a/circuits/bin/dkg/e_sm_share_computation/src/main.nr b/circuits/bin/dkg/e_sm_share_computation/src/main.nr deleted file mode 100644 index 49b28c143e..0000000000 --- a/circuits/bin/dkg/e_sm_share_computation/src/main.nr +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. - -use lib::configs::default::dkg::{ - L_THRESHOLD, N, PARITY_MATRIX, SHARE_COMPUTATION_BIT_SHARE, SHARE_COMPUTATION_E_SM_BIT_SECRET, - SHARE_COMPUTATION_E_SM_CONFIGS, -}; -use lib::configs::default::{N_PARTIES, T}; -use lib::core::dkg::share_computation::SmudgingNoiseShareComputation; -use lib::math::polynomial::Polynomial; - -fn main( - expected_secret_commitment: pub Field, - e_sm_secret: [Polynomial; L_THRESHOLD], - y: [[[Field; N_PARTIES + 1]; L_THRESHOLD]; N], -) -> pub [[Field; L_THRESHOLD]; N_PARTIES] { - let share_computation_e_sm: SmudgingNoiseShareComputation = SmudgingNoiseShareComputation::new( - SHARE_COMPUTATION_E_SM_CONFIGS, - expected_secret_commitment, - e_sm_secret, - y, - PARITY_MATRIX, - ); - - share_computation_e_sm.execute() -} diff --git a/circuits/bin/dkg/esm_share_computation_base/Nargo.toml b/circuits/bin/dkg/esm_share_computation_base/Nargo.toml new file mode 100644 index 0000000000..9647284797 --- /dev/null +++ b/circuits/bin/dkg/esm_share_computation_base/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "esm_share_computation_base" +type = "bin" +authors = [""] + +[dependencies] +lib = { path = "../../../lib" } \ No newline at end of file diff --git a/circuits/bin/dkg/esm_share_computation_base/src/main.nr b/circuits/bin/dkg/esm_share_computation_base/src/main.nr new file mode 100644 index 0000000000..3b18aba939 --- /dev/null +++ b/circuits/bin/dkg/esm_share_computation_base/src/main.nr @@ -0,0 +1,21 @@ +use lib::configs::default::dkg::{ + L_THRESHOLD, N, SHARE_COMPUTATION_CHUNK_SIZE, SHARE_COMPUTATION_E_SM_BASE_CONFIGS, + SHARE_COMPUTATION_E_SM_BIT_SECRET, SHARE_COMPUTATION_N_CHUNKS, +}; +use lib::configs::default::{N_PARTIES, T}; +use lib::core::dkg::share_computation::base::SmudgingNoiseShareComputationBase; +use lib::math::polynomial::Polynomial; + +fn main( + expected_secret_commitment: pub Field, + e_sm_secret: [Polynomial; L_THRESHOLD], + y: [[[Field; N_PARTIES + 1]; L_THRESHOLD]; N], +) -> pub ([Field; SHARE_COMPUTATION_N_CHUNKS], [[Field; L_THRESHOLD]; N_PARTIES]) { + let circuit: SmudgingNoiseShareComputationBase = SmudgingNoiseShareComputationBase::new( + SHARE_COMPUTATION_E_SM_BASE_CONFIGS, + expected_secret_commitment, + e_sm_secret, + y, + ); + circuit.execute() +} diff --git a/circuits/bin/dkg/share_computation_chunk/Nargo.toml b/circuits/bin/dkg/share_computation_chunk/Nargo.toml new file mode 100644 index 0000000000..83e4227462 --- /dev/null +++ b/circuits/bin/dkg/share_computation_chunk/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "share_computation_chunk" +type = "bin" +authors = [""] + +[dependencies] +lib = { path = "../../../lib" } \ No newline at end of file diff --git a/circuits/bin/dkg/share_computation_chunk/src/main.nr b/circuits/bin/dkg/share_computation_chunk/src/main.nr new file mode 100644 index 0000000000..6a5e147cf6 --- /dev/null +++ b/circuits/bin/dkg/share_computation_chunk/src/main.nr @@ -0,0 +1,21 @@ +use lib::configs::default::dkg::{ + L_THRESHOLD, PARITY_MATRIX, SHARE_COMPUTATION_BIT_SHARE, SHARE_COMPUTATION_CHUNK_SIZE, + SHARE_COMPUTATION_SK_CHUNK_CONFIGS, +}; +use lib::configs::default::{N_PARTIES, T}; +use lib::core::dkg::share_computation::chunk::ShareComputationChunk; + +global CHUNK_IDX: u32 = 0; // change per chunk main + +fn main( + chunk_commitment: pub Field, + y_chunk: [[[Field; N_PARTIES + 1]; L_THRESHOLD]; SHARE_COMPUTATION_CHUNK_SIZE], +) { + let circuit: ShareComputationChunk = ShareComputationChunk::new( + SHARE_COMPUTATION_SK_CHUNK_CONFIGS, + chunk_commitment, + y_chunk, + PARITY_MATRIX, + ); + circuit.execute() +} diff --git a/circuits/bin/dkg/sk_share_computation/Nargo.toml b/circuits/bin/dkg/sk_share_computation/Nargo.toml deleted file mode 100644 index 25770d2eb3..0000000000 --- a/circuits/bin/dkg/sk_share_computation/Nargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "sk_share_computation" -type = "bin" -authors = ["Gnosis Guild / Enclave"] - - -[dependencies] -lib = { path = "../../../lib" } diff --git a/circuits/bin/dkg/sk_share_computation/README.md b/circuits/bin/dkg/sk_share_computation/README.md deleted file mode 100644 index 1090c57822..0000000000 --- a/circuits/bin/dkg/sk_share_computation/README.md +++ /dev/null @@ -1 +0,0 @@ -instantiation of correct Secret Key Secret Share Computation (PVSS #2a) diff --git a/circuits/bin/dkg/sk_share_computation/src/main.nr b/circuits/bin/dkg/sk_share_computation/src/main.nr deleted file mode 100644 index 755fc878f6..0000000000 --- a/circuits/bin/dkg/sk_share_computation/src/main.nr +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. - -use lib::configs::default::dkg::{ - L_THRESHOLD, N, PARITY_MATRIX, SHARE_COMPUTATION_BIT_SHARE, SHARE_COMPUTATION_SK_BIT_SECRET, - SHARE_COMPUTATION_SK_CONFIGS, -}; -use lib::configs::default::{N_PARTIES, T}; -use lib::core::dkg::share_computation::SecretKeyShareComputation; -use lib::math::polynomial::Polynomial; - -fn main( - expected_secret_commitment: pub Field, - sk_secret: Polynomial, - y: [[[Field; N_PARTIES + 1]; L_THRESHOLD]; N], -) -> pub [[Field; L_THRESHOLD]; N_PARTIES] { - let sk_share_computation: SecretKeyShareComputation = SecretKeyShareComputation::new( - SHARE_COMPUTATION_SK_CONFIGS, - expected_secret_commitment, - sk_secret, - y, - PARITY_MATRIX, - ); - - sk_share_computation.execute() -} diff --git a/circuits/bin/dkg/sk_share_computation_base/Nargo.toml b/circuits/bin/dkg/sk_share_computation_base/Nargo.toml new file mode 100644 index 0000000000..6641d2761d --- /dev/null +++ b/circuits/bin/dkg/sk_share_computation_base/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "sk_share_computation_base" +type = "bin" +authors = [""] + +[dependencies] +lib = { path = "../../../lib" } \ No newline at end of file diff --git a/circuits/bin/dkg/sk_share_computation_base/src/main.nr b/circuits/bin/dkg/sk_share_computation_base/src/main.nr new file mode 100644 index 0000000000..e5b95fa586 --- /dev/null +++ b/circuits/bin/dkg/sk_share_computation_base/src/main.nr @@ -0,0 +1,21 @@ +use lib::configs::default::dkg::{ + L_THRESHOLD, N, SHARE_COMPUTATION_CHUNK_SIZE, SHARE_COMPUTATION_N_CHUNKS, + SHARE_COMPUTATION_SK_BASE_CONFIGS, SHARE_COMPUTATION_SK_BIT_SECRET, +}; +use lib::configs::default::{N_PARTIES, T}; +use lib::core::dkg::share_computation::base::SecretKeyShareComputationBase; +use lib::math::polynomial::Polynomial; + +fn main( + expected_secret_commitment: pub Field, + sk_secret: Polynomial, + y: [[[Field; N_PARTIES + 1]; L_THRESHOLD]; N], +) -> pub ([Field; SHARE_COMPUTATION_N_CHUNKS], [[Field; L_THRESHOLD]; N_PARTIES]) { + let sk_share_computation_base: SecretKeyShareComputationBase = SecretKeyShareComputationBase::new( + SHARE_COMPUTATION_SK_BASE_CONFIGS, + expected_secret_commitment, + sk_secret, + y, + ); + sk_share_computation_base.execute() +} diff --git a/circuits/lib/src/configs/insecure/dkg.nr b/circuits/lib/src/configs/insecure/dkg.nr index cb34cdaade..b24559abd0 100644 --- a/circuits/lib/src/configs/insecure/dkg.nr +++ b/circuits/lib/src/configs/insecure/dkg.nr @@ -6,7 +6,8 @@ use crate::configs::default::{N_PARTIES, T}; pub use crate::configs::insecure::threshold::{L as L_THRESHOLD, QIS as QIS_THRESHOLD}; -use crate::core::dkg::share_computation::Configs as ShareComputationConfigs; +use crate::core::dkg::share_computation::base::Configs as ShareComputationBaseConfigs; +use crate::core::dkg::share_computation::chunk::Configs as ShareComputationChunkConfigs; use crate::core::dkg::share_encryption::Configs as ShareEncryptionConfigs; // Global configs for DKG insecure preset @@ -41,9 +42,15 @@ share_computation_sk (CIRCUIT 2a) pub global SHARE_COMPUTATION_BIT_SHARE: u32 = 36; pub global SHARE_COMPUTATION_SK_BIT_SECRET: u32 = 1; -// share_computation_sk - configs -pub global SHARE_COMPUTATION_SK_CONFIGS: ShareComputationConfigs = - ShareComputationConfigs::new(QIS_THRESHOLD); +// With N=512 and 5 parties, a single chunk covers all coefficients +pub global SHARE_COMPUTATION_CHUNK_SIZE: u32 = 512; +pub global SHARE_COMPUTATION_N_CHUNKS: u32 = 1; // N / CHUNK_SIZE = 512 / 512 + +pub global SHARE_COMPUTATION_SK_BASE_CONFIGS: ShareComputationBaseConfigs = + ShareComputationBaseConfigs::new(QIS_THRESHOLD); + +pub global SHARE_COMPUTATION_SK_CHUNK_CONFIGS: ShareComputationChunkConfigs = + ShareComputationChunkConfigs::new(QIS_THRESHOLD); /************************************ ------------------------------------- @@ -54,9 +61,11 @@ share_computation_e_sm (CIRCUIT 2b) // share_computation_e_sm - bit parameters pub global SHARE_COMPUTATION_E_SM_BIT_SECRET: u32 = 23; -// verify_shares - configs -pub global SHARE_COMPUTATION_E_SM_CONFIGS: ShareComputationConfigs = - ShareComputationConfigs::new(QIS_THRESHOLD); +pub global SHARE_COMPUTATION_E_SM_BASE_CONFIGS: ShareComputationBaseConfigs = + ShareComputationBaseConfigs::new(QIS_THRESHOLD); + +pub global SHARE_COMPUTATION_E_SM_CHUNK_CONFIGS: ShareComputationChunkConfigs = + ShareComputationChunkConfigs::new(QIS_THRESHOLD); /************************************ ------------------------------------- diff --git a/circuits/lib/src/configs/secure/dkg.nr b/circuits/lib/src/configs/secure/dkg.nr index 216418a4d6..21d2dcab66 100644 --- a/circuits/lib/src/configs/secure/dkg.nr +++ b/circuits/lib/src/configs/secure/dkg.nr @@ -6,7 +6,8 @@ use crate::configs::default::{N_PARTIES, T}; pub use crate::configs::secure::threshold::{L as L_THRESHOLD, QIS as QIS_THRESHOLD}; -use crate::core::dkg::share_computation::Configs as ShareComputationConfigs; +use crate::core::dkg::share_computation::base::Configs as ShareComputationBaseConfigs; +use crate::core::dkg::share_computation::chunk::Configs as ShareComputationChunkConfigs; use crate::core::dkg::share_encryption::Configs as ShareEncryptionConfigs; // Global configs for DKG secure preset @@ -39,13 +40,21 @@ share_computation_sk (CIRCUIT 2a) ------------------------------------- ************************************/ -// share_computation_sk - bit parameters pub global SHARE_COMPUTATION_BIT_SHARE: u32 = 53; pub global SHARE_COMPUTATION_SK_BIT_SECRET: u32 = 1; -// share_computation_sk - configs -pub global SHARE_COMPUTATION_SK_CONFIGS: ShareComputationConfigs = - ShareComputationConfigs::new(QIS_THRESHOLD); +// Chunk size controls circuit size vs number of chunks tradeoff. +// N_CHUNKS = N / CHUNK_SIZE +// At 5 parties: CHUNK_SIZE=512, N_CHUNKS=16 +// At 50 parties: reduce CHUNK_SIZE to keep chunk circuit constant size +pub global SHARE_COMPUTATION_CHUNK_SIZE: u32 = 512; +// N / CHUNK_SIZE = 8192 / 512 =1 6 +pub global SHARE_COMPUTATION_N_CHUNKS: u32 = 16; +pub global SHARE_COMPUTATION_SK_BASE_CONFIGS: ShareComputationBaseConfigs = + ShareComputationBaseConfigs::new(QIS_THRESHOLD); + +pub global SHARE_COMPUTATION_SK_CHUNK_CONFIGS: ShareComputationChunkConfigs = + ShareComputationChunkConfigs::new(QIS_THRESHOLD); /************************************ ------------------------------------- @@ -53,13 +62,13 @@ share_computation_e_sm (CIRCUIT 2b) ------------------------------------- ************************************/ -// share_computation_e_sm - bit parameters pub global SHARE_COMPUTATION_E_SM_BIT_SECRET: u32 = 192; -// verify_shares - configs -pub global SHARE_COMPUTATION_E_SM_CONFIGS: ShareComputationConfigs = - ShareComputationConfigs::new(QIS_THRESHOLD); +pub global SHARE_COMPUTATION_E_SM_BASE_CONFIGS: ShareComputationBaseConfigs = + ShareComputationBaseConfigs::new(QIS_THRESHOLD); +pub global SHARE_COMPUTATION_E_SM_CHUNK_CONFIGS: ShareComputationChunkConfigs = + ShareComputationChunkConfigs::new(QIS_THRESHOLD); /************************************ ------------------------------------- share_encryption_sk (CIRCUIT 3a) diff --git a/circuits/lib/src/core/dkg/share_computation.nr b/circuits/lib/src/core/dkg/share_computation.nr deleted file mode 100644 index d5edcbd253..0000000000 --- a/circuits/lib/src/core/dkg/share_computation.nr +++ /dev/null @@ -1,294 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. - -use crate::math::commitments::{ - compute_share_computation_e_sm_commitment, compute_share_computation_sk_commitment, - compute_share_encryption_commitment_from_shares, -}; -use crate::math::modulo::U128::ModU128; -use crate::math::polynomial::Polynomial; - -/// Cryptographic parameters for Threshold secret share verification circuit. -pub struct Configs { - /// CRT moduli: [q_0, q_1, ..., q_{L-1}] - pub qis: [Field; L], -} - -impl Configs { - pub fn new(qis: [Field; L]) -> Self { - Configs { qis } - } -} - -/// Correct Threshold Secret Key Share Computation (Circuit 2a). -/// -/// Verifies: -/// 1. secret commitment: verify secret hashes to expected_secret_commitment -/// 2. secret consistency: y[i][j][0] == sk_secret[i] for all i, j -/// 3. Range check: shares are in [0, q_j) -/// 4. Parity check: H[j] * y[i][j]^T == 0 mod q_j for all i, j -/// -/// For SK: sk_secret is the trinary coefficients -pub struct SecretKeyShareComputation { - configs: Configs, - /// Expected commitment to secret (from C1) - /// (public witness) - expected_secret_commitment: Field, - /// Secret key polynomial: Polynomial - /// trinary coefficients - /// (secret witness) - sk_secret: Polynomial, - /// Shares: y[coeff_idx][mod_idx][0..N_PARTIES+1] - /// y[i][j][0] = sk_secret[i] = f(0), y[i][j][k] = f(k) for k = 1..N_PARTIES - /// (secret witnesses) - y: [[[Field; N_PARTIES + 1]; L]; N], - /// Parity check matrices: H[mod_idx][row][col] - /// Size per modulus: (N_PARTIES - T) * (N_PARTIES + 1) - /// H * y^T = 0 mod q_j - /// (public constants) - h: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L], -} - -/// Correct Threshold Smudging Noise Share Computation (Circuit 2b). -/// -/// Verifies: -/// 1. secret commitment: verify secret hashes to expected_secret_commitment -/// 2. secret consistency: y[i][j][0] == e_sm[j][i] for all i, j -/// 3. Range check: shares are in [0, q_j) -/// 4. Parity check: H[j] * y[i][j]^T == 0 mod q_j for all i, j -/// -/// For ESM: e_sm[j] is the RNS representation at modulus j -pub struct SmudgingNoiseShareComputation { - configs: Configs, - /// Expected commitment to secret (from C1) - /// This is computed from all L RNS polynomials (matching - /// multiple_polynomial_payload's behavior which hashes all L modulus polynomials) - expected_secret_commitment: Field, - /// Smudging noise polynomial per modulus: [Polynomial; L] - /// For ESM: each modulus has its own polynomial (RNS representation) - e_sm_secret: [Polynomial; L], - /// Shares: y[coeff_idx][mod_idx][0..N_PARTIES+1] - /// y[i][j][0] = e_sm[j][i] = f(0), y[i][j][k] = f(k) for k = 1..N_PARTIES - y: [[[Field; N_PARTIES + 1]; L]; N], - /// Parity check matrices: H[mod_idx][row][col] - /// Size per modulus: (N_PARTIES - T) * (N_PARTIES + 1) - /// H * y^T = 0 mod q_j - h: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L], -} - -impl SecretKeyShareComputation { - pub fn new( - configs: Configs, - expected_secret_commitment: Field, - sk_secret: Polynomial, - y: [[[Field; N_PARTIES + 1]; L]; N], - h: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L], - ) -> Self { - SecretKeyShareComputation { configs, expected_secret_commitment, sk_secret, y, h } - } - - /// Main verification function - pub fn execute(self) -> [[Field; L]; N_PARTIES] { - // Step 1: Verify secret commitment matches expected - self.verify_secret_commitment(); - - // Step 2: Verify secret consistency - self.verify_secret_consistency(); - - // Step 3: Range checks - check_range_bounds::(self.configs.qis, self.y); - - // Step 4: Verify parity check - verify_parity_check::(self.configs.qis, self.h, self.y); - - // Step 5: Commit to shares for each party and modulus - commit_to_party_shares::(self.y) - } - - /// Verifies that secret hashes to expected_secret_commitment - fn verify_secret_commitment(self) { - assert( - compute_share_computation_sk_commitment::(self.sk_secret) - == self.expected_secret_commitment, - "SK commitment mismatch", - ); - } - - /// Verifies secret consistency: `y[i][j][0] == sk_secret[i]` for all i, j. - /// - /// This function ensures that for each coefficient i and CRT basis j, the share - /// at party ID 0 equals the corresponding secret coefficient for that modulus. - /// This is a fundamental property of Shamir secret sharing where the secret is the - /// evaluation of the sharing polynomial at point 0. - /// - /// sk_secret is the trinary coefficients, so y[i][j][0] is the same for all j. - /// - /// # Panics - /// The circuit will fail if secret consistency doesn't hold for any - /// coefficient or CRT basis. - fn verify_secret_consistency(self) { - for coeff_idx in 0..N { - let secret_coeff = self.sk_secret.coefficients[coeff_idx]; - - for mod_idx in 0..L { - assert(self.y[coeff_idx][mod_idx][0] == secret_coeff); - } - } - } -} - -impl SmudgingNoiseShareComputation { - pub fn new( - configs: Configs, - expected_secret_commitment: Field, - e_sm_secret: [Polynomial; L], - y: [[[Field; N_PARTIES + 1]; L]; N], - h: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L], - ) -> Self { - SmudgingNoiseShareComputation { configs, expected_secret_commitment, e_sm_secret, y, h } - } - - /// Main verification function - pub fn execute(self) -> [[Field; L]; N_PARTIES] { - // Step 1: Verify secret commitment matches expected - self.verify_secret_commitment(); - - // Step 2: Verify secret consistency - self.verify_secret_consistency(); - - // Step 3: Range checks - check_range_bounds::(self.configs.qis, self.y); - - // Step 4: Verify parity check - verify_parity_check::(self.configs.qis, self.h, self.y); - - // Step 5: Commit to shares for each party and modulus - commit_to_party_shares::(self.y) - } - - /// Verifies that secret hashes to expected_secret_commitment - /// The commitment is computed over all L RNS polynomials (matching - /// multiple_polynomial_payload's behavior which hashes all L modulus polynomials) - fn verify_secret_commitment(self) { - assert( - compute_share_computation_e_sm_commitment::(self.e_sm_secret) - == self.expected_secret_commitment, - "ESM commitment mismatch", - ); - } - - /// Verifies secret consistency: `y[i][j][0] == e_sm_secret[j][i]` for all i, j. - /// - /// This function ensures that for each coefficient i and CRT basis j, the share - /// at party ID 0 equals the corresponding secret coefficient for that modulus. - /// This is a fundamental property of Shamir secret sharing where the secret is the - /// evaluation of the sharing polynomial at point 0. - /// - /// e_sm_secret[j] is the RNS representation at modulus j, so y[i][j][0] varies per modulus. - /// - /// # Panics - /// The circuit will fail if secret consistency doesn't hold for any - /// coefficient or CRT basis. - fn verify_secret_consistency(self) { - for coeff_idx in 0..N { - for mod_idx in 0..L { - let secret_coeff = self.e_sm_secret[mod_idx].coefficients[coeff_idx]; - assert( - self.y[coeff_idx][mod_idx][0] == secret_coeff, - "Secret consistency check failed", - ); - } - } - } -} - -/// Performs range checks on secret key and share values. -/// -/// This function constrains all values to be within their expected bounds: -/// - Share values for parties k >= 1 must be in [0, q_j) for each CRT modulus q_j -/// -/// These bounds are critical for security and correctness of the Threshold scheme. -/// -/// # Panics -/// This function will cause the circuit to fail if any value is outside -/// its expected bounds. -pub fn check_range_bounds( - qis: [Field; L], - y: [[[Field; N_PARTIES + 1]; L]; N], -) { - // Shares y[i][j][k] for k >= 1 should be in [0, q_j) - for mod_idx in 0..L { - let q_j = qis[mod_idx]; - - for coeff_idx in 0..N { - for party_idx in 1..(N_PARTIES + 1) { - // Use range_check_standard from Polynomial by creating a single-coefficient polynomial - Polynomial::new([y[coeff_idx][mod_idx][party_idx]]) - .range_check_standard::(q_j); - } - } - } -} - -/// Verifies Reed-Solomon parity check: `H[j] * y[i][j]^T == 0 mod q_j` for all i, j. -/// -/// This function verifies that for each coefficient i and CRT basis j, the share -/// vector `y[i][j]` forms a valid Reed-Solomon codeword by satisfying the parity -/// check equation with the parity check matrix `H[j]`. -/// -/// The parity check matrix H[j] has dimensions `(N_PARTIES - T) * (N_PARTIES + 1)`, -/// and the share vector `y[i][j]` has length `N_PARTIES + 1`. The parity check -/// ensures that any T+1 shares can correctly reconstruct the secret key via -/// Lagrange interpolation. -/// -/// # Panics -/// The circuit will fail if the parity check doesn't hold for any coefficient, -/// CRT basis, or parity check row. -pub fn verify_parity_check( - qis: [Field; L], - h: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L], - y: [[[Field; N_PARTIES + 1]; L]; N], -) { - for coeff_idx in 0..N { - for mod_idx in 0..L { - let q_j = qis[mod_idx]; - - // For each row of H, compute dot product with y and verify == 0 - for row in 0..(N_PARTIES - T) { - let mut sum: Field = 0; - - for col in 0..(N_PARTIES + 1) { - sum = sum + h[mod_idx][row][col] * y[coeff_idx][mod_idx][col]; - } - - // Reduce mod q_j and verify == 0 - let m = ModU128::new(q_j); - let result = m.reduce_mod(sum); - assert(result == 0, "Parity check failed"); - } - } - } -} - -/// Commits to shares for each party and modulus -/// Returns [[Field; L]; N_PARTIES] where commitments[party_idx][mod_idx] -pub fn commit_to_party_shares( - y: [[[Field; N_PARTIES + 1]; L]; N], -) -> [[Field; L]; N_PARTIES] { - let mut commitments: [[Field; L]; N_PARTIES] = [[0; L]; N_PARTIES]; - - for party_idx in 0..N_PARTIES { - for mod_idx in 0..L { - commitments[party_idx][mod_idx] = compute_share_encryption_commitment_from_shares::( - y, - party_idx, - mod_idx, - ); - } - } - - commitments -} diff --git a/circuits/lib/src/core/dkg/share_computation/base.nr b/circuits/lib/src/core/dkg/share_computation/base.nr new file mode 100644 index 0000000000..22fb6c3571 --- /dev/null +++ b/circuits/lib/src/core/dkg/share_computation/base.nr @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// 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 chunk and party commitments +// to bind the chunk circuits and provide share commitments for downstream C3/C4. + +use crate::math::commitments::{ + compute_share_computation_chunk_commitment, compute_share_computation_e_sm_commitment, + compute_share_computation_sk_commitment, compute_share_encryption_commitment_from_shares, +}; +use crate::math::polynomial::Polynomial; + +pub struct Configs { + pub qis: [Field; L], +} + +impl Configs { + pub fn new(qis: [Field; L]) -> Self { + Configs { qis } + } +} + +// ===== BASE CIRCUIT FOR SK ===== + +pub struct SecretKeyShareComputationBase { + configs: Configs, + /// 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] + y: [[[Field; N_PARTIES + 1]; L]; N], +} + +impl SecretKeyShareComputationBase { + + pub fn new( + configs: Configs, + expected_secret_commitment: Field, + sk_secret: Polynomial, + y: [[[Field; N_PARTIES + 1]; L]; N], + ) -> Self { + SecretKeyShareComputationBase { configs, expected_secret_commitment, sk_secret, y } + } + + fn verify_secret_commitment(self) { + assert( + compute_share_computation_sk_commitment::(self.sk_secret) + == self.expected_secret_commitment, + "SK commitment mismatch", + ); + } + + /// Verifies y[i][j][0] == sk_secret[i] for all i, j + fn verify_secret_consistency(self) { + for coeff_idx in 0..N { + let secret_coeff = self.sk_secret.coefficients[coeff_idx]; + for mod_idx in 0..L { + assert( + self.y[coeff_idx][mod_idx][0] == secret_coeff, + "SK secret consistency check failed", + ); + } + } + } + + fn compute_chunk_commitments(self) -> [Field; N_CHUNKS] { + let mut chunk_commitments: [Field; N_CHUNKS] = [0; N_CHUNKS]; + for chunk_idx in 0..N_CHUNKS { + let start = chunk_idx * CHUNK_SIZE; + let mut y_chunk: [[[Field; N_PARTIES + 1]; L]; CHUNK_SIZE] = + [[[0; N_PARTIES + 1]; L]; CHUNK_SIZE]; + for i in 0..CHUNK_SIZE { + y_chunk[i] = self.y[start + i]; + } + chunk_commitments[chunk_idx] = compute_share_computation_chunk_commitment::( + y_chunk, + chunk_idx, + ); + } + chunk_commitments + } + + fn compute_party_commitments(self) -> [[Field; L]; N_PARTIES] { + let mut party_commitments: [[Field; L]; N_PARTIES] = [[0; L]; N_PARTIES]; + for party_idx in 0..N_PARTIES { + for mod_idx in 0..L { + party_commitments[party_idx][mod_idx] = compute_share_encryption_commitment_from_shares::( + self.y, + party_idx, + mod_idx, + ); + } + } + party_commitments + } + + /// Returns (chunk_commitments, party_commitments) + /// chunk_commitments bind the chunk circuits + /// party_commitments[party_idx][mod_idx] are used downstream in C3/C4 + pub fn execute(self) -> ([Field; N_CHUNKS], [[Field; L]; N_PARTIES]) { + self.verify_secret_commitment(); + self.verify_secret_consistency(); + let chunk_commitments = self.compute_chunk_commitments(); + let party_commitments = self.compute_party_commitments(); + (chunk_commitments, party_commitments) + } +} + +// ===== BASE CIRCUIT FOR ESM ===== + +pub struct SmudgingNoiseShareComputationBase { + configs: Configs, + /// 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] + y: [[[Field; N_PARTIES + 1]; L]; N], +} + +impl SmudgingNoiseShareComputationBase { + + pub fn new( + configs: Configs, + expected_secret_commitment: Field, + e_sm_secret: [Polynomial; L], + y: [[[Field; N_PARTIES + 1]; L]; N], + ) -> Self { + SmudgingNoiseShareComputationBase { configs, expected_secret_commitment, e_sm_secret, y } + } + + fn verify_secret_commitment(self) { + assert( + compute_share_computation_e_sm_commitment::(self.e_sm_secret) + == self.expected_secret_commitment, + "ESM commitment mismatch", + ); + } + + /// Verifies y[i][j][0] == e_sm_secret[j][i] for all i, j + fn verify_secret_consistency(self) { + for coeff_idx in 0..N { + for mod_idx in 0..L { + let secret_coeff = self.e_sm_secret[mod_idx].coefficients[coeff_idx]; + assert( + self.y[coeff_idx][mod_idx][0] == secret_coeff, + "ESM secret consistency check failed", + ); + } + } + } + + fn compute_chunk_commitments(self) -> [Field; N_CHUNKS] { + let mut chunk_commitments: [Field; N_CHUNKS] = [0; N_CHUNKS]; + for chunk_idx in 0..N_CHUNKS { + let start = chunk_idx * CHUNK_SIZE; + let mut y_chunk: [[[Field; N_PARTIES + 1]; L]; CHUNK_SIZE] = + [[[0; N_PARTIES + 1]; L]; CHUNK_SIZE]; + for i in 0..CHUNK_SIZE { + y_chunk[i] = self.y[start + i]; + } + chunk_commitments[chunk_idx] = compute_share_computation_chunk_commitment::( + y_chunk, + chunk_idx, + ); + } + chunk_commitments + } + + fn compute_party_commitments(self) -> [[Field; L]; N_PARTIES] { + let mut party_commitments: [[Field; L]; N_PARTIES] = [[0; L]; N_PARTIES]; + for party_idx in 0..N_PARTIES { + for mod_idx in 0..L { + party_commitments[party_idx][mod_idx] = compute_share_encryption_commitment_from_shares::( + self.y, + party_idx, + mod_idx, + ); + } + } + party_commitments + } + + /// Returns (chunk_commitments, party_commitments) + /// chunk_commitments bind the chunk circuits + /// party_commitments[party_idx][mod_idx] are used downstream in C3/C4 + pub fn execute(self) -> ([Field; N_CHUNKS], [[Field; L]; N_PARTIES]) { + self.verify_secret_commitment(); + self.verify_secret_consistency(); + let chunk_commitments = self.compute_chunk_commitments(); + let party_commitments = self.compute_party_commitments(); + (chunk_commitments, party_commitments) + } +} diff --git a/circuits/lib/src/core/dkg/share_computation/chunk.nr b/circuits/lib/src/core/dkg/share_computation/chunk.nr new file mode 100644 index 0000000000..82bf2a5234 --- /dev/null +++ b/circuits/lib/src/core/dkg/share_computation/chunk.nr @@ -0,0 +1,87 @@ +// Chunk circuit for C2. Verifies range checks and parity check for a chunk of +// CHUNK_SIZE coefficients. CHUNK_IDX identifies which chunk this is, used to +// verify consistency against the chunk commitment from the base circuit. + +use crate::math::commitments::compute_share_computation_chunk_commitment; +use crate::math::modulo::U128::ModU128; +use crate::math::polynomial::Polynomial; + +pub struct Configs { + pub qis: [Field; L], +} + +impl Configs { + pub fn new(qis: [Field; L]) -> Self { + Configs { qis } + } +} + +pub struct ShareComputationChunk { + configs: Configs, + /// Public input from base circuit + chunk_commitment: Field, + /// Slice of y for this chunk: y[CHUNK_IDX*CHUNK_SIZE..(CHUNK_IDX+1)*CHUNK_SIZE] + y_chunk: [[[Field; N_PARTIES + 1]; L]; CHUNK_SIZE], + /// Parity check matrix + h: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L], +} + +impl ShareComputationChunk { + + pub fn new( + configs: Configs, + chunk_commitment: Field, + y_chunk: [[[Field; N_PARTIES + 1]; L]; CHUNK_SIZE], + h: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L], + ) -> Self { + ShareComputationChunk { configs, chunk_commitment, y_chunk, h } + } + + /// Verifies y_chunk is consistent with chunk_commitment from base circuit. + /// CHUNK_IDX is a compile-time generic so each chunk circuit is uniquely bound + /// to its position, a prover cannot reuse the same chunk data in a different position. + fn verify_chunk_commitment(self) { + let computed = compute_share_computation_chunk_commitment::( + self.y_chunk, + CHUNK_IDX, + ); + assert(computed == self.chunk_commitment, "chunk commitment mismatch"); + } + + /// Range checks: shares y[i][j][k] for k >= 1 must be in [0, q_j) + /// k == 0 is the secret itself, checked in base circuit via secret consistency + fn check_range_bounds(self) { + for mod_idx in 0..L { + let q_j = self.configs.qis[mod_idx]; + for coeff_idx in 0..CHUNK_SIZE { + for party_idx in 1..(N_PARTIES + 1) { + Polynomial::new([self.y_chunk[coeff_idx][mod_idx][party_idx]]) + .range_check_standard::(q_j); + } + } + } + } + + /// Parity check: H[j] * y[i][j]^T == 0 mod q_j for all i in chunk, j + fn verify_parity_check(self) { + for coeff_idx in 0..CHUNK_SIZE { + for mod_idx in 0..L { + let q_j = self.configs.qis[mod_idx]; + let m = ModU128::new(q_j); + for row in 0..(N_PARTIES - T) { + let mut sum: Field = 0; + for col in 0..(N_PARTIES + 1) { + sum += self.h[mod_idx][row][col] * self.y_chunk[coeff_idx][mod_idx][col]; + } + assert(m.reduce_mod(sum) == 0, "Parity check failed"); + } + } + } + } + + pub fn execute(self) { + self.verify_chunk_commitment(); + self.check_range_bounds(); + self.verify_parity_check(); + } +} diff --git a/circuits/lib/src/core/dkg/share_computation/mod.nr b/circuits/lib/src/core/dkg/share_computation/mod.nr new file mode 100644 index 0000000000..466ad29080 --- /dev/null +++ b/circuits/lib/src/core/dkg/share_computation/mod.nr @@ -0,0 +1,2 @@ +pub mod base; +pub mod chunk; diff --git a/circuits/lib/src/math/commitments.nr b/circuits/lib/src/math/commitments.nr index 4e47af542f..5e6288a953 100644 --- a/circuits/lib/src/math/commitments.nr +++ b/circuits/lib/src/math/commitments.nr @@ -109,6 +109,21 @@ pub global DS_USER_DATA_ENCRYPTION_COMMITMENT: [u8; 64] = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; +// Domain separator - "SHARE_COMPUTATION_CHUNK" +pub global DS_SHARE_COMPUTATION_CHUNK: [u8; 64] = [ + 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x55, 0x54, 0x41, 0x54, 0x49, 0x4f, + 0x4e, 0x5f, 0x43, 0x48, 0x55, 0x4e, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +// Domain separator - "SHARE_COMPUTATION_PARTY" +pub global DS_SHARE_COMPUTATION_PARTY: [u8; 64] = [ + 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x55, 0x54, 0x41, 0x54, 0x49, 0x4f, + 0x4e, 0x5f, 0x50, 0x41, 0x52, 0x54, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; /// WRAPPERS pub fn compute_commitment(inputs: Vec, domain_separator: [u8; 64]) -> Field { @@ -274,3 +289,33 @@ pub fn compute_user_data_encryption_ct0_challenge(payload: Vec(payload: Vec) -> Vec { compute_challenge::(payload, DS_CLG_USER_DATA_ENCRYPTION) } + +pub fn compute_share_computation_chunk_commitment( + y_chunk: [[[Field; N_PARTIES + 1]; L]; CHUNK_SIZE], + chunk_idx: u32, +) -> Field { + let mut payload = Vec::new(); + for coeff_idx in 0..CHUNK_SIZE { + for mod_idx in 0..L { + for party_idx in 0..(N_PARTIES + 1) { + payload.push(y_chunk[coeff_idx][mod_idx][party_idx]); + } + } + } + payload.push(chunk_idx as Field); + compute_commitment(payload, DS_SHARE_COMPUTATION_CHUNK) +} + +pub fn compute_share_computation_party_commitment( + y: [[[Field; N_PARTIES + 1]; L]; N], + party_idx: u32, + mod_idx: u32, +) -> Field { + let mut payload = Vec::new(); + for coeff_idx in 0..N { + payload.push(y[coeff_idx][mod_idx][party_idx + 1]); + } + payload.push(party_idx as Field); + payload.push(mod_idx as Field); + compute_commitment(payload, DS_SHARE_COMPUTATION_PARTY) +} From 54f688b0366acaeac548796455420daefac1c91e Mon Sep 17 00:00:00 2001 From: Zara Date: Mon, 9 Mar 2026 09:50:46 -0700 Subject: [PATCH 02/27] moved the chunk commit to wrapper from the base --- circuits/bin/dkg/Nargo.toml | 2 - .../esm_share_computation_base/src/main.nr | 10 +-- .../dkg/share_computation_chunk/src/main.nr | 20 ++--- .../dkg/share_computation_wrapper/Nargo.toml | 8 ++ .../dkg/share_computation_wrapper/src/main.nr | 76 +++++++++++++++++++ .../dkg/sk_share_computation_base/src/main.nr | 12 +-- .../src/core/dkg/share_computation/base.nr | 73 +++++------------- .../src/core/dkg/share_computation/chunk.nr | 35 ++++----- circuits/lib/src/math/commitments.nr | 21 +---- 9 files changed, 136 insertions(+), 121 deletions(-) create mode 100644 circuits/bin/dkg/share_computation_wrapper/Nargo.toml create mode 100644 circuits/bin/dkg/share_computation_wrapper/src/main.nr diff --git a/circuits/bin/dkg/Nargo.toml b/circuits/bin/dkg/Nargo.toml index f13092c377..2e99c1d1db 100644 --- a/circuits/bin/dkg/Nargo.toml +++ b/circuits/bin/dkg/Nargo.toml @@ -1,8 +1,6 @@ [workspace] members = [ "pk", - "sk_share_computation", - "e_sm_share_computation", "share_encryption", "share_decryption", "sk_share_computation_base", diff --git a/circuits/bin/dkg/esm_share_computation_base/src/main.nr b/circuits/bin/dkg/esm_share_computation_base/src/main.nr index 3b18aba939..aef2e8e3bd 100644 --- a/circuits/bin/dkg/esm_share_computation_base/src/main.nr +++ b/circuits/bin/dkg/esm_share_computation_base/src/main.nr @@ -1,6 +1,5 @@ use lib::configs::default::dkg::{ - L_THRESHOLD, N, SHARE_COMPUTATION_CHUNK_SIZE, SHARE_COMPUTATION_E_SM_BASE_CONFIGS, - SHARE_COMPUTATION_E_SM_BIT_SECRET, SHARE_COMPUTATION_N_CHUNKS, + L_THRESHOLD, N, SHARE_COMPUTATION_E_SM_BASE_CONFIGS, SHARE_COMPUTATION_E_SM_BIT_SECRET, }; use lib::configs::default::{N_PARTIES, T}; use lib::core::dkg::share_computation::base::SmudgingNoiseShareComputationBase; @@ -9,9 +8,10 @@ use lib::math::polynomial::Polynomial; fn main( expected_secret_commitment: pub Field, e_sm_secret: [Polynomial; L_THRESHOLD], - y: [[[Field; N_PARTIES + 1]; L_THRESHOLD]; N], -) -> pub ([Field; SHARE_COMPUTATION_N_CHUNKS], [[Field; L_THRESHOLD]; N_PARTIES]) { - let circuit: SmudgingNoiseShareComputationBase = SmudgingNoiseShareComputationBase::new( + // y is public so wrapper can enforce consistency with chunk circuits + y: pub [[[Field; N_PARTIES + 1]; L_THRESHOLD]; N], +) -> pub [[Field; L_THRESHOLD]; N_PARTIES] { + let circuit: SmudgingNoiseShareComputationBase = SmudgingNoiseShareComputationBase::new( SHARE_COMPUTATION_E_SM_BASE_CONFIGS, expected_secret_commitment, e_sm_secret, diff --git a/circuits/bin/dkg/share_computation_chunk/src/main.nr b/circuits/bin/dkg/share_computation_chunk/src/main.nr index 6a5e147cf6..074eed7f3e 100644 --- a/circuits/bin/dkg/share_computation_chunk/src/main.nr +++ b/circuits/bin/dkg/share_computation_chunk/src/main.nr @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + use lib::configs::default::dkg::{ L_THRESHOLD, PARITY_MATRIX, SHARE_COMPUTATION_BIT_SHARE, SHARE_COMPUTATION_CHUNK_SIZE, SHARE_COMPUTATION_SK_CHUNK_CONFIGS, @@ -5,17 +11,11 @@ use lib::configs::default::dkg::{ use lib::configs::default::{N_PARTIES, T}; use lib::core::dkg::share_computation::chunk::ShareComputationChunk; -global CHUNK_IDX: u32 = 0; // change per chunk main - fn main( - chunk_commitment: pub Field, - y_chunk: [[[Field; N_PARTIES + 1]; L_THRESHOLD]; SHARE_COMPUTATION_CHUNK_SIZE], + // y_chunk is public so wrapper can enforce consistency with base circuit + y_chunk: pub [[[Field; N_PARTIES + 1]; L_THRESHOLD]; SHARE_COMPUTATION_CHUNK_SIZE], ) { - let circuit: ShareComputationChunk = ShareComputationChunk::new( - SHARE_COMPUTATION_SK_CHUNK_CONFIGS, - chunk_commitment, - y_chunk, - PARITY_MATRIX, - ); + let circuit: ShareComputationChunk = + ShareComputationChunk::new(SHARE_COMPUTATION_SK_CHUNK_CONFIGS, y_chunk, PARITY_MATRIX); circuit.execute() } diff --git a/circuits/bin/dkg/share_computation_wrapper/Nargo.toml b/circuits/bin/dkg/share_computation_wrapper/Nargo.toml new file mode 100644 index 0000000000..c628a64873 --- /dev/null +++ b/circuits/bin/dkg/share_computation_wrapper/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "share_computation_wrapper" +type = "bin" +authors = [""] + +[dependencies] +lib = { path = "../../../lib" } +bb_proof_verification = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v3.0.0-nightly.20260102", directory = "barretenberg/noir/bb_proof_verification" } \ No newline at end of file diff --git a/circuits/bin/dkg/share_computation_wrapper/src/main.nr b/circuits/bin/dkg/share_computation_wrapper/src/main.nr new file mode 100644 index 0000000000..b1c8833ed2 --- /dev/null +++ b/circuits/bin/dkg/share_computation_wrapper/src/main.nr @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use bb_proof_verification::{UltraHonkProof, UltraHonkVerificationKey, verify_honk_proof_non_zk}; +use lib::configs::default::dkg::{ + L_THRESHOLD, N, SHARE_COMPUTATION_CHUNK_SIZE, SHARE_COMPUTATION_N_CHUNKS, +}; +use lib::configs::default::N_PARTIES; +use lib::math::commitments::compute_recursive_aggregation_commitment; + +// 1 base proof + N_CHUNKS chunk proofs +pub global N_PROOFS: u32 = 1 + SHARE_COMPUTATION_N_CHUNKS; + +// Base proof public inputs: +// expected_secret_commitment (1) + y (N * L_THRESHOLD * (N_PARTIES+1)) + party_commitments (N_PARTIES * L_THRESHOLD) +pub global BASE_PUBLIC_INPUTS: u32 = + 1 + (N * L_THRESHOLD * (N_PARTIES + 1)) + (N_PARTIES * L_THRESHOLD); + +// Chunk proof public inputs: +// y_chunk (CHUNK_SIZE * L_THRESHOLD * (N_PARTIES+1)) +pub global CHUNK_PUBLIC_INPUTS: u32 = SHARE_COMPUTATION_CHUNK_SIZE * L_THRESHOLD * (N_PARTIES + 1); + +fn main( + base_verification_key: UltraHonkVerificationKey, + base_proof: UltraHonkProof, + base_public_inputs: [Field; BASE_PUBLIC_INPUTS], + chunk_verification_key: UltraHonkVerificationKey, + chunk_proofs: [UltraHonkProof; SHARE_COMPUTATION_N_CHUNKS], + chunk_public_inputs: [[Field; CHUNK_PUBLIC_INPUTS]; SHARE_COMPUTATION_N_CHUNKS], + key_hash: pub Field, +) -> pub Field { + // Step 1: Verify base proof + verify_honk_proof_non_zk( + base_verification_key, + base_proof, + base_public_inputs, + key_hash, + ); + + // Step 2: Verify each chunk proof and enforce y consistency with base + // y starts at index 1 in base_public_inputs (after expected_secret_commitment) + // y_chunk for chunk i starts at offset i * CHUNK_PUBLIC_INPUTS within base y + for chunk_idx in 0..SHARE_COMPUTATION_N_CHUNKS { + verify_honk_proof_non_zk( + chunk_verification_key, + chunk_proofs[chunk_idx], + chunk_public_inputs[chunk_idx], + key_hash, + ); + + // Enforce y consistency: base_public_inputs y slice == chunk y_chunk + let base_y_start = 1 + chunk_idx * CHUNK_PUBLIC_INPUTS; + for i in 0..CHUNK_PUBLIC_INPUTS { + assert( + base_public_inputs[base_y_start + i] == chunk_public_inputs[chunk_idx][i], + "y consistency check failed between base and chunk", + ); + } + } + + // Step 3: Aggregate all public inputs into a single commitment + let mut aggregated_public_inputs = Vec::new(); + for i in 0..BASE_PUBLIC_INPUTS { + aggregated_public_inputs.push(base_public_inputs[i]); + } + for chunk_idx in 0..SHARE_COMPUTATION_N_CHUNKS { + for i in 0..CHUNK_PUBLIC_INPUTS { + aggregated_public_inputs.push(chunk_public_inputs[chunk_idx][i]); + } + } + + compute_recursive_aggregation_commitment(aggregated_public_inputs) +} diff --git a/circuits/bin/dkg/sk_share_computation_base/src/main.nr b/circuits/bin/dkg/sk_share_computation_base/src/main.nr index e5b95fa586..ac4188fa4f 100644 --- a/circuits/bin/dkg/sk_share_computation_base/src/main.nr +++ b/circuits/bin/dkg/sk_share_computation_base/src/main.nr @@ -1,6 +1,5 @@ use lib::configs::default::dkg::{ - L_THRESHOLD, N, SHARE_COMPUTATION_CHUNK_SIZE, SHARE_COMPUTATION_N_CHUNKS, - SHARE_COMPUTATION_SK_BASE_CONFIGS, SHARE_COMPUTATION_SK_BIT_SECRET, + L_THRESHOLD, N, SHARE_COMPUTATION_SK_BASE_CONFIGS, SHARE_COMPUTATION_SK_BIT_SECRET, }; use lib::configs::default::{N_PARTIES, T}; use lib::core::dkg::share_computation::base::SecretKeyShareComputationBase; @@ -9,13 +8,14 @@ use lib::math::polynomial::Polynomial; fn main( expected_secret_commitment: pub Field, sk_secret: Polynomial, - y: [[[Field; N_PARTIES + 1]; L_THRESHOLD]; N], -) -> pub ([Field; SHARE_COMPUTATION_N_CHUNKS], [[Field; L_THRESHOLD]; N_PARTIES]) { - let sk_share_computation_base: SecretKeyShareComputationBase = SecretKeyShareComputationBase::new( + // y is public so wrapper can enforce consistency with chunk circuits + y: pub [[[Field; N_PARTIES + 1]; L_THRESHOLD]; N], +) -> pub [[Field; L_THRESHOLD]; N_PARTIES] { + let circuit: SecretKeyShareComputationBase = SecretKeyShareComputationBase::new( SHARE_COMPUTATION_SK_BASE_CONFIGS, expected_secret_commitment, sk_secret, y, ); - sk_share_computation_base.execute() + circuit.execute() } diff --git a/circuits/lib/src/core/dkg/share_computation/base.nr b/circuits/lib/src/core/dkg/share_computation/base.nr index 22fb6c3571..2370e8e21f 100644 --- a/circuits/lib/src/core/dkg/share_computation/base.nr +++ b/circuits/lib/src/core/dkg/share_computation/base.nr @@ -5,12 +5,13 @@ // or FITNESS FOR A PARTICULAR PURPOSE. // Base circuit for C2 (SecretKeyShareComputation and SmudgingNoiseShareComputation). -// Verifies secret commitment and consistency, and outputs chunk and party commitments -// to bind the chunk circuits and provide share commitments for downstream C3/C4. +// 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. use crate::math::commitments::{ - compute_share_computation_chunk_commitment, compute_share_computation_e_sm_commitment, - compute_share_computation_sk_commitment, compute_share_encryption_commitment_from_shares, + compute_share_computation_e_sm_commitment, compute_share_computation_sk_commitment, + compute_share_encryption_commitment_from_shares, }; use crate::math::polynomial::Polynomial; @@ -26,17 +27,18 @@ impl Configs { // ===== BASE CIRCUIT FOR SK ===== -pub struct SecretKeyShareComputationBase { +pub struct SecretKeyShareComputationBase { configs: Configs, /// 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 y: [[[Field; N_PARTIES + 1]; L]; N], } -impl SecretKeyShareComputationBase { +impl SecretKeyShareComputationBase { pub fn new( configs: Configs, @@ -68,23 +70,6 @@ impl [Field; N_CHUNKS] { - let mut chunk_commitments: [Field; N_CHUNKS] = [0; N_CHUNKS]; - for chunk_idx in 0..N_CHUNKS { - let start = chunk_idx * CHUNK_SIZE; - let mut y_chunk: [[[Field; N_PARTIES + 1]; L]; CHUNK_SIZE] = - [[[0; N_PARTIES + 1]; L]; CHUNK_SIZE]; - for i in 0..CHUNK_SIZE { - y_chunk[i] = self.y[start + i]; - } - chunk_commitments[chunk_idx] = compute_share_computation_chunk_commitment::( - y_chunk, - chunk_idx, - ); - } - chunk_commitments - } - fn compute_party_commitments(self) -> [[Field; L]; N_PARTIES] { let mut party_commitments: [[Field; L]; N_PARTIES] = [[0; L]; N_PARTIES]; for party_idx in 0..N_PARTIES { @@ -99,31 +84,28 @@ impl ([Field; N_CHUNKS], [[Field; L]; N_PARTIES]) { + /// Returns party_commitments[party_idx][mod_idx] for downstream use in C3/C4 + pub fn execute(self) -> [[Field; L]; N_PARTIES] { self.verify_secret_commitment(); self.verify_secret_consistency(); - let chunk_commitments = self.compute_chunk_commitments(); - let party_commitments = self.compute_party_commitments(); - (chunk_commitments, party_commitments) + self.compute_party_commitments() } } // ===== BASE CIRCUIT FOR ESM ===== -pub struct SmudgingNoiseShareComputationBase { +pub struct SmudgingNoiseShareComputationBase { configs: Configs, /// 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 y: [[[Field; N_PARTIES + 1]; L]; N], } -impl SmudgingNoiseShareComputationBase { +impl SmudgingNoiseShareComputationBase { pub fn new( configs: Configs, @@ -155,23 +137,6 @@ impl [Field; N_CHUNKS] { - let mut chunk_commitments: [Field; N_CHUNKS] = [0; N_CHUNKS]; - for chunk_idx in 0..N_CHUNKS { - let start = chunk_idx * CHUNK_SIZE; - let mut y_chunk: [[[Field; N_PARTIES + 1]; L]; CHUNK_SIZE] = - [[[0; N_PARTIES + 1]; L]; CHUNK_SIZE]; - for i in 0..CHUNK_SIZE { - y_chunk[i] = self.y[start + i]; - } - chunk_commitments[chunk_idx] = compute_share_computation_chunk_commitment::( - y_chunk, - chunk_idx, - ); - } - chunk_commitments - } - fn compute_party_commitments(self) -> [[Field; L]; N_PARTIES] { let mut party_commitments: [[Field; L]; N_PARTIES] = [[0; L]; N_PARTIES]; for party_idx in 0..N_PARTIES { @@ -186,14 +151,10 @@ impl ([Field; N_CHUNKS], [[Field; L]; N_PARTIES]) { + /// Returns party_commitments[party_idx][mod_idx] for downstream use in C3/C4 + pub fn execute(self) -> [[Field; L]; N_PARTIES] { self.verify_secret_commitment(); self.verify_secret_consistency(); - let chunk_commitments = self.compute_chunk_commitments(); - let party_commitments = self.compute_party_commitments(); - (chunk_commitments, party_commitments) + 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 82bf2a5234..5dc4722ea7 100644 --- a/circuits/lib/src/core/dkg/share_computation/chunk.nr +++ b/circuits/lib/src/core/dkg/share_computation/chunk.nr @@ -1,8 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// 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. CHUNK_IDX identifies which chunk this is, used to -// verify consistency against the chunk commitment from the base circuit. +// CHUNK_SIZE coefficients. y_chunk is public so the wrapper can enforce consistency +// with the base circuit without hashing overhead in either circuit. -use crate::math::commitments::compute_share_computation_chunk_commitment; use crate::math::modulo::U128::ModU128; use crate::math::polynomial::Polynomial; @@ -16,36 +21,23 @@ impl Configs { } } -pub struct ShareComputationChunk { +pub struct ShareComputationChunk { configs: Configs, - /// Public input from base circuit - chunk_commitment: Field, - /// Slice of y for this chunk: y[CHUNK_IDX*CHUNK_SIZE..(CHUNK_IDX+1)*CHUNK_SIZE] + /// Slice of y for this chunk, public so wrapper can enforce + /// consistency with base circuit via public input equality y_chunk: [[[Field; N_PARTIES + 1]; L]; CHUNK_SIZE], /// Parity check matrix h: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L], } -impl ShareComputationChunk { +impl ShareComputationChunk { pub fn new( configs: Configs, - chunk_commitment: Field, y_chunk: [[[Field; N_PARTIES + 1]; L]; CHUNK_SIZE], h: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L], ) -> Self { - ShareComputationChunk { configs, chunk_commitment, y_chunk, h } - } - - /// Verifies y_chunk is consistent with chunk_commitment from base circuit. - /// CHUNK_IDX is a compile-time generic so each chunk circuit is uniquely bound - /// to its position, a prover cannot reuse the same chunk data in a different position. - fn verify_chunk_commitment(self) { - let computed = compute_share_computation_chunk_commitment::( - self.y_chunk, - CHUNK_IDX, - ); - assert(computed == self.chunk_commitment, "chunk commitment mismatch"); + ShareComputationChunk { configs, y_chunk, h } } /// Range checks: shares y[i][j][k] for k >= 1 must be in [0, q_j) @@ -80,7 +72,6 @@ impl, domain_separator: [u8; 64]) -> Field { @@ -306,16 +300,3 @@ pub fn compute_share_computation_chunk_commitment( - y: [[[Field; N_PARTIES + 1]; L]; N], - party_idx: u32, - mod_idx: u32, -) -> Field { - let mut payload = Vec::new(); - for coeff_idx in 0..N { - payload.push(y[coeff_idx][mod_idx][party_idx + 1]); - } - payload.push(party_idx as Field); - payload.push(mod_idx as Field); - compute_commitment(payload, DS_SHARE_COMPUTATION_PARTY) -} From f422856f48a3e7a0d8f4121cfad58b314e2e7548 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Mon, 9 Mar 2026 21:42:36 +0100 Subject: [PATCH 03/27] adapt script to base and chunk split --- circuits/lib/src/math/commitments.nr | 2 - crates/zk-helpers/README.md | 29 ++-- crates/zk-helpers/src/bin/zk_cli.rs | 47 ++++-- crates/zk-helpers/src/circuits/commitments.rs | 39 +++++ .../circuits/dkg/share_computation/circuit.rs | 47 ++++++ .../circuits/dkg/share_computation/codegen.rs | 152 ++++++++++++++---- .../dkg/share_computation/computation.rs | 135 +++++++++++++++- .../src/circuits/dkg/share_computation/mod.rs | 7 +- 8 files changed, 390 insertions(+), 68 deletions(-) diff --git a/circuits/lib/src/math/commitments.nr b/circuits/lib/src/math/commitments.nr index 7d509315fc..d03bae6224 100644 --- a/circuits/lib/src/math/commitments.nr +++ b/circuits/lib/src/math/commitments.nr @@ -117,7 +117,6 @@ pub global DS_SHARE_COMPUTATION_CHUNK: [u8; 64] = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - /// WRAPPERS pub fn compute_commitment(inputs: Vec, domain_separator: [u8; 64]) -> Field { @@ -299,4 +298,3 @@ pub fn compute_share_computation_chunk_commitment` | Circuit name (e.g. `pk`, `share-computation`, `threshold-share-decryption`) | -| `--preset ` | Security preset: `insecure` (512) or `secure` (8192) | -| `--inputs ` | For DKG circuit inputs when using `--toml`: `secret-key` or `smudging-noise` | -| `--output ` | Output dir (default: `output`) | -| `--toml` | Also write Prover.toml (default: configs.nr only) | -| `--no-configs` | With `--toml`: do not write configs.nr (e.g. for circuit benchmarks) | +| Flag | Description | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| `--list_circuits` | List circuits and exit | +| `--circuit ` | Circuit name (e.g. `pk`, `share-computation-base`, `share-computation-chunk`) | +| `--preset ` | Security preset: `insecure` (512) or `secure` (8192) | +| `--inputs ` | Select the witness family when sample generation depends on it: `secret-key` or `smudging-noise` | +| `--chunk-idx ` | For `share-computation-chunk`: select which `y` slice to export as `y_chunk` | +| `--output ` | Output dir (default: `output`) | +| `--toml` | Also write Prover.toml (default: configs.nr only) | +| `--no-configs` | With `--toml`: do not write configs.nr (e.g. for circuit benchmarks) | diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 56711a818c..2dbeab5f68 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -16,7 +16,8 @@ use e3_fhe_params::{BfvPreset, ParameterType}; use e3_zk_helpers::ciphernodes_committee::CiphernodesCommitteeSize; use e3_zk_helpers::circuits::dkg::pk::circuit::{PkCircuit, PkCircuitData}; use e3_zk_helpers::circuits::dkg::share_computation::circuit::{ - ShareComputationCircuit, ShareComputationCircuitData, + ShareComputationBaseCircuit, ShareComputationChunkCircuit, ShareComputationChunkCircuitData, + ShareComputationCircuitData, }; use e3_zk_helpers::codegen::{write_artifacts, write_toml, CircuitCodegen}; use e3_zk_helpers::computation::DkgInputType; @@ -46,7 +47,7 @@ use std::sync::Arc; use std::thread; use std::time::Duration; -/// DKG input type for share-computation circuit: secret key or smudging noise. +/// DKG input type for circuits that derive witnesses from secret-key or smudging-noise samples. #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum DkgInputTypeArg { SecretKey, @@ -163,18 +164,21 @@ struct Cli { /// List all available circuits and exit. #[arg(long)] list_circuits: bool, - /// Circuit name to generate artifacts for (e.g. pk, share-computation). + /// Circuit name to generate artifacts for (e.g. pk, share-computation-base). #[arg(long, required_unless_present = "list_circuits")] circuit: Option, /// Preset: "insecure"|"secure" or λ (2|80). Drives both threshold and DKG params. #[arg(long, required_unless_present = "list_circuits")] preset: Option, - /// For share-computation only: inputs type "secret-key" or "smudging-noise". Required when writing Prover.toml for share-computation. Ignored for pk (always secret key). + /// Select the witness family when sample generation depends on it. #[arg(long)] inputs: Option, /// Committee size: "micro" or "small". #[arg(long, default_value = "micro")] committee: String, + /// For `share-computation-chunk`: which `y` slice to export as `y_chunk`. + #[arg(long, default_value_t = 0)] + chunk_idx: usize, /// Output directory for generated artifacts. #[arg(long, default_value = "output")] output: PathBuf, @@ -192,7 +196,8 @@ fn main() -> Result<()> { // Register all circuits in the registry (metadata only). let mut registry = CircuitRegistry::new(); registry.register(Arc::new(PkCircuit)); - registry.register(Arc::new(ShareComputationCircuit)); + registry.register(Arc::new(ShareComputationBaseCircuit)); + registry.register(Arc::new(ShareComputationChunkCircuit)); registry.register(Arc::new(UserDataEncryptionCircuit)); registry.register(Arc::new(PkGenerationCircuit)); registry.register(Arc::new(ShareEncryptionCircuit)); @@ -248,13 +253,18 @@ fn main() -> Result<()> { let write_prover_toml = args.toml; let no_configs = args.no_configs && args.toml; - // DKG circuits have a inputs-type choice (secret-key vs smudging-noise) excluding `pk` or C0 circuit. - let has_inputs_type = circuit_meta.name() == ShareComputationCircuit::NAME + let circuit_name = circuit_meta.name(); + + // Some circuits reuse one helper entrypoint for multiple witness families, so `--inputs` + // selects whether we derive secret-key or smudging-noise sample data. + let requires_inputs_arg = circuit_name == ShareComputationChunkCircuit::NAME || circuit_meta.name() == ShareEncryptionCircuit::NAME || circuit_meta.name() == DkgShareDecryptionCircuit::NAME; - let dkg_input_type = if has_inputs_type { - // Share-computation: require --inputs when generating Prover.toml; default secret-key for configs-only. + let show_input_type = requires_inputs_arg || circuit_name == ShareComputationBaseCircuit::NAME; + + let dkg_input_type = if circuit_name == ShareComputationBaseCircuit::NAME || requires_inputs_arg + { let inputs_str = if !args.toml { args.inputs.as_deref().unwrap_or("secret-key") } else { @@ -271,7 +281,6 @@ fn main() -> Result<()> { DkgInputTypeArg::SmudgingNoise => DkgInputType::SmudgingNoise, } } else { - // pk circuit: always secret key (no smudging noise). DkgInputType::SecretKey }; @@ -282,7 +291,7 @@ fn main() -> Result<()> { &circuit, preset, committee_size, - has_inputs_type, + show_input_type, dkg_input_type.clone(), &args.output, write_prover_toml, @@ -290,7 +299,6 @@ fn main() -> Result<()> { ); run_with_spinner(|| { - let circuit_name = circuit_meta.name(); let committee = committee_size.values(); let artifacts = match circuit_name { name if name == ::NAME => { @@ -299,14 +307,25 @@ fn main() -> Result<()> { let circuit = PkCircuit; circuit.codegen(preset, &sample)? } - name if name == ::NAME => { + name if name == ::NAME => { let sample = ShareComputationCircuitData::generate_sample( preset, committee, dkg_input_type, )?; - let circuit = ShareComputationCircuit; + let circuit = ShareComputationBaseCircuit; + circuit.codegen(preset, &sample)? + } + name if name == ::NAME => { + let sample = ShareComputationChunkCircuitData::generate_sample( + preset, + committee, + dkg_input_type, + args.chunk_idx, + )?; + + let circuit = ShareComputationChunkCircuit; circuit.codegen(preset, &sample)? } name if name == ::NAME => { diff --git a/crates/zk-helpers/src/circuits/commitments.rs b/crates/zk-helpers/src/circuits/commitments.rs index b145b1808d..7581e106af 100644 --- a/crates/zk-helpers/src/circuits/commitments.rs +++ b/crates/zk-helpers/src/circuits/commitments.rs @@ -47,6 +47,14 @@ const DS_SHARE_COMPUTATION: [u8; 64] = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; +/// String: "SHARE_COMPUTATION_CHUNK" +const DS_SHARE_COMPUTATION_CHUNK: [u8; 64] = [ + 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x55, 0x54, 0x41, 0x54, 0x49, 0x4f, + 0x4e, 0x5f, 0x43, 0x48, 0x55, 0x4e, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + /// String: "SHARE_ENCRYPTION" const DS_SHARE_ENCRYPTION: [u8; 64] = [ 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x45, 0x4e, 0x43, 0x52, 0x59, 0x50, 0x54, 0x49, 0x4f, 0x4e, @@ -309,6 +317,37 @@ pub fn compute_share_encryption_commitment_from_shares( BigInt::from_bytes_le(num_bigint::Sign::Plus, &commitment_bytes) } +/// Compute share-computation chunk commitment. +/// +/// This matches the Noir `compute_share_computation_chunk_commitment` function exactly. +/// +/// # Arguments +/// * `y_chunk` - 3D chunk slice `y_chunk[coeff_idx][mod_idx][party_idx]` +/// * `chunk_idx` - Index of the chunk +pub fn compute_share_computation_chunk_commitment( + y_chunk: &[Vec>], + chunk_idx: usize, +) -> BigInt { + let mut payload = Vec::new(); + + for coeff_y in y_chunk { + for mod_y in coeff_y { + for share_value in mod_y { + payload.push(crate::utils::bigint_to_field(share_value)); + } + } + } + + payload.push(Field::from(chunk_idx as u64)); + + let input_size = payload.len() as u32; + let io_pattern = [0x80000000 | input_size, 1]; + + let commitment_field = compute_commitments(payload, DS_SHARE_COMPUTATION_CHUNK, io_pattern)[0]; + let commitment_bytes = commitment_field.into_bigint().to_bytes_le(); + BigInt::from_bytes_le(num_bigint::Sign::Plus, &commitment_bytes) +} + /// Compute threshold public key aggregation commitment. /// /// This matches the Noir `compute_pk_aggregation_commitment` function exactly. diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs index 001c022461..3be874b3f8 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs @@ -6,12 +6,15 @@ use crate::computation::DkgInputType; use crate::registry::Circuit; +use crate::CircuitsErrors; +use e3_fhe_params::BfvPreset; use e3_fhe_params::ParameterType; use e3_parity_matrix::ParityMatrix; use e3_polynomial::CrtPolynomial; use ndarray::Array2; use num_bigint::BigInt; +// todo: remove this (keep it until we update zk-prover) #[derive(Debug)] pub struct ShareComputationCircuit; @@ -23,6 +26,27 @@ impl Circuit for ShareComputationCircuit { const DKG_INPUT_TYPE: Option = None; } +#[derive(Debug)] +pub struct ShareComputationBaseCircuit; + +impl Circuit for ShareComputationBaseCircuit { + const NAME: &'static str = "share-computation-base"; + const PREFIX: &'static str = "SHARE_COMPUTATION"; + const SUPPORTED_PARAMETER: ParameterType = ParameterType::THRESHOLD; + const DKG_INPUT_TYPE: Option = None; +} + +#[derive(Debug)] +pub struct ShareComputationChunkCircuit; + +impl Circuit for ShareComputationChunkCircuit { + const NAME: &'static str = "share-computation-chunk"; + const PREFIX: &'static str = "SHARE_COMPUTATION"; + const SUPPORTED_PARAMETER: ParameterType = ParameterType::THRESHOLD; + const DKG_INPUT_TYPE: Option = None; +} + +// todo: currently reusing this but should be renamed when we change zk-prover pub struct ShareComputationCircuitData { /// Which secret type this data is for (determines which branch to use in data). pub dkg_input_type: DkgInputType, @@ -32,3 +56,26 @@ pub struct ShareComputationCircuitData { pub n_parties: u32, pub threshold: u32, } + +pub struct ShareComputationChunkCircuitData { + pub share_data: ShareComputationCircuitData, + pub chunk_idx: usize, +} + +impl ShareComputationChunkCircuitData { + pub fn generate_sample( + preset: BfvPreset, + committee: crate::CiphernodesCommittee, + dkg_input_type: DkgInputType, + chunk_idx: usize, + ) -> Result { + Ok(Self { + share_data: ShareComputationCircuitData::generate_sample( + preset, + committee, + dkg_input_type, + )?, + chunk_idx, + }) + } +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs index 56b678684a..6c8008c6e3 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -4,21 +4,20 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Code generation for the share-computation BFV circuit: Prover.toml and configs.nr. +//! Code generation for the share-computation helper scripts: Prover.toml and configs.nr. -use crate::circuits::computation::CircuitComputation; -use crate::circuits::computation::Computation; +use crate::circuits::computation::{CircuitComputation, Computation}; use crate::circuits::dkg::share_computation::{ - utils::parity_matrix_constant_string, Bits, Inputs, ShareComputationCircuit, - ShareComputationCircuitData, ShareComputationOutput, + utils::parity_matrix_constant_string, Bits, Bounds, ChunkInputs, Configs, Inputs, + ShareComputationBaseCircuit, ShareComputationChunkCircuit, ShareComputationChunkCircuitData, + ShareComputationCircuit, ShareComputationCircuitData, ShareComputationOutput, }; use crate::circuits::{Artifacts, CircuitCodegen, CircuitsErrors, CodegenToml}; use crate::codegen::CodegenConfigs; use crate::registry::Circuit; -use e3_fhe_params::build_pair_for_preset; -use e3_fhe_params::BfvPreset; +use e3_fhe_params::{build_pair_for_preset, BfvPreset}; -/// Implementation of [`CircuitCodegen`] for [`ShareComputationCircuit`]. +/// Implementation of [`CircuitCodegen`] for the shared share-computation input builder. impl CircuitCodegen for ShareComputationCircuit { type Preset = BfvPreset; type Data = ShareComputationCircuitData; @@ -27,44 +26,100 @@ impl CircuitCodegen for ShareComputationCircuit { fn codegen(&self, preset: Self::Preset, data: &Self::Data) -> Result { let ShareComputationOutput { inputs, bits, .. } = ShareComputationCircuit::compute(preset, data)?; + let configs = Configs::compute(preset, data)?; - let toml = generate_toml(&inputs)?; - let configs = generate_configs( + build_base_artifacts(preset, data, inputs, bits, &configs) + } +} + +impl CircuitCodegen for ShareComputationBaseCircuit { + type Preset = BfvPreset; + type Data = ShareComputationCircuitData; + type Error = CircuitsErrors; + + fn codegen(&self, preset: Self::Preset, data: &Self::Data) -> Result { + let ShareComputationOutput { inputs, bits, .. } = + ShareComputationCircuit::compute(preset, data)?; + let configs = Configs::compute(preset, data)?; + + build_base_artifacts(preset, data, inputs, bits, &configs) + } +} + +impl CircuitCodegen for ShareComputationChunkCircuit { + type Preset = BfvPreset; + type Data = ShareComputationChunkCircuitData; + type Error = CircuitsErrors; + + fn codegen(&self, preset: Self::Preset, data: &Self::Data) -> Result { + let inputs = ChunkInputs::compute(preset, data)?; + let bounds = Bounds::compute(preset, &data.share_data)?; + let bits = Bits::compute(preset, &bounds)?; + let configs = Configs::compute(preset, &data.share_data)?; + + Ok(Artifacts { + toml: generate_chunk_toml(&inputs)?, + configs: generate_configs( + preset, + &bits, + data.share_data.n_parties as usize, + data.share_data.threshold as usize, + configs.chunk_size, + configs.n_chunks, + )?, + }) + } +} + +fn build_base_artifacts( + preset: BfvPreset, + data: &ShareComputationCircuitData, + inputs: Inputs, + bits: Bits, + configs: &Configs, +) -> Result { + Ok(Artifacts { + toml: generate_toml(&inputs)?, + configs: generate_configs( preset, &bits, data.n_parties as usize, data.threshold as usize, - )?; - - Ok(Artifacts { toml, configs }) - } + configs.chunk_size, + configs.n_chunks, + )?, + }) } pub fn generate_toml(witness: &Inputs) -> Result { - let json = witness - .to_json() - .map_err(|e| CircuitsErrors::SerdeJson(e))?; + let json = witness.to_json().map_err(CircuitsErrors::SerdeJson)?; + Ok(toml::to_string(&json)?) +} +pub fn generate_chunk_toml(witness: &ChunkInputs) -> Result { + let json = witness.to_json().map_err(CircuitsErrors::SerdeJson)?; Ok(toml::to_string(&json)?) } -/// Builds the configs.nr string (N, L, parity matrix, bit parameters, configs) for the Noir prover. -/// -/// `n_parties` and `threshold` are used to build the parity matrix (Reed–Solomon generator null space) -/// and must match the committee size used for the input/sample. +/// Builds the configs.nr string used by the split base/chunk share-computation circuits. pub fn generate_configs( preset: BfvPreset, bits: &Bits, n_parties: usize, threshold: usize, + chunk_size: usize, + n_chunks: usize, ) -> Result { let (threshold_params, _) = build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let config_name = preset.metadata().security.as_config_str(); let parity_matrix_str = parity_matrix_constant_string(&threshold_params, n_parties, threshold)?; let prefix = ::PREFIX; - let configs = format!( - r#" + + Ok(format!( + r#"use crate::core::dkg::share_computation::base::Configs as ShareComputationBaseConfigs; +use crate::core::dkg::share_computation::chunk::Configs as ShareComputationChunkConfigs; + pub use crate::configs::{}::threshold::{{L as L_THRESHOLD, QIS as QIS_THRESHOLD}}; pub global N: u32 = {}; @@ -76,13 +131,15 @@ share_computation_sk (CIRCUIT 2a) ------------------------------------- ************************************/ -// share_computation_sk - bit parameters pub global {}_BIT_SHARE: u32 = {}; pub global {}_SK_BIT_SECRET: u32 = {}; +pub global {}_CHUNK_SIZE: u32 = {}; +pub global {}_N_CHUNKS: u32 = {}; -// share_computation_sk - configs -pub global {}_SK_CONFIGS: ShareComputationConfigs = - ShareComputationConfigs::new(QIS_THRESHOLD); +pub global {}_SK_BASE_CONFIGS: ShareComputationBaseConfigs = + ShareComputationBaseConfigs::new(QIS_THRESHOLD); +pub global {}_SK_CHUNK_CONFIGS: ShareComputationChunkConfigs = + ShareComputationChunkConfigs::new(QIS_THRESHOLD); /************************************ ------------------------------------- @@ -90,12 +147,12 @@ share_computation_e_sm (CIRCUIT 2b) ------------------------------------- ************************************/ -// share_computation_e_sm - bit parameters pub global {}_E_SM_BIT_SECRET: u32 = {}; -// verify_shares - configs -pub global {}_E_SM_CONFIGS: ShareComputationConfigs = - ShareComputationConfigs::new(QIS_THRESHOLD); +pub global {}_E_SM_BASE_CONFIGS: ShareComputationBaseConfigs = + ShareComputationBaseConfigs::new(QIS_THRESHOLD); +pub global {}_E_SM_CHUNK_CONFIGS: ShareComputationChunkConfigs = + ShareComputationChunkConfigs::new(QIS_THRESHOLD); "#, config_name, preset.metadata().degree, @@ -105,12 +162,16 @@ pub global {}_E_SM_CONFIGS: ShareComputationConfigs = prefix, bits.bit_sk_secret, prefix, + chunk_size, + prefix, + n_chunks, + prefix, + prefix, prefix, bits.bit_e_sm_secret, prefix, - ); - - Ok(configs) + prefix, + )) } #[cfg(test)] @@ -118,7 +179,7 @@ mod tests { use super::*; use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::circuits::computation::Computation; - use crate::circuits::dkg::share_computation::{Bits, Bounds}; + use crate::circuits::dkg::share_computation::{Bits, Bounds, ShareComputationChunkCircuitData}; use crate::codegen::write_artifacts; use crate::computation::DkgInputType; use crate::Circuit; @@ -184,8 +245,29 @@ mod tests { .contains(format!("{}_BIT_SHARE: u32 = {}", prefix, bits.bit_share).as_str())); assert!(configs_content .contains(format!("{}_SK_BIT_SECRET: u32 = {}", prefix, bits.bit_sk_secret).as_str())); + assert!(configs_content.contains(format!("{}_CHUNK_SIZE: u32 = {}", prefix, 512).as_str())); assert!(configs_content.contains( format!("{}_E_SM_BIT_SECRET: u32 = {}", prefix, bits.bit_e_sm_secret).as_str() )); } + + #[test] + fn test_chunk_toml_generation_and_structure() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = ShareComputationChunkCircuitData::generate_sample( + BfvPreset::SecureThreshold8192, + committee, + DkgInputType::SecretKey, + 1, + ) + .unwrap(); + + let artifacts = ShareComputationChunkCircuit + .codegen(BfvPreset::SecureThreshold8192, &sample) + .unwrap(); + + let parsed: toml::Value = artifacts.toml.parse().unwrap(); + let y_chunk = parsed.get("y_chunk").and_then(|v| v.as_array()).unwrap(); + assert_eq!(y_chunk.len(), 512); + } } diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs index feb90ac85d..03b8d96565 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -15,8 +15,9 @@ use crate::circuits::commitments::{ compute_share_computation_e_sm_commitment, compute_share_computation_sk_commitment, }; use crate::computation::DkgInputType; -use crate::dkg::share_computation::ShareComputationCircuit; -use crate::dkg::share_computation::ShareComputationCircuitData; +use crate::dkg::share_computation::{ + ShareComputationChunkCircuitData, ShareComputationCircuit, ShareComputationCircuitData, +}; use crate::CircuitsErrors; use crate::{calculate_bit_width, crt_polynomial_to_toml_json, poly_coefficients_to_toml_json}; use crate::{CircuitComputation, Computation}; @@ -28,6 +29,9 @@ use fhe::trbfv::{SmudgingBoundCalculator, SmudgingBoundCalculatorConfig}; use num_bigint::{BigInt, BigUint}; use serde::{Deserialize, Serialize}; +// todo: understand where to put this! +const SHARE_COMPUTATION_CHUNK_SIZE: usize = 512; + /// Output of [`CircuitComputation::compute`] for [`ShareComputationCircuit`]: bounds, bit widths, and input. #[derive(Debug)] pub struct ShareComputationOutput { @@ -61,6 +65,8 @@ pub struct Configs { pub n: usize, pub l: usize, pub moduli: Vec, + pub chunk_size: usize, + pub n_chunks: usize, pub bits: Bits, pub bounds: Bounds, } @@ -80,7 +86,8 @@ pub struct Bounds { pub e_sm_bound: BigUint, } -/// Input for the share-computation circuit: secret in CRT form, y (secret + shares per coeff/modulus), and commitment. +/// Input for the share-computation base circuit: secret in CRT form, full public `y`, +/// and the expected secret commitment. /// /// All coefficients are reduced to the ZKP field modulus for serialization. Before that, /// secret_crt and y are normalized so that per modulus j: secret and shares are in [0, q_j), @@ -97,6 +104,16 @@ pub struct Inputs { pub dkg_input_type: DkgInputType, } +/// Input for a single share-computation chunk circuit. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ChunkInputs { + /// Chunk index used by the helper to select the correct slice from `y`. + /// It is not serialized because the current chunk Noir circuit only takes `y_chunk`. + pub chunk_idx: usize, + /// y_chunk[coeff_idx][mod_idx][party_idx] + pub y_chunk: Vec>>, +} + impl Computation for Configs { type Preset = BfvPreset; type Data = ShareComputationCircuitData; @@ -115,12 +132,22 @@ impl Computation for Configs { n: threshold_params.degree(), l, moduli, + chunk_size: compute_chunk_size(), + n_chunks: compute_n_chunks(threshold_params.degree(), compute_chunk_size()), bits, bounds, }) } } +fn compute_chunk_size() -> usize { + SHARE_COMPUTATION_CHUNK_SIZE +} + +fn compute_n_chunks(degree: usize, chunk_size: usize) -> usize { + degree.div_ceil(chunk_size) +} + impl Computation for Bits { type Preset = BfvPreset; type Data = Bounds; @@ -263,6 +290,58 @@ impl Computation for Inputs { } } +impl Inputs { + pub fn split_into_chunks(&self, chunk_size: usize) -> Result, CircuitsErrors> { + if chunk_size == 0 { + return Err(CircuitsErrors::Sample( + "share computation chunk size must be > 0".to_string(), + )); + } + + let degree = self.y.len(); + let n_chunks = compute_n_chunks(degree, chunk_size); + let mut chunks = Vec::with_capacity(n_chunks); + + for chunk_idx in 0..n_chunks { + let start = chunk_idx * chunk_size; + let end = usize::min(start + chunk_size, degree); + let y_chunk = self.y[start..end].to_vec(); + + chunks.push(ChunkInputs { chunk_idx, y_chunk }); + } + + Ok(chunks) + } +} + +impl Computation for ChunkInputs { + type Preset = BfvPreset; + type Data = ShareComputationChunkCircuitData; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, data: &Self::Data) -> Result { + let base_inputs = Inputs::compute(preset, &data.share_data)?; + let configs = Configs::compute(preset, &data.share_data)?; + let chunks = base_inputs.split_into_chunks(configs.chunk_size)?; + + chunks + .into_iter() + .find(|chunk| chunk.chunk_idx == data.chunk_idx) + .ok_or_else(|| { + CircuitsErrors::Sample(format!( + "chunk index {} out of range for {} chunks", + data.chunk_idx, configs.n_chunks + )) + }) + } + + fn to_json(&self) -> serde_json::Result { + Ok(serde_json::json!({ + "y_chunk": bigint_3d_to_json_values(&self.y_chunk), + })) + } +} + #[cfg(test)] mod tests { use super::*; @@ -331,7 +410,57 @@ mod tests { assert_eq!(decoded.n, constants.n); assert_eq!(decoded.l, constants.l); assert_eq!(decoded.moduli, constants.moduli); + assert_eq!(decoded.chunk_size, constants.chunk_size); + assert_eq!(decoded.n_chunks, constants.n_chunks); assert_eq!(decoded.bits, constants.bits); assert_eq!(decoded.bounds, constants.bounds); } + + #[test] + fn test_split_inputs_produces_expected_chunk_count() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = ShareComputationCircuitData::generate_sample( + BfvPreset::InsecureThreshold512, + committee, + DkgInputType::SecretKey, + ) + .unwrap(); + + let base = Inputs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); + let configs = Configs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); + let chunks = base.split_into_chunks(configs.chunk_size).unwrap(); + + assert_eq!(chunks.len(), configs.n_chunks); + assert_eq!(base.y.len(), configs.n); + } + + #[test] + fn test_chunk_input_matches_base_slice() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = ShareComputationCircuitData::generate_sample( + BfvPreset::SecureThreshold8192, + committee, + DkgInputType::SecretKey, + ) + .unwrap(); + + let configs = Configs::compute(BfvPreset::SecureThreshold8192, &sample).unwrap(); + assert!(configs.n_chunks > 1); + + let base_inputs = Inputs::compute(BfvPreset::SecureThreshold8192, &sample).unwrap(); + let chunk = ChunkInputs::compute( + BfvPreset::SecureThreshold8192, + &ShareComputationChunkCircuitData { + share_data: sample, + chunk_idx: 1, + }, + ) + .unwrap(); + + let expected_start = configs.chunk_size; + let expected_end = expected_start + configs.chunk_size; + let expected_slice = base_inputs.y[expected_start..expected_end].to_vec(); + + assert_eq!(chunk.y_chunk, expected_slice); + } } diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs index d379ad5768..3a4c1f497d 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs @@ -10,6 +10,9 @@ pub mod computation; pub mod sample; pub mod utils; -pub use circuit::{ShareComputationCircuit, ShareComputationCircuitData}; -pub use computation::{Bits, Bounds, Configs, Inputs, ShareComputationOutput}; +pub use circuit::{ + ShareComputationBaseCircuit, ShareComputationChunkCircuit, ShareComputationChunkCircuitData, + ShareComputationCircuit, ShareComputationCircuitData, +}; +pub use computation::{Bits, Bounds, ChunkInputs, Configs, Inputs, ShareComputationOutput}; pub use sample::SecretShares; From 0c7731fa054b69c014141ae7b58be377a9b48256 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Mon, 9 Mar 2026 21:44:49 +0100 Subject: [PATCH 04/27] update name --- circuits/bin/dkg/Nargo.toml | 2 +- .../Nargo.toml | 2 +- .../src/main.nr | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename circuits/bin/dkg/{esm_share_computation_base => e_sm_share_computation_base}/Nargo.toml (69%) rename circuits/bin/dkg/{esm_share_computation_base => e_sm_share_computation_base}/src/main.nr (100%) diff --git a/circuits/bin/dkg/Nargo.toml b/circuits/bin/dkg/Nargo.toml index 2e99c1d1db..be267b844c 100644 --- a/circuits/bin/dkg/Nargo.toml +++ b/circuits/bin/dkg/Nargo.toml @@ -4,7 +4,7 @@ members = [ "share_encryption", "share_decryption", "sk_share_computation_base", - "esm_share_computation_base", + "e_sm_share_computation_base", "share_computation_chunk", ] diff --git a/circuits/bin/dkg/esm_share_computation_base/Nargo.toml b/circuits/bin/dkg/e_sm_share_computation_base/Nargo.toml similarity index 69% rename from circuits/bin/dkg/esm_share_computation_base/Nargo.toml rename to circuits/bin/dkg/e_sm_share_computation_base/Nargo.toml index 9647284797..674adc8587 100644 --- a/circuits/bin/dkg/esm_share_computation_base/Nargo.toml +++ b/circuits/bin/dkg/e_sm_share_computation_base/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "esm_share_computation_base" +name = "e_sm_share_computation_base" type = "bin" authors = [""] diff --git a/circuits/bin/dkg/esm_share_computation_base/src/main.nr b/circuits/bin/dkg/e_sm_share_computation_base/src/main.nr similarity index 100% rename from circuits/bin/dkg/esm_share_computation_base/src/main.nr rename to circuits/bin/dkg/e_sm_share_computation_base/src/main.nr From b285629a76bb9c573836b34df1d26d19ec6ec09f Mon Sep 17 00:00:00 2001 From: 0xjei Date: Mon, 9 Mar 2026 21:45:56 +0100 Subject: [PATCH 05/27] add missing licenses --- circuits/bin/dkg/e_sm_share_computation_base/src/main.nr | 6 ++++++ circuits/bin/dkg/sk_share_computation_base/src/main.nr | 6 ++++++ circuits/lib/src/core/dkg/share_computation/mod.nr | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/circuits/bin/dkg/e_sm_share_computation_base/src/main.nr b/circuits/bin/dkg/e_sm_share_computation_base/src/main.nr index aef2e8e3bd..b1c4bd968f 100644 --- a/circuits/bin/dkg/e_sm_share_computation_base/src/main.nr +++ b/circuits/bin/dkg/e_sm_share_computation_base/src/main.nr @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + use lib::configs::default::dkg::{ L_THRESHOLD, N, SHARE_COMPUTATION_E_SM_BASE_CONFIGS, SHARE_COMPUTATION_E_SM_BIT_SECRET, }; diff --git a/circuits/bin/dkg/sk_share_computation_base/src/main.nr b/circuits/bin/dkg/sk_share_computation_base/src/main.nr index ac4188fa4f..58fa73c595 100644 --- a/circuits/bin/dkg/sk_share_computation_base/src/main.nr +++ b/circuits/bin/dkg/sk_share_computation_base/src/main.nr @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + use lib::configs::default::dkg::{ L_THRESHOLD, N, SHARE_COMPUTATION_SK_BASE_CONFIGS, SHARE_COMPUTATION_SK_BIT_SECRET, }; diff --git a/circuits/lib/src/core/dkg/share_computation/mod.nr b/circuits/lib/src/core/dkg/share_computation/mod.nr index 466ad29080..4bbf3bcbba 100644 --- a/circuits/lib/src/core/dkg/share_computation/mod.nr +++ b/circuits/lib/src/core/dkg/share_computation/mod.nr @@ -1,2 +1,8 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + pub mod base; pub mod chunk; From cf81a4fe3576d50f9341a84a443e9ce4fd9600a2 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 10 Mar 2026 22:16:30 +0100 Subject: [PATCH 06/27] add key hashes and remove stale code from #1399 --- circuits/bin/dkg/Nargo.toml | 1 + .../dkg/share_computation_wrapper/src/main.nr | 7 ++-- .../wrapper/dkg/share_computation/src/main.nr | 2 +- circuits/lib/src/math/commitments.nr | 24 ------------ crates/zk-helpers/src/circuits/commitments.rs | 39 ------------------- 5 files changed, 6 insertions(+), 67 deletions(-) diff --git a/circuits/bin/dkg/Nargo.toml b/circuits/bin/dkg/Nargo.toml index be267b844c..f7ce936051 100644 --- a/circuits/bin/dkg/Nargo.toml +++ b/circuits/bin/dkg/Nargo.toml @@ -6,5 +6,6 @@ members = [ "sk_share_computation_base", "e_sm_share_computation_base", "share_computation_chunk", + "share_computation_wrapper" ] diff --git a/circuits/bin/dkg/share_computation_wrapper/src/main.nr b/circuits/bin/dkg/share_computation_wrapper/src/main.nr index b1c8833ed2..9fdac1294d 100644 --- a/circuits/bin/dkg/share_computation_wrapper/src/main.nr +++ b/circuits/bin/dkg/share_computation_wrapper/src/main.nr @@ -27,17 +27,18 @@ fn main( base_verification_key: UltraHonkVerificationKey, base_proof: UltraHonkProof, base_public_inputs: [Field; BASE_PUBLIC_INPUTS], + base_key_hash: pub Field, chunk_verification_key: UltraHonkVerificationKey, chunk_proofs: [UltraHonkProof; SHARE_COMPUTATION_N_CHUNKS], chunk_public_inputs: [[Field; CHUNK_PUBLIC_INPUTS]; SHARE_COMPUTATION_N_CHUNKS], - key_hash: pub Field, + chunk_key_hash: pub Field, ) -> pub Field { // Step 1: Verify base proof verify_honk_proof_non_zk( base_verification_key, base_proof, base_public_inputs, - key_hash, + base_key_hash, ); // Step 2: Verify each chunk proof and enforce y consistency with base @@ -48,7 +49,7 @@ fn main( chunk_verification_key, chunk_proofs[chunk_idx], chunk_public_inputs[chunk_idx], - key_hash, + chunk_key_hash, ); // Enforce y consistency: base_public_inputs y slice == chunk y_chunk 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 e87b30c5a6..9440840d12 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 @@ -11,7 +11,7 @@ use lib::{configs::default::N_PARTIES, math::commitments::compute_recursive_aggr // Number of proofs. pub global N_PROOFS: u32 = 1; /// Number of public inputs/outputs per proof. -pub global N_PUBLIC_INPUTS: u32 = (L_THRESHOLD * N_PARTIES) + 1; +pub global N_PUBLIC_INPUTS: u32 = (L_THRESHOLD * N_PARTIES) + 3; fn main( verification_key: UltraHonkVerificationKey, diff --git a/circuits/lib/src/math/commitments.nr b/circuits/lib/src/math/commitments.nr index d03bae6224..4e47af542f 100644 --- a/circuits/lib/src/math/commitments.nr +++ b/circuits/lib/src/math/commitments.nr @@ -109,14 +109,6 @@ pub global DS_USER_DATA_ENCRYPTION_COMMITMENT: [u8; 64] = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; -// Domain separator - "SHARE_COMPUTATION_CHUNK" -pub global DS_SHARE_COMPUTATION_CHUNK: [u8; 64] = [ - 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x55, 0x54, 0x41, 0x54, 0x49, 0x4f, - 0x4e, 0x5f, 0x43, 0x48, 0x55, 0x4e, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; - /// WRAPPERS pub fn compute_commitment(inputs: Vec, domain_separator: [u8; 64]) -> Field { @@ -282,19 +274,3 @@ pub fn compute_user_data_encryption_ct0_challenge(payload: Vec(payload: Vec) -> Vec { compute_challenge::(payload, DS_CLG_USER_DATA_ENCRYPTION) } - -pub fn compute_share_computation_chunk_commitment( - y_chunk: [[[Field; N_PARTIES + 1]; L]; CHUNK_SIZE], - chunk_idx: u32, -) -> Field { - let mut payload = Vec::new(); - for coeff_idx in 0..CHUNK_SIZE { - for mod_idx in 0..L { - for party_idx in 0..(N_PARTIES + 1) { - payload.push(y_chunk[coeff_idx][mod_idx][party_idx]); - } - } - } - payload.push(chunk_idx as Field); - compute_commitment(payload, DS_SHARE_COMPUTATION_CHUNK) -} diff --git a/crates/zk-helpers/src/circuits/commitments.rs b/crates/zk-helpers/src/circuits/commitments.rs index 7581e106af..b145b1808d 100644 --- a/crates/zk-helpers/src/circuits/commitments.rs +++ b/crates/zk-helpers/src/circuits/commitments.rs @@ -47,14 +47,6 @@ const DS_SHARE_COMPUTATION: [u8; 64] = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; -/// String: "SHARE_COMPUTATION_CHUNK" -const DS_SHARE_COMPUTATION_CHUNK: [u8; 64] = [ - 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x55, 0x54, 0x41, 0x54, 0x49, 0x4f, - 0x4e, 0x5f, 0x43, 0x48, 0x55, 0x4e, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; - /// String: "SHARE_ENCRYPTION" const DS_SHARE_ENCRYPTION: [u8; 64] = [ 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x45, 0x4e, 0x43, 0x52, 0x59, 0x50, 0x54, 0x49, 0x4f, 0x4e, @@ -317,37 +309,6 @@ pub fn compute_share_encryption_commitment_from_shares( BigInt::from_bytes_le(num_bigint::Sign::Plus, &commitment_bytes) } -/// Compute share-computation chunk commitment. -/// -/// This matches the Noir `compute_share_computation_chunk_commitment` function exactly. -/// -/// # Arguments -/// * `y_chunk` - 3D chunk slice `y_chunk[coeff_idx][mod_idx][party_idx]` -/// * `chunk_idx` - Index of the chunk -pub fn compute_share_computation_chunk_commitment( - y_chunk: &[Vec>], - chunk_idx: usize, -) -> BigInt { - let mut payload = Vec::new(); - - for coeff_y in y_chunk { - for mod_y in coeff_y { - for share_value in mod_y { - payload.push(crate::utils::bigint_to_field(share_value)); - } - } - } - - payload.push(Field::from(chunk_idx as u64)); - - let input_size = payload.len() as u32; - let io_pattern = [0x80000000 | input_size, 1]; - - let commitment_field = compute_commitments(payload, DS_SHARE_COMPUTATION_CHUNK, io_pattern)[0]; - let commitment_bytes = commitment_field.into_bigint().to_bytes_le(); - BigInt::from_bytes_le(num_bigint::Sign::Plus, &commitment_bytes) -} - /// Compute threshold public key aggregation commitment. /// /// This matches the Noir `compute_pk_aggregation_commitment` function exactly. From ffebbc76b55e0368b125bd1adaad3ce17eb79bf5 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 10 Mar 2026 22:40:35 +0100 Subject: [PATCH 07/27] removed unused configs and update codegen --- .../e_sm_share_computation_base/src/main.nr | 12 +--- .../dkg/share_computation_chunk/src/main.nr | 6 +- .../dkg/sk_share_computation_base/src/main.nr | 12 +--- circuits/lib/src/configs/insecure/dkg.nr | 37 +++++------ circuits/lib/src/configs/secure/dkg.nr | 40 ++++++------ .../src/core/dkg/share_computation/base.nr | 18 +----- .../circuits/dkg/share_computation/codegen.rs | 61 ++++++++----------- 7 files changed, 69 insertions(+), 117 deletions(-) diff --git a/circuits/bin/dkg/e_sm_share_computation_base/src/main.nr b/circuits/bin/dkg/e_sm_share_computation_base/src/main.nr index b1c4bd968f..7e77cf50b4 100644 --- a/circuits/bin/dkg/e_sm_share_computation_base/src/main.nr +++ b/circuits/bin/dkg/e_sm_share_computation_base/src/main.nr @@ -4,9 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use lib::configs::default::dkg::{ - L_THRESHOLD, N, SHARE_COMPUTATION_E_SM_BASE_CONFIGS, SHARE_COMPUTATION_E_SM_BIT_SECRET, -}; +use lib::configs::default::dkg::{L_THRESHOLD, N, SHARE_COMPUTATION_E_SM_BIT_SECRET}; use lib::configs::default::{N_PARTIES, T}; use lib::core::dkg::share_computation::base::SmudgingNoiseShareComputationBase; use lib::math::polynomial::Polynomial; @@ -17,11 +15,7 @@ fn main( // y is public so wrapper can enforce consistency with chunk circuits y: pub [[[Field; N_PARTIES + 1]; L_THRESHOLD]; N], ) -> pub [[Field; L_THRESHOLD]; N_PARTIES] { - let circuit: SmudgingNoiseShareComputationBase = SmudgingNoiseShareComputationBase::new( - SHARE_COMPUTATION_E_SM_BASE_CONFIGS, - expected_secret_commitment, - e_sm_secret, - y, - ); + let circuit: SmudgingNoiseShareComputationBase = + SmudgingNoiseShareComputationBase::new(expected_secret_commitment, e_sm_secret, y); circuit.execute() } diff --git a/circuits/bin/dkg/share_computation_chunk/src/main.nr b/circuits/bin/dkg/share_computation_chunk/src/main.nr index 074eed7f3e..2011aecb31 100644 --- a/circuits/bin/dkg/share_computation_chunk/src/main.nr +++ b/circuits/bin/dkg/share_computation_chunk/src/main.nr @@ -5,8 +5,8 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use lib::configs::default::dkg::{ - L_THRESHOLD, PARITY_MATRIX, SHARE_COMPUTATION_BIT_SHARE, SHARE_COMPUTATION_CHUNK_SIZE, - SHARE_COMPUTATION_SK_CHUNK_CONFIGS, + L_THRESHOLD, PARITY_MATRIX, SHARE_COMPUTATION_BIT_SHARE, SHARE_COMPUTATION_CHUNK_CONFIGS, + SHARE_COMPUTATION_CHUNK_SIZE, }; use lib::configs::default::{N_PARTIES, T}; use lib::core::dkg::share_computation::chunk::ShareComputationChunk; @@ -16,6 +16,6 @@ fn main( y_chunk: pub [[[Field; N_PARTIES + 1]; L_THRESHOLD]; SHARE_COMPUTATION_CHUNK_SIZE], ) { let circuit: ShareComputationChunk = - ShareComputationChunk::new(SHARE_COMPUTATION_SK_CHUNK_CONFIGS, y_chunk, PARITY_MATRIX); + ShareComputationChunk::new(SHARE_COMPUTATION_CHUNK_CONFIGS, y_chunk, PARITY_MATRIX); circuit.execute() } diff --git a/circuits/bin/dkg/sk_share_computation_base/src/main.nr b/circuits/bin/dkg/sk_share_computation_base/src/main.nr index 58fa73c595..e74ce61398 100644 --- a/circuits/bin/dkg/sk_share_computation_base/src/main.nr +++ b/circuits/bin/dkg/sk_share_computation_base/src/main.nr @@ -4,9 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use lib::configs::default::dkg::{ - L_THRESHOLD, N, SHARE_COMPUTATION_SK_BASE_CONFIGS, SHARE_COMPUTATION_SK_BIT_SECRET, -}; +use lib::configs::default::dkg::{L_THRESHOLD, N, SHARE_COMPUTATION_SK_BIT_SECRET}; use lib::configs::default::{N_PARTIES, T}; use lib::core::dkg::share_computation::base::SecretKeyShareComputationBase; use lib::math::polynomial::Polynomial; @@ -17,11 +15,7 @@ fn main( // y is public so wrapper can enforce consistency with chunk circuits y: pub [[[Field; N_PARTIES + 1]; L_THRESHOLD]; N], ) -> pub [[Field; L_THRESHOLD]; N_PARTIES] { - let circuit: SecretKeyShareComputationBase = SecretKeyShareComputationBase::new( - SHARE_COMPUTATION_SK_BASE_CONFIGS, - expected_secret_commitment, - sk_secret, - y, - ); + let circuit: SecretKeyShareComputationBase = + SecretKeyShareComputationBase::new(expected_secret_commitment, sk_secret, y); circuit.execute() } diff --git a/circuits/lib/src/configs/insecure/dkg.nr b/circuits/lib/src/configs/insecure/dkg.nr index b24559abd0..3379f65589 100644 --- a/circuits/lib/src/configs/insecure/dkg.nr +++ b/circuits/lib/src/configs/insecure/dkg.nr @@ -6,7 +6,6 @@ use crate::configs::default::{N_PARTIES, T}; pub use crate::configs::insecure::threshold::{L as L_THRESHOLD, QIS as QIS_THRESHOLD}; -use crate::core::dkg::share_computation::base::Configs as ShareComputationBaseConfigs; use crate::core::dkg::share_computation::chunk::Configs as ShareComputationChunkConfigs; use crate::core::dkg::share_encryption::Configs as ShareEncryptionConfigs; @@ -18,6 +17,11 @@ pub global PLAINTEXT_MODULUS: Field = 68719403009; pub global Q_MOD_T: Field = 2415755265; pub global Q_MOD_T_CENTERED: Field = 2415755265; +pub global PARITY_MATRIX: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L_THRESHOLD] = [ + [[1, 68719403007, 1, 0], [2, 68719403006, 0, 1]], + [[1, 68719230975, 1, 0], [2, 68719230974, 0, 1]], +]; + /************************************ ------------------------------------- pk (CIRCUIT 0) @@ -27,44 +31,33 @@ pk (CIRCUIT 0) // pk - bit parameters pub global PK_BIT_PK: u32 = 50; -pub global PARITY_MATRIX: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L_THRESHOLD] = [ - [[1, 68719403007, 1, 0], [2, 68719403006, 0, 1]], - [[1, 68719230975, 1, 0], [2, 68719230974, 0, 1]], -]; - /************************************ ------------------------------------- share_computation_sk (CIRCUIT 2a) ------------------------------------- ************************************/ -// share_computation_sk - bit parameters pub global SHARE_COMPUTATION_BIT_SHARE: u32 = 36; pub global SHARE_COMPUTATION_SK_BIT_SECRET: u32 = 1; -// With N=512 and 5 parties, a single chunk covers all coefficients -pub global SHARE_COMPUTATION_CHUNK_SIZE: u32 = 512; -pub global SHARE_COMPUTATION_N_CHUNKS: u32 = 1; // N / CHUNK_SIZE = 512 / 512 - -pub global SHARE_COMPUTATION_SK_BASE_CONFIGS: ShareComputationBaseConfigs = - ShareComputationBaseConfigs::new(QIS_THRESHOLD); - -pub global SHARE_COMPUTATION_SK_CHUNK_CONFIGS: ShareComputationChunkConfigs = - ShareComputationChunkConfigs::new(QIS_THRESHOLD); - /************************************ ------------------------------------- share_computation_e_sm (CIRCUIT 2b) ------------------------------------- ************************************/ -// share_computation_e_sm - bit parameters -pub global SHARE_COMPUTATION_E_SM_BIT_SECRET: u32 = 23; +pub global SHARE_COMPUTATION_E_SM_BIT_SECRET: u32 = 24; + +/************************************ +------------------------------------- +share_computation_chunk (CIRCUIT 2c) +------------------------------------- +************************************/ -pub global SHARE_COMPUTATION_E_SM_BASE_CONFIGS: ShareComputationBaseConfigs = - ShareComputationBaseConfigs::new(QIS_THRESHOLD); +pub global SHARE_COMPUTATION_CHUNK_SIZE: u32 = 512; +pub global SHARE_COMPUTATION_N_CHUNKS: u32 = 1; -pub global SHARE_COMPUTATION_E_SM_CHUNK_CONFIGS: ShareComputationChunkConfigs = +pub global SHARE_COMPUTATION_CHUNK_CONFIGS: ShareComputationChunkConfigs = ShareComputationChunkConfigs::new(QIS_THRESHOLD); /************************************ diff --git a/circuits/lib/src/configs/secure/dkg.nr b/circuits/lib/src/configs/secure/dkg.nr index 21d2dcab66..2725231c2d 100644 --- a/circuits/lib/src/configs/secure/dkg.nr +++ b/circuits/lib/src/configs/secure/dkg.nr @@ -6,7 +6,6 @@ use crate::configs::default::{N_PARTIES, T}; pub use crate::configs::secure::threshold::{L as L_THRESHOLD, QIS as QIS_THRESHOLD}; -use crate::core::dkg::share_computation::base::Configs as ShareComputationBaseConfigs; use crate::core::dkg::share_computation::chunk::Configs as ShareComputationChunkConfigs; use crate::core::dkg::share_encryption::Configs as ShareEncryptionConfigs; @@ -18,6 +17,13 @@ pub global PLAINTEXT_MODULUS: Field = 18014398509481984; pub global Q_MOD_T: Field = 1082658244788225; pub global Q_MOD_T_CENTERED: Field = 1082658244788225; +pub global PARITY_MATRIX: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L_THRESHOLD] = [ + [[1, 2251799822204927, 1, 0], [2, 2251799822204926, 0, 1]], + [[1, 4503599627763711, 1, 0], [2, 4503599627763710, 0, 1]], + [[1, 4503599631433727, 1, 0], [2, 4503599631433726, 0, 1]], + [[1, 4503599634579455, 1, 0], [2, 4503599634579454, 0, 1]], +]; + /************************************ ------------------------------------- pk (CIRCUIT 0) @@ -28,12 +34,6 @@ pk (CIRCUIT 0) pub global PK_BIT_PK: u32 = 56; -pub global PARITY_MATRIX: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L_THRESHOLD] = [ - [[1, 2251799822204927, 1, 0], [2, 2251799822204926, 0, 1]], - [[1, 4503599627763711, 1, 0], [2, 4503599627763710, 0, 1]], - [[1, 4503599631433727, 1, 0], [2, 4503599631433726, 0, 1]], - [[1, 4503599634579455, 1, 0], [2, 4503599634579454, 0, 1]], -]; /************************************ ------------------------------------- share_computation_sk (CIRCUIT 2a) @@ -43,19 +43,6 @@ share_computation_sk (CIRCUIT 2a) pub global SHARE_COMPUTATION_BIT_SHARE: u32 = 53; pub global SHARE_COMPUTATION_SK_BIT_SECRET: u32 = 1; -// Chunk size controls circuit size vs number of chunks tradeoff. -// N_CHUNKS = N / CHUNK_SIZE -// At 5 parties: CHUNK_SIZE=512, N_CHUNKS=16 -// At 50 parties: reduce CHUNK_SIZE to keep chunk circuit constant size -pub global SHARE_COMPUTATION_CHUNK_SIZE: u32 = 512; -// N / CHUNK_SIZE = 8192 / 512 =1 6 -pub global SHARE_COMPUTATION_N_CHUNKS: u32 = 16; -pub global SHARE_COMPUTATION_SK_BASE_CONFIGS: ShareComputationBaseConfigs = - ShareComputationBaseConfigs::new(QIS_THRESHOLD); - -pub global SHARE_COMPUTATION_SK_CHUNK_CONFIGS: ShareComputationChunkConfigs = - ShareComputationChunkConfigs::new(QIS_THRESHOLD); - /************************************ ------------------------------------- share_computation_e_sm (CIRCUIT 2b) @@ -64,11 +51,18 @@ share_computation_e_sm (CIRCUIT 2b) pub global SHARE_COMPUTATION_E_SM_BIT_SECRET: u32 = 192; -pub global SHARE_COMPUTATION_E_SM_BASE_CONFIGS: ShareComputationBaseConfigs = - ShareComputationBaseConfigs::new(QIS_THRESHOLD); +/************************************ +------------------------------------- +share_computation_chunk (CIRCUIT 2c) +------------------------------------- +************************************/ + +pub global SHARE_COMPUTATION_CHUNK_SIZE: u32 = 512; +pub global SHARE_COMPUTATION_N_CHUNKS: u32 = 16; -pub global SHARE_COMPUTATION_E_SM_CHUNK_CONFIGS: ShareComputationChunkConfigs = +pub global SHARE_COMPUTATION_CHUNK_CONFIGS: ShareComputationChunkConfigs = ShareComputationChunkConfigs::new(QIS_THRESHOLD); + /************************************ ------------------------------------- share_encryption_sk (CIRCUIT 3a) diff --git a/circuits/lib/src/core/dkg/share_computation/base.nr b/circuits/lib/src/core/dkg/share_computation/base.nr index 2370e8e21f..44420f2082 100644 --- a/circuits/lib/src/core/dkg/share_computation/base.nr +++ b/circuits/lib/src/core/dkg/share_computation/base.nr @@ -15,20 +15,9 @@ use crate::math::commitments::{ }; use crate::math::polynomial::Polynomial; -pub struct Configs { - pub qis: [Field; L], -} - -impl Configs { - pub fn new(qis: [Field; L]) -> Self { - Configs { qis } - } -} - // ===== BASE CIRCUIT FOR SK ===== pub struct SecretKeyShareComputationBase { - configs: Configs, /// Expected commitment to sk secret (from C1, public input) expected_secret_commitment: Field, /// Secret key polynomial @@ -41,12 +30,11 @@ pub struct SecretKeyShareComputationBase SecretKeyShareComputationBase { pub fn new( - configs: Configs, expected_secret_commitment: Field, sk_secret: Polynomial, y: [[[Field; N_PARTIES + 1]; L]; N], ) -> Self { - SecretKeyShareComputationBase { configs, expected_secret_commitment, sk_secret, y } + SecretKeyShareComputationBase { expected_secret_commitment, sk_secret, y } } fn verify_secret_commitment(self) { @@ -95,7 +83,6 @@ impl { - configs: Configs, /// Expected commitment to e_sm secret (from C1, public input) expected_secret_commitment: Field, /// Smudging noise polynomial per modulus @@ -108,12 +95,11 @@ pub struct SmudgingNoiseShareComputationBase SmudgingNoiseShareComputationBase { pub fn new( - configs: Configs, expected_secret_commitment: Field, e_sm_secret: [Polynomial; L], y: [[[Field; N_PARTIES + 1]; L]; N], ) -> Self { - SmudgingNoiseShareComputationBase { configs, expected_secret_commitment, e_sm_secret, y } + SmudgingNoiseShareComputationBase { expected_secret_commitment, e_sm_secret, y } } fn verify_secret_commitment(self) { diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs index 6c8008c6e3..37e9ba34be 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -117,29 +117,21 @@ pub fn generate_configs( let prefix = ::PREFIX; Ok(format!( - r#"use crate::core::dkg::share_computation::base::Configs as ShareComputationBaseConfigs; -use crate::core::dkg::share_computation::chunk::Configs as ShareComputationChunkConfigs; + r#"use crate::core::dkg::share_computation::chunk::Configs as ShareComputationChunkConfigs; +pub use crate::configs::{config_name}::threshold::{{L as L_THRESHOLD, QIS as QIS_THRESHOLD}}; -pub use crate::configs::{}::threshold::{{L as L_THRESHOLD, QIS as QIS_THRESHOLD}}; +pub global N: u32 = {degree}; -pub global N: u32 = {}; +{parity_matrix} -{} /************************************ ------------------------------------- share_computation_sk (CIRCUIT 2a) ------------------------------------- ************************************/ -pub global {}_BIT_SHARE: u32 = {}; -pub global {}_SK_BIT_SECRET: u32 = {}; -pub global {}_CHUNK_SIZE: u32 = {}; -pub global {}_N_CHUNKS: u32 = {}; - -pub global {}_SK_BASE_CONFIGS: ShareComputationBaseConfigs = - ShareComputationBaseConfigs::new(QIS_THRESHOLD); -pub global {}_SK_CHUNK_CONFIGS: ShareComputationChunkConfigs = - ShareComputationChunkConfigs::new(QIS_THRESHOLD); +pub global {prefix}_BIT_SHARE: u32 = {bit_share}; +pub global {prefix}_SK_BIT_SECRET: u32 = {bit_sk_secret}; /************************************ ------------------------------------- @@ -147,30 +139,29 @@ share_computation_e_sm (CIRCUIT 2b) ------------------------------------- ************************************/ -pub global {}_E_SM_BIT_SECRET: u32 = {}; +pub global {prefix}_E_SM_BIT_SECRET: u32 = {bit_e_sm_secret}; + +/************************************ +------------------------------------- +share_computation_chunk (CIRCUIT 2c) +------------------------------------- +************************************/ + +pub global {prefix}_CHUNK_SIZE: u32 = {chunk_size}; +pub global {prefix}_N_CHUNKS: u32 = {n_chunks}; -pub global {}_E_SM_BASE_CONFIGS: ShareComputationBaseConfigs = - ShareComputationBaseConfigs::new(QIS_THRESHOLD); -pub global {}_E_SM_CHUNK_CONFIGS: ShareComputationChunkConfigs = +pub global {prefix}_CHUNK_CONFIGS: ShareComputationChunkConfigs = ShareComputationChunkConfigs::new(QIS_THRESHOLD); "#, - config_name, - preset.metadata().degree, - parity_matrix_str, - prefix, - bits.bit_share, - prefix, - bits.bit_sk_secret, - prefix, - chunk_size, - prefix, - n_chunks, - prefix, - prefix, - prefix, - bits.bit_e_sm_secret, - prefix, - prefix, + config_name = config_name, + degree = preset.metadata().degree, + parity_matrix = parity_matrix_str, + prefix = prefix, + bit_share = bits.bit_share, + bit_sk_secret = bits.bit_sk_secret, + bit_e_sm_secret = bits.bit_e_sm_secret, + chunk_size = chunk_size, + n_chunks = n_chunks, )) } From 7890c112c48025146d2cdce1706722aff9d22cc0 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 10 Mar 2026 23:05:06 +0100 Subject: [PATCH 08/27] rename inner wrapper to just share_computation --- circuits/bin/dkg/Nargo.toml | 2 +- .../{share_computation_wrapper => share_computation}/Nargo.toml | 2 +- .../src/main.nr | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename circuits/bin/dkg/{share_computation_wrapper => share_computation}/Nargo.toml (88%) rename circuits/bin/dkg/{share_computation_wrapper => share_computation}/src/main.nr (100%) diff --git a/circuits/bin/dkg/Nargo.toml b/circuits/bin/dkg/Nargo.toml index f7ce936051..ae0c94a464 100644 --- a/circuits/bin/dkg/Nargo.toml +++ b/circuits/bin/dkg/Nargo.toml @@ -6,6 +6,6 @@ members = [ "sk_share_computation_base", "e_sm_share_computation_base", "share_computation_chunk", - "share_computation_wrapper" + "share_computation" ] diff --git a/circuits/bin/dkg/share_computation_wrapper/Nargo.toml b/circuits/bin/dkg/share_computation/Nargo.toml similarity index 88% rename from circuits/bin/dkg/share_computation_wrapper/Nargo.toml rename to circuits/bin/dkg/share_computation/Nargo.toml index c628a64873..0a9963a259 100644 --- a/circuits/bin/dkg/share_computation_wrapper/Nargo.toml +++ b/circuits/bin/dkg/share_computation/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "share_computation_wrapper" +name = "share_computation" type = "bin" authors = [""] diff --git a/circuits/bin/dkg/share_computation_wrapper/src/main.nr b/circuits/bin/dkg/share_computation/src/main.nr similarity index 100% rename from circuits/bin/dkg/share_computation_wrapper/src/main.nr rename to circuits/bin/dkg/share_computation/src/main.nr From b17a3aaacc92dd73b769d75dfe249a092c7c459d Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 11 Mar 2026 12:54:24 +0100 Subject: [PATCH 09/27] integrate new share computation circuits flow in zk-prover --- crates/events/src/enclave_event/proof.rs | 24 ++++-- .../events/src/enclave_event/signed_proof.rs | 4 +- crates/multithread/src/multithread.rs | 80 +++++++++++++++--- .../circuits/dkg/share_computation/circuit.rs | 1 + .../src/circuits/dkg/share_computation.rs | 29 ++++--- .../src/circuits/recursive_aggregation/mod.rs | 81 +++++++++++++++++++ crates/zk-prover/src/lib.rs | 4 +- 7 files changed, 193 insertions(+), 30 deletions(-) diff --git a/crates/events/src/enclave_event/proof.rs b/crates/events/src/enclave_event/proof.rs index eb48acc2a7..f664733116 100644 --- a/crates/events/src/enclave_event/proof.rs +++ b/crates/events/src/enclave_event/proof.rs @@ -81,10 +81,14 @@ pub enum CircuitName { PkBfv, /// TrBFV public key share proof (C1). PkGeneration, - /// Sk Share computation proof (C2a). - SkShareComputation, - /// E_SM share computation proof (C2b). - ESmShareComputation, + /// Sk share computation base proof (C2a base). + SkShareComputationBase, + /// E_SM share computation base proof (C2b base). + ESmShareComputationBase, + /// Share computation chunk proof (C2c, proven N times). + ShareComputationChunk, + /// Share computation inner circuit proof (C2 — binds base + N chunks). + ShareComputation, /// Share encryption proof (C3). ShareEncryption, /// DKG share decryption proof (C4). @@ -104,8 +108,10 @@ impl CircuitName { match self { CircuitName::PkBfv => "pk", CircuitName::PkGeneration => "pk_generation", - CircuitName::SkShareComputation => "sk_share_computation", - CircuitName::ESmShareComputation => "e_sm_share_computation", + CircuitName::SkShareComputationBase => "sk_share_computation_base", + CircuitName::ESmShareComputationBase => "e_sm_share_computation_base", + CircuitName::ShareComputationChunk => "share_computation_chunk", + CircuitName::ShareComputation => "share_computation", CircuitName::ShareEncryption => "share_encryption", CircuitName::DkgShareDecryption => "share_decryption", CircuitName::PkAggregation => "pk_aggregation", @@ -118,8 +124,10 @@ impl CircuitName { pub fn group(&self) -> &'static str { match self { CircuitName::PkBfv => "dkg", - CircuitName::SkShareComputation => "dkg", - CircuitName::ESmShareComputation => "dkg", + CircuitName::SkShareComputationBase => "dkg", + CircuitName::ESmShareComputationBase => "dkg", + CircuitName::ShareComputationChunk => "dkg", + CircuitName::ShareComputation => "dkg", CircuitName::ShareEncryption => "dkg", CircuitName::DkgShareDecryption => "dkg", CircuitName::PkGeneration => "threshold", diff --git a/crates/events/src/enclave_event/signed_proof.rs b/crates/events/src/enclave_event/signed_proof.rs index fd7bcbe90d..c7c95bac94 100644 --- a/crates/events/src/enclave_event/signed_proof.rs +++ b/crates/events/src/enclave_event/signed_proof.rs @@ -55,8 +55,8 @@ impl ProofType { match self { ProofType::C0PkBfv => vec![CircuitName::PkBfv], ProofType::C1PkGeneration => vec![CircuitName::PkGeneration], - ProofType::C2aSkShareComputation => vec![CircuitName::SkShareComputation], - ProofType::C2bESmShareComputation => vec![CircuitName::ESmShareComputation], + ProofType::C2aSkShareComputation => vec![CircuitName::ShareComputation], + ProofType::C2bESmShareComputation => vec![CircuitName::ShareComputation], ProofType::C3aSkShareEncryption => vec![CircuitName::ShareEncryption], ProofType::C3bESmShareEncryption => vec![CircuitName::ShareEncryption], ProofType::C4DkgShareDecryption => vec![CircuitName::DkgShareDecryption], diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index f9f145a430..20e26ce9db 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -61,14 +61,19 @@ use e3_zk_helpers::circuits::threshold::pk_generation::circuit::{ PkGenerationCircuit, PkGenerationCircuitData, }; use e3_zk_helpers::computation::DkgInputType; -use e3_zk_helpers::dkg::share_computation::{ShareComputationCircuit, ShareComputationCircuitData}; +use e3_zk_helpers::dkg::share_computation::{ + Configs, ShareComputationBaseCircuit, ShareComputationChunkCircuit, + ShareComputationChunkCircuitData, ShareComputationCircuitData, +}; use e3_zk_helpers::dkg::share_decryption::{ShareDecryptionCircuit, ShareDecryptionCircuitData}; use e3_zk_helpers::dkg::share_encryption::{ShareEncryptionCircuit, ShareEncryptionCircuitData}; use e3_zk_helpers::threshold::pk_aggregation::PkAggregationCircuit; use e3_zk_helpers::threshold::pk_aggregation::PkAggregationCircuitData; use e3_zk_helpers::CiphernodesCommittee; +use e3_zk_helpers::Computation; use e3_zk_prover::{ - generate_fold_proof, generate_wrapper_proof, CircuitVariant, Provable, ZkBackend, ZkProver, + generate_fold_proof, generate_share_computation_proof, generate_wrapper_proof, CircuitVariant, + Provable, ZkBackend, ZkProver, }; use fhe::bfv::{Ciphertext, Encoding, Plaintext, PublicKey, SecretKey}; use fhe::mbfv::PublicKeyShare; @@ -708,13 +713,13 @@ fn handle_share_computation_proof( .map(|arr| arr.mapv(|v| BigInt::from(v))) .collect(); - // 4. Compute parity matrix + // 5. Compute parity matrix let committee = req.committee_size.values(); let parity_matrix = compute_parity_matrix(threshold_params.moduli(), committee.n, committee.threshold) .map_err(|e| make_zk_error(&request, format!("compute_parity_matrix: {}", e)))?; - // 5. Build circuit data + // 6. Build circuit data let circuit_data = ShareComputationCircuitData { dkg_input_type: req.dkg_input_type, secret, @@ -724,12 +729,17 @@ fn handle_share_computation_proof( threshold: committee.threshold as u32, }; - // 6. Generate proof - let circuit = ShareComputationCircuit; let e3_id_str = request.e3_id.to_string(); - let proof = circuit - .prove(prover, &req.params_preset, &circuit_data, &e3_id_str) + // 7. Prove base circuit (C2a or C2b base) + let base_circuit = ShareComputationBaseCircuit; + let base_proof = base_circuit + .prove( + prover, + &req.params_preset, + &circuit_data, + &format!("{e3_id_str}_base"), + ) .map_err(|e| { ComputeRequestError::new( ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), @@ -737,14 +747,64 @@ fn handle_share_computation_proof( ) })?; - let wrapped_proof = generate_wrapper_proof(prover, &proof, &e3_id_str).map_err(|e| { + let wrapped_proof = generate_wrapper_proof(prover, &base_proof, &e3_id_str).map_err(|e| { + ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), + request.clone(), + ) + })?; + + // 8. Determine number of chunks and prove each chunk circuit + let n_chunks = Configs::compute(req.params_preset.clone(), &circuit_data) + .map_err(|e| make_zk_error(&request, format!("Configs::compute: {}", e)))? + .n_chunks; + + let chunk_circuit = ShareComputationChunkCircuit; + let mut chunk_proofs = Vec::with_capacity(n_chunks); + for chunk_idx in 0..n_chunks { + let chunk_data = ShareComputationChunkCircuitData { + share_data: circuit_data.clone(), + chunk_idx, + }; + let chunk_proof = chunk_circuit + .prove( + prover, + &req.params_preset, + &chunk_data, + &format!("{e3_id_str}_chunk_{chunk_idx}"), + ) + .map_err(|e| { + ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), + request.clone(), + ) + })?; + chunk_proofs.push(chunk_proof); + } + + // 9. Prove the share_computation circuit (binds base + N chunks) — this IS the C2 proof + let c2_proof = generate_share_computation_proof( + prover, + &base_proof, + &chunk_proofs, + &format!("{e3_id_str}_c2"), + ) + .map_err(|e| { + ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), + request.clone(), + ) + })?; + + // 10. Wrap with the standard outer wrapper + let proof = generate_wrapper_proof(prover, &c2_proof, &e3_id_str).map_err(|e| { ComputeRequestError::new( ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), request.clone(), ) })?; - // 7. Return response + // 11. Return response Ok(ComputeResponse::zk( ZkResponse::ShareComputation(ShareComputationProofResponse { proof, diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs index 3be874b3f8..12bdb8d5c8 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs @@ -47,6 +47,7 @@ impl Circuit for ShareComputationChunkCircuit { } // todo: currently reusing this but should be renamed when we change zk-prover +#[derive(Clone)] pub struct ShareComputationCircuitData { /// Which secret type this data is for (determines which branch to use in data). pub dkg_input_type: DkgInputType, diff --git a/crates/zk-prover/src/circuits/dkg/share_computation.rs b/crates/zk-prover/src/circuits/dkg/share_computation.rs index fe3cf64c0c..d44267bd0f 100644 --- a/crates/zk-prover/src/circuits/dkg/share_computation.rs +++ b/crates/zk-prover/src/circuits/dkg/share_computation.rs @@ -9,29 +9,40 @@ use e3_events::CircuitName; use e3_fhe_params::BfvPreset; use e3_zk_helpers::computation::DkgInputType; use e3_zk_helpers::dkg::share_computation::{ - Inputs, ShareComputationCircuit, ShareComputationCircuitData, + ChunkInputs, Inputs, ShareComputationBaseCircuit, ShareComputationChunkCircuit, + ShareComputationChunkCircuitData, ShareComputationCircuitData, }; -impl Provable for ShareComputationCircuit { +impl Provable for ShareComputationBaseCircuit { type Params = BfvPreset; type Input = ShareComputationCircuitData; type Inputs = Inputs; - fn resolve_circuit_name(&self, _params: &Self::Params, _input: &Self::Input) -> CircuitName { - match _input.dkg_input_type { - DkgInputType::SecretKey => CircuitName::SkShareComputation, - DkgInputType::SmudgingNoise => CircuitName::ESmShareComputation, + fn resolve_circuit_name(&self, _params: &Self::Params, input: &Self::Input) -> CircuitName { + match input.dkg_input_type { + DkgInputType::SecretKey => CircuitName::SkShareComputationBase, + DkgInputType::SmudgingNoise => CircuitName::ESmShareComputationBase, } } fn valid_circuits(&self) -> Vec { vec![ - CircuitName::SkShareComputation, - CircuitName::ESmShareComputation, + CircuitName::SkShareComputationBase, + CircuitName::ESmShareComputationBase, ] } fn circuit(&self) -> CircuitName { - CircuitName::SkShareComputation + CircuitName::SkShareComputationBase + } +} + +impl Provable for ShareComputationChunkCircuit { + type Params = BfvPreset; + type Input = ShareComputationChunkCircuitData; + type Inputs = ChunkInputs; + + fn circuit(&self) -> CircuitName { + CircuitName::ShareComputationChunk } } diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs index bcb10a1dcb..c341477f7d 100644 --- a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs +++ b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs @@ -20,6 +20,87 @@ use e3_events::{CircuitName, CircuitVariant, Proof}; use self::utils::bytes_to_field_strings; +////////////////////////////////////////////////////////////////////////////// +// Share Computation inner circuit proof +////////////////////////////////////////////////////////////////////////////// + +/// Input for the share_computation circuit that binds 1 base proof + N chunk proofs. +/// Field names match Noir parameter names exactly for witness generation. +#[derive(serde::Serialize)] +struct ShareComputationInput { + base_verification_key: Vec, + base_proof: Vec, + base_public_inputs: Vec, + base_key_hash: String, + chunk_verification_key: Vec, + chunk_proofs: Vec>, + chunk_public_inputs: Vec>, + chunk_key_hash: String, +} + +/// Proves the share_computation circuit that binds 1 base proof + N chunk proofs. +/// +/// This IS the C2a/C2b proof — the successor to the old monolithic SkShareComputation / +/// ESmShareComputation circuits. The returned proof (tagged `ShareComputation`) is then +/// passed to [`generate_wrapper_proof`] exactly like the old circuit proofs were. +/// +/// # Arguments +/// * `prover` - ZK prover with bb and circuits configured +/// * `base_proof` - The proved base circuit (SkShareComputationBase or ESmShareComputationBase) +/// * `chunk_proofs` - The N proved chunk circuits (ShareComputationChunk) +/// * `e3_id` - Job identifier for work dir +pub fn generate_share_computation_proof( + prover: &ZkProver, + base_proof: &Proof, + chunk_proofs: &[Proof], + e3_id: &str, +) -> Result { + let recursive_dir = prover.circuits_dir(CircuitVariant::Recursive); + + let base_vk = vk::load_vk_artifacts(&recursive_dir, base_proof.circuit)?; + let chunk_vk = vk::load_vk_artifacts(&recursive_dir, CircuitName::ShareComputationChunk)?; + + let base_proof_fields = bytes_to_field_strings(&base_proof.data)?; + let base_public_inputs = bytes_to_field_strings(&base_proof.public_signals)?; + + let mut chunk_proof_fields = Vec::with_capacity(chunk_proofs.len()); + let mut chunk_public_inputs = Vec::with_capacity(chunk_proofs.len()); + for cp in chunk_proofs { + chunk_proof_fields.push(bytes_to_field_strings(&cp.data)?); + chunk_public_inputs.push(bytes_to_field_strings(&cp.public_signals)?); + } + + let full_input = ShareComputationInput { + base_verification_key: base_vk.verification_key, + base_proof: base_proof_fields, + base_public_inputs, + base_key_hash: base_vk.key_hash, + chunk_verification_key: chunk_vk.verification_key, + chunk_proofs: chunk_proof_fields, + chunk_public_inputs, + chunk_key_hash: chunk_vk.key_hash, + }; + + let circuit_path = recursive_dir + .join(CircuitName::ShareComputation.dir_path()) + .join(format!("{}.json", CircuitName::ShareComputation.as_str())); + let compiled = CompiledCircuit::from_file(&circuit_path)?; + + let json = serde_json::to_value(&full_input) + .map_err(|e| ZkError::SerializationError(e.to_string()))?; + let input_map = inputs_json_to_input_map(&json)?; + + let witness_gen = WitnessGenerator::new(); + let witness = witness_gen.generate_witness(&compiled, input_map)?; + + prover.generate_proof_with_variant( + CircuitName::ShareComputation, + &witness, + e3_id, + CircuitVariant::Recursive, + ) +} + /// Full input for the recursive wrapper circuit. struct WrapperInput { verification_key: Vec, diff --git a/crates/zk-prover/src/lib.rs b/crates/zk-prover/src/lib.rs index 04c8dbec96..a32c7efa72 100644 --- a/crates/zk-prover/src/lib.rs +++ b/crates/zk-prover/src/lib.rs @@ -21,7 +21,9 @@ pub use actors::{ }; pub use backend::{SetupStatus, ZkBackend}; -pub use circuits::recursive_aggregation::{generate_fold_proof, generate_wrapper_proof}; +pub use circuits::recursive_aggregation::{ + generate_fold_proof, generate_share_computation_proof, generate_wrapper_proof, +}; pub use config::{verify_checksum, BbTarget, CircuitInfo, VersionInfo, ZkConfig}; pub use e3_events::CircuitVariant; pub use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuit; From 05ebb1a49ffe8ba7c8959c4d21256220a5a24a48 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 11 Mar 2026 15:14:49 +0100 Subject: [PATCH 10/27] fix wrong non_zk verifier for inner wrapper --- circuits/bin/dkg/share_computation/src/main.nr | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/circuits/bin/dkg/share_computation/src/main.nr b/circuits/bin/dkg/share_computation/src/main.nr index 9fdac1294d..5699207b88 100644 --- a/circuits/bin/dkg/share_computation/src/main.nr +++ b/circuits/bin/dkg/share_computation/src/main.nr @@ -4,7 +4,9 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use bb_proof_verification::{UltraHonkProof, UltraHonkVerificationKey, verify_honk_proof_non_zk}; +// Base and chunk proofs are produced with noir-recursive (ZK) target, so they use +// UltraHonkZKProof (508 elements). Non-ZK type would expect 457 and cause LengthMismatch. +use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; use lib::configs::default::dkg::{ L_THRESHOLD, N, SHARE_COMPUTATION_CHUNK_SIZE, SHARE_COMPUTATION_N_CHUNKS, }; @@ -25,16 +27,16 @@ pub global CHUNK_PUBLIC_INPUTS: u32 = SHARE_COMPUTATION_CHUNK_SIZE * L_THRESHOLD fn main( base_verification_key: UltraHonkVerificationKey, - base_proof: UltraHonkProof, + base_proof: UltraHonkZKProof, base_public_inputs: [Field; BASE_PUBLIC_INPUTS], base_key_hash: pub Field, chunk_verification_key: UltraHonkVerificationKey, - chunk_proofs: [UltraHonkProof; SHARE_COMPUTATION_N_CHUNKS], + chunk_proofs: [UltraHonkZKProof; SHARE_COMPUTATION_N_CHUNKS], chunk_public_inputs: [[Field; CHUNK_PUBLIC_INPUTS]; SHARE_COMPUTATION_N_CHUNKS], chunk_key_hash: pub Field, ) -> pub Field { - // Step 1: Verify base proof - verify_honk_proof_non_zk( + // Step 1: Verify base proof (ZK verifier; proof is 508 elements) + verify_honk_proof( base_verification_key, base_proof, base_public_inputs, @@ -45,7 +47,7 @@ fn main( // y starts at index 1 in base_public_inputs (after expected_secret_commitment) // y_chunk for chunk i starts at offset i * CHUNK_PUBLIC_INPUTS within base y for chunk_idx in 0..SHARE_COMPUTATION_N_CHUNKS { - verify_honk_proof_non_zk( + verify_honk_proof( chunk_verification_key, chunk_proofs[chunk_idx], chunk_public_inputs[chunk_idx], From b4d7a40375bd58f0c19360bbde6e01570545a69d Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 11 Mar 2026 15:15:05 +0100 Subject: [PATCH 11/27] adjust local_e2e_test to reflect new c2 recursive commitments --- Cargo.lock | 2 + crates/zk-prover/Cargo.toml | 2 + .../src/circuits/dkg/share_computation.rs | 75 +++++++- crates/zk-prover/tests/local_e2e_tests.rs | 165 ++++++++++++++---- 4 files changed, 207 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e2e3a7119..830a1a5c43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3859,6 +3859,8 @@ dependencies = [ "acvm", "alloy", "anyhow", + "ark-bn254 0.5.0", + "ark-ff 0.5.0", "async-trait", "base64", "bincode 1.3.3", diff --git a/crates/zk-prover/Cargo.toml b/crates/zk-prover/Cargo.toml index ab7d1120df..0e7771ea4b 100644 --- a/crates/zk-prover/Cargo.toml +++ b/crates/zk-prover/Cargo.toml @@ -48,6 +48,8 @@ walkdir = "2.5" [dev-dependencies] e3-test-helpers = { workspace = true } +ark-bn254 = { workspace = true } +ark-ff = { workspace = true } paste = "1" tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/crates/zk-prover/src/circuits/dkg/share_computation.rs b/crates/zk-prover/src/circuits/dkg/share_computation.rs index d44267bd0f..7f0973be24 100644 --- a/crates/zk-prover/src/circuits/dkg/share_computation.rs +++ b/crates/zk-prover/src/circuits/dkg/share_computation.rs @@ -4,14 +4,85 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE +use crate::circuits::recursive_aggregation::generate_share_computation_proof; +use crate::prover::ZkProver; use crate::traits::Provable; use e3_events::CircuitName; use e3_fhe_params::BfvPreset; use e3_zk_helpers::computation::DkgInputType; use e3_zk_helpers::dkg::share_computation::{ - ChunkInputs, Inputs, ShareComputationBaseCircuit, ShareComputationChunkCircuit, - ShareComputationChunkCircuitData, ShareComputationCircuitData, + ChunkInputs, Configs, Inputs, ShareComputationBaseCircuit, ShareComputationChunkCircuit, + ShareComputationChunkCircuitData, ShareComputationCircuit, ShareComputationCircuitData, }; +use e3_zk_helpers::Computation; + +/// Full share-computation proof (base + chunks + aggregation + wrapper). +/// Used by local e2e tests; the enclave uses the same pipeline via multithread handler. +impl Provable for ShareComputationCircuit { + type Params = BfvPreset; + type Input = ShareComputationCircuitData; + type Inputs = Inputs; + + fn circuit(&self) -> CircuitName { + CircuitName::ShareComputation + } + + fn valid_circuits(&self) -> Vec { + vec![CircuitName::ShareComputation] + } + + fn prove( + &self, + prover: &ZkProver, + params: &Self::Params, + input: &Self::Input, + e3_id: &str, + ) -> Result { + let base_circuit = ShareComputationBaseCircuit; + let base_proof = base_circuit.prove(prover, params, input, &format!("{e3_id}_base"))?; + + let n_chunks = Configs::compute(params.clone(), input) + .map_err(|e| crate::error::ZkError::InputsGenerationFailed(e.to_string()))? + .n_chunks; + + let chunk_circuit = ShareComputationChunkCircuit; + let mut chunk_proofs = Vec::with_capacity(n_chunks); + for chunk_idx in 0..n_chunks { + let chunk_data = ShareComputationChunkCircuitData { + share_data: input.clone(), + chunk_idx, + }; + let chunk_proof = chunk_circuit.prove( + prover, + params, + &chunk_data, + &format!("{e3_id}_chunk_{chunk_idx}"), + )?; + chunk_proofs.push(chunk_proof); + } + + let c2_proof = generate_share_computation_proof(prover, &base_proof, &chunk_proofs, e3_id)?; + // Return the inner share_computation proof (no wrapper). Tests verify with Recursive variant. + Ok(c2_proof) + } + + /// Inner share_computation proof is verified with Recursive variant. + fn verify( + &self, + prover: &ZkProver, + proof: &e3_events::Proof, + e3_id: &str, + party_id: u64, + ) -> Result { + self.verify_with_variant( + prover, + proof, + e3_id, + party_id, + e3_events::CircuitVariant::Recursive, + ) + } +} impl Provable for ShareComputationBaseCircuit { type Params = BfvPreset; diff --git a/crates/zk-prover/tests/local_e2e_tests.rs b/crates/zk-prover/tests/local_e2e_tests.rs index 27d032148b..67221ea8d1 100644 --- a/crates/zk-prover/tests/local_e2e_tests.rs +++ b/crates/zk-prover/tests/local_e2e_tests.rs @@ -21,9 +21,15 @@ use common::{ use e3_fhe_params::BfvPreset; use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuit; use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuitData; -use e3_zk_helpers::circuits::{commitments::compute_dkg_pk_commitment, CircuitComputation}; +use e3_zk_helpers::circuits::{ + commitments::{compute_dkg_pk_commitment, compute_recursive_aggregation_commitment}, + CircuitComputation, +}; use e3_zk_helpers::computation::DkgInputType; -use e3_zk_helpers::dkg::share_computation::{ShareComputationCircuit, ShareComputationCircuitData}; +use e3_zk_helpers::dkg::share_computation::{ + Configs, ShareComputationBaseCircuit, ShareComputationChunkCircuit, + ShareComputationChunkCircuitData, ShareComputationCircuit, ShareComputationCircuitData, +}; use e3_zk_helpers::dkg::share_encryption::{ShareEncryptionCircuit, ShareEncryptionCircuitData}; use e3_zk_helpers::threshold::pk_generation::{PkGenerationCircuit, PkGenerationCircuitData}; use e3_zk_helpers::threshold::{ @@ -37,8 +43,19 @@ use e3_zk_helpers::threshold::{ }, }; use e3_zk_helpers::CiphernodesCommitteeSize; +use e3_zk_helpers::Computation; use e3_zk_helpers::{compute_share_computation_sk_commitment, compute_threshold_pk_commitment}; -use e3_zk_prover::{Provable, ZkBackend, ZkProver}; +use ark_bn254::Fr; +use ark_ff::PrimeField; +use e3_zk_prover::{generate_share_computation_proof, Provable, ZkBackend, ZkProver}; + +/// Convert raw public signals bytes (32-byte big-endian chunks) to ark_bn254::Fr field elements. +fn public_signals_to_fields(signals: &[u8]) -> Vec { + signals + .chunks(32) + .map(|chunk| Fr::from_be_bytes_mod_order(chunk)) + .collect() +} async fn setup_share_encryption_e_sm_test() -> Option<( ZkBackend, @@ -120,7 +137,7 @@ async fn setup_share_encryption_sk_test() -> Option<( )) } -async fn setup_share_computation_sk_test() -> Option<( +async fn setup_share_computation_sk_base_chunk_test() -> Option<( ZkBackend, tempfile::TempDir, ZkProver, @@ -134,7 +151,11 @@ async fn setup_share_computation_sk_test() -> Option<( let bb = find_bb().await?; let (backend, temp) = setup_test_prover(&bb).await; - setup_compiled_circuit(&backend, "dkg", "sk_share_computation").await; + // Inner share_computation only: base, chunk, share_computation aggregator (no wrapper) + setup_compiled_circuit(&backend, "dkg", "sk_share_computation_base").await; + setup_compiled_circuit(&backend, "dkg", "e_sm_share_computation_base").await; + setup_compiled_circuit(&backend, "dkg", "share_computation_chunk").await; + setup_compiled_circuit(&backend, "dkg", "share_computation").await; let sample = ShareComputationCircuitData::generate_sample(preset, committee, DkgInputType::SecretKey) @@ -152,7 +173,7 @@ async fn setup_share_computation_sk_test() -> Option<( )) } -async fn setup_share_computation_e_sm_test() -> Option<( +async fn setup_share_computation_e_sm_base_chunk_test() -> Option<( ZkBackend, tempfile::TempDir, ZkProver, @@ -166,7 +187,11 @@ async fn setup_share_computation_e_sm_test() -> Option<( let bb = find_bb().await?; let (backend, temp) = setup_test_prover(&bb).await; - setup_compiled_circuit(&backend, "dkg", "e_sm_share_computation").await; + // Inner share_computation only: base, chunk, share_computation aggregator (no wrapper) + setup_compiled_circuit(&backend, "dkg", "sk_share_computation_base").await; + setup_compiled_circuit(&backend, "dkg", "e_sm_share_computation_base").await; + setup_compiled_circuit(&backend, "dkg", "share_computation_chunk").await; + setup_compiled_circuit(&backend, "dkg", "share_computation").await; let sample = ShareComputationCircuitData::generate_sample( preset, @@ -366,8 +391,8 @@ macro_rules! e2e_proof_tests { e2e_proof_tests! { (pk_generation, setup_pk_generation_test()), (pk, setup_pk_test()), - (share_computation_sk, setup_share_computation_sk_test()), - (share_computation_e_sm, setup_share_computation_e_sm_test()), + (share_computation_sk, setup_share_computation_sk_base_chunk_test()), + (share_computation_e_sm, setup_share_computation_e_sm_base_chunk_test()), (share_encryption_sk, setup_share_encryption_sk_test()), (share_encryption_e_sm, setup_share_encryption_e_sm_test()), (share_decryption, setup_share_decryption_test()), @@ -460,28 +485,63 @@ async fn test_pk_bfv_commitment_consistency() { #[tokio::test] async fn test_share_computation_sk_commitment_consistency() { - let Some((_backend, _temp, prover, circuit, sample, preset, e3_id)) = - setup_share_computation_sk_test().await + let Some((_backend, _temp, prover, _circuit, sample, preset, e3_id)) = + setup_share_computation_sk_base_chunk_test().await else { println!("skipping: bb not found"); return; }; - let proof = circuit - .prove(&prover, &preset, &sample, e3_id) - .expect("proof generation should succeed"); + // Run the pipeline manually to capture intermediate public signals + let base_proof = ShareComputationBaseCircuit + .prove(&prover, &preset, &sample, &format!("{e3_id}_base")) + .expect("base proof should succeed"); + + let n_chunks = Configs::compute(preset, &sample) + .expect("configs") + .n_chunks; + + let mut chunk_proofs = Vec::with_capacity(n_chunks); + for chunk_idx in 0..n_chunks { + let chunk_data = ShareComputationChunkCircuitData { + share_data: sample.clone(), + chunk_idx, + }; + let chunk_proof = ShareComputationChunkCircuit + .prove( + &prover, + &preset, + &chunk_data, + &format!("{e3_id}_chunk_{chunk_idx}"), + ) + .expect("chunk proof should succeed"); + chunk_proofs.push(chunk_proof); + } - // Verify the commitment from the proof is a valid field element - let commitment_from_proof = extract_field(&proof.public_signals, 0); + let proof = generate_share_computation_proof(&prover, &base_proof, &chunk_proofs, e3_id) + .expect("share_computation proof should succeed"); - // Compute the commitment independently to ensure consistency - let computation_output = - ShareComputationCircuit::compute(preset, &sample).expect("computation should succeed"); - let commitment_calculated = computation_output.inputs.expected_secret_commitment.clone(); + // The share_computation inner circuit has 3 public outputs (each 32 bytes): + // [0] base_key_hash + // [1] chunk_key_hash + // [2] aggregated commitment = compute_recursive_aggregation_commitment(base_pub ++ chunk_pubs) + assert_eq!( + proof.public_signals.len(), + 3 * 32, + "share_computation inner circuit should have exactly 3 public outputs (96 bytes)" + ); + + // Reconstruct the aggregated payload from intermediate proofs' public signals + let mut payload: Vec = public_signals_to_fields(&base_proof.public_signals); + for cp in &chunk_proofs { + payload.extend(public_signals_to_fields(&cp.public_signals)); + } + let expected_commitment = compute_recursive_aggregation_commitment(payload); + let commitment_from_proof = extract_field(&proof.public_signals, 2); assert_eq!( - commitment_from_proof, commitment_calculated, - "Commitment from proof must match independently calculated commitment" + commitment_from_proof, expected_commitment, + "Aggregated commitment from proof must match independently calculated commitment" ); prover.cleanup(e3_id).unwrap(); @@ -489,28 +549,63 @@ async fn test_share_computation_sk_commitment_consistency() { #[tokio::test] async fn test_share_computation_e_sm_commitment_consistency() { - let Some((_backend, _temp, prover, circuit, sample, preset, e3_id)) = - setup_share_computation_e_sm_test().await + let Some((_backend, _temp, prover, _circuit, sample, preset, e3_id)) = + setup_share_computation_e_sm_base_chunk_test().await else { println!("skipping: bb not found"); return; }; - let proof = circuit - .prove(&prover, &preset, &sample, e3_id) - .expect("proof generation should succeed"); + // Run the pipeline manually to capture intermediate public signals + let base_proof = ShareComputationBaseCircuit + .prove(&prover, &preset, &sample, &format!("{e3_id}_base")) + .expect("base proof should succeed"); + + let n_chunks = Configs::compute(preset, &sample) + .expect("configs") + .n_chunks; + + let mut chunk_proofs = Vec::with_capacity(n_chunks); + for chunk_idx in 0..n_chunks { + let chunk_data = ShareComputationChunkCircuitData { + share_data: sample.clone(), + chunk_idx, + }; + let chunk_proof = ShareComputationChunkCircuit + .prove( + &prover, + &preset, + &chunk_data, + &format!("{e3_id}_chunk_{chunk_idx}"), + ) + .expect("chunk proof should succeed"); + chunk_proofs.push(chunk_proof); + } - // Verify the commitment from the proof is a valid field element - let commitment_from_proof = extract_field(&proof.public_signals, 0); + let proof = generate_share_computation_proof(&prover, &base_proof, &chunk_proofs, e3_id) + .expect("share_computation proof should succeed"); - // Compute the commitment independently to ensure consistency - let computation_output = - ShareComputationCircuit::compute(preset, &sample).expect("computation should succeed"); - let commitment_calculated = computation_output.inputs.expected_secret_commitment.clone(); + // The share_computation inner circuit has 3 public outputs (each 32 bytes): + // [0] base_key_hash + // [1] chunk_key_hash + // [2] aggregated commitment = compute_recursive_aggregation_commitment(base_pub ++ chunk_pubs) + assert_eq!( + proof.public_signals.len(), + 3 * 32, + "share_computation inner circuit should have exactly 3 public outputs (96 bytes)" + ); + // Reconstruct the aggregated payload from intermediate proofs' public signals + let mut payload: Vec = public_signals_to_fields(&base_proof.public_signals); + for cp in &chunk_proofs { + payload.extend(public_signals_to_fields(&cp.public_signals)); + } + let expected_commitment = compute_recursive_aggregation_commitment(payload); + + let commitment_from_proof = extract_field(&proof.public_signals, 2); assert_eq!( - commitment_from_proof, commitment_calculated, - "Commitment from proof must match independently calculated commitment" + commitment_from_proof, expected_commitment, + "Aggregated commitment from proof must match independently calculated commitment" ); prover.cleanup(e3_id).unwrap(); From 80e4a802a75ae5aff4319d2cb630f6df2367622a Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 11 Mar 2026 15:15:40 +0100 Subject: [PATCH 12/27] format --- crates/zk-prover/tests/local_e2e_tests.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/zk-prover/tests/local_e2e_tests.rs b/crates/zk-prover/tests/local_e2e_tests.rs index 67221ea8d1..c1c7b290fb 100644 --- a/crates/zk-prover/tests/local_e2e_tests.rs +++ b/crates/zk-prover/tests/local_e2e_tests.rs @@ -15,6 +15,8 @@ mod common; +use ark_bn254::Fr; +use ark_ff::PrimeField; use common::{ extract_field, extract_field_from_end, find_bb, setup_compiled_circuit, setup_test_prover, }; @@ -45,8 +47,6 @@ use e3_zk_helpers::threshold::{ use e3_zk_helpers::CiphernodesCommitteeSize; use e3_zk_helpers::Computation; use e3_zk_helpers::{compute_share_computation_sk_commitment, compute_threshold_pk_commitment}; -use ark_bn254::Fr; -use ark_ff::PrimeField; use e3_zk_prover::{generate_share_computation_proof, Provable, ZkBackend, ZkProver}; /// Convert raw public signals bytes (32-byte big-endian chunks) to ark_bn254::Fr field elements. @@ -497,9 +497,7 @@ async fn test_share_computation_sk_commitment_consistency() { .prove(&prover, &preset, &sample, &format!("{e3_id}_base")) .expect("base proof should succeed"); - let n_chunks = Configs::compute(preset, &sample) - .expect("configs") - .n_chunks; + let n_chunks = Configs::compute(preset, &sample).expect("configs").n_chunks; let mut chunk_proofs = Vec::with_capacity(n_chunks); for chunk_idx in 0..n_chunks { @@ -561,9 +559,7 @@ async fn test_share_computation_e_sm_commitment_consistency() { .prove(&prover, &preset, &sample, &format!("{e3_id}_base")) .expect("base proof should succeed"); - let n_chunks = Configs::compute(preset, &sample) - .expect("configs") - .n_chunks; + let n_chunks = Configs::compute(preset, &sample).expect("configs").n_chunks; let mut chunk_proofs = Vec::with_capacity(n_chunks); for chunk_idx in 0..n_chunks { From 228f63e93075e6e09de2b2ad58d524eac6eb0e60 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 11 Mar 2026 15:58:54 +0100 Subject: [PATCH 13/27] adjust wrapper to take inner wrapper pub inputs --- .../wrapper/dkg/share_computation/src/main.nr | 13 +++++++------ crates/keyshare/src/threshold_share_collector.rs | 2 +- crates/multithread/src/multithread.rs | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) 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 9440840d12..e9b5c3ecdb 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 @@ -5,13 +5,13 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; -use lib::configs::default::dkg::L_THRESHOLD; -use lib::{configs::default::N_PARTIES, math::commitments::compute_recursive_aggregation_commitment}; +use lib::math::commitments::compute_recursive_aggregation_commitment; -// Number of proofs. -pub global N_PROOFS: u32 = 1; -/// Number of public inputs/outputs per proof. -pub global N_PUBLIC_INPUTS: u32 = (L_THRESHOLD * N_PARTIES) + 3; +// Both SK and ESM share_computation inner proofs wrapped together during aggregation. +pub global N_PROOFS: u32 = 2; +// The share_computation inner circuit has 3 public outputs: +// base_key_hash, chunk_key_hash, aggregated_commitment (return value). +pub global N_PUBLIC_INPUTS: u32 = 3; fn main( verification_key: UltraHonkVerificationKey, @@ -21,6 +21,7 @@ fn main( ) -> pub Field { for i in 0..N_PROOFS { verify_honk_proof(verification_key, proofs[i], public_inputs[i], key_hash); + verify_honk_proof(verification_key, proofs[i], public_inputs[i], key_hash); } let mut aggregated_public_inputs = Vec::new(); diff --git a/crates/keyshare/src/threshold_share_collector.rs b/crates/keyshare/src/threshold_share_collector.rs index 74c31df3f5..a62a34a4a0 100644 --- a/crates/keyshare/src/threshold_share_collector.rs +++ b/crates/keyshare/src/threshold_share_collector.rs @@ -34,7 +34,7 @@ pub struct ReceivedShareProofs { pub signed_c3b_proofs: Vec, } -const DEFAULT_COLLECTION_TIMEOUT: Duration = Duration::from_secs(1200); +const DEFAULT_COLLECTION_TIMEOUT: Duration = Duration::from_secs(800); pub(crate) enum CollectorState { Collecting, diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 20e26ce9db..838fc69aad 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -782,7 +782,8 @@ fn handle_share_computation_proof( chunk_proofs.push(chunk_proof); } - // 9. Prove the share_computation circuit (binds base + N chunks) — this IS the C2 proof + // 9. Prove the share_computation circuit (binds base + N chunks) — this IS the C2 proof. + // Wrapping happens later during aggregation (both SK + ESM wrapped together). let c2_proof = generate_share_computation_proof( prover, &base_proof, From a5d86c80c8fa7e7d5aaf1cdbd01fda90ebf02512 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 11 Mar 2026 16:59:55 +0100 Subject: [PATCH 14/27] adjust duration and avoid concurrency in CI tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c648364898..ead13f3975 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -923,7 +923,7 @@ jobs: find circuits/bin/evm -name '*.vk' | head -20 - name: Run ZK prover e2e tests - run: cargo test -p e3-zk-prover --test local_e2e_tests -- --nocapture + run: cargo test -p e3-zk-prover --test local_e2e_tests -- --nocapture --test-threads=1 build_e3_support_dev: needs: [detect_changes] From a17fdb17d421d93b83cd740314bf67be05d6871d Mon Sep 17 00:00:00 2001 From: Zara Date: Wed, 11 Mar 2026 14:16:18 -0700 Subject: [PATCH 15/27] 2 levels of wrapper for share computation --- circuits/bin/dkg/Nargo.toml | 1 + .../bin/dkg/share_computation/src/main.nr | 78 ++++--------------- .../share_computation_chunk_batch/Nargo.toml | 8 ++ .../share_computation_chunk_batch/src/main.nr | 71 +++++++++++++++++ circuits/lib/src/configs/insecure/dkg.nr | 6 +- circuits/lib/src/configs/secure/dkg.nr | 5 +- 6 files changed, 104 insertions(+), 65 deletions(-) create mode 100644 circuits/bin/dkg/share_computation_chunk_batch/Nargo.toml create mode 100644 circuits/bin/dkg/share_computation_chunk_batch/src/main.nr diff --git a/circuits/bin/dkg/Nargo.toml b/circuits/bin/dkg/Nargo.toml index ae0c94a464..4c492c975c 100644 --- a/circuits/bin/dkg/Nargo.toml +++ b/circuits/bin/dkg/Nargo.toml @@ -6,6 +6,7 @@ members = [ "sk_share_computation_base", "e_sm_share_computation_base", "share_computation_chunk", + "share_computation_chunk_batch", "share_computation" ] diff --git a/circuits/bin/dkg/share_computation/src/main.nr b/circuits/bin/dkg/share_computation/src/main.nr index 5699207b88..22e30a9222 100644 --- a/circuits/bin/dkg/share_computation/src/main.nr +++ b/circuits/bin/dkg/share_computation/src/main.nr @@ -1,77 +1,29 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. - -// Base and chunk proofs are produced with noir-recursive (ZK) target, so they use -// UltraHonkZKProof (508 elements). Non-ZK type would expect 457 and cause LengthMismatch. +// Level 2: final_wrapper use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; -use lib::configs::default::dkg::{ - L_THRESHOLD, N, SHARE_COMPUTATION_CHUNK_SIZE, SHARE_COMPUTATION_N_CHUNKS, -}; -use lib::configs::default::N_PARTIES; +use lib::configs::default::dkg::SHARE_COMPUTATION_N_BATCHES as N_BATCHES; use lib::math::commitments::compute_recursive_aggregation_commitment; -// 1 base proof + N_CHUNKS chunk proofs -pub global N_PROOFS: u32 = 1 + SHARE_COMPUTATION_N_CHUNKS; - -// Base proof public inputs: -// expected_secret_commitment (1) + y (N * L_THRESHOLD * (N_PARTIES+1)) + party_commitments (N_PARTIES * L_THRESHOLD) -pub global BASE_PUBLIC_INPUTS: u32 = - 1 + (N * L_THRESHOLD * (N_PARTIES + 1)) + (N_PARTIES * L_THRESHOLD); - -// Chunk proof public inputs: -// y_chunk (CHUNK_SIZE * L_THRESHOLD * (N_PARTIES+1)) -pub global CHUNK_PUBLIC_INPUTS: u32 = SHARE_COMPUTATION_CHUNK_SIZE * L_THRESHOLD * (N_PARTIES + 1); +pub global BATCH_WRAPPER_PUBLIC_INPUTS: u32 = 1; // just the aggregated commitment fn main( - base_verification_key: UltraHonkVerificationKey, - base_proof: UltraHonkZKProof, - base_public_inputs: [Field; BASE_PUBLIC_INPUTS], - base_key_hash: pub Field, - chunk_verification_key: UltraHonkVerificationKey, - chunk_proofs: [UltraHonkZKProof; SHARE_COMPUTATION_N_CHUNKS], - chunk_public_inputs: [[Field; CHUNK_PUBLIC_INPUTS]; SHARE_COMPUTATION_N_CHUNKS], - chunk_key_hash: pub Field, + batch_verification_key: UltraHonkVerificationKey, + batch_proofs: [UltraHonkZKProof; N_BATCHES], + batch_public_inputs: [[Field; BATCH_WRAPPER_PUBLIC_INPUTS]; N_BATCHES], + batch_key_hash: pub Field, ) -> pub Field { - // Step 1: Verify base proof (ZK verifier; proof is 508 elements) - verify_honk_proof( - base_verification_key, - base_proof, - base_public_inputs, - base_key_hash, - ); - - // Step 2: Verify each chunk proof and enforce y consistency with base - // y starts at index 1 in base_public_inputs (after expected_secret_commitment) - // y_chunk for chunk i starts at offset i * CHUNK_PUBLIC_INPUTS within base y - for chunk_idx in 0..SHARE_COMPUTATION_N_CHUNKS { + for i in 0..N_BATCHES { verify_honk_proof( - chunk_verification_key, - chunk_proofs[chunk_idx], - chunk_public_inputs[chunk_idx], - chunk_key_hash, + batch_verification_key, + batch_proofs[i], + batch_public_inputs[i], + batch_key_hash, ); - - // Enforce y consistency: base_public_inputs y slice == chunk y_chunk - let base_y_start = 1 + chunk_idx * CHUNK_PUBLIC_INPUTS; - for i in 0..CHUNK_PUBLIC_INPUTS { - assert( - base_public_inputs[base_y_start + i] == chunk_public_inputs[chunk_idx][i], - "y consistency check failed between base and chunk", - ); - } } - // Step 3: Aggregate all public inputs into a single commitment let mut aggregated_public_inputs = Vec::new(); - for i in 0..BASE_PUBLIC_INPUTS { - aggregated_public_inputs.push(base_public_inputs[i]); - } - for chunk_idx in 0..SHARE_COMPUTATION_N_CHUNKS { - for i in 0..CHUNK_PUBLIC_INPUTS { - aggregated_public_inputs.push(chunk_public_inputs[chunk_idx][i]); + for i in 0..N_BATCHES { + for j in 0..BATCH_WRAPPER_PUBLIC_INPUTS { + aggregated_public_inputs.push(batch_public_inputs[i][j]); } } diff --git a/circuits/bin/dkg/share_computation_chunk_batch/Nargo.toml b/circuits/bin/dkg/share_computation_chunk_batch/Nargo.toml new file mode 100644 index 0000000000..f9383dee1c --- /dev/null +++ b/circuits/bin/dkg/share_computation_chunk_batch/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "share_computation_chunk_batch" +type = "bin" +authors = [""] + +[dependencies] +lib = { path = "../../../lib" } +bb_proof_verification = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v3.0.0-nightly.20260102", directory = "barretenberg/noir/bb_proof_verification" } \ No newline at end of file diff --git a/circuits/bin/dkg/share_computation_chunk_batch/src/main.nr b/circuits/bin/dkg/share_computation_chunk_batch/src/main.nr new file mode 100644 index 0000000000..21a90ad879 --- /dev/null +++ b/circuits/bin/dkg/share_computation_chunk_batch/src/main.nr @@ -0,0 +1,71 @@ +// Level 1: chunk_batch_wrapper + +use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; +use lib::configs::default::dkg::{ + L_THRESHOLD, N, SHARE_COMPUTATION_CHUNK_SIZE, + SHARE_COMPUTATION_CHUNKS_PER_BATCH as CHUNKS_PER_BATCH, +}; +use lib::configs::default::N_PARTIES; +use lib::math::commitments::compute_recursive_aggregation_commitment; + +pub global BASE_PUBLIC_INPUTS: u32 = + 1 + (N * L_THRESHOLD * (N_PARTIES + 1)) + (N_PARTIES * L_THRESHOLD); + +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 + +fn main( + base_verification_key: UltraHonkVerificationKey, + base_proof: UltraHonkZKProof, + base_public_inputs: [Field; BASE_PUBLIC_INPUTS], + base_key_hash: pub Field, + chunk_verification_key: UltraHonkVerificationKey, + chunk_proofs: [UltraHonkZKProof; CHUNKS_PER_BATCH], + chunk_public_inputs: [[Field; CHUNK_PUBLIC_INPUTS]; CHUNKS_PER_BATCH], + chunk_key_hash: pub Field, + batch_idx: pub u32, // which batch this is +) -> pub Field { + // Verify base proof + verify_honk_proof( + base_verification_key, + base_proof, + base_public_inputs, + base_key_hash, + ); + + // Verify each chunk in this batch and enforce y consistency + for i in 0..CHUNKS_PER_BATCH { + verify_honk_proof( + chunk_verification_key, + chunk_proofs[i], + chunk_public_inputs[i], + chunk_key_hash, + ); + + let chunk_idx = batch_idx * CHUNKS_PER_BATCH + i; + let base_y_start = 1 + chunk_idx * CHUNK_PUBLIC_INPUTS; + for j in 0..CHUNK_PUBLIC_INPUTS { + assert( + base_public_inputs[base_y_start + j] == chunk_public_inputs[i][j], + "y consistency check failed", + ); + } + } + + // Aggregate public inputs + let mut aggregated_public_inputs = Vec::new(); + for i in 0..BASE_PUBLIC_INPUTS { + aggregated_public_inputs.push(base_public_inputs[i]); + } + for i in 0..CHUNKS_PER_BATCH { + for j in 0..CHUNK_PUBLIC_INPUTS { + aggregated_public_inputs.push(chunk_public_inputs[i][j]); + } + } + + compute_recursive_aggregation_commitment(aggregated_public_inputs) +} diff --git a/circuits/lib/src/configs/insecure/dkg.nr b/circuits/lib/src/configs/insecure/dkg.nr index 3379f65589..d399ddf22e 100644 --- a/circuits/lib/src/configs/insecure/dkg.nr +++ b/circuits/lib/src/configs/insecure/dkg.nr @@ -55,7 +55,11 @@ share_computation_chunk (CIRCUIT 2c) ************************************/ pub global SHARE_COMPUTATION_CHUNK_SIZE: u32 = 512; -pub global SHARE_COMPUTATION_N_CHUNKS: u32 = 1; +pub global SHARE_COMPUTATION_N_CHUNKS: u32 = N / SHARE_COMPUTATION_CHUNK_SIZE; // 512/512 = 1 + +pub global SHARE_COMPUTATION_CHUNKS_PER_BATCH: u32 = 1; +pub global SHARE_COMPUTATION_N_BATCHES: u32 = + SHARE_COMPUTATION_N_CHUNKS / SHARE_COMPUTATION_CHUNKS_PER_BATCH; pub global SHARE_COMPUTATION_CHUNK_CONFIGS: ShareComputationChunkConfigs = ShareComputationChunkConfigs::new(QIS_THRESHOLD); diff --git a/circuits/lib/src/configs/secure/dkg.nr b/circuits/lib/src/configs/secure/dkg.nr index 2725231c2d..af58d738f6 100644 --- a/circuits/lib/src/configs/secure/dkg.nr +++ b/circuits/lib/src/configs/secure/dkg.nr @@ -58,7 +58,10 @@ share_computation_chunk (CIRCUIT 2c) ************************************/ pub global SHARE_COMPUTATION_CHUNK_SIZE: u32 = 512; -pub global SHARE_COMPUTATION_N_CHUNKS: u32 = 16; +pub global SHARE_COMPUTATION_N_CHUNKS: u32 = N / SHARE_COMPUTATION_CHUNK_SIZE; // 8192/512 = 16 +pub global SHARE_COMPUTATION_CHUNKS_PER_BATCH: u32 = 4; +pub global SHARE_COMPUTATION_N_BATCHES: u32 = + SHARE_COMPUTATION_N_CHUNKS / SHARE_COMPUTATION_CHUNKS_PER_BATCH; pub global SHARE_COMPUTATION_CHUNK_CONFIGS: ShareComputationChunkConfigs = ShareComputationChunkConfigs::new(QIS_THRESHOLD); From 6057163335747a7af0ab6cc20d7fc70f34b3923e Mon Sep 17 00:00:00 2001 From: Zara Date: Wed, 11 Mar 2026 16:03:55 -0700 Subject: [PATCH 16/27] fixed the level 2 wrapper public inputes to include party share commitments --- circuits/bin/dkg/share_computation/src/main.nr | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/circuits/bin/dkg/share_computation/src/main.nr b/circuits/bin/dkg/share_computation/src/main.nr index 22e30a9222..64533d5e8d 100644 --- a/circuits/bin/dkg/share_computation/src/main.nr +++ b/circuits/bin/dkg/share_computation/src/main.nr @@ -1,9 +1,10 @@ // Level 2: final_wrapper use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; -use lib::configs::default::dkg::SHARE_COMPUTATION_N_BATCHES as N_BATCHES; +use lib::configs::default::dkg::{L_THRESHOLD, SHARE_COMPUTATION_N_BATCHES as N_BATCHES}; +use lib::configs::default::N_PARTIES; use lib::math::commitments::compute_recursive_aggregation_commitment; - -pub global BATCH_WRAPPER_PUBLIC_INPUTS: u32 = 1; // just the aggregated commitment +pub global PARTY_COMMITMENTS_SIZE: u32 = N_PARTIES * L_THRESHOLD; +pub global BATCH_WRAPPER_PUBLIC_INPUTS: u32 = PARTY_COMMITMENTS_SIZE + 1; // party_commitments + aggregated commitment fn main( batch_verification_key: UltraHonkVerificationKey, From c36954c32b6bfcbb2fbe8778f23d1ec558f805fb Mon Sep 17 00:00:00 2001 From: 0xjei Date: Thu, 12 Mar 2026 11:45:40 +0100 Subject: [PATCH 17/27] update zk-helper script, configs and add licenses --- .../bin/dkg/share_computation/src/main.nr | 6 +++ .../share_computation_chunk_batch/src/main.nr | 6 +++ circuits/lib/src/configs/insecure/dkg.nr | 2 +- circuits/lib/src/configs/secure/dkg.nr | 3 +- .../circuits/dkg/share_computation/codegen.rs | 40 +++++++++++++------ .../dkg/share_computation/computation.rs | 31 ++++++++++++-- 6 files changed, 70 insertions(+), 18 deletions(-) diff --git a/circuits/bin/dkg/share_computation/src/main.nr b/circuits/bin/dkg/share_computation/src/main.nr index 64533d5e8d..fe04765b3a 100644 --- a/circuits/bin/dkg/share_computation/src/main.nr +++ b/circuits/bin/dkg/share_computation/src/main.nr @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + // Level 2: final_wrapper use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; use lib::configs::default::dkg::{L_THRESHOLD, SHARE_COMPUTATION_N_BATCHES as N_BATCHES}; 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 21a90ad879..6cc2a23bd3 100644 --- a/circuits/bin/dkg/share_computation_chunk_batch/src/main.nr +++ b/circuits/bin/dkg/share_computation_chunk_batch/src/main.nr @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + // Level 1: chunk_batch_wrapper use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; diff --git a/circuits/lib/src/configs/insecure/dkg.nr b/circuits/lib/src/configs/insecure/dkg.nr index d399ddf22e..101d10a886 100644 --- a/circuits/lib/src/configs/insecure/dkg.nr +++ b/circuits/lib/src/configs/insecure/dkg.nr @@ -55,7 +55,7 @@ share_computation_chunk (CIRCUIT 2c) ************************************/ pub global SHARE_COMPUTATION_CHUNK_SIZE: u32 = 512; -pub global SHARE_COMPUTATION_N_CHUNKS: u32 = N / SHARE_COMPUTATION_CHUNK_SIZE; // 512/512 = 1 +pub global SHARE_COMPUTATION_N_CHUNKS: u32 = N / SHARE_COMPUTATION_CHUNK_SIZE; pub global SHARE_COMPUTATION_CHUNKS_PER_BATCH: u32 = 1; pub global SHARE_COMPUTATION_N_BATCHES: u32 = diff --git a/circuits/lib/src/configs/secure/dkg.nr b/circuits/lib/src/configs/secure/dkg.nr index af58d738f6..bf58ebfbcb 100644 --- a/circuits/lib/src/configs/secure/dkg.nr +++ b/circuits/lib/src/configs/secure/dkg.nr @@ -58,7 +58,8 @@ share_computation_chunk (CIRCUIT 2c) ************************************/ pub global SHARE_COMPUTATION_CHUNK_SIZE: u32 = 512; -pub global SHARE_COMPUTATION_N_CHUNKS: u32 = N / SHARE_COMPUTATION_CHUNK_SIZE; // 8192/512 = 16 +pub global SHARE_COMPUTATION_N_CHUNKS: u32 = N / SHARE_COMPUTATION_CHUNK_SIZE; + pub global SHARE_COMPUTATION_CHUNKS_PER_BATCH: u32 = 4; pub global SHARE_COMPUTATION_N_BATCHES: u32 = SHARE_COMPUTATION_N_CHUNKS / SHARE_COMPUTATION_CHUNKS_PER_BATCH; diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs index 37e9ba34be..b589ae510a 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -59,14 +59,15 @@ impl CircuitCodegen for ShareComputationChunkCircuit { Ok(Artifacts { toml: generate_chunk_toml(&inputs)?, - configs: generate_configs( - preset, - &bits, - data.share_data.n_parties as usize, - data.share_data.threshold as usize, - configs.chunk_size, - configs.n_chunks, - )?, + configs: generate_configs( + preset, + &bits, + data.share_data.n_parties as usize, + data.share_data.threshold as usize, + configs.chunk_size, + configs.chunks_per_batch, + configs.n_batches, + )?, }) } } @@ -86,7 +87,8 @@ fn build_base_artifacts( data.n_parties as usize, data.threshold as usize, configs.chunk_size, - configs.n_chunks, + configs.chunks_per_batch, + configs.n_batches, )?, }) } @@ -108,7 +110,8 @@ pub fn generate_configs( n_parties: usize, threshold: usize, chunk_size: usize, - n_chunks: usize, + chunks_per_batch: usize, + _n_batches: usize, ) -> Result { let (threshold_params, _) = build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; @@ -148,7 +151,11 @@ share_computation_chunk (CIRCUIT 2c) ************************************/ pub global {prefix}_CHUNK_SIZE: u32 = {chunk_size}; -pub global {prefix}_N_CHUNKS: u32 = {n_chunks}; +pub global {prefix}_N_CHUNKS: u32 = N / {prefix}_CHUNK_SIZE; + +pub global {prefix}_CHUNKS_PER_BATCH: u32 = {chunks_per_batch}; +pub global {prefix}_N_BATCHES: u32 = + {prefix}_N_CHUNKS / {prefix}_CHUNKS_PER_BATCH; pub global {prefix}_CHUNK_CONFIGS: ShareComputationChunkConfigs = ShareComputationChunkConfigs::new(QIS_THRESHOLD); @@ -161,7 +168,7 @@ pub global {prefix}_CHUNK_CONFIGS: ShareComputationChunkConfigs = bit_sk_secret = bits.bit_sk_secret, bit_e_sm_secret = bits.bit_e_sm_secret, chunk_size = chunk_size, - n_chunks = n_chunks, + chunks_per_batch = chunks_per_batch, )) } @@ -237,6 +244,15 @@ mod tests { assert!(configs_content .contains(format!("{}_SK_BIT_SECRET: u32 = {}", prefix, bits.bit_sk_secret).as_str())); assert!(configs_content.contains(format!("{}_CHUNK_SIZE: u32 = {}", prefix, 512).as_str())); + assert!(configs_content.contains( + format!("{}_N_CHUNKS: u32 = N / {}_CHUNK_SIZE", prefix, prefix).as_str() + )); + assert!(configs_content.contains( + format!("{}_CHUNKS_PER_BATCH: u32 = 1", prefix).as_str() + )); + assert!(configs_content.contains( + format!("{}_N_BATCHES: u32 =", prefix).as_str() + )); assert!(configs_content.contains( format!("{}_E_SM_BIT_SECRET: u32 = {}", prefix, bits.bit_e_sm_secret).as_str() )); diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs index 03b8d96565..af2bdf3845 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -22,13 +22,13 @@ use crate::CircuitsErrors; use crate::{calculate_bit_width, crt_polynomial_to_toml_json, poly_coefficients_to_toml_json}; use crate::{CircuitComputation, Computation}; use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvParamSet; use e3_fhe_params::BfvPreset; use e3_polynomial::{reduce, CrtPolynomial}; use fhe::bfv::SecretKey; use fhe::trbfv::{SmudgingBoundCalculator, SmudgingBoundCalculatorConfig}; use num_bigint::{BigInt, BigUint}; use serde::{Deserialize, Serialize}; - // todo: understand where to put this! const SHARE_COMPUTATION_CHUNK_SIZE: usize = 512; @@ -67,6 +67,8 @@ pub struct Configs { pub moduli: Vec, pub chunk_size: usize, pub n_chunks: usize, + pub chunks_per_batch: usize, + pub n_batches: usize, pub bits: Bits, pub bounds: Bounds, } @@ -128,12 +130,20 @@ impl Computation for Configs { let bounds = Bounds::compute(preset, data)?; let bits = Bits::compute(preset, &bounds)?; + let degree = threshold_params.degree(); + let chunk_size = compute_chunk_size(); + let n_chunks = compute_n_chunks(degree, chunk_size); + let chunks_per_batch = compute_chunks_per_batch(degree); + let n_batches = compute_n_batches(n_chunks, chunks_per_batch); + Ok(Configs { - n: threshold_params.degree(), + n: degree, l, moduli, - chunk_size: compute_chunk_size(), - n_chunks: compute_n_chunks(threshold_params.degree(), compute_chunk_size()), + chunk_size, + n_chunks, + chunks_per_batch, + n_batches, bits, bounds, }) @@ -148,6 +158,19 @@ fn compute_n_chunks(degree: usize, chunk_size: usize) -> usize { degree.div_ceil(chunk_size) } +fn compute_chunks_per_batch(degree: usize) -> usize { + // Matches Noir configs: insecure (N<=512) uses 1, secure (N=8192) uses 4 + if degree == BfvParamSet::from(BfvPreset::InsecureThreshold512).degree { + 1 + } else { + 4 + } +} + +fn compute_n_batches(n_chunks: usize, chunks_per_batch: usize) -> usize { + n_chunks.div_ceil(chunks_per_batch) +} + impl Computation for Bits { type Preset = BfvPreset; type Data = Bounds; From fd95c0751b0b1e12ad5b809349e3f0f9e09aeec6 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Thu, 12 Mar 2026 13:47:59 +0100 Subject: [PATCH 18/27] expose pub inputs for c2 instead of commitments --- .../bin/dkg/share_computation/src/main.nr | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/circuits/bin/dkg/share_computation/src/main.nr b/circuits/bin/dkg/share_computation/src/main.nr index fe04765b3a..7bf6f42990 100644 --- a/circuits/bin/dkg/share_computation/src/main.nr +++ b/circuits/bin/dkg/share_computation/src/main.nr @@ -6,18 +6,19 @@ // Level 2: final_wrapper use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; -use lib::configs::default::dkg::{L_THRESHOLD, SHARE_COMPUTATION_N_BATCHES as N_BATCHES}; -use lib::configs::default::N_PARTIES; -use lib::math::commitments::compute_recursive_aggregation_commitment; -pub global PARTY_COMMITMENTS_SIZE: u32 = N_PARTIES * L_THRESHOLD; -pub global BATCH_WRAPPER_PUBLIC_INPUTS: u32 = PARTY_COMMITMENTS_SIZE + 1; // party_commitments + aggregated commitment +use lib::configs::default::dkg::SHARE_COMPUTATION_N_BATCHES as N_BATCHES; + +// Public inputs of each batch wrapper proof (as exposed by `share_computation_chunk_batch`). +// Currently: [base_key_hash, chunk_key_hash, batch_idx, aggregated_commitment]. +pub global BATCH_WRAPPER_PUBLIC_INPUTS: u32 = 4; fn main( batch_verification_key: UltraHonkVerificationKey, batch_proofs: [UltraHonkZKProof; N_BATCHES], batch_public_inputs: [[Field; BATCH_WRAPPER_PUBLIC_INPUTS]; N_BATCHES], batch_key_hash: pub Field, -) -> pub Field { +) -> pub [Field; BATCH_WRAPPER_PUBLIC_INPUTS] { + // 1. Verify all batch proofs against their public inputs and key hash. for i in 0..N_BATCHES { verify_honk_proof( batch_verification_key, @@ -27,12 +28,14 @@ fn main( ); } - let mut aggregated_public_inputs = Vec::new(); - for i in 0..N_BATCHES { - for j in 0..BATCH_WRAPPER_PUBLIC_INPUTS { - aggregated_public_inputs.push(batch_public_inputs[i][j]); - } + // 2. Expose the batch wrapper public inputs (without recomputing a commitment) + // as the C2 public interface. We take them from the first batch, since each + // batch has the same shape. + let mut c2_public_inputs: [Field; BATCH_WRAPPER_PUBLIC_INPUTS] = + [0 as Field; BATCH_WRAPPER_PUBLIC_INPUTS]; + for i in 0..BATCH_WRAPPER_PUBLIC_INPUTS { + c2_public_inputs[i] = batch_public_inputs[0][i]; } - compute_recursive_aggregation_commitment(aggregated_public_inputs) + c2_public_inputs } From e9ee6da492b810a3d698e38c7db966de8a523258 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Thu, 12 Mar 2026 13:48:38 +0100 Subject: [PATCH 19/27] update configs --- .../circuits/dkg/share_computation/codegen.rs | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs index b589ae510a..5605e4589d 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -59,15 +59,15 @@ impl CircuitCodegen for ShareComputationChunkCircuit { Ok(Artifacts { toml: generate_chunk_toml(&inputs)?, - configs: generate_configs( - preset, - &bits, - data.share_data.n_parties as usize, - data.share_data.threshold as usize, - configs.chunk_size, - configs.chunks_per_batch, - configs.n_batches, - )?, + configs: generate_configs( + preset, + &bits, + data.share_data.n_parties as usize, + data.share_data.threshold as usize, + configs.chunk_size, + configs.chunks_per_batch, + configs.n_batches, + )?, }) } } @@ -244,15 +244,10 @@ mod tests { assert!(configs_content .contains(format!("{}_SK_BIT_SECRET: u32 = {}", prefix, bits.bit_sk_secret).as_str())); assert!(configs_content.contains(format!("{}_CHUNK_SIZE: u32 = {}", prefix, 512).as_str())); - assert!(configs_content.contains( - format!("{}_N_CHUNKS: u32 = N / {}_CHUNK_SIZE", prefix, prefix).as_str() - )); - assert!(configs_content.contains( - format!("{}_CHUNKS_PER_BATCH: u32 = 1", prefix).as_str() - )); - assert!(configs_content.contains( - format!("{}_N_BATCHES: u32 =", prefix).as_str() - )); + assert!(configs_content + .contains(format!("{}_N_CHUNKS: u32 = N / {}_CHUNK_SIZE", prefix, prefix).as_str())); + assert!(configs_content.contains(format!("{}_CHUNKS_PER_BATCH: u32 = 1", prefix).as_str())); + assert!(configs_content.contains(format!("{}_N_BATCHES: u32 =", prefix).as_str())); assert!(configs_content.contains( format!("{}_E_SM_BIT_SECRET: u32 = {}", prefix, bits.bit_e_sm_secret).as_str() )); From 02f98cb55b512cfba2f5c0767943f16f8a057cfe Mon Sep 17 00:00:00 2001 From: 0xjei Date: Thu, 12 Mar 2026 13:50:26 +0100 Subject: [PATCH 20/27] integrate inside zk-prover --- crates/events/src/enclave_event/proof.rs | 6 +- crates/multithread/src/multithread.rs | 63 ++++---- crates/zk-prover/src/circuits/dkg/mod.rs | 2 +- .../src/circuits/dkg/share_computation.rs | 153 +++++++++++++++--- crates/zk-prover/src/circuits/mod.rs | 3 +- .../src/circuits/recursive_aggregation/mod.rs | 96 +---------- .../circuits/recursive_aggregation/utils.rs | 22 --- crates/zk-prover/src/circuits/utils.rs | 44 +++++ .../{recursive_aggregation => }/vk.rs | 0 crates/zk-prover/src/lib.rs | 5 +- crates/zk-prover/tests/local_e2e_tests.rs | 126 +++++++++------ 11 files changed, 302 insertions(+), 218 deletions(-) delete mode 100644 crates/zk-prover/src/circuits/recursive_aggregation/utils.rs rename crates/zk-prover/src/circuits/{recursive_aggregation => }/vk.rs (100%) diff --git a/crates/events/src/enclave_event/proof.rs b/crates/events/src/enclave_event/proof.rs index f664733116..2c3a18ebdb 100644 --- a/crates/events/src/enclave_event/proof.rs +++ b/crates/events/src/enclave_event/proof.rs @@ -87,7 +87,9 @@ pub enum CircuitName { ESmShareComputationBase, /// Share computation chunk proof (C2c, proven N times). ShareComputationChunk, - /// Share computation inner circuit proof (C2 — binds base + N chunks). + /// Share computation chunk batch proof (C2d — binds base + CHUNKS_PER_BATCH chunks). + ShareComputationChunkBatch, + /// Share computation final wrapper proof (C2 — binds N_BATCHES batch proofs). ShareComputation, /// Share encryption proof (C3). ShareEncryption, @@ -111,6 +113,7 @@ impl CircuitName { CircuitName::SkShareComputationBase => "sk_share_computation_base", CircuitName::ESmShareComputationBase => "e_sm_share_computation_base", CircuitName::ShareComputationChunk => "share_computation_chunk", + CircuitName::ShareComputationChunkBatch => "share_computation_chunk_batch", CircuitName::ShareComputation => "share_computation", CircuitName::ShareEncryption => "share_encryption", CircuitName::DkgShareDecryption => "share_decryption", @@ -127,6 +130,7 @@ impl CircuitName { CircuitName::SkShareComputationBase => "dkg", CircuitName::ESmShareComputationBase => "dkg", CircuitName::ShareComputationChunk => "dkg", + CircuitName::ShareComputationChunkBatch => "dkg", CircuitName::ShareComputation => "dkg", CircuitName::ShareEncryption => "dkg", CircuitName::DkgShareDecryption => "dkg", diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 838fc69aad..c5dd4412ba 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -72,8 +72,8 @@ use e3_zk_helpers::threshold::pk_aggregation::PkAggregationCircuitData; use e3_zk_helpers::CiphernodesCommittee; use e3_zk_helpers::Computation; use e3_zk_prover::{ - generate_fold_proof, generate_share_computation_proof, generate_wrapper_proof, CircuitVariant, - Provable, ZkBackend, ZkProver, + generate_chunk_batch_proof, generate_fold_proof, generate_share_computation_final_proof, + generate_wrapper_proof, CircuitVariant, Provable, ZkBackend, ZkProver, }; use fhe::bfv::{Ciphertext, Encoding, Plaintext, PublicKey, SecretKey}; use fhe::mbfv::PublicKeyShare; @@ -755,13 +755,12 @@ fn handle_share_computation_proof( })?; // 8. Determine number of chunks and prove each chunk circuit - let n_chunks = Configs::compute(req.params_preset.clone(), &circuit_data) - .map_err(|e| make_zk_error(&request, format!("Configs::compute: {}", e)))? - .n_chunks; + let configs = Configs::compute(req.params_preset.clone(), &circuit_data) + .map_err(|e| make_zk_error(&request, format!("Configs::compute: {}", e)))?; let chunk_circuit = ShareComputationChunkCircuit; - let mut chunk_proofs = Vec::with_capacity(n_chunks); - for chunk_idx in 0..n_chunks { + let mut chunk_proofs = Vec::with_capacity(configs.n_chunks); + for chunk_idx in 0..configs.n_chunks { let chunk_data = ShareComputationChunkCircuitData { share_data: circuit_data.clone(), chunk_idx, @@ -782,30 +781,38 @@ fn handle_share_computation_proof( chunk_proofs.push(chunk_proof); } - // 9. Prove the share_computation circuit (binds base + N chunks) — this IS the C2 proof. - // Wrapping happens later during aggregation (both SK + ESM wrapped together). - let c2_proof = generate_share_computation_proof( - prover, - &base_proof, - &chunk_proofs, - &format!("{e3_id_str}_c2"), - ) - .map_err(|e| { - ComputeRequestError::new( - ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), - request.clone(), + // 9. Level 1: group chunks into batches and prove each batch + let mut batch_proofs = Vec::with_capacity(configs.n_batches); + for batch_idx in 0..configs.n_batches { + let start = batch_idx * configs.chunks_per_batch; + let end = start + configs.chunks_per_batch; + let batch_proof = generate_chunk_batch_proof( + prover, + &base_proof, + &chunk_proofs[start..end], + batch_idx as u32, + &format!("{e3_id_str}_batch_{batch_idx}"), ) - })?; + .map_err(|e| { + ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), + request.clone(), + ) + })?; + batch_proofs.push(batch_proof); + } - // 10. Wrap with the standard outer wrapper - let proof = generate_wrapper_proof(prover, &c2_proof, &e3_id_str).map_err(|e| { - ComputeRequestError::new( - ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), - request.clone(), - ) - })?; + // 10. Level 2: aggregate batch proofs into final C2 proof + let proof = + generate_share_computation_final_proof(prover, &batch_proofs, &format!("{e3_id_str}_c2")) + .map_err(|e| { + ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), + request.clone(), + ) + })?; - // 11. Return response + // 11. Return final C2 proof Ok(ComputeResponse::zk( ZkResponse::ShareComputation(ShareComputationProofResponse { proof, diff --git a/crates/zk-prover/src/circuits/dkg/mod.rs b/crates/zk-prover/src/circuits/dkg/mod.rs index 0a60030a28..6a7fe1818a 100644 --- a/crates/zk-prover/src/circuits/dkg/mod.rs +++ b/crates/zk-prover/src/circuits/dkg/mod.rs @@ -5,6 +5,6 @@ // or FITNESS FOR A PARTICULAR PURPOSE mod pk; -mod share_computation; +pub(crate) mod share_computation; mod share_decryption; mod share_encryption; diff --git a/crates/zk-prover/src/circuits/dkg/share_computation.rs b/crates/zk-prover/src/circuits/dkg/share_computation.rs index 7f0973be24..b3bd753665 100644 --- a/crates/zk-prover/src/circuits/dkg/share_computation.rs +++ b/crates/zk-prover/src/circuits/dkg/share_computation.rs @@ -4,10 +4,12 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE -use crate::circuits::recursive_aggregation::generate_share_computation_proof; +use crate::circuits::utils::{bytes_to_field_strings, prove_recursive_circuit}; +use crate::circuits::vk; +use crate::error::ZkError; use crate::prover::ZkProver; use crate::traits::Provable; -use e3_events::CircuitName; +use e3_events::{CircuitName, CircuitVariant, Proof}; use e3_fhe_params::BfvPreset; use e3_zk_helpers::computation::DkgInputType; use e3_zk_helpers::dkg::share_computation::{ @@ -16,7 +18,109 @@ use e3_zk_helpers::dkg::share_computation::{ }; use e3_zk_helpers::Computation; -/// Full share-computation proof (base + chunks + aggregation + wrapper). +////////////////////////////////////////////////////////////////////////////// +// Two-level wrapper proof generation +////////////////////////////////////////////////////////////////////////////// + +/// Level 1: Input for the chunk_batch circuit (base + CHUNKS_PER_BATCH chunks). +/// Field names match Noir parameter names exactly for witness generation. +#[derive(serde::Serialize)] +struct ChunkBatchInput { + base_verification_key: Vec, + base_proof: Vec, + base_public_inputs: Vec, + base_key_hash: String, + chunk_verification_key: Vec, + chunk_proofs: Vec>, + chunk_public_inputs: Vec>, + chunk_key_hash: String, + batch_idx: String, +} + +/// Level 1: Proves the share_computation_chunk_batch circuit that binds +/// 1 base proof + CHUNKS_PER_BATCH chunk proofs for a single batch. +pub fn generate_chunk_batch_proof( + prover: &ZkProver, + base_proof: &Proof, + chunk_proofs: &[Proof], + batch_idx: u32, + e3_id: &str, +) -> Result { + let recursive_dir = prover.circuits_dir(CircuitVariant::Recursive); + + let base_vk = vk::load_vk_artifacts(&recursive_dir, base_proof.circuit)?; + let chunk_vk = vk::load_vk_artifacts(&recursive_dir, CircuitName::ShareComputationChunk)?; + + let mut chunk_proof_fields = Vec::with_capacity(chunk_proofs.len()); + let mut chunk_public_inputs = Vec::with_capacity(chunk_proofs.len()); + for cp in chunk_proofs { + chunk_proof_fields.push(bytes_to_field_strings(&cp.data)?); + chunk_public_inputs.push(bytes_to_field_strings(&cp.public_signals)?); + } + + let input = ChunkBatchInput { + base_verification_key: base_vk.verification_key, + base_proof: bytes_to_field_strings(&base_proof.data)?, + base_public_inputs: bytes_to_field_strings(&base_proof.public_signals)?, + base_key_hash: base_vk.key_hash, + chunk_verification_key: chunk_vk.verification_key, + chunk_proofs: chunk_proof_fields, + chunk_public_inputs, + chunk_key_hash: chunk_vk.key_hash, + batch_idx: batch_idx.to_string(), + }; + + prove_recursive_circuit( + prover, + CircuitName::ShareComputationChunkBatch, + &input, + e3_id, + ) +} + +/// Level 2: Input for the final share_computation wrapper (N_BATCHES batch proofs). +/// Field names match Noir parameter names exactly for witness generation. +#[derive(serde::Serialize)] +struct ShareComputationFinalInput { + batch_verification_key: Vec, + batch_proofs: Vec>, + batch_public_inputs: Vec>, + batch_key_hash: String, +} + +/// Level 2: Proves the final share_computation circuit that aggregates N_BATCHES +/// batch wrapper proofs into a single C2 proof. +pub fn generate_share_computation_final_proof( + prover: &ZkProver, + batch_proofs: &[Proof], + e3_id: &str, +) -> Result { + let recursive_dir = prover.circuits_dir(CircuitVariant::Recursive); + + let batch_vk = vk::load_vk_artifacts(&recursive_dir, CircuitName::ShareComputationChunkBatch)?; + + let mut batch_proof_fields = Vec::with_capacity(batch_proofs.len()); + let mut batch_public_inputs = Vec::with_capacity(batch_proofs.len()); + for bp in batch_proofs { + batch_proof_fields.push(bytes_to_field_strings(&bp.data)?); + batch_public_inputs.push(bytes_to_field_strings(&bp.public_signals)?); + } + + let input = ShareComputationFinalInput { + batch_verification_key: batch_vk.verification_key, + batch_proofs: batch_proof_fields, + batch_public_inputs, + batch_key_hash: batch_vk.key_hash, + }; + + prove_recursive_circuit(prover, CircuitName::ShareComputation, &input, e3_id) +} + +////////////////////////////////////////////////////////////////////////////// +// Provable impls +////////////////////////////////////////////////////////////////////////////// + +/// Full share-computation proof (base + chunks + two-level wrapper). /// Used by local e2e tests; the enclave uses the same pipeline via multithread handler. impl Provable for ShareComputationCircuit { type Params = BfvPreset; @@ -37,17 +141,16 @@ impl Provable for ShareComputationCircuit { params: &Self::Params, input: &Self::Input, e3_id: &str, - ) -> Result { + ) -> Result { let base_circuit = ShareComputationBaseCircuit; let base_proof = base_circuit.prove(prover, params, input, &format!("{e3_id}_base"))?; - let n_chunks = Configs::compute(params.clone(), input) - .map_err(|e| crate::error::ZkError::InputsGenerationFailed(e.to_string()))? - .n_chunks; + let configs = Configs::compute(params.clone(), input) + .map_err(|e| ZkError::InputsGenerationFailed(e.to_string()))?; let chunk_circuit = ShareComputationChunkCircuit; - let mut chunk_proofs = Vec::with_capacity(n_chunks); - for chunk_idx in 0..n_chunks { + let mut chunk_proofs = Vec::with_capacity(configs.n_chunks); + for chunk_idx in 0..configs.n_chunks { let chunk_data = ShareComputationChunkCircuitData { share_data: input.clone(), chunk_idx, @@ -61,26 +164,34 @@ impl Provable for ShareComputationCircuit { chunk_proofs.push(chunk_proof); } - let c2_proof = generate_share_computation_proof(prover, &base_proof, &chunk_proofs, e3_id)?; - // Return the inner share_computation proof (no wrapper). Tests verify with Recursive variant. - Ok(c2_proof) + // Level 1: group chunks into batches and prove each batch + let mut batch_proofs = Vec::with_capacity(configs.n_batches); + for batch_idx in 0..configs.n_batches { + let start = batch_idx * configs.chunks_per_batch; + let end = start + configs.chunks_per_batch; + let batch_proof = generate_chunk_batch_proof( + prover, + &base_proof, + &chunk_proofs[start..end], + batch_idx as u32, + &format!("{e3_id}_batch_{batch_idx}"), + )?; + batch_proofs.push(batch_proof); + } + + // Level 2: aggregate batch proofs into final C2 proof + generate_share_computation_final_proof(prover, &batch_proofs, e3_id) } /// Inner share_computation proof is verified with Recursive variant. fn verify( &self, prover: &ZkProver, - proof: &e3_events::Proof, + proof: &Proof, e3_id: &str, party_id: u64, - ) -> Result { - self.verify_with_variant( - prover, - proof, - e3_id, - party_id, - e3_events::CircuitVariant::Recursive, - ) + ) -> Result { + self.verify_with_variant(prover, proof, e3_id, party_id, CircuitVariant::Recursive) } } diff --git a/crates/zk-prover/src/circuits/mod.rs b/crates/zk-prover/src/circuits/mod.rs index 8c107cb660..a8878a7166 100644 --- a/crates/zk-prover/src/circuits/mod.rs +++ b/crates/zk-prover/src/circuits/mod.rs @@ -4,7 +4,8 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -mod dkg; +pub(crate) mod dkg; pub mod recursive_aggregation; mod threshold; pub(crate) mod utils; +pub(crate) mod vk; diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs index c341477f7d..4d8a1eb59b 100644 --- a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs +++ b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs @@ -4,103 +4,19 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Proof aggregation for recursive circuits. +//! Generic proof aggregation for recursive circuits. //! -//! Aggregates proofs by executing a wrapper circuit (e.g. dkg pk) that verifies -//! inner proofs and produces a non-ZK aggregated proof. +//! Contains the wrapper circuit (verifies 1-2 inner proofs) and the fold circuit +//! (folds two wrapper proofs). Share-computation-specific logic lives in +//! `circuits::dkg::share_computation`. -mod utils; -mod vk; - -use crate::circuits::utils::inputs_json_to_input_map; +use crate::circuits::utils::{bytes_to_field_strings, inputs_json_to_input_map}; +use crate::circuits::vk; use crate::error::ZkError; use crate::prover::ZkProver; use crate::witness::{CompiledCircuit, WitnessGenerator}; use e3_events::{CircuitName, CircuitVariant, Proof}; -use self::utils::bytes_to_field_strings; - -////////////////////////////////////////////////////////////////////////////// -// Share Computation inner circuit proof -////////////////////////////////////////////////////////////////////////////// - -/// Input for the share_computation circuit that binds 1 base proof + N chunk proofs. -/// Field names match Noir parameter names exactly for witness generation. -#[derive(serde::Serialize)] -struct ShareComputationInput { - base_verification_key: Vec, - base_proof: Vec, - base_public_inputs: Vec, - base_key_hash: String, - chunk_verification_key: Vec, - chunk_proofs: Vec>, - chunk_public_inputs: Vec>, - chunk_key_hash: String, -} - -/// Proves the share_computation circuit that binds 1 base proof + N chunk proofs. -/// -/// This IS the C2a/C2b proof — the successor to the old monolithic SkShareComputation / -/// ESmShareComputation circuits. The returned proof (tagged `ShareComputation`) is then -/// passed to [`generate_wrapper_proof`] exactly like the old circuit proofs were. -/// -/// # Arguments -/// * `prover` - ZK prover with bb and circuits configured -/// * `base_proof` - The proved base circuit (SkShareComputationBase or ESmShareComputationBase) -/// * `chunk_proofs` - The N proved chunk circuits (ShareComputationChunk) -/// * `e3_id` - Job identifier for work dir -pub fn generate_share_computation_proof( - prover: &ZkProver, - base_proof: &Proof, - chunk_proofs: &[Proof], - e3_id: &str, -) -> Result { - let recursive_dir = prover.circuits_dir(CircuitVariant::Recursive); - - let base_vk = vk::load_vk_artifacts(&recursive_dir, base_proof.circuit)?; - let chunk_vk = vk::load_vk_artifacts(&recursive_dir, CircuitName::ShareComputationChunk)?; - - let base_proof_fields = bytes_to_field_strings(&base_proof.data)?; - let base_public_inputs = bytes_to_field_strings(&base_proof.public_signals)?; - - let mut chunk_proof_fields = Vec::with_capacity(chunk_proofs.len()); - let mut chunk_public_inputs = Vec::with_capacity(chunk_proofs.len()); - for cp in chunk_proofs { - chunk_proof_fields.push(bytes_to_field_strings(&cp.data)?); - chunk_public_inputs.push(bytes_to_field_strings(&cp.public_signals)?); - } - - let full_input = ShareComputationInput { - base_verification_key: base_vk.verification_key, - base_proof: base_proof_fields, - base_public_inputs, - base_key_hash: base_vk.key_hash, - chunk_verification_key: chunk_vk.verification_key, - chunk_proofs: chunk_proof_fields, - chunk_public_inputs, - chunk_key_hash: chunk_vk.key_hash, - }; - - let circuit_path = recursive_dir - .join(CircuitName::ShareComputation.dir_path()) - .join(format!("{}.json", CircuitName::ShareComputation.as_str())); - let compiled = CompiledCircuit::from_file(&circuit_path)?; - - let json = serde_json::to_value(&full_input) - .map_err(|e| ZkError::SerializationError(e.to_string()))?; - let input_map = inputs_json_to_input_map(&json)?; - - let witness_gen = WitnessGenerator::new(); - let witness = witness_gen.generate_witness(&compiled, input_map)?; - - prover.generate_proof_with_variant( - CircuitName::ShareComputation, - &witness, - e3_id, - CircuitVariant::Recursive, - ) -} - /// Full input for the recursive wrapper circuit. struct WrapperInput { verification_key: Vec, diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/utils.rs b/crates/zk-prover/src/circuits/recursive_aggregation/utils.rs deleted file mode 100644 index e1f24a9666..0000000000 --- a/crates/zk-prover/src/circuits/recursive_aggregation/utils.rs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. - -use crate::error::ZkError; - -const FIELD_SIZE: usize = 32; - -pub fn bytes_to_field_strings(bytes: &[u8]) -> Result, ZkError> { - if bytes.len() % FIELD_SIZE != 0 { - return Err(ZkError::InvalidInput(format!( - "expected length multiple of {FIELD_SIZE}, got {}", - bytes.len() - ))); - } - Ok(bytes - .chunks(FIELD_SIZE) - .map(|chunk| format!("0x{}", hex::encode(chunk))) - .collect()) -} diff --git a/crates/zk-prover/src/circuits/utils.rs b/crates/zk-prover/src/circuits/utils.rs index c128dd774f..f970445bc3 100644 --- a/crates/zk-prover/src/circuits/utils.rs +++ b/crates/zk-prover/src/circuits/utils.rs @@ -7,9 +7,53 @@ use std::collections::BTreeMap; use crate::error::ZkError; +use crate::prover::ZkProver; +use crate::witness::{CompiledCircuit, WitnessGenerator}; use acir::FieldElement; +use e3_events::{CircuitName, CircuitVariant, Proof}; use noirc_abi::{input_parser::InputValue, InputMap}; +const FIELD_SIZE: usize = 32; + +/// Converts raw proof/public-signal bytes (32-byte big-endian chunks) to hex-encoded field strings. +pub fn bytes_to_field_strings(bytes: &[u8]) -> Result, ZkError> { + if bytes.len() % FIELD_SIZE != 0 { + return Err(ZkError::InvalidInput(format!( + "expected length multiple of {FIELD_SIZE}, got {}", + bytes.len() + ))); + } + Ok(bytes + .chunks(FIELD_SIZE) + .map(|chunk| format!("0x{}", hex::encode(chunk))) + .collect()) +} + +/// Proves a circuit given a serializable input struct. +/// +/// Handles the common pattern: serialize → load compiled circuit → generate witness → prove. +pub fn prove_recursive_circuit( + prover: &ZkProver, + circuit_name: CircuitName, + input: &impl serde::Serialize, + e3_id: &str, +) -> Result { + let recursive_dir = prover.circuits_dir(CircuitVariant::Recursive); + let circuit_path = recursive_dir + .join(circuit_name.dir_path()) + .join(format!("{}.json", circuit_name.as_str())); + let compiled = CompiledCircuit::from_file(&circuit_path)?; + + let json = + serde_json::to_value(input).map_err(|e| ZkError::SerializationError(e.to_string()))?; + let input_map = inputs_json_to_input_map(&json)?; + + let witness_gen = WitnessGenerator::new(); + let witness = witness_gen.generate_witness(&compiled, input_map)?; + + prover.generate_proof_with_variant(circuit_name, &witness, e3_id, CircuitVariant::Recursive) +} + /// Converts inputs JSON (from `Inputs::to_json()`) to `InputMap` for Noir ABI. /// Expects the same structure: CRT fields as arrays of `{coefficients: [...]}`, /// polynomial fields as `{coefficients: [...]}`. diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs b/crates/zk-prover/src/circuits/vk.rs similarity index 100% rename from crates/zk-prover/src/circuits/recursive_aggregation/vk.rs rename to crates/zk-prover/src/circuits/vk.rs diff --git a/crates/zk-prover/src/lib.rs b/crates/zk-prover/src/lib.rs index a32c7efa72..17696fb1b8 100644 --- a/crates/zk-prover/src/lib.rs +++ b/crates/zk-prover/src/lib.rs @@ -21,9 +21,10 @@ pub use actors::{ }; pub use backend::{SetupStatus, ZkBackend}; -pub use circuits::recursive_aggregation::{ - generate_fold_proof, generate_share_computation_proof, generate_wrapper_proof, +pub use circuits::dkg::share_computation::{ + generate_chunk_batch_proof, generate_share_computation_final_proof, }; +pub use circuits::recursive_aggregation::{generate_fold_proof, generate_wrapper_proof}; pub use config::{verify_checksum, BbTarget, CircuitInfo, VersionInfo, ZkConfig}; pub use e3_events::CircuitVariant; pub use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuit; diff --git a/crates/zk-prover/tests/local_e2e_tests.rs b/crates/zk-prover/tests/local_e2e_tests.rs index c1c7b290fb..5111a87e14 100644 --- a/crates/zk-prover/tests/local_e2e_tests.rs +++ b/crates/zk-prover/tests/local_e2e_tests.rs @@ -16,17 +16,14 @@ mod common; use ark_bn254::Fr; -use ark_ff::PrimeField; +use ark_ff::{PrimeField, Zero}; use common::{ extract_field, extract_field_from_end, find_bb, setup_compiled_circuit, setup_test_prover, }; use e3_fhe_params::BfvPreset; use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuit; use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuitData; -use e3_zk_helpers::circuits::{ - commitments::{compute_dkg_pk_commitment, compute_recursive_aggregation_commitment}, - CircuitComputation, -}; +use e3_zk_helpers::circuits::{commitments::compute_dkg_pk_commitment, CircuitComputation}; use e3_zk_helpers::computation::DkgInputType; use e3_zk_helpers::dkg::share_computation::{ Configs, ShareComputationBaseCircuit, ShareComputationChunkCircuit, @@ -47,7 +44,10 @@ use e3_zk_helpers::threshold::{ use e3_zk_helpers::CiphernodesCommitteeSize; use e3_zk_helpers::Computation; use e3_zk_helpers::{compute_share_computation_sk_commitment, compute_threshold_pk_commitment}; -use e3_zk_prover::{generate_share_computation_proof, Provable, ZkBackend, ZkProver}; +use e3_zk_prover::{ + generate_chunk_batch_proof, generate_share_computation_final_proof, Provable, ZkBackend, + ZkProver, +}; /// Convert raw public signals bytes (32-byte big-endian chunks) to ark_bn254::Fr field elements. fn public_signals_to_fields(signals: &[u8]) -> Vec { @@ -151,10 +151,11 @@ async fn setup_share_computation_sk_base_chunk_test() -> Option<( let bb = find_bb().await?; let (backend, temp) = setup_test_prover(&bb).await; - // Inner share_computation only: base, chunk, share_computation aggregator (no wrapper) + // Two-level wrapper: base, chunk, chunk_batch (level 1), share_computation (level 2) setup_compiled_circuit(&backend, "dkg", "sk_share_computation_base").await; setup_compiled_circuit(&backend, "dkg", "e_sm_share_computation_base").await; setup_compiled_circuit(&backend, "dkg", "share_computation_chunk").await; + setup_compiled_circuit(&backend, "dkg", "share_computation_chunk_batch").await; setup_compiled_circuit(&backend, "dkg", "share_computation").await; let sample = @@ -187,10 +188,11 @@ async fn setup_share_computation_e_sm_base_chunk_test() -> Option<( let bb = find_bb().await?; let (backend, temp) = setup_test_prover(&bb).await; - // Inner share_computation only: base, chunk, share_computation aggregator (no wrapper) + // Two-level wrapper: base, chunk, chunk_batch (level 1), share_computation (level 2) setup_compiled_circuit(&backend, "dkg", "sk_share_computation_base").await; setup_compiled_circuit(&backend, "dkg", "e_sm_share_computation_base").await; setup_compiled_circuit(&backend, "dkg", "share_computation_chunk").await; + setup_compiled_circuit(&backend, "dkg", "share_computation_chunk_batch").await; setup_compiled_circuit(&backend, "dkg", "share_computation").await; let sample = ShareComputationCircuitData::generate_sample( @@ -497,10 +499,10 @@ async fn test_share_computation_sk_commitment_consistency() { .prove(&prover, &preset, &sample, &format!("{e3_id}_base")) .expect("base proof should succeed"); - let n_chunks = Configs::compute(preset, &sample).expect("configs").n_chunks; + let configs = Configs::compute(preset, &sample).expect("configs"); - let mut chunk_proofs = Vec::with_capacity(n_chunks); - for chunk_idx in 0..n_chunks { + let mut chunk_proofs = Vec::with_capacity(configs.n_chunks); + for chunk_idx in 0..configs.n_chunks { let chunk_data = ShareComputationChunkCircuitData { share_data: sample.clone(), chunk_idx, @@ -516,30 +518,40 @@ async fn test_share_computation_sk_commitment_consistency() { chunk_proofs.push(chunk_proof); } - let proof = generate_share_computation_proof(&prover, &base_proof, &chunk_proofs, e3_id) - .expect("share_computation proof should succeed"); + // Level 1: group chunks into batches and prove each batch + let mut batch_proofs = Vec::with_capacity(configs.n_batches); + for batch_idx in 0..configs.n_batches { + let start = batch_idx * configs.chunks_per_batch; + let end = start + configs.chunks_per_batch; + let batch_proof = generate_chunk_batch_proof( + &prover, + &base_proof, + &chunk_proofs[start..end], + batch_idx as u32, + &format!("{e3_id}_batch_{batch_idx}"), + ) + .expect("chunk batch proof should succeed"); + batch_proofs.push(batch_proof); + } + + // Level 2: aggregate batch proofs into final C2 proof + let proof = generate_share_computation_final_proof(&prover, &batch_proofs, e3_id) + .expect("final share_computation proof should succeed"); - // The share_computation inner circuit has 3 public outputs (each 32 bytes): - // [0] base_key_hash - // [1] chunk_key_hash - // [2] aggregated commitment = compute_recursive_aggregation_commitment(base_pub ++ chunk_pubs) + // Final wrapper now exposes: + // [0] batch_key_hash (pub param) + // [1..=4] batch wrapper public inputs (length 4 fields). assert_eq!( proof.public_signals.len(), - 3 * 32, - "share_computation inner circuit should have exactly 3 public outputs (96 bytes)" + 5 * 32, + "final share_computation wrapper should expose 5 field public inputs (160 bytes)" ); - // Reconstruct the aggregated payload from intermediate proofs' public signals - let mut payload: Vec = public_signals_to_fields(&base_proof.public_signals); - for cp in &chunk_proofs { - payload.extend(public_signals_to_fields(&cp.public_signals)); - } - let expected_commitment = compute_recursive_aggregation_commitment(payload); - - let commitment_from_proof = extract_field(&proof.public_signals, 2); - assert_eq!( - commitment_from_proof, expected_commitment, - "Aggregated commitment from proof must match independently calculated commitment" + // Sanity check: at least one public input is non-zero. + let fields = public_signals_to_fields(&proof.public_signals); + assert!( + fields.iter().any(|f| !f.is_zero()), + "party commitments from final wrapper should not all be zero" ); prover.cleanup(e3_id).unwrap(); @@ -559,10 +571,10 @@ async fn test_share_computation_e_sm_commitment_consistency() { .prove(&prover, &preset, &sample, &format!("{e3_id}_base")) .expect("base proof should succeed"); - let n_chunks = Configs::compute(preset, &sample).expect("configs").n_chunks; + let configs = Configs::compute(preset, &sample).expect("configs"); - let mut chunk_proofs = Vec::with_capacity(n_chunks); - for chunk_idx in 0..n_chunks { + let mut chunk_proofs = Vec::with_capacity(configs.n_chunks); + for chunk_idx in 0..configs.n_chunks { let chunk_data = ShareComputationChunkCircuitData { share_data: sample.clone(), chunk_idx, @@ -578,30 +590,40 @@ async fn test_share_computation_e_sm_commitment_consistency() { chunk_proofs.push(chunk_proof); } - let proof = generate_share_computation_proof(&prover, &base_proof, &chunk_proofs, e3_id) - .expect("share_computation proof should succeed"); + // Level 1: group chunks into batches and prove each batch + let mut batch_proofs = Vec::with_capacity(configs.n_batches); + for batch_idx in 0..configs.n_batches { + let start = batch_idx * configs.chunks_per_batch; + let end = start + configs.chunks_per_batch; + let batch_proof = generate_chunk_batch_proof( + &prover, + &base_proof, + &chunk_proofs[start..end], + batch_idx as u32, + &format!("{e3_id}_batch_{batch_idx}"), + ) + .expect("chunk batch proof should succeed"); + batch_proofs.push(batch_proof); + } + + // Level 2: aggregate batch proofs into final C2 proof + let proof = generate_share_computation_final_proof(&prover, &batch_proofs, e3_id) + .expect("final share_computation proof should succeed"); - // The share_computation inner circuit has 3 public outputs (each 32 bytes): - // [0] base_key_hash - // [1] chunk_key_hash - // [2] aggregated commitment = compute_recursive_aggregation_commitment(base_pub ++ chunk_pubs) + // Final wrapper now exposes: + // [0] batch_key_hash (pub param) + // [1..=4] batch wrapper public inputs (length 4 fields). assert_eq!( proof.public_signals.len(), - 3 * 32, - "share_computation inner circuit should have exactly 3 public outputs (96 bytes)" + 5 * 32, + "final share_computation wrapper should expose 5 field public inputs (160 bytes)" ); - // Reconstruct the aggregated payload from intermediate proofs' public signals - let mut payload: Vec = public_signals_to_fields(&base_proof.public_signals); - for cp in &chunk_proofs { - payload.extend(public_signals_to_fields(&cp.public_signals)); - } - let expected_commitment = compute_recursive_aggregation_commitment(payload); - - let commitment_from_proof = extract_field(&proof.public_signals, 2); - assert_eq!( - commitment_from_proof, expected_commitment, - "Aggregated commitment from proof must match independently calculated commitment" + // Sanity check: at least one public input is non-zero. + let fields = public_signals_to_fields(&proof.public_signals); + assert!( + fields.iter().any(|f| !f.is_zero()), + "party commitments from final wrapper should not all be zero" ); prover.cleanup(e3_id).unwrap(); From ddff19a874b4c67f357db09d85231107be14ad33 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Thu, 12 Mar 2026 14:01:52 +0100 Subject: [PATCH 21/27] avoid noisy lint warnings for unused vars --- .../test/Slashing/SlashingManager.spec.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/enclave-contracts/test/Slashing/SlashingManager.spec.ts b/packages/enclave-contracts/test/Slashing/SlashingManager.spec.ts index 131b36383a..8a051fb929 100644 --- a/packages/enclave-contracts/test/Slashing/SlashingManager.spec.ts +++ b/packages/enclave-contracts/test/Slashing/SlashingManager.spec.ts @@ -195,7 +195,7 @@ describe("SlashingManager", function () { await enclaveTicketToken.getAddress(), owner, ); - const mockVerifier = MockCircuitVerifierFactory.connect( + const _mockVerifier = MockCircuitVerifierFactory.connect( await mockCircuitVerifier.getAddress(), owner, ); @@ -248,7 +248,7 @@ describe("SlashingManager", function () { enclaveToken, ticketToken, usdcToken, - mockVerifier, + _mockVerifier, mockCiphernodeRegistry, }; } @@ -297,13 +297,13 @@ describe("SlashingManager", function () { describe("setSlashPolicy()", function () { it("should set a valid proof-based slash policy", async function () { - const { slashingManager, mockVerifier } = await loadFixture(setup); + const { slashingManager, _mockVerifier } = await loadFixture(setup); const policy = { ticketPenalty: ethers.parseUnits("50", 6), licensePenalty: ethers.parseEther("100"), requiresProof: true, - proofVerifier: await mockVerifier.getAddress(), + proofVerifier: await _mockVerifier.getAddress(), banNode: false, appealWindow: 0, enabled: true, @@ -448,13 +448,13 @@ describe("SlashingManager", function () { }); it("should revert if proof required but appeal window set", async function () { - const { slashingManager, mockVerifier } = await loadFixture(setup); + const { slashingManager, _mockVerifier } = await loadFixture(setup); const policy = { ticketPenalty: ethers.parseUnits("50", 6), licensePenalty: ethers.parseEther("100"), requiresProof: true, - proofVerifier: await mockVerifier.getAddress(), + proofVerifier: await _mockVerifier.getAddress(), banNode: false, appealWindow: APPEAL_WINDOW, enabled: true, @@ -1471,13 +1471,13 @@ describe("SlashingManager", function () { describe("view functions", function () { it("should return correct slash policy", async function () { - const { slashingManager, mockVerifier } = await loadFixture(setup); + const { slashingManager, _mockVerifier } = await loadFixture(setup); const policy = { ticketPenalty: ethers.parseUnits("50", 6), licensePenalty: ethers.parseEther("100"), requiresProof: true, - proofVerifier: await mockVerifier.getAddress(), + proofVerifier: await _mockVerifier.getAddress(), banNode: true, appealWindow: 0, enabled: true, From 8e854075014e056e4d0b75b83536af5b635a6425 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 17 Mar 2026 09:29:57 +0100 Subject: [PATCH 22/27] fix share_computation wrapper --- .../wrapper/dkg/share_computation/src/main.nr | 11 +++--- crates/multithread/src/multithread.rs | 17 ++++---- crates/tests/tests/integration.rs | 39 ++++++++++++++++--- crates/zk-prover/src/prover.rs | 3 +- 4 files changed, 48 insertions(+), 22 deletions(-) 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 e9b5c3ecdb..a2599961df 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,11 +7,11 @@ use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; use lib::math::commitments::compute_recursive_aggregation_commitment; -// Both SK and ESM share_computation inner proofs wrapped together during aggregation. -pub global N_PROOFS: u32 = 2; -// The share_computation inner circuit has 3 public outputs: -// base_key_hash, chunk_key_hash, aggregated_commitment (return value). -pub global N_PUBLIC_INPUTS: u32 = 3; +// Each SK/ESM final C2 proof is wrapped individually after the two-level pipeline. +pub global N_PROOFS: u32 = 1; +// The final share_computation circuit exposes 5 public outputs: +// batch_key_hash (pub param) + [Field; 4] return (base_key_hash, chunk_key_hash, batch_idx, commitment). +pub global N_PUBLIC_INPUTS: u32 = 5; fn main( verification_key: UltraHonkVerificationKey, @@ -21,7 +21,6 @@ fn main( ) -> pub Field { for i in 0..N_PROOFS { verify_honk_proof(verification_key, proofs[i], public_inputs[i], key_hash); - verify_honk_proof(verification_key, proofs[i], public_inputs[i], key_hash); } let mut aggregated_public_inputs = Vec::new(); diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index c5dd4412ba..0c423800f5 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -747,13 +747,6 @@ fn handle_share_computation_proof( ) })?; - let wrapped_proof = generate_wrapper_proof(prover, &base_proof, &e3_id_str).map_err(|e| { - ComputeRequestError::new( - ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), - request.clone(), - ) - })?; - // 8. Determine number of chunks and prove each chunk circuit let configs = Configs::compute(req.params_preset.clone(), &circuit_data) .map_err(|e| make_zk_error(&request, format!("Configs::compute: {}", e)))?; @@ -812,7 +805,15 @@ fn handle_share_computation_proof( ) })?; - // 11. Return final C2 proof + // 11. Wrap the final C2 proof for fold aggregation + let wrapped_proof = generate_wrapper_proof(prover, &proof, &e3_id_str).map_err(|e| { + ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), + request.clone(), + ) + })?; + + // 12. Return final C2 proof Ok(ComputeResponse::zk( ZkResponse::ShareComputation(ShareComputationProofResponse { proof, diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index 6afcd83207..53073ec8bf 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -144,20 +144,47 @@ async fn setup_test_zk_backend() -> (ZkBackend, tempfile::TempDir) { ".vk_noir_hash", ) .await; - // C2a (sk_share_computation) + // C2a base (sk_share_computation_base) copy_circuit( &dkg_target, - &rv.join("dkg/sk_share_computation"), - "sk_share_computation", + &rv.join("dkg/sk_share_computation_base"), + "sk_share_computation_base", ".vk_noir", ".vk_noir_hash", ) .await; - // C2b (e_sm_share_computation) + // C2b base (e_sm_share_computation_base) copy_circuit( &dkg_target, - &rv.join("dkg/e_sm_share_computation"), - "e_sm_share_computation", + &rv.join("dkg/e_sm_share_computation_base"), + "e_sm_share_computation_base", + ".vk_noir", + ".vk_noir_hash", + ) + .await; + // C2 chunk (share_computation_chunk) + copy_circuit( + &dkg_target, + &rv.join("dkg/share_computation_chunk"), + "share_computation_chunk", + ".vk_noir", + ".vk_noir_hash", + ) + .await; + // C2 chunk_batch (share_computation_chunk_batch) + copy_circuit( + &dkg_target, + &rv.join("dkg/share_computation_chunk_batch"), + "share_computation_chunk_batch", + ".vk_noir", + ".vk_noir_hash", + ) + .await; + // C2 final (share_computation) + copy_circuit( + &dkg_target, + &rv.join("dkg/share_computation"), + "share_computation", ".vk_noir", ".vk_noir_hash", ) diff --git a/crates/zk-prover/src/prover.rs b/crates/zk-prover/src/prover.rs index 39ef9becdf..78746b4053 100644 --- a/crates/zk-prover/src/prover.rs +++ b/crates/zk-prover/src/prover.rs @@ -145,10 +145,9 @@ impl ZkProver { } let job_dir = self.work_dir.join(e3_id); - fs::create_dir_all(&job_dir)?; - let witness_path = job_dir.join("witness.gz"); let output_dir = job_dir.join("out"); + fs::create_dir_all(&job_dir)?; fs::write(&witness_path, witness_data)?; From ef2ca25621af8580afb3a583bd01d930472c99c7 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 17 Mar 2026 10:11:21 +0100 Subject: [PATCH 23/27] fix fixture build --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 42a1b061dc..fbe950e735 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "noir:test": "./scripts/test-circuits.sh", "noir:lint": "./scripts/lint-circuits.sh", "rust:build": "cargo build --locked --release", - "prerust:build": "pnpm evm:build", + "prerust:build": "pnpm evm:build && pnpm fixtures:build", "committee:new": "cd packages/enclave-contracts && pnpm committee:new", "committee:publish": "cd packages/enclave-contracts && pnpm hardhat committee:publish", "e3:activate": "cd packages/enclave-contracts && pnpm e3:activate", @@ -64,6 +64,7 @@ "npm:release": "pnpm build && pnpm config:release && pnpm evm:release && pnpm wasm:release && pnpm sdk:release && pnpm react:release && pnpm mcp:release", "support:build": "cd crates/support && ./scripts/build.sh", "build": "pnpm compile", + "fixtures:build": "cd crates/evm-helpers && ./scripts/build_fixtures.sh && cd ../indexer && ./scripts/build_fixtures.sh", "wasm:build": "cd ./crates/wasm && pnpm build", "build:ts": "pnpm evm:build && pnpm sdk:build && pnpm react:build && pnpm mcp:build", "template:build": "cd templates/default && pnpm compile", From e6a698843babc14ae35b2989a420dbd4319f8299 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 17 Mar 2026 10:33:51 +0100 Subject: [PATCH 24/27] update contracts json --- .../RecursiveAggregationFoldVerifier.json | 10 +++++----- .../ZKTranscriptLib.json | 2 +- .../ThresholdDecryptedSharesAggregationVerifier.json | 10 +++++----- .../ZKTranscriptLib.json | 2 +- .../ThresholdPkAggregationVerifier.json | 10 +++++----- .../ZKTranscriptLib.json | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/enclave-contracts/artifacts/contracts/verifier/RecursiveAggregationFoldVerifier.sol/RecursiveAggregationFoldVerifier.json b/packages/enclave-contracts/artifacts/contracts/verifier/RecursiveAggregationFoldVerifier.sol/RecursiveAggregationFoldVerifier.json index 6d4cc8a2ff..ff4eb97f39 100644 --- a/packages/enclave-contracts/artifacts/contracts/verifier/RecursiveAggregationFoldVerifier.sol/RecursiveAggregationFoldVerifier.json +++ b/packages/enclave-contracts/artifacts/contracts/verifier/RecursiveAggregationFoldVerifier.sol/RecursiveAggregationFoldVerifier.json @@ -102,7 +102,7 @@ } }, "immutableReferences": { - "35080": [ + "7752": [ { "length": 32, "start": 91 @@ -152,13 +152,13 @@ "start": 11167 } ], - "35082": [ + "7754": [ { "length": 32, "start": 398 } ], - "35084": [ + "7756": [ { "length": 32, "start": 432 @@ -168,7 +168,7 @@ "start": 2303 } ], - "35086": [ + "7758": [ { "length": 32, "start": 3156 @@ -184,5 +184,5 @@ ] }, "inputSourceName": "project/contracts/verifier/RecursiveAggregationFoldVerifier.sol", - "buildInfoId": "solc-0_8_28-bd46eae6f77be9a8538850385226c1ee4fc57e42" + "buildInfoId": "solc-0_8_28-543ccb35c9b85334df682667ad98480ea27e7555" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/verifier/RecursiveAggregationFoldVerifier.sol/ZKTranscriptLib.json b/packages/enclave-contracts/artifacts/contracts/verifier/RecursiveAggregationFoldVerifier.sol/ZKTranscriptLib.json index de153d1923..d05f0d8dd0 100644 --- a/packages/enclave-contracts/artifacts/contracts/verifier/RecursiveAggregationFoldVerifier.sol/ZKTranscriptLib.json +++ b/packages/enclave-contracts/artifacts/contracts/verifier/RecursiveAggregationFoldVerifier.sol/ZKTranscriptLib.json @@ -391,5 +391,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/verifier/RecursiveAggregationFoldVerifier.sol", - "buildInfoId": "solc-0_8_28-bd46eae6f77be9a8538850385226c1ee4fc57e42" + "buildInfoId": "solc-0_8_28-543ccb35c9b85334df682667ad98480ea27e7555" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdDecryptedSharesAggregationVerifier.sol/ThresholdDecryptedSharesAggregationVerifier.json b/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdDecryptedSharesAggregationVerifier.sol/ThresholdDecryptedSharesAggregationVerifier.json index f18f207a1a..bb670eee0f 100644 --- a/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdDecryptedSharesAggregationVerifier.sol/ThresholdDecryptedSharesAggregationVerifier.json +++ b/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdDecryptedSharesAggregationVerifier.sol/ThresholdDecryptedSharesAggregationVerifier.json @@ -102,7 +102,7 @@ } }, "immutableReferences": { - "7752": [ + "17861": [ { "length": 32, "start": 91 @@ -152,13 +152,13 @@ "start": 11167 } ], - "7754": [ + "17863": [ { "length": 32, "start": 398 } ], - "7756": [ + "17865": [ { "length": 32, "start": 432 @@ -168,7 +168,7 @@ "start": 2303 } ], - "7758": [ + "17867": [ { "length": 32, "start": 3156 @@ -184,5 +184,5 @@ ] }, "inputSourceName": "project/contracts/verifier/ThresholdDecryptedSharesAggregationVerifier.sol", - "buildInfoId": "solc-0_8_28-ea784e9448f647a2ad29ccdb24e4cacb8fdc4660" + "buildInfoId": "solc-0_8_28-543ccb35c9b85334df682667ad98480ea27e7555" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdDecryptedSharesAggregationVerifier.sol/ZKTranscriptLib.json b/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdDecryptedSharesAggregationVerifier.sol/ZKTranscriptLib.json index c534800666..80f6e10e7d 100644 --- a/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdDecryptedSharesAggregationVerifier.sol/ZKTranscriptLib.json +++ b/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdDecryptedSharesAggregationVerifier.sol/ZKTranscriptLib.json @@ -391,5 +391,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/verifier/ThresholdDecryptedSharesAggregationVerifier.sol", - "buildInfoId": "solc-0_8_28-ea784e9448f647a2ad29ccdb24e4cacb8fdc4660" + "buildInfoId": "solc-0_8_28-543ccb35c9b85334df682667ad98480ea27e7555" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdPkAggregationVerifier.sol/ThresholdPkAggregationVerifier.json b/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdPkAggregationVerifier.sol/ThresholdPkAggregationVerifier.json index d161c2ad7b..ad9f38524a 100644 --- a/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdPkAggregationVerifier.sol/ThresholdPkAggregationVerifier.json +++ b/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdPkAggregationVerifier.sol/ThresholdPkAggregationVerifier.json @@ -102,7 +102,7 @@ } }, "immutableReferences": { - "65407": [ + "27970": [ { "length": 32, "start": 91 @@ -152,13 +152,13 @@ "start": 11167 } ], - "65409": [ + "27972": [ { "length": 32, "start": 398 } ], - "65411": [ + "27974": [ { "length": 32, "start": 432 @@ -168,7 +168,7 @@ "start": 2303 } ], - "65413": [ + "27976": [ { "length": 32, "start": 3156 @@ -184,5 +184,5 @@ ] }, "inputSourceName": "project/contracts/verifier/ThresholdPkAggregationVerifier.sol", - "buildInfoId": "solc-0_8_28-bd46eae6f77be9a8538850385226c1ee4fc57e42" + "buildInfoId": "solc-0_8_28-543ccb35c9b85334df682667ad98480ea27e7555" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdPkAggregationVerifier.sol/ZKTranscriptLib.json b/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdPkAggregationVerifier.sol/ZKTranscriptLib.json index b6bbee1be1..ca4afdec17 100644 --- a/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdPkAggregationVerifier.sol/ZKTranscriptLib.json +++ b/packages/enclave-contracts/artifacts/contracts/verifier/ThresholdPkAggregationVerifier.sol/ZKTranscriptLib.json @@ -391,5 +391,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/verifier/ThresholdPkAggregationVerifier.sol", - "buildInfoId": "solc-0_8_28-bd46eae6f77be9a8538850385226c1ee4fc57e42" + "buildInfoId": "solc-0_8_28-543ccb35c9b85334df682667ad98480ea27e7555" } \ No newline at end of file From 1df646d73aef642d5e4998aaa9e53e08f2f613a1 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 17 Mar 2026 12:16:24 +0100 Subject: [PATCH 25/27] introduce feedbacks from reviews --- .../bin/dkg/share_computation/src/main.nr | 53 +++++++++++----- .../wrapper/dkg/share_computation/src/main.nr | 6 +- crates/multithread/src/multithread.rs | 45 ++++++++------ .../dkg/share_computation/computation.rs | 21 +++++++ .../src/circuits/dkg/share_computation.rs | 60 +++++++++++++++---- crates/zk-prover/src/circuits/utils.rs | 28 +++++++-- crates/zk-prover/src/lib.rs | 2 +- crates/zk-prover/src/prover.rs | 40 ++++++++++++- packages/enclave-sdk/tsup.config.js | 2 + templates/default/tests/integration.spec.ts | 2 +- 10 files changed, 204 insertions(+), 55 deletions(-) diff --git a/circuits/bin/dkg/share_computation/src/main.nr b/circuits/bin/dkg/share_computation/src/main.nr index 7bf6f42990..57038aebb9 100644 --- a/circuits/bin/dkg/share_computation/src/main.nr +++ b/circuits/bin/dkg/share_computation/src/main.nr @@ -5,22 +5,23 @@ // or FITNESS FOR A PARTICULAR PURPOSE. // Level 2: final_wrapper -use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof}; +use bb_proof_verification::{UltraHonkProof, UltraHonkVerificationKey, verify_honk_proof_non_zk}; use lib::configs::default::dkg::SHARE_COMPUTATION_N_BATCHES as N_BATCHES; +use lib::math::commitments::{compute_recursive_aggregation_commitment, compute_vk_hash}; // Public inputs of each batch wrapper proof (as exposed by `share_computation_chunk_batch`). -// Currently: [base_key_hash, chunk_key_hash, batch_idx, aggregated_commitment]. +// Layout: [base_key_hash, chunk_key_hash, batch_idx, aggregated_commitment]. pub global BATCH_WRAPPER_PUBLIC_INPUTS: u32 = 4; fn main( batch_verification_key: UltraHonkVerificationKey, - batch_proofs: [UltraHonkZKProof; N_BATCHES], + batch_proofs: [UltraHonkProof; N_BATCHES], batch_public_inputs: [[Field; BATCH_WRAPPER_PUBLIC_INPUTS]; N_BATCHES], batch_key_hash: pub Field, -) -> pub [Field; BATCH_WRAPPER_PUBLIC_INPUTS] { - // 1. Verify all batch proofs against their public inputs and key hash. +) -> pub (Field, Field) { + // 1. Verify all batch proofs (non-zk). for i in 0..N_BATCHES { - verify_honk_proof( + verify_honk_proof_non_zk( batch_verification_key, batch_proofs[i], batch_public_inputs[i], @@ -28,14 +29,38 @@ fn main( ); } - // 2. Expose the batch wrapper public inputs (without recomputing a commitment) - // as the C2 public interface. We take them from the first batch, since each - // batch has the same shape. - let mut c2_public_inputs: [Field; BATCH_WRAPPER_PUBLIC_INPUTS] = - [0 as Field; BATCH_WRAPPER_PUBLIC_INPUTS]; - for i in 0..BATCH_WRAPPER_PUBLIC_INPUTS { - c2_public_inputs[i] = batch_public_inputs[0][i]; + // 2. Assert shared fields are identical across all batches. + for i in 1..N_BATCHES { + assert( + batch_public_inputs[i][0] == batch_public_inputs[0][0], + "base_key_hash mismatch across batches", + ); + assert( + batch_public_inputs[i][1] == batch_public_inputs[0][1], + "chunk_key_hash mismatch across batches", + ); + } + + // 3. Assert batch_idx values are ordered 0..N_BATCHES. + for i in 0..N_BATCHES { + assert(batch_public_inputs[i][2] == i as Field, "batch_idx out of order"); } - c2_public_inputs + // 4. Fold all per-batch aggregated_commitment values into a single commitment. + let mut commitments = Vec::new(); + for i in 0..N_BATCHES { + commitments.push(batch_public_inputs[i][3]); + } + let final_commitment = compute_recursive_aggregation_commitment(commitments); + + // 5. Hash the full VK chain: inner VK hashes (base, chunk) from batch public + // inputs + the batch VK hash that verified this level. This combined fingerprint + // lets the verifier check the entire proof genealogy. + let mut vk_hashes: Vec = Vec::new(); + vk_hashes.push(batch_public_inputs[0][0]); // base_key_hash (same across all batches) + vk_hashes.push(batch_public_inputs[0][1]); // chunk_key_hash (same across all batches) + vk_hashes.push(batch_key_hash); // VK hash of the batch circuit that produced these proofs + let key_hash = compute_vk_hash(vk_hashes); + + (key_hash, final_commitment) } 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 a2599961df..727d156e17 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 @@ -9,9 +9,9 @@ use lib::math::commitments::compute_recursive_aggregation_commitment; // Each SK/ESM final C2 proof is wrapped individually after the two-level pipeline. pub global N_PROOFS: u32 = 1; -// The final share_computation circuit exposes 5 public outputs: -// batch_key_hash (pub param) + [Field; 4] return (base_key_hash, chunk_key_hash, batch_idx, commitment). -pub global N_PUBLIC_INPUTS: u32 = 5; +// The final share_computation circuit exposes 3 public outputs: +// batch_key_hash (pub param) + (key_hash, commitment) return tuple. +pub global N_PUBLIC_INPUTS: u32 = 3; fn main( verification_key: UltraHonkVerificationKey, diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 0c423800f5..8dfff92aa6 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -62,8 +62,7 @@ use e3_zk_helpers::circuits::threshold::pk_generation::circuit::{ }; use e3_zk_helpers::computation::DkgInputType; use e3_zk_helpers::dkg::share_computation::{ - Configs, ShareComputationBaseCircuit, ShareComputationChunkCircuit, - ShareComputationChunkCircuitData, ShareComputationCircuitData, + ChunkInputs, Configs, Inputs, ShareComputationBaseCircuit, ShareComputationCircuitData, }; use e3_zk_helpers::dkg::share_decryption::{ShareDecryptionCircuit, ShareDecryptionCircuitData}; use e3_zk_helpers::dkg::share_encryption::{ShareEncryptionCircuit, ShareEncryptionCircuitData}; @@ -72,8 +71,9 @@ use e3_zk_helpers::threshold::pk_aggregation::PkAggregationCircuitData; use e3_zk_helpers::CiphernodesCommittee; use e3_zk_helpers::Computation; use e3_zk_prover::{ - generate_chunk_batch_proof, generate_fold_proof, generate_share_computation_final_proof, - generate_wrapper_proof, CircuitVariant, Provable, ZkBackend, ZkProver, + generate_chunk_batch_proof, generate_chunk_proof, generate_fold_proof, + generate_share_computation_final_proof, generate_wrapper_proof, CircuitVariant, Provable, + ZkBackend, ZkProver, }; use fhe::bfv::{Ciphertext, Encoding, Plaintext, PublicKey, SecretKey}; use fhe::mbfv::PublicKeyShare; @@ -751,21 +751,19 @@ fn handle_share_computation_proof( let configs = Configs::compute(req.params_preset.clone(), &circuit_data) .map_err(|e| make_zk_error(&request, format!("Configs::compute: {}", e)))?; - let chunk_circuit = ShareComputationChunkCircuit; + let base_inputs = Inputs::compute(req.params_preset.clone(), &circuit_data) + .map_err(|e| make_zk_error(&request, format!("Inputs::compute: {}", e)))?; + let mut chunk_proofs = Vec::with_capacity(configs.n_chunks); for chunk_idx in 0..configs.n_chunks { - let chunk_data = ShareComputationChunkCircuitData { - share_data: circuit_data.clone(), - chunk_idx, - }; - let chunk_proof = chunk_circuit - .prove( - prover, - &req.params_preset, - &chunk_data, - &format!("{e3_id_str}_chunk_{chunk_idx}"), - ) - .map_err(|e| { + let chunk_inputs = ChunkInputs::from_inputs(&base_inputs, &configs, chunk_idx) + .map_err(|e| make_zk_error(&request, format!("ChunkInputs::from_inputs: {}", e)))?; + let chunk_proof = generate_chunk_proof( + prover, + &chunk_inputs, + &format!("{e3_id_str}_chunk_{chunk_idx}"), + ) + .map_err(|e| { ComputeRequestError::new( ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), request.clone(), @@ -778,11 +776,20 @@ fn handle_share_computation_proof( let mut batch_proofs = Vec::with_capacity(configs.n_batches); for batch_idx in 0..configs.n_batches { let start = batch_idx * configs.chunks_per_batch; - let end = start + configs.chunks_per_batch; + let end = usize::min(start + configs.chunks_per_batch, chunk_proofs.len()); + let batch_chunks = chunk_proofs.get(start..end).ok_or_else(|| { + ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(format!( + "chunk_proofs slice out of bounds: batch_idx={batch_idx}, start={start}, end={end}, len={}", + chunk_proofs.len() + ))), + request.clone(), + ) + })?; let batch_proof = generate_chunk_batch_proof( prover, &base_proof, - &chunk_proofs[start..end], + batch_chunks, batch_idx as u32, &format!("{e3_id_str}_batch_{batch_idx}"), ) diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs index af2bdf3845..7eabda7690 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -337,6 +337,27 @@ impl Inputs { } } +impl ChunkInputs { + /// Select chunk inputs from pre-computed base inputs and configs. + /// Avoids redundant recomputation when generating multiple chunk proofs. + pub fn from_inputs( + inputs: &Inputs, + configs: &Configs, + chunk_idx: usize, + ) -> Result { + let chunks = inputs.split_into_chunks(configs.chunk_size)?; + chunks + .into_iter() + .find(|chunk| chunk.chunk_idx == chunk_idx) + .ok_or_else(|| { + CircuitsErrors::Sample(format!( + "chunk index {} out of range for {} chunks", + chunk_idx, configs.n_chunks + )) + }) + } +} + impl Computation for ChunkInputs { type Preset = BfvPreset; type Data = ShareComputationChunkCircuitData; diff --git a/crates/zk-prover/src/circuits/dkg/share_computation.rs b/crates/zk-prover/src/circuits/dkg/share_computation.rs index b3bd753665..b1378c53ed 100644 --- a/crates/zk-prover/src/circuits/dkg/share_computation.rs +++ b/crates/zk-prover/src/circuits/dkg/share_computation.rs @@ -4,7 +4,9 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE -use crate::circuits::utils::{bytes_to_field_strings, prove_recursive_circuit}; +use crate::circuits::utils::{ + bytes_to_field_strings, prove_recursive_circuit, prove_recursive_circuit_non_zk, +}; use crate::circuits::vk; use crate::error::ZkError; use crate::prover::ZkProver; @@ -70,7 +72,9 @@ pub fn generate_chunk_batch_proof( batch_idx: batch_idx.to_string(), }; - prove_recursive_circuit( + // Non-ZK: chunk_batch proofs are intermediate, consumed by the final + // share_computation circuit which verifies with verify_honk_proof_non_zk. + prove_recursive_circuit_non_zk( prover, CircuitName::ShareComputationChunkBatch, &input, @@ -116,6 +120,34 @@ pub fn generate_share_computation_final_proof( prove_recursive_circuit(prover, CircuitName::ShareComputation, &input, e3_id) } +/// Proves a single chunk circuit from pre-computed [`ChunkInputs`]. +/// Avoids redundant `Inputs::compute` when proving multiple chunks in a loop. +pub fn generate_chunk_proof( + prover: &ZkProver, + chunk_inputs: &ChunkInputs, + e3_id: &str, +) -> Result { + use crate::circuits::utils::inputs_json_to_input_map; + use crate::witness::{CompiledCircuit, WitnessGenerator}; + + let circuit_name = CircuitName::ShareComputationChunk; + let recursive_dir = prover.circuits_dir(CircuitVariant::Recursive); + let circuit_path = recursive_dir + .join(circuit_name.dir_path()) + .join(format!("{}.json", circuit_name.as_str())); + let compiled = CompiledCircuit::from_file(&circuit_path)?; + + let json = chunk_inputs + .to_json() + .map_err(|e| ZkError::SerializationError(e.to_string()))?; + let input_map = inputs_json_to_input_map(&json)?; + + let witness_gen = WitnessGenerator::new(); + let witness = witness_gen.generate_witness(&compiled, input_map)?; + + prover.generate_proof_with_variant(circuit_name, &witness, e3_id, CircuitVariant::Recursive) +} + ////////////////////////////////////////////////////////////////////////////// // Provable impls ////////////////////////////////////////////////////////////////////////////// @@ -147,18 +179,16 @@ impl Provable for ShareComputationCircuit { let configs = Configs::compute(params.clone(), input) .map_err(|e| ZkError::InputsGenerationFailed(e.to_string()))?; + let base_inputs = Inputs::compute(params.clone(), input) + .map_err(|e| ZkError::InputsGenerationFailed(e.to_string()))?; - let chunk_circuit = ShareComputationChunkCircuit; let mut chunk_proofs = Vec::with_capacity(configs.n_chunks); for chunk_idx in 0..configs.n_chunks { - let chunk_data = ShareComputationChunkCircuitData { - share_data: input.clone(), - chunk_idx, - }; - let chunk_proof = chunk_circuit.prove( + let chunk_inputs = ChunkInputs::from_inputs(&base_inputs, &configs, chunk_idx) + .map_err(|e| ZkError::InputsGenerationFailed(e.to_string()))?; + let chunk_proof = generate_chunk_proof( prover, - params, - &chunk_data, + &chunk_inputs, &format!("{e3_id}_chunk_{chunk_idx}"), )?; chunk_proofs.push(chunk_proof); @@ -168,11 +198,17 @@ impl Provable for ShareComputationCircuit { let mut batch_proofs = Vec::with_capacity(configs.n_batches); for batch_idx in 0..configs.n_batches { let start = batch_idx * configs.chunks_per_batch; - let end = start + configs.chunks_per_batch; + let end = usize::min(start + configs.chunks_per_batch, chunk_proofs.len()); + let batch_chunks = chunk_proofs.get(start..end).ok_or_else(|| { + ZkError::ProveFailed(format!( + "chunk_proofs slice out of bounds: batch_idx={batch_idx}, start={start}, end={end}, len={}", + chunk_proofs.len() + )) + })?; let batch_proof = generate_chunk_batch_proof( prover, &base_proof, - &chunk_proofs[start..end], + batch_chunks, batch_idx as u32, &format!("{e3_id}_batch_{batch_idx}"), )?; diff --git a/crates/zk-prover/src/circuits/utils.rs b/crates/zk-prover/src/circuits/utils.rs index f970445bc3..573b7232f0 100644 --- a/crates/zk-prover/src/circuits/utils.rs +++ b/crates/zk-prover/src/circuits/utils.rs @@ -29,7 +29,7 @@ pub fn bytes_to_field_strings(bytes: &[u8]) -> Result, ZkError> { .collect()) } -/// Proves a circuit given a serializable input struct. +/// Proves a circuit given a serializable input struct (ZK blinding enabled). /// /// Handles the common pattern: serialize → load compiled circuit → generate witness → prove. pub fn prove_recursive_circuit( @@ -38,6 +38,28 @@ pub fn prove_recursive_circuit( input: &impl serde::Serialize, e3_id: &str, ) -> Result { + let witness = generate_recursive_witness(prover, circuit_name, input)?; + prover.generate_proof_with_variant(circuit_name, &witness, e3_id, CircuitVariant::Recursive) +} + +/// Like [`prove_recursive_circuit`] but produces a non-ZK proof (UltraHonkProof, 457 fields). +/// Use for intermediate proofs consumed by circuits that verify with `verify_honk_proof_non_zk`. +pub fn prove_recursive_circuit_non_zk( + prover: &ZkProver, + circuit_name: CircuitName, + input: &impl serde::Serialize, + e3_id: &str, +) -> Result { + let witness = generate_recursive_witness(prover, circuit_name, input)?; + prover.generate_recursive_non_zk_proof(circuit_name, &witness, e3_id) +} + +/// Shared helper: load compiled circuit from Recursive dir, serialize input, generate witness. +fn generate_recursive_witness( + prover: &ZkProver, + circuit_name: CircuitName, + input: &impl serde::Serialize, +) -> Result, ZkError> { let recursive_dir = prover.circuits_dir(CircuitVariant::Recursive); let circuit_path = recursive_dir .join(circuit_name.dir_path()) @@ -49,9 +71,7 @@ pub fn prove_recursive_circuit( let input_map = inputs_json_to_input_map(&json)?; let witness_gen = WitnessGenerator::new(); - let witness = witness_gen.generate_witness(&compiled, input_map)?; - - prover.generate_proof_with_variant(circuit_name, &witness, e3_id, CircuitVariant::Recursive) + Ok(witness_gen.generate_witness(&compiled, input_map)?) } /// Converts inputs JSON (from `Inputs::to_json()`) to `InputMap` for Noir ABI. diff --git a/crates/zk-prover/src/lib.rs b/crates/zk-prover/src/lib.rs index 17696fb1b8..2c65f9509d 100644 --- a/crates/zk-prover/src/lib.rs +++ b/crates/zk-prover/src/lib.rs @@ -22,7 +22,7 @@ pub use actors::{ pub use backend::{SetupStatus, ZkBackend}; pub use circuits::dkg::share_computation::{ - generate_chunk_batch_proof, generate_share_computation_final_proof, + generate_chunk_batch_proof, generate_chunk_proof, generate_share_computation_final_proof, }; pub use circuits::recursive_aggregation::{generate_fold_proof, generate_wrapper_proof}; pub use config::{verify_checksum, BbTarget, CircuitInfo, VersionInfo, ZkConfig}; diff --git a/crates/zk-prover/src/prover.rs b/crates/zk-prover/src/prover.rs index 78746b4053..ea3d05900f 100644 --- a/crates/zk-prover/src/prover.rs +++ b/crates/zk-prover/src/prover.rs @@ -68,6 +68,25 @@ impl ZkProver { self.generate_proof_impl(circuit, witness_data, e3_id, &circuit.dir_path(), variant) } + /// Generate a non-ZK proof using circuit artifacts from the Recursive directory. + /// Useful for intermediate proofs that don't need ZK blinding but whose artifacts + /// only exist in the recursive dir (e.g., chunk_batch proofs). + pub fn generate_recursive_non_zk_proof( + &self, + circuit: CircuitName, + witness_data: &[u8], + e3_id: &str, + ) -> Result { + self.generate_proof_impl_with_dir( + circuit, + witness_data, + e3_id, + &circuit.dir_path(), + CircuitVariant::Default, + self.circuits_dir(CircuitVariant::Recursive), + ) + } + /// Wrapper proof (Default variant, wrapper dir). pub fn generate_wrapper_proof( &self, @@ -119,6 +138,25 @@ impl ZkProver { e3_id: &str, dir_path: &str, variant: CircuitVariant, + ) -> Result { + self.generate_proof_impl_with_dir( + circuit, + witness_data, + e3_id, + dir_path, + variant, + self.circuits_dir(variant), + ) + } + + fn generate_proof_impl_with_dir( + &self, + circuit: CircuitName, + witness_data: &[u8], + e3_id: &str, + dir_path: &str, + variant: CircuitVariant, + base_dir: std::path::PathBuf, ) -> Result { if !self.bb_binary.exists() { return Err(ZkError::BbNotInstalled); @@ -126,7 +164,7 @@ impl ZkProver { let verifier_target = variant.verifier_target(); - let circuit_dir = self.circuits_dir(variant).join(dir_path); + let circuit_dir = base_dir.join(dir_path); let circuit_path = circuit_dir.join(format!("{}.json", circuit.as_str())); let vk_path = circuit_dir.join(format!("{}.vk", circuit.as_str())); diff --git a/packages/enclave-sdk/tsup.config.js b/packages/enclave-sdk/tsup.config.js index b607d9cf52..42fbe4f8b1 100644 --- a/packages/enclave-sdk/tsup.config.js +++ b/packages/enclave-sdk/tsup.config.js @@ -29,6 +29,8 @@ export default defineConfig([ entry, include: ['./src/**/*.ts'], format: ['cjs'], + // Avoid running DTS build twice for the same entries. + dts: false, outExtension: () => ({ js: '.cjs', }), diff --git a/templates/default/tests/integration.spec.ts b/templates/default/tests/integration.spec.ts index 369b42ab7e..7776c95b93 100644 --- a/templates/default/tests/integration.spec.ts +++ b/templates/default/tests/integration.spec.ts @@ -189,7 +189,7 @@ describe('Integration', () => { const { waitForEvent } = await setupEventListeners(sdk, store) const committeeSize = CommitteeSize.Micro - const duration = 700 + const duration = 1000 const inputWindow = await calculateInputWindow(publicClient, duration) const thresholdBfvParams = await sdk.getThresholdBfvParamsSet() const e3ProgramParams = encodeBfvParams(thresholdBfvParams) From 78beb61e66c8d471e754fb591bd86184e3ade1ea Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 17 Mar 2026 12:16:54 +0100 Subject: [PATCH 26/27] format --- crates/multithread/src/multithread.rs | 10 +++++----- crates/zk-prover/src/circuits/dkg/share_computation.rs | 7 ++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 8dfff92aa6..a40104fd76 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -764,11 +764,11 @@ fn handle_share_computation_proof( &format!("{e3_id_str}_chunk_{chunk_idx}"), ) .map_err(|e| { - ComputeRequestError::new( - ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), - request.clone(), - ) - })?; + ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), + request.clone(), + ) + })?; chunk_proofs.push(chunk_proof); } diff --git a/crates/zk-prover/src/circuits/dkg/share_computation.rs b/crates/zk-prover/src/circuits/dkg/share_computation.rs index b1378c53ed..fb1b5ab55b 100644 --- a/crates/zk-prover/src/circuits/dkg/share_computation.rs +++ b/crates/zk-prover/src/circuits/dkg/share_computation.rs @@ -186,11 +186,8 @@ impl Provable for ShareComputationCircuit { for chunk_idx in 0..configs.n_chunks { let chunk_inputs = ChunkInputs::from_inputs(&base_inputs, &configs, chunk_idx) .map_err(|e| ZkError::InputsGenerationFailed(e.to_string()))?; - let chunk_proof = generate_chunk_proof( - prover, - &chunk_inputs, - &format!("{e3_id}_chunk_{chunk_idx}"), - )?; + let chunk_proof = + generate_chunk_proof(prover, &chunk_inputs, &format!("{e3_id}_chunk_{chunk_idx}"))?; chunk_proofs.push(chunk_proof); } From af9ae1eaf2cf683a5a6c76e170057754664b1ec1 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 17 Mar 2026 12:40:22 +0100 Subject: [PATCH 27/27] fix local_e2e_test --- crates/zk-prover/src/prover.rs | 3 +++ crates/zk-prover/tests/local_e2e_tests.rs | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/zk-prover/src/prover.rs b/crates/zk-prover/src/prover.rs index ea3d05900f..324ae75536 100644 --- a/crates/zk-prover/src/prover.rs +++ b/crates/zk-prover/src/prover.rs @@ -77,6 +77,9 @@ impl ZkProver { witness_data: &[u8], e3_id: &str, ) -> Result { + // Intentionally crossing variant and directory: + // - CircuitVariant::Default → "noir-recursive-no-zk" verifier target (non-ZK proof, 457 fields) + // - circuits_dir(Recursive) → read artifacts from recursive/ (where chunk_batch lives) self.generate_proof_impl_with_dir( circuit, witness_data, diff --git a/crates/zk-prover/tests/local_e2e_tests.rs b/crates/zk-prover/tests/local_e2e_tests.rs index 5111a87e14..ab36f1e640 100644 --- a/crates/zk-prover/tests/local_e2e_tests.rs +++ b/crates/zk-prover/tests/local_e2e_tests.rs @@ -538,13 +538,14 @@ async fn test_share_computation_sk_commitment_consistency() { let proof = generate_share_computation_final_proof(&prover, &batch_proofs, e3_id) .expect("final share_computation proof should succeed"); - // Final wrapper now exposes: + // Final circuit exposes 3 public outputs: // [0] batch_key_hash (pub param) - // [1..=4] batch wrapper public inputs (length 4 fields). + // [1] key_hash (from return tuple) + // [2] final_commitment (from return tuple) assert_eq!( proof.public_signals.len(), - 5 * 32, - "final share_computation wrapper should expose 5 field public inputs (160 bytes)" + 3 * 32, + "final share_computation should expose 3 field public inputs (96 bytes)" ); // Sanity check: at least one public input is non-zero. @@ -610,13 +611,14 @@ async fn test_share_computation_e_sm_commitment_consistency() { let proof = generate_share_computation_final_proof(&prover, &batch_proofs, e3_id) .expect("final share_computation proof should succeed"); - // Final wrapper now exposes: + // Final circuit exposes 3 public outputs: // [0] batch_key_hash (pub param) - // [1..=4] batch wrapper public inputs (length 4 fields). + // [1] key_hash (from return tuple) + // [2] final_commitment (from return tuple) assert_eq!( proof.public_signals.len(), - 5 * 32, - "final share_computation wrapper should expose 5 field public inputs (160 bytes)" + 3 * 32, + "final share_computation should expose 3 field public inputs (96 bytes)" ); // Sanity check: at least one public input is non-zero.