Merge pull request #42 from mir-protocol/interpolation_gate

Interpolation gate
This commit is contained in:
Daniel Lubarov 2021-05-20 05:36:17 -07:00 committed by GitHub
commit 3619c2dd1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 461 additions and 386 deletions

View File

@ -142,13 +142,14 @@ impl<F: Field> SimpleGenerator<F> for ArithmeticGenerator<F> {
}
}
// #[cfg(test)]
// mod tests {
// use crate::{test_gate_low_degree, ArithmeticGate, Tweedledum};
//
// test_gate_low_degree!(
// low_degree_ArithmeticGate,
// Tweedledum,
// ArithmeticGate<Tweedledum>
// );
// }
#[cfg(test)]
mod tests {
use crate::field::crandall_field::CrandallField;
use crate::gates::arithmetic::ArithmeticGate;
use crate::gates::gate_testing::test_low_degree;
#[test]
fn low_degree() {
test_low_degree(ArithmeticGate::new::<CrandallField>())
}
}

View File

@ -89,3 +89,15 @@ impl<F: Field> SimpleGenerator<F> for ConstantGenerator<F> {
PartialWitness::singleton_target(Target::Wire(wire), self.constant)
}
}
#[cfg(test)]
mod tests {
use crate::field::crandall_field::CrandallField;
use crate::gates::constant::ConstantGate;
use crate::gates::gate_testing::test_low_degree;
#[test]
fn low_degree() {
test_low_degree(ConstantGate::get::<CrandallField>())
}
}

View File

