diff --git a/benches/compressed-snark.rs b/benches/compressed-snark.rs index c0412431..e79094f8 100644 --- a/benches/compressed-snark.rs +++ b/benches/compressed-snark.rs @@ -1,6 +1,7 @@ #![allow(non_snake_case)] use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; +use halo2curves::bn256::Bn256; use core::marker::PhantomData; use criterion::{measurement::WallTime, *}; use ff::PrimeField; @@ -17,7 +18,7 @@ use std::time::Duration; type E1 = Bn256EngineKZG; type E2 = GrumpkinEngine; -type EE1 = nova_snark::provider::hyperkzg::EvaluationEngine; +type EE1 = nova_snark::provider::hyperkzg::EvaluationEngine; type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine; // SNARKs without computational commitments type S1 = nova_snark::spartan::snark::RelaxedR1CSSNARK; diff --git a/benches/ppsnark.rs b/benches/ppsnark.rs index 56a24ac0..f36d0dc7 100644 --- a/benches/ppsnark.rs +++ b/benches/ppsnark.rs @@ -1,6 +1,7 @@ #![allow(non_snake_case)] use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; +use halo2curves::bn256::Bn256; use core::marker::PhantomData; use criterion::*; use ff::PrimeField; @@ -12,7 +13,7 @@ use nova_snark::{ use std::time::Duration; type E = Bn256EngineKZG; -type EE = nova_snark::provider::hyperkzg::EvaluationEngine; +type EE = nova_snark::provider::hyperkzg::EvaluationEngine; type S = nova_snark::spartan::ppsnark::RelaxedR1CSSNARK; // To run these benchmarks, first download `criterion` with `cargo install cargo install cargo-criterion`. diff --git a/examples/and.rs b/examples/and.rs index 33b50bb7..4b3e5b62 100644 --- a/examples/and.rs +++ b/examples/and.rs @@ -8,6 +8,7 @@ use core::marker::PhantomData; use ff::Field; use ff::{PrimeField, PrimeFieldBits}; use flate2::{write::ZlibEncoder, Compression}; +use halo2curves::bn256::Bn256; use nova_snark::{ provider::{Bn256EngineKZG, GrumpkinEngine}, traits::{ @@ -22,7 +23,7 @@ use std::time::Instant; type E1 = Bn256EngineKZG; type E2 = GrumpkinEngine; -type EE1 = nova_snark::provider::hyperkzg::EvaluationEngine; +type EE1 = nova_snark::provider::hyperkzg::EvaluationEngine; type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine; type S1 = nova_snark::spartan::snark::RelaxedR1CSSNARK; // non-preprocessing SNARK type S2 = nova_snark::spartan::snark::RelaxedR1CSSNARK; // non-preprocessing SNARK diff --git a/examples/hashchain.rs b/examples/hashchain.rs index d0554568..16609ee5 100644 --- a/examples/hashchain.rs +++ b/examples/hashchain.rs @@ -4,6 +4,7 @@ use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use ff::Field; use flate2::{write::ZlibEncoder, Compression}; use generic_array::typenum::U24; +use halo2curves::bn256::Bn256; use neptune::{ circuit2::Elt, sponge::{ @@ -26,7 +27,7 @@ use std::time::Instant; type E1 = Bn256EngineKZG; type E2 = GrumpkinEngine; -type EE1 = nova_snark::provider::hyperkzg::EvaluationEngine; +type EE1 = nova_snark::provider::hyperkzg::EvaluationEngine; type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine; type S1 = nova_snark::spartan::snark::RelaxedR1CSSNARK; // non-preprocessing SNARK type S2 = nova_snark::spartan::snark::RelaxedR1CSSNARK; // non-preprocessing SNARK diff --git a/examples/minroot.rs b/examples/minroot.rs index 7a589b95..a0845809 100644 --- a/examples/minroot.rs +++ b/examples/minroot.rs @@ -4,6 +4,7 @@ use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use ff::Field; use flate2::{write::ZlibEncoder, Compression}; +use halo2curves::bn256::Bn256; use nova_snark::{ provider::{Bn256EngineKZG, GrumpkinEngine}, traits::{ @@ -18,7 +19,7 @@ use std::time::Instant; type E1 = Bn256EngineKZG; type E2 = GrumpkinEngine; -type EE1 = nova_snark::provider::hyperkzg::EvaluationEngine; +type EE1 = nova_snark::provider::hyperkzg::EvaluationEngine; type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine; type S1 = nova_snark::spartan::snark::RelaxedR1CSSNARK; // non-preprocessing SNARK type S2 = nova_snark::spartan::snark::RelaxedR1CSSNARK; // non-preprocessing SNARK diff --git a/src/lib.rs b/src/lib.rs index f7a91213..0b49e74a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -862,7 +862,7 @@ mod tests { use halo2curves::bn256::Bn256; type EE = provider::ipa_pc::EvaluationEngine; - type EEPrime = provider::hyperkzg::EvaluationEngine; + type EEPrime = provider::hyperkzg::EvaluationEngine; type S = spartan::snark::RelaxedR1CSSNARK; type SPrime = spartan::ppsnark::RelaxedR1CSSNARK; @@ -1202,7 +1202,7 @@ mod tests { test_ivc_nontrivial_with_spark_compression_with::< Bn256EngineKZG, GrumpkinEngine, - provider::hyperkzg::EvaluationEngine<_>, + provider::hyperkzg::EvaluationEngine, EE<_>, >(); } diff --git a/src/provider/hyperkzg.rs b/src/provider/hyperkzg.rs index 6afc9a53..b3040092 100644 --- a/src/provider/hyperkzg.rs +++ b/src/provider/hyperkzg.rs @@ -8,365 +8,132 @@ #![allow(non_snake_case)] use crate::{ errors::NovaError, - provider::traits::{CompressedGroup, DlogGroup, PairingGroup}, + provider::{traits::DlogGroup, + pedersen::Commitment, + kzg_commitment::KZGCommitmentEngine, non_hiding_kzg::{KZGProverKey, KZGVerifierKey, UniversalKZGParam}}, + spartan::polys::univariate::UniPoly, traits::{ - commitment::{CommitmentEngineTrait, CommitmentTrait, Len}, + commitment::{CommitmentEngineTrait, Len}, evaluation::EvaluationEngineTrait, - AbsorbInROTrait, Engine, ROTrait, TranscriptEngineTrait, TranscriptReprTrait, + Engine as NovaEngine, Group, TranscriptEngineTrait, TranscriptReprTrait, }, zip_with, }; -use core::{ - marker::PhantomData, - ops::{Add, Mul, MulAssign}, -}; -use ff::Field; +use core::marker::PhantomData; +use ff::{Field, PrimeFieldBits}; +use group::{Group as _, Curve}; +use halo2curves::pairing::{Engine, MillerLoopResult, MultiMillerLoop}; use itertools::Itertools; -use rand_core::OsRng; use rayon::prelude::*; -use serde::{Deserialize, Serialize}; - -/// Alias to points on G1 that are in preprocessed form -type G1Affine = <::GE as DlogGroup>::AffineGroupElement; - -/// Alias to points on G1 that are in preprocessed form -type G2Affine = <<::GE as PairingGroup>::G2 as DlogGroup>::AffineGroupElement; - -/// KZG commitment key -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CommitmentKey -where - E::GE: PairingGroup, -{ - ck: Vec<::AffineGroupElement>, - tau_H: <::G2 as DlogGroup>::AffineGroupElement, // needed only for the verifier key -} - -impl Len for CommitmentKey -where - E::GE: PairingGroup, -{ - fn length(&self) -> usize { - self.ck.len() - } -} - -/// A KZG commitment -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct Commitment -where - E: Engine, - E::GE: PairingGroup, -{ - comm: ::GE, -} - -/// A compressed commitment (suitable for serialization) -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct CompressedCommitment -where - E: Engine, - E::GE: PairingGroup, -{ - comm: ::CompressedGroupElement, -} - -impl CommitmentTrait for Commitment -where - E: Engine, - E::GE: PairingGroup, -{ - type CompressedCommitment = CompressedCommitment; - - fn compress(&self) -> Self::CompressedCommitment { - CompressedCommitment { - comm: self.comm.compress(), - } - } - - fn to_coordinates(&self) -> (E::Base, E::Base, bool) { - self.comm.to_coordinates() - } - - fn decompress(c: &Self::CompressedCommitment) -> Result { - let comm = <::GE as DlogGroup>::CompressedGroupElement::decompress(&c.comm)?; - Ok(Commitment { comm }) - } -} - -impl Default for Commitment -where - E: Engine, - E::GE: PairingGroup, -{ - fn default() -> Self { - Commitment { - comm: E::GE::zero(), - } - } -} - -impl TranscriptReprTrait for Commitment -where - E: Engine, - E::GE: PairingGroup, -{ - fn to_transcript_bytes(&self) -> Vec { - let (x, y, is_infinity) = self.comm.to_coordinates(); - let is_infinity_byte = (!is_infinity).into(); - [ - x.to_transcript_bytes(), - y.to_transcript_bytes(), - [is_infinity_byte].to_vec(), - ] - .concat() - } -} - -impl AbsorbInROTrait for Commitment -where - E: Engine, - E::GE: PairingGroup, -{ - fn absorb_in_ro(&self, ro: &mut E::RO) { - let (x, y, is_infinity) = self.comm.to_coordinates(); - ro.absorb(x); - ro.absorb(y); - ro.absorb(if is_infinity { - E::Base::ONE - } else { - E::Base::ZERO - }); - } -} - -impl TranscriptReprTrait for CompressedCommitment -where - E::GE: PairingGroup, -{ - fn to_transcript_bytes(&self) -> Vec { - self.comm.to_transcript_bytes() - } -} - -impl MulAssign for Commitment -where - E: Engine, - E::GE: PairingGroup, -{ - fn mul_assign(&mut self, scalar: E::Scalar) { - let result = (self as &Commitment).comm * scalar; - *self = Commitment { comm: result }; - } -} - -impl<'a, 'b, E> Mul<&'b E::Scalar> for &'a Commitment -where - E: Engine, - E::GE: PairingGroup, -{ - type Output = Commitment; - - fn mul(self, scalar: &'b E::Scalar) -> Commitment { - Commitment { - comm: self.comm * scalar, - } - } -} - -impl Mul for Commitment -where - E: Engine, - E::GE: PairingGroup, -{ - type Output = Commitment; - - fn mul(self, scalar: E::Scalar) -> Commitment { - Commitment { - comm: self.comm * scalar, - } - } -} - -impl Add for Commitment -where - E: Engine, - E::GE: PairingGroup, -{ - type Output = Commitment; - - fn add(self, other: Commitment) -> Commitment { - Commitment { - comm: self.comm + other.comm, - } - } -} - -/// Provides a commitment engine -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct CommitmentEngine { - _p: PhantomData, -} - -impl CommitmentEngineTrait for CommitmentEngine -where - E: Engine, - E::GE: PairingGroup, -{ - type Commitment = Commitment; - type CommitmentKey = CommitmentKey; - - fn setup(_label: &'static [u8], n: usize) -> Self::CommitmentKey { - // NOTE: this is for testing purposes and should not be used in production - // TODO: we need to decide how to generate load/store parameters - let tau = E::Scalar::random(OsRng); - let num_gens = n.next_power_of_two(); - - // Compute powers of tau in E::Scalar, then scalar muls in parallel - let mut powers_of_tau: Vec = Vec::with_capacity(num_gens); - powers_of_tau.insert(0, E::Scalar::ONE); - for i in 1..num_gens { - powers_of_tau.insert(i, powers_of_tau[i - 1] * tau); - } - - let ck: Vec> = (0..num_gens) - .into_par_iter() - .map(|i| (::gen() * powers_of_tau[i]).affine()) - .collect(); - - let tau_H = (<::G2 as DlogGroup>::gen() * tau).affine(); - - Self::CommitmentKey { ck, tau_H } - } - - fn commit(ck: &Self::CommitmentKey, v: &[E::Scalar]) -> Self::Commitment { - assert!(ck.ck.len() >= v.len()); - Commitment { - comm: E::GE::vartime_multiscalar_mul(v, &ck.ck[..v.len()]), - } - } -} - -/// Provides an implementation of generators for proving evaluations -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct ProverKey { - _p: PhantomData, -} - -/// A verifier key -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct VerifierKey -where - E::GE: PairingGroup, -{ - G: G1Affine, - H: G2Affine, - tau_H: G2Affine, -} +use ref_cast::RefCast; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; /// Provides an implementation of a polynomial evaluation argument #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct EvaluationArgument -where - E::GE: PairingGroup, -{ - com: Vec>, - w: Vec>, - v: Vec>, +#[serde(bound( + serialize = "E::G1Affine: Serialize, E::Fr: Serialize", + deserialize = "E::G1Affine: Deserialize<'de>, E::Fr: Deserialize<'de>" +))] +pub struct EvaluationArgument { + evals_r: Vec, + evals_neg_r: Vec, + evals_r_squared: Vec>, } /// Provides an implementation of a polynomial evaluation engine using KZG #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct EvaluationEngine { - _p: PhantomData, +pub struct EvaluationEngine { + _p: PhantomData<(E, NE)>, } -impl EvaluationEngine +// This impl block defines helper functions that are not a part of +// EvaluationEngineTrait, but that we will use to implement the trait methods. +impl EvaluationEngine where E: Engine, - E::GE: PairingGroup, + NE: NovaEngine, + E::G1: DlogGroup, + E::Fr: TranscriptReprTrait, + E::G1Affine: TranscriptReprTrait, // TODO: this bound on DlogGroup is really unusable! { - // This impl block defines helper functions that are not a part of - // EvaluationEngineTrait, but that we will use to implement the trait methods. - fn compute_challenge(com: &[G1Affine], transcript: &mut ::TE) -> E::Scalar { + fn compute_challenge( + com: &[E::G1Affine], + transcript: &mut impl TranscriptEngineTrait, + ) -> E::Fr { transcript.absorb(b"c", &com.to_vec().as_slice()); - transcript.squeeze(b"c").unwrap() } // Compute challenge q = Hash(vk, C0, ..., C_{k-1}, u0, ...., u_{t-1}, // (f_i(u_j))_{i=0..k-1,j=0..t-1}) - fn get_batch_challenge(v: &[Vec], transcript: &mut ::TE) -> E::Scalar { + // It is assumed that both 'C' and 'u' are already absorbed by the transcript + fn get_batch_challenge( + v: &[Vec], + transcript: &mut impl TranscriptEngineTrait, + ) -> E::Fr { transcript.absorb( b"v", &v.iter() .flatten() .cloned() - .collect::>() + .collect::>() .as_slice(), ); transcript.squeeze(b"r").unwrap() } - fn batch_challenge_powers(q: E::Scalar, k: usize) -> Vec { + fn batch_challenge_powers(q: E::Fr, k: usize) -> Vec { // Compute powers of q : (1, q, q^2, ..., q^(k-1)) - let mut q_powers = vec![E::Scalar::ONE; k]; - for i in 1..k { - q_powers[i] = q_powers[i - 1] * q; - } - q_powers + std::iter::successors(Some(E::Fr::ONE), |&x| Some(x * q)) + .take(k) + .collect() } - fn verifier_second_challenge(W: &[G1Affine], transcript: &mut ::TE) -> E::Scalar { + fn verifier_second_challenge( + W: &[E::G1Affine], + transcript: &mut impl TranscriptEngineTrait, + ) -> E::Fr { transcript.absorb(b"W", &W.to_vec().as_slice()); transcript.squeeze(b"d").unwrap() } } -impl EvaluationEngineTrait for EvaluationEngine +impl EvaluationEngineTrait for EvaluationEngine where - E: Engine>, - E::GE: PairingGroup, + E: MultiMillerLoop, + NE: NovaEngine>, + E::Fr: Serialize + DeserializeOwned, + E::G1Affine: Serialize + DeserializeOwned, + E::G2Affine: Serialize + DeserializeOwned, + E::G1: DlogGroup, + ::Base: TranscriptReprTrait, // Note: due to the move of the bound TranscriptReprTrait on G::Base from Group to Engine + E::Fr: PrimeFieldBits, // TODO due to use of gen_srs_for_testing, make optional + E::Fr: TranscriptReprTrait, + E::G1Affine: TranscriptReprTrait, { type EvaluationArgument = EvaluationArgument; - type ProverKey = ProverKey; - type VerifierKey = VerifierKey; - - fn setup( - ck: &>::CommitmentKey, - ) -> (Self::ProverKey, Self::VerifierKey) { - let pk = ProverKey { - _p: Default::default(), - }; - - let vk = VerifierKey { - G: E::GE::gen().affine(), - H: <::G2 as DlogGroup>::gen().affine(), - tau_H: ck.tau_H.clone(), - }; + type ProverKey = KZGProverKey; + type VerifierKey = KZGVerifierKey; - (pk, vk) + fn setup(ck: &UniversalKZGParam) -> (Self::ProverKey, Self::VerifierKey) { + ck.trim(ck.length() - 1) } fn prove( - ck: &CommitmentKey, + ck: &UniversalKZGParam, _pk: &Self::ProverKey, - transcript: &mut ::TE, - _C: &Commitment, - hat_P: &[E::Scalar], - point: &[E::Scalar], - _eval: &E::Scalar, + transcript: &mut ::TE, + C: &Commitment, + hat_P: &[E::Fr], + point: &[E::Fr], + eval: &E::Fr, ) -> Result { - let x: Vec = point.to_vec(); + let x: Vec = point.to_vec(); //////////////// begin helper closures ////////// - let kzg_open = |f: &[E::Scalar], u: E::Scalar| -> G1Affine { + let kzg_open = |f: &[E::Fr], u: E::Fr| -> E::G1Affine { // On input f(x) and u compute the witness polynomial used to prove // that f(u) = v. The main part of this is to compute the // division (f(x) - f(u)) / (x - u), but we don't use a general @@ -381,11 +148,11 @@ where // same. One advantage is that computing f(u) could be decoupled // from kzg_open, it could be done later or separate from computing W. - let compute_witness_polynomial = |f: &[E::Scalar], u: E::Scalar| -> Vec { + let compute_witness_polynomial = |f: &[E::Fr], u: E::Fr| -> Vec { let d = f.len(); // Compute h(x) = f(x)/(x - u) - let mut h = vec![E::Scalar::ZERO; d]; + let mut h = vec![E::Fr::ZERO; d]; for i in (1..d).rev() { h[i - 1] = f[i] + h[i] * u; } @@ -395,33 +162,24 @@ where let h = compute_witness_polynomial(f, u); - E::CE::commit(ck, &h).comm.affine() + >::commit(ck, &h) + .comm + .affine() }; - let kzg_open_batch = |f: &[Vec], - u: &[E::Scalar], - transcript: &mut ::TE| - -> (Vec>, Vec>) { - let poly_eval = |f: &[E::Scalar], u: E::Scalar| -> E::Scalar { - let mut v = f[0]; - let mut u_power = E::Scalar::ONE; - - for fi in f.iter().skip(1) { - u_power *= u; - v += u_power * fi; - } - - v - }; - - let scalar_vector_muladd = |a: &mut Vec, v: &Vec, s: E::Scalar| { + let kzg_open_batch = |C: &[E::G1Affine], + f: &[Vec], + u: &[E::Fr], + transcript: &mut ::TE| + -> (Vec, Vec>) { + let scalar_vector_muladd = |a: &mut Vec, v: &Vec, s: E::Fr| { assert!(a.len() >= v.len()); - for i in 0..v.len() { - a[i] += s * v[i]; - } + a.par_iter_mut() + .zip(v.par_iter()) + .for_each(|(c, v)| *c += s * v); }; - let kzg_compute_batch_polynomial = |f: &[Vec], q: E::Scalar| -> Vec { + let kzg_compute_batch_polynomial = |f: &[Vec], q: E::Fr| -> Vec { let k = f.len(); // Number of polynomials we're batching let q_powers = Self::batch_challenge_powers(q, k); @@ -438,16 +196,16 @@ where let k = f.len(); let t = u.len(); + assert!(C.len() == k); // The verifier needs f_i(u_j), so we compute them here // (V will compute B(u_j) itself) - let mut v = vec![vec!(E::Scalar::ZERO; k); t]; + let mut v = vec![vec!(E::Fr::ZERO; k); t]; v.par_iter_mut().enumerate().for_each(|(i, v_i)| { // for each point u v_i.par_iter_mut().zip_eq(f).for_each(|(v_ij, f)| { // for each poly f - // for each poly f (except the last one - since it is constant) - *v_ij = poly_eval(f, u[i]); + *v_ij = UniPoly::ref_cast(f).evaluate(&u[i]); }); }); @@ -455,10 +213,7 @@ where let B = kzg_compute_batch_polynomial(f, q); // Now open B at u0, ..., u_{t-1} - let w = u - .into_par_iter() - .map(|ui| kzg_open(&B, *ui)) - .collect::>>(); + let w = u.par_iter().map(|ui| kzg_open(&B, *ui)).collect::>(); // The prover computes the challenge to keep the transcript in the same // state as that of the verifier @@ -474,60 +229,74 @@ where assert_eq!(n, 1 << ell); // Below we assume that n is a power of two // Phase 1 -- create commitments com_1, ..., com_\ell - // We do not compute final Pi (and its commitment) as it is constant and equals to 'eval' - // also known to verifier, so can be derived on its side as well - let mut polys: Vec> = Vec::new(); + let mut polys: Vec> = Vec::new(); polys.push(hat_P.to_vec()); - for i in 0..ell - 1 { + for i in 0..ell { let Pi_len = polys[i].len() / 2; - let mut Pi = vec![E::Scalar::ZERO; Pi_len]; + let mut Pi = vec![E::Fr::ZERO; Pi_len]; #[allow(clippy::needless_range_loop)] - Pi.par_iter_mut().enumerate().for_each(|(j, Pi_j)| { - *Pi_j = x[ell - i - 1] * (polys[i][2 * j + 1] - polys[i][2 * j]) + polys[i][2 * j]; - }); + for j in 0..Pi_len { + Pi[j] = x[ell-i-1] * polys[i][2*j + 1] // Odd part of P^(i-1) + + (E::Fr::ONE - x[ell-i-1]) * polys[i][2*j]; // Even part of P^(i-1) + } + + if i == ell - 1 && *eval != Pi[0] { + return Err(NovaError::UnSat); + } polys.push(Pi); } // We do not need to commit to the first polynomial as it is already committed. // Compute commitments in parallel - let com: Vec> = (1..polys.len()) + let com: Vec = (1..polys.len()) .into_par_iter() - .map(|i| E::CE::commit(ck, &polys[i]).comm.affine()) + .map(|i| { + >::commit(ck, &polys[i]) + .comm + .affine() + }) .collect(); // Phase 2 - // We do not need to add x to the transcript, because in our context x was obtained from the transcript. - // We also do not need to absorb `C` and `eval` as they are already absorbed by the transcript by the caller + // We do not need to add x to the transcript, because in our context x was + // obtained from the transcript. let r = Self::compute_challenge(&com, transcript); let u = vec![r, -r, r * r]; // Phase 3 -- create response - let (w, v) = kzg_open_batch(&polys, &u, transcript); - - Ok(EvaluationArgument { com, w, v }) + let mut com_all = com.clone(); + com_all.insert(0, C.comm.affine()); + let (w, v) = kzg_open_batch(&com_all, &polys, &u, transcript); + + Ok(EvaluationArgument { + evals_r: com, + evals_neg_r: w, + evals_r_squared: v, + }) } /// A method to verify purported evaluations of a batch of polynomials fn verify( vk: &Self::VerifierKey, - transcript: &mut ::TE, - C: &Commitment, - point: &[E::Scalar], - P_of_x: &E::Scalar, + transcript: &mut ::TE, + C: &Commitment, + point: &[E::Fr], + P_of_x: &E::Fr, pi: &Self::EvaluationArgument, ) -> Result<(), NovaError> { let x = point.to_vec(); let y = P_of_x; // vk is hashed in transcript already, so we do not add it here - let kzg_verify_batch = |vk: &VerifierKey, - C: &Vec>, - W: &Vec>, - u: &Vec, - v: &Vec>, - transcript: &mut ::TE| + + let kzg_verify_batch = |vk: &KZGVerifierKey, + C: &Vec, + W: &Vec, + u: &Vec, + v: &Vec>, + transcript: &mut ::TE| -> bool { let k = C.len(); let t = u.len(); @@ -535,86 +304,65 @@ where let q = Self::get_batch_challenge(v, transcript); let q_powers = Self::batch_challenge_powers(q, k); // 1, q, q^2, ..., q^(k-1) - let d_0 = Self::verifier_second_challenge(W, transcript); - let d_1 = d_0 * d_0; + // Compute the commitment to the batched polynomial B(X) + let c_0: E::G1 = C[0].into(); + let C_B = (c_0 + NE::GE::vartime_multiscalar_mul(&q_powers[1..k], &C[1..k])).affine(); - // Shorthand to convert from preprocessed G1 elements to non-preprocessed - let from_ppG1 = |P: &G1Affine| ::group(P); - // Shorthand to convert from preprocessed G2 elements to non-preprocessed - let from_ppG2 = |P: &G2Affine| <::G2 as DlogGroup>::group(P); + // Compute the batched openings + // compute B(u_i) = v[i][0] + q*v[i][1] + ... + q^(t-1) * v[i][t-1] + let B_u = v + .iter() + .map(|v_i| zip_with!(iter, (q_powers, v_i), |a, b| *a * *b).sum()) + .collect::>(); - assert_eq!(t, 3); - assert_eq!(W.len(), 3); + let d_0 = Self::verifier_second_challenge(W, transcript); + let d = [d_0, d_0 * d_0]; + + assert!(t == 3); // We write a special case for t=3, since this what is required for - // hyperkzg. Following the paper directly, we must compute: + // mlkzg. Following the paper directly, we must compute: // let L0 = C_B - vk.G * B_u[0] + W[0] * u[0]; // let L1 = C_B - vk.G * B_u[1] + W[1] * u[1]; // let L2 = C_B - vk.G * B_u[2] + W[2] * u[2]; // let R0 = -W[0]; // let R1 = -W[1]; // let R2 = -W[2]; - // let L = L0 + L1*d_0 + L2*d_1; - // let R = R0 + R1*d_0 + R2*d_1; + // let L = L0 + L1*d[0] + L2*d[1]; + // let R = R0 + R1*d[0] + R2*d[1]; // // We group terms to reduce the number of scalar mults (to seven): // In Rust, we could use MSMs for these, and speed up verification. - // - // Note, that while computing L, the intermediate computation of C_B together with computing - // L0, L1, L2 can be replaced by single MSM of C with the powers of q multiplied by (1 + d_0 + d_1) - // with additionally concatenated inputs for scalars/bases. + let L = E::G1::from(C_B) * (E::Fr::ONE + d[0] + d[1]) + - E::G1::from(vk.g) * (B_u[0] + d[0] * B_u[1] + d[1] * B_u[2]) + + E::G1::from(W[0]) * u[0] + + E::G1::from(W[1]) * (u[1] * d[0]) + + E::G1::from(W[2]) * (u[2] * d[1]); - let q_power_multiplier = E::Scalar::ONE + d_0 + d_1; - - let q_powers_multiplied: Vec = q_powers - .par_iter() - .map(|q_power| *q_power * q_power_multiplier) - .collect(); - - // Compute the batched openings - // compute B(u_i) = v[i][0] + q*v[i][1] + ... + q^(t-1) * v[i][t-1] - let B_u = v - .into_par_iter() - .map(|v_i| zip_with!(iter, (q_powers, v_i), |a, b| *a * *b).sum()) - .collect::>(); - - let L = E::GE::vartime_multiscalar_mul( - &[ - &q_powers_multiplied[..k], - &[ - u[0], - (u[1] * d_0), - (u[2] * d_1), - -(B_u[0] + d_0 * B_u[1] + d_1 * B_u[2]), - ], - ] - .concat(), - &[ - &C[..k], - &[W[0].clone(), W[1].clone(), W[2].clone(), vk.G.clone()], - ] - .concat(), - ); - - let R0 = from_ppG1(&W[0]); - let R1 = from_ppG1(&W[1]); - let R2 = from_ppG1(&W[2]); - let R = R0 + R1 * d_0 + R2 * d_1; + let R0 = E::G1::from(W[0]); + let R1 = E::G1::from(W[1]); + let R2 = E::G1::from(W[2]); + let R = R0 + R1 * d[0] + R2 * d[1]; // Check that e(L, vk.H) == e(R, vk.tau_H) - (::pairing(&L, &from_ppG2(&vk.H))) - == (::pairing(&R, &from_ppG2(&vk.tau_H))) + let pairing_inputs = [ + (&(-L).to_affine(), &E::G2Prepared::from(vk.h)), + (&R.to_affine(), &E::G2Prepared::from(vk.beta_h)), + ]; + + let pairing_result = E::multi_miller_loop(&pairing_inputs).final_exponentiation(); + pairing_result.is_identity().into() }; ////// END verify() closure helpers let ell = x.len(); - let mut com = pi.com.clone(); + let mut com = pi.evals_r.clone(); // we do not need to add x to the transcript, because in our context x was // obtained from the transcript let r = Self::compute_challenge(&com, transcript); - if r == E::Scalar::ZERO || C.comm == E::GE::zero() { + if r == E::Fr::ZERO || C.comm == E::G1::zero() { return Err(NovaError::ProofVerifyError); } com.insert(0, C.comm.affine()); // set com_0 = C, shifts other commitments to the right @@ -622,23 +370,26 @@ where let u = vec![r, -r, r * r]; // Setup vectors (Y, ypos, yneg) from pi.v - let v = &pi.v; + let v = &pi.evals_r_squared; if v.len() != 3 { return Err(NovaError::ProofVerifyError); } - if v[0].len() != ell || v[1].len() != ell || v[2].len() != ell { + if v[0].len() != ell + 1 || v[1].len() != ell + 1 || v[2].len() != ell + 1 { return Err(NovaError::ProofVerifyError); } let ypos = &v[0]; let yneg = &v[1]; - let mut Y = v[2].to_vec(); - Y.push(*y); + let Y = &v[2]; // Check consistency of (Y, ypos, yneg) - let two = E::Scalar::from(2u64); + if Y[ell] != *y { + return Err(NovaError::ProofVerifyError); + } + + let two = E::Fr::from(2u64); for i in 0..ell { if two * r * Y[i + 1] - != r * (E::Scalar::ONE - x[ell - i - 1]) * (ypos[i] + yneg[i]) + != r * (E::Fr::ONE - x[ell - i - 1]) * (ypos[i] + yneg[i]) + x[ell - i - 1] * (ypos[i] - yneg[i]) { return Err(NovaError::ProofVerifyError); @@ -648,7 +399,14 @@ where } // Check commitments to (Y, ypos, yneg) are valid - if !kzg_verify_batch(vk, &com, &pi.w, &u, &pi.v, transcript) { + if !kzg_verify_batch( + vk, + &com, + &pi.evals_neg_r, + &u, + &pi.evals_r_squared, + transcript, + ) { return Err(NovaError::ProofVerifyError); } @@ -660,64 +418,60 @@ where mod tests { use super::*; use crate::{ - provider::{keccak::Keccak256Transcript, Bn256EngineKZG}, - spartan::polys::multilinear::MultilinearPolynomial, + provider::keccak::Keccak256Transcript, spartan::polys::multilinear::MultilinearPolynomial, + CommitmentKey, }; use bincode::Options; use rand::SeedableRng; - type E = Bn256EngineKZG; - type Fr = ::Scalar; + type E = halo2curves::bn256::Bn256; + type NE = crate::provider::Bn256EngineKZG; + type Fr = ::Scalar; #[test] fn test_hyperkzg_eval() { // Test with poly(X1, X2) = 1 + X1 + X2 + X1*X2 let n = 4; - let ck: CommitmentKey = CommitmentEngine::setup(b"test", n); - let (pk, vk): (ProverKey, VerifierKey) = EvaluationEngine::setup(&ck); + let ck: CommitmentKey = + as CommitmentEngineTrait>::setup(b"test", n); + let (pk, _vk): (KZGProverKey, KZGVerifierKey) = EvaluationEngine::::setup(&ck); // poly is in eval. representation; evaluated at [(0,0), (0,1), (1,0), (1,1)] let poly = vec![Fr::from(1), Fr::from(2), Fr::from(2), Fr::from(4)]; - let C = CommitmentEngine::commit(&ck, &poly); + let C = as CommitmentEngineTrait>::commit(&ck, &poly); + let mut tr = Keccak256Transcript::::new(b"TestEval"); - let test_inner = |point: Vec, eval: Fr| -> Result<(), NovaError> { - let mut tr = Keccak256Transcript::new(b"TestEval"); - let proof = EvaluationEngine::prove(&ck, &pk, &mut tr, &C, &poly, &point, &eval).unwrap(); - let mut tr = Keccak256Transcript::new(b"TestEval"); - EvaluationEngine::verify(&vk, &mut tr, &C, &point, &eval, &proof) - }; - - // Call the prover with a (point, eval) pair. - // The prover does not recompute so it may produce a proof, but it should not verify + // Call the prover with a (point, eval) pair. The prover recomputes + // poly(point) = eval', and fails if eval' != eval let point = vec![Fr::from(0), Fr::from(0)]; let eval = Fr::ONE; - assert!(test_inner(point, eval).is_ok()); + assert!(EvaluationEngine::::prove(&ck, &pk, &mut tr, &C, &poly, &point, &eval).is_ok()); let point = vec![Fr::from(0), Fr::from(1)]; let eval = Fr::from(2); - assert!(test_inner(point, eval).is_ok()); + assert!(EvaluationEngine::::prove(&ck, &pk, &mut tr, &C, &poly, &point, &eval).is_ok()); let point = vec![Fr::from(1), Fr::from(1)]; let eval = Fr::from(4); - assert!(test_inner(point, eval).is_ok()); + assert!(EvaluationEngine::::prove(&ck, &pk, &mut tr, &C, &poly, &point, &eval).is_ok()); let point = vec![Fr::from(0), Fr::from(2)]; let eval = Fr::from(3); - assert!(test_inner(point, eval).is_ok()); + assert!(EvaluationEngine::::prove(&ck, &pk, &mut tr, &C, &poly, &point, &eval).is_ok()); let point = vec![Fr::from(2), Fr::from(2)]; let eval = Fr::from(9); - assert!(test_inner(point, eval).is_ok()); + assert!(EvaluationEngine::::prove(&ck, &pk, &mut tr, &C, &poly, &point, &eval).is_ok()); // Try a couple incorrect evaluations and expect failure let point = vec![Fr::from(2), Fr::from(2)]; let eval = Fr::from(50); - assert!(test_inner(point, eval).is_err()); + assert!(EvaluationEngine::::prove(&ck, &pk, &mut tr, &C, &poly, &point, &eval).is_err()); let point = vec![Fr::from(0), Fr::from(2)]; let eval = Fr::from(4); - assert!(test_inner(point, eval).is_err()); + assert!(EvaluationEngine::::prove(&ck, &pk, &mut tr, &C, &poly, &point, &eval).is_err()); } #[test] @@ -733,24 +487,31 @@ mod tests { // eval = 28 let eval = Fr::from(28); - let ck: CommitmentKey = CommitmentEngine::setup(b"test", n); - let (pk, vk) = EvaluationEngine::setup(&ck); + let ck: CommitmentKey = + as CommitmentEngineTrait>::setup(b"test", n); + let (pk, vk): (KZGProverKey, KZGVerifierKey) = EvaluationEngine::::setup(&ck); // make a commitment - let C = CommitmentEngine::commit(&ck, &poly); + let C = KZGCommitmentEngine::commit(&ck, &poly); // prove an evaluation let mut prover_transcript = Keccak256Transcript::new(b"TestEval"); let proof = - EvaluationEngine::::prove(&ck, &pk, &mut prover_transcript, &C, &poly, &point, &eval) + EvaluationEngine::::prove(&ck, &pk, &mut prover_transcript, &C, &poly, &point, &eval) .unwrap(); let post_c_p = prover_transcript.squeeze(b"c").unwrap(); // verify the evaluation - let mut verifier_transcript = Keccak256Transcript::new(b"TestEval"); - assert!( - EvaluationEngine::verify(&vk, &mut verifier_transcript, &C, &point, &eval, &proof).is_ok() - ); + let mut verifier_transcript = Keccak256Transcript::::new(b"TestEval"); + assert!(EvaluationEngine::::verify( + &vk, + &mut verifier_transcript, + &C, + &point, + &eval, + &proof + ) + .is_ok()); let post_c_v = verifier_transcript.squeeze(b"c").unwrap(); // check if the prover transcript and verifier transcript are kept in the same state @@ -765,9 +526,9 @@ mod tests { // Change the proof and expect verification to fail let mut bad_proof = proof.clone(); - bad_proof.v[0] = bad_proof.v[1].clone(); - let mut verifier_transcript2 = Keccak256Transcript::new(b"TestEval"); - assert!(EvaluationEngine::verify( + bad_proof.evals_r[0] = (bad_proof.evals_r[0] + bad_proof.evals_r[1]).to_affine(); + let mut verifier_transcript2 = Keccak256Transcript::::new(b"TestEval"); + assert!(EvaluationEngine::::verify( &vk, &mut verifier_transcript2, &C, @@ -790,29 +551,45 @@ mod tests { let point = (0..ell).map(|_| Fr::random(&mut rng)).collect::>(); let eval = MultilinearPolynomial::evaluate_with(&poly, &point); - let ck: CommitmentKey = CommitmentEngine::setup(b"test", n); - let (pk, vk) = EvaluationEngine::setup(&ck); + let ck: CommitmentKey = + as CommitmentEngineTrait>::setup(b"test", n); + let (pk, vk): (KZGProverKey, KZGVerifierKey) = EvaluationEngine::::setup(&ck); // make a commitment - let C = CommitmentEngine::commit(&ck, &poly); + let C = as CommitmentEngineTrait>::commit(&ck, &poly); // prove an evaluation - let mut prover_transcript = Keccak256Transcript::new(b"TestEval"); - let proof: EvaluationArgument = - EvaluationEngine::prove(&ck, &pk, &mut prover_transcript, &C, &poly, &point, &eval) - .unwrap(); + let mut prover_transcript = Keccak256Transcript::::new(b"TestEval"); + let proof: EvaluationArgument = EvaluationEngine::::prove( + &ck, + &pk, + &mut prover_transcript, + &C, + &poly, + &point, + &eval, + ) + .unwrap(); // verify the evaluation - let mut verifier_tr = Keccak256Transcript::new(b"TestEval"); - assert!(EvaluationEngine::verify(&vk, &mut verifier_tr, &C, &point, &eval, &proof).is_ok()); + let mut verifier_tr = Keccak256Transcript::::new(b"TestEval"); + assert!( + EvaluationEngine::::verify(&vk, &mut verifier_tr, &C, &point, &eval, &proof).is_ok() + ); // Change the proof and expect verification to fail let mut bad_proof = proof.clone(); - bad_proof.v[0] = bad_proof.v[1].clone(); - let mut verifier_tr2 = Keccak256Transcript::new(b"TestEval"); - assert!( - EvaluationEngine::verify(&vk, &mut verifier_tr2, &C, &point, &eval, &bad_proof).is_err() - ); + bad_proof.evals_r[0] = (bad_proof.evals_r[0] + bad_proof.evals_r[1]).to_affine(); + let mut verifier_tr2 = Keccak256Transcript::::new(b"TestEval"); + assert!(EvaluationEngine::::verify( + &vk, + &mut verifier_tr2, + &C, + &point, + &eval, + &bad_proof + ) + .is_err()); } } } diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 04d3fc61..0ceea347 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -23,7 +23,6 @@ mod keccak; use crate::{ provider::{ bn256_grumpkin::{bn256, grumpkin}, - hyperkzg::CommitmentEngine as HyperKZGCommitmentEngine, keccak::Keccak256Transcript, pedersen::CommitmentEngine as PedersenCommitmentEngine, poseidon::{PoseidonRO, PoseidonROCircuit}, @@ -56,7 +55,7 @@ impl Engine for Bn256EngineKZG { type RO = PoseidonRO; type ROCircuit = PoseidonROCircuit; type TE = Keccak256Transcript; - type CE = HyperKZGCommitmentEngine; + type CE = KZGCommitmentEngine; } impl Engine for Bn256EngineIPA { @@ -152,7 +151,7 @@ impl Engine for VestaEngine { #[cfg(test)] mod tests { use crate::provider::{bn256_grumpkin::bn256, secp_secq::secp256k1, traits::DlogGroup}; - + use digest::{ExtendableOutput, Update}; use group::Curve; use halo2curves::CurveExt; diff --git a/src/provider/non_hiding_zeromorph.rs b/src/provider/non_hiding_zeromorph.rs index 3c8fa8c0..a90d0d70 100644 --- a/src/provider/non_hiding_zeromorph.rs +++ b/src/provider/non_hiding_zeromorph.rs @@ -20,8 +20,8 @@ use crate::{ }; use ff::{BatchInvert, Field, PrimeField, PrimeFieldBits}; use group::{Curve, Group as _}; -use itertools::Itertools as _; use halo2curves::pairing::{Engine, MillerLoopResult, MultiMillerLoop}; +use itertools::Itertools as _; use rayon::{ iter::IntoParallelRefIterator, prelude::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}, @@ -515,8 +515,8 @@ mod test { use ff::{Field, PrimeField, PrimeFieldBits}; use halo2curves::bn256::Bn256; use halo2curves::bn256::Fr as Scalar; - use itertools::Itertools as _; use halo2curves::pairing::MultiMillerLoop; + use itertools::Itertools as _; use rand::thread_rng; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/src/spartan/direct.rs b/src/spartan/direct.rs index 80986a2a..ae62fe95 100644 --- a/src/spartan/direct.rs +++ b/src/spartan/direct.rs @@ -167,6 +167,7 @@ mod tests { use super::*; use crate::provider::{Bn256EngineKZG, PallasEngine, Secp256k1Engine}; use ::bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; +use halo2curves::bn256::Bn256; use core::marker::PhantomData; use ff::PrimeField; @@ -229,7 +230,7 @@ mod tests { test_direct_snark_with::(); type E2 = Bn256EngineKZG; - type EE2 = crate::provider::hyperkzg::EvaluationEngine; + type EE2 = crate::provider::hyperkzg::EvaluationEngine; type S2 = crate::spartan::snark::RelaxedR1CSSNARK; test_direct_snark_with::();