diff --git a/halo2_alt/src/backend.rs b/halo2_alt/src/backend.rs index b2e25b1b02..5887745b7c 100644 --- a/halo2_alt/src/backend.rs +++ b/halo2_alt/src/backend.rs @@ -1,3 +1,5 @@ +//! Plonkish proof systems implementations. + use crate::protocol::Protocol; use halo2_proofs::plonk::Error; use rand_core::RngCore; @@ -5,13 +7,18 @@ use std::fmt::Debug; pub mod fflonk; +/// `Circuit` is an abstraction to make proof systems easier to handle. pub trait Circuit: Debug { + /// A arbitrary buffer for multi-phase circuit to be stateful when witnessing. type WitnessBuf: Debug + Default; + /// Returns `Protocol` of the circuit. fn protocol(&self) -> Protocol; + /// Returns values of preprocessed polynomials. fn preprocess(&self) -> Result>, Error>; + /// Returns values of advice polynomials of given `phase`. fn witness( &self, phase: usize, diff --git a/halo2_alt/src/backend/fflonk.rs b/halo2_alt/src/backend/fflonk.rs index b8c966f418..9d791f579f 100644 --- a/halo2_alt/src/backend/fflonk.rs +++ b/halo2_alt/src/backend/fflonk.rs @@ -1,3 +1,7 @@ +//! Implementation of [fflonk] proof system. +//! +//! [fflonk]: https://eprint.iacr.org/2021/1167 + use crate::{ protocol::Protocol, util::{chain, div_ceil, evaluator::Evaluator, izip, lcm, root_of_unity}, @@ -19,13 +23,17 @@ mod keygen; mod prover; mod verifier; +#[cfg(test)] +mod test; + pub use circuit::FflonkCircuit; pub use keygen::{keygen_pk, keygen_vk}; pub use prover::create_proof; pub use verifier::verify_proof; +/// Query information of a phase. #[derive(Clone, Debug)] -struct QueryInfo { +pub struct QueryInfo { num_polys: usize, t: usize, omega_t: F, @@ -65,8 +73,9 @@ impl QueryInfo { } } +/// Query information and constraint indices of a phase. #[derive(Clone, Debug)] -struct PhaseInfo { +pub struct PhaseInfo { query_info: QueryInfo, constraints: Vec, } @@ -88,6 +97,7 @@ impl PhaseInfo { } } +/// Verifying key of fflonk. #[derive(Debug)] pub struct VerifyingKey { domain: EvaluationDomain, @@ -109,7 +119,7 @@ impl VerifyingKey { C::Scalar: FromUniformBytes<64>, { let mut vk = Self { - domain: EvaluationDomain::new(protocol.degree() as u32, protocol.k as u32), + domain: protocol.domain(), protocol, preprocessed_query_info, preprocessed_commitment, @@ -134,7 +144,37 @@ impl VerifyingKey { vk } - pub fn hash_into, T: Transcript>( + /// Returns `EvaluationDomain`. + pub fn domain(&self) -> &EvaluationDomain { + &self.domain + } + + /// Returns `Protocol`. + pub fn protocol(&self) -> &Protocol { + &self.protocol + } + + /// Returns preprocessed commitment. + pub fn preprocessed_commitment(&self) -> &C { + &self.preprocessed_commitment + } + + /// Returns preprocessed `QueryInfo`. + pub fn preprocessed_query_info(&self) -> &QueryInfo { + &self.preprocessed_query_info + } + + /// Returns `PhaseInfo` of phases. + pub fn phase_infos(&self) -> &[PhaseInfo] { + &self.phase_infos + } + + /// Returns transcript representation of this `VerifyingKey`. + pub fn transcript_repr(&self) -> &C::Scalar { + &self.transcript_repr + } + + fn hash_into, T: Transcript>( &self, transcript: &mut T, ) -> io::Result<()> { @@ -155,6 +195,14 @@ impl VerifyingKey { .unwrap() } + fn max_combined_poly_size(&self) -> usize { + max_combined_poly_size( + &self.protocol, + &self.preprocessed_query_info, + &self.phase_infos, + ) + } + fn pinned(&self) -> PinnedVerificationKey { PinnedVerificationKey { base_modulus: C::Base::MODULUS, @@ -168,6 +216,7 @@ impl VerifyingKey { } } +/// Proving key of fflonk. #[derive(Debug)] pub struct ProvingKey { vk: VerifyingKey, @@ -178,9 +227,16 @@ pub struct ProvingKey { evaluators: Vec>>, } +impl ProvingKey { + /// Returns verifying key. + pub fn vk(&self) -> &VerifyingKey { + &self.vk + } +} + #[allow(dead_code)] #[derive(Debug)] -pub struct PinnedVerificationKey<'a, C: CurveAffine> { +struct PinnedVerificationKey<'a, C: CurveAffine> { base_modulus: &'static str, scalar_modulus: &'static str, domain: PinnedEvaluationDomain<'a, C::Scalar>, @@ -190,6 +246,28 @@ pub struct PinnedVerificationKey<'a, C: CurveAffine> { phase_infos: &'a [PhaseInfo], } +fn max_combined_poly_size( + protocol: &Protocol, + preprocessed_query_info: &QueryInfo, + phase_infos: &[PhaseInfo], +) -> usize { + let max_combined_degree = chain![ + [preprocessed_query_info.t], + phase_infos.iter().map(|phase_info| { + let max_degree = phase_info + .constraints + .iter() + .map(|idx| protocol.constraints()[*idx].degree().saturating_sub(1)) + .max() + .unwrap_or(1); + phase_info.t * max_degree + }) + ] + .max() + .unwrap(); + max_combined_degree << protocol.k() +} + fn combine_polys<'a, F: Field>( t: usize, polys: impl IntoIterator>, @@ -202,11 +280,17 @@ fn combine_polys<'a, F: Field>( .max() .unwrap_or_default() * t; - let mut combined = Polynomial::new(vec![F::ZERO; size]); - izip!(0.., polys).for_each(|(idx, poly)| { - izip!(combined[idx..].iter_mut().step_by(t), &poly[..]).for_each(|(lhs, rhs)| *lhs = *rhs) - }); - combined + let combined = (0..size) + .into_par_iter() + .map(|idx| { + polys + .get(idx % t) + .and_then(|poly| poly.get(idx / t)) + .copied() + .unwrap_or(F::ZERO) + }) + .collect(); + Polynomial::new(combined) } fn eval_combined_polynomial(t: usize, num_evals: usize, poly: &[F], point: F) -> Vec { @@ -234,176 +318,3 @@ fn eval_combined_polynomial(t: usize, num_evals: usize, poly: &[F], po }) .unwrap_or_default() } - -#[cfg(test)] -mod test { - use crate::backend::fflonk::{ - circuit::FflonkCircuit, - keygen::{keygen_pk, keygen_vk}, - prover::create_proof, - verifier::verify_proof, - }; - use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - halo2curves::{ - bn256::{Bn256, Fr}, - ff::Field, - }, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed}, - poly::{ - kzg::{ - commitment::{KZGCommitmentScheme, ParamsKZG}, - multiopen::{ProverSHPLONK, VerifierSHPLONK}, - strategy::SingleStrategy, - }, - Rotation, - }, - transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, - }, - }; - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - - #[test] - fn vanilla_plonk_with_lookup() { - #[derive(Clone, Debug)] - pub struct SampleConfig { - selectors: [Column; 7], - wires: [Column; 4], - } - - impl SampleConfig { - fn configure(meta: &mut ConstraintSystem) -> Self { - let pi = meta.instance_column(); - let [q_l, q_r, q_m, q_o, q_c, q_lookup, t] = [(); 7].map(|_| meta.fixed_column()); - let [w_l, w_r, w_o, w_lookup] = [(); 4].map(|_| meta.advice_column()); - [w_l, w_r, w_o, w_lookup].map(|column| meta.enable_equality(column)); - meta.create_gate( - "q_l·w_l + q_r·w_r + q_m·w_l·w_r + q_o·w_o + q_c + pi = 0", - |meta| { - let [q_l, q_r, q_o, q_m, q_c] = [q_l, q_r, q_o, q_m, q_c] - .map(|column| meta.query_fixed(column, Rotation::cur())); - let [w_l, w_r, w_o] = [w_l, w_r, w_o] - .map(|column| meta.query_advice(column, Rotation::cur())); - let pi = meta.query_instance(pi, Rotation::cur()); - Some( - q_l * w_l.clone() - + q_r * w_r.clone() - + q_m * w_l * w_r - + q_o * w_o - + q_c - + pi, - ) - }, - ); - meta.lookup_any("(q_lookup * w_lookup) in (t)", |meta| { - let [q_lookup, t] = - [q_lookup, t].map(|column| meta.query_fixed(column, Rotation::cur())); - let w_lookup = meta.query_advice(w_lookup, Rotation::cur()); - vec![(q_lookup * w_lookup, t)] - }); - SampleConfig { - selectors: [q_l, q_r, q_m, q_o, q_c, q_lookup, t], - wires: [w_l, w_r, w_o, w_lookup], - } - } - } - - #[derive(Clone, Debug, Default)] - pub struct Sample(Vec); - - impl Circuit for Sample { - type Config = SampleConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - unimplemented!() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - meta.set_minimum_degree(6); - SampleConfig::configure(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - layouter.assign_region( - || "", - |mut region| { - let one = Value::known(F::ONE); - for (row, value) in self.0.iter().enumerate() { - let minus_value = Value::known(-*value); - region.assign_fixed(|| "", config.selectors[0], row, || one)?; - region.assign_advice(|| "", config.wires[0], row, || minus_value)?; - } - let offset = self.0.len(); - let minus_four = Value::known(-F::ONE.double().double()); - for selector in &config.selectors { - region.assign_fixed(|| "", *selector, offset, || one)?; - } - let a = region.assign_advice(|| "", config.wires[0], offset, || one)?; - let b = region.assign_advice(|| "", config.wires[1], offset, || one)?; - let c = region.assign_advice(|| "", config.wires[2], offset + 1, || one)?; - let d = region.assign_advice(|| "", config.wires[3], offset, || one)?; - region.constrain_equal(a.cell(), b.cell())?; - region.constrain_equal(b.cell(), c.cell())?; - region.constrain_equal(c.cell(), d.cell())?; - region.assign_advice(|| "", config.wires[2], offset, || minus_four)?; - Ok(()) - }, - ) - } - } - - let mut rng = ChaCha20Rng::seed_from_u64(0); - let instances = vec![Fr::random(&mut rng), Fr::random(&mut rng)]; - let circuit = FflonkCircuit::new(4, Sample(instances.clone())); - - let params = ParamsKZG::::setup(circuit.min_params_k() as u32, &mut rng); - let pk = keygen_pk::, _>(¶ms, &circuit).unwrap(); - let vk = keygen_vk::, _>(¶ms, &circuit).unwrap(); - assert_eq!(pk.vk.transcript_repr, vk.transcript_repr); - - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - create_proof::, ProverSHPLONK<_>, _, _>( - ¶ms, - &pk, - &circuit, - &[&instances], - &mut rng, - &mut transcript, - ) - .unwrap(); - let proof = transcript.finalize(); - - let mut transcript = Blake2bRead::init(proof.as_slice()); - verify_proof::, VerifierSHPLONK<_>, _, _>( - ¶ms, - &vk, - &[&instances], - SingleStrategy::new(¶ms), - &mut transcript, - ) - .unwrap(); - - assert_eq!(proof.len(), { - let num_commitments = vk.phase_infos.len() + 2; - let num_evals = vk.preprocessed_query_info.rotations.len() - * vk.preprocessed_query_info.num_polys - + vk.phase_infos - .iter() - .map(|phase_info| { - phase_info.rotations.len() * phase_info.num_polys - - phase_info.constraints.len() - }) - .sum::(); - num_commitments * 32 + num_evals * 32 - }); - } -} diff --git a/halo2_alt/src/backend/fflonk/circuit.rs b/halo2_alt/src/backend/fflonk/circuit.rs index bd6f2f3bac..81a2e51db8 100644 --- a/halo2_alt/src/backend/fflonk/circuit.rs +++ b/halo2_alt/src/backend/fflonk/circuit.rs @@ -1,5 +1,8 @@ use crate::{ - backend::{fflonk::keygen::query_infos, Circuit}, + backend::{ + fflonk::{keygen::query_infos, max_combined_poly_size}, + Circuit, + }, protocol::{Expression, PolynomialRef, Protocol}, util::{ chain, div_ceil, felt_from_bool, @@ -26,6 +29,7 @@ use std::{ ops::Range, }; +/// Wrapper for `halo2_proofs::plonk::Circuit` to fit `crate::backend::Circuit` abstraction. #[derive(Clone, Debug)] pub struct FflonkCircuit where @@ -50,6 +54,7 @@ where C: Debug + plonk::Circuit, C::Config: Debug, { + /// Wraps `halo2_proofs::plonk::Circuit` to fit `crate::backend::Circuit` abstraction. pub fn new(k: usize, circuit: C) -> Self { let mut cs = ConstraintSystem::default(); let circuit_config = C::configure(&mut cs); @@ -154,13 +159,13 @@ where } }; - let protocol = Protocol { + let protocol = Protocol::new( k, num_preprocessed_polys, num_instance_polys, phases, constraints, - }; + ); Self { circuit, @@ -172,23 +177,12 @@ where } } + /// Returns minimum required `k` of public parameters. pub fn min_params_k(&self) -> usize { let (preprocessed_query_info, phase_infos) = query_infos(&self.protocol); - let max_combined_degree = chain![ - [preprocessed_query_info.t], - phase_infos.iter().map(|phase_info| { - let max_degree = phase_info - .constraints - .iter() - .map(|idx| self.protocol.constraints[*idx].degree().saturating_sub(1)) - .max() - .unwrap_or(1); - phase_info.t * max_degree - }) - ] - .max() - .unwrap(); - self.protocol.k + log2_ceil(max_combined_degree) + let max_combined_poly_size = + max_combined_poly_size(&self.protocol, &preprocessed_query_info, &phase_infos); + log2_ceil(max_combined_poly_size) } } @@ -222,7 +216,7 @@ where fn preprocess(&self) -> Result>, Error> { let n = self.protocol.n(); let mut collector = PreprocessCollector { - k: self.protocol.k as u32, + k: self.protocol.k() as u32, fixeds: vec![vec![F::ZERO.into(); n]; self.cs.num_fixed_columns()], permutation: Permutation::new(self.cs.permutation().get_columns()), selectors: vec![vec![false; n]; self.cs.num_selectors()], @@ -264,7 +258,7 @@ where let mut values = match phase { 0 => { let mut collector = WitnessCollector { - k: self.protocol.k as u32, + k: self.protocol.k() as u32, phase: phase as u8, instance_values: &values[self.protocol.poly_range().instance], advices: vec![vec![F::ZERO.into(); n]; self.cs.num_advice_columns()], diff --git a/halo2_alt/src/backend/fflonk/keygen.rs b/halo2_alt/src/backend/fflonk/keygen.rs index 43b79008b5..45a64cb9f8 100644 --- a/halo2_alt/src/backend/fflonk/keygen.rs +++ b/halo2_alt/src/backend/fflonk/keygen.rs @@ -14,7 +14,7 @@ use halo2_proofs::{ plonk::Error, poly::{ commitment::{Blind, CommitmentScheme, ParamsProver}, - EvaluationDomain, ExtendedLagrangeCoeff, Polynomial, + ExtendedLagrangeCoeff, Polynomial, }, }; use rayon::prelude::*; @@ -25,6 +25,7 @@ use std::{ iter, }; +/// Generate fflonk verifying key. pub fn keygen_vk( params: &S::ParamsProver, circuit: &C, @@ -34,14 +35,13 @@ where C: Circuit, { let protocol = circuit.protocol(); - protocol.assert_valid(); let (preprocessed_query_info, phase_infos) = query_infos(&protocol); let preprocessed_commitment = { - let domain = EvaluationDomain::new(protocol.degree() as u32, protocol.k as u32); + let domain = protocol.domain(); let preprocessed_values = circuit.preprocess()?; - if preprocessed_values.len() != protocol.num_preprocessed_polys { + if preprocessed_values.len() != protocol.num_preprocessed_polys() { return Err(Error::Synthesis); } @@ -61,6 +61,7 @@ where )) } +/// Generate fflonk proving key. pub fn keygen_pk( params: &S::ParamsProver, circuit: &C, @@ -70,14 +71,13 @@ where C: Circuit, { let protocol = circuit.protocol(); - protocol.assert_valid(); let (preprocessed_query_info, phase_infos) = query_infos(&protocol); - let domain = EvaluationDomain::new(protocol.degree() as u32, protocol.k as u32); + let domain = protocol.domain(); let preprocessed_values = circuit.preprocess()?; - assert_eq!(preprocessed_values.len(), protocol.num_preprocessed_polys); + assert_eq!(preprocessed_values.len(), protocol.num_preprocessed_polys()); let preprocessed_polys = preprocessed_values .iter() @@ -122,7 +122,7 @@ pub(super) fn query_infos( .flat_map(|(range, phase)| izip!(range, iter::repeat(phase))) .collect::>>(); let rotations = protocol - .constraints + .constraints() .iter() .flat_map(Expression::used_query) .fold( @@ -139,12 +139,12 @@ pub(super) fn query_infos( }, ); let num_polys = chain![ - [protocol.num_preprocessed_polys], - izip!(&protocol.phases, &constraints_by_earliest_phase) + [protocol.num_preprocessed_polys()], + izip!(protocol.phases(), &constraints_by_earliest_phase) .map(|((num_advice_polys, _), constraints)| *num_advice_polys + constraints.len()), ]; let mut query_infos = izip!(num_polys, rotations) - .map(|(num_polys, rotations)| QueryInfo::new(protocol.k, num_polys, rotations)) + .map(|(num_polys, rotations)| QueryInfo::new(protocol.k(), num_polys, rotations)) .collect::>() .into_iter(); let preprocessed_query_info = query_infos.next().unwrap(); @@ -166,7 +166,7 @@ fn constraints_by_earliest_phase(protocol: &Protocol) -> Vec>(); - protocol.constraints.iter().enumerate().fold( + protocol.constraints().iter().enumerate().fold( vec![vec![]; protocol.num_phases()], |mut constraints_by_earliest_phase, (idx, constraint)| { let phase = constraint.evaluate( @@ -232,14 +232,14 @@ fn evaluators + Hash>( phase_info .constraints .iter() - .map(|idx| Evaluator::new(protocol.k, &replace(&protocol.constraints[*idx]))) + .map(|idx| Evaluator::new(protocol.k(), &replace(&protocol.constraints()[*idx]))) .collect() }) .collect(); let transparent_cosets = { - let domain = EvaluationDomain::new(protocol.degree() as u32, protocol.k as u32); - let n = 1 << protocol.k; + let domain = protocol.domain(); + let n = protocol.n() as i32; let mut transparents = transparents.take().into_iter().collect::>(); transparents.sort_by(|(_, a), (_, b)| a.cmp(b)); diff --git a/halo2_alt/src/backend/fflonk/prover.rs b/halo2_alt/src/backend/fflonk/prover.rs index 68bd54b8b5..3507589c91 100644 --- a/halo2_alt/src/backend/fflonk/prover.rs +++ b/halo2_alt/src/backend/fflonk/prover.rs @@ -21,6 +21,7 @@ use halo2_proofs::{ use rand_core::RngCore; use std::iter; +/// Create a fflonk proof. pub fn create_proof<'params, S, P, E, C>( params: &'params S::ParamsProver, pk: &ProvingKey, @@ -38,7 +39,7 @@ where { let vk = &pk.vk; let domain = &vk.domain; - if instances.len() != vk.protocol.num_instance_polys { + if instances.len() != vk.protocol.num_instance_polys() { return Err(Error::InvalidInstances); } @@ -70,7 +71,7 @@ where let mut challenges = vec![]; let mut buf = C::WitnessBuf::default(); for (phase, (num_advice_polys, num_challegnes), phase_info, evaluators) in - izip!(0.., &vk.protocol.phases, &vk.phase_infos, &pk.evaluators) + izip!(0.., vk.protocol.phases(), &vk.phase_infos, &pk.evaluators) { let values = { let values = chain![&pk.preprocessed_values, &instance_values, &advice_values] @@ -155,12 +156,12 @@ where // NOTE: Because `Polynomial` assumes all values have same length, we need to pad here. let combineds = { - let max_size = combineds.iter().map(|(poly, _)| poly.len()).max().unwrap(); + let size = vk.max_combined_poly_size(); combineds .into_iter() .map(|(poly, blind)| { let mut poly = poly.into_vec(); - poly.resize(max_size, S::Scalar::ZERO); + poly.resize(size, S::Scalar::ZERO); (Polynomial::new(poly), blind) }) .collect::>() diff --git a/halo2_alt/src/backend/fflonk/test.rs b/halo2_alt/src/backend/fflonk/test.rs new file mode 100644 index 0000000000..be1cd86706 --- /dev/null +++ b/halo2_alt/src/backend/fflonk/test.rs @@ -0,0 +1,168 @@ +use crate::backend::fflonk::{ + circuit::FflonkCircuit, + keygen::{keygen_pk, keygen_vk}, + prover::create_proof, + verifier::verify_proof, +}; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + halo2curves::{ + bn256::{Bn256, Fr}, + ff::Field, + }, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed}, + poly::{ + kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::{ProverSHPLONK, VerifierSHPLONK}, + strategy::SingleStrategy, + }, + Rotation, + }, + transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, + }, +}; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +#[test] +fn vanilla_plonk_with_lookup() { + #[derive(Clone, Debug)] + pub struct SampleConfig { + selectors: [Column; 7], + wires: [Column; 4], + } + + impl SampleConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let pi = meta.instance_column(); + let [q_l, q_r, q_m, q_o, q_c, q_lookup, t] = [(); 7].map(|_| meta.fixed_column()); + let [w_l, w_r, w_o, w_lookup] = [(); 4].map(|_| meta.advice_column()); + [w_l, w_r, w_o, w_lookup].map(|column| meta.enable_equality(column)); + meta.create_gate( + "q_l·w_l + q_r·w_r + q_m·w_l·w_r + q_o·w_o + q_c + pi = 0", + |meta| { + let [q_l, q_r, q_o, q_m, q_c] = [q_l, q_r, q_o, q_m, q_c] + .map(|column| meta.query_fixed(column, Rotation::cur())); + let [w_l, w_r, w_o] = + [w_l, w_r, w_o].map(|column| meta.query_advice(column, Rotation::cur())); + let pi = meta.query_instance(pi, Rotation::cur()); + Some( + q_l * w_l.clone() + + q_r * w_r.clone() + + q_m * w_l * w_r + + q_o * w_o + + q_c + + pi, + ) + }, + ); + meta.lookup_any("(q_lookup * w_lookup) in (t)", |meta| { + let [q_lookup, t] = + [q_lookup, t].map(|column| meta.query_fixed(column, Rotation::cur())); + let w_lookup = meta.query_advice(w_lookup, Rotation::cur()); + vec![(q_lookup * w_lookup, t)] + }); + SampleConfig { + selectors: [q_l, q_r, q_m, q_o, q_c, q_lookup, t], + wires: [w_l, w_r, w_o, w_lookup], + } + } + } + + #[derive(Clone, Debug, Default)] + pub struct Sample(Vec); + + impl Circuit for Sample { + type Config = SampleConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + unimplemented!() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + meta.set_minimum_degree(6); + SampleConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + let one = Value::known(F::ONE); + for (row, value) in self.0.iter().enumerate() { + let minus_value = Value::known(-*value); + region.assign_fixed(|| "", config.selectors[0], row, || one)?; + region.assign_advice(|| "", config.wires[0], row, || minus_value)?; + } + let offset = self.0.len(); + let minus_four = Value::known(-F::ONE.double().double()); + for selector in &config.selectors { + region.assign_fixed(|| "", *selector, offset, || one)?; + } + let a = region.assign_advice(|| "", config.wires[0], offset, || one)?; + let b = region.assign_advice(|| "", config.wires[1], offset, || one)?; + let c = region.assign_advice(|| "", config.wires[2], offset + 1, || one)?; + let d = region.assign_advice(|| "", config.wires[3], offset, || one)?; + region.constrain_equal(a.cell(), b.cell())?; + region.constrain_equal(b.cell(), c.cell())?; + region.constrain_equal(c.cell(), d.cell())?; + region.assign_advice(|| "", config.wires[2], offset, || minus_four)?; + Ok(()) + }, + ) + } + } + + let mut rng = ChaCha20Rng::seed_from_u64(0); + let instances = vec![Fr::random(&mut rng), Fr::random(&mut rng)]; + let circuit = FflonkCircuit::new(4, Sample(instances.clone())); + + let params = ParamsKZG::::setup(circuit.min_params_k() as u32, &mut rng); + let pk = keygen_pk::, _>(¶ms, &circuit).unwrap(); + let vk = keygen_vk::, _>(¶ms, &circuit).unwrap(); + assert_eq!(pk.vk.transcript_repr, vk.transcript_repr); + + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof::, ProverSHPLONK<_>, _, _>( + ¶ms, + &pk, + &circuit, + &[&instances], + &mut rng, + &mut transcript, + ) + .unwrap(); + let proof = transcript.finalize(); + + let mut transcript = Blake2bRead::init(proof.as_slice()); + verify_proof::, VerifierSHPLONK<_>, _, _>( + ¶ms, + &vk, + &[&instances], + SingleStrategy::new(¶ms), + &mut transcript, + ) + .unwrap(); + + assert_eq!(proof.len(), { + let num_commitments = vk.phase_infos.len() + 2; + let num_evals = vk.preprocessed_query_info.rotations.len() + * vk.preprocessed_query_info.num_polys + + vk.phase_infos + .iter() + .map(|phase_info| { + phase_info.rotations.len() * phase_info.num_polys - phase_info.constraints.len() + }) + .sum::(); + num_commitments * 32 + num_evals * 32 + }); +} diff --git a/halo2_alt/src/backend/fflonk/verifier.rs b/halo2_alt/src/backend/fflonk/verifier.rs index e720ac7c9e..5539ca3822 100644 --- a/halo2_alt/src/backend/fflonk/verifier.rs +++ b/halo2_alt/src/backend/fflonk/verifier.rs @@ -18,6 +18,7 @@ use halo2_proofs::{ }; use std::collections::{BTreeSet, HashMap}; +/// Verify a fflonk proof. pub fn verify_proof<'params, S, V, E, ST>( params: &'params S::ParamsVerifier, vk: &VerifyingKey, @@ -32,7 +33,7 @@ where E: EncodedChallenge, ST: VerificationStrategy<'params, S, V>, { - if instances.len() != vk.protocol.num_instance_polys { + if instances.len() != vk.protocol.num_instance_polys() { return Err(Error::InvalidInstances); } @@ -46,7 +47,7 @@ where let mut combined_commitments = vec![vk.preprocessed_commitment]; let mut challenges = vec![]; - for (_, num_challenges) in &vk.protocol.phases { + for (_, num_challenges) in vk.protocol.phases() { combined_commitments.push(transcript.read_point()?); for _ in 0..*num_challenges { @@ -117,12 +118,12 @@ fn quotient_evals( let domain = &vk.domain; let poly_range = vk.protocol.poly_range(); - let x_to_n = squares(x).nth(vk.protocol.k).unwrap(); + let x_to_n = squares(x).nth(vk.protocol.k()).unwrap(); let (lagrange_evals, instance_evals) = { let instances = izip!(poly_range.instance.clone(), instances).collect::>(); let instance_queries = vk .protocol - .constraints + .constraints() .iter() .flat_map(Expression::used_query) .filter(|query| poly_range.instance.contains(&query.index)) @@ -145,7 +146,7 @@ fn quotient_evals( let i_s = chain![ max_rotation..max_instance_len as i32 + min_rotation.abs(), vk.protocol - .constraints + .constraints() .iter() .flat_map(Expression::used_langrange), ] @@ -186,7 +187,7 @@ fn quotient_evals( .constraints .iter() .map(|idx| { - vk.protocol.constraints[*idx].evaluate_felt(&|poly| match poly { + vk.protocol.constraints()[*idx].evaluate_felt(&|poly| match poly { Constant(constant) => constant, Challenge(idx) => challenges[idx], Identity => x, diff --git a/halo2_alt/src/lib.rs b/halo2_alt/src/lib.rs index 20a098d4f3..eff6432e92 100644 --- a/halo2_alt/src/lib.rs +++ b/halo2_alt/src/lib.rs @@ -1,3 +1,10 @@ +//! Alternative Plonkish proof systems for `halo2_proofs` circuits. + +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![deny(unsafe_code)] + pub mod backend; pub mod protocol; mod util; diff --git a/halo2_alt/src/protocol.rs b/halo2_alt/src/protocol.rs index 67a15c6beb..bd9809face 100644 --- a/halo2_alt/src/protocol.rs +++ b/halo2_alt/src/protocol.rs @@ -1,53 +1,109 @@ +//! Shared structures for Plonkish proof systems. + +use halo2_proofs::{halo2curves::ff::WithSmallOrderMulGroup, poly::EvaluationDomain}; + use crate::util::chain; use std::{fmt::Debug, ops::Range}; mod expression; -pub(crate) use crate::protocol::expression::{Expression, PolynomialRef, Query}; +pub use crate::protocol::expression::{Expression, PolynomialRef, Query}; +/// `Protocol` holds minimal information of a Plonkish proof system. #[derive(Clone, Debug)] pub struct Protocol { - pub k: usize, - pub num_preprocessed_polys: usize, - pub num_instance_polys: usize, - pub phases: Vec<(usize, usize)>, - pub constraints: Vec>, + k: usize, + num_preprocessed_polys: usize, + num_instance_polys: usize, + phases: Vec<(usize, usize)>, + constraints: Vec>, } impl Protocol { - pub(crate) fn assert_valid(&self) + /// Returns `Protocol` from parts. + /// + /// # Panics + /// + /// It panics if given `constraints` contain any `PolynomialRef` out of bound. + pub fn new( + k: usize, + num_preprocessed_polys: usize, + num_instance_polys: usize, + phases: Vec<(usize, usize)>, + constraints: Vec>, + ) -> Self where F: Clone, { - let num_challenges = self.num_challenges(); - let num_opaque_polys = self.num_opaque_polys(); - for constraint in self.constraints.iter() { - constraint.evaluate( - &|inner| match inner { - PolynomialRef::Challenge(idx) => assert!(idx < num_challenges), - PolynomialRef::Opaque(query) => assert!(query.index < num_opaque_polys), - _ => (), - }, - &|_| (), - &|_, _| (), - &|_, _| (), - ); - } + let protocol = Self { + k, + num_preprocessed_polys, + num_instance_polys, + phases, + constraints, + }; + protocol.assert_valid(); + protocol + } + + /// Returns log2 size of polynomials. + pub fn k(&self) -> usize { + self.k + } + + /// Returns size of polynomials. + pub fn n(&self) -> usize { + 1 << self.k + } + + /// Returns interaction phases containing `(num_advice_polys, num_challenges)` in each phase, + /// where the former is number of advice polynomials sent from prover to verifier, the latter + /// is number of challenges sent back from verifier to prover. + pub fn phases(&self) -> &[(usize, usize)] { + &self.phases + } + + /// Returns constraints that need to be satisfied. + pub fn constraints(&self) -> &[Expression] { + &self.constraints + } + + /// Returns max degree among all constraints. + pub fn max_constraint_degree(&self) -> usize { + self.constraints + .iter() + .map(Expression::degree) + .max() + .unwrap_or_default() + } + + /// Returns number of preprocessed polynomials. + pub fn num_preprocessed_polys(&self) -> usize { + self.num_preprocessed_polys } - pub(crate) fn num_phases(&self) -> usize { + /// Returns number of instance polynomials. + pub fn num_instance_polys(&self) -> usize { + self.num_instance_polys + } + + /// Returns number of interaction phases. + pub fn num_phases(&self) -> usize { self.phases.len() } - pub(crate) fn num_advice_polys(&self) -> usize { + /// Returns number of advice polynomials in all phases. + pub fn num_advice_polys(&self) -> usize { self.phases.iter().map(|(n, _)| n).sum() } - pub(crate) fn num_challenges(&self) -> usize { + /// Returns number of challenges in all phases. + pub fn num_challenges(&self) -> usize { self.phases.iter().map(|(_, n)| n).sum() } - pub(crate) fn num_opaque_polys(&self) -> usize { + /// Returns number of all polynomials. + pub fn num_opaque_polys(&self) -> usize { self.num_preprocessed_polys + self.num_instance_polys + self.num_advice_polys() } @@ -64,16 +120,30 @@ impl Protocol { } } - pub fn n(&self) -> usize { - 1 << self.k + fn assert_valid(&self) + where + F: Clone, + { + let num_challenges = self.num_challenges(); + let num_opaque_polys = self.num_opaque_polys(); + for constraint in self.constraints.iter() { + constraint.evaluate( + &|inner| match inner { + PolynomialRef::Challenge(idx) => assert!(idx < num_challenges), + PolynomialRef::Opaque(query) => assert!(query.index < num_opaque_polys), + _ => (), + }, + &|_| (), + &|_, _| (), + &|_, _| (), + ); + } } +} - pub fn degree(&self) -> usize { - self.constraints - .iter() - .map(Expression::degree) - .max() - .unwrap_or_default() +impl> Protocol { + pub(crate) fn domain(&self) -> EvaluationDomain { + EvaluationDomain::new(self.max_constraint_degree() as u32, self.k() as u32) } } diff --git a/halo2_alt/src/protocol/expression.rs b/halo2_alt/src/protocol/expression.rs index 847f391121..8b6aded6cf 100644 --- a/halo2_alt/src/protocol/expression.rs +++ b/halo2_alt/src/protocol/expression.rs @@ -8,18 +8,39 @@ use std::{ ops::{Add, Mul, Neg, Sub}, }; +/// Query to a opaque polynomial. #[derive(Clone, Copy, Debug, Eq)] pub struct Query { - pub index: usize, - pub rotation: Rotation, + pub(crate) index: usize, + pub(crate) rotation: Rotation, +} + +impl Query { + /// Returns `Query`. + pub fn new(index: usize, rotation: Rotation) -> Self { + Self { index, rotation } + } + + /// Returns index of opaque polynomial. + pub fn index(&self) -> usize { + self.index + } + + /// Returns rotation of query. + pub fn rotation(&self) -> Rotation { + self.rotation + } } impl From<(usize, i32)> for Query { fn from((index, rotation): (usize, i32)) -> Self { - Self { - index, - rotation: Rotation(rotation), - } + Self::new(index, Rotation(rotation)) + } +} + +impl From<(usize, Rotation)> for Query { + fn from((index, rotation): (usize, Rotation)) -> Self { + Self::new(index, rotation) } } @@ -48,20 +69,29 @@ impl Hash for Query { } } +/// `PolynomialRef` represents different kinds of polynomials that might be used in a Plonkish +/// proof system. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum PolynomialRef { - Constant(F), // f(X) = c - Challenge(usize), // f(X) = challenges[idx] - Identity, // f(X) = X - Lagrange(i32), // f(X) = 1 when X == omega^i otherwise 0 for X in omega^(0..n) - Opaque(Query), // f(X) + /// `f(X) = c` + Constant(F), + /// `f(X) = challenges[idx]` + Challenge(usize), + /// `f(X) = X` + Identity, + /// `f(X) = 1 if X == omega^i else 0 for X in omega^(0..n)` + Lagrange(i32), + /// `f(X)` without specific structure + Opaque(Query), } impl PolynomialRef { + /// Returns `PolynomialRef::Opaque(query)`. pub fn opaque(query: impl Into) -> Self { Self::Opaque(query.into()) } + /// Returns degree of the referenced polynomial. pub fn degree(&self) -> usize { match self { Self::Constant(_) | Self::Challenge(_) => 0, @@ -69,6 +99,7 @@ impl PolynomialRef { } } + /// Evaluate `PolynomialRef` using the provided closures to perform the operations. pub fn evaluate( &self, constant: &impl Fn(F) -> T, @@ -90,11 +121,16 @@ impl PolynomialRef { } } +/// Arithmetic expression of `PolynomialRef`. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Expression { + /// It holds a `PolynomialRef`. Polynomial(PolynomialRef), + /// Negated `Expression`. Neg(Box), + /// Sum of two `Expression`. Sum(Box, Box), + /// Product of two `Expression`. Product(Box, Box), } @@ -105,6 +141,7 @@ impl From> for Expression { } impl Expression { + /// Returns degree of the `Expression`. pub fn degree(&self) -> usize { match self { Self::Polynomial(inner) => inner.degree(), @@ -116,6 +153,7 @@ impl Expression { } impl Expression { + /// Evaluate `Expression` using the provided closures to perform the operations. pub fn evaluate( &self, poly: &impl Fn(PolynomialRef) -> T, @@ -132,6 +170,7 @@ impl Expression { } } + /// Evaluate `Expression` using the provided closures which return field element. pub fn evaluate_felt(&self, poly: &impl Fn(PolynomialRef) -> F) -> F where F: Field, @@ -139,9 +178,10 @@ impl Expression { self.evaluate(poly, &|a| -a, &|a, b| a + b, &|a, b| a * b) } - pub fn used_term(&self, poly: &impl Fn(PolynomialRef) -> Option) -> BTreeSet + fn used_poly(&self, poly: &impl Fn(PolynomialRef) -> I) -> BTreeSet where T: Clone + Ord, + I: IntoIterator, { self.evaluate( &|a| BTreeSet::from_iter(poly(a)), @@ -151,22 +191,25 @@ impl Expression { ) } + /// Returns used `PolynomialRef::Lagrange` of the `Expression`. pub fn used_langrange(&self) -> BTreeSet { - self.used_term(&|poly| match poly { + self.used_poly(&|poly| match poly { PolynomialRef::Lagrange(i) => i.into(), _ => None, }) } + /// Returns used `Query` of the `Expression`. pub fn used_query(&self) -> BTreeSet { - self.used_term(&|poly| match poly { + self.used_poly(&|poly| match poly { PolynomialRef::Opaque(query) => query.into(), _ => None, }) } + /// Returns used `PolynomialRef::Challenge` of the `Expression`. pub fn used_challenge(&self) -> BTreeSet { - self.used_term(&|poly| match poly { + self.used_poly(&|poly| match poly { PolynomialRef::Challenge(idx) => idx.into(), _ => None, }) diff --git a/halo2_alt/src/util/evaluator.rs b/halo2_alt/src/util/evaluator.rs index 12099ccd22..b3324e706b 100644 --- a/halo2_alt/src/util/evaluator.rs +++ b/halo2_alt/src/util/evaluator.rs @@ -201,7 +201,7 @@ impl ExpressionRegistry { fn register_expression(&mut self, expr: &Expression) -> ValueSource { match expr { - Expression::Polynomial(term) => match term { + Expression::Polynomial(poly) => match poly { PolynomialRef::Constant(constant) => self.register_constant(constant), PolynomialRef::Challenge(idx) => self.register_challenge(idx), PolynomialRef::Identity => self.register_identity(),