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::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::PolynomialCoeffs; /// Interpolation gate with constraints of degree at most `1<, const D: usize> { pub subgroup_bits: usize, _phantom: PhantomData, } impl, const D: usize> InterpolationGate for HighDegreeInterpolationGate { fn new(subgroup_bits: usize) -> Self { Self { subgroup_bits, _phantom: PhantomData, } } fn num_points(&self) -> usize { 1 << self.subgroup_bits } } impl, const D: usize> HighDegreeInterpolationGate { /// End of wire indices, exclusive. fn end(&self) -> usize { self.start_coeffs() + self.num_points() * D } /// 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) } /// The domain of the points we're interpolating. fn coset_ext(&self, shift: F::Extension) -> impl Iterator { let g = F::primitive_root_of_unity(self.subgroup_bits); let size = 1 << self.subgroup_bits; g.powers().take(size).map(move |x| shift.scalar_mul(x)) } /// The domain of the points we're interpolating. fn coset_ext_recursive( &self, builder: &mut CircuitBuilder, shift: ExtensionTarget, ) -> Vec> { let g = F::primitive_root_of_unity(self.subgroup_bits); let size = 1 << self.subgroup_bits; g.powers() .take(size) .map(move |x| { let subgroup_element = builder.constant(x); builder.scalar_mul_ext(subgroup_element, shift) }) .collect() } } impl, const D: usize> Gate for HighDegreeInterpolationGate { 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 interpolant = PolynomialCoeffsAlgebra::new(coeffs); let coset = self.coset_ext(vars.local_wires[self.wire_shift()]); for (i, point) in coset.into_iter().enumerate() { let value = vars.get_local_ext_algebra(self.wires_value(i)); let computed_value = interpolant.eval_base(point); constraints.extend(&(value - computed_value).to_basefield_array()); } 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(evaluation_point); 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 interpolant = PolynomialCoeffs::new(coeffs); let coset = self.coset(vars.local_wires[self.wire_shift()]); for (i, point) in coset.into_iter().enumerate() { let value = vars.get_local_ext(self.wires_value(i)); let computed_value = interpolant.eval_base(point); constraints.extend(&(value - computed_value).to_basefield_array()); } let evaluation_point = vars.get_local_ext(self.wires_evaluation_point()); let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); let computed_evaluation_value = interpolant.eval(evaluation_point); 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 interpolant = PolynomialCoeffsExtAlgebraTarget(coeffs); let coset = self.coset_ext_recursive(builder, vars.local_wires[self.wire_shift()]); for (i, point) in coset.into_iter().enumerate() { let value = vars.get_local_ext_algebra(self.wires_value(i)); let computed_value = interpolant.eval_scalar(builder, point); constraints.extend( &builder .sub_ext_algebra(value, computed_value) .to_ext_target_array(), ); } 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 { // The highest power of x is `num_points - 1`, and then multiplication by the coefficient // adds 1. self.num_points() } 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. self.num_points() * D + D } } #[derive(Debug)] struct InterpolationGenerator, const D: usize> { gate_index: usize, gate: HighDegreeInterpolationGate, _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) }; // Compute the interpolant. let points = self.gate.coset(get_local_wire(self.gate.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()); 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 std::marker::PhantomData; use anyhow::Result; 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::HighDegreeInterpolationGate; use crate::hash::hash_types::HashOut; use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use crate::plonk::vars::EvaluationVars; use crate::polynomial::PolynomialCoeffs; #[test] fn wire_indices() { let gate = HighDegreeInterpolationGate:: { subgroup_bits: 1, _phantom: PhantomData, }; // The exact indices aren't really important, but we want to make sure we don't have any // overlaps or gaps. assert_eq!(gate.wire_shift(), 0); assert_eq!(gate.wires_value(0), 1..5); assert_eq!(gate.wires_value(1), 5..9); assert_eq!(gate.wires_evaluation_point(), 9..13); assert_eq!(gate.wires_evaluation_value(), 13..17); assert_eq!(gate.wires_coeff(0), 17..21); assert_eq!(gate.wires_coeff(1), 21..25); assert_eq!(gate.num_wires(), 25); } #[test] fn low_degree() { test_low_degree::(HighDegreeInterpolationGate::new(2)); } #[test] fn eval_fns() -> Result<()> { const D: usize = 2; type C = PoseidonGoldilocksConfig; type F = >::F; test_eval_fns::(HighDegreeInterpolationGate::new(2)) } #[test] fn test_gate_constraint() { const D: usize = 2; type C = PoseidonGoldilocksConfig; type F = >::F; type FF = >::FE; /// Returns the local wires for an interpolation gate for given coeffs, points and eval point. fn get_wires( gate: &HighDegreeInterpolationGate, 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.iter().map(|&x| x.into()).collect::>() } // Get a working row for InterpolationGate. let shift = F::rand(); let coeffs = PolynomialCoeffs::new(vec![FF::rand(), FF::rand()]); let eval_point = FF::rand(); let gate = HighDegreeInterpolationGate::::new(1); 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." ); } }