Merge pull request #374 from mir-protocol/arity4

Low-degree interpolation gate and log-arity of 4 in recursive benchmark + 2^12 shrinking recursion with 100 security bits
This commit is contained in:
wborgeaud 2021-11-22 23:01:45 +01:00 committed by GitHub
commit 1fed718a70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 700 additions and 94 deletions

View File

@ -160,12 +160,32 @@ impl<F: OEF<D>, const D: usize> PolynomialCoeffsAlgebra<F, D> {
.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<F, D>]) -> ExtensionAlgebra<F, D> {
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<F, D> {
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<F, D> {
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)]

View File

@ -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<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
arity_bits: usize,
evals: &[ExtensionTarget<D>],
beta: ExtensionTarget<D>,
common_data: &CommonCircuitData<F, D>,
) -> ExtensionTarget<D> {
let arity = 1 << arity_bits;
debug_assert_eq!(evals.len(), arity);
@ -43,25 +46,50 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
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::<LowDegreeInterpolationGate<F, D>>(
arity_bits,
coset_start,
&evals,
beta,
)
} else {
self.interpolate_coset::<HighDegreeInterpolationGate<F, D>>(
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<F, D>,
) {
let random_access = RandomAccessGate::<F, D>::new_from_config(
&self.config,
max_fri_arity_bits.max(self.config.cap_height),
);
let interpolation_gate = InterpolationGate::<F, D>::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::<F, D>::new(max_fri_arity_bits);
(gate.num_wires(), gate.num_routed_wires())
} else {
let gate = HighDegreeInterpolationGate::<F, D>::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<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
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<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
arity_bits,
evals,
betas[i],
&common_data
)
);

View File

@ -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<F: RichField + Extendable<D>, const D: usize>:
Gate<F, D> + 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<usize> {
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<usize> {
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<usize> {
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<usize> {
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<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// 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<G: InterpolationGate<F, D>>(
&mut self,
subgroup_bits: usize,
coset_shift: Target,
values: &[ExtensionTarget<D>],
evaluation_point: ExtensionTarget<D>,
) -> ExtensionTarget<D> {
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<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
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<GoldilocksField>;
const D: usize = 2;
type FF = QuadraticExtension<GoldilocksField>;
let config = CircuitConfig::standard_recursion_config();
let pw = PartialWitness::new();
let mut builder = CircuitBuilder::<F, 4>::new(config);
let mut builder = CircuitBuilder::<F, D>::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)| (<FF as FieldExtension<4>>::from_basefield(a), b))
.map(|(&a, &b)| (<FF as FieldExtension<D>>::from_basefield(a), b))
.collect::<Vec<_>>();
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::<HighDegreeInterpolationGate<F, D>>(
subgroup_bits,
coset_shift_target,
&value_targets,
zt,
);
let eval_ld = builder.interpolate_coset::<LowDegreeInterpolationGate<F, D>>(
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)?;

View File

@ -64,4 +64,21 @@ impl<const D: usize> PolynomialCoeffsExtAlgebraTarget<D> {
}
acc
}
/// Evaluate the polynomial at a point given its powers. The first power is the point itself, not 1.
pub fn eval_with_powers<F>(
&self,
builder: &mut CircuitBuilder<F, D>,
powers: &[ExtensionAlgebraTarget<D>],
) -> ExtensionAlgebraTarget<D>
where
F: RichField + Extendable<D>,
{
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))
}
}

View File

@ -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::<F, D, 12>::new()),
GateRef::new(InterpolationGate::new(2)),
GateRef::new(HighDegreeInterpolationGate::new(2)),
];
let (tree, _, _) = Tree::from_gates(gates.clone());

View File

@ -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<F: RichField + Extendable<D>, const D: usize> {
/// Interpolation gate with constraints of degree at most `1<<subgroup_bits`.
/// `eval_unfiltered_recursively` uses less gates than `LowDegreeInterpolationGate`.
#[derive(Copy, Clone, Debug)]
pub(crate) struct HighDegreeInterpolationGate<F: RichField + Extendable<D>, const D: usize> {
pub subgroup_bits: usize,
_phantom: PhantomData<F>,
}
impl<F: RichField + Extendable<D>, const D: usize> InterpolationGate<F, D> {
pub fn new(subgroup_bits: usize) -> Self {
impl<F: RichField + Extendable<D>, const D: usize> InterpolationGate<F, D>
for HighDegreeInterpolationGate<F, D>
{
fn new(subgroup_bits: usize) -> Self {
Self {
subgroup_bits,
_phantom: PhantomData,
@ -36,60 +38,9 @@ impl<F: RichField + Extendable<D>, const D: usize> InterpolationGate<F, D> {
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<usize> {
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<usize> {
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<usize> {
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<usize> {
debug_assert!(i < self.num_points());
let start = self.start_coeffs() + i * D;
start..start + D
}
impl<F: RichField + Extendable<D>, const D: usize> HighDegreeInterpolationGate<F, D> {
/// End of wire indices, exclusive.
fn end(&self) -> usize {
self.start_coeffs() + self.num_points() * D
@ -128,7 +79,9 @@ impl<F: RichField + Extendable<D>, const D: usize> InterpolationGate<F, D> {
}
}
impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for InterpolationGate<F, D> {
impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D>
for HighDegreeInterpolationGate<F, D>
{
fn id(&self) -> String {
format!("{:?}<D={}>", self, D)
}
@ -221,7 +174,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for InterpolationG
) -> Vec<Box<dyn WitnessGenerator<F>>> {
let gen = InterpolationGenerator::<F, D> {
gate_index,
gate: self.clone(),
gate: *self,
_phantom: PhantomData,
};
vec![Box::new(gen.adapter())]
@ -251,7 +204,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for InterpolationG
#[derive(Debug)]
struct InterpolationGenerator<F: RichField + Extendable<D>, const D: usize> {
gate_index: usize,
gate: InterpolationGate<F, D>,
gate: HighDegreeInterpolationGate<F, D>,
_phantom: PhantomData<F>,
}
@ -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::<GoldilocksField, 4> {
let gate = HighDegreeInterpolationGate::<GoldilocksField, 4> {
subgroup_bits: 1,
_phantom: PhantomData,
};
@ -352,12 +306,12 @@ mod tests {
#[test]
fn low_degree() {
test_low_degree::<GoldilocksField, _, 4>(InterpolationGate::new(2));
test_low_degree::<GoldilocksField, _, 4>(HighDegreeInterpolationGate::new(2));
}
#[test]
fn eval_fns() -> Result<()> {
test_eval_fns::<GoldilocksField, _, 4>(InterpolationGate::new(2))
test_eval_fns::<GoldilocksField, _, 4>(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<F, D>,
gate: &HighDegreeInterpolationGate<F, D>,
shift: F,
coeffs: PolynomialCoeffs<FF>,
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::<F, D>::new(1);
let gate = HighDegreeInterpolationGate::<F, D>::new(1);
let vars = EvaluationVars {
local_constants: &[],
local_wires: &get_wires(&gate, shift, coeffs, eval_point),

View File

@ -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<F: RichField + Extendable<D>, const D: usize> {
pub subgroup_bits: usize,
_phantom: PhantomData<F>,
}
impl<F: RichField + Extendable<D>, const D: usize> InterpolationGate<F, D>
for LowDegreeInterpolationGate<F, D>
{
fn new(subgroup_bits: usize) -> Self {
Self {
subgroup_bits,
_phantom: PhantomData,
}
}
fn num_points(&self) -> usize {
1 << self.subgroup_bits
}
}
impl<F: RichField + Extendable<D>, const D: usize> LowDegreeInterpolationGate<F, D> {
/// `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<usize> {
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<Item = F> {
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<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for LowDegreeInterpolationGate<F, D> {
fn id(&self) -> String {
format!("{:?}<D={}>", self, D)
}
fn eval_unfiltered(&self, vars: EvaluationVars<F, D>) -> Vec<F::Extension> {
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::<Vec<_>>();
let mut powers_shift = (1..self.num_points())
.map(|i| vars.local_wires[self.powers_shift(i)])
.collect::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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<F>) -> Vec<F> {
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::<Vec<_>>();
let mut powers_shift = (1..self.num_points())
.map(|i| vars.local_wires[self.powers_shift(i)])
.collect::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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<F, D>,
vars: EvaluationTargets<D>,
) -> Vec<ExtensionTarget<D>> {
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::<Vec<_>>();
let mut powers_shift = (1..self.num_points())
.map(|i| vars.local_wires[self.powers_shift(i)])
.collect::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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<Box<dyn WitnessGenerator<F>>> {
let gen = InterpolationGenerator::<F, D> {
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<F: RichField + Extendable<D>, const D: usize> {
gate_index: usize,
gate: LowDegreeInterpolationGate<F, D>,
_phantom: PhantomData<F>,
}
impl<F: RichField + Extendable<D>, const D: usize> SimpleGenerator<F>
for InterpolationGenerator<F, D>
{
fn dependencies(&self) -> Vec<Target> {
let local_target = |input| {
Target::Wire(Wire {
gate: self.gate_index,
input,
})
};
let local_targets = |inputs: Range<usize>| 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<F>, out_buffer: &mut GeneratedValues<F>) {
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<usize>| {
debug_assert_eq!(wire_range.len(), D);
let values = wire_range.map(get_local_wire).collect::<Vec<_>>();
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::<Vec<_>>();
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::<GoldilocksField, _, 4>(LowDegreeInterpolationGate::new(4));
}
#[test]
fn eval_fns() -> Result<()> {
test_eval_fns::<GoldilocksField, _, 4>(LowDegreeInterpolationGate::new(4))
}
#[test]
fn test_gate_constraint() {
type F = GoldilocksField;
type FF = QuadraticExtension<GoldilocksField>;
const D: usize = 2;
/// Returns the local wires for an interpolation gate for given coeffs, points and eval point.
fn get_wires(
gate: &LowDegreeInterpolationGate<F, D>,
shift: F,
coeffs: PolynomialCoeffs<FF>,
eval_point: FF,
) -> Vec<FF> {
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::<Vec<_>>()
}
// 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::<F, D>::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."
);
}
}

View File

@ -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;

View File

@ -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()
}
}

View File

@ -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)?;

View File

@ -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::<F, D>(&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

View File

@ -120,6 +120,16 @@ impl<F: Field> PolynomialCoeffs<F> {
.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<const D: usize>(&self, x: F::BaseField) -> F
where
F: FieldExtension<D>,
@ -130,6 +140,19 @@ impl<F: Field> PolynomialCoeffs<F> {
.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<const D: usize>(&self, powers: &[F::BaseField]) -> F
where
F: FieldExtension<D>,
{
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<Self> {
polys.into_iter().map(|p| p.lde(rate_bits)).collect()
}