diff --git a/src/lib.rs b/src/lib.rs index f1f8835..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, @@ -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/provider/hyrax_pc.rs b/src/provider/hyrax_pc.rs index ba0ed51..528b20c 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, @@ -31,7 +32,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 @@ -59,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; @@ -83,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, @@ -98,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, @@ -110,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, @@ -120,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(); @@ -135,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, @@ -146,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/provider/pedersen.rs b/src/provider/pedersen.rs index 6bd70bc..baeb651 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -13,13 +13,38 @@ 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)] pub struct CommitmentKey { - ck: Vec, + /// Commitment key + pub ck: Vec, } +/// 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 + } +} + + /// A type that holds a commitment #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(bound = "")] @@ -60,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/r1cs.rs b/src/r1cs.rs index a6c3599..11ad113 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -785,3 +785,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/mod.rs b/src/spartan/mod.rs index 13a768a..b31acb3 100644 --- a/src/spartan/mod.rs +++ b/src/spartan/mod.rs @@ -67,14 +67,17 @@ 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()]; - 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] + let mut p: Vec<::Scalar> = vec![G::Scalar::ZERO; p_vec[0].len()]; + 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 } } } @@ -106,28 +109,43 @@ impl PolyEvalInstance { } } + #[tracing::instrument(skip_all)] fn batch( c_vec: &[Commitment], x: &[G::Scalar], 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/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`. 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 2548cfe..0068db9 100644 --- a/src/spartan/upsnark.rs +++ b/src/spartan/upsnark.rs @@ -15,17 +15,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 +34,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 +146,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, } @@ -224,7 +224,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 @@ -397,11 +397,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![], // Hack from above? eval_arg, }) } @@ -410,7 +410,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 +487,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 +551,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 +575,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, @@ -589,14 +589,17 @@ 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, + 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 +618,430 @@ 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); + + 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. + 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(), + (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(&witness, &u.X, 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 { + // 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 + num_rounds_x, + &mut poly_tau, + &mut poly_Az, + &mut poly_Bz, + &mut poly_Cz, + 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 + 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(), + ); + + // 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, 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; + + // 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 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_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] + }).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 comb_func = |poly_A_comp: &G::Scalar, poly_B_comp: &G::Scalar| -> G::Scalar { + if *poly_A_comp == G::Scalar::ZERO || *poly_B_comp == G::Scalar::ZERO { + G::Scalar::ZERO + } else { + *poly_A_comp * *poly_B_comp + } + }; + 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 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. + // 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 span = tracing::span!(tracing::Level::TRACE, "evaluate_segments"); + let _enter = span.enter(); + 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, &witness_evals, &c); + + let eval_arg = EE::prove( + &pk.ck, + &pk.pk_ee, + &mut transcript, + &u.c, + &w.p, + &r_y_point, + &mut Some(u.e), + )?; + + let compressed_commitments = comm_vec.par_iter().map(|elem| elem.compress()).collect::>(); + + Ok(R1CSSNARK { + comm_W: compressed_commitments, + sc_proof_outer, + claims_outer: (claim_Az, claim_Bz, claim_Cz), + sc_proof_inner, + eval_W: witness_evals, + 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 + // 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 = { + 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