diff --git a/Cargo.toml b/Cargo.toml index a040868..89994ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,9 @@ flate2 = "1.0" bitvec = "1.0" byteorder = "1.4.3" thiserror = "1.0" -halo2curves = { version = "0.1.0", features = ["derive_serde"] } +halo2curves = { version = "0.4.0", features = ["derive_serde"] } +group = "0.13.0" +once_cell = "1.18.0" [target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] pasta-msm = { version = "0.1.4" } diff --git a/src/lib.rs b/src/lib.rs index 92c0b9e..37e9641 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ // private modules mod bellpepper; +mod constants; +mod digest; mod r1cs; // public modules @@ -29,10 +31,8 @@ use crate::bellpepper::{ use bellpepper_core::{Circuit, ConstraintSystem}; use core::marker::PhantomData; use errors::SpartanError; -use ff::Field; use r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness}; use serde::{Deserialize, Serialize}; -use sha3::{Digest, Sha3_256}; use traits::{ commitment::{CommitmentEngineTrait, CommitmentTrait}, snark::RelaxedR1CSSNARKTrait, @@ -111,7 +111,7 @@ impl, C: Circuit> SNARK = <::CE as CommitmentEngineTrait>::Commitment; type CompressedCommitment = <<::CE as CommitmentEngineTrait>::Commitment as CommitmentTrait>::CompressedCommitment; type CE = ::CE; -fn compute_digest(o: &T) -> G::Scalar { - // obtain a vector of bytes representing public parameters - let bytes = bincode::serialize(o).unwrap(); - // convert pp_bytes into a short digest - let mut hasher = Sha3_256::new(); - hasher.update(&bytes); - let digest = hasher.finalize(); - - // truncate the digest to NUM_HASH_BITS bits - let bv = (0..250).map(|i| { - let (byte_pos, bit_pos) = (i / 8, i % 8); - let bit = (digest[byte_pos] >> bit_pos) & 1; - bit == 1 - }); - - // turn the bit vector into a scalar - let mut digest = G::Scalar::ZERO; - let mut coeff = G::Scalar::ONE; - for bit in bv { - if bit { - digest += coeff; - } - coeff += coeff; - } - digest -} - #[cfg(test)] mod tests { use super::*; - use crate::provider::bn256_grumpkin::bn256; + use crate::provider::{bn256_grumpkin::bn256, secp_secq::secp256k1}; use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use core::marker::PhantomData; use ff::PrimeField; @@ -227,6 +200,13 @@ mod tests { type S2pp = crate::spartan::ppsnark::RelaxedR1CSSNARK; test_snark_with::(); test_snark_with::(); + + type G3 = secp256k1::Point; + type EE3 = crate::provider::ipa_pc::EvaluationEngine; + type S3 = crate::spartan::snark::RelaxedR1CSSNARK; + type S3pp = crate::spartan::ppsnark::RelaxedR1CSSNARK; + test_snark_with::(); + test_snark_with::(); } fn test_snark_with>() { diff --git a/src/provider/bn256_grumpkin.rs b/src/provider/bn256_grumpkin.rs index 644a202..eb3fc9f 100644 --- a/src/provider/bn256_grumpkin.rs +++ b/src/provider/bn256_grumpkin.rs @@ -1,17 +1,16 @@ -//! This module implements the Spartan traits for bn256::Point, bn256::Scalar, grumpkin::Point, grumpkin::Scalar. +//! This module implements the Spartan traits for `bn256::Point`, `bn256::Scalar`, `grumpkin::Point`, `grumpkin::Scalar`. use crate::{ + impl_traits, provider::{cpu_best_multiexp, keccak::Keccak256Transcript, pedersen::CommitmentEngine}, traits::{CompressedGroup, Group, PrimeFieldExt, TranscriptReprTrait}, }; use digest::{ExtendableOutput, Update}; use ff::{FromUniformBytes, PrimeField}; +use group::{cofactor::CofactorCurveAffine, Curve, Group as AnotherGroup, GroupEncoding}; use num_bigint::BigInt; use num_traits::Num; -use pasta_curves::{ - self, - arithmetic::{CurveAffine, CurveExt}, - group::{cofactor::CofactorCurveAffine, Curve, Group as AnotherGroup, GroupEncoding}, -}; +// Remove this when https://github.com/zcash/pasta_curves/issues/41 resolves +use pasta_curves::arithmetic::{CurveAffine, CurveExt}; use rayon::prelude::*; use sha3::Shake256; use std::io::Read; @@ -37,143 +36,6 @@ pub mod grumpkin { }; } -// This implementation behaves in ways specific to the bn256/grumpkin curves in: -// - to_coordinates, -// - vartime_multiscalar_mul, where it does not call into accelerated implementations. -macro_rules! impl_traits { - ( - $name:ident, - $name_compressed:ident, - $name_curve:ident, - $name_curve_affine:ident, - $order_str:literal - ) => { - impl Group for $name::Point { - type Base = $name::Base; - type Scalar = $name::Scalar; - type CompressedGroupElement = $name_compressed; - type PreprocessedGroupElement = $name::Affine; - type TE = Keccak256Transcript; - type CE = CommitmentEngine; - - fn vartime_multiscalar_mul( - scalars: &[Self::Scalar], - bases: &[Self::PreprocessedGroupElement], - ) -> Self { - cpu_best_multiexp(scalars, bases) - } - - fn preprocessed(&self) -> Self::PreprocessedGroupElement { - self.to_affine() - } - - fn compress(&self) -> Self::CompressedGroupElement { - self.to_bytes() - } - - fn from_label(label: &'static [u8], n: usize) -> Vec { - let mut shake = Shake256::default(); - shake.update(label); - let mut reader = shake.finalize_xof(); - let mut uniform_bytes_vec = Vec::new(); - for _ in 0..n { - let mut uniform_bytes = [0u8; 32]; - reader.read_exact(&mut uniform_bytes).unwrap(); - uniform_bytes_vec.push(uniform_bytes); - } - let gens_proj: Vec<$name_curve> = (0..n) - .collect::>() - .into_par_iter() - .map(|i| { - let hash = $name_curve::hash_to_curve("from_uniform_bytes"); - hash(&uniform_bytes_vec[i]) - }) - .collect(); - - let num_threads = rayon::current_num_threads(); - if gens_proj.len() > num_threads { - let chunk = (gens_proj.len() as f64 / num_threads as f64).ceil() as usize; - (0..num_threads) - .collect::>() - .into_par_iter() - .map(|i| { - let start = i * chunk; - let end = if i == num_threads - 1 { - gens_proj.len() - } else { - core::cmp::min((i + 1) * chunk, gens_proj.len()) - }; - if end > start { - let mut gens = vec![$name_curve_affine::identity(); end - start]; - ::batch_normalize(&gens_proj[start..end], &mut gens); - gens - } else { - vec![] - } - }) - .collect::>>() - .into_par_iter() - .flatten() - .collect() - } else { - let mut gens = vec![$name_curve_affine::identity(); n]; - ::batch_normalize(&gens_proj, &mut gens); - gens - } - } - - fn to_coordinates(&self) -> (Self::Base, Self::Base, bool) { - let coordinates = self.to_affine().coordinates(); - if coordinates.is_some().unwrap_u8() == 1 - // The bn256/grumpkin convention is to define and return the identity point's affine encoding (not None) - && (Self::PreprocessedGroupElement::identity() != self.to_affine()) - { - (*coordinates.unwrap().x(), *coordinates.unwrap().y(), false) - } else { - (Self::Base::zero(), Self::Base::zero(), true) - } - } - - fn get_curve_params() -> (Self::Base, Self::Base, BigInt) { - let A = $name::Point::a(); - let B = $name::Point::b(); - let order = BigInt::from_str_radix($order_str, 16).unwrap(); - - (A, B, order) - } - - fn zero() -> Self { - $name::Point::identity() - } - - fn get_generator() -> Self { - $name::Point::generator() - } - } - - impl PrimeFieldExt for $name::Scalar { - fn from_uniform(bytes: &[u8]) -> Self { - let bytes_arr: [u8; 64] = bytes.try_into().unwrap(); - $name::Scalar::from_uniform_bytes(&bytes_arr) - } - } - - impl TranscriptReprTrait for $name_compressed { - fn to_transcript_bytes(&self) -> Vec { - self.as_ref().to_vec() - } - } - - impl CompressedGroup for $name_compressed { - type GroupElement = $name::Point; - - fn decompress(&self) -> Option<$name::Point> { - Some($name_curve::from_bytes(&self).unwrap()) - } - } - }; -} - impl TranscriptReprTrait for grumpkin::Base { fn to_transcript_bytes(&self) -> Vec { self.to_repr().to_vec() diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index 1cee808..413ae47 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -3,7 +3,7 @@ use crate::{ errors::SpartanError, provider::pedersen::CommitmentKeyExtTrait, - spartan::polynomial::EqPolynomial, + spartan::polys::eq::EqPolynomial, traits::{ commitment::{CommitmentEngineTrait, CommitmentTrait}, evaluation::EvaluationEngineTrait, @@ -32,13 +32,6 @@ pub struct VerifierKey { ck_s: CommitmentKey, } -/// Provides an implementation of a polynomial evaluation argument -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct EvaluationArgument { - ipa: InnerProductArgument, -} - /// Provides an implementation of a polynomial evaluation engine using IPA #[derive(Clone, Debug, Serialize, Deserialize)] pub struct EvaluationEngine { @@ -48,23 +41,21 @@ pub struct EvaluationEngine { impl EvaluationEngineTrait for EvaluationEngine where G: Group, - CommitmentKey: CommitmentKeyExtTrait, + CommitmentKey: CommitmentKeyExtTrait, { - type CE = G::CE; type ProverKey = ProverKey; type VerifierKey = VerifierKey; - type EvaluationArgument = EvaluationArgument; + type EvaluationArgument = InnerProductArgument; fn setup( - ck: &>::CommitmentKey, + ck: &<::CE as CommitmentEngineTrait>::CommitmentKey, ) -> (Self::ProverKey, Self::VerifierKey) { - let pk = ProverKey { - ck_s: G::CE::setup(b"ipa", 1), - }; + let ck_c = G::CE::setup(b"ipa", 1); + let pk = ProverKey { ck_s: ck_c.clone() }; let vk = VerifierKey { ck_v: ck.clone(), - ck_s: G::CE::setup(b"ipa", 1), + ck_s: ck_c, }; (pk, vk) @@ -82,9 +73,7 @@ where let u = InnerProductInstance::new(comm, &EqPolynomial::new(point.to_vec()).evals(), eval); let w = InnerProductWitness::new(poly); - Ok(EvaluationArgument { - ipa: InnerProductArgument::prove(ck, &pk.ck_s, &u, &w, transcript)?, - }) + InnerProductArgument::prove(ck, &pk.ck_s, &u, &w, transcript) } /// A method to verify purported evaluations of a batch of polynomials @@ -98,7 +87,7 @@ where ) -> Result<(), SpartanError> { let u = InnerProductInstance::new(comm, &EqPolynomial::new(point.to_vec()).evals(), eval); - arg.ipa.verify( + arg.verify( &vk.ck_v, &vk.ck_s, (2_usize).pow(point.len() as u32), @@ -165,19 +154,18 @@ impl InnerProductWitness { /// An inner product argument #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] -struct InnerProductArgument { +pub struct InnerProductArgument { L_vec: Vec>, R_vec: Vec>, a_hat: G::Scalar, - _p: PhantomData, } impl InnerProductArgument where G: Group, - CommitmentKey: CommitmentKeyExtTrait, + CommitmentKey: CommitmentKeyExtTrait, { - fn protocol_name() -> &'static [u8] { + const fn protocol_name() -> &'static [u8] { b"IPA" } @@ -275,7 +263,7 @@ where let mut a_vec = W.a_vec.to_vec(); let mut b_vec = U.b_vec.to_vec(); let mut ck = ck; - for _i in 0..(U.b_vec.len() as f64).log2() as usize { + for _i in 0..usize::try_from(U.b_vec.len().ilog2()).unwrap() { let (L, R, a_vec_folded, b_vec_folded, ck_folded) = prove_inner(&a_vec, &b_vec, &ck, transcript)?; L_vec.push(L); @@ -290,7 +278,6 @@ where L_vec, R_vec, a_hat: a_vec[0], - _p: Default::default(), }) } @@ -374,7 +361,7 @@ where let mut s = vec![G::Scalar::ZERO; n]; s[0] = { let mut v = G::Scalar::ONE; - for r_inverse_i in &r_inverse { + for r_inverse_i in r_inverse { v *= r_inverse_i; } v diff --git a/src/provider/keccak.rs b/src/provider/keccak.rs index 3c7d007..119478e 100644 --- a/src/provider/keccak.rs +++ b/src/provider/keccak.rs @@ -1,4 +1,4 @@ -//! This module provides an implementation of TranscriptEngineTrait using keccak256 +//! This module provides an implementation of `TranscriptEngineTrait` using keccak256 use crate::traits::PrimeFieldExt; use crate::{ errors::SpartanError, @@ -13,7 +13,7 @@ const KECCAK256_STATE_SIZE: usize = 64; const KECCAK256_PREFIX_CHALLENGE_LO: u8 = 0; const KECCAK256_PREFIX_CHALLENGE_HI: u8 = 1; -/// Provides an implementation of TranscriptEngine +/// Provides an implementation of `TranscriptEngine` #[derive(Debug, Clone)] pub struct Keccak256Transcript { round: u16, @@ -55,7 +55,7 @@ impl TranscriptEngineTrait for Keccak256Transcript { round: 0u16, state: output, transcript: keccak_instance, - _p: Default::default(), + _p: PhantomData, } } @@ -100,7 +100,7 @@ impl TranscriptEngineTrait for Keccak256Transcript { mod tests { use crate::{ provider::bn256_grumpkin::bn256, - provider::keccak::Keccak256Transcript, + provider::{self, keccak::Keccak256Transcript, secp_secq}, traits::{Group, PrimeFieldExt, TranscriptEngineTrait, TranscriptReprTrait}, }; use ff::PrimeField; @@ -144,6 +144,11 @@ mod tests { "9fb71e3b74bfd0b60d97349849b895595779a240b92a6fae86bd2812692b6b0e", "bfd4c50b7d6317e9267d5d65c985eb455a3561129c0b3beef79bfc8461a84f18", ); + + test_keccak_transcript_with::( + "9723aafb69ec8f0e9c7de756df0993247d98cf2b2f72fa353e3de654a177e310", + "a6a90fcb6e1b1a2a2f84c950ef1510d369aea8e42085f5c629bfa66d00255f25", + ); } #[test] @@ -239,5 +244,7 @@ mod tests { fn test_keccak_transcript_incremental_vs_explicit() { test_keccak_transcript_incremental_vs_explicit_with::(); test_keccak_transcript_incremental_vs_explicit_with::(); + test_keccak_transcript_incremental_vs_explicit_with::(); + test_keccak_transcript_incremental_vs_explicit_with::(); } } diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 4af9c0f..2ef0027 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -1,6 +1,7 @@ //! This module implements Spartan's traits using the following configuration: //! `CommitmentEngine` with Pedersen's commitments //! `Group` with pasta curves and BN256/Grumpkin +//! `RO` traits with Poseidon //! `EvaluationEngine` with an IPA-based polynomial evaluation argument pub mod bn256_grumpkin; @@ -8,6 +9,7 @@ pub mod ipa_pc; pub mod keccak; pub mod pasta; pub mod pedersen; +pub mod secp_secq; use ff::PrimeField; use pasta_curves::{self, arithmetic::CurveAffine, group::Group as AnotherGroup}; @@ -136,3 +138,138 @@ pub(crate) fn cpu_best_multiexp(coeffs: &[C::Scalar], bases: &[C acc } } + +/// Curve ops +/// This implementation behaves in ways specific to the halo2curves suite of curves in: +// - to_coordinates, +// - vartime_multiscalar_mul, where it does not call into accelerated implementations. +// A specific reimplementation exists for the pasta curves in their own module. +#[macro_export] +macro_rules! impl_traits { + ( + $name:ident, + $name_compressed:ident, + $name_curve:ident, + $name_curve_affine:ident, + $order_str:literal + ) => { + impl Group for $name::Point { + type Base = $name::Base; + type Scalar = $name::Scalar; + type CompressedGroupElement = $name_compressed; + type PreprocessedGroupElement = $name::Affine; + type TE = Keccak256Transcript; + type CE = CommitmentEngine; + + fn vartime_multiscalar_mul( + scalars: &[Self::Scalar], + bases: &[Self::PreprocessedGroupElement], + ) -> Self { + cpu_best_multiexp(scalars, bases) + } + + fn preprocessed(&self) -> Self::PreprocessedGroupElement { + self.to_affine() + } + + fn compress(&self) -> Self::CompressedGroupElement { + self.to_bytes() + } + + fn from_label(label: &'static [u8], n: usize) -> Vec { + let mut shake = Shake256::default(); + shake.update(label); + let mut reader = shake.finalize_xof(); + let mut uniform_bytes_vec = Vec::new(); + for _ in 0..n { + let mut uniform_bytes = [0u8; 32]; + reader.read_exact(&mut uniform_bytes).unwrap(); + uniform_bytes_vec.push(uniform_bytes); + } + let gens_proj: Vec<$name_curve> = (0..n) + .into_par_iter() + .map(|i| { + let hash = $name_curve::hash_to_curve("from_uniform_bytes"); + hash(&uniform_bytes_vec[i]) + }) + .collect(); + + let num_threads = rayon::current_num_threads(); + if gens_proj.len() > num_threads { + let chunk = (gens_proj.len() as f64 / num_threads as f64).ceil() as usize; + (0..num_threads) + .into_par_iter() + .flat_map(|i| { + let start = i * chunk; + let end = if i == num_threads - 1 { + gens_proj.len() + } else { + core::cmp::min((i + 1) * chunk, gens_proj.len()) + }; + if end > start { + let mut gens = vec![$name_curve_affine::identity(); end - start]; + ::batch_normalize(&gens_proj[start..end], &mut gens); + gens + } else { + vec![] + } + }) + .collect() + } else { + let mut gens = vec![$name_curve_affine::identity(); n]; + ::batch_normalize(&gens_proj, &mut gens); + gens + } + } + + fn to_coordinates(&self) -> (Self::Base, Self::Base, bool) { + // see: grumpkin implementation at src/provider/bn256_grumpkin.rs + let coordinates = self.to_affine().coordinates(); + if coordinates.is_some().unwrap_u8() == 1 + && (Self::PreprocessedGroupElement::identity() != self.to_affine()) + { + (*coordinates.unwrap().x(), *coordinates.unwrap().y(), false) + } else { + (Self::Base::zero(), Self::Base::zero(), true) + } + } + + fn get_curve_params() -> (Self::Base, Self::Base, BigInt) { + let A = $name::Point::a(); + let B = $name::Point::b(); + let order = BigInt::from_str_radix($order_str, 16).unwrap(); + + (A, B, order) + } + + fn zero() -> Self { + $name::Point::identity() + } + + fn get_generator() -> Self { + $name::Point::generator() + } + } + + impl PrimeFieldExt for $name::Scalar { + fn from_uniform(bytes: &[u8]) -> Self { + let bytes_arr: [u8; 64] = bytes.try_into().unwrap(); + $name::Scalar::from_uniform_bytes(&bytes_arr) + } + } + + impl TranscriptReprTrait for $name_compressed { + fn to_transcript_bytes(&self) -> Vec { + self.as_ref().to_vec() + } + } + + impl CompressedGroup for $name_compressed { + type GroupElement = $name::Point; + + fn decompress(&self) -> Option<$name::Point> { + Some($name_curve::from_bytes(&self).unwrap()) + } + } + }; +} diff --git a/src/provider/pasta.rs b/src/provider/pasta.rs index 1123d65..9a40f01 100644 --- a/src/provider/pasta.rs +++ b/src/provider/pasta.rs @@ -1,4 +1,4 @@ -//! This module implements the Spartan traits for pallas::Point, pallas::Scalar, vesta::Point, vesta::Scalar. +//! This module implements the Spartan traits for `pallas::Point`, `pallas::Scalar`, `vesta::Point`, `vesta::Scalar`. use crate::{ provider::{cpu_best_multiexp, keccak::Keccak256Transcript, pedersen::CommitmentEngine}, traits::{CompressedGroup, Group, PrimeFieldExt, TranscriptReprTrait}, @@ -26,7 +26,7 @@ pub struct PallasCompressedElementWrapper { impl PallasCompressedElementWrapper { /// Wraps repr into the wrapper - pub fn new(repr: [u8; 32]) -> Self { + pub const fn new(repr: [u8; 32]) -> Self { Self { repr } } } @@ -39,7 +39,7 @@ pub struct VestaCompressedElementWrapper { impl VestaCompressedElementWrapper { /// Wraps repr into the wrapper - pub fn new(repr: [u8; 32]) -> Self { + pub const fn new(repr: [u8; 32]) -> Self { Self { repr } } } @@ -99,7 +99,6 @@ macro_rules! impl_traits { uniform_bytes_vec.push(uniform_bytes); } let ck_proj: Vec<$name_curve> = (0..n) - .collect::>() .into_par_iter() .map(|i| { let hash = $name_curve::hash_to_curve("from_uniform_bytes"); @@ -111,9 +110,8 @@ macro_rules! impl_traits { if ck_proj.len() > num_threads { let chunk = (ck_proj.len() as f64 / num_threads as f64).ceil() as usize; (0..num_threads) - .collect::>() .into_par_iter() - .map(|i| { + .flat_map(|i| { let start = i * chunk; let end = if i == num_threads - 1 { ck_proj.len() @@ -128,9 +126,6 @@ macro_rules! impl_traits { vec![] } }) - .collect::>>() - .into_par_iter() - .flatten() .collect() } else { let mut ck = vec![$name_curve_affine::identity(); n]; diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index e035b35..3d687b4 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -18,7 +18,6 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct CommitmentKey { ck: Vec, - _p: PhantomData, } /// A type that holds a commitment @@ -177,7 +176,6 @@ impl CommitmentEngineTrait for CommitmentEngine { fn setup(label: &'static [u8], n: usize) -> Self::CommitmentKey { Self::CommitmentKey { ck: G::from_label(label, n.next_power_of_two()), - _p: Default::default(), } } @@ -191,9 +189,6 @@ impl CommitmentEngineTrait for CommitmentEngine { /// A trait listing properties of a commitment key that can be managed in a divide-and-conquer fashion pub trait CommitmentKeyExtTrait { - /// Holds the type of the commitment engine - type CE: CommitmentEngineTrait; - /// Splits the commitment key into two pieces at a specified point fn split_at(&self, n: usize) -> (Self, Self) where @@ -210,24 +205,20 @@ pub trait CommitmentKeyExtTrait { /// Reinterprets commitments as commitment keys fn reinterpret_commitments_as_ck( - c: &[<<>::CE as CommitmentEngineTrait>::Commitment as CommitmentTrait>::CompressedCommitment], + c: &[<<::CE as CommitmentEngineTrait>::Commitment as CommitmentTrait>::CompressedCommitment], ) -> Result where Self: Sized; } -impl CommitmentKeyExtTrait for CommitmentKey { - type CE = CommitmentEngine; - +impl>> CommitmentKeyExtTrait for CommitmentKey { fn split_at(&self, n: usize) -> (CommitmentKey, CommitmentKey) { ( CommitmentKey { ck: self.ck[0..n].to_vec(), - _p: Default::default(), }, CommitmentKey { ck: self.ck[n..].to_vec(), - _p: Default::default(), }, ) } @@ -238,10 +229,7 @@ impl CommitmentKeyExtTrait for CommitmentKey { c.extend(other.ck.clone()); c }; - CommitmentKey { - ck, - _p: Default::default(), - } + CommitmentKey { ck } } // combines the left and right halves of `self` using `w1` and `w2` as the weights @@ -257,10 +245,7 @@ impl CommitmentKeyExtTrait for CommitmentKey { }) .collect(); - CommitmentKey { - ck, - _p: Default::default(), - } + CommitmentKey { ck } } /// Scales each element in `self` by `r` @@ -272,10 +257,7 @@ impl CommitmentKeyExtTrait for CommitmentKey { .map(|g| G::vartime_multiscalar_mul(&[*r], &[g]).preprocessed()) .collect(); - CommitmentKey { - ck: ck_scaled, - _p: Default::default(), - } + CommitmentKey { ck: ck_scaled } } /// reinterprets a vector of commitments as a set of generators @@ -288,9 +270,6 @@ impl CommitmentKeyExtTrait for CommitmentKey { .into_par_iter() .map(|i| d[i].comm.preprocessed()) .collect(); - Ok(CommitmentKey { - ck, - _p: Default::default(), - }) + Ok(CommitmentKey { ck }) } } diff --git a/src/r1cs.rs b/src/r1cs.rs index ec51b61..0d3f2e3 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -121,6 +121,16 @@ impl R1CSShape { }) } + // Checks regularity conditions on the R1CSShape, required in Spartan-class SNARKs + // Panics if num_cons, num_vars, or num_io are not powers of two, or if num_io > num_vars + #[inline] + pub(crate) fn check_regular_shape(&self) { + assert_eq!(self.num_cons.next_power_of_two(), self.num_cons); + assert_eq!(self.num_vars.next_power_of_two(), self.num_vars); + assert_eq!(self.num_io.next_power_of_two(), self.num_io); + assert!(self.num_io < self.num_vars); + } + pub fn multiply_vec( &self, z: &[G::Scalar], diff --git a/src/spartan/math.rs b/src/spartan/math.rs index ba809d7..691fec5 100644 --- a/src/spartan/math.rs +++ b/src/spartan/math.rs @@ -11,7 +11,7 @@ impl Math for usize { base.pow(self as u32) } - /// Returns the num_bits from n in a canonical order + /// Returns the `num_bits` from n in a canonical order fn get_bits(self, num_bits: usize) -> Vec { (0..num_bits) .map(|shift_amount| ((self & (1 << (num_bits - shift_amount - 1))) > 0)) diff --git a/src/spartan/mod.rs b/src/spartan/mod.rs index 6441c09..d8ae008 100644 --- a/src/spartan/mod.rs +++ b/src/spartan/mod.rs @@ -1,16 +1,19 @@ -//! This module implements RelaxedR1CSSNARKTrait using Spartan that is generic +//! This module implements `RelaxedR1CSSNARKTrait` using Spartan that is generic //! over the polynomial commitment and evaluation argument (i.e., a PCS) //! We provide two implementations, one in snark.rs (which does not use any preprocessing) -//! and another in ppsnark.rs (which uses preprocessing to keep the verifier's state small if the PCS scheme provides a succinct verifier) -mod math; -pub(crate) mod polynomial; +//! and another in ppsnark.rs (which uses preprocessing to keep the verifier's state small if the PCS provides a succinct verifier) +//! We also provide direct.rs that allows proving a step circuit directly with either of the two SNARKs. +//! +//! In polynomial.rs we also provide foundational types and functions for manipulating multilinear polynomials. +pub(crate) mod math; +pub mod polys; pub mod ppsnark; pub mod snark; mod sumcheck; use crate::{traits::Group, Commitment}; use ff::Field; -use polynomial::SparsePolynomial; +use polys::multilinear::SparsePolynomial; fn powers(s: &G::Scalar, n: usize) -> Vec { assert!(n >= 1); @@ -33,8 +36,8 @@ impl PolyEvalWitness { if let Some(n) = W.iter().map(|w| w.p.len()).max() { W.iter() .map(|w| { - let mut p = w.p.clone(); - p.resize(n, G::Scalar::ZERO); + let mut p = vec![G::Scalar::ZERO; n]; + p[..w.p.len()].copy_from_slice(&w.p); PolyEvalWitness { p } }) .collect() diff --git a/src/spartan/ppsnark.rs b/src/spartan/ppsnark.rs index 8faead2..1064995 100644 --- a/src/spartan/ppsnark.rs +++ b/src/spartan/ppsnark.rs @@ -1,16 +1,20 @@ -//! This module implements RelaxedR1CSSNARK traits using a spark-based approach to prove evaluations of +//! This module implements `RelaxedR1CSSNARK` traits using a spark-based approach to prove evaluations of //! sparse multilinear polynomials involved in Spartan's sum-check protocol, thereby providing a preprocessing SNARK //! The verifier in this preprocessing SNARK maintains a commitment to R1CS matrices. This is beneficial when using a //! polynomial commitment scheme in which the verifier's costs is succinct. use crate::{ - compute_digest, + digest::{DigestComputer, SimpleDigestible}, errors::SpartanError, r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness}, spartan::{ math::Math, - polynomial::{EqPolynomial, MultilinearPolynomial}, + polys::{ + eq::EqPolynomial, + multilinear::MultilinearPolynomial, + univariate::{CompressedUniPoly, UniPoly}, + }, powers, - sumcheck::{CompressedUniPoly, SumcheckProof, UniPoly}, + sumcheck::SumcheckProof, PolyEvalInstance, PolyEvalWitness, SparsePolynomial, }, traits::{ @@ -23,7 +27,8 @@ use crate::{ }; use core::{cmp::max, marker::PhantomData}; use ff::{Field, PrimeField}; -use itertools::concat; +use once_cell::sync::OnceCell; + use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -41,19 +46,25 @@ impl IdentityPolynomial { pub fn new(ell: usize) -> Self { IdentityPolynomial { ell, - _p: Default::default(), + _p: PhantomData, } } pub fn evaluate(&self, r: &[Scalar]) -> Scalar { assert_eq!(self.ell, r.len()); + let mut power_of_two = 1_u64; (0..self.ell) - .map(|i| Scalar::from(2_usize.pow((self.ell - i - 1) as u32) as u64) * r[i]) + .rev() + .map(|i| { + let result = Scalar::from(power_of_two) * r[i]; + power_of_two *= 2; + result + }) .fold(Scalar::ZERO, |acc, item| acc + item) } } -/// A type that holds R1CSShape in a form amenable to memory checking +/// A type that holds `R1CSShape` in a form amenable to memory checking #[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] pub struct R1CSShapeSparkRepr { @@ -112,58 +123,41 @@ impl TranscriptReprTrait for R1CSShapeSparkCommitment { } impl R1CSShapeSparkRepr { - /// represents R1CSShape in a Spark-friendly format amenable to memory checking + /// represents `R1CSShape` in a Spark-friendly format amenable to memory checking pub fn new(S: &R1CSShape) -> R1CSShapeSparkRepr { let N = { let total_nz = S.A.len() + S.B.len() + S.C.len(); max(total_nz, max(2 * S.num_vars, S.num_cons)).next_power_of_two() }; - let row = { - let mut r = S - .A - .iter() - .chain(S.B.iter()) - .chain(S.C.iter()) - .map(|(r, _, _)| *r) - .collect::>(); - r.resize(N, 0usize); - r - }; + let (mut row, mut col) = (vec![0usize; N], vec![0usize; N]); - let col = { - let mut c = S - .A - .iter() - .chain(S.B.iter()) - .chain(S.C.iter()) - .map(|(_, c, _)| *c) - .collect::>(); - c.resize(N, 0usize); - c - }; + for (i, (r, c, _)) in S.A.iter().chain(S.B.iter()).chain(S.C.iter()).enumerate() { + row[i] = *r; + col[i] = *c; + } let val_A = { - let mut val = S.A.iter().map(|(_, _, v)| *v).collect::>(); - val.resize(N, G::Scalar::ZERO); + let mut val = vec![G::Scalar::ZERO; N]; + for (i, (_, _, v)) in S.A.iter().enumerate() { + val[i] = *v; + } val }; let val_B = { - // prepend zeros - let mut val = vec![G::Scalar::ZERO; S.A.len()]; - val.extend(S.B.iter().map(|(_, _, v)| *v).collect::>()); - // append zeros - val.resize(N, G::Scalar::ZERO); + let mut val = vec![G::Scalar::ZERO; N]; + for (i, (_, _, v)) in S.B.iter().enumerate() { + val[S.A.len() + i] = *v; + } val }; let val_C = { - // prepend zeros - let mut val = vec![G::Scalar::ZERO; S.A.len() + S.B.len()]; - val.extend(S.C.iter().map(|(_, _, v)| *v).collect::>()); - // append zeros - val.resize(N, G::Scalar::ZERO); + let mut val = vec![G::Scalar::ZERO; N]; + for (i, (_, _, v)) in S.C.iter().enumerate() { + val[S.A.len() + S.B.len() + i] = *v; + } val }; @@ -265,29 +259,30 @@ impl R1CSShapeSparkRepr { let mem_row = EqPolynomial::new(r_x_padded).evals(); let mem_col = { - let mut z = z.to_vec(); - z.resize(self.N, G::Scalar::ZERO); - z + let mut val = vec![G::Scalar::ZERO; self.N]; + for (i, v) in z.iter().enumerate() { + val[i] = *v; + } + val }; - let mut E_row = S - .A - .iter() - .chain(S.B.iter()) - .chain(S.C.iter()) - .map(|(r, _, _)| mem_row[*r]) - .collect::>(); - - let mut E_col = S - .A - .iter() - .chain(S.B.iter()) - .chain(S.C.iter()) - .map(|(_, c, _)| mem_col[*c]) - .collect::>(); + let (E_row, E_col) = { + let mut E_row = vec![mem_row[0]; self.N]; // we place mem_row[0] since resized row is appended with 0s + let mut E_col = vec![mem_col[0]; self.N]; - E_row.resize(self.N, mem_row[0]); // we place mem_row[0] since resized row is appended with 0s - E_col.resize(self.N, mem_col[0]); + for (i, (val_r, val_c)) in S + .A + .iter() + .chain(S.B.iter()) + .chain(S.C.iter()) + .map(|(r, c, _)| (mem_row[*r], mem_col[*c])) + .enumerate() + { + E_row[i] = val_r; + E_col[i] = val_c; + } + (E_row, E_col) + }; (mem_row, mem_col, E_row, E_col) } @@ -411,12 +406,10 @@ impl ProductSumcheckInstance { let poly_A = MultilinearPolynomial::new(EqPolynomial::new(rand_eq).evals()); let poly_B_vec = left_vec - .clone() .into_par_iter() .map(MultilinearPolynomial::new) .collect::>(); let poly_C_vec = right_vec - .clone() .into_par_iter() .map(MultilinearPolynomial::new) .collect::>(); @@ -477,43 +470,10 @@ impl SumcheckEngine for ProductSumcheckInstance { .zip(self.poly_C_vec.iter()) .zip(self.poly_D_vec.iter()) .map(|((poly_B, poly_C), poly_D)| { - let len = poly_B.len() / 2; // Make an iterator returning the contributions to the evaluations - let (eval_point_0, eval_point_2, eval_point_3) = (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 3: bound_func is -2A(low) + 3A(high); computed incrementally with bound_func applied to eval(2) - let poly_A_bound_point = poly_A_bound_point + poly_A[len + i] - poly_A[i]; - let poly_B_bound_point = poly_B_bound_point + poly_B[len + i] - poly_B[i]; - let poly_C_bound_point = poly_C_bound_point + poly_C[len + i] - poly_C[i]; - let poly_D_bound_point = poly_D_bound_point + poly_D[len + i] - poly_D[i]; - let eval_point_3 = comb_func( - &poly_A_bound_point, - &poly_B_bound_point, - &poly_C_bound_point, - &poly_D_bound_point, - ); - (eval_point_0, eval_point_2, eval_point_3) - }) - .reduce( - || (G::Scalar::ZERO, G::Scalar::ZERO, G::Scalar::ZERO), - |a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2), - ); + let (eval_point_0, eval_point_2, eval_point_3) = + SumcheckProof::::compute_eval_points_cubic(poly_A, poly_B, poly_C, poly_D, &comb_func); + vec![eval_point_0, eval_point_2, eval_point_3] }) .collect::>>() @@ -584,44 +544,10 @@ impl SumcheckEngine for OuterSumcheckInstance { 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 len = poly_A.len() / 2; // Make an iterator returning the contributions to the evaluations - let (eval_point_0, eval_point_2, eval_point_3) = (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 3: bound_func is -2A(low) + 3A(high); computed incrementally with bound_func applied to eval(2) - let poly_A_bound_point = poly_A_bound_point + poly_A[len + i] - poly_A[i]; - let poly_B_bound_point = poly_B_bound_point + poly_B[len + i] - poly_B[i]; - let poly_C_bound_point = poly_C_bound_point + poly_C[len + i] - poly_C[i]; - let poly_D_bound_point = poly_D_bound_point + poly_D[len + i] - poly_D[i]; - let eval_point_3 = comb_func( - &poly_A_bound_point, - &poly_B_bound_point, - &poly_C_bound_point, - &poly_D_bound_point, - ); - (eval_point_0, eval_point_2, eval_point_3) - }) - .reduce( - || (G::Scalar::ZERO, G::Scalar::ZERO, G::Scalar::ZERO), - |a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2), - ); + let (eval_point_0, eval_point_2, eval_point_3) = + SumcheckProof::::compute_eval_points_cubic(poly_A, poly_B, poly_C, poly_D, &comb_func); vec![vec![eval_point_0, eval_point_2, eval_point_3]] } @@ -673,6 +599,8 @@ impl SumcheckEngine for InnerSumcheckInstance { -> G::Scalar { *poly_A_comp * *poly_B_comp * *poly_C_comp }; let len = poly_A.len() / 2; + // TODO: make this call a function in sumcheck.rs by writing an n-ary variant of crate::spartan::sumcheck::SumcheckProof::::compute_eval_points_cubic + // once #[feature(array_methods)] stabilizes (this n-ary variant would need array::each_ref) // Make an iterator returning the contributions to the evaluations let (eval_point_0, eval_point_2, eval_point_3) = (0..len) .into_par_iter() @@ -727,8 +655,9 @@ impl SumcheckEngine for InnerSumcheckInstance { /// A type that represents the prover's key #[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] -pub struct ProverKey> { +pub struct ProverKey> { pk_ee: EE::ProverKey, + S: R1CSShape, S_repr: R1CSShapeSparkRepr, S_comm: R1CSShapeSparkCommitment, vk_digest: G::Scalar, // digest of verifier's key @@ -737,20 +666,23 @@ pub struct ProverKey> { /// A type that represents the verifier's key #[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] -pub struct VerifierKey> { +pub struct VerifierKey> { num_cons: usize, num_vars: usize, vk_ee: EE::VerifierKey, S_comm: R1CSShapeSparkCommitment, - digest: G::Scalar, + #[serde(skip, default = "OnceCell::new")] + digest: OnceCell, } +impl> SimpleDigestible for VerifierKey {} + /// A succinct proof of knowledge of a witness to a relaxed R1CS instance /// The proof is produced using Spartan's combination of the sum-check and /// the commitment to a vector viewed as a polynomial commitment #[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] -pub struct RelaxedR1CSSNARK> { +pub struct RelaxedR1CSSNARK> { // commitment to oracles comm_Az: CompressedCommitment, comm_Bz: CompressedCommitment, @@ -803,7 +735,7 @@ pub struct RelaxedR1CSSNARK> eval_arg: EE::EvaluationArgument, } -impl> RelaxedR1CSSNARK { +impl> RelaxedR1CSSNARK { fn prove_inner( mem: &mut T1, outer: &mut T2, @@ -861,7 +793,7 @@ impl> RelaxedR1CSSNARK let mut e = claim; let mut r: Vec = Vec::new(); - let mut cubic_polys: Vec> = Vec::new(); + let mut cubic_polys: Vec> = Vec::new(); let num_rounds = mem.size().log_2(); for _i in 0..num_rounds { let mut evals: Vec> = Vec::new(); @@ -911,9 +843,36 @@ impl> RelaxedR1CSSNARK } } -impl> RelaxedR1CSSNARKTrait - for RelaxedR1CSSNARK -{ +impl> VerifierKey { + fn new( + num_cons: usize, + num_vars: usize, + S_comm: R1CSShapeSparkCommitment, + vk_ee: EE::VerifierKey, + ) -> Self { + VerifierKey { + num_cons, + num_vars, + S_comm, + vk_ee, + digest: Default::default(), + } + } + + /// Returns the digest of the verifier's key + pub fn digest(&self) -> G::Scalar { + self + .digest + .get_or_try_init(|| { + let dc = DigestComputer::new(self); + dc.digest() + }) + .cloned() + .expect("Failure to retrieve digest!") + } +} + +impl> RelaxedR1CSSNARKTrait for RelaxedR1CSSNARK { type ProverKey = ProverKey; type VerifierKey = VerifierKey; @@ -929,56 +888,44 @@ impl> RelaxedR1CSSNARKTrait>(&vk); - vk - }; + let vk = VerifierKey::new(S.num_cons, S.num_vars, S_comm.clone(), vk_ee); let pk = ProverKey { pk_ee, + S, S_repr, S_comm, - vk_digest: vk.digest, + vk_digest: vk.digest(), }; Ok((pk, vk)) } - /// produces a succinct proof of satisfiability of a RelaxedR1CS instance + /// produces a succinct proof of satisfiability of a `RelaxedR1CS` instance fn prove( ck: &CommitmentKey, pk: &Self::ProverKey, - S: &R1CSShape, U: &RelaxedR1CSInstance, W: &RelaxedR1CSWitness, ) -> Result { - let W = W.pad(S); // pad the witness + let W = W.pad(&pk.S); // pad the witness let mut transcript = G::TE::new(b"RelaxedR1CSSNARK"); // a list of polynomial evaluation claims that will be batched let mut w_u_vec = Vec::new(); // sanity check that R1CSShape has certain size characteristics - assert_eq!(S.num_cons.next_power_of_two(), S.num_cons); - assert_eq!(S.num_vars.next_power_of_two(), S.num_vars); - assert!(S.num_io < S.num_vars); + pk.S.check_regular_shape(); // append the verifier key (which includes commitment to 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 z = concat(vec![W.W.clone(), vec![U.u], U.X.clone()]); + let z = [W.W.clone(), vec![U.u], U.X.clone()].concat(); // compute Az, Bz, Cz - let (mut Az, mut Bz, mut Cz) = S.multiply_vec(&z)?; + let (mut Az, mut Bz, mut Cz) = pk.S.multiply_vec(&z)?; // commit to Az, Bz, Cz let (comm_Az, (comm_Bz, comm_Cz)) = rayon::join( @@ -1000,8 +947,13 @@ impl> RelaxedR1CSSNARKTrait> RelaxedR1CSSNARKTrait> RelaxedR1CSSNARKTrait> RelaxedR1CSSNARKTrait) -> Result<(), SpartanError> { let mut transcript = G::TE::new(b"RelaxedR1CSSNARK"); let mut u_vec: Vec> = Vec::new(); // append the verifier key (including commitment to R1CS matrices) and the RelaxedR1CSInstance to the transcript - transcript.absorb(b"vk", &vk.digest); + transcript.absorb(b"vk", &vk.digest()); transcript.absorb(b"U", U); let comm_Az = Commitment::::decompress(&self.comm_Az)?; @@ -1860,7 +1812,7 @@ impl> RelaxedR1CSSNARKTrait>(), ); - SparsePolynomial::new((vk.num_vars as f64).log2() as usize, poly_X) + SparsePolynomial::new(usize::try_from(vk.num_vars.ilog2()).unwrap(), poly_X) .evaluate(&r_prod_unpad[1..]) }; let eval_Z = @@ -1878,7 +1830,7 @@ impl> RelaxedR1CSSNARKTrait> { +pub struct ProverKey> { pk_ee: EE::ProverKey, + S: R1CSShape, vk_digest: G::Scalar, // digest of the verifier's key } /// A type that represents the verifier's key #[derive(Serialize, Deserialize)] #[serde(bound = "")] -pub struct VerifierKey> { +pub struct VerifierKey> { vk_ee: EE::VerifierKey, S: R1CSShape, - digest: G::Scalar, + #[serde(skip, default = "OnceCell::new")] + digest: OnceCell, +} + +impl> SimpleDigestible for VerifierKey {} + +impl> VerifierKey { + fn new(shape: R1CSShape, vk_ee: EE::VerifierKey) -> Self { + VerifierKey { + vk_ee, + S: shape, + digest: OnceCell::new(), + } + } + + /// Returns the digest of the verifier's key. + pub fn digest(&self) -> G::Scalar { + self + .digest + .get_or_try_init(|| { + let dc = DigestComputer::::new(self); + dc.digest() + }) + .cloned() + .expect("Failure to retrieve digest!") + } } /// A succinct proof of knowledge of a witness to a relaxed R1CS instance @@ -46,7 +73,7 @@ pub struct VerifierKey> { /// the commitment to a vector viewed as a polynomial commitment #[derive(Serialize, Deserialize)] #[serde(bound = "")] -pub struct RelaxedR1CSSNARK> { +pub struct RelaxedR1CSSNARK> { sc_proof_outer: SumcheckProof, claims_outer: (G::Scalar, G::Scalar, G::Scalar), eval_E: G::Scalar, @@ -57,9 +84,7 @@ pub struct RelaxedR1CSSNARK> eval_arg: EE::EvaluationArgument, } -impl> RelaxedR1CSSNARKTrait - for RelaxedR1CSSNARK -{ +impl> RelaxedR1CSSNARKTrait for RelaxedR1CSSNARK { type ProverKey = ProverKey; type VerifierKey = VerifierKey; @@ -69,52 +94,42 @@ impl> RelaxedR1CSSNARKTrait Result<(Self::ProverKey, Self::VerifierKey), SpartanError> { let (pk_ee, vk_ee) = EE::setup(ck); - let S = &S.pad(); + let S = S.pad(); - let vk = { - let mut vk = VerifierKey { - vk_ee, - S: S.clone(), - digest: G::Scalar::ZERO, - }; - vk.digest = compute_digest::>(&vk); - vk - }; + let vk: VerifierKey = VerifierKey::new(S.clone(), vk_ee); let pk = ProverKey { pk_ee, - vk_digest: vk.digest, + S, + vk_digest: vk.digest(), }; Ok((pk, vk)) } - /// produces a succinct proof of satisfiability of a RelaxedR1CS instance + /// produces a succinct proof of satisfiability of a `RelaxedR1CS` instance fn prove( ck: &CommitmentKey, pk: &Self::ProverKey, - S: &R1CSShape, U: &RelaxedR1CSInstance, W: &RelaxedR1CSWitness, ) -> Result { - let W = W.pad(S); // pad the witness + let W = W.pad(&pk.S); // pad the witness let mut transcript = G::TE::new(b"RelaxedR1CSSNARK"); // sanity check that R1CSShape has certain size characteristics - assert_eq!(S.num_cons.next_power_of_two(), S.num_cons); - assert_eq!(S.num_vars.next_power_of_two(), S.num_vars); - assert!(S.num_io < S.num_vars); + pk.S.check_regular_shape(); // 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 z = concat(vec![W.W.clone(), vec![U.u], U.X.clone()]); + let mut z = [W.W.clone(), vec![U.u], U.X.clone()].concat(); let (num_rounds_x, num_rounds_y) = ( - (S.num_cons as f64).log2() as usize, - ((S.num_vars as f64).log2() as usize + 1), + usize::try_from(pk.S.num_cons.ilog2()).unwrap(), + (usize::try_from(pk.S.num_vars.ilog2()).unwrap() + 1), ); // outer sum-check @@ -124,8 +139,8 @@ impl> RelaxedR1CSSNARKTrait>(); ( @@ -206,7 +221,7 @@ impl> RelaxedR1CSSNARKTrait> RelaxedR1CSSNARKTrait> RelaxedR1CSSNARKTrait) -> Result<(), SpartanError> { let mut transcript = G::TE::new(b"RelaxedR1CSSNARK"); // append the digest of R1CS matrices and the RelaxedR1CSInstance to the transcript - transcript.absorb(b"vk", &vk.digest); + transcript.absorb(b"vk", &vk.digest()); transcript.absorb(b"U", U); let (num_rounds_x, num_rounds_y) = ( - (vk.S.num_cons as f64).log2() as usize, - ((vk.S.num_vars as f64).log2() as usize + 1), + usize::try_from(vk.S.num_cons.ilog2()).unwrap(), + (usize::try_from(vk.S.num_vars.ilog2()).unwrap() + 1), ); // outer sum-check @@ -406,7 +421,8 @@ impl> RelaxedR1CSSNARKTrait>(), ); - SparsePolynomial::new((vk.S.num_vars as f64).log2() as usize, poly_X).evaluate(&r_y[1..]) + SparsePolynomial::new(usize::try_from(vk.S.num_vars.ilog2()).unwrap(), poly_X) + .evaluate(&r_y[1..]) }; (G::Scalar::ONE - r_y[0]) * self.eval_W + r_y[0] * eval_X }; @@ -419,13 +435,12 @@ impl> RelaxedR1CSSNARKTrait G::Scalar { (0..M.len()) - .collect::>() - .par_iter() - .map(|&i| { + .into_par_iter() + .map(|i| { let (row, col, val) = M[i]; T_x[row] * T_y[col] * val }) - .reduce(|| G::Scalar::ZERO, |acc, x| acc + x) + .sum() }; let (T_x, T_y) = rayon::join( @@ -434,9 +449,8 @@ impl> RelaxedR1CSSNARKTrait>() - .par_iter() - .map(|&i| evaluate_with_table(M_vec[i], &T_x, &T_y)) + .into_par_iter() + .map(|i| evaluate_with_table(M_vec[i], &T_x, &T_y)) .collect() }; diff --git a/src/spartan/sumcheck.rs b/src/spartan/sumcheck.rs index b113c3e..08fd1da 100644 --- a/src/spartan/sumcheck.rs +++ b/src/spartan/sumcheck.rs @@ -1,9 +1,11 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] -use super::polynomial::MultilinearPolynomial; use crate::errors::SpartanError; -use crate::traits::{Group, TranscriptEngineTrait, TranscriptReprTrait}; -use core::marker::PhantomData; +use crate::spartan::polys::{ + multilinear::MultilinearPolynomial, + univariate::{CompressedUniPoly, UniPoly}, +}; +use crate::traits::{Group, TranscriptEngineTrait}; use ff::Field; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -11,11 +13,11 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] pub(crate) struct SumcheckProof { - compressed_polys: Vec>, + compressed_polys: Vec>, } impl SumcheckProof { - pub fn new(compressed_polys: Vec>) -> Self { + pub fn new(compressed_polys: Vec>) -> Self { Self { compressed_polys } } @@ -61,6 +63,34 @@ impl SumcheckProof { Ok((e, r)) } + #[inline] + pub(in crate::spartan) fn compute_eval_points_quadratic( + poly_A: &MultilinearPolynomial, + poly_B: &MultilinearPolynomial, + comb_func: &F, + ) -> (G::Scalar, G::Scalar) + where + F: Fn(&G::Scalar, &G::Scalar) -> G::Scalar + Sync, + { + let len = poly_A.len() / 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]); + + // 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 eval_point_2 = comb_func(&poly_A_bound_point, &poly_B_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), + ) + } + pub fn prove_quad( claim: &G::Scalar, num_rounds: usize, @@ -73,29 +103,12 @@ impl SumcheckProof { F: Fn(&G::Scalar, &G::Scalar) -> G::Scalar + Sync, { let mut r: Vec = Vec::new(); - let mut polys: 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]); - - // 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 eval_point_2 = comb_func(&poly_A_bound_point, &poly_B_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 (eval_point_0, eval_point_2) = + Self::compute_eval_points_quadratic(poly_A, poly_B, &comb_func); let evals = vec![eval_point_0, claim_per_round - eval_point_0, eval_point_2]; UniPoly::from_evals(&evals) @@ -136,30 +149,18 @@ impl SumcheckProof { transcript: &mut G::TE, ) -> Result<(Self, Vec, (Vec, Vec)), SpartanError> where - F: Fn(&G::Scalar, &G::Scalar) -> G::Scalar, + F: Fn(&G::Scalar, &G::Scalar) -> G::Scalar + Sync, { let mut e = *claim; let mut r: Vec = Vec::new(); - let mut quad_polys: Vec> = Vec::new(); + let mut quad_polys: Vec> = Vec::new(); for _j in 0..num_rounds { let mut evals: Vec<(G::Scalar, G::Scalar)> = Vec::new(); for (poly_A, poly_B) in poly_A_vec.iter().zip(poly_B_vec.iter()) { - let mut eval_point_0 = G::Scalar::ZERO; - let mut eval_point_2 = G::Scalar::ZERO; - - let len = poly_A.len() / 2; - for i in 0..len { - // eval 0: bound_func is A(low) - eval_point_0 += comb_func(&poly_A[i], &poly_B[i]); - - // eval 2: bound_func is -A(low) + 2*A(high) - let poly_A_bound_point = poly_A[len + i] + poly_A[len + i] - poly_A[i]; - let poly_B_bound_point = poly_B[len + i] + poly_B[len + i] - poly_B[i]; - eval_point_2 += comb_func(&poly_A_bound_point, &poly_B_bound_point); - } - + let (eval_point_0, eval_point_2) = + Self::compute_eval_points_quadratic(poly_A, poly_B, &comb_func); evals.push((eval_point_0, eval_point_2)); } @@ -193,6 +194,55 @@ impl SumcheckProof { Ok((SumcheckProof::new(quad_polys), r, claims_prod)) } + #[inline] + pub(in crate::spartan) fn compute_eval_points_cubic( + poly_A: &MultilinearPolynomial, + poly_B: &MultilinearPolynomial, + poly_C: &MultilinearPolynomial, + poly_D: &MultilinearPolynomial, + comb_func: &F, + ) -> (G::Scalar, G::Scalar, G::Scalar) + where + F: Fn(&G::Scalar, &G::Scalar, &G::Scalar, &G::Scalar) -> G::Scalar + Sync, + { + let len = poly_A.len() / 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 3: bound_func is -2A(low) + 3A(high); computed incrementally with bound_func applied to eval(2) + let poly_A_bound_point = poly_A_bound_point + poly_A[len + i] - poly_A[i]; + let poly_B_bound_point = poly_B_bound_point + poly_B[len + i] - poly_B[i]; + let poly_C_bound_point = poly_C_bound_point + poly_C[len + i] - poly_C[i]; + let poly_D_bound_point = poly_D_bound_point + poly_D[len + i] - poly_D[i]; + let eval_point_3 = comb_func( + &poly_A_bound_point, + &poly_B_bound_point, + &poly_C_bound_point, + &poly_D_bound_point, + ); + (eval_point_0, eval_point_2, eval_point_3) + }) + .reduce( + || (G::Scalar::ZERO, G::Scalar::ZERO, G::Scalar::ZERO), + |a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2), + ) + } + pub fn prove_cubic_with_additive_term( claim: &G::Scalar, num_rounds: usize, @@ -207,49 +257,14 @@ impl SumcheckProof { 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 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, eval_point_3) = (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 3: bound_func is -2A(low) + 3A(high); computed incrementally with bound_func applied to eval(2) - let poly_A_bound_point = poly_A_bound_point + poly_A[len + i] - poly_A[i]; - let poly_B_bound_point = poly_B_bound_point + poly_B[len + i] - poly_B[i]; - let poly_C_bound_point = poly_C_bound_point + poly_C[len + i] - poly_C[i]; - let poly_D_bound_point = poly_D_bound_point + poly_D[len + i] - poly_D[i]; - let eval_point_3 = comb_func( - &poly_A_bound_point, - &poly_B_bound_point, - &poly_C_bound_point, - &poly_D_bound_point, - ); - (eval_point_0, eval_point_2, eval_point_3) - }) - .reduce( - || (G::Scalar::ZERO, G::Scalar::ZERO, G::Scalar::ZERO), - |a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2), - ); + let (eval_point_0, eval_point_2, eval_point_3) = + Self::compute_eval_points_cubic(poly_A, poly_B, poly_C, poly_D, &comb_func); let evals = vec![ eval_point_0, @@ -287,113 +302,3 @@ impl SumcheckProof { )) } } - -// ax^2 + bx + c stored as vec![a,b,c] -// ax^3 + bx^2 + cx + d stored as vec![a,b,c,d] -#[derive(Debug)] -pub struct UniPoly { - coeffs: Vec, -} - -// ax^2 + bx + c stored as vec![a,c] -// ax^3 + bx^2 + cx + d stored as vec![a,c,d] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CompressedUniPoly { - coeffs_except_linear_term: Vec, - _p: PhantomData, -} - -impl UniPoly { - pub fn from_evals(evals: &[G::Scalar]) -> Self { - // we only support degree-2 or degree-3 univariate polynomials - assert!(evals.len() == 3 || evals.len() == 4); - let coeffs = if evals.len() == 3 { - // ax^2 + bx + c - let two_inv = G::Scalar::from(2).invert().unwrap(); - - let c = evals[0]; - let a = two_inv * (evals[2] - evals[1] - evals[1] + c); - let b = evals[1] - c - a; - vec![c, b, a] - } else { - // ax^3 + bx^2 + cx + d - let two_inv = G::Scalar::from(2).invert().unwrap(); - let six_inv = G::Scalar::from(6).invert().unwrap(); - - let d = evals[0]; - let a = six_inv - * (evals[3] - evals[2] - evals[2] - evals[2] + evals[1] + evals[1] + evals[1] - evals[0]); - let b = two_inv - * (evals[0] + evals[0] - evals[1] - evals[1] - evals[1] - evals[1] - evals[1] - + evals[2] - + evals[2] - + evals[2] - + evals[2] - - evals[3]); - let c = evals[1] - d - a - b; - vec![d, c, b, a] - }; - - UniPoly { coeffs } - } - - pub fn degree(&self) -> usize { - self.coeffs.len() - 1 - } - - pub fn eval_at_zero(&self) -> G::Scalar { - self.coeffs[0] - } - - pub fn eval_at_one(&self) -> G::Scalar { - (0..self.coeffs.len()) - .into_par_iter() - .map(|i| self.coeffs[i]) - .reduce(|| G::Scalar::ZERO, |a, b| a + b) - } - - pub fn evaluate(&self, r: &G::Scalar) -> G::Scalar { - let mut eval = self.coeffs[0]; - let mut power = *r; - for coeff in self.coeffs.iter().skip(1) { - eval += power * coeff; - power *= r; - } - eval - } - - pub fn compress(&self) -> CompressedUniPoly { - let coeffs_except_linear_term = [&self.coeffs[0..1], &self.coeffs[2..]].concat(); - assert_eq!(coeffs_except_linear_term.len() + 1, self.coeffs.len()); - CompressedUniPoly { - coeffs_except_linear_term, - _p: Default::default(), - } - } -} - -impl CompressedUniPoly { - // we require eval(0) + eval(1) = hint, so we can solve for the linear term as: - // linear_term = hint - 2 * constant_term - deg2 term - deg3 term - pub fn decompress(&self, hint: &G::Scalar) -> UniPoly { - let mut linear_term = - *hint - self.coeffs_except_linear_term[0] - self.coeffs_except_linear_term[0]; - for i in 1..self.coeffs_except_linear_term.len() { - linear_term -= self.coeffs_except_linear_term[i]; - } - - let mut coeffs: Vec = Vec::new(); - coeffs.push(self.coeffs_except_linear_term[0]); - coeffs.push(linear_term); - coeffs.extend(&self.coeffs_except_linear_term[1..]); - assert_eq!(self.coeffs_except_linear_term.len() + 1, coeffs.len()); - UniPoly { coeffs } - } -} - -impl TranscriptReprTrait for UniPoly { - fn to_transcript_bytes(&self) -> Vec { - let coeffs = self.compress().coeffs_except_linear_term; - coeffs.as_slice().to_transcript_bytes() - } -} diff --git a/src/traits/evaluation.rs b/src/traits/evaluation.rs index cd7bf16..9657d4a 100644 --- a/src/traits/evaluation.rs +++ b/src/traits/evaluation.rs @@ -8,12 +8,7 @@ use crate::{ use serde::{Deserialize, Serialize}; /// A trait that ties different pieces of the commitment evaluation together -pub trait EvaluationEngineTrait: - Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> -{ - /// A type that holds the associated commitment engine - type CE: CommitmentEngineTrait; - +pub trait EvaluationEngineTrait: Clone + Send + Sync { /// A type that holds the prover key type ProverKey: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>; @@ -25,15 +20,15 @@ pub trait EvaluationEngineTrait: /// A method to perform any additional setup needed to produce proofs of evaluations fn setup( - ck: &>::CommitmentKey, + ck: &<::CE as CommitmentEngineTrait>::CommitmentKey, ) -> (Self::ProverKey, Self::VerifierKey); /// A method to prove the evaluation of a multilinear polynomial fn prove( - ck: &>::CommitmentKey, + ck: &<::CE as CommitmentEngineTrait>::CommitmentKey, pk: &Self::ProverKey, transcript: &mut G::TE, - comm: &>::Commitment, + comm: &<::CE as CommitmentEngineTrait>::Commitment, poly: &[G::Scalar], point: &[G::Scalar], eval: &G::Scalar, @@ -43,7 +38,7 @@ pub trait EvaluationEngineTrait: fn verify( vk: &Self::VerifierKey, transcript: &mut G::TE, - comm: &>::Commitment, + comm: &<::CE as CommitmentEngineTrait>::Commitment, point: &[G::Scalar], eval: &G::Scalar, arg: &Self::EvaluationArgument, diff --git a/src/traits/snark.rs b/src/traits/snark.rs index 5ed56c8..8b136b5 100644 --- a/src/traits/snark.rs +++ b/src/traits/snark.rs @@ -28,7 +28,6 @@ pub trait RelaxedR1CSSNARKTrait: fn prove( ck: &CommitmentKey, pk: &Self::ProverKey, - S: &R1CSShape, U: &RelaxedR1CSInstance, W: &RelaxedR1CSWitness, ) -> Result;