diff --git a/Cargo.toml b/Cargo.toml index d64d0ae..1948e8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nova-snark" -version = "0.15.0" +version = "0.16.0" authors = ["Srinath Setty "] edition = "2021" description = "Recursive zkSNARKs without trusted setup" diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index d160a91..87a7628 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -11,7 +11,7 @@ use crate::{ }, Commitment, CommitmentGens, CompressedCommitment, CE, }; -use core::{cmp::max, iter}; +use core::iter; use ff::Field; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -29,7 +29,6 @@ pub struct EvaluationGens { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] pub struct EvaluationArgument { - nifs: Vec>, ipa: InnerProductArgument, } @@ -55,89 +54,38 @@ where } } - fn prove_batch( + fn prove( gens: &Self::EvaluationGens, transcript: &mut G::TE, - comms: &[Commitment], - polys: &[Vec], - points: &[Vec], - evals: &[G::Scalar], + comm: &Commitment, + poly: &[G::Scalar], + point: &[G::Scalar], + eval: &G::Scalar, ) -> Result { - // sanity checks (these should never fail) - assert!(polys.len() >= 2); - assert_eq!(comms.len(), polys.len()); - assert_eq!(comms.len(), points.len()); - assert_eq!(comms.len(), evals.len()); - - let mut r_U = InnerProductInstance::new( - &comms[0], - &EqPolynomial::new(points[0].clone()).evals(), - &evals[0], - ); - let mut r_W = InnerProductWitness::new(&polys[0]); - let mut nifs = Vec::new(); - - for i in 1..polys.len() { - let (n, u, w) = NIFSForInnerProduct::prove( - &r_U, - &r_W, - &InnerProductInstance::new( - &comms[i], - &EqPolynomial::new(points[i].clone()).evals(), - &evals[i], - ), - &InnerProductWitness::new(&polys[i]), - transcript, - )?; - nifs.push(n); - r_U = u; - r_W = w; - } - - let ipa = InnerProductArgument::prove(&gens.gens_v, &gens.gens_s, &r_U, &r_W, transcript)?; + let u = InnerProductInstance::new(comm, &EqPolynomial::new(point.to_vec()).evals(), eval); + let w = InnerProductWitness::new(poly); - Ok(EvaluationArgument { nifs, ipa }) + Ok(EvaluationArgument { + ipa: InnerProductArgument::prove(&gens.gens_v, &gens.gens_s, &u, &w, transcript)?, + }) } /// A method to verify purported evaluations of a batch of polynomials - fn verify_batch( + fn verify( gens: &Self::EvaluationGens, transcript: &mut G::TE, - comms: &[Commitment], - points: &[Vec], - evals: &[G::Scalar], + comm: &Commitment, + point: &[G::Scalar], + eval: &G::Scalar, arg: &Self::EvaluationArgument, ) -> Result<(), NovaError> { - // sanity checks (these should never fail) - assert!(comms.len() >= 2); - assert_eq!(comms.len(), points.len()); - assert_eq!(comms.len(), evals.len()); - - let mut r_U = InnerProductInstance::new( - &comms[0], - &EqPolynomial::new(points[0].clone()).evals(), - &evals[0], - ); - let mut num_vars = points[0].len(); - for i in 1..comms.len() { - let u = arg.nifs[i - 1].verify( - &r_U, - &InnerProductInstance::new( - &comms[i], - &EqPolynomial::new(points[i].clone()).evals(), - &evals[i], - ), - transcript, - )?; - r_U = u; - num_vars = max(num_vars, points[i].len()); - } + let u = InnerProductInstance::new(comm, &EqPolynomial::new(point.to_vec()).evals(), eval); arg.ipa.verify( &gens.gens_v, &gens.gens_s, - (2_usize).pow(num_vars as u32), - &r_U, + (2_usize).pow(point.len() as u32), + &u, transcript, )?; @@ -172,16 +120,6 @@ impl InnerProductInstance { c: *c, } } - - fn pad(&self, n: usize) -> InnerProductInstance { - let mut b_vec = self.b_vec.clone(); - b_vec.resize(n, G::Scalar::zero()); - InnerProductInstance { - comm_a_vec: self.comm_a_vec, - b_vec, - c: self.c, - } - } } struct InnerProductWitness { @@ -194,134 +132,6 @@ impl InnerProductWitness { a_vec: a_vec.to_vec(), } } - - fn pad(&self, n: usize) -> InnerProductWitness { - let mut a_vec = self.a_vec.clone(); - a_vec.resize(n, G::Scalar::zero()); - InnerProductWitness { a_vec } - } -} - -/// A non-interactive folding scheme (NIFS) for inner product relations -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct NIFSForInnerProduct { - cross_term: G::Scalar, -} - -impl NIFSForInnerProduct { - fn protocol_name() -> &'static [u8] { - b"NIFSForInnerProduct" - } - - fn prove( - U1: &InnerProductInstance, - W1: &InnerProductWitness, - U2: &InnerProductInstance, - W2: &InnerProductWitness, - transcript: &mut G::TE, - ) -> Result<(Self, InnerProductInstance, InnerProductWitness), NovaError> { - transcript.absorb_bytes(b"protocol-name", Self::protocol_name()); - - // pad the instances and witness so they are of the same length - let U1 = U1.pad(max(U1.b_vec.len(), U2.b_vec.len())); - let U2 = U2.pad(max(U1.b_vec.len(), U2.b_vec.len())); - let W1 = W1.pad(max(U1.b_vec.len(), U2.b_vec.len())); - let W2 = W2.pad(max(U1.b_vec.len(), U2.b_vec.len())); - - // add the two commitments and two public vectors to the transcript - // we do not need to add public vectors as their compressed versions were - // read from the transcript - U1.comm_a_vec - .append_to_transcript(b"U1_comm_a_vec", transcript); - U2.comm_a_vec - .append_to_transcript(b"U2_comm_a_vec", transcript); - - // compute the cross-term - let cross_term = inner_product(&W1.a_vec, &U2.b_vec) + inner_product(&W2.a_vec, &U1.b_vec); - - // add the cross-term to the transcript - >::append_to_transcript( - &cross_term, - b"cross_term", - transcript, - ); - - // obtain a random challenge - let r = G::Scalar::challenge(b"r", transcript)?; - - // fold the vectors and their inner product - let a_vec = W1 - .a_vec - .par_iter() - .zip(W2.a_vec.par_iter()) - .map(|(x1, x2)| *x1 + r * x2) - .collect::>(); - let b_vec = U1 - .b_vec - .par_iter() - .zip(U2.b_vec.par_iter()) - .map(|(a1, a2)| *a1 + r * a2) - .collect::>(); - - let c = U1.c + r * r * U2.c + r * cross_term; - let comm_a_vec = U1.comm_a_vec + U2.comm_a_vec * r; - - let W = InnerProductWitness { a_vec }; - let U = InnerProductInstance { - comm_a_vec, - b_vec, - c, - }; - - Ok((NIFSForInnerProduct { cross_term }, U, W)) - } - - fn verify( - &self, - U1: &InnerProductInstance, - U2: &InnerProductInstance, - transcript: &mut G::TE, - ) -> Result, NovaError> { - transcript.absorb_bytes(b"protocol-name", Self::protocol_name()); - - // pad the instances so they are of the same length - let U1 = U1.pad(max(U1.b_vec.len(), U2.b_vec.len())); - let U2 = U2.pad(max(U1.b_vec.len(), U2.b_vec.len())); - - // add the two commitments and two public vectors to the transcript - // we do not need to add public vectors as their compressed representation - // were derived from the transcript - U1.comm_a_vec - .append_to_transcript(b"U1_comm_a_vec", transcript); - U2.comm_a_vec - .append_to_transcript(b"U2_comm_a_vec", transcript); - - // add the cross-term to the transcript - >::append_to_transcript( - &self.cross_term, - b"cross_term", - transcript, - ); - - // obtain a random challenge - let r = G::Scalar::challenge(b"r", transcript)?; - - // fold the vectors and their inner product - let b_vec = U1 - .b_vec - .par_iter() - .zip(U2.b_vec.par_iter()) - .map(|(a1, a2)| *a1 + r * a2) - .collect::>(); - let c = U1.c + r * r * U2.c + r * self.cross_term; - let comm_a_vec = U1.comm_a_vec + U2.comm_a_vec * r; - - Ok(InnerProductInstance { - comm_a_vec, - b_vec, - c, - }) - } } /// An inner product argument diff --git a/src/spartan/mod.rs b/src/spartan/mod.rs index b1c8dbb..9eb50d5 100644 --- a/src/spartan/mod.rs +++ b/src/spartan/mod.rs @@ -63,9 +63,12 @@ impl> VerifierKeyTrait pub struct RelaxedR1CSSNARK> { sc_proof_outer: SumcheckProof, claims_outer: (G::Scalar, G::Scalar, G::Scalar), - sc_proof_inner: SumcheckProof, eval_E: G::Scalar, + sc_proof_inner: SumcheckProof, eval_W: G::Scalar, + sc_proof_batch: SumcheckProof, + eval_E_prime: G::Scalar, + eval_W_prime: G::Scalar, eval_arg: EE::EvaluationArgument, } @@ -103,7 +106,7 @@ impl> RelaxedR1CSSNARKTrait, NovaError>>()?; let mut poly_tau = MultilinearPolynomial::new(EqPolynomial::new(tau).evals()); @@ -140,35 +143,17 @@ impl> RelaxedR1CSSNARKTrait>::append_to_transcript( - &claim_Az, - b"claim_Az", - &mut transcript, - ); - >::append_to_transcript( - &claim_Bz, - b"claim_Bz", - &mut transcript, - ); - >::append_to_transcript( - &claim_Cz, - b"claim_Cz", - &mut transcript, - ); - let eval_E = MultilinearPolynomial::new(W.E.clone()).evaluate(&r_x); - >::append_to_transcript( - &eval_E, - b"eval_E", + + <[G::Scalar] as AppendToTranscriptTrait>::append_to_transcript( + &[claim_Az, claim_Bz, claim_Cz, eval_E], + b"claims_outer", &mut transcript, ); // inner sum-check - let r_A = G::Scalar::challenge(b"challenge_rA", &mut transcript)?; - let r_B = G::Scalar::challenge(b"challenge_rB", &mut transcript)?; - let r_C = G::Scalar::challenge(b"challenge_rC", &mut transcript)?; - let claim_inner_joint = r_A * claim_Az + r_B * claim_Bz + r_C * claim_Cz; + let r = G::Scalar::challenge(b"r", &mut transcript)?; + let claim_inner_joint = claim_Az + r * claim_Bz + r * r * claim_Cz; let poly_ABC = { // compute the initial evaluation table for R(\tau, x) @@ -216,7 +201,7 @@ impl> RelaxedR1CSSNARKTrait>() }; @@ -244,21 +229,63 @@ impl> RelaxedR1CSSNARKTrait 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( + &claim_batch_joint, + num_rounds_z, + &mut MultilinearPolynomial::new(EqPolynomial::new(r_x).evals()), + &mut MultilinearPolynomial::new(W.E.clone()), + &mut MultilinearPolynomial::new(EqPolynomial::new(r_y[1..].to_vec()).evals()), + &mut MultilinearPolynomial::new(W.W.clone()), + comb_func, &mut transcript, - &[U.comm_E, U.comm_W], - &[W.E.clone(), W.W.clone()], - &[r_x, r_y[1..].to_vec()], - &[eval_E, eval_W], )?; + let eval_E_prime = claims_batch[1]; + let eval_W_prime = claims_batch[3]; + <[G::Scalar] as AppendToTranscriptTrait>::append_to_transcript( + &[eval_E_prime, eval_W_prime], + b"claims_batch", + &mut transcript, + ); + + // we now combine evaluation claims at the same point rz into one + let gamma = G::Scalar::challenge(b"gamma", &mut transcript)?; + let comm = U.comm_E + U.comm_W * gamma; + let poly = W + .E + .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(&pk.gens, &mut transcript, &comm, &poly, &r_z, &eval)?; + Ok(RelaxedR1CSSNARK { sc_proof_outer, claims_outer: (claim_Az, claim_Bz, claim_Cz), + eval_E, sc_proof_inner, eval_W, - eval_E, + sc_proof_batch, + eval_E_prime, + eval_W_prime, eval_arg, }) } @@ -278,7 +305,7 @@ impl> RelaxedR1CSSNARKTrait, NovaError>>()?; let (claim_outer_final, r_x) = @@ -295,33 +322,21 @@ impl> RelaxedR1CSSNARKTrait>::append_to_transcript( - &self.claims_outer.0, - b"claim_Az", - &mut transcript, - ); - >::append_to_transcript( - &self.claims_outer.1, - b"claim_Bz", - &mut transcript, - ); - >::append_to_transcript( - &self.claims_outer.2, - b"claim_Cz", - &mut transcript, - ); - >::append_to_transcript( - &self.eval_E, - b"eval_E", + <[G::Scalar] as AppendToTranscriptTrait>::append_to_transcript( + &[ + self.claims_outer.0, + self.claims_outer.1, + self.claims_outer.2, + self.eval_E, + ], + b"claims_outer", &mut transcript, ); // inner sum-check - let r_A = G::Scalar::challenge(b"challenge_rA", &mut transcript)?; - let r_B = G::Scalar::challenge(b"challenge_rB", &mut transcript)?; - let r_C = G::Scalar::challenge(b"challenge_rC", &mut transcript)?; + let r = G::Scalar::challenge(b"r", &mut transcript)?; let claim_inner_joint = - r_A * self.claims_outer.0 + r_B * self.claims_outer.1 + r_C * self.claims_outer.2; + self.claims_outer.0 + r * self.claims_outer.1 + r * r * self.claims_outer.2; let (claim_inner_final, r_y) = self @@ -351,11 +366,13 @@ impl> RelaxedR1CSSNARKTrait G::Scalar { (0..M.len()) - .map(|i| { + .collect::>() + .par_iter() + .map(|&i| { let (row, col, val) = M[i]; T_x[row] * T_y[col] * val }) - .fold(G::Scalar::zero(), |acc, x| acc + x) + .reduce(G::Scalar::zero, |acc, x| acc + x) }; let T_x = EqPolynomial::new(r_x.to_vec()).evals(); @@ -367,24 +384,55 @@ impl> RelaxedR1CSSNARKTrait>::append_to_transcript( &self.eval_W, b"eval_W", &mut transcript, - ); //eval_E is already in the transcript + ); + + let rho = G::Scalar::challenge(b"rho", &mut transcript)?; + let claim_batch_joint = self.eval_E + rho * self.eval_W; + let num_rounds_z = num_rounds_x; + let (claim_batch_final, r_z) = + self + .sc_proof_batch + .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 + }; + + if claim_batch_final != claim_batch_final_expected { + return Err(NovaError::InvalidSumcheckProof); + } + + <[G::Scalar] as AppendToTranscriptTrait>::append_to_transcript( + &[self.eval_E_prime, self.eval_W_prime], + b"claims_batch", + &mut transcript, + ); + + // we now combine evaluation claims at the same point rz into one + let gamma = G::Scalar::challenge(b"gamma", &mut transcript)?; + let comm = U.comm_E + U.comm_W * gamma; + let eval = self.eval_E_prime + gamma * self.eval_W_prime; - EE::verify_batch( + // verify eval_W and eval_E + EE::verify( &vk.gens, &mut transcript, - &[U.comm_E, U.comm_W], - &[r_x, r_y[1..].to_vec()], - &[self.eval_E, self.eval_W], + &comm, + &r_z, + &eval, &self.eval_arg, )?; diff --git a/src/spartan/sumcheck.rs b/src/spartan/sumcheck.rs index d84eb9b..eea2b6e 100644 --- a/src/spartan/sumcheck.rs +++ b/src/spartan/sumcheck.rs @@ -46,7 +46,7 @@ impl SumcheckProof { poly.append_to_transcript(b"poly", transcript); //derive the verifier's challenge for the next round - let r_i = G::Scalar::challenge(b"challenge_nextround", transcript)?; + let r_i = G::Scalar::challenge(b"challenge", transcript)?; r.push(r_i); @@ -101,7 +101,7 @@ impl SumcheckProof { poly.append_to_transcript(b"poly", transcript); //derive the verifier's challenge for the next round - let r_i = G::Scalar::challenge(b"challenge_nextround", transcript)?; + let r_i = G::Scalar::challenge(b"challenge", transcript)?; r.push(r_i); polys.push(poly.compress()); @@ -122,6 +122,82 @@ impl SumcheckProof { )) } + pub fn prove_quad_sum( + claim: &G::Scalar, + num_rounds: usize, + poly_A: &mut MultilinearPolynomial, + poly_B: &mut MultilinearPolynomial, + poly_C: &mut MultilinearPolynomial, + poly_D: &mut MultilinearPolynomial, + comb_func: F, + transcript: &mut G::TE, + ) -> Result<(Self, Vec, Vec), NovaError> + where + F: Fn(&G::Scalar, &G::Scalar, &G::Scalar, &G::Scalar) -> G::Scalar + Sync, + { + 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 len = poly_A.len() / 2; + + // 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( + || (G::Scalar::zero(), G::Scalar::zero()), + |a, b| (a.0 + b.0, a.1 + b.1), + ); + + let evals = vec![eval_point_0, claim_per_round - eval_point_0, eval_point_2]; + UniPoly::from_evals(&evals) + }; + + // append the prover's message to the transcript + poly.append_to_transcript(b"poly", transcript); + + //derive the verifier's challenge for the next round + let r_i = G::Scalar::challenge(b"challenge", transcript)?; + 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); + } + + Ok(( + SumcheckProof { + compressed_polys: polys, + }, + r, + vec![poly_A[0], poly_B[0], poly_C[0], poly_D[0]], + )) + } + pub fn prove_cubic_with_additive_term( claim: &G::Scalar, num_rounds: usize, @@ -193,7 +269,7 @@ impl SumcheckProof { poly.append_to_transcript(b"poly", transcript); //derive the verifier's challenge for the next round - let r_i = G::Scalar::challenge(b"challenge_nextround", transcript)?; + let r_i = G::Scalar::challenge(b"challenge", transcript)?; r.push(r_i); polys.push(poly.compress()); diff --git a/src/traits/evaluation.rs b/src/traits/evaluation.rs index 6853faf..1840462 100644 --- a/src/traits/evaluation.rs +++ b/src/traits/evaluation.rs @@ -23,23 +23,23 @@ pub trait EvaluationEngineTrait: /// A method to perform any additional setup needed to produce proofs of evaluations fn setup(gens: &>::CommitmentGens) -> Self::EvaluationGens; - /// A method to prove evaluations of a batch of polynomials - fn prove_batch( + /// A method to prove the evaluation of a multilinear polynomial + fn prove( gens: &Self::EvaluationGens, transcript: &mut G::TE, - comm: &[>::Commitment], - polys: &[Vec], - points: &[Vec], - evals: &[G::Scalar], + comm: &>::Commitment, + poly: &[G::Scalar], + point: &[G::Scalar], + eval: &G::Scalar, ) -> Result; - /// A method to verify purported evaluations of a batch of polynomials - fn verify_batch( + /// A method to verify the purported evaluation of a multilinear polynomials + fn verify( gens: &Self::EvaluationGens, transcript: &mut G::TE, - comm: &[>::Commitment], - points: &[Vec], - evals: &[G::Scalar], + comm: &>::Commitment, + point: &[G::Scalar], + eval: &G::Scalar, arg: &Self::EvaluationArgument, ) -> Result<(), NovaError>; }