@ -1,372 +0,0 @@
// use crate::circuit_data::CircuitConfig;
// use crate::constraint_polynomial::ConstraintPolynomial;
// use crate::field::fft::coset_ifft;
// use crate::field::field::Field;
// use crate::gadgets::split_join::{split_le_constraints, split_le_generator_local_wires};
// use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter};
// use crate::gates::gate::GateRef;
// use crate::gates::output_graph::{OutputGraph, GateOutputLocation, ExpandableOutputGraph};
// use crate::generator::{SimpleGenerator, WitnessGenerator};
// use crate::target::Target;
// use crate::wire::Wire;
// use crate::witness::PartialWitness;
// use crate::gadgets::conditionals::conditional_multiply_poly;
//
// /// Performs a FRI consistency check. The goal is to check consistency between polynomials `f^(i)`
// /// and `f^(i + 1)`, where `f^(i + 1)` is supposed to be an `arity`-to-one reduction of `f^(i)`. See
// /// the FRI paper for details.
// ///
// /// This check involves `arity` openings of `f^(i)` and a single opening of `f^(i + 1)`. Let's call
// /// the set of `f^(i)` opening locations `{s_j}`, and the `f^(i + 1)` opening location `y`. Note
// /// that all of these points can be derived from the query path. So, we take as input an integer
// /// whose binary decomposition represents the left and right turns in the query path.
// ///
// /// Since our protocol uses a variant of FRI with `k` commit phases, this gate takes `k` opening
// /// sets for `f^(i)`, and `k` openings for `f^(i + 1)`.
// ///
// /// Putting it together, this gate does the following:
// /// - Computes the binary decomposition of the input representing the query path.
// /// - Computes `{s_j}` and `y` from the query path.
// /// - For each commit phase:
// /// - Non-deterministically interpolates a polynomial through each `(s_j, f^(i))`.
// /// - Evaluates the purported interpolant at each `s_j`, and checks that the result matches the
// /// associated opening of `f^(i)(s_j)`.
// /// - Evaluates the purported interpolant at `y`, and checks that the result matches the opening
// /// of `f^(i + 1)(y)`.
// #[derive(Debug, Copy, Clone)]
// pub(crate) struct FriConsistencyGate {
// /// The arity of this reduction step.
// arity_bits: usize,
//
// /// The number of commit phases.
// num_commits: usize,
//
// /// The maximum number of bits in any query path.
// max_path_bits: usize,
// }
//
// impl FriConsistencyGate {
// pub fn new<F: Field>(
// arity_bits: usize,
// num_commits: usize,
// max_path_bits: usize,
// ) -> GateRef<F> {
// let gate = Self { arity_bits, num_commits, max_path_bits };
// GateRef::new(DeterministicGateAdapter::new(gate))
// }
//
// fn arity(&self) -> usize {
// 1 << self.arity_bits
// }
//
// /// Generator for the `i`'th layer of FRI.
// pub const CONST_GENERATOR_I: usize = 0;
//
// // Note: These methods relating to wire indices are ordered by wire index. The index
// // calculations are a little hairy since there are quite a few different "sections" of wires,
// // and each section has to calculate where the previous sections left off. To make it more
// // manageable, we have separate functions to calculate the start of each section. There is also
// // a test to make sure that they behave as expected, with no overlapping indices or what not.
//
// /// An integer representing the location of the `f^(i + 1)` node in the reduction tree. Its
// /// `i`th bit in little-endian form represents the direction of the `i`th turn in the query
// /// path, starting from the root.
// pub const WIRE_PATH: usize = 0;
//
// fn start_wire_f_i(&self) -> usize {
// 1
// }
//
// pub fn wire_f_i(&self, commit_idx: usize, j: usize) -> usize {
// debug_assert!(commit_idx < self.num_commits);
// debug_assert!(j < self.arity());
// self.start_wire_f_i() + self.arity() * commit_idx + j
// }
//
// fn start_wire_f_i_plus_1(&self) -> usize {
// self.start_wire_f_i() + self.arity() * self.num_commits
// }
//
// pub fn wire_f_i_plus_1(&self, commit_idx: usize) -> usize {
// debug_assert!(commit_idx < self.num_commits);
// self.start_wire_f_i_plus_1() + commit_idx
// }
//
// fn start_wire_path_bits(&self) -> usize {
// self.start_wire_f_i_plus_1() + self.num_commits
// }
//
// /// The `i`th bit of the path.
// fn wire_path_bit_i(&self, i: usize) -> usize {
// self.start_wire_path_bits() + i
// }
//
// fn start_wire_s_j(&self) -> usize {
// self.start_wire_path_bits() + self.max_path_bits
// }
//
// /// The input index of `s_j` (see the FRI paper).
// fn wire_s_j(&self, j: usize) -> usize {
// debug_assert!(j < self.arity());
// self.start_wire_s_j() + j
// }
//
// fn start_wire_y(&self) -> usize {
// self.start_wire_s_j() + self.arity()
// }
//
// /// The input index of `y` (see the FRI paper).
// fn wire_y(&self) -> usize {
// self.start_wire_y()
// }
//
// fn start_wire_coefficient(&self) -> usize {
// self.start_wire_y() + 1
// }
//
// /// The wire input index of the j'th coefficient of the interpolant.
// fn wire_coefficient(&self, commit_idx: usize, j: usize) -> usize {
// debug_assert!(commit_idx < self.num_commits);
// debug_assert!(j < self.arity());
// self.start_wire_coefficient() + commit_idx * self.arity() + j
// }
//
// fn start_unnamed_wires(&self) -> usize {
// self.start_wire_coefficient() + self.num_commits * self.arity()
// }
//
// fn add_s_j_outputs<F: Field>(&self, output_graph: &mut ExpandableOutputGraph<F>) {
// // Each s_j = g^path, where g is the generator for s_j's layer (see CONST_GENERATOR), and
// // path is an integer representing the location of s_j in the reduction tree. This assumes
// // that path is encoded such that its less significant bits are closer to the root of the
// // tree.
//
// // Note about bit ordering: in a FRI reduction tree, the `j`th node in a layer can be
// // written as `g^rev(j)`, where `rev` reverses the bits of its inputs. One way to think of
// // this is that squaring left-shifts the exponent, so for adjacent nodes to have the same
// // square, they must differ only in the left-most bit (which will "overflow" after the
// // left-shift). FFT trees have the same property.
//
// // We start by computing g^0, g^10, g^100, g^1000, ...
// let mut squares = vec![ConstraintPolynomial::local_constant(0)];
// for _ in 1..self.max_path_bits {
// let prev_square = squares.last().unwrap();
// let next_square = output_graph.add(prev_square.square());
// squares.push(next_square)
// }
//
// // We can think of path as having two parts: a less significant part that is common to all
// // {s_j}, and a more significant part that depends on j. We start by computing
// // g^path_common:
// let mut g_exp_path_common = ConstraintPolynomial::zero();
// let shared_path_bits = self.max_path_bits - self.arity_bits;
// for i in 0..shared_path_bits {
// let bit = ConstraintPolynomial::local_wire(self.wire_path_bit_i(i));
// g_exp_path_common = conditional_multiply_poly(&g_exp_path_common, &squares[i], &bit);
// g_exp_path_common = output_graph.add(g_exp_path_common);
// }
//
// // Then, we factor in the "extra" powers of g specific to each child.
// for j in 0..self.arity() {
// let mut s_j = g_exp_path_common.clone();
// for bit_index in 0..self.arity_bits {
// let bit = (j >> bit_index & 1) != 0;
// if bit {
// // See the comment near the top about bit ordering.
// s_j *= &squares[shared_path_bits + self.arity_bits - 1 - bit_index];
// }
// }
// let s_j_loc = GateOutputLocation::LocalWire(self.wire_s_j(j));
// output_graph.output_graph.add(s_j_loc, s_j);
// }
// }
//
// fn add_y_output<F: Field>(&self, output_graph: &mut ExpandableOutputGraph<F>) {
// let loc = GateOutputLocation::LocalWire(self.wire_y());
// // We can start with any s_j and repeatedly square it. We arbitrary pick s_0.
// let mut out = ConstraintPolynomial::local_wire(self.wire_s_j(0));
// for _ in 0..self.arity_bits {
// out = out.square();
// }
// output_graph.output_graph.add(loc, out);
// }
//
// fn evaluate_each_poly<F: Field>(&self, commit_idx: usize) -> Vec<ConstraintPolynomial<F>> {
// let coefficients = (0..self.arity())
// .map(|i| ConstraintPolynomial::local_wire(self.wire_coefficient(commit_idx, i)))
// .collect::<Vec<ConstraintPolynomial<F>>>();
// let mut constraints = Vec::new();
//
// for j in 0..self.arity() {
// // Check the evaluation of f^(i) at s_j.
// let expected = ConstraintPolynomial::local_wire(self.wire_f_i(commit_idx, j));
// let actual = self.evaluate_poly(&coefficients,
// ConstraintPolynomial::local_wire(self.wire_s_j(j)));
// constraints.push(actual - expected);
// }
//
// // Check the evaluation of f^(i + 1) at y.
// let expected = ConstraintPolynomial::local_wire(self.wire_f_i_plus_1(commit_idx));
// let actual = self.evaluate_poly(&coefficients,
// ConstraintPolynomial::local_wire(self.wire_y()));
// constraints.push(actual - expected);
//
// constraints
// }
//
// /// Given a polynomial's coefficients, naively evaluate it at a point.
// fn evaluate_poly<F: Field>(
// &self,
// coefficients: &[ConstraintPolynomial<F>],
// point: ConstraintPolynomial<F>,
// ) -> ConstraintPolynomial<F> {
// coefficients.iter()
// .enumerate()
// .map(|(i, coeff)| coeff * point.exp(i))
// .sum()
// }
// }
//
// impl<F: Field> DeterministicGate<F> for FriConsistencyGate {
// fn id(&self) -> String {
// format!("{:?}", self)
// }
//
// fn outputs(&self, _config: CircuitConfig) -> OutputGraph<F> {
// let mut output_graph = ExpandableOutputGraph::new(self.start_unnamed_wires());
// self.add_s_j_outputs(&mut output_graph);
// self.add_y_output(&mut output_graph);
// output_graph.output_graph
// }
//
// fn additional_constraints(&self, _config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> {
// let mut constraints = Vec::new();
//
// // Add constraints for splitting the path into its binary representation.
// let bits = (0..self.max_path_bits)
// .map(|i| ConstraintPolynomial::local_wire(self.wire_path_bit_i(i)))
// .collect::<Vec<_>>();
// let split_constraints = split_le_constraints(
// ConstraintPolynomial::local_wire(Self::WIRE_PATH),
// &bits);
// constraints.extend(split_constraints);
//
// // Add constraints for checking each polynomial evaluation.
// for commit_idx in 0..self.num_commits {
// constraints.extend(self.evaluate_each_poly(commit_idx));
// }
//
// constraints
// }
//
// fn additional_generators(
// &self,
// _config: CircuitConfig,
// gate_index: usize,
// local_constants: Vec<F>,
// _next_constants: Vec<F>,
// ) -> Vec<Box<dyn WitnessGenerator<F>>> {
// let interpolant_generator = Box::new(
// InterpolantGenerator {
// gate: *self,
// gate_index,
// generator_i: local_constants[Self::CONST_GENERATOR_I],
// }
// );
//
// let bit_input_indices = (0..self.max_path_bits)
// .map(|i| self.wire_path_bit_i(i))
// .collect::<Vec<_>>();
// let split_generator = split_le_generator_local_wires(
// gate_index, Self::WIRE_PATH, &bit_input_indices);
//
// vec![interpolant_generator, split_generator]
// }
// }
//
// #[derive(Debug)]
// struct InterpolantGenerator<F: Field> {
// gate: FriConsistencyGate,
// gate_index: usize,
// generator_i: F,
// }
//
// impl<F: Field> InterpolantGenerator<F> {
// /// Convenience method for converting a wire input index into a Target with our gate index.
// fn local_wire(&self, input: usize) -> Target {
// Target::Wire(Wire { gate: self.gate_index, input })
// }
// }
//
// impl<F: Field> SimpleGenerator<F> for InterpolantGenerator<F> {
// fn dependencies(&self) -> Vec<Target> {
// let mut deps = vec![self.local_wire(FriConsistencyGate::WIRE_PATH)];
// for i in 0..self.gate.arity() {
// deps.push(self.local_wire(self.gate.wire_s_j(i)));
// for commit_idx in 0..self.gate.num_commits {
// deps.push(self.local_wire(self.gate.wire_f_i(commit_idx, i)));
// }
// }
// deps
// }
//
// fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
// let mut result = PartialWitness::new();
//
// for commit_idx in 0..self.gate.num_commits {
// let values = (0..self.gate.arity())
// .map(|j| witness.get_target(self.local_wire(self.gate.wire_f_i(commit_idx, j))))
// .collect();
//
// let path = witness.get_target(self.local_wire(FriConsistencyGate::WIRE_PATH));
// let shift = self.generator_i.exp(path);
// let coeffs = coset_ifft(values, shift);
//
// for (i, coeff) in coeffs.into_iter().enumerate() {
// result.set_target(
// self.local_wire(self.gate.wire_coefficient(commit_idx, i)),
// coeff);
// }
// }
//
// result
// }
// }
//
// #[cfg(test)]
// mod tests {
// use crate::gates::fri_consistency_gate::FriConsistencyGate;
//
// #[test]
// fn wire_indices() {
// let gate = FriConsistencyGate {
// arity_bits: 1,
// num_commits: 2,
// max_path_bits: 4,
// };
//
// // The actual indices aren't really important, but we want to make sure that
// // - there are no overlaps
// // - there are no gaps
// // - the routed inputs come first
// assert_eq!(0, FriConsistencyGate::WIRE_PATH);
// assert_eq!(1, gate.wire_f_i(0, 0));
// assert_eq!(2, gate.wire_f_i(0, 1));
// assert_eq!(3, gate.wire_f_i(1, 0));
// assert_eq!(4, gate.wire_f_i(1, 1));
// assert_eq!(5, gate.wire_f_i_plus_1(0));
// assert_eq!(6, gate.wire_f_i_plus_1(1));
// assert_eq!(7, gate.wire_path_bit_i(0));
// assert_eq!(8, gate.wire_path_bit_i(1));
// assert_eq!(9, gate.wire_path_bit_i(2));
// assert_eq!(10, gate.wire_path_bit_i(3));
// assert_eq!(11, gate.wire_s_j(0));
// assert_eq!(12, gate.wire_s_j(1));
// assert_eq!(13, gate.wire_y());
// assert_eq!(14, gate.wire_coefficient(0, 0));
// assert_eq!(15, gate.wire_coefficient(0, 1));
// assert_eq!(16, gate.wire_coefficient(1, 0));
// assert_eq!(17, gate.wire_coefficient(1, 1));
// assert_eq!(18, gate.start_unnamed_wires());
// }
// }

