diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs index 1a3739b4..0526ed7e 100644 --- a/plonky2/src/fri/recursive_verifier.rs +++ b/plonky2/src/fri/recursive_verifier.rs @@ -10,7 +10,7 @@ use crate::fri::structure::{FriBatchInfoTarget, FriInstanceInfoTarget, FriOpenin use crate::fri::{FriConfig, FriParams}; use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; -use crate::gates::interpolation::HighDegreeInterpolationGate; +use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; use crate::gates::random_access::RandomAccessGate; use crate::hash::hash_types::MerkleCapTarget; diff --git a/plonky2/src/gadgets/interpolation.rs b/plonky2/src/gadgets/interpolation.rs deleted file mode 100644 index b22f3b59..00000000 --- a/plonky2/src/gadgets/interpolation.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::ops::Range; - -use plonky2_field::extension::Extendable; - -use crate::gates::gate::Gate; -use crate::hash::hash_types::RichField; -use crate::iop::ext_target::ExtensionTarget; -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(crate) fn interpolate_coset>( - &mut self, - subgroup_bits: usize, - coset_shift: Target, - values: &[ExtensionTarget], - evaluation_point: ExtensionTarget, - ) -> ExtensionTarget { - let gate = G::new(subgroup_bits); - let row = self.add_gate(gate, vec![]); - self.connect(coset_shift, Target::wire(row, gate.wire_shift())); - for (i, &v) in values.iter().enumerate() { - self.connect_extension(v, ExtensionTarget::from_range(row, gate.wires_value(i))); - } - self.connect_extension( - evaluation_point, - ExtensionTarget::from_range(row, gate.wires_evaluation_point()), - ); - - ExtensionTarget::from_range(row, gate.wires_evaluation_value()) - } -} - -#[cfg(test)] -mod tests { - use anyhow::Result; - use plonky2_field::extension::FieldExtension; - use plonky2_field::interpolation::interpolant; - use plonky2_field::types::Field; - - 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; - use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; - use crate::plonk::verifier::verify; - - #[test] - fn test_interpolate() -> Result<()> { - const D: usize = 2; - type C = PoseidonGoldilocksConfig; - type F = >::F; - type FF = >::FE; - let config = CircuitConfig::standard_recursion_config(); - let pw = PartialWitness::new(); - let mut builder = CircuitBuilder::::new(config); - - let subgroup_bits = 2; - let len = 1 << subgroup_bits; - let coset_shift = F::rand(); - let g = F::primitive_root_of_unity(subgroup_bits); - let points = F::cyclic_subgroup_coset_known_order(g, coset_shift, len); - let values = FF::rand_vec(len); - - let homogeneous_points = points - .iter() - .zip(values.iter()) - .map(|(&a, &b)| (>::from_basefield(a), b)) - .collect::>(); - - let true_interpolant = interpolant(&homogeneous_points); - - let z = FF::rand(); - let true_eval = true_interpolant.eval(z); - - let coset_shift_target = builder.constant(coset_shift); - - let value_targets = values - .iter() - .map(|&v| (builder.constant_extension(v))) - .collect::>(); - - let zt = builder.constant_extension(z); - - 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_hd, true_eval_target); - builder.connect_extension(eval_ld, true_eval_target); - - let data = builder.build::(); - let proof = data.prove(pw)?; - - verify(proof, &data.verifier_only, &data.common) - } -} diff --git a/plonky2/src/gadgets/mod.rs b/plonky2/src/gadgets/mod.rs index 6309eb3d..a3e50c4e 100644 --- a/plonky2/src/gadgets/mod.rs +++ b/plonky2/src/gadgets/mod.rs @@ -1,7 +1,6 @@ pub mod arithmetic; pub mod arithmetic_extension; pub mod hash; -pub mod interpolation; pub mod polynomial; pub mod random_access; pub mod range_check; diff --git a/plonky2/src/gates/high_degree_interpolation.rs b/plonky2/src/gates/high_degree_interpolation.rs new file mode 100644 index 00000000..1c78a6e6 --- /dev/null +++ b/plonky2/src/gates/high_degree_interpolation.rs @@ -0,0 +1,363 @@ +use std::marker::PhantomData; +use std::ops::Range; + +use plonky2_field::extension::algebra::PolynomialCoeffsAlgebra; +use plonky2_field::extension::{Extendable, FieldExtension}; +use plonky2_field::interpolation::interpolant; +use plonky2_field::polynomial::PolynomialCoeffs; + +use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; +use crate::gates::gate::Gate; +use crate::gates::interpolation::InterpolationGate; +use crate::gates::util::StridedConstraintConsumer; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +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}; + +/// One of the instantiations of `InterpolationGate`: allows constraints of variable +/// degree, up to `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_circuit( + &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_one( + &self, + vars: EvaluationVarsBase, + mut yield_constr: StridedConstraintConsumer, + ) { + 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); + yield_constr.many((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); + yield_constr.many((evaluation_value - computed_evaluation_value).to_basefield_array()); + } + + fn eval_unfiltered_circuit( + &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_circuit(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, row: usize, _local_constants: &[F]) -> Vec>> { + let gen = InterpolationGenerator:: { + row, + 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> { + row: usize, + gate: HighDegreeInterpolationGate, + _phantom: PhantomData, +} + +impl, const D: usize> SimpleGenerator + for InterpolationGenerator +{ + fn dependencies(&self) -> Vec { + let local_target = |column| { + Target::Wire(Wire { + row: self.row, + column, + }) + }; + + let local_targets = |columns: Range| columns.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 = |column| Wire { + row: self.row, + column, + }; + + let get_local_wire = |column| witness.get_wire(local_wire(column)); + + 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 plonky2_field::goldilocks_field::GoldilocksField; + use plonky2_field::polynomial::PolynomialCoeffs; + use plonky2_field::types::Field; + + use crate::gadgets::interpolation::InterpolationGate; + use crate::gates::gate::Gate; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; + use crate::hash::hash_types::HashOut; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::plonk::vars::EvaluationVars; + + #[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." + ); + } +} diff --git a/plonky2/src/gates/interpolation.rs b/plonky2/src/gates/interpolation.rs index a619d1f2..d417fa6b 100644 --- a/plonky2/src/gates/interpolation.rs +++ b/plonky2/src/gates/interpolation.rs @@ -1,361 +1,178 @@ -use std::marker::PhantomData; use std::ops::Range; -use plonky2_field::extension::algebra::PolynomialCoeffsAlgebra; -use plonky2_field::extension::{Extendable, FieldExtension}; -use plonky2_field::interpolation::interpolant; -use plonky2_field::polynomial::PolynomialCoeffs; +use plonky2_field::extension::Extendable; -use crate::gadgets::interpolation::InterpolationGate; -use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::Gate; -use crate::gates::util::StridedConstraintConsumer; use crate::hash::hash_types::RichField; use crate::iop::ext_target::ExtensionTarget; -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}; -/// 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 +/// 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 { - Self { - subgroup_bits, - _phantom: PhantomData, - } - } + fn new(subgroup_bits: usize) -> Self; - fn num_points(&self) -> usize { - 1 << self.subgroup_bits - } -} + fn num_points(&self) -> usize; -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_circuit( - &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_one( - &self, - vars: EvaluationVarsBase, - mut yield_constr: StridedConstraintConsumer, - ) { - 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); - yield_constr.many((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); - yield_constr.many((evaluation_value - computed_evaluation_value).to_basefield_array()); - } - - fn eval_unfiltered_circuit( - &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_circuit(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, row: usize, _local_constants: &[F]) -> Vec>> { - let gen = InterpolationGenerator:: { - row, - gate: *self, - _phantom: PhantomData, - }; - vec![Box::new(gen.adapter())] - } - - fn num_wires(&self) -> usize { - self.end() - } - - fn num_constants(&self) -> usize { + /// Wire index of the coset shift. + fn wire_shift(&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 start_values(&self) -> usize { + 1 } - 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 + /// 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() } } -#[derive(Debug)] -struct InterpolationGenerator, const D: usize> { - row: usize, - gate: HighDegreeInterpolationGate, - _phantom: PhantomData, -} - -impl, const D: usize> SimpleGenerator - for InterpolationGenerator -{ - fn dependencies(&self) -> Vec { - let local_target = |column| { - Target::Wire(Wire { - row: self.row, - column, - }) - }; - - let local_targets = |columns: Range| columns.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))); +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(crate) fn interpolate_coset>( + &mut self, + subgroup_bits: usize, + coset_shift: Target, + values: &[ExtensionTarget], + evaluation_point: ExtensionTarget, + ) -> ExtensionTarget { + let gate = G::new(subgroup_bits); + let row = self.add_gate(gate, vec![]); + self.connect(coset_shift, Target::wire(row, gate.wire_shift())); + for (i, &v) in values.iter().enumerate() { + self.connect_extension(v, ExtensionTarget::from_range(row, gate.wires_value(i))); } - deps - } + self.connect_extension( + evaluation_point, + ExtensionTarget::from_range(row, gate.wires_evaluation_point()), + ); - fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let local_wire = |column| Wire { - row: self.row, - column, - }; - - let get_local_wire = |column| witness.get_wire(local_wire(column)); - - 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); + ExtensionTarget::from_range(row, gate.wires_evaluation_value()) } } #[cfg(test)] mod tests { - use std::marker::PhantomData; - use anyhow::Result; - use plonky2_field::goldilocks_field::GoldilocksField; - use plonky2_field::polynomial::PolynomialCoeffs; + use plonky2_field::extension::FieldExtension; + use plonky2_field::interpolation::interpolant; use plonky2_field::types::Field; - 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::gates::high_degree_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; use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; - use crate::plonk::vars::EvaluationVars; + use crate::plonk::verifier::verify; #[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() { + fn test_interpolate() -> Result<()> { const D: usize = 2; type C = PoseidonGoldilocksConfig; type F = >::F; type FF = >::FE; + let config = CircuitConfig::standard_recursion_config(); + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); - /// 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() - } + let subgroup_bits = 2; + let len = 1 << subgroup_bits; + let coset_shift = F::rand(); + let g = F::primitive_root_of_unity(subgroup_bits); + let points = F::cyclic_subgroup_coset_known_order(g, coset_shift, len); + let values = FF::rand_vec(len); - // 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(), - }; + let homogeneous_points = points + .iter() + .zip(values.iter()) + .map(|(&a, &b)| (>::from_basefield(a), b)) + .collect::>(); - assert!( - gate.eval_unfiltered(vars).iter().all(|x| x.is_zero()), - "Gate constraints are not satisfied." + let true_interpolant = interpolant(&homogeneous_points); + + let z = FF::rand(); + let true_eval = true_interpolant.eval(z); + + let coset_shift_target = builder.constant(coset_shift); + + let value_targets = values + .iter() + .map(|&v| (builder.constant_extension(v))) + .collect::>(); + + let zt = builder.constant_extension(z); + + 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_hd, true_eval_target); + builder.connect_extension(eval_ld, true_eval_target); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) } } diff --git a/plonky2/src/gates/low_degree_interpolation.rs b/plonky2/src/gates/low_degree_interpolation.rs index dabadfa4..3ffe5922 100644 --- a/plonky2/src/gates/low_degree_interpolation.rs +++ b/plonky2/src/gates/low_degree_interpolation.rs @@ -7,9 +7,9 @@ use plonky2_field::interpolation::interpolant; use plonky2_field::polynomial::PolynomialCoeffs; use plonky2_field::types::Field; -use crate::gadgets::interpolation::InterpolationGate; use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::Gate; +use crate::gates::interpolation::InterpolationGate; use crate::gates::util::StridedConstraintConsumer; use crate::hash::hash_types::RichField; use crate::iop::ext_target::ExtensionTarget; diff --git a/plonky2/src/gates/mod.rs b/plonky2/src/gates/mod.rs index 48e319ef..1d2fc058 100644 --- a/plonky2/src/gates/mod.rs +++ b/plonky2/src/gates/mod.rs @@ -7,6 +7,7 @@ pub mod base_sum; pub mod constant; pub mod exponentiation; pub mod gate; +pub mod high_degree_interpolation; pub mod interpolation; pub mod low_degree_interpolation; pub mod multiplication_extension;