From 8278d4406f8cb39cec558034ab52f3a87932a891 Mon Sep 17 00:00:00 2001 From: Mamy Ratsimbazafy Date: Mon, 11 Dec 2023 12:28:10 +0100 Subject: [PATCH] Migrate to new ZAL API Deprecate pre-ZAL API Insert patch in `Cargo.toml` for `../halo2curves` move ZAL to middleware ZAL: introduce modular MSM/FFT per prover accelerators ZAL: address clippy halo2_common: best_fft and best_multiexp have been moved to halo2curves zal: store engine at the prover level zal: fix clippy halo2_common::arithmetic -> halo2_backend::arithmetic sync https://github.com/privacy-scaling-explorations/halo2/pull/281 Remove Copy trait requirement from descriptors run fmt zal: remove Option from PlonkEngineConfig zal: add default engine test --- halo2_backend/src/arithmetic.rs | 205 +--------- halo2_backend/src/plonk/keygen.rs | 2 + halo2_backend/src/plonk/lookup/prover.rs | 14 +- halo2_backend/src/plonk/permutation/keygen.rs | 3 +- halo2_backend/src/plonk/permutation/prover.rs | 8 +- halo2_backend/src/plonk/prover.rs | 207 +++++++---- halo2_backend/src/plonk/shuffle/prover.rs | 7 +- halo2_backend/src/plonk/vanishing/prover.rs | 10 +- halo2_backend/src/plonk/verifier.rs | 8 +- halo2_backend/src/plonk/verifier/batch.rs | 4 +- halo2_backend/src/poly/commitment.rs | 38 +- halo2_backend/src/poly/ipa/commitment.rs | 46 ++- .../src/poly/ipa/commitment/prover.rs | 18 +- halo2_backend/src/poly/ipa/msm.rs | 30 +- .../src/poly/ipa/multiopen/prover.rs | 19 +- halo2_backend/src/poly/ipa/strategy.rs | 13 +- halo2_backend/src/poly/kzg/commitment.rs | 29 +- halo2_backend/src/poly/kzg/msm.rs | 20 +- .../src/poly/kzg/multiopen/gwc/prover.rs | 6 +- .../src/poly/kzg/multiopen/shplonk/prover.rs | 14 +- halo2_backend/src/poly/kzg/strategy.rs | 9 +- halo2_backend/src/poly/multiopen_test.rs | 23 +- halo2_middleware/Cargo.toml | 4 +- halo2_middleware/src/lib.rs | 1 + halo2_middleware/src/zal.rs | 350 ++++++++++++++++++ halo2_proofs/benches/arithmetic.rs | 7 +- halo2_proofs/src/lib.rs | 4 +- halo2_proofs/src/plonk.rs | 2 +- halo2_proofs/src/plonk/prover.rs | 41 +- halo2_proofs/tests/frontend_backend_split.rs | 31 +- halo2_proofs/tests/plonk_api.rs | 36 +- 31 files changed, 821 insertions(+), 388 deletions(-) create mode 100644 halo2_middleware/src/zal.rs diff --git a/halo2_backend/src/arithmetic.rs b/halo2_backend/src/arithmetic.rs index 86ed7474de..1706bafa67 100644 --- a/halo2_backend/src/arithmetic.rs +++ b/halo2_backend/src/arithmetic.rs @@ -3,202 +3,13 @@ use group::{ ff::{BatchInvert, PrimeField}, - Curve, Group, GroupOpsOwned, ScalarMulOwned, + Curve, }; use halo2_common::multicore; pub use halo2_middleware::ff::Field; - -use halo2curves::msm::multiexp_serial; +pub use halo2curves::{fft::best_fft, msm::best_multiexp}; pub use halo2curves::{CurveAffine, CurveExt}; -/// This represents an element of a group with basic operations that can be -/// performed. This allows an FFT implementation (for example) to operate -/// generically over either a field or elliptic curve group. -pub trait FftGroup: - Copy + Send + Sync + 'static + GroupOpsOwned + ScalarMulOwned -{ -} - -impl FftGroup for T -where - Scalar: Field, - T: Copy + Send + Sync + 'static + GroupOpsOwned + ScalarMulOwned, -{ -} - -/// Performs a small multi-exponentiation operation. -/// Uses the double-and-add algorithm with doublings shared across points. -pub fn small_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { - let coeffs: Vec<_> = coeffs.iter().map(|a| a.to_repr()).collect(); - let mut acc = C::Curve::identity(); - - // for byte idx - for byte_idx in (0..((C::Scalar::NUM_BITS as usize + 7) / 8)).rev() { - // for bit idx - for bit_idx in (0..8).rev() { - acc = acc.double(); - // for each coeff - for coeff_idx in 0..coeffs.len() { - let byte = coeffs[coeff_idx].as_ref()[byte_idx]; - if ((byte >> bit_idx) & 1) != 0 { - acc += bases[coeff_idx]; - } - } - } - } - - acc -} - -/// Performs a multi-exponentiation operation. -/// -/// This function will panic if coeffs and bases have a different length. -/// -/// This will use multithreading if beneficial. -pub fn best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { - assert_eq!(coeffs.len(), bases.len()); - - let num_threads = multicore::current_num_threads(); - if coeffs.len() > num_threads { - let chunk = coeffs.len() / num_threads; - let num_chunks = coeffs.chunks(chunk).len(); - let mut results = vec![C::Curve::identity(); num_chunks]; - multicore::scope(|scope| { - let chunk = coeffs.len() / num_threads; - - for ((coeffs, bases), acc) in coeffs - .chunks(chunk) - .zip(bases.chunks(chunk)) - .zip(results.iter_mut()) - { - scope.spawn(move |_| { - multiexp_serial(coeffs, bases, acc); - }); - } - }); - results.iter().fold(C::Curve::identity(), |a, b| a + b) - } else { - let mut acc = C::Curve::identity(); - multiexp_serial(coeffs, bases, &mut acc); - acc - } -} - -/// Performs a radix-$2$ Fast-Fourier Transformation (FFT) on a vector of size -/// $n = 2^k$, when provided `log_n` = $k$ and an element of multiplicative -/// order $n$ called `omega` ($\omega$). The result is that the vector `a`, when -/// interpreted as the coefficients of a polynomial of degree $n - 1$, is -/// transformed into the evaluations of this polynomial at each of the $n$ -/// distinct powers of $\omega$. This transformation is invertible by providing -/// $\omega^{-1}$ in place of $\omega$ and dividing each resulting field element -/// by $n$. -/// -/// This will use multithreading if beneficial. -pub fn best_fft>(a: &mut [G], omega: Scalar, log_n: u32) { - fn bitreverse(mut n: usize, l: usize) -> usize { - let mut r = 0; - for _ in 0..l { - r = (r << 1) | (n & 1); - n >>= 1; - } - r - } - - let threads = multicore::current_num_threads(); - let log_threads = log2_floor(threads); - let n = a.len(); - assert_eq!(n, 1 << log_n); - - for k in 0..n { - let rk = bitreverse(k, log_n as usize); - if k < rk { - a.swap(rk, k); - } - } - - // precompute twiddle factors - let twiddles: Vec<_> = (0..(n / 2)) - .scan(Scalar::ONE, |w, _| { - let tw = *w; - *w *= ω - Some(tw) - }) - .collect(); - - if log_n <= log_threads { - let mut chunk = 2_usize; - let mut twiddle_chunk = n / 2; - for _ in 0..log_n { - a.chunks_mut(chunk).for_each(|coeffs| { - let (left, right) = coeffs.split_at_mut(chunk / 2); - - // case when twiddle factor is one - let (a, left) = left.split_at_mut(1); - let (b, right) = right.split_at_mut(1); - let t = b[0]; - b[0] = a[0]; - a[0] += &t; - b[0] -= &t; - - left.iter_mut() - .zip(right.iter_mut()) - .enumerate() - .for_each(|(i, (a, b))| { - let mut t = *b; - t *= &twiddles[(i + 1) * twiddle_chunk]; - *b = *a; - *a += &t; - *b -= &t; - }); - }); - chunk *= 2; - twiddle_chunk /= 2; - } - } else { - recursive_butterfly_arithmetic(a, n, 1, &twiddles) - } -} - -/// This perform recursive butterfly arithmetic -pub fn recursive_butterfly_arithmetic>( - a: &mut [G], - n: usize, - twiddle_chunk: usize, - twiddles: &[Scalar], -) { - if n == 2 { - let t = a[1]; - a[1] = a[0]; - a[0] += &t; - a[1] -= &t; - } else { - let (left, right) = a.split_at_mut(n / 2); - multicore::join( - || recursive_butterfly_arithmetic(left, n / 2, twiddle_chunk * 2, twiddles), - || recursive_butterfly_arithmetic(right, n / 2, twiddle_chunk * 2, twiddles), - ); - - // case when twiddle factor is one - let (a, left) = left.split_at_mut(1); - let (b, right) = right.split_at_mut(1); - let t = b[0]; - b[0] = a[0]; - a[0] += &t; - b[0] -= &t; - - left.iter_mut() - .zip(right.iter_mut()) - .enumerate() - .for_each(|(i, (a, b))| { - let mut t = *b; - t *= &twiddles[(i + 1) * twiddle_chunk]; - *b = *a; - *a += &t; - *b -= &t; - }); - } -} - /// Convert coefficient bases group elements to lagrange basis by inverse FFT. pub fn g_to_lagrange(g_projective: Vec, k: u32) -> Vec { let n_inv = C::Scalar::TWO_INV.pow_vartime([k as u64, 0, 0, 0]); @@ -344,18 +155,6 @@ pub fn parallelize(v: &mu }); } -fn log2_floor(num: usize) -> u32 { - assert!(num > 0); - - let mut pow = 0; - - while (1 << (pow + 1)) <= num { - pow += 1; - } - - pow -} - /// Returns coefficients of an n - 1 degree polynomial given a set of n points /// and their evaluations. This function will panic if two values in `points` /// are the same. diff --git a/halo2_backend/src/plonk/keygen.rs b/halo2_backend/src/plonk/keygen.rs index 7d3bf8c1b4..df1710eb7f 100644 --- a/halo2_backend/src/plonk/keygen.rs +++ b/halo2_backend/src/plonk/keygen.rs @@ -2,6 +2,7 @@ use group::Curve; use halo2_middleware::ff::{Field, FromUniformBytes}; +use halo2_middleware::zal::impls::H2cEngine; use super::{evaluation::Evaluator, permutation, Polynomial, ProvingKey, VerifyingKey}; use crate::{ @@ -72,6 +73,7 @@ where .map(|poly| { params .commit_lagrange( + &H2cEngine::new(), &Polynomial::new_lagrange_from_vec(poly.clone()), Blind::default(), ) diff --git a/halo2_backend/src/plonk/lookup/prover.rs b/halo2_backend/src/plonk/lookup/prover.rs index f95054608f..7df0ae1812 100644 --- a/halo2_backend/src/plonk/lookup/prover.rs +++ b/halo2_backend/src/plonk/lookup/prover.rs @@ -18,6 +18,7 @@ use halo2_common::plonk::{ }; use halo2_middleware::ff::WithSmallOrderMulGroup; use halo2_middleware::poly::Rotation; +use halo2_middleware::zal::{impls::PlonkEngine, traits::MsmAccel}; use rand_core::RngCore; use std::{ collections::BTreeMap, @@ -70,7 +71,9 @@ pub(in crate::plonk) fn lookup_commit_permuted< E: EncodedChallenge, R: RngCore, T: TranscriptWrite, + M: MsmAccel, >( + engine: &PlonkEngine, arg: &Argument, pk: &ProvingKey, params: &P, @@ -128,7 +131,9 @@ where let mut commit_values = |values: &Polynomial| { let poly = pk.vk.domain.lagrange_to_coeff(values.clone()); let blind = Blind(C::Scalar::random(&mut rng)); - let commitment = params.commit_lagrange(values, blind).to_affine(); + let commitment = params + .commit_lagrange(&engine.msm_backend, values, blind) + .to_affine(); (poly, blind, commitment) }; @@ -164,14 +169,17 @@ impl Permuted { /// grand product polynomial over the lookup. The grand product polynomial /// is used to populate the Product struct. The Product struct is /// added to the Lookup and finally returned by the method. + #[allow(clippy::too_many_arguments)] pub(in crate::plonk) fn commit_product< 'params, P: Params<'params, C>, E: EncodedChallenge, R: RngCore, T: TranscriptWrite, + M: MsmAccel, >( self, + engine: &PlonkEngine, pk: &ProvingKey, params: &P, beta: ChallengeBeta, @@ -288,7 +296,9 @@ impl Permuted { } let product_blind = Blind(C::Scalar::random(rng)); - let product_commitment = params.commit_lagrange(&z, product_blind).to_affine(); + let product_commitment = params + .commit_lagrange(&engine.msm_backend, &z, product_blind) + .to_affine(); let z = pk.vk.domain.lagrange_to_coeff(z); // Hash product commitment diff --git a/halo2_backend/src/plonk/permutation/keygen.rs b/halo2_backend/src/plonk/permutation/keygen.rs index a4a6ee6cdc..7c295ef445 100644 --- a/halo2_backend/src/plonk/permutation/keygen.rs +++ b/halo2_backend/src/plonk/permutation/keygen.rs @@ -1,5 +1,6 @@ use group::Curve; use halo2_middleware::ff::{Field, PrimeField}; +use halo2_middleware::zal::impls::H2cEngine; use super::{Argument, ProvingKey, VerifyingKey}; use crate::{ @@ -477,7 +478,7 @@ pub(crate) fn build_vk<'params, C: CurveAffine, P: Params<'params, C>>( // Compute commitment to permutation polynomial commitments.push( params - .commit_lagrange(permutation, Blind::default()) + .commit_lagrange(&H2cEngine::new(), permutation, Blind::default()) .to_affine(), ); } diff --git a/halo2_backend/src/plonk/permutation/prover.rs b/halo2_backend/src/plonk/permutation/prover.rs index b4ccc1e35a..4976984e62 100644 --- a/halo2_backend/src/plonk/permutation/prover.rs +++ b/halo2_backend/src/plonk/permutation/prover.rs @@ -2,7 +2,8 @@ use group::{ ff::{BatchInvert, Field}, Curve, }; -use halo2_middleware::ff::PrimeField; +use halo2_middleware::zal::traits::MsmAccel; +use halo2_middleware::{ff::PrimeField, zal::impls::PlonkEngine}; use rand_core::RngCore; use std::iter::{self, ExactSizeIterator}; @@ -54,7 +55,9 @@ pub(in crate::plonk) fn permutation_commit< E: EncodedChallenge, R: RngCore, T: TranscriptWrite, + M: MsmAccel, >( + engine: &PlonkEngine, arg: &Argument, params: &P, pk: &plonk::ProvingKey, @@ -172,7 +175,8 @@ pub(in crate::plonk) fn permutation_commit< let blind = Blind(C::Scalar::random(&mut rng)); - let permutation_product_commitment_projective = params.commit_lagrange(&z, blind); + let permutation_product_commitment_projective = + params.commit_lagrange(&engine.msm_backend, &z, blind); let permutation_product_blind = blind; let z = domain.lagrange_to_coeff(z); let permutation_product_poly = z.clone(); diff --git a/halo2_backend/src/plonk/prover.rs b/halo2_backend/src/plonk/prover.rs index 82ad9cdc47..25af20c632 100644 --- a/halo2_backend/src/plonk/prover.rs +++ b/halo2_backend/src/plonk/prover.rs @@ -1,5 +1,9 @@ use group::Curve; use halo2_middleware::ff::{Field, FromUniformBytes, WithSmallOrderMulGroup}; +use halo2_middleware::zal::{ + impls::{H2cEngine, PlonkEngine, PlonkEngineConfig}, + traits::MsmAccel, +}; use rand_core::RngCore; use std::collections::{BTreeSet, HashSet}; use std::{collections::HashMap, iter}; @@ -45,7 +49,8 @@ pub struct ProverV2Single< E: EncodedChallenge, R: RngCore, T: TranscriptWrite, ->(ProverV2<'a, 'params, Scheme, P, E, R, T>); + M: MsmAccel, +>(ProverV2<'a, 'params, Scheme, P, E, R, T, M>); impl< 'a, @@ -55,10 +60,12 @@ impl< E: EncodedChallenge, R: RngCore, T: TranscriptWrite, - > ProverV2Single<'a, 'params, Scheme, P, E, R, T> + M: MsmAccel, + > ProverV2Single<'a, 'params, Scheme, P, E, R, T, M> { /// Create a new prover object - pub fn new( + pub fn new_with_engine( + engine: PlonkEngine, params: &'params Scheme::ParamsProver, pk: &'a ProvingKey, // TODO: If this was a vector the usage would be simpler @@ -70,7 +77,8 @@ impl< where Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, { - Ok(Self(ProverV2::new( + Ok(Self(ProverV2::new_with_engine( + engine, params, pk, &[instance], @@ -79,6 +87,22 @@ impl< )?)) } + pub fn new( + params: &'params Scheme::ParamsProver, + pk: &'a ProvingKey, + // TODO: If this was a vector the usage would be simpler + // https://github.com/privacy-scaling-explorations/halo2/issues/265 + instance: &[&[Scheme::Scalar]], + rng: R, + transcript: &'a mut T, + ) -> Result, Error> + where + Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + { + let engine = PlonkEngineConfig::build_default(); + ProverV2Single::new_with_engine(engine, params, pk, instance, rng, transcript) + } + /// Commit the `witness` at `phase` and return the challenges after `phase`. pub fn commit_phase( &mut self, @@ -111,7 +135,9 @@ pub struct ProverV2< E: EncodedChallenge, R: RngCore, T: TranscriptWrite, + M: MsmAccel, > { + engine: PlonkEngine, // Circuit and setup fields params: &'params Scheme::ParamsProver, pk: &'a ProvingKey, @@ -135,10 +161,12 @@ impl< E: EncodedChallenge, R: RngCore, T: TranscriptWrite, - > ProverV2<'a, 'params, Scheme, P, E, R, T> + M: MsmAccel, + > ProverV2<'a, 'params, Scheme, P, E, R, T, M> { /// Create a new prover object - pub fn new( + pub fn new_with_engine( + engine: PlonkEngine, params: &'params Scheme::ParamsProver, pk: &'a ProvingKey, // TODO: If this was a vector the usage would be simpler. @@ -187,7 +215,9 @@ impl< if P::QUERY_INSTANCE { let instance_commitments_projective: Vec<_> = instance_values .iter() - .map(|poly| params.commit_lagrange(poly, Blind::default())) + .map(|poly| { + params.commit_lagrange(&engine.msm_backend, poly, Blind::default()) + }) .collect(); let mut instance_commitments = vec![Scheme::Curve::identity(); instance_commitments_projective.len()]; @@ -235,6 +265,7 @@ impl< let challenges = HashMap::::with_capacity(meta.num_challenges); Ok(ProverV2 { + engine, params, pk, phases, @@ -332,65 +363,66 @@ impl< } } - let mut commit_phase_fn = - |advice: &mut AdviceSingle, - witness: Vec>>| - -> Result<(), Error> { - let unusable_rows_start = params.n() as usize - (meta.blinding_factors() + 1); - let mut advice_values: Vec<_> = witness.into_iter().flatten().collect(); - let unblinded_advice: HashSet = - HashSet::from_iter(meta.unblinded_advice_columns.clone()); - - // Add blinding factors to advice columns - for (column_index, advice_values) in column_indices.iter().zip(&mut advice_values) { - if !unblinded_advice.contains(column_index) { - for cell in &mut advice_values[unusable_rows_start..] { - *cell = Scheme::Scalar::random(&mut rng); - } - } else { - #[cfg(feature = "sanity-checks")] - for cell in &advice_values[unusable_rows_start..] { - assert_eq!(*cell, Scheme::Scalar::ZERO); - } + let mut commit_phase_fn = |advice: &mut AdviceSingle, + witness: Vec< + Option>, + >| + -> Result<(), Error> { + let unusable_rows_start = params.n() as usize - (meta.blinding_factors() + 1); + let mut advice_values: Vec<_> = witness.into_iter().flatten().collect(); + let unblinded_advice: HashSet = + HashSet::from_iter(meta.unblinded_advice_columns.clone()); + + // Add blinding factors to advice columns + for (column_index, advice_values) in column_indices.iter().zip(&mut advice_values) { + if !unblinded_advice.contains(column_index) { + for cell in &mut advice_values[unusable_rows_start..] { + *cell = Scheme::Scalar::random(&mut rng); + } + } else { + #[cfg(feature = "sanity-checks")] + for cell in &advice_values[unusable_rows_start..] { + assert_eq!(*cell, Scheme::Scalar::ZERO); } } + } - // Compute commitments to advice column polynomials - let blinds: Vec<_> = column_indices - .iter() - .map(|i| { - if unblinded_advice.contains(i) { - Blind::default() - } else { - Blind(Scheme::Scalar::random(&mut rng)) - } - }) - .collect(); - let advice_commitments_projective: Vec<_> = advice_values - .iter() - .zip(blinds.iter()) - .map(|(poly, blind)| params.commit_lagrange(poly, *blind)) - .collect(); - let mut advice_commitments = - vec![Scheme::Curve::identity(); advice_commitments_projective.len()]; - ::CurveExt::batch_normalize( - &advice_commitments_projective, - &mut advice_commitments, - ); - let advice_commitments = advice_commitments; - drop(advice_commitments_projective); - - for commitment in &advice_commitments { - self.transcript.write_point(*commitment)?; - } - for ((column_index, advice_values), blind) in - column_indices.iter().zip(advice_values).zip(blinds) - { - advice.advice_polys[*column_index] = advice_values; - advice.advice_blinds[*column_index] = blind; - } - Ok(()) - }; + // Compute commitments to advice column polynomials + let blinds: Vec<_> = column_indices + .iter() + .map(|i| { + if unblinded_advice.contains(i) { + Blind::default() + } else { + Blind(Scheme::Scalar::random(&mut rng)) + } + }) + .collect(); + let advice_commitments_projective: Vec<_> = advice_values + .iter() + .zip(blinds.iter()) + .map(|(poly, blind)| params.commit_lagrange(&self.engine.msm_backend, poly, *blind)) + .collect(); + let mut advice_commitments = + vec![Scheme::Curve::identity(); advice_commitments_projective.len()]; + ::CurveExt::batch_normalize( + &advice_commitments_projective, + &mut advice_commitments, + ); + let advice_commitments = advice_commitments; + drop(advice_commitments_projective); + + for commitment in &advice_commitments { + self.transcript.write_point(*commitment)?; + } + for ((column_index, advice_values), blind) in + column_indices.iter().zip(advice_values).zip(blinds) + { + advice.advice_polys[*column_index] = advice_values; + advice.advice_blinds[*column_index] = blind; + } + Ok(()) + }; for (witness, advice) in witness.into_iter().zip(advice.iter_mut()) { commit_phase_fn( @@ -447,6 +479,7 @@ impl< .iter() .map(|lookup| { lookup_commit_permuted( + &self.engine, lookup, pk, params, @@ -483,6 +516,7 @@ impl< .zip(advice.iter()) .map(|(instance, advice)| { permutation_commit( + &self.engine, &meta.permutation, params, pk, @@ -505,7 +539,15 @@ impl< lookups .into_iter() .map(|lookup| { - lookup.commit_product(pk, params, beta, gamma, &mut rng, self.transcript) + lookup.commit_product( + &self.engine, + pk, + params, + beta, + gamma, + &mut rng, + self.transcript, + ) }) .collect::, _>>() }) @@ -520,6 +562,7 @@ impl< .iter() .map(|shuffle| { shuffle_commit_product( + &self.engine, shuffle, pk, params, @@ -539,7 +582,13 @@ impl< .collect::, _>>()?; // Commit to the vanishing argument's random polynomial for blinding h(x_3) - let vanishing = vanishing::Argument::commit(params, domain, &mut rng, self.transcript)?; + let vanishing = vanishing::Argument::commit( + &self.engine.msm_backend, + params, + domain, + &mut rng, + self.transcript, + )?; // Obtain challenge for keeping all separate gates linearly independent let y: ChallengeY<_> = self.transcript.squeeze_challenge_scalar(); @@ -585,7 +634,14 @@ impl< ); // Construct the vanishing argument's h(X) commitments - let vanishing = vanishing.construct(params, domain, h_poly, &mut rng, self.transcript)?; + let vanishing = vanishing.construct( + &self.engine, + params, + domain, + h_poly, + &mut rng, + self.transcript, + )?; let x: ChallengeX<_> = self.transcript.squeeze_challenge_scalar(); let xn = x.pow([params.n()]); @@ -725,9 +781,26 @@ impl< let prover = P::new(params); prover - .create_proof(rng, self.transcript, instances) + .create_proof_with_engine(&self.engine.msm_backend, rng, self.transcript, instances) .map_err(|_| Error::ConstraintSystemFailure)?; Ok(()) } + + /// Create a new prover object + pub fn new( + params: &'params Scheme::ParamsProver, + pk: &'a ProvingKey, + // TODO: If this was a vector the usage would be simpler. + // https://github.com/privacy-scaling-explorations/halo2/issues/265 + instances: &[&[&[Scheme::Scalar]]], + rng: R, + transcript: &'a mut T, + ) -> Result, Error> + where + Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + { + let engine = PlonkEngineConfig::build_default(); + ProverV2::new_with_engine(engine, params, pk, instances, rng, transcript) + } } diff --git a/halo2_backend/src/plonk/shuffle/prover.rs b/halo2_backend/src/plonk/shuffle/prover.rs index 31dc9714bb..7a119c06fd 100644 --- a/halo2_backend/src/plonk/shuffle/prover.rs +++ b/halo2_backend/src/plonk/shuffle/prover.rs @@ -13,6 +13,7 @@ use group::{ff::BatchInvert, Curve}; use halo2_common::plonk::{ChallengeGamma, ChallengeTheta, ChallengeX, Error, Expression}; use halo2_middleware::ff::WithSmallOrderMulGroup; use halo2_middleware::poly::Rotation; +use halo2_middleware::zal::{impls::PlonkEngine, traits::MsmAccel}; use rand_core::RngCore; use std::{ iter, @@ -102,7 +103,9 @@ pub(in crate::plonk) fn shuffle_commit_product< E: EncodedChallenge, R: RngCore, T: TranscriptWrite, + M: MsmAccel, >( + engine: &PlonkEngine, arg: &Argument, pk: &ProvingKey, params: &P, @@ -188,7 +191,9 @@ where } let product_blind = Blind(C::Scalar::random(rng)); - let product_commitment = params.commit_lagrange(&z, product_blind).to_affine(); + let product_commitment = params + .commit_lagrange(&engine.msm_backend, &z, product_blind) + .to_affine(); let z = pk.vk.domain.lagrange_to_coeff(z); // Hash product commitment diff --git a/halo2_backend/src/plonk/vanishing/prover.rs b/halo2_backend/src/plonk/vanishing/prover.rs index 942441aefa..6a6bf51256 100644 --- a/halo2_backend/src/plonk/vanishing/prover.rs +++ b/halo2_backend/src/plonk/vanishing/prover.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, iter}; use group::Curve; use halo2_common::plonk::{ChallengeX, Error}; use halo2_middleware::ff::Field; +use halo2_middleware::zal::{impls::PlonkEngine, traits::MsmAccel}; use rand_chacha::ChaCha20Rng; use rand_core::{RngCore, SeedableRng}; @@ -42,6 +43,7 @@ impl Argument { R: RngCore, T: TranscriptWrite, >( + engine: &impl MsmAccel, params: &P, domain: &EvaluationDomain, mut rng: R, @@ -83,7 +85,9 @@ impl Argument { let random_blind = Blind(C::Scalar::random(rng)); // Commit - let c = params.commit(&random_poly, random_blind).to_affine(); + let c = params + .commit(engine, &random_poly, random_blind) + .to_affine(); transcript.write_point(c)?; Ok(Committed { @@ -100,8 +104,10 @@ impl Committed { E: EncodedChallenge, R: RngCore, T: TranscriptWrite, + M: MsmAccel, >( self, + engine: &PlonkEngine, params: &P, domain: &EvaluationDomain, h_poly: Polynomial, @@ -129,7 +135,7 @@ impl Committed { let h_commitments_projective: Vec<_> = h_pieces .iter() .zip(h_blinds.iter()) - .map(|(h_piece, blind)| params.commit(h_piece, *blind)) + .map(|(h_piece, blind)| params.commit(&engine.msm_backend, h_piece, *blind)) .collect(); let mut h_commitments = vec![C::identity(); h_commitments_projective.len()]; C::Curve::batch_normalize(&h_commitments_projective, &mut h_commitments); diff --git a/halo2_backend/src/plonk/verifier.rs b/halo2_backend/src/plonk/verifier.rs index c8e81f43e2..cd10535571 100644 --- a/halo2_backend/src/plonk/verifier.rs +++ b/halo2_backend/src/plonk/verifier.rs @@ -3,6 +3,7 @@ use halo2_common::plonk::{ ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error, }; use halo2_middleware::ff::{Field, FromUniformBytes, WithSmallOrderMulGroup}; +use halo2_middleware::zal::impls::H2cEngine; use std::iter; use super::{vanishing, VerifyingKey}; @@ -63,6 +64,9 @@ pub fn verify_proof< where Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, { + // ZAL: Verification is (supposedly) cheap, hence we don't use an accelerator engine + let default_engine = H2cEngine::new(); + // Check that instances matches the expected number of instance columns for instances in instances.iter() { if instances.len() != vk.cs.num_instance_columns { @@ -84,7 +88,9 @@ where poly.resize(params.n() as usize, Scheme::Scalar::ZERO); let poly = vk.domain.lagrange_from_vec(poly); - Ok(params.commit_lagrange(&poly, Blind::default()).to_affine()) + Ok(params + .commit_lagrange(&default_engine, &poly, Blind::default()) + .to_affine()) }) .collect::, _>>() }) diff --git a/halo2_backend/src/plonk/verifier/batch.rs b/halo2_backend/src/plonk/verifier/batch.rs index 883e3605c8..f7d5ba6341 100644 --- a/halo2_backend/src/plonk/verifier/batch.rs +++ b/halo2_backend/src/plonk/verifier/batch.rs @@ -1,6 +1,7 @@ use group::ff::Field; use halo2_common::plonk::Error; use halo2_middleware::ff::FromUniformBytes; +use halo2_middleware::zal::impls::H2cEngine; use halo2curves::CurveAffine; use rand_core::OsRng; @@ -129,7 +130,8 @@ where ); match final_msm { - Ok(msm) => msm.check(), + // ZAL: Verification is (supposedly) cheap, hence we don't use an accelerator engine + Ok(msm) => msm.check(&H2cEngine::new()), Err(_) => false, } } diff --git a/halo2_backend/src/poly/commitment.rs b/halo2_backend/src/poly/commitment.rs index e0266a9154..7f0c2f0c16 100644 --- a/halo2_backend/src/poly/commitment.rs +++ b/halo2_backend/src/poly/commitment.rs @@ -6,6 +6,7 @@ use super::{ use crate::poly::Error; use crate::transcript::{EncodedChallenge, TranscriptRead, TranscriptWrite}; use halo2_middleware::ff::Field; +use halo2_middleware::zal::{impls::PlonkEngineConfig, traits::MsmAccel}; use halo2curves::CurveAffine; use rand_core::RngCore; use std::{ @@ -62,6 +63,7 @@ pub trait Params<'params, C: CurveAffine>: Sized + Clone + Debug { /// `r`. fn commit_lagrange( &self, + engine: &impl MsmAccel, poly: &Polynomial, r: Blind, ) -> C::CurveExt; @@ -84,8 +86,12 @@ pub trait ParamsProver<'params, C: CurveAffine>: Params<'params, C> { /// This computes a commitment to a polynomial described by the provided /// slice of coefficients. The commitment may be blinded by the blinding /// factor `r`. - fn commit(&self, poly: &Polynomial, r: Blind) - -> C::CurveExt; + fn commit( + &self, + engine: &impl MsmAccel, + poly: &Polynomial, + r: Blind, + ) -> C::CurveExt; /// Getter for g generators fn get_g(&self) -> &[C]; @@ -111,10 +117,10 @@ pub trait MSM: Clone + Debug + Send + Sync { fn scale(&mut self, factor: C::Scalar); /// Perform multiexp and check that it results in zero - fn check(&self) -> bool; + fn check(&self, engine: &impl MsmAccel) -> bool; /// Perform multiexp and return the result - fn eval(&self) -> C::CurveExt; + fn eval(&self, engine: &impl MsmAccel) -> C::CurveExt; /// Return base points fn bases(&self) -> Vec; @@ -132,7 +138,7 @@ pub trait Prover<'params, Scheme: CommitmentScheme> { fn new(params: &'params Scheme::ParamsProver) -> Self; /// Create a multi-opening proof - fn create_proof< + fn create_proof_with_engine< 'com, E: EncodedChallenge, T: TranscriptWrite, @@ -140,6 +146,7 @@ pub trait Prover<'params, Scheme: CommitmentScheme> { I, >( &self, + engine: &impl MsmAccel, rng: R, transcript: &mut T, queries: I, @@ -147,6 +154,27 @@ pub trait Prover<'params, Scheme: CommitmentScheme> { where I: IntoIterator> + Clone, R: RngCore; + + /// Create a multi-opening proof + fn create_proof< + 'com, + E: EncodedChallenge, + T: TranscriptWrite, + R, + I, + >( + &self, + rng: R, + transcript: &mut T, + queries: I, + ) -> io::Result<()> + where + I: IntoIterator> + Clone, + R: RngCore, + { + let engine = PlonkEngineConfig::build_default::(); + self.create_proof_with_engine(&engine.msm_backend, rng, transcript, queries) + } } /// Common multi-open verifier interface for various commitment schemes diff --git a/halo2_backend/src/poly/ipa/commitment.rs b/halo2_backend/src/poly/ipa/commitment.rs index 388adfa0a4..d5b1430ddd 100644 --- a/halo2_backend/src/poly/ipa/commitment.rs +++ b/halo2_backend/src/poly/ipa/commitment.rs @@ -3,19 +3,20 @@ //! //! [halo]: https://eprint.iacr.org/2019/1021 -use crate::arithmetic::{best_multiexp, g_to_lagrange, parallelize, CurveAffine, CurveExt}; +use crate::arithmetic::{g_to_lagrange, parallelize, CurveAffine, CurveExt}; use crate::helpers::CurveRead; use crate::poly::commitment::{Blind, CommitmentScheme, Params, ParamsProver, ParamsVerifier}; use crate::poly::ipa::msm::MSMIPA; use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; use group::{Curve, Group}; +use halo2_middleware::zal::traits::MsmAccel; use std::marker::PhantomData; mod prover; mod verifier; -pub use prover::create_proof; +pub use prover::create_proof_with_engine; pub use verifier::verify_proof; use std::io; @@ -53,7 +54,7 @@ impl CommitmentScheme for IPACommitmentScheme { } } -/// Verifier parameters +/// ZAL: Verifier parameters pub type ParamsVerifierIPA = ParamsIPA; impl<'params, C: CurveAffine> ParamsVerifier<'params, C> for ParamsIPA {} @@ -87,6 +88,7 @@ impl<'params, C: CurveAffine> Params<'params, C> for ParamsIPA { /// `r`. fn commit_lagrange( &self, + engine: &impl MsmAccel, poly: &Polynomial, r: Blind, ) -> C::Curve { @@ -99,7 +101,7 @@ impl<'params, C: CurveAffine> Params<'params, C> for ParamsIPA { tmp_bases.extend(self.g_lagrange.iter()); tmp_bases.push(self.w); - best_multiexp::(&tmp_scalars, &tmp_bases) + engine.msm(&tmp_scalars, &tmp_bases) } /// Writes params to a buffer. @@ -209,7 +211,12 @@ impl<'params, C: CurveAffine> ParamsProver<'params, C> for ParamsIPA { /// This computes a commitment to a polynomial described by the provided /// slice of coefficients. The commitment will be blinded by the blinding /// factor `r`. - fn commit(&self, poly: &Polynomial, r: Blind) -> C::Curve { + fn commit( + &self, + engine: &impl MsmAccel, + poly: &Polynomial, + r: Blind, + ) -> C::Curve { let mut tmp_scalars = Vec::with_capacity(poly.len() + 1); let mut tmp_bases = Vec::with_capacity(poly.len() + 1); @@ -219,7 +226,7 @@ impl<'params, C: CurveAffine> ParamsProver<'params, C> for ParamsIPA { tmp_bases.extend(self.g.iter()); tmp_bases.push(self.w); - best_multiexp::(&tmp_scalars, &tmp_bases) + engine.msm(&tmp_scalars, &tmp_bases) } fn get_g(&self) -> &[C] { @@ -231,11 +238,12 @@ impl<'params, C: CurveAffine> ParamsProver<'params, C> for ParamsIPA { mod test { use crate::poly::commitment::ParamsProver; use crate::poly::commitment::{Blind, Params, MSM}; - use crate::poly::ipa::commitment::{create_proof, verify_proof, ParamsIPA}; + use crate::poly::ipa::commitment::{create_proof_with_engine, verify_proof, ParamsIPA}; use crate::poly::ipa::msm::MSMIPA; use group::Curve; use halo2_middleware::ff::Field; + use halo2_middleware::zal::impls::H2cEngine; #[test] fn test_commit_lagrange_epaffine() { @@ -246,6 +254,7 @@ mod test { use crate::poly::EvaluationDomain; use halo2curves::pasta::{EpAffine, Fq}; + let engine = H2cEngine::new(); let params = ParamsIPA::::new(K); let domain = EvaluationDomain::new(1, K); @@ -259,7 +268,10 @@ mod test { let alpha = Blind(Fq::random(OsRng)); - assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); + assert_eq!( + params.commit(&engine, &b, alpha), + params.commit_lagrange(&engine, &a, alpha) + ); } #[test] @@ -271,6 +283,7 @@ mod test { use crate::poly::EvaluationDomain; use halo2curves::pasta::{EqAffine, Fp}; + let engine = H2cEngine::new(); let params: ParamsIPA = ParamsIPA::::new(K); let domain = EvaluationDomain::new(1, K); @@ -284,7 +297,10 @@ mod test { let alpha = Blind(Fp::random(OsRng)); - assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); + assert_eq!( + params.commit(&engine, &b, alpha), + params.commit_lagrange(&engine, &a, alpha) + ); } #[test] @@ -307,6 +323,7 @@ mod test { let rng = OsRng; + let engine = H2cEngine::new(); let params = ParamsIPA::::new(K); let mut params_buffer = vec![]; as Params<_>>::write(¶ms, &mut params_buffer).unwrap(); @@ -322,7 +339,7 @@ mod test { let blind = Blind(Fq::random(rng)); - let p = params.commit(&px, blind).to_affine(); + let p = params.commit(&engine, &px, blind).to_affine(); let mut transcript = Blake2bWrite::, EpAffine, Challenge255>::init(vec![]); @@ -333,7 +350,8 @@ mod test { transcript.write_scalar(v).unwrap(); let (proof, ch_prover) = { - create_proof(¶ms, rng, &mut transcript, &px, blind, *x).unwrap(); + create_proof_with_engine(&engine, ¶ms, rng, &mut transcript, &px, blind, *x) + .unwrap(); let ch_prover = transcript.squeeze_challenge(); (transcript.finalize(), ch_prover) }; @@ -359,12 +377,12 @@ mod test { { // Test use_challenges() let msm_challenges = guard.clone().use_challenges(); - assert!(msm_challenges.check()); + assert!(msm_challenges.check(&engine)); // Test use_g() - let g = guard.compute_g(); + let g = guard.compute_g(&engine); let (msm_g, _accumulator) = guard.clone().use_g(g); - assert!(msm_g.check()); + assert!(msm_g.check(&engine)); } } } diff --git a/halo2_backend/src/poly/ipa/commitment/prover.rs b/halo2_backend/src/poly/ipa/commitment/prover.rs index 3a23cd152a..1f50046449 100644 --- a/halo2_backend/src/poly/ipa/commitment/prover.rs +++ b/halo2_backend/src/poly/ipa/commitment/prover.rs @@ -1,10 +1,9 @@ use halo2_middleware::ff::Field; +use halo2_middleware::zal::traits::MsmAccel; use rand_core::RngCore; use super::ParamsIPA; -use crate::arithmetic::{ - best_multiexp, compute_inner_product, eval_polynomial, parallelize, CurveAffine, -}; +use crate::arithmetic::{compute_inner_product, eval_polynomial, parallelize, CurveAffine}; use crate::poly::commitment::ParamsProver; use crate::poly::{commitment::Blind, Coeff, Polynomial}; @@ -26,12 +25,13 @@ use std::io::{self}; /// opening v, and the point x. It's probably also nice for the transcript /// to have seen the elliptic curve description and the URS, if you want to /// be rigorous. -pub fn create_proof< +pub fn create_proof_with_engine< C: CurveAffine, E: EncodedChallenge, R: RngCore, T: TranscriptWrite, >( + engine: &impl MsmAccel, params: &ParamsIPA, mut rng: R, transcript: &mut T, @@ -56,7 +56,7 @@ pub fn create_proof< let s_poly_blind = Blind(C::Scalar::random(&mut rng)); // Write a commitment to the random polynomial to the transcript - let s_poly_commitment = params.commit(&s_poly, s_poly_blind).to_affine(); + let s_poly_commitment = params.commit(engine, &s_poly, s_poly_blind).to_affine(); transcript.write_point(s_poly_commitment)?; // Challenge that will ensure that the prover cannot change P but can only @@ -106,14 +106,14 @@ pub fn create_proof< // // TODO: If we modify multiexp to take "extra" bases, we could speed // this piece up a bit by combining the multiexps. - let l_j = best_multiexp(&p_prime[half..], &g_prime[0..half]); - let r_j = best_multiexp(&p_prime[0..half], &g_prime[half..]); + let l_j = engine.msm(&p_prime[half..], &g_prime[0..half]); + let r_j = engine.msm(&p_prime[0..half], &g_prime[half..]); let value_l_j = compute_inner_product(&p_prime[half..], &b[0..half]); let value_r_j = compute_inner_product(&p_prime[0..half], &b[half..]); let l_j_randomness = C::Scalar::random(&mut rng); let r_j_randomness = C::Scalar::random(&mut rng); - let l_j = l_j + best_multiexp(&[value_l_j * z, l_j_randomness], &[params.u, params.w]); - let r_j = r_j + best_multiexp(&[value_r_j * z, r_j_randomness], &[params.u, params.w]); + let l_j = l_j + engine.msm(&[value_l_j * z, l_j_randomness], &[params.u, params.w]); + let r_j = r_j + engine.msm(&[value_r_j * z, r_j_randomness], &[params.u, params.w]); let l_j = l_j.to_affine(); let r_j = r_j.to_affine(); diff --git a/halo2_backend/src/poly/ipa/msm.rs b/halo2_backend/src/poly/ipa/msm.rs index 0154bdef31..b2869e9dd0 100644 --- a/halo2_backend/src/poly/ipa/msm.rs +++ b/halo2_backend/src/poly/ipa/msm.rs @@ -1,7 +1,8 @@ -use crate::arithmetic::{best_multiexp, CurveAffine}; +use crate::arithmetic::CurveAffine; use crate::poly::{commitment::MSM, ipa::commitment::ParamsVerifierIPA}; use group::Group; use halo2_middleware::ff::Field; +use halo2_middleware::zal::traits::MsmAccel; use std::collections::BTreeMap; /// A multiscalar multiplication in the polynomial commitment scheme @@ -130,11 +131,11 @@ impl<'a, C: CurveAffine> MSM for MSMIPA<'a, C> { self.u_scalar = self.u_scalar.map(|a| a * factor); } - fn check(&self) -> bool { - bool::from(self.eval().is_identity()) + fn check(&self, engine: &impl MsmAccel) -> bool { + bool::from(self.eval(engine).is_identity()) } - fn eval(&self) -> C::Curve { + fn eval(&self, engine: &impl MsmAccel) -> C::Curve { let len = self.g_scalars.as_ref().map(|v| v.len()).unwrap_or(0) + self.w_scalar.map(|_| 1).unwrap_or(0) + self.u_scalar.map(|_| 1).unwrap_or(0) @@ -165,8 +166,7 @@ impl<'a, C: CurveAffine> MSM for MSMIPA<'a, C> { } assert_eq!(scalars.len(), len); - - best_multiexp(&scalars, &bases) + engine.msm(&scalars, &bases) } fn bases(&self) -> Vec { @@ -222,6 +222,7 @@ mod tests { commitment::{ParamsProver, MSM}, ipa::{commitment::ParamsIPA, msm::MSMIPA}, }; + use halo2_middleware::zal::impls::H2cEngine; use halo2curves::{ pasta::{Ep, EpAffine, Fp, Fq}, CurveAffine, @@ -232,40 +233,41 @@ mod tests { let base: Ep = EpAffine::from_xy(-Fp::one(), Fp::from(2)).unwrap().into(); let base_viol = base + base; + let engine = H2cEngine::new(); let params = ParamsIPA::new(4); let mut a: MSMIPA = MSMIPA::new(¶ms); a.append_term(Fq::one(), base); // a = [1] P - assert!(!a.clone().check()); + assert!(!a.clone().check(&engine)); a.append_term(Fq::one(), base); // a = [1+1] P - assert!(!a.clone().check()); + assert!(!a.clone().check(&engine)); a.append_term(-Fq::one(), base_viol); // a = [1+1] P + [-1] 2P - assert!(a.clone().check()); + assert!(a.clone().check(&engine)); let b = a.clone(); // Append a point that is the negation of an existing one. a.append_term(Fq::from(4), -base); // a = [1+1-4] P + [-1] 2P - assert!(!a.clone().check()); + assert!(!a.clone().check(&engine)); a.append_term(Fq::from(2), base_viol); // a = [1+1-4] P + [-1+2] 2P - assert!(a.clone().check()); + assert!(a.clone().check(&engine)); // Add two MSMs with common bases. a.scale(Fq::from(3)); a.add_msm(&b); // a = [3*(1+1)+(1+1-4)] P + [3*(-1)+(-1+2)] 2P - assert!(a.clone().check()); + assert!(a.clone().check(&engine)); let mut c: MSMIPA = MSMIPA::new(¶ms); c.append_term(Fq::from(2), base); c.append_term(Fq::one(), -base_viol); // c = [2] P + [1] (-2P) - assert!(c.clone().check()); + assert!(c.clone().check(&engine)); // Add two MSMs with bases that differ only in sign. a.add_msm(&c); - assert!(a.check()); + assert!(a.check(&engine)); } } diff --git a/halo2_backend/src/poly/ipa/multiopen/prover.rs b/halo2_backend/src/poly/ipa/multiopen/prover.rs index 62934afe79..e16582b080 100644 --- a/halo2_backend/src/poly/ipa/multiopen/prover.rs +++ b/halo2_backend/src/poly/ipa/multiopen/prover.rs @@ -9,6 +9,7 @@ use crate::transcript::{EncodedChallenge, TranscriptWrite}; use group::Curve; use halo2_middleware::ff::Field; +use halo2_middleware::zal::traits::MsmAccel; use rand_core::RngCore; use std::io; use std::marker::PhantomData; @@ -27,8 +28,9 @@ impl<'params, C: CurveAffine> Prover<'params, IPACommitmentScheme> for Prover } /// Create a multi-opening proof - fn create_proof<'com, Z: EncodedChallenge, T: TranscriptWrite, R, I>( + fn create_proof_with_engine<'com, Z: EncodedChallenge, T: TranscriptWrite, R, I>( &self, + engine: &impl MsmAccel, mut rng: R, transcript: &mut T, queries: I, @@ -93,7 +95,10 @@ impl<'params, C: CurveAffine> Prover<'params, IPACommitmentScheme> for Prover .unwrap(); let q_prime_blind = Blind(C::Scalar::random(&mut rng)); - let q_prime_commitment = self.params.commit(&q_prime_poly, q_prime_blind).to_affine(); + let q_prime_commitment = self + .params + .commit(engine, &q_prime_poly, q_prime_blind) + .to_affine(); transcript.write_point(q_prime_commitment)?; @@ -117,6 +122,14 @@ impl<'params, C: CurveAffine> Prover<'params, IPACommitmentScheme> for Prover }, ); - commitment::create_proof(self.params, rng, transcript, &p_poly, p_poly_blind, *x_3) + commitment::create_proof_with_engine( + engine, + self.params, + rng, + transcript, + &p_poly, + p_poly_blind, + *x_3, + ) } } diff --git a/halo2_backend/src/poly/ipa/strategy.rs b/halo2_backend/src/poly/ipa/strategy.rs index 6900981f01..c74086377b 100644 --- a/halo2_backend/src/poly/ipa/strategy.rs +++ b/halo2_backend/src/poly/ipa/strategy.rs @@ -2,7 +2,6 @@ use super::commitment::{IPACommitmentScheme, ParamsIPA}; use super::msm::MSMIPA; use super::multiopen::VerifierIPA; use crate::{ - arithmetic::best_multiexp, plonk::Error, poly::{ commitment::MSM, @@ -11,6 +10,7 @@ use crate::{ }; use group::Curve; use halo2_middleware::ff::Field; +use halo2_middleware::zal::{impls::H2cEngine, traits::MsmAccel}; use halo2curves::CurveAffine; use rand_core::OsRng; @@ -64,10 +64,9 @@ impl<'params, C: CurveAffine> GuardIPA<'params, C> { } /// Computes G = ⟨s, params.g⟩ - pub fn compute_g(&self) -> C { + pub fn compute_g(&self, engine: &impl MsmAccel) -> C { let s = compute_s(&self.u, C::Scalar::ONE); - - best_multiexp(&s, &self.msm.params.g).to_affine() + engine.msm(&s, &self.msm.params.g).to_affine() } } @@ -107,7 +106,8 @@ impl<'params, C: CurveAffine> /// specific failing proofs, it must re-process the proofs separately. #[must_use] fn finalize(self) -> bool { - self.msm.check() + // TODO: Verification is cheap, ZkAccel on verifier is not a priority. + self.msm.check(&H2cEngine::new()) } } @@ -135,7 +135,8 @@ impl<'params, C: CurveAffine> ) -> Result { let guard = f(self.msm)?; let msm = guard.use_challenges(); - if msm.check() { + // ZAL: Verification is (supposedly) cheap, hence we don't use an accelerator engine + if msm.check(&H2cEngine::new()) { Ok(()) } else { Err(Error::ConstraintSystemFailure) diff --git a/halo2_backend/src/poly/kzg/commitment.rs b/halo2_backend/src/poly/kzg/commitment.rs index c9d2285a09..0a43558e71 100644 --- a/halo2_backend/src/poly/kzg/commitment.rs +++ b/halo2_backend/src/poly/kzg/commitment.rs @@ -1,4 +1,4 @@ -use crate::arithmetic::{best_multiexp, g_to_lagrange, parallelize}; +use crate::arithmetic::{g_to_lagrange, parallelize, CurveExt}; use crate::helpers::SerdeCurveAffine; use crate::poly::commitment::{Blind, CommitmentScheme, Params, ParamsProver, ParamsVerifier}; use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; @@ -6,8 +6,8 @@ use crate::SerdeFormat; use group::{prime::PrimeCurveAffine, Curve, Group}; use halo2_middleware::ff::{Field, PrimeField}; +use halo2_middleware::zal::traits::MsmAccel; use halo2curves::pairing::Engine; -use halo2curves::CurveExt; use rand_core::{OsRng, RngCore}; use std::fmt::Debug; use std::marker::PhantomData; @@ -302,13 +302,18 @@ where MSMKZG::new() } - fn commit_lagrange(&self, poly: &Polynomial, _: Blind) -> E::G1 { + fn commit_lagrange( + &self, + engine: &impl MsmAccel, + poly: &Polynomial, + _: Blind, + ) -> E::G1 { let mut scalars = Vec::with_capacity(poly.len()); scalars.extend(poly.iter()); let bases = &self.g_lagrange; let size = scalars.len(); assert!(bases.len() >= size); - best_multiexp(&scalars, &bases[0..size]) + engine.msm(&scalars, &bases[0..size]) } /// Writes params to a buffer. @@ -346,13 +351,18 @@ where Self::setup(k, OsRng) } - fn commit(&self, poly: &Polynomial, _: Blind) -> E::G1 { + fn commit( + &self, + engine: &impl MsmAccel, + poly: &Polynomial, + _: Blind, + ) -> E::G1 { let mut scalars = Vec::with_capacity(poly.len()); scalars.extend(poly.iter()); let bases = &self.g; let size = scalars.len(); assert!(bases.len() >= size); - best_multiexp(&scalars, &bases[0..size]) + engine.msm(&scalars, &bases[0..size]) } fn get_g(&self) -> &[E::G1Affine] { @@ -366,6 +376,7 @@ mod test { use crate::poly::commitment::{Blind, Params}; use crate::poly::kzg::commitment::ParamsKZG; use halo2_middleware::ff::Field; + use halo2_middleware::zal::impls::H2cEngine; #[test] fn test_commit_lagrange() { @@ -376,6 +387,7 @@ mod test { use crate::poly::EvaluationDomain; use halo2curves::bn256::{Bn256, Fr}; + let engine = H2cEngine::new(); let params = ParamsKZG::::new(K); let domain = EvaluationDomain::new(1, K); @@ -389,7 +401,10 @@ mod test { let alpha = Blind(Fr::random(OsRng)); - assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); + assert_eq!( + params.commit(&engine, &b, alpha), + params.commit_lagrange(&engine, &a, alpha) + ); } #[test] diff --git a/halo2_backend/src/poly/kzg/msm.rs b/halo2_backend/src/poly/kzg/msm.rs index 2c0fa4a6e3..82897c6e73 100644 --- a/halo2_backend/src/poly/kzg/msm.rs +++ b/halo2_backend/src/poly/kzg/msm.rs @@ -1,11 +1,9 @@ use std::fmt::Debug; use super::commitment::ParamsKZG; -use crate::{ - arithmetic::{best_multiexp, parallelize}, - poly::commitment::MSM, -}; +use crate::{arithmetic::parallelize, poly::commitment::MSM}; use group::{Curve, Group}; +use halo2_middleware::zal::traits::MsmAccel; use halo2curves::{ pairing::{Engine, MillerLoopResult, MultiMillerLoop}, CurveAffine, CurveExt, @@ -73,15 +71,15 @@ where } } - fn check(&self) -> bool { - bool::from(self.eval().is_identity()) + fn check(&self, engine: &impl MsmAccel) -> bool { + bool::from(self.eval(engine).is_identity()) } - fn eval(&self) -> E::G1 { + fn eval(&self, engine: &impl MsmAccel) -> E::G1 { use group::prime::PrimeCurveAffine; let mut bases = vec![E::G1Affine::identity(); self.scalars.len()]; E::G1::batch_normalize(&self.bases, &mut bases); - best_multiexp(&self.scalars, &bases) + engine.msm(&self.scalars, &bases) } fn bases(&self) -> Vec { @@ -187,12 +185,12 @@ where } /// Performs final pairing check with given verifier params and two channel linear combination - pub fn check(self) -> bool { + pub fn check(self, engine: &impl MsmAccel) -> bool { let s_g2_prepared = E::G2Prepared::from(self.params.s_g2); let n_g2_prepared = E::G2Prepared::from(-self.params.g2); - let left = self.left.eval(); - let right = self.right.eval(); + let left = self.left.eval(engine); + let right = self.right.eval(engine); let (term_1, term_2) = ( (&left.into(), &s_g2_prepared), diff --git a/halo2_backend/src/poly/kzg/multiopen/gwc/prover.rs b/halo2_backend/src/poly/kzg/multiopen/gwc/prover.rs index ecea01cb01..4b9cda2470 100644 --- a/halo2_backend/src/poly/kzg/multiopen/gwc/prover.rs +++ b/halo2_backend/src/poly/kzg/multiopen/gwc/prover.rs @@ -9,6 +9,7 @@ use crate::poly::{commitment::Blind, Polynomial}; use crate::transcript::{EncodedChallenge, TranscriptWrite}; use group::Curve; +use halo2_middleware::zal::traits::MsmAccel; use halo2curves::pairing::Engine; use halo2curves::CurveExt; use rand_core::RngCore; @@ -36,7 +37,7 @@ where } /// Create a multi-opening proof - fn create_proof< + fn create_proof_with_engine< 'com, Ch: EncodedChallenge, T: TranscriptWrite, @@ -44,6 +45,7 @@ where I, >( &self, + engine: &impl MsmAccel, _: R, transcript: &mut T, queries: I, @@ -79,7 +81,7 @@ where }; let w = self .params - .commit(&witness_poly, Blind::default()) + .commit(engine, &witness_poly, Blind::default()) .to_affine(); transcript.write_point(w)?; diff --git a/halo2_backend/src/poly/kzg/multiopen/shplonk/prover.rs b/halo2_backend/src/poly/kzg/multiopen/shplonk/prover.rs index 388fa91147..194215e6da 100644 --- a/halo2_backend/src/poly/kzg/multiopen/shplonk/prover.rs +++ b/halo2_backend/src/poly/kzg/multiopen/shplonk/prover.rs @@ -15,6 +15,7 @@ use crate::transcript::{EncodedChallenge, TranscriptWrite}; use crate::multicore::{IntoParallelIterator, ParallelIterator}; use group::Curve; use halo2_middleware::ff::Field; +use halo2_middleware::zal::traits::MsmAccel; use halo2curves::pairing::Engine; use halo2curves::CurveExt; use rand_core::RngCore; @@ -117,7 +118,7 @@ where } /// Create a multi-opening proof - fn create_proof< + fn create_proof_with_engine< 'com, Ch: EncodedChallenge, T: TranscriptWrite, @@ -125,6 +126,7 @@ where I, >( &self, + engine: &impl MsmAccel, _: R, transcript: &mut T, queries: I, @@ -208,7 +210,10 @@ where .reduce(|acc, poly| acc + &poly) .unwrap(); - let h = self.params.commit(&h_x, Blind::default()).to_affine(); + let h = self + .params + .commit(engine, &h_x, Blind::default()) + .to_affine(); transcript.write_point(h)?; let u: ChallengeU<_> = transcript.squeeze_challenge_scalar(); @@ -290,7 +295,10 @@ where _marker: PhantomData, }; - let h = self.params.commit(&h_x, Blind::default()).to_affine(); + let h = self + .params + .commit(engine, &h_x, Blind::default()) + .to_affine(); transcript.write_point(h)?; Ok(()) diff --git a/halo2_backend/src/poly/kzg/strategy.rs b/halo2_backend/src/poly/kzg/strategy.rs index 78d182fbf6..96573b0c74 100644 --- a/halo2_backend/src/poly/kzg/strategy.rs +++ b/halo2_backend/src/poly/kzg/strategy.rs @@ -11,6 +11,7 @@ use crate::{ }, }; use halo2_middleware::ff::Field; +use halo2_middleware::zal::impls::H2cEngine; use halo2curves::{ pairing::{Engine, MultiMillerLoop}, CurveAffine, CurveExt, @@ -136,7 +137,9 @@ where } fn finalize(self) -> bool { - self.msm_accumulator.check() + // ZAL: Verification is (supposedly) cheap, hence we don't use an accelerator engine + let default_engine = H2cEngine::new(); + self.msm_accumulator.check(&default_engine) } } @@ -168,7 +171,9 @@ where // Guard is updated with new msm contributions let guard = f(self.msm)?; let msm = guard.msm_accumulator; - if msm.check() { + // Verification is (supposedly) cheap, hence we don't use an accelerator engine + let default_engine = H2cEngine::new(); + if msm.check(&default_engine) { Ok(()) } else { Err(Error::ConstraintSystemFailure) diff --git a/halo2_backend/src/poly/multiopen_test.rs b/halo2_backend/src/poly/multiopen_test.rs index 7ee3e7c9d3..c37b809b7a 100644 --- a/halo2_backend/src/poly/multiopen_test.rs +++ b/halo2_backend/src/poly/multiopen_test.rs @@ -16,6 +16,7 @@ mod test { }; use group::Curve; use halo2_middleware::ff::WithSmallOrderMulGroup; + use halo2_middleware::zal::{impls::H2cEngine, traits::MsmAccel}; use rand_core::OsRng; #[test] @@ -27,6 +28,7 @@ mod test { const K: u32 = 4; + let engine = H2cEngine::new(); let params = ParamsIPA::::new(K); let proof = create_proof::< @@ -34,7 +36,7 @@ mod test { ProverIPA<_>, _, Blake2bWrite<_, _, Challenge255<_>>, - >(¶ms); + >(&engine, ¶ms); let verifier_params = params.verifier_params(); @@ -64,6 +66,7 @@ mod test { const K: u32 = 4; + let engine = H2cEngine::new(); let params = ParamsIPA::::new(K); let proof = create_proof::< @@ -71,7 +74,7 @@ mod test { ProverIPA<_>, _, Keccak256Write<_, _, Challenge255<_>>, - >(¶ms); + >(&engine, ¶ms); let verifier_params = params.verifier_params(); @@ -101,10 +104,12 @@ mod test { const K: u32 = 4; + let engine = H2cEngine::new(); let params = ParamsKZG::::new(K); - let proof = - create_proof::<_, ProverGWC<_>, _, Blake2bWrite<_, _, Challenge255<_>>>(¶ms); + let proof = create_proof::<_, ProverGWC<_>, _, Blake2bWrite<_, _, Challenge255<_>>>( + &engine, ¶ms, + ); let verifier_params = params.verifier_params(); @@ -132,6 +137,7 @@ mod test { const K: u32 = 4; + let engine = H2cEngine::new(); let params = ParamsKZG::::new(K); let proof = create_proof::< @@ -139,7 +145,7 @@ mod test { ProverSHPLONK<_>, _, Blake2bWrite<_, _, Challenge255<_>>, - >(¶ms); + >(&engine, ¶ms); let verifier_params = params.verifier_params(); @@ -225,6 +231,7 @@ mod test { E: EncodedChallenge, T: TranscriptWriterBuffer, Scheme::Curve, E>, >( + engine: &impl MsmAccel, params: &'params Scheme::ParamsProver, ) -> Vec where @@ -250,9 +257,9 @@ mod test { let mut transcript = T::init(vec![]); let blind = Blind::new(&mut OsRng); - let a = params.commit(&ax, blind).to_affine(); - let b = params.commit(&bx, blind).to_affine(); - let c = params.commit(&cx, blind).to_affine(); + let a = params.commit(engine, &ax, blind).to_affine(); + let b = params.commit(engine, &bx, blind).to_affine(); + let c = params.commit(engine, &cx, blind).to_affine(); transcript.write_point(a).unwrap(); transcript.write_point(b).unwrap(); diff --git a/halo2_middleware/Cargo.toml b/halo2_middleware/Cargo.toml index 575c25dc8a..ec4f724f9c 100644 --- a/halo2_middleware/Cargo.toml +++ b/halo2_middleware/Cargo.toml @@ -26,14 +26,16 @@ rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] [dependencies] ff = "0.13" +halo2curves = { version = "0.6.0", default-features = false } serde = { version = "1", optional = true, features = ["derive"] } serde_derive = { version = "1", optional = true} rayon = "1.8" [dev-dependencies] +ark-std = { version = "0.3" } proptest = "1" group = "0.13" -halo2curves = { version = "0.6.0", default-features = false } +rand_core = { version = "0.6", default-features = false } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/halo2_middleware/src/lib.rs b/halo2_middleware/src/lib.rs index db9734d819..70ff0c4af9 100644 --- a/halo2_middleware/src/lib.rs +++ b/halo2_middleware/src/lib.rs @@ -4,5 +4,6 @@ pub mod metadata; pub mod permutation; pub mod poly; pub mod shuffle; +pub mod zal; pub use ff; diff --git a/halo2_middleware/src/zal.rs b/halo2_middleware/src/zal.rs new file mode 100644 index 0000000000..28d9491ce6 --- /dev/null +++ b/halo2_middleware/src/zal.rs @@ -0,0 +1,350 @@ +//! This module provides "ZK Acceleration Layer" traits +//! to abstract away the execution engine for performance-critical primitives. +//! +//! Terminology +//! ----------- +//! +//! We use the name Backend+Engine for concrete implementations of ZalEngine. +//! For example H2cEngine for pure Halo2curves implementation. +//! +//! Alternative names considered were Executor or Driver however +//! - executor is already used in Rust (and the name is long) +//! - driver will be confusing as we work quite low-level with GPUs and FPGAs. +//! +//! Unfortunately the "Engine" name is used in bn256 for pairings. +//! Fortunately a ZalEngine is only used in the prover (at least for now) +//! while "pairing engine" is only used in the verifier +//! +//! Initialization design space +//! --------------------------- +//! +//! It is recommended that ZAL backends provide: +//! - an initialization function: +//! - either "fn new() -> ZalEngine" for simple libraries +//! - or a builder pattern for complex initializations +//! - a shutdown function or document when it is not needed (when it's a global threadpool like Rayon for example). +//! +//! Backends might want to add as an option: +//! - The number of threads (CPU) +//! - The device(s) to run on (multi-sockets machines, multi-GPUs machines, ...) +//! - The curve (JIT-compiled backend) +//! +//! Descriptors +//! --------------------------- +//! +//! Descriptors enable providers to configure opaque details on data +//! when doing repeated computations with the same input(s). +//! For example: +//! - Pointer(s) caching to limit data movement between CPU and GPU, FPGAs +//! - Length of data +//! - data in layout: +//! - canonical or Montgomery fields, unsaturated representation, endianness +//! - jacobian or projective coordinates or maybe even Twisted Edwards for faster elliptic curve additions, +//! - FFT: canonical or bit-reversed permuted +//! - data out layout +//! - Device(s) ID +//! +//! For resources that need special cleanup like GPU memory, a custom `Drop` is required. +//! +//! Note that resources can also be stored in the engine in a hashmap +//! and an integer ID or a pointer can be opaquely given as a descriptor. + +// The ZK Accel Layer API +// --------------------------------------------------- +pub mod traits { + use halo2curves::CurveAffine; + + pub trait MsmAccel { + fn msm(&self, coeffs: &[C::Scalar], base: &[C]) -> C::Curve; + + // Caching API + // ------------------------------------------------- + // From here we propose an extended API + // that allows reusing coeffs and/or the base points + // + // This is inspired by CuDNN API (Nvidia GPU) + // and oneDNN API (CPU, OpenCL) https://docs.nvidia.com/deeplearning/cudnn/api/index.html#cudnn-ops-infer-so-opaque + // usage of descriptors + // + // https://github.com/oneapi-src/oneDNN/blob/master/doc/programming_model/basic_concepts.md + // + // Descriptors are opaque pointers that hold the input in a format suitable for the accelerator engine. + // They may be: + // - Input moved on accelerator device (only once for repeated calls) + // - Endianess conversion + // - Converting from Montgomery to Canonical form + // - Input changed from Projective to Jacobian coordinates or even to a Twisted Edwards curve. + // - other form of expensive preprocessing + type CoeffsDescriptor<'c>; + type BaseDescriptor<'b>; + + fn get_coeffs_descriptor<'c>(&self, coeffs: &'c [C::Scalar]) -> Self::CoeffsDescriptor<'c>; + fn get_base_descriptor<'b>(&self, base: &'b [C]) -> Self::BaseDescriptor<'b>; + + fn msm_with_cached_scalars( + &self, + coeffs: &Self::CoeffsDescriptor<'_>, + base: &[C], + ) -> C::Curve; + + fn msm_with_cached_base( + &self, + coeffs: &[C::Scalar], + base: &Self::BaseDescriptor<'_>, + ) -> C::Curve; + + fn msm_with_cached_inputs( + &self, + coeffs: &Self::CoeffsDescriptor<'_>, + base: &Self::BaseDescriptor<'_>, + ) -> C::Curve; + // Execute MSM according to descriptors + // Unsure of naming, msm_with_cached_inputs, msm_apply, msm_cached, msm_with_descriptors, ... + } +} + +// ZAL using Halo2curves as a backend +// --------------------------------------------------- + +pub mod impls { + use std::marker::PhantomData; + + use crate::zal::traits::MsmAccel; + use halo2curves::msm::best_multiexp; + use halo2curves::CurveAffine; + + // Halo2curve Backend + // --------------------------------------------------- + #[derive(Default)] + pub struct H2cEngine; + + pub struct H2cMsmCoeffsDesc<'c, C: CurveAffine> { + raw: &'c [C::Scalar], + } + + pub struct H2cMsmBaseDesc<'b, C: CurveAffine> { + raw: &'b [C], + } + + impl H2cEngine { + pub fn new() -> Self { + Self {} + } + } + + impl MsmAccel for H2cEngine { + fn msm(&self, coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { + best_multiexp(coeffs, bases) + } + + // Caching API + // ------------------------------------------------- + + type CoeffsDescriptor<'c> = H2cMsmCoeffsDesc<'c, C>; + type BaseDescriptor<'b> = H2cMsmBaseDesc<'b, C>; + + fn get_coeffs_descriptor<'c>(&self, coeffs: &'c [C::Scalar]) -> Self::CoeffsDescriptor<'c> { + // Do expensive device/library specific preprocessing here + Self::CoeffsDescriptor { raw: coeffs } + } + fn get_base_descriptor<'b>(&self, base: &'b [C]) -> Self::BaseDescriptor<'b> { + Self::BaseDescriptor { raw: base } + } + + fn msm_with_cached_scalars( + &self, + coeffs: &Self::CoeffsDescriptor<'_>, + base: &[C], + ) -> C::Curve { + best_multiexp(coeffs.raw, base) + } + + fn msm_with_cached_base( + &self, + coeffs: &[C::Scalar], + base: &Self::BaseDescriptor<'_>, + ) -> C::Curve { + best_multiexp(coeffs, base.raw) + } + + fn msm_with_cached_inputs( + &self, + coeffs: &Self::CoeffsDescriptor<'_>, + base: &Self::BaseDescriptor<'_>, + ) -> C::Curve { + best_multiexp(coeffs.raw, base.raw) + } + } + + // Backend-agnostic engine objects + // --------------------------------------------------- + #[derive(Debug)] + pub struct PlonkEngine> { + pub msm_backend: MsmEngine, + _marker: PhantomData, // compiler complains about unused C otherwise + } + + #[derive(Default)] + pub struct PlonkEngineConfig { + curve: PhantomData, + msm_backend: M, + } + + #[derive(Default)] + pub struct NoCurve; + + #[derive(Default)] + pub struct HasCurve(PhantomData); + + #[derive(Default)] + pub struct NoMsmEngine; + + pub struct HasMsmEngine>(M, PhantomData); + + impl PlonkEngineConfig { + pub fn new() -> PlonkEngineConfig { + Default::default() + } + + pub fn set_curve(self) -> PlonkEngineConfig, NoMsmEngine> { + Default::default() + } + + pub fn build_default() -> PlonkEngine { + PlonkEngine { + msm_backend: H2cEngine::new(), + _marker: Default::default(), + } + } + } + + impl PlonkEngineConfig, M> { + pub fn set_msm>( + self, + engine: MsmEngine, + ) -> PlonkEngineConfig, HasMsmEngine> { + // Copy all other parameters + let Self { curve, .. } = self; + // Return with modified MSM engine + PlonkEngineConfig { + curve, + msm_backend: HasMsmEngine(engine, Default::default()), + } + } + } + + impl> PlonkEngineConfig, HasMsmEngine> { + pub fn build(self) -> PlonkEngine { + PlonkEngine { + msm_backend: self.msm_backend.0, + _marker: Default::default(), + } + } + } +} + +// Testing +// --------------------------------------------------- + +#[cfg(test)] +mod test { + use crate::zal::impls::{H2cEngine, PlonkEngineConfig}; + use crate::zal::traits::MsmAccel; + use halo2curves::bn256::G1Affine; + use halo2curves::msm::best_multiexp; + use halo2curves::CurveAffine; + + use ark_std::{end_timer, start_timer}; + use ff::Field; + use group::{Curve, Group}; + use rand_core::OsRng; + + fn run_msm_zal_default(min_k: usize, max_k: usize) { + let points = (0..1 << max_k) + .map(|_| C::Curve::random(OsRng)) + .collect::>(); + let mut affine_points = vec![C::identity(); 1 << max_k]; + C::Curve::batch_normalize(&points[..], &mut affine_points[..]); + let points = affine_points; + + let scalars = (0..1 << max_k) + .map(|_| C::Scalar::random(OsRng)) + .collect::>(); + + for k in min_k..=max_k { + let points = &points[..1 << k]; + let scalars = &scalars[..1 << k]; + + let t0 = start_timer!(|| format!("freestanding msm k={}", k)); + let e0 = best_multiexp(scalars, points); + end_timer!(t0); + + let engine = PlonkEngineConfig::build_default::(); + let t1 = start_timer!(|| format!("H2cEngine msm k={}", k)); + let e1 = engine.msm_backend.msm(scalars, points); + end_timer!(t1); + + assert_eq!(e0, e1); + + // Caching API + // ----------- + let t2 = start_timer!(|| format!("H2cEngine msm cached base k={}", k)); + let base_descriptor = engine.msm_backend.get_base_descriptor(points); + let e2 = engine + .msm_backend + .msm_with_cached_base(scalars, &base_descriptor); + end_timer!(t2); + + assert_eq!(e0, e2) + } + } + + fn run_msm_zal_custom(min_k: usize, max_k: usize) { + let points = (0..1 << max_k) + .map(|_| C::Curve::random(OsRng)) + .collect::>(); + let mut affine_points = vec![C::identity(); 1 << max_k]; + C::Curve::batch_normalize(&points[..], &mut affine_points[..]); + let points = affine_points; + + let scalars = (0..1 << max_k) + .map(|_| C::Scalar::random(OsRng)) + .collect::>(); + + for k in min_k..=max_k { + let points = &points[..1 << k]; + let scalars = &scalars[..1 << k]; + + let t0 = start_timer!(|| format!("freestanding msm k={}", k)); + let e0 = best_multiexp(scalars, points); + end_timer!(t0); + + let engine = PlonkEngineConfig::new() + .set_curve::() + .set_msm(H2cEngine::new()) + .build(); + let t1 = start_timer!(|| format!("H2cEngine msm k={}", k)); + let e1 = engine.msm_backend.msm(scalars, points); + end_timer!(t1); + + assert_eq!(e0, e1); + + // Caching API + // ----------- + let t2 = start_timer!(|| format!("H2cEngine msm cached base k={}", k)); + let base_descriptor = engine.msm_backend.get_base_descriptor(points); + let e2 = engine + .msm_backend + .msm_with_cached_base(scalars, &base_descriptor); + end_timer!(t2); + + assert_eq!(e0, e2) + } + } + + #[test] + fn test_msm_zal() { + run_msm_zal_default::(3, 14); + run_msm_zal_custom::(3, 14); + } +} diff --git a/halo2_proofs/benches/arithmetic.rs b/halo2_proofs/benches/arithmetic.rs index 4ae88af137..51f769c9e9 100644 --- a/halo2_proofs/benches/arithmetic.rs +++ b/halo2_proofs/benches/arithmetic.rs @@ -1,10 +1,10 @@ #[macro_use] extern crate criterion; -use crate::arithmetic::small_multiexp; -use crate::halo2curves::pasta::{EqAffine, Fp}; use group::ff::Field; +use halo2_middleware::zal::{impls::H2cEngine, traits::MsmAccel}; use halo2_proofs::*; +use halo2curves::pasta::{EqAffine, Fp}; use halo2_proofs::poly::{commitment::ParamsProver, ipa::commitment::ParamsIPA}; @@ -16,6 +16,7 @@ fn criterion_benchmark(c: &mut Criterion) { // small multiexp { + let engine = H2cEngine::new(); let params: ParamsIPA = ParamsIPA::new(5); let g = &mut params.get_g().to_vec(); let len = g.len() / 2; @@ -27,7 +28,7 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function("double-and-add", |b| { b.iter(|| { for (g_lo, g_hi) in g_lo.iter().zip(g_hi.iter()) { - small_multiexp(&[black_box(coeff_1), black_box(coeff_2)], &[*g_lo, *g_hi]); + engine.msm(&[black_box(coeff_1), black_box(coeff_2)], &[*g_lo, *g_hi]); } }) }); diff --git a/halo2_proofs/src/lib.rs b/halo2_proofs/src/lib.rs index 4f72856a10..109bcdbed4 100644 --- a/halo2_proofs/src/lib.rs +++ b/halo2_proofs/src/lib.rs @@ -23,9 +23,7 @@ pub mod circuit { /// This module provides common utilities, traits and structures for group, /// field and polynomial arithmetic. pub mod arithmetic { - pub use halo2_backend::arithmetic::{ - best_fft, parallelize, small_multiexp, CurveAffine, CurveExt, Field, - }; + pub use halo2_backend::arithmetic::{best_fft, parallelize, CurveAffine, CurveExt, Field}; } /// Tools for developing circuits. pub mod dev { diff --git a/halo2_proofs/src/plonk.rs b/halo2_proofs/src/plonk.rs index 1cfb9ec4e4..935e4dd425 100644 --- a/halo2_proofs/src/plonk.rs +++ b/halo2_proofs/src/plonk.rs @@ -13,7 +13,7 @@ mod verifier { pub use keygen::{keygen_pk, keygen_vk}; -pub use prover::create_proof; +pub use prover::{create_proof, create_proof_with_engine}; pub use verifier::verify_proof; pub use halo2_backend::plonk::{ProvingKey, VerifyingKey}; diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index 4a22afc18c..071580372d 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -4,6 +4,10 @@ use halo2_common::plonk::{circuit::Circuit, Error}; use halo2_common::transcript::{EncodedChallenge, TranscriptWrite}; use halo2_frontend::circuit::{compile_circuit, WitnessCalculator}; use halo2_middleware::ff::{FromUniformBytes, WithSmallOrderMulGroup}; +use halo2_middleware::zal::{ + impls::{PlonkEngine, PlonkEngineConfig}, + traits::MsmAccel, +}; use rand_core::RngCore; use std::collections::HashMap; @@ -11,7 +15,7 @@ use std::collections::HashMap; /// parameters `params` and the proving key [`ProvingKey`] that was /// generated previously for the same circuit. The provided `instances` /// are zero-padded internally. -pub fn create_proof< +pub fn create_proof_with_engine< 'params, Scheme: CommitmentScheme, P: Prover<'params, Scheme>, @@ -19,7 +23,9 @@ pub fn create_proof< R: RngCore, T: TranscriptWrite, ConcreteCircuit: Circuit, + M: MsmAccel, >( + engine: PlonkEngine, params: &'params Scheme::ParamsProver, pk: &ProvingKey, circuits: &[ConcreteCircuit], @@ -40,7 +46,9 @@ where .enumerate() .map(|(i, circuit)| WitnessCalculator::new(params.k(), circuit, &config, &cs, instances[i])) .collect(); - let mut prover = ProverV2::::new(params, pk, instances, rng, transcript)?; + let mut prover = ProverV2::::new_with_engine( + engine, params, pk, instances, rng, transcript, + )?; let mut challenges = HashMap::new(); let phases = prover.phases.clone(); for phase in &phases { @@ -53,6 +61,35 @@ where prover.create_proof() } +/// This creates a proof for the provided `circuit` when given the public +/// parameters `params` and the proving key [`ProvingKey`] that was +/// generated previously for the same circuit. The provided `instances` +/// are zero-padded internally. +pub fn create_proof< + 'params, + Scheme: CommitmentScheme, + P: Prover<'params, Scheme>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWrite, + ConcreteCircuit: Circuit, +>( + params: &'params Scheme::ParamsProver, + pk: &ProvingKey, + circuits: &[ConcreteCircuit], + instances: &[&[&[Scheme::Scalar]]], + rng: R, + transcript: &mut T, +) -> Result<(), Error> +where + Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, +{ + let engine = PlonkEngineConfig::build_default(); + create_proof_with_engine::( + engine, params, pk, circuits, instances, rng, transcript, + ) +} + #[test] fn test_create_proof() { use crate::{ diff --git a/halo2_proofs/tests/frontend_backend_split.rs b/halo2_proofs/tests/frontend_backend_split.rs index 4b7621b9ea..9657b1961d 100644 --- a/halo2_proofs/tests/frontend_backend_split.rs +++ b/halo2_proofs/tests/frontend_backend_split.rs @@ -548,9 +548,15 @@ fn test_mycircuit_full_legacy() { #[test] fn test_mycircuit_full_split() { + use halo2_middleware::zal::impls::{H2cEngine, PlonkEngineConfig}; + #[cfg(feature = "heap-profiling")] let _profiler = dhat::Profiler::new_heap(); + let engine = PlonkEngineConfig::new() + .set_curve::() + .set_msm(H2cEngine::new()) + .build(); let k = K; let circuit: MyCircuit = MyCircuit::new(k, 42); let (compiled_circuit, config, cs) = compile_circuit(k, &circuit, false).unwrap(); @@ -577,15 +583,22 @@ fn test_mycircuit_full_split() { let start = Instant::now(); let mut witness_calc = WitnessCalculator::new(k, &circuit, &config, &cs, instances_slice); let mut transcript = Blake2bWrite::<_, G1Affine, Challenge255<_>>::init(vec![]); - let mut prover = - ProverV2Single::, ProverSHPLONK<'_, Bn256>, _, _, _>::new( - ¶ms, - &pk, - instances_slice, - &mut rng, - &mut transcript, - ) - .unwrap(); + let mut prover = ProverV2Single::< + KZGCommitmentScheme, + ProverSHPLONK<'_, Bn256>, + _, + _, + _, + _, + >::new_with_engine( + engine, + ¶ms, + &pk, + instances_slice, + &mut rng, + &mut transcript, + ) + .unwrap(); let mut challenges = HashMap::new(); for phase in 0..cs.phases().count() { println!("phase {phase}"); diff --git a/halo2_proofs/tests/plonk_api.rs b/halo2_proofs/tests/plonk_api.rs index c3e21112aa..b39d1ec2ef 100644 --- a/halo2_proofs/tests/plonk_api.rs +++ b/halo2_proofs/tests/plonk_api.rs @@ -3,13 +3,17 @@ use assert_matches::assert_matches; use ff::{FromUniformBytes, WithSmallOrderMulGroup}; +use halo2_middleware::zal::{ + impls::{PlonkEngine, PlonkEngineConfig}, + traits::MsmAccel, +}; use halo2_proofs::arithmetic::Field; use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; use halo2_proofs::dev::MockProver; use halo2_proofs::plonk::{ - create_proof as create_plonk_proof, keygen_pk, keygen_vk, verify_proof as verify_plonk_proof, - Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, ProvingKey, TableColumn, - VerifyingKey, + create_proof_with_engine as create_plonk_proof_with_engine, keygen_pk, keygen_vk, + verify_proof as verify_plonk_proof, Advice, Assigned, Circuit, Column, ConstraintSystem, Error, + Fixed, ProvingKey, TableColumn, VerifyingKey, }; use halo2_proofs::poly::commitment::{CommitmentScheme, ParamsProver, Prover, Verifier}; use halo2_proofs::poly::Rotation; @@ -454,14 +458,16 @@ fn plonk_api() { keygen_pk(params, vk, &empty_circuit).expect("keygen_pk should not fail") } - fn create_proof< + fn create_proof_with_engine< 'params, Scheme: CommitmentScheme, P: Prover<'params, Scheme>, E: EncodedChallenge, R: RngCore, T: TranscriptWriterBuffer, Scheme::Curve, E>, + M: MsmAccel, >( + engine: PlonkEngine, rng: R, params: &'params Scheme::ParamsProver, pk: &ProvingKey, @@ -478,7 +484,8 @@ fn plonk_api() { let mut transcript = T::init(vec![]); - create_plonk_proof::( + create_plonk_proof_with_engine::( + engine, params, pk, &[circuit.clone(), circuit.clone()], @@ -498,6 +505,25 @@ fn plonk_api() { transcript.finalize() } + fn create_proof< + 'params, + Scheme: CommitmentScheme, + P: Prover<'params, Scheme>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWriterBuffer, Scheme::Curve, E>, + >( + rng: R, + params: &'params Scheme::ParamsProver, + pk: &ProvingKey, + ) -> Vec + where + Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + { + let engine = PlonkEngineConfig::build_default(); + create_proof_with_engine::(engine, rng, params, pk) + } + fn verify_proof< 'a, 'params,