68
src/gates/gate_testing.rs Normal file
View File

@ -0,0 +1,68 @@
use crate::field::field::Field;
use crate::gates::gate::{Gate, GateRef};
use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues};
use crate::util::{log2_ceil, transpose};
use crate::vars::EvaluationVars;
const WITNESS_SIZE: usize = 1 << 5;
const WITNESS_DEGREE: usize = WITNESS_SIZE - 1;
/// Tests that the constraints imposed by the given gate are low-degree by applying them to random
/// low-degree witness polynomials.
pub(crate) fn test_low_degree<F: Field>(gate: GateRef<F>) {
let gate = gate.0;
let rate_bits = log2_ceil(gate.degree() + 1);
let wire_ldes = random_low_degree_matrix(gate.num_wires(), rate_bits);
let constant_ldes = random_low_degree_matrix::<F>(gate.num_constants(), rate_bits);
assert_eq!(wire_ldes.len(), constant_ldes.len());
let constraint_evals = wire_ldes
.iter()
.zip(constant_ldes.iter())
.map(|(local_wires, local_constants)| EvaluationVars {
local_constants,
local_wires,
})
.map(|vars| gate.eval_unfiltered(vars))
.collect::<Vec<_>>();
let constraint_eval_degrees = transpose(&constraint_evals)
.into_iter()
.map(PolynomialValues::new)
.map(|p| p.degree())
.collect::<Vec<_>>();
let expected_eval_degree = WITNESS_DEGREE * gate.degree();
assert!(
constraint_eval_degrees
.iter()
.all(|&deg| deg <= expected_eval_degree),
"Expected degrees at most {} * {} = {}, actual {:?}",
WITNESS_SIZE,
gate.degree(),
expected_eval_degree,
constraint_eval_degrees
);
}
fn random_low_degree_matrix<F: Field>(num_polys: usize, rate_bits: usize) -> Vec<Vec<F>> {
let polys = (0..num_polys)
.map(|_| random_low_degree_values(rate_bits))
.collect::<Vec<_>>();
if polys.is_empty() {
// We want a Vec of many empty Vecs, whereas transpose would just give an empty Vec.
vec![Vec::new(); WITNESS_SIZE << rate_bits]
} else {
transpose(&polys)
}
}
fn random_low_degree_values<F: Field>(rate_bits: usize) -> Vec<F> {
PolynomialCoeffs::new(F::rand_vec(WITNESS_SIZE))
.lde(rate_bits)
.fft()
.values
}

