From a0d74a5eb725a8b9d89be55038a5ff8d13d69143 Mon Sep 17 00:00:00 2001 From: Arasu Arun Date: Thu, 29 Feb 2024 19:37:07 -0500 Subject: [PATCH 01/16] working precommitted --- src/lib.rs | 22 ++- src/r1cs.rs | 59 ++++++ src/spartan/upsnark.rs | 426 +++++++++++++++++++++++++++++++++++++++-- src/traits/upsnark.rs | 17 +- 4 files changed, 503 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f1f8835..c03917e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,8 +88,8 @@ impl + UniformSNARKTrait + Precommitted } /// Produces prover and verifier keys for the direct SNARK - pub fn setup_precommitted(circuit: C, n: usize) -> Result<(ProverKey, VerifierKey), SpartanError> { - let (pk, vk) = S::setup_precommitted(circuit, n)?; + pub fn setup_precommitted(circuit: C, n: usize, ck: CommitmentKey::) -> Result<(ProverKey, VerifierKey), SpartanError> { + let (pk, vk) = S::setup_precommitted(circuit, n, ck)?; Ok((ProverKey { pk }, VerifierKey { vk })) } @@ -105,11 +105,29 @@ impl + UniformSNARKTrait + Precommitted }) } + /// Produces a proof of satisfiability of the provided circuit + pub fn prove_precommitted(pk: &ProverKey, circuit: C, w_segments: Vec>, comm_w_vec: Vec> ) -> Result { + // prove the instance using Spartan + let snark = S::prove_precommitted(&pk.pk, circuit, w_segments, comm_w_vec)?; + + Ok(SNARK { + snark, + _p: Default::default(), + _p2: Default::default(), + }) + } + /// Verifies a proof of satisfiability pub fn verify(&self, vk: &VerifierKey, io: &[G::Scalar]) -> Result<(), SpartanError> { // verify the snark using the constructed instance self.snark.verify(&vk.vk, io) } + + /// Verifies a proof of satisfiability + pub fn verify_precommitted(&self, vk: &VerifierKey, io: &[G::Scalar]) -> Result<(), SpartanError> { + // verify the snark using the constructed instance + self.snark.verify_precommitted(&vk.vk, io) + } } type CommitmentKey = <::CE as CommitmentEngineTrait>::CommitmentKey; diff --git a/src/r1cs.rs b/src/r1cs.rs index a6c3599..b12da4e 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -250,6 +250,7 @@ impl R1CSShape { X: &[G::Scalar], num_steps: usize, ) -> Result<(Vec, Vec, Vec), SpartanError> { +<<<<<<< HEAD if W.len() + X.len() != (self.num_io + self.num_vars) * num_steps { return Err(SpartanError::InvalidWitnessLength); } @@ -267,6 +268,12 @@ impl R1CSShape { } }; +======= + // if z.len() != (self.num_io + self.num_vars) * num_steps + 1 { + // return Err(SpartanError::InvalidWitnessLength); + // } + +>>>>>>> aac138f (working precommitted) // Pre-processes matrix to return the indices of the start of each row let get_row_pointers = |M: &Vec<(usize, usize, G::Scalar)>| -> Vec { let mut indptr = vec![0; self.num_cons + 1]; @@ -785,3 +792,55 @@ impl TranscriptReprTrait for RelaxedR1CSInstance { .concat() } } + +// Segmented structs for Precommitted version + +/// A type that holds a witness for a given R1CS instance +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PrecommittedR1CSWitness { + pub(crate) W: Vec>, +} + +/// A type that holds an R1CS instance +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct PrecommittedR1CSInstance { + pub(crate) comm_W: Vec>, + pub(crate) X: Vec, +} + + +impl PrecommittedR1CSWitness { + /// A method to create a witness object using a vector of scalars + pub fn new(_S: &R1CSShape, W: Vec>) -> Result, SpartanError> { + let w = PrecommittedR1CSWitness { W: W }; + Ok(w) + } +} + +impl PrecommittedR1CSInstance { + /// A method to create an instance object using consitituent elements + pub fn new( + S: &R1CSShape, + comm_W: Vec>, + X: &[G::Scalar], + ) -> Result, SpartanError> { + if S.num_io != X.len() { + Err(SpartanError::InvalidInputLength) + } else { + Ok(PrecommittedR1CSInstance{ + comm_W: comm_W, + X: X.to_owned(), + }) + } + } +} + +impl TranscriptReprTrait for PrecommittedR1CSInstance { + fn to_transcript_bytes(&self) -> Vec { + self.comm_W.iter() + .flat_map(|elem| elem.to_transcript_bytes()) + .chain(self.X.as_slice().to_transcript_bytes()) + .collect::>() + } +} diff --git a/src/spartan/upsnark.rs b/src/spartan/upsnark.rs index 2548cfe..7cab680 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -16,16 +16,15 @@ use crate::{ digest::{DigestComputer, SimpleDigestible}, errors::SpartanError, r1cs::{R1CSInstance, R1CSShape}, + r1cs::{PrecommittedR1CSInstance, PrecommittedR1CSWitness, R1CSInstance, R1CSShape}, spartan::{ - polys::{eq::EqPolynomial, multilinear::MultilinearPolynomial, multilinear::SparsePolynomial}, + polys::{eq::EqPolynomial, multilinear::{MultilinearPolynomial, SparsePolynomial}}, sumcheck::SumcheckProof, // PolyEvalInstance, PolyEvalWitness, }, traits::{ - commitment::CommitmentTrait, - evaluation::EvaluationEngineTrait, - snark::RelaxedR1CSSNARKTrait, - upsnark::{PrecommittedSNARKTrait, UniformSNARKTrait}, + commitment::{CommitmentEngineTrait, CommitmentTrait}, evaluation::EvaluationEngineTrait, snark::RelaxedR1CSSNARKTrait, + upsnark::{PrecommittedSNARKTrait, UniformSNARKTrait}, Group, TranscriptEngineTrait, }, Commitment, CommitmentKey, CompressedCommitment, @@ -36,6 +35,8 @@ use once_cell::sync::OnceCell; use rayon::prelude::*; use serde::{Deserialize, Serialize}; +use super::{PolyEvalInstance, PolyEvalWitness}; + /// A type that represents the prover's key #[derive(Serialize, Deserialize)] #[serde(bound = "")] @@ -146,11 +147,11 @@ impl> UniformVerifierKey { #[derive(Serialize, Deserialize)] #[serde(bound = "")] pub struct R1CSSNARK> { - comm_W: CompressedCommitment, + comm_W: Vec>, sc_proof_outer: SumcheckProof, claims_outer: (G::Scalar, G::Scalar, G::Scalar), sc_proof_inner: SumcheckProof, - eval_W: G::Scalar, + eval_W: Vec, eval_arg: EE::EvaluationArgument, } @@ -397,11 +398,11 @@ impl> RelaxedR1CSSNARKTrait for R1CSSN )?; Ok(R1CSSNARK { - comm_W: u.comm_W.compress(), + comm_W: vec![u.comm_W.compress()], sc_proof_outer, claims_outer: (claim_Az, claim_Bz, claim_Cz), sc_proof_inner, - eval_W: eval_W.unwrap(), + eval_W: vec![eval_W], eval_arg, }) } @@ -410,7 +411,7 @@ impl> RelaxedR1CSSNARKTrait for R1CSSN #[tracing::instrument(skip_all, name = "SNARK::verify")] fn verify(&self, vk: &Self::VerifierKey, io: &[G::Scalar]) -> Result<(), SpartanError> { // construct an instance using the provided commitment to the witness and IO - let comm_W = Commitment::::decompress(&self.comm_W)?; + let comm_W = Commitment::::decompress(&self.comm_W[0])?; // This object has the shape dimensions but no matrices. // A convenience to work with Spartan's functions without changing their signature. @@ -487,7 +488,7 @@ impl> RelaxedR1CSSNARKTrait for R1CSSN SparsePolynomial::new(usize::try_from(vk.num_vars_total.ilog2()).unwrap(), poly_X) .evaluate(&r_y[1..]) }; - (G::Scalar::ONE - r_y[0]) * self.eval_W + r_y[0] * eval_X + (G::Scalar::ONE - r_y[0]) * self.eval_W[0] + r_y[0] * eval_X }; // compute evaluations of R1CS matrices @@ -551,7 +552,7 @@ impl> RelaxedR1CSSNARKTrait for R1CSSN &mut transcript, &u.comm_W.clone(), &r_y[1..].to_vec(), - &self.eval_W, + &self.eval_W[0], &self.eval_arg, )?; @@ -575,7 +576,7 @@ impl> UniformSNARKTrait for R1CSSNARK< UniformVerifierKey::new(vk_ee, S.clone(), num_steps, num_cons_total, num_vars_total); let pk = UniformProverKey { - ck, + ck, pk_ee, S, num_steps, @@ -592,11 +593,14 @@ impl> PrecommittedSNARKTrait for R1CSS #[tracing::instrument(skip_all, name = "SNARK::setup_uniform")] fn setup_precommitted>( circuit: C, - num_steps: usize, + num_steps: usize, + ck: <::CE as CommitmentEngineTrait>::CommitmentKey, ) -> Result<(UniformProverKey, UniformVerifierKey), SpartanError> { let mut cs: ShapeCS = ShapeCS::new(); let _ = circuit.synthesize(&mut cs); - let (S, ck, num_cons_total, num_vars_total) = cs.r1cs_shape_uniform(num_steps); // TODO(arasuarun): replace with precommitted version + + // TODO(arasuarun): don't generate ck (minor optimization) + let (S, _ck, num_cons_total, num_vars_total) = cs.r1cs_shape_uniform(num_steps); let (pk_ee, vk_ee) = EE::setup(&ck); @@ -615,4 +619,396 @@ impl> PrecommittedSNARKTrait for R1CSS Ok((pk, vk)) } + + /// produces a succinct proof of satisfiability of a `RelaxedR1CS` instance + #[tracing::instrument(skip_all, name = "Spartan2::UPSnark::prove")] + fn prove_precommitted>( + pk: &Self::ProverKey, + circuit: C, + w_segments: Vec>, + comm_w_vec: Vec>, + ) -> Result { + let mut cs: SatisfyingAssignment = SatisfyingAssignment::new(); + let _ = circuit.synthesize(&mut cs); + + // Create a hollow shape with the right dimensions but no matrices. + // This is a convenience to work with Spartan's r1cs_instance_and_witness function + // and other padding functions without changing their signature. + let hollow_S = R1CSShape:: { + num_cons: pk.num_cons_total, + num_vars: pk.num_vars_total, + num_io: 0, + A: vec![], + B: vec![], + C: vec![], + }; + + let u: PrecommittedR1CSInstance = PrecommittedR1CSInstance::new(&hollow_S, comm_w_vec.clone(), &[])?; + let w = PrecommittedR1CSWitness::new(&hollow_S, w_segments)?; + + let non_commitment_span = tracing::span!(tracing::Level::INFO, "PostCommitProve"); + let _guard = non_commitment_span.enter(); + + let mut transcript = G::TE::new(b"R1CSSNARK"); + + // append the digest of vk (which includes R1CS matrices) and the RelaxedR1CSInstance to the transcript + transcript.absorb(b"vk", &pk.vk_digest); + transcript.absorb(b"U", &u); + + // compute the full satisfying assignment by concatenating W.W, U.u, and U.X + let mut w_full = w.W.iter().flat_map(|segment| segment.clone()).collect::>(); + w_full.extend(vec![G::Scalar::ZERO; pk.num_vars_total - w_full.len()]); + let mut z = [w_full, vec![1.into()], u.X.clone()].concat(); + + + let (num_rounds_x, num_rounds_y) = ( + usize::try_from(pk.num_cons_total.ilog2()).unwrap(), + (usize::try_from(pk.num_vars_total.ilog2()).unwrap() + 1), + ); + + // outer sum-check + let tau = (0..num_rounds_x) + .map(|_i| transcript.squeeze(b"t")) + .collect::, SpartanError>>()?; + + let mut poly_tau = MultilinearPolynomial::new(EqPolynomial::new(tau).evals()); + // poly_Az is the polynomial extended from the vector Az + let (mut poly_Az, mut poly_Bz, mut poly_Cz) = { + let (poly_Az, poly_Bz, poly_Cz) = pk.S.multiply_vec_uniform(&z, pk.num_steps)?; + ( + MultilinearPolynomial::new(poly_Az), + MultilinearPolynomial::new(poly_Bz), + MultilinearPolynomial::new(poly_Cz), + ) + }; + + let comb_func_outer = + |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 * *poly_C_comp - *poly_D_comp) }; + let (sc_proof_outer, r_x, claims_outer) = SumcheckProof::prove_cubic_with_additive_term( + &G::Scalar::ZERO, // claim is zero + num_rounds_x, + &mut poly_tau, + &mut poly_Az, + &mut poly_Bz, + &mut poly_Cz, + comb_func_outer, + &mut transcript, + )?; + + // claims from the end of sum-check + // claim_Az is the (scalar) value v_A = \sum_y A(r_x, y) * z(r_x) where r_x is the sumcheck randomness + let (claim_Az, claim_Bz): (G::Scalar, G::Scalar) = (claims_outer[1], claims_outer[2]); + let claim_Cz = claims_outer[3]; + transcript.absorb( + b"claims_outer", + &[claim_Az, claim_Bz, claim_Cz].as_slice(), + ); + + // inner sum-check + let r = transcript.squeeze(b"r")?; + let claim_inner_joint = claim_Az + r * claim_Bz + r * r * claim_Cz; + + let span = tracing::span!(tracing::Level::TRACE, "poly_ABC"); + let _enter = span.enter(); + + // this is the polynomial extended from the vector r_A * A(r_x, y) + r_B * B(r_x, y) + r_C * C(r_x, y) for all y + let poly_ABC = { + let NUM_STEPS_BITS = pk.num_steps.trailing_zeros(); + let (rx_con, rx_ts) = r_x.split_at(r_x.len() - NUM_STEPS_BITS as usize); + let eq_rx_con = EqPolynomial::new(rx_con.to_vec()).evals(); + let eq_rx_ts = EqPolynomial::new(rx_ts.to_vec()).evals(); + + let N_STEPS = pk.num_steps; + + // With uniformity, each entry of the RLC of A, B, C can be expressed using + // the RLC of the small_A, small_B, small_C matrices. + + // 1. Evaluate \tilde smallM(r_x, y) for all y + let compute_eval_table_sparse_single= |small_M: &Vec<(usize, usize, G::Scalar)>| -> Vec { + let mut small_M_evals = vec![G::Scalar::ZERO; pk.S.num_vars + 1]; + for (row, col, val) in small_M.iter() { + small_M_evals[*col] += eq_rx_con[*row] * val; + } + small_M_evals + }; + + let (small_A_evals, (small_B_evals, small_C_evals)) = rayon::join( + || compute_eval_table_sparse_single(&pk.S.A), + || rayon::join( + || compute_eval_table_sparse_single(&pk.S.B), + || compute_eval_table_sparse_single(&pk.S.C), + ), + ); + + let small_RLC_evals = (0..small_A_evals.len()).into_par_iter().map(|i| { + small_A_evals[i] + small_B_evals[i] * r + small_C_evals[i] * r * r + }).collect::>(); + + // 2. Handles all entries but the last one with the constant 1 variable + let mut RLC_evals: Vec = (0..pk.num_vars_total).into_par_iter().map(|col| { + eq_rx_ts[col % N_STEPS] * small_RLC_evals[col / N_STEPS] + }).collect(); + let next_pow_2 = 2 * pk.num_vars_total; + RLC_evals.resize(next_pow_2, G::Scalar::ZERO); + + // 3. Handles the constant 1 variable + let compute_eval_constant_column = |small_M: &Vec<(usize, usize, G::Scalar)>| -> G::Scalar { + let constant_sum: G::Scalar = small_M.iter() + .filter(|(_, col, _)| *col == pk.S.num_vars) // expecting ~1 + .map(|(row, _, val)| { + let eq_sum = (0..N_STEPS).into_par_iter().map(|t| eq_rx_ts[t]).sum::(); + *val * eq_rx_con[*row] * eq_sum + }).sum(); + + constant_sum + }; + + let (constant_term_A, (constant_term_B, constant_term_C)) = rayon::join( + || compute_eval_constant_column(&pk.S.A), + || rayon::join( + || compute_eval_constant_column(&pk.S.B), + || compute_eval_constant_column(&pk.S.C), + ), + ); + + RLC_evals[pk.num_vars_total] = constant_term_A + r * constant_term_B + r * r * constant_term_C; + + RLC_evals + }; + drop(_enter); + drop(span); + + let poly_z = { + z.resize(pk.num_vars_total * 2, G::Scalar::ZERO); + z + }; + + let comb_func = |poly_A_comp: &G::Scalar, poly_B_comp: &G::Scalar| -> G::Scalar { + *poly_A_comp * *poly_B_comp + }; + let (sc_proof_inner, r_y, _claims_inner) = SumcheckProof::prove_quad( + &claim_inner_joint, // r_A * v_A + r_B * v_B + r_C * v_C + num_rounds_y, + &mut MultilinearPolynomial::new(poly_ABC), // r_A * A(r_x, y) + r_B * B(r_x, y) + r_C * C(r_x, y) for all y + &mut MultilinearPolynomial::new(poly_z), // z(y) for all y + comb_func, + &mut transcript, + )?; + + + // The number of prefix bits needed to identify a segment within the witness vector + // assuming that num_vars_total is a power of 2 and each segment has length num_steps, which is also a power of 2. + // The +1 is the first element used to separate the inputs and the witness. + let N_PREFIX = (pk.num_vars_total.trailing_zeros() as usize - pk.num_steps.trailing_zeros() as usize) + 1; + let r_y_point = &r_y[N_PREFIX..]; + + // Evaluate each segment on r_y_point + let eval_vec = w.W.iter().map(|segment| { + MultilinearPolynomial::evaluate_with(segment, &r_y_point) + }).collect::>(); + let poly_vec = w.W.as_slice(); + let comm_vec = comm_w_vec; + + // now batch these together + let c = transcript.squeeze(b"c")?; + let w: PolyEvalWitness = PolyEvalWitness::batch(&poly_vec.iter().map(|v| v.as_ref()).collect::>(), &c); + let u: PolyEvalInstance = PolyEvalInstance::batch(&comm_vec, &r_y_point, &eval_vec, &c); + + let eval_arg = EE::prove( + &pk.ck, + &pk.pk_ee, + &mut transcript, + &u.c, + &w.p, + &r_y_point, + &u.e + )?; + + Ok(R1CSSNARK { + comm_W: comm_vec.iter().map(|elem| elem.compress()).collect::>(), + sc_proof_outer, + claims_outer: (claim_Az, claim_Bz, claim_Cz), + sc_proof_inner, + eval_W: eval_vec, + eval_arg, + }) + } + + + /// verifies a proof of satisfiability of a `RelaxedR1CS` instance + #[tracing::instrument(skip_all, name = "SNARK::verify")] + fn verify_precommitted(&self, vk: &Self::VerifierKey, io: &[G::Scalar]) -> Result<(), SpartanError> { + let N_SEGMENTS = self.comm_W.len(); + + // construct an instance using the provided commitment to the witness and IO + let comm_W_vec = self.comm_W.iter() + .map(|c| Commitment::::decompress(c).unwrap()) + .collect::::CE as CommitmentEngineTrait>::Commitment>>(); + + // This object has the shape dimensions but no matrices. + // A convenience to work with Spartan's functions without changing their signature. + let hollow_S = R1CSShape:: { + num_cons: vk.num_cons_total, + num_vars: vk.num_vars_total, + num_io: 0, + A: vec![], + B: vec![], + C: vec![], + }; + let u = PrecommittedR1CSInstance::new(&hollow_S, comm_W_vec.clone(), io)?; + + let mut transcript = G::TE::new(b"R1CSSNARK"); + + // append the digest of R1CS matrices and the RelaxedR1CSInstance to the transcript + transcript.absorb(b"vk", &vk.digest()); + transcript.absorb(b"U", &u); + + let (num_rounds_x, num_rounds_y) = ( + usize::try_from(vk.num_cons_total.ilog2()).unwrap(), + (usize::try_from(vk.num_vars_total.ilog2()).unwrap() + 1), + ); + + // outer sum-check + let tau = (0..num_rounds_x) + .map(|_i| transcript.squeeze(b"t")) + .collect::, SpartanError>>()?; + + let (claim_outer_final, r_x) = + self + .sc_proof_outer + .verify(G::Scalar::ZERO, num_rounds_x, 3, &mut transcript)?; + + // verify claim_outer_final + let (claim_Az, claim_Bz, claim_Cz) = self.claims_outer; + let taus_bound_rx = EqPolynomial::new(tau).evaluate(&r_x); + let claim_outer_final_expected = + taus_bound_rx * (claim_Az * claim_Bz - claim_Cz); + if claim_outer_final != claim_outer_final_expected { + return Err(SpartanError::InvalidSumcheckProof); + } + + transcript.absorb( + b"claims_outer", + &[ + self.claims_outer.0, + self.claims_outer.1, + self.claims_outer.2, + ] + .as_slice(), + ); + + // inner sum-check + let r = transcript.squeeze(b"r")?; + let claim_inner_joint = + self.claims_outer.0 + r * self.claims_outer.1 + r * r * self.claims_outer.2; + + let (claim_inner_final, r_y) = + self + .sc_proof_inner + .verify(claim_inner_joint, num_rounds_y, 2, &mut transcript)?; + + + // verify claim_inner_final + let N_PREFIX = (vk.num_vars_total.trailing_zeros() as usize - vk.num_steps.trailing_zeros() as usize) + 1; + + let eval_Z = { + let eval_X = { + // constant term + let mut poly_X = vec![(0, 1.into())]; + //remaining inputs + poly_X.extend( + (0..u.X.len()) + .map(|i| (i + 1, u.X[i])) + .collect::>(), + ); + SparsePolynomial::new(usize::try_from(vk.num_vars_total.ilog2()).unwrap(), poly_X) + .evaluate(&r_y[1..]) + }; + + // evaluate the segments of W + let r_y_witness = &r_y[1..N_PREFIX]; // skip the first as it's used to separate the inputs and the witness + let eval_W = (0..N_SEGMENTS).map(|i| { + let bin = format!("{:0width$b}", i, width = N_PREFIX-1); // write i in binary using N_PREFIX bits + + let product = bin.chars().enumerate().fold(G::Scalar::ONE, |acc, (j, bit)| { + acc * if bit == '0' { + G::Scalar::ONE - r_y_witness[j] + } else { + r_y_witness[j] + } + }); + + product * self.eval_W[i] + }).sum::(); + + (G::Scalar::ONE - r_y[0]) * eval_W + r_y[0] * eval_X + }; + + // compute evaluations of R1CS matrices + let multi_evaluate_uniform = |M_vec: &[&[(usize, usize, G::Scalar)]], + r_x: &[G::Scalar], + r_y: &[G::Scalar], + num_steps: usize,| + -> Vec { + let evaluate_with_table_uniform = + |M: &[(usize, usize, G::Scalar)], T_x: &[G::Scalar], T_y: &[G::Scalar], num_steps: usize| -> G::Scalar { + (0..M.len()) + .into_par_iter() + .map(|i| { + let (row, col, val) = M[i]; + (0..num_steps).into_par_iter().map(|j| { + let row = row * num_steps + j; + let col = if col != vk.S_single.num_vars { col * num_steps + j } else { vk.num_vars_total }; + let val = val * T_x[row] * T_y[col]; + val + }) + .sum::() + }) + .sum() + }; + + let (T_x, T_y) = rayon::join( + || EqPolynomial::new(r_x.to_vec()).evals(), + || EqPolynomial::new(r_y.to_vec()).evals(), + ); + + (0..M_vec.len()) + .into_par_iter() + .map(|i| evaluate_with_table_uniform(M_vec[i], &T_x, &T_y, num_steps)) + .collect() + }; + + + let evals = multi_evaluate_uniform(&[&vk.S_single.A, &vk.S_single.B, &vk.S_single.C], &r_x, &r_y, vk.num_steps); + + let claim_inner_final_expected = (evals[0] + r * evals[1] + r * r * evals[2]) * eval_Z; + if claim_inner_final != claim_inner_final_expected { + // DEDUPE(arasuarun): add + return Err(SpartanError::InvalidSumcheckProof); + } + + // we now combine evaluation claims at the same point rz into one + let comm_vec = comm_W_vec; + let eval_vec = &self.eval_W; + + let r_y_point = &r_y[N_PREFIX..]; + let c = transcript.squeeze(b"c")?; + let u: PolyEvalInstance = PolyEvalInstance::batch(&comm_vec, &r_y_point, &eval_vec, &c); + + // verify + EE::verify( + &vk.vk_ee, + &mut transcript, + &u.c, + &r_y_point, + &u.e, + &self.eval_arg, + )?; + + Ok(()) + } } diff --git a/src/traits/upsnark.rs b/src/traits/upsnark.rs index 9db0cde..7f7c49b 100644 --- a/src/traits/upsnark.rs +++ b/src/traits/upsnark.rs @@ -3,6 +3,8 @@ use crate::{errors::SpartanError, traits::Group}; //, CommitmentKey, Commitment} use bellpepper_core::Circuit; use serde::{Deserialize, Serialize}; use crate::traits::snark::RelaxedR1CSSNARKTrait; +use crate::{CommitmentKey, Commitment}; + /// A SNARK that derives the R1CS Shape given a single step's shape pub trait UniformSNARKTrait: @@ -20,13 +22,20 @@ pub trait PrecommittedSNARKTrait: Sized + Send + Sync + Serialize + for<'de> Deserialize<'de> + UniformSNARKTrait { /// Setup that takes in the generators used to pre-committed the witness - /// TODO(arasuarun): currently just sets up the circuit with variable-wise uniformity fn setup_precommitted>( circuit: C, num_steps: usize, + ck: CommitmentKey, ) -> Result<(Self::ProverKey, Self::VerifierKey), SpartanError>; -// /// Produces a new SNARK for a relaxed R1CS -// // fn prove_precommitted>(pk: &Self::ProverKey, circuit: C, comm_W: Commitment) -> Result; -// fn prove_precommitted>(pk: &Self::ProverKey, circuit: C, comm_W: Commitment) -> Result; + /// Produces a new SNARK for a relaxed R1CS + fn prove_precommitted>( + pk: &Self::ProverKey, + circuit: C, + w_segments: Vec>, + comm_w: Vec>, + ) -> Result; + + /// Verifies a SNARK for a relaxed R1CS + fn verify_precommitted(&self, vk: &Self::VerifierKey, io: &[G::Scalar]) -> Result<(), SpartanError>; } \ No newline at end of file From c6b3820b546d839af00c697a5ad727b3c8989ae6 Mon Sep 17 00:00:00 2001 From: Arasu Arun Date: Fri, 1 Mar 2024 20:56:52 -0500 Subject: [PATCH 02/16] NOT WORKING ARK -> SPARTAN --- src/provider/pedersen.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index 6bd70bc..444a996 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -17,9 +17,24 @@ use serde::{Deserialize, Serialize}; /// A type that holds commitment generators #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct CommitmentKey { - ck: Vec, + /// Commitment key + pub ck: Vec, } +impl CommitmentKey { + /// Creates a new commitment key from preprocessed generators + pub fn from_preprocessed_gens(generators: Vec) -> Self + where G::PreprocessedGroupElement: From { + let ck: Vec = generators.into_iter().map(|g| { + g.into() + }).collect(); + Self { + ck, + } + } +} + + /// A type that holds a commitment #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(bound = "")] From b07bd62205d69691052fe80094c5e94f5ac6e059 Mon Sep 17 00:00:00 2001 From: Arasu Arun Date: Fri, 1 Mar 2024 20:58:34 -0500 Subject: [PATCH 03/16] NOT WORKING ARK -> SPARTAN --- src/provider/pedersen.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index 444a996..1243d6a 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -24,7 +24,10 @@ pub struct CommitmentKey { impl CommitmentKey { /// Creates a new commitment key from preprocessed generators pub fn from_preprocessed_gens(generators: Vec) -> Self - where G::PreprocessedGroupElement: From { + // Arasu: we need to bridge SpartanAffine and G::PreprocessedGroupElement using something like this? + // If you uncomment the line below, Spartan will not compile. + // where G::PreprocessedGroupElement: From + { let ck: Vec = generators.into_iter().map(|g| { g.into() }).collect(); From 7b904c314c6276da697d1d45ae2a95879860b46b Mon Sep 17 00:00:00 2001 From: Arasu Arun Date: Tue, 5 Mar 2024 00:46:01 -0500 Subject: [PATCH 04/16] ck constructors --- src/provider/hyrax_pc.rs | 3 ++- src/provider/pedersen.rs | 33 ++++++++++++++++++++------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/provider/hyrax_pc.rs b/src/provider/hyrax_pc.rs index ba0ed51..0bafbe9 100644 --- a/src/provider/hyrax_pc.rs +++ b/src/provider/hyrax_pc.rs @@ -31,7 +31,8 @@ use std::marker::PhantomData; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] pub struct HyraxCommitmentKey { - ck: PedersenCommitmentKey, + /// ck + pub ck: PedersenCommitmentKey, } /// Structure that holds commitments diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index 1243d6a..a2af8bb 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -13,6 +13,7 @@ use core::{ }; use rayon::prelude::*; use serde::{Deserialize, Serialize}; +use super::bn256_grumpkin::bn256; /// A type that holds commitment generators #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -21,19 +22,25 @@ pub struct CommitmentKey { pub ck: Vec, } -impl CommitmentKey { - /// Creates a new commitment key from preprocessed generators - pub fn from_preprocessed_gens(generators: Vec) -> Self - // Arasu: we need to bridge SpartanAffine and G::PreprocessedGroupElement using something like this? - // If you uncomment the line below, Spartan will not compile. - // where G::PreprocessedGroupElement: From - { - let ck: Vec = generators.into_iter().map(|g| { - g.into() - }).collect(); - Self { - ck, - } +/// Generators ck for the bn256 curve +pub fn from_preprocessed_gens(generators: Vec) -> CommitmentKey +where SpartanAffine: halo2curves::CurveAffine, +{ + let ck: Vec<::PreprocessedGroupElement> = generators.into_iter().map(|g| { + let (x, y) = (*g.coordinates().unwrap().x(), *g.coordinates().unwrap().y()); + (bn256::Affine {x: x, y: y}).into() + }).collect(); + + CommitmentKey { + ck, + } +} + +/// Generators ck for the bn256 curve +pub fn from_gens_bn256(generators: Vec) -> CommitmentKey +{ + CommitmentKey { + ck: generators } } From 5ef3f3e137b64c6b85a1e8f0f89337c96ce58b47 Mon Sep 17 00:00:00 2001 From: Arasu Arun Date: Sun, 10 Mar 2024 17:36:51 -0400 Subject: [PATCH 05/16] From trait for Commitments --- src/provider/hyrax_pc.rs | 11 +++++++++++ src/provider/pedersen.rs | 9 +++++++++ src/spartan/upsnark.rs | 3 ++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/provider/hyrax_pc.rs b/src/provider/hyrax_pc.rs index 0bafbe9..32feb0c 100644 --- a/src/provider/hyrax_pc.rs +++ b/src/provider/hyrax_pc.rs @@ -19,6 +19,7 @@ use crate::{ Commitment, CommitmentKey, }; use core::ops::{Add, AddAssign, Mul, MulAssign}; +use crate::provider::bn256_grumpkin::bn256; use itertools::{ EitherOrBoth::{Both, Left, Right}, Itertools, @@ -60,6 +61,16 @@ impl Default for HyraxCommitment { } } +impl From> for HyraxCommitment { + fn from(v: Vec) -> Self { + + HyraxCommitment { + comm: v.iter().map(|x| PedersenCommitment::from(*x)).collect_vec(), + is_default: false, + } + } +} + impl CommitmentTrait for HyraxCommitment { type CompressedCommitment = HyraxCompressedCommitment; diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index a2af8bb..baeb651 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -85,6 +85,15 @@ impl Default for Commitment { } } +impl From for Commitment { + fn from(v: bn256::Affine) -> Self { + + Commitment { + comm: v.into(), + } + } +} + impl TranscriptReprTrait for Commitment { fn to_transcript_bytes(&self) -> Vec { let (x, y, is_infinity) = self.comm.to_coordinates(); diff --git a/src/spartan/upsnark.rs b/src/spartan/upsnark.rs index 7cab680..aeaaa44 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -225,7 +225,7 @@ impl> RelaxedR1CSSNARKTrait for R1CSSN let (num_rounds_x, num_rounds_y) = ( usize::try_from(pk.num_cons_total.ilog2()).unwrap(), - (usize::try_from(pk.num_vars_total.ilog2()).unwrap() + 1), + (usize::try_from(pk.num_vars_total.ilog2()).unwrap() + 1), // doubled because IO is padded to be equal to W later ); // outer sum-check @@ -913,6 +913,7 @@ impl> PrecommittedSNARKTrait for R1CSS // verify claim_inner_final + // this should be log (num segments) let N_PREFIX = (vk.num_vars_total.trailing_zeros() as usize - vk.num_steps.trailing_zeros() as usize) + 1; let eval_Z = { From 0ca30249579c98c33ef834fef9cd24d723934591 Mon Sep 17 00:00:00 2001 From: Arasu Arun Date: Sun, 10 Mar 2024 17:56:57 -0400 Subject: [PATCH 06/16] rebasing with main --- src/r1cs.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/r1cs.rs b/src/r1cs.rs index b12da4e..11ad113 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -250,7 +250,6 @@ impl R1CSShape { X: &[G::Scalar], num_steps: usize, ) -> Result<(Vec, Vec, Vec), SpartanError> { -<<<<<<< HEAD if W.len() + X.len() != (self.num_io + self.num_vars) * num_steps { return Err(SpartanError::InvalidWitnessLength); } @@ -268,12 +267,6 @@ impl R1CSShape { } }; -======= - // if z.len() != (self.num_io + self.num_vars) * num_steps + 1 { - // return Err(SpartanError::InvalidWitnessLength); - // } - ->>>>>>> aac138f (working precommitted) // Pre-processes matrix to return the indices of the start of each row let get_row_pointers = |M: &Vec<(usize, usize, G::Scalar)>| -> Vec { let mut indptr = vec![0; self.num_cons + 1]; From f10f0e57425300a7e79cccb16949bd181725aa1f Mon Sep 17 00:00:00 2001 From: Arasu Arun Date: Sun, 10 Mar 2024 18:05:28 -0400 Subject: [PATCH 07/16] rebased with main --- src/spartan/upsnark.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/spartan/upsnark.rs b/src/spartan/upsnark.rs index aeaaa44..9c4530d 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -15,7 +15,6 @@ use crate::{ }, digest::{DigestComputer, SimpleDigestible}, errors::SpartanError, - r1cs::{R1CSInstance, R1CSShape}, r1cs::{PrecommittedR1CSInstance, PrecommittedR1CSWitness, R1CSInstance, R1CSShape}, spartan::{ polys::{eq::EqPolynomial, multilinear::{MultilinearPolynomial, SparsePolynomial}}, @@ -402,7 +401,7 @@ impl> RelaxedR1CSSNARKTrait for R1CSSN sc_proof_outer, claims_outer: (claim_Az, claim_Bz, claim_Cz), sc_proof_inner, - eval_W: vec![eval_W], + eval_W: vec![], // Hack from above? eval_arg, }) } @@ -658,7 +657,6 @@ impl> PrecommittedSNARKTrait for R1CSS // compute the full satisfying assignment by concatenating W.W, U.u, and U.X let mut w_full = w.W.iter().flat_map(|segment| segment.clone()).collect::>(); w_full.extend(vec![G::Scalar::ZERO; pk.num_vars_total - w_full.len()]); - let mut z = [w_full, vec![1.into()], u.X.clone()].concat(); let (num_rounds_x, num_rounds_y) = ( @@ -674,7 +672,7 @@ impl> PrecommittedSNARKTrait for R1CSS let mut poly_tau = MultilinearPolynomial::new(EqPolynomial::new(tau).evals()); // poly_Az is the polynomial extended from the vector Az let (mut poly_Az, mut poly_Bz, mut poly_Cz) = { - let (poly_Az, poly_Bz, poly_Cz) = pk.S.multiply_vec_uniform(&z, pk.num_steps)?; + let (poly_Az, poly_Bz, poly_Cz) = pk.S.multiply_vec_uniform(&w_full, &u.X, pk.num_steps)?; ( MultilinearPolynomial::new(poly_Az), MultilinearPolynomial::new(poly_Bz), @@ -782,6 +780,7 @@ impl> PrecommittedSNARKTrait for R1CSS drop(_enter); drop(span); + let mut z = [w_full, vec![1.into()], u.X.clone()].concat(); let poly_z = { z.resize(pk.num_vars_total * 2, G::Scalar::ZERO); z @@ -825,7 +824,7 @@ impl> PrecommittedSNARKTrait for R1CSS &u.c, &w.p, &r_y_point, - &u.e + &mut Some(u.e), )?; Ok(R1CSSNARK { From e258e4ac79eea0018787d38fa1068fd87decc86d Mon Sep 17 00:00:00 2001 From: sragss Date: Tue, 12 Mar 2024 16:23:33 -0700 Subject: [PATCH 08/16] nits --- src/spartan/upsnark.rs | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/spartan/upsnark.rs b/src/spartan/upsnark.rs index 9c4530d..fd2d08e 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -655,9 +655,8 @@ impl> PrecommittedSNARKTrait for R1CSS transcript.absorb(b"U", &u); // compute the full satisfying assignment by concatenating W.W, U.u, and U.X - let mut w_full = w.W.iter().flat_map(|segment| segment.clone()).collect::>(); - w_full.extend(vec![G::Scalar::ZERO; pk.num_vars_total - w_full.len()]); - + let mut witness = w.W.iter().flat_map(|segment| segment.clone()).collect::>(); + witness.resize(pk.num_vars_total, G::Scalar::ZERO); let (num_rounds_x, num_rounds_y) = ( usize::try_from(pk.num_cons_total.ilog2()).unwrap(), @@ -672,7 +671,7 @@ impl> PrecommittedSNARKTrait for R1CSS let mut poly_tau = MultilinearPolynomial::new(EqPolynomial::new(tau).evals()); // poly_Az is the polynomial extended from the vector Az let (mut poly_Az, mut poly_Bz, mut poly_Cz) = { - let (poly_Az, poly_Bz, poly_Cz) = pk.S.multiply_vec_uniform(&w_full, &u.X, pk.num_steps)?; + let (poly_Az, poly_Bz, poly_Cz) = pk.S.multiply_vec_uniform(&witness, &u.X, pk.num_steps)?; ( MultilinearPolynomial::new(poly_Az), MultilinearPolynomial::new(poly_Bz), @@ -686,6 +685,7 @@ impl> PrecommittedSNARKTrait for R1CSS poly_C_comp: &G::Scalar, poly_D_comp: &G::Scalar| -> G::Scalar { *poly_A_comp * (*poly_B_comp * *poly_C_comp - *poly_D_comp) }; + let (sc_proof_outer, r_x, claims_outer) = SumcheckProof::prove_cubic_with_additive_term( &G::Scalar::ZERO, // claim is zero num_rounds_x, @@ -699,8 +699,7 @@ impl> PrecommittedSNARKTrait for R1CSS // claims from the end of sum-check // claim_Az is the (scalar) value v_A = \sum_y A(r_x, y) * z(r_x) where r_x is the sumcheck randomness - let (claim_Az, claim_Bz): (G::Scalar, G::Scalar) = (claims_outer[1], claims_outer[2]); - let claim_Cz = claims_outer[3]; + let (claim_Az, claim_Bz, claim_Cz): (G::Scalar, G::Scalar, G::Scalar) = (claims_outer[1], claims_outer[2], claims_outer[3]); transcript.absorb( b"claims_outer", &[claim_Az, claim_Bz, claim_Cz].as_slice(), @@ -715,12 +714,14 @@ impl> PrecommittedSNARKTrait for R1CSS // this is the polynomial extended from the vector r_A * A(r_x, y) + r_B * B(r_x, y) + r_C * C(r_x, y) for all y let poly_ABC = { - let NUM_STEPS_BITS = pk.num_steps.trailing_zeros(); - let (rx_con, rx_ts) = r_x.split_at(r_x.len() - NUM_STEPS_BITS as usize); - let eq_rx_con = EqPolynomial::new(rx_con.to_vec()).evals(); - let eq_rx_ts = EqPolynomial::new(rx_ts.to_vec()).evals(); + let num_steps_bits = pk.num_steps.trailing_zeros(); + let (rx_con, rx_ts) = r_x.split_at(r_x.len() - num_steps_bits as usize); + let (eq_rx_con, eq_rx_ts) = rayon::join( + || EqPolynomial::new(rx_con.to_vec()).evals(), + || EqPolynomial::new(rx_ts.to_vec()).evals(), + ); - let N_STEPS = pk.num_steps; + let n_steps = pk.num_steps; // With uniformity, each entry of the RLC of A, B, C can be expressed using // the RLC of the small_A, small_B, small_C matrices. @@ -742,13 +743,14 @@ impl> PrecommittedSNARKTrait for R1CSS ), ); + let r_sq = r * r; let small_RLC_evals = (0..small_A_evals.len()).into_par_iter().map(|i| { - small_A_evals[i] + small_B_evals[i] * r + small_C_evals[i] * r * r + small_A_evals[i] + small_B_evals[i] * r + small_C_evals[i] * r_sq }).collect::>(); // 2. Handles all entries but the last one with the constant 1 variable let mut RLC_evals: Vec = (0..pk.num_vars_total).into_par_iter().map(|col| { - eq_rx_ts[col % N_STEPS] * small_RLC_evals[col / N_STEPS] + eq_rx_ts[col % n_steps] * small_RLC_evals[col / n_steps] }).collect(); let next_pow_2 = 2 * pk.num_vars_total; RLC_evals.resize(next_pow_2, G::Scalar::ZERO); @@ -758,7 +760,7 @@ impl> PrecommittedSNARKTrait for R1CSS let constant_sum: G::Scalar = small_M.iter() .filter(|(_, col, _)| *col == pk.S.num_vars) // expecting ~1 .map(|(row, _, val)| { - let eq_sum = (0..N_STEPS).into_par_iter().map(|t| eq_rx_ts[t]).sum::(); + let eq_sum = (0..n_steps).into_par_iter().map(|t| eq_rx_ts[t]).sum::(); *val * eq_rx_con[*row] * eq_sum }).sum(); @@ -780,7 +782,7 @@ impl> PrecommittedSNARKTrait for R1CSS drop(_enter); drop(span); - let mut z = [w_full, vec![1.into()], u.X.clone()].concat(); + let mut z = [witness, vec![1.into()], u.X.clone()].concat(); let poly_z = { z.resize(pk.num_vars_total * 2, G::Scalar::ZERO); z @@ -802,8 +804,8 @@ impl> PrecommittedSNARKTrait for R1CSS // The number of prefix bits needed to identify a segment within the witness vector // assuming that num_vars_total is a power of 2 and each segment has length num_steps, which is also a power of 2. // The +1 is the first element used to separate the inputs and the witness. - let N_PREFIX = (pk.num_vars_total.trailing_zeros() as usize - pk.num_steps.trailing_zeros() as usize) + 1; - let r_y_point = &r_y[N_PREFIX..]; + let n_prefix = (pk.num_vars_total.trailing_zeros() as usize - pk.num_steps.trailing_zeros() as usize) + 1; + let r_y_point = &r_y[n_prefix..]; // Evaluate each segment on r_y_point let eval_vec = w.W.iter().map(|segment| { From 3a3dc501c13f90cf211757c522d44873738683d3 Mon Sep 17 00:00:00 2001 From: sragss Date: Tue, 12 Mar 2024 16:33:46 -0700 Subject: [PATCH 09/16] don't clone before second sumcheck --- src/lib.rs | 2 +- src/spartan/upsnark.rs | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c03917e..c6f9f1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! This library implements Spartan, a high-speed SNARK. #![deny( - warnings, + // warnings, // unused, future_incompatible, nonstandard_style, diff --git a/src/spartan/upsnark.rs b/src/spartan/upsnark.rs index fd2d08e..06cd607 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -782,24 +782,24 @@ impl> PrecommittedSNARKTrait for R1CSS drop(_enter); drop(span); - let mut z = [witness, vec![1.into()], u.X.clone()].concat(); - let poly_z = { - z.resize(pk.num_vars_total * 2, G::Scalar::ZERO); - z - }; - let comb_func = |poly_A_comp: &G::Scalar, poly_B_comp: &G::Scalar| -> G::Scalar { - *poly_A_comp * *poly_B_comp + if *poly_A_comp == G::Scalar::ZERO || *poly_B_comp == G::Scalar::ZERO { + G::Scalar::ZERO + } else { + *poly_A_comp * *poly_B_comp + } }; - let (sc_proof_inner, r_y, _claims_inner) = SumcheckProof::prove_quad( + let mut poly_ABC = MultilinearPolynomial::new(poly_ABC); + let (sc_proof_inner, r_y, _claims_inner) = SumcheckProof::prove_quad_unrolled( &claim_inner_joint, // r_A * v_A + r_B * v_B + r_C * v_C num_rounds_y, - &mut MultilinearPolynomial::new(poly_ABC), // r_A * A(r_x, y) + r_B * B(r_x, y) + r_C * C(r_x, y) for all y - &mut MultilinearPolynomial::new(poly_z), // z(y) for all y + &mut poly_ABC, // r_A * A(r_x, y) + r_B * B(r_x, y) + r_C * C(r_x, y) for all y + &witness, + &u.X, comb_func, &mut transcript, )?; - + std::thread::spawn(|| drop(poly_ABC)); // The number of prefix bits needed to identify a segment within the witness vector // assuming that num_vars_total is a power of 2 and each segment has length num_steps, which is also a power of 2. From c725ee8b3d55e6307f5be36cfb878ded401f4b4e Mon Sep 17 00:00:00 2001 From: sragss Date: Tue, 12 Mar 2024 16:51:12 -0700 Subject: [PATCH 10/16] better tracing; optimized sumcheck combo function; better drops --- src/spartan/mod.rs | 4 +++- src/spartan/upsnark.rs | 40 +++++++++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/spartan/mod.rs b/src/spartan/mod.rs index 13a768a..a036f22 100644 --- a/src/spartan/mod.rs +++ b/src/spartan/mod.rs @@ -67,9 +67,10 @@ impl PolyEvalWitness { PolyEvalWitness { p } } + #[tracing::instrument(skip_all)] fn batch(p_vec: &[&Vec], s: &G::Scalar) -> PolyEvalWitness { let powers_of_s = powers::(s, p_vec.len()); - let mut p = vec![G::Scalar::ZERO; p_vec[0].len()]; + let mut p: Vec<::Scalar> = vec![G::Scalar::ZERO; p_vec[0].len()]; for i in 0..p_vec.len() { for (j, item) in p.iter_mut().enumerate().take(p_vec[i].len()) { *item += p_vec[i][j] * powers_of_s[i] @@ -106,6 +107,7 @@ impl PolyEvalInstance { } } + #[tracing::instrument(skip_all)] fn batch( c_vec: &[Commitment], x: &[G::Scalar], diff --git a/src/spartan/upsnark.rs b/src/spartan/upsnark.rs index 06cd607..276fda0 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -679,12 +679,22 @@ impl> PrecommittedSNARKTrait for R1CSS ) }; - let comb_func_outer = - |poly_A_comp: &G::Scalar, + let comb_func_outer = |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 * *poly_C_comp - *poly_D_comp) }; + -> G::Scalar { + // Below is an optimized form of: *poly_A_comp * (*poly_B_comp * *poly_C_comp - *poly_D_comp) + if *poly_B_comp == G::Scalar::ZERO || *poly_C_comp == G::Scalar::ZERO { + if *poly_D_comp == G::Scalar::ZERO { + G::Scalar::ZERO + } else { + *poly_A_comp * (-(*poly_D_comp)) + } + } else { + *poly_A_comp * (*poly_B_comp * *poly_C_comp - *poly_D_comp) + } + }; let (sc_proof_outer, r_x, claims_outer) = SumcheckProof::prove_cubic_with_additive_term( &G::Scalar::ZERO, // claim is zero @@ -696,6 +706,10 @@ impl> PrecommittedSNARKTrait for R1CSS comb_func_outer, &mut transcript, )?; + std::thread::spawn(|| drop(poly_Az)); + std::thread::spawn(|| drop(poly_Bz)); + std::thread::spawn(|| drop(poly_Cz)); + std::thread::spawn(|| drop(poly_tau)); // claims from the end of sum-check // claim_Az is the (scalar) value v_A = \sum_y A(r_x, y) * z(r_x) where r_x is the sumcheck randomness @@ -808,15 +822,17 @@ impl> PrecommittedSNARKTrait for R1CSS let r_y_point = &r_y[n_prefix..]; // Evaluate each segment on r_y_point - let eval_vec = w.W.iter().map(|segment| { + let span = tracing::span!(tracing::Level::TRACE, "evaluate_segments"); + let _enter = span.enter(); + let eval_vec = w.W.par_iter().map(|segment| { MultilinearPolynomial::evaluate_with(segment, &r_y_point) }).collect::>(); - let poly_vec = w.W.as_slice(); + drop(_enter); let comm_vec = comm_w_vec; // now batch these together let c = transcript.squeeze(b"c")?; - let w: PolyEvalWitness = PolyEvalWitness::batch(&poly_vec.iter().map(|v| v.as_ref()).collect::>(), &c); + let w: PolyEvalWitness = PolyEvalWitness::batch(&w.W.as_slice().iter().map(|v| v.as_ref()).collect::>(), &c); let u: PolyEvalInstance = PolyEvalInstance::batch(&comm_vec, &r_y_point, &eval_vec, &c); let eval_arg = EE::prove( @@ -829,8 +845,10 @@ impl> PrecommittedSNARKTrait for R1CSS &mut Some(u.e), )?; + let compressed_commitments = comm_vec.par_iter().map(|elem| elem.compress()).collect::>(); + Ok(R1CSSNARK { - comm_W: comm_vec.iter().map(|elem| elem.compress()).collect::>(), + comm_W: compressed_commitments, sc_proof_outer, claims_outer: (claim_Az, claim_Bz, claim_Cz), sc_proof_inner, @@ -915,7 +933,7 @@ impl> PrecommittedSNARKTrait for R1CSS // verify claim_inner_final // this should be log (num segments) - let N_PREFIX = (vk.num_vars_total.trailing_zeros() as usize - vk.num_steps.trailing_zeros() as usize) + 1; + let n_prefix = (vk.num_vars_total.trailing_zeros() as usize - vk.num_steps.trailing_zeros() as usize) + 1; let eval_Z = { let eval_X = { @@ -932,9 +950,9 @@ impl> PrecommittedSNARKTrait for R1CSS }; // evaluate the segments of W - let r_y_witness = &r_y[1..N_PREFIX]; // skip the first as it's used to separate the inputs and the witness + let r_y_witness = &r_y[1..n_prefix]; // skip the first as it's used to separate the inputs and the witness let eval_W = (0..N_SEGMENTS).map(|i| { - let bin = format!("{:0width$b}", i, width = N_PREFIX-1); // write i in binary using N_PREFIX bits + let bin = format!("{:0width$b}", i, width = n_prefix-1); // write i in binary using N_PREFIX bits let product = bin.chars().enumerate().fold(G::Scalar::ONE, |acc, (j, bit)| { acc * if bit == '0' { @@ -997,7 +1015,7 @@ impl> PrecommittedSNARKTrait for R1CSS let comm_vec = comm_W_vec; let eval_vec = &self.eval_W; - let r_y_point = &r_y[N_PREFIX..]; + let r_y_point = &r_y[n_prefix..]; let c = transcript.squeeze(b"c")?; let u: PolyEvalInstance = PolyEvalInstance::batch(&comm_vec, &r_y_point, &eval_vec, &c); From 5fcfde3e3cd0048d01acc7ee84d3fc7820886d09 Mon Sep 17 00:00:00 2001 From: sragss Date: Tue, 12 Mar 2024 17:04:06 -0700 Subject: [PATCH 11/16] dedupe work on chi opening proofs --- src/spartan/polys/multilinear.rs | 13 +++++++++++++ src/spartan/upsnark.rs | 8 +++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/spartan/polys/multilinear.rs b/src/spartan/polys/multilinear.rs index a2a6ad5..7025ac4 100644 --- a/src/spartan/polys/multilinear.rs +++ b/src/spartan/polys/multilinear.rs @@ -137,6 +137,19 @@ impl MultilinearPolynomial { .sum() } + /// Evaluates |Zs| different polynomials at a single point r. + pub fn batch_evaluate(Zs: &[Vec], r: &[Scalar]) -> Vec { + let chi = EqPolynomial::new(r.to_vec()).evals(); + + let results: Vec = Zs.par_iter() + .map(|Z| { + chi.par_iter().zip(Z.into_par_iter()) + .map(|(a, b)| *a * b) + .sum() + }).collect(); + results + } + /// Evaluates polynomial given lagrange basis #[tracing::instrument(skip_all, name = "MultilinearPolynomial::evaluate_with_chi")] pub fn evaluate_with_chi(&self, chis: &[Scalar]) -> Scalar { diff --git a/src/spartan/upsnark.rs b/src/spartan/upsnark.rs index 276fda0..629f263 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -824,16 +824,14 @@ impl> PrecommittedSNARKTrait for R1CSS // Evaluate each segment on r_y_point let span = tracing::span!(tracing::Level::TRACE, "evaluate_segments"); let _enter = span.enter(); - let eval_vec = w.W.par_iter().map(|segment| { - MultilinearPolynomial::evaluate_with(segment, &r_y_point) - }).collect::>(); + let witness_evals = MultilinearPolynomial::batch_evaluate(&w.W, &r_y_point); drop(_enter); let comm_vec = comm_w_vec; // now batch these together let c = transcript.squeeze(b"c")?; let w: PolyEvalWitness = PolyEvalWitness::batch(&w.W.as_slice().iter().map(|v| v.as_ref()).collect::>(), &c); - let u: PolyEvalInstance = PolyEvalInstance::batch(&comm_vec, &r_y_point, &eval_vec, &c); + let u: PolyEvalInstance = PolyEvalInstance::batch(&comm_vec, &r_y_point, &witness_evals, &c); let eval_arg = EE::prove( &pk.ck, @@ -852,7 +850,7 @@ impl> PrecommittedSNARKTrait for R1CSS sc_proof_outer, claims_outer: (claim_Az, claim_Bz, claim_Cz), sc_proof_inner, - eval_W: eval_vec, + eval_W: witness_evals, eval_arg, }) } From a73d6e26a0196545d7226e80788e24521dc2e9b1 Mon Sep 17 00:00:00 2001 From: sragss Date: Tue, 12 Mar 2024 17:15:16 -0700 Subject: [PATCH 12/16] optimize witness batching --- src/spartan/upsnark.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/spartan/upsnark.rs b/src/spartan/upsnark.rs index 629f263..3bdd078 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -655,8 +655,19 @@ impl> PrecommittedSNARKTrait for R1CSS transcript.absorb(b"U", &u); // compute the full satisfying assignment by concatenating W.W, U.u, and U.X - let mut witness = w.W.iter().flat_map(|segment| segment.clone()).collect::>(); + // TODO(sragss/arasuarun/moodlezoup): We can do this by reference in prove_quad_batched_unrolled. + let span = tracing::span!(tracing::Level::INFO, "witness_batching"); + let _guard = span.enter(); + let mut witness = Vec::with_capacity(w.W.len() * w.W[0].len()); + w.W.iter().for_each(|segment| { + witness.par_extend(segment); + }); + drop(_guard); + + let span = tracing::span!(tracing::Level::INFO, "witness_resizing"); + let _guard = span.enter(); witness.resize(pk.num_vars_total, G::Scalar::ZERO); + drop(_guard); let (num_rounds_x, num_rounds_y) = ( usize::try_from(pk.num_cons_total.ilog2()).unwrap(), From 5974413ea6fe56de6ad3b41fbc4d8aa504bdce9b Mon Sep 17 00:00:00 2001 From: sragss Date: Tue, 12 Mar 2024 17:19:10 -0700 Subject: [PATCH 13/16] add tracing for 'commit u' --- src/spartan/upsnark.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/spartan/upsnark.rs b/src/spartan/upsnark.rs index 3bdd078..0b6f7fa 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -652,7 +652,11 @@ impl> PrecommittedSNARKTrait for R1CSS // append the digest of vk (which includes R1CS matrices) and the RelaxedR1CSInstance to the transcript transcript.absorb(b"vk", &pk.vk_digest); + + let span_u = tracing::span!(tracing::Level::INFO, "absorb_u"); + let _guard_u = span_u.enter(); transcript.absorb(b"U", &u); + drop(_guard_u); // compute the full satisfying assignment by concatenating W.W, U.u, and U.X // TODO(sragss/arasuarun/moodlezoup): We can do this by reference in prove_quad_batched_unrolled. From f36b2d4bd8db439e1edee0d316c83e2752b43a0a Mon Sep 17 00:00:00 2001 From: Arasu Arun Date: Tue, 12 Mar 2024 22:52:26 -0400 Subject: [PATCH 14/16] use jolt matrix dimensions --- src/spartan/polys/eq.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/spartan/polys/eq.rs b/src/spartan/polys/eq.rs index b573ad3..d8403d8 100644 --- a/src/spartan/polys/eq.rs +++ b/src/spartan/polys/eq.rs @@ -3,6 +3,8 @@ use ff::PrimeField; use rayon::prelude::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; +use crate::spartan::math::Math; + /// Represents the multilinear extension polynomial (MLE) of the equality polynomial $eq(x,e)$, denoted as $\tilde{eq}(x, e)$. /// /// The polynomial is defined by the formula: @@ -69,8 +71,15 @@ impl EqPolynomial { } /// Computes the lengths of the left and right halves of the `EqPolynomial`'s vector `r`. + /// Note: Using Jolt implementation with matrix_aspect_ratio = 1 pub fn compute_factored_lens(ell: usize) -> (usize, usize) { - (ell / 2, ell - ell / 2) + let mut row_size = (ell/ 2).pow2(); + row_size = row_size.next_power_of_two(); + + let right_num_vars = std::cmp::min(row_size.log_2(), ell - 1); + let left_num_vars = ell - right_num_vars; + + (left_num_vars, right_num_vars) } /// Computes the left and right halves of the `EqPolynomial`. From e56fc757f242c14485b95afb1e36338affee9113 Mon Sep 17 00:00:00 2001 From: sragss Date: Wed, 13 Mar 2024 11:06:28 -0700 Subject: [PATCH 15/16] more tracing --- src/provider/hyrax_pc.rs | 17 ++++++++++++----- src/spartan/mod.rs | 32 ++++++++++++++++++++++++-------- src/spartan/upsnark.rs | 2 +- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/provider/hyrax_pc.rs b/src/provider/hyrax_pc.rs index 32feb0c..528b20c 100644 --- a/src/provider/hyrax_pc.rs +++ b/src/provider/hyrax_pc.rs @@ -95,12 +95,13 @@ impl CommitmentTrait for HyraxCommitment { } impl MulAssign for HyraxCommitment { + #[tracing::instrument(skip_all)] fn mul_assign(&mut self, scalar: G::Scalar) { let result = (self as &HyraxCommitment) .comm - .iter() + .par_iter() .map(|c| c * &scalar) - .collect(); + .collect::>(); *self = HyraxCommitment { comm: result, is_default: self.is_default, @@ -110,8 +111,10 @@ impl MulAssign for HyraxCommitment { impl<'a, 'b, G: Group> Mul<&'b G::Scalar> for &'a HyraxCommitment { type Output = HyraxCommitment; + + #[tracing::instrument(name="HyraxCommitment::mul(&scalar)", skip_all)] fn mul(self, scalar: &'b G::Scalar) -> HyraxCommitment { - let result = self.comm.iter().map(|c| c * scalar).collect(); + let result = self.comm.par_iter().map(|c| c * scalar).collect::>(); HyraxCommitment { comm: result, is_default: self.is_default, @@ -122,8 +125,9 @@ impl<'a, 'b, G: Group> Mul<&'b G::Scalar> for &'a HyraxCommitment { impl Mul for HyraxCommitment { type Output = HyraxCommitment; + #[tracing::instrument(name="HyraxCommitment::mul(scalar)", skip_all)] fn mul(self, scalar: G::Scalar) -> HyraxCommitment { - let result = self.comm.iter().map(|c| c * &scalar).collect(); + let result = self.comm.par_iter().map(|c| c * &scalar).collect::>(); HyraxCommitment { comm: result, is_default: self.is_default, @@ -132,6 +136,7 @@ impl Mul for HyraxCommitment { } impl<'b, G: Group> AddAssign<&'b HyraxCommitment> for HyraxCommitment { + #[tracing::instrument(skip_all)] fn add_assign(&mut self, other: &'b HyraxCommitment) { if self.is_default { *self = other.clone(); @@ -147,7 +152,7 @@ impl<'b, G: Group> AddAssign<&'b HyraxCommitment> for HyraxCommitment { Left(a) => *a, Right(b) => *b, }) - .collect(); + .collect::>(); *self = HyraxCommitment { comm: result, is_default: self.is_default, @@ -158,6 +163,8 @@ impl<'b, G: Group> AddAssign<&'b HyraxCommitment> for HyraxCommitment { impl<'a, 'b, G: Group> Add<&'b HyraxCommitment> for &'a HyraxCommitment { type Output = HyraxCommitment; + + #[tracing::instrument(skip_all)] fn add(self, other: &'b HyraxCommitment) -> HyraxCommitment { if self.is_default { other.clone() diff --git a/src/spartan/mod.rs b/src/spartan/mod.rs index a036f22..b31acb3 100644 --- a/src/spartan/mod.rs +++ b/src/spartan/mod.rs @@ -71,11 +71,13 @@ impl PolyEvalWitness { fn batch(p_vec: &[&Vec], s: &G::Scalar) -> PolyEvalWitness { let powers_of_s = powers::(s, p_vec.len()); let mut p: Vec<::Scalar> = vec![G::Scalar::ZERO; p_vec[0].len()]; - for i in 0..p_vec.len() { - for (j, item) in p.iter_mut().enumerate().take(p_vec[i].len()) { - *item += p_vec[i][j] * powers_of_s[i] + p.par_iter_mut().enumerate().for_each(|(j, item)| { + for i in 0..p_vec.len() { + if j < p_vec[i].len() { + *item += p_vec[i][j] * powers_of_s[i]; + } } - } + }); PolyEvalWitness { p } } } @@ -114,22 +116,36 @@ impl PolyEvalInstance { e_vec: &[G::Scalar], s: &G::Scalar, ) -> PolyEvalInstance { + let span = tracing::span!(tracing::Level::INFO, "powers_of_s_calculation"); + let _guard = span.enter(); let powers_of_s = powers::(s, c_vec.len()); + drop(_guard); + println!("c_vec len {:?}", c_vec.len()); let e = e_vec .iter() .zip(powers_of_s.iter()) .map(|(e, p)| *e * p) .sum(); + let span_compute_RLC = tracing::span!(tracing::Level::INFO, "compute_RLC"); + let _guard_compute_RLC = span_compute_RLC.enter(); + let c = c_vec - .iter() - .zip(powers_of_s.iter()) + .par_iter() + .zip(powers_of_s.par_iter()) .map(|(c, p)| c.clone() * *p) - .fold(Commitment::::default(), |acc, item| acc + item); + .reduce_with(|acc, item| acc + item) + .unwrap_or_else(Commitment::::default); + // let c = G::vartime_multiscalar_mul(&powers_of_s, c_vec.iter().map(|c| c.preprocessed()).collect()); + // use crate::traits::commitment::CommitmentTrait; + // let raw_gs: Vec = c_vec.iter().map(|c| c.raw()).collect(); + // let pre_processed = c_vec.iter().map(|c| c.clone().raw().preprocessed()).collect(); + // let c = G::vartime_multiscalar_mul(&powers_of_s, &pre_processed); + drop(_guard_compute_RLC); PolyEvalInstance { c, x: x.to_vec(), - e, + e } } } diff --git a/src/spartan/upsnark.rs b/src/spartan/upsnark.rs index 0b6f7fa..cfa7e08 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -589,7 +589,7 @@ impl> UniformSNARKTrait for R1CSSNARK< } impl> PrecommittedSNARKTrait for R1CSSNARK { - #[tracing::instrument(skip_all, name = "SNARK::setup_uniform")] + #[tracing::instrument(skip_all, name = "SNARK::setup_precommitted")] fn setup_precommitted>( circuit: C, num_steps: usize, From 0463c98bbc37b702e89dc47c295bfed2ec5eebcd Mon Sep 17 00:00:00 2001 From: sragss Date: Wed, 13 Mar 2024 13:34:41 -0700 Subject: [PATCH 16/16] remove call to JoltCircuit::synthesize --- src/spartan/upsnark.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spartan/upsnark.rs b/src/spartan/upsnark.rs index cfa7e08..0068db9 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -627,8 +627,8 @@ impl> PrecommittedSNARKTrait for R1CSS w_segments: Vec>, comm_w_vec: Vec>, ) -> Result { - let mut cs: SatisfyingAssignment = SatisfyingAssignment::new(); - let _ = circuit.synthesize(&mut cs); + // let mut cs: SatisfyingAssignment = SatisfyingAssignment::new(); + // let _ = circuit.synthesize(&mut cs); // Create a hollow shape with the right dimensions but no matrices. // This is a convenience to work with Spartan's r1cs_instance_and_witness function