use std::convert::TryInto; 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::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::PartialWitness; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; use crate::polynomial::polynomial::PolynomialCoeffs; /// Evaluates the interpolant of some given elements from a field extension. /// /// In particular, this gate takes as inputs `num_points` points, `num_points` values, and the point /// to evaluate the interpolant at. It computes the interpolant and outputs its evaluation at the /// given point. #[derive(Clone, Debug)] pub(crate) struct InterpolationGate, const D: usize> { pub num_points: usize, _phantom: PhantomData, } impl, const D: usize> InterpolationGate { pub fn new(num_points: usize) -> Self { Self { num_points, _phantom: PhantomData, } } fn start_points(&self) -> usize { 0 } /// Wire indices of the `i`th interpolant point. pub fn wire_point(&self, i: usize) -> usize { debug_assert!(i < self.num_points); self.start_points() + i } fn start_values(&self) -> usize { self.start_points() + self.num_points } /// 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 } /// 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 } /// End of wire indices, exclusive. fn end(&self) -> usize { self.start_coeffs() + self.num_points * D } } impl, const D: usize> Gate for InterpolationGate { 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); for i in 0..self.num_points { let point = vars.local_wires[self.wire_point(i)]; let value = vars.get_local_ext_algebra(self.wires_value(i)); let computed_value = interpolant.eval(point.into()); 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); for i in 0..self.num_points { let point = vars.local_wires[self.wire_point(i)]; let value = vars.get_local_ext(self.wires_value(i)); let computed_value = interpolant.eval(point.into()); 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); for i in 0..self.num_points { let point = vars.local_wires[self.wire_point(i)]; 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.clone(), _phantom: PhantomData, }; vec![Box::new(gen)] } 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 } } struct InterpolationGenerator, const D: usize> { gate_index: usize, gate: InterpolationGate, _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 mut deps = Vec::new(); deps.extend(local_targets(self.gate.wires_evaluation_point())); for i in 0..self.gate.num_points { deps.push(local_target(self.gate.wire_point(i))); deps.extend(local_targets(self.gate.wires_value(i))); } deps } fn run_once(&self, witness: &PartialWitness) -> GeneratedValues { let n = self.gate.num_points; 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 = (0..n) .map(|i| { ( F::Extension::from_basefield(get_local_wire(self.gate.wire_point(i))), get_local_ext(self.gate.wires_value(i)), ) }) .collect::>(); let interpolant = interpolant(&points); let mut result = GeneratedValues::::with_capacity(D * (self.gate.num_points + 1)); for (i, &coeff) in interpolant.coeffs.iter().enumerate() { let wires = self.gate.wires_coeff(i).map(local_wire); result.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); result.set_ext_wires(evaluation_value_wires, evaluation_value); result } } #[cfg(test)] mod tests { use std::marker::PhantomData; use anyhow::Result; use crate::field::crandall_field::CrandallField; use crate::field::extension_field::quartic::QuarticCrandallField; use crate::field::field_types::Field; use crate::gates::gate::Gate; use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; use crate::gates::interpolation::InterpolationGate; use crate::hash::hash_types::HashOut; use crate::plonk::vars::EvaluationVars; use crate::polynomial::polynomial::PolynomialCoeffs; #[test] fn wire_indices() { let gate = InterpolationGate:: { num_points: 2, _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_point(0), 0); assert_eq!(gate.wire_point(1), 1); assert_eq!(gate.wires_value(0), 2..6); assert_eq!(gate.wires_value(1), 6..10); assert_eq!(gate.wires_evaluation_point(), 10..14); assert_eq!(gate.wires_evaluation_value(), 14..18); assert_eq!(gate.wires_coeff(0), 18..22); assert_eq!(gate.wires_coeff(1), 22..26); assert_eq!(gate.num_wires(), 26); } #[test] fn low_degree() { test_low_degree::(InterpolationGate::new(4)); } #[test] fn eval_fns() -> Result<()> { test_eval_fns::(InterpolationGate::new(4)) } #[test] fn test_gate_constraint() { type F = CrandallField; type FF = QuarticCrandallField; const D: usize = 4; /// Returns the local wires for an interpolation gate for given coeffs, points and eval point. fn get_wires( num_points: usize, coeffs: PolynomialCoeffs, points: Vec, eval_point: FF, ) -> Vec { let mut v = Vec::new(); v.extend_from_slice(&points); for j in 0..num_points { v.extend(coeffs.eval(points[j].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 coeffs = PolynomialCoeffs::new(vec![FF::rand(), FF::rand()]); let points = vec![F::rand(), F::rand()]; let eval_point = FF::rand(); let gate = InterpolationGate:: { num_points: 2, _phantom: PhantomData, }; let vars = EvaluationVars { local_constants: &[], local_wires: &get_wires(2, coeffs, points, eval_point), public_inputs_hash: &HashOut::rand(), }; assert!( gate.eval_unfiltered(vars).iter().all(|x| x.is_zero()), "Gate constraints are not satisfied." ); } }