diff --git a/Cargo.toml b/Cargo.toml index 937114937..1f2692354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ cfg-if = "1.0.0" once_cell = "1.18.0" anyhow = "1.0.72" rand = "0.8.4" +rand_xorshift = "0.3.0" [target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] pasta-msm = { git="https://github.com/lurk-lab/pasta-msm", branch="dev", version = "0.1.4" } diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 5e91e6179..008fe1d5b 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -52,3 +52,22 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------ +https://github.com/AztecProtocol/aztec-packages/ + +Licensed under Apache 2.0 + +Copyright 2022 Aztec + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/examples/minroot_serde.rs b/examples/minroot_serde.rs index 5bd805150..c8a9275ce 100644 --- a/examples/minroot_serde.rs +++ b/examples/minroot_serde.rs @@ -194,7 +194,7 @@ fn main() { MinRootCircuit<::Scalar>, TrivialCircuit<::Scalar>, >::new(&circuit_primary, &circuit_secondary, None, None); - assert!(result.clone() == pp, "not equal!"); + assert!(*result == pp, "not equal!"); assert!(remaining.is_empty()); } else { println!("Something terrible happened"); diff --git a/src/lib.rs b/src/lib.rs index c5fc5ac28..93ad704c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -948,13 +948,10 @@ mod tests { use core::fmt::Write; use super::*; - #[allow(dead_code)] - type ZM = provider::non_hiding_zeromorph::ZMEvaluation; + type ZM = provider::non_hiding_zeromorph::ZMPCS; type EE = provider::ipa_pc::EvaluationEngine; type S = spartan::snark::RelaxedR1CSSNARK; type SPrime = spartan::ppsnark::RelaxedR1CSSNARK; - #[allow(dead_code)] - type SZM = spartan::snark::RelaxedR1CSSNARK>; use ::bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use core::marker::PhantomData; @@ -1068,12 +1065,12 @@ mod tests { let trivial_circuit2_grumpkin = TrivialCircuit::<::Scalar>::default(); let cubic_circuit1_grumpkin = CubicCircuit::<::Scalar>::default(); - test_pp_digest_with::, EE<_>>( + test_pp_digest_with::, EE<_>>( &trivial_circuit1_grumpkin, &trivial_circuit2_grumpkin, "184d05f08dca260f010cb48c6cf8c5eb61dedfc270e5a18226eb622cf7da0203", ); - test_pp_digest_with::, EE<_>>( + test_pp_digest_with::, EE<_>>( &cubic_circuit1_grumpkin, &trivial_circuit2_grumpkin, "2fb992932b2a642b4ce8f52646a7ef6a5a486682716cf969df50021107afff03", @@ -1306,8 +1303,7 @@ mod tests { let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp).unwrap(); // produce a compressed SNARK - let res = - CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark); + let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark); assert!(res.is_ok()); let compressed_snark = res.unwrap(); @@ -1330,7 +1326,7 @@ mod tests { test_ivc_nontrivial_with_compression_with::< bn256::Point, grumpkin::Point, - S>, // SZM, + S>, S>, >(); test_ivc_nontrivial_with_compression_with::< @@ -1341,17 +1337,6 @@ mod tests { >(); } - #[test] - #[ignore] - fn test_ivc_nontrivial_with_zm_compression() { - test_ivc_nontrivial_with_compression_with::< - bn256::Point, - grumpkin::Point, - S>, // SZM, - S>, - >(); - } - fn test_ivc_nontrivial_with_spark_compression_with() where G1: Group::Scalar>, @@ -1456,8 +1441,12 @@ mod tests { type G2 = pasta_curves::vesta::Point; test_ivc_nontrivial_with_spark_compression_with::, EE<_>>(); - test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( - ); + test_ivc_nontrivial_with_spark_compression_with::< + bn256::Point, + grumpkin::Point, + ZM, + EE<_>, + >(); test_ivc_nontrivial_with_spark_compression_with::< secp256k1::Point, secq256k1::Point, @@ -1610,7 +1599,12 @@ mod tests { type G2 = pasta_curves::vesta::Point; test_ivc_nondet_with_compression_with::, EE<_>>(); - test_ivc_nondet_with_compression_with::, EE<_>>(); + test_ivc_nondet_with_compression_with::< + bn256::Point, + grumpkin::Point, + ZM, + EE<_>, + >(); test_ivc_nondet_with_compression_with::, EE<_>>(); } diff --git a/src/provider/bn256_grumpkin.rs b/src/provider/bn256_grumpkin.rs index 4a2cbbe94..ee720937d 100644 --- a/src/provider/bn256_grumpkin.rs +++ b/src/provider/bn256_grumpkin.rs @@ -27,6 +27,8 @@ use halo2curves::grumpkin::{ G1Affine as GrumpkinAffine, G1Compressed as GrumpkinCompressed, G1 as GrumpkinPoint, }; +use super::kzg_commitment::KZGCommitmentEngine; + /// Re-exports that give access to the standard aliases used in the code base, for bn256 pub mod bn256 { pub use halo2curves::bn256::{ @@ -58,7 +60,8 @@ impl_traits!( Bn256Compressed, Bn256Point, Bn256Affine, - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", + KZGCommitmentEngine ); impl_traits!( diff --git a/src/provider/kzg_commitment.rs b/src/provider/kzg_commitment.rs new file mode 100644 index 000000000..3fbcaac76 --- /dev/null +++ b/src/provider/kzg_commitment.rs @@ -0,0 +1,72 @@ +//! Commitment engine for KZG commitments +//! + +use std::marker::PhantomData; + +use group::{prime::PrimeCurveAffine, Curve}; +use pairing::Engine; +use rand::rngs::StdRng; +use rand_core::SeedableRng; +use serde::{Deserialize, Serialize}; + +use crate::traits::{ + commitment::{CommitmentEngineTrait, Len}, + Group, +}; + +use super::{ + non_hiding_kzg::{UVKZGCommitment, UVUniversalKZGParam}, + pedersen::Commitment, +}; + +/// Provides a commitment engine +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct KZGCommitmentEngine { + _p: PhantomData, +} + +impl CommitmentEngineTrait for KZGCommitmentEngine +where + E::G1: Group, + E::G1Affine: Serialize + for<'de> Deserialize<'de>, + E::G2Affine: Serialize + for<'de> Deserialize<'de>, +{ + type CommitmentKey = UVUniversalKZGParam; + type Commitment = Commitment; + + fn setup(label: &'static [u8], n: usize) -> Self::CommitmentKey { + // TODO: this is just for testing, replace by grabbing from a real setup for production + let mut bytes = [0u8; 32]; + let len = label.len().min(32); + bytes[..len].copy_from_slice(&label[..len]); + let rng = &mut StdRng::from_seed(bytes); + UVUniversalKZGParam::gen_srs_for_testing(rng, n.next_power_of_two()) + } + + fn commit(ck: &Self::CommitmentKey, v: &[::Scalar]) -> Self::Commitment { + assert!(ck.length() >= v.len()); + Commitment { + comm: E::G1::vartime_multiscalar_mul(v, &ck.powers_of_g[..v.len()]), + } + } +} + +impl From> for UVKZGCommitment +where + E::G1: Group, +{ + fn from(c: Commitment) -> Self { + UVKZGCommitment(c.comm.to_affine()) + } +} + +impl From> for Commitment +where + E::G1: Group, +{ + fn from(c: UVKZGCommitment) -> Self { + Commitment { + comm: c.0.to_curve(), + } + } +} diff --git a/src/provider/mod.rs b/src/provider/mod.rs index df2cd4220..a72769589 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -13,6 +13,7 @@ pub mod poseidon; pub mod secp_secq; // a non-hiding variant of {kzg, zeromorph} +pub mod kzg_commitment; pub mod non_hiding_kzg; pub mod non_hiding_zeromorph; @@ -156,7 +157,24 @@ macro_rules! impl_traits { $name_compressed:ident, $name_curve:ident, $name_curve_affine:ident, - $order_str:literal + $order_str:expr + ) => { + impl_traits!( + $name, + $name_compressed, + $name_curve, + $name_curve_affine, + $order_str, + CommitmentEngine + ); + }; + ( + $name:ident, + $name_compressed:ident, + $name_curve:ident, + $name_curve_affine:ident, + $order_str:literal, + $commitment_engine:ty ) => { impl Group for $name::Point { type Base = $name::Base; @@ -166,7 +184,7 @@ macro_rules! impl_traits { type RO = PoseidonRO; type ROCircuit = PoseidonROCircuit; type TE = Keccak256Transcript; - type CE = CommitmentEngine; + type CE = $commitment_engine; fn vartime_multiscalar_mul( scalars: &[Self::Scalar], diff --git a/src/provider/non_hiding_kzg.rs b/src/provider/non_hiding_kzg.rs index 22dd6e8c2..f4ab8c4c2 100644 --- a/src/provider/non_hiding_kzg.rs +++ b/src/provider/non_hiding_kzg.rs @@ -11,19 +11,40 @@ use std::{borrow::Borrow, marker::PhantomData, ops::Mul}; use crate::{ errors::{NovaError, PCSError}, - traits::{Group, TranscriptReprTrait}, + traits::{commitment::Len, Group, TranscriptReprTrait}, }; /// `UniversalParams` are the universal parameters for the KZG10 scheme. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, Serialize, Deserialize, Abomonation)] +#[serde(bound( + serialize = "E::G1Affine: Serialize, E::G2Affine: Serialize", + deserialize = "E::G1Affine: Deserialize<'de>, E::G2Affine: Deserialize<'de>" +))] +#[abomonation_omit_bounds] pub struct UVUniversalKZGParam { /// Group elements of the form `{ \beta^i G }`, where `i` ranges from 0 to /// `degree`. + #[abomonate_with(Vec<[u64; 8]>)] // // this is a hack; we just assume the size of the element. pub powers_of_g: Vec, /// Group elements of the form `{ \beta^i H }`, where `i` ranges from 0 to /// `degree`. + #[abomonate_with(Vec<[u64; 16]>)] // this is a hack; we just assume the size of the element. pub powers_of_h: Vec, } + +impl PartialEq for UVUniversalKZGParam { + fn eq(&self, other: &UVUniversalKZGParam) -> bool { + self.powers_of_g == other.powers_of_g && self.powers_of_h == other.powers_of_h + } +} + +// for the purpose of the Len trait, we count commitment bases, i.e. G1 elements +impl Len for UVUniversalKZGParam { + fn length(&self) -> usize { + self.powers_of_g.len() + } +} + /// `UnivariateProverKey` is used to generate a proof #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Abomonation)] #[abomonation_omit_bounds] @@ -115,27 +136,34 @@ impl UVUniversalKZGParam { let g = E::G1::random(&mut rng); let h = E::G2::random(rng); - let powers_of_g_projective = (0..=max_degree) - .scan(g, |acc, _| { - let val = *acc; - *acc *= beta; - Some(val) - }) - .collect::>(); + let (powers_of_g_projective, powers_of_h_projective) = rayon::join( + || { + (0..=max_degree) + .scan(g, |acc, _| { + let val = *acc; + *acc *= beta; + Some(val) + }) + .collect::>() + }, + || { + (0..=max_degree) + .scan(h, |acc, _| { + let val = *acc; + *acc *= beta; + Some(val) + }) + .collect::>() + }, + ); let mut powers_of_g = vec![E::G1Affine::identity(); powers_of_g_projective.len()]; - E::G1::batch_normalize(&powers_of_g_projective, &mut powers_of_g); - - let powers_of_h_projective = (0..=max_degree) - .scan(h, |acc, _| { - let val = *acc; - *acc *= beta; - Some(val) - }) - .collect::>(); - let mut powers_of_h = vec![E::G2Affine::identity(); powers_of_h_projective.len()]; - E::G2::batch_normalize(&powers_of_h_projective, &mut powers_of_h); + + rayon::join( + || E::G1::batch_normalize(&powers_of_g_projective, &mut powers_of_g), + || E::G2::batch_normalize(&powers_of_h_projective, &mut powers_of_h), + ); Self { powers_of_g, @@ -145,7 +173,7 @@ impl UVUniversalKZGParam { } /// Commitments -#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)] #[serde(bound( serialize = "E::G1Affine: Serialize", deserialize = "E::G1Affine: Deserialize<'de>" @@ -160,8 +188,15 @@ where E::G1: Group, { fn to_transcript_bytes(&self) -> Vec { - // TODO: avoid the round-trip through the group - E::G1::from(self.0).compress().to_transcript_bytes() + // TODO: avoid the round-trip through the group (to_curve .. to_coordinates) + let (x, y, is_infinity) = self.0.to_curve().to_coordinates(); + let is_infinity_byte = (!is_infinity).into(); + [ + x.to_transcript_bytes(), + y.to_transcript_bytes(), + [is_infinity_byte].to_vec(), + ] + .concat() } } @@ -177,57 +212,13 @@ pub struct UVKZGProof { pub proof: E::G1Affine, } -// TODO: we are extending this into a real Dense UV Polynomial type, -// and this is probably better organized elsewhere. /// Polynomial and its associated types pub type UVKZGPoly = crate::spartan::polys::univariate::UniPoly; -impl UVKZGPoly { - fn zero() -> Self { - UVKZGPoly::new(Vec::new()) - } - - pub fn random(degree: usize, mut rng: &mut R) -> Self { - let coeffs = (0..=degree).map(|_| F::random(&mut rng)).collect(); - UVKZGPoly::new(coeffs) - } - - /// Divide self by another polynomial, and returns the - /// quotient and remainder. - fn divide_with_q_and_r(&self, divisor: &Self) -> Option<(UVKZGPoly, UVKZGPoly)> { - if self.is_zero() { - Some((UVKZGPoly::zero(), UVKZGPoly::zero())) - } else if divisor.is_zero() { - panic!("Dividing by zero polynomial") - } else if self.degree() < divisor.degree() { - Some((UVKZGPoly::zero(), self.clone())) - } else { - // Now we know that self.degree() >= divisor.degree(); - let mut quotient = vec![F::ZERO; self.degree() - divisor.degree() + 1]; - let mut remainder: UVKZGPoly = self.clone(); - // Can unwrap here because we know self is not zero. - let divisor_leading_inv = divisor.leading_coefficient().unwrap().invert().unwrap(); - while !remainder.is_zero() && remainder.degree() >= divisor.degree() { - let cur_q_coeff = *remainder.leading_coefficient().unwrap() * divisor_leading_inv; - let cur_q_degree = remainder.degree() - divisor.degree(); - quotient[cur_q_degree] = cur_q_coeff; - - for (i, div_coeff) in divisor.coeffs.iter().enumerate() { - remainder.coeffs[cur_q_degree + i] -= &(cur_q_coeff * div_coeff); - } - while let Some(true) = remainder.coeffs.last().map(|c| c == &F::ZERO) { - remainder.coeffs.pop(); - } - } - Some((UVKZGPoly::new(quotient), remainder)) - } - } -} - #[derive(Debug, Clone, Eq, PartialEq, Default)] /// KZG Polynomial Commitment Scheme on univariate polynomial. -/// Note: this is non-hiding, which is why we will implement the EvaluationEngineTrait on this token struct, -/// as we will have several impls for the trait pegged on the same instance of a pairing::Engine. +/// Note: this is non-hiding, which is why we will implement traits on this token struct, +/// as we expect to have several impls for the trait pegged on the same instance of a pairing::Engine. pub struct UVKZGPCS { #[doc(hidden)] phantom: PhantomData, @@ -237,8 +228,6 @@ impl UVKZGPCS where E::G1: Group, { - // TODO: this relies on NovaError::InvalidIPA, which should really be extended to a sub-error enum - // called "PCSError" /// Generate a commitment for a polynomial /// Note that the scheme is not hidding pub fn commit( @@ -281,7 +270,6 @@ where let divisor = UVKZGPoly { coeffs: vec![-*point, E::Fr::ONE], }; - // TODO: Better error let witness_polynomial = polynomial .divide_with_q_and_r(&divisor) .map(|(q, _r)| q) @@ -311,7 +299,6 @@ where points: &[E::Fr], ) -> Result<(Vec>, Vec>), NovaError> { if polynomials.len() != points.len() { - // TODO: a better Error return Err(NovaError::PCSError(PCSError::LengthError)); } let mut batch_proof = vec![]; diff --git a/src/provider/non_hiding_zeromorph.rs b/src/provider/non_hiding_zeromorph.rs index 9936858ca..8cb2b012c 100644 --- a/src/provider/non_hiding_zeromorph.rs +++ b/src/provider/non_hiding_zeromorph.rs @@ -4,24 +4,26 @@ use crate::{ errors::{NovaError, PCSError}, - spartan::{math::Math, polys::multilinear::MultilinearPolynomial}, + spartan::polys::multilinear::MultilinearPolynomial, traits::{commitment::Len, evaluation::EvaluationEngineTrait, Group, TranscriptEngineTrait}, - Commitment, CommitmentKey, + Commitment, }; use abomonation_derive::Abomonation; -use ff::{BatchInvert, Field}; +use ff::{BatchInvert, Field, PrimeField}; use group::{Curve, Group as _}; use pairing::{Engine, MillerLoopResult, MultiMillerLoop}; -use rand::thread_rng; use rayon::prelude::{ IndexedParallelIterator, IntoParallelIterator, IntoParallelRefMutIterator, ParallelIterator, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{borrow::Borrow, iter, marker::PhantomData}; -use super::non_hiding_kzg::{ - UVKZGCommitment, UVKZGEvaluation, UVKZGPoly, UVKZGProof, UVKZGProverKey, UVKZGVerifierKey, - UVUniversalKZGParam, UVKZGPCS, +use super::{ + kzg_commitment::KZGCommitmentEngine, + non_hiding_kzg::{ + UVKZGCommitment, UVKZGEvaluation, UVKZGPoly, UVKZGProof, UVKZGProverKey, UVKZGVerifierKey, + UVUniversalKZGParam, UVKZGPCS, + }, }; /// `ZMProverKey` is used to generate a proof @@ -149,7 +151,7 @@ where ) -> Result, NovaError> { let pp = pp.borrow(); if pp.commit_pp.powers_of_g.len() < poly.Z.len() { - return Err(NovaError::PCSError(PCSError::LengthError)); // TODO: better error + return Err(PCSError::LengthError.into()); } // TODO: remove the undue clone in the creation of an UVKZGPoly here UVKZGPCS::commit(&pp.commit_pp, &UVKZGPoly::new(poly.Z.clone())).map(|c| c.into()) @@ -187,9 +189,9 @@ where } debug_assert_eq!(Self::commit(pp, poly).unwrap().0, comm.0); - debug_assert_eq!(poly.evaluate_opt(point), eval.0); + debug_assert_eq!(poly.evaluate(point), eval.0); - let (quotients, remainder) = poly.quotients(point); + let (quotients, remainder) = quotients(poly, point); debug_assert_eq!(remainder, eval.0); // TODO: this should be a Cow @@ -316,9 +318,50 @@ where } } -// TODO : move this somewhere else -fn eval_and_quotient_scalars(y: F, x: F, z: F, u: &[F]) -> (F, Vec) { - let num_vars = u.len(); +/// Compute quotient polynomials of the polynomial w.r.t. an input point +/// i.e. q_k s.t. $$self - v = \Sum_{k=0}^(n-1) q_k (X_k-point_k)$$ +/// +/// The polynomials q_k can be computed explicitly as the difference of the partial evaluation of self in the last +/// (n - k) variables at, respectively, point'' = (point_k + 1, point_{k+1}, ..., point_{n-1}) and +/// point' = (point_k, ..., point_{n-1}). +fn quotients(poly: &MultilinearPolynomial, point: &[F]) -> (Vec>, F) { + let num_var = poly.get_num_vars(); + assert_eq!(num_var, point.len()); + + let mut remainder = poly.Z.to_vec(); + let mut quotients = point + .iter() // assume polynomial variables come in LE form + .enumerate() + .map(|(idx, x_i)| { + let (remainder_lo, remainder_hi) = remainder.split_at_mut(1 << (num_var - 1 - idx)); + let mut quotient = vec![F::ZERO; remainder_lo.len()]; + + quotient + .par_iter_mut() + .zip(&*remainder_lo) + .zip(&*remainder_hi) + .for_each(|((q, r_lo), r_hi)| { + *q = *r_hi - *r_lo; + }); + remainder_lo + .par_iter_mut() + .zip(remainder_hi) + .for_each(|(r_lo, r_hi)| { + *r_lo += (*r_hi - r_lo as &_) * x_i; + }); + + remainder.truncate(1 << (num_var - 1 - idx)); + + quotient + }) + .collect::>>(); + quotients.reverse(); + + (quotients, remainder[0]) +} + +fn eval_and_quotient_scalars(y: F, x: F, z: F, point: &[F]) -> (F, Vec) { + let num_vars = point.len(); let squares_of_x = iter::successors(Some(x), |&x| Some(x.square())) .take(num_vars + 1) @@ -353,7 +396,7 @@ fn eval_and_quotient_scalars(y: F, x: F, z: F, u: &[F]) -> (F, Vec) .zip(squares_of_x) .zip(&vs) .zip(&vs[1..]) - .zip(u) + .zip(point.iter().rev()) // assume variables come in LE form .map( |(((((power_of_y, offset_of_x), square_of_x), v_i), v_j), u_i)| { -(power_of_y * offset_of_x + z * (square_of_x * v_j - *u_i * v_i)) @@ -366,27 +409,22 @@ fn eval_and_quotient_scalars(y: F, x: F, z: F, u: &[F]) -> (F, Vec) impl EvaluationEngineTrait for ZMPCS where - E::G1: Group, + E::G1: Group>, E::G1Affine: Serialize + DeserializeOwned, E::G2Affine: Serialize + DeserializeOwned, { type ProverKey = ZMProverKey; - type VerifierKey = ZMVerifierKey; type EvaluationArgument = ZMProof; - fn setup(ck: &CommitmentKey) -> (Self::ProverKey, Self::VerifierKey) { - let max_vars = ck.length().log_2(); - let mut rng = thread_rng(); - let max_poly_size = 1 << (max_vars + 1); - let universal_setup = UVUniversalKZGParam::::gen_srs_for_testing(&mut rng, max_poly_size); - - trim(&universal_setup, max_poly_size) + fn setup(ck: &UVUniversalKZGParam) -> (Self::ProverKey, Self::VerifierKey) { + // TODO: refine!! + trim(ck, ck.length() - 1) } fn prove( - ck: &CommitmentKey, + _ck: &UVUniversalKZGParam, pk: &Self::ProverKey, transcript: &mut ::TE, comm: &Commitment, @@ -394,7 +432,12 @@ where point: &[::Scalar], eval: &::Scalar, ) -> Result { - todo!() + let commitment = ZMCommitment::from(UVKZGCommitment::from(*comm)); + // TODO: the following two lines will need to change base + let polynomial = MultilinearPolynomial::new(poly.to_vec()); + + let evaluation = ZMEvaluation(*eval); + ZMPCS::open(pk, &commitment, &polynomial, point, &evaluation, transcript) } fn verify( @@ -405,7 +448,12 @@ where eval: &::Scalar, arg: &Self::EvaluationArgument, ) -> Result<(), NovaError> { - todo!() + let commitment = ZMCommitment::from(UVKZGCommitment::from(*comm)); + let evaluation = ZMEvaluation(*eval); + + // TODO: this clone is unsightly! + ZMPCS::verify(vk, transcript, &commitment, point, &evaluation, arg.clone())?; + Ok(()) } } @@ -420,6 +468,7 @@ mod test { use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; + use super::quotients; use crate::{ provider::{ bn256_grumpkin::bn256, @@ -457,7 +506,7 @@ mod test { let point = iter::from_fn(|| transcript.squeeze(b"pt").ok()) .take(num_vars) .collect::>(); - let eval = ZMEvaluation(poly.evaluate_opt(&point)); + let eval = ZMEvaluation(poly.evaluate(&point)); let mut transcript_prover = Keccak256Transcript::::new(b"test"); let proof = ZMPCS::open(&pp, &comm, &poly, &point, &eval, &mut transcript_prover).unwrap(); @@ -505,13 +554,13 @@ mod test { for scalar in point.iter() { println!("scalar: {:?}", scalar); } - let (_quotients, remainder) = poly.quotients(&point); + let (_quotients, remainder) = quotients(&poly, &point); assert_eq!( - poly.evaluate_opt(&point), + poly.evaluate(&point), remainder, "point: {:?}, \n eval: {:?}, remainder:{:?}", point, - poly.evaluate_opt(&point), + poly.evaluate(&point), remainder ); } diff --git a/src/spartan/direct.rs b/src/spartan/direct.rs index c736d6e3c..81353d1ef 100644 --- a/src/spartan/direct.rs +++ b/src/spartan/direct.rs @@ -153,7 +153,7 @@ impl, C: StepCircuit> DirectSNA #[cfg(test)] mod tests { use super::*; - use crate::provider::{bn256_grumpkin::bn256, secp_secq::secp256k1}; + use crate::provider::{bn256_grumpkin::bn256, non_hiding_zeromorph::ZMPCS, secp_secq::secp256k1}; use ::bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use core::marker::PhantomData; use ff::PrimeField; @@ -219,7 +219,7 @@ mod tests { test_direct_snark_with::(); type G2 = bn256::Point; - type EE2 = crate::provider::ipa_pc::EvaluationEngine; + type EE2 = ZMPCS; type S2 = crate::spartan::snark::RelaxedR1CSSNARK; type S2pp = crate::spartan::ppsnark::RelaxedR1CSSNARK; test_direct_snark_with::(); diff --git a/src/spartan/polys/multilinear.rs b/src/spartan/polys/multilinear.rs index f2fc770e2..d742cefca 100644 --- a/src/spartan/polys/multilinear.rs +++ b/src/spartan/polys/multilinear.rs @@ -45,6 +45,11 @@ impl MultilinearPolynomial { } } + /// evaluations of the polynomial in all the 2^num_vars Boolean inputs + pub fn evaluations(&self) -> &[Scalar] { + &self.Z[..] + } + /// Returns the number of variables in the multilinear polynomial pub const fn get_num_vars(&self) -> usize { self.num_vars @@ -125,82 +130,6 @@ impl MultilinearPolynomial { } new_poly } - - /// Compute quotient polynomials of the polynomial w.r.t. an input point - /// i.e. q_k s.t. $$self - v = \Sum_{k=0}^(n-1) q_k (X_k-point_k)$$ - pub fn quotients(&self, point: &[Scalar]) -> (Vec>, Scalar) { - assert_eq!(self.get_num_vars(), point.len()); - - let mut remainder = self.Z.to_vec(); - let mut quotients = point - .iter() - .enumerate() - .rev() - .map(|(num_var, x_i)| { - let (remainder_lo, remainder_hi) = remainder.split_at_mut(1 << num_var); - let mut quotient = vec![Scalar::ZERO; remainder_lo.len()]; - - quotient - .par_iter_mut() - .zip(&*remainder_lo) - .zip(&*remainder_hi) - .for_each(|((q, r_lo), r_hi)| { - *q = *r_hi - *r_lo; - }); - remainder_lo - .par_iter_mut() - .zip(remainder_hi) - .for_each(|(r_lo, r_hi)| { - *r_lo += (*r_hi - r_lo as &_) * x_i; - }); - - remainder.truncate(1 << num_var); - - quotient - }) - .collect::>>(); - quotients.reverse(); - - (quotients, remainder[0]) - } - - /// Evaluate the MLE at a point - pub fn evaluate_opt(&self, point: &[Scalar]) -> Scalar { - assert_eq!(self.num_vars, point.len()); - self.fix_variables(point).Z[0] - } - - /// Fix one variable of the MLE - pub fn fix_variables(&self, partial_point: &[Scalar]) -> Self { - assert!( - partial_point.len() <= self.num_vars, - "invalid size of partial point" - ); - let nv = self.num_vars; - let mut poly = self.Z.clone(); - let dim = partial_point.len(); - // evaluate single variable of partial point from left to right - for (i, point) in partial_point.iter().enumerate().take(dim) { - poly = Self::fix_one_variable_helper(&poly, nv - i, point); - } - poly.truncate(1 << (nv - dim)); - - MultilinearPolynomial::new(poly) - } - - fn fix_one_variable_helper(data: &[Scalar], nv: usize, point: &Scalar) -> Vec { - let mut res = vec![Scalar::ZERO; 1 << (nv - 1)]; - - // evaluate single variable of partial point from left to right - // for i in 0..(1 << (nv - 1)) { - // res[i] = data[i] + (data[(i << 1) + 1] - data[i << 1]) * point; - // } - res.par_iter_mut().enumerate().for_each(|(i, x)| { - *x = data[i << 1] + (data[(i << 1) + 1] - data[i << 1]) * point; - }); - - res - } } impl Index for MultilinearPolynomial { @@ -284,6 +213,7 @@ mod tests { use super::*; use pasta_curves::Fp; + use rand_core::SeedableRng; fn make_mlp(len: usize, value: F) -> MultilinearPolynomial { MultilinearPolynomial { @@ -378,7 +308,7 @@ mod tests { let num_evals = 4; let mut evals: Vec = Vec::with_capacity(num_evals); for _ in 0..num_evals { - evals.push(F::from_u128(8)); + evals.push(F::from(8)); } let dense_poly: MultilinearPolynomial = MultilinearPolynomial::new(evals.clone()); @@ -406,4 +336,115 @@ mod tests { test_evaluation_with::(); test_evaluation_with::(); } + + pub fn partial_eval( + poly: &MultilinearPolynomial, + point: &[F], + ) -> MultilinearPolynomial { + // Get size of partial evaluation point u = (u_0,...,u_{m-1}) + let m = point.len(); + + // Assert that the size of the polynomial being evaluated is a power of 2 greater than (1 << m) + assert!(poly.Z.len().is_power_of_two()); + assert!(poly.Z.len() >= 1 << m); + let n = poly.Z.len().trailing_zeros() as usize; + + // Partial evaluation is done in m rounds l = 0,...,m-1. + let mut n_l = 1 << (n - 1); + + // Temporary buffer of half the size of the polynomial + let mut tmp = vec![F::ZERO; n_l]; + + let prev = &poly.Z; + + // Evaluate variable X_{n-1} at u_{m-1} + let u_l = point[m - 1]; + for i in 0..n_l { + tmp[i] = prev[i] + u_l * (prev[i + n_l] - prev[i]); + } + + // Evaluate m-1 variables X_{n-l-1}, ..., X_{n-2} at m-1 remaining values u_0,...,u_{m-2}) + for l in 1..m { + n_l = 1 << (n - l - 1); + let u_l = point[m - l - 1]; + for i in 0..n_l { + tmp[i] = tmp[i] + u_l * (tmp[i + n_l] - tmp[i]); + } + } + tmp.truncate(1 << (poly.num_vars - m)); + + MultilinearPolynomial::new(tmp) + } + + fn make_rand_mlp( + var_count: usize, + mut rng: &mut R, + ) -> MultilinearPolynomial { + let eqpoly = EqPolynomial::new( + std::iter::from_fn(|| Some(F::random(&mut rng))) + .take(var_count) + .collect::>(), + ); + MultilinearPolynomial::new(eqpoly.evals()) + } + + fn partial_evaluate_mle_with() { + // Initialize a random polynomial + let n = 5; + let mut rng = rand_xorshift::XorShiftRng::from_seed([0u8; 16]); + let poly = make_rand_mlp::(n, &mut rng); + + // Define a random multivariate evaluation point u = (u_0, u_1, u_2, u_3, u_4) + let u_0 = F::random(&mut rng); + let u_1 = F::random(&mut rng); + let u_2 = F::random(&mut rng); + let u_3 = F::random(&mut rng); + let u_4 = F::random(&mut rng); + let u_challenge = [u_4, u_3, u_2, u_1, u_0]; + + // Directly computing v = p(u_0,...,u_4) and comparing it with the result of + // first computing the partial evaluation in the last 3 variables + // g(X_0,X_1) = p(X_0,X_1,u_2,u_3,u_4), then v = g(u_0,u_1) + + // Compute v = p(u_0,...,u_4) + let v_expected = poly.evaluate(&u_challenge[..]); + + // Compute g(X_0,X_1) = p(X_0,X_1,u_2,u_3,u_4), then v = g(u_0,u_1) + let u_part_1 = [u_1, u_0]; // note the endianness difference + let u_part_2 = [u_2, u_3, u_4]; + let partial_evaluated_poly = partial_eval(&poly, &u_part_2); + let v_result = partial_evaluated_poly.evaluate(&u_part_1); + + assert_eq!(v_result, v_expected); + } + + #[test] + fn test_partial_evaluate_mle() { + partial_evaluate_mle_with::(); + partial_evaluate_mle_with::(); + partial_evaluate_mle_with::(); + } + + fn partial_and_evaluate_with() { + for _i in 0..50 { + // Initialize a random polynomial + let n = 7; + let mut rng = rand_xorshift::XorShiftRng::from_seed([0u8; 16]); + let poly = make_rand_mlp::(n, &mut rng); + + // draw a random point + let pt: Vec<_> = std::iter::from_fn(|| Some(F::random(&mut rng))) + .take(n) + .collect(); + let rev_pt: Vec<_> = pt.iter().cloned().rev().collect(); + assert_eq!(poly.evaluate(&pt), partial_eval(&poly, &rev_pt).Z[0]) + } + } + + #[test] + fn test_partial_and_evaluate() { + partial_and_evaluate_with::(); + partial_and_evaluate_with::(); + partial_and_evaluate_with::(); + } } diff --git a/src/spartan/polys/univariate.rs b/src/spartan/polys/univariate.rs index 8b0d02151..d2347e94c 100644 --- a/src/spartan/polys/univariate.rs +++ b/src/spartan/polys/univariate.rs @@ -7,6 +7,7 @@ use std::{ }; use ff::PrimeField; +use rand_core::{CryptoRng, RngCore}; use rayon::prelude::{IntoParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; @@ -33,6 +34,46 @@ impl UniPoly { res } + fn zero() -> Self { + UniPoly::new(Vec::new()) + } + + pub fn random(degree: usize, mut rng: &mut R) -> Self { + let coeffs = (0..=degree).map(|_| Scalar::random(&mut rng)).collect(); + UniPoly::new(coeffs) + } + + /// Divide self by another polynomial, and returns the + /// quotient and remainder. + pub fn divide_with_q_and_r(&self, divisor: &Self) -> Option<(UniPoly, UniPoly)> { + if self.is_zero() { + Some((UniPoly::zero(), UniPoly::zero())) + } else if divisor.is_zero() { + panic!("Dividing by zero polynomial") + } else if self.degree() < divisor.degree() { + Some((UniPoly::zero(), self.clone())) + } else { + // Now we know that self.degree() >= divisor.degree(); + let mut quotient = vec![Scalar::ZERO; self.degree() - divisor.degree() + 1]; + let mut remainder: UniPoly = self.clone(); + // Can unwrap here because we know self is not zero. + let divisor_leading_inv = divisor.leading_coefficient().unwrap().invert().unwrap(); + while !remainder.is_zero() && remainder.degree() >= divisor.degree() { + let cur_q_coeff = *remainder.leading_coefficient().unwrap() * divisor_leading_inv; + let cur_q_degree = remainder.degree() - divisor.degree(); + quotient[cur_q_degree] = cur_q_coeff; + + for (i, div_coeff) in divisor.coeffs.iter().enumerate() { + remainder.coeffs[cur_q_degree + i] -= &(cur_q_coeff * div_coeff); + } + while let Some(true) = remainder.coeffs.last().map(|c| c == &Scalar::ZERO) { + remainder.coeffs.pop(); + } + } + Some((UniPoly::new(quotient), remainder)) + } + } + pub fn is_zero(&self) -> bool { self.coeffs.is_empty() || self.coeffs.iter().all(|c| c == &Scalar::ZERO) } diff --git a/src/supernova/test.rs b/src/supernova/test.rs index d60713cfb..4e15c943c 100644 --- a/src/supernova/test.rs +++ b/src/supernova/test.rs @@ -717,7 +717,7 @@ fn test_supernova_pp_digest() { test_pp_digest_with::( &test_rom_grumpkin, - "0b8c080ffa823b95d1dd75b6a5b49852c53ff60fbead6652af6d2a0bd177b800", + "f8de057dc64151079516d660f929b5f92e514a54d1c5ab70b1f1520c0043e902", ); let rom = vec![