View File

@ -59,6 +59,11 @@ impl<F: Field, const R: usize> GMiMCGate<F, R> {
fn wire_cubing_input(i: usize) -> usize {
2 * W + 3 + i
}
/// End of wire indices, exclusive.
fn end() -> usize {
2 * W + 3 + R
}
}
impl<F: Field, const R: usize> Gate<F> for GMiMCGate<F, R> {
@ -223,7 +228,7 @@ impl<F: Field, const R: usize> Gate<F> for GMiMCGate<F, R> {
}
fn num_wires(&self) -> usize {
W + 1 + R
Self::end()
}
fn num_constants(&self) -> usize {
@ -349,6 +354,7 @@ mod tests {
use crate::gmimc::gmimc_permute_naive;
use crate::wire::Wire;
use crate::witness::PartialWitness;
use crate::gates::gate_testing::test_low_degree;
#[test]
fn generated_output() {
@ -411,4 +417,14 @@ mod tests {
});
assert_eq!(acc_new, F::from_canonical_usize(7 * 2));
}
#[test]
fn low_degree() {
type F = CrandallField;
const R: usize = 101;
let constants = Arc::new([F::TWO; R]);
type Gate = GMiMCGate<F, R>;
let gate = Gate::with_constants(constants);
test_low_degree(gate)
}
}

View File

@ -1,3 +1,5 @@
use std::marker::PhantomData;
use crate::circuit_builder::CircuitBuilder;
use crate::field::field::Field;
use crate::gates::gate::{Gate, GateRef};
@ -6,7 +8,6 @@ use crate::target::Target;
use crate::vars::{EvaluationTargets, EvaluationVars};
use crate::wire::Wire;
use crate::witness::PartialWitness;
use std::marker::PhantomData;
/// Performs some arithmetic involved in the evaluation of GMiMC's constraint polynomials for one
/// round. In particular, this performs the following computations:
@ -224,3 +225,15 @@ impl<F: Field> SimpleGenerator<F> for GMiMCEvalGenerator<F> {
witness
}
}
#[cfg(test)]
mod tests {
use crate::field::crandall_field::CrandallField;
use crate::gates::gate_testing::test_low_degree;
use crate::gates::gmimc_eval::GMiMCEvalGate;
#[test]
fn low_degree() {
test_low_degree(GMiMCEvalGate::<CrandallField>::get())
}
}

