From 14fdcba7ba73e34c5d0e24ebe93be7f9eba46a74 Mon Sep 17 00:00:00 2001 From: AntoineF4C5 Date: Fri, 25 Apr 2025 16:35:26 -0500 Subject: [PATCH 1/3] batch polynomial evaluations - from 4aab459 --- examples/and_delegated.rs | 4 +- src/spartan/delegatedsnark.rs | 448 ++++++++++++++++++++++++++-------- src/spartan/direct_deleg.rs | 2 +- src/spartan/mod.rs | 41 ++++ src/spartan/spark/mod.rs | 256 ++++++++++++------- src/spartan/spark/sparse.rs | 184 +++++++------- src/spartan/sumcheck.rs | 100 ++++---- 7 files changed, 698 insertions(+), 337 deletions(-) diff --git a/examples/and_delegated.rs b/examples/and_delegated.rs index d3b277d79..6fd239dc2 100644 --- a/examples/and_delegated.rs +++ b/examples/and_delegated.rs @@ -21,8 +21,8 @@ type E1 = Bn256EngineKZG; type E2 = GrumpkinEngine; type EE1 = nova_snark::provider::hyperkzg::EvaluationEngine; type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine; -type S1 = nova_snark::spartan::delegatedsnark::RelaxedR1CSSNARK>; // non-preprocessing SNARK -type S2 = nova_snark::spartan::delegatedsnark::RelaxedR1CSSNARK>; // non-preprocessing SNARK +type S1 = nova_snark::spartan::delegatedsnark::RelaxedR1CSSNARK>; // non-preprocessing SNARK +type S2 = nova_snark::spartan::delegatedsnark::RelaxedR1CSSNARK>; // non-preprocessing SNARK // type S1 = nova_snark::spartan::ppsnark::RelaxedR1CSSNARK; // preprocessing SNARK // type S2 = nova_snark::spartan::snark::RelaxedR1CSSNARK; // preprocessing SNARK diff --git a/src/spartan/delegatedsnark.rs b/src/spartan/delegatedsnark.rs index 714c47ad7..c4decd9d1 100644 --- a/src/spartan/delegatedsnark.rs +++ b/src/spartan/delegatedsnark.rs @@ -19,13 +19,14 @@ use crate::{ }, spark::CompCommitmentEngineTrait, sumcheck::SumcheckProof, + PolyEvalInstance, PolyEvalWitness, }, traits::{ evaluation::EvaluationEngineTrait, snark::{DigestHelperTrait, RelaxedR1CSSNARKTrait}, Engine, TranscriptEngineTrait, }, - CommitmentKey, + Commitment, CommitmentKey, }; use ff::Field; use once_cell::sync::OnceCell; @@ -35,8 +36,7 @@ use serde::{Deserialize, Serialize}; /// A type that represents the prover's key #[derive(Serialize, Deserialize)] #[serde(bound = "")] -pub struct ProverKey, CC: CompCommitmentEngineTrait> -{ +pub struct ProverKey, CC: CompCommitmentEngineTrait> { pk_ee: EE::ProverKey, S: R1CSShape, decomm: CC::Decommitment, @@ -46,11 +46,7 @@ pub struct ProverKey, CC: CompCommitment /// A type that represents the verifier's key #[derive(Serialize, Deserialize)] #[serde(bound = "")] -pub struct VerifierKey< - E: Engine, - EE: EvaluationEngineTrait, - CC: CompCommitmentEngineTrait, -> { +pub struct VerifierKey, CC: CompCommitmentEngineTrait> { num_cons: usize, num_vars: usize, vk_ee: EE::VerifierKey, @@ -59,7 +55,7 @@ pub struct VerifierKey< digest: OnceCell, } -impl, CC: CompCommitmentEngineTrait> +impl, CC: CompCommitmentEngineTrait> VerifierKey { fn new(num_cons: usize, num_vars: usize, S_comm: CC::Commitment, vk_ee: EE::VerifierKey) -> Self { @@ -72,8 +68,8 @@ impl, CC: CompCommitmentEngineTrait, CC: CompCommitmentEngineTrait> - DigestHelperTrait for VerifierKey +impl, CC: CompCommitmentEngineTrait> DigestHelperTrait + for VerifierKey { /// Returns the digest of the verifier's key fn digest(&self) -> E::Scalar { @@ -88,7 +84,7 @@ impl, CC: CompCommitmentEngineTrait, CC: CompCommitmentEngineTrait> SimpleDigestible +impl, CC: CompCommitmentEngineTrait> SimpleDigestible for VerifierKey { } @@ -101,21 +97,24 @@ impl, CC: CompCommitmentEngineTrait, - CC: CompCommitmentEngineTrait, + CC: CompCommitmentEngineTrait, > { sc_proof_outer: SumcheckProof, claims_outer: (E::Scalar, E::Scalar, E::Scalar), eval_E: E::Scalar, sc_proof_inner: SumcheckProof, eval_W: E::Scalar, - sc_proof_batch: SumcheckProof, - eval_E_prime: E::Scalar, - eval_W_prime: E::Scalar, - eval_arg: EE::EvaluationArgument, + sc_proof_batch_prover: SumcheckProof, + eval_arg_prover: EE::EvaluationArgument, + evals_batch_prover: Vec, + eval_arg_cc: CC::EvaluationArgument, + sc_proof_batch_deleg: SumcheckProof, + eval_arg_deleg: EE::EvaluationArgument, + evals_batch_deleg: Vec, } -impl, CC: CompCommitmentEngineTrait> +impl, CC: CompCommitmentEngineTrait> RelaxedR1CSSNARKTrait for RelaxedR1CSSNARK { type ProverKey = ProverKey; @@ -233,19 +232,107 @@ impl, CC: CompCommitmentEngineTrait> = Vec::new(); + u_vec_prover.push(PolyEvalInstance { + c: U.comm_W, + x: r_y[1..].to_vec(), + e: self.eval_W, + }); + + u_vec_prover.push(PolyEvalInstance { + c: U.comm_E, + x: r_x.clone(), + e: self.eval_E, + }); + + let u_vec_padded_prover = PolyEvalInstance::pad(&u_vec_prover); // pad the evaluation points + + let powers = |s: &E::Scalar, n: usize| -> Vec { + assert!(n >= 1); + let mut powers = Vec::new(); + powers.push(E::Scalar::ONE); + for i in 1..n { + powers.push(powers[i - 1] * s); + } + powers + }; + + // generate a challenge + let rho = transcript.squeeze(b"r")?; + let num_claims = u_vec_prover.len(); + let powers_of_rho = powers(&rho, num_claims); + let claim_batch_joint_prover = u_vec_prover + .iter() + .zip(powers_of_rho.iter()) + .map(|(u, p)| u.e * p) + .fold(E::Scalar::ZERO, |acc, item| acc + item); + + let num_rounds_z = u_vec_padded_prover[0].x.len(); + let (claim_batch_prover_final, r_z) = self.sc_proof_batch_prover.verify( + claim_batch_joint_prover, + num_rounds_z, + 2, + &mut transcript, + )?; + + let claim_batch_prover_final_expected = { + let poly_rz = EqPolynomial::new(r_z.clone()); + let evals = u_vec_padded_prover + .iter() + .map(|u| poly_rz.evaluate(&u.x)) + .collect::>(); + + evals + .iter() + .zip(self.evals_batch_prover.iter()) + .zip(powers_of_rho.iter()) + .map(|((e_i, p_i), rho_i)| *e_i * *p_i * rho_i) + .fold(E::Scalar::ZERO, |acc, item| acc + item) + }; + + if claim_batch_prover_final != claim_batch_prover_final_expected { + return Err(NovaError::InvalidSumcheckProof); + } + + transcript.absorb(b"l", &self.evals_batch_prover.as_slice()); + + // we now combine evaluation claims at the same point rz into one + let gamma = transcript.squeeze(b"g")?; + let powers_of_gamma: Vec = powers(&gamma, num_claims); + let comm_joint_prover = u_vec_padded_prover + .iter() + .zip(powers_of_gamma.iter()) + .map(|(u, g_i)| u.c * *g_i) + .fold(Commitment::::default(), |acc, item| acc + item); + let eval_joint_prover = self + .evals_batch_prover + .iter() + .zip(powers_of_gamma.iter()) + .map(|(e, g_i)| *e * *g_i) + .fold(E::Scalar::ZERO, |acc, item| acc + item); + + // verify + EE::verify( + &vk.vk_ee, + &mut transcript, + &comm_joint_prover, + &r_z, + &eval_joint_prover, + &self.eval_arg_prover, + )?; + // verify evaluation argument to retrieve evaluations of R1CS matrices - // CHANGED TO USE A DIFFERENT TRANSCRIPT + // CHANEED TO USE A DIFFERENT TRANSCRIPT // this second transcript is fed with r_x and r_y - let mut transcript_Delegated = E::TE::new(b"RelaxedR1CSSNARK_Delegated"); - transcript_Delegated.absorb(b"r_x", &r_x.as_slice()); - transcript_Delegated.absorb(b"r_y", &r_y.as_slice()); - let (eval_A, eval_B, eval_C) = CC::verify( - &vk.vk_ee, + let mut transcript_delegated = E::TE::new(b"RelaxedR1CSSNARK_Delegated"); + transcript_delegated.absorb(b"r_x", &r_x.as_slice()); + transcript_delegated.absorb(b"r_y", &r_y.as_slice()); + let (eval_A, eval_B, eval_C, u_vec_deleg) = CC::verify( &vk.comm, &(&r_x, &r_y), &self.eval_arg_cc, - &mut transcript_Delegated, + &mut transcript_delegated, )?; let claim_inner_final_expected = (eval_A + r * eval_B + r * r * eval_C) * eval_Z; @@ -253,46 +340,70 @@ impl, CC: CompCommitmentEngineTrait>(); + + evals + .iter() + .zip(self.evals_batch_deleg.iter()) + .zip(powers_of_rho.iter()) + .map(|((e_i, p_i), rho_i)| *e_i * *p_i * rho_i) + .fold(E::Scalar::ZERO, |acc, item| acc + item) }; - if claim_batch_final != claim_batch_final_expected { + if claim_batch_deleg_final != claim_batch_deleg_final_expected { return Err(NovaError::InvalidSumcheckProof); } - transcript.absorb( - b"claims_batch", - &[self.eval_E_prime, self.eval_W_prime].as_slice(), - ); + transcript_delegated.absorb(b"l", &self.evals_batch_deleg.as_slice()); // we now combine evaluation claims at the same point rz into one - let gamma = transcript.squeeze(b"gamma")?; - let comm = U.comm_E + U.comm_W * gamma; - let eval = self.eval_E_prime + gamma * self.eval_W_prime; + let gamma = transcript_delegated.squeeze(b"g")?; + let powers_of_gamma: Vec = powers(&gamma, num_claims); + let comm_joint_deleg = u_vec_padded_deleg + .iter() + .zip(powers_of_gamma.iter()) + .map(|(u, g_i)| u.c * *g_i) + .fold(Commitment::::default(), |acc, item| acc + item); + let eval_joint_deleg = self + .evals_batch_deleg + .iter() + .zip(powers_of_gamma.iter()) + .map(|(e, g_i)| *e * *g_i) + .fold(E::Scalar::ZERO, |acc, item| acc + item); - // verify eval_W and eval_E + // verify EE::verify( &vk.vk_ee, - &mut transcript, - &comm, + &mut transcript_delegated, + &comm_joint_deleg, &r_z, - &eval, - &self.eval_arg, + &eval_joint_deleg, + &self.eval_arg_deleg, )?; Ok(()) @@ -341,10 +452,9 @@ pub struct RelaxedR1CSProver> { eval_E: E::Scalar, sc_proof_inner: SumcheckProof, eval_W: E::Scalar, - sc_proof_batch: SumcheckProof, - eval_E_prime: E::Scalar, - eval_W_prime: E::Scalar, - eval_arg: EE::EvaluationArgument, + sc_proof_batch_prover: SumcheckProof, + eval_arg_prover: EE::EvaluationArgument, + evals_batch_prover: Vec, } /// A type that represents the non-witness related part of the proof @@ -353,12 +463,15 @@ pub struct RelaxedR1CSProver> { pub struct RelaxedR1CSDelegated< E: Engine, EE: EvaluationEngineTrait, - CC: CompCommitmentEngineTrait, + CC: CompCommitmentEngineTrait, > { eval_arg_cc: CC::EvaluationArgument, + sc_proof_batch_deleg: SumcheckProof, + eval_arg_deleg: EE::EvaluationArgument, + evals_batch_deleg: Vec, } -impl, CC: CompCommitmentEngineTrait> Delegatable +impl, CC: CompCommitmentEngineTrait> Delegatable for RelaxedR1CSSNARK { type ProverProofPart = RelaxedR1CSProver; @@ -509,53 +622,111 @@ impl, CC: CompCommitmentEngineTrait= 2); + + let (w_vec, u_vec): (Vec>, Vec>) = + w_u_vec.into_iter().unzip(); + let w_vec_padded = PolyEvalWitness::pad(&w_vec); // pad the polynomials to be of the same size + let u_vec_padded = PolyEvalInstance::pad(&u_vec); // pad the evaluation points + + let powers = |s: &E::Scalar, n: usize| -> Vec { + assert!(n >= 1); + let mut powers = Vec::new(); + powers.push(E::Scalar::ONE); + for i in 1..n { + powers.push(powers[i - 1] * s); + } + powers + }; - let claim_batch_joint = eval_E + rho * eval_W; - let num_rounds_z = num_rounds_x; - let comb_func = - |poly_A_comp: &E::Scalar, - poly_B_comp: &E::Scalar, - poly_C_comp: &E::Scalar, - poly_D_comp: &E::Scalar| - -> E::Scalar { *poly_A_comp * *poly_B_comp + rho * *poly_C_comp * *poly_D_comp }; - let (sc_proof_batch, r_z, claims_batch) = SumcheckProof::prove_quad_sum( + // generate a challenge + let rho = transcript.squeeze(b"r")?; + let num_claims = w_vec_padded.len(); + let powers_of_rho = powers(&rho, num_claims); + let claim_batch_joint = u_vec_padded + .iter() + .zip(powers_of_rho.iter()) + .map(|(u, p)| u.e * p) + .fold(E::Scalar::ZERO, |acc, item| acc + item); + + let mut polys_left: Vec> = w_vec_padded + .iter() + .map(|w| MultilinearPolynomial::new(w.p.clone())) + .collect(); + let mut polys_right: Vec> = u_vec_padded + .iter() + .map(|u| MultilinearPolynomial::new(EqPolynomial::new(u.x.clone()).evals())) + .collect(); + + let num_rounds_z = u_vec_padded[0].x.len(); + let comb_func = |poly_A_comp: &E::Scalar, poly_B_comp: &E::Scalar| -> E::Scalar { + *poly_A_comp * *poly_B_comp + }; + let (sc_proof_batch_prover, r_z, claims_batch) = SumcheckProof::prove_quad_batch_scaled( &claim_batch_joint, num_rounds_z, - &mut MultilinearPolynomial::new(EqPolynomial::new(r_x.clone()).evals()), - &mut MultilinearPolynomial::new(W.E.clone()), - &mut MultilinearPolynomial::new(EqPolynomial::new(r_y[1..].to_vec()).evals()), - &mut MultilinearPolynomial::new(W.W.clone()), + &mut polys_left, + &mut polys_right, + &powers_of_rho, comb_func, &mut transcript, )?; - let eval_E_prime = claims_batch[1]; - let eval_W_prime = claims_batch[3]; - transcript.absorb(b"claims_batch", &[eval_E_prime, eval_W_prime].as_slice()); + let (claims_batch_left, _): (Vec, Vec) = claims_batch; + + transcript.absorb(b"l", &claims_batch_left.as_slice()); // we now combine evaluation claims at the same point rz into one - let gamma = transcript.squeeze(b"gamma")?; - let comm = U.comm_E + U.comm_W * gamma; - let poly = W - .E + let gamma = transcript.squeeze(b"g")?; + let powers_of_gamma: Vec = powers(&gamma, num_claims); + let comm_joint = u_vec_padded .iter() - .zip(W.W.iter()) - .map(|(e, w)| *e + gamma * w) - .collect::>(); - let eval = eval_E_prime + gamma * eval_W_prime; - - let eval_arg = EE::prove(ck, &pk.pk_ee, &mut transcript, &comm, &poly, &r_z, &eval)?; + .zip(powers_of_gamma.iter()) + .map(|(u, g_i)| u.c * *g_i) + .fold(Commitment::::default(), |acc, item| acc + item); + let poly_joint = PolyEvalWitness::weighted_sum(&w_vec_padded, &powers_of_gamma); + let eval_joint = claims_batch_left + .iter() + .zip(powers_of_gamma.iter()) + .map(|(e, g_i)| *e * *g_i) + .fold(E::Scalar::ZERO, |acc, item| acc + item); + let eval_arg_prover = EE::prove( + ck, + &pk.pk_ee, + &mut transcript, + &comm_joint, + &poly_joint.p, + &r_z, + &eval_joint, + )?; Ok(( Self::ProverProofPart { sc_proof_outer, @@ -563,10 +734,9 @@ impl, CC: CompCommitmentEngineTrait, CC: CompCommitmentEngineTrait>, Vec>) = + w_u_vec.into_iter().unzip(); + let w_vec_padded = PolyEvalWitness::pad(&w_vec); // pad the polynomials to be of the same size + let u_vec_padded = PolyEvalInstance::pad(&u_vec); // pad the evaluation points + + let powers = |s: &E::Scalar, n: usize| -> Vec { + assert!(n >= 1); + let mut powers = Vec::new(); + powers.push(E::Scalar::ONE); + for i in 1..n { + powers.push(powers[i - 1] * s); + } + powers + }; + + // generate a challenge + let rho = transcript.squeeze(b"r")?; + let num_claims = w_vec_padded.len(); + let powers_of_rho = powers(&rho, num_claims); + let claim_batch_joint = u_vec_padded + .iter() + .zip(powers_of_rho.iter()) + .map(|(u, p)| u.e * p) + .fold(E::Scalar::ZERO, |acc, item| acc + item); + + let mut polys_left: Vec> = w_vec_padded + .iter() + .map(|w| MultilinearPolynomial::new(w.p.clone())) + .collect(); + let mut polys_right: Vec> = u_vec_padded + .iter() + .map(|u| MultilinearPolynomial::new(EqPolynomial::new(u.x.clone()).evals())) + .collect(); + + let num_rounds_z = u_vec_padded[0].x.len(); + let comb_func = |poly_A_comp: &E::Scalar, poly_B_comp: &E::Scalar| -> E::Scalar { + *poly_A_comp * *poly_B_comp + }; + let (sc_proof_batch_deleg, r_z, claims_batch) = SumcheckProof::::prove_quad_batch_scaled( + &claim_batch_joint, + num_rounds_z, + &mut polys_left, + &mut polys_right, + &powers_of_rho, + comb_func, + &mut transcript, + )?; + + let (claims_batch_left, _): (Vec, Vec) = claims_batch; + + transcript.absorb(b"l", &claims_batch_left.as_slice()); + + // we now combine evaluation claims at the same point rz into one + let gamma = transcript.squeeze(b"g")?; + let powers_of_gamma: Vec = powers(&gamma, num_claims); + let comm_joint = u_vec_padded + .iter() + .zip(powers_of_gamma.iter()) + .map(|(u, g_i)| u.c * *g_i) + .fold(Commitment::::default(), |acc, item| acc + item); + let poly_joint = PolyEvalWitness::weighted_sum(&w_vec_padded, &powers_of_gamma); + let eval_joint = claims_batch_left + .iter() + .zip(powers_of_gamma.iter()) + .map(|(e, g_i)| *e * *g_i) + .fold(E::Scalar::ZERO, |acc, item| acc + item); + + let eval_arg_deleg = EE::prove( ck, &pk.pk_ee, - &pk.S, - &pk.decomm, - &pk.comm, - &r, &mut transcript, + &comm_joint, + &poly_joint.p, + &r_z, + &eval_joint, )?; - Ok(Self::DelegatedProofPart { eval_arg_cc }) + + Ok(Self::DelegatedProofPart { + eval_arg_cc, + sc_proof_batch_deleg, + eval_arg_deleg, + evals_batch_deleg: claims_batch_left, + }) } fn combine_proofs( @@ -604,10 +848,12 @@ impl, CC: CompCommitmentEngineTrait; - type CC2 = SparkEngine; + type CC2 = SparkEngine; type S2 = crate::spartan::snark::RelaxedR1CSSNARK; test_direct_snark_with::("snark", num_steps); diff --git a/src/spartan/mod.rs b/src/spartan/mod.rs index bdf9dd978..1e1825ff2 100644 --- a/src/spartan/mod.rs +++ b/src/spartan/mod.rs @@ -163,6 +163,32 @@ impl PolyEvalWitness { PolyEvalWitness { p } } + + fn pad(W: &[PolyEvalWitness]) -> Vec> { + // determine the maximum size + if let Some(n) = W.iter().map(|w| w.p.len()).max() { + W.iter() + .map(|w| { + let mut p = w.p.clone(); + p.resize(n, E::Scalar::ZERO); + PolyEvalWitness { p } + }) + .collect() + } else { + Vec::new() + } + } + + fn weighted_sum(W: &[PolyEvalWitness], s: &[E::Scalar]) -> PolyEvalWitness { + assert_eq!(W.len(), s.len()); + let mut p = vec![E::Scalar::ZERO; W[0].p.len()]; + for i in 0..W.len() { + for j in 0..W[i].p.len() { + p[j] += W[i].p[j] * s[i] + } + } + PolyEvalWitness { p } + } } /// A type that holds a polynomial evaluation instance @@ -239,6 +265,21 @@ impl PolyEvalInstance { e, } } + + fn pad(U: &[PolyEvalInstance]) -> Vec> { + // determine the maximum size + if let Some(ell) = U.iter().map(|u| u.x.len()).max() { + U.iter() + .map(|u| { + let mut x = vec![E::Scalar::ZERO; ell - u.x.len()]; + x.extend(u.x.clone()); + PolyEvalInstance { c: u.c, x, e: u.e } + }) + .collect() + } else { + Vec::new() + } + } } /// Bounds "row" variables of (A, B, C) matrices viewed as 2d multilinear polynomials diff --git a/src/spartan/spark/mod.rs b/src/spartan/spark/mod.rs index 4b6b74d57..6822586f3 100644 --- a/src/spartan/spark/mod.rs +++ b/src/spartan/spark/mod.rs @@ -3,13 +3,119 @@ use crate::{ errors::NovaError, r1cs::R1CSShape, - spartan::math::Math, + spartan::{math::Math, PolyEvalInstance, PolyEvalWitness}, traits::{evaluation::EvaluationEngineTrait, Engine, TranscriptReprTrait}, CommitmentKey, }; use core::marker::PhantomData; use serde::{Deserialize, Serialize}; +/* /// A type that holds a witness to a polynomial evaluation instance +#[allow(dead_code)] +pub struct PolyEvalWitness { + p: Vec, // polynomial +} + +impl PolyEvalWitness { + fn pad(W: &[PolyEvalWitness]) -> Vec> { + // determine the maximum size + if let Some(n) = W.iter().map(|w| w.p.len()).max() { + W.iter() + .map(|w| { + let mut p = w.p.clone(); + p.resize(n, E::Scalar::ZERO); + PolyEvalWitness { p } + }) + .collect() + } else { + Vec::new() + } + } + + fn weighted_sum(W: &[PolyEvalWitness], s: &[E::Scalar]) -> PolyEvalWitness { + assert_eq!(W.len(), s.len()); + let mut p = vec![E::Scalar::ZERO; W[0].p.len()]; + for i in 0..W.len() { + for j in 0..W[i].p.len() { + p[j] += W[i].p[j] * s[i] + } + } + PolyEvalWitness { p } + } +} + +/// A type that holds a polynomial evaluation instance +#[allow(dead_code)] +pub struct PolyEvalInstance { + c: Commitment, // commitment to the polynomial + x: Vec, // evaluation point + e: G::Scalar, // claimed evaluation +} + +impl PolyEvalInstance { + fn pad(U: &[PolyEvalInstance]) -> Vec> { + // determine the maximum size + if let Some(ell) = U.iter().map(|u| u.x.len()).max() { + U.iter() + .map(|u| { + let mut x = vec![G::Scalar::ZERO; ell - u.x.len()]; + x.extend(u.x.clone()); + PolyEvalInstance { c: u.c, x, e: u.e } + }) + .collect() + } else { + Vec::new() + } + } +} */ + +/// Engine to compute the commitment and decommitment for the Spark protocol +pub trait CompCommitmentEngineTrait { + /// A type that holds opening hint + type Decommitment: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>; + + /// A type that holds a commitment + type Commitment: Clone + + Send + + Sync + + TranscriptReprTrait + + Serialize + + for<'de> Deserialize<'de>; + + /// A type that holds an evaluation argument + type EvaluationArgument: Send + Sync + Serialize + for<'de> Deserialize<'de>; + + /// commits to R1CS matrices + fn commit( + ck: &CommitmentKey, + S: &R1CSShape, + ) -> Result<(Self::Commitment, Self::Decommitment), NovaError>; + + /// proves an evaluation of R1CS matrices viewed as polynomials + fn prove( + ck: &CommitmentKey, + S: &R1CSShape, + decomm: &Self::Decommitment, + comm: &Self::Commitment, + r: &(&[E::Scalar], &[E::Scalar]), + transcript: &mut E::TE, + ) -> Result< + ( + Self::EvaluationArgument, + Vec<(PolyEvalWitness, PolyEvalInstance)>, + ), + NovaError, + >; + + /// verifies an evaluation of R1CS matrices viewed as polynomials and returns verified evaluations + fn verify( + comm: &Self::Commitment, + r: &(&[E::Scalar], &[E::Scalar]), + arg: &Self::EvaluationArgument, + transcript: &mut E::TE, + ) -> Result<(E::Scalar, E::Scalar, E::Scalar, Vec>), NovaError>; +} + /// A trivial implementation of `ComputationCommitmentEngineTrait` pub struct TrivialCompComputationEngine> { _p: PhantomData, @@ -43,7 +149,7 @@ impl TranscriptReprTrait for TrivialCommitment { } } -impl> CompCommitmentEngineTrait +impl> CompCommitmentEngineTrait for TrivialCompComputationEngine { type Decommitment = TrivialDecommitment; @@ -66,29 +172,36 @@ impl> CompCommitmentEngineTrait /// proves an evaluation of R1CS matrices viewed as polynomials fn prove( _ck: &CommitmentKey, - _ek: &EE::ProverKey, _S: &R1CSShape, _decomm: &Self::Decommitment, _comm: &Self::Commitment, _r: &(&[E::Scalar], &[E::Scalar]), _transcript: &mut E::TE, - ) -> Result { - Ok(TrivialEvaluationArgument { - _p: Default::default(), - }) + ) -> Result< + ( + Self::EvaluationArgument, + Vec<(PolyEvalWitness, PolyEvalInstance)>, + ), + NovaError, + > { + Ok(( + TrivialEvaluationArgument { + _p: Default::default(), + }, + Vec::new(), + )) } /// verifies an evaluation of R1CS matrices viewed as polynomials fn verify( - _vk: &EE::VerifierKey, comm: &Self::Commitment, r: &(&[E::Scalar], &[E::Scalar]), _arg: &Self::EvaluationArgument, _transcript: &mut E::TE, - ) -> Result<(E::Scalar, E::Scalar, E::Scalar), NovaError> { + ) -> Result<(E::Scalar, E::Scalar, E::Scalar, Vec>), NovaError> { let (r_x, r_y) = r; let evals = SparsePolynomial::::multi_evaluate(&[&comm.S.A, &comm.S.B, &comm.S.C], r_x, r_y); - Ok((evals[0], evals[1], evals[2])) + Ok((evals[0], evals[1], evals[2], Vec::new())) } } @@ -98,9 +211,8 @@ mod sparse; use sparse::{SparseEvaluationArgument, SparsePolynomial, SparsePolynomialCommitment}; /// A non-trivial implementation of `CompCommitmentEngineTrait` using Spartan's SPARK compiler -pub struct SparkEngine> { +pub struct SparkEngine { _p: PhantomData, - _p2: PhantomData, } /// An implementation of Spark decommitment @@ -156,18 +268,16 @@ impl TranscriptReprTrait for SparkCommitment { /// Provides an implementation of a trivial evaluation argument #[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] -pub struct SparkEvaluationArgument> { - arg_A: SparseEvaluationArgument, - arg_B: SparseEvaluationArgument, - arg_C: SparseEvaluationArgument, +pub struct SparkEvaluationArgument { + arg_A: SparseEvaluationArgument, + arg_B: SparseEvaluationArgument, + arg_C: SparseEvaluationArgument, } -impl> CompCommitmentEngineTrait - for SparkEngine -{ +impl CompCommitmentEngineTrait for SparkEngine { type Decommitment = SparkDecommitment; type Commitment = SparkCommitment; - type EvaluationArgument = SparkEvaluationArgument; + type EvaluationArgument = SparkEvaluationArgument; /// commits to R1CS matrices fn commit( @@ -182,82 +292,60 @@ impl> CompCommitmentEngineTrait /// proves an evaluation of R1CS matrices viewed as polynomials fn prove( ck: &CommitmentKey, - pk_ee: &EE::ProverKey, S: &R1CSShape, decomm: &Self::Decommitment, comm: &Self::Commitment, r: &(&[E::Scalar], &[E::Scalar]), transcript: &mut E::TE, - ) -> Result { - let arg_A = - SparseEvaluationArgument::prove(ck, pk_ee, &decomm.A, &S.A, &comm.comm_A, r, transcript)?; - let arg_B = - SparseEvaluationArgument::prove(ck, pk_ee, &decomm.B, &S.B, &comm.comm_B, r, transcript)?; - let arg_C = - SparseEvaluationArgument::prove(ck, pk_ee, &decomm.C, &S.C, &comm.comm_C, r, transcript)?; - - Ok(SparkEvaluationArgument { - arg_A, - arg_B, - arg_C, - }) + ) -> Result< + ( + Self::EvaluationArgument, + Vec<(PolyEvalWitness, PolyEvalInstance)>, + ), + NovaError, + > { + let (arg_A, u_w_vec_A) = + SparseEvaluationArgument::prove(ck, &decomm.A, &S.A, &comm.comm_A, r, transcript)?; + let (arg_B, u_w_vec_B) = + SparseEvaluationArgument::prove(ck, &decomm.B, &S.B, &comm.comm_B, r, transcript)?; + let (arg_C, u_w_vec_C) = + SparseEvaluationArgument::prove(ck, &decomm.C, &S.C, &comm.comm_C, r, transcript)?; + + let u_w_vec = { + let mut u_w_vec = u_w_vec_A; + u_w_vec.extend(u_w_vec_B); + u_w_vec.extend(u_w_vec_C); + u_w_vec + }; + + Ok(( + SparkEvaluationArgument { + arg_A, + arg_B, + arg_C, + }, + u_w_vec, + )) } /// verifies an evaluation of R1CS matrices viewed as polynomials fn verify( - vk_ee: &EE::VerifierKey, comm: &Self::Commitment, r: &(&[E::Scalar], &[E::Scalar]), arg: &Self::EvaluationArgument, transcript: &mut E::TE, - ) -> Result<(E::Scalar, E::Scalar, E::Scalar), NovaError> { - let eval_A = arg.arg_A.verify(vk_ee, &comm.comm_A, r, transcript)?; - let eval_B = arg.arg_B.verify(vk_ee, &comm.comm_B, r, transcript)?; - let eval_C = arg.arg_C.verify(vk_ee, &comm.comm_C, r, transcript)?; - - Ok((eval_A, eval_B, eval_C)) + ) -> Result<(E::Scalar, E::Scalar, E::Scalar, Vec>), NovaError> { + let (eval_A, u_vec_A) = arg.arg_A.verify(&comm.comm_A, r, transcript)?; + let (eval_B, u_vec_B) = arg.arg_B.verify(&comm.comm_B, r, transcript)?; + let (eval_C, u_vec_C) = arg.arg_C.verify(&comm.comm_C, r, transcript)?; + + let u_vec = { + let mut u_vec = u_vec_A; + u_vec.extend(u_vec_B); + u_vec.extend(u_vec_C); + u_vec + }; + + Ok((eval_A, eval_B, eval_C, u_vec)) } } - -/// Engine to compute the commitment and decommitment for the Spark protocol -pub trait CompCommitmentEngineTrait> { - /// A type that holds opening hint - type Decommitment: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>; - - /// A type that holds a commitment - type Commitment: Clone - + Send - + Sync - + TranscriptReprTrait - + Serialize - + for<'de> Deserialize<'de>; - - /// A type that holds an evaluation argument - type EvaluationArgument: Send + Sync + Serialize + for<'de> Deserialize<'de>; - - /// commits to R1CS matrices - fn commit( - ck: &CommitmentKey, - S: &R1CSShape, - ) -> Result<(Self::Commitment, Self::Decommitment), NovaError>; - - /// proves an evaluation of R1CS matrices viewed as polynomials - fn prove( - ck: &CommitmentKey, - ek: &EE::ProverKey, - S: &R1CSShape, - decomm: &Self::Decommitment, - comm: &Self::Commitment, - r: &(&[E::Scalar], &[E::Scalar]), - transcript: &mut E::TE, - ) -> Result; - - /// verifies an evaluation of R1CS matrices viewed as polynomials and returns verified evaluations - fn verify( - vk: &EE::VerifierKey, - comm: &Self::Commitment, - r: &(&[E::Scalar], &[E::Scalar]), - arg: &Self::EvaluationArgument, - transcript: &mut E::TE, - ) -> Result<(E::Scalar, E::Scalar, E::Scalar), NovaError>; -} diff --git a/src/spartan/spark/sparse.rs b/src/spartan/spark/sparse.rs index b73c2c7ef..77950290a 100644 --- a/src/spartan/spark/sparse.rs +++ b/src/spartan/spark/sparse.rs @@ -9,11 +9,9 @@ use crate::{ polys::{eq::EqPolynomial, multilinear::MultilinearPolynomial}, spark::product::{IdentityPolynomial, ProductArgumentBatched}, sumcheck::SumcheckProof, + PolyEvalInstance, PolyEvalWitness, }, - traits::{ - commitment::CommitmentEngineTrait, evaluation::EvaluationEngineTrait, Engine, - TranscriptEngineTrait, TranscriptReprTrait, - }, + traits::{commitment::CommitmentEngineTrait, Engine, TranscriptEngineTrait, TranscriptReprTrait}, Commitment, CommitmentKey, }; use ff::Field; @@ -256,7 +254,7 @@ impl SparsePolynomial { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] -pub struct SparseEvaluationArgument> { +pub struct SparseEvaluationArgument { // claimed evaluation eval: E::Scalar, @@ -269,7 +267,6 @@ pub struct SparseEvaluationArgument> { eval_E_row: E::Scalar, eval_E_col: E::Scalar, eval_val: E::Scalar, - arg_eval: EE::EvaluationArgument, // proof that E_row is well-formed eval_init_row: E::Scalar, @@ -291,24 +288,23 @@ pub struct SparseEvaluationArgument> { eval_col_read_ts: E::Scalar, eval_E_col2: E::Scalar, eval_col_audit_ts: E::Scalar, - arg_row_col_joint: EE::EvaluationArgument, - arg_row_audit_ts: EE::EvaluationArgument, - arg_col_audit_ts: EE::EvaluationArgument, } -impl> SparseEvaluationArgument { +impl SparseEvaluationArgument { pub fn prove( ck: &CommitmentKey, - pk_ee: &EE::ProverKey, poly: &SparsePolynomial, sparse: &SparseMatrix, comm: &SparsePolynomialCommitment, r: &(&[E::Scalar], &[E::Scalar]), transcript: &mut E::TE, - ) -> Result { + ) -> Result<(Self, Vec<(PolyEvalWitness, PolyEvalInstance)>), NovaError> { let (r_x, r_y) = r; let eval = SparsePolynomial::::multi_evaluate(&[sparse], r_x, r_y)[0]; + // keep track of evaluation claims + let mut w_u_vec: Vec<(PolyEvalWitness, PolyEvalInstance)> = Vec::new(); + // compute oracles to prove the correctness of `eval` let (E_row, E_col, T_x, T_y) = SparsePolynomial::::evaluation_oracles(sparse, r_x, r_y); let val = poly.val.clone(); @@ -345,15 +341,16 @@ impl> SparseEvaluationArgument { .zip(val.iter()) .map(|((a, b), c)| *a + rho * *b + rho * rho * *c) .collect::>(); - let arg_eval = EE::prove( - ck, - pk_ee, - transcript, - &comm_joint, - &poly_eval, - &r_eval, - &eval_joint, - )?; + + // add the claim to prove for later + w_u_vec.push(( + PolyEvalWitness { p: poly_eval }, + PolyEvalInstance { + c: comm_joint, + x: r_eval, + e: eval_joint, + }, + )); // we now need to prove that E_row and E_col are well-formed // we use memory checking: H(INIT) * H(WS) =? H(RS) * H(FINAL) @@ -491,37 +488,41 @@ impl> SparseEvaluationArgument { }) .collect::>(); - let arg_row_col_joint = EE::prove( - ck, - pk_ee, - transcript, - &comm_joint, - &poly_joint, - &r_read_write_row_col, - &eval_joint, - )?; - - let arg_row_audit_ts = EE::prove( - ck, - pk_ee, - transcript, - &comm.comm_row_audit_ts, - &poly.row_audit_ts, - &r_init_audit_row, - &eval_row_audit_ts, - )?; - - let arg_col_audit_ts = EE::prove( - ck, - pk_ee, - transcript, - &comm.comm_col_audit_ts, - &poly.col_audit_ts, - &r_init_audit_col, - &eval_col_audit_ts, - )?; - - Ok(Self { + // add the claim to prove for later + w_u_vec.push(( + PolyEvalWitness { p: poly_joint }, + PolyEvalInstance { + c: comm_joint, + x: r_read_write_row_col, + e: eval_joint, + }, + )); + + transcript.absorb(b"a", &eval_row_audit_ts); // add evaluation to transcript, commitment is already in + w_u_vec.push(( + PolyEvalWitness { + p: poly.row_audit_ts.clone(), + }, + PolyEvalInstance { + c: comm.comm_row_audit_ts, + x: r_init_audit_row, + e: eval_row_audit_ts, + }, + )); + + transcript.absorb(b"a", &eval_col_audit_ts); // add evaluation to transcript, commitment is already in + w_u_vec.push(( + PolyEvalWitness { + p: poly.col_audit_ts.clone(), + }, + PolyEvalInstance { + c: comm.comm_col_audit_ts, + x: r_init_audit_col, + e: eval_col_audit_ts, + }, + )); + + let eval_arg = Self { // claimed evaluation eval, @@ -534,7 +535,6 @@ impl> SparseEvaluationArgument { eval_E_row: claims_eval[0], eval_E_col: claims_eval[1], eval_val: claims_eval[2], - arg_eval, // proof that E_row and E_row are well-formed eval_init_row: eval_init_audit_row[0], @@ -556,21 +556,22 @@ impl> SparseEvaluationArgument { eval_col_read_ts, eval_E_col2, eval_col_audit_ts, - arg_row_col_joint, - arg_row_audit_ts, - arg_col_audit_ts, - }) + }; + + Ok((eval_arg, w_u_vec)) } pub fn verify( &self, - vk_ee: &EE::VerifierKey, comm: &SparsePolynomialCommitment, r: &(&[E::Scalar], &[E::Scalar]), transcript: &mut E::TE, - ) -> Result { + ) -> Result<(E::Scalar, Vec>), NovaError> { let (r_x, r_y) = r; + // keep track of evaluation claims + let mut u_vec: Vec> = Vec::new(); + // append the transcript and scalar transcript.absorb(b"E", &vec![self.comm_E_row, self.comm_E_col].as_slice()); transcript.absorb(b"e", &self.eval); @@ -591,14 +592,13 @@ impl> SparseEvaluationArgument { let rho = transcript.squeeze(b"r")?; let comm_joint = self.comm_E_row + self.comm_E_col * rho + comm.comm_val * rho * rho; let eval_joint = self.eval_E_row + rho * self.eval_E_col + rho * rho * self.eval_val; - EE::verify( - vk_ee, - transcript, - &comm_joint, - &r_eval, - &eval_joint, - &self.arg_eval, - )?; + + // add the claim to prove for later + u_vec.push(PolyEvalInstance { + c: comm_joint, + x: r_eval, + e: eval_joint, + }); // (2) verify if E_row and E_col are well formed let gamma_1 = transcript.squeeze(b"g1")?; @@ -728,34 +728,26 @@ impl> SparseEvaluationArgument { + comm.comm_col * c * c * c + comm.comm_col_read_ts * c * c * c * c + self.comm_E_col * c * c * c * c * c; - - EE::verify( - vk_ee, - transcript, - &comm_joint, - &r_read_write_row_col, - &eval_joint, - &self.arg_row_col_joint, - )?; - - EE::verify( - vk_ee, - transcript, - &comm.comm_row_audit_ts, - &r_init_audit_row, - &self.eval_row_audit_ts, - &self.arg_row_audit_ts, - )?; - - EE::verify( - vk_ee, - transcript, - &comm.comm_col_audit_ts, - &r_init_audit_col, - &self.eval_col_audit_ts, - &self.arg_col_audit_ts, - )?; - - Ok(self.eval) + u_vec.push(PolyEvalInstance { + c: comm_joint, + x: r_read_write_row_col, + e: eval_joint, + }); + + transcript.absorb(b"a", &self.eval_row_audit_ts); // add evaluation to transcript, commitment is already in + u_vec.push(PolyEvalInstance { + c: comm.comm_row_audit_ts, + x: r_init_audit_row, + e: self.eval_row_audit_ts, + }); + + transcript.absorb(b"a", &self.eval_col_audit_ts); // add evaluation to transcript, commitment is already in + u_vec.push(PolyEvalInstance { + c: comm.comm_col_audit_ts, + x: r_init_audit_col, + e: self.eval_col_audit_ts, + }); + + Ok((self.eval, u_vec)) } } diff --git a/src/spartan/sumcheck.rs b/src/spartan/sumcheck.rs index 2ad4b8919..9034485ea 100644 --- a/src/spartan/sumcheck.rs +++ b/src/spartan/sumcheck.rs @@ -197,54 +197,51 @@ impl SumcheckProof { )) } - pub fn prove_quad_sum( + pub fn prove_quad_batch_scaled( claim: &E::Scalar, num_rounds: usize, - poly_A: &mut MultilinearPolynomial, - poly_B: &mut MultilinearPolynomial, - poly_C: &mut MultilinearPolynomial, - poly_D: &mut MultilinearPolynomial, + poly_A_vec: &mut Vec>, + poly_B_vec: &mut Vec>, + coeffs: &[E::Scalar], comb_func: F, transcript: &mut E::TE, - ) -> Result<(Self, Vec, Vec), NovaError> + ) -> Result<(Self, Vec, (Vec, Vec)), NovaError> where - F: Fn(&E::Scalar, &E::Scalar, &E::Scalar, &E::Scalar) -> E::Scalar + Sync, + F: Fn(&E::Scalar, &E::Scalar) -> E::Scalar, { + let mut e = *claim; let mut r: Vec = Vec::new(); - let mut polys: Vec> = Vec::new(); - let mut claim_per_round = *claim; - for _ in 0..num_rounds { - let poly = { + let mut quad_polys: Vec> = Vec::new(); + for _j in 0..num_rounds { + let mut evals: Vec<(E::Scalar, E::Scalar)> = Vec::new(); + + for (poly_A, poly_B) in poly_A_vec.iter().zip(poly_B_vec.iter()) { + let mut eval_point_0 = E::Scalar::ZERO; + let mut eval_point_2 = E::Scalar::ZERO; + let len = poly_A.len() / 2; + for i in 0..len { + // eval 0: bound_func is A(low) + eval_point_0 += comb_func(&poly_A[i], &poly_B[i]); + + // eval 2: bound_func is -A(low) + 2*A(high) + let poly_A_bound_point = poly_A[len + i] + poly_A[len + i] - poly_A[i]; + let poly_B_bound_point = poly_B[len + i] + poly_B[len + i] - poly_B[i]; + eval_point_2 += comb_func(&poly_A_bound_point, &poly_B_bound_point); + } - // Make an iterator returning the contributions to the evaluations - let (eval_point_0, eval_point_2) = (0..len) - .into_par_iter() - .map(|i| { - // eval 0: bound_func is A(low) - let eval_point_0 = comb_func(&poly_A[i], &poly_B[i], &poly_C[i], &poly_D[i]); - - // eval 2: bound_func is -A(low) + 2*A(high) - let poly_A_bound_point = poly_A[len + i] + poly_A[len + i] - poly_A[i]; - let poly_B_bound_point = poly_B[len + i] + poly_B[len + i] - poly_B[i]; - let poly_C_bound_point = poly_C[len + i] + poly_C[len + i] - poly_C[i]; - let poly_D_bound_point = poly_D[len + i] + poly_D[len + i] - poly_D[i]; - let eval_point_2 = comb_func( - &poly_A_bound_point, - &poly_B_bound_point, - &poly_C_bound_point, - &poly_D_bound_point, - ); - (eval_point_0, eval_point_2) - }) - .reduce( - || (E::Scalar::ZERO, E::Scalar::ZERO), - |a, b| (a.0 + b.0, a.1 + b.1), - ); + evals.push((eval_point_0, eval_point_2)); + } - let evals = vec![eval_point_0, claim_per_round - eval_point_0, eval_point_2]; - UniPoly::from_evals(&evals) - }; + let evals_combined_0 = (0..evals.len()) + .map(|i| evals[i].0 * coeffs[i]) + .fold(E::Scalar::ZERO, |acc, item| acc + item); + let evals_combined_2 = (0..evals.len()) + .map(|i| evals[i].1 * coeffs[i]) + .fold(E::Scalar::ZERO, |acc, item| acc + item); + + let evals = vec![evals_combined_0, e - evals_combined_0, evals_combined_2]; + let poly = UniPoly::from_evals(&evals); // append the prover's message to the transcript transcript.absorb(b"p", &poly); @@ -252,25 +249,22 @@ impl SumcheckProof { // derive the verifier's challenge for the next round let r_i = transcript.squeeze(b"c")?; r.push(r_i); - polys.push(poly.compress()); - - // Set up next round - claim_per_round = poly.evaluate(&r_i); // bound all tables to the verifier's challenege - poly_A.bind_poly_var_top(&r_i); - poly_B.bind_poly_var_top(&r_i); - poly_C.bind_poly_var_top(&r_i); - poly_D.bind_poly_var_top(&r_i); + for (poly_A, poly_B) in poly_A_vec.iter_mut().zip(poly_B_vec.iter_mut()) { + poly_A.bind_poly_var_top(&r_i); + poly_B.bind_poly_var_top(&r_i); + } + + e = poly.evaluate(&r_i); + quad_polys.push(poly.compress()); } - Ok(( - SumcheckProof { - compressed_polys: polys, - }, - r, - vec![poly_A[0], poly_B[0], poly_C[0], poly_D[0]], - )) + let poly_A_final = (0..poly_A_vec.len()).map(|i| poly_A_vec[i][0]).collect(); + let poly_B_final = (0..poly_B_vec.len()).map(|i| poly_B_vec[i][0]).collect(); + let claims_prod = (poly_A_final, poly_B_final); + + Ok((SumcheckProof::new(quad_polys), r, claims_prod)) } pub fn prove_quad_batch( From e04a242e131ef0f6eb532fb03ebb72d3482906b1 Mon Sep 17 00:00:00 2001 From: AntoineF4C5 Date: Mon, 28 Apr 2025 15:32:34 -0500 Subject: [PATCH 2/3] optimisations from commit 4aab459 --- examples/and_delegated.rs | 4 +- src/spartan/direct.rs | 347 +++++++++++++++++++++++++++++++++++++- 2 files changed, 341 insertions(+), 10 deletions(-) diff --git a/examples/and_delegated.rs b/examples/and_delegated.rs index 58d46be53..727b54626 100644 --- a/examples/and_delegated.rs +++ b/examples/and_delegated.rs @@ -21,8 +21,8 @@ type E1 = Bn256EngineKZG; type E2 = GrumpkinEngine; type EE1 = nova_snark::provider::hyperkzg::EvaluationEngine; type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine; -type S1 = nova_snark::spartan::delegatedsnark::RelaxedR1CSSNARK>; -type S2 = nova_snark::spartan::delegatedsnark::RelaxedR1CSSNARK>; +type S1 = nova_snark::spartan::delegatedsnark::RelaxedR1CSSNARK>; +type S2 = nova_snark::spartan::delegatedsnark::RelaxedR1CSSNARK>; #[derive(Clone, Debug)] struct AndInstance { diff --git a/src/spartan/direct.rs b/src/spartan/direct.rs index 5b8295088..52101bce4 100644 --- a/src/spartan/direct.rs +++ b/src/spartan/direct.rs @@ -11,6 +11,7 @@ use crate::{ Circuit, ConstraintSystem, SynthesisError, }, r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness}, + spartan::delegatedsnark::Delegatable, traits::{ circuit::StepCircuit, commitment::CommitmentEngineTrait, @@ -192,16 +193,90 @@ impl, C: StepCircuit> DirectSN } } +impl, C: StepCircuit> DirectSNARK { + /// Prepare instance and witness + pub fn setup_u_w( + pk: &ProverKey, + sc: C, + z_i: &[E::Scalar], + ) -> Result< + ( + Commitment, + E::Scalar, + RelaxedR1CSInstance, + RelaxedR1CSWitness, + ), + NovaError, + > { + let mut cs = SatisfyingAssignment::::new(); + + let circuit: DirectCircuit = DirectCircuit { + z_i: Some(z_i.to_vec()), + sc, + }; + + let _ = circuit.synthesize(&mut cs); + let (u, w) = cs + .r1cs_instance_and_witness(&pk.S, &pk.ck) + .map_err(|_e| NovaError::UnSat { + reason: "Unable to generate a satisfying witness".to_string(), + })?; + + // convert the instance and witness to relaxed form + let (u_relaxed, w_relaxed) = ( + RelaxedR1CSInstance::from_r1cs_instance_unchecked(&u.comm_W, &u.X), + RelaxedR1CSWitness::from_r1cs_witness(&pk.S, &w), + ); + + let (derandom_w_relaxed, blind_W, blind_E) = w_relaxed.derandomize(); + let derandom_u_relaxed = u_relaxed.derandomize(&E::CE::derand_key(&pk.ck), &blind_W, &blind_E); + + Ok(( + u.comm_W, + w_relaxed.r_W, + derandom_u_relaxed, + derandom_w_relaxed, + )) + } + + /// Builds witness-related part of proof + pub fn prover_step( + pk: &ProverKey, + u: &RelaxedR1CSInstance, + w: &RelaxedR1CSWitness, + ) -> Result<(S::ProverProofPart, (Vec, Vec)), NovaError> { + // prove the instance using Spartan + let (prover_part, r) = S::prover_step(&pk.ck, &pk.pk, &pk.S, u, w)?; + + Ok((prover_part, r)) + } + + /// Builds non-witness-related part of proof + pub fn delegated_step( + pk: &ProverKey, + r: (&[E::Scalar], &[E::Scalar]), + ) -> Result { + // prove the instance using Spartan + let delegated_part = S::delegated_step(&pk.ck, &pk.pk, &pk.S, r)?; + + Ok(delegated_part) + } +} + #[cfg(test)] mod tests { use super::*; use crate::{ - frontend::{num::AllocatedNum, ConstraintSystem, SynthesisError}, + frontend::{ + num::AllocatedNum, AllocatedBit, ConstraintSystem, LinearCombination, SynthesisError, + }, provider::{Bn256EngineKZG, PallasEngine, Secp256k1Engine}, - spartan::spark::{SparkEngine, TrivialCompComputationEngine}, + spartan::spark::SparkEngine, }; use core::marker::PhantomData; - use ff::PrimeField; + use ff::{PrimeField, PrimeFieldBits}; + use rand::Rng; + use std::time::Instant; #[derive(Clone, Debug, Default)] struct CubicCircuit { @@ -317,15 +392,271 @@ mod tests { assert_eq!(z_i, vec![::Scalar::from(2460515u64)]); } + #[derive(Clone, Debug)] + struct AndInstance { + a: u64, + b: u64, + _p: PhantomData, + } + + impl AndInstance { + // produces an AND instance + fn new() -> Self { + let mut rng = rand::thread_rng(); + let a: u64 = rng.gen(); + let b: u64 = rng.gen(); + Self { + a, + b, + _p: PhantomData, + } + } + } + + #[derive(Clone, Debug)] + struct AndCircuit { + batch: Vec>, + } + + impl AndCircuit { + // produces a batch of AND instances + fn new(num_ops_per_step: usize) -> Self { + let mut batch = Vec::new(); + for _ in 0..num_ops_per_step { + batch.push(AndInstance::new()); + } + Self { batch } + } + } + + pub fn u64_into_bit_vec_le>( + mut cs: CS, + value: Option, + ) -> Result, SynthesisError> { + let values = match value { + Some(ref value) => { + let mut tmp = Vec::with_capacity(64); + + for i in 0..64 { + tmp.push(Some((*value >> i) & 1 == 1)); + } + + tmp + } + None => vec![None; 64], + }; + + let bits = values + .into_iter() + .enumerate() + .map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("bit {}", i)), b)) + .collect::, SynthesisError>>()?; + + Ok(bits) + } + + /// Gets as input the little indian representation of a number and spits out the number + pub fn le_bits_to_num( + mut cs: CS, + bits: &[AllocatedBit], + ) -> Result, SynthesisError> + where + Scalar: PrimeField + PrimeFieldBits, + CS: ConstraintSystem, + { + // We loop over the input bits and construct the constraint + // and the field element that corresponds to the result + let mut lc = LinearCombination::zero(); + let mut coeff = Scalar::ONE; + let mut fe = Some(Scalar::ZERO); + for bit in bits.iter() { + lc = lc + (coeff, bit.get_variable()); + fe = bit.get_value().map(|val| { + if val { + fe.unwrap() + coeff + } else { + fe.unwrap() + } + }); + coeff = coeff.double(); + } + let num = AllocatedNum::alloc(cs.namespace(|| "Field element"), || { + fe.ok_or(SynthesisError::AssignmentMissing) + })?; + lc = lc - num.get_variable(); + cs.enforce(|| "compute number from bits", |lc| lc, |lc| lc, |_| lc); + Ok(num) + } + + impl StepCircuit for AndCircuit { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + z_in: &[AllocatedNum], + ) -> Result>, SynthesisError> { + for i in 0..self.batch.len() { + // allocate a and b as field elements + let a = AllocatedNum::alloc(cs.namespace(|| format!("a_{}", i)), || { + Ok(E::Scalar::from(self.batch[i].a)) + })?; + let b = AllocatedNum::alloc(cs.namespace(|| format!("b_{}", i)), || { + Ok(E::Scalar::from(self.batch[i].b)) + })?; + + // obtain bit representations of a and b + let a_bits = u64_into_bit_vec_le( + cs.namespace(|| format!("a_bits_{}", i)), + Some(self.batch[i].a), + )?; // little endian + let b_bits = u64_into_bit_vec_le( + cs.namespace(|| format!("b_bits_{}", i)), + Some(self.batch[i].b), + )?; // little endian + + // enforce that bits of a and b are correct + let a_from_bits = le_bits_to_num(cs.namespace(|| format!("a_{}", i)), &a_bits)?; + let b_from_bits = le_bits_to_num(cs.namespace(|| format!("b_{}", i)), &b_bits)?; + + cs.enforce( + || format!("a_{} == a_from_bits", i), + |lc| lc + a.get_variable(), + |lc| lc + CS::one(), + |lc| lc + a_from_bits.get_variable(), + ); + cs.enforce( + || format!("b_{} == b_from_bits", i), + |lc| lc + b.get_variable(), + |lc| lc + CS::one(), + |lc| lc + b_from_bits.get_variable(), + ); + + let mut c_bits = Vec::new(); + + // perform bitwise AND + for i in 0..64 { + let c_bit = AllocatedBit::and( + cs.namespace(|| format!("and_bit_{}", i)), + &a_bits[i], + &b_bits[i], + )?; + c_bits.push(c_bit); + } + + // convert back to an allocated num + let c_from_bits = le_bits_to_num(cs.namespace(|| format!("c_{}", i)), &c_bits)?; + + let c = AllocatedNum::alloc(cs.namespace(|| format!("c_{}", i)), || { + Ok(E::Scalar::from(self.batch[i].a & self.batch[i].b)) + })?; + + // enforce that c is correct + cs.enforce( + || format!("c_{} == c_from_bits", i), + |lc| lc + c.get_variable(), + |lc| lc + CS::one(), + |lc| lc + c_from_bits.get_variable(), + ); + } + + Ok(z_in.to_vec()) + } + } + + impl AndCircuit { + fn output(&self, z: &[E::Scalar]) -> Vec { + vec![z[0]] + } + } + #[test] fn test_delegated_direct_snark() { type E = PallasEngine; type EE = crate::provider::ipa_pc::EvaluationEngine; - type S = - crate::spartan::delegatedsnark::RelaxedR1CSSNARK>; - test_direct_snark_with::(); - type Spp = crate::spartan::delegatedsnark::RelaxedR1CSSNARK>; - test_direct_snark_with::(); + type Spp = crate::spartan::delegatedsnark::RelaxedR1CSSNARK>; + test_direct_deleg_snark_with::("Delegaged SNARK", 1); + } + + fn test_direct_deleg_snark_with>( + proof_type: &str, + num_steps: usize, + ) { + let circuit = AndCircuit::new(num_steps); + + // produce keys + let (pk, vk) = DirectSNARK::>::setup(circuit.clone()).unwrap(); + + // setup inputs + let z0 = vec![::Scalar::ZERO]; + let mut z_i = z0; + + let total = Instant::now(); + // produce a SNARK + let (comm_W, r_W, u, w) = + DirectSNARK::>::setup_u_w(&pk, circuit.clone(), &z_i).unwrap(); + + let start = Instant::now(); + let (prover_step, r) = DirectSNARK::>::prover_step(&pk, &u, &w).unwrap(); + println!( + "Time elapsed for witness-related part of proving with {} is: {:?}", + proof_type, + start.elapsed() + ); + + let mut encoder = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default()); + bincode::serialize_into(&mut encoder, &prover_step).unwrap(); + let compressed_prover_step_encoded = encoder.finish().unwrap(); + println!( + "Compressed prover step len {:?} bytes", + compressed_prover_step_encoded.len() + ); + + let r = (r.0.as_slice(), r.1.as_slice()); + let start = Instant::now(); + let delegated_step = DirectSNARK::>::delegated_step(&pk, r).unwrap(); + println!( + "Time elapsed for delegated part of proving with {} is: {:?}", + proof_type, + start.elapsed() + ); + + let mut encoder = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default()); + bincode::serialize_into(&mut encoder, &delegated_step).unwrap(); + let compressed_delegated_step_encoded = encoder.finish().unwrap(); + println!( + "Compressed delegated step len {:?} bytes", + compressed_delegated_step_encoded.len() + ); + + println!( + "Total time elapsed for proving with {} is: {:?}\n", + proof_type, + total.elapsed() + ); + + let z_i_plus_one = circuit.output(&z_i); + + let snark = DirectSNARK::> { + comm_W, + blind_r_W: r_W, + snark: S::combine_proofs(prover_step, delegated_step), + _p: PhantomData, + }; + + // verify the SNARK + let io = z_i + .clone() + .into_iter() + .chain(z_i_plus_one.clone()) + .collect::>(); + let res = snark.verify(&vk, &io); + assert!(res.is_ok()); + + // set input to the next step + z_i.clone_from(&z_i_plus_one); } } From ea9874741fb8784f27515922f36380c028d523cb Mon Sep 17 00:00:00 2001 From: AntoineF4C5 Date: Wed, 11 Jun 2025 15:09:08 -0500 Subject: [PATCH 3/3] clean and add api to create new DirectSNARK --- examples/and_delegated.rs | 2 +- src/spartan/direct.rs | 19 +++++++++---- src/spartan/spark/mod.rs | 59 --------------------------------------- 3 files changed, 15 insertions(+), 65 deletions(-) diff --git a/examples/and_delegated.rs b/examples/and_delegated.rs index 727b54626..0953b32cc 100644 --- a/examples/and_delegated.rs +++ b/examples/and_delegated.rs @@ -198,7 +198,7 @@ impl StepCircuit for AndCircuit { } } -/// cargo run --release --example and +/// cargo run --release --example and_delegated fn main() { println!("========================================================="); println!("Nova-based 64-bit bitwise AND example"); diff --git a/src/spartan/direct.rs b/src/spartan/direct.rs index 52101bce4..64f779b5e 100644 --- a/src/spartan/direct.rs +++ b/src/spartan/direct.rs @@ -114,6 +114,16 @@ where } impl, C: StepCircuit> DirectSNARK { + /// Creates a new direct SNARK + pub fn new(comm_W: Commitment, blind_r_W: E::Scalar, snark: S) -> Self { + DirectSNARK { + comm_W, + blind_r_W, + snark, + _p: PhantomData, + } + } + /// Produces prover and verifier keys for the direct SNARK pub fn setup(sc: C) -> Result<(ProverKey, VerifierKey), NovaError> { // construct a circuit that can be synthesized @@ -640,12 +650,11 @@ mod tests { let z_i_plus_one = circuit.output(&z_i); - let snark = DirectSNARK::> { + let snark = DirectSNARK::>::new( comm_W, - blind_r_W: r_W, - snark: S::combine_proofs(prover_step, delegated_step), - _p: PhantomData, - }; + r_W, + S::combine_proofs(prover_step, delegated_step), + ); // verify the SNARK let io = z_i diff --git a/src/spartan/spark/mod.rs b/src/spartan/spark/mod.rs index 6fc69ee60..0eaa13b5b 100644 --- a/src/spartan/spark/mod.rs +++ b/src/spartan/spark/mod.rs @@ -10,65 +10,6 @@ use crate::{ use core::marker::PhantomData; use serde::{Deserialize, Serialize}; -/* /// A type that holds a witness to a polynomial evaluation instance -#[allow(dead_code)] -pub struct PolyEvalWitness { - p: Vec, // polynomial -} - -impl PolyEvalWitness { - fn pad(W: &[PolyEvalWitness]) -> Vec> { - // determine the maximum size - if let Some(n) = W.iter().map(|w| w.p.len()).max() { - W.iter() - .map(|w| { - let mut p = w.p.clone(); - p.resize(n, E::Scalar::ZERO); - PolyEvalWitness { p } - }) - .collect() - } else { - Vec::new() - } - } - - fn weighted_sum(W: &[PolyEvalWitness], s: &[E::Scalar]) -> PolyEvalWitness { - assert_eq!(W.len(), s.len()); - let mut p = vec![E::Scalar::ZERO; W[0].p.len()]; - for i in 0..W.len() { - for j in 0..W[i].p.len() { - p[j] += W[i].p[j] * s[i] - } - } - PolyEvalWitness { p } - } -} - -/// A type that holds a polynomial evaluation instance -#[allow(dead_code)] -pub struct PolyEvalInstance { - c: Commitment, // commitment to the polynomial - x: Vec, // evaluation point - e: G::Scalar, // claimed evaluation -} - -impl PolyEvalInstance { - fn pad(U: &[PolyEvalInstance]) -> Vec> { - // determine the maximum size - if let Some(ell) = U.iter().map(|u| u.x.len()).max() { - U.iter() - .map(|u| { - let mut x = vec![G::Scalar::ZERO; ell - u.x.len()]; - x.extend(u.x.clone()); - PolyEvalInstance { c: u.c, x, e: u.e } - }) - .collect() - } else { - Vec::new() - } - } -} */ - /// Engine to compute the commitment and decommitment for the Spark protocol pub trait CompCommitmentEngineTrait { /// A type that holds opening hint