diff --git a/src/ivc/protogalaxy/mod.rs b/src/ivc/protogalaxy/mod.rs index e6453f6c..3f381def 100644 --- a/src/ivc/protogalaxy/mod.rs +++ b/src/ivc/protogalaxy/mod.rs @@ -2,7 +2,7 @@ mod verify_chip { use std::iter; use halo2_proofs::{ - circuit::AssignedCell, + circuit::{AssignedCell, Value as Halo2Value}, halo2curves::{ ff::{FromUniformBytes, PrimeField, PrimeFieldBits}, CurveAffine, @@ -183,8 +183,59 @@ mod verify_chip { } } + /// Powers of one assigned value counted on-circuit + /// + /// Since powers are required many times during synthesis, let's keep these values separate + /// ```math + /// x^0, x^1, x^2, x^3, ... x^i, ... + /// ``` + pub struct ValuePowers { + powers: Vec>, + } + + impl ValuePowers { + pub fn new(one: AssignedValue, value: AssignedValue) -> Self { + Self { + powers: vec![one, value], + } + } + + pub fn iter(&self) -> impl Iterator> { + self.powers.iter() + } + + pub fn value(&self) -> &AssignedValue { + self.powers + .get(1) + .expect("Cannot be created without at least one element inside") + } + + /// Get from cache or calculate the `exp` degree of original value + /// + /// `self.value^exp` + pub fn get_or_eval( + &mut self, + region: &mut RegionCtx, + main_gate: &MainGate, + exp: usize, + ) -> Result, Halo2PlonkError> { + if let Some(value) = self.powers.get(exp) { + return Ok(value.clone()); + } + + while self.powers.len() <= exp { + let value = self.value(); + let last = self.powers.last().unwrap(); + let new = main_gate.mul(region, value, last)?; + self.powers.push(new); + } + + Ok(self.powers.get(exp).cloned().unwrap()) + } + } + /// Assigned version of [`crate::polynomial::univariate::UnivariatePoly`] - pub struct AssignedUnivariatePoly(Box<[AssignedValue]>); + pub struct AssignedUnivariatePoly(UnivariatePoly>); impl AssignedUnivariatePoly { pub fn assign( @@ -193,13 +244,13 @@ mod verify_chip { annotation: &'static str, poly: &UnivariatePoly, ) -> Result { - let up = AssignedUnivariatePoly( + let up = AssignedUnivariatePoly(UnivariatePoly( main_gate_config .advice_cycle_assigner() .assign_all_advice(region, || annotation, poly.iter().copied()) .map_err(|err| Error::Assign { annotation, err })? .into_boxed_slice(), - ); + )); region.next(); @@ -214,6 +265,103 @@ mod verify_chip { .inspect(|coeff| debug!("coeff {coeff:?}")) .map(|coeff| WrapValue::Assigned(coeff.clone())) } + + fn degree(&self) -> usize { + self.0.len() + } + + fn len(&self) -> usize { + self.0.len() + } + + pub fn eval( + &self, + region: &mut RegionCtx, + main_gate_config: MainGateConfig, + mut challenge_powers: ValuePowers, + ) -> Result, Halo2PlonkError> { + let main_gate = MainGate::::new(main_gate_config.clone()); + + let enable_selectors = |region: &mut RegionCtx| { + [ + main_gate_config.q_m[0], + main_gate_config.q_m[1], + main_gate_config.q_i, + main_gate_config.q_o, + ] + .iter() + .try_for_each(|col| region.assign_fixed(|| "one", *col, F::ZERO).map(|_| ())) + }; + let coeffs_col = [main_gate_config.state[0], main_gate_config.state[2]]; + let cha_col = [main_gate_config.state[1], main_gate_config.state[3]]; + let prev_col = &main_gate_config.input; + let result_col = &main_gate_config.out; + + challenge_powers.get_or_eval(region, &main_gate, self.len().saturating_sub(1))?; + + self.0 + .iter() + .zip_eq(challenge_powers.iter()) + .chunks(2) + .into_iter() + .try_fold(Option::>::None, |prev, chunks| { + let (coeffs, cha_in_power): (Vec<_>, Vec<_>) = chunks.unzip(); + enable_selectors(region)?; + + let assigned_prev = match prev { + None => { + region.assign_advice(|| "zero", *prev_col, Halo2Value::known(F::ZERO)) + } + Some(prev_cell) => region.assign_advice_from( + || "previous chunk values", + *prev_col, + prev_cell, + ), + }?; + + let assigned_coeffs = coeffs + .iter() + .zip_eq(coeffs_col) + .map(|(coeff, col)| region.assign_advice_from(|| "coeff", col, *coeff)) + .collect::, _>>()?; + + let assigned_cha = cha_in_power + .iter() + .zip_eq(cha_col) + .map(|(cha_in_power, col)| { + region.assign_advice_from(|| "cha", col, *cha_in_power) + }) + .collect::, _>>()?; + + let output = assigned_coeffs + .iter() + .zip_eq(assigned_cha.iter()) + .fold(assigned_prev.value().copied(), |res, (coeff, cha)| { + res + (coeff.value().copied() * cha.value()) + }); + + let assigned_output = region.assign_advice(|| "result", *result_col, output); + + debug!( + "coeffs: {:?}; cha_in_power: {:?}, prev: {:?}, output: {:?}", + coeffs.iter().map(|cell| cell.value()).collect::>(), + cha_in_power + .iter() + .map(|cell| cell.value()) + .collect::>(), + assigned_prev.value(), + assigned_output + .as_ref() + .ok() + .and_then(|cell| cell.value().unwrap()), + ); + + region.next(); + + assigned_output.map(Some) + })? + .ok_or_else(|| Halo2PlonkError::Synthesis) + } } /// Assigned version of [`crate::nifs::protogalaxy::Proof] @@ -427,7 +575,11 @@ mod verify_chip { mod tests { use halo2_proofs::{ arithmetic::Field, - circuit::{floor_planner::single_pass::SingleChipLayouter, Layouter}, + circuit::{ + floor_planner::single_pass::SingleChipLayouter, Layouter, SimpleFloorPlanner, + }, + dev::MockProver, + plonk::Circuit, }; use tracing_test::traced_test; @@ -647,5 +799,86 @@ mod verify_chip { assert_eq!(off_circuit_beta_strokes, on_circuit_beta_strokes); } + + #[traced_test] + #[test] + fn poly_eval() { + struct TestCircuit; + + impl Circuit for TestCircuit { + type Config = MainGateConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + todo!() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + MainGate::configure(meta) + } + + fn synthesize( + &self, + main_gate_config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Halo2PlonkError> { + let cha = Base::from_u128(123); + let poly = UnivariatePoly::from_iter((0..).map(Into::into).take(10)); + + let off_circuit_res = poly.eval(cha); + + let on_circuit_res = layouter.assign_region( + || "assigned_poly_eval", + move |region| { + let mut region = RegionCtx::new(region, 0); + + let cha = region + .assign_advice( + || "", + main_gate_config.state[0], + Halo2Value::known(cha), + ) + .unwrap(); + + let one = region + .assign_advice( + || "", + main_gate_config.state[1], + Halo2Value::known(Base::ONE), + ) + .unwrap(); + + region.next(); + + let cha = ValuePowers::new(one, cha); + + let poly = AssignedUnivariatePoly::assign( + &mut region, + main_gate_config.clone(), + "test poly", + &poly, + ) + .unwrap(); + + Ok(poly + .eval(&mut region, main_gate_config.clone(), cha) + .unwrap()) + }, + )?; + + assert_eq!( + off_circuit_res, + on_circuit_res.value().unwrap().copied().unwrap() + ); + + Ok(()) + } + } + + MockProver::run(12, &TestCircuit {}, vec![]) + .unwrap() + .verify() + .unwrap(); + } } } diff --git a/src/polynomial/univariate.rs b/src/polynomial/univariate.rs index 612d157d..0563b352 100644 --- a/src/polynomial/univariate.rs +++ b/src/polynomial/univariate.rs @@ -5,6 +5,7 @@ use std::{ }; use halo2_proofs::halo2curves::ff::{PrimeField, WithSmallOrderMulGroup}; +use tracing::*; use crate::{ff::Field, fft, util}; @@ -12,15 +13,12 @@ use crate::{ff::Field, fft, util}; /// /// Coefficients of the polynomial are presented from smaller degree to larger degree #[derive(Debug, PartialEq, Eq, Clone)] -pub struct UnivariatePoly(pub(crate) Box<[F]>); +pub struct UnivariatePoly(pub(crate) Box<[F]>); impl UnivariatePoly { pub fn new_zeroed(size: usize) -> Self { Self::from_iter(iter::repeat(F::ZERO).take(size)) } - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } pub fn degree(&self) -> usize { self.0 .iter() @@ -31,7 +29,19 @@ impl UnivariatePoly { } } -impl IntoIterator for UnivariatePoly { +impl UnivariatePoly { + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + pub fn len(&self) -> usize { + self.0.len() + } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl IntoIterator for UnivariatePoly { type Item = F; type IntoIter = as IntoIterator>::IntoIter; @@ -40,13 +50,13 @@ impl IntoIterator for UnivariatePoly { } } -impl AsMut<[F]> for UnivariatePoly { +impl AsMut<[F]> for UnivariatePoly { fn as_mut(&mut self) -> &mut [F] { self.0.as_mut() } } -impl FromIterator for UnivariatePoly { +impl FromIterator for UnivariatePoly { fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect()) } @@ -58,6 +68,7 @@ impl UnivariatePoly { self.0 .iter() .zip(iter::successors(Some(F::ONE), |val| Some(*val * challenge))) + .inspect(|(coeff, cha)| debug!("coeff: {coeff:?}, challenge_in_degree: {cha:?}")) .fold(F::ZERO, |res, (coeff, challenge_in_degree)| { res + (challenge_in_degree * *coeff) }) @@ -75,14 +86,6 @@ impl UnivariatePoly { } } - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - /// Multiplies all coefficients of the polynomial by a field element. pub fn scale(&self, factor: F) -> UnivariatePoly { let scaled_coeffs: Vec = self.iter().map(|&coeff| coeff * factor).collect();