273
src/gates/interpolation.rs Normal file
View File

@ -0,0 +1,273 @@
use std::convert::TryInto;
use std::marker::PhantomData;
use std::ops::Range;
use crate::circuit_builder::CircuitBuilder;
use crate::field::extension_field::{Extendable, FieldExtension};
use crate::field::field::Field;
use crate::field::lagrange::interpolant;
use crate::gates::gate::{Gate, GateRef};
use crate::generator::{SimpleGenerator, WitnessGenerator};
use crate::polynomial::polynomial::PolynomialCoeffs;
use crate::target::Target;
use crate::vars::{EvaluationTargets, EvaluationVars};
use crate::wire::Wire;
use crate::witness::PartialWitness;
/// 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<F: Field + Extendable<D>, const D: usize> {
num_points: usize,
_phantom: PhantomData<F>,
}
impl<F: Field + Extendable<D>, const D: usize> InterpolationGate<F, D> {
pub fn new(num_points: usize) -> GateRef<F> {
let gate = Self {
num_points,
_phantom: PhantomData,
};
GateRef::new(gate)
}
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<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
}
/// 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
}
/// End of wire indices, exclusive.
fn end(&self) -> usize {
self.start_coeffs() + self.num_points * D
}
}
impl<F: Field + Extendable<D>, const D: usize> Gate<F> for InterpolationGate<F, D> {
fn id(&self) -> String {
format!("{:?}<D={}>", self, D)
}
fn eval_unfiltered(&self, vars: EvaluationVars<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();
let interpolant = PolynomialCoeffs::new(coeffs);
for i in 0..self.num_points {
let point = F::Extension::from_basefield(vars.local_wires[self.wire_point(i)]);
let value = vars.get_local_ext(self.wires_value(i));
let computed_value = interpolant.eval(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<F>,
vars: EvaluationTargets,
) -> Vec<Target> {
todo!()
}
fn generators(
&self,
gate_index: usize,
_local_constants: &[F],
) -> Vec<Box<dyn WitnessGenerator<F>>> {
let gen = InterpolationGenerator::<F, D> {
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<F: Field + Extendable<D>, const D: usize> {
gate_index: usize,
gate: InterpolationGate<F, D>,
_phantom: PhantomData<F>,
}
impl<F: Field + 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 mut deps = Vec::new();
deps.extend(local_targets(self.gate.wires_evaluation_point()));
deps.extend(local_targets(self.gate.wires_evaluation_value()));
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.extend(local_targets(self.gate.wires_coeff(i)));
}
deps
}
fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
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<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)
};
// 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::<Vec<_>>();
let interpolant = interpolant(&points);
let mut result = PartialWitness::<F>::new();
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 crate::field::crandall_field::CrandallField;
use crate::gates::gate::Gate;
use crate::gates::gate_testing::test_low_degree;
use crate::gates::interpolation::InterpolationGate;
#[test]
fn wire_indices() {
let gate = InterpolationGate::<CrandallField, 4> {
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() {
type F = CrandallField;
test_low_degree(InterpolationGate::<F, 2>::new(4));
test_low_degree(InterpolationGate::<F, 4>::new(4));
}
}

View File

@ -1,7 +1,10 @@
pub(crate) mod arithmetic;
pub mod constant;
pub(crate) mod fri_consistency_gate;
pub(crate) mod gate;
pub mod gmimc;
pub(crate) mod gmimc_eval;
mod interpolation;
pub(crate) mod noop;
#[cfg(test)]
mod gate_testing;

View File

@ -55,3 +55,15 @@ impl<F: Field> Gate<F> for NoopGate {
0
}
}
#[cfg(test)]
mod tests {
use crate::field::crandall_field::CrandallField;
use crate::gates::gate_testing::test_low_degree;
use crate::gates::noop::NoopGate;
#[test]
fn low_degree() {
test_low_degree(NoopGate::get::<CrandallField>())
}
}

View File

@ -304,7 +304,7 @@ mod tests {
}
let config = CircuitConfig {
num_wires: 114,
num_wires: 12 + 12 + 3 + 101,
num_routed_wires: 27,
..CircuitConfig::default()
};

View File

@ -32,6 +32,7 @@ impl<F: Field> PolynomialValues<F> {
pub fn ifft(self) -> PolynomialCoeffs<F> {
ifft(self)
}
pub fn lde_multiple(polys: Vec<Self>, rate_bits: usize) -> Vec<Self> {
polys.into_iter().map(|p| p.lde(rate_bits)).collect()
}
@ -40,6 +41,16 @@ impl<F: Field> PolynomialValues<F> {
let coeffs = ifft(self).lde(rate_bits);
fft(coeffs)
}
pub fn degree(&self) -> usize {
self.degree_plus_one()
.checked_sub(1)
.expect("deg(0) is undefined")
}
pub fn degree_plus_one(&self) -> usize {
self.clone().ifft().degree_plus_one()
}
}
impl<F: Field> From<Vec<F>> for PolynomialValues<F> {

View File

@ -32,6 +32,10 @@ pub(crate) fn transpose_poly_values<F: Field>(polys: Vec<PolynomialValues<F>>) -
}
pub(crate) fn transpose<T: Clone>(matrix: &[Vec<T>]) -> Vec<Vec<T>> {
if matrix.is_empty() {
return Vec::new();
}
let old_rows = matrix.len();
let old_cols = matrix[0].len();
let mut transposed = vec![Vec::with_capacity(old_rows); old_cols];

View File

@ -1,3 +1,7 @@
use std::convert::TryInto;
use std::ops::Range;
use crate::field::extension_field::{Extendable, FieldExtension};
use crate::field::field::Field;
use crate::target::Target;
@ -7,6 +11,17 @@ pub struct EvaluationVars<'a, F: Field> {
pub(crate) local_wires: &'a [F],
}
impl<'a, F: Field> EvaluationVars<'a, F> {
pub fn get_local_ext<const D: usize>(&self, wire_range: Range<usize>) -> F::Extension
where
F: Extendable<D>,
{
debug_assert_eq!(wire_range.len(), D);
let arr = self.local_wires[wire_range].try_into().unwrap();
F::Extension::from_basefield_array(arr)
}
}
#[derive(Copy, Clone)]
pub struct EvaluationTargets<'a> {
pub(crate) local_constants: &'a [Target],

View File

@ -1,5 +1,6 @@
use std::collections::HashMap;
use crate::field::extension_field::{Extendable, FieldExtension};
use crate::field::field::Field;
use crate::target::Target;
use crate::wire::Wire;
@ -73,6 +74,24 @@ impl<F: Field> PartialWitness<F> {
self.set_target(Target::Wire(wire), value)
}
pub fn set_wires<W>(&mut self, wires: W, values: &[F])
where
W: IntoIterator<Item = Wire>,
{
// If we used itertools, we could use zip_eq for extra safety.
for (wire, &value) in wires.into_iter().zip(values) {
self.set_wire(wire, value);
}
}
pub fn set_ext_wires<W, const D: usize>(&mut self, wires: W, value: F::Extension)
where
F: Extendable<D>,
W: IntoIterator<Item = Wire>,
{
self.set_wires(wires, &value.to_basefield_array());
}
pub fn extend(&mut self, other: PartialWitness<F>) {
for (target, value) in other.target_values {
self.set_target(target, value);