diff --git a/benchmark/benches/proof_system.rs b/benchmark/benches/proof_system.rs index 6cef79ef..99000df6 100644 --- a/benchmark/benches/proof_system.rs +++ b/benchmark/benches/proof_system.rs @@ -50,8 +50,8 @@ fn bench_hyperplonk>(k: usize) { let circuit = C::rand(k, std_rng()); let circuit = Halo2Circuit::new::(k, circuit); - let instances = circuit.instance_slices(); let circuit_info = circuit.circuit_info().unwrap(); + let instances = circuit.instances(); let timer = start_timer(|| format!("hyperplonk_setup-{k}")); let param = HyperPlonk::setup(&circuit_info, std_rng()).unwrap(); @@ -64,14 +64,14 @@ fn bench_hyperplonk>(k: usize) { let proof = sample(System::HyperPlonk, k, || { let _timer = start_timer(|| format!("hyperplonk_prove-{k}")); let mut transcript = Keccak256Transcript::default(); - HyperPlonk::prove(&pp, (), &instances, &circuit, &mut transcript, std_rng()).unwrap(); + HyperPlonk::prove(&pp, &circuit, &mut transcript, std_rng()).unwrap(); transcript.into_proof() }); let _timer = start_timer(|| format!("hyperplonk_verify-{k}")); let accept = { - let mut transcript = Keccak256Transcript::from_proof(proof.as_slice()); - HyperPlonk::verify(&vp, (), &instances, &mut transcript, std_rng()).is_ok() + let mut transcript = Keccak256Transcript::from_proof((), proof.as_slice()); + HyperPlonk::verify(&vp, instances, &mut transcript, std_rng()).is_ok() }; assert!(accept); } diff --git a/benchmark/src/bin/plotter.rs b/benchmark/src/bin/plotter.rs index 7fe8e62f..84483a52 100644 --- a/benchmark/src/bin/plotter.rs +++ b/benchmark/src/bin/plotter.rs @@ -339,6 +339,10 @@ impl Log { let mut stack = Vec::new(); logs.iter().fold(Vec::new(), |mut logs, log| { let (indent, log) = log.rsplit_once('ยท').unwrap_or(("", log)); + if log.len() < 9 { + return logs; + } + let (prefix, log) = log.split_at(9); let depth = (indent.len() + 2) / 4; if depth == stack.len() && prefix.starts_with("Start:") { diff --git a/benchmark/src/halo2/circuit.rs b/benchmark/src/halo2/circuit.rs index 208fd8e9..15483bc1 100644 --- a/benchmark/src/halo2/circuit.rs +++ b/benchmark/src/halo2/circuit.rs @@ -380,10 +380,6 @@ mod aggregation { } } - fn num_instances() -> Vec { - vec![4 * LIMBS] - } - fn instances(&self) -> Vec> { vec![self.instances.clone()] } @@ -476,10 +472,6 @@ mod sha256 { Self { input_size } } - fn num_instances() -> Vec { - Vec::new() - } - fn instances(&self) -> Vec> { Vec::new() } diff --git a/plonkish_backend/Cargo.toml b/plonkish_backend/Cargo.toml index e6bb29ca..85761157 100644 --- a/plonkish_backend/Cargo.toml +++ b/plonkish_backend/Cargo.toml @@ -4,12 +4,18 @@ version = "0.1.0" edition = "2021" [dependencies] -halo2_curves = { git = "https://github.com/privacy-scaling-explorations/halo2curves", tag = "0.3.3", package = "halo2curves" } +halo2_curves = { git = "https://github.com/privacy-scaling-explorations/halo2curves", tag = "0.3.3", package = "halo2curves", features = ["derive_serde"] } +pasta_curves = { version = "0.5.0", features = ["serde"] } +generic-array = { version = "0.14.7", features = ["serde"] } +bitvec = "1.0.1" itertools = "0.10.5" num-bigint = "0.4.3" num-integer = "0.1.45" rand = "0.8" +serde = { version = "1.0", features = ["derive"] } +bincode = "1.3.3" sha3 = "0.10.6" +poseidon = { git = "https://github.com/han0110/poseidon", branch = "feature/with-spec" } # timer ark-std = { version = "^0.4.0", default-features = false, optional = true } diff --git a/plonkish_backend/src/accumulation.rs b/plonkish_backend/src/accumulation.rs new file mode 100644 index 00000000..12ca8ca6 --- /dev/null +++ b/plonkish_backend/src/accumulation.rs @@ -0,0 +1,262 @@ +use crate::{ + backend::{PlonkishCircuit, PlonkishCircuitInfo}, + pcs::{CommitmentChunk, PolynomialCommitmentScheme}, + util::{ + arithmetic::Field, + transcript::{TranscriptRead, TranscriptWrite}, + DeserializeOwned, Serialize, + }, + Error, +}; +use rand::RngCore; +use std::{borrow::BorrowMut, fmt::Debug}; + +pub mod protostar; +pub mod sangria; + +pub trait AccumulationScheme: Clone + Debug { + type Pcs: PolynomialCommitmentScheme; + type ProverParam: Debug + Serialize + DeserializeOwned; + type VerifierParam: Debug + Serialize + DeserializeOwned; + type Accumulator: Debug + AsRef; + type AccumulatorInstance: Clone + Debug + Serialize + DeserializeOwned; + + fn setup( + circuit_info: &PlonkishCircuitInfo, + rng: impl RngCore, + ) -> Result<>::Param, Error>; + + fn preprocess( + param: &>::Param, + circuit_info: &PlonkishCircuitInfo, + ) -> Result<(Self::ProverParam, Self::VerifierParam), Error>; + + fn init_accumulator(pp: &Self::ProverParam) -> Result; + + fn init_accumulator_from_nark( + pp: &Self::ProverParam, + nark: PlonkishNark, + ) -> Result; + + fn prove_nark( + pp: &Self::ProverParam, + circuit: &impl PlonkishCircuit, + transcript: &mut impl TranscriptWrite, F>, + rng: impl RngCore, + ) -> Result, Error>; + + fn prove_accumulation( + pp: &Self::ProverParam, + accumulator: impl BorrowMut, + incoming: &Self::Accumulator, + transcript: &mut impl TranscriptWrite, F>, + rng: impl RngCore, + ) -> Result<(), Error>; + + fn prove_accumulation_from_nark( + pp: &Self::ProverParam, + accumulator: impl BorrowMut, + circuit: &impl PlonkishCircuit, + transcript: &mut impl TranscriptWrite, F>, + mut rng: impl RngCore, + ) -> Result<(), Error> { + let nark = Self::prove_nark(pp, circuit, transcript, &mut rng)?; + let incoming = Self::init_accumulator_from_nark(pp, nark)?; + Self::prove_accumulation::(pp, accumulator, &incoming, transcript, &mut rng)?; + Ok(()) + } + + fn verify_accumulation_from_nark( + vp: &Self::VerifierParam, + accumulator: impl BorrowMut, + instances: &[Vec], + transcript: &mut impl TranscriptRead, F>, + rng: impl RngCore, + ) -> Result<(), Error>; + + fn prove_decider( + pp: &Self::ProverParam, + accumulator: &Self::Accumulator, + transcript: &mut impl TranscriptWrite, F>, + rng: impl RngCore, + ) -> Result<(), Error>; + + fn prove_decider_with_last_nark( + pp: &Self::ProverParam, + mut accumulator: impl BorrowMut, + circuit: &impl PlonkishCircuit, + transcript: &mut impl TranscriptWrite, F>, + mut rng: impl RngCore, + ) -> Result<(), Error> { + Self::prove_accumulation_from_nark( + pp, + accumulator.borrow_mut(), + circuit, + transcript, + &mut rng, + )?; + Self::prove_decider(pp, accumulator.borrow(), transcript, &mut rng)?; + Ok(()) + } + + fn verify_decider( + vp: &Self::VerifierParam, + accumulator: &Self::AccumulatorInstance, + transcript: &mut impl TranscriptRead, F>, + rng: impl RngCore, + ) -> Result<(), Error>; + + fn verify_decider_with_last_nark( + vp: &Self::VerifierParam, + mut accumulator: impl BorrowMut, + instances: &[Vec], + transcript: &mut impl TranscriptRead, F>, + mut rng: impl RngCore, + ) -> Result<(), Error> { + Self::verify_accumulation_from_nark( + vp, + accumulator.borrow_mut(), + instances, + transcript, + &mut rng, + )?; + Self::verify_decider(vp, accumulator.borrow(), transcript, &mut rng)?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct PlonkishNark +where + F: Field, + Pcs: PolynomialCommitmentScheme, +{ + instance: PlonkishNarkInstance, + witness_polys: Vec, +} + +impl PlonkishNark +where + F: Field, + Pcs: PolynomialCommitmentScheme, +{ + fn new( + instances: Vec>, + challenges: Vec, + witness_comms: Vec, + witness_polys: Vec, + ) -> Self { + Self { + instance: PlonkishNarkInstance::new(instances, challenges, witness_comms), + witness_polys, + } + } +} + +#[derive(Clone, Debug)] +pub struct PlonkishNarkInstance { + instances: Vec>, + challenges: Vec, + witness_comms: Vec, +} + +impl PlonkishNarkInstance { + fn new(instances: Vec>, challenges: Vec, witness_comms: Vec) -> Self { + Self { + instances, + challenges, + witness_comms, + } + } +} + +#[cfg(test)] +pub(crate) mod test { + use crate::{ + accumulation::AccumulationScheme, + backend::{PlonkishCircuit, PlonkishCircuitInfo}, + pcs::PolynomialCommitmentScheme, + util::{ + arithmetic::PrimeField, + end_timer, start_timer, + test::seeded_std_rng, + transcript::{InMemoryTranscript, TranscriptRead, TranscriptWrite}, + DeserializeOwned, Serialize, + }, + }; + use std::{hash::Hash, ops::Range}; + + pub(crate) fn run_accumulation_scheme( + num_vars_range: Range, + circuit_fn: impl Fn(usize) -> (PlonkishCircuitInfo, Vec), + ) where + F: PrimeField + Hash + Serialize + DeserializeOwned, + Fs: AccumulationScheme, + T: TranscriptRead<>::CommitmentChunk, F> + + TranscriptWrite<>::CommitmentChunk, F> + + InMemoryTranscript, + C: PlonkishCircuit, + { + for num_vars in num_vars_range { + let (circuit_info, circuits) = circuit_fn(num_vars); + let last_circuit = circuits.last().unwrap(); + + let timer = start_timer(|| format!("setup-{num_vars}")); + let param = Fs::setup(&circuit_info, seeded_std_rng()).unwrap(); + end_timer(timer); + + let timer = start_timer(|| format!("preprocess-{num_vars}")); + let (pp, vp) = Fs::preprocess(¶m, &circuit_info).unwrap(); + end_timer(timer); + + let (accumulator_before_last, proof) = { + let mut accumulator = Fs::init_accumulator(&pp).unwrap(); + for circuit in circuits[..circuits.len() - 1].iter() { + let timer = start_timer(|| format!("prove_accumulation_from_nark-{num_vars}")); + Fs::prove_accumulation_from_nark( + &pp, + &mut accumulator, + circuit, + &mut T::new(()), + seeded_std_rng(), + ) + .unwrap(); + end_timer(timer); + } + + let accumulator_before_last = accumulator.as_ref().clone(); + + let timer = start_timer(|| format!("prove_decider_with_last_nark-{num_vars}")); + let proof = { + let mut transcript = T::new(()); + Fs::prove_decider_with_last_nark( + &pp, + &mut accumulator, + last_circuit, + &mut transcript, + seeded_std_rng(), + ) + .unwrap(); + transcript.into_proof() + }; + end_timer(timer); + + (accumulator_before_last, proof) + }; + + let timer = start_timer(|| format!("verify_decider_with_last_nark-{num_vars}")); + let result = { + let mut transcript = T::from_proof((), proof.as_slice()); + Fs::verify_decider_with_last_nark( + &vp, + accumulator_before_last, + last_circuit.instances(), + &mut transcript, + seeded_std_rng(), + ) + }; + assert!(matches!(result, Ok(_))); + end_timer(timer); + } + } +} diff --git a/plonkish_backend/src/accumulation/protostar.rs b/plonkish_backend/src/accumulation/protostar.rs new file mode 100644 index 00000000..04adf872 --- /dev/null +++ b/plonkish_backend/src/accumulation/protostar.rs @@ -0,0 +1,310 @@ +use crate::{ + accumulation::{ + protostar::ProtostarStrategy::{Compressing, NoCompressing}, + PlonkishNark, PlonkishNarkInstance, + }, + backend::PlonkishBackend, + pcs::{AdditiveCommitment, PolynomialCommitmentScheme}, + poly::Polynomial, + util::{ + arithmetic::{inner_product, powers, Field}, + chain, + expression::Expression, + izip, izip_eq, + transcript::Transcript, + Deserialize, Itertools, Serialize, + }, + Error, +}; +use std::{iter, marker::PhantomData}; + +pub mod hyperplonk; + +#[derive(Clone, Debug)] +pub struct Protostar(PhantomData); + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +pub enum ProtostarStrategy { + // As known as Sangria + NoCompressing = 0, + // Compressing verification as described in 2023/620 section 3.5 but without square-root optimization + #[default] + Compressing = 1, + // TODO: + // Compressing verification with square-root optimization applied as described in 2023/620 section 3.5 + // CompressingWithSqrtPowers = 3, +} + +impl From for ProtostarStrategy { + fn from(strategy: usize) -> Self { + [NoCompressing, Compressing][strategy] + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ProtostarProverParam +where + F: Field, + Pb: PlonkishBackend, +{ + pp: Pb::ProverParam, + strategy: ProtostarStrategy, + num_theta_primes: usize, + num_alpha_primes: usize, + num_folding_witness_polys: usize, + num_folding_challenges: usize, + cross_term_expressions: Vec>, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ProtostarVerifierParam +where + F: Field, + Pb: PlonkishBackend, +{ + vp: Pb::VerifierParam, + strategy: ProtostarStrategy, + num_theta_primes: usize, + num_alpha_primes: usize, + num_folding_witness_polys: usize, + num_folding_challenges: usize, + num_cross_terms: usize, +} + +#[derive(Clone, Debug)] +pub struct ProtostarAccumulator +where + F: Field, + Pcs: PolynomialCommitmentScheme, +{ + instance: ProtostarAccumulatorInstance, + witness_polys: Vec, + e_poly: Pcs::Polynomial, + _marker: PhantomData, +} + +impl AsRef> + for ProtostarAccumulator +where + F: Field, + Pcs: PolynomialCommitmentScheme, +{ + fn as_ref(&self) -> &ProtostarAccumulatorInstance { + &self.instance + } +} + +impl ProtostarAccumulator +where + F: Field, + Pcs: PolynomialCommitmentScheme, +{ + fn init( + strategy: ProtostarStrategy, + k: usize, + num_instances: &[usize], + num_witness_polys: usize, + num_challenges: usize, + ) -> Self { + let zero_poly = Pcs::Polynomial::from_evals(vec![F::ZERO; 1 << k]); + Self { + instance: ProtostarAccumulatorInstance::init( + strategy, + num_instances, + num_witness_polys, + num_challenges, + ), + witness_polys: iter::repeat_with(|| zero_poly.clone()) + .take(num_witness_polys) + .collect(), + e_poly: zero_poly, + _marker: PhantomData, + } + } + + fn from_nark(strategy: ProtostarStrategy, k: usize, nark: PlonkishNark) -> Self { + let witness_polys = nark.witness_polys; + Self { + instance: ProtostarAccumulatorInstance::from_nark(strategy, nark.instance), + witness_polys, + e_poly: Pcs::Polynomial::from_evals(vec![F::ZERO; 1 << k]), + _marker: PhantomData, + } + } + + fn fold_uncompressed( + &mut self, + rhs: &Self, + cross_term_polys: &[Pcs::Polynomial], + cross_term_comms: &[Pcs::Commitment], + r: &F, + ) where + Pcs::Commitment: AdditiveCommitment, + { + self.instance + .fold_uncompressed(&rhs.instance, cross_term_comms, r); + izip_eq!(&mut self.witness_polys, &rhs.witness_polys) + .for_each(|(lhs, rhs)| *lhs += (r, rhs)); + izip!(powers(*r).skip(1), chain![cross_term_polys, [&rhs.e_poly]]) + .for_each(|(power_of_r, poly)| self.e_poly += (&power_of_r, poly)); + } + + fn fold_compressed( + &mut self, + rhs: &Self, + zeta_cross_term_poly: &Pcs::Polynomial, + zeta_cross_term_comm: &Pcs::Commitment, + compressed_cross_term_sums: &[F], + r: &F, + ) where + Pcs::Commitment: AdditiveCommitment, + { + self.instance.fold_compressed( + &rhs.instance, + zeta_cross_term_comm, + compressed_cross_term_sums, + r, + ); + izip_eq!(&mut self.witness_polys, &rhs.witness_polys) + .for_each(|(lhs, rhs)| *lhs += (r, rhs)); + izip!(powers(*r).skip(1), [zeta_cross_term_poly, &rhs.e_poly]) + .for_each(|(power_of_r, poly)| self.e_poly += (&power_of_r, poly)); + } + + pub fn instance(&self) -> &ProtostarAccumulatorInstance { + &self.instance + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ProtostarAccumulatorInstance { + instances: Vec>, + witness_comms: Vec, + challenges: Vec, + u: F, + e_comm: C, + compressed_e_sum: Option, +} + +impl ProtostarAccumulatorInstance { + fn instances(&self) -> &[Vec] { + &self.instances + } +} + +impl ProtostarAccumulatorInstance +where + F: Field, + C: Default, +{ + fn init( + strategy: ProtostarStrategy, + num_instances: &[usize], + num_witness_polys: usize, + num_challenges: usize, + ) -> Self { + Self { + instances: num_instances.iter().map(|n| vec![F::ZERO; *n]).collect(), + witness_comms: iter::repeat_with(C::default) + .take(num_witness_polys) + .collect(), + challenges: vec![F::ZERO; num_challenges], + u: F::ZERO, + e_comm: C::default(), + compressed_e_sum: match strategy { + NoCompressing => None, + Compressing => Some(F::ZERO), + }, + } + } + + fn claimed_sum(&self) -> F { + self.compressed_e_sum.unwrap_or(F::ZERO) + } + + fn absorb_into( + &self, + transcript: &mut impl Transcript, + ) -> Result<(), Error> + where + C: AsRef<[CommitmentChunk]>, + { + self.instances + .iter() + .try_for_each(|instances| transcript.common_field_elements(instances))?; + self.witness_comms + .iter() + .try_for_each(|comm| transcript.common_commitments(comm.as_ref()))?; + transcript.common_field_elements(&self.challenges)?; + transcript.common_field_element(&self.u)?; + transcript.common_commitments(self.e_comm.as_ref())?; + if let Some(compressed_e_sum) = self.compressed_e_sum.as_ref() { + transcript.common_field_element(compressed_e_sum)?; + } + Ok(()) + } + + fn from_nark(strategy: ProtostarStrategy, nark: PlonkishNarkInstance) -> Self { + Self { + instances: nark.instances, + witness_comms: nark.witness_comms, + challenges: nark.challenges, + u: F::ONE, + e_comm: C::default(), + compressed_e_sum: match strategy { + NoCompressing => None, + Compressing => Some(F::ZERO), + }, + } + } + + fn fold_uncompressed(&mut self, rhs: &Self, cross_term_comms: &[C], r: &F) + where + C: AdditiveCommitment, + { + let one = F::ONE; + let powers_of_r = powers(*r).take(cross_term_comms.len() + 2).collect_vec(); + izip_eq!(&mut self.instances, &rhs.instances) + .for_each(|(lhs, rhs)| izip_eq!(lhs, rhs).for_each(|(lhs, rhs)| *lhs += &(*rhs * r))); + izip_eq!(&mut self.witness_comms, &rhs.witness_comms) + .for_each(|(lhs, rhs)| *lhs = C::sum_with_scalar([&one, r], [lhs, rhs])); + izip_eq!(&mut self.challenges, &rhs.challenges).for_each(|(lhs, rhs)| *lhs += &(*rhs * r)); + self.u += &(rhs.u * r); + self.e_comm = { + let comms = chain![[&self.e_comm], cross_term_comms, [&rhs.e_comm]]; + C::sum_with_scalar(&powers_of_r, comms) + }; + } + + fn fold_compressed( + &mut self, + rhs: &Self, + zeta_cross_term_comm: &C, + compressed_cross_term_sums: &[F], + r: &F, + ) where + C: AdditiveCommitment, + { + let one = F::ONE; + let powers_of_r = powers(*r) + .take(compressed_cross_term_sums.len().max(1) + 2) + .collect_vec(); + izip_eq!(&mut self.instances, &rhs.instances) + .for_each(|(lhs, rhs)| izip_eq!(lhs, rhs).for_each(|(lhs, rhs)| *lhs += &(*rhs * r))); + izip_eq!(&mut self.witness_comms, &rhs.witness_comms) + .for_each(|(lhs, rhs)| *lhs = C::sum_with_scalar([&one, r], [lhs, rhs])); + izip_eq!(&mut self.challenges, &rhs.challenges).for_each(|(lhs, rhs)| *lhs += &(*rhs * r)); + self.u += &(rhs.u * r); + self.e_comm = { + let comms = [&self.e_comm, zeta_cross_term_comm, &rhs.e_comm]; + C::sum_with_scalar(&powers_of_r[..3], comms) + }; + *self.compressed_e_sum.as_mut().unwrap() += &inner_product( + &powers_of_r[1..], + chain![ + compressed_cross_term_sums, + [rhs.compressed_e_sum.as_ref().unwrap()] + ], + ); + } +} diff --git a/plonkish_backend/src/accumulation/protostar/hyperplonk.rs b/plonkish_backend/src/accumulation/protostar/hyperplonk.rs new file mode 100644 index 00000000..541dfc1b --- /dev/null +++ b/plonkish_backend/src/accumulation/protostar/hyperplonk.rs @@ -0,0 +1,654 @@ +use crate::{ + accumulation::{ + protostar::{ + hyperplonk::{ + preprocessor::{batch_size, preprocess}, + prover::{ + evaluate_compressed_cross_term_sums, evaluate_cross_term_polys, + evaluate_zeta_cross_term_poly, lookup_h_polys, powers_of_zeta_poly, + }, + }, + Protostar, ProtostarAccumulator, ProtostarAccumulatorInstance, ProtostarProverParam, + ProtostarStrategy::{Compressing, NoCompressing}, + ProtostarVerifierParam, + }, + AccumulationScheme, PlonkishNark, PlonkishNarkInstance, + }, + backend::{ + hyperplonk::{ + prover::{ + instance_polys, lookup_compressed_polys, lookup_m_polys, permutation_z_polys, + prove_sum_check, + }, + verifier::verify_sum_check, + HyperPlonk, + }, + PlonkishCircuit, PlonkishCircuitInfo, + }, + pcs::{AdditiveCommitment, CommitmentChunk, PolynomialCommitmentScheme}, + poly::multilinear::MultilinearPolynomial, + util::{ + arithmetic::{powers, PrimeField}, + end_timer, start_timer, + transcript::{TranscriptRead, TranscriptWrite}, + DeserializeOwned, Itertools, Serialize, + }, + Error, +}; +use rand::RngCore; +use std::{borrow::BorrowMut, hash::Hash, iter}; + +mod preprocessor; +mod prover; + +impl AccumulationScheme for Protostar, STRATEGY> +where + F: PrimeField + Hash + Serialize + DeserializeOwned, + Pcs: PolynomialCommitmentScheme>, + Pcs::Commitment: AdditiveCommitment, + Pcs::CommitmentChunk: AdditiveCommitment, +{ + type Pcs = Pcs; + type ProverParam = ProtostarProverParam>; + type VerifierParam = ProtostarVerifierParam>; + type Accumulator = ProtostarAccumulator; + type AccumulatorInstance = ProtostarAccumulatorInstance; + + fn setup( + circuit_info: &PlonkishCircuitInfo, + rng: impl RngCore, + ) -> Result { + assert!(circuit_info.is_well_formed()); + + let num_vars = circuit_info.k; + let poly_size = 1 << num_vars; + let batch_size = batch_size(circuit_info, STRATEGY.into()); + Pcs::setup(poly_size, batch_size, rng) + } + + fn preprocess( + param: &Pcs::Param, + circuit_info: &PlonkishCircuitInfo, + ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { + assert!(circuit_info.is_well_formed()); + + preprocess(param, circuit_info, STRATEGY.into()) + } + + fn init_accumulator(pp: &Self::ProverParam) -> Result { + Ok(ProtostarAccumulator::init( + pp.strategy, + pp.pp.num_vars, + &pp.pp.num_instances, + pp.num_folding_witness_polys, + pp.num_folding_challenges, + )) + } + + fn init_accumulator_from_nark( + pp: &Self::ProverParam, + nark: PlonkishNark, + ) -> Result { + Ok(ProtostarAccumulator::from_nark( + pp.strategy, + pp.pp.num_vars, + nark, + )) + } + + fn prove_nark( + pp: &Self::ProverParam, + circuit: &impl PlonkishCircuit, + transcript: &mut impl TranscriptWrite, F>, + _: impl RngCore, + ) -> Result, Error> { + let ProtostarProverParam { + pp, + strategy, + num_theta_primes, + num_alpha_primes, + .. + } = pp; + + let instances = circuit.instances(); + for (num_instances, instances) in pp.num_instances.iter().zip_eq(instances) { + assert_eq!(instances.len(), *num_instances); + for instance in instances.iter() { + transcript.common_field_element(instance)?; + } + } + + // Round 0..n + + let mut witness_polys = Vec::with_capacity(pp.num_witness_polys.iter().sum()); + let mut witness_comms = Vec::with_capacity(witness_polys.len()); + let mut challenges = Vec::with_capacity(pp.num_challenges.iter().sum()); + for (round, (num_witness_polys, num_challenges)) in pp + .num_witness_polys + .iter() + .zip_eq(pp.num_challenges.iter()) + .enumerate() + { + let timer = start_timer(|| format!("witness_collector-{round}")); + let polys = circuit + .synthesize(round, &challenges)? + .into_iter() + .map(MultilinearPolynomial::new) + .collect_vec(); + assert_eq!(polys.len(), *num_witness_polys); + end_timer(timer); + + witness_comms.extend(Pcs::batch_commit_and_write(&pp.pcs, &polys, transcript)?); + witness_polys.extend(polys); + challenges.extend(transcript.squeeze_challenges(*num_challenges)); + } + + // Round n + + let theta_primes = powers(transcript.squeeze_challenge()) + .skip(1) + .take(*num_theta_primes) + .collect_vec(); + + let timer = start_timer(|| format!("lookup_compressed_polys-{}", pp.lookups.len())); + let lookup_compressed_polys = { + let instance_polys = instance_polys(pp.num_vars, instances); + let polys = iter::empty() + .chain(instance_polys.iter()) + .chain(pp.preprocess_polys.iter()) + .chain(witness_polys.iter()) + .collect_vec(); + let thetas = iter::empty() + .chain(Some(F::ONE)) + .chain(theta_primes.iter().cloned()) + .collect_vec(); + lookup_compressed_polys(&pp.lookups, &polys, &challenges, &thetas) + }; + end_timer(timer); + + let timer = start_timer(|| format!("lookup_m_polys-{}", pp.lookups.len())); + let lookup_m_polys = lookup_m_polys(&lookup_compressed_polys)?; + end_timer(timer); + + let lookup_m_comms = Pcs::batch_commit_and_write(&pp.pcs, &lookup_m_polys, transcript)?; + + // Round n+1 + + let beta_prime = transcript.squeeze_challenge(); + + let timer = start_timer(|| format!("lookup_h_polys-{}", pp.lookups.len())); + let lookup_h_polys = lookup_h_polys(&lookup_compressed_polys, &lookup_m_polys, &beta_prime); + end_timer(timer); + + let lookup_h_comms = { + let polys = lookup_h_polys.iter().flatten(); + Pcs::batch_commit_and_write(&pp.pcs, polys, transcript)? + }; + + // Round n+2 + + let (zeta, powers_of_zeta_poly, powers_of_zeta_comm) = match strategy { + NoCompressing => (None, None, None), + Compressing => { + let zeta = transcript.squeeze_challenge(); + + let timer = start_timer(|| "powers_of_zeta_poly"); + let powers_of_zeta_poly = powers_of_zeta_poly(pp.num_vars, zeta); + end_timer(timer); + + let powers_of_zeta_comm = + Pcs::commit_and_write(&pp.pcs, &powers_of_zeta_poly, transcript)?; + + ( + Some(zeta), + Some(powers_of_zeta_poly), + Some(powers_of_zeta_comm), + ) + } + }; + + // Round n+3 + + let alpha_primes = powers(transcript.squeeze_challenge()) + .skip(1) + .take(*num_alpha_primes) + .collect_vec(); + + Ok(PlonkishNark::new( + instances.to_vec(), + iter::empty() + .chain(challenges) + .chain(theta_primes) + .chain(Some(beta_prime)) + .chain(zeta) + .chain(alpha_primes) + .collect(), + iter::empty() + .chain(witness_comms) + .chain(lookup_m_comms) + .chain(lookup_h_comms) + .chain(powers_of_zeta_comm) + .collect(), + iter::empty() + .chain(witness_polys) + .chain(lookup_m_polys) + .chain(lookup_h_polys.into_iter().flatten()) + .chain(powers_of_zeta_poly) + .collect(), + )) + } + + fn prove_accumulation( + pp: &Self::ProverParam, + mut accumulator: impl BorrowMut, + incoming: &Self::Accumulator, + transcript: &mut impl TranscriptWrite, F>, + _: impl RngCore, + ) -> Result<(), Error> { + let ProtostarProverParam { + pp, + strategy, + num_alpha_primes, + cross_term_expressions, + .. + } = pp; + let accumulator = accumulator.borrow_mut(); + + accumulator.instance.absorb_into(transcript)?; + if !IS_INCOMING_ABSORBED { + incoming.instance.absorb_into(transcript)?; + } + + match strategy { + NoCompressing => { + let timer = start_timer(|| { + format!("evaluate_cross_term_polys-{}", cross_term_expressions.len()) + }); + let cross_term_polys = evaluate_cross_term_polys( + cross_term_expressions, + pp.num_vars, + &pp.preprocess_polys, + accumulator, + incoming, + ); + end_timer(timer); + + let cross_term_comms = + Pcs::batch_commit_and_write(&pp.pcs, &cross_term_polys, transcript)?; + + // Round 0 + + let r = transcript.squeeze_challenge(); + + let timer = start_timer(|| "fold_uncompressed"); + accumulator.fold_uncompressed(incoming, &cross_term_polys, &cross_term_comms, &r); + end_timer(timer); + } + Compressing => { + let timer = start_timer(|| "evaluate_zeta_cross_term_poly"); + let zeta_cross_term_poly = evaluate_zeta_cross_term_poly( + pp.num_vars, + *num_alpha_primes, + accumulator, + incoming, + ); + end_timer(timer); + + let timer = start_timer(|| { + let len = cross_term_expressions.len(); + format!("evaluate_compressed_cross_term_sums-{len}") + }); + let compressed_cross_term_sums = evaluate_compressed_cross_term_sums( + cross_term_expressions, + pp.num_vars, + &pp.preprocess_polys, + accumulator, + incoming, + ); + end_timer(timer); + + let zeta_cross_term_comm = + Pcs::commit_and_write(&pp.pcs, &zeta_cross_term_poly, transcript)?; + transcript.write_field_elements(&compressed_cross_term_sums)?; + + // Round 0 + + let r = transcript.squeeze_challenge(); + + let timer = start_timer(|| "fold_compressed"); + accumulator.fold_compressed( + incoming, + &zeta_cross_term_poly, + &zeta_cross_term_comm, + &compressed_cross_term_sums, + &r, + ); + end_timer(timer); + } + } + + Ok(()) + } + + fn verify_accumulation_from_nark( + vp: &Self::VerifierParam, + mut accumulator: impl BorrowMut, + instances: &[Vec], + transcript: &mut impl TranscriptRead, F>, + _: impl RngCore, + ) -> Result<(), Error> { + let ProtostarVerifierParam { + vp, + strategy, + num_theta_primes, + num_alpha_primes, + num_cross_terms, + .. + } = vp; + let accumulator = accumulator.borrow_mut(); + + for (num_instances, instances) in vp.num_instances.iter().zip_eq(instances) { + assert_eq!(instances.len(), *num_instances); + for instance in instances.iter() { + transcript.common_field_element(instance)?; + } + } + + // Round 0..n + + let mut witness_comms = Vec::with_capacity(vp.num_witness_polys.iter().sum()); + let mut challenges = Vec::with_capacity(vp.num_challenges.iter().sum()); + for (num_polys, num_challenges) in + vp.num_witness_polys.iter().zip_eq(vp.num_challenges.iter()) + { + witness_comms.extend(Pcs::read_commitments(&vp.pcs, *num_polys, transcript)?); + challenges.extend(transcript.squeeze_challenges(*num_challenges)); + } + + // Round n + + let theta_primes = powers(transcript.squeeze_challenge()) + .skip(1) + .take(*num_theta_primes) + .collect_vec(); + + let lookup_m_comms = Pcs::read_commitments(&vp.pcs, vp.num_lookups, transcript)?; + + // Round n+1 + + let beta_prime = transcript.squeeze_challenge(); + + let lookup_h_comms = Pcs::read_commitments(&vp.pcs, 2 * vp.num_lookups, transcript)?; + + // Round n+2 + + let (zeta, powers_of_zeta_comm) = match strategy { + NoCompressing => (None, None), + Compressing => { + let zeta = transcript.squeeze_challenge(); + + let powers_of_zeta_comm = Pcs::read_commitment(&vp.pcs, transcript)?; + + (Some(zeta), Some(powers_of_zeta_comm)) + } + }; + + // Round n+3 + + let alpha_primes = powers(transcript.squeeze_challenge()) + .skip(1) + .take(*num_alpha_primes) + .collect_vec(); + + let nark = PlonkishNarkInstance::new( + instances.to_vec(), + iter::empty() + .chain(challenges) + .chain(theta_primes) + .chain(Some(beta_prime)) + .chain(zeta) + .chain(alpha_primes) + .collect(), + iter::empty() + .chain(witness_comms) + .chain(lookup_m_comms) + .chain(lookup_h_comms) + .chain(powers_of_zeta_comm) + .collect(), + ); + let incoming = ProtostarAccumulatorInstance::from_nark(*strategy, nark); + accumulator.absorb_into(transcript)?; + + match strategy { + NoCompressing => { + let cross_term_comms = + Pcs::read_commitments(&vp.pcs, *num_cross_terms, transcript)?; + + // Round n+4 + + let r = transcript.squeeze_challenge(); + + accumulator.fold_uncompressed(&incoming, &cross_term_comms, &r); + } + Compressing => { + let zeta_cross_term_comm = Pcs::read_commitment(&vp.pcs, transcript)?; + let compressed_cross_term_sums = + transcript.read_field_elements(*num_cross_terms)?; + + // Round n+4 + + let r = transcript.squeeze_challenge(); + + accumulator.fold_compressed( + &incoming, + &zeta_cross_term_comm, + &compressed_cross_term_sums, + &r, + ); + } + }; + + Ok(()) + } + + fn prove_decider( + pp: &Self::ProverParam, + accumulator: &Self::Accumulator, + transcript: &mut impl TranscriptWrite, F>, + _: impl RngCore, + ) -> Result<(), Error> { + let ProtostarProverParam { pp, .. } = pp; + + accumulator.instance.absorb_into(transcript)?; + + // Round 0 + + let beta = transcript.squeeze_challenge(); + let gamma = transcript.squeeze_challenge(); + + let timer = start_timer(|| format!("permutation_z_polys-{}", pp.permutation_polys.len())); + let builtin_witness_poly_offset = pp.num_witness_polys.iter().sum::(); + let instance_polys = instance_polys(pp.num_vars, &accumulator.instance.instances); + let polys = iter::empty() + .chain(&instance_polys) + .chain(&pp.preprocess_polys) + .chain(&accumulator.witness_polys[..builtin_witness_poly_offset]) + .chain(pp.permutation_polys.iter().map(|(_, poly)| poly)) + .collect_vec(); + let permutation_z_polys = permutation_z_polys( + pp.num_permutation_z_polys, + &pp.permutation_polys, + &polys, + &beta, + &gamma, + ); + end_timer(timer); + + let permutation_z_comms = + Pcs::batch_commit_and_write(&pp.pcs, &permutation_z_polys, transcript)?; + + // Round 1 + + let alpha = transcript.squeeze_challenge(); + let y = transcript.squeeze_challenges(pp.num_vars); + + let polys = iter::empty() + .chain(polys) + .chain(&accumulator.witness_polys[builtin_witness_poly_offset..]) + .chain(permutation_z_polys.iter()) + .chain(Some(&accumulator.e_poly)) + .collect_vec(); + let challenges = iter::empty() + .chain(accumulator.instance.challenges.iter().copied()) + .chain([accumulator.instance.u]) + .chain([beta, gamma, alpha]) + .collect(); + let (points, evals) = { + prove_sum_check( + pp.num_instances.len(), + &pp.expression, + accumulator.instance.claimed_sum(), + &polys, + challenges, + y, + transcript, + )? + }; + + // PCS open + + let dummy_comm = Pcs::Commitment::default(); + let comms = iter::empty() + .chain(iter::repeat(&dummy_comm).take(pp.num_instances.len())) + .chain(&pp.preprocess_comms) + .chain(&accumulator.instance.witness_comms[..builtin_witness_poly_offset]) + .chain(&pp.permutation_comms) + .chain(&accumulator.instance.witness_comms[builtin_witness_poly_offset..]) + .chain(&permutation_z_comms) + .chain(Some(&accumulator.instance.e_comm)) + .collect_vec(); + let timer = start_timer(|| format!("pcs_batch_open-{}", evals.len())); + Pcs::batch_open(&pp.pcs, polys, comms, &points, &evals, transcript)?; + end_timer(timer); + + Ok(()) + } + + fn verify_decider( + vp: &Self::VerifierParam, + accumulator: &Self::AccumulatorInstance, + transcript: &mut impl TranscriptRead, F>, + _: impl RngCore, + ) -> Result<(), Error> { + let ProtostarVerifierParam { vp, .. } = vp; + + accumulator.absorb_into(transcript)?; + + // Round 0 + + let beta = transcript.squeeze_challenge(); + let gamma = transcript.squeeze_challenge(); + + let permutation_z_comms = + Pcs::read_commitments(&vp.pcs, vp.num_permutation_z_polys, transcript)?; + + // Round 1 + + let alpha = transcript.squeeze_challenge(); + let y = transcript.squeeze_challenges(vp.num_vars); + + let challenges = iter::empty() + .chain(accumulator.challenges.iter().copied()) + .chain([accumulator.u]) + .chain([beta, gamma, alpha]) + .collect_vec(); + let (points, evals) = { + verify_sum_check( + vp.num_vars, + &vp.expression, + accumulator.claimed_sum(), + accumulator.instances(), + &challenges, + &y, + transcript, + )? + }; + + // PCS verify + + let builtin_witness_poly_offset = vp.num_witness_polys.iter().sum::(); + let dummy_comm = Pcs::Commitment::default(); + let comms = iter::empty() + .chain(iter::repeat(&dummy_comm).take(vp.num_instances.len())) + .chain(&vp.preprocess_comms) + .chain(&accumulator.witness_comms[..builtin_witness_poly_offset]) + .chain(vp.permutation_comms.iter().map(|(_, comm)| comm)) + .chain(&accumulator.witness_comms[builtin_witness_poly_offset..]) + .chain(&permutation_z_comms) + .chain(Some(&accumulator.e_comm)) + .collect_vec(); + Pcs::batch_verify(&vp.pcs, comms, &points, &evals, transcript)?; + + Ok(()) + } +} + +#[cfg(test)] +pub(crate) mod test { + use crate::{ + accumulation::{protostar::Protostar, test::run_accumulation_scheme}, + backend::hyperplonk::{ + util::{rand_vanilla_plonk_circuit, rand_vanilla_plonk_with_lookup_circuit}, + HyperPlonk, + }, + pcs::{ + multilinear::{Gemini, MultilinearIpa, MultilinearKzg, Zeromorph}, + univariate::UnivariateKzg, + }, + util::{ + test::{seeded_std_rng, std_rng}, + transcript::Keccak256Transcript, + Itertools, + }, + }; + use halo2_curves::{bn256::Bn256, grumpkin}; + use std::iter; + + macro_rules! tests { + ($name:ident, $pcs:ty, $num_vars_range:expr) => { + paste::paste! { + #[test] + fn [<$name _protostar_hyperplonk_vanilla_plonk>]() { + run_accumulation_scheme::<_, Protostar>, Keccak256Transcript<_>, _>($num_vars_range, |num_vars| { + let (circuit_info, _) = rand_vanilla_plonk_circuit(num_vars, std_rng(), seeded_std_rng()); + let circuits = iter::repeat_with(|| { + let (_, circuit) = rand_vanilla_plonk_circuit(num_vars, std_rng(), seeded_std_rng()); + circuit + }).take(3).collect_vec(); + (circuit_info, circuits) + }); + } + + #[test] + fn [<$name _protostar_hyperplonk_vanilla_plonk_with_lookup>]() { + run_accumulation_scheme::<_, Protostar>, Keccak256Transcript<_>, _>($num_vars_range, |num_vars| { + let (circuit_info, _) = rand_vanilla_plonk_with_lookup_circuit(num_vars, std_rng(), seeded_std_rng()); + let circuits = iter::repeat_with(|| { + let (_, circuit) = rand_vanilla_plonk_with_lookup_circuit(num_vars, std_rng(), seeded_std_rng()); + circuit + }).take(3).collect_vec(); + (circuit_info, circuits) + }); + } + } + }; + ($name:ident, $pcs:ty) => { + tests!($name, $pcs, 2..16); + }; + } + + tests!(ipa, MultilinearIpa); + tests!(kzg, MultilinearKzg); + tests!(gemini_kzg, Gemini>); + tests!(zeromorph_kzg, Zeromorph>); +} diff --git a/plonkish_backend/src/accumulation/protostar/hyperplonk/preprocessor.rs b/plonkish_backend/src/accumulation/protostar/hyperplonk/preprocessor.rs new file mode 100644 index 00000000..1ca692ca --- /dev/null +++ b/plonkish_backend/src/accumulation/protostar/hyperplonk/preprocessor.rs @@ -0,0 +1,406 @@ +use crate::{ + accumulation::protostar::{ + ProtostarProverParam, ProtostarStrategy, + ProtostarStrategy::{Compressing, NoCompressing}, + ProtostarVerifierParam, + }, + backend::{ + hyperplonk::{preprocessor::permutation_constraints, HyperPlonk}, + PlonkishBackend, PlonkishCircuitInfo, + }, + pcs::PolynomialCommitmentScheme, + poly::multilinear::MultilinearPolynomial, + util::{ + arithmetic::{div_ceil, PrimeField}, + chain, + expression::{ + relaxed::{cross_term_expressions, products, relaxed_expression, PolynomialSet}, + Expression, Query, Rotation, + }, + DeserializeOwned, Itertools, Serialize, + }, + Error, +}; +use std::{array, borrow::Cow, collections::BTreeSet, hash::Hash, iter}; + +pub(crate) fn batch_size( + circuit_info: &PlonkishCircuitInfo, + strategy: ProtostarStrategy, +) -> usize { + let num_lookups = circuit_info.lookups.len(); + let num_permutation_polys = circuit_info.permutation_polys().len(); + chain![ + [circuit_info.preprocess_polys.len() + circuit_info.permutation_polys().len()], + circuit_info.num_witness_polys.clone(), + [num_lookups], + match strategy { + NoCompressing => { + vec![] + } + Compressing => { + vec![1] + } + }, + [2 * num_lookups + div_ceil(num_permutation_polys, max_degree(circuit_info, None) - 1)], + [1], + ] + .sum() +} + +#[allow(clippy::type_complexity)] +pub(super) fn preprocess( + param: &Pcs::Param, + circuit_info: &PlonkishCircuitInfo, + strategy: ProtostarStrategy, +) -> Result< + ( + ProtostarProverParam>, + ProtostarVerifierParam>, + ), + Error, +> +where + F: PrimeField + Hash + Serialize + DeserializeOwned, + Pcs: PolynomialCommitmentScheme>, +{ + let challenge_offset = circuit_info.num_challenges.iter().sum::(); + let max_lookup_width = circuit_info.lookups.iter().map(Vec::len).max().unwrap_or(0); + let num_theta_primes = max_lookup_width.checked_sub(1).unwrap_or_default(); + let theta_primes = (challenge_offset..) + .take(num_theta_primes) + .map(Expression::::Challenge) + .collect_vec(); + let beta_prime = &Expression::::Challenge(challenge_offset + num_theta_primes); + + let (lookup_constraints, lookup_zero_checks) = + lookup_constraints(circuit_info, &theta_primes, beta_prime); + + let max_degree = max_degree(circuit_info, Some(&lookup_constraints)); + + let num_constraints = circuit_info.constraints.len() + lookup_constraints.len(); + let num_alpha_primes = num_constraints.checked_sub(1).unwrap_or_default(); + + let witness_poly_offset = + circuit_info.num_instances.len() + circuit_info.preprocess_polys.len(); + let num_witness_polys = circuit_info.num_witness_polys.iter().sum::(); + let num_permutation_z_polys = div_ceil(circuit_info.permutation_polys().len(), max_degree - 1); + + let ( + num_builtin_witness_polys, + alpha_prime_offset, + cross_term_expressions, + sum_check, + zero_check_on_every_row, + ) = match strategy { + NoCompressing => { + let alpha_prime_offset = challenge_offset + num_theta_primes + 1; + let num_builtin_witness_polys = 3 * circuit_info.lookups.len(); + let builtin_witness_poly_offset = + witness_poly_offset + num_witness_polys + circuit_info.permutation_polys().len(); + + let poly_set = PolynomialSet { + preprocess: iter::empty() + .chain( + (circuit_info.num_instances.len()..) + .take(circuit_info.preprocess_polys.len()), + ) + .collect(), + folding: iter::empty() + .chain(0..circuit_info.num_instances.len()) + .chain((witness_poly_offset..).take(num_witness_polys)) + .chain((builtin_witness_poly_offset..).take(num_builtin_witness_polys)) + .collect(), + }; + + let products = { + let mut constraints = iter::empty() + .chain(circuit_info.constraints.iter()) + .chain(lookup_constraints.iter()) + .collect_vec(); + let folding_degrees = constraints + .iter() + .map(|constraint| folding_degree(&poly_set.preprocess, constraint)) + .enumerate() + .sorted_by(|a, b| b.1.cmp(&a.1)) + .collect_vec(); + if let &[a, b, ..] = &folding_degrees[..] { + if a.1 != b.1 { + constraints.swap(0, a.0); + } + } + let compressed_constraint = iter::empty() + .chain(constraints.first().cloned().cloned()) + .chain( + constraints + .into_iter() + .skip(1) + .zip((alpha_prime_offset..).map(Expression::Challenge)) + .map(|(constraint, challenge)| constraint * challenge), + ) + .sum::>(); + products(&poly_set.preprocess, &compressed_constraint) + }; + + let num_folding_challenges = alpha_prime_offset + num_alpha_primes; + let cross_term_expressions = + cross_term_expressions(&poly_set, &products, num_folding_challenges); + + let u = num_folding_challenges; + let relexed_constraint = { + let e = builtin_witness_poly_offset + + circuit_info.lookups.len() * 3 + + num_permutation_z_polys; + relaxed_expression(&products, u) + - Expression::Polynomial(Query::new(e, Rotation::cur())) + }; + + ( + num_builtin_witness_polys, + alpha_prime_offset, + cross_term_expressions, + None, + relexed_constraint, + ) + } + Compressing => { + let zeta = challenge_offset + num_theta_primes + 1; + let alpha_prime_offset = zeta + 1; + let num_builtin_witness_polys = 3 * circuit_info.lookups.len() + 1; + let builtin_witness_poly_offset = + witness_poly_offset + num_witness_polys + circuit_info.permutation_polys().len(); + + let poly_set = PolynomialSet { + preprocess: iter::empty() + .chain( + (circuit_info.num_instances.len()..) + .take(circuit_info.preprocess_polys.len()), + ) + .collect(), + folding: iter::empty() + .chain(0..circuit_info.num_instances.len()) + .chain((witness_poly_offset..).take(num_witness_polys)) + .chain((builtin_witness_poly_offset..).take(num_builtin_witness_polys)) + .collect(), + }; + + let powers_of_zeta = builtin_witness_poly_offset + circuit_info.lookups.len() * 3; + let compressed_products = { + let mut constraints = iter::empty() + .chain(circuit_info.constraints.iter()) + .chain(lookup_constraints.iter()) + .collect_vec(); + let folding_degrees = constraints + .iter() + .map(|constraint| folding_degree(&poly_set.preprocess, constraint)) + .enumerate() + .sorted_by(|a, b| b.1.cmp(&a.1)) + .collect_vec(); + if let &[a, b, ..] = &folding_degrees[..] { + if a.1 != b.1 { + constraints.swap(0, a.0); + } + } + let powers_of_zeta = + Expression::::Polynomial(Query::new(powers_of_zeta, Rotation::cur())); + let compressed_constraint = iter::empty() + .chain(constraints.first().cloned().cloned()) + .chain( + constraints + .into_iter() + .skip(1) + .zip((alpha_prime_offset..).map(Expression::Challenge)) + .map(|(constraint, challenge)| constraint * challenge), + ) + .sum::>() + * powers_of_zeta; + products(&poly_set.preprocess, &compressed_constraint) + }; + let powers_of_zeta_constraint = powers_of_zeta_constraint(zeta, powers_of_zeta); + let zeta_products = products(&poly_set.preprocess, &powers_of_zeta_constraint); + + let num_folding_challenges = alpha_prime_offset + num_alpha_primes; + let cross_term_expressions = + cross_term_expressions(&poly_set, &compressed_products, num_folding_challenges); + + let u = num_folding_challenges; + let relexed_compressed_constraint = relaxed_expression(&compressed_products, u); + let relexed_zeta_constraint = { + let e = powers_of_zeta + num_permutation_z_polys + 1; + relaxed_expression(&zeta_products, u) + - Expression::Polynomial(Query::new(e, Rotation::cur())) + }; + + ( + num_builtin_witness_polys, + alpha_prime_offset, + cross_term_expressions, + Some(relexed_compressed_constraint), + relexed_zeta_constraint, + ) + } + }; + + let num_folding_witness_polys = num_witness_polys + num_builtin_witness_polys; + let num_folding_challenges = alpha_prime_offset + num_alpha_primes; + + let [beta, gamma, alpha] = + &array::from_fn(|idx| Expression::::Challenge(num_folding_challenges + 1 + idx)); + let (_, permutation_constraints) = permutation_constraints( + circuit_info, + max_degree, + beta, + gamma, + num_builtin_witness_polys, + ); + + let expression = { + let zero_check_on_every_row = Expression::distribute_powers( + iter::empty() + .chain(Some(&zero_check_on_every_row)) + .chain(&permutation_constraints), + alpha, + ) * Expression::eq_xy(0); + Expression::distribute_powers( + iter::empty() + .chain(&sum_check) + .chain(lookup_zero_checks.iter()) + .chain(Some(&zero_check_on_every_row)), + alpha, + ) + }; + + let (pp, vp) = { + let (mut pp, mut vp) = HyperPlonk::preprocess(param, circuit_info)?; + let batch_size = batch_size(circuit_info, strategy); + let (pcs_pp, pcs_vp) = Pcs::trim(param, 1 << circuit_info.k, batch_size)?; + pp.pcs = pcs_pp; + vp.pcs = pcs_vp; + pp.num_permutation_z_polys = num_permutation_z_polys; + vp.num_permutation_z_polys = num_permutation_z_polys; + pp.expression = expression.clone(); + vp.expression = expression; + (pp, vp) + }; + + let num_cross_terms = cross_term_expressions.len(); + + Ok(( + ProtostarProverParam { + pp, + strategy, + num_theta_primes, + num_alpha_primes, + num_folding_witness_polys, + num_folding_challenges, + cross_term_expressions, + }, + ProtostarVerifierParam { + vp, + strategy, + num_theta_primes, + num_alpha_primes, + num_folding_witness_polys, + num_folding_challenges, + num_cross_terms, + }, + )) +} + +pub(crate) fn max_degree( + circuit_info: &PlonkishCircuitInfo, + lookup_constraints: Option<&[Expression]>, +) -> usize { + let lookup_constraints = lookup_constraints.map(Cow::Borrowed).unwrap_or_else(|| { + let n = circuit_info.lookups.iter().map(Vec::len).max().unwrap_or(1); + let dummy_challenges = vec![Expression::zero(); n]; + Cow::Owned( + self::lookup_constraints(circuit_info, &dummy_challenges, &dummy_challenges[0]).0, + ) + }); + iter::empty() + .chain(circuit_info.constraints.iter().map(Expression::degree)) + .chain(lookup_constraints.iter().map(Expression::degree)) + .chain(circuit_info.max_degree) + .chain(Some(2)) + .max() + .unwrap() +} + +pub(crate) fn folding_degree( + preprocess_polys: &BTreeSet, + expression: &Expression, +) -> usize { + expression.evaluate( + &|_| 0, + &|_| 0, + &|query| (!preprocess_polys.contains(&query.poly())) as usize, + &|_| 1, + &|a| a, + &|a, b| a.max(b), + &|a, b| a + b, + &|a, _| a, + ) +} + +pub(crate) fn lookup_constraints( + circuit_info: &PlonkishCircuitInfo, + theta_primes: &[Expression], + beta_prime: &Expression, +) -> (Vec>, Vec>) { + let one = &Expression::one(); + let m_offset = circuit_info.num_poly() + circuit_info.permutation_polys().len(); + let h_offset = m_offset + circuit_info.lookups.len(); + let constraints = circuit_info + .lookups + .iter() + .zip(m_offset..) + .zip((h_offset..).step_by(2)) + .flat_map(|((lookup, m), h)| { + let [m, h_input, h_table] = &[m, h, h + 1] + .map(|poly| Query::new(poly, Rotation::cur())) + .map(Expression::::Polynomial); + let (inputs, tables) = lookup + .iter() + .map(|(input, table)| (input, table)) + .unzip::<_, _, Vec<_>, Vec<_>>(); + let [input, table] = &[inputs, tables].map(|exprs| { + iter::empty() + .chain(exprs.first().cloned().cloned()) + .chain( + exprs + .into_iter() + .skip(1) + .zip(theta_primes) + .map(|(expr, theta_prime)| expr * theta_prime), + ) + .sum::>() + }); + [ + h_input * (input + beta_prime) - one, + h_table * (table + beta_prime) - m, + ] + }) + .collect_vec(); + let sum_check = (h_offset..) + .step_by(2) + .take(circuit_info.lookups.len()) + .map(|h| { + let [h_input, h_table] = &[h, h + 1] + .map(|poly| Query::new(poly, Rotation::cur())) + .map(Expression::::Polynomial); + h_input - h_table + }) + .collect_vec(); + (constraints, sum_check) +} + +fn powers_of_zeta_constraint(zeta: usize, powers_of_zeta: usize) -> Expression { + let l_0 = &Expression::::lagrange(0); + let l_last = &Expression::::lagrange(-1); + let one = &Expression::one(); + let zeta = &Expression::Challenge(zeta); + let [powers_of_zeta, powers_of_zeta_next] = &[Rotation::cur(), Rotation::next()] + .map(|rotation| Expression::Polynomial(Query::new(powers_of_zeta, rotation))); + + powers_of_zeta_next - (l_0 + l_last * zeta + (one - (l_0 + l_last)) * powers_of_zeta * zeta) +} diff --git a/plonkish_backend/src/accumulation/protostar/hyperplonk/prover.rs b/plonkish_backend/src/accumulation/protostar/hyperplonk/prover.rs new file mode 100644 index 00000000..6fcf02b5 --- /dev/null +++ b/plonkish_backend/src/accumulation/protostar/hyperplonk/prover.rs @@ -0,0 +1,334 @@ +use crate::{ + accumulation::protostar::ProtostarAccumulator, + backend::hyperplonk::prover::instance_polys, + pcs::PolynomialCommitmentScheme, + poly::multilinear::MultilinearPolynomial, + util::{ + arithmetic::{div_ceil, powers, sum, BatchInvert, BooleanHypercube, PrimeField}, + expression::{evaluator::ExpressionRegistry, Expression, Rotation}, + izip, izip_eq, + parallel::{num_threads, par_map_collect, parallelize, parallelize_iter}, + Itertools, + }, +}; +use std::{borrow::Cow, hash::Hash, iter}; + +pub(crate) fn lookup_h_polys( + compressed_polys: &[[MultilinearPolynomial; 2]], + m_polys: &[MultilinearPolynomial], + beta: &F, +) -> Vec<[MultilinearPolynomial; 2]> { + compressed_polys + .iter() + .zip(m_polys.iter()) + .map(|(compressed_polys, m_poly)| lookup_h_poly(compressed_polys, m_poly, beta)) + .collect() +} + +fn lookup_h_poly( + compressed_polys: &[MultilinearPolynomial; 2], + m_poly: &MultilinearPolynomial, + beta: &F, +) -> [MultilinearPolynomial; 2] { + let [input, table] = compressed_polys; + let mut h_input = vec![F::ZERO; 1 << input.num_vars()]; + let mut h_table = vec![F::ZERO; 1 << input.num_vars()]; + + parallelize(&mut h_input, |(h_input, start)| { + for (h_input, input) in h_input.iter_mut().zip(input[start..].iter()) { + *h_input = *beta + input; + } + }); + parallelize(&mut h_table, |(h_table, start)| { + for (h_table, table) in h_table.iter_mut().zip(table[start..].iter()) { + *h_table = *beta + table; + } + }); + + let chunk_size = div_ceil(2 * h_input.len(), num_threads()); + parallelize_iter( + iter::empty() + .chain(h_input.chunks_mut(chunk_size)) + .chain(h_table.chunks_mut(chunk_size)), + |h| { + h.iter_mut().batch_invert(); + }, + ); + + parallelize(&mut h_table, |(h_table, start)| { + for (h_table, m) in h_table.iter_mut().zip(m_poly[start..].iter()) { + *h_table *= m; + } + }); + + if cfg!(feature = "sanity-check") { + assert_eq!(sum::(&h_input), sum::(&h_table)); + } + + [ + MultilinearPolynomial::new(h_input), + MultilinearPolynomial::new(h_table), + ] +} + +pub(super) fn powers_of_zeta_poly( + num_vars: usize, + zeta: F, +) -> MultilinearPolynomial { + let powers_of_zeta = powers(zeta).take(1 << num_vars).collect_vec(); + let nth_map = BooleanHypercube::new(num_vars).nth_map(); + MultilinearPolynomial::new(par_map_collect(&nth_map, |b| powers_of_zeta[*b])) +} + +pub(crate) fn evaluate_cross_term_polys( + cross_term_expressions: &[Expression], + num_vars: usize, + preprocess_polys: &[MultilinearPolynomial], + accumulator: &ProtostarAccumulator, + incoming: &ProtostarAccumulator, +) -> Vec> +where + F: PrimeField, + Pcs: PolynomialCommitmentScheme>, +{ + if cross_term_expressions.is_empty() { + return Vec::new(); + } + + let ev = init_hadamard_evaluator( + cross_term_expressions, + num_vars, + preprocess_polys, + accumulator, + incoming, + ); + + let size = 1 << ev.num_vars; + let chunk_size = div_ceil(size, num_threads()); + let num_cross_terms = ev.reg.indexed_outputs().len(); + + let mut outputs = vec![F::ZERO; num_cross_terms * size]; + parallelize_iter( + outputs + .chunks_mut(chunk_size * num_cross_terms) + .zip((0..).step_by(chunk_size)), + |(outputs, start)| { + let mut data = ev.cache(); + let bs = start..(start + chunk_size).min(size); + izip!(bs, outputs.chunks_mut(num_cross_terms)) + .for_each(|(b, outputs)| ev.evaluate(outputs, &mut data, b)); + }, + ); + + (0..num_cross_terms) + .map(|offset| par_map_collect(0..size, |idx| outputs[idx * num_cross_terms + offset])) + .map(MultilinearPolynomial::new) + .collect_vec() +} + +pub(super) fn evaluate_compressed_cross_term_sums( + cross_term_expressions: &[Expression], + num_vars: usize, + preprocess_polys: &[MultilinearPolynomial], + accumulator: &ProtostarAccumulator, + incoming: &ProtostarAccumulator, +) -> Vec +where + F: PrimeField, + Pcs: PolynomialCommitmentScheme>, +{ + if cross_term_expressions.is_empty() { + return Vec::new(); + } + + let ev = init_hadamard_evaluator( + cross_term_expressions, + num_vars, + preprocess_polys, + accumulator, + incoming, + ); + + let size = 1 << ev.num_vars; + let num_threads = num_threads(); + let chunk_size = div_ceil(size, num_threads); + let num_cross_terms = ev.reg.indexed_outputs().len(); + + let mut partial_sums = vec![vec![F::ZERO; num_cross_terms]; num_threads]; + parallelize_iter( + partial_sums.iter_mut().zip((0..).step_by(chunk_size)), + |(partial_sums, start)| { + let mut data = ev.cache(); + (start..(start + chunk_size).min(size)) + .for_each(|b| ev.evaluate_and_sum(partial_sums, &mut data, b)) + }, + ); + + partial_sums + .into_iter() + .reduce(|mut sums, partial_sums| { + izip_eq!(&mut sums, &partial_sums).for_each(|(sum, partial_sum)| *sum += partial_sum); + sums + }) + .unwrap() +} + +pub(crate) fn evaluate_zeta_cross_term_poly( + num_vars: usize, + zeta_nth_back: usize, + accumulator: &ProtostarAccumulator, + incoming: &ProtostarAccumulator, +) -> MultilinearPolynomial +where + F: PrimeField, + Pcs: PolynomialCommitmentScheme>, +{ + let [(acc_pow, acc_zeta, acc_u), (incoming_pow, incoming_zeta, incoming_u)] = + [accumulator, incoming].map(|witness| { + let pow = witness.witness_polys.last().unwrap(); + let zeta = witness + .instance + .challenges + .iter() + .nth_back(zeta_nth_back) + .unwrap(); + (pow, zeta, witness.instance.u) + }); + assert_eq!(incoming_u, F::ONE); + + let size = 1 << num_vars; + let mut cross_term = vec![F::ZERO; size]; + + let bh = BooleanHypercube::new(num_vars); + let next_map = bh.rotation_map(Rotation::next()); + parallelize(&mut cross_term, |(cross_term, start)| { + cross_term + .iter_mut() + .zip(start..) + .for_each(|(cross_term, b)| { + *cross_term = acc_pow[next_map[b]] + acc_u * incoming_pow[next_map[b]] + - (acc_pow[b] * incoming_zeta + incoming_pow[b] * acc_zeta); + }) + }); + let b_0 = 0; + let b_last = bh.rotate(1, Rotation::prev()); + cross_term[b_0] += acc_pow[b_0] * incoming_zeta + incoming_pow[b_0] * acc_zeta - acc_u.double(); + cross_term[b_last] += acc_pow[b_last] * incoming_zeta + incoming_pow[b_last] * acc_zeta + - acc_u * incoming_zeta + - acc_zeta; + + MultilinearPolynomial::new(cross_term) +} + +fn init_hadamard_evaluator<'a, F, Pcs>( + expressions: &[Expression], + num_vars: usize, + preprocess_polys: &'a [MultilinearPolynomial], + accumulator: &'a ProtostarAccumulator, + incoming: &'a ProtostarAccumulator, +) -> HadamardEvaluator<'a, F> +where + F: PrimeField, + Pcs: PolynomialCommitmentScheme>, +{ + assert!(!expressions.is_empty()); + + let acc_instance_polys = instance_polys(num_vars, &accumulator.instance.instances); + let incoming_instance_polys = instance_polys(num_vars, &incoming.instance.instances); + let polys = iter::empty() + .chain(preprocess_polys.iter().map(Cow::Borrowed)) + .chain(acc_instance_polys.into_iter().map(Cow::Owned)) + .chain(accumulator.witness_polys.iter().map(Cow::Borrowed)) + .chain(incoming_instance_polys.into_iter().map(Cow::Owned)) + .chain(incoming.witness_polys.iter().map(Cow::Borrowed)) + .collect_vec(); + let challenges = iter::empty() + .chain(accumulator.instance.challenges.iter().cloned()) + .chain(Some(accumulator.instance.u)) + .chain(incoming.instance.challenges.iter().cloned()) + .chain(Some(incoming.instance.u)) + .collect_vec(); + + let expressions = expressions + .iter() + .map(|expression| { + expression + .simplified(Some(&challenges)) + .unwrap_or_else(Expression::zero) + }) + .collect_vec(); + + HadamardEvaluator::new(num_vars, &expressions, polys) +} + +#[derive(Clone, Debug)] +pub(crate) struct HadamardEvaluator<'a, F: PrimeField> { + pub(crate) num_vars: usize, + pub(crate) reg: ExpressionRegistry, + lagranges: Vec, + polys: Vec>>, +} + +impl<'a, F: PrimeField> HadamardEvaluator<'a, F> { + pub(crate) fn new( + num_vars: usize, + expressions: &[Expression], + polys: Vec>>, + ) -> Self { + let mut reg = ExpressionRegistry::new(); + for expression in expressions.iter() { + reg.register(expression); + } + assert!(reg.eq_xys().is_empty()); + + let bh = BooleanHypercube::new(num_vars).iter().collect_vec(); + let lagranges = reg + .lagranges() + .iter() + .map(|i| bh[i.rem_euclid(1 << num_vars) as usize]) + .collect_vec(); + + Self { + num_vars, + reg, + lagranges, + polys, + } + } + + pub(crate) fn cache(&self) -> Vec { + self.reg.cache() + } + + pub(crate) fn evaluate(&self, evals: &mut [F], cache: &mut [F], b: usize) { + self.evaluate_calculations(cache, b); + izip_eq!(evals, self.reg.indexed_outputs()).for_each(|(eval, idx)| *eval = cache[*idx]) + } + + pub(crate) fn evaluate_and_sum(&self, sums: &mut [F], cache: &mut [F], b: usize) { + self.evaluate_calculations(cache, b); + izip_eq!(sums, self.reg.indexed_outputs()).for_each(|(sum, idx)| *sum += cache[*idx]) + } + + fn evaluate_calculations(&self, cache: &mut [F], b: usize) { + let bh = BooleanHypercube::new(self.num_vars); + if self.reg.has_identity() { + cache[self.reg.offsets().identity()] = F::from(b as u64); + } + cache[self.reg.offsets().lagranges()..] + .iter_mut() + .zip(&self.lagranges) + .for_each(|(value, i)| *value = if &b == i { F::ONE } else { F::ZERO }); + cache[self.reg.offsets().polys()..] + .iter_mut() + .zip(self.reg.polys()) + .for_each(|(value, (query, _))| { + *value = self.polys[query.poly()][bh.rotate(b, query.rotation())] + }); + self.reg + .indexed_calculations() + .iter() + .zip(self.reg.offsets().calculations()..) + .for_each(|(calculation, idx)| calculation.calculate(cache, idx)); + } +} diff --git a/plonkish_backend/src/accumulation/sangria.rs b/plonkish_backend/src/accumulation/sangria.rs new file mode 100644 index 00000000..83c41185 --- /dev/null +++ b/plonkish_backend/src/accumulation/sangria.rs @@ -0,0 +1,20 @@ +use crate::{ + accumulation::protostar::{ + Protostar, ProtostarAccumulator, ProtostarAccumulatorInstance, ProtostarProverParam, + ProtostarStrategy::NoCompressing, ProtostarVerifierParam, + }, + pcs::PolynomialCommitmentScheme, +}; + +mod hyperplonk; + +pub type Sangria = Protostar; + +pub type SangriaProverParam = ProtostarProverParam; + +pub type SangriaVerifierParam = ProtostarVerifierParam; + +pub type SangriaAccumulator = ProtostarAccumulator; + +pub type SangriaAccumulatorInstance = + ProtostarAccumulatorInstance>::Commitment>; diff --git a/plonkish_backend/src/accumulation/sangria/hyperplonk.rs b/plonkish_backend/src/accumulation/sangria/hyperplonk.rs new file mode 100644 index 00000000..314b2f71 --- /dev/null +++ b/plonkish_backend/src/accumulation/sangria/hyperplonk.rs @@ -0,0 +1,59 @@ +#[cfg(test)] +pub(crate) mod test { + use crate::{ + accumulation::{sangria::Sangria, test::run_accumulation_scheme}, + backend::hyperplonk::{ + util::{rand_vanilla_plonk_circuit, rand_vanilla_plonk_with_lookup_circuit}, + HyperPlonk, + }, + pcs::{ + multilinear::{Gemini, MultilinearIpa, MultilinearKzg, Zeromorph}, + univariate::UnivariateKzg, + }, + util::{ + test::{seeded_std_rng, std_rng}, + transcript::Keccak256Transcript, + Itertools, + }, + }; + use halo2_curves::{bn256::Bn256, grumpkin}; + use std::iter; + + macro_rules! tests { + ($name:ident, $pcs:ty, $num_vars_range:expr) => { + paste::paste! { + #[test] + fn [<$name _sangria_hyperplonk_vanilla_plonk>]() { + run_accumulation_scheme::<_, Sangria>, Keccak256Transcript<_>, _>($num_vars_range, |num_vars| { + let (circuit_info, _) = rand_vanilla_plonk_circuit(num_vars, std_rng(), seeded_std_rng()); + let circuits = iter::repeat_with(|| { + let (_, circuit) = rand_vanilla_plonk_circuit(num_vars, std_rng(), seeded_std_rng()); + circuit + }).take(3).collect_vec(); + (circuit_info, circuits) + }); + } + + #[test] + fn [<$name _sangria_hyperplonk_vanilla_plonk_with_lookup>]() { + run_accumulation_scheme::<_, Sangria>, Keccak256Transcript<_>, _>($num_vars_range, |num_vars| { + let (circuit_info, _) = rand_vanilla_plonk_with_lookup_circuit(num_vars, std_rng(), seeded_std_rng()); + let circuits = iter::repeat_with(|| { + let (_, circuit) = rand_vanilla_plonk_with_lookup_circuit(num_vars, std_rng(), seeded_std_rng()); + circuit + }).take(3).collect_vec(); + (circuit_info, circuits) + }); + } + } + }; + ($name:ident, $pcs:ty) => { + tests!($name, $pcs, 2..16); + }; + } + + tests!(ipa, MultilinearIpa); + tests!(kzg, MultilinearKzg); + tests!(gemini_kzg, Gemini>); + tests!(zeromorph_kzg, Zeromorph>); +} diff --git a/plonkish_backend/src/backend.rs b/plonkish_backend/src/backend.rs index da17e2bf..db879eee 100644 --- a/plonkish_backend/src/backend.rs +++ b/plonkish_backend/src/backend.rs @@ -1,55 +1,49 @@ use crate::{ - pcs::PolynomialCommitmentScheme, + pcs::{CommitmentChunk, PolynomialCommitmentScheme}, util::{ arithmetic::Field, expression::Expression, transcript::{TranscriptRead, TranscriptWrite}, - Itertools, + Deserialize, DeserializeOwned, Itertools, Serialize, }, Error, }; use rand::RngCore; -use std::{borrow::BorrowMut, collections::BTreeSet, fmt::Debug, iter}; +use std::{collections::BTreeSet, fmt::Debug, iter}; pub mod hyperplonk; -pub trait PlonkishBackend: Clone + Debug -where - F: Field, - Pcs: PolynomialCommitmentScheme, -{ - type ProverParam: Debug; - type VerifierParam: Debug; - type ProverState: Debug; - type VerifierState: Debug; +pub trait PlonkishBackend: Clone + Debug { + type Pcs: PolynomialCommitmentScheme; + type ProverParam: Clone + Debug + Serialize + DeserializeOwned; + type VerifierParam: Clone + Debug + Serialize + DeserializeOwned; - fn setup(circuit_info: &PlonkishCircuitInfo, rng: impl RngCore) - -> Result; + fn setup( + circuit_info: &PlonkishCircuitInfo, + rng: impl RngCore, + ) -> Result<>::Param, Error>; fn preprocess( - param: &Pcs::Param, + param: &>::Param, circuit_info: &PlonkishCircuitInfo, ) -> Result<(Self::ProverParam, Self::VerifierParam), Error>; fn prove( pp: &Self::ProverParam, - state: impl BorrowMut, - instances: &[&[F]], circuit: &impl PlonkishCircuit, - transcript: &mut impl TranscriptWrite, + transcript: &mut impl TranscriptWrite, F>, rng: impl RngCore, ) -> Result<(), Error>; fn verify( vp: &Self::VerifierParam, - state: impl BorrowMut, - instances: &[&[F]], - transcript: &mut impl TranscriptRead, + instances: &[Vec], + transcript: &mut impl TranscriptRead, F>, rng: impl RngCore, ) -> Result<(), Error>; } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct PlonkishCircuitInfo { /// 2^k is the size of the circuit pub k: usize, @@ -136,8 +130,12 @@ impl PlonkishCircuitInfo { } pub trait PlonkishCircuit { + fn circuit_info_without_preprocess(&self) -> Result, Error>; + fn circuit_info(&self) -> Result, Error>; + fn instances(&self) -> &[Vec]; + fn synthesize(&self, round: usize, challenges: &[F]) -> Result>, Error>; } @@ -146,20 +144,99 @@ pub trait WitnessEncoding { } #[cfg(any(test, feature = "benchmark"))] -mod test { +mod mock { use crate::{ backend::{PlonkishCircuit, PlonkishCircuitInfo}, Error, }; - impl PlonkishCircuit for Vec> { + pub(crate) struct MockCircuit { + instances: Vec>, + witnesses: Vec>, + } + + impl MockCircuit { + pub(crate) fn new(instances: Vec>, witnesses: Vec>) -> Self { + Self { + instances, + witnesses, + } + } + } + + impl PlonkishCircuit for MockCircuit { + fn circuit_info_without_preprocess(&self) -> Result, Error> { + unreachable!() + } + fn circuit_info(&self) -> Result, Error> { unreachable!() } + fn instances(&self) -> &[Vec] { + &self.instances + } + fn synthesize(&self, round: usize, challenges: &[F]) -> Result>, Error> { assert!(round == 0 && challenges.is_empty()); - Ok(self.to_vec()) + Ok(self.witnesses.clone()) + } + } +} + +#[cfg(test)] +pub(crate) mod test { + use crate::{ + backend::{PlonkishBackend, PlonkishCircuit, PlonkishCircuitInfo}, + pcs::PolynomialCommitmentScheme, + util::{ + arithmetic::PrimeField, + end_timer, start_timer, + test::seeded_std_rng, + transcript::{InMemoryTranscript, TranscriptRead, TranscriptWrite}, + DeserializeOwned, Serialize, + }, + }; + use std::{hash::Hash, ops::Range}; + + pub fn run_plonkish_backend( + num_vars_range: Range, + circuit_fn: impl Fn(usize) -> (PlonkishCircuitInfo, C), + ) where + F: PrimeField + Hash + Serialize + DeserializeOwned, + Pb: PlonkishBackend, + T: TranscriptRead<>::CommitmentChunk, F> + + TranscriptWrite<>::CommitmentChunk, F> + + InMemoryTranscript, + C: PlonkishCircuit, + { + for num_vars in num_vars_range { + let (circuit_info, circuit) = circuit_fn(num_vars); + let instances = circuit.instances(); + + let timer = start_timer(|| format!("setup-{num_vars}")); + let param = Pb::setup(&circuit_info, seeded_std_rng()).unwrap(); + end_timer(timer); + + let timer = start_timer(|| format!("preprocess-{num_vars}")); + let (pp, vp) = Pb::preprocess(¶m, &circuit_info).unwrap(); + end_timer(timer); + + let timer = start_timer(|| format!("prove-{num_vars}")); + let proof = { + let mut transcript = T::new(()); + Pb::prove(&pp, &circuit, &mut transcript, seeded_std_rng()).unwrap(); + transcript.into_proof() + }; + end_timer(timer); + + let timer = start_timer(|| format!("verify-{num_vars}")); + let result = { + let mut transcript = T::from_proof((), proof.as_slice()); + Pb::verify(&vp, instances, &mut transcript, seeded_std_rng()) + }; + assert_eq!(result, Ok(())); + end_timer(timer); } } } diff --git a/plonkish_backend/src/backend/hyperplonk.rs b/plonkish_backend/src/backend/hyperplonk.rs index af1e499d..e94d29fc 100644 --- a/plonkish_backend/src/backend/hyperplonk.rs +++ b/plonkish_backend/src/backend/hyperplonk.rs @@ -18,18 +18,16 @@ use crate::{ expression::Expression, start_timer, transcript::{TranscriptRead, TranscriptWrite}, - Itertools, + Deserialize, DeserializeOwned, Itertools, Serialize, }, Error, }; use rand::RngCore; -use std::{borrow::BorrowMut, fmt::Debug, hash::Hash, iter, marker::PhantomData}; +use std::{fmt::Debug, hash::Hash, iter, marker::PhantomData}; -mod preprocessor; -mod prover; -mod verifier; - -pub mod folding; +pub(crate) mod preprocessor; +pub(crate) mod prover; +pub(crate) mod verifier; #[cfg(any(test, feature = "benchmark"))] pub mod util; @@ -37,53 +35,52 @@ pub mod util; #[derive(Clone, Debug)] pub struct HyperPlonk(PhantomData); -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct HyperPlonkProverParam where F: PrimeField, Pcs: PolynomialCommitmentScheme, { - pcs: Pcs::ProverParam, - num_instances: Vec, - num_witness_polys: Vec, - num_challenges: Vec, - lookups: Vec, Expression)>>, - num_permutation_z_polys: usize, - num_vars: usize, - expression: Expression, - preprocess_polys: Vec>, - preprocess_comms: Vec, - permutation_polys: Vec<(usize, MultilinearPolynomial)>, - permutation_comms: Vec, + pub(crate) pcs: Pcs::ProverParam, + pub(crate) num_instances: Vec, + pub(crate) num_witness_polys: Vec, + pub(crate) num_challenges: Vec, + pub(crate) lookups: Vec, Expression)>>, + pub(crate) num_permutation_z_polys: usize, + pub(crate) num_vars: usize, + pub(crate) expression: Expression, + pub(crate) preprocess_polys: Vec>, + pub(crate) preprocess_comms: Vec, + pub(crate) permutation_polys: Vec<(usize, MultilinearPolynomial)>, + pub(crate) permutation_comms: Vec, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct HyperPlonkVerifierParam where F: PrimeField, Pcs: PolynomialCommitmentScheme, { - pcs: Pcs::VerifierParam, - num_instances: Vec, - num_witness_polys: Vec, - num_challenges: Vec, - num_lookups: usize, - num_permutation_z_polys: usize, - num_vars: usize, - expression: Expression, - preprocess_comms: Vec, - permutation_comms: Vec<(usize, Pcs::Commitment)>, + pub(crate) pcs: Pcs::VerifierParam, + pub(crate) num_instances: Vec, + pub(crate) num_witness_polys: Vec, + pub(crate) num_challenges: Vec, + pub(crate) num_lookups: usize, + pub(crate) num_permutation_z_polys: usize, + pub(crate) num_vars: usize, + pub(crate) expression: Expression, + pub(crate) preprocess_comms: Vec, + pub(crate) permutation_comms: Vec<(usize, Pcs::Commitment)>, } -impl PlonkishBackend for HyperPlonk +impl PlonkishBackend for HyperPlonk where - F: PrimeField + Hash, + F: PrimeField + Hash + Serialize + DeserializeOwned, Pcs: PolynomialCommitmentScheme>, { + type Pcs = Pcs; type ProverParam = HyperPlonkProverParam; type VerifierParam = HyperPlonkVerifierParam; - type ProverState = (); - type VerifierState = (); fn setup( circuit_info: &PlonkishCircuitInfo, @@ -166,19 +163,20 @@ where fn prove( pp: &Self::ProverParam, - _: impl BorrowMut, - instances: &[&[F]], circuit: &impl PlonkishCircuit, transcript: &mut impl TranscriptWrite, _: impl RngCore, ) -> Result<(), Error> { - for (num_instances, instances) in pp.num_instances.iter().zip_eq(instances) { - assert_eq!(instances.len(), *num_instances); - for instance in instances.iter() { - transcript.common_field_element(instance)?; + let instance_polys = { + let instances = circuit.instances(); + for (num_instances, instances) in pp.num_instances.iter().zip_eq(instances) { + assert_eq!(instances.len(), *num_instances); + for instance in instances.iter() { + transcript.common_field_element(instance)?; + } } - } - let instance_polys = instance_polys(pp.num_vars, instances.iter().cloned()); + instance_polys(pp.num_vars, instances) + }; // Round 0..n @@ -294,8 +292,7 @@ where fn verify( vp: &Self::VerifierParam, - _: impl BorrowMut, - instances: &[&[F]], + instances: &[Vec], transcript: &mut impl TranscriptRead, _: impl RngCore, ) -> Result<(), Error> { @@ -372,105 +369,45 @@ impl WitnessEncoding for HyperPlonk { } #[cfg(test)] -pub(crate) mod test { +mod test { use crate::{ backend::{ hyperplonk::{ util::{rand_vanilla_plonk_circuit, rand_vanilla_plonk_with_lookup_circuit}, HyperPlonk, }, - PlonkishBackend, PlonkishCircuit, PlonkishCircuitInfo, + test::run_plonkish_backend, }, pcs::{ multilinear::{ - MultilinearBrakedown, MultilinearHyrax, MultilinearIpa, MultilinearKzg, - MultilinearSimulator, + Gemini, MultilinearBrakedown, MultilinearHyrax, MultilinearIpa, MultilinearKzg, + Zeromorph, }, univariate::UnivariateKzg, - PolynomialCommitmentScheme, }, - poly::multilinear::MultilinearPolynomial, util::{ - arithmetic::PrimeField, - code::BrakedownSpec6, - end_timer, - hash::Keccak256, - start_timer, - test::seeded_std_rng, - transcript::{ - InMemoryTranscript, Keccak256Transcript, TranscriptRead, TranscriptWrite, - }, - Itertools, + code::BrakedownSpec6, hash::Keccak256, test::seeded_std_rng, + transcript::Keccak256Transcript, }, }; use halo2_curves::{ bn256::{self, Bn256}, grumpkin, }; - use std::{hash::Hash, ops::Range}; - - pub(crate) fn run_hyperplonk( - num_vars_range: Range, - circuit_fn: impl Fn(usize) -> (PlonkishCircuitInfo, Vec>, C), - ) where - F: PrimeField + Hash, - Pcs: PolynomialCommitmentScheme>, - T: TranscriptRead - + TranscriptWrite - + InMemoryTranscript, - C: PlonkishCircuit, - { - for num_vars in num_vars_range { - let (circuit_info, instances, circuit) = circuit_fn(num_vars); - let instances = instances.iter().map(Vec::as_slice).collect_vec(); - - let timer = start_timer(|| format!("setup-{num_vars}")); - let param = HyperPlonk::::setup(&circuit_info, seeded_std_rng()).unwrap(); - end_timer(timer); - - let timer = start_timer(|| format!("preprocess-{num_vars}")); - let (pp, vp) = HyperPlonk::::preprocess(¶m, &circuit_info).unwrap(); - end_timer(timer); - - let timer = start_timer(|| format!("prove-{num_vars}")); - let proof = { - let mut transcript = T::default(); - HyperPlonk::::prove( - &pp, - (), - &instances, - &circuit, - &mut transcript, - seeded_std_rng(), - ) - .unwrap(); - transcript.into_proof() - }; - end_timer(timer); - - let timer = start_timer(|| format!("verify-{num_vars}")); - let result = { - let mut transcript = T::from_proof(proof.as_slice()); - HyperPlonk::::verify(&vp, (), &instances, &mut transcript, seeded_std_rng()) - }; - assert_eq!(result, Ok(())); - end_timer(timer); - } - } macro_rules! tests { ($name:ident, $pcs:ty, $num_vars_range:expr) => { paste::paste! { #[test] fn [<$name _hyperplonk_vanilla_plonk>]() { - run_hyperplonk::<_, $pcs, Keccak256Transcript<_>, _>($num_vars_range, |num_vars| { + run_plonkish_backend::<_, HyperPlonk<$pcs>, Keccak256Transcript<_>, _>($num_vars_range, |num_vars| { rand_vanilla_plonk_circuit(num_vars, seeded_std_rng(), seeded_std_rng()) }); } #[test] fn [<$name _hyperplonk_vanilla_plonk_with_lookup>]() { - run_hyperplonk::<_, $pcs, Keccak256Transcript<_>, _>($num_vars_range, |num_vars| { + run_plonkish_backend::<_, HyperPlonk<$pcs>, Keccak256Transcript<_>, _>($num_vars_range, |num_vars| { rand_vanilla_plonk_with_lookup_circuit(num_vars, seeded_std_rng(), seeded_std_rng()) }); } @@ -485,5 +422,6 @@ pub(crate) mod test { tests!(hyrax, MultilinearHyrax, 5..16); tests!(ipa, MultilinearIpa); tests!(kzg, MultilinearKzg); - tests!(sim_kzg, MultilinearSimulator>); + tests!(gemini_kzg, Gemini>); + tests!(zeromorph_kzg, Zeromorph>); } diff --git a/plonkish_backend/src/backend/hyperplonk/folding.rs b/plonkish_backend/src/backend/hyperplonk/folding.rs deleted file mode 100644 index e46fffad..00000000 --- a/plonkish_backend/src/backend/hyperplonk/folding.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod sangria; - -pub use sangria::{Sangria, SangriaProverParam, SangriaProverState, SangriaVerifierParam}; diff --git a/plonkish_backend/src/backend/hyperplonk/folding/sangria.rs b/plonkish_backend/src/backend/hyperplonk/folding/sangria.rs deleted file mode 100644 index e471cbd6..00000000 --- a/plonkish_backend/src/backend/hyperplonk/folding/sangria.rs +++ /dev/null @@ -1,642 +0,0 @@ -use crate::{ - backend::{ - hyperplonk::{ - folding::sangria::{ - preprocessor::batch_size, - preprocessor::preprocess, - prover::{evaluate_cross_term, lookup_h_polys, SangriaWitness}, - verifier::SangriaInstance, - }, - prover::{ - instance_polys, lookup_compressed_polys, lookup_m_polys, permutation_z_polys, - prove_zero_check, - }, - verifier::verify_zero_check, - HyperPlonk, HyperPlonkProverParam, HyperPlonkVerifierParam, - }, - PlonkishBackend, PlonkishCircuit, PlonkishCircuitInfo, WitnessEncoding, - }, - pcs::{AdditiveCommitment, PolynomialCommitmentScheme}, - poly::multilinear::MultilinearPolynomial, - util::{ - arithmetic::PrimeField, - end_timer, - expression::Expression, - start_timer, - transcript::{TranscriptRead, TranscriptWrite}, - Itertools, - }, - Error, -}; -use rand::RngCore; -use std::{borrow::BorrowMut, hash::Hash, iter, marker::PhantomData}; - -pub(crate) mod preprocessor; -pub(crate) mod prover; -mod verifier; - -#[derive(Clone, Debug)] -pub struct Sangria(PhantomData); - -#[derive(Debug)] -pub struct SangriaProverParam -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - pp: HyperPlonkProverParam, - num_theta_primes: usize, - num_alpha_primes: usize, - num_folding_wintess_polys: usize, - num_folding_challenges: usize, - cross_term_expressions: Vec>, - zero_check_expression: Expression, -} - -impl SangriaProverParam -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - pub fn init(&self) -> SangriaProverState { - SangriaProverState { - is_folding: true, - witness: SangriaWitness::init( - self.pp.num_vars, - &self.pp.num_instances, - self.num_folding_wintess_polys, - self.num_folding_challenges, - ), - } - } -} - -#[derive(Debug)] -pub struct SangriaVerifierParam -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - vp: HyperPlonkVerifierParam, - num_theta_primes: usize, - num_alpha_primes: usize, - num_folding_wintess_polys: usize, - num_folding_challenges: usize, - num_cross_terms: usize, - zero_check_expression: Expression, -} - -impl SangriaVerifierParam -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - pub fn init(&self) -> SangriaVerifierState { - SangriaVerifierState { - is_folding: true, - instance: SangriaInstance::init( - &self.vp.num_instances, - self.num_folding_wintess_polys, - self.num_folding_challenges, - ), - } - } -} -#[derive(Debug)] -pub struct SangriaProverState -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - is_folding: bool, - witness: SangriaWitness, -} - -impl SangriaProverState -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - pub fn set_folding(&mut self, is_folding: bool) { - self.is_folding = is_folding; - } -} - -#[derive(Debug)] -pub struct SangriaVerifierState -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - is_folding: bool, - instance: SangriaInstance, -} - -impl SangriaVerifierState -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - pub fn set_folding(&mut self, is_folding: bool) { - self.is_folding = is_folding; - } -} - -impl PlonkishBackend for Sangria> -where - F: PrimeField + Ord + Hash, - Pcs: PolynomialCommitmentScheme>, - Pcs::Commitment: AdditiveCommitment, - Pcs::CommitmentChunk: AdditiveCommitment, -{ - type ProverParam = SangriaProverParam; - type VerifierParam = SangriaVerifierParam; - type ProverState = SangriaProverState; - type VerifierState = SangriaVerifierState; - - fn setup( - circuit_info: &PlonkishCircuitInfo, - rng: impl RngCore, - ) -> Result { - assert!(circuit_info.is_well_formed()); - - let num_vars = circuit_info.k; - let poly_size = 1 << num_vars; - let batch_size = batch_size(circuit_info); - Pcs::setup(poly_size, batch_size, rng) - } - - fn preprocess( - param: &Pcs::Param, - circuit_info: &PlonkishCircuitInfo, - ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { - assert!(circuit_info.is_well_formed()); - - preprocess(param, circuit_info) - } - - fn prove( - pp: &Self::ProverParam, - mut state: impl BorrowMut, - instances: &[&[F]], - circuit: &impl PlonkishCircuit, - transcript: &mut impl TranscriptWrite, - _: impl RngCore, - ) -> Result<(), Error> { - let SangriaProverParam { - pp, - num_theta_primes, - num_alpha_primes, - cross_term_expressions, - zero_check_expression, - .. - } = pp; - let state = state.borrow_mut(); - - for (num_instances, instances) in pp.num_instances.iter().zip_eq(instances) { - assert_eq!(instances.len(), *num_instances); - for instance in instances.iter() { - transcript.common_field_element(instance)?; - } - } - - // Round 0..n - - let mut witness_polys = Vec::with_capacity(pp.num_witness_polys.iter().sum()); - let mut witness_comms = Vec::with_capacity(witness_polys.len()); - let mut challenges = Vec::with_capacity(pp.num_challenges.iter().sum::()); - for (round, (num_witness_polys, num_folding_challenges)) in pp - .num_witness_polys - .iter() - .zip_eq(pp.num_challenges.iter()) - .enumerate() - { - let timer = start_timer(|| format!("witness_collector-{round}")); - let polys = circuit - .synthesize(round, &challenges)? - .into_iter() - .map(MultilinearPolynomial::new) - .collect_vec(); - assert_eq!(polys.len(), *num_witness_polys); - end_timer(timer); - - witness_comms.extend(Pcs::batch_commit_and_write(&pp.pcs, &polys, transcript)?); - witness_polys.extend(polys); - challenges.extend(transcript.squeeze_challenges(*num_folding_challenges)); - } - - // Round n - - let theta_primes = transcript.squeeze_challenges(*num_theta_primes); - - let timer = start_timer(|| format!("lookup_compressed_polys-{}", pp.lookups.len())); - let lookup_compressed_polys = { - let instance_polys = instance_polys(pp.num_vars, instances.iter().cloned()); - let polys = iter::empty() - .chain(instance_polys.iter()) - .chain(pp.preprocess_polys.iter()) - .chain(witness_polys.iter()) - .collect_vec(); - let thetas = iter::empty() - .chain(Some(F::ONE)) - .chain(theta_primes.iter().cloned()) - .collect_vec(); - lookup_compressed_polys(&pp.lookups, &polys, &challenges, &thetas) - }; - end_timer(timer); - - let timer = start_timer(|| format!("lookup_m_polys-{}", pp.lookups.len())); - let lookup_m_polys = lookup_m_polys(&lookup_compressed_polys)?; - end_timer(timer); - - let lookup_m_comms = Pcs::batch_commit_and_write(&pp.pcs, &lookup_m_polys, transcript)?; - - // Round n+1 - - let beta_prime = transcript.squeeze_challenge(); - - let timer = start_timer(|| format!("lookup_h_polys-{}", pp.lookups.len())); - let lookup_h_polys = lookup_h_polys(&lookup_compressed_polys, &lookup_m_polys, &beta_prime); - end_timer(timer); - - let lookup_h_comms = { - let polys = lookup_h_polys.iter().flatten(); - Pcs::batch_commit_and_write(&pp.pcs, polys, transcript)? - }; - - // Round n+2 - - let alpha_primes = transcript.squeeze_challenges(*num_alpha_primes); - - let incoming = SangriaWitness::from_committed( - pp.num_vars, - instances, - iter::empty() - .chain(witness_polys) - .chain(lookup_m_polys) - .chain(lookup_h_polys.into_iter().flatten()), - iter::empty() - .chain(witness_comms) - .chain(lookup_m_comms) - .chain(lookup_h_comms), - iter::empty() - .chain(challenges) - .chain(theta_primes) - .chain(Some(beta_prime)) - .chain(alpha_primes) - .collect(), - ); - - let timer = start_timer(|| format!("cross_term_polys-{}", cross_term_expressions.len())); - let cross_term_polys = evaluate_cross_term( - cross_term_expressions, - pp.num_vars, - &pp.preprocess_polys, - &state.witness, - &incoming, - ); - end_timer(timer); - - let cross_term_comms = Pcs::batch_commit_and_write(&pp.pcs, &cross_term_polys, transcript)?; - - // Round n+3 - - let r = transcript.squeeze_challenge(); - - let timer = start_timer(|| "fold"); - state - .witness - .fold(&incoming, &cross_term_polys, &cross_term_comms, &r); - end_timer(timer); - - if !state.is_folding { - let beta = transcript.squeeze_challenge(); - let gamma = transcript.squeeze_challenge(); - - let timer = - start_timer(|| format!("permutation_z_polys-{}", pp.permutation_polys.len())); - let builtin_witness_poly_offset = pp.num_witness_polys.iter().sum::(); - let instance_polys = instance_polys(pp.num_vars, &state.witness.instance.instances); - let polys = iter::empty() - .chain(&instance_polys) - .chain(&pp.preprocess_polys) - .chain(&state.witness.witness_polys[..builtin_witness_poly_offset]) - .chain(pp.permutation_polys.iter().map(|(_, poly)| poly)) - .collect_vec(); - let permutation_z_polys = permutation_z_polys( - pp.num_permutation_z_polys, - &pp.permutation_polys, - &polys, - &beta, - &gamma, - ); - end_timer(timer); - - let permutation_z_comms = - Pcs::batch_commit_and_write(&pp.pcs, &permutation_z_polys, transcript)?; - - // Round n+4 - - let alpha = transcript.squeeze_challenge(); - let y = transcript.squeeze_challenges(pp.num_vars); - - let polys = iter::empty() - .chain(polys) - .chain(&state.witness.witness_polys[builtin_witness_poly_offset..]) - .chain(permutation_z_polys.iter()) - .chain(Some(&state.witness.e_poly)) - .collect_vec(); - let challenges = iter::empty() - .chain(state.witness.instance.challenges.iter().copied()) - .chain([beta, gamma, alpha, state.witness.instance.u]) - .collect(); - let (points, evals) = { - prove_zero_check( - pp.num_instances.len(), - zero_check_expression, - &polys, - challenges, - y, - transcript, - )? - }; - - // PCS open - - let dummy_comm = Pcs::Commitment::default(); - let comms = iter::empty() - .chain(iter::repeat(&dummy_comm).take(pp.num_instances.len())) - .chain(&pp.preprocess_comms) - .chain(&state.witness.instance.witness_comms[..builtin_witness_poly_offset]) - .chain(&pp.permutation_comms) - .chain(&state.witness.instance.witness_comms[builtin_witness_poly_offset..]) - .chain(&permutation_z_comms) - .chain(Some(&state.witness.instance.e_comm)) - .collect_vec(); - let timer = start_timer(|| format!("pcs_batch_open-{}", evals.len())); - Pcs::batch_open(&pp.pcs, polys, comms, &points, &evals, transcript)?; - end_timer(timer); - } - - Ok(()) - } - - fn verify( - vp: &Self::VerifierParam, - mut state: impl BorrowMut, - instances: &[&[F]], - transcript: &mut impl TranscriptRead, - _: impl RngCore, - ) -> Result<(), Error> { - let SangriaVerifierParam { - vp, - num_theta_primes, - num_alpha_primes, - num_cross_terms, - zero_check_expression, - .. - } = vp; - let state = state.borrow_mut(); - - for (num_instances, instances) in vp.num_instances.iter().zip_eq(instances) { - assert_eq!(instances.len(), *num_instances); - for instance in instances.iter() { - transcript.common_field_element(instance)?; - } - } - - // Round 0..n - - let mut witness_comms = Vec::with_capacity(vp.num_witness_polys.iter().sum()); - let mut challenges = Vec::with_capacity(vp.num_challenges.iter().sum::() + 4); - for (num_polys, num_folding_challenges) in - vp.num_witness_polys.iter().zip_eq(vp.num_challenges.iter()) - { - witness_comms.extend(Pcs::read_commitments(&vp.pcs, *num_polys, transcript)?); - challenges.extend(transcript.squeeze_challenges(*num_folding_challenges)); - } - - // Round n - - let theta_primes = transcript.squeeze_challenges(*num_theta_primes); - - let lookup_m_comms = Pcs::read_commitments(&vp.pcs, vp.num_lookups, transcript)?; - - // Round n+1 - - let beta_prime = transcript.squeeze_challenge(); - - let lookup_h_comms = Pcs::read_commitments(&vp.pcs, 2 * vp.num_lookups, transcript)?; - - // Round n+2 - - let alpha_primes = transcript.squeeze_challenges(*num_alpha_primes); - - let incoming = SangriaInstance::from_committed( - instances, - iter::empty() - .chain(witness_comms) - .chain(lookup_m_comms) - .chain(lookup_h_comms), - iter::empty() - .chain(challenges) - .chain(theta_primes) - .chain(Some(beta_prime)) - .chain(alpha_primes) - .collect(), - ); - - let cross_term_comms = Pcs::read_commitments(&vp.pcs, *num_cross_terms, transcript)?; - - // Round n+3 - - let r = transcript.squeeze_challenge(); - - state.instance.fold(&incoming, &cross_term_comms, &r); - - if !state.is_folding { - let beta = transcript.squeeze_challenge(); - let gamma = transcript.squeeze_challenge(); - - let permutation_z_comms = - Pcs::read_commitments(&vp.pcs, vp.num_permutation_z_polys, transcript)?; - - // Round n+4 - - let alpha = transcript.squeeze_challenge(); - let y = transcript.squeeze_challenges(vp.num_vars); - - let instances = state.instance.instance_slices(); - let challenges = iter::empty() - .chain(state.instance.challenges.iter().copied()) - .chain([beta, gamma, alpha, state.instance.u]) - .collect_vec(); - let (points, evals) = { - verify_zero_check( - vp.num_vars, - zero_check_expression, - &instances, - &challenges, - &y, - transcript, - )? - }; - - // PCS verify - - let builtin_witness_poly_offset = vp.num_witness_polys.iter().sum::(); - let dummy_comm = Pcs::Commitment::default(); - let comms = iter::empty() - .chain(iter::repeat(&dummy_comm).take(vp.num_instances.len())) - .chain(&vp.preprocess_comms) - .chain(&state.instance.witness_comms[..builtin_witness_poly_offset]) - .chain(vp.permutation_comms.iter().map(|(_, comm)| comm)) - .chain(&state.instance.witness_comms[builtin_witness_poly_offset..]) - .chain(&permutation_z_comms) - .chain(Some(&state.instance.e_comm)) - .collect_vec(); - Pcs::batch_verify(&vp.pcs, comms, &points, &evals, transcript)?; - } - - Ok(()) - } -} - -impl WitnessEncoding for Sangria { - fn row_mapping(k: usize) -> Vec { - Pb::row_mapping(k) - } -} - -#[cfg(test)] -pub(crate) mod test { - use crate::{ - backend::{ - hyperplonk::{ - folding::sangria::Sangria, - util::{rand_vanilla_plonk_circuit, rand_vanilla_plonk_with_lookup_circuit}, - HyperPlonk, - }, - PlonkishBackend, PlonkishCircuit, PlonkishCircuitInfo, - }, - pcs::{ - multilinear::{MultilinearIpa, MultilinearKzg, MultilinearSimulator}, - univariate::UnivariateKzg, - AdditiveCommitment, PolynomialCommitmentScheme, - }, - poly::multilinear::MultilinearPolynomial, - util::{ - arithmetic::PrimeField, - end_timer, start_timer, - test::{seeded_std_rng, std_rng}, - transcript::{ - InMemoryTranscript, Keccak256Transcript, TranscriptRead, TranscriptWrite, - }, - Itertools, - }, - }; - use halo2_curves::{bn256::Bn256, grumpkin}; - use std::{hash::Hash, iter, ops::Range}; - - pub(crate) fn run_sangria_hyperplonk( - num_vars_range: Range, - circuit_fn: impl Fn(usize) -> (PlonkishCircuitInfo, Vec>>, Vec), - ) where - F: PrimeField + Ord + Hash, - Pcs: PolynomialCommitmentScheme>, - Pcs::Commitment: AdditiveCommitment, - Pcs::CommitmentChunk: AdditiveCommitment, - T: TranscriptRead - + TranscriptWrite - + InMemoryTranscript, - C: PlonkishCircuit, - { - for num_vars in num_vars_range { - let (circuit_info, instances, circuits) = circuit_fn(num_vars); - - let timer = start_timer(|| format!("setup-{num_vars}")); - let param = Sangria::>::setup(&circuit_info, seeded_std_rng()).unwrap(); - end_timer(timer); - - let timer = start_timer(|| format!("preprocess-{num_vars}")); - let (pp, vp) = Sangria::>::preprocess(¶m, &circuit_info).unwrap(); - end_timer(timer); - - let (mut prover_state, mut verifier_state) = (pp.init(), vp.init()); - for (idx, (instances, circuit)) in instances.iter().zip_eq(circuits.iter()).enumerate() - { - let is_folding = idx != circuits.len() - 1; - let instances = instances.iter().map(Vec::as_slice).collect_vec(); - - let timer = start_timer(|| format!("prove-{num_vars}")); - let proof = { - prover_state.set_folding(is_folding); - let mut transcript = T::default(); - Sangria::>::prove( - &pp, - &mut prover_state, - &instances, - circuit, - &mut transcript, - seeded_std_rng(), - ) - .unwrap(); - transcript.into_proof() - }; - end_timer(timer); - - let timer = start_timer(|| format!("verify-{num_vars}")); - let result = { - verifier_state.set_folding(is_folding); - let mut transcript = T::from_proof(proof.as_slice()); - Sangria::>::verify( - &vp, - &mut verifier_state, - &instances, - &mut transcript, - seeded_std_rng(), - ) - }; - assert_eq!(result, Ok(())); - end_timer(timer); - } - } - } - - macro_rules! tests { - ($name:ident, $pcs:ty, $num_vars_range:expr) => { - paste::paste! { - #[test] - fn [<$name _hyperplonk_sangria_vanilla_plonk>]() { - run_sangria_hyperplonk::<_, $pcs, Keccak256Transcript<_>, _>($num_vars_range, |num_vars| { - let (circuit_info, _, _) = rand_vanilla_plonk_circuit(num_vars, std_rng(), seeded_std_rng()); - let (instances, circuits) = iter::repeat_with(|| { - let (_, instances, circuit) = rand_vanilla_plonk_circuit(num_vars, std_rng(), seeded_std_rng()); - (instances, circuit) - }).take(3).unzip(); - (circuit_info, instances, circuits) - }); - } - - #[test] - fn [<$name _hyperplonk_sangria_vanilla_plonk_with_lookup>]() { - run_sangria_hyperplonk::<_, $pcs, Keccak256Transcript<_>, _>($num_vars_range, |num_vars| { - let (circuit_info, _, _) = rand_vanilla_plonk_with_lookup_circuit(num_vars, std_rng(), seeded_std_rng()); - let (instances, circuits) = iter::repeat_with(|| { - let (_, instances, circuit) = rand_vanilla_plonk_with_lookup_circuit(num_vars, std_rng(), seeded_std_rng()); - (instances, circuit) - }).take(3).unzip(); - (circuit_info, instances, circuits) - }); - } - } - }; - ($name:ident, $pcs:ty) => { - tests!($name, $pcs, 2..16); - }; - } - - tests!(ipa, MultilinearIpa); - tests!(kzg, MultilinearKzg); - tests!(sim_kzg, MultilinearSimulator>); -} diff --git a/plonkish_backend/src/backend/hyperplonk/folding/sangria/preprocessor.rs b/plonkish_backend/src/backend/hyperplonk/folding/sangria/preprocessor.rs deleted file mode 100644 index b114e2b0..00000000 --- a/plonkish_backend/src/backend/hyperplonk/folding/sangria/preprocessor.rs +++ /dev/null @@ -1,280 +0,0 @@ -use crate::{ - backend::{ - hyperplonk::{ - folding::sangria::{SangriaProverParam, SangriaVerifierParam}, - preprocessor::permutation_constraints, - HyperPlonk, - }, - PlonkishBackend, PlonkishCircuitInfo, - }, - pcs::PolynomialCommitmentScheme, - poly::multilinear::MultilinearPolynomial, - util::{ - arithmetic::{div_ceil, PrimeField}, - chain, - expression::{ - relaxed::{cross_term_expressions, products, relaxed_expression}, - Expression, Query, Rotation, - }, - Itertools, - }, - Error, -}; -use std::{ - array, - borrow::Cow, - collections::{BTreeSet, HashSet}, - hash::Hash, - iter, -}; - -pub(crate) fn batch_size(circuit_info: &PlonkishCircuitInfo) -> usize { - let num_lookups = circuit_info.lookups.len(); - let num_permutation_polys = circuit_info.permutation_polys().len(); - chain![ - [circuit_info.preprocess_polys.len() + circuit_info.permutation_polys().len()], - circuit_info.num_witness_polys.clone(), - [num_lookups], - [2 * num_lookups + div_ceil(num_permutation_polys, max_degree(circuit_info, None) - 1)], - [1], - ] - .sum() -} - -#[allow(clippy::type_complexity)] -pub(super) fn preprocess( - param: &Pcs::Param, - circuit_info: &PlonkishCircuitInfo, -) -> Result<(SangriaProverParam, SangriaVerifierParam), Error> -where - F: PrimeField + Ord + Hash, - Pcs: PolynomialCommitmentScheme>, -{ - let challenge_offset = circuit_info.num_challenges.iter().sum::(); - let max_lookup_width = circuit_info.lookups.iter().map(Vec::len).max().unwrap_or(0); - let num_theta_primes = max_lookup_width.checked_sub(1).unwrap_or_default(); - let theta_primes = (challenge_offset..) - .take(num_theta_primes) - .map(Expression::::Challenge) - .collect_vec(); - let beta_prime = &Expression::::Challenge(challenge_offset + num_theta_primes); - - let (lookup_constraints, lookup_zero_checks) = - lookup_constraints(circuit_info, &theta_primes, beta_prime); - - let max_degree = iter::empty() - .chain(circuit_info.constraints.iter()) - .chain(lookup_constraints.iter()) - .map(Expression::degree) - .chain(circuit_info.max_degree) - .chain(Some(2)) - .max() - .unwrap(); - - let permutation_polys = circuit_info.permutation_polys(); - let preprocess_polys = iter::empty() - .chain((circuit_info.num_instances.len()..).take(circuit_info.preprocess_polys.len())) - .chain((circuit_info.num_poly()..).take(permutation_polys.len())) - .collect(); - - let num_constraints = circuit_info.constraints.len() + lookup_constraints.len(); - let num_alpha_primes = num_constraints.checked_sub(1).unwrap_or_default(); - - let products = { - let mut constraints = iter::empty() - .chain(circuit_info.constraints.iter()) - .chain(lookup_constraints.iter()) - .collect_vec(); - let folding_degrees = constraints - .iter() - .map(|constraint| folding_degree(&preprocess_polys, constraint)) - .enumerate() - .sorted_by(|a, b| b.1.cmp(&a.1)) - .collect_vec(); - if let &[a, b, ..] = &folding_degrees[..] { - if a.1 != b.1 { - constraints.swap(0, a.0); - } - } - let constraint = iter::empty() - .chain(constraints.first().cloned().cloned()) - .chain( - constraints - .into_iter() - .skip(1) - .zip((challenge_offset + num_theta_primes + 1..).map(Expression::Challenge)) - .map(|(constraint, challenge)| constraint * challenge), - ) - .sum(); - products(&preprocess_polys, &constraint) - }; - - let num_witness_polys = circuit_info.num_witness_polys.iter().sum::(); - let witness_poly_offset = - circuit_info.num_instances.len() + circuit_info.preprocess_polys.len(); - let internal_witness_poly_offset = - witness_poly_offset + num_witness_polys + permutation_polys.len(); - - let folding_polys = iter::empty() - .chain(0..circuit_info.num_instances.len()) - .chain((witness_poly_offset..).take(num_witness_polys)) - .chain((internal_witness_poly_offset..).take(3 * circuit_info.lookups.len())) - .collect::>(); - let num_folding_wintess_polys = num_witness_polys + 3 * circuit_info.lookups.len(); - let num_folding_challenges = challenge_offset + num_theta_primes + 1 + num_alpha_primes; - - let cross_term_expressions = cross_term_expressions( - circuit_info.num_instances.len(), - circuit_info.preprocess_polys.len(), - folding_polys, - num_folding_challenges, - &products, - ); - let num_cross_terms = cross_term_expressions.len(); - - let [beta, gamma, alpha] = - &array::from_fn(|idx| Expression::::Challenge(num_folding_challenges + idx)); - let (num_chunks, permutation_constraints) = permutation_constraints( - circuit_info, - max_degree, - beta, - gamma, - 3 * circuit_info.lookups.len(), - ); - - let relexed_constraint = { - let u = num_folding_challenges + 3; - let e_poly = circuit_info.num_poly() - + permutation_polys.len() - + circuit_info.lookups.len() * 3 - + num_chunks; - relaxed_expression(&products, u) - - Expression::Polynomial(Query::new(e_poly, Rotation::cur())) - }; - let zero_check_on_every_row = Expression::distribute_powers( - iter::empty() - .chain(Some(&relexed_constraint)) - .chain(permutation_constraints.iter()), - alpha, - ) * Expression::eq_xy(0); - let zero_check_expression = Expression::distribute_powers( - iter::empty() - .chain(lookup_zero_checks.iter()) - .chain(Some(&zero_check_on_every_row)), - alpha, - ); - - let (mut pp, mut vp) = HyperPlonk::preprocess(param, circuit_info)?; - let (pcs_pp, pcs_vp) = Pcs::trim(param, 1 << circuit_info.k, batch_size(circuit_info))?; - pp.pcs = pcs_pp; - vp.pcs = pcs_vp; - - Ok(( - SangriaProverParam { - pp, - num_theta_primes, - num_alpha_primes, - num_folding_wintess_polys, - num_folding_challenges, - cross_term_expressions, - zero_check_expression: zero_check_expression.clone(), - }, - SangriaVerifierParam { - vp, - num_theta_primes, - num_alpha_primes, - num_folding_wintess_polys, - num_folding_challenges, - num_cross_terms, - zero_check_expression, - }, - )) -} - -pub(crate) fn max_degree( - circuit_info: &PlonkishCircuitInfo, - lookup_constraints: Option<&[Expression]>, -) -> usize { - let lookup_constraints = lookup_constraints.map(Cow::Borrowed).unwrap_or_else(|| { - let n = circuit_info.lookups.iter().map(Vec::len).max().unwrap_or(1); - let dummy_challenges = vec![Expression::zero(); n]; - Cow::Owned( - self::lookup_constraints(circuit_info, &dummy_challenges, &dummy_challenges[0]).0, - ) - }); - iter::empty() - .chain(circuit_info.constraints.iter().map(Expression::degree)) - .chain(lookup_constraints.iter().map(Expression::degree)) - .chain(circuit_info.max_degree) - .chain(Some(2)) - .max() - .unwrap() -} - -pub(crate) fn lookup_constraints( - circuit_info: &PlonkishCircuitInfo, - theta_primes: &[Expression], - beta_prime: &Expression, -) -> (Vec>, Vec>) { - let one = &Expression::one(); - let m_offset = circuit_info.num_poly() + circuit_info.permutation_polys().len(); - let h_offset = m_offset + circuit_info.lookups.len(); - let constraints = circuit_info - .lookups - .iter() - .zip(m_offset..) - .zip((h_offset..).step_by(2)) - .flat_map(|((lookup, m), h)| { - let [m, h_input, h_table] = &[m, h, h + 1] - .map(|poly| Query::new(poly, Rotation::cur())) - .map(Expression::::Polynomial); - let (inputs, tables) = lookup - .iter() - .map(|(input, table)| (input, table)) - .unzip::<_, _, Vec<_>, Vec<_>>(); - let [input, table] = &[inputs, tables].map(|exprs| { - iter::empty() - .chain(exprs.first().cloned().cloned()) - .chain( - exprs - .into_iter() - .skip(1) - .zip(theta_primes) - .map(|(expr, theta_prime)| expr * theta_prime), - ) - .sum::>() - }); - [ - h_input * (input + beta_prime) - one, - h_table * (table + beta_prime) - m, - ] - }) - .collect_vec(); - let sum_check = (h_offset..) - .step_by(2) - .take(circuit_info.lookups.len()) - .map(|h| { - let [h_input, h_table] = &[h, h + 1] - .map(|poly| Query::new(poly, Rotation::cur())) - .map(Expression::::Polynomial); - h_input - h_table - }) - .collect_vec(); - (constraints, sum_check) -} - -pub(crate) fn folding_degree( - preprocess_polys: &HashSet, - expression: &Expression, -) -> usize { - expression.evaluate( - &|_| 0, - &|_| 0, - &|query| (!preprocess_polys.contains(&query.poly())) as usize, - &|_| 1, - &|a| a, - &|a, b| a.max(b), - &|a, b| a + b, - &|a, _| a, - ) -} diff --git a/plonkish_backend/src/backend/hyperplonk/folding/sangria/prover.rs b/plonkish_backend/src/backend/hyperplonk/folding/sangria/prover.rs deleted file mode 100644 index ec837c77..00000000 --- a/plonkish_backend/src/backend/hyperplonk/folding/sangria/prover.rs +++ /dev/null @@ -1,271 +0,0 @@ -use crate::{ - backend::hyperplonk::{folding::sangria::verifier::SangriaInstance, prover::instance_polys}, - pcs::{AdditiveCommitment, Polynomial, PolynomialCommitmentScheme}, - poly::multilinear::MultilinearPolynomial, - util::{ - arithmetic::{div_ceil, powers, sum, BatchInvert, BooleanHypercube, PrimeField}, - chain, - expression::{evaluator::ExpressionRegistry, Expression}, - izip, izip_eq, - parallel::{num_threads, par_map_collect, parallelize, parallelize_iter}, - Itertools, - }, -}; -use std::{hash::Hash, iter}; - -#[derive(Debug)] -pub(crate) struct SangriaWitness -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - pub(crate) instance: SangriaInstance, - pub(crate) witness_polys: Vec, - pub(crate) e_poly: Pcs::Polynomial, -} - -impl SangriaWitness -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - pub(crate) fn init( - k: usize, - num_instances: &[usize], - num_witness_polys: usize, - num_challenges: usize, - ) -> Self { - let zero_poly = Pcs::Polynomial::from_evals(vec![F::ZERO; 1 << k]); - Self { - instance: SangriaInstance::init(num_instances, num_witness_polys, num_challenges), - witness_polys: iter::repeat_with(|| zero_poly.clone()) - .take(num_witness_polys) - .collect(), - e_poly: zero_poly, - } - } - - pub(crate) fn from_committed( - k: usize, - instances: &[&[F]], - witness_polys: impl IntoIterator, - witness_comms: impl IntoIterator, - challenges: Vec, - ) -> Self { - Self { - instance: SangriaInstance::from_committed(instances, witness_comms, challenges), - witness_polys: witness_polys.into_iter().collect(), - e_poly: Pcs::Polynomial::from_evals(vec![F::ZERO; 1 << k]), - } - } -} - -impl SangriaWitness -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, - Pcs::Commitment: AdditiveCommitment, -{ - pub(crate) fn fold( - &mut self, - rhs: &Self, - cross_term_polys: &[Pcs::Polynomial], - cross_term_comms: &[Pcs::Commitment], - r: &F, - ) { - self.instance.fold(&rhs.instance, cross_term_comms, r); - izip_eq!(&mut self.witness_polys, &rhs.witness_polys) - .for_each(|(lhs, rhs)| *lhs += (r, rhs)); - izip!(powers(*r).skip(1), chain![cross_term_polys, [&rhs.e_poly]]) - .for_each(|(power_of_r, poly)| self.e_poly += (&power_of_r, poly)); - } -} - -pub(crate) fn lookup_h_polys( - compressed_polys: &[[MultilinearPolynomial; 2]], - m_polys: &[MultilinearPolynomial], - beta: &F, -) -> Vec<[MultilinearPolynomial; 2]> { - compressed_polys - .iter() - .zip(m_polys.iter()) - .map(|(compressed_polys, m_poly)| lookup_h_poly(compressed_polys, m_poly, beta)) - .collect() -} - -fn lookup_h_poly( - compressed_polys: &[MultilinearPolynomial; 2], - m_poly: &MultilinearPolynomial, - beta: &F, -) -> [MultilinearPolynomial; 2] { - let [input, table] = compressed_polys; - let mut h_input = vec![F::ZERO; 1 << input.num_vars()]; - let mut h_table = vec![F::ZERO; 1 << input.num_vars()]; - - parallelize(&mut h_input, |(h_input, start)| { - for (h_input, input) in h_input.iter_mut().zip(input[start..].iter()) { - *h_input = *beta + input; - } - }); - parallelize(&mut h_table, |(h_table, start)| { - for (h_table, table) in h_table.iter_mut().zip(table[start..].iter()) { - *h_table = *beta + table; - } - }); - - let chunk_size = div_ceil(2 * h_input.len(), num_threads()); - parallelize_iter( - iter::empty() - .chain(h_input.chunks_mut(chunk_size)) - .chain(h_table.chunks_mut(chunk_size)), - |h| { - h.iter_mut().batch_invert(); - }, - ); - - parallelize(&mut h_table, |(h_table, start)| { - for (h_table, m) in h_table.iter_mut().zip(m_poly[start..].iter()) { - *h_table *= m; - } - }); - - if cfg!(feature = "sanity-check") { - assert_eq!(sum::(&h_input), sum::(&h_table)); - } - - [ - MultilinearPolynomial::new(h_input), - MultilinearPolynomial::new(h_table), - ] -} - -pub(crate) fn evaluate_cross_term( - cross_term_expressions: &[Expression], - num_vars: usize, - preprocess_polys: &[MultilinearPolynomial], - folded: &SangriaWitness, - incoming: &SangriaWitness, -) -> Vec> -where - F: PrimeField + Ord, - Pcs: PolynomialCommitmentScheme>, -{ - if cross_term_expressions.is_empty() { - return Vec::new(); - } - - let folded_instance_polys = instance_polys(num_vars, &folded.instance.instances); - let incoming_instance_polys = instance_polys(num_vars, &incoming.instance.instances); - let polys = iter::empty() - .chain(preprocess_polys) - .chain(&folded_instance_polys) - .chain(&folded.witness_polys) - .chain(&incoming_instance_polys) - .chain(&incoming.witness_polys) - .collect_vec(); - let challenges = iter::empty() - .chain(folded.instance.challenges.iter().cloned()) - .chain(Some(folded.instance.u)) - .chain(incoming.instance.challenges.iter().cloned()) - .chain(Some(incoming.instance.u)) - .collect_vec(); - - let cross_term_expressions = cross_term_expressions - .iter() - .map(|expression| { - expression - .simplified(Some(&challenges)) - .unwrap_or_else(Expression::zero) - }) - .collect_vec(); - - let ev = HadamardEvaluator::new(num_vars, &cross_term_expressions); - let size = 1 << num_vars; - let chunk_size = div_ceil(size, num_threads()); - let num_cross_terms = cross_term_expressions.len(); - - let mut outputs = vec![F::ZERO; num_cross_terms << num_vars]; - parallelize_iter( - outputs - .chunks_mut(chunk_size * num_cross_terms) - .zip((0..).step_by(chunk_size)), - |(outputs, start)| { - let mut data = ev.cache(); - let bs = start..(start + chunk_size).min(size); - for (b, outputs) in bs.zip(outputs.chunks_mut(num_cross_terms)) { - ev.evaluate(outputs, &mut data, polys.as_slice(), b); - } - }, - ); - - (0..num_cross_terms) - .map(|offset| par_map_collect(0..size, |idx| outputs[idx * num_cross_terms + offset])) - .map(MultilinearPolynomial::new) - .collect_vec() -} - -#[derive(Clone, Debug)] -pub(crate) struct HadamardEvaluator { - num_vars: usize, - reg: ExpressionRegistry, - lagranges: Vec, -} - -impl HadamardEvaluator { - pub(crate) fn new(num_vars: usize, expressions: &[Expression]) -> Self { - let mut reg = ExpressionRegistry::new(); - for expression in expressions.iter() { - reg.register(expression); - } - assert!(reg.eq_xys().is_empty()); - - let bh = BooleanHypercube::new(num_vars).iter().collect_vec(); - let lagranges = reg - .lagranges() - .iter() - .map(|i| bh[i.rem_euclid(1 << num_vars) as usize]) - .collect_vec(); - - Self { - num_vars, - reg, - lagranges, - } - } - - pub(crate) fn cache(&self) -> Vec { - self.reg.cache() - } - - pub(crate) fn evaluate( - &self, - evals: &mut [F], - cache: &mut [F], - polys: &[&MultilinearPolynomial], - b: usize, - ) { - let bh = BooleanHypercube::new(self.num_vars); - if self.reg.has_identity() { - cache[self.reg.offsets().identity()] = F::from(b as u64); - } - cache[self.reg.offsets().lagranges()..] - .iter_mut() - .zip(&self.lagranges) - .for_each(|(value, i)| *value = if &b == i { F::ONE } else { F::ZERO }); - cache[self.reg.offsets().polys()..] - .iter_mut() - .zip(self.reg.polys()) - .for_each(|(value, (query, _))| { - *value = polys[query.poly()][bh.rotate(b, query.rotation())] - }); - self.reg - .indexed_calculations() - .iter() - .zip(self.reg.offsets().calculations()..) - .for_each(|(calculation, idx)| calculation.calculate(cache, idx)); - evals - .iter_mut() - .zip(self.reg.indexed_outputs()) - .for_each(|(eval, idx)| *eval = cache[*idx]) - } -} diff --git a/plonkish_backend/src/backend/hyperplonk/folding/sangria/verifier.rs b/plonkish_backend/src/backend/hyperplonk/folding/sangria/verifier.rs deleted file mode 100644 index 81f2c66c..00000000 --- a/plonkish_backend/src/backend/hyperplonk/folding/sangria/verifier.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::{ - pcs::{AdditiveCommitment, PolynomialCommitmentScheme}, - util::{ - arithmetic::{powers, PrimeField}, - chain, izip_eq, Itertools, - }, -}; -use std::{fmt::Debug, iter}; - -#[derive(Debug)] -pub(crate) struct SangriaInstance -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - pub(crate) instances: Vec>, - pub(crate) witness_comms: Vec, - pub(crate) challenges: Vec, - pub(crate) u: F, - pub(crate) e_comm: Pcs::Commitment, -} - -impl SangriaInstance -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, -{ - pub(crate) fn init( - num_instances: &[usize], - num_witness_polys: usize, - num_challenges: usize, - ) -> Self { - Self { - instances: num_instances.iter().map(|n| vec![F::ZERO; *n]).collect(), - witness_comms: iter::repeat_with(Pcs::Commitment::default) - .take(num_witness_polys) - .collect(), - challenges: vec![F::ZERO; num_challenges], - u: F::ZERO, - e_comm: Pcs::Commitment::default(), - } - } - - pub(crate) fn from_committed( - instances: &[&[F]], - witness_comms: impl IntoIterator, - challenges: Vec, - ) -> Self { - Self { - instances: instances - .iter() - .map(|instances| instances.to_vec()) - .collect(), - witness_comms: witness_comms.into_iter().collect(), - challenges, - u: F::ONE, - e_comm: Pcs::Commitment::default(), - } - } - - pub(crate) fn instance_slices(&self) -> Vec<&[F]> { - self.instances.iter().map(Vec::as_slice).collect() - } -} - -impl SangriaInstance -where - F: PrimeField, - Pcs: PolynomialCommitmentScheme, - Pcs::Commitment: AdditiveCommitment, -{ - pub(crate) fn fold(&mut self, rhs: &Self, cross_term_comms: &[Pcs::Commitment], r: &F) { - let one = F::ONE; - let powers_of_r = powers(*r).take(cross_term_comms.len() + 2).collect_vec(); - izip_eq!(&mut self.instances, &rhs.instances) - .for_each(|(lhs, rhs)| izip_eq!(lhs, rhs).for_each(|(lhs, rhs)| *lhs += &(*rhs * r))); - izip_eq!(&mut self.witness_comms, &rhs.witness_comms) - .for_each(|(lhs, rhs)| *lhs = Pcs::Commitment::sum_with_scalar([&one, r], [lhs, rhs])); - izip_eq!(&mut self.challenges, &rhs.challenges).for_each(|(lhs, rhs)| *lhs += &(*rhs * r)); - self.u += &(rhs.u * r); - self.e_comm = { - let comms = chain![[&self.e_comm], cross_term_comms, [&rhs.e_comm]]; - Pcs::Commitment::sum_with_scalar(&powers_of_r, comms) - }; - } -} diff --git a/plonkish_backend/src/backend/hyperplonk/preprocessor.rs b/plonkish_backend/src/backend/hyperplonk/preprocessor.rs index 12552144..c23c9b07 100644 --- a/plonkish_backend/src/backend/hyperplonk/preprocessor.rs +++ b/plonkish_backend/src/backend/hyperplonk/preprocessor.rs @@ -108,18 +108,18 @@ pub(super) fn lookup_constraints( (constraints, sum_check) } -pub(super) fn permutation_constraints( +pub(crate) fn permutation_constraints( circuit_info: &PlonkishCircuitInfo, max_degree: usize, beta: &Expression, gamma: &Expression, - num_lookup_witness_polys: usize, + num_builtin_witness_polys: usize, ) -> (usize, Vec>) { let permutation_polys = circuit_info.permutation_polys(); let chunk_size = max_degree - 1; let num_chunks = div_ceil(permutation_polys.len(), chunk_size); let permutation_offset = circuit_info.num_poly(); - let z_offset = permutation_offset + permutation_polys.len() + num_lookup_witness_polys; + let z_offset = permutation_offset + permutation_polys.len() + num_builtin_witness_polys; let polys = permutation_polys .iter() .map(|idx| Expression::Polynomial(Query::new(*idx, Rotation::cur()))) diff --git a/plonkish_backend/src/backend/hyperplonk/prover.rs b/plonkish_backend/src/backend/hyperplonk/prover.rs index 1cc0e279..19ef148a 100644 --- a/plonkish_backend/src/backend/hyperplonk/prover.rs +++ b/plonkish_backend/src/backend/hyperplonk/prover.rs @@ -6,12 +6,12 @@ use crate::{ }, WitnessEncoding, }, - pcs::{Evaluation, Polynomial}, + pcs::Evaluation, piop::sum_check::{ classic::{ClassicSumCheck, EvaluationsProver}, SumCheck, VirtualPolynomial, }, - poly::multilinear::MultilinearPolynomial, + poly::{multilinear::MultilinearPolynomial, Polynomial}, util::{ arithmetic::{div_ceil, steps_by, sum, BatchInvert, BooleanHypercube, PrimeField}, end_timer, @@ -29,7 +29,7 @@ use std::{ iter, }; -pub(super) fn instance_polys<'a, F: PrimeField>( +pub(crate) fn instance_polys<'a, F: PrimeField>( num_vars: usize, instances: impl IntoIterator>, ) -> Vec> { @@ -47,7 +47,7 @@ pub(super) fn instance_polys<'a, F: PrimeField>( .collect() } -pub(super) fn lookup_compressed_polys( +pub(crate) fn lookup_compressed_polys( lookups: &[Vec<(Expression, Expression)>], polys: &[&MultilinearPolynomial], challenges: &[F], @@ -136,7 +136,7 @@ pub(super) fn lookup_compressed_poly( [compressed_input_poly, compressed_table_poly] } -pub(super) fn lookup_m_polys( +pub(crate) fn lookup_m_polys( compressed_polys: &[[MultilinearPolynomial; 2]], ) -> Result>, Error> { compressed_polys.iter().map(lookup_m_poly).try_collect() @@ -249,7 +249,7 @@ pub(super) fn lookup_h_poly( MultilinearPolynomial::new(h_input) } -pub(super) fn permutation_z_polys( +pub(crate) fn permutation_z_polys( num_chunks: usize, permutation_polys: &[(usize, MultilinearPolynomial)], polys: &[&MultilinearPolynomial], @@ -365,7 +365,7 @@ pub(super) fn prove_zero_check( } #[allow(clippy::type_complexity)] -pub(super) fn prove_sum_check( +pub(crate) fn prove_sum_check( num_instance_poly: usize, expression: &Expression, sum: F, diff --git a/plonkish_backend/src/backend/hyperplonk/util.rs b/plonkish_backend/src/backend/hyperplonk/util.rs index 09f117d5..30965e54 100644 --- a/plonkish_backend/src/backend/hyperplonk/util.rs +++ b/plonkish_backend/src/backend/hyperplonk/util.rs @@ -7,10 +7,10 @@ use crate::{ permutation_z_polys, }, }, + mock::MockCircuit, PlonkishCircuit, PlonkishCircuitInfo, }, - pcs::Polynomial, - poly::multilinear::MultilinearPolynomial, + poly::{multilinear::MultilinearPolynomial, Polynomial}, util::{ arithmetic::{powers, BooleanHypercube, PrimeField}, expression::{Expression, Query, Rotation}, @@ -101,7 +101,7 @@ pub fn rand_vanilla_plonk_circuit( num_vars: usize, mut preprocess_rng: impl RngCore, mut witness_rng: impl RngCore, -) -> (PlonkishCircuitInfo, Vec>, impl PlonkishCircuit) { +) -> (PlonkishCircuitInfo, impl PlonkishCircuit) { let size = 1 << num_vars; let mut polys = [(); 9].map(|_| vec![F::ZERO; size]); @@ -162,7 +162,10 @@ pub fn rand_vanilla_plonk_circuit( [q_l, q_r, q_m, q_o, q_c], permutation.into_cycles(), ); - (circuit_info, vec![instances], vec![w_l, w_r, w_o]) + ( + circuit_info, + MockCircuit::new(vec![instances], vec![w_l, w_r, w_o]), + ) } pub fn rand_vanilla_plonk_assignment( @@ -171,11 +174,11 @@ pub fn rand_vanilla_plonk_assignment( mut witness_rng: impl RngCore, ) -> (Vec>, Vec) { let (polys, permutations) = { - let (circuit_info, instances, circuit) = + let (circuit_info, circuit) = rand_vanilla_plonk_circuit(num_vars, &mut preprocess_rng, &mut witness_rng); let witness = circuit.synthesize(0, &[]).unwrap(); let polys = iter::empty() - .chain(instance_polys(num_vars, &instances)) + .chain(instance_polys(num_vars, circuit.instances())) .chain( iter::empty() .chain(circuit_info.preprocess_polys) @@ -214,7 +217,7 @@ pub fn rand_vanilla_plonk_with_lookup_circuit( num_vars: usize, mut preprocess_rng: impl RngCore, mut witness_rng: impl RngCore, -) -> (PlonkishCircuitInfo, Vec>, impl PlonkishCircuit) { +) -> (PlonkishCircuitInfo, impl PlonkishCircuit) { let size = 1 << num_vars; let mut polys = [(); 13].map(|_| vec![F::ZERO; size]); @@ -306,7 +309,10 @@ pub fn rand_vanilla_plonk_with_lookup_circuit( [q_l, q_r, q_m, q_o, q_c, q_lookup, t_l, t_r, t_o], permutation.into_cycles(), ); - (circuit_info, vec![instances], vec![w_l, w_r, w_o]) + ( + circuit_info, + MockCircuit::new(vec![instances], vec![w_l, w_r, w_o]), + ) } pub fn rand_vanilla_plonk_with_lookup_assignment( @@ -315,11 +321,11 @@ pub fn rand_vanilla_plonk_with_lookup_assignment( mut witness_rng: impl RngCore, ) -> (Vec>, Vec) { let (polys, permutations) = { - let (circuit_info, instances, circuit) = + let (circuit_info, circuit) = rand_vanilla_plonk_with_lookup_circuit(num_vars, &mut preprocess_rng, &mut witness_rng); let witness = circuit.synthesize(0, &[]).unwrap(); let polys = iter::empty() - .chain(instance_polys(num_vars, &instances)) + .chain(instance_polys(num_vars, circuit.instances())) .chain( iter::empty() .chain(circuit_info.preprocess_polys) diff --git a/plonkish_backend/src/backend/hyperplonk/verifier.rs b/plonkish_backend/src/backend/hyperplonk/verifier.rs index c1e9b033..dcc602c7 100644 --- a/plonkish_backend/src/backend/hyperplonk/verifier.rs +++ b/plonkish_backend/src/backend/hyperplonk/verifier.rs @@ -19,7 +19,7 @@ use std::collections::{BTreeSet, HashMap}; pub(super) fn verify_zero_check( num_vars: usize, expression: &Expression, - instances: &[&[F]], + instances: &[Vec], challenges: &[F], y: &[F], transcript: &mut impl FieldTranscriptRead, @@ -36,11 +36,11 @@ pub(super) fn verify_zero_check( } #[allow(clippy::type_complexity)] -pub(super) fn verify_sum_check( +pub(crate) fn verify_sum_check( num_vars: usize, expression: &Expression, sum: F, - instances: &[&[F]], + instances: &[Vec], challenges: &[F], y: &[F], transcript: &mut impl FieldTranscriptRead, @@ -92,7 +92,7 @@ pub(super) fn verify_sum_check( fn instance_evals( num_vars: usize, expression: &Expression, - instances: &[&[F]], + instances: &[Vec], x: &[F], ) -> Vec<(Query, F)> { let mut instance_query = expression.used_query(); @@ -136,7 +136,7 @@ fn instance_evals( .collect_vec() }; let eval = inner_product( - instances[query.poly()], + &instances[query.poly()], is.iter().map(|i| lagrange_evals.get(i).unwrap()), ); (query, eval) @@ -163,7 +163,7 @@ pub(super) fn points(pcs_query: &BTreeSet, x: &[F]) -> Vec .collect_vec() } -pub(super) fn point_offset(pcs_query: &BTreeSet) -> HashMap { +pub(crate) fn point_offset(pcs_query: &BTreeSet) -> HashMap { let rotations = pcs_query .iter() .map(Query::rotation) diff --git a/plonkish_backend/src/frontend/halo2.rs b/plonkish_backend/src/frontend/halo2.rs index 0a178482..9bae2ad8 100644 --- a/plonkish_backend/src/frontend/halo2.rs +++ b/plonkish_backend/src/frontend/halo2.rs @@ -25,11 +25,18 @@ pub mod circuit; mod test; pub trait CircuitExt: Circuit { - fn rand(k: usize, rng: impl RngCore) -> Self; - - fn num_instances() -> Vec; + fn rand(_k: usize, _rng: impl RngCore) -> Self + where + Self: Sized, + { + unimplemented!() + } fn instances(&self) -> Vec>; + + fn num_instances(&self) -> Vec { + self.instances().iter().map(Vec::len).collect() + } } pub struct Halo2Circuit> { @@ -73,12 +80,13 @@ impl> Halo2Circuit { } } - pub fn instance_slices(&self) -> Vec<&[F]> { - self.instances.iter().map(Vec::as_slice).collect() + pub fn circuit(&self) -> &C { + &self.circuit } - pub fn instances(&self) -> Vec> { - self.instances.clone() + pub fn update_witness(&mut self, f: impl FnOnce(&mut C)) { + f(&mut self.circuit); + self.instances = self.circuit.instances(); } } @@ -89,16 +97,12 @@ impl> AsRef for Halo2Circuit { } impl> PlonkishCircuit for Halo2Circuit { - fn circuit_info(&self) -> Result, crate::Error> { + fn circuit_info_without_preprocess(&self) -> Result, crate::Error> { let Self { k, instances, cs, - config, - circuit, - constants, challenge_idx, - row_mapping, .. } = self; let advice_idx = advice_idx(cs); @@ -129,6 +133,46 @@ impl> PlonkishCircuit for Halo2Circuit { }) .collect(); + let num_instances = instances.iter().map(Vec::len).collect_vec(); + let preprocess_polys = + vec![vec![F::ZERO; 1 << k]; cs.num_selectors() + cs.num_fixed_columns()]; + let column_idx = column_idx(cs); + let permutations = cs + .permutation() + .get_columns() + .iter() + .map(|column| { + let key = (*column.column_type(), column.index()); + vec![(column_idx[&key], 1)] + }) + .collect_vec(); + + Ok(PlonkishCircuitInfo { + k: *k as usize, + num_instances, + preprocess_polys, + num_witness_polys: num_by_phase(&cs.advice_column_phase()), + num_challenges: num_by_phase(&cs.challenge_phase()), + constraints, + lookups, + permutations, + max_degree: Some(cs.degree::()), + }) + } + + fn circuit_info(&self) -> Result, crate::Error> { + let Self { + k, + instances, + cs, + config, + circuit, + constants, + row_mapping, + .. + } = self; + let mut circuit_info = self.circuit_info_without_preprocess()?; + let num_instances = instances.iter().map(Vec::len).collect_vec(); let column_idx = column_idx(cs); let permutation_column_idx = cs @@ -142,7 +186,7 @@ impl> PlonkishCircuit for Halo2Circuit { .collect(); let mut preprocess_collector = PreprocessCollector { k: *k, - num_instances: num_instances.clone(), + num_instances, fixeds: vec![vec![F::ZERO.into(); 1 << k]; cs.num_fixed_columns()], permutation: Permutation::new(permutation_column_idx), selectors: vec![vec![false; 1 << k]; cs.num_selectors()], @@ -155,9 +199,9 @@ impl> PlonkishCircuit for Halo2Circuit { config.clone(), constants.clone(), ) - .map_err(|_| crate::Error::InvalidSnark("Synthesize failure".to_string()))?; + .map_err(|err| crate::Error::InvalidSnark(format!("Synthesize failure: {err:?}")))?; - let preprocess_polys = iter::empty() + circuit_info.preprocess_polys = iter::empty() .chain(batch_invert_assigned(preprocess_collector.fixeds)) .chain(preprocess_collector.selectors.into_iter().map(|selectors| { selectors @@ -166,19 +210,13 @@ impl> PlonkishCircuit for Halo2Circuit { .collect() })) .collect(); - let permutations = preprocess_collector.permutation.into_cycles(); + circuit_info.permutations = preprocess_collector.permutation.into_cycles(); - Ok(PlonkishCircuitInfo { - k: *k as usize, - num_instances, - preprocess_polys, - num_witness_polys: num_by_phase(&cs.advice_column_phase()), - num_challenges: num_by_phase(&cs.challenge_phase()), - constraints, - lookups, - permutations, - max_degree: Some(cs.degree::()), - }) + Ok(circuit_info) + } + + fn instances(&self) -> &[Vec] { + &self.instances } fn synthesize(&self, phase: usize, challenges: &[F]) -> Result>, crate::Error> { @@ -200,7 +238,7 @@ impl> PlonkishCircuit for Halo2Circuit { self.config.clone(), self.constants.clone(), ) - .map_err(|_| crate::Error::InvalidSnark("Synthesize failure".to_string()))?; + .map_err(|err| crate::Error::InvalidSnark(format!("Synthesize failure: {err:?}")))?; Ok(batch_invert_assigned(witness_collector.advices)) } diff --git a/plonkish_backend/src/frontend/halo2/circuit.rs b/plonkish_backend/src/frontend/halo2/circuit.rs index 39751a3a..cf6af234 100644 --- a/plonkish_backend/src/frontend/halo2/circuit.rs +++ b/plonkish_backend/src/frontend/halo2/circuit.rs @@ -116,10 +116,6 @@ mod vanilla_plonk { Self(k, values) } - fn num_instances() -> Vec { - vec![1] - } - fn instances(&self) -> Vec> { let [q_l, q_r, q_m, q_o, q_c, w_l, w_r, w_o] = self.1[0]; let pi = (-(q_l * w_l + q_r * w_r + q_m * w_l * w_r + q_o * w_o + q_c)).evaluate(); diff --git a/plonkish_backend/src/frontend/halo2/test.rs b/plonkish_backend/src/frontend/halo2/test.rs index 67f41c26..6eb2d736 100644 --- a/plonkish_backend/src/frontend/halo2/test.rs +++ b/plonkish_backend/src/frontend/halo2/test.rs @@ -1,5 +1,6 @@ use crate::backend::{ - hyperplonk::{test::run_hyperplonk, util, HyperPlonk}, + hyperplonk::{util, HyperPlonk}, + test::run_plonkish_backend, PlonkishCircuit, }; use crate::{ @@ -29,13 +30,9 @@ fn vanilla_plonk_circuit_info() { #[test] fn e2e_vanilla_plonk() { - run_hyperplonk::<_, MultilinearKzg, Keccak256Transcript<_>, _>(3..16, |num_vars| { - let circuit = - Halo2Circuit::new::>(num_vars, VanillaPlonk::rand(num_vars, OsRng)); - ( - circuit.circuit_info().unwrap(), - circuit.instances(), - circuit, - ) + type Pb = HyperPlonk>; + run_plonkish_backend::<_, Pb, Keccak256Transcript<_>, _>(3..16, |num_vars| { + let circuit = Halo2Circuit::new::(num_vars, VanillaPlonk::rand(num_vars, OsRng)); + (circuit.circuit_info().unwrap(), circuit) }); } diff --git a/plonkish_backend/src/lib.rs b/plonkish_backend/src/lib.rs index 054488dd..1cd59eec 100644 --- a/plonkish_backend/src/lib.rs +++ b/plonkish_backend/src/lib.rs @@ -1,5 +1,6 @@ #![allow(clippy::op_ref)] +pub mod accumulation; pub mod backend; pub mod frontend; pub mod pcs; diff --git a/plonkish_backend/src/pcs.rs b/plonkish_backend/src/pcs.rs index a535ae9f..6982e5ac 100644 --- a/plonkish_backend/src/pcs.rs +++ b/plonkish_backend/src/pcs.rs @@ -1,37 +1,35 @@ use crate::{ + poly::Polynomial, util::{ arithmetic::{variable_base_msm, Curve, CurveAffine, Field}, transcript::{TranscriptRead, TranscriptWrite}, - Itertools, + DeserializeOwned, Itertools, Serialize, }, Error, }; use rand::RngCore; -use std::{fmt::Debug, ops::AddAssign}; +use std::fmt::Debug; pub mod multilinear; pub mod univariate; -pub trait Polynomial: Clone + Debug + for<'a> AddAssign<(&'a F, &'a Self)> { - type Point: Clone + Debug; - - fn from_evals(evals: Vec) -> Self; - - fn into_evals(self) -> Vec; - - fn evals(&self) -> &[F]; +pub type Point =

>::Point; - fn evaluate(&self, point: &Self::Point) -> F; -} +pub type Commitment = >::Commitment; -pub type Point =

>::Point; +pub type CommitmentChunk = >::CommitmentChunk; pub trait PolynomialCommitmentScheme: Clone + Debug { - type Param: Debug; - type ProverParam: Debug; - type VerifierParam: Debug; - type Polynomial: Polynomial; - type Commitment: Clone + Debug + Default + AsRef<[Self::CommitmentChunk]>; + type Param: Clone + Debug + Serialize + DeserializeOwned; + type ProverParam: Clone + Debug + Serialize + DeserializeOwned; + type VerifierParam: Clone + Debug + Serialize + DeserializeOwned; + type Polynomial: Polynomial + Serialize + DeserializeOwned; + type Commitment: Clone + + Debug + + Default + + AsRef<[Self::CommitmentChunk]> + + Serialize + + DeserializeOwned; type CommitmentChunk: Clone + Debug + Default; fn setup(poly_size: usize, batch_size: usize, rng: impl RngCore) -> Result; @@ -132,13 +130,13 @@ pub trait PolynomialCommitmentScheme: Clone + Debug { } #[derive(Clone, Debug)] -pub struct Evaluation { +pub struct Evaluation { poly: usize, point: usize, value: F, } -impl Evaluation { +impl Evaluation { pub fn new(poly: usize, point: usize, value: F) -> Self { Self { poly, point, value } } diff --git a/plonkish_backend/src/pcs/multilinear.rs b/plonkish_backend/src/pcs/multilinear.rs index 81a1b44e..877b39d4 100644 --- a/plonkish_backend/src/pcs/multilinear.rs +++ b/plonkish_backend/src/pcs/multilinear.rs @@ -1,25 +1,27 @@ use crate::{ - poly::multilinear::MultilinearPolynomial, - util::{arithmetic::Field, Itertools}, + poly::{multilinear::MultilinearPolynomial, Polynomial}, + util::{arithmetic::Field, end_timer, izip, parallel::parallelize, start_timer, Itertools}, Error, }; mod brakedown; +mod gemini; mod hyrax; mod ipa; mod kzg; -mod simulator; +mod zeromorph; pub use brakedown::{ MultilinearBrakedown, MultilinearBrakedownCommitment, MultilinearBrakedownParams, }; +pub use gemini::Gemini; pub use hyrax::{MultilinearHyrax, MultilinearHyraxCommitment, MultilinearHyraxParams}; pub use ipa::{MultilinearIpa, MultilinearIpaCommitment, MultilinearIpaParams}; pub use kzg::{ MultilinearKzg, MultilinearKzgCommitment, MultilinearKzgParams, MultilinearKzgProverParams, MultilinearKzgVerifierParams, }; -pub use simulator::MultilinearSimulator; +pub use zeromorph::{Zeromorph, ZeromorphKzgProverParam, ZeromorphKzgVerifierParam}; fn validate_input<'a, F: Field>( function: &str, @@ -67,17 +69,54 @@ fn err_too_many_variates(function: &str, upto: usize, got: usize) -> Error { }) } +fn quotients( + poly: &MultilinearPolynomial, + point: &[F], + f: impl Fn(usize, Vec) -> T, +) -> (Vec, F) { + assert_eq!(poly.num_vars(), point.len()); + + let mut remainder = poly.evals().to_vec(); + let mut quotients = point + .iter() + .zip(0..poly.num_vars()) + .rev() + .map(|(x_i, num_vars)| { + let timer = start_timer(|| "quotients"); + let (remaimder_lo, remainder_hi) = remainder.split_at_mut(1 << num_vars); + let mut quotient = vec![F::ZERO; remaimder_lo.len()]; + + parallelize(&mut quotient, |(quotient, start)| { + izip!(quotient, &remaimder_lo[start..], &remainder_hi[start..]) + .for_each(|(q, r_lo, r_hi)| *q = *r_hi - r_lo); + }); + parallelize(remaimder_lo, |(remaimder_lo, start)| { + izip!(remaimder_lo, &remainder_hi[start..]) + .for_each(|(r_lo, r_hi)| *r_lo += (*r_hi - r_lo as &_) * x_i); + }); + + remainder.truncate(1 << num_vars); + end_timer(timer); + + f(num_vars, quotient) + }) + .collect_vec(); + quotients.reverse(); + + (quotients, remainder[0]) +} + mod additive { use crate::{ pcs::{ - multilinear::validate_input, AdditiveCommitment, Evaluation, Point, Polynomial, + multilinear::validate_input, AdditiveCommitment, Evaluation, Point, PolynomialCommitmentScheme, }, piop::sum_check::{ classic::{ClassicSumCheck, CoefficientsProver}, eq_xy_eval, SumCheck as _, VirtualPolynomial, }, - poly::multilinear::MultilinearPolynomial, + poly::{multilinear::MultilinearPolynomial, Polynomial}, util::{ arithmetic::{inner_product, PrimeField}, end_timer, @@ -257,7 +296,7 @@ mod test { Pcs: PolynomialCommitmentScheme>, T: TranscriptRead + TranscriptWrite - + InMemoryTranscript, + + InMemoryTranscript, { for num_vars in 3..16 { // Setup @@ -269,7 +308,7 @@ mod test { }; // Commit and open let proof = { - let mut transcript = T::default(); + let mut transcript = T::new(()); let poly = MultilinearPolynomial::rand(num_vars, OsRng); let comm = Pcs::commit_and_write(&pp, &poly, &mut transcript).unwrap(); let point = transcript.squeeze_challenges(num_vars); @@ -280,7 +319,7 @@ mod test { }; // Verify let result = { - let mut transcript = T::from_proof(proof.as_slice()); + let mut transcript = T::from_proof((), proof.as_slice()); Pcs::verify( &vp, &Pcs::read_commitment(&vp, &mut transcript).unwrap(), @@ -299,7 +338,7 @@ mod test { Pcs: PolynomialCommitmentScheme>, T: TranscriptRead + TranscriptWrite - + InMemoryTranscript, + + InMemoryTranscript, { for num_vars in 3..16 { let batch_size = 8; @@ -321,7 +360,7 @@ mod test { .unique() .collect_vec(); let proof = { - let mut transcript = T::default(); + let mut transcript = T::new(()); let polys = iter::repeat_with(|| MultilinearPolynomial::rand(num_vars, OsRng)) .take(batch_size) .collect_vec(); @@ -346,7 +385,7 @@ mod test { }; // Batch verify let result = { - let mut transcript = T::from_proof(proof.as_slice()); + let mut transcript = T::from_proof((), proof.as_slice()); Pcs::batch_verify( &vp, &Pcs::read_commitments(&vp, batch_size, &mut transcript).unwrap(), diff --git a/plonkish_backend/src/pcs/multilinear/brakedown.rs b/plonkish_backend/src/pcs/multilinear/brakedown.rs index 5dedfd59..b879f80c 100644 --- a/plonkish_backend/src/pcs/multilinear/brakedown.rs +++ b/plonkish_backend/src/pcs/multilinear/brakedown.rs @@ -6,15 +6,15 @@ //! [GLSTW21]: https://eprint.iacr.org/2021/1043.pdf use crate::{ - pcs::{multilinear::validate_input, Evaluation, Point, Polynomial, PolynomialCommitmentScheme}, - poly::multilinear::MultilinearPolynomial, + pcs::{multilinear::validate_input, Evaluation, Point, PolynomialCommitmentScheme}, + poly::{multilinear::MultilinearPolynomial, Polynomial}, util::{ arithmetic::{div_ceil, inner_product, PrimeField}, code::{Brakedown, BrakedownSpec, LinearCodes}, hash::{Hash, Output}, parallel::{num_threads, parallelize, parallelize_iter}, transcript::{FieldTranscript, TranscriptRead, TranscriptWrite}, - Itertools, + Deserialize, DeserializeOwned, Itertools, Serialize, }, Error, }; @@ -30,7 +30,7 @@ impl Clone for MultilinearBrakedown { num_vars: usize, num_rows: usize, @@ -51,8 +51,9 @@ impl MultilinearBrakedownParams { } } -#[derive(Clone, Debug, Default)] -pub struct MultilinearBrakedownCommitment { +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(bound(serialize = "F: Serialize", deserialize = "F: DeserializeOwned"))] +pub struct MultilinearBrakedownCommitment { rows: Vec, intermediate_hashes: Vec>, root: Output, @@ -85,15 +86,18 @@ impl AsRef<[Output]> for MultilinearBrakedownCommitme } } -impl PolynomialCommitmentScheme - for MultilinearBrakedown +impl PolynomialCommitmentScheme for MultilinearBrakedown +where + F: PrimeField + Serialize + DeserializeOwned, + H: Hash, + S: BrakedownSpec, { type Param = MultilinearBrakedownParams; type ProverParam = MultilinearBrakedownParams; type VerifierParam = MultilinearBrakedownParams; type Polynomial = MultilinearPolynomial; - type CommitmentChunk = Output; type Commitment = MultilinearBrakedownCommitment; + type CommitmentChunk = Output; fn setup(poly_size: usize, _: usize, rng: impl RngCore) -> Result { assert!(poly_size.is_power_of_two()); diff --git a/plonkish_backend/src/pcs/multilinear/simulator.rs b/plonkish_backend/src/pcs/multilinear/gemini.rs similarity index 75% rename from plonkish_backend/src/pcs/multilinear/simulator.rs rename to plonkish_backend/src/pcs/multilinear/gemini.rs index 9cdbfc11..9fbbc3dc 100644 --- a/plonkish_backend/src/pcs/multilinear/simulator.rs +++ b/plonkish_backend/src/pcs/multilinear/gemini.rs @@ -1,38 +1,45 @@ +//! Implementation of section 2.4.2 of 2022/420, with improvement ported from Aztec's Barretenberg +//! https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/honk/pcs/gemini/gemini.cpp. + use crate::{ pcs::{ multilinear::additive, univariate::{UnivariateKzg, UnivariateKzgCommitment}, - Evaluation, Point, Polynomial, PolynomialCommitmentScheme, + Evaluation, Point, PolynomialCommitmentScheme, }, poly::{ multilinear::{merge_into, MultilinearPolynomial}, univariate::UnivariatePolynomial, + Polynomial, }, util::{ - arithmetic::{Field, MultiMillerLoop}, + arithmetic::{squares, Field, MultiMillerLoop}, chain, transcript::{TranscriptRead, TranscriptWrite}, - Itertools, + DeserializeOwned, Itertools, Serialize, }, Error, }; use rand::RngCore; -use std::marker::PhantomData; +use std::{marker::PhantomData, ops::Neg}; #[derive(Clone, Debug)] -pub struct MultilinearSimulator(PhantomData); +pub struct Gemini(PhantomData); -impl PolynomialCommitmentScheme for MultilinearSimulator> +impl PolynomialCommitmentScheme for Gemini> where M: MultiMillerLoop, + M::Scalar: Serialize + DeserializeOwned, + M::G1Affine: Serialize + DeserializeOwned, + M::G2Affine: Serialize + DeserializeOwned, { type Param = as PolynomialCommitmentScheme>::Param; type ProverParam = as PolynomialCommitmentScheme>::ProverParam; type VerifierParam = as PolynomialCommitmentScheme>::VerifierParam; type Polynomial = MultilinearPolynomial; + type Commitment = as PolynomialCommitmentScheme>::Commitment; type CommitmentChunk = as PolynomialCommitmentScheme>::CommitmentChunk; - type Commitment = as PolynomialCommitmentScheme>::Commitment; fn setup(poly_size: usize, batch_size: usize, rng: impl RngCore) -> Result { UnivariateKzg::::setup(poly_size, batch_size, rng) @@ -55,7 +62,7 @@ where ))); } - Ok(UnivariateKzg::::commit_coeffs(pp, poly.evals())) + Ok(UnivariateKzg::commit_coeffs(pp, poly.evals())) } fn batch_commit<'a>( @@ -76,6 +83,7 @@ where eval: &M::Scalar, transcript: &mut impl TranscriptWrite, ) -> Result<(), Error> { + let num_vars = point.len(); if pp.degree() + 1 < poly.evals().len() { return Err(Error::InvalidPcsParam(format!( "Too large degree of poly to open (param supports degree up to {} but got {})", @@ -90,9 +98,9 @@ where } let fs = { - let mut fs = Vec::with_capacity(point.len()); + let mut fs = Vec::with_capacity(num_vars); fs.push(UnivariatePolynomial::new(poly.evals().to_vec())); - for x_i in &point[..point.len() - 1] { + for x_i in &point[..num_vars - 1] { let f_i_minus_one = fs.last().unwrap().coeffs(); let mut f_i = Vec::with_capacity(f_i_minus_one.len() >> 1); merge_into(&mut f_i, f_i_minus_one, x_i, 1, 0); @@ -117,17 +125,14 @@ where .collect_vec(); let beta = transcript.squeeze_challenge(); - let points = [beta, -beta, beta.square()]; - - let evals = fs - .iter() - .enumerate() - .flat_map(|(idx, f)| { - chain![(idx != 0).then_some(2), [0, 1]] - .map(move |point| Evaluation::new(idx, point, f.evaluate(&points[point]))) - }) + let points = chain![[beta], squares(beta).map(Neg::neg)] + .take(num_vars + 1) + .collect_vec(); + + let evals = chain!([(0, 0), (0, 1)], (1..num_vars).zip(2..)) + .map(|(idx, point)| Evaluation::new(idx, point, fs[idx].evaluate(&points[point]))) .collect_vec(); - transcript.write_field_elements(evals.iter().map(Evaluation::value))?; + transcript.write_field_elements(evals[1..].iter().map(Evaluation::value))?; UnivariateKzg::::batch_open(pp, &fs, &comms, &points, &evals, transcript) } @@ -170,30 +175,24 @@ where .collect_vec(); let beta = transcript.squeeze_challenge(); - let points = [beta, -beta, beta.square()]; - - let evals = (0..num_vars) - .flat_map(|idx| { - chain![(idx != 0).then_some(2), [0, 1]] - .map(|point| { - transcript - .read_field_element() - .map(|eval| Evaluation::new(idx, point, eval)) - }) - .collect_vec() - }) - .try_collect::<_, Vec<_>, _>()?; - - let two = M::Scalar::ONE + M::Scalar::ONE; - let beta_inv = beta.invert().unwrap(); - for ((a, b, c), x_i) in chain![evals.iter().map(Evaluation::value), [eval]] - .tuples() - .zip_eq(point) - { - (two * c == (*a + b) * (M::Scalar::ONE - x_i) + (*a - b) * beta_inv * x_i) - .then_some(()) - .ok_or_else(|| Error::InvalidPcsOpen("Consistency failure".to_string()))?; - } + let squares_of_beta = squares(beta).take(num_vars).collect_vec(); + + let evals = transcript.read_field_elements(num_vars)?; + + let one = M::Scalar::ONE; + let two = one.double(); + let eval_0 = evals.iter().zip(&squares_of_beta).zip(point).rev().fold( + *eval, + |eval_pos, ((eval_neg, sqaure_of_beta), x_i)| { + (two * sqaure_of_beta * eval_pos - ((one - x_i) * sqaure_of_beta - x_i) * eval_neg) + * ((one - x_i) * sqaure_of_beta + x_i).invert().unwrap() + }, + ); + let evals = chain!([(0, 0), (0, 1)], (1..num_vars).zip(2..)) + .zip(chain![[eval_0], evals]) + .map(|((idx, point), eval)| Evaluation::new(idx, point, eval)) + .collect_vec(); + let points = chain!([beta], squares_of_beta.into_iter().map(Neg::neg)).collect_vec(); UnivariateKzg::::batch_verify(vp, &comms, &points, &evals, transcript) } @@ -216,7 +215,7 @@ mod test { use crate::{ pcs::{ multilinear::{ - simulator::MultilinearSimulator, + gemini::Gemini, test::{run_batch_commit_open_verify, run_commit_open_verify}, }, univariate::UnivariateKzg, @@ -225,7 +224,7 @@ mod test { }; use halo2_curves::bn256::Bn256; - type Pcs = MultilinearSimulator>; + type Pcs = Gemini>; #[test] fn commit_open_verify() { diff --git a/plonkish_backend/src/pcs/multilinear/hyrax.rs b/plonkish_backend/src/pcs/multilinear/hyrax.rs index aaccb57e..65b4cca3 100644 --- a/plonkish_backend/src/pcs/multilinear/hyrax.rs +++ b/plonkish_backend/src/pcs/multilinear/hyrax.rs @@ -5,14 +5,14 @@ use crate::{ ipa::{MultilinearIpa, MultilinearIpaCommitment, MultilinearIpaParams}, validate_input, }, - AdditiveCommitment, Evaluation, Point, Polynomial, PolynomialCommitmentScheme, + AdditiveCommitment, Evaluation, Point, PolynomialCommitmentScheme, }, - poly::multilinear::MultilinearPolynomial, + poly::{multilinear::MultilinearPolynomial, Polynomial}, util::{ arithmetic::{div_ceil, variable_base_msm, Curve, CurveAffine, Group}, parallel::parallelize, transcript::{TranscriptRead, TranscriptWrite}, - Itertools, + Deserialize, DeserializeOwned, Itertools, Serialize, }, Error, }; @@ -23,7 +23,7 @@ use std::{borrow::Cow, iter, marker::PhantomData}; #[derive(Clone, Debug)] pub struct MultilinearHyrax(PhantomData); -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct MultilinearHyraxParams { num_vars: usize, batch_num_vars: usize, @@ -61,7 +61,7 @@ impl MultilinearHyraxParams { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct MultilinearHyraxCommitment(pub Vec); impl Default for MultilinearHyraxCommitment { @@ -106,13 +106,17 @@ impl AdditiveCommitment for MultilinearHyraxCommitmen } } -impl PolynomialCommitmentScheme for MultilinearHyrax { +impl PolynomialCommitmentScheme for MultilinearHyrax +where + C: CurveAffine + Serialize + DeserializeOwned, + C::ScalarExt: Serialize + DeserializeOwned, +{ type Param = MultilinearHyraxParams; type ProverParam = MultilinearHyraxParams; type VerifierParam = MultilinearHyraxParams; type Polynomial = MultilinearPolynomial; - type CommitmentChunk = C; type Commitment = MultilinearHyraxCommitment; + type CommitmentChunk = C; fn setup(poly_size: usize, batch_size: usize, rng: impl RngCore) -> Result { assert!(poly_size.is_power_of_two()); diff --git a/plonkish_backend/src/pcs/multilinear/ipa.rs b/plonkish_backend/src/pcs/multilinear/ipa.rs index d6c830aa..bda66786 100644 --- a/plonkish_backend/src/pcs/multilinear/ipa.rs +++ b/plonkish_backend/src/pcs/multilinear/ipa.rs @@ -1,9 +1,9 @@ use crate::{ pcs::{ multilinear::{additive, err_too_many_variates, validate_input}, - AdditiveCommitment, Evaluation, Point, Polynomial, PolynomialCommitmentScheme, + AdditiveCommitment, Evaluation, Point, PolynomialCommitmentScheme, }, - poly::multilinear::MultilinearPolynomial, + poly::{multilinear::MultilinearPolynomial, Polynomial}, util::{ arithmetic::{ inner_product, variable_base_msm, Curve, CurveAffine, CurveExt, Field, Group, @@ -11,7 +11,7 @@ use crate::{ chain, parallel::parallelize, transcript::{TranscriptRead, TranscriptWrite}, - Itertools, + Deserialize, DeserializeOwned, Itertools, Serialize, }, Error, }; @@ -22,7 +22,7 @@ use std::{iter, marker::PhantomData, slice}; #[derive(Clone, Debug)] pub struct MultilinearIpa(PhantomData); -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct MultilinearIpaParams { num_vars: usize, g: Vec, @@ -43,7 +43,7 @@ impl MultilinearIpaParams { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct MultilinearIpaCommitment(pub C); impl Default for MultilinearIpaCommitment { @@ -58,6 +58,18 @@ impl AsRef<[C]> for MultilinearIpaCommitment { } } +impl AsRef for MultilinearIpaCommitment { + fn as_ref(&self) -> &C { + &self.0 + } +} + +impl From for MultilinearIpaCommitment { + fn from(comm: C) -> Self { + Self(comm) + } +} + impl AdditiveCommitment for MultilinearIpaCommitment { fn sum_with_scalar<'a>( scalars: impl IntoIterator + 'a, @@ -71,13 +83,17 @@ impl AdditiveCommitment for MultilinearIpaCommitment< } } -impl PolynomialCommitmentScheme for MultilinearIpa { +impl PolynomialCommitmentScheme for MultilinearIpa +where + C: CurveAffine + Serialize + DeserializeOwned, + C::ScalarExt: Serialize + DeserializeOwned, +{ type Param = MultilinearIpaParams; type ProverParam = MultilinearIpaParams; type VerifierParam = MultilinearIpaParams; type Polynomial = MultilinearPolynomial; - type CommitmentChunk = C; type Commitment = MultilinearIpaCommitment; + type CommitmentChunk = C; fn setup(poly_size: usize, _: usize, _: impl RngCore) -> Result { assert!(poly_size.is_power_of_two()); @@ -121,7 +137,7 @@ impl PolynomialCommitmentScheme for MultilinearIpa return Err(err_too_many_variates("trim", param.num_vars(), num_vars)); } let param = Self::ProverParam { - num_vars: param.num_vars, + num_vars, g: param.g[..poly_size].to_vec(), h: param.h, }; diff --git a/plonkish_backend/src/pcs/multilinear/kzg.rs b/plonkish_backend/src/pcs/multilinear/kzg.rs index 46a01399..c6e2f6a7 100644 --- a/plonkish_backend/src/pcs/multilinear/kzg.rs +++ b/plonkish_backend/src/pcs/multilinear/kzg.rs @@ -1,30 +1,28 @@ use crate::{ pcs::{ - multilinear::{additive, err_too_many_variates, validate_input}, - AdditiveCommitment, Evaluation, Point, Polynomial, PolynomialCommitmentScheme, + multilinear::{additive, err_too_many_variates, quotients, validate_input}, + AdditiveCommitment, Evaluation, Point, PolynomialCommitmentScheme, }, - poly::multilinear::MultilinearPolynomial, + poly::{multilinear::MultilinearPolynomial, Polynomial}, util::{ arithmetic::{ - div_ceil, fixed_base_msm, variable_base_msm, window_size, window_table, Curve, Field, - MultiMillerLoop, PrimeCurveAffine, + fixed_base_msm, variable_base_msm, window_size, window_table, Curve, CurveAffine, + Field, MultiMillerLoop, PrimeCurveAffine, }, - end_timer, - parallel::{num_threads, parallelize, parallelize_iter}, - start_timer, + izip, + parallel::parallelize, transcript::{TranscriptRead, TranscriptWrite}, - Itertools, + Deserialize, DeserializeOwned, Itertools, Serialize, }, Error, }; -use num_integer::Integer; use rand::RngCore; use std::{iter, marker::PhantomData, ops::Neg, slice}; #[derive(Clone, Debug)] pub struct MultilinearKzg(PhantomData); -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct MultilinearKzgParams { g1: M::G1Affine, eqs: Vec>, @@ -54,7 +52,7 @@ impl MultilinearKzgParams { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct MultilinearKzgProverParams { g1: M::G1Affine, eqs: Vec>, @@ -62,7 +60,7 @@ pub struct MultilinearKzgProverParams { impl MultilinearKzgProverParams { pub fn num_vars(&self) -> usize { - self.eqs.len() + self.eqs.len() - 1 } pub fn g1(&self) -> M::G1Affine { @@ -74,11 +72,11 @@ impl MultilinearKzgProverParams { } pub fn eq(&self, num_vars: usize) -> &[M::G1Affine] { - &self.eqs[self.num_vars() - num_vars] + &self.eqs[num_vars] } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct MultilinearKzgVerifierParams { g1: M::G1Affine, g2: M::G2Affine, @@ -99,36 +97,48 @@ impl MultilinearKzgVerifierParams { } pub fn ss(&self, num_vars: usize) -> &[M::G2Affine] { - &self.ss[self.num_vars() - num_vars..] + &self.ss[..num_vars] } } -#[derive(Clone, Debug)] -pub struct MultilinearKzgCommitment(pub M::G1Affine); +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MultilinearKzgCommitment(pub C); -impl Default for MultilinearKzgCommitment { +impl Default for MultilinearKzgCommitment { fn default() -> Self { - Self(M::G1Affine::identity()) + Self(C::identity()) } } -impl PartialEq for MultilinearKzgCommitment { +impl PartialEq for MultilinearKzgCommitment { fn eq(&self, other: &Self) -> bool { self.0.eq(&other.0) } } -impl Eq for MultilinearKzgCommitment {} +impl Eq for MultilinearKzgCommitment {} -impl AsRef<[M::G1Affine]> for MultilinearKzgCommitment { - fn as_ref(&self) -> &[M::G1Affine] { +impl AsRef<[C]> for MultilinearKzgCommitment { + fn as_ref(&self) -> &[C] { slice::from_ref(&self.0) } } -impl AdditiveCommitment for MultilinearKzgCommitment { +impl AsRef for MultilinearKzgCommitment { + fn as_ref(&self) -> &C { + &self.0 + } +} + +impl From for MultilinearKzgCommitment { + fn from(comm: C) -> Self { + Self(comm) + } +} + +impl AdditiveCommitment for MultilinearKzgCommitment { fn sum_with_scalar<'a>( - scalars: impl IntoIterator + 'a, + scalars: impl IntoIterator + 'a, bases: impl IntoIterator + 'a, ) -> Self { let scalars = scalars.into_iter().collect_vec(); @@ -139,13 +149,19 @@ impl AdditiveCommitment for MultilinearKzgCommitm } } -impl PolynomialCommitmentScheme for MultilinearKzg { +impl PolynomialCommitmentScheme for MultilinearKzg +where + M: MultiMillerLoop, + M::Scalar: Serialize + DeserializeOwned, + M::G1Affine: Serialize + DeserializeOwned, + M::G2Affine: Serialize + DeserializeOwned, +{ type Param = MultilinearKzgParams; type ProverParam = MultilinearKzgProverParams; type VerifierParam = MultilinearKzgVerifierParams; type Polynomial = MultilinearPolynomial; + type Commitment = MultilinearKzgCommitment; type CommitmentChunk = M::G1Affine; - type Commitment = MultilinearKzgCommitment; fn setup(poly_size: usize, _: usize, mut rng: impl RngCore) -> Result { assert!(poly_size.is_power_of_two()); @@ -154,35 +170,25 @@ impl PolynomialCommitmentScheme for MultilinearKz .take(num_vars) .collect_vec(); - let expand_serial = |evals: &mut [M::Scalar], last_evals: &[M::Scalar], s_i: &M::Scalar| { - for (evals, last_eval) in evals.chunks_mut(2).zip(last_evals.iter()) { - evals[1] = *last_eval * s_i; - evals[0] = *last_eval - &evals[1]; - } - }; - let g1 = M::G1Affine::generator(); let eqs = { - let mut eqs = Vec::with_capacity(num_vars); - let init_evals = vec![M::Scalar::ONE]; - for s_i in ss.iter().rev() { - let last_evals = eqs.last().unwrap_or(&init_evals); + let mut eqs = Vec::with_capacity(1 << (num_vars + 1)); + eqs.push(vec![M::Scalar::ONE]); + + for s_i in ss.iter() { + let last_evals = eqs.last().unwrap(); let mut evals = vec![M::Scalar::ZERO; 2 * last_evals.len()]; - if evals.len() < 32 { - expand_serial(&mut evals, last_evals, s_i); - } else { - let mut chunk_size = div_ceil(evals.len(), num_threads()); - if chunk_size.is_odd() { - chunk_size += 1; - } - parallelize_iter( - evals - .chunks_mut(chunk_size) - .zip(last_evals.chunks(chunk_size >> 1)), - |(evals, last_evals)| expand_serial(evals, last_evals, s_i), - ); - } + let (evals_lo, evals_hi) = evals.split_at_mut(last_evals.len()); + + parallelize(evals_hi, |(evals_hi, start)| { + izip!(evals_hi, &last_evals[start..]) + .for_each(|(eval_hi, last_eval)| *eval_hi = *s_i * last_eval); + }); + parallelize(evals_lo, |(evals_lo, start)| { + izip!(evals_lo, &evals_hi[start..], &last_evals[start..]) + .for_each(|(eval_lo, eval_hi, last_eval)| *eval_lo = *last_eval - eval_hi); + }); eqs.push(evals) } @@ -192,7 +198,7 @@ impl PolynomialCommitmentScheme for MultilinearKz let eqs_projective = fixed_base_msm( window_size, &window_table, - eqs.iter().rev().flat_map(|evals| evals.iter()), + eqs.iter().flat_map(|evals| evals.iter()), ); let mut eqs = vec![M::G1Affine::identity(); eqs_projective.len()]; @@ -200,8 +206,8 @@ impl PolynomialCommitmentScheme for MultilinearKz M::G1::batch_normalize(&eqs_projective[start..(start + eqs.len())], eqs); }); let eqs = &mut eqs.drain(..); - (0..num_vars) - .map(move |idx| eqs.take(1 << (num_vars - idx)).collect_vec()) + (0..num_vars + 1) + .map(move |idx| eqs.take(1 << idx).collect_vec()) .collect_vec() }; @@ -233,12 +239,12 @@ impl PolynomialCommitmentScheme for MultilinearKz } let pp = Self::ProverParam { g1: param.g1, - eqs: param.eqs[param.num_vars() - num_vars..].to_vec(), + eqs: param.eqs[..num_vars + 1].to_vec(), }; let vp = Self::VerifierParam { g1: param.g1, g2: param.g2, - ss: param.ss[param.num_vars() - num_vars..].to_vec(), + ss: param.ss[..num_vars].to_vec(), }; Ok((pp, vp)) } @@ -282,53 +288,15 @@ impl PolynomialCommitmentScheme for MultilinearKz assert_eq!(poly.evaluate(point), *eval); } - let mut remainder = poly.evals().to_vec(); - let quotients = point - .iter() - .enumerate() - .map(|(idx, x_i)| { - let timer = start_timer(|| "quotients"); - let mut quotient = vec![M::Scalar::ZERO; remainder.len() >> 1]; - parallelize(&mut quotient, |(quotient, start)| { - for (quotient, (remainder_0, remainder_1)) in quotient.iter_mut().zip( - remainder[2 * start..] - .iter() - .step_by(2) - .zip(remainder[2 * start + 1..].iter().step_by(2)), - ) { - *quotient = *remainder_1 - remainder_0; - } - }); - - let mut next_remainder = vec![M::Scalar::ZERO; remainder.len() >> 1]; - parallelize(&mut next_remainder, |(next_remainder, start)| { - for (next_remainder, (remainder_0, remainder_1)) in - next_remainder.iter_mut().zip( - remainder[2 * start..] - .iter() - .step_by(2) - .zip(remainder[2 * start + 1..].iter().step_by(2)), - ) - { - *next_remainder = (*remainder_1 - remainder_0) * x_i + remainder_0; - } - }); - remainder = next_remainder; - end_timer(timer); - - if quotient.len() == 1 { - variable_base_msm("ient, &[pp.g1]).into() - } else { - variable_base_msm("ient, pp.eq(poly.num_vars() - idx - 1)).into() - } - }) - .collect_vec(); + let (quotient_comms, remainder) = quotients(poly, point, |num_vars, quotient| { + variable_base_msm("ient, pp.eq(num_vars)).into() + }); if cfg!(feature = "sanity-check") { - assert_eq!(&remainder[0], eval); + assert_eq!(&remainder, eval); } - transcript.write_commitments("ients)?; + transcript.write_commitments("ient_comms)?; Ok(()) } diff --git a/plonkish_backend/src/pcs/multilinear/zeromorph.rs b/plonkish_backend/src/pcs/multilinear/zeromorph.rs new file mode 100644 index 00000000..67d07039 --- /dev/null +++ b/plonkish_backend/src/pcs/multilinear/zeromorph.rs @@ -0,0 +1,326 @@ +use crate::{ + pcs::{ + multilinear::{additive, quotients}, + univariate::{UnivariateKzg, UnivariateKzgProverParam, UnivariateKzgVerifierParam}, + Evaluation, Point, PolynomialCommitmentScheme, + }, + poly::{multilinear::MultilinearPolynomial, univariate::UnivariatePolynomial, Polynomial}, + util::{ + arithmetic::{ + powers, squares, variable_base_msm, BatchInvert, Curve, Field, MultiMillerLoop, + }, + chain, izip, + parallel::parallelize, + transcript::{TranscriptRead, TranscriptWrite}, + Deserialize, DeserializeOwned, Itertools, Serialize, + }, + Error, +}; +use rand::RngCore; +use std::marker::PhantomData; + +#[derive(Clone, Debug)] +pub struct Zeromorph(PhantomData); + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound( + serialize = "M::G1Affine: Serialize", + deserialize = "M::G1Affine: DeserializeOwned", +))] +pub struct ZeromorphKzgProverParam { + commit_pp: UnivariateKzgProverParam, + open_pp: UnivariateKzgProverParam, +} + +impl ZeromorphKzgProverParam { + pub fn degree(&self) -> usize { + self.commit_pp.powers_of_s_g1().len() - 1 + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound( + serialize = "M::G1Affine: Serialize, M::G2Affine: Serialize", + deserialize = "M::G1Affine: DeserializeOwned, M::G2Affine: DeserializeOwned", +))] +pub struct ZeromorphKzgVerifierParam { + vp: UnivariateKzgVerifierParam, + s_offset_g2: M::G2Affine, +} + +impl ZeromorphKzgVerifierParam { + pub fn g1(&self) -> M::G1Affine { + self.vp.g1() + } + + pub fn g2(&self) -> M::G2Affine { + self.vp.g2() + } + + pub fn s_g2(&self) -> M::G2Affine { + self.vp.s_g2() + } +} + +impl PolynomialCommitmentScheme for Zeromorph> +where + M: MultiMillerLoop, + M::Scalar: Serialize + DeserializeOwned, + M::G1Affine: Serialize + DeserializeOwned, + M::G2Affine: Serialize + DeserializeOwned, +{ + type Param = as PolynomialCommitmentScheme>::Param; + type ProverParam = ZeromorphKzgProverParam; + type VerifierParam = ZeromorphKzgVerifierParam; + type Polynomial = MultilinearPolynomial; + type Commitment = as PolynomialCommitmentScheme>::Commitment; + type CommitmentChunk = + as PolynomialCommitmentScheme>::CommitmentChunk; + + fn setup(poly_size: usize, batch_size: usize, rng: impl RngCore) -> Result { + UnivariateKzg::::setup(poly_size, batch_size, rng) + } + + fn trim( + param: &Self::Param, + poly_size: usize, + batch_size: usize, + ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { + let (commit_pp, vp) = UnivariateKzg::::trim(param, poly_size, batch_size)?; + let offset = param.powers_of_s_g1().len() - poly_size; + let open_pp = { + let powers_of_s_g1 = param.powers_of_s_g1()[offset..].to_vec(); + UnivariateKzgProverParam::new(powers_of_s_g1) + }; + let s_offset_g2 = param.powers_of_s_g2()[offset]; + + Ok(( + ZeromorphKzgProverParam { commit_pp, open_pp }, + ZeromorphKzgVerifierParam { vp, s_offset_g2 }, + )) + } + + fn commit(pp: &Self::ProverParam, poly: &Self::Polynomial) -> Result { + if pp.degree() + 1 < poly.evals().len() { + return Err(Error::InvalidPcsParam(format!( + "Too large degree of poly to commit (param supports degree up to {} but got {})", + pp.degree(), + poly.evals().len() + ))); + } + + Ok(UnivariateKzg::commit_coeffs(&pp.commit_pp, poly.evals())) + } + + fn batch_commit<'a>( + pp: &Self::ProverParam, + polys: impl IntoIterator, + ) -> Result, Error> { + polys + .into_iter() + .map(|poly| Self::commit(pp, poly)) + .collect() + } + + fn open( + pp: &Self::ProverParam, + poly: &Self::Polynomial, + comm: &Self::Commitment, + point: &Point, + eval: &M::Scalar, + transcript: &mut impl TranscriptWrite, + ) -> Result<(), Error> { + let num_vars = poly.num_vars(); + if pp.degree() + 1 < poly.evals().len() { + return Err(Error::InvalidPcsParam(format!( + "Too large degree of poly to open (param supports degree up to {} but got {})", + pp.degree(), + poly.evals().len() + ))); + } + + if cfg!(feature = "sanity-check") { + assert_eq!(Self::commit(pp, poly).unwrap().0, comm.0); + assert_eq!(poly.evaluate(point), *eval); + } + + let (quotients, remainder) = quotients(poly, point, |_, q| UnivariatePolynomial::new(q)); + UnivariateKzg::batch_commit_and_write(&pp.commit_pp, "ients, transcript)?; + + if cfg!(feature = "sanity-check") { + assert_eq!(&remainder, eval); + } + + let y = transcript.squeeze_challenge(); + + let q_hat = { + let mut q_hat = vec![M::Scalar::ZERO; 1 << num_vars]; + for (idx, (power_of_y, q)) in izip!(powers(y), "ients).enumerate() { + let offset = (1 << num_vars) - (1 << idx); + parallelize(&mut q_hat[offset..], |(q_hat, start)| { + izip!(q_hat, q.iter().skip(start)) + .for_each(|(q_hat, q)| *q_hat += power_of_y * q) + }); + } + UnivariatePolynomial::new(q_hat) + }; + UnivariateKzg::commit_and_write(&pp.commit_pp, &q_hat, transcript)?; + + let x = transcript.squeeze_challenge(); + let z = transcript.squeeze_challenge(); + + let (eval_scalar, q_scalars) = eval_and_quotient_scalars(y, x, z, point); + + let mut f = UnivariatePolynomial::new(poly.evals().to_vec()); + f *= &z; + f += &q_hat; + f[0] += eval_scalar * eval; + izip!("ients, &q_scalars).for_each(|(q, scalar)| f += (scalar, q)); + + let comm = if cfg!(feature = "sanity-check") { + assert_eq!(f.evaluate(&x), M::Scalar::ZERO); + + UnivariateKzg::commit_coeffs(&pp.open_pp, f.coeffs()) + } else { + Default::default() + }; + + UnivariateKzg::::open(&pp.open_pp, &f, &comm, &x, &M::Scalar::ZERO, transcript) + } + + fn batch_open<'a>( + pp: &Self::ProverParam, + polys: impl IntoIterator, + comms: impl IntoIterator, + points: &[Point], + evals: &[Evaluation], + transcript: &mut impl TranscriptWrite, + ) -> Result<(), Error> + where + Self::Commitment: 'a, + { + let polys = polys.into_iter().collect_vec(); + let comms = comms.into_iter().collect_vec(); + let num_vars = points.first().map(|point| point.len()).unwrap_or_default(); + additive::batch_open::<_, Self>(pp, num_vars, polys, comms, points, evals, transcript) + } + + fn read_commitments( + vp: &Self::VerifierParam, + num_polys: usize, + transcript: &mut impl TranscriptRead, + ) -> Result, Error> { + UnivariateKzg::read_commitments(&vp.vp, num_polys, transcript) + } + + fn verify( + vp: &Self::VerifierParam, + comm: &Self::Commitment, + point: &Point, + eval: &M::Scalar, + transcript: &mut impl TranscriptRead, + ) -> Result<(), Error> { + let num_vars = point.len(); + + let q_comms = transcript.read_commitments(num_vars)?; + + let y = transcript.squeeze_challenge(); + + let q_hat_comm = transcript.read_commitment()?; + + let x = transcript.squeeze_challenge(); + let z = transcript.squeeze_challenge(); + + let (eval_scalar, q_scalars) = eval_and_quotient_scalars(y, x, z, point); + + let scalars = chain![[M::Scalar::ONE, z, eval_scalar * eval], q_scalars].collect_vec(); + let bases = chain![[q_hat_comm, comm.0, vp.g1()], q_comms].collect_vec(); + let c = variable_base_msm(&scalars, &bases).into(); + + let pi = transcript.read_commitment()?; + + M::pairings_product_is_identity(&[ + (&c, &(-vp.s_offset_g2).into()), + (&pi, &(vp.s_g2() - (vp.g2() * x).into()).to_affine().into()), + ]) + .then_some(()) + .ok_or_else(|| Error::InvalidPcsOpen("Invalid Zeromorph KZG open".to_string())) + } + + fn batch_verify<'a>( + vp: &Self::VerifierParam, + comms: impl IntoIterator, + points: &[Point], + evals: &[Evaluation], + transcript: &mut impl TranscriptRead, + ) -> Result<(), Error> { + let num_vars = points.first().map(|point| point.len()).unwrap_or_default(); + let comms = comms.into_iter().collect_vec(); + additive::batch_verify::<_, Self>(vp, num_vars, comms, points, evals, transcript) + } +} + +fn eval_and_quotient_scalars(y: F, x: F, z: F, u: &[F]) -> (F, Vec) { + let num_vars = u.len(); + + let squares_of_x = squares(x).take(num_vars + 1).collect_vec(); + let offsets_of_x = { + let mut offsets_of_x = squares_of_x + .iter() + .rev() + .skip(1) + .scan(F::ONE, |state, power_of_x| { + *state *= power_of_x; + Some(*state) + }) + .collect_vec(); + offsets_of_x.reverse(); + offsets_of_x + }; + let vs = { + let v_numer = squares_of_x[num_vars] - F::ONE; + let mut v_denoms = squares_of_x + .iter() + .map(|square_of_x| *square_of_x - F::ONE) + .collect_vec(); + v_denoms.iter_mut().batch_invert(); + v_denoms + .iter() + .map(|v_denom| v_numer * v_denom) + .collect_vec() + }; + let q_scalars = izip!(powers(y), offsets_of_x, squares_of_x, &vs, &vs[1..], u) + .map(|(power_of_y, offset_of_x, square_of_x, v_i, v_j, u_i)| { + -(power_of_y * offset_of_x + z * (square_of_x * v_j - *u_i * v_i)) + }) + .collect_vec(); + + (-vs[0] * z, q_scalars) +} + +#[cfg(test)] +mod test { + use crate::{ + pcs::{ + multilinear::{ + test::{run_batch_commit_open_verify, run_commit_open_verify}, + zeromorph::Zeromorph, + }, + univariate::UnivariateKzg, + }, + util::transcript::Keccak256Transcript, + }; + use halo2_curves::bn256::Bn256; + + type Pcs = Zeromorph>; + + #[test] + fn commit_open_verify() { + run_commit_open_verify::<_, Pcs, Keccak256Transcript<_>>(); + } + + #[test] + fn batch_commit_open_verify() { + run_batch_commit_open_verify::<_, Pcs, Keccak256Transcript<_>>(); + } +} diff --git a/plonkish_backend/src/pcs/univariate/kzg.rs b/plonkish_backend/src/pcs/univariate/kzg.rs index 426a52ce..9350d815 100644 --- a/plonkish_backend/src/pcs/univariate/kzg.rs +++ b/plonkish_backend/src/pcs/univariate/kzg.rs @@ -4,13 +4,13 @@ use crate::{ util::{ arithmetic::{ barycentric_interpolate, barycentric_weights, fixed_base_msm, inner_product, powers, - variable_base_msm, window_size, window_table, Curve, Field, MultiMillerLoop, - PrimeCurveAffine, + variable_base_msm, window_size, window_table, Curve, CurveAffine, Field, + MultiMillerLoop, PrimeCurveAffine, }, chain, izip, izip_eq, parallel::parallelize, transcript::{TranscriptRead, TranscriptWrite}, - Itertools, + Deserialize, DeserializeOwned, Itertools, Serialize, }, Error, }; @@ -24,62 +24,72 @@ impl UnivariateKzg { pub(crate) fn commit_coeffs( pp: &UnivariateKzgProverParam, coeffs: &[M::Scalar], - ) -> UnivariateKzgCommitment { - UnivariateKzgCommitment(variable_base_msm(coeffs, &pp.powers_of_s[..coeffs.len()]).into()) + ) -> UnivariateKzgCommitment { + let comm = variable_base_msm(coeffs, &pp.powers_of_s_g1[..coeffs.len()]).into(); + UnivariateKzgCommitment(comm) } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound( + serialize = "M::G1Affine: Serialize, M::G2Affine: Serialize", + deserialize = "M::G1Affine: DeserializeOwned, M::G2Affine: DeserializeOwned", +))] pub struct UnivariateKzgParam { - g1: M::G1Affine, - powers_of_s: Vec, - g2: M::G2Affine, - s_g2: M::G2Affine, + powers_of_s_g1: Vec, + powers_of_s_g2: Vec, } impl UnivariateKzgParam { pub fn degree(&self) -> usize { - self.powers_of_s.len() - 1 + self.powers_of_s_g1.len() - 1 } pub fn g1(&self) -> M::G1Affine { - self.g1 + self.powers_of_s_g1[0] } - pub fn powers_of_s(&self) -> &[M::G1Affine] { - &self.powers_of_s + pub fn powers_of_s_g1(&self) -> &[M::G1Affine] { + &self.powers_of_s_g1 } pub fn g2(&self) -> M::G2Affine { - self.g2 + self.powers_of_s_g2[0] } - pub fn s_g2(&self) -> M::G2Affine { - self.s_g2 + pub fn powers_of_s_g2(&self) -> &[M::G2Affine] { + &self.powers_of_s_g2 } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound( + serialize = "M::G1Affine: Serialize", + deserialize = "M::G1Affine: DeserializeOwned", +))] pub struct UnivariateKzgProverParam { - g1: M::G1Affine, - powers_of_s: Vec, + powers_of_s_g1: Vec, } impl UnivariateKzgProverParam { + pub(crate) fn new(powers_of_s_g1: Vec) -> Self { + Self { powers_of_s_g1 } + } + pub fn degree(&self) -> usize { - self.powers_of_s.len() - 1 + self.powers_of_s_g1.len() - 1 } pub fn g1(&self) -> M::G1Affine { - self.g1 + self.powers_of_s_g1[0] } - pub fn powers_of_s(&self) -> &[M::G1Affine] { - &self.powers_of_s + pub fn powers_of_s_g1(&self) -> &[M::G1Affine] { + &self.powers_of_s_g1 } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct UnivariateKzgVerifierParam { g1: M::G1Affine, g2: M::G2Affine, @@ -100,32 +110,44 @@ impl UnivariateKzgVerifierParam { } } -#[derive(Clone, Debug)] -pub struct UnivariateKzgCommitment(pub M::G1Affine); +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct UnivariateKzgCommitment(pub C); -impl Default for UnivariateKzgCommitment { +impl Default for UnivariateKzgCommitment { fn default() -> Self { - Self(M::G1Affine::identity()) + Self(C::identity()) } } -impl PartialEq for UnivariateKzgCommitment { +impl PartialEq for UnivariateKzgCommitment { fn eq(&self, other: &Self) -> bool { self.0.eq(&other.0) } } -impl Eq for UnivariateKzgCommitment {} +impl Eq for UnivariateKzgCommitment {} -impl AsRef<[M::G1Affine]> for UnivariateKzgCommitment { - fn as_ref(&self) -> &[M::G1Affine] { +impl AsRef<[C]> for UnivariateKzgCommitment { + fn as_ref(&self) -> &[C] { slice::from_ref(&self.0) } } -impl AdditiveCommitment for UnivariateKzgCommitment { +impl AsRef for UnivariateKzgCommitment { + fn as_ref(&self) -> &C { + &self.0 + } +} + +impl From for UnivariateKzgCommitment { + fn from(comm: C) -> Self { + Self(comm) + } +} + +impl AdditiveCommitment for UnivariateKzgCommitment { fn sum_with_scalar<'a>( - scalars: impl IntoIterator + 'a, + scalars: impl IntoIterator + 'a, bases: impl IntoIterator + 'a, ) -> Self { let scalars = scalars.into_iter().collect_vec(); @@ -136,42 +158,62 @@ impl AdditiveCommitment for UnivariateKzgCommitme } } -impl PolynomialCommitmentScheme for UnivariateKzg { +impl PolynomialCommitmentScheme for UnivariateKzg +where + M: MultiMillerLoop, + M::Scalar: Serialize + DeserializeOwned, + M::G1Affine: Serialize + DeserializeOwned, + M::G2Affine: Serialize + DeserializeOwned, +{ type Param = UnivariateKzgParam; type ProverParam = UnivariateKzgProverParam; type VerifierParam = UnivariateKzgVerifierParam; type Polynomial = UnivariatePolynomial; + type Commitment = UnivariateKzgCommitment; type CommitmentChunk = M::G1Affine; - type Commitment = UnivariateKzgCommitment; fn setup(poly_size: usize, _: usize, rng: impl RngCore) -> Result { let s = M::Scalar::random(rng); let g1 = M::G1Affine::generator(); - let powers_of_s = { - let powers_of_s = powers(s).take(poly_size).collect_vec(); + let powers_of_s_g1 = { + let powers_of_s_g1 = powers(s).take(poly_size).collect_vec(); let window_size = window_size(poly_size); let window_table = window_table(window_size, g1); - let powers_of_s_projective = fixed_base_msm(window_size, &window_table, &powers_of_s); + let powers_of_s_projective = + fixed_base_msm(window_size, &window_table, &powers_of_s_g1); - let mut powers_of_s = vec![M::G1Affine::identity(); powers_of_s_projective.len()]; - parallelize(&mut powers_of_s, |(powers_of_s, starts)| { + let mut powers_of_s_g1 = vec![M::G1Affine::identity(); powers_of_s_projective.len()]; + parallelize(&mut powers_of_s_g1, |(powers_of_s_g1, starts)| { M::G1::batch_normalize( - &powers_of_s_projective[starts..(starts + powers_of_s.len())], - powers_of_s, + &powers_of_s_projective[starts..(starts + powers_of_s_g1.len())], + powers_of_s_g1, ); }); - powers_of_s + powers_of_s_g1 }; let g2 = M::G2Affine::generator(); - let s_g2 = (g2 * s).into(); + let powers_of_s_g2 = { + let powers_of_s_g2 = powers(s).take(poly_size).collect_vec(); + let window_size = window_size(poly_size); + let window_table = window_table(window_size, g2); + let powers_of_s_projective = + fixed_base_msm(window_size, &window_table, &powers_of_s_g2); + + let mut powers_of_s_g2 = vec![M::G2Affine::identity(); powers_of_s_projective.len()]; + parallelize(&mut powers_of_s_g2, |(powers_of_s_g2, starts)| { + M::G2::batch_normalize( + &powers_of_s_projective[starts..(starts + powers_of_s_g2.len())], + powers_of_s_g2, + ); + }); + powers_of_s_g2 + }; Ok(Self::Param { - g1, - powers_of_s, - g2, - s_g2, + powers_of_s_g1, + powers_of_s_g2, }) } @@ -180,22 +222,19 @@ impl PolynomialCommitmentScheme for UnivariateKzg poly_size: usize, _: usize, ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { - if param.powers_of_s.len() < poly_size { + if param.powers_of_s_g1.len() < poly_size { return Err(Error::InvalidPcsParam(format!( "Too large poly_size to trim to (param supports poly_size up to {} but got {poly_size})", - param.powers_of_s.len(), + param.powers_of_s_g1.len(), ))); } - let powers_of_s = param.powers_of_s[..poly_size].to_vec(); - let pp = Self::ProverParam { - g1: param.g1, - powers_of_s, - }; + let powers_of_s_g1 = param.powers_of_s_g1[..poly_size].to_vec(); + let pp = Self::ProverParam { powers_of_s_g1 }; let vp = Self::VerifierParam { - g1: param.powers_of_s[0], - g2: param.g2, - s_g2: param.s_g2, + g1: param.g1(), + g2: param.g2(), + s_g2: param.powers_of_s_g2[1], }; Ok((pp, vp)) } @@ -331,10 +370,9 @@ impl PolynomialCommitmentScheme for UnivariateKzg eval: &M::Scalar, transcript: &mut impl TranscriptRead, ) -> Result<(), Error> { - let quotient = transcript.read_commitment()?; - let lhs = (quotient * point + comm.0 - vp.g1 * eval).into(); - let rhs = quotient; - M::pairings_product_is_identity(&[(&lhs, &vp.g2.neg().into()), (&rhs, &vp.s_g2.into())]) + let pi = transcript.read_commitment()?; + let c = (pi * point + comm.0 - vp.g1 * eval).into(); + M::pairings_product_is_identity(&[(&c, &(-vp.g2).into()), (&pi, &vp.s_g2.into())]) .then_some(()) .ok_or_else(|| Error::InvalidPcsOpen("Invalid univariate KZG open".to_string())) } @@ -559,7 +597,7 @@ mod test { }; // Verify let result = { - let mut transcript = Keccak256Transcript::from_proof(proof.as_slice()); + let mut transcript = Keccak256Transcript::from_proof((), proof.as_slice()); Pcs::verify( &vp, &Pcs::read_commitment(&vp, &mut transcript).unwrap(), @@ -617,7 +655,7 @@ mod test { }; // Batch verify let result = { - let mut transcript = Keccak256Transcript::from_proof(proof.as_slice()); + let mut transcript = Keccak256Transcript::from_proof((), proof.as_slice()); Pcs::batch_verify( &vp, &Pcs::read_commitments(&vp, batch_size, &mut transcript).unwrap(), diff --git a/plonkish_backend/src/piop/sum_check.rs b/plonkish_backend/src/piop/sum_check.rs index 6617f5cd..4c292a74 100644 --- a/plonkish_backend/src/piop/sum_check.rs +++ b/plonkish_backend/src/piop/sum_check.rs @@ -157,7 +157,7 @@ pub(super) mod test { transcript.into_proof() }; let accept = { - let mut transcript = Keccak256Transcript::from_proof(proof.as_slice()); + let mut transcript = Keccak256Transcript::from_proof((), proof.as_slice()); let (x_eval, x) = S::verify(&vp, num_vars, degree, Fr::zero(), &mut transcript).unwrap(); let evals = expression diff --git a/plonkish_backend/src/piop/sum_check/classic/eval.rs b/plonkish_backend/src/piop/sum_check/classic/eval.rs index be3ea1de..fe1f3410 100644 --- a/plonkish_backend/src/piop/sum_check/classic/eval.rs +++ b/plonkish_backend/src/piop/sum_check/classic/eval.rs @@ -313,7 +313,7 @@ impl SumCheckEvaluator { state: &ProverState, b: usize, ) { - assert!(evals.0.len() > 2); + debug_assert!(evals.0.len() > 2); self.evaluate_next::(&mut evals[1], state, cache, b); for eval in evals[2..].iter_mut() { diff --git a/plonkish_backend/src/poly.rs b/plonkish_backend/src/poly.rs index 0ff199ae..33a21d3d 100644 --- a/plonkish_backend/src/poly.rs +++ b/plonkish_backend/src/poly.rs @@ -1,2 +1,17 @@ +use crate::util::arithmetic::Field; +use std::{fmt::Debug, ops::AddAssign}; + pub mod multilinear; pub mod univariate; + +pub trait Polynomial: Clone + Debug + for<'a> AddAssign<(&'a F, &'a Self)> { + type Point: Clone + Debug; + + fn from_evals(evals: Vec) -> Self; + + fn into_evals(self) -> Vec; + + fn evals(&self) -> &[F]; + + fn evaluate(&self, point: &Self::Point) -> F; +} diff --git a/plonkish_backend/src/poly/multilinear.rs b/plonkish_backend/src/poly/multilinear.rs index 406c8a4f..20ccf63b 100644 --- a/plonkish_backend/src/poly/multilinear.rs +++ b/plonkish_backend/src/poly/multilinear.rs @@ -1,11 +1,11 @@ use crate::{ - pcs::Polynomial, + poly::Polynomial, util::{ arithmetic::{div_ceil, usize_from_bits_le, BooleanHypercube, Field}, expression::Rotation, impl_index, parallel::{num_threads, parallelize, parallelize_iter}, - BitIndex, Itertools, + BitIndex, Deserialize, Itertools, Serialize, }, }; use num_integer::Integer; @@ -17,7 +17,7 @@ use std::{ ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}, }; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct MultilinearPolynomial { evals: Vec, num_vars: usize, @@ -518,7 +518,10 @@ pub fn rotation_eval_points(x: &[F], rotation: Rotation) -> Vec } } -fn rotation_eval_point_pattern(num_vars: usize, distance: usize) -> Vec { +pub(crate) fn rotation_eval_point_pattern( + num_vars: usize, + distance: usize, +) -> Vec { let bh = BooleanHypercube::new(num_vars); let remainder = if NEXT { bh.primitive() } else { bh.x_inv() }; let mut pattern = vec![0; 1 << distance]; @@ -536,7 +539,10 @@ fn rotation_eval_point_pattern(num_vars: usize, distance: usiz pattern } -fn rotation_eval_coeff_pattern(num_vars: usize, distance: usize) -> Vec { +pub(crate) fn rotation_eval_coeff_pattern( + num_vars: usize, + distance: usize, +) -> Vec { let bh = BooleanHypercube::new(num_vars); let remainder = if NEXT { bh.primitive() - (1 << num_vars) @@ -626,8 +632,10 @@ pub(crate) use zip_self; #[cfg(test)] mod test { use crate::{ - pcs::Polynomial, - poly::multilinear::{rotation_eval, zip_self, MultilinearPolynomial}, + poly::{ + multilinear::{rotation_eval, zip_self, MultilinearPolynomial}, + Polynomial, + }, util::{ arithmetic::{BooleanHypercube, Field}, expression::Rotation, diff --git a/plonkish_backend/src/poly/univariate.rs b/plonkish_backend/src/poly/univariate.rs index ce7096ea..da2531fd 100644 --- a/plonkish_backend/src/poly/univariate.rs +++ b/plonkish_backend/src/poly/univariate.rs @@ -1,10 +1,10 @@ use crate::{ - pcs::Polynomial, + poly::Polynomial, util::{ arithmetic::{div_ceil, horner, powers, Field}, impl_index, parallel::{num_threads, parallelize, parallelize_iter}, - Itertools, + Deserialize, Itertools, Serialize, }, }; use rand::RngCore; @@ -19,12 +19,12 @@ use std::{ pub trait Basis: Clone + Copy + Debug {} -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct CoefficientBasis; impl Basis for CoefficientBasis {} -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct UnivariatePolynomial { values: Vec, _marker: PhantomData, diff --git a/plonkish_backend/src/util.rs b/plonkish_backend/src/util.rs index 6669e6a0..30df4dcb 100644 --- a/plonkish_backend/src/util.rs +++ b/plonkish_backend/src/util.rs @@ -7,6 +7,8 @@ mod timer; pub mod transcript; pub use itertools::{chain, izip, Itertools}; +pub use num_bigint::BigUint; +pub use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer}; pub use timer::{end_timer, start_timer, start_unit_timer}; macro_rules! izip_eq { diff --git a/plonkish_backend/src/util/arithmetic.rs b/plonkish_backend/src/util/arithmetic.rs index 873d1562..1a44679d 100644 --- a/plonkish_backend/src/util/arithmetic.rs +++ b/plonkish_backend/src/util/arithmetic.rs @@ -1,6 +1,9 @@ -use crate::util::Itertools; -use halo2_curves::pairing::{self, MillerLoopResult}; -use num_bigint::BigUint; +use crate::util::{BigUint, Itertools}; +use halo2_curves::{ + bn256, grumpkin, + pairing::{self, MillerLoopResult}, + pasta::{pallas, vesta}, +}; use num_integer::Integer; use std::{borrow::Borrow, fmt::Debug, iter}; @@ -8,9 +11,10 @@ mod bh; mod msm; pub use bh::BooleanHypercube; +pub use bitvec::field::BitField; pub use halo2_curves::{ group::{ - ff::{BatchInvert, Field, PrimeField, PrimeFieldBits}, + ff::{BatchInvert, Field, FromUniformBytes, PrimeField, PrimeFieldBits}, prime::PrimeCurveAffine, Curve, Group, }, @@ -29,6 +33,26 @@ pub trait MultiMillerLoop: pairing::MultiMillerLoop + Debug + Sync { impl MultiMillerLoop for M where M: pairing::MultiMillerLoop + Debug + Sync {} +pub trait TwoChainCurve: CurveAffine { + type Secondary: TwoChainCurve; +} + +impl TwoChainCurve for bn256::G1Affine { + type Secondary = grumpkin::G1Affine; +} + +impl TwoChainCurve for grumpkin::G1Affine { + type Secondary = bn256::G1Affine; +} + +impl TwoChainCurve for pallas::Affine { + type Secondary = vesta::Affine; +} + +impl TwoChainCurve for vesta::Affine { + type Secondary = pallas::Affine; +} + pub fn field_size() -> usize { let neg_one = (-F::ONE).to_repr(); let bytes = neg_one.as_ref(); @@ -54,6 +78,10 @@ pub fn powers(scalar: F) -> impl Iterator { iter::successors(Some(F::ONE), move |power| Some(scalar * power)) } +pub fn squares(scalar: F) -> impl Iterator { + iter::successors(Some(scalar), move |scalar| Some(scalar.square())) +} + pub fn product(values: impl IntoIterator>) -> F { values .into_iter() @@ -111,11 +139,44 @@ pub fn modulus() -> BigUint { BigUint::from_bytes_le((-F::ONE).to_repr().as_ref()) + 1u64 } -pub fn fe_from_bytes_le(bytes: impl AsRef<[u8]>) -> F { - let bytes = (BigUint::from_bytes_le(bytes.as_ref()) % modulus::()).to_bytes_le(); +pub fn fe_from_bool(value: bool) -> F { + if value { + F::ONE + } else { + F::ZERO + } +} + +pub fn fe_mod_from_le_bytes(bytes: impl AsRef<[u8]>) -> F { + fe_from_le_bytes((BigUint::from_bytes_le(bytes.as_ref()) % modulus::()).to_bytes_le()) +} + +pub fn fe_truncated_from_le_bytes(bytes: impl AsRef<[u8]>, num_bits: usize) -> F { + let mut big = BigUint::from_bytes_le(bytes.as_ref()); + (num_bits as u64..big.bits()).for_each(|idx| big.set_bit(idx, false)); + fe_from_le_bytes(big.to_bytes_le()) +} + +pub fn fe_from_le_bytes(bytes: impl AsRef<[u8]>) -> F { + let bytes = bytes.as_ref(); let mut repr = F::Repr::default(); assert!(bytes.len() <= repr.as_ref().len()); - repr.as_mut()[..bytes.len()].copy_from_slice(&bytes); + repr.as_mut()[..bytes.len()].copy_from_slice(bytes); + F::from_repr(repr).unwrap() +} + +pub fn fe_to_fe(fe: F1) -> F2 { + debug_assert!(BigUint::from_bytes_le(fe.to_repr().as_ref()) < modulus::()); + let mut repr = F2::Repr::default(); + repr.as_mut().copy_from_slice(fe.to_repr().as_ref()); + F2::from_repr(repr).unwrap() +} + +pub fn fe_truncated(fe: F, num_bits: usize) -> F { + let (num_bytes, num_bits_last_byte) = div_rem(num_bits, 8); + let mut repr = fe.to_repr(); + repr.as_mut()[num_bytes + 1..].fill(0); + repr.as_mut()[num_bytes] &= (1 << num_bits_last_byte) - 1; F::from_repr(repr).unwrap() } @@ -125,6 +186,10 @@ pub fn usize_from_bits_le(bits: &[bool]) -> usize { .fold(0, |int, bit| (int << 1) + (*bit as usize)) } +pub fn div_rem(dividend: usize, divisor: usize) -> (usize, usize) { + Integer::div_rem(÷nd, &divisor) +} + pub fn div_ceil(dividend: usize, divisor: usize) -> usize { Integer::div_ceil(÷nd, &divisor) } diff --git a/plonkish_backend/src/util/code/brakedown.rs b/plonkish_backend/src/util/code/brakedown.rs index 16459461..5eeafa00 100644 --- a/plonkish_backend/src/util/code/brakedown.rs +++ b/plonkish_backend/src/util/code/brakedown.rs @@ -7,7 +7,7 @@ use crate::util::{ arithmetic::{horner, steps, Field, PrimeField}, code::LinearCodes, - Itertools, + Deserialize, Itertools, Serialize, }; use rand::{distributions::Uniform, Rng, RngCore}; use std::{ @@ -17,7 +17,7 @@ use std::{ iter, }; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Brakedown { row_len: usize, codeword_len: usize, @@ -259,7 +259,7 @@ impl_spec_128!( (BrakedownSpec6, 0.2380, 0.1205, 1.720) ); -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct SparseMatrixDimension { n: usize, m: usize, @@ -272,7 +272,7 @@ impl SparseMatrixDimension { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct SparseMatrix { dimension: SparseMatrixDimension, cells: Vec<(usize, F)>, diff --git a/plonkish_backend/src/util/expression.rs b/plonkish_backend/src/util/expression.rs index f2c05b8a..102cbe94 100644 --- a/plonkish_backend/src/util/expression.rs +++ b/plonkish_backend/src/util/expression.rs @@ -1,4 +1,4 @@ -use crate::util::{arithmetic::Field, izip, Itertools}; +use crate::util::{arithmetic::Field, izip, Deserialize, Itertools, Serialize}; use std::{ collections::BTreeSet, fmt::Debug, @@ -10,7 +10,7 @@ use std::{ pub(crate) mod evaluator; pub mod relaxed; -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Rotation(pub i32); impl Rotation { @@ -37,7 +37,7 @@ impl From for Rotation { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Query { poly: usize, rotation: Rotation, @@ -57,14 +57,14 @@ impl Query { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum CommonPolynomial { Identity, Lagrange(i32), EqXY(usize), } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum Expression { Constant(F), CommonPolynomial(CommonPolynomial), diff --git a/plonkish_backend/src/util/expression/relaxed.rs b/plonkish_backend/src/util/expression/relaxed.rs index 5f9cd0c3..8642521e 100644 --- a/plonkish_backend/src/util/expression/relaxed.rs +++ b/plonkish_backend/src/util/expression/relaxed.rs @@ -1,41 +1,65 @@ use crate::util::{ arithmetic::PrimeField, - expression::{Expression, Query}, + expression::{CommonPolynomial, Expression, Query}, BitIndex, Itertools, }; use std::{ - collections::{BTreeMap, BTreeSet, HashSet}, + collections::{BTreeMap, BTreeSet}, fmt::Debug, iter, }; -pub(crate) fn cross_term_expressions( - num_instance_polys: usize, - num_preprocess_polys: usize, - folding_polys: BTreeSet, - num_challenges: usize, +pub(crate) struct PolynomialSet { + pub(crate) preprocess: BTreeSet, + pub(crate) folding: BTreeSet, +} + +#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)] +enum ExpressionPolynomial { + CommonPolynomial(CommonPolynomial), + Polynomial(Query), +} + +impl From for Expression { + fn from(poly_foldee: ExpressionPolynomial) -> Self { + match poly_foldee { + ExpressionPolynomial::CommonPolynomial(common_poly) => { + Expression::CommonPolynomial(common_poly) + } + ExpressionPolynomial::Polynomial(query) => Expression::Polynomial(query), + } + } +} + +pub(crate) fn cross_term_expressions( + poly_set: &PolynomialSet, products: &[Product], + num_challenges: usize, ) -> Vec> { let folding_degree = folding_degree(products); let num_ts = folding_degree.checked_sub(1).unwrap_or_default(); let u = num_challenges; - let folding_poly_indices = folding_polys.iter().zip(0..).collect::>(); + let [preprocess_poly_indices, folding_poly_indices] = [&poly_set.preprocess, &poly_set.folding] + .map(|polys| polys.iter().zip(0..).collect::>()); products .iter() .fold( - vec![BTreeMap::>, Expression>::new(); num_ts], + vec![BTreeMap::, Expression>::new(); num_ts], |mut scalars, product| { - let (common_scalar, common_expr) = product.preprocess.evaluate( + let (common_scalar, common_poly) = product.preprocess.evaluate( &|constant| (constant, Vec::new()), - &|common_poly| (F::ONE, vec![Expression::CommonPolynomial(common_poly)]), - &|query| { - let poly = query.poly() - num_instance_polys; + &|common_poly| { ( F::ONE, - vec![Expression::Polynomial(Query::new(poly, query.rotation()))], + vec![ExpressionPolynomial::CommonPolynomial(common_poly)], ) }, + &|query| { + let poly = preprocess_poly_indices[&query.poly()]; + let query = Query::new(poly, query.rotation()); + (F::ONE, vec![ExpressionPolynomial::Polynomial(query)]) + }, &|_| unreachable!(), &|(scalar, expr)| (-scalar, expr), &|_, _| unreachable!(), @@ -45,20 +69,20 @@ pub(crate) fn cross_term_expressions( &|(lhs, expr), rhs| (lhs * rhs, expr), ); for idx in 1usize..(1 << folding_degree) - 1 { - let (scalar, mut exprs) = iter::empty() + let (scalar, mut polys) = iter::empty() .chain(iter::repeat(None).take(folding_degree - product.folding_degree())) .chain(product.foldees.iter().map(Some)) .enumerate() .fold( - (Expression::Constant(common_scalar), common_expr.clone()), - |(mut scalar, mut exprs), (nth, foldee)| { + (Expression::Constant(common_scalar), common_poly.clone()), + |(mut scalar, mut polys), (nth, foldee)| { let (poly_offset, challenge_offset) = if idx.nth_bit(nth) { ( - num_preprocess_polys + folding_poly_indices.len(), + preprocess_poly_indices.len() + folding_poly_indices.len(), num_challenges + 1, ) } else { - (num_preprocess_polys, 0) + (preprocess_poly_indices.len(), 0) }; match foldee { None => { @@ -73,16 +97,16 @@ pub(crate) fn cross_term_expressions( let poly = poly_offset + folding_poly_indices[&query.poly()]; let query = Query::new(poly, query.rotation()); - exprs.push(Expression::Polynomial(query)); + polys.push(ExpressionPolynomial::Polynomial(query)); } _ => unreachable!(), } - (scalar, exprs) + (scalar, polys) }, ); - exprs.sort_unstable(); + polys.sort_unstable(); scalars[idx.count_ones() as usize - 1] - .entry(exprs) + .entry(polys) .and_modify(|value| *value = value as &Expression<_> + &scalar) .or_insert(scalar); } @@ -93,7 +117,13 @@ pub(crate) fn cross_term_expressions( .map(|exprs| { exprs .into_iter() - .map(|(polys, scalar)| polys.into_iter().product::>() * scalar) + .map(|(polys, scalar)| { + polys + .into_iter() + .map_into::>() + .product::>() + * scalar + }) .sum::>() }) .collect_vec() @@ -118,7 +148,7 @@ pub(crate) fn relaxed_expression( } pub(crate) fn products( - preprocess_polys: &HashSet, + preprocess_polys: &BTreeSet, constraint: &Expression, ) -> Vec> { let products = constraint.evaluate( diff --git a/plonkish_backend/src/util/hash.rs b/plonkish_backend/src/util/hash.rs index fdaf08d5..3dd262d3 100644 --- a/plonkish_backend/src/util/hash.rs +++ b/plonkish_backend/src/util/hash.rs @@ -7,6 +7,8 @@ pub use sha3::{ Keccak256, }; +pub use poseidon::{self, Poseidon}; + pub trait Hash: 'static + Sized + Clone + Debug + FixedOutputReset + Default + Update + HashMarker { @@ -17,6 +19,12 @@ pub trait Hash: fn update_field_element(&mut self, field: &impl PrimeField) { Digest::update(self, field.to_repr()); } + + fn digest(data: impl AsRef<[u8]>) -> Output { + let mut hasher = Self::default(); + hasher.update(data.as_ref()); + hasher.finalize() + } } impl Hash diff --git a/plonkish_backend/src/util/transcript.rs b/plonkish_backend/src/util/transcript.rs index a8833c2c..bbe2ca80 100644 --- a/plonkish_backend/src/util/transcript.rs +++ b/plonkish_backend/src/util/transcript.rs @@ -1,12 +1,16 @@ use crate::{ util::{ - arithmetic::{fe_from_bytes_le, Coordinates, CurveAffine, PrimeField}, + arithmetic::{fe_mod_from_le_bytes, Coordinates, CurveAffine, PrimeField}, hash::{Hash, Keccak256, Output, Update}, + Itertools, }, Error, }; use halo2_curves::{bn256, grumpkin, pasta}; -use std::io::{self, Cursor}; +use std::{ + fmt::Debug, + io::{self, Cursor}, +}; pub trait FieldTranscript { fn squeeze_challenge(&mut self) -> F; @@ -16,6 +20,12 @@ pub trait FieldTranscript { } fn common_field_element(&mut self, fe: &F) -> Result<(), Error>; + + fn common_field_elements(&mut self, fes: &[F]) -> Result<(), Error> { + fes.iter() + .map(|fe| self.common_field_element(fe)) + .try_collect() + } } pub trait FieldTranscriptRead: FieldTranscript { @@ -45,6 +55,13 @@ pub trait FieldTranscriptWrite: FieldTranscript { pub trait Transcript: FieldTranscript { fn common_commitment(&mut self, comm: &C) -> Result<(), Error>; + + fn common_commitments(&mut self, comms: &[C]) -> Result<(), Error> { + comms + .iter() + .map(|comm| self.common_commitment(comm)) + .try_collect() + } } pub trait TranscriptRead: Transcript + FieldTranscriptRead { @@ -69,10 +86,14 @@ pub trait TranscriptWrite: Transcript + FieldTranscriptWrite { } } -pub trait InMemoryTranscript: Default { +pub trait InMemoryTranscript { + type Param: Clone + Debug; + + fn new(param: Self::Param) -> Self; + fn into_proof(self) -> Vec; - fn from_proof(proof: &[u8]) -> Self; + fn from_proof(param: Self::Param, proof: &[u8]) -> Self; } pub type Keccak256Transcript = FiatShamirTranscript; @@ -84,11 +105,17 @@ pub struct FiatShamirTranscript { } impl InMemoryTranscript for FiatShamirTranscript>> { + type Param = (); + + fn new(_: Self::Param) -> Self { + Self::default() + } + fn into_proof(self) -> Vec { self.stream.into_inner() } - fn from_proof(proof: &[u8]) -> Self { + fn from_proof(_: Self::Param, proof: &[u8]) -> Self { Self { state: H::default(), stream: Cursor::new(proof.to_vec()), @@ -98,9 +125,9 @@ impl InMemoryTranscript for FiatShamirTranscript>> { impl FieldTranscript for FiatShamirTranscript { fn squeeze_challenge(&mut self) -> F { - let challenge = self.state.finalize_fixed_reset(); - self.state.update(&challenge); - fe_from_bytes_le(&challenge) + let hash = self.state.finalize_fixed_reset(); + self.state.update(&hash); + fe_mod_from_le_bytes(hash) } fn common_field_element(&mut self, fe: &F) -> Result<(), Error> {