diff --git a/Cargo.toml b/Cargo.toml index b44700ec43..7d8cee84c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ "halo2", + "halo2_alt", "halo2_proofs", ] diff --git a/halo2_alt/Cargo.toml b/halo2_alt/Cargo.toml new file mode 100644 index 0000000000..c607462ff5 --- /dev/null +++ b/halo2_alt/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "halo2_alt" +version = "0.1.0" +edition = "2021" + +[dependencies] +halo2_proofs = { path = "../halo2_proofs" } +rand_core = { version = "0.6", default-features = false } +rayon = "1.8" +blake2b_simd = "1" + +[dev-dependencies] +rand_chacha = "0.3" + +[features] +sanity-checks = [] diff --git a/halo2_alt/src/backend.rs b/halo2_alt/src/backend.rs new file mode 100644 index 0000000000..b2e25b1b02 --- /dev/null +++ b/halo2_alt/src/backend.rs @@ -0,0 +1,23 @@ +use crate::protocol::Protocol; +use halo2_proofs::plonk::Error; +use rand_core::RngCore; +use std::fmt::Debug; + +pub mod fflonk; + +pub trait Circuit: Debug { + type WitnessBuf: Debug + Default; + + fn protocol(&self) -> Protocol; + + fn preprocess(&self) -> Result>, Error>; + + fn witness( + &self, + phase: usize, + values: &[&[F]], + challenges: &[F], + buf: &mut Self::WitnessBuf, + rng: impl RngCore, + ) -> Result>, Error>; +} diff --git a/halo2_alt/src/backend/fflonk.rs b/halo2_alt/src/backend/fflonk.rs new file mode 100644 index 0000000000..fda4573dc9 --- /dev/null +++ b/halo2_alt/src/backend/fflonk.rs @@ -0,0 +1,407 @@ +use crate::{ + protocol::Protocol, + util::{chain, div_ceil, evaluator::Evaluator, izip, lcm, root_of_unity}, +}; +use blake2b_simd::Params as Blake2bParams; +use halo2_proofs::{ + halo2curves::{ + ff::{Field, FromUniformBytes, PrimeField}, + CurveAffine, + }, + poly::{Coeff, EvaluationDomain, ExtendedLagrangeCoeff, PinnedEvaluationDomain, Polynomial}, + transcript::{EncodedChallenge, Transcript}, +}; +use rayon::{current_num_threads, prelude::*}; +use std::{collections::BTreeSet, io, iter, ops::Deref}; + +mod circuit; +mod keygen; +mod prover; +mod verifier; + +pub use circuit::FflonkCircuit; +pub use keygen::{keygen_pk, keygen_vk}; +pub use prover::create_proof; +pub use verifier::verify_proof; + +#[derive(Clone, Debug)] +struct QueryInfo { + num_polys: usize, + t: usize, + omega_t: F, + omega_kt: F, + omega_kt_inv: F, + rotations: BTreeSet, +} + +impl QueryInfo { + fn new(k: usize, num_polys: usize, rotations: BTreeSet) -> Self { + assert!(rotations.contains(&0)); + // NOTE: Currently it only supports `t` as some power of 2, but we can find other domain + // with smaller size (e.g. 3) that `num_polys` fits to reduce prover opening cost. + let t = num_polys.next_power_of_two(); + let omega_t = root_of_unity(t); + let omega_kt = root_of_unity::(t << k); + let omega_kt_inv = omega_kt.invert().unwrap(); + Self { + num_polys, + t, + omega_t, + omega_kt, + omega_kt_inv, + rotations, + } + } + + fn roots(&self, lcm_t: usize, x_lcm_t: F, rotation: i32) -> impl Iterator + '_ { + assert_eq!(lcm_t % self.t, 0); + let x_t = x_lcm_t.pow([(lcm_t / self.t) as u64]); + let omega = if rotation >= 0 { + self.omega_kt.pow([rotation as u64]) + } else { + self.omega_kt_inv.pow([(rotation as i64).unsigned_abs()]) + }; + iter::successors(Some(omega * x_t), |root| Some(self.omega_t * root)).take(self.t) + } +} + +#[derive(Clone, Debug)] +struct PhaseInfo { + query_info: QueryInfo, + constraints: Vec, +} + +impl Deref for PhaseInfo { + type Target = QueryInfo; + + fn deref(&self) -> &Self::Target { + &self.query_info + } +} + +impl PhaseInfo { + fn new(query_info: QueryInfo, constraints: Vec) -> Self { + Self { + query_info, + constraints, + } + } +} + +#[derive(Debug)] +pub struct VerifyingKey { + domain: EvaluationDomain, + protocol: Protocol, + preprocessed_commitment: C, + preprocessed_query_info: QueryInfo, + phase_infos: Vec>, + transcript_repr: C::Scalar, +} + +impl VerifyingKey { + fn new( + protocol: Protocol, + preprocessed_commitment: C, + preprocessed_query_info: QueryInfo, + phase_infos: Vec>, + ) -> Self + where + C::Scalar: FromUniformBytes<64>, + { + let mut vk = Self { + domain: EvaluationDomain::new(protocol.degree() as u32, protocol.k as u32), + protocol, + preprocessed_query_info, + preprocessed_commitment, + phase_infos, + transcript_repr: C::Scalar::ZERO, + }; + + vk.transcript_repr = { + let mut hasher = Blake2bParams::new() + .hash_length(64) + .personal(b"Fflonk-Vk") + .to_state(); + + let s = format!("{:?}", vk.pinned()); + + hasher.update(&(s.len() as u64).to_le_bytes()); + hasher.update(s.as_bytes()); + + C::Scalar::from_uniform_bytes(hasher.finalize().as_array()) + }; + + vk + } + + pub fn hash_into, T: Transcript>( + &self, + transcript: &mut T, + ) -> io::Result<()> { + transcript.common_scalar(self.transcript_repr) + } + + fn query_infos(&self) -> impl Iterator> + '_ { + chain![ + [&self.preprocessed_query_info], + self.phase_infos.iter().map(Deref::deref), + ] + } + + fn lcm_t(&self) -> usize { + self.query_infos() + .map(|query_info| query_info.t) + .reduce(lcm) + .unwrap() + } + + fn pinned(&self) -> PinnedVerificationKey { + PinnedVerificationKey { + base_modulus: C::Base::MODULUS, + scalar_modulus: C::Scalar::MODULUS, + domain: self.domain.pinned(), + protocol: &self.protocol, + preprocessed_commitment: &self.preprocessed_commitment, + preprocessed_query_info: &self.preprocessed_query_info, + phase_infos: &self.phase_infos, + } + } +} + +#[derive(Debug)] +pub struct ProvingKey { + vk: VerifyingKey, + preprocessed_values: Vec>, + preprocessed_polys: Vec>, + preprocessed_cosets: Vec>, + transparent_cosets: Vec>, + evaluators: Vec>>, +} + +#[allow(dead_code)] +#[derive(Debug)] +pub struct PinnedVerificationKey<'a, C: CurveAffine> { + base_modulus: &'static str, + scalar_modulus: &'static str, + domain: PinnedEvaluationDomain<'a, C::Scalar>, + protocol: &'a Protocol, + preprocessed_commitment: &'a C, + preprocessed_query_info: &'a QueryInfo, + phase_infos: &'a [PhaseInfo], +} + +fn combine_polys<'a, F: Field>( + t: usize, + polys: impl IntoIterator>, +) -> Polynomial { + let polys = polys.into_iter().collect::>(); + assert!(t >= polys.len()); + let size = polys + .iter() + .map(|poly| poly.len()) + .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 +} + +fn eval_combined_polynomial(t: usize, num_evals: usize, poly: &[F], point: F) -> Vec { + let num_threads = current_num_threads(); + let chunk_size = div_ceil(poly.len() / t, num_threads).max(2 * num_threads); + let point_to_chunk_size = point.pow([chunk_size as u64]); + poly.par_chunks(chunk_size * t) + .rev() + .map(|poly| { + poly.chunks(t) + .rfold(vec![F::ZERO; num_evals], |mut acc, coeffs| { + izip!(&mut acc, coeffs).for_each(|(acc, coeff)| { + *acc *= point; + *acc += coeff + }); + acc + }) + }) + .reduce_with(|mut acc, chunk| { + izip!(&mut acc, chunk).for_each(|(acc, chunk)| { + *acc *= point_to_chunk_size; + *acc += chunk + }); + acc + }) + .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 test_create_proof() { + #[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; + + 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 new file mode 100644 index 0000000000..8474811054 --- /dev/null +++ b/halo2_alt/src/backend/fflonk/circuit.rs @@ -0,0 +1,648 @@ +use crate::{ + backend::{fflonk::keygen::query_infos, Circuit}, + protocol::{Expression, PolynomialRef, Protocol}, + util::{ + chain, div_ceil, felt_from_bool, + halo2::{ + batch_invert_assigned, convert_expressions, ColumnType, Permutation, + PreprocessCollector, WitnessCollector, + }, + horner, izip, log2_ceil, powers, + }, +}; +use halo2_proofs::{ + halo2curves::ff::{BatchInvert, Field, PrimeField, WithSmallOrderMulGroup}, + plonk::{self, ConstraintSystem, Error, FloorPlanner, Gate}, + poly::EvaluationDomain, +}; +use rand_core::RngCore; +use rayon::{current_num_threads, prelude::*}; +use std::{ + collections::HashMap, + fmt::Debug, + hash::Hash, + iter::{self, Cycle, Product, Sum}, + mem, + ops::Range, +}; + +#[derive(Clone, Debug)] +pub struct FflonkCircuit +where + F: Field, + C: Debug + plonk::Circuit, + C::Config: Debug, +{ + circuit: C, + circuit_config: C::Config, + cs: ConstraintSystem, + protocol: Protocol, + // NOTE: In the future we can traitify these 2 to let users plug their own protocols, + // the methods might contain one that takes `protocol` and return a diff to protocol to + // be merged later, and the other takes `(phase, values)` and returns some witnesses. + lookups: Vec>, + permutation: ZigZagPermutation, +} + +impl FflonkCircuit +where + F: WithSmallOrderMulGroup<3>, + C: Debug + plonk::Circuit, + C::Config: Debug, +{ + pub fn new(k: usize, circuit: C) -> Self { + let mut cs = ConstraintSystem::default(); + let circuit_config = C::configure(&mut cs); + let cs = cs; + + // NOTE: To support multi-phase circuit we might need to mess up with the query index, + // and lookup and permutation needs to choose which phase to insert their extra + // polynomials. + assert!( + !cs.advice_column_phase().into_iter().any(|phase| phase != 0), + "Multi-phase circuit is not yet supported", + ); + assert!( + cs.challenge_phase().is_empty(), + "Multi-phase circuit is not yet supported", + ); + + let column_idx = column_idx(&cs); + let permutation_columns = cs.permutation().get_columns(); + let num_preprocessed_polys = + cs.num_selectors() + cs.num_fixed_columns() + permutation_columns.len(); + let num_instance_polys = cs.num_instance_columns(); + + let m_offset = num_preprocessed_polys + num_instance_polys + cs.num_advice_columns(); + let phi_offset = m_offset + cs.lookups().len(); + let z_offset = phi_offset + cs.lookups().len(); + let gamma = 0; + let beta = 1; + + let lookups = izip!(cs.lookups(), m_offset.., phi_offset..) + .map(|(lookup, m, phi)| { + let [input, table] = [lookup.input_expressions(), lookup.table_expressions()] + .map(|expressions| convert_expressions(expressions, &column_idx)); + LogUp { + input, + table, + m, + phi, + gamma, + beta, + } + }) + .collect::>(); + let permutation = { + let chunk_size = cs.degree() - 2; + let inputs = permutation_columns + .iter() + .map(|column| { + column_idx[&(ColumnType::from(*column.column_type()), column.index())] + }) + .collect::>(); + let permutations = (cs.num_selectors() + cs.num_fixed_columns()..) + .take(inputs.len()) + .collect::>(); + let zs = (z_offset..) + .take(div_ceil(inputs.len(), chunk_size)) + .collect(); + ZigZagPermutation { + inputs, + permutations, + chunk_size, + zs, + gamma, + beta, + omega: EvaluationDomain::new(1, k as u32).get_omega(), + } + }; + + let constraints = { + let last_row = -(cs.blinding_factors() as i32 + 1); + let l_0 = &PolynomialRef::::Lagrange(0); + let l_last = &PolynomialRef::::Lagrange(last_row); + let l_active = &(PolynomialRef::Constant(F::ONE) + - Expression::sum((last_row..0).map(PolynomialRef::Lagrange))); + chain![ + convert_expressions(cs.gates().iter().flat_map(Gate::polynomials), &column_idx), + lookups + .iter() + .flat_map(|lookup| lookup.constraints(l_0, l_last, l_active)), + permutation.constraints(l_0, l_last, l_active) + ] + .collect::>() + }; + + let has_beta = + lookups.iter().any(|lookup| lookup.input.len() > 1) || !permutation.inputs.is_empty(); + let phases = match (lookups.len(), permutation.zs.len()) { + (0, 0) => vec![(cs.num_advice_columns(), 0)], + (num_lookups, num_zs) => vec![ + (cs.num_advice_columns() + num_lookups, 1 + has_beta as usize), + (num_lookups + num_zs, 0), + ], + }; + + let protocol = Protocol { + k, + num_preprocessed_polys, + num_instance_polys, + phases, + constraints, + }; + + Self { + circuit, + circuit_config, + cs, + protocol, + lookups, + permutation, + } + } + + 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()) + .max() + .unwrap_or(1); + phase_info.t * max_degree + }) + ] + .max() + .unwrap(); + self.protocol.k + log2_ceil(max_combined_degree) + } +} + +fn column_idx(cs: &ConstraintSystem) -> HashMap<(ColumnType, usize), usize> { + izip!( + chain![ + (0..cs.num_selectors()).map(|idx| Some((ColumnType::Selector, idx))), + (0..cs.num_fixed_columns()).map(|idx| Some((ColumnType::Fixed, idx))), + (0..cs.permutation().get_columns().len()).map(|_| None), + (0..cs.num_instance_columns()).map(|idx| Some((ColumnType::Instance, idx))), + (0..cs.num_advice_columns()).map(|idx| Some((ColumnType::Advice, idx))) + ], + 0.. + ) + .flat_map(|(k, v)| k.map(|k| (k, v))) + .collect() +} + +impl Circuit for FflonkCircuit +where + F: PrimeField + Hash, + C: Debug + plonk::Circuit, + C::Config: Debug, +{ + type WitnessBuf = Vec<[Vec>; 2]>; + + fn protocol(&self) -> Protocol { + self.protocol.clone() + } + + fn preprocess(&self) -> Result>, Error> { + let n = self.protocol.n(); + let mut collector = PreprocessCollector { + 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()], + usable_rows: 0..n - (self.cs.blinding_factors() + 1), + }; + + C::FloorPlanner::synthesize( + &mut collector, + &self.circuit, + self.circuit_config.clone(), + self.cs.constants().clone(), + ) + .map_err(|_| Error::Synthesis)?; + + let fixeds = batch_invert_assigned(collector.fixeds); + let selectors = collector + .selectors + .into_iter() + .map(|selectors| selectors.into_iter().map(felt_from_bool).collect()); + let permutations = self + .permutation + .preprocess(n, collector.permutation.into_cycles()); + + Ok(chain![fixeds, selectors, permutations].collect()) + } + + fn witness( + &self, + phase: usize, + values: &[&[F]], + challenges: &[F], + buf: &mut Self::WitnessBuf, + mut rng: impl RngCore, + ) -> Result>, Error> { + let n = self.protocol.n(); + let usable_rows = 0..n - (self.cs.blinding_factors() + 1); + + let mut values = match phase { + 0 => { + let mut collector = WitnessCollector { + 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()], + usable_rows: usable_rows.clone(), + }; + + C::FloorPlanner::synthesize( + &mut collector, + &self.circuit, + self.circuit_config.clone(), + self.cs.constants().clone(), + ) + .map_err(|_| Error::Synthesis)?; + + let advices = batch_invert_assigned(collector.advices); + + let values = chain![values.iter().cloned(), advices.iter().map(Vec::as_slice)] + .collect::>(); + + *buf = vec![Default::default(); self.lookups.len()]; + let ms = izip!(&self.lookups, buf) + .map(|(lookup, buf)| lookup.witness_m(n, &usable_rows, &values, buf)) + .collect::, _>>()?; + + chain![advices, ms].collect::>() + } + 1 => { + let phis = izip!(&self.lookups, buf) + .map(|(lookup, buf)| { + lookup.witness_phi(n, &usable_rows, values, challenges, buf) + }) + .collect::>(); + + let zs = self + .permutation + .witness_zs(n, &usable_rows, values, challenges); + + chain![phis, zs].collect() + } + _ => unimplemented!(), + }; + + values.iter_mut().for_each(|values| { + values[..] + .iter_mut() + .rev() + .take(self.cs.blinding_factors()) + .for_each(|value| *value = F::random(&mut rng)) + }); + + Ok(values) + } +} + +#[derive(Clone, Debug)] +struct LogUp { + // NOTE: In the future we can extend this to multi-inputs lookup to multi-tables, and sharing + // the same `m` and `phi` poly. + input: Vec>, + table: Vec>, + m: usize, + phi: usize, + gamma: usize, + beta: usize, +} + +impl LogUp { + fn constraints( + &self, + l_0: &PolynomialRef, + l_last: &PolynomialRef, + l_active: &Expression, + ) -> Vec> { + let gamma = &PolynomialRef::Challenge(self.gamma); + let beta = &PolynomialRef::Challenge(self.beta); + let f = &(horner(&self.input, beta) + gamma); + let t = &(horner(&self.table, beta) + gamma); + let m = &PolynomialRef::opaque((self.m, 0)); + let phi = &PolynomialRef::opaque((self.phi, 0)); + let phi_next = &PolynomialRef::opaque((self.phi, 1)); + vec![ + l_0 * phi, + l_last * phi, + l_active * (((phi_next - phi) * t + m) * f - t), + ] + } + + fn witness_m( + &self, + n: usize, + usable_rows: &Range, + values: &[&[F]], + buf: &mut [Vec>; 2], + ) -> Result, Error> + where + F: Hash, + { + let evaluate_row = |expressions: &Vec>, row: usize| { + expressions + .iter() + .map(|expression| { + expression.evaluate_felt(&|poly| match poly { + PolynomialRef::Constant(constant) => constant, + PolynomialRef::Opaque(query) => { + let rotated = (row as i32 + query.rotation.0).rem_euclid(n as i32); + values[query.index][rotated as usize] + } + PolynomialRef::Challenge(_) + | PolynomialRef::Identity + | PolynomialRef::Lagrange(_) => unimplemented!(), + }) + }) + .collect::>() + }; + + *buf = [&self.input, &self.table].map(|expressions| { + usable_rows + .clone() + .into_par_iter() + .map(|row| evaluate_row(expressions, row)) + .collect::>() + }); + + let table = buf[1] + .par_iter() + .zip(usable_rows.clone()) + .collect::>(); + let counts = buf[0][usable_rows.clone()] + .par_iter() + .map(|input| table.get(&input)) + .try_fold(HashMap::new, |mut counts, row| { + counts + .entry(row?) + .and_modify(|count| *count += 1) + .or_insert(1); + Some(counts) + }) + .try_reduce(HashMap::new, |mut acc, counts| { + counts.into_iter().for_each(|(row, count)| { + acc.entry(row) + .and_modify(|acc| *acc += count) + .or_insert(count); + }); + Some(acc) + }) + .ok_or(Error::Synthesis)?; + Ok(counts + .into_iter() + .fold(vec![F::ZERO; n], |mut m, (row, count)| { + m[*row] = F::from(count); + m + })) + } + + fn witness_phi( + &self, + n: usize, + usable_rows: &Range, + values: &[&[F]], + challenges: &[F], + buf: &mut [Vec>; 2], + ) -> Vec { + let gamma = challenges[self.gamma]; + let beta = challenges.get(self.beta).copied().unwrap_or_default(); + + let [mut input, mut table] = mem::take(buf).map(|value| { + value + .par_iter() + .map(|row| horner(row, &beta) + gamma) + .collect::>() + }); + + let par_chunk_size = div_ceil(2 * n, current_num_threads()); + input + .par_chunks_mut(par_chunk_size) + .chain(table.par_chunks_mut(par_chunk_size)) + .for_each(|values| { + values.batch_invert(); + }); + + let sum = input + .par_iter() + .zip(&table) + .zip(values[self.m]) + .map(|((input, table), m)| *input - *table * m) + .collect::>(); + + let phi = chain![ + sum.iter() + .scan(F::ZERO, |acc, sum| mem::replace(acc, *acc + sum).into()), + iter::repeat(F::ZERO) + ] + .take(n) + .collect::>(); + + if cfg!(feature = "sanity-checks") { + assert_eq!(phi[usable_rows.end], F::ZERO); + } + + phi + } +} + +#[derive(Clone, Debug)] +struct ZigZagPermutation { + inputs: Vec, + permutations: Vec, + chunk_size: usize, + zs: Vec, + beta: usize, + gamma: usize, + omega: F, +} + +impl ZigZagPermutation { + fn constraints( + &self, + l_0: &PolynomialRef, + l_last: &PolynomialRef, + l_active: &Expression, + ) -> Vec> { + let gamma = &PolynomialRef::Challenge(self.gamma); + let beta = &PolynomialRef::Challenge(self.beta); + let [inputs, permutations] = [&self.inputs, &self.permutations].map(|indices| { + indices + .iter() + .map(|idx| PolynomialRef::opaque((*idx, 0)).into()) + .collect::>() + }); + let delta_omegas = powers(F::DELTA) + .take(inputs.len()) + .map(|delta| PolynomialRef::Identity * PolynomialRef::Constant(delta)) + .collect::>(); + let zs = self + .zs + .iter() + .map(|idx| PolynomialRef::opaque((*idx, 0))) + .collect::>(); + let z_0_next = self.zs.first().map(|z_0| PolynomialRef::opaque((*z_0, 1))); + let one = PolynomialRef::Constant(F::ONE); + chain![ + zs.first().map(|z_0| l_0 * (z_0 - one)), + zs.first().map(|z_0| l_last * (z_0 * z_0 - z_0)), + izip!( + inputs.chunks(self.chunk_size), + delta_omegas.chunks(self.chunk_size), + permutations.chunks(self.chunk_size), + &zs, + chain![zs.iter().skip(1), &z_0_next] + ) + .map(|(inputs, delta_omegas, permutations, z_lhs, z_rhs)| { + let [lhs, rhs] = [delta_omegas, permutations].map(|ids| { + Expression::product( + izip!(inputs, ids).map(|(input, id)| input + beta * id + gamma), + ) + }); + l_active * (z_lhs * lhs - z_rhs * rhs) + }), + ] + .collect::>() + } + + fn preprocess(&self, n: usize, cycles: Vec>) -> Vec> { + let mut permutations = powers(F::DELTA) + .map(|delta| { + let mut permutation = vec![F::ZERO; n]; + let chunk_size = div_ceil(n, current_num_threads()); + permutation + .par_chunks_mut(chunk_size) + .enumerate() + .for_each(|(idx, chunk)| { + let mut delta_omega = delta * self.omega.pow([(idx * chunk_size) as u64]); + chunk.iter_mut().for_each(|value| { + *value = delta_omega; + delta_omega *= self.omega + }); + }); + permutation + }) + .take(self.permutations.len()) + .collect::>(); + for cycle in cycles.iter() { + let (i0, j0) = cycle[0]; + let mut last = permutations[i0][j0]; + for &(i, j) in cycle.iter().cycle().skip(1).take(cycle.len()) { + mem::swap(&mut permutations[i][j], &mut last); + } + } + permutations + } + + fn witness_zs( + &self, + n: usize, + usable_rows: &Range, + values: &[&[F]], + challenges: &[F], + ) -> Vec> { + let gamma = challenges[self.gamma]; + let beta = challenges[self.beta]; + + let products = izip!( + 0.., + self.inputs.chunks(self.chunk_size), + self.permutations.chunks(self.chunk_size) + ) + .map(|(chunk_idx, inputs, permutations)| { + let mut product = vec![F::ONE; n]; + + izip!(inputs, permutations).for_each(|(input, permutation)| { + let input = values[*input]; + let permutation = values[*permutation]; + product[usable_rows.clone()] + .par_iter_mut() + .enumerate() + .for_each(|(row, product)| { + *product *= input[row] + beta * permutation[row] + gamma + }); + }); + + let par_chunk_size = div_ceil(usable_rows.len(), current_num_threads()); + + product[usable_rows.clone()] + .par_chunks_mut(par_chunk_size) + .for_each(|product| { + product.batch_invert(); + }); + + let deltas = powers(F::DELTA).skip(chunk_idx * self.chunk_size); + izip!(inputs, deltas).for_each(|(input, delta)| { + let input = values[*input]; + product.par_chunks_mut(par_chunk_size).enumerate().for_each( + |(chunk_idx, product)| { + let start = chunk_idx * par_chunk_size; + let mut beta_delta_omega = beta * delta * self.omega.pow([start as u64]); + izip!(start.., product).for_each(|(row, product)| { + *product *= input[row] + beta_delta_omega + gamma; + beta_delta_omega *= self.omega; + }); + }, + ); + }); + + product + }) + .collect::>(); + + let products = { + let mut products = Multizip::new( + products + .iter() + .map(|product| product[usable_rows.clone()].iter()) + .collect(), + ); + iter::successors(Some(F::ONE), |state| { + products.next().map(|product| *state * product) + }) + .collect::>() + }; + + if cfg!(feature = "sanity-checks") { + assert_eq!(products[self.zs.len() * usable_rows.end], F::ONE); + } + + let mut zs = vec![vec![F::ZERO; n]; self.zs.len()]; + zs[0][usable_rows.end] = F::ONE; + zs.par_iter_mut().enumerate().for_each(|(col, z)| { + z[usable_rows.clone()] + .par_iter_mut() + .enumerate() + .for_each(|(row, value)| *value = products[col + self.zs.len() * row]) + }); + zs + } +} + +struct Multizip(Vec, Cycle>); + +impl Multizip { + fn new(values: Vec) -> Self { + let ptr = (0..values.len()).cycle(); + Self(values, ptr) + } +} + +impl Iterator for Multizip { + type Item = T::Item; + + fn next(&mut self) -> Option { + self.0[self.1.next().unwrap()].next() + } +} diff --git a/halo2_alt/src/backend/fflonk/keygen.rs b/halo2_alt/src/backend/fflonk/keygen.rs new file mode 100644 index 0000000000..43b79008b5 --- /dev/null +++ b/halo2_alt/src/backend/fflonk/keygen.rs @@ -0,0 +1,263 @@ +use crate::{ + backend::{ + fflonk::{combine_polys, PhaseInfo, ProvingKey, QueryInfo, VerifyingKey}, + Circuit, + }, + protocol::{Expression, PolynomialRef::*, Protocol}, + util::{chain, evaluator::Evaluator, felt_from_bool, izip}, +}; +use halo2_proofs::{ + halo2curves::{ + ff::{Field, FromUniformBytes, PrimeField, WithSmallOrderMulGroup}, + group::Curve, + }, + plonk::Error, + poly::{ + commitment::{Blind, CommitmentScheme, ParamsProver}, + EvaluationDomain, ExtendedLagrangeCoeff, Polynomial, + }, +}; +use rayon::prelude::*; +use std::{ + cell::RefCell, + collections::{BTreeSet, HashMap}, + hash::Hash, + iter, +}; + +pub fn keygen_vk( + params: &S::ParamsProver, + circuit: &C, +) -> Result, Error> +where + S::Scalar: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, + 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 preprocessed_values = circuit.preprocess()?; + if preprocessed_values.len() != protocol.num_preprocessed_polys { + return Err(Error::Synthesis); + } + + let preprocessed_polys = preprocessed_values + .into_iter() + .map(|values| domain.lagrange_to_coeff(Polynomial::new(values))) + .collect::>(); + let combined = combine_polys(preprocessed_query_info.t, &preprocessed_polys); + params.commit(&combined, Blind::default()).to_affine() + }; + + Ok(VerifyingKey::new( + protocol, + preprocessed_commitment, + preprocessed_query_info, + phase_infos, + )) +} + +pub fn keygen_pk( + params: &S::ParamsProver, + circuit: &C, +) -> Result, Error> +where + S::Scalar: FromUniformBytes<64> + WithSmallOrderMulGroup<3> + Hash, + 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 preprocessed_values = circuit.preprocess()?; + assert_eq!(preprocessed_values.len(), protocol.num_preprocessed_polys); + + let preprocessed_polys = preprocessed_values + .iter() + .map(|values| domain.lagrange_to_coeff(Polynomial::new(values.clone()))) + .collect::>(); + let preprocessed_cosets = preprocessed_polys + .iter() + .map(|poly| domain.coeff_to_extended(poly.clone())) + .collect::>(); + let preprocessed_commitment = { + let combined = combine_polys(preprocessed_query_info.t, &preprocessed_polys); + params.commit(&combined, Blind::default()).to_affine() + }; + + let (evaluators, transparent_cosets) = evaluators(&protocol, &phase_infos); + + Ok(ProvingKey { + vk: VerifyingKey::new( + protocol, + preprocessed_commitment, + preprocessed_query_info, + phase_infos, + ), + preprocessed_values, + preprocessed_polys, + preprocessed_cosets, + transparent_cosets, + evaluators, + }) +} + +pub(super) fn query_infos( + protocol: &Protocol, +) -> (QueryInfo, Vec>) { + let constraints_by_earliest_phase = constraints_by_earliest_phase(protocol); + let poly_ranges = protocol.poly_range(); + let poly_combination_idx = chain![ + [(poly_ranges.preprocessed, Some(0))], + [(poly_ranges.instance, None)], + izip!(poly_ranges.advices, (1..).map(Some)) + ] + .flat_map(|(range, phase)| izip!(range, iter::repeat(phase))) + .collect::>>(); + let rotations = protocol + .constraints + .iter() + .flat_map(Expression::used_query) + .fold( + chain![ + [BTreeSet::new()], + iter::repeat(BTreeSet::from([0])).take(protocol.num_phases()) + ] + .collect::>(), + |mut rotations, query| { + if let Some(idx) = poly_combination_idx[&query.index] { + rotations[idx].insert(query.rotation.0); + } + rotations + }, + ); + let num_polys = chain![ + [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)) + .collect::>() + .into_iter(); + let preprocessed_query_info = query_infos.next().unwrap(); + let phase_infos = izip!(query_infos, constraints_by_earliest_phase) + .map(|(query_info, constraints)| PhaseInfo::new(query_info, constraints)) + .collect(); + (preprocessed_query_info, phase_infos) +} + +fn constraints_by_earliest_phase(protocol: &Protocol) -> Vec> { + let poly_ranges = protocol.poly_range(); + let poly_usable_phase = chain![ + [(poly_ranges.preprocessed, 0)], + [(poly_ranges.instance, 0)], + izip!(poly_ranges.advices.clone(), 0..) + ] + .flat_map(|(range, phase)| izip!(range, iter::repeat(phase))) + .collect::>(); + let challenge_usable_phase = izip!(poly_ranges.challenges, 1..) + .flat_map(|(range, phase)| izip!(range, iter::repeat(phase))) + .collect::>(); + protocol.constraints.iter().enumerate().fold( + vec![vec![]; protocol.num_phases()], + |mut constraints_by_earliest_phase, (idx, constraint)| { + let phase = constraint.evaluate( + &|poly| match poly { + Constant(_) | Identity | Lagrange(_) => 0, + Challenge(idx) => challenge_usable_phase[&idx], + Opaque(query) => poly_usable_phase[&query.index], + }, + &|a| a, + &|a, b| a.max(b), + &|a, b| a.max(b), + ); + constraints_by_earliest_phase[phase].push(idx); + constraints_by_earliest_phase + }, + ) +} + +#[allow(clippy::type_complexity)] +fn evaluators + Hash>( + protocol: &Protocol, + phase_infos: &[PhaseInfo], +) -> ( + Vec>>, + Vec>, +) { + let num_opaque_polys = protocol.num_opaque_polys(); + let transparents = RefCell::new(HashMap::<_, usize>::new()); + let replace = |constraint: &Expression| { + let inner = |(is_transparent, constraint): (bool, Expression)| { + if !is_transparent || constraint.degree() == 0 { + return constraint; + }; + + let mut transparents = transparents.borrow_mut(); + let idx = if let Some(idx) = transparents.get(&constraint).copied() { + idx + } else { + let idx = num_opaque_polys + transparents.len(); + transparents.insert(constraint.clone(), idx); + idx + }; + Opaque((idx, 0).into()).into() + }; + + inner(constraint.evaluate::<(bool, Expression)>( + &|poly| match poly { + Constant(_) | Lagrange(_) => (true, poly.into()), + Challenge(_) | Identity | Opaque(_) => (false, poly.into()), + }, + &|value| (value.0, -value.1), + &|a, b| match a.0 && b.0 { + true => (true, a.1 + b.1), + false => (false, inner(a) + inner(b)), + }, + &|a, b| (false, inner(a) * inner(b)), + )) + }; + + let evaluators = phase_infos + .iter() + .map(|phase_info| { + phase_info + .constraints + .iter() + .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 mut transparents = transparents.take().into_iter().collect::>(); + transparents.sort_by(|(_, a), (_, b)| a.cmp(b)); + transparents + .into_iter() + .map(|(expr, _)| { + let mut poly = domain.empty_lagrange(); + poly.par_iter_mut().enumerate().for_each(|(idx, eval)| { + *eval = expr.evaluate_felt(&|poly| match poly { + Constant(constant) => constant, + Lagrange(i) => felt_from_bool(i.rem_euclid(n) == idx as i32), + _ => unreachable!(), + }); + }); + domain.coeff_to_extended(domain.lagrange_to_coeff(poly)) + }) + .collect() + }; + + (evaluators, transparent_cosets) +} diff --git a/halo2_alt/src/backend/fflonk/prover.rs b/halo2_alt/src/backend/fflonk/prover.rs new file mode 100644 index 0000000000..35ae64e687 --- /dev/null +++ b/halo2_alt/src/backend/fflonk/prover.rs @@ -0,0 +1,179 @@ +use crate::{ + backend::{ + fflonk::{combine_polys, eval_combined_polynomial, ProvingKey}, + Circuit, + }, + util::{chain, izip}, +}; +use halo2_proofs::{ + arithmetic::eval_polynomial, + halo2curves::{ + ff::{Field, FromUniformBytes, WithSmallOrderMulGroup}, + group::Curve, + }, + plonk::Error, + poly::{ + commitment::{Blind, CommitmentScheme, ParamsProver, Prover}, + Polynomial, ProverQuery, Rotation, + }, + transcript::{EncodedChallenge, TranscriptWrite}, +}; +use rand_core::RngCore; +use std::iter; + +pub fn create_proof<'params, S, P, E, C>( + params: &'params S::ParamsProver, + pk: &ProvingKey, + circuit: &C, + instances: &[&[S::Scalar]], + mut rng: impl RngCore, + transcript: &mut impl TranscriptWrite, +) -> Result<(), Error> +where + S: CommitmentScheme, + S::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + P: Prover<'params, S>, + E: EncodedChallenge, + C: Circuit, +{ + let vk = &pk.vk; + let domain = &vk.domain; + if instances.len() != vk.protocol.num_instance_polys { + return Err(Error::InvalidInstances); + } + + vk.hash_into(transcript)?; + + for instance in instances.iter() { + for instance in instance.iter() { + transcript.common_scalar(*instance)?; + } + } + + let instance_values = instances + .iter() + .map(|instance| { + chain![instance.iter().cloned(), iter::repeat(S::Scalar::ZERO)] + .take(vk.protocol.n()) + .collect::>() + }) + .collect::>(); + let instance_cosets = instance_values + .iter() + .cloned() + .map(|value| domain.coeff_to_extended(domain.lagrange_to_coeff(Polynomial::new(value)))) + .collect::>(); + + let mut combineds = vec![(Polynomial::new(Vec::new()), Default::default())]; + let mut advice_values = vec![]; + let mut advice_cosets = vec![]; + 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) + { + let values = { + let values = chain![&pk.preprocessed_values, &instance_values, &advice_values] + .map(Vec::as_slice) + .collect::>(); + circuit.witness(phase, &values, &challenges, &mut buf, &mut rng)? + }; + assert_eq!(values.len(), *num_advice_polys); + let polys = values + .iter() + .map(|value| domain.lagrange_to_coeff(Polynomial::new(value.clone()))) + .collect::>(); + let cosets = polys + .iter() + .map(|poly| domain.coeff_to_extended(poly.clone())) + .collect::>(); + advice_values.extend(values); + advice_cosets.extend(cosets); + + let quotient_polys = { + let dummy_coset = Polynomial::new(Vec::new()); + let polys = chain![ + &pk.preprocessed_cosets, + &instance_cosets, + chain![&advice_cosets, iter::repeat(&dummy_coset)] + .take(vk.protocol.num_advice_polys()), + &pk.transparent_cosets + ] + .collect::>(); + evaluators + .iter() + .map(|evaluator| evaluator.evaluate_quotient(&polys, &challenges)) + .collect::>() + }; + + let polys = chain![polys, quotient_polys].collect::>(); + let combined_poly = combine_polys(phase_info.t, &polys); + let combined_blind = Blind::new(&mut rng); + let combined_commitment = params.commit(&combined_poly, combined_blind).to_affine(); + transcript.write_point(combined_commitment)?; + combineds.push((combined_poly, combined_blind)); + + for _ in 0..*num_challegnes { + challenges.push(*transcript.squeeze_challenge_scalar::<()>()); + } + } + + drop(advice_values); + drop(advice_cosets); + drop(challenges); + drop(buf); + + let lcm_t = vk.lcm_t(); + let x_lcm_t = *transcript.squeeze_challenge_scalar::<()>(); + let x = x_lcm_t.pow([lcm_t as u64]); + + for rotation in &vk.preprocessed_query_info.rotations { + let point = domain.rotate_omega(x, Rotation(*rotation)); + for poly in &pk.preprocessed_polys { + transcript.write_scalar(eval_polynomial(poly, point))?; + } + } + for (phase_info, (poly, _)) in izip!(&vk.phase_infos, &combineds[1..]) { + for rotation in &phase_info.rotations { + let point = domain.rotate_omega(x, Rotation(*rotation)); + let num_evals = if *rotation == 0 { + phase_info.num_polys - phase_info.constraints.len() + } else { + phase_info.num_polys + }; + for eval in eval_combined_polynomial(phase_info.t, num_evals, poly, point) { + transcript.write_scalar(eval)?; + } + } + } + + combineds[0].0 = combine_polys(vk.preprocessed_query_info.t, &pk.preprocessed_polys); + + // 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(); + combineds + .into_iter() + .map(|(poly, blind)| { + let mut poly = poly.into_vec(); + poly.resize(max_size, S::Scalar::ZERO); + (Polynomial::new(poly), blind) + }) + .collect::>() + }; + + let queries = izip!(vk.query_infos(), &combineds) + .flat_map(|(query_info, (poly, blind))| { + query_info.rotations.iter().flat_map(move |rotation| { + query_info + .roots(lcm_t, x_lcm_t, *rotation) + .map(move |point| ProverQuery::new(point, poly, *blind)) + }) + }) + .collect::>(); + + let prover = P::new(params); + prover + .create_proof(rng, transcript, queries) + .map_err(|_| Error::ConstraintSystemFailure) +} diff --git a/halo2_alt/src/backend/fflonk/verifier.rs b/halo2_alt/src/backend/fflonk/verifier.rs new file mode 100644 index 0000000000..e720ac7c9e --- /dev/null +++ b/halo2_alt/src/backend/fflonk/verifier.rs @@ -0,0 +1,220 @@ +use crate::{ + backend::fflonk::VerifyingKey, + protocol::{Expression, PolynomialRef::*, Query}, + util::{chain, halo2::TranscriptReadVec, izip, powers, squares}, +}; +use halo2_proofs::{ + arithmetic::eval_polynomial, + halo2curves::{ + ff::{BatchInvert, Field, FromUniformBytes, WithSmallOrderMulGroup}, + CurveAffine, + }, + plonk::Error, + poly::{ + commitment::{CommitmentScheme, Verifier}, + EvaluationDomain, Rotation, VerificationStrategy, VerifierQuery, + }, + transcript::{EncodedChallenge, TranscriptRead}, +}; +use std::collections::{BTreeSet, HashMap}; + +pub fn verify_proof<'params, S, V, E, ST>( + params: &'params S::ParamsVerifier, + vk: &VerifyingKey, + instances: &[&[S::Scalar]], + strategy: ST, + transcript: &mut impl TranscriptRead, +) -> Result +where + S: CommitmentScheme, + S::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + V: Verifier<'params, S>, + E: EncodedChallenge, + ST: VerificationStrategy<'params, S, V>, +{ + if instances.len() != vk.protocol.num_instance_polys { + return Err(Error::InvalidInstances); + } + + vk.hash_into(transcript)?; + + for instance in instances.iter() { + for instance in instance.iter() { + transcript.common_scalar(*instance)?; + } + } + + let mut combined_commitments = vec![vk.preprocessed_commitment]; + let mut challenges = vec![]; + for (_, num_challenges) in &vk.protocol.phases { + combined_commitments.push(transcript.read_point()?); + + for _ in 0..*num_challenges { + challenges.push(*transcript.squeeze_challenge_scalar::<()>()); + } + } + + let lcm_t = vk.lcm_t(); + let x_lcm_t = *transcript.squeeze_challenge_scalar::<()>(); + let x = x_lcm_t.pow([lcm_t as u64]); + + let evals = { + let mut evals = vec![Vec::new(); combined_commitments.len()]; + for _ in &vk.preprocessed_query_info.rotations { + let num_evals = vk.preprocessed_query_info.num_polys; + evals[0].push(transcript.read_scalars(num_evals)?); + } + for (phase_info, evals) in izip!(&vk.phase_infos, &mut evals[1..]) { + for rotation in &phase_info.rotations { + let num_evals = if *rotation == 0 { + phase_info.num_polys - phase_info.constraints.len() + } else { + phase_info.num_polys + }; + evals.push(transcript.read_scalars(num_evals)?); + } + } + + let quotient_evals = quotient_evals(vk, instances, &challenges, x, &evals); + for (phase_info, evals, quotient_evals) in + izip!(&vk.phase_infos, &mut evals[1..], quotient_evals) + { + let idx = phase_info.rotations.iter().position(|r| *r == 0).unwrap(); + evals[idx].extend(quotient_evals); + } + + evals + }; + + let queries = izip!(vk.query_infos(), &combined_commitments, evals) + .flat_map(|(query_info, commitment, evals)| { + izip!(&query_info.rotations, evals).flat_map(move |(rotation, evals)| { + query_info + .roots(lcm_t, x_lcm_t, *rotation) + .map(move |point| { + let eval = eval_polynomial(&evals, point); + VerifierQuery::new_commitment(commitment, point, eval) + }) + }) + }) + .collect::>(); + + let verifier = V::new(params); + strategy.process(|msm| { + verifier + .verify_proof(transcript, queries, msm) + .map_err(|_| Error::Opening) + }) +} + +fn quotient_evals( + vk: &VerifyingKey, + instances: &[&[C::Scalar]], + challenges: &[C::Scalar], + x: C::Scalar, + evals: &[Vec>], +) -> Vec> { + let domain = &vk.domain; + let poly_range = vk.protocol.poly_range(); + + 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 + .iter() + .flat_map(Expression::used_query) + .filter(|query| poly_range.instance.contains(&query.index)) + .collect::>(); + let (min_rotation, max_rotation) = + instance_queries.iter().fold((0, 0), |(min, max), query| { + if query.rotation.0 < min { + (query.rotation.0, max) + } else if query.rotation.0 > max { + (min, query.rotation.0) + } else { + (min, max) + } + }); + let max_instance_len = instances + .values() + .map(|instance| instance.len()) + .max_by(Ord::cmp) + .unwrap_or_default(); + let i_s = chain![ + max_rotation..max_instance_len as i32 + min_rotation.abs(), + vk.protocol + .constraints + .iter() + .flat_map(Expression::used_langrange), + ] + .collect::>(); + let lagrange_evals = lagrange_evals(domain, i_s, x, x_to_n); + let instance_evals = instance_queries + .into_iter() + .map(|query| { + let instance = instances[&query.index]; + let eval = izip!(*instance, max_rotation - query.rotation.0..) + .map(|(instance, i)| *instance * lagrange_evals[&i]) + .sum(); + (query, eval) + }) + .collect::>(); + (lagrange_evals, instance_evals) + }; + + let evals = chain![ + instance_evals, + izip!( + vk.query_infos(), + chain![[poly_range.preprocessed], poly_range.advices], + evals + ) + .flat_map(|(query_info, polys, evals)| { + izip!(&query_info.rotations, evals).flat_map(move |(rotation, evals)| { + izip!(polys.clone(), evals).map(|(idx, eval)| ((idx, *rotation).into(), *eval)) + }) + }), + ] + .collect::>(); + let vanishing_eval = (x_to_n - C::Scalar::ONE).invert().unwrap(); + vk.phase_infos + .iter() + .map(|phase_info| { + phase_info + .constraints + .iter() + .map(|idx| { + vk.protocol.constraints[*idx].evaluate_felt(&|poly| match poly { + Constant(constant) => constant, + Challenge(idx) => challenges[idx], + Identity => x, + Lagrange(i) => lagrange_evals[&i], + Opaque(query) => evals[&query], + }) * vanishing_eval + }) + .collect::>() + }) + .collect() +} + +fn lagrange_evals>( + domain: &EvaluationDomain, + i_s: impl IntoIterator, + x: F, + x_to_n: F, +) -> HashMap { + let i_s = i_s.into_iter().collect::>(); + let n_inv = powers(F::TWO_INV).nth(domain.k() as usize).unwrap(); + let common = (x_to_n - F::ONE) * n_inv; + + let mut evals = i_s + .iter() + .map(|i| x - domain.rotate_omega(F::ONE, Rotation(*i))) + .collect::>(); + evals.batch_invert(); + izip!(i_s, evals) + .map(|(i, eval)| (i, domain.rotate_omega(common * eval, Rotation(i)))) + .collect() +} diff --git a/halo2_alt/src/lib.rs b/halo2_alt/src/lib.rs new file mode 100644 index 0000000000..20a098d4f3 --- /dev/null +++ b/halo2_alt/src/lib.rs @@ -0,0 +1,5 @@ +pub mod backend; +pub mod protocol; +mod util; + +pub use halo2_proofs; diff --git a/halo2_alt/src/protocol.rs b/halo2_alt/src/protocol.rs new file mode 100644 index 0000000000..67a15c6beb --- /dev/null +++ b/halo2_alt/src/protocol.rs @@ -0,0 +1,95 @@ +use crate::util::chain; +use std::{fmt::Debug, ops::Range}; + +mod expression; + +pub(crate) use crate::protocol::expression::{Expression, PolynomialRef, Query}; + +#[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>, +} + +impl Protocol { + pub(crate) 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(crate) fn num_phases(&self) -> usize { + self.phases.len() + } + + pub(crate) fn num_advice_polys(&self) -> usize { + self.phases.iter().map(|(n, _)| n).sum() + } + + pub(crate) fn num_challenges(&self) -> usize { + self.phases.iter().map(|(_, n)| n).sum() + } + + pub(crate) fn num_opaque_polys(&self) -> usize { + self.num_preprocessed_polys + self.num_instance_polys + self.num_advice_polys() + } + + pub(crate) fn poly_range(&self) -> PolynomialRange { + let mut opaque_ranges = lens_to_cont_ranges(chain![ + [self.num_preprocessed_polys, self.num_instance_polys], + self.phases.iter().map(|(n, _)| *n), + ]); + PolynomialRange { + preprocessed: opaque_ranges.next().unwrap(), + instance: opaque_ranges.next().unwrap(), + advices: opaque_ranges.collect(), + challenges: lens_to_cont_ranges(self.phases.iter().map(|(_, n)| *n)).collect(), + } + } + + pub fn n(&self) -> usize { + 1 << self.k + } + + pub fn degree(&self) -> usize { + self.constraints + .iter() + .map(Expression::degree) + .max() + .unwrap_or_default() + } +} + +pub(crate) struct PolynomialRange { + pub(crate) preprocessed: Range, + pub(crate) instance: Range, + pub(crate) advices: Vec>, + pub(crate) challenges: Vec>, +} + +fn lens_to_cont_ranges( + lens: impl IntoIterator, +) -> impl Iterator> { + lens.into_iter().scan(0, |state, len| { + let range = *state..*state + len; + *state += len; + Some(range) + }) +} diff --git a/halo2_alt/src/protocol/expression.rs b/halo2_alt/src/protocol/expression.rs new file mode 100644 index 0000000000..847f391121 --- /dev/null +++ b/halo2_alt/src/protocol/expression.rs @@ -0,0 +1,299 @@ +use halo2_proofs::{halo2curves::ff::Field, poly::Rotation}; +use std::{ + collections::BTreeSet, + convert::identity, + fmt::Debug, + hash::{Hash, Hasher}, + iter::{Product, Sum}, + ops::{Add, Mul, Neg, Sub}, +}; + +#[derive(Clone, Copy, Debug, Eq)] +pub struct Query { + pub index: usize, + pub rotation: Rotation, +} + +impl From<(usize, i32)> for Query { + fn from((index, rotation): (usize, i32)) -> Self { + Self { + index, + rotation: Rotation(rotation), + } + } +} + +impl PartialEq for Query { + fn eq(&self, other: &Self) -> bool { + (self.index, self.rotation).eq(&(other.index, other.rotation)) + } +} + +impl PartialOrd for Query { + fn partial_cmp(&self, other: &Self) -> Option { + (self.index, self.rotation.0).partial_cmp(&(other.index, other.rotation.0)) + } +} + +impl Ord for Query { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + +impl Hash for Query { + fn hash(&self, state: &mut H) { + self.index.hash(state); + self.rotation.0.hash(state); + } +} + +#[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) +} + +impl PolynomialRef { + pub fn opaque(query: impl Into) -> Self { + Self::Opaque(query.into()) + } + + pub fn degree(&self) -> usize { + match self { + Self::Constant(_) | Self::Challenge(_) => 0, + Self::Identity | Self::Lagrange(_) | Self::Opaque(_) => 1, + } + } + + pub fn evaluate( + &self, + constant: &impl Fn(F) -> T, + challenge: &impl Fn(usize) -> T, + identity: &impl Fn() -> T, + lagrange: &impl Fn(i32) -> T, + opaque: &impl Fn(Query) -> T, + ) -> T + where + F: Clone, + { + match self { + Self::Constant(value) => constant(value.clone()), + Self::Challenge(value) => challenge(*value), + Self::Identity => identity(), + Self::Lagrange(value) => lagrange(*value), + Self::Opaque(value) => opaque(*value), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Expression { + Polynomial(PolynomialRef), + Neg(Box), + Sum(Box, Box), + Product(Box, Box), +} + +impl From> for Expression { + fn from(poly: PolynomialRef) -> Self { + Expression::Polynomial(poly) + } +} + +impl Expression { + pub fn degree(&self) -> usize { + match self { + Self::Polynomial(inner) => inner.degree(), + Self::Neg(inner) => inner.degree(), + Self::Sum(lhs, rhs) => lhs.degree().max(rhs.degree()), + Self::Product(lhs, rhs) => lhs.degree() + rhs.degree(), + } + } +} + +impl Expression { + pub fn evaluate( + &self, + poly: &impl Fn(PolynomialRef) -> T, + neg: &impl Fn(T) -> T, + sum: &impl Fn(T, T) -> T, + product: &impl Fn(T, T) -> T, + ) -> T { + let evaluate = |expr: &Self| expr.evaluate(poly, neg, sum, product); + match self { + Self::Polynomial(a) => poly(a.clone()), + Self::Neg(a) => neg(evaluate(a)), + Self::Sum(a, b) => sum(evaluate(a), evaluate(b)), + Self::Product(a, b) => product(evaluate(a), evaluate(b)), + } + } + + pub fn evaluate_felt(&self, poly: &impl Fn(PolynomialRef) -> F) -> F + where + F: Field, + { + self.evaluate(poly, &|a| -a, &|a, b| a + b, &|a, b| a * b) + } + + pub fn used_term(&self, poly: &impl Fn(PolynomialRef) -> Option) -> BTreeSet + where + T: Clone + Ord, + { + self.evaluate( + &|a| BTreeSet::from_iter(poly(a)), + &|a| a, + &merge_set, + &merge_set, + ) + } + + pub fn used_langrange(&self) -> BTreeSet { + self.used_term(&|poly| match poly { + PolynomialRef::Lagrange(i) => i.into(), + _ => None, + }) + } + + pub fn used_query(&self) -> BTreeSet { + self.used_term(&|poly| match poly { + PolynomialRef::Opaque(query) => query.into(), + _ => None, + }) + } + + pub fn used_challenge(&self) -> BTreeSet { + self.used_term(&|poly| match poly { + PolynomialRef::Challenge(idx) => idx.into(), + _ => None, + }) + } +} + +macro_rules! impl_ops { + (@ $lhs:ty, $rhs:ty, $trait:ident, $op:ident, $variant:ident, $rhs_transformer:expr) => { + impl $trait<$rhs> for $lhs { + type Output = Expression; + fn $op(self, rhs: $rhs) -> Self::Output { + Expression::$variant( + (Expression::from(self)).into(), + $rhs_transformer(Expression::from(rhs)).into(), + ) + } + } + + impl $trait<$rhs> for &$lhs { + type Output = Expression; + fn $op(self, rhs: $rhs) -> Self::Output { + Expression::$variant( + (Expression::from(self.clone())).into(), + $rhs_transformer(Expression::from(rhs)).into(), + ) + } + } + + impl $trait<&$rhs> for $lhs { + type Output = Expression; + fn $op(self, rhs: &$rhs) -> Self::Output { + Expression::$variant( + (Expression::from(self)).into(), + $rhs_transformer(Expression::from(rhs.clone())).into(), + ) + } + } + + impl $trait<&$rhs> for &$lhs { + type Output = Expression; + fn $op(self, rhs: &$rhs) -> Self::Output { + Expression::$variant( + (Expression::from(self.clone())).into(), + $rhs_transformer(Expression::from(rhs.clone())).into(), + ) + } + } + }; + ($trait:ident, $op:ident, $variant:ident, $rhs_transformer:expr) => { + impl_ops!(@ PolynomialRef, PolynomialRef, $trait, $op, $variant, $rhs_transformer); + impl_ops!(@ PolynomialRef, Expression, $trait, $op, $variant, $rhs_transformer); + impl_ops!(@ Expression, PolynomialRef, $trait, $op, $variant, $rhs_transformer); + impl_ops!(@ Expression, Expression, $trait, $op, $variant, $rhs_transformer); + }; + ($trait:ident, $op:ident, $variant:ident) => { + impl_ops!($trait, $op, $variant, identity); + }; +} + +impl_ops!(Mul, mul, Product); +impl_ops!(Add, add, Sum); +impl_ops!(Sub, sub, Sum, Neg::neg); + +impl Neg for PolynomialRef { + type Output = Expression; + fn neg(self) -> Self::Output { + -Expression::from(self) + } +} + +impl Neg for &PolynomialRef { + type Output = Expression; + fn neg(self) -> Self::Output { + -Expression::from(self.clone()) + } +} + +impl Neg for Expression { + type Output = Expression; + fn neg(self) -> Self::Output { + Expression::Neg(self.into()) + } +} + +impl Neg for &Expression { + type Output = Expression; + fn neg(self) -> Self::Output { + -self.clone() + } +} + +macro_rules! impl_sum_product { + ($name:ty) => { + impl<'a, F: Field> Sum<&'a $name> for Expression { + fn sum>(iter: I) -> Self { + iter.cloned().sum() + } + } + + impl Sum<$name> for Expression { + fn sum>(iter: I) -> Self { + iter.map(Expression::from) + .reduce(|acc, item| acc + item) + .unwrap_or_else(|| PolynomialRef::Constant(F::ZERO).into()) + } + } + + impl<'a, F: Field> Product<&'a $name> for Expression { + fn product>(iter: I) -> Self { + iter.cloned().product() + } + } + + impl Product<$name> for Expression { + fn product>(iter: I) -> Self { + iter.map(Expression::from) + .reduce(|acc, item| acc * item) + .unwrap_or_else(|| PolynomialRef::Constant(F::ONE).into()) + } + } + }; +} + +impl_sum_product!(Expression); +impl_sum_product!(PolynomialRef); + +fn merge_set(mut lhs: BTreeSet, mut rhs: BTreeSet) -> BTreeSet { + lhs.append(&mut rhs); + lhs +} diff --git a/halo2_alt/src/util.rs b/halo2_alt/src/util.rs new file mode 100644 index 0000000000..b8c32789f5 --- /dev/null +++ b/halo2_alt/src/util.rs @@ -0,0 +1,105 @@ +use halo2_proofs::halo2curves::ff::{Field, PrimeField}; +use std::{ + iter, + ops::{Add, Mul}, +}; + +pub(crate) mod evaluator; +pub(crate) mod halo2; + +pub(crate) fn gcd(mut n: usize, mut m: usize) -> usize { + assert_ne!(n, 0); + assert_ne!(m, 0); + + while n != m { + if n > m { + n -= m; + } else { + m -= n; + } + } + n +} + +pub(crate) fn lcm(n: usize, m: usize) -> usize { + n * m / gcd(n, m) +} + +pub(crate) fn log2_ceil(value: usize) -> usize { + assert_ne!(value, 0); + value.next_power_of_two().trailing_zeros() as usize +} + +pub(crate) fn div_ceil(numer: usize, denom: usize) -> usize { + (numer + denom - 1) / denom +} + +pub(crate) fn squares(base: F) -> impl Iterator { + iter::successors(Some(base), move |value| Some(value.square())) +} + +pub(crate) fn powers(base: F) -> impl Iterator { + iter::successors(Some(F::ONE), move |value| Some(base * value)) +} + +pub(crate) fn horner(values: &[T1], x: &T2) -> T1 +where + T1: Clone + for<'a> Mul<&'a T2, Output = T1> + for<'a> Add<&'a T1, Output = T1>, +{ + let mut values = values.iter().rev(); + let last = values.next().unwrap().clone(); + values.fold(last, |acc, value| acc * x + value) +} + +pub(crate) fn root_of_unity(n: usize) -> F { + assert!( + n.is_power_of_two(), + "Only power of 2 domain size is supported" + ); + let k = log2_ceil(n); + assert!(k <= F::S as usize); + squares(F::ROOT_OF_UNITY).nth(F::S as usize - k).unwrap() +} + +pub(crate) fn felt_from_bool(value: bool) -> F { + if value { + F::ONE + } else { + F::ZERO + } +} + +/// Copied and modified from `itertools` +macro_rules! chain { + () => { + core::iter::empty() + }; + ($first:expr $(,$rest:expr)* $(,)?) => {{ + let iter = core::iter::IntoIterator::into_iter($first); + $(let iter = core::iter::Iterator::chain(iter, core::iter::IntoIterator::into_iter($rest));)* + iter + }}; +} + +/// Copied and modified from `itertools` +macro_rules! izip { + (@closure $p:pat => $tup:expr) => { + |$p| $tup + }; + (@closure $p:pat => ($($tup:tt)*) ,$_iter:expr $(, $tail:expr)*) => { + $crate::util::izip!(@closure ($p, b) => ( $($tup)*, b ) $( , $tail )*) + }; + ($first:expr $(,)*) => { + core::iter::IntoIterator::into_iter($first) + }; + ($first:expr, $second:expr $(,)*) => { + $crate::util::izip!($first).zip($second) + }; + ($first:expr $(, $rest:expr)* $(,)*) => { + $crate::util::izip!($first) + $(.zip($rest))* + .map($crate::util::izip!(@closure a => (a) $(, $rest)*)) + }; +} + +pub(crate) use {chain, izip}; diff --git a/halo2_alt/src/util/evaluator.rs b/halo2_alt/src/util/evaluator.rs new file mode 100644 index 0000000000..12099ccd22 --- /dev/null +++ b/halo2_alt/src/util/evaluator.rs @@ -0,0 +1,402 @@ +use crate::{ + protocol::{Expression, PolynomialRef, Query}, + util::{izip, powers, squares}, +}; +use halo2_proofs::{ + arithmetic::parallelize, + halo2curves::ff::{BatchInvert, Field, WithSmallOrderMulGroup}, + poly::{Coeff, EvaluationDomain, ExtendedLagrangeCoeff, Polynomial, Rotation}, +}; +use std::{ + fmt::Debug, + ops::{Deref, Index}, +}; + +#[derive(Clone, Debug)] +pub(crate) struct Evaluator { + n: usize, + domain: EvaluationDomain, + magnification: i32, + extended_n: i32, + reg: ExpressionRegistry, + vanishing_invs: Vec, +} + +impl> Evaluator { + pub(crate) fn new(k: usize, constraint: &Expression) -> Self { + let domain = EvaluationDomain::::new(constraint.degree() as u32, k as u32); + let diff_k = domain.extended_k() - domain.k(); + let magnification = 1 << diff_k; + let extended_n = 1 << domain.extended_k(); + let reg = ExpressionRegistry::new([constraint]); + + let vanishing_invs = { + let zeta_to_n = [F::ZETA, F::ZETA.square()][(domain.k() & 1) as usize]; + let extended_omega_to_n = squares(domain.get_extended_omega()).nth(k).unwrap(); + let mut vanishing_invs = powers(extended_omega_to_n) + .map(|value| zeta_to_n * value - F::ONE) + .take(magnification) + .collect::>(); + vanishing_invs.batch_invert(); + vanishing_invs + }; + + Self { + n: 1 << k, + domain, + magnification: magnification as i32, + extended_n, + reg, + vanishing_invs, + } + } + + pub(crate) fn evaluate_quotient( + &self, + polys: &[&Polynomial], + challenges: &[F], + ) -> Polynomial { + let reg = &self.reg; + let polys = polys + .iter() + .map(|poly| PolynomialView::new(self.domain.extended_k() as usize, poly)) + .collect::>(); + let extended_omega = self.domain.get_extended_omega(); + + let mut q = self.domain.empty_extended(); + parallelize(&mut q[..], |q, start| { + let mut buf = reg.buf(challenges); + if reg.has_identity { + buf[reg.offsets.identity()] = F::ZETA * extended_omega.pow([start as u64]); + } + + izip!(start.., q).for_each(|(row, q)| { + izip!(&mut buf[reg.offsets().polys()..], reg.polys()).for_each(|(value, query)| { + *value = polys[query.index][self.rotated_row(row, query.rotation)]; + }); + izip!(reg.calcs(), reg.offsets().calcs()..) + .for_each(|(calc, idx)| buf[idx] = calc.calculate(&buf)); + + *q = *buf.last().unwrap() * self.vanishing_inv(row); + + if reg.has_identity { + buf[reg.offsets.identity()] *= extended_omega; + } + }); + }); + + let mut q = self.domain.extended_to_coeff(q); + q.truncate(self.domain.get_quotient_poly_degree() * self.n); + Polynomial::new(q) + } + + fn vanishing_inv(&self, row: usize) -> &F { + &self.vanishing_invs[row % self.vanishing_invs.len()] + } + + fn rotated_row(&self, row: usize, rotation: Rotation) -> usize { + ((row as i32 + self.magnification * rotation.0).rem_euclid(self.extended_n)) as usize + } +} + +#[derive(Clone, Debug)] +struct ExpressionRegistry { + offsets: Offsets, + constants: Vec, + challenges: Vec, + has_identity: bool, + polys: Vec, + raw_calcs: Vec>, + calcs: Vec>, + raw_outputs: Vec, + outputs: Vec, +} + +impl Default for ExpressionRegistry { + fn default() -> Self { + Self { + offsets: Default::default(), + constants: vec![F::ZERO, F::ONE, F::ONE.double()], + challenges: Default::default(), + has_identity: Default::default(), + polys: Default::default(), + raw_calcs: Default::default(), + calcs: Default::default(), + raw_outputs: Default::default(), + outputs: Default::default(), + } + } +} + +impl ExpressionRegistry { + fn new<'a>(expressions: impl IntoIterator>) -> Self { + let mut reg = Self::default(); + expressions + .into_iter() + .for_each(|expression| reg.register(expression)); + reg + } + + fn register(&mut self, expression: &Expression) { + let output = self.register_expression(expression); + self.offsets = Offsets::new( + self.constants.len(), + self.challenges.len(), + self.has_identity, + self.polys.len(), + ); + self.calcs = self + .raw_calcs + .iter() + .map(|calc| calc.indexed(&self.offsets)) + .collect(); + self.raw_outputs.push(output); + self.outputs = self + .raw_outputs + .iter() + .map(|output| output.indexed(&self.offsets)) + .collect(); + } + + fn offsets(&self) -> &Offsets { + &self.offsets + } + + fn polys(&self) -> &[Query] { + &self.polys + } + + fn calcs(&self) -> &[Calculation] { + &self.calcs + } + + fn buf(&self, challenges: &[F]) -> Vec { + let mut buf = vec![F::ZERO; self.offsets.calcs() + self.calcs.len()]; + buf[..self.constants.len()].copy_from_slice(&self.constants); + izip!(&mut buf[self.offsets.challenges()..], &self.challenges) + .for_each(|(buf, idx)| *buf = challenges[*idx]); + buf + } + + fn register_constant(&mut self, constant: &F) -> ValueSource { + ValueSource::Constant(position_or_insert(&mut self.constants, constant)) + } + + fn register_challenge(&mut self, idx: &usize) -> ValueSource { + ValueSource::Challenge(position_or_insert(&mut self.challenges, idx)) + } + + fn register_identity(&mut self) -> ValueSource { + self.has_identity = true; + ValueSource::Identity + } + + fn register_poly(&mut self, query: &Query) -> ValueSource { + ValueSource::Polynomial(position_or_insert(&mut self.polys, query)) + } + + fn register_calc(&mut self, calculation: Calculation) -> ValueSource { + ValueSource::Calculation(position_or_insert(&mut self.raw_calcs, &calculation)) + } + + fn register_expression(&mut self, expr: &Expression) -> ValueSource { + match expr { + Expression::Polynomial(term) => match term { + PolynomialRef::Constant(constant) => self.register_constant(constant), + PolynomialRef::Challenge(idx) => self.register_challenge(idx), + PolynomialRef::Identity => self.register_identity(), + PolynomialRef::Opaque(query) => self.register_poly(query), + PolynomialRef::Lagrange(_) => unreachable!(), + }, + Expression::Neg(value) => { + if let Expression::Polynomial(PolynomialRef::Constant(constant)) = value.deref() { + self.register_constant(&-*constant) + } else { + let value = self.register_expression(value); + if let ValueSource::Constant(idx) = value { + self.register_constant(&-self.constants[idx]) + } else { + self.register_calc(Calculation::Neg(value)) + } + } + } + Expression::Sum(lhs, rhs) => match (lhs.deref(), rhs.deref()) { + (minuend, Expression::Neg(subtrahend)) | (Expression::Neg(subtrahend), minuend) => { + let minuend = self.register_expression(minuend); + let subtrahend = self.register_expression(subtrahend); + match (minuend, subtrahend) { + (ValueSource::Constant(minuend), ValueSource::Constant(subtrahend)) => { + let output = self.constants[minuend] - self.constants[subtrahend]; + self.register_constant(&output) + } + (ValueSource::Constant(0), _) => { + self.register_calc(Calculation::Neg(subtrahend)) + } + (_, ValueSource::Constant(0)) => minuend, + _ => self.register_calc(Calculation::Sub(minuend, subtrahend)), + } + } + _ => { + let lhs = self.register_expression(lhs); + let rhs = self.register_expression(rhs); + match (lhs, rhs) { + (ValueSource::Constant(lhs), ValueSource::Constant(rhs)) => { + self.register_constant(&(self.constants[lhs] + self.constants[rhs])) + } + (ValueSource::Constant(0), other) | (other, ValueSource::Constant(0)) => { + other + } + _ => { + if lhs <= rhs { + self.register_calc(Calculation::Add(lhs, rhs)) + } else { + self.register_calc(Calculation::Add(rhs, lhs)) + } + } + } + } + }, + Expression::Product(lhs, rhs) => { + let lhs = self.register_expression(lhs); + let rhs = self.register_expression(rhs); + match (lhs, rhs) { + (ValueSource::Constant(0), _) | (_, ValueSource::Constant(0)) => { + ValueSource::Constant(0) + } + (ValueSource::Constant(1), other) | (other, ValueSource::Constant(1)) => other, + (ValueSource::Constant(2), other) | (other, ValueSource::Constant(2)) => { + self.register_calc(Calculation::Add(other, other)) + } + (lhs, rhs) => { + if lhs <= rhs { + self.register_calc(Calculation::Mul(lhs, rhs)) + } else { + self.register_calc(Calculation::Mul(rhs, lhs)) + } + } + } + } + } + } +} + +fn position_or_insert(values: &mut Vec, item: &T) -> usize { + if let Some(idx) = values.iter().position(|lhs| lhs == item) { + idx + } else { + let idx = values.len(); + values.push(item.clone()); + idx + } +} + +#[derive(Clone, Copy, Debug, Default)] +struct Offsets(usize, usize, usize, usize); + +impl Offsets { + fn new( + num_constants: usize, + num_challenges: usize, + has_identity: bool, + num_polys: usize, + ) -> Self { + let mut offset = Self::default(); + offset.0 = num_constants; + offset.1 = offset.0 + num_challenges; + offset.2 = offset.1 + has_identity as usize; + offset.3 = offset.2 + num_polys; + offset + } + + fn challenges(&self) -> usize { + self.0 + } + + fn identity(&self) -> usize { + self.1 + } + + fn polys(&self) -> usize { + self.2 + } + + fn calcs(&self) -> usize { + self.3 + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Calculation { + Neg(T), + Add(T, T), + Sub(T, T), + Mul(T, T), +} + +impl Calculation { + fn indexed(&self, offsets: &Offsets) -> Calculation { + use Calculation::*; + match self { + Neg(value) => Neg(value.indexed(offsets)), + Add(lhs, rhs) => Add(lhs.indexed(offsets), rhs.indexed(offsets)), + Sub(lhs, rhs) => Sub(lhs.indexed(offsets), rhs.indexed(offsets)), + Mul(lhs, rhs) => Mul(lhs.indexed(offsets), rhs.indexed(offsets)), + } + } +} + +impl Calculation { + fn calculate(&self, buf: &[F]) -> F { + use Calculation::*; + match self { + Neg(idx) => -buf[*idx], + Add(lhs, rhs) => buf[*lhs] + buf[*rhs], + Sub(lhs, rhs) => buf[*lhs] - buf[*rhs], + Mul(lhs, rhs) => buf[*lhs] * buf[*rhs], + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +enum ValueSource { + Constant(usize), + Challenge(usize), + Identity, + Polynomial(usize), + Calculation(usize), +} + +impl ValueSource { + fn indexed(&self, offsets: &Offsets) -> usize { + use ValueSource::*; + match self { + Constant(idx) => *idx, + Challenge(idx) => offsets.challenges() + idx, + Identity => offsets.identity(), + Polynomial(idx) => offsets.polys() + idx, + Calculation(idx) => offsets.calcs() + idx, + } + } +} + +struct PolynomialView<'a, F> { + poly: &'a Polynomial, + step: usize, +} + +impl<'a, F> PolynomialView<'a, F> { + fn new(extended_k: usize, poly: &'a Polynomial) -> Self { + Self { + poly, + step: poly.len() >> extended_k, + } + } +} + +impl<'a, F> Index for PolynomialView<'a, F> { + type Output = F; + + fn index(&self, index: usize) -> &Self::Output { + &self.poly[index * self.step] + } +} diff --git a/halo2_alt/src/util/halo2.rs b/halo2_alt/src/util/halo2.rs new file mode 100644 index 0000000000..b4e3a68fff --- /dev/null +++ b/halo2_alt/src/util/halo2.rs @@ -0,0 +1,437 @@ +use crate::{ + protocol::{Expression, PolynomialRef}, + util::izip, +}; +use halo2_proofs::{ + circuit::Value, + halo2curves::{ff::Field, CurveAffine}, + plonk::{ + self, Advice, Any, Assigned, Assignment, Challenge, Column, Error, Fixed, Instance, + Selector, + }, + poly::Polynomial, + transcript::{EncodedChallenge, TranscriptRead}, +}; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, + io, iter, mem, + ops::Range, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub(crate) enum ColumnType { + Selector, + Fixed, + Instance, + Advice, +} + +impl From for ColumnType { + fn from(any: Any) -> Self { + match any { + Any::Advice(_) => Self::Advice, + Any::Fixed => Self::Fixed, + Any::Instance => Self::Instance, + } + } +} + +pub(crate) fn convert_expressions<'a, F: Field>( + expressions: impl IntoIterator>, + column_idx: &HashMap<(ColumnType, usize), usize>, +) -> Vec> { + expressions + .into_iter() + .map(|expression| convert_expression(expression, column_idx)) + .collect() +} + +pub(crate) fn convert_expression( + expression: &plonk::Expression, + column_idx: &HashMap<(ColumnType, usize), usize>, +) -> Expression { + expression.evaluate::>( + &|constant| PolynomialRef::Constant(constant).into(), + &|selector| { + let poly = column_idx[&(ColumnType::Selector, selector.index())]; + PolynomialRef::Opaque((poly, 0).into()).into() + }, + &|query| { + let poly = column_idx[&(ColumnType::Fixed, query.column_index())]; + PolynomialRef::Opaque((poly, query.rotation().0).into()).into() + }, + &|query| { + let poly = column_idx[&(ColumnType::Advice, query.column_index())]; + PolynomialRef::Opaque((poly, query.rotation().0).into()).into() + }, + &|query| { + let poly = column_idx[&(ColumnType::Instance, query.column_index())]; + PolynomialRef::Opaque((poly, query.rotation().0).into()).into() + }, + &|_| unimplemented!(), + &|value| -value, + &|lhs, rhs| lhs + rhs, + &|lhs, rhs| lhs * rhs, + &|value, scalar| value * PolynomialRef::Constant(scalar), + ) +} + +pub(crate) fn batch_invert_assigned(assigned: Vec>>) -> Vec> { + halo2_proofs::poly::batch_invert_assigned(assigned.into_iter().map(Polynomial::new).collect()) + .into_iter() + .map(Polynomial::into_vec) + .collect() +} + +pub(crate) trait TranscriptReadVec>: + TranscriptRead +{ + fn read_points(&mut self, n: usize) -> io::Result> { + iter::repeat_with(|| self.read_point()).take(n).collect() + } + + fn read_scalars(&mut self, n: usize) -> io::Result> { + iter::repeat_with(|| self.read_scalar()).take(n).collect() + } +} + +impl, T: TranscriptRead> TranscriptReadVec + for T +{ +} + +#[derive(Debug)] +pub(crate) struct PreprocessCollector { + pub(crate) k: u32, + pub(crate) fixeds: Vec>>, + pub(crate) permutation: Permutation, + pub(crate) selectors: Vec>, + pub(crate) usable_rows: Range, +} + +impl Assignment for PreprocessCollector { + fn enter_region(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + } + + fn exit_region(&mut self) {} + + fn annotate_column(&mut self, _: A, _: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + } + + fn enable_selector(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error> + where + A: FnOnce() -> AR, + AR: Into, + { + if !self.usable_rows.contains(&row) { + return Err(Error::NotEnoughRowsAvailable { current_k: self.k }); + }; + + self.selectors[selector.index()][row] = true; + + Ok(()) + } + + fn query_instance(&self, _: Column, row: usize) -> Result, Error> { + if !self.usable_rows.contains(&row) { + return Err(Error::NotEnoughRowsAvailable { current_k: self.k }); + } + + Ok(Value::unknown()) + } + + fn assign_advice( + &mut self, + _: A, + _: Column, + _: usize, + _: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into, + { + Ok(()) + } + + fn assign_fixed( + &mut self, + _: A, + column: Column, + row: usize, + to: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into, + { + if !self.usable_rows.contains(&row) { + return Err(Error::NotEnoughRowsAvailable { current_k: self.k }); + }; + + *self + .fixeds + .get_mut(column.index()) + .and_then(|v| v.get_mut(row)) + .ok_or(Error::BoundsFailure)? = to().into_field().assign()?; + + Ok(()) + } + + fn copy( + &mut self, + lhs_column: Column, + lhs_row: usize, + rhs_column: Column, + rhs_row: usize, + ) -> Result<(), Error> { + if !self.usable_rows.contains(&lhs_row) { + return Err(Error::NotEnoughRowsAvailable { current_k: self.k }); + }; + if !self.usable_rows.contains(&rhs_row) { + return Err(Error::NotEnoughRowsAvailable { current_k: self.k }); + }; + self.permutation + .copy(lhs_column, lhs_row, rhs_column, rhs_row) + } + + fn fill_from_row( + &mut self, + column: Column, + from_row: usize, + to: Value>, + ) -> Result<(), Error> { + if !self.usable_rows.contains(&from_row) { + return Err(Error::NotEnoughRowsAvailable { current_k: self.k }); + }; + + let col = self + .fixeds + .get_mut(column.index()) + .ok_or(Error::BoundsFailure)?; + + let filler = to.assign()?; + for row in self.usable_rows.clone().skip(from_row) { + col[row] = filler; + } + + Ok(()) + } + + fn get_challenge(&self, _: Challenge) -> Value { + Value::unknown() + } + + fn push_namespace(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + } + + fn pop_namespace(&mut self, _: Option) {} +} + +#[derive(Debug)] +pub(crate) struct Permutation { + pub(crate) column_idx: HashMap, usize>, + pub(crate) cycles: Vec>, + pub(crate) cycle_idx: HashMap<(usize, usize), usize>, +} + +impl Permutation { + pub(crate) fn new(columns: Vec>) -> Self { + Self { + column_idx: izip!(columns, 0..).collect(), + cycles: Default::default(), + cycle_idx: Default::default(), + } + } + + fn copy( + &mut self, + lhs_column: Column, + lhs_row: usize, + rhs_column: Column, + rhs_row: usize, + ) -> Result<(), Error> { + let lhs_idx = *self + .column_idx + .get(&lhs_column) + .ok_or(Error::ColumnNotInPermutation(lhs_column))?; + let rhs_idx = *self + .column_idx + .get(&rhs_column) + .ok_or(Error::ColumnNotInPermutation(rhs_column))?; + + match ( + self.cycle_idx.get(&(lhs_idx, lhs_row)).copied(), + self.cycle_idx.get(&(rhs_idx, rhs_row)).copied(), + ) { + (Some(lhs_cycle_idx), Some(rhs_cycle_idx)) => { + for cell in self.cycles[rhs_cycle_idx].iter().copied() { + self.cycle_idx.insert(cell, lhs_cycle_idx); + } + let rhs_cycle = mem::take(&mut self.cycles[rhs_cycle_idx]); + self.cycles[lhs_cycle_idx].extend(rhs_cycle); + } + cycle_idx => { + let cycle_idx = if let (Some(cycle_idx), None) | (None, Some(cycle_idx)) = cycle_idx + { + cycle_idx + } else { + let cycle_idx = self.cycles.len(); + self.cycles.push(Default::default()); + cycle_idx + }; + for cell in [(lhs_idx, lhs_row), (rhs_idx, rhs_row)] { + self.cycles[cycle_idx].insert(cell); + self.cycle_idx.insert(cell, cycle_idx); + } + } + }; + + Ok(()) + } + + pub(crate) fn into_cycles(self) -> Vec> { + self.cycles + .into_iter() + .filter_map(|cycle| { + (!cycle.is_empty()).then(|| { + let mut cycle = cycle.into_iter().collect::>(); + cycle.sort(); + cycle + }) + }) + .collect() + } +} + +#[derive(Debug)] +pub(crate) struct WitnessCollector<'a, F: Field> { + pub(crate) k: u32, + pub(crate) phase: u8, + pub(crate) instance_values: &'a [&'a [F]], + pub(crate) advices: Vec>>, + pub(crate) usable_rows: Range, +} + +impl<'a, F: Field> Assignment for WitnessCollector<'a, F> { + fn enter_region(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + } + + fn exit_region(&mut self) {} + + fn annotate_column(&mut self, _: A, _: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + } + + fn enable_selector(&mut self, _: A, _: &Selector, _: usize) -> Result<(), Error> + where + A: FnOnce() -> AR, + AR: Into, + { + Ok(()) + } + + fn query_instance(&self, column: Column, row: usize) -> Result, Error> { + self.instance_values + .get(column.index()) + .and_then(|column| column.get(row)) + .map(|v| Value::known(*v)) + .ok_or(Error::BoundsFailure) + } + + fn assign_advice( + &mut self, + _: A, + column: Column, + row: usize, + to: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into, + { + if self.phase != column.column_type().phase() { + return Ok(()); + } + + if !self.usable_rows.contains(&row) { + return Err(Error::NotEnoughRowsAvailable { current_k: self.k }); + }; + + *self + .advices + .get_mut(column.index()) + .and_then(|v| v.get_mut(row)) + .ok_or(Error::BoundsFailure)? = to().into_field().assign()?; + + Ok(()) + } + + fn assign_fixed( + &mut self, + _: A, + _: Column, + _: usize, + _: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into, + { + Ok(()) + } + + fn copy(&mut self, _: Column, _: usize, _: Column, _: usize) -> Result<(), Error> { + Ok(()) + } + + fn fill_from_row( + &mut self, + _: Column, + _: usize, + _: Value>, + ) -> Result<(), Error> { + Ok(()) + } + + fn get_challenge(&self, _: Challenge) -> Value { + unimplemented!() + } + + fn push_namespace(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + } + + fn pop_namespace(&mut self, _: Option) {} +} diff --git a/halo2_proofs/src/circuit/value.rs b/halo2_proofs/src/circuit/value.rs index f3ea6a39ea..f091065d6b 100644 --- a/halo2_proofs/src/circuit/value.rs +++ b/halo2_proofs/src/circuit/value.rs @@ -45,7 +45,7 @@ impl Value { /// Obtains the inner value for assigning into the circuit. /// /// Returns `Error::Synthesis` if this is [`Value::unknown()`]. - pub(crate) fn assign(self) -> Result { + pub fn assign(self) -> Result { self.inner.ok_or(Error::Synthesis) } diff --git a/halo2_proofs/src/poly.rs b/halo2_proofs/src/poly.rs index 9cb6b149bc..2a2e2b524d 100644 --- a/halo2_proofs/src/poly.rs +++ b/halo2_proofs/src/poly.rs @@ -69,6 +69,21 @@ pub struct Polynomial { _marker: PhantomData, } +impl Polynomial { + /// Returns a `Polynomial` + pub fn new(values: Vec) -> Self { + Self { + values, + _marker: PhantomData, + } + } + + /// Returns `values` + pub fn into_vec(self) -> Vec { + self.values + } +} + impl Index for Polynomial { type Output = F; @@ -175,7 +190,8 @@ impl Polynomial { } } -pub(crate) fn batch_invert_assigned( +/// Batch invert `Vec, LagrangeCoeff>>` into `Vec>` +pub fn batch_invert_assigned( assigned: Vec, LagrangeCoeff>>, ) -> Vec> { let mut assigned_denominators: Vec<_> = assigned