Batch polynomial evaluations (#154) * Ability to collect evaluation claims * defer polynomial evaluation claims * address cargo clippy
@@ -1,6 +1,6 @@
[package] name = "nova-snark" -version = "0.19.0" +version = "0.19.1" authors = ["Srinath Setty <srinath@microsoft.com>"] edition = "2021" description = "Recursive zkSNARKs without trusted setup"
@@ -1095,8 +1095,8 @@ assert_eq!(zn_secondary, zn_secondary_direct);
assert_eq!(zn_secondary, vec![<G2 as Group>::Scalar::from(2460515u64)]); // run the compressed snark with Spark compiler - type CC1Prime = spartan::spark::SparkEngine<G1, EE1>; - type CC2Prime = spartan::spark::SparkEngine<G2, EE2>; + type CC1Prime = spartan::spark::SparkEngine<G1>; + type CC2Prime = spartan::spark::SparkEngine<G2>; type S1Prime = spartan::RelaxedR1CSSNARK<G1, EE1, CC1Prime>; type S2Prime = spartan::RelaxedR1CSSNARK<G2, EE2, CC2Prime>;
@@ -12,7 +12,7 @@ traits::{
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<G: Group> { + p: Vec<G::Scalar>, // polynomial +} + +impl<G: Group> PolyEvalWitness<G> { + fn pad(W: &[PolyEvalWitness<G>]) -> Vec<PolyEvalWitness<G>> { + // 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<G>], s: &[G::Scalar]) -> PolyEvalWitness<G> { + 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<G: Group> { + c: Commitment<G>, // commitment to the polynomial + x: Vec<G::Scalar>, // evaluation point + e: G::Scalar, // claimed evaluation +} + +impl<G: Group> PolyEvalInstance<G> { + fn pad(U: &[PolyEvalInstance<G>]) -> Vec<PolyEvalInstance<G>> { + // 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<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> { +pub trait CompCommitmentEngineTrait<G: Group> { /// A type that holds opening hint type Decommitment: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>;@@ -46,22 +105,26 @@
/// proves an evaluation of R1CS matrices viewed as polynomials fn prove( ck: &CommitmentKey<G>, - ek: &EE::ProverKey, S: &R1CSShape<G>, decomm: &Self::Decommitment, comm: &Self::Commitment, r: &(&[G::Scalar], &[G::Scalar]), transcript: &mut G::TE, - ) -> Result<Self::EvaluationArgument, NovaError>; + ) -> Result< + ( + Self::EvaluationArgument, + Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)>, + ), + 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<PolyEvalInstance<G>>), NovaError>; } /// A type that represents the prover's key@@ -70,7 +133,7 @@ #[serde(bound = "")]
pub struct ProverKey< G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, - CC: CompCommitmentEngineTrait<G, EE>, + CC: CompCommitmentEngineTrait<G>, > { pk_ee: EE::ProverKey, S: R1CSShape<G>,@@ -84,7 +147,7 @@ #[serde(bound = "")]
pub struct VerifierKey< G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, - CC: CompCommitmentEngineTrait<G, EE>, + CC: CompCommitmentEngineTrait<G>, > { num_cons: usize, num_vars: usize,@@ -100,21 +163,20 @@ #[serde(bound = "")]
pub struct RelaxedR1CSSNARK< G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, - CC: CompCommitmentEngineTrait<G, EE>, + CC: CompCommitmentEngineTrait<G>, > { sc_proof_outer: SumcheckProof<G>, claims_outer: (G::Scalar, G::Scalar, G::Scalar), eval_E: G::Scalar, sc_proof_inner: SumcheckProof<G>, eval_W: G::Scalar, + eval_arg_cc: CC::EvaluationArgument, sc_proof_batch: SumcheckProof<G>, - eval_E_prime: G::Scalar, - eval_W_prime: G::Scalar, + evals_batch: Vec<G::Scalar>, eval_arg: EE::EvaluationArgument, - eval_arg_cc: CC::EvaluationArgument, } -impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, CC: CompCommitmentEngineTrait<G, EE>> +impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>, CC: CompCommitmentEngineTrait<G>> RelaxedR1CSSNARKTrait<G> for RelaxedR1CSSNARK<G, EE, CC> { type ProverKey = ProverKey<G, EE, CC>;@@ -292,9 +354,8 @@ &mut transcript,
)?; // 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 @@ &(&r_x, &r_y),
&mut transcript, )?; - let eval_W = MultilinearPolynomial::new(W.W.clone()).evaluate(&r_y[1..]); - transcript.absorb(b"eval_W", &eval_W); + // 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, + }, + )); - // We will now reduce eval_W =? W(r_y[1..]) and eval_W =? E(r_x) into + 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 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( + let (w_vec, u_vec): (Vec<PolyEvalWitness<G>>, Vec<PolyEvalInstance<G>>) = + 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<G::Scalar> { + 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 = 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<MultilinearPolynomial<G::Scalar>> = w_vec_padded + .iter() + .map(|w| MultilinearPolynomial::new(w.p.clone())) + .collect(); + let mut polys_right: Vec<MultilinearPolynomial<G::Scalar>> = 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<G::Scalar>, Vec<G::Scalar>) = 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<G::Scalar> = 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::<G>::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::<Vec<G::Scalar>>(); - 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 @@ claims_outer: (claim_Az, claim_Bz, claim_Cz),
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 @@ (G::Scalar::one() - r_y[0]) * self.eval_W + r_y[0] * eval_X
}; // 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 rho = transcript.squeeze(b"rho")?; - let claim_batch_joint = self.eval_E + rho * self.eval_W; - let num_rounds_z = num_rounds_x; + let powers = |s: &G::Scalar, n: usize| -> Vec<G::Scalar> { + 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 num_rounds_z = u_vec_padded[0].x.len(); let (claim_batch_final, r_z) = self .sc_proof_batch@@ -459,32 +603,47 @@ .verify(claim_batch_joint, num_rounds_z, 2, &mut transcript)?;
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::<Vec<G::Scalar>>(); + + 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<G::Scalar> = 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::<G>::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, )?;
@@ -3,7 +3,7 @@ //! We also provide a trivial implementation that has the verifier evaluate the sparse polynomials
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 @@ self.S.to_transcript_bytes()
} } -impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> CompCommitmentEngineTrait<G, EE> +impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> CompCommitmentEngineTrait<G> for TrivialCompComputationEngine<G, EE> { type Decommitment = TrivialDecommitment<G>;@@ -66,29 +66,36 @@
/// proves an evaluation of R1CS matrices viewed as polynomials fn prove( _ck: &CommitmentKey<G>, - _ek: &EE::ProverKey, _S: &R1CSShape<G>, _decomm: &Self::Decommitment, _comm: &Self::Commitment, _r: &(&[G::Scalar], &[G::Scalar]), _transcript: &mut G::TE, - ) -> Result<Self::EvaluationArgument, NovaError> { - Ok(TrivialEvaluationArgument { - _p: Default::default(), - }) + ) -> Result< + ( + Self::EvaluationArgument, + Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)>, + ), + 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<PolyEvalInstance<G>>), NovaError> { let (r_x, r_y) = r; let evals = SparsePolynomial::<G>::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 @@
use sparse::{SparseEvaluationArgument, SparsePolynomial, SparsePolynomialCommitment}; /// A non-trivial implementation of `CompCommitmentEngineTrait` using Spartan's SPARK compiler -pub struct SparkEngine<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> { +pub struct SparkEngine<G: Group> { _p: PhantomData<G>, - _p2: PhantomData<EE>, } /// An implementation of Spark decommitment@@ -156,18 +162,16 @@
/// Provides an implementation of a trivial evaluation argument #[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] -pub struct SparkEvaluationArgument<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> { - arg_A: SparseEvaluationArgument<G, EE>, - arg_B: SparseEvaluationArgument<G, EE>, - arg_C: SparseEvaluationArgument<G, EE>, +pub struct SparkEvaluationArgument<G: Group> { + arg_A: SparseEvaluationArgument<G>, + arg_B: SparseEvaluationArgument<G>, + arg_C: SparseEvaluationArgument<G>, } -impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> CompCommitmentEngineTrait<G, EE> - for SparkEngine<G, EE> -{ +impl<G: Group> CompCommitmentEngineTrait<G> for SparkEngine<G> { type Decommitment = SparkDecommitment<G>; type Commitment = SparkCommitment<G>; - type EvaluationArgument = SparkEvaluationArgument<G, EE>; + type EvaluationArgument = SparkEvaluationArgument<G>; /// commits to R1CS matrices fn commit(@@ -182,39 +186,60 @@
/// proves an evaluation of R1CS matrices viewed as polynomials fn prove( ck: &CommitmentKey<G>, - pk_ee: &EE::ProverKey, S: &R1CSShape<G>, decomm: &Self::Decommitment, comm: &Self::Commitment, r: &(&[G::Scalar], &[G::Scalar]), transcript: &mut G::TE, - ) -> Result<Self::EvaluationArgument, NovaError> { - 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)?; + ) -> Result< + ( + Self::EvaluationArgument, + Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)>, + ), + 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)?; - Ok(SparkEvaluationArgument { - arg_A, - arg_B, - arg_C, - }) + 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)?; + ) -> Result<(G::Scalar, G::Scalar, G::Scalar, Vec<PolyEvalInstance<G>>), 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)) + Ok((eval_A, eval_B, eval_C, u_vec)) } }
@@ -7,12 +7,9 @@ spartan::{
math::Math, polynomial::{EqPolynomial, MultilinearPolynomial}, spark::product::{IdentityPolynomial, ProductArgumentBatched}, - SumcheckProof, + PolyEvalInstance, PolyEvalWitness, SumcheckProof, }, - traits::{ - commitment::CommitmentEngineTrait, evaluation::EvaluationEngineTrait, Group, - TranscriptEngineTrait, TranscriptReprTrait, - }, + traits::{commitment::CommitmentEngineTrait, Group, TranscriptEngineTrait, TranscriptReprTrait}, Commitment, CommitmentKey, }; use ff::Field;@@ -227,7 +224,7 @@ }
#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] -pub struct SparseEvaluationArgument<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> { +pub struct SparseEvaluationArgument<G: Group> { // claimed evaluation eval: G::Scalar,@@ -240,7 +237,6 @@ sc_proof_eval: SumcheckProof<G>,
eval_E_row: G::Scalar, eval_E_col: G::Scalar, eval_val: G::Scalar, - arg_eval: EE::EvaluationArgument, // proof that E_row is well-formed eval_init_row: G::Scalar,@@ -262,23 +258,22 @@ eval_col: G::Scalar,
eval_col_read_ts: G::Scalar, eval_E_col2: G::Scalar, eval_col_audit_ts: G::Scalar, - arg_row_col_joint: EE::EvaluationArgument, - arg_row_audit_ts: EE::EvaluationArgument, - arg_col_audit_ts: EE::EvaluationArgument, } -impl<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> SparseEvaluationArgument<G, EE> { +impl<G: Group> SparseEvaluationArgument<G> { pub fn prove( ck: &CommitmentKey<G>, - pk_ee: &EE::ProverKey, poly: &SparsePolynomial<G>, sparse: &[(usize, usize, G::Scalar)], comm: &SparsePolynomialCommitment<G>, r: &(&[G::Scalar], &[G::Scalar]), transcript: &mut G::TE, - ) -> Result<Self, NovaError> { + ) -> Result<(Self, Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)>), NovaError> { let (r_x, r_y) = r; let eval = SparsePolynomial::<G>::multi_evaluate(&[sparse], r_x, r_y)[0]; + + // keep track of evaluation claims + let mut w_u_vec: Vec<(PolyEvalWitness<G>, PolyEvalInstance<G>)> = Vec::new(); // compute oracles to prove the correctness of `eval` let (E_row, E_col, T_x, T_y) = SparsePolynomial::<G>::evaluation_oracles(sparse, r_x, r_y);@@ -316,15 +311,16 @@ .zip(E_col.iter())
.zip(val.iter()) .map(|((a, b), c)| *a + rho * *b + rho * rho * *c) .collect::<Vec<G::Scalar>>(); - 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 @@ *x + c * y + c * c * z + c * c * c * m + c * c * c * c * n + c * c * c * c * c * q
}) .collect::<Vec<_>>(); - let arg_row_col_joint = EE::prove( - ck, - pk_ee, - transcript, - &comm_joint, - &poly_joint, - &r_read_write_row_col, - &eval_joint, - )?; + // 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, + }, + )); - 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, - )?; + 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, + }, + )); - 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, - )?; + 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, + }, + )); - Ok(Self { + let eval_arg = Self { // claimed evaluation eval,@@ -505,7 +505,6 @@ sc_proof_eval,
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,20 +526,21 @@ eval_col,
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<G>, r: &(&[G::Scalar], &[G::Scalar]), transcript: &mut G::TE, - ) -> Result<G::Scalar, NovaError> { + ) -> Result<(G::Scalar, Vec<PolyEvalInstance<G>>), NovaError> { let (r_x, r_y) = r; + + // keep track of evaluation claims + let mut u_vec: Vec<PolyEvalInstance<G>> = Vec::new(); // append the transcript and scalar transcript.absorb(b"E", &vec![self.comm_E_row, self.comm_E_col].as_slice());@@ -562,14 +562,13 @@ // prove evaluations of E_row, E_col and val at r_eval
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 @@ + 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, - )?; + u_vec.push(PolyEvalInstance { + c: comm_joint, + x: r_read_write_row_col, + e: eval_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, - )?; + 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, + }); - EE::verify( - vk_ee, - transcript, - &comm.comm_col_audit_ts, - &r_init_audit_col, - &self.eval_col_audit_ts, - &self.arg_col_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) + Ok((self.eval, u_vec)) } }
@@ -126,54 +126,52 @@ vec![poly_A[0], poly_B[0]],
)) } - pub fn prove_quad_sum<F>( + pub fn prove_quad_batch<F>( claim: &G::Scalar, num_rounds: usize, - poly_A: &mut MultilinearPolynomial<G::Scalar>, - poly_B: &mut MultilinearPolynomial<G::Scalar>, - poly_C: &mut MultilinearPolynomial<G::Scalar>, - poly_D: &mut MultilinearPolynomial<G::Scalar>, + poly_A_vec: &mut Vec<MultilinearPolynomial<G::Scalar>>, + poly_B_vec: &mut Vec<MultilinearPolynomial<G::Scalar>>, + coeffs: &[G::Scalar], comb_func: F, transcript: &mut G::TE, - ) -> Result<(Self, Vec<G::Scalar>, Vec<G::Scalar>), NovaError> + ) -> Result<(Self, Vec<G::Scalar>, (Vec<G::Scalar>, Vec<G::Scalar>)), 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<G::Scalar> = Vec::new(); - let mut polys: Vec<CompressedUniPoly<G>> = Vec::new(); - let mut claim_per_round = *claim; - for _ in 0..num_rounds { - let poly = { + let mut quad_polys: Vec<CompressedUniPoly<G>> = 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); + } + + evals.push((eval_point_0, eval_point_2)); + } - // 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), - ); + 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![eval_point_0, claim_per_round - eval_point_0, eval_point_2]; - UniPoly::from_evals(&evals) - }; + 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 @@
// 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 + 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); + } - // 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); + 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<F>(