From deffd05047ff98f2caa1a743146b5dfd91c7afd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Thu, 25 Jan 2024 18:18:06 -0500 Subject: [PATCH] refactor: Refactor testing setup and remove dependencies in provider module - Removal of the redundant `src/provider/util/fb_msm.rs` file which included functions for scalar multiplication - Update of `kzg_commitment.rs` with the removal of `ff::PrimeFieldBits` dependency, transitional update of requirements, and improved method setups. --- src/provider/hyperkzg.rs | 15 ++-- src/provider/ipa_pc.rs | 2 +- src/provider/kzg_commitment.rs | 2 - src/provider/mod.rs | 3 +- src/provider/non_hiding_kzg.rs | 39 ++++---- src/provider/non_hiding_zeromorph.rs | 5 +- src/provider/util/fb_msm.rs | 130 --------------------------- src/provider/util/mod.rs | 124 ------------------------- 8 files changed, 32 insertions(+), 288 deletions(-) delete mode 100644 src/provider/util/fb_msm.rs delete mode 100644 src/provider/util/mod.rs diff --git a/src/provider/hyperkzg.rs b/src/provider/hyperkzg.rs index fd531e6cf..630ec63cf 100644 --- a/src/provider/hyperkzg.rs +++ b/src/provider/hyperkzg.rs @@ -1,4 +1,10 @@ -//! This module implements Nova's evaluation engine using multilinear KZG +//! This module implements Nova's evaluation engine using `HyperKZG`, a KZG-based polynomial commitment for multilinear polynomials +//! HyperKZG is based on the transformation from univariate PCS to multilinear PCS in the Gemini paper (section 2.4.2 in https://eprint.iacr.org/2022/420.pdf). +//! However, there are some key differences: +//! (1) HyperKZG works with multilinear polynomials represented in evaluation form (rather than in coefficient form in Gemini's transformation). +//! This means that Spartan's polynomial IOP can use commit to its polynomials as-is without incurring any interpolations or FFTs. +//! (2) HyperKZG is specialized to use KZG as the univariate commitment scheme, so it includes several optimizations (both during the transformation of multilinear-to-univariate claims +//! and within the KZG commitment scheme implementation itself). #![allow(non_snake_case)] use crate::{ errors::NovaError, @@ -17,7 +23,7 @@ use crate::{ zip_with, }; use core::marker::PhantomData; -use ff::{Field, PrimeFieldBits}; +use ff::Field; use group::{Curve, Group as _}; use halo2curves::pairing::{Engine, MillerLoopResult, MultiMillerLoop}; use itertools::Itertools as _; @@ -51,7 +57,7 @@ where NE: NovaEngine, E::G1: DlogGroup, E::Fr: TranscriptReprTrait, - E::G1Affine: TranscriptReprTrait, // TODO: this bound on DlogGroup is really unusable! + E::G1Affine: TranscriptReprTrait, { fn compute_challenge( com: &[E::G1Affine], @@ -106,7 +112,6 @@ where 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, { @@ -419,7 +424,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::provider::util::test_utils::prove_verify_from_num_vars; + use crate::provider::test_utils::prove_verify_from_num_vars; use crate::{ provider::keccak::Keccak256Transcript, traits::commitment::CommitmentTrait, CommitmentKey, }; diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index abcd9f745..34f57b03c 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -411,7 +411,7 @@ where #[cfg(test)] mod test { use crate::provider::ipa_pc::EvaluationEngine; - use crate::provider::util::test_utils::prove_verify_from_num_vars; + use crate::provider::test_utils::prove_verify_from_num_vars; use crate::provider::GrumpkinEngine; #[test] diff --git a/src/provider/kzg_commitment.rs b/src/provider/kzg_commitment.rs index 360ec1edd..405808a69 100644 --- a/src/provider/kzg_commitment.rs +++ b/src/provider/kzg_commitment.rs @@ -3,7 +3,6 @@ use std::marker::PhantomData; -use ff::PrimeFieldBits; use group::{prime::PrimeCurveAffine, Curve}; use halo2curves::pairing::Engine; use rand::rngs::StdRng; @@ -33,7 +32,6 @@ where E::G1: DlogGroup, E::G1Affine: Serialize + for<'de> Deserialize<'de>, E::G2Affine: Serialize + for<'de> Deserialize<'de>, - E::Fr: PrimeFieldBits, // TODO due to use of gen_srs_for_testing, make optional { type CommitmentKey = UniversalKZGParam; type Commitment = Commitment; diff --git a/src/provider/mod.rs b/src/provider/mod.rs index a337d8b03..36aa5547e 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -15,7 +15,8 @@ pub(crate) mod traits; // a non-hiding variant of {kzg, zeromorph} pub(crate) mod kzg_commitment; pub(crate) mod non_hiding_kzg; -pub(crate) mod util; +#[cfg(test)] +pub(crate) mod test_utils; // crate-private modules mod keccak; diff --git a/src/provider/non_hiding_kzg.rs b/src/provider/non_hiding_kzg.rs index 3c4ceb113..67f4d6408 100644 --- a/src/provider/non_hiding_kzg.rs +++ b/src/provider/non_hiding_kzg.rs @@ -1,5 +1,5 @@ //! Non-hiding variant of KZG10 scheme for univariate polynomials. -use ff::{Field, PrimeField, PrimeFieldBits}; +use ff::Field; use group::{prime::PrimeCurveAffine, Curve, Group as _}; use halo2curves::pairing::{Engine, MillerLoopResult, MultiMillerLoop}; use rand_core::{CryptoRng, RngCore}; @@ -9,7 +9,6 @@ use std::{borrow::Borrow, marker::PhantomData, ops::Mul}; use crate::{ errors::{NovaError, PCSError}, provider::traits::DlogGroup, - provider::util::fb_msm, traits::{commitment::Len, Group, TranscriptReprTrait}, }; @@ -119,10 +118,7 @@ impl UniversalKZGParam { } } -impl UniversalKZGParam -where - E::Fr: PrimeFieldBits, -{ +impl UniversalKZGParam { /// Build SRS for testing. /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY. /// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION. @@ -131,25 +127,24 @@ where let g = E::G1::random(&mut rng); let h = E::G2::random(rng); - let nz_powers_of_beta = (0..=max_degree) - .scan(beta, |acc, _| { - let val = *acc; - *acc *= beta; - Some(val) - }) - .collect::>(); - - let window_size = fb_msm::get_mul_window_size(max_degree); - let scalar_bits = E::Fr::NUM_BITS as usize; - let (powers_of_g_projective, powers_of_h_projective) = rayon::join( || { - let g_table = fb_msm::get_window_table(scalar_bits, window_size, g); - fb_msm::multi_scalar_mul::(scalar_bits, window_size, &g_table, &nz_powers_of_beta) + (0..=max_degree) + .scan(g, |acc, _| { + let val = *acc; + *acc *= beta; + Some(val) + }) + .collect::>() }, || { - let h_table = fb_msm::get_window_table(scalar_bits, window_size, h); - fb_msm::multi_scalar_mul::(scalar_bits, window_size, &h_table, &nz_powers_of_beta) + (0..=max_degree) + .scan(h, |acc, _| { + let val = *acc; + *acc *= beta; + Some(val) + }) + .collect::>() }, ); @@ -306,6 +301,7 @@ where mod tests { use super::*; use crate::spartan::polys::univariate::UniPoly; + use ff::PrimeField; use rand::{thread_rng, Rng}; use rand_core::{CryptoRng, RngCore}; @@ -318,7 +314,6 @@ mod tests { where E: MultiMillerLoop, E::G1: DlogGroup, - E::Fr: PrimeFieldBits, { for _ in 0..100 { let mut rng = &mut thread_rng(); diff --git a/src/provider/non_hiding_zeromorph.rs b/src/provider/non_hiding_zeromorph.rs index b7e75f614..9ee8c04e8 100644 --- a/src/provider/non_hiding_zeromorph.rs +++ b/src/provider/non_hiding_zeromorph.rs @@ -18,7 +18,7 @@ use crate::{ }, Commitment, }; -use ff::{BatchInvert, Field, PrimeField, PrimeFieldBits}; +use ff::{BatchInvert, Field, PrimeField}; use group::{Curve, Group as _}; use halo2curves::pairing::{Engine, MillerLoopResult, MultiMillerLoop}; use itertools::Itertools as _; @@ -463,7 +463,6 @@ where E::G1Affine: Serialize + DeserializeOwned, E::G2Affine: Serialize + DeserializeOwned, ::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 { type ProverKey = ZMProverKey; type VerifierKey = ZMVerifierKey; @@ -529,8 +528,8 @@ mod test { non_hiding_zeromorph::{ batched_lifted_degree_quotient, eval_and_quotient_scalars, trim, ZMEvaluation, ZMPCS, }, + test_utils::prove_verify_from_num_vars, traits::DlogGroup, - util::test_utils::prove_verify_from_num_vars, Bn256Engine, Bn256EngineZM, }, spartan::polys::multilinear::MultilinearPolynomial, diff --git a/src/provider/util/fb_msm.rs b/src/provider/util/fb_msm.rs deleted file mode 100644 index bc5b88bee..000000000 --- a/src/provider/util/fb_msm.rs +++ /dev/null @@ -1,130 +0,0 @@ -/// # Fixed-base Scalar Multiplication -/// -/// This module provides an implementation of fixed-base scalar multiplication on elliptic curves. -/// -/// The multiplication is optimized through a windowed method, where scalars are broken into fixed-size -/// windows, pre-computation tables are generated, and results are efficiently combined. -use ff::{PrimeField, PrimeFieldBits}; -use group::{prime::PrimeCurve, Curve}; - -use rayon::prelude::*; - -/// Determines the window size for scalar multiplication based on the number of scalars. -/// -/// This is used to balance between pre-computation and number of point additions. -pub(crate) fn get_mul_window_size(num_scalars: usize) -> usize { - if num_scalars < 32 { - 3 - } else { - (num_scalars as f64).ln().ceil() as usize - } -} - -/// Generates a table of multiples of a base point `g` for use in windowed scalar multiplication. -/// -/// This pre-computes multiples of a base point for each window and organizes them -/// into a table for quick lookup during the scalar multiplication process. The table is a vector -/// of vectors, each inner vector corresponding to a window and containing the multiples of `g` -/// for that window. -pub(crate) fn get_window_table( - scalar_size: usize, - window: usize, - g: T, -) -> Vec> -where - T: Curve, - T::AffineRepr: Send, -{ - let in_window = 1 << window; - // Number of outer iterations needed to cover the entire scalar - let outerc = (scalar_size + window - 1) / window; - - // Number of multiples of the window's "outer point" needed for each window (fewer for the last window) - let last_in_window = 1 << (scalar_size - (outerc - 1) * window); - - let mut multiples_of_g = vec![vec![T::identity(); in_window]; outerc]; - - // Compute the multiples of g for each window - // g_outers = [ 2^{k*window}*g for k in 0..outerc] - let mut g_outer = g; - let mut g_outers = Vec::with_capacity(outerc); - for _ in 0..outerc { - g_outers.push(g_outer); - for _ in 0..window { - g_outer = g_outer.double(); - } - } - multiples_of_g - .par_iter_mut() - .enumerate() - .zip_eq(g_outers) - .for_each(|((outer, multiples_of_g), g_outer)| { - let cur_in_window = if outer == outerc - 1 { - last_in_window - } else { - in_window - }; - - // multiples_of_g = [id, g_outer, 2*g_outer, 3*g_outer, ...], - // where g_outer = 2^{outer*window}*g - let mut g_inner = T::identity(); - for inner in multiples_of_g.iter_mut().take(cur_in_window) { - *inner = g_inner; - g_inner.add_assign(&g_outer); - } - }); - multiples_of_g - .par_iter() - .map(|s| s.iter().map(|s| s.to_affine()).collect()) - .collect() -} - -/// Performs the actual windowed scalar multiplication using a pre-computed table of points. -/// -/// Given a scalar and a table of pre-computed multiples of a base point, this function -/// efficiently computes the scalar multiplication by breaking the scalar into windows and -/// adding the corresponding multiples from the table. -pub(crate) fn windowed_mul( - outerc: usize, - window: usize, - multiples_of_g: &[Vec], - scalar: &T::Scalar, -) -> T -where - T: PrimeCurve, - T::Scalar: PrimeFieldBits, -{ - let modulus_size = ::NUM_BITS as usize; - let scalar_val: Vec = scalar.to_le_bits().into_iter().collect(); - - let mut res = T::identity(); - for outer in 0..outerc { - let mut inner = 0usize; - for i in 0..window { - if outer * window + i < modulus_size && scalar_val[outer * window + i] { - inner |= 1 << i; - } - } - res.add_assign(&multiples_of_g[outer][inner]); - } - res -} - -/// Computes multiple scalar multiplications simultaneously using the windowed method. -pub(crate) fn multi_scalar_mul( - scalar_size: usize, - window: usize, - table: &[Vec], - v: &[T::Scalar], -) -> Vec -where - T: PrimeCurve, - T::Scalar: PrimeFieldBits, -{ - let outerc = (scalar_size + window - 1) / window; - assert!(outerc <= table.len()); - - v.par_iter() - .map(|e| windowed_mul::(outerc, window, table, e)) - .collect::>() -} diff --git a/src/provider/util/mod.rs b/src/provider/util/mod.rs deleted file mode 100644 index d11fa8555..000000000 --- a/src/provider/util/mod.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! Utilities for provider module. -pub(in crate::provider) mod fb_msm; - -#[cfg(test)] -pub mod test_utils { - //! Contains utilities for testing and benchmarking. - use crate::spartan::polys::multilinear::MultilinearPolynomial; - use crate::traits::{ - commitment::CommitmentEngineTrait, evaluation::EvaluationEngineTrait, Engine, - }; - use ff::Field; - use rand::rngs::StdRng; - use rand_core::{CryptoRng, RngCore}; - - /// Returns a random polynomial, a point and calculate its evaluation. - pub fn random_poly_with_eval( - num_vars: usize, - mut rng: &mut R, - ) -> ( - MultilinearPolynomial<::Scalar>, - Vec<::Scalar>, - ::Scalar, - ) { - // Generate random polynomial and point. - let poly = MultilinearPolynomial::random(num_vars, &mut rng); - let point = (0..num_vars) - .map(|_| ::Scalar::random(&mut rng)) - .collect::>(); - - // Calculation evaluation of point over polynomial. - let eval = MultilinearPolynomial::evaluate_with(poly.evaluations(), &point); - - (poly, point, eval) - } - - /// Methods used to test the prove and verify flow of [`MultilinearPolynomial`] Commitment Schemes - /// (PCS). - /// - /// Generates a random polynomial and point from a seed to test a proving/verifying flow of one - /// of our [`EvaluationEngine`]. - pub(crate) fn prove_verify_from_num_vars>( - num_vars: usize, - ) { - use rand_core::SeedableRng; - - let mut rng = rand::rngs::StdRng::seed_from_u64(num_vars as u64); - - let (poly, point, eval) = random_poly_with_eval::(num_vars, &mut rng); - - // Mock commitment key. - let ck = E::CE::setup(b"test", 1 << num_vars); - // Commits to the provided vector using the provided generators. - let commitment = E::CE::commit(&ck, poly.evaluations()); - - prove_verify_with::(&ck, &commitment, &poly, &point, &eval, true) - } - - pub(crate) fn prove_verify_with>( - ck: &<::CE as CommitmentEngineTrait>::CommitmentKey, - commitment: &<::CE as CommitmentEngineTrait>::Commitment, - poly: &MultilinearPolynomial<::Scalar>, - point: &[::Scalar], - eval: &::Scalar, - evaluate_bad_proof: bool, - ) { - use crate::traits::TranscriptEngineTrait; - use std::ops::Add; - - // Generate Prover and verifier key for given commitment key. - let (prover_key, verifier_key) = EE::setup(ck); - - // Generate proof. - let mut prover_transcript = E::TE::new(b"TestEval"); - let proof = EE::prove( - ck, - &prover_key, - &mut prover_transcript, - commitment, - poly.evaluations(), - point, - eval, - ) - .unwrap(); - let pcp = prover_transcript.squeeze(b"c").unwrap(); - - // Verify proof. - let mut verifier_transcript = E::TE::new(b"TestEval"); - EE::verify( - &verifier_key, - &mut verifier_transcript, - commitment, - point, - eval, - &proof, - ) - .unwrap(); - let pcv = verifier_transcript.squeeze(b"c").unwrap(); - - // Check if the prover transcript and verifier transcript are kept in the same state. - assert_eq!(pcp, pcv); - - if evaluate_bad_proof { - // Generate another point to verify proof. Also produce eval. - let altered_verifier_point = point - .iter() - .map(|s| s.add(::Scalar::ONE)) - .collect::>(); - let altered_verifier_eval = - MultilinearPolynomial::evaluate_with(poly.evaluations(), &altered_verifier_point); - - // Verify proof, should fail. - let mut verifier_transcript = E::TE::new(b"TestEval"); - assert!(EE::verify( - &verifier_key, - &mut verifier_transcript, - commitment, - &altered_verifier_point, - &altered_verifier_eval, - &proof, - ) - .is_err()); - } - } -}