From 4aab459050df3e386cc0d84f0ce0cfbfa76af12b Mon Sep 17 00:00:00 2001 From: Srinath Setty Date: Mon, 27 Mar 2023 17:59:52 -0700 Subject: [PATCH] Batch polynomial evaluations (#154) * Ability to collect evaluation claims * defer polynomial evaluation claims * address cargo clippy --- Cargo.toml | 2 +- src/lib.rs | 4 +- src/spartan/mod.rs | 305 +++++++++++++++++++++++++++--------- src/spartan/spark/mod.rs | 107 ++++++++----- src/spartan/spark/sparse.rs | 184 +++++++++++----------- src/spartan/sumcheck.rs | 99 ++++++------ 6 files changed, 436 insertions(+), 265 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a11e9f1..2c186a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nova-snark" -version = "0.19.0" +version = "0.19.1" authors = ["Srinath Setty "] edition = "2021" description = "Recursive zkSNARKs without trusted setup" diff --git a/src/lib.rs b/src/lib.rs index 6f424a6..171f2f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1095,8 +1095,8 @@ mod tests { assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); // run the compressed snark with Spark compiler - type CC1Prime = spartan::spark::SparkEngine; - type CC2Prime = spartan::spark::SparkEngine; + type CC1Prime = spartan::spark::SparkEngine; + type CC2Prime = spartan::spark::SparkEngine; type S1Prime = spartan::RelaxedR1CSSNARK; type S2Prime = spartan::RelaxedR1CSSNARK; diff --git a/src/spartan/mod.rs b/src/spartan/mod.rs index b6834d2..18fcde8 100644 --- a/src/spartan/mod.rs +++ b/src/spartan/mod.rs @@ -12,7 +12,7 @@ use crate::{ evaluation::EvaluationEngineTrait, snark::RelaxedR1CSSNARKTrait, Group, TranscriptEngineTrait, TranscriptReprTrait, }, - CommitmentKey, + Commitment, CommitmentKey, }; use ff::Field; use itertools::concat; @@ -21,8 +21,67 @@ use rayon::prelude::*; use serde::{Deserialize, Serialize}; use sumcheck::SumcheckProof; +/// 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, G::Scalar::zero()); + PolyEvalWitness { p } + }) + .collect() + } else { + Vec::new() + } + } + + fn weighted_sum(W: &[PolyEvalWitness], s: &[G::Scalar]) -> PolyEvalWitness { + assert_eq!(W.len(), s.len()); + let mut p = vec![G::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() + } + } +} + /// A trait that defines the behavior of a computation commitment engine -pub trait CompCommitmentEngineTrait> { +pub trait CompCommitmentEngineTrait { /// A type that holds opening hint type Decommitment: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>; @@ -46,22 +105,26 @@ pub trait CompCommitmentEngineTrait, - ek: &EE::ProverKey, S: &R1CSShape, decomm: &Self::Decommitment, comm: &Self::Commitment, r: &(&[G::Scalar], &[G::Scalar]), transcript: &mut G::TE, - ) -> Result; + ) -> Result< + ( + Self::EvaluationArgument, + Vec<(PolyEvalWitness, PolyEvalInstance)>, + ), + NovaError, + >; /// verifies an evaluation of R1CS matrices viewed as polynomials and returns verified evaluations fn verify( - vk: &EE::VerifierKey, comm: &Self::Commitment, r: &(&[G::Scalar], &[G::Scalar]), arg: &Self::EvaluationArgument, transcript: &mut G::TE, - ) -> Result<(G::Scalar, G::Scalar, G::Scalar), NovaError>; + ) -> Result<(G::Scalar, G::Scalar, G::Scalar, Vec>), NovaError>; } /// A type that represents the prover's key @@ -70,7 +133,7 @@ pub trait CompCommitmentEngineTrait, - CC: CompCommitmentEngineTrait, + CC: CompCommitmentEngineTrait, > { pk_ee: EE::ProverKey, S: R1CSShape, @@ -84,7 +147,7 @@ pub struct ProverKey< pub struct VerifierKey< G: Group, EE: EvaluationEngineTrait, - CC: CompCommitmentEngineTrait, + CC: CompCommitmentEngineTrait, > { num_cons: usize, num_vars: usize, @@ -100,21 +163,20 @@ pub struct VerifierKey< pub struct RelaxedR1CSSNARK< G: Group, EE: EvaluationEngineTrait, - CC: CompCommitmentEngineTrait, + CC: CompCommitmentEngineTrait, > { sc_proof_outer: SumcheckProof, claims_outer: (G::Scalar, G::Scalar, G::Scalar), eval_E: G::Scalar, sc_proof_inner: SumcheckProof, eval_W: G::Scalar, + eval_arg_cc: CC::EvaluationArgument, sc_proof_batch: SumcheckProof, - eval_E_prime: G::Scalar, - eval_W_prime: G::Scalar, + evals_batch: Vec, eval_arg: EE::EvaluationArgument, - eval_arg_cc: CC::EvaluationArgument, } -impl, CC: CompCommitmentEngineTrait> +impl, CC: CompCommitmentEngineTrait> RelaxedR1CSSNARKTrait for RelaxedR1CSSNARK { type ProverKey = ProverKey; @@ -292,9 +354,8 @@ impl, CC: CompCommitmentEngin )?; // we now prove evaluations of R1CS matrices at (r_x, r_y) - let eval_arg_cc = CC::prove( + let (eval_arg_cc, mut w_u_vec) = CC::prove( ck, - &pk.pk_ee, &pk.S, &pk.decomm, &pk.comm, @@ -302,52 +363,111 @@ impl, CC: CompCommitmentEngin &mut transcript, )?; - let eval_W = MultilinearPolynomial::new(W.W.clone()).evaluate(&r_y[1..]); - transcript.absorb(b"eval_W", &eval_W); - - // We will now reduce eval_W =? W(r_y[1..]) and eval_W =? E(r_x) into + // add additional claims about W and E polynomials to the list from CC + let eval_W = MultilinearPolynomial::evaluate_with(&W.W, &r_y[1..]); + w_u_vec.push(( + PolyEvalWitness { p: W.W.clone() }, + PolyEvalInstance { + c: U.comm_W, + x: r_y[1..].to_vec(), + e: eval_W, + }, + )); + + w_u_vec.push(( + PolyEvalWitness { p: W.E }, + PolyEvalInstance { + c: U.comm_E, + x: r_x, + e: eval_E, + }, + )); + + // We will now reduce a vector of claims of evaluations at different points into claims about them at the same point. + // For example, eval_W =? W(r_y[1..]) and eval_W =? E(r_x) into // two claims: eval_W_prime =? W(rz) and eval_E_prime =? E(rz) // We can them combine the two into one: eval_W_prime + gamma * eval_E_prime =? (W + gamma*E)(rz), // where gamma is a public challenge // Since commitments to W and E are homomorphic, the verifier can compute a commitment // to the batched polynomial. - let rho = transcript.squeeze(b"rho")?; + assert!(w_u_vec.len() >= 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: &G::Scalar, n: usize| -> Vec { + assert!(n >= 1); + let mut powers = Vec::new(); + powers.push(G::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: &G::Scalar, - poly_B_comp: &G::Scalar, - poly_C_comp: &G::Scalar, - poly_D_comp: &G::Scalar| - -> G::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(G::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: &G::Scalar, poly_B_comp: &G::Scalar| -> G::Scalar { + *poly_A_comp * *poly_B_comp + }; + let (sc_proof_batch, r_z, claims_batch) = SumcheckProof::prove_quad_batch( &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(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(W.W.iter()) - .map(|(e, w)| *e + gamma * w) - .collect::>(); - let eval = eval_E_prime + gamma * eval_W_prime; + .zip(powers_of_gamma.iter()) + .map(|(e, g_i)| *e * *g_i) + .fold(G::Scalar::zero(), |acc, item| acc + item); - let eval_arg = EE::prove(ck, &pk.pk_ee, &mut transcript, &comm, &poly, &r_z, &eval)?; + let eval_arg = EE::prove( + ck, + &pk.pk_ee, + &mut transcript, + &comm_joint, + &poly_joint.p, + &r_z, + &eval_joint, + )?; Ok(RelaxedR1CSSNARK { sc_proof_outer, @@ -355,11 +475,10 @@ impl, CC: CompCommitmentEngin eval_E, sc_proof_inner, eval_W, + eval_arg_cc, sc_proof_batch, - eval_E_prime, - eval_W_prime, + evals_batch: claims_batch_left, eval_arg, - eval_arg_cc, }) } @@ -433,25 +552,50 @@ impl, CC: CompCommitmentEngin }; // verify evaluation argument to retrieve evaluations of R1CS matrices - let (eval_A, eval_B, eval_C) = CC::verify( - &vk.vk_ee, - &vk.comm, - &(&r_x, &r_y), - &self.eval_arg_cc, - &mut transcript, - )?; + let (eval_A, eval_B, eval_C, mut u_vec) = + CC::verify(&vk.comm, &(&r_x, &r_y), &self.eval_arg_cc, &mut transcript)?; let claim_inner_final_expected = (eval_A + r * eval_B + r * r * eval_C) * eval_Z; if claim_inner_final != claim_inner_final_expected { return Err(NovaError::InvalidSumcheckProof); } - // batch sum-check - transcript.absorb(b"eval_W", &self.eval_W); + // add additional claims about W and E polynomials to the list from CC + u_vec.push(PolyEvalInstance { + c: U.comm_W, + x: r_y[1..].to_vec(), + e: self.eval_W, + }); + + u_vec.push(PolyEvalInstance { + c: U.comm_E, + x: r_x, + e: self.eval_E, + }); + + let u_vec_padded = PolyEvalInstance::pad(&u_vec); // pad the evaluation points + + let powers = |s: &G::Scalar, n: usize| -> Vec { + assert!(n >= 1); + let mut powers = Vec::new(); + powers.push(G::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.len(); + let powers_of_rho = powers(&rho, num_claims); + let claim_batch_joint = u_vec + .iter() + .zip(powers_of_rho.iter()) + .map(|(u, p)| u.e * p) + .fold(G::Scalar::zero(), |acc, item| acc + item); - let rho = transcript.squeeze(b"rho")?; - let claim_batch_joint = self.eval_E + rho * self.eval_W; - let num_rounds_z = num_rounds_x; + let num_rounds_z = u_vec_padded[0].x.len(); let (claim_batch_final, r_z) = self .sc_proof_batch @@ -459,32 +603,47 @@ impl, CC: CompCommitmentEngin let claim_batch_final_expected = { let poly_rz = EqPolynomial::new(r_z.clone()); - let rz_rx = poly_rz.evaluate(&r_x); - let rz_ry = poly_rz.evaluate(&r_y[1..]); - rz_rx * self.eval_E_prime + rho * rz_ry * self.eval_W_prime + let evals = u_vec_padded + .iter() + .map(|u| poly_rz.evaluate(&u.x)) + .collect::>(); + + evals + .iter() + .zip(self.evals_batch.iter()) + .zip(powers_of_rho.iter()) + .map(|((e_i, p_i), rho_i)| *e_i * *p_i * rho_i) + .fold(G::Scalar::zero(), |acc, item| acc + item) }; if claim_batch_final != claim_batch_final_expected { return Err(NovaError::InvalidSumcheckProof); } - transcript.absorb( - b"claims_batch", - &[self.eval_E_prime, self.eval_W_prime].as_slice(), - ); + transcript.absorb(b"l", &self.evals_batch.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.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 eval_joint = self + .evals_batch + .iter() + .zip(powers_of_gamma.iter()) + .map(|(e, g_i)| *e * *g_i) + .fold(G::Scalar::zero(), |acc, item| acc + item); - // verify eval_W and eval_E + // verify EE::verify( &vk.vk_ee, &mut transcript, - &comm, + &comm_joint, &r_z, - &eval, + &eval_joint, &self.eval_arg, )?; diff --git a/src/spartan/spark/mod.rs b/src/spartan/spark/mod.rs index af7923d..6fc44b0 100644 --- a/src/spartan/spark/mod.rs +++ b/src/spartan/spark/mod.rs @@ -3,7 +3,7 @@ use crate::{ errors::NovaError, r1cs::R1CSShape, - spartan::{math::Math, CompCommitmentEngineTrait}, + spartan::{math::Math, CompCommitmentEngineTrait, PolyEvalInstance, PolyEvalWitness}, traits::{evaluation::EvaluationEngineTrait, Group, TranscriptReprTrait}, CommitmentKey, }; @@ -43,7 +43,7 @@ impl TranscriptReprTrait for TrivialCommitment { } } -impl> CompCommitmentEngineTrait +impl> CompCommitmentEngineTrait for TrivialCompComputationEngine { type Decommitment = TrivialDecommitment; @@ -66,29 +66,36 @@ impl> CompCommitmentEngineTra /// 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: &(&[G::Scalar], &[G::Scalar]), _transcript: &mut G::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: &(&[G::Scalar], &[G::Scalar]), _arg: &Self::EvaluationArgument, _transcript: &mut G::TE, - ) -> Result<(G::Scalar, G::Scalar, G::Scalar), NovaError> { + ) -> Result<(G::Scalar, G::Scalar, G::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 +105,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 +162,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,39 +186,60 @@ impl> CompCommitmentEngineTra /// 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: &(&[G::Scalar], &[G::Scalar]), transcript: &mut G::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: &(&[G::Scalar], &[G::Scalar]), arg: &Self::EvaluationArgument, transcript: &mut G::TE, - ) -> Result<(G::Scalar, G::Scalar, G::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<(G::Scalar, G::Scalar, G::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)) } } diff --git a/src/spartan/spark/sparse.rs b/src/spartan/spark/sparse.rs index 2f1d597..89afbd0 100644 --- a/src/spartan/spark/sparse.rs +++ b/src/spartan/spark/sparse.rs @@ -7,12 +7,9 @@ use crate::{ math::Math, polynomial::{EqPolynomial, MultilinearPolynomial}, spark::product::{IdentityPolynomial, ProductArgumentBatched}, - SumcheckProof, - }, - traits::{ - commitment::CommitmentEngineTrait, evaluation::EvaluationEngineTrait, Group, - TranscriptEngineTrait, TranscriptReprTrait, + PolyEvalInstance, PolyEvalWitness, SumcheckProof, }, + traits::{commitment::CommitmentEngineTrait, Group, TranscriptEngineTrait, TranscriptReprTrait}, Commitment, CommitmentKey, }; use ff::Field; @@ -227,7 +224,7 @@ impl SparsePolynomial { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] -pub struct SparseEvaluationArgument> { +pub struct SparseEvaluationArgument { // claimed evaluation eval: G::Scalar, @@ -240,7 +237,6 @@ pub struct SparseEvaluationArgument> SparseEvaluationArgument { +impl SparseEvaluationArgument { pub fn prove( ck: &CommitmentKey, - pk_ee: &EE::ProverKey, poly: &SparsePolynomial, sparse: &[(usize, usize, G::Scalar)], comm: &SparsePolynomialCommitment, r: &(&[G::Scalar], &[G::Scalar]), transcript: &mut G::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(); @@ -316,15 +311,16 @@ impl> SparseEvaluationArgumen .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) @@ -462,37 +458,41 @@ impl> SparseEvaluationArgumen }) .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, @@ -505,7 +505,6 @@ impl> SparseEvaluationArgumen 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], @@ -527,21 +526,22 @@ impl> SparseEvaluationArgumen 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: &(&[G::Scalar], &[G::Scalar]), transcript: &mut G::TE, - ) -> Result { + ) -> Result<(G::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); @@ -562,14 +562,13 @@ impl> SparseEvaluationArgumen 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")?; @@ -700,33 +699,26 @@ impl> SparseEvaluationArgumen + 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 ec0fd19..a80e56b 100644 --- a/src/spartan/sumcheck.rs +++ b/src/spartan/sumcheck.rs @@ -126,54 +126,52 @@ impl SumcheckProof { )) } - pub fn prove_quad_sum( + pub fn prove_quad_batch( claim: &G::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: &[G::Scalar], comb_func: F, transcript: &mut G::TE, - ) -> Result<(Self, Vec, Vec), NovaError> + ) -> Result<(Self, Vec, (Vec, Vec)), NovaError> where - F: Fn(&G::Scalar, &G::Scalar, &G::Scalar, &G::Scalar) -> G::Scalar + Sync, + F: Fn(&G::Scalar, &G::Scalar) -> G::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<(G::Scalar, G::Scalar)> = Vec::new(); + + for (poly_A, poly_B) in poly_A_vec.iter().zip(poly_B_vec.iter()) { + let mut eval_point_0 = G::Scalar::zero(); + let mut eval_point_2 = G::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]); - // 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]; + eval_point_2 += comb_func(&poly_A_bound_point, &poly_B_bound_point); + } - // 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( - || (G::Scalar::zero(), G::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(G::Scalar::zero(), |acc, item| acc + item); + let evals_combined_2 = (0..evals.len()) + .map(|i| evals[i].1 * coeffs[i]) + .fold(G::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); @@ -181,25 +179,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.bound_poly_var_top(&r_i); - poly_B.bound_poly_var_top(&r_i); - poly_C.bound_poly_var_top(&r_i); - poly_D.bound_poly_var_top(&r_i); + for (poly_A, poly_B) in poly_A_vec.iter_mut().zip(poly_B_vec.iter_mut()) { + poly_A.bound_poly_var_top(&r_i); + poly_B.bound_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_cubic_with_additive_term(