From 5c31088f8530470227c411b5d168b445cb59858b Mon Sep 17 00:00:00 2001 From: Han Date: Tue, 8 Nov 2022 19:58:00 -0800 Subject: [PATCH] feat: implement ipa pcs and accumulation (#14) --- Cargo.toml | 7 +- src/loader/halo2/test.rs | 21 +- src/pcs.rs | 1 + src/pcs/ipa.rs | 447 ++++++++++++++++++++++++++++ src/pcs/ipa/accumulation.rs | 291 ++++++++++++++++++ src/pcs/ipa/accumulator.rs | 21 ++ src/pcs/ipa/decider.rs | 57 ++++ src/pcs/ipa/multiopen.rs | 3 + src/pcs/ipa/multiopen/bgh19.rs | 417 ++++++++++++++++++++++++++ src/system/halo2.rs | 1 + src/system/halo2/strategy.rs | 53 ++++ src/system/halo2/test.rs | 1 + src/system/halo2/test/ipa.rs | 143 +++++++++ src/system/halo2/test/ipa/native.rs | 59 ++++ src/system/halo2/test/kzg/halo2.rs | 9 +- src/util.rs | 40 +++ src/util/arithmetic.rs | 12 + src/util/msm.rs | 123 +++++++- src/util/poly.rs | 175 +++++++++++ 19 files changed, 1866 insertions(+), 15 deletions(-) create mode 100644 src/pcs/ipa.rs create mode 100644 src/pcs/ipa/accumulation.rs create mode 100644 src/pcs/ipa/accumulator.rs create mode 100644 src/pcs/ipa/decider.rs create mode 100644 src/pcs/ipa/multiopen.rs create mode 100644 src/pcs/ipa/multiopen/bgh19.rs create mode 100644 src/system/halo2/strategy.rs create mode 100644 src/system/halo2/test/ipa.rs create mode 100644 src/system/halo2/test/ipa/native.rs create mode 100644 src/util/poly.rs diff --git a/Cargo.toml b/Cargo.toml index e5bbb0d2..44dcd60b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,9 @@ rand = "0.8" hex = "0.4" halo2_curves = { git = "https://github.com/privacy-scaling-explorations/halo2curves", tag = "0.3.0", package = "halo2curves" } +# parallel +rayon = { version = "1.5.3", optional = true } + # system_halo2 halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2022_10_22", optional = true } @@ -41,13 +44,13 @@ tui = { version = "0.19", default-features = false, features = ["crossterm"] } [features] default = ["loader_evm", "loader_halo2", "system_halo2"] +parallel = ["dep:rayon"] + loader_evm = ["dep:ethereum_types", "dep:sha3", "dep:revm", "dep:bytes", "dep:rlp"] loader_halo2 = ["dep:halo2_proofs", "dep:halo2_wrong_ecc", "dep:poseidon"] system_halo2 = ["dep:halo2_proofs"] -sanity_check = [] - [[example]] name = "evm-verifier" required-features = ["loader_evm", "system_halo2"] diff --git a/src/loader/halo2/test.rs b/src/loader/halo2/test.rs index 08551fe0..dd2fccaa 100644 --- a/src/loader/halo2/test.rs +++ b/src/loader/halo2/test.rs @@ -4,6 +4,7 @@ use crate::{ }; use halo2_proofs::circuit::Value; +#[derive(Clone, Debug)] pub struct Snark { pub protocol: Protocol, pub instances: Vec>, @@ -27,6 +28,7 @@ impl Snark { } } +#[derive(Clone, Debug)] pub struct SnarkWitness { pub protocol: Protocol, pub instances: Vec>>, @@ -48,18 +50,23 @@ impl From> for SnarkWitness { } impl SnarkWitness { - pub fn without_witnesses(&self) -> Self { + pub fn new_without_witness(protocol: Protocol) -> Self { + let instances = protocol + .num_instance + .iter() + .map(|num_instance| vec![Value::unknown(); *num_instance]) + .collect(); SnarkWitness { - protocol: self.protocol.clone(), - instances: self - .instances - .iter() - .map(|instances| vec![Value::unknown(); instances.len()]) - .collect(), + protocol, + instances, proof: Value::unknown(), } } + pub fn without_witnesses(&self) -> Self { + SnarkWitness::new_without_witness(self.protocol.clone()) + } + pub fn proof(&self) -> Value<&[u8]> { self.proof.as_ref().map(Vec::as_slice) } diff --git a/src/pcs.rs b/src/pcs.rs index 23fdda2a..bf944a43 100644 --- a/src/pcs.rs +++ b/src/pcs.rs @@ -10,6 +10,7 @@ use crate::{ use rand::Rng; use std::fmt::Debug; +pub mod ipa; pub mod kzg; pub trait PolynomialCommitmentScheme: Clone + Debug diff --git a/src/pcs/ipa.rs b/src/pcs/ipa.rs new file mode 100644 index 00000000..a2b34824 --- /dev/null +++ b/src/pcs/ipa.rs @@ -0,0 +1,447 @@ +use crate::{ + loader::{native::NativeLoader, LoadedScalar, Loader, ScalarLoader}, + pcs::PolynomialCommitmentScheme, + util::{ + arithmetic::{ + inner_product, powers, Curve, CurveAffine, Domain, Field, Fraction, PrimeField, + }, + msm::{multi_scalar_multiplication, Msm}, + parallelize, + poly::Polynomial, + transcript::{TranscriptRead, TranscriptWrite}, + Itertools, + }, + Error, +}; +use rand::Rng; +use std::{fmt::Debug, iter, marker::PhantomData}; + +mod accumulation; +mod accumulator; +mod decider; +mod multiopen; + +pub use accumulation::{IpaAs, IpaAsProof}; +pub use accumulator::IpaAccumulator; +pub use decider::IpaDecidingKey; +pub use multiopen::{Bgh19, Bgh19Proof, Bgh19SuccinctVerifyingKey}; + +#[derive(Clone, Debug)] +pub struct Ipa(PhantomData<(C, MOS)>); + +impl PolynomialCommitmentScheme for Ipa +where + C: CurveAffine, + L: Loader, + MOS: Clone + Debug, +{ + type Accumulator = IpaAccumulator; +} + +impl Ipa +where + C: CurveAffine, +{ + pub fn create_proof( + pk: &IpaProvingKey, + p: &[C::Scalar], + z: &C::Scalar, + omega: Option<&C::Scalar>, + transcript: &mut T, + mut rng: R, + ) -> Result, Error> + where + T: TranscriptWrite, + R: Rng, + { + let mut p_prime = Polynomial::new(p.to_vec()); + if pk.zk() { + let p_bar = { + let mut p_bar = Polynomial::rand(p.len(), &mut rng); + let p_bar_at_z = p_bar.evaluate(*z); + p_bar[0] -= p_bar_at_z; + p_bar + }; + let omega_bar = C::Scalar::random(&mut rng); + let c_bar = pk.commit(&p_bar, Some(omega_bar)); + transcript.write_ec_point(c_bar)?; + + let alpha = transcript.squeeze_challenge(); + let omega_prime = *omega.unwrap() + alpha * omega_bar; + transcript.write_scalar(omega_prime)?; + + p_prime = p_prime + &(p_bar * alpha); + }; + + let xi_0 = transcript.squeeze_challenge(); + let h_prime = pk.h * xi_0; + let mut bases = pk.g.clone(); + let mut coeffs = p_prime.to_vec(); + let mut zs = powers(*z).take(coeffs.len()).collect_vec(); + + let k = pk.domain.k; + let mut xi = Vec::with_capacity(k); + for i in 0..k { + let half = 1 << (k - i - 1); + + let l_i = multi_scalar_multiplication(&coeffs[half..], &bases[..half]) + + h_prime * inner_product(&coeffs[half..], &zs[..half]); + let r_i = multi_scalar_multiplication(&coeffs[..half], &bases[half..]) + + h_prime * inner_product(&coeffs[..half], &zs[half..]); + transcript.write_ec_point(l_i.to_affine())?; + transcript.write_ec_point(r_i.to_affine())?; + + let xi_i = transcript.squeeze_challenge(); + let xi_i_inv = Field::invert(&xi_i).unwrap(); + + let (bases_l, bases_r) = bases.split_at_mut(half); + let (coeffs_l, coeffs_r) = coeffs.split_at_mut(half); + let (zs_l, zs_r) = zs.split_at_mut(half); + parallelize(bases_l, |(bases_l, start)| { + let mut tmp = Vec::with_capacity(bases_l.len()); + for (lhs, rhs) in bases_l.iter().zip(bases_r[start..].iter()) { + tmp.push(lhs.to_curve() + *rhs * xi_i); + } + C::Curve::batch_normalize(&tmp, bases_l); + }); + parallelize(coeffs_l, |(coeffs_l, start)| { + for (lhs, rhs) in coeffs_l.iter_mut().zip(coeffs_r[start..].iter()) { + *lhs += xi_i_inv * rhs; + } + }); + parallelize(zs_l, |(zs_l, start)| { + for (lhs, rhs) in zs_l.iter_mut().zip(zs_r[start..].iter()) { + *lhs += xi_i * rhs; + } + }); + bases = bases_l.to_vec(); + coeffs = coeffs_l.to_vec(); + zs = zs_l.to_vec(); + + xi.push(xi_i); + } + + transcript.write_ec_point(bases[0])?; + transcript.write_scalar(coeffs[0])?; + + Ok(IpaAccumulator::new(xi, bases[0])) + } + + pub fn read_proof>( + svk: &IpaSuccinctVerifyingKey, + transcript: &mut T, + ) -> Result, Error> + where + T: TranscriptRead, + { + IpaProof::read(svk, transcript) + } + + pub fn succinct_verify>( + svk: &IpaSuccinctVerifyingKey, + commitment: &Msm, + z: &L::LoadedScalar, + eval: &L::LoadedScalar, + proof: &IpaProof, + ) -> Result, Error> { + let loader = z.loader(); + let h = loader.ec_point_load_const(&svk.h); + let s = svk.s.as_ref().map(|s| loader.ec_point_load_const(s)); + let h = Msm::::base(&h); + + let h_prime = h * &proof.xi_0; + let lhs = { + let c_prime = match ( + s.as_ref(), + proof.c_bar_alpha.as_ref(), + proof.omega_prime.as_ref(), + ) { + (Some(s), Some((c_bar, alpha)), Some(omega_prime)) => { + let s = Msm::::base(s); + commitment.clone() + Msm::base(c_bar) * alpha - s * omega_prime + } + (None, None, None) => commitment.clone(), + _ => unreachable!(), + }; + let c_0 = c_prime + h_prime.clone() * eval; + let c_k = c_0 + + proof + .rounds + .iter() + .zip(proof.xi_inv().iter()) + .flat_map(|(Round { l, r, xi }, xi_inv)| [(l, xi_inv), (r, xi)]) + .map(|(base, scalar)| Msm::::base(base) * scalar) + .sum::>(); + c_k.evaluate(None) + }; + let rhs = { + let u = Msm::::base(&proof.u); + let v_prime = h_eval(&proof.xi(), z) * &proof.c; + (u * &proof.c + h_prime * &v_prime).evaluate(None) + }; + + loader.ec_point_assert_eq("C_k == c[U] + v'[H']", &lhs, &rhs)?; + + Ok(IpaAccumulator::new(proof.xi(), proof.u.clone())) + } +} + +#[derive(Clone, Debug)] +pub struct IpaProvingKey { + pub domain: Domain, + pub g: Vec, + pub h: C, + pub s: Option, +} + +impl IpaProvingKey { + pub fn new(domain: Domain, g: Vec, h: C, s: Option) -> Self { + Self { domain, g, h, s } + } + + pub fn zk(&self) -> bool { + self.s.is_some() + } + + pub fn svk(&self) -> IpaSuccinctVerifyingKey { + IpaSuccinctVerifyingKey::new(self.domain.clone(), self.h, self.s) + } + + pub fn dk(&self) -> IpaDecidingKey { + IpaDecidingKey::new(self.g.clone()) + } + + pub fn commit(&self, poly: &Polynomial, omega: Option) -> C { + let mut c = multi_scalar_multiplication(&poly[..], &self.g); + match (self.s, omega) { + (Some(s), Some(omega)) => c += s * omega, + (None, None) => {} + _ => unreachable!(), + }; + c.to_affine() + } +} + +impl IpaProvingKey { + #[cfg(test)] + pub fn rand(k: usize, zk: bool, mut rng: R) -> Self { + use crate::util::arithmetic::{root_of_unity, Group}; + + let domain = Domain::new(k, root_of_unity(k)); + let mut g = vec![C::default(); 1 << k]; + C::Curve::batch_normalize( + &iter::repeat_with(|| C::Curve::random(&mut rng)) + .take(1 << k) + .collect_vec(), + &mut g, + ); + let h = C::Curve::random(&mut rng).to_affine(); + let s = zk.then(|| C::Curve::random(&mut rng).to_affine()); + Self { domain, g, h, s } + } +} + +#[derive(Clone, Debug)] +pub struct IpaSuccinctVerifyingKey { + pub domain: Domain, + pub h: C, + pub s: Option, +} + +impl IpaSuccinctVerifyingKey { + pub fn new(domain: Domain, h: C, s: Option) -> Self { + Self { domain, h, s } + } + + pub fn zk(&self) -> bool { + self.s.is_some() + } +} + +#[derive(Clone, Debug)] +pub struct IpaProof +where + C: CurveAffine, + L: Loader, +{ + c_bar_alpha: Option<(L::LoadedEcPoint, L::LoadedScalar)>, + omega_prime: Option, + xi_0: L::LoadedScalar, + rounds: Vec>, + u: L::LoadedEcPoint, + c: L::LoadedScalar, +} + +impl IpaProof +where + C: CurveAffine, + L: Loader, +{ + pub fn new( + c_bar_alpha: Option<(L::LoadedEcPoint, L::LoadedScalar)>, + omega_prime: Option, + xi_0: L::LoadedScalar, + rounds: Vec>, + u: L::LoadedEcPoint, + c: L::LoadedScalar, + ) -> Self { + Self { + c_bar_alpha, + omega_prime, + xi_0, + rounds, + u, + c, + } + } + + pub fn read(svk: &IpaSuccinctVerifyingKey, transcript: &mut T) -> Result + where + T: TranscriptRead, + { + let c_bar_alpha = svk + .zk() + .then(|| { + let c_bar = transcript.read_ec_point()?; + let alpha = transcript.squeeze_challenge(); + Ok((c_bar, alpha)) + }) + .transpose()?; + let omega_prime = svk.zk().then(|| transcript.read_scalar()).transpose()?; + let xi_0 = transcript.squeeze_challenge(); + let rounds = iter::repeat_with(|| { + Ok(Round::new( + transcript.read_ec_point()?, + transcript.read_ec_point()?, + transcript.squeeze_challenge(), + )) + }) + .take(svk.domain.k) + .collect::, _>>()?; + let u = transcript.read_ec_point()?; + let c = transcript.read_scalar()?; + Ok(Self { + c_bar_alpha, + omega_prime, + xi_0, + rounds, + u, + c, + }) + } + + pub fn xi(&self) -> Vec { + self.rounds.iter().map(|round| round.xi.clone()).collect() + } + + pub fn xi_inv(&self) -> Vec { + let mut xi_inv = self.xi().into_iter().map(Fraction::one_over).collect_vec(); + L::batch_invert(xi_inv.iter_mut().filter_map(Fraction::denom_mut)); + xi_inv.iter_mut().for_each(Fraction::evaluate); + xi_inv + .into_iter() + .map(|xi_inv| xi_inv.evaluated().clone()) + .collect() + } +} + +#[derive(Clone, Debug)] +pub struct Round +where + C: CurveAffine, + L: Loader, +{ + l: L::LoadedEcPoint, + r: L::LoadedEcPoint, + xi: L::LoadedScalar, +} + +impl Round +where + C: CurveAffine, + L: Loader, +{ + pub fn new(l: L::LoadedEcPoint, r: L::LoadedEcPoint, xi: L::LoadedScalar) -> Self { + Self { l, r, xi } + } +} + +pub fn h_eval>(xi: &[T], z: &T) -> T { + let loader = z.loader(); + let one = loader.load_one(); + loader.product( + &iter::successors(Some(z.clone()), |z| Some(z.square())) + .zip(xi.iter().rev()) + .map(|(z, xi)| z * xi + &one) + .collect_vec() + .iter() + .collect_vec(), + ) +} + +pub fn h_coeffs(xi: &[F], scalar: F) -> Vec { + assert!(!xi.is_empty()); + + let mut coeffs = vec![F::zero(); 1 << xi.len()]; + coeffs[0] = scalar; + + for (len, xi) in xi.iter().rev().enumerate().map(|(i, xi)| (1 << i, xi)) { + let (left, right) = coeffs.split_at_mut(len); + let right = &mut right[0..len]; + right.copy_from_slice(left); + for coeffs in right { + *coeffs *= xi; + } + } + + coeffs +} + +#[cfg(all(test, feature = "system_halo2"))] +mod test { + use crate::{ + pcs::{ + ipa::{self, IpaProvingKey}, + Decider, + }, + util::{arithmetic::Field, msm::Msm, poly::Polynomial}, + }; + use halo2_curves::pasta::pallas; + use halo2_proofs::transcript::{ + Blake2bRead, Blake2bWrite, TranscriptReadBuffer, TranscriptWriterBuffer, + }; + use rand::rngs::OsRng; + + #[test] + fn test_ipa() { + type Ipa = ipa::Ipa; + + let k = 10; + let mut rng = OsRng; + + for zk in [false, true] { + let pk = IpaProvingKey::::rand(k, zk, &mut rng); + let (c, z, v, proof) = { + let p = Polynomial::::rand(pk.domain.n, &mut rng); + let omega = pk.zk().then(|| pallas::Scalar::random(&mut rng)); + let c = pk.commit(&p, omega); + let z = pallas::Scalar::random(&mut rng); + let v = p.evaluate(z); + let mut transcript = Blake2bWrite::init(Vec::new()); + Ipa::create_proof(&pk, &p[..], &z, omega.as_ref(), &mut transcript, &mut rng) + .unwrap(); + (c, z, v, transcript.finalize()) + }; + + let svk = pk.svk(); + let accumulator = { + let mut transcript = Blake2bRead::init(proof.as_slice()); + let proof = Ipa::read_proof(&svk, &mut transcript).unwrap(); + Ipa::succinct_verify(&svk, &Msm::base(&c), &z, &v, &proof).unwrap() + }; + + let dk = pk.dk(); + assert!(Ipa::decide(&dk, accumulator)); + } + } +} diff --git a/src/pcs/ipa/accumulation.rs b/src/pcs/ipa/accumulation.rs new file mode 100644 index 00000000..eeea9efe --- /dev/null +++ b/src/pcs/ipa/accumulation.rs @@ -0,0 +1,291 @@ +use crate::{ + loader::{native::NativeLoader, LoadedScalar, Loader}, + pcs::{ + ipa::{ + h_coeffs, h_eval, Ipa, IpaAccumulator, IpaProof, IpaProvingKey, IpaSuccinctVerifyingKey, + }, + AccumulationScheme, AccumulationSchemeProver, PolynomialCommitmentScheme, + }, + util::{ + arithmetic::{Curve, CurveAffine, Field}, + msm::Msm, + poly::Polynomial, + transcript::{TranscriptRead, TranscriptWrite}, + Itertools, + }, + Error, +}; +use rand::Rng; +use std::{array, iter, marker::PhantomData}; + +#[derive(Clone, Debug)] +pub struct IpaAs(PhantomData); + +impl AccumulationScheme for IpaAs +where + C: CurveAffine, + L: Loader, + PCS: PolynomialCommitmentScheme>, +{ + type VerifyingKey = IpaSuccinctVerifyingKey; + type Proof = IpaAsProof; + + fn read_proof( + vk: &Self::VerifyingKey, + instances: &[PCS::Accumulator], + transcript: &mut T, + ) -> Result + where + T: TranscriptRead, + { + IpaAsProof::read(vk, instances, transcript) + } + + fn verify( + vk: &Self::VerifyingKey, + instances: &[PCS::Accumulator], + proof: &Self::Proof, + ) -> Result { + let loader = proof.z.loader(); + let s = vk.s.as_ref().map(|s| loader.ec_point_load_const(s)); + + let (u, h) = instances + .iter() + .map(|IpaAccumulator { u, xi }| (u.clone(), h_eval(xi, &proof.z))) + .chain( + proof + .a_b_u + .as_ref() + .map(|(a, b, u)| (u.clone(), a.clone() * &proof.z + b)), + ) + .unzip::<_, _, Vec<_>, Vec<_>>(); + let powers_of_alpha = proof.alpha.powers(u.len()); + + let mut c = powers_of_alpha + .iter() + .zip(u.iter()) + .map(|(power_of_alpha, u)| Msm::::base(u) * power_of_alpha) + .sum::>(); + if let Some(omega) = proof.omega.as_ref() { + c += Msm::base(s.as_ref().unwrap()) * omega; + } + let v = loader.sum_products(&powers_of_alpha.iter().zip(h.iter()).collect_vec()); + + Ipa::::succinct_verify(vk, &c, &proof.z, &v, &proof.ipa) + } +} + +#[derive(Clone, Debug)] +pub struct IpaAsProof +where + C: CurveAffine, + L: Loader, + PCS: PolynomialCommitmentScheme>, +{ + a_b_u: Option<(L::LoadedScalar, L::LoadedScalar, L::LoadedEcPoint)>, + omega: Option, + alpha: L::LoadedScalar, + z: L::LoadedScalar, + ipa: IpaProof, + _marker: PhantomData, +} + +impl IpaAsProof +where + C: CurveAffine, + L: Loader, + PCS: PolynomialCommitmentScheme>, +{ + fn read( + vk: &IpaSuccinctVerifyingKey, + instances: &[PCS::Accumulator], + transcript: &mut T, + ) -> Result + where + T: TranscriptRead, + { + assert!(instances.len() > 1); + + let a_b_u = vk + .zk() + .then(|| { + let a = transcript.read_scalar()?; + let b = transcript.read_scalar()?; + let u = transcript.read_ec_point()?; + Ok((a, b, u)) + }) + .transpose()?; + let omega = vk + .zk() + .then(|| { + let omega = transcript.read_scalar()?; + Ok(omega) + }) + .transpose()?; + + for accumulator in instances { + for xi in accumulator.xi.iter() { + transcript.common_scalar(xi)?; + } + transcript.common_ec_point(&accumulator.u)?; + } + + let alpha = transcript.squeeze_challenge(); + let z = transcript.squeeze_challenge(); + + let ipa = IpaProof::read(vk, transcript)?; + + Ok(Self { + a_b_u, + omega, + alpha, + z, + ipa, + _marker: PhantomData, + }) + } +} + +impl AccumulationSchemeProver for IpaAs +where + C: CurveAffine, + PCS: PolynomialCommitmentScheme>, +{ + type ProvingKey = IpaProvingKey; + + fn create_proof( + pk: &Self::ProvingKey, + instances: &[PCS::Accumulator], + transcript: &mut T, + mut rng: R, + ) -> Result + where + T: TranscriptWrite, + R: Rng, + { + assert!(instances.len() > 1); + + let a_b_u = pk + .zk() + .then(|| { + let [a, b] = array::from_fn(|_| C::Scalar::random(&mut rng)); + let u = (pk.g[1] * a + pk.g[0] * b).to_affine(); + transcript.write_scalar(a)?; + transcript.write_scalar(b)?; + transcript.write_ec_point(u)?; + Ok((a, b, u)) + }) + .transpose()?; + let omega = pk + .zk() + .then(|| { + let omega = C::Scalar::random(&mut rng); + transcript.write_scalar(omega)?; + Ok(omega) + }) + .transpose()?; + + for accumulator in instances { + for xi in accumulator.xi.iter() { + transcript.common_scalar(xi)?; + } + transcript.common_ec_point(&accumulator.u)?; + } + + let alpha = transcript.squeeze_challenge(); + let z = transcript.squeeze_challenge(); + + let (u, h) = instances + .iter() + .map(|IpaAccumulator { u, xi }| (*u, h_coeffs(xi, C::Scalar::one()))) + .chain(a_b_u.map(|(a, b, u)| { + ( + u, + iter::empty() + .chain([b, a]) + .chain(iter::repeat_with(C::Scalar::zero).take(pk.domain.n - 2)) + .collect(), + ) + })) + .unzip::<_, _, Vec<_>, Vec<_>>(); + let powers_of_alpha = alpha.powers(u.len()); + + let h = powers_of_alpha + .into_iter() + .zip(h.into_iter().map(Polynomial::new)) + .map(|(power_of_alpha, h)| h * power_of_alpha) + .sum::>(); + + Ipa::::create_proof(pk, &h.to_vec(), &z, omega.as_ref(), transcript, &mut rng) + } +} + +#[cfg(test)] +mod test { + use crate::{ + pcs::{ + ipa::{self, IpaProvingKey}, + AccumulationScheme, AccumulationSchemeProver, Decider, + }, + util::{arithmetic::Field, msm::Msm, poly::Polynomial, Itertools}, + }; + use halo2_curves::pasta::pallas; + use halo2_proofs::transcript::{ + Blake2bRead, Blake2bWrite, TranscriptReadBuffer, TranscriptWriterBuffer, + }; + use rand::rngs::OsRng; + use std::iter; + + #[test] + fn test_ipa_as() { + type Ipa = ipa::Ipa; + type IpaAs = ipa::IpaAs; + + let k = 10; + let zk = true; + let mut rng = OsRng; + + let pk = IpaProvingKey::::rand(k, zk, &mut rng); + let accumulators = iter::repeat_with(|| { + let (c, z, v, proof) = { + let p = Polynomial::::rand(pk.domain.n, &mut rng); + let omega = pk.zk().then(|| pallas::Scalar::random(&mut rng)); + let c = pk.commit(&p, omega); + let z = pallas::Scalar::random(&mut rng); + let v = p.evaluate(z); + let mut transcript = Blake2bWrite::init(Vec::new()); + Ipa::create_proof(&pk, &p[..], &z, omega.as_ref(), &mut transcript, &mut rng) + .unwrap(); + (c, z, v, transcript.finalize()) + }; + + let svk = pk.svk(); + let accumulator = { + let mut transcript = Blake2bRead::init(proof.as_slice()); + let proof = Ipa::read_proof(&svk, &mut transcript).unwrap(); + Ipa::succinct_verify(&svk, &Msm::base(&c), &z, &v, &proof).unwrap() + }; + + accumulator + }) + .take(10) + .collect_vec(); + + let proof = { + let apk = pk.clone(); + let mut transcript = Blake2bWrite::init(Vec::new()); + IpaAs::create_proof(&apk, &accumulators, &mut transcript, &mut rng).unwrap(); + transcript.finalize() + }; + + let accumulator = { + let avk = pk.svk(); + let mut transcript = Blake2bRead::init(proof.as_slice()); + let proof = IpaAs::read_proof(&avk, &accumulators, &mut transcript).unwrap(); + IpaAs::verify(&avk, &accumulators, &proof).unwrap() + }; + + let dk = pk.dk(); + assert!(Ipa::decide(&dk, accumulator)); + } +} diff --git a/src/pcs/ipa/accumulator.rs b/src/pcs/ipa/accumulator.rs new file mode 100644 index 00000000..27d9d5c7 --- /dev/null +++ b/src/pcs/ipa/accumulator.rs @@ -0,0 +1,21 @@ +use crate::{loader::Loader, util::arithmetic::CurveAffine}; + +#[derive(Clone, Debug)] +pub struct IpaAccumulator +where + C: CurveAffine, + L: Loader, +{ + pub xi: Vec, + pub u: L::LoadedEcPoint, +} + +impl IpaAccumulator +where + C: CurveAffine, + L: Loader, +{ + pub fn new(xi: Vec, u: L::LoadedEcPoint) -> Self { + Self { xi, u } + } +} diff --git a/src/pcs/ipa/decider.rs b/src/pcs/ipa/decider.rs new file mode 100644 index 00000000..2cf8c6cc --- /dev/null +++ b/src/pcs/ipa/decider.rs @@ -0,0 +1,57 @@ +#[derive(Clone, Debug)] +pub struct IpaDecidingKey { + pub g: Vec, +} + +impl IpaDecidingKey { + pub fn new(g: Vec) -> Self { + Self { g } + } +} + +impl From> for IpaDecidingKey { + fn from(g: Vec) -> IpaDecidingKey { + IpaDecidingKey::new(g) + } +} + +mod native { + use crate::{ + loader::native::NativeLoader, + pcs::{ + ipa::{h_coeffs, Ipa, IpaAccumulator, IpaDecidingKey}, + Decider, + }, + util::{ + arithmetic::{Curve, CurveAffine, Field}, + msm::multi_scalar_multiplication, + }, + }; + use std::fmt::Debug; + + impl Decider for Ipa + where + C: CurveAffine, + MOS: Clone + Debug, + { + type DecidingKey = IpaDecidingKey; + type Output = bool; + + fn decide( + dk: &Self::DecidingKey, + IpaAccumulator { u, xi }: IpaAccumulator, + ) -> bool { + let h = h_coeffs(&xi, C::Scalar::one()); + u == multi_scalar_multiplication(&h, &dk.g).to_affine() + } + + fn decide_all( + dk: &Self::DecidingKey, + accumulators: Vec>, + ) -> bool { + !accumulators + .into_iter() + .any(|accumulator| !Self::decide(dk, accumulator)) + } + } +} diff --git a/src/pcs/ipa/multiopen.rs b/src/pcs/ipa/multiopen.rs new file mode 100644 index 00000000..9f685e76 --- /dev/null +++ b/src/pcs/ipa/multiopen.rs @@ -0,0 +1,3 @@ +mod bgh19; + +pub use bgh19::{Bgh19, Bgh19Proof, Bgh19SuccinctVerifyingKey}; diff --git a/src/pcs/ipa/multiopen/bgh19.rs b/src/pcs/ipa/multiopen/bgh19.rs new file mode 100644 index 00000000..29d291ad --- /dev/null +++ b/src/pcs/ipa/multiopen/bgh19.rs @@ -0,0 +1,417 @@ +use crate::{ + loader::{LoadedScalar, Loader, ScalarLoader}, + pcs::{ + ipa::{Ipa, IpaProof, IpaSuccinctVerifyingKey, Round}, + MultiOpenScheme, Query, + }, + util::{ + arithmetic::{ilog2, CurveAffine, Domain, FieldExt, Fraction}, + msm::Msm, + transcript::TranscriptRead, + Itertools, + }, + Error, +}; +use std::{ + collections::{BTreeMap, BTreeSet}, + iter, + marker::PhantomData, +}; + +#[derive(Clone, Debug)] +pub struct Bgh19; + +impl MultiOpenScheme for Ipa +where + C: CurveAffine, + L: Loader, +{ + type SuccinctVerifyingKey = Bgh19SuccinctVerifyingKey; + type Proof = Bgh19Proof; + + fn read_proof( + svk: &Self::SuccinctVerifyingKey, + queries: &[Query], + transcript: &mut T, + ) -> Result + where + T: TranscriptRead, + { + Bgh19Proof::read(svk, queries, transcript) + } + + fn succinct_verify( + svk: &Self::SuccinctVerifyingKey, + commitments: &[Msm], + x: &L::LoadedScalar, + queries: &[Query], + proof: &Self::Proof, + ) -> Result { + let loader = x.loader(); + let g = loader.ec_point_load_const(&svk.g); + + // Multiopen + let sets = query_sets(queries); + let p = { + let coeffs = query_set_coeffs(&sets, x, &proof.x_3); + + let powers_of_x_1 = proof + .x_1 + .powers(sets.iter().map(|set| set.polys.len()).max().unwrap()); + let f_eval = { + let powers_of_x_2 = proof.x_2.powers(sets.len()); + let f_evals = sets + .iter() + .zip(coeffs.iter()) + .zip(proof.q_evals.iter()) + .map(|((set, coeff), q_eval)| set.f_eval(coeff, q_eval, &powers_of_x_1)) + .collect_vec(); + x.loader() + .sum_products(&powers_of_x_2.iter().zip(f_evals.iter().rev()).collect_vec()) + }; + let msms = sets + .iter() + .zip(proof.q_evals.iter()) + .map(|(set, q_eval)| set.msm(commitments, q_eval, &powers_of_x_1)); + + let (mut msm, constant) = iter::once(Msm::base(&proof.f) - Msm::constant(f_eval)) + .chain(msms) + .zip(proof.x_4.powers(sets.len() + 1).into_iter().rev()) + .map(|(msm, power_of_x_4)| msm * &power_of_x_4) + .sum::>() + .split(); + if let Some(constant) = constant { + msm += Msm::base(&g) * &constant; + } + msm + }; + + // IPA + Ipa::::succinct_verify(&svk.ipa, &p, &proof.x_3, &loader.load_zero(), &proof.ipa) + } +} + +#[derive(Clone, Debug)] +pub struct Bgh19SuccinctVerifyingKey { + g: C, + ipa: IpaSuccinctVerifyingKey, +} + +impl Bgh19SuccinctVerifyingKey { + pub fn new(domain: Domain, g: C, w: C, u: C) -> Self { + Self { + g, + ipa: IpaSuccinctVerifyingKey::new(domain, u, Some(w)), + } + } +} + +#[derive(Clone, Debug)] +pub struct Bgh19Proof +where + C: CurveAffine, + L: Loader, +{ + // Multiopen + x_1: L::LoadedScalar, + x_2: L::LoadedScalar, + f: L::LoadedEcPoint, + x_3: L::LoadedScalar, + q_evals: Vec, + x_4: L::LoadedScalar, + // IPA + ipa: IpaProof, +} + +impl Bgh19Proof +where + C: CurveAffine, + L: Loader, +{ + fn read>( + svk: &Bgh19SuccinctVerifyingKey, + queries: &[Query], + transcript: &mut T, + ) -> Result { + // Multiopen + let x_1 = transcript.squeeze_challenge(); + let x_2 = transcript.squeeze_challenge(); + let f = transcript.read_ec_point()?; + let x_3 = transcript.squeeze_challenge(); + let q_evals = transcript.read_n_scalars(query_sets(queries).len())?; + let x_4 = transcript.squeeze_challenge(); + // IPA + let s = transcript.read_ec_point()?; + let xi = transcript.squeeze_challenge(); + let z = transcript.squeeze_challenge(); + let rounds = iter::repeat_with(|| { + Ok(Round::new( + transcript.read_ec_point()?, + transcript.read_ec_point()?, + transcript.squeeze_challenge(), + )) + }) + .take(svk.ipa.domain.k) + .collect::, _>>()?; + let c = transcript.read_scalar()?; + let blind = transcript.read_scalar()?; + let g = transcript.read_ec_point()?; + Ok(Bgh19Proof { + x_1, + x_2, + f, + x_3, + q_evals, + x_4, + ipa: IpaProof::new(Some((s, xi)), Some(blind), z, rounds, g, c), + }) + } +} + +fn query_sets(queries: &[Query]) -> Vec> +where + F: FieldExt, + T: Clone, +{ + let poly_shifts = queries.iter().fold( + Vec::<(usize, Vec, Vec<&T>)>::new(), + |mut poly_shifts, query| { + if let Some(pos) = poly_shifts + .iter() + .position(|(poly, _, _)| *poly == query.poly) + { + let (_, shifts, evals) = &mut poly_shifts[pos]; + if !shifts.contains(&query.shift) { + shifts.push(query.shift); + evals.push(&query.eval); + } + } else { + poly_shifts.push((query.poly, vec![query.shift], vec![&query.eval])); + } + poly_shifts + }, + ); + + poly_shifts.into_iter().fold( + Vec::>::new(), + |mut sets, (poly, shifts, evals)| { + if let Some(pos) = sets.iter().position(|set| { + BTreeSet::from_iter(set.shifts.iter()) == BTreeSet::from_iter(shifts.iter()) + }) { + let set = &mut sets[pos]; + if !set.polys.contains(&poly) { + set.polys.push(poly); + set.evals.push( + set.shifts + .iter() + .map(|lhs| { + let idx = shifts.iter().position(|rhs| lhs == rhs).unwrap(); + evals[idx] + }) + .collect(), + ); + } + } else { + let set = QuerySet { + shifts, + polys: vec![poly], + evals: vec![evals], + }; + sets.push(set); + } + sets + }, + ) +} + +fn query_set_coeffs(sets: &[QuerySet], x: &T, x_3: &T) -> Vec> +where + F: FieldExt, + T: LoadedScalar, +{ + let loader = x.loader(); + let superset = sets + .iter() + .flat_map(|set| set.shifts.clone()) + .sorted() + .dedup(); + + let size = 2.max( + ilog2((sets.iter().map(|set| set.shifts.len()).max().unwrap() - 1).next_power_of_two()) + 1, + ); + let powers_of_x = x.powers(size); + let x_3_minus_x_shift_i = BTreeMap::from_iter( + superset.map(|shift| (shift, x_3.clone() - x.clone() * loader.load_const(&shift))), + ); + + let mut coeffs = sets + .iter() + .map(|set| QuerySetCoeff::new(&set.shifts, &powers_of_x, x_3, &x_3_minus_x_shift_i)) + .collect_vec(); + + T::Loader::batch_invert(coeffs.iter_mut().flat_map(QuerySetCoeff::denoms)); + T::Loader::batch_invert(coeffs.iter_mut().flat_map(QuerySetCoeff::denoms)); + coeffs.iter_mut().for_each(QuerySetCoeff::evaluate); + + coeffs +} + +#[derive(Clone, Debug)] +struct QuerySet<'a, F, T> { + shifts: Vec, + polys: Vec, + evals: Vec>, +} + +impl<'a, F, T> QuerySet<'a, F, T> +where + F: FieldExt, + T: LoadedScalar, +{ + fn msm>( + &self, + commitments: &[Msm<'a, C, L>], + q_eval: &T, + powers_of_x_1: &[T], + ) -> Msm { + self.polys + .iter() + .rev() + .zip(powers_of_x_1) + .map(|(poly, power_of_x_1)| commitments[*poly].clone() * power_of_x_1) + .sum::>() + - Msm::constant(q_eval.clone()) + } + + fn f_eval(&self, coeff: &QuerySetCoeff, q_eval: &T, powers_of_x_1: &[T]) -> T { + let loader = q_eval.loader(); + let r_eval = { + let r_evals = self + .evals + .iter() + .map(|evals| { + loader.sum_products( + &coeff + .eval_coeffs + .iter() + .zip(evals.iter()) + .map(|(coeff, eval)| (coeff.evaluated(), *eval)) + .collect_vec(), + ) * coeff.r_eval_coeff.as_ref().unwrap().evaluated() + }) + .collect_vec(); + loader.sum_products(&r_evals.iter().rev().zip(powers_of_x_1).collect_vec()) + }; + + (q_eval.clone() - r_eval) * coeff.f_eval_coeff.evaluated() + } +} + +#[derive(Clone, Debug)] +struct QuerySetCoeff { + eval_coeffs: Vec>, + r_eval_coeff: Option>, + f_eval_coeff: Fraction, + _marker: PhantomData, +} + +impl QuerySetCoeff +where + F: FieldExt, + T: LoadedScalar, +{ + fn new(shifts: &[F], powers_of_x: &[T], x_3: &T, x_3_minus_x_shift_i: &BTreeMap) -> Self { + let loader = x_3.loader(); + let normalized_ell_primes = shifts + .iter() + .enumerate() + .map(|(j, shift_j)| { + shifts + .iter() + .enumerate() + .filter(|&(i, _)| i != j) + .map(|(_, shift_i)| (*shift_j - shift_i)) + .reduce(|acc, value| acc * value) + .unwrap_or_else(|| F::one()) + }) + .collect_vec(); + + let x = &powers_of_x[1].clone(); + let x_pow_k_minus_one = { + let k_minus_one = shifts.len() - 1; + powers_of_x + .iter() + .enumerate() + .skip(1) + .filter_map(|(i, power_of_x)| { + (k_minus_one & (1 << i) == 1).then(|| power_of_x.clone()) + }) + .reduce(|acc, value| acc * value) + .unwrap_or_else(|| loader.load_one()) + }; + + let barycentric_weights = shifts + .iter() + .zip(normalized_ell_primes.iter()) + .map(|(shift, normalized_ell_prime)| { + loader.sum_products_with_coeff(&[ + (*normalized_ell_prime, &x_pow_k_minus_one, x_3), + (-(*normalized_ell_prime * shift), &x_pow_k_minus_one, x), + ]) + }) + .map(Fraction::one_over) + .collect_vec(); + + let f_eval_coeff = Fraction::one_over( + loader.product( + &shifts + .iter() + .map(|shift| x_3_minus_x_shift_i.get(shift).unwrap()) + .collect_vec(), + ), + ); + + Self { + eval_coeffs: barycentric_weights, + r_eval_coeff: None, + f_eval_coeff, + _marker: PhantomData, + } + } + + fn denoms(&mut self) -> impl IntoIterator { + if self.eval_coeffs.first().unwrap().denom().is_some() { + return self + .eval_coeffs + .iter_mut() + .chain(Some(&mut self.f_eval_coeff)) + .filter_map(Fraction::denom_mut) + .collect_vec(); + } + + if self.r_eval_coeff.is_none() { + self.eval_coeffs + .iter_mut() + .chain(Some(&mut self.f_eval_coeff)) + .for_each(Fraction::evaluate); + + let loader = self.f_eval_coeff.evaluated().loader(); + let barycentric_weights_sum = loader.sum( + &self + .eval_coeffs + .iter() + .map(Fraction::evaluated) + .collect_vec(), + ); + self.r_eval_coeff = Some(Fraction::one_over(barycentric_weights_sum)); + + return vec![self.r_eval_coeff.as_mut().unwrap().denom_mut().unwrap()]; + } + + unreachable!() + } + + fn evaluate(&mut self) { + self.r_eval_coeff.as_mut().unwrap().evaluate(); + } +} diff --git a/src/system/halo2.rs b/src/system/halo2.rs index a49fa6ef..7c26387d 100644 --- a/src/system/halo2.rs +++ b/src/system/halo2.rs @@ -16,6 +16,7 @@ use halo2_proofs::{ use num_integer::Integer; use std::{io, iter, mem::size_of}; +pub mod strategy; pub mod transcript; #[cfg(test)] diff --git a/src/system/halo2/strategy.rs b/src/system/halo2/strategy.rs new file mode 100644 index 00000000..de66f8e3 --- /dev/null +++ b/src/system/halo2/strategy.rs @@ -0,0 +1,53 @@ +pub mod ipa { + use crate::util::arithmetic::CurveAffine; + use halo2_proofs::{ + plonk::Error, + poly::{ + commitment::MSM, + ipa::{ + commitment::{IPACommitmentScheme, ParamsIPA}, + msm::MSMIPA, + multiopen::VerifierIPA, + strategy::GuardIPA, + }, + VerificationStrategy, + }, + }; + + #[derive(Clone, Debug)] + pub struct SingleStrategy<'a, C: CurveAffine> { + msm: MSMIPA<'a, C>, + } + + impl<'a, C: CurveAffine> VerificationStrategy<'a, IPACommitmentScheme, VerifierIPA<'a, C>> + for SingleStrategy<'a, C> + { + type Output = C; + + fn new(params: &'a ParamsIPA) -> Self { + SingleStrategy { + msm: MSMIPA::new(params), + } + } + + fn process( + self, + f: impl FnOnce(MSMIPA<'a, C>) -> Result, Error>, + ) -> Result { + let guard = f(self.msm)?; + + let g = guard.compute_g(); + let (msm, _) = guard.use_g(g); + + if msm.check() { + Ok(g) + } else { + Err(Error::ConstraintSystemFailure) + } + } + + fn finalize(self) -> bool { + unreachable!() + } + } +} diff --git a/src/system/halo2/test.rs b/src/system/halo2/test.rs index 9cd4a2fc..1ec03306 100644 --- a/src/system/halo2/test.rs +++ b/src/system/halo2/test.rs @@ -12,6 +12,7 @@ use rand_chacha::rand_core::RngCore; use std::{fs, io::Cursor}; mod circuit; +mod ipa; mod kzg; pub use circuit::{ diff --git a/src/system/halo2/test/ipa.rs b/src/system/halo2/test/ipa.rs new file mode 100644 index 00000000..07fd6efd --- /dev/null +++ b/src/system/halo2/test/ipa.rs @@ -0,0 +1,143 @@ +use crate::util::arithmetic::CurveAffine; +use halo2_proofs::poly::{ + commitment::{Params, ParamsProver}, + ipa::commitment::ParamsIPA, +}; +use std::mem::size_of; + +mod native; + +pub const TESTDATA_DIR: &str = "./src/system/halo2/test/ipa/testdata"; + +pub fn setup(k: u32) -> ParamsIPA { + ParamsIPA::new(k) +} + +pub fn w_u() -> (C, C) { + let mut buf = Vec::new(); + setup::(1).write(&mut buf).unwrap(); + + let repr = C::Repr::default(); + let repr_len = repr.as_ref().len(); + let offset = size_of::() + 4 * repr_len; + + let [w, u] = [offset, offset + repr_len].map(|offset| { + let mut repr = C::Repr::default(); + repr.as_mut() + .copy_from_slice(&buf[offset..offset + repr_len]); + C::from_bytes(&repr).unwrap() + }); + + (w, u) +} + +macro_rules! halo2_ipa_config { + ($zk:expr, $num_proof:expr) => { + $crate::system::halo2::Config::ipa() + .set_zk($zk) + .with_num_proof($num_proof) + }; + ($zk:expr, $num_proof:expr, $accumulator_indices:expr) => { + $crate::system::halo2::Config::ipa() + .set_zk($zk) + .with_num_proof($num_proof) + .with_accumulator_indices($accumulator_indices) + }; +} + +macro_rules! halo2_ipa_prepare { + ($dir:expr, $curve:path, $k:expr, $config:expr, $create_circuit:expr) => {{ + use $crate::system::halo2::test::{halo2_prepare, ipa::setup}; + + halo2_prepare!($dir, $k, setup::<$curve>, $config, $create_circuit) + }}; + (pallas::Affine, $k:expr, $config:expr, $create_circuit:expr) => {{ + use halo2_curves::pasta::pallas; + use $crate::system::halo2::test::ipa::TESTDATA_DIR; + + halo2_ipa_prepare!( + &format!("{TESTDATA_DIR}/pallas"), + pallas::Affine, + $k, + $config, + $create_circuit + ) + }}; + (vesta::Affine, $k:expr, $config:expr, $create_circuit:expr) => {{ + use halo2_curves::pasta::vesta; + use $crate::system::halo2::test::ipa::TESTDATA_DIR; + + halo2_ipa_prepare!( + &format!("{TESTDATA_DIR}/vesta"), + vesta::Affine, + $k, + $config, + $create_circuit + ) + }}; +} + +macro_rules! halo2_ipa_create_snark { + ( + $prover:ty, + $verifier:ty, + $transcript_read:ty, + $transcript_write:ty, + $encoded_challenge:ty, + $params:expr, + $pk:expr, + $protocol:expr, + $circuits:expr + ) => {{ + use halo2_proofs::poly::ipa::commitment::IPACommitmentScheme; + use $crate::{ + system::halo2::{strategy::ipa::SingleStrategy, test::halo2_create_snark}, + util::arithmetic::GroupEncoding, + }; + + halo2_create_snark!( + IPACommitmentScheme<_>, + $prover, + $verifier, + SingleStrategy<_>, + $transcript_read, + $transcript_write, + $encoded_challenge, + |proof, g| { [proof, g.to_bytes().as_ref().to_vec()].concat() }, + $params, + $pk, + $protocol, + $circuits + ) + }}; +} + +macro_rules! halo2_ipa_native_verify { + ( + $plonk_verifier:ty, + $params:expr, + $protocol:expr, + $instances:expr, + $transcript:expr + ) => {{ + use $crate::{ + pcs::ipa::{Bgh19SuccinctVerifyingKey, IpaDecidingKey}, + system::halo2::test::{halo2_native_verify, ipa::w_u}, + }; + + let (w, u) = w_u(); + halo2_native_verify!( + $plonk_verifier, + $params, + $protocol, + $instances, + $transcript, + &Bgh19SuccinctVerifyingKey::new($protocol.domain.clone(), $params.get_g()[0], w, u), + &IpaDecidingKey::new($params.get_g().to_vec()) + ) + }}; +} + +pub(crate) use { + halo2_ipa_config, halo2_ipa_create_snark, halo2_ipa_native_verify, halo2_ipa_prepare, +}; diff --git a/src/system/halo2/test/ipa/native.rs b/src/system/halo2/test/ipa/native.rs new file mode 100644 index 00000000..7d9e09bb --- /dev/null +++ b/src/system/halo2/test/ipa/native.rs @@ -0,0 +1,59 @@ +use crate::{ + pcs::ipa::{Bgh19, Ipa}, + system::halo2::test::ipa::{ + halo2_ipa_config, halo2_ipa_create_snark, halo2_ipa_native_verify, halo2_ipa_prepare, + }, + system::halo2::test::StandardPlonk, + verifier::Plonk, +}; +use halo2_curves::pasta::pallas; +use halo2_proofs::{ + poly::ipa::multiopen::{ProverIPA, VerifierIPA}, + transcript::{Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer}, +}; +use paste::paste; +use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; + +macro_rules! test { + (@ $name:ident, $k:expr, $config:expr, $create_cirucit:expr, $prover:ty, $verifier:ty, $plonk_verifier:ty) => { + paste! { + #[test] + fn []() { + let (params, pk, protocol, circuits) = halo2_ipa_prepare!( + pallas::Affine, + $k, + $config, + $create_cirucit + ); + let snark = halo2_ipa_create_snark!( + $prover, + $verifier, + Blake2bWrite<_, _, _>, + Blake2bRead<_, _, _>, + Challenge255<_>, + ¶ms, + &pk, + &protocol, + &circuits + ); + halo2_ipa_native_verify!( + $plonk_verifier, + params, + &snark.protocol, + &snark.instances, + &mut Blake2bRead::<_, pallas::Affine, _>::init(snark.proof.as_slice()) + ); + } + } + }; + ($name:ident, $k:expr, $config:expr, $create_cirucit:expr) => { + test!(@ $name, $k, $config, $create_cirucit, ProverIPA, VerifierIPA, Plonk::>); + } +} + +test!( + zk_standard_plonk_rand, + 9, + halo2_ipa_config!(true, 1), + StandardPlonk::rand(ChaCha20Rng::from_seed(Default::default())) +); diff --git a/src/system/halo2/test/kzg/halo2.rs b/src/system/halo2/test/kzg/halo2.rs index 11a6046f..314af5c7 100644 --- a/src/system/halo2/test/kzg/halo2.rs +++ b/src/system/halo2/test/kzg/halo2.rs @@ -1,6 +1,6 @@ use crate::{ - loader, loader::{ + self, halo2::test::{Snark, SnarkWitness}, native::NativeLoader, }, @@ -30,8 +30,7 @@ use crate::{ use halo2_curves::bn256::{Bn256, Fq, Fr, G1Affine}; use halo2_proofs::{ circuit::{floor_planner::V1, Layouter, Value}, - plonk, - plonk::{Circuit, Error}, + plonk::{Circuit, ConstraintSystem, Error}, poly::{ commitment::ParamsProver, kzg::{ @@ -263,7 +262,7 @@ impl Circuit for Accumulation { } } - fn configure(meta: &mut plonk::ConstraintSystem) -> Self::Config { + fn configure(meta: &mut ConstraintSystem) -> Self::Config { MainGateWithRangeConfig::configure( meta, vec![BITS / LIMBS], @@ -275,7 +274,7 @@ impl Circuit for Accumulation { &self, config: Self::Config, mut layouter: impl Layouter, - ) -> Result<(), plonk::Error> { + ) -> Result<(), Error> { let main_gate = config.main_gate(); let range_chip = config.range_chip(); diff --git a/src/util.rs b/src/util.rs index 3d5d0d79..b42db61c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,47 @@ pub mod arithmetic; pub mod hash; pub mod msm; +pub mod poly; pub mod protocol; pub mod transcript; pub(crate) use itertools::Itertools; + +#[cfg(feature = "parallel")] +pub(crate) use rayon::current_num_threads; + +pub fn parallelize_iter(iter: I, f: F) +where + I: Send + Iterator, + T: Send, + F: Fn(T) + Send + Sync + Clone, +{ + #[cfg(feature = "parallel")] + rayon::scope(|scope| { + for item in iter { + let f = f.clone(); + scope.spawn(move |_| f(item)); + } + }); + #[cfg(not(feature = "parallel"))] + iter.for_each(f); +} + +pub fn parallelize(v: &mut [T], f: F) +where + T: Send, + F: Fn((&mut [T], usize)) + Send + Sync + Clone, +{ + #[cfg(feature = "parallel")] + { + let num_threads = current_num_threads(); + let chunk_size = v.len() / num_threads; + if chunk_size < num_threads { + f((v, 0)); + } else { + parallelize_iter(v.chunks_mut(chunk_size).zip((0..).step_by(chunk_size)), f); + } + } + #[cfg(not(feature = "parallel"))] + f((v, 0)); +} diff --git a/src/util/arithmetic.rs b/src/util/arithmetic.rs index 02d6da32..dacd2443 100644 --- a/src/util/arithmetic.rs +++ b/src/util/arithmetic.rs @@ -250,3 +250,15 @@ pub fn fe_to_limbs(scalar: F) -> impl Iterator { + iter::successors(Some(F::one()), move |power| Some(scalar * power)) +} + +pub fn inner_product(lhs: &[F], rhs: &[F]) -> F { + lhs.iter() + .zip_eq(rhs.iter()) + .map(|(lhs, rhs)| *lhs * rhs) + .reduce(|acc, product| acc + product) + .unwrap_or_default() +} diff --git a/src/util/msm.rs b/src/util/msm.rs index f35ea197..014a29e8 100644 --- a/src/util/msm.rs +++ b/src/util/msm.rs @@ -1,10 +1,15 @@ use crate::{ loader::{LoadedEcPoint, Loader}, - util::{arithmetic::CurveAffine, Itertools}, + util::{ + arithmetic::{CurveAffine, Group, PrimeField}, + Itertools, + }, }; +use num_integer::Integer; use std::{ default::Default, iter::{self, Sum}, + mem::size_of, ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, }; @@ -209,3 +214,119 @@ where iter.reduce(|acc, item| acc + item).unwrap_or_default() } } + +#[derive(Clone, Copy)] +enum Bucket { + None, + Affine(C), + Projective(C::Curve), +} + +impl Bucket { + fn add_assign(&mut self, rhs: &C) { + *self = match *self { + Bucket::None => Bucket::Affine(*rhs), + Bucket::Affine(lhs) => Bucket::Projective(lhs + *rhs), + Bucket::Projective(mut lhs) => { + lhs += *rhs; + Bucket::Projective(lhs) + } + } + } + + fn add(self, mut rhs: C::Curve) -> C::Curve { + match self { + Bucket::None => rhs, + Bucket::Affine(lhs) => { + rhs += lhs; + rhs + } + Bucket::Projective(lhs) => lhs + rhs, + } + } +} + +fn multi_scalar_multiplication_serial( + scalars: &[C::Scalar], + bases: &[C], + result: &mut C::Curve, +) { + let scalars = scalars.iter().map(|scalar| scalar.to_repr()).collect_vec(); + let num_bytes = scalars[0].as_ref().len(); + let num_bits = 8 * num_bytes; + + let window_size = (scalars.len() as f64).ln().ceil() as usize + 2; + let num_buckets = (1 << window_size) - 1; + + let windowed_scalar = |idx: usize, bytes: &::Repr| { + let skip_bits = idx * window_size; + let skip_bytes = skip_bits / 8; + + let mut value = [0; size_of::()]; + for (dst, src) in value.iter_mut().zip(bytes.as_ref()[skip_bytes..].iter()) { + *dst = *src; + } + + (usize::from_le_bytes(value) >> (skip_bits - (skip_bytes * 8))) & num_buckets + }; + + let num_window = Integer::div_ceil(&num_bits, &window_size); + for idx in (0..num_window).rev() { + for _ in 0..window_size { + *result = result.double(); + } + + let mut buckets = vec![Bucket::None; num_buckets]; + + for (scalar, base) in scalars.iter().zip(bases.iter()) { + let scalar = windowed_scalar(idx, scalar); + if scalar != 0 { + buckets[scalar - 1].add_assign(base); + } + } + + let mut running_sum = C::Curve::identity(); + for bucket in buckets.into_iter().rev() { + running_sum = bucket.add(running_sum); + *result += &running_sum; + } + } +} + +// Copy from https://github.com/zcash/halo2/blob/main/halo2_proofs/src/arithmetic.rs +pub fn multi_scalar_multiplication(scalars: &[C::Scalar], bases: &[C]) -> C::Curve { + assert_eq!(scalars.len(), bases.len()); + + #[cfg(feature = "parallel")] + { + use crate::util::{current_num_threads, parallelize_iter}; + + let num_threads = current_num_threads(); + if scalars.len() < num_threads { + let mut result = C::Curve::identity(); + multi_scalar_multiplication_serial(scalars, bases, &mut result); + return result; + } + + let chunk_size = Integer::div_ceil(&scalars.len(), &num_threads); + let mut results = vec![C::Curve::identity(); num_threads]; + parallelize_iter( + scalars + .chunks(chunk_size) + .zip(bases.chunks(chunk_size)) + .zip(results.iter_mut()), + |((scalars, bases), result)| { + multi_scalar_multiplication_serial(scalars, bases, result); + }, + ); + results + .iter() + .fold(C::Curve::identity(), |acc, result| acc + result) + } + #[cfg(not(feature = "parallel"))] + { + let mut result = C::Curve::identity(); + multi_scalar_multiplication_serial(scalars, bases, &mut result); + result + } +} diff --git a/src/util/poly.rs b/src/util/poly.rs new file mode 100644 index 00000000..ea120b33 --- /dev/null +++ b/src/util/poly.rs @@ -0,0 +1,175 @@ +use crate::util::{arithmetic::Field, parallelize}; +use rand::Rng; +use std::{ + iter::{self, Sum}, + ops::{ + Add, Index, IndexMut, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, + RangeToInclusive, Sub, + }, +}; + +#[derive(Clone, Debug)] +pub struct Polynomial(Vec); + +impl Polynomial { + pub fn new(inner: Vec) -> Self { + Self(inner) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.0.iter_mut() + } + + pub fn to_vec(self) -> Vec { + self.0 + } +} + +impl Polynomial { + pub fn rand(n: usize, mut rng: R) -> Self { + Self::new(iter::repeat_with(|| F::random(&mut rng)).take(n).collect()) + } + + pub fn evaluate(&self, x: F) -> F { + let evaluate_serial = |coeffs: &[F]| { + coeffs + .iter() + .rev() + .fold(F::zero(), |acc, coeff| acc * x + coeff) + }; + + #[cfg(feature = "parallel")] + { + use crate::util::{arithmetic::powers, current_num_threads, parallelize_iter}; + use num_integer::Integer; + + let num_threads = current_num_threads(); + if self.len() * 2 < num_threads { + return evaluate_serial(&self.0); + } + + let chunk_size = Integer::div_ceil(&self.len(), &num_threads); + let mut results = vec![F::zero(); num_threads]; + parallelize_iter( + results + .iter_mut() + .zip(self.0.chunks(chunk_size)) + .zip(powers(x.pow_vartime(&[chunk_size as u64, 0, 0, 0]))), + |((result, coeffs), scalar)| *result = evaluate_serial(coeffs) * scalar, + ); + results.iter().fold(F::zero(), |acc, result| acc + result) + } + #[cfg(not(feature = "parallel"))] + evaluate_serial(&self.0) + } +} + +impl<'a, F: Field> Add<&'a Polynomial> for Polynomial { + type Output = Polynomial; + + fn add(mut self, rhs: &'a Polynomial) -> Polynomial { + parallelize(&mut self.0, |(lhs, start)| { + for (lhs, rhs) in lhs.iter_mut().zip(rhs.0[start..].iter()) { + *lhs += *rhs; + } + }); + self + } +} + +impl<'a, F: Field> Sub<&'a Polynomial> for Polynomial { + type Output = Polynomial; + + fn sub(mut self, rhs: &'a Polynomial) -> Polynomial { + parallelize(&mut self.0, |(lhs, start)| { + for (lhs, rhs) in lhs.iter_mut().zip(rhs.0[start..].iter()) { + *lhs -= *rhs; + } + }); + self + } +} + +impl Sub for Polynomial { + type Output = Polynomial; + + fn sub(mut self, rhs: F) -> Polynomial { + self.0[0] -= rhs; + self + } +} + +impl Add for Polynomial { + type Output = Polynomial; + + fn add(mut self, rhs: F) -> Polynomial { + self.0[0] += rhs; + self + } +} + +impl Mul for Polynomial { + type Output = Polynomial; + + fn mul(mut self, rhs: F) -> Polynomial { + if rhs == F::zero() { + return Polynomial::new(vec![F::zero(); self.len()]); + } + if rhs == F::one() { + return self; + } + parallelize(&mut self.0, |(lhs, _)| { + for lhs in lhs.iter_mut() { + *lhs *= rhs; + } + }); + self + } +} + +impl Sum for Polynomial { + fn sum>(iter: I) -> Self { + iter.reduce(|acc, item| acc + &item).unwrap() + } +} + +macro_rules! impl_index { + ($($range:ty => $output:ty,)*) => { + $( + impl Index<$range> for Polynomial { + type Output = $output; + + fn index(&self, index: $range) -> &$output { + self.0.index(index) + } + } + impl IndexMut<$range> for Polynomial { + fn index_mut(&mut self, index: $range) -> &mut $output { + self.0.index_mut(index) + } + } + )* + }; +} + +impl_index!( + usize => F, + Range => [F], + RangeFrom => [F], + RangeFull => [F], + RangeInclusive => [F], + RangeTo => [F], + RangeToInclusive => [F], +);