From 92cd127cd42633391ebddd700e426d5f3779281e Mon Sep 17 00:00:00 2001 From: han0110 Date: Wed, 10 Jan 2024 09:52:19 +0000 Subject: [PATCH] feat: implement piop part --- halo2_proofs/src/arithmetic.rs | 2 +- halo2_proofs/src/fflonk.rs | 406 +++++++++ halo2_proofs/src/fflonk/evaluation.rs | 315 +++++++ halo2_proofs/src/fflonk/keygen.rs | 232 ++++++ halo2_proofs/src/fflonk/permutation.rs | 148 ++++ halo2_proofs/src/fflonk/prover.rs | 821 +++++++++++++++++++ halo2_proofs/src/lib.rs | 1 + halo2_proofs/src/plonk.rs | 36 +- halo2_proofs/src/plonk/circuit.rs | 2 +- halo2_proofs/src/plonk/evaluation.rs | 6 +- halo2_proofs/src/plonk/keygen.rs | 14 +- halo2_proofs/src/plonk/permutation.rs | 14 +- halo2_proofs/src/plonk/permutation/prover.rs | 2 +- halo2_proofs/src/poly.rs | 9 + 14 files changed, 1970 insertions(+), 38 deletions(-) create mode 100644 halo2_proofs/src/fflonk.rs create mode 100644 halo2_proofs/src/fflonk/evaluation.rs create mode 100644 halo2_proofs/src/fflonk/keygen.rs create mode 100644 halo2_proofs/src/fflonk/permutation.rs create mode 100644 halo2_proofs/src/fflonk/prover.rs diff --git a/halo2_proofs/src/arithmetic.rs b/halo2_proofs/src/arithmetic.rs index 0163e355eb..f85a9090d8 100644 --- a/halo2_proofs/src/arithmetic.rs +++ b/halo2_proofs/src/arithmetic.rs @@ -433,7 +433,7 @@ pub fn parallelize(v: &mu }); } -fn log2_floor(num: usize) -> u32 { +pub(crate) fn log2_floor(num: usize) -> u32 { assert!(num > 0); let mut pow = 0; diff --git a/halo2_proofs/src/fflonk.rs b/halo2_proofs/src/fflonk.rs new file mode 100644 index 0000000000..e01e2ca812 --- /dev/null +++ b/halo2_proofs/src/fflonk.rs @@ -0,0 +1,406 @@ +//! Docs + +use crate::{ + halo2curves::{ + ff::{Field, FromUniformBytes, PrimeField}, + CurveAffine, + }, + helpers::{ + polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, + SerdeCurveAffine, SerdePrimeField, + }, + plonk::{self, Circuit, ConstraintSystem, PinnedConstraintSystem}, + poly::{ + Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, PinnedEvaluationDomain, + Polynomial, + }, + transcript::{EncodedChallenge, Transcript}, + SerdeFormat, +}; +use blake2b_simd::Params as Blake2bParams; +use std::io; + +mod evaluation; +mod keygen; +mod permutation; +mod prover; + +use evaluation::*; +pub use keygen::*; +pub use prover::*; + +/// Docs +#[derive(Clone, Debug)] +pub struct VerifyingKey { + domain: EvaluationDomain, + cs: ConstraintSystem, + cs_degree: usize, + preprocessed_commitment: C, + transcript_repr: C::Scalar, + selectors: Vec>, + compress_selectors: bool, +} + +impl VerifyingKey +where + C::Scalar: SerdePrimeField + FromUniformBytes<64>, +{ + /// Docs + pub fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> { + writer.write_all(&[0x01])?; + writer.write_all(&self.domain.k().to_le_bytes())?; + writer.write_all(&[self.compress_selectors as u8])?; + self.preprocessed_commitment.write(writer, format)?; + + if !self.compress_selectors { + assert!(self.selectors.is_empty()); + } + + for selector in &self.selectors { + for bits in selector.chunks(8) { + writer.write_all(&[crate::helpers::pack(bits)])?; + } + } + Ok(()) + } + + /// Docs + pub fn read>( + reader: &mut R, + format: SerdeFormat, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + ) -> io::Result { + let mut version_byte = [0u8; 1]; + reader.read_exact(&mut version_byte)?; + if 0x02 != version_byte[0] { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "unexpected version byte", + )); + } + let mut k = [0u8; 4]; + reader.read_exact(&mut k)?; + let k = u32::from_le_bytes(k); + let mut compress_selectors = [0u8; 1]; + reader.read_exact(&mut compress_selectors)?; + if compress_selectors[0] != 0 && compress_selectors[0] != 1 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "unexpected compress_selectors not boolean", + )); + } + let compress_selectors = compress_selectors[0] == 1; + let (domain, cs, _) = plonk::keygen::create_domain::( + k, + #[cfg(feature = "circuit-params")] + params, + ); + + let preprocessed_commitment = C::read(reader, format)?; + + let (cs, selectors) = if compress_selectors { + let selectors: Vec> = vec![vec![false; 1 << k]; cs.num_selectors] + .into_iter() + .map(|mut selector| { + let mut selector_bytes = vec![0u8; (selector.len() + 7) / 8]; + reader.read_exact(&mut selector_bytes)?; + for (bits, byte) in selector.chunks_mut(8).zip(selector_bytes) { + crate::helpers::unpack(byte, bits); + } + Ok(selector) + }) + .collect::>()?; + let (cs, _) = cs.compress_selectors(selectors.clone()); + (cs, selectors) + } else { + let fake_selectors = vec![vec![]; cs.num_selectors]; + let (cs, _) = cs.directly_convert_selectors_to_fixed(fake_selectors); + (cs, vec![]) + }; + + Ok(Self::from_parts( + domain, + preprocessed_commitment, + cs, + selectors, + compress_selectors, + )) + } + + /// Docs + pub fn to_bytes(&self, format: SerdeFormat) -> Vec { + let mut bytes = Vec::::with_capacity(self.bytes_length(format)); + Self::write(self, &mut bytes, format).expect("Writing to vector should not fail"); + bytes + } + + /// Docs + pub fn from_bytes>( + mut bytes: &[u8], + format: SerdeFormat, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + ) -> io::Result { + Self::read::<_, ConcreteCircuit>( + &mut bytes, + format, + #[cfg(feature = "circuit-params")] + params, + ) + } +} + +impl VerifyingKey { + fn bytes_length(&self, format: SerdeFormat) -> usize + where + C: SerdeCurveAffine, + { + 10 + C::byte_length(format) + + self.selectors.len() + * (self + .selectors + .get(0) + .map(|selector| (selector.len() + 7) / 8) + .unwrap_or(0)) + } + + fn from_parts( + domain: EvaluationDomain, + preprocessed_commitment: C, + cs: ConstraintSystem, + selectors: Vec>, + compress_selectors: bool, + ) -> Self + where + C::ScalarExt: FromUniformBytes<64>, + { + let cs_degree = cs.degree(); + + let mut vk = Self { + domain, + preprocessed_commitment, + cs, + cs_degree, + + transcript_repr: C::Scalar::ZERO, + selectors, + compress_selectors, + }; + + let mut hasher = Blake2bParams::new() + .hash_length(64) + .personal(b"Halo2-Verify-Key") + .to_state(); + + let s = format!("{:?}", vk.pinned()); + + hasher.update(&(s.len() as u64).to_le_bytes()); + hasher.update(s.as_bytes()); + + vk.transcript_repr = C::Scalar::from_uniform_bytes(hasher.finalize().as_array()); + + vk + } + + /// Docs + pub fn hash_into, T: Transcript>( + &self, + transcript: &mut T, + ) -> io::Result<()> { + transcript.common_scalar(self.transcript_repr)?; + + Ok(()) + } + + /// Docs + pub fn pinned(&self) -> PinnedVerificationKey<'_, C> { + PinnedVerificationKey { + base_modulus: C::Base::MODULUS, + scalar_modulus: C::Scalar::MODULUS, + domain: self.domain.pinned(), + preprocessed_commitment: &self.preprocessed_commitment, + cs: self.cs.pinned(), + } + } + + /// Docs + pub fn preprocessed_commitment(&self) -> &C { + &self.preprocessed_commitment + } + + /// Docs + pub fn cs(&self) -> &ConstraintSystem { + &self.cs + } + + /// Docs + pub fn transcript_repr(&self) -> C::Scalar { + self.transcript_repr + } +} + +/// Docs +#[allow(dead_code)] +#[derive(Debug)] +pub struct PinnedVerificationKey<'a, C: CurveAffine> { + base_modulus: &'static str, + scalar_modulus: &'static str, + domain: PinnedEvaluationDomain<'a, C::Scalar>, + cs: PinnedConstraintSystem<'a, C::Scalar>, + preprocessed_commitment: &'a C, +} + +/// Docs +#[derive(Clone, Debug)] +pub struct ProvingKey { + vk: VerifyingKey, + l0: Polynomial, + l_last: Polynomial, + l_active_row: Polynomial, + fixed_values: Vec>, + fixed_polys: Vec>, + fixed_cosets: Vec>, + permutation: plonk::permutation::ProvingKey, + ev: Evaluator, +} + +impl ProvingKey +where + C::Scalar: FromUniformBytes<64>, +{ + /// Docs + pub fn get_vk(&self) -> &VerifyingKey { + &self.vk + } + + fn bytes_length(&self, format: SerdeFormat) -> usize + where + C: SerdeCurveAffine, + { + let scalar_len = C::Scalar::default().to_repr().as_ref().len(); + self.vk.bytes_length(format) + + 12 + + scalar_len * (self.l0.len() + self.l_last.len() + self.l_active_row.len()) + + polynomial_slice_byte_length(&self.fixed_values) + + polynomial_slice_byte_length(&self.fixed_polys) + + polynomial_slice_byte_length(&self.fixed_cosets) + + self.permutation.bytes_length() + } +} + +impl ProvingKey +where + C::Scalar: SerdePrimeField + FromUniformBytes<64>, +{ + /// Docs + pub fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> { + self.vk.write(writer, format)?; + self.l0.write(writer, format)?; + self.l_last.write(writer, format)?; + self.l_active_row.write(writer, format)?; + write_polynomial_slice(&self.fixed_values, writer, format)?; + write_polynomial_slice(&self.fixed_polys, writer, format)?; + write_polynomial_slice(&self.fixed_cosets, writer, format)?; + self.permutation.write(writer, format)?; + Ok(()) + } + + /// Docs + pub fn read>( + reader: &mut R, + format: SerdeFormat, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + ) -> io::Result { + let vk = VerifyingKey::::read::( + reader, + format, + #[cfg(feature = "circuit-params")] + params, + )?; + let l0 = Polynomial::read(reader, format)?; + let l_last = Polynomial::read(reader, format)?; + let l_active_row = Polynomial::read(reader, format)?; + let fixed_values = read_polynomial_vec(reader, format)?; + let fixed_polys = read_polynomial_vec(reader, format)?; + let fixed_cosets = read_polynomial_vec(reader, format)?; + let permutation = plonk::permutation::ProvingKey::read(reader, format)?; + let ev = Evaluator::new(vk.cs()); + Ok(Self { + vk, + l0, + l_last, + l_active_row, + fixed_values, + fixed_polys, + fixed_cosets, + permutation, + ev, + }) + } + + /// Docs + pub fn to_bytes(&self, format: SerdeFormat) -> Vec { + let mut bytes = Vec::::with_capacity(self.bytes_length(format)); + Self::write(self, &mut bytes, format).expect("Writing to vector should not fail"); + bytes + } + + /// Docs + pub fn from_bytes>( + mut bytes: &[u8], + format: SerdeFormat, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + ) -> io::Result { + Self::read::<_, ConcreteCircuit>( + &mut bytes, + format, + #[cfg(feature = "circuit-params")] + params, + ) + } +} + +impl VerifyingKey { + /// Docs + pub fn get_domain(&self) -> &EvaluationDomain { + &self.domain + } +} + +fn merge_polys<'a, F: Field>( + polys: impl IntoIterator>, +) -> Polynomial { + let polys = polys.into_iter().collect::>(); + let size = polys + .iter() + .map(|poly| poly.len()) + .max() + .unwrap_or_default() + * polys.len(); + let mut merged = Polynomial::new(vec![F::ZERO; size]); + polys.iter().enumerate().for_each(|(idx, poly)| { + merged[idx..] + .iter_mut() + .step_by(polys.len()) + .zip(poly.iter()) + .for_each(|(lhs, rhs)| *lhs = *rhs) + }); + merged +} + +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 +} + +fn lcm(n: usize, m: usize) -> usize { + n * m / gcd(n, m) +} diff --git a/halo2_proofs/src/fflonk/evaluation.rs b/halo2_proofs/src/fflonk/evaluation.rs new file mode 100644 index 0000000000..7bae662f69 --- /dev/null +++ b/halo2_proofs/src/fflonk/evaluation.rs @@ -0,0 +1,315 @@ +use crate::{ + fflonk::ProvingKey, + halo2curves::{ + ff::{Field, PrimeField, WithSmallOrderMulGroup}, + CurveAffine, + }, + multicore, + plonk::{ + evaluation::{get_rotation_idx, GraphEvaluator}, + permutation, Any, ConstraintSystem, + }, + poly::{Coeff, EvaluationDomain, ExtendedLagrangeCoeff, Polynomial}, +}; +use std::collections::HashMap; + +#[derive(Clone, Default, Debug)] +pub(crate) struct Evaluator { + log2_cs_degree: usize, + phase_evs: Vec)>>, +} + +impl Evaluator { + pub(crate) fn new(cs: &ConstraintSystem) -> Self { + assert!(!cs.advice_column_phase().iter().any(|phase| *phase != 0)); + + let log2_cs_degree = log2_ceil(cs.degree().saturating_sub(1)); + + let evs = cs + .gates + .iter() + .flat_map(|gate| { + gate.polynomials() + .iter() + .map(|poly| { + let mut ev = GraphEvaluator::default(); + ev.add_expression(poly); + (poly.degree() as u32, ev) + }) + .collect::>() + }) + .collect(); + + Self { + log2_cs_degree, + phase_evs: vec![evs], + } + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn evaluate_hs( + &self, + pk: &ProvingKey, + phase: usize, + advices: &[&[Polynomial]], + instances: &[&[Polynomial]], + _challenges: &HashMap, + ) -> Vec> { + let num_threads = multicore::current_num_threads(); + let k = pk.vk.domain.k(); + let rot_scale = 1 << (pk.vk.domain.extended_k() - pk.vk.domain.k()); + let fixed = &pk.fixed_cosets[..]; + let zero = C::ScalarExt::ZERO; + let isize = pk.vk.domain.extended_len() as i32; + advices + .iter() + .zip(instances) + .flat_map(|(advice, instance)| { + self.phase_evs[phase].iter().map(move |(degree, ev)| { + let domain = EvaluationDomain::new(*degree, k); + let step = + 1 << (self.log2_cs_degree - log2_ceil(degree.saturating_sub(1) as usize)); + + let mut values = domain.empty_extended(); + multicore::scope(|scope| { + let chunk_size = (values.len() + num_threads - 1) / num_threads; + for (thread_idx, values) in values.chunks_mut(chunk_size).enumerate() { + let start = thread_idx * chunk_size; + scope.spawn(move |_| { + let mut eval_data = ev.instance(); + for (i, value) in values.iter_mut().enumerate() { + let idx = (start + i) * step; + *value = ev.evaluate( + &mut eval_data, + fixed, + advice, + instance, + &[], + &zero, + &zero, + &zero, + &zero, + value, + idx, + rot_scale, + isize, + ); + } + }); + } + }); + + Polynomial::new( + domain.extended_to_coeff(domain.divide_by_vanishing_poly(values)), + ) + }) + }) + .collect() + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn evaluate_last_hs( + &self, + pk: &ProvingKey, + advices: &[&[Polynomial]], + instances: &[&[Polynomial]], + _challenges: &[C::ScalarExt], + beta: C::ScalarExt, + gamma: C::ScalarExt, + permutations: &[permutation::prover::Committed], + ) -> Vec> { + let num_threads = multicore::current_num_threads(); + let k = pk.vk.domain.k(); + let rot_scale = 1 << (pk.vk.domain.extended_k() - pk.vk.domain.k()); + let fixed = &pk.fixed_cosets[..]; + let isize = pk.vk.domain.extended_len() as i32; + let one = C::ScalarExt::ONE; + let l0 = &pk.l0; + let l_last = &pk.l_last; + let l_active_row = &pk.l_active_row; + let p = &pk.vk.cs.permutation; + + advices + .iter() + .zip(instances) + .zip(permutations) + .flat_map(|((advice, instance), permutation)| { + let mut h = vec![]; + + let sets = &permutation.sets; + if !sets.is_empty() { + let blinding_factors = pk.vk.cs.blinding_factors(); + let last_rotation = -((blinding_factors + 1) as i32); + let chunk_len = pk.vk.cs.degree() - 2; + + let first_set = sets.first().unwrap(); + let last_set = sets.last().unwrap(); + + // l_0(X) * (1 - z_0(X)) = 0 + { + let domain = EvaluationDomain::new(2, k); + let step = 1 << self.log2_cs_degree; + let mut values = domain.empty_extended(); + multicore::scope(|scope| { + let chunk_size = (values.len() + num_threads - 1) / num_threads; + for (thread_idx, values) in values.chunks_mut(chunk_size).enumerate() { + let start = thread_idx * chunk_size; + scope.spawn(move |_| { + for (i, value) in values.iter_mut().enumerate() { + let idx = (start + i) * step; + *value = (one - first_set.permutation_product_coset[idx]) + * l0[idx]; + } + }); + } + }); + h.push(Polynomial::new( + domain.extended_to_coeff(domain.divide_by_vanishing_poly(values)), + )) + } + + // l_last(X) * (z_l(X)^2 - z_l(X)) = 0 + { + let domain = EvaluationDomain::new(3, k); + let step = 1 << (self.log2_cs_degree - 1); + let mut values = domain.empty_extended(); + multicore::scope(|scope| { + let chunk_size = (values.len() + num_threads - 1) / num_threads; + for (thread_idx, values) in values.chunks_mut(chunk_size).enumerate() { + let start = thread_idx * chunk_size; + scope.spawn(move |_| { + for (i, value) in values.iter_mut().enumerate() { + let idx = (start + i) * step; + *value = (last_set.permutation_product_coset[idx] + * last_set.permutation_product_coset[idx] + - last_set.permutation_product_coset[idx]) + * l_last[idx]; + } + }); + } + }); + h.push(Polynomial::new( + domain.extended_to_coeff(domain.divide_by_vanishing_poly(values)), + )) + } + + // l_0(X) * (z_i(X) - z_{i-1}(\omega^(last) X)) = 0 + for (set_idx, set) in sets.iter().enumerate() { + if set_idx != 0 { + let domain = EvaluationDomain::new(2, k); + let step = 1 << self.log2_cs_degree; + let mut values = domain.empty_extended(); + multicore::scope(|scope| { + let chunk_size = (values.len() + num_threads - 1) / num_threads; + for (thread_idx, values) in + values.chunks_mut(chunk_size).enumerate() + { + let start = thread_idx * chunk_size; + scope.spawn(move |_| { + for (i, value) in values.iter_mut().enumerate() { + let idx = (start + i) * step; + let r_last = get_rotation_idx( + idx, + last_rotation, + rot_scale, + isize, + ); + + *value = (set.permutation_product_coset[idx] + - permutation.sets[set_idx - 1] + .permutation_product_coset[r_last]) + * l0[idx]; + } + }); + } + }); + h.push(Polynomial::new( + domain.extended_to_coeff(domain.divide_by_vanishing_poly(values)), + )) + } + } + + // (1 - (l_last(X) + l_blind(X))) * ( + // z_i(\omega X) \prod_j (p(X) + \beta s_j(X) + \gamma) + // - z_i(X) \prod_j (p(X) + \delta^j \beta X + \gamma) + // ) + { + for (set_idx, ((set, columns), cosets)) in sets + .iter() + .zip(p.columns.chunks(chunk_len)) + .zip(pk.permutation.cosets.chunks(chunk_len)) + .enumerate() + { + let degree = (columns.len() + 2) as u32; + let domain = EvaluationDomain::::new(degree, k); + let extended_omega = domain.get_extended_omega(); + let step = 1 + << (self.log2_cs_degree + - log2_ceil(degree.saturating_sub(1) as usize)); + let mut values = domain.empty_extended(); + multicore::scope(|scope| { + let chunk_size = (values.len() + num_threads - 1) / num_threads; + for (thread_idx, values) in + values.chunks_mut(chunk_size).enumerate() + { + let start = thread_idx * chunk_size; + scope.spawn(move |_| { + let mut beta_term = beta + * C::Scalar::ZETA + * C::Scalar::DELTA + .pow_vartime([(set_idx * chunk_len) as u64]) + * extended_omega.pow_vartime([start as u64]); + for (i, value) in values.iter_mut().enumerate() { + let idx = (start + i) * step; + let r_next = get_rotation_idx(idx, 1, rot_scale, isize); + + let mut left = set.permutation_product_coset[r_next]; + for (values, permutation) in columns + .iter() + .map(|&column| match column.column_type() { + Any::Advice(_) => &advice[column.index()], + Any::Fixed => &fixed[column.index()], + Any::Instance => &instance[column.index()], + }) + .zip(cosets.iter()) + { + left *= + values[idx] + beta * permutation[idx] + gamma; + } + + let mut current_delta = beta_term; + let mut right = set.permutation_product_coset[idx]; + for values in columns.iter().map(|&column| match column + .column_type() + { + Any::Advice(_) => &advice[column.index()], + Any::Fixed => &fixed[column.index()], + Any::Instance => &instance[column.index()], + }) { + right *= values[idx] + current_delta + gamma; + current_delta *= &C::Scalar::DELTA; + } + + *value = (left - right) * l_active_row[idx]; + + beta_term *= &extended_omega; + } + }); + } + }); + h.push(Polynomial::new( + domain.extended_to_coeff(domain.divide_by_vanishing_poly(values)), + )) + } + } + } + + h + }) + .collect() + } +} + +fn log2_ceil(n: usize) -> usize { + (n.next_power_of_two() as f64).log2() as usize +} diff --git a/halo2_proofs/src/fflonk/keygen.rs b/halo2_proofs/src/fflonk/keygen.rs new file mode 100644 index 0000000000..47c893f3a5 --- /dev/null +++ b/halo2_proofs/src/fflonk/keygen.rs @@ -0,0 +1,232 @@ +use crate::{ + arithmetic::{parallelize, CurveAffine}, + fflonk::{merge_polys, Evaluator, ProvingKey, VerifyingKey}, + plonk::{ + circuit::{Circuit, ConstraintSystem, FloorPlanner}, + create_domain, permutation, Assembly, Error, + }, + poly::{ + batch_invert_assigned, + commitment::{Blind, Params, ParamsProver}, + }, +}; +use ff::{Field, FromUniformBytes}; +use group::Curve; + +/// Generate a `VerifyingKey` from an instance of `Circuit`. +/// By default, selector compression is turned **off**. +pub fn keygen_vk<'params, C, P, ConcreteCircuit>( + k: u32, + params: &P, + circuit: &ConcreteCircuit, +) -> Result, Error> +where + C: CurveAffine, + P: ParamsProver<'params, C>, + ConcreteCircuit: Circuit, + C::Scalar: FromUniformBytes<64>, +{ + keygen_vk_custom(k, params, circuit, true) +} + +/// Generate a `VerifyingKey` from an instance of `Circuit`. +/// +/// The selector compression optimization is turned on only if `compress_selectors` is `true`. +pub fn keygen_vk_custom<'params, C, P, ConcreteCircuit>( + k: u32, + params: &P, + circuit: &ConcreteCircuit, + compress_selectors: bool, +) -> Result, Error> +where + C: CurveAffine, + P: ParamsProver<'params, C>, + ConcreteCircuit: Circuit, + C::Scalar: FromUniformBytes<64>, +{ + let n = 1 << k; + + // TODO: check params.n() > t * n for each t + + let (domain, cs, config) = create_domain::( + k, + #[cfg(feature = "circuit-params")] + circuit.params(), + ); + + if (n as usize) < cs.minimum_rows() { + return Err(Error::not_enough_rows_available(k)); + } + + let mut assembly: Assembly = Assembly { + k, + fixed: vec![domain.empty_lagrange_assigned(); cs.num_fixed_columns], + permutation: permutation::keygen::Assembly::new(n as usize, &cs.permutation), + selectors: vec![vec![false; n as usize]; cs.num_selectors], + usable_rows: 0..n as usize - (cs.blinding_factors() + 1), + _marker: std::marker::PhantomData, + }; + + // Synthesize the circuit to obtain URS + ConcreteCircuit::FloorPlanner::synthesize( + &mut assembly, + circuit, + config, + cs.constants.clone(), + )?; + + let mut fixed = batch_invert_assigned(assembly.fixed); + let (cs, selector_polys) = if compress_selectors { + cs.compress_selectors(assembly.selectors.clone()) + } else { + // After this, the ConstraintSystem should not have any selectors: `verify` does not need them, and `keygen_pk` regenerates `cs` from scratch anyways. + let selectors = std::mem::take(&mut assembly.selectors); + cs.directly_convert_selectors_to_fixed(selectors) + }; + fixed.extend( + selector_polys + .into_iter() + .map(|poly| domain.lagrange_from_vec(poly)), + ); + let mut preprocessed_polys = fixed + .into_iter() + .map(|value| domain.lagrange_to_coeff(value)) + .collect::>(); + preprocessed_polys.extend( + assembly + .permutation + .build_pk(params, &domain, &cs.permutation) + .polys, + ); + + let preprocessed_poly = merge_polys(&preprocessed_polys); + let preprocessed_commitment = params + .commit(&preprocessed_poly, Blind::default()) + .to_affine(); + + Ok(VerifyingKey::from_parts( + domain, + preprocessed_commitment, + cs, + assembly.selectors, + compress_selectors, + )) +} + +/// Generate a `ProvingKey` from a `VerifyingKey` and an instance of `Circuit`. +pub fn keygen_pk<'params, C, P, ConcreteCircuit>( + k: u32, + params: &P, + vk: VerifyingKey, + circuit: &ConcreteCircuit, +) -> Result, Error> +where + C: CurveAffine, + P: Params<'params, C>, + ConcreteCircuit: Circuit, +{ + let n = 1 << k; + + let mut cs = ConstraintSystem::default(); + #[cfg(feature = "circuit-params")] + let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params()); + #[cfg(not(feature = "circuit-params"))] + let config = ConcreteCircuit::configure(&mut cs); + + let cs = cs; + + if (n as usize) < cs.minimum_rows() { + return Err(Error::not_enough_rows_available(k)); + } + + let mut assembly: Assembly = Assembly { + k, + fixed: vec![vk.domain.empty_lagrange_assigned(); cs.num_fixed_columns], + permutation: permutation::keygen::Assembly::new(n as usize, &cs.permutation), + selectors: vec![vec![false; n as usize]; cs.num_selectors], + usable_rows: 0..n as usize - (cs.blinding_factors() + 1), + _marker: std::marker::PhantomData, + }; + + // Synthesize the circuit to obtain URS + ConcreteCircuit::FloorPlanner::synthesize( + &mut assembly, + circuit, + config, + cs.constants.clone(), + )?; + + let mut fixed = batch_invert_assigned(assembly.fixed); + let (cs, selector_polys) = if vk.compress_selectors { + cs.compress_selectors(assembly.selectors) + } else { + cs.directly_convert_selectors_to_fixed(assembly.selectors) + }; + fixed.extend( + selector_polys + .into_iter() + .map(|poly| vk.domain.lagrange_from_vec(poly)), + ); + + let fixed_polys: Vec<_> = fixed + .iter() + .map(|poly| vk.domain.lagrange_to_coeff(poly.clone())) + .collect(); + + let fixed_cosets = fixed_polys + .iter() + .map(|poly| vk.domain.coeff_to_extended(poly.clone())) + .collect(); + + let permutation_pk = assembly + .permutation + .build_pk(params, &vk.domain, &cs.permutation); + + // Compute l_0(X) + // TODO: this can be done more efficiently + let mut l0 = vk.domain.empty_lagrange(); + l0[0] = C::Scalar::ONE; + let l0 = vk.domain.lagrange_to_coeff(l0); + let l0 = vk.domain.coeff_to_extended(l0); + + // Compute l_blind(X) which evaluates to 1 for each blinding factor row + // and 0 otherwise over the domain. + let mut l_blind = vk.domain.empty_lagrange(); + for evaluation in l_blind[..].iter_mut().rev().take(cs.blinding_factors()) { + *evaluation = C::Scalar::ONE; + } + let l_blind = vk.domain.lagrange_to_coeff(l_blind); + let l_blind = vk.domain.coeff_to_extended(l_blind); + + // Compute l_last(X) which evaluates to 1 on the first inactive row (just + // before the blinding factors) and 0 otherwise over the domain + let mut l_last = vk.domain.empty_lagrange(); + l_last[n as usize - cs.blinding_factors() - 1] = C::Scalar::ONE; + let l_last = vk.domain.lagrange_to_coeff(l_last); + let l_last = vk.domain.coeff_to_extended(l_last); + + // Compute l_active_row(X) + let one = C::Scalar::ONE; + let mut l_active_row = vk.domain.empty_extended(); + parallelize(&mut l_active_row, |values, start| { + for (i, value) in values.iter_mut().enumerate() { + let idx = i + start; + *value = one - (l_last[idx] + l_blind[idx]); + } + }); + + // Compute the optimized evaluation data structure + let ev = Evaluator::new(&vk.cs); + + Ok(ProvingKey { + vk, + l0, + l_last, + l_active_row, + fixed_values: fixed, + fixed_polys, + fixed_cosets, + permutation: permutation_pk, + ev, + }) +} diff --git a/halo2_proofs/src/fflonk/permutation.rs b/halo2_proofs/src/fflonk/permutation.rs new file mode 100644 index 0000000000..02a19c3c83 --- /dev/null +++ b/halo2_proofs/src/fflonk/permutation.rs @@ -0,0 +1,148 @@ +use crate::{ + arithmetic::{parallelize, CurveAffine}, + fflonk, + halo2curves::ff::{BatchInvert, Field, PrimeField}, + plonk::{ + circuit::Any, + permutation::{ + prover::{Committed, CommittedSet}, + Argument, ProvingKey, + }, + ChallengeBeta, ChallengeGamma, Error, + }, + poly::{commitment::Blind, LagrangeCoeff, Polynomial}, +}; +use rand_core::RngCore; + +impl Argument { + #[allow(clippy::too_many_arguments)] + pub(crate) fn compute_z( + &self, + pk: &fflonk::ProvingKey, + pkey: &ProvingKey, + advice: &[Polynomial], + fixed: &[Polynomial], + instance: &[Polynomial], + beta: ChallengeBeta, + gamma: ChallengeGamma, + mut rng: R, + ) -> Result, Error> { + let domain = &pk.vk.domain; + let n = 1 << pk.vk.domain.k(); + + // How many columns can be included in a single permutation polynomial? + // We need to multiply by z(X) and (1 - (l_last(X) + l_blind(X))). This + // will never underflow because of the requirement of at least a degree + // 3 circuit for the permutation argument. + assert!(pk.vk.cs_degree >= 3); + let chunk_len = pk.vk.cs_degree - 2; + let blinding_factors = pk.vk.cs.blinding_factors(); + + // Each column gets its own delta power. + let mut deltaomega = C::Scalar::ONE; + + // Track the "last" value from the previous column set + let mut last_z = C::Scalar::ONE; + + let mut sets = vec![]; + + for (columns, permutations) in self + .columns + .chunks(chunk_len) + .zip(pkey.permutations.chunks(chunk_len)) + { + // Goal is to compute the products of fractions + // + // (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) / + // (p_j(\omega^i) + \beta s_j(\omega^i) + \gamma) + // + // where p_j(X) is the jth column in this permutation, + // and i is the ith row of the column. + + let mut modified_values = vec![C::Scalar::ONE; n]; + + // Iterate over each column of the permutation + for (&column, permuted_column_values) in columns.iter().zip(permutations.iter()) { + let values = match column.column_type() { + Any::Advice(_) => advice, + Any::Fixed => fixed, + Any::Instance => instance, + }; + parallelize(&mut modified_values, |modified_values, start| { + for ((modified_values, value), permuted_value) in modified_values + .iter_mut() + .zip(values[column.index()][start..].iter()) + .zip(permuted_column_values[start..].iter()) + { + *modified_values *= &(*beta * permuted_value + &*gamma + value); + } + }); + } + + // Invert to obtain the denominator for the permutation product polynomial + modified_values.batch_invert(); + + // Iterate over each column again, this time finishing the computation + // of the entire fraction by computing the numerators + for &column in columns.iter() { + let omega = domain.get_omega(); + let values = match column.column_type() { + Any::Advice(_) => advice, + Any::Fixed => fixed, + Any::Instance => instance, + }; + parallelize(&mut modified_values, |modified_values, start| { + let mut deltaomega = deltaomega * &omega.pow_vartime([start as u64, 0, 0, 0]); + for (modified_values, value) in modified_values + .iter_mut() + .zip(values[column.index()][start..].iter()) + { + // Multiply by p_j(\omega^i) + \delta^j \omega^i \beta + *modified_values *= &(deltaomega * &*beta + &*gamma + value); + deltaomega *= ω + } + }); + deltaomega *= &::DELTA; + } + + // The modified_values vector is a vector of products of fractions + // of the form + // + // (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) / + // (p_j(\omega^i) + \beta s_j(\omega^i) + \gamma) + // + // where i is the index into modified_values, for the jth column in + // the permutation + + // Compute the evaluations of the permutation product polynomial + // over our domain, starting with z[0] = 1 + let mut z = vec![last_z]; + for row in 1..n { + let mut tmp = z[row - 1]; + + tmp *= &modified_values[row - 1]; + z.push(tmp); + } + let mut z = domain.lagrange_from_vec(z); + // Set blinding factors + for z in &mut z[n - blinding_factors..] { + *z = C::Scalar::random(&mut rng); + } + // Set new last_z + last_z = z[n - (blinding_factors + 1)]; + + let z = domain.lagrange_to_coeff(z); + let permutation_product_poly = z.clone(); + + let permutation_product_coset = domain.coeff_to_extended(z.clone()); + + sets.push(CommittedSet { + permutation_product_poly, + permutation_product_coset, + permutation_product_blind: Blind::default(), + }); + } + + Ok(Committed { sets }) + } +} diff --git a/halo2_proofs/src/fflonk/prover.rs b/halo2_proofs/src/fflonk/prover.rs new file mode 100644 index 0000000000..eb4abcb693 --- /dev/null +++ b/halo2_proofs/src/fflonk/prover.rs @@ -0,0 +1,821 @@ +//! Docs + +use crate::{ + arithmetic::eval_polynomial, + circuit::Value, + fflonk::{lcm, merge_polys, ProvingKey}, + halo2curves::{ + ff::{Field, FromUniformBytes, PrimeField, WithSmallOrderMulGroup}, + group::{prime::PrimeCurveAffine, Curve}, + CurveAffine, + }, + plonk::{ + permutation, sealed, Advice, Any, Assigned, Assignment, Challenge, ChallengeBeta, + ChallengeGamma, ChallengeX, Circuit, Column, ConstraintSystem, Error, Fixed, FloorPlanner, + Instance, Selector, + }, + poly::{ + batch_invert_assigned, + commitment::{Blind, CommitmentScheme, Params, ParamsProver, Prover}, + Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation, + }, + transcript::{EncodedChallenge, TranscriptWrite}, +}; +use rand_core::RngCore; +use std::{ + collections::{BTreeSet, HashMap, HashSet}, + ops::RangeTo, +}; + +/// Create proof +pub fn create_proof<'a, PCS, P, E, C>( + params: &'a PCS::ParamsProver, + pk: &ProvingKey, + circuits: &[C], + instances: &[&[&[PCS::Scalar]]], + mut rng: impl RngCore, + transcript: &mut impl TranscriptWrite, +) -> Result<(), Error> +where + PCS: CommitmentScheme, + PCS::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + P: Prover<'a, PCS>, + E: EncodedChallenge, + C: Circuit, +{ + if circuits.len() != instances.len() { + return Err(Error::InvalidInstances); + } + + for instance in instances.iter() { + if instance.len() != pk.vk.cs.num_instance_columns { + return Err(Error::InvalidInstances); + } + } + + // Hash verification key into transcript + pk.vk.hash_into(transcript)?; + + let domain = &pk.vk.domain; + let mut meta = ConstraintSystem::default(); + #[cfg(feature = "circuit-params")] + let config = C::configure_with_params(&mut meta, circuits[0].params()); + #[cfg(not(feature = "circuit-params"))] + let config = C::configure(&mut meta); + + // Selector optimizations cannot be applied here; use the ConstraintSystem + // from the verification key. + let meta = &pk.vk.cs; + + struct InstanceSingle { + pub instance_values: Vec>, + pub _instance_polys: Vec>, + pub instance_extendeds: Vec>, + } + + let instance: Vec> = instances + .iter() + .map(|instance| -> Result, Error> { + let instance_values = instance + .iter() + .map(|values| { + let mut poly = domain.empty_lagrange(); + // assert_eq!(poly.len(), params.n() as usize); + if values.len() > (poly.len() - (meta.blinding_factors() + 1)) { + return Err(Error::InstanceTooLarge); + } + for (poly, value) in poly.iter_mut().zip(values.iter()) { + if !P::QUERY_INSTANCE { + transcript.common_scalar(*value)?; + } + *poly = *value; + } + Ok(poly) + }) + .collect::, _>>()?; + + if P::QUERY_INSTANCE { + let instance_commitments_projective: Vec<_> = instance_values + .iter() + .map(|poly| params.commit_lagrange(poly, Blind::default())) + .collect(); + let mut instance_commitments = + vec![PCS::Curve::identity(); instance_commitments_projective.len()]; + ::CurveExt::batch_normalize( + &instance_commitments_projective, + &mut instance_commitments, + ); + let instance_commitments = instance_commitments; + drop(instance_commitments_projective); + + for commitment in &instance_commitments { + transcript.common_point(*commitment)?; + } + } + + let instance_polys: Vec<_> = instance_values + .iter() + .map(|poly| { + let lagrange_vec = domain.lagrange_from_vec(poly.to_vec()); + domain.lagrange_to_coeff(lagrange_vec) + }) + .collect(); + + let instance_extendeds: Vec<_> = instance_polys + .iter() + .map(|poly| domain.coeff_to_extended(poly.clone())) + .collect(); + + Ok(InstanceSingle { + instance_values, + _instance_polys: instance_polys, + instance_extendeds, + }) + }) + .collect::, _>>()?; + + #[derive(Clone)] + struct AdviceSingle { + pub advice_values: Vec>, + pub advice_polys: Vec>, + pub advice_extendeds: Vec>, + } + + struct WitnessCollection<'a, F: Field> { + k: u32, + current_phase: sealed::Phase, + advice: Vec, LagrangeCoeff>>, + unblinded_advice: HashSet, + challenges: &'a HashMap, + instances: &'a [&'a [F]], + usable_rows: RangeTo, + _marker: std::marker::PhantomData, + } + + impl<'a, F: Field> Assignment for WitnessCollection<'a, F> { + fn enter_region(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // Do nothing; we don't care about regions in this context. + } + + fn exit_region(&mut self) { + // Do nothing; we don't care about regions in this context. + } + + fn enable_selector(&mut self, _: A, _: &Selector, _: usize) -> Result<(), Error> + where + A: FnOnce() -> AR, + AR: Into, + { + // We only care about advice columns here + + Ok(()) + } + + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } + + fn query_instance(&self, column: Column, row: usize) -> Result, Error> { + if !self.usable_rows.contains(&row) { + return Err(Error::not_enough_rows_available(self.k)); + } + + self.instances + .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, + { + // Ignore assignment of advice column in different phase than current one. + if self.current_phase != column.column_type().phase { + return Ok(()); + } + + if !self.usable_rows.contains(&row) { + return Err(Error::not_enough_rows_available(self.k)); + } + + *self + .advice + .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, + { + // We only care about advice columns here + + Ok(()) + } + + fn copy( + &mut self, + _: Column, + _: usize, + _: Column, + _: usize, + ) -> Result<(), Error> { + // We only care about advice columns here + + Ok(()) + } + + fn fill_from_row( + &mut self, + _: Column, + _: usize, + _: Value>, + ) -> Result<(), Error> { + Ok(()) + } + + fn get_challenge(&self, challenge: Challenge) -> Value { + self.challenges + .get(&challenge.index()) + .cloned() + .map(Value::known) + .unwrap_or_else(Value::unknown) + } + + fn push_namespace(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // Do nothing; we don't care about namespaces in this context. + } + + fn pop_namespace(&mut self, _: Option) { + // Do nothing; we don't care about namespaces in this context. + } + } + + let (advice, challenges, mut merged) = { + let mut advice = vec![ + AdviceSingle:: { + advice_values: vec![domain.empty_lagrange(); meta.num_advice_columns], + advice_polys: vec![Polynomial::new(Vec::new()); meta.num_advice_columns], + advice_extendeds: vec![Polynomial::new(Vec::new()); meta.num_advice_columns], + }; + instances.len() + ]; + let mut challenges = HashMap::::with_capacity(meta.num_challenges); + let mut merged = Vec::with_capacity(meta.phases().count() + 2); + + let unusable_rows_start = (1 << domain.k()) as usize - (meta.blinding_factors() + 1); + for current_phase in pk.vk.cs.phases() { + let column_indices = meta + .advice_column_phase + .iter() + .enumerate() + .filter_map(|(column_index, phase)| { + if current_phase == *phase { + Some(column_index) + } else { + None + } + }) + .collect::>(); + + for ((circuit, advice), instances) in + circuits.iter().zip(advice.iter_mut()).zip(instances) + { + let mut witness = WitnessCollection { + k: domain.k(), + current_phase, + advice: vec![domain.empty_lagrange_assigned(); meta.num_advice_columns], + unblinded_advice: HashSet::from_iter(meta.unblinded_advice_columns.clone()), + instances, + challenges: &challenges, + // The prover will not be allowed to assign values to advice + // cells that exist within inactive rows, which include some + // number of blinding factors and an extra row for use in the + // permutation argument. + usable_rows: ..unusable_rows_start, + _marker: std::marker::PhantomData, + }; + + // Synthesize the circuit to obtain the witness and other information. + C::FloorPlanner::synthesize( + &mut witness, + circuit, + config.clone(), + meta.constants.clone(), + )?; + + let mut advice_values = batch_invert_assigned::( + witness + .advice + .into_iter() + .enumerate() + .filter_map(|(column_index, advice)| { + if column_indices.contains(&column_index) { + Some(advice) + } else { + None + } + }) + .collect(), + ); + + // Add blinding factors to advice columns + for (column_index, advice_values) in column_indices.iter().zip(&mut advice_values) { + if !witness.unblinded_advice.contains(column_index) { + for cell in &mut advice_values[unusable_rows_start..] { + *cell = PCS::Scalar::random(&mut rng); + } + } else { + #[cfg(feature = "sanity-checks")] + for cell in &advice_values[unusable_rows_start..] { + assert_eq!(*cell, PCS::Scalar::ZERO); + } + } + } + + for (column_index, advice_values) in column_indices.iter().zip(advice_values) { + advice.advice_values[*column_index] = advice_values; + advice.advice_polys[*column_index] = + domain.lagrange_to_coeff(advice.advice_values[*column_index].clone()); + advice.advice_extendeds[*column_index] = + domain.coeff_to_extended(advice.advice_polys[*column_index].clone()); + } + } + + let hs = pk.ev.evaluate_hs( + pk, + 0, + &advice + .iter() + .map(|a| a.advice_extendeds.as_slice()) + .collect::>(), + &instance + .iter() + .map(|i| i.instance_extendeds.as_slice()) + .collect::>(), + &challenges, + ); + + // Sanity check + { + assert_eq!(hs.len(), 1); + assert_eq!(pk.fixed_polys.len(), 5); + assert_eq!(advice[0].advice_polys.len(), 3); + + let x = PCS::Scalar::random(&mut rng); + let h_eval = eval_polynomial(&hs[0], x); + let f_evals: Vec<_> = pk + .fixed_polys + .iter() + .map(|p| eval_polynomial(p, x)) + .collect(); + let a_evals: Vec<_> = advice[0] + .advice_polys + .iter() + .map(|p| eval_polynomial(p, x)) + .collect(); + let z_eval = x.pow([1 << domain.k()]) - PCS::Scalar::ONE; + + assert_eq!( + h_eval * z_eval, + f_evals[0] * a_evals[0] + + f_evals[1] * a_evals[1] + + f_evals[2] * a_evals[0] * a_evals[1] + + f_evals[3] * a_evals[2] + + f_evals[4] + ); + } + + let poly = merge_polys( + advice + .iter() + .flat_map(|advice| column_indices.iter().map(|idx| &advice.advice_polys[*idx])) + .chain(&hs), + ); + let blind = Blind(PCS::Scalar::random(&mut rng)); + let commitment = params.commit(&poly, blind).to_affine(); + transcript.write_point(commitment)?; + + merged.push(( + advice.len() * (column_indices.len() + 1), + hs, + poly, + blind, + commitment, + )); + + for (index, phase) in meta.challenge_phase.iter().enumerate() { + if current_phase == *phase { + let existing = + challenges.insert(index, *transcript.squeeze_challenge_scalar::<()>()); + assert!(existing.is_none()); + } + } + } + + assert_eq!(challenges.len(), meta.num_challenges); + let challenges = (0..meta.num_challenges) + .map(|index| challenges.remove(&index).unwrap()) + .collect::>(); + + (advice, challenges, merged) + }; + + // Sample beta challenge + let beta: ChallengeBeta<_> = transcript.squeeze_challenge_scalar(); + + // Sample gamma challenge + let gamma: ChallengeGamma<_> = transcript.squeeze_challenge_scalar(); + + // Commit to permutations. + let permutations: Vec> = instance + .iter() + .zip(advice.iter()) + .map(|(instance, advice)| { + pk.vk.cs.permutation.compute_z( + pk, + &pk.permutation, + &advice.advice_values, + &pk.fixed_values, + &instance.instance_values, + beta, + gamma, + &mut rng, + ) + }) + .collect::, _>>()?; + + let last_hs = pk.ev.evaluate_last_hs( + pk, + &advice + .iter() + .map(|a| a.advice_extendeds.as_slice()) + .collect::>(), + &instance + .iter() + .map(|i| i.instance_extendeds.as_slice()) + .collect::>(), + &challenges, + *beta, + *gamma, + &permutations, + ); + + if !permutations.is_empty() { + // Sanity check + { + let x = PCS::Scalar::random(&mut rng); + let xn = x.pow([1 << pk.vk.domain.k()]); + let blinding_factors = pk.vk.cs.blinding_factors(); + + let h_evals: Vec<_> = last_hs.iter().map(|h| eval_polynomial(h, x)).collect(); + let p_evals: Vec<_> = pk + .permutation + .polys + .iter() + .map(|p| eval_polynomial(p, x)) + .collect(); + let a_evals: Vec<_> = advice[0] + .advice_polys + .iter() + .map(|p| eval_polynomial(p, x)) + .collect(); + let z_eval = eval_polynomial(&permutations[0].sets[0].permutation_product_poly, x); + let z_next_eval = eval_polynomial( + &permutations[0].sets[0].permutation_product_poly, + pk.vk.domain.rotate_omega(x, Rotation::next()), + ); + let zh_eval = x.pow([1 << domain.k()]) - PCS::Scalar::ONE; + + let l_evals = pk + .vk + .domain + .l_i_range(x, xn, (-((blinding_factors + 1) as i32))..=0); + assert_eq!(l_evals.len(), 2 + blinding_factors); + let l_last = l_evals[0]; + let l_blind: PCS::Scalar = l_evals[1..(1 + blinding_factors)] + .iter() + .fold(PCS::Scalar::ZERO, |acc, eval| acc + eval); + let l_0 = l_evals[1 + blinding_factors]; + + assert_eq!(h_evals.len(), 3); + // l_0(X) * (1 - z_0(X)) = 0 + assert_eq!(h_evals[0] * zh_eval, l_0 * (PCS::Scalar::ONE - z_eval)); + // l_last(X) * (z_l(X)^2 - z_l(X)) = 0 + assert_eq!(h_evals[1] * zh_eval, l_last * (z_eval * z_eval - z_eval)); + // (1 - (l_last(X) + l_blind(X))) * ( + // z_i(\omega X) \prod_j (p(X) + \beta s_j(X) + \gamma) + // - z_i(X) \prod_j (p(X) + \delta^j \beta X + \gamma) + // ) + assert_eq!( + h_evals[2] * zh_eval, + (PCS::Scalar::ONE - l_last - l_blind) + * (z_next_eval + * (a_evals[0] + p_evals[0] * *beta + *gamma) + * (a_evals[1] + p_evals[1] * *beta + *gamma) + * (a_evals[2] + p_evals[2] * *beta + *gamma) + - z_eval + * (a_evals[0] + x * *beta + *gamma) + * (a_evals[1] + PCS::Scalar::DELTA * x * *beta + *gamma) + * (a_evals[2] + PCS::Scalar::DELTA.square() * x * *beta + *gamma)) + ); + } + + let poly = merge_polys( + permutations + .iter() + .flat_map(|p| p.sets.iter().map(|set| &set.permutation_product_poly)) + .chain(&last_hs), + ); + let blind = Blind(PCS::Scalar::random(&mut rng)); + let commitment = params.commit(&poly, blind).to_affine(); + transcript.write_point(commitment)?; + merged.push(( + (permutations[0].sets.len() + 1) * permutations.len(), + last_hs, + poly, + blind, + commitment, + )); + } + + let lcm_t = merged.iter().map(|(t, _, _, _, _)| *t).reduce(lcm).unwrap(); + + let root_of_x: ChallengeX<_> = transcript.squeeze_challenge_scalar(); + let root_of_x = *root_of_x; + let x = root_of_x.pow([lcm_t as u64]); + + let fixed_rotations = { + let mut fixed_rotations = vec![]; + + pk.vk.cs.fixed_queries.iter().for_each(|(_, at)| { + if !fixed_rotations.contains(at) { + fixed_rotations.push(*at); + } + }); + + fixed_rotations + }; + let phase_rotations = { + let mut phase_rotations = vec![vec![]; merged.len()]; + + pk.vk.cs.advice_queries.iter().for_each(|(column, at)| { + if !phase_rotations[column.column_type().phase() as usize].contains(at) { + phase_rotations[column.column_type().phase() as usize].push(*at); + } + }); + + if let Some(last) = phase_rotations.last_mut() { + if !last.contains(&Rotation::cur()) { + last.push(Rotation::cur()) + } + if !last.contains(&Rotation::next()) { + last.push(Rotation::next()) + } + } + + phase_rotations + }; + + for advice in advice.iter() { + for current_phase in pk.vk.cs.phases() { + let column_indices = meta + .advice_column_phase + .iter() + .enumerate() + .filter_map(|(column_index, phase)| { + if current_phase == *phase { + Some(column_index) + } else { + None + } + }) + .collect::>(); + + for rotation in phase_rotations[current_phase.0 as usize].iter() { + for idx in column_indices.iter() { + transcript.write_scalar(eval_polynomial( + &advice.advice_polys[*idx], + domain.rotate_omega(x, *rotation), + ))?; + } + } + } + } + for permutation in permutations.iter() { + if permutation.sets.is_empty() { + break; + } + if permutation.sets.len() > 1 { + unimplemented!() + } + + for set in permutation.sets.iter() { + transcript.write_scalar(eval_polynomial(&set.permutation_product_poly, x))?; + } + for set in permutation.sets.iter() { + transcript.write_scalar(eval_polynomial( + &set.permutation_product_poly, + domain.rotate_omega(x, Rotation::next()), + ))?; + } + } + + for ((_, hs, _, _, _), rotations) in merged.iter().zip(phase_rotations.iter()) { + for rotation in rotations { + if *rotation != Rotation::cur() { + for h in hs { + transcript + .write_scalar(eval_polynomial(h, domain.rotate_omega(x, *rotation)))?; + } + } + } + } + for rotation in fixed_rotations { + for poly in &pk.fixed_polys { + transcript.write_scalar(eval_polynomial(poly, domain.rotate_omega(x, rotation)))?; + } + for poly in &pk.permutation.polys { + transcript.write_scalar(eval_polynomial(poly, domain.rotate_omega(x, rotation)))?; + } + } + + for (_t, _, _, _, _) in &merged { + // TODO: Find root_of_unity(lcm_t) and set opening points at root_of_x * root_of_unity(lcm_t) ^ i for in in t + } + + // let prover = P::new(params); + // prover + // .create_proof(rng, transcript, instances) + // .map_err(|_| Error::ConstraintSystemFailure) + + Ok(()) +} + +#[test] +fn test_create_proof() { + use crate::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::MockProver, + fflonk::{keygen_pk, keygen_vk}, + poly::{ + kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::ProverSHPLONK, + }, + Rotation, + }, + transcript::{Challenge255, Keccak256Write, TranscriptWriterBuffer}, + }; + use halo2curves::bn256::{Bn256, Fr}; + use rand_core::OsRng; + + #[derive(Clone)] + pub struct VanillaPlonkConfig { + selectors: [Column; 5], + wires: [Column; 3], + } + + impl VanillaPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let pi = meta.instance_column(); + let [q_l, q_r, q_m, q_o, q_c] = [(); 5].map(|_| meta.fixed_column()); + let [w_l, w_r, w_o] = [(); 3].map(|_| meta.advice_column()); + [w_l, w_r, w_o].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 [w_l, w_r, w_o] = + [w_l, w_r, w_o].map(|column| meta.query_advice(column, Rotation::cur())); + 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 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, + ) + }, + ); + VanillaPlonkConfig { + selectors: [q_l, q_r, q_m, q_o, q_c], + wires: [w_l, w_r, w_o], + } + } + } + + #[derive(Clone, Default)] + pub struct VanillaPlonk; + + impl Circuit for VanillaPlonk { + type Config = VanillaPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + unimplemented!() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + meta.set_minimum_degree(5); + VanillaPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + region.assign_fixed(|| "", config.selectors[0], 0, || Value::known(F::ONE))?; + region.assign_fixed(|| "", config.selectors[1], 0, || Value::known(F::ONE))?; + region.assign_fixed(|| "", config.selectors[2], 0, || Value::known(F::ONE))?; + region.assign_fixed(|| "", config.selectors[3], 0, || Value::known(F::ONE))?; + region.assign_fixed(|| "", config.selectors[4], 0, || Value::known(F::ONE))?; + let a = + region.assign_advice(|| "", config.wires[0], 0, || Value::known(F::ONE))?; + let b = + region.assign_advice(|| "", config.wires[1], 0, || Value::known(F::ONE))?; + let c = + region.assign_advice(|| "", config.wires[2], 1, || Value::known(F::ONE))?; + region.constrain_equal(a.cell(), b.cell())?; + region.constrain_equal(b.cell(), c.cell())?; + region.assign_advice( + || "", + config.wires[2], + 0, + || Value::known(-F::ONE.double().double()), + )?; + Ok(()) + }, + ) + } + } + + let k = 4; + let circuit = VanillaPlonk; + + MockProver::::run(k, &circuit, vec![vec![]]) + .unwrap() + .assert_satisfied(); + + let params: ParamsKZG = ParamsKZG::setup(k + 4, OsRng); + let vk = keygen_vk(k, ¶ms, &circuit).expect("keygen_vk should not fail"); + let pk = keygen_pk(k, ¶ms, vk, &circuit).expect("keygen_pk should not fail"); + let mut transcript = Keccak256Write::<_, _, Challenge255<_>>::init(vec![]); + + // Create proof with correct number of instances + create_proof::, ProverSHPLONK<_>, _, _>( + ¶ms, + &pk, + &[circuit], + &[&[&[]]], + OsRng, + &mut transcript, + ) + .expect("proof generation should not fail"); + let proof = transcript.finalize(); + let proof_size = proof.len() + + 2 * 32 // since 2 points are in compressed format + + 2 * 64; // shplonk + assert_eq!(proof_size, 768); +} diff --git a/halo2_proofs/src/lib.rs b/halo2_proofs/src/lib.rs index acc26aff15..17deba3a33 100644 --- a/halo2_proofs/src/lib.rs +++ b/halo2_proofs/src/lib.rs @@ -11,6 +11,7 @@ pub mod arithmetic; pub mod circuit; pub use halo2curves; +pub mod fflonk; mod multicore; pub mod plonk; pub mod poly; diff --git a/halo2_proofs/src/plonk.rs b/halo2_proofs/src/plonk.rs index 5506f94a68..86ff3e31a5 100644 --- a/halo2_proofs/src/plonk.rs +++ b/halo2_proofs/src/plonk.rs @@ -21,14 +21,14 @@ use crate::transcript::{ChallengeScalar, EncodedChallenge, Transcript}; use crate::SerdeFormat; mod assigned; -mod circuit; +pub(crate) mod circuit; mod error; -mod evaluation; -mod keygen; -mod lookup; +pub(crate) mod evaluation; +pub(crate) mod keygen; +pub(crate) mod lookup; pub mod permutation; -mod shuffle; -mod vanishing; +pub(crate) mod shuffle; +pub(crate) mod vanishing; mod prover; mod verifier; @@ -40,7 +40,7 @@ pub use keygen::*; pub use prover::*; pub use verifier::*; -use evaluation::Evaluator; +pub(crate) use evaluation::Evaluator; use std::io; /// This is a verifying key which allows for the verification of proofs for a @@ -202,7 +202,7 @@ where } impl VerifyingKey { - fn bytes_length(&self, format: SerdeFormat) -> usize + pub(crate) fn bytes_length(&self, format: SerdeFormat) -> usize where C: SerdeCurveAffine, { @@ -453,21 +453,21 @@ impl VerifyingKey { } #[derive(Clone, Copy, Debug)] -struct Theta; -type ChallengeTheta = ChallengeScalar; +pub(crate) struct Theta; +pub(crate) type ChallengeTheta = ChallengeScalar; #[derive(Clone, Copy, Debug)] -struct Beta; -type ChallengeBeta = ChallengeScalar; +pub(crate) struct Beta; +pub(crate) type ChallengeBeta = ChallengeScalar; #[derive(Clone, Copy, Debug)] -struct Gamma; -type ChallengeGamma = ChallengeScalar; +pub(crate) struct Gamma; +pub(crate) type ChallengeGamma = ChallengeScalar; #[derive(Clone, Copy, Debug)] -struct Y; -type ChallengeY = ChallengeScalar; +pub(crate) struct Y; +pub(crate) type ChallengeY = ChallengeScalar; #[derive(Clone, Copy, Debug)] -struct X; -type ChallengeX = ChallengeScalar; +pub(crate) struct X; +pub(crate) type ChallengeX = ChallengeScalar; diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index 98445a5881..3a061c85a6 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -97,7 +97,7 @@ impl PartialOrd for Column { pub(crate) mod sealed { /// Phase of advice column #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] - pub struct Phase(pub(super) u8); + pub struct Phase(pub(crate) u8); impl Phase { pub fn prev(&self) -> Option { diff --git a/halo2_proofs/src/plonk/evaluation.rs b/halo2_proofs/src/plonk/evaluation.rs index 431c487c7e..00c9863acd 100644 --- a/halo2_proofs/src/plonk/evaluation.rs +++ b/halo2_proofs/src/plonk/evaluation.rs @@ -10,7 +10,7 @@ use group::ff::{Field, PrimeField, WithSmallOrderMulGroup}; use super::{shuffle, ConstraintSystem, Expression}; /// Return the index in the polynomial of size `isize` after rotation `rot`. -fn get_rotation_idx(idx: usize, rot: i32, rot_scale: i32, isize: i32) -> usize { +pub(crate) fn get_rotation_idx(idx: usize, rot: i32, rot_scale: i32, isize: i32) -> usize { (((idx as i32) + (rot * rot_scale)).rem_euclid(isize)) as usize } @@ -650,7 +650,7 @@ impl GraphEvaluator { /// Currently does the simplest thing possible: just stores the /// resulting value so the result can be reused when that calculation /// is done multiple times. - fn add_calculation(&mut self, calculation: Calculation) -> ValueSource { + pub(crate) fn add_calculation(&mut self, calculation: Calculation) -> ValueSource { let existing_calculation = self .calculations .iter() @@ -670,7 +670,7 @@ impl GraphEvaluator { } /// Generates an optimized evaluation for the expression - fn add_expression(&mut self, expr: &Expression) -> ValueSource { + pub(crate) fn add_expression(&mut self, expr: &Expression) -> ValueSource { match expr { Expression::Constant(scalar) => self.add_constant(scalar), Expression::Selector(_selector) => unreachable!(), diff --git a/halo2_proofs/src/plonk/keygen.rs b/halo2_proofs/src/plonk/keygen.rs index 984eecb9e8..455b0bc69c 100644 --- a/halo2_proofs/src/plonk/keygen.rs +++ b/halo2_proofs/src/plonk/keygen.rs @@ -50,14 +50,14 @@ where /// Assembly to be used in circuit synthesis. #[derive(Debug)] -struct Assembly { - k: u32, - fixed: Vec, LagrangeCoeff>>, - permutation: permutation::keygen::Assembly, - selectors: Vec>, +pub(crate) struct Assembly { + pub(crate) k: u32, + pub(crate) fixed: Vec, LagrangeCoeff>>, + pub(crate) permutation: permutation::keygen::Assembly, + pub(crate) selectors: Vec>, // A range of available rows for assignment and copies. - usable_rows: Range, - _marker: std::marker::PhantomData, + pub(crate) usable_rows: Range, + pub(crate) _marker: std::marker::PhantomData, } impl Assignment for Assembly { diff --git a/halo2_proofs/src/plonk/permutation.rs b/halo2_proofs/src/plonk/permutation.rs index 22c1fad6c3..9df4f6e8b6 100644 --- a/halo2_proofs/src/plonk/permutation.rs +++ b/halo2_proofs/src/plonk/permutation.rs @@ -23,7 +23,7 @@ use std::io; #[derive(Debug, Clone)] pub struct Argument { /// A sequence of columns involved in the argument. - pub(super) columns: Vec>, + pub(crate) columns: Vec>, } impl Argument { @@ -128,9 +128,9 @@ impl VerifyingKey { /// The proving key for a single permutation argument. #[derive(Clone, Debug)] pub(crate) struct ProvingKey { - permutations: Vec>, - polys: Vec>, - pub(super) cosets: Vec>, + pub(crate) permutations: Vec>, + pub(crate) polys: Vec>, + pub(crate) cosets: Vec>, } impl ProvingKey @@ -138,7 +138,7 @@ where C::Scalar: SerdePrimeField, { /// Reads proving key for a single permutation argument from buffer using `Polynomial::read`. - pub(super) fn read(reader: &mut R, format: SerdeFormat) -> io::Result { + pub(crate) fn read(reader: &mut R, format: SerdeFormat) -> io::Result { let permutations = read_polynomial_vec(reader, format)?; let polys = read_polynomial_vec(reader, format)?; let cosets = read_polynomial_vec(reader, format)?; @@ -150,7 +150,7 @@ where } /// Writes proving key for a single permutation argument to buffer using `Polynomial::write`. - pub(super) fn write( + pub(crate) fn write( &self, writer: &mut W, format: SerdeFormat, @@ -164,7 +164,7 @@ where impl ProvingKey { /// Gets the total number of bytes in the serialization of `self` - pub(super) fn bytes_length(&self) -> usize { + pub(crate) fn bytes_length(&self) -> usize { polynomial_slice_byte_length(&self.permutations) + polynomial_slice_byte_length(&self.polys) + polynomial_slice_byte_length(&self.cosets) diff --git a/halo2_proofs/src/plonk/permutation/prover.rs b/halo2_proofs/src/plonk/permutation/prover.rs index d6b108554d..cc3db8463e 100644 --- a/halo2_proofs/src/plonk/permutation/prover.rs +++ b/halo2_proofs/src/plonk/permutation/prover.rs @@ -21,7 +21,7 @@ use crate::{ pub(crate) struct CommittedSet { pub(crate) permutation_product_poly: Polynomial, pub(crate) permutation_product_coset: Polynomial, - permutation_product_blind: Blind, + pub(crate) permutation_product_blind: Blind, } pub(crate) struct Committed { diff --git a/halo2_proofs/src/poly.rs b/halo2_proofs/src/poly.rs index 9cb6b149bc..74cfd3a94c 100644 --- a/halo2_proofs/src/poly.rs +++ b/halo2_proofs/src/poly.rs @@ -69,6 +69,15 @@ pub struct Polynomial { _marker: PhantomData, } +impl Polynomial { + pub(crate) fn new(values: Vec) -> Self { + Self { + values, + _marker: PhantomData, + } + } +} + impl Index for Polynomial { type Output = F;