diff --git a/src/field/extension_field/algebra.rs b/src/field/extension_field/algebra.rs index 21438262..93d25de4 100644 --- a/src/field/extension_field/algebra.rs +++ b/src/field/extension_field/algebra.rs @@ -160,12 +160,32 @@ impl, const D: usize> PolynomialCoeffsAlgebra { .fold(ExtensionAlgebra::ZERO, |acc, &c| acc * x + c) } + /// Evaluate the polynomial at a point given its powers. The first power is the point itself, not 1. + pub fn eval_with_powers(&self, powers: &[ExtensionAlgebra]) -> ExtensionAlgebra { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + c * x) + } + pub fn eval_base(&self, x: F) -> ExtensionAlgebra { self.coeffs .iter() .rev() .fold(ExtensionAlgebra::ZERO, |acc, &c| acc.scalar_mul(x) + c) } + + /// Evaluate the polynomial at a point given its powers. The first power is the point itself, not 1. + pub fn eval_base_with_powers(&self, powers: &[F]) -> ExtensionAlgebra { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + x.scalar_mul(c)) + } } #[cfg(test)] diff --git a/src/fri/recursive_verifier.rs b/src/fri/recursive_verifier.rs index a08dd99e..b31e01a8 100644 --- a/src/fri/recursive_verifier.rs +++ b/src/fri/recursive_verifier.rs @@ -3,8 +3,10 @@ use crate::field::extension_field::Extendable; use crate::field::field_types::{Field, RichField}; use crate::fri::proof::{FriInitialTreeProofTarget, FriProofTarget, FriQueryRoundTarget}; use crate::fri::FriConfig; +use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; -use crate::gates::interpolation::InterpolationGate; +use crate::gates::interpolation::HighDegreeInterpolationGate; +use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; use crate::gates::random_access::RandomAccessGate; use crate::hash::hash_types::MerkleCapTarget; use crate::iop::challenger::RecursiveChallenger; @@ -27,6 +29,7 @@ impl, const D: usize> CircuitBuilder { arity_bits: usize, evals: &[ExtensionTarget], beta: ExtensionTarget, + common_data: &CommonCircuitData, ) -> ExtensionTarget { let arity = 1 << arity_bits; debug_assert_eq!(evals.len(), arity); @@ -43,25 +46,50 @@ impl, const D: usize> CircuitBuilder { let coset_start = self.mul(start, x); // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at beta. - self.interpolate_coset(arity_bits, coset_start, &evals, beta) + // `HighDegreeInterpolationGate` has degree `arity`, so we use the low-degree gate if + // the arity is too large. + if arity > common_data.quotient_degree_factor { + self.interpolate_coset::>( + arity_bits, + coset_start, + &evals, + beta, + ) + } else { + self.interpolate_coset::>( + arity_bits, + coset_start, + &evals, + beta, + ) + } } /// Make sure we have enough wires and routed wires to do the FRI checks efficiently. This check /// isn't required -- without it we'd get errors elsewhere in the stack -- but just gives more /// helpful errors. - fn check_recursion_config(&self, max_fri_arity_bits: usize) { + fn check_recursion_config( + &self, + max_fri_arity_bits: usize, + common_data: &CommonCircuitData, + ) { let random_access = RandomAccessGate::::new_from_config( &self.config, max_fri_arity_bits.max(self.config.cap_height), ); - let interpolation_gate = InterpolationGate::::new(max_fri_arity_bits); + let (interpolation_wires, interpolation_routed_wires) = + if 1 << max_fri_arity_bits > common_data.quotient_degree_factor { + let gate = LowDegreeInterpolationGate::::new(max_fri_arity_bits); + (gate.num_wires(), gate.num_routed_wires()) + } else { + let gate = HighDegreeInterpolationGate::::new(max_fri_arity_bits); + (gate.num_wires(), gate.num_routed_wires()) + }; - let min_wires = random_access - .num_wires() - .max(interpolation_gate.num_wires()); + let min_wires = random_access.num_wires().max(interpolation_wires); let min_routed_wires = random_access .num_routed_wires() - .max(interpolation_gate.num_routed_wires()); + .max(interpolation_routed_wires); assert!( self.config.num_wires >= min_wires, @@ -108,7 +136,7 @@ impl, const D: usize> CircuitBuilder { let config = &common_data.config; if let Some(max_arity_bits) = common_data.fri_params.max_arity_bits() { - self.check_recursion_config(max_arity_bits); + self.check_recursion_config(max_arity_bits, common_data); } debug_assert_eq!( @@ -379,6 +407,7 @@ impl, const D: usize> CircuitBuilder { arity_bits, evals, betas[i], + &common_data ) ); diff --git a/src/gadgets/interpolation.rs b/src/gadgets/interpolation.rs index 09b6329b..7f6f98f3 100644 --- a/src/gadgets/interpolation.rs +++ b/src/gadgets/interpolation.rs @@ -1,23 +1,93 @@ +use std::ops::Range; + use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::field::field_types::RichField; -use crate::gates::interpolation::InterpolationGate; +use crate::gates::gate::Gate; use crate::iop::target::Target; use crate::plonk::circuit_builder::CircuitBuilder; +/// Trait for gates which interpolate a polynomial, whose points are a (base field) coset of the multiplicative subgroup +/// with the given size, and whose values are extension field elements, given by input wires. +/// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. +pub(crate) trait InterpolationGate, const D: usize>: + Gate + Copy +{ + fn new(subgroup_bits: usize) -> Self; + + fn num_points(&self) -> usize; + + /// Wire index of the coset shift. + fn wire_shift(&self) -> usize { + 0 + } + + fn start_values(&self) -> usize { + 1 + } + + /// Wire indices of the `i`th interpolant value. + fn wires_value(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_values() + i * D; + start..start + D + } + + fn start_evaluation_point(&self) -> usize { + self.start_values() + self.num_points() * D + } + + /// Wire indices of the point to evaluate the interpolant at. + fn wires_evaluation_point(&self) -> Range { + let start = self.start_evaluation_point(); + start..start + D + } + + fn start_evaluation_value(&self) -> usize { + self.start_evaluation_point() + D + } + + /// Wire indices of the interpolated value. + fn wires_evaluation_value(&self) -> Range { + let start = self.start_evaluation_value(); + start..start + D + } + + fn start_coeffs(&self) -> usize { + self.start_evaluation_value() + D + } + + /// The number of routed wires required in the typical usage of this gate, where the points to + /// interpolate, the evaluation point, and the corresponding value are all routed. + fn num_routed_wires(&self) -> usize { + self.start_coeffs() + } + + /// Wire indices of the interpolant's `i`th coefficient. + fn wires_coeff(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_coeffs() + i * D; + start..start + D + } + + fn end_coeffs(&self) -> usize { + self.start_coeffs() + D * self.num_points() + } +} + impl, const D: usize> CircuitBuilder { /// Interpolates a polynomial, whose points are a coset of the multiplicative subgroup with the /// given size, and whose values are given. Returns the evaluation of the interpolant at /// `evaluation_point`. - pub fn interpolate_coset( + pub(crate) fn interpolate_coset>( &mut self, subgroup_bits: usize, coset_shift: Target, values: &[ExtensionTarget], evaluation_point: ExtensionTarget, ) -> ExtensionTarget { - let gate = InterpolationGate::new(subgroup_bits); - let gate_index = self.add_gate(gate.clone(), vec![]); + let gate = G::new(subgroup_bits); + let gate_index = self.add_gate(gate, vec![]); self.connect(coset_shift, Target::wire(gate_index, gate.wire_shift())); for (i, &v) in values.iter().enumerate() { self.connect_extension( @@ -38,11 +108,13 @@ impl, const D: usize> CircuitBuilder { mod tests { use anyhow::Result; - use crate::field::extension_field::quartic::QuarticExtension; + use crate::field::extension_field::quadratic::QuadraticExtension; use crate::field::extension_field::FieldExtension; use crate::field::field_types::Field; use crate::field::goldilocks_field::GoldilocksField; use crate::field::interpolation::interpolant; + use crate::gates::interpolation::HighDegreeInterpolationGate; + use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; use crate::iop::witness::PartialWitness; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::CircuitConfig; @@ -51,10 +123,11 @@ mod tests { #[test] fn test_interpolate() -> Result<()> { type F = GoldilocksField; - type FF = QuarticExtension; + const D: usize = 2; + type FF = QuadraticExtension; let config = CircuitConfig::standard_recursion_config(); let pw = PartialWitness::new(); - let mut builder = CircuitBuilder::::new(config); + let mut builder = CircuitBuilder::::new(config); let subgroup_bits = 2; let len = 1 << subgroup_bits; @@ -66,7 +139,7 @@ mod tests { let homogeneous_points = points .iter() .zip(values.iter()) - .map(|(&a, &b)| (>::from_basefield(a), b)) + .map(|(&a, &b)| (>::from_basefield(a), b)) .collect::>(); let true_interpolant = interpolant(&homogeneous_points); @@ -83,9 +156,21 @@ mod tests { let zt = builder.constant_extension(z); - let eval = builder.interpolate_coset(subgroup_bits, coset_shift_target, &value_targets, zt); + let eval_hd = builder.interpolate_coset::>( + subgroup_bits, + coset_shift_target, + &value_targets, + zt, + ); + let eval_ld = builder.interpolate_coset::>( + subgroup_bits, + coset_shift_target, + &value_targets, + zt, + ); let true_eval_target = builder.constant_extension(true_eval); - builder.connect_extension(eval, true_eval_target); + builder.connect_extension(eval_hd, true_eval_target); + builder.connect_extension(eval_ld, true_eval_target); let data = builder.build(); let proof = data.prove(pw)?; diff --git a/src/gadgets/polynomial.rs b/src/gadgets/polynomial.rs index 9f631c10..ff7fffd7 100644 --- a/src/gadgets/polynomial.rs +++ b/src/gadgets/polynomial.rs @@ -64,4 +64,21 @@ impl PolynomialCoeffsExtAlgebraTarget { } acc } + + /// Evaluate the polynomial at a point given its powers. The first power is the point itself, not 1. + pub fn eval_with_powers( + &self, + builder: &mut CircuitBuilder, + powers: &[ExtensionAlgebraTarget], + ) -> ExtensionAlgebraTarget + where + F: RichField + Extendable, + { + debug_assert_eq!(self.0.len(), powers.len() + 1); + let acc = self.0[0]; + self.0[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| builder.mul_add_ext_algebra(c, x, acc)) + } } diff --git a/src/gates/gate_tree.rs b/src/gates/gate_tree.rs index 704b410a..c2a517d0 100644 --- a/src/gates/gate_tree.rs +++ b/src/gates/gate_tree.rs @@ -225,11 +225,12 @@ mod tests { use super::*; use crate::field::goldilocks_field::GoldilocksField; + use crate::gadgets::interpolation::InterpolationGate; use crate::gates::arithmetic_extension::ArithmeticExtensionGate; use crate::gates::base_sum::BaseSumGate; use crate::gates::constant::ConstantGate; use crate::gates::gmimc::GMiMCGate; - use crate::gates::interpolation::InterpolationGate; + use crate::gates::interpolation::HighDegreeInterpolationGate; use crate::gates::noop::NoopGate; #[test] @@ -244,7 +245,7 @@ mod tests { GateRef::new(ArithmeticExtensionGate { num_ops: 4 }), GateRef::new(BaseSumGate::<4>::new(4)), GateRef::new(GMiMCGate::::new()), - GateRef::new(InterpolationGate::new(2)), + GateRef::new(HighDegreeInterpolationGate::new(2)), ]; let (tree, _, _) = Tree::from_gates(gates.clone()); diff --git a/src/gates/interpolation.rs b/src/gates/interpolation.rs index d97eb009..0225ce59 100644 --- a/src/gates/interpolation.rs +++ b/src/gates/interpolation.rs @@ -6,6 +6,7 @@ use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::{Extendable, FieldExtension}; use crate::field::field_types::RichField; use crate::field::interpolation::interpolant; +use crate::gadgets::interpolation::InterpolationGate; use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::Gate; use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; @@ -16,17 +17,18 @@ use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; use crate::polynomial::polynomial::PolynomialCoeffs; -/// Interpolates a polynomial, whose points are a (base field) coset of the multiplicative subgroup -/// with the given size, and whose values are extension field elements, given by input wires. -/// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. -#[derive(Clone, Debug)] -pub(crate) struct InterpolationGate, const D: usize> { +/// Interpolation gate with constraints of degree at most `1<, const D: usize> { pub subgroup_bits: usize, _phantom: PhantomData, } -impl, const D: usize> InterpolationGate { - pub fn new(subgroup_bits: usize) -> Self { +impl, const D: usize> InterpolationGate + for HighDegreeInterpolationGate +{ + fn new(subgroup_bits: usize) -> Self { Self { subgroup_bits, _phantom: PhantomData, @@ -36,60 +38,9 @@ impl, const D: usize> InterpolationGate { fn num_points(&self) -> usize { 1 << self.subgroup_bits } +} - /// Wire index of the coset shift. - pub fn wire_shift(&self) -> usize { - 0 - } - - fn start_values(&self) -> usize { - 1 - } - - /// Wire indices of the `i`th interpolant value. - pub fn wires_value(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_values() + i * D; - start..start + D - } - - fn start_evaluation_point(&self) -> usize { - self.start_values() + self.num_points() * D - } - - /// Wire indices of the point to evaluate the interpolant at. - pub fn wires_evaluation_point(&self) -> Range { - let start = self.start_evaluation_point(); - start..start + D - } - - fn start_evaluation_value(&self) -> usize { - self.start_evaluation_point() + D - } - - /// Wire indices of the interpolated value. - pub fn wires_evaluation_value(&self) -> Range { - let start = self.start_evaluation_value(); - start..start + D - } - - fn start_coeffs(&self) -> usize { - self.start_evaluation_value() + D - } - - /// The number of routed wires required in the typical usage of this gate, where the points to - /// interpolate, the evaluation point, and the corresponding value are all routed. - pub(crate) fn num_routed_wires(&self) -> usize { - self.start_coeffs() - } - - /// Wire indices of the interpolant's `i`th coefficient. - pub fn wires_coeff(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_coeffs() + i * D; - start..start + D - } - +impl, const D: usize> HighDegreeInterpolationGate { /// End of wire indices, exclusive. fn end(&self) -> usize { self.start_coeffs() + self.num_points() * D @@ -128,7 +79,9 @@ impl, const D: usize> InterpolationGate { } } -impl, const D: usize> Gate for InterpolationGate { +impl, const D: usize> Gate + for HighDegreeInterpolationGate +{ fn id(&self) -> String { format!("{:?}", self, D) } @@ -221,7 +174,7 @@ impl, const D: usize> Gate for InterpolationG ) -> Vec>> { let gen = InterpolationGenerator:: { gate_index, - gate: self.clone(), + gate: *self, _phantom: PhantomData, }; vec![Box::new(gen.adapter())] @@ -251,7 +204,7 @@ impl, const D: usize> Gate for InterpolationG #[derive(Debug)] struct InterpolationGenerator, const D: usize> { gate_index: usize, - gate: InterpolationGate, + gate: HighDegreeInterpolationGate, _phantom: PhantomData, } @@ -324,16 +277,17 @@ mod tests { use crate::field::extension_field::quartic::QuarticExtension; use crate::field::field_types::Field; use crate::field::goldilocks_field::GoldilocksField; + use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; - use crate::gates::interpolation::InterpolationGate; + use crate::gates::interpolation::HighDegreeInterpolationGate; use crate::hash::hash_types::HashOut; use crate::plonk::vars::EvaluationVars; use crate::polynomial::polynomial::PolynomialCoeffs; #[test] fn wire_indices() { - let gate = InterpolationGate:: { + let gate = HighDegreeInterpolationGate:: { subgroup_bits: 1, _phantom: PhantomData, }; @@ -352,12 +306,12 @@ mod tests { #[test] fn low_degree() { - test_low_degree::(InterpolationGate::new(2)); + test_low_degree::(HighDegreeInterpolationGate::new(2)); } #[test] fn eval_fns() -> Result<()> { - test_eval_fns::(InterpolationGate::new(2)) + test_eval_fns::(HighDegreeInterpolationGate::new(2)) } #[test] @@ -368,7 +322,7 @@ mod tests { /// Returns the local wires for an interpolation gate for given coeffs, points and eval point. fn get_wires( - gate: &InterpolationGate, + gate: &HighDegreeInterpolationGate, shift: F, coeffs: PolynomialCoeffs, eval_point: FF, @@ -390,7 +344,7 @@ mod tests { let shift = F::rand(); let coeffs = PolynomialCoeffs::new(vec![FF::rand(), FF::rand()]); let eval_point = FF::rand(); - let gate = InterpolationGate::::new(1); + let gate = HighDegreeInterpolationGate::::new(1); let vars = EvaluationVars { local_constants: &[], local_wires: &get_wires(&gate, shift, coeffs, eval_point), diff --git a/src/gates/low_degree_interpolation.rs b/src/gates/low_degree_interpolation.rs new file mode 100644 index 00000000..1c2b41e4 --- /dev/null +++ b/src/gates/low_degree_interpolation.rs @@ -0,0 +1,459 @@ +use std::marker::PhantomData; +use std::ops::Range; + +use crate::field::extension_field::algebra::PolynomialCoeffsAlgebra; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::{Extendable, FieldExtension}; +use crate::field::field_types::{Field, RichField}; +use crate::field::interpolation::interpolant; +use crate::gadgets::interpolation::InterpolationGate; +use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; +use crate::gates::gate::Gate; +use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; +use crate::iop::target::Target; +use crate::iop::wire::Wire; +use crate::iop::witness::{PartitionWitness, Witness}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; +use crate::polynomial::polynomial::PolynomialCoeffs; + +/// Interpolation gate with constraints of degree 2. +/// `eval_unfiltered_recursively` uses more gates than `HighDegreeInterpolationGate`. +#[derive(Copy, Clone, Debug)] +pub(crate) struct LowDegreeInterpolationGate, const D: usize> { + pub subgroup_bits: usize, + _phantom: PhantomData, +} + +impl, const D: usize> InterpolationGate + for LowDegreeInterpolationGate +{ + fn new(subgroup_bits: usize) -> Self { + Self { + subgroup_bits, + _phantom: PhantomData, + } + } + + fn num_points(&self) -> usize { + 1 << self.subgroup_bits + } +} + +impl, const D: usize> LowDegreeInterpolationGate { + /// `powers_shift(i)` is the wire index of `wire_shift^i`. + pub fn powers_shift(&self, i: usize) -> usize { + debug_assert!(0 < i && i < self.num_points()); + if i == 1 { + return self.wire_shift(); + } + self.end_coeffs() + i - 2 + } + + /// `powers_evalutation_point(i)` is the wire index of `evalutation_point^i`. + pub fn powers_evaluation_point(&self, i: usize) -> Range { + debug_assert!(0 < i && i < self.num_points()); + if i == 1 { + return self.wires_evaluation_point(); + } + let start = self.end_coeffs() + self.num_points() - 2 + (i - 2) * D; + start..start + D + } + + /// End of wire indices, exclusive. + fn end(&self) -> usize { + self.powers_evaluation_point(self.num_points() - 1).end + } + + /// The domain of the points we're interpolating. + fn coset(&self, shift: F) -> impl Iterator { + let g = F::primitive_root_of_unity(self.subgroup_bits); + let size = 1 << self.subgroup_bits; + // Speed matters here, so we avoid `cyclic_subgroup_coset_known_order` which allocates. + g.powers().take(size).map(move |x| x * shift) + } +} + +impl, const D: usize> Gate for LowDegreeInterpolationGate { + fn id(&self) -> String { + format!("{:?}", self, D) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) + .collect::>(); + + let mut powers_shift = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_shift(i)]) + .collect::>(); + let shift = powers_shift[0]; + for i in 1..self.num_points() - 1 { + constraints.push(powers_shift[i - 1] * shift - powers_shift[i]); + } + powers_shift.insert(0, F::Extension::ONE); + // `altered_coeffs[i] = c_i * shift^i`, where `c_i` is the original coefficient. + // Then, `altered(w^i) = original(shift*w^i)`. + let altered_coeffs = coeffs + .iter() + .zip(powers_shift) + .map(|(&c, p)| c.scalar_mul(p)) + .collect::>(); + let interpolant = PolynomialCoeffsAlgebra::new(coeffs); + let altered_interpolant = PolynomialCoeffsAlgebra::new(altered_coeffs); + + for (i, point) in F::Extension::two_adic_subgroup(self.subgroup_bits) + .into_iter() + .enumerate() + { + let value = vars.get_local_ext_algebra(self.wires_value(i)); + let computed_value = altered_interpolant.eval_base(point); + constraints.extend(&(value - computed_value).to_basefield_array()); + } + + let evaluation_point_powers = (1..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.powers_evaluation_point(i))) + .collect::>(); + let evaluation_point = evaluation_point_powers[0]; + for i in 1..self.num_points() - 1 { + constraints.extend( + (evaluation_point_powers[i - 1] * evaluation_point - evaluation_point_powers[i]) + .to_basefield_array(), + ); + } + let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + let computed_evaluation_value = interpolant.eval_with_powers(&evaluation_point_powers); + constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); + + constraints + } + + fn eval_unfiltered_base(&self, vars: EvaluationVarsBase) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext(self.wires_coeff(i))) + .collect::>(); + + let mut powers_shift = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_shift(i)]) + .collect::>(); + let shift = powers_shift[0]; + for i in 1..self.num_points() - 1 { + constraints.push(powers_shift[i - 1] * shift - powers_shift[i]); + } + powers_shift.insert(0, F::ONE); + // `altered_coeffs[i] = c_i * shift^i`, where `c_i` is the original coefficient. + // Then, `altered(w^i) = original(shift*w^i)`. + let altered_coeffs = coeffs + .iter() + .zip(powers_shift) + .map(|(&c, p)| c.scalar_mul(p)) + .collect::>(); + let interpolant = PolynomialCoeffs::new(coeffs); + let altered_interpolant = PolynomialCoeffs::new(altered_coeffs); + + for (i, point) in F::two_adic_subgroup(self.subgroup_bits) + .into_iter() + .enumerate() + { + let value = vars.get_local_ext(self.wires_value(i)); + let computed_value = altered_interpolant.eval_base(point); + constraints.extend(&(value - computed_value).to_basefield_array()); + } + + let evaluation_point_powers = (1..self.num_points()) + .map(|i| vars.get_local_ext(self.powers_evaluation_point(i))) + .collect::>(); + let evaluation_point = evaluation_point_powers[0]; + for i in 1..self.num_points() - 1 { + constraints.extend( + (evaluation_point_powers[i - 1] * evaluation_point - evaluation_point_powers[i]) + .to_basefield_array(), + ); + } + let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); + let computed_evaluation_value = interpolant.eval_with_powers(&evaluation_point_powers); + constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); + + constraints + } + + fn eval_unfiltered_recursively( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) + .collect::>(); + + let mut powers_shift = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_shift(i)]) + .collect::>(); + let shift = powers_shift[0]; + for i in 1..self.num_points() - 1 { + constraints.push(builder.mul_sub_extension( + powers_shift[i - 1], + shift, + powers_shift[i], + )); + } + powers_shift.insert(0, builder.one_extension()); + // `altered_coeffs[i] = c_i * shift^i`, where `c_i` is the original coefficient. + // Then, `altered(w^i) = original(shift*w^i)`. + let altered_coeffs = coeffs + .iter() + .zip(powers_shift) + .map(|(&c, p)| builder.scalar_mul_ext_algebra(p, c)) + .collect::>(); + let interpolant = PolynomialCoeffsExtAlgebraTarget(coeffs); + let altered_interpolant = PolynomialCoeffsExtAlgebraTarget(altered_coeffs); + + for (i, point) in F::Extension::two_adic_subgroup(self.subgroup_bits) + .into_iter() + .enumerate() + { + let value = vars.get_local_ext_algebra(self.wires_value(i)); + let point = builder.constant_extension(point); + let computed_value = altered_interpolant.eval_scalar(builder, point); + constraints.extend( + &builder + .sub_ext_algebra(value, computed_value) + .to_ext_target_array(), + ); + } + + let evaluation_point_powers = (1..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.powers_evaluation_point(i))) + .collect::>(); + let evaluation_point = evaluation_point_powers[0]; + for i in 1..self.num_points() - 1 { + let neg_one_ext = builder.neg_one_extension(); + let neg_new_power = + builder.scalar_mul_ext_algebra(neg_one_ext, evaluation_point_powers[i]); + let constraint = builder.mul_add_ext_algebra( + evaluation_point, + evaluation_point_powers[i - 1], + neg_new_power, + ); + constraints.extend(constraint.to_ext_target_array()); + } + let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + let computed_evaluation_value = + interpolant.eval_with_powers(builder, &evaluation_point_powers); + // let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); + // let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + // let computed_evaluation_value = interpolant.eval(builder, evaluation_point); + constraints.extend( + &builder + .sub_ext_algebra(evaluation_value, computed_evaluation_value) + .to_ext_target_array(), + ); + + constraints + } + + fn generators( + &self, + gate_index: usize, + _local_constants: &[F], + ) -> Vec>> { + let gen = InterpolationGenerator:: { + gate_index, + gate: *self, + _phantom: PhantomData, + }; + vec![Box::new(gen.adapter())] + } + + fn num_wires(&self) -> usize { + self.end() + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 2 + } + + fn num_constraints(&self) -> usize { + // `num_points * D` constraints to check for consistency between the coefficients and the + // point-value pairs, plus `D` constraints for the evaluation value, plus `(D+1)*(num_points-2)` + // to check power constraints for evaluation point and shift. + self.num_points() * D + D + (D + 1) * (self.num_points() - 2) + } +} + +#[derive(Debug)] +struct InterpolationGenerator, const D: usize> { + gate_index: usize, + gate: LowDegreeInterpolationGate, + _phantom: PhantomData, +} + +impl, const D: usize> SimpleGenerator + for InterpolationGenerator +{ + fn dependencies(&self) -> Vec { + let local_target = |input| { + Target::Wire(Wire { + gate: self.gate_index, + input, + }) + }; + + let local_targets = |inputs: Range| inputs.map(local_target); + + let num_points = self.gate.num_points(); + let mut deps = Vec::with_capacity(1 + D + num_points * D); + + deps.push(local_target(self.gate.wire_shift())); + deps.extend(local_targets(self.gate.wires_evaluation_point())); + for i in 0..num_points { + deps.extend(local_targets(self.gate.wires_value(i))); + } + deps + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let local_wire = |input| Wire { + gate: self.gate_index, + input, + }; + + let get_local_wire = |input| witness.get_wire(local_wire(input)); + + let get_local_ext = |wire_range: Range| { + debug_assert_eq!(wire_range.len(), D); + let values = wire_range.map(get_local_wire).collect::>(); + let arr = values.try_into().unwrap(); + F::Extension::from_basefield_array(arr) + }; + + let wire_shift = get_local_wire(self.gate.wire_shift()); + + for (i, power) in wire_shift + .powers() + .take(self.gate.num_points()) + .enumerate() + .skip(2) + { + out_buffer.set_wire(local_wire(self.gate.powers_shift(i)), power); + } + + // Compute the interpolant. + let points = self.gate.coset(wire_shift); + let points = points + .into_iter() + .enumerate() + .map(|(i, point)| (point.into(), get_local_ext(self.gate.wires_value(i)))) + .collect::>(); + let interpolant = interpolant(&points); + + for (i, &coeff) in interpolant.coeffs.iter().enumerate() { + let wires = self.gate.wires_coeff(i).map(local_wire); + out_buffer.set_ext_wires(wires, coeff); + } + + let evaluation_point = get_local_ext(self.gate.wires_evaluation_point()); + for (i, power) in evaluation_point + .powers() + .take(self.gate.num_points()) + .enumerate() + .skip(2) + { + out_buffer.set_extension_target( + ExtensionTarget::from_range(self.gate_index, self.gate.powers_evaluation_point(i)), + power, + ); + } + let evaluation_value = interpolant.eval(evaluation_point); + let evaluation_value_wires = self.gate.wires_evaluation_value().map(local_wire); + out_buffer.set_ext_wires(evaluation_value_wires, evaluation_value); + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use crate::field::extension_field::quadratic::QuadraticExtension; + use crate::field::field_types::Field; + use crate::field::goldilocks_field::GoldilocksField; + use crate::gadgets::interpolation::InterpolationGate; + use crate::gates::gate::Gate; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; + use crate::hash::hash_types::HashOut; + use crate::plonk::vars::EvaluationVars; + use crate::polynomial::polynomial::PolynomialCoeffs; + + #[test] + fn low_degree() { + test_low_degree::(LowDegreeInterpolationGate::new(4)); + } + + #[test] + fn eval_fns() -> Result<()> { + test_eval_fns::(LowDegreeInterpolationGate::new(4)) + } + + #[test] + fn test_gate_constraint() { + type F = GoldilocksField; + type FF = QuadraticExtension; + const D: usize = 2; + + /// Returns the local wires for an interpolation gate for given coeffs, points and eval point. + fn get_wires( + gate: &LowDegreeInterpolationGate, + shift: F, + coeffs: PolynomialCoeffs, + eval_point: FF, + ) -> Vec { + let points = gate.coset(shift); + let mut v = vec![shift]; + for x in points { + v.extend(coeffs.eval(x.into()).0); + } + v.extend(eval_point.0); + v.extend(coeffs.eval(eval_point).0); + for i in 0..coeffs.len() { + v.extend(coeffs.coeffs[i].0); + } + v.extend(shift.powers().skip(2).take(gate.num_points() - 2)); + v.extend( + eval_point + .powers() + .skip(2) + .take(gate.num_points() - 2) + .flat_map(|ff| ff.0), + ); + v.iter().map(|&x| x.into()).collect::>() + } + + // Get a working row for LowDegreeInterpolationGate. + let subgroup_bits = 4; + let shift = F::rand(); + let coeffs = PolynomialCoeffs::new(FF::rand_vec(1 << subgroup_bits)); + let eval_point = FF::rand(); + let gate = LowDegreeInterpolationGate::::new(subgroup_bits); + let vars = EvaluationVars { + local_constants: &[], + local_wires: &get_wires(&gate, shift, coeffs, eval_point), + public_inputs_hash: &HashOut::rand(), + }; + + assert!( + gate.eval_unfiltered(vars).iter().all(|x| x.is_zero()), + "Gate constraints are not satisfied." + ); + } +} diff --git a/src/gates/mod.rs b/src/gates/mod.rs index 369c9ea5..54289733 100644 --- a/src/gates/mod.rs +++ b/src/gates/mod.rs @@ -14,6 +14,7 @@ pub mod gate_tree; pub mod gmimc; pub mod insertion; pub mod interpolation; +pub mod low_degree_interpolation; pub mod multiplication_extension; pub mod noop; pub mod poseidon; diff --git a/src/plonk/circuit_data.rs b/src/plonk/circuit_data.rs index c2a8d6d0..bf8024df 100644 --- a/src/plonk/circuit_data.rs +++ b/src/plonk/circuit_data.rs @@ -63,16 +63,29 @@ impl CircuitConfig { num_routed_wires: 80, constant_gate_size: 5, use_base_arithmetic_gate: true, - security_bits: 93, + security_bits: 100, rate_bits: 3, num_challenges: 2, zero_knowledge: false, + cap_height: 4, + fri_config: FriConfig { + proof_of_work_bits: 16, + reduction_strategy: FriReductionStrategy::ConstantArityBits(4, 5), + num_query_rounds: 28, + }, + } + } + + pub fn size_optimized_recursion_config() -> Self { + Self { + security_bits: 93, cap_height: 3, fri_config: FriConfig { - proof_of_work_bits: 15, reduction_strategy: FriReductionStrategy::ConstantArityBits(3, 5), num_query_rounds: 26, + ..CircuitConfig::standard_recursion_config().fri_config }, + ..CircuitConfig::standard_recursion_config() } } diff --git a/src/plonk/proof.rs b/src/plonk/proof.rs index 815f807d..70e38588 100644 --- a/src/plonk/proof.rs +++ b/src/plonk/proof.rs @@ -310,6 +310,7 @@ mod tests { use crate::field::field_types::Field; use crate::field::goldilocks_field::GoldilocksField; use crate::fri::reduction_strategies::FriReductionStrategy; + use crate::gates::noop::NoopGate; use crate::iop::witness::PartialWitness; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::CircuitConfig; @@ -336,6 +337,9 @@ mod tests { let zt = builder.constant(z); let comp_zt = builder.mul(xt, yt); builder.connect(zt, comp_zt); + for _ in 0..100 { + builder.add_gate(NoopGate, vec![]); + } let data = builder.build(); let proof = data.prove(pw)?; verify(proof.clone(), &data.verifier_only, &data.common)?; diff --git a/src/plonk/recursive_verifier.rs b/src/plonk/recursive_verifier.rs index 2dc0223b..98c866f8 100644 --- a/src/plonk/recursive_verifier.rs +++ b/src/plonk/recursive_verifier.rs @@ -408,7 +408,7 @@ mod tests { type F = GoldilocksField; const D: usize = 2; - let standard_config = CircuitConfig::standard_recursion_config(); + let standard_config = CircuitConfig::size_optimized_recursion_config(); // An initial dummy proof. let (proof, vd, cd) = dummy_proof::(&standard_config, 4_000)?; @@ -456,7 +456,7 @@ mod tests { num_routed_wires: 25, fri_config: FriConfig { proof_of_work_bits: 21, - reduction_strategy: FriReductionStrategy::MinSize(None), + reduction_strategy: FriReductionStrategy::MinSize(Some(3)), num_query_rounds: 9, }, ..high_rate_config diff --git a/src/polynomial/polynomial.rs b/src/polynomial/polynomial.rs index a021ecd2..b5a7fabd 100644 --- a/src/polynomial/polynomial.rs +++ b/src/polynomial/polynomial.rs @@ -120,6 +120,16 @@ impl PolynomialCoeffs { .fold(F::ZERO, |acc, &c| acc * x + c) } + /// Evaluate the polynomial at a point given its powers. The first power is the point itself, not 1. + pub fn eval_with_powers(&self, powers: &[F]) -> F { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + c * x) + } + pub fn eval_base(&self, x: F::BaseField) -> F where F: FieldExtension, @@ -130,6 +140,19 @@ impl PolynomialCoeffs { .fold(F::ZERO, |acc, &c| acc.scalar_mul(x) + c) } + /// Evaluate the polynomial at a point given its powers. The first power is the point itself, not 1. + pub fn eval_base_with_powers(&self, powers: &[F::BaseField]) -> F + where + F: FieldExtension, + { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + x.scalar_mul(c)) + } + pub fn lde_multiple(polys: Vec<&Self>, rate_bits: usize) -> Vec { polys.into_iter().map(|p| p.lde(rate_bits)).collect() }