FriConsistencyGate

This commit is contained in:
Daniel Lubarov 2021-03-18 12:44:45 -07:00
parent ea33c5567f
commit ca7f20bf45
15 changed files with 565 additions and 38 deletions

View File

@ -16,8 +16,8 @@ use crate::wire::Wire;
pub(crate) struct EvaluationVars<'a, F: Field> {
pub(crate) local_constants: &'a [F],
pub(crate) next_constants: &'a [F],
pub(crate) local_wire_values: &'a [F],
pub(crate) next_wire_values: &'a [F],
pub(crate) local_wires: &'a [F],
pub(crate) next_wires: &'a [F],
}
/// A polynomial over all the variables that are subject to constraints (local constants, next
@ -39,6 +39,7 @@ impl<F: Field> ConstraintPolynomial<F> {
Self::constant(F::from_canonical_usize(c))
}
// TODO: const?
pub fn zero() -> Self {
Self::constant(F::ZERO)
}
@ -55,18 +56,18 @@ impl<F: Field> ConstraintPolynomial<F> {
Self::from_inner(ConstraintPolynomialInner::NextConstant(index))
}
pub fn local_wire_value(index: usize) -> Self {
pub fn local_wire(index: usize) -> Self {
Self::from_inner(ConstraintPolynomialInner::LocalWireValue(index))
}
pub fn next_wire_value(index: usize) -> Self {
pub fn next_wire(index: usize) -> Self {
Self::from_inner(ConstraintPolynomialInner::NextWireValue(index))
}
pub fn from_gate_output(gate_output: GateOutputLocation) -> Self {
match gate_output {
GateOutputLocation::LocalWire(i) => Self::local_wire_value(i),
GateOutputLocation::NextWire(i) => Self::next_wire_value(i),
GateOutputLocation::LocalWire(i) => Self::local_wire(i),
GateOutputLocation::NextWire(i) => Self::next_wire(i),
}
}
@ -522,8 +523,8 @@ impl<F: Field> ConstraintPolynomialInner<F> {
ConstraintPolynomialInner::Constant(c) => *c,
ConstraintPolynomialInner::LocalConstant(i) => vars.local_constants[*i],
ConstraintPolynomialInner::NextConstant(i) => vars.next_constants[*i],
ConstraintPolynomialInner::LocalWireValue(i) => vars.local_wire_values[*i],
ConstraintPolynomialInner::NextWireValue(i) => vars.next_wire_values[*i],
ConstraintPolynomialInner::LocalWireValue(i) => vars.local_wires[*i],
ConstraintPolynomialInner::NextWireValue(i) => vars.next_wires[*i],
ConstraintPolynomialInner::Sum { lhs, rhs } => {
let lhs = lhs.evaluate_memoized(vars, mem);
let rhs = rhs.evaluate_memoized(vars, mem);
@ -564,7 +565,7 @@ mod tests {
#[test]
fn equality() {
type F = CrandallField;
let wire0 = ConstraintPolynomial::<F>::local_wire_value(0);
let wire0 = ConstraintPolynomial::<F>::local_wire(0);
// == should compare the pointers, and the clone should point to the same underlying
// ConstraintPolynomialInner.
assert_eq!(wire0.clone(), wire0);

View File

@ -121,6 +121,22 @@ pub fn fft_with_precomputation_power_of_2<F: Field>(
reverse_index_bits(evaluations)
}
pub fn coset_fft<F: Field>(coefficients: Vec<F>, shift: F) -> Vec<F> {
fft(coefficients)
.into_iter()
.map(|x| x * shift)
.collect()
}
pub fn coset_ifft<F: Field>(points: Vec<F>, shift: F) -> Vec<F> {
let shift_inv = shift.inverse();
let precomputation = fft_precompute(points.len());
ifft_with_precomputation_power_of_2(points, &precomputation)
.into_iter()
.map(|x| x * shift_inv)
.collect()
}
// #[cfg(test)]
// mod tests {
// use crate::{Bls12377Scalar, fft_precompute, fft_with_precomputation, CrandallField, ifft_with_precomputation_power_of_2};

View File

@ -2,20 +2,35 @@
/// while increasing L, potentially requiring more challenge points.
const EPSILON: f64 = 0.01;
struct FriConfig {
proof_of_work_bits: usize,
/// The arity of each FRI reduction step, expressed (i.e. the log2 of the actual arity).
/// For example, `[3, 2, 1]` would describe a FRI reduction tree with 8-to-1 reduction, then
/// a 4-to-1 reduction, then a 2-to-1 reduction. After these reductions, the reduced polynomial
/// is sent directly.
reduction_arity_bits: Vec<usize>,
}
fn fri_delta(rate_log: usize, conjecture: bool) -> f64 {
let rate = (1 << rate_log) as f64;
if conjecture {
todo!()
// See Conjecture 2.3 in DEEP-FRI.
1.0 - rate - EPSILON
} else {
return 1.0 - rate.sqrt() - EPSILON;
// See the Johnson radius.
1.0 - rate.sqrt() - EPSILON
}
}
fn fri_l(rate_log: usize, conjecture: bool) -> f64 {
fn fri_l(codeword_len: usize, rate_log: usize, conjecture: bool) -> f64 {
let rate = (1 << rate_log) as f64;
if conjecture {
todo!()
// See Conjecture 2.3 in DEEP-FRI.
// We assume the conjecture holds with a constant of 1 (as do other STARK implementations).
(codeword_len as f64) / EPSILON
} else {
return 1.0 / (2.0 * EPSILON * rate.sqrt());
// See the Johnson bound.
1.0 / (2.0 * EPSILON * rate.sqrt())
}
}

View File

@ -0,0 +1,13 @@
use crate::constraint_polynomial::ConstraintPolynomial;
use crate::field::field::Field;
/// Evaluates to `x` if `c == 0`, or `x * y` if `c == 1`.
pub fn conditional_multiply_poly<F: Field>(
x: &ConstraintPolynomial<F>,
y: &ConstraintPolynomial<F>,
c: &ConstraintPolynomial<F>,
) -> ConstraintPolynomial<F> {
let product = x * y;
let delta = product - x;
x + c * delta
}

2
src/gadgets/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub(crate) mod split_join;
pub(crate) mod conditionals;

71
src/gadgets/split_join.rs Normal file
View File

@ -0,0 +1,71 @@
use std::iter;
use crate::constraint_polynomial::ConstraintPolynomial;
use crate::field::field::Field;
use crate::generator::{SimpleGenerator, WitnessGenerator};
use crate::target::Target;
use crate::witness::PartialWitness;
use crate::wire::Wire;
/// Constraints for a little-endian split.
pub fn split_le_constraints<F: Field>(
integer: ConstraintPolynomial<F>,
bits: &[ConstraintPolynomial<F>],
) -> Vec<ConstraintPolynomial<F>> {
let weighted_sum = bits.iter()
.fold(ConstraintPolynomial::zero(), |acc, b| acc.double() + b);
bits.iter()
.rev()
.map(|b| b * (b - 1))
.chain(iter::once(weighted_sum - integer))
.collect()
}
/// Generator for a little-endian split.
pub fn split_le_generator<F: Field>(
integer: Target,
bits: Vec<Target>,
) -> Box<dyn WitnessGenerator<F>> {
Box::new(SplitGenerator { integer, bits })
}
/// Generator for a little-endian split.
pub fn split_le_generator_local_wires<F: Field>(
gate: usize,
integer_input_index: usize,
bit_input_indices: &[usize],
) -> Box<dyn WitnessGenerator<F>> {
let integer = Target::Wire(
Wire { gate, input: integer_input_index });
let bits = bit_input_indices.iter()
.map(|&input| Target::Wire(Wire { gate, input }))
.collect();
Box::new(SplitGenerator { integer, bits })
}
struct SplitGenerator {
integer: Target,
bits: Vec<Target>,
}
impl<F: Field> SimpleGenerator<F> for SplitGenerator {
fn dependencies(&self) -> Vec<Target> {
vec![self.integer]
}
fn run_once(&mut self, witness: &PartialWitness<F>) -> PartialWitness<F> {
let mut integer_value = witness.get_target(self.integer).to_canonical_u64();
let mut result = PartialWitness::new();
for &b in &self.bits {
let b_value = integer_value & 1;
result.set_target(b, F::from_canonical_u64(b_value));
integer_value >>= 1;
}
debug_assert_eq!(integer_value, 0,
"Integer too large to fit in given number of bits");
result
}
}

View File

@ -35,6 +35,8 @@ pub trait DeterministicGate<F: Field>: 'static {
&self,
_config: CircuitConfig,
_gate_index: usize,
local_constants: Vec<F>,
next_constants: Vec<F>,
) -> Vec<Box<dyn WitnessGenerator<F>>> {
Vec::new()
}
@ -90,7 +92,8 @@ impl<F: Field, DG: DeterministicGate<F>> Gate<F> for DeterministicGateAdapter<F,
let b: Box::<dyn WitnessGenerator<F>> = Box::new(og);
b
})
.chain(self.gate.additional_generators(config, gate_index))
.chain(self.gate.additional_generators(
config, gate_index, local_constants.clone(), next_constants.clone()))
.collect()
}
}
@ -136,8 +139,8 @@ impl<F: Field> SimpleGenerator<F> for OutputGenerator<F> {
let vars = EvaluationVars {
local_constants: &self.local_constants,
next_constants: &self.next_constants,
local_wire_values: &local_wire_values,
next_wire_values: &next_wire_values,
local_wires: &local_wire_values,
next_wires: &next_wire_values,
};
let result_wire = match self.location {

View File

@ -0,0 +1,370 @@
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)]
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);
}
// 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]
}
}
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(&mut 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());
}
}

View File

@ -1,6 +1,6 @@
use std::sync::Arc;
use num::{BigUint, FromPrimitive, One};
use num::{BigUint, One};
use crate::circuit_data::CircuitConfig;
use crate::constraint_polynomial::ConstraintPolynomial;
@ -29,7 +29,6 @@ impl<F: Field, const W: usize, const R: usize> GMiMCGate<F, W, R> {
/// If this is set to 1, the first four inputs will be swapped with the next four inputs. This
/// is useful for ordering hashes in Merkle proofs. Otherwise, this should be set to 0.
// TODO: Assert binary.
pub const WIRE_SWITCH: usize = W;
/// The wire index for the i'th input to the permutation.
@ -48,13 +47,14 @@ impl<F: Field, const W: usize, const R: usize> GMiMCGate<F, W, R> {
///
/// This may seem like it belongs in `OutputGraph`, but it is not that general, since it uses
/// a notion of "next available" local wire indices specific to this gate.
// TODO: Switch to `ExpandableOutputGraph`.
fn add_local(
outputs: &mut OutputGraph<F>,
poly: ConstraintPolynomial<F>,
) -> ConstraintPolynomial<F> {
let index = outputs.max_local_output_index().map_or(W + 1, |i| i + 1);
outputs.add(GateOutputLocation::LocalWire(index), poly);
ConstraintPolynomial::local_wire_value(index)
ConstraintPolynomial::local_wire(index)
}
}
@ -64,15 +64,15 @@ impl<F: Field, const W: usize, const R: usize> DeterministicGate<F> for GMiMCGat
format!("{:?}", self)
}
fn outputs(&self, config: CircuitConfig) -> OutputGraph<F> {
fn outputs(&self, _config: CircuitConfig) -> OutputGraph<F> {
let original_inputs = (0..W)
.map(|i| ConstraintPolynomial::local_wire_value(Self::wire_input(i)))
.map(|i| ConstraintPolynomial::local_wire(Self::wire_input(i)))
.collect::<Vec<_>>();
let mut outputs = OutputGraph::new();
// Conditionally switch inputs based on the (boolean) switch wire.
let switch = ConstraintPolynomial::local_wire_value(Self::WIRE_SWITCH);
let switch = ConstraintPolynomial::local_wire(Self::WIRE_SWITCH);
let mut state = Vec::new();
for i in 0..4 {
let a = &original_inputs[i];
@ -112,7 +112,7 @@ impl<F: Field, const W: usize, const R: usize> DeterministicGate<F> for GMiMCGat
}
fn additional_constraints(&self, _config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> {
let switch = ConstraintPolynomial::local_wire_value(Self::WIRE_SWITCH);
let switch = ConstraintPolynomial::local_wire(Self::WIRE_SWITCH);
let switch_bool_constraint = &switch * (&switch - 1);
vec![switch_bool_constraint]
}
@ -123,8 +123,6 @@ mod tests {
use std::convert::TryInto;
use std::sync::Arc;
use num::ToPrimitive;
use crate::circuit_data::CircuitConfig;
use crate::field::crandall_field::CrandallField;
use crate::field::field::Field;

View File

@ -3,3 +3,4 @@ pub(crate) mod deterministic_gate;
pub(crate) mod gate;
pub(crate) mod gmimc;
pub(crate) mod output_graph;
pub(crate) mod fri_consistency_gate;

View File

@ -1,10 +1,9 @@
use std::{fmt, iter};
use std::collections::HashMap;
use std::fmt;
use std::fmt::{Display, Formatter};
use num::{BigUint, FromPrimitive, One, ToPrimitive};
use num::ToPrimitive;
use crate::constraint_polynomial::{ConstraintPolynomial, EvaluationVars};
use crate::constraint_polynomial::ConstraintPolynomial;
use crate::field::field::Field;
/// Represents a set of deterministic gate outputs, expressed as polynomials over witness
@ -32,7 +31,7 @@ impl<F: Field> OutputGraph<F> {
/// The largest polynomial degree among all polynomials in this graph.
pub fn degree(&self) -> usize {
self.outputs.iter()
.map(|(loc, out)| out.degree().to_usize().unwrap())
.map(|(_loc, out)| out.degree().to_usize().unwrap())
.max()
.unwrap_or(0)
}
@ -40,7 +39,7 @@ impl<F: Field> OutputGraph<F> {
/// The largest local wire index among this graph's output locations.
pub fn max_local_output_index(&self) -> Option<usize> {
self.outputs.iter()
.filter_map(|(loc, out)| match loc {
.filter_map(|(loc, _out)| match loc {
GateOutputLocation::LocalWire(i) => Some(*i),
GateOutputLocation::NextWire(_) => None
})
@ -74,3 +73,41 @@ impl Display for GateOutputLocation {
}
}
}
/// Like `OutputGraph`, but tracks the index of the next available local wire.
///
/// Many gates have special wires, such as inputs and outputs, which must be placed at specific,
/// known indices. Many gates also use some wires for "intermediate values" which are internal to
/// the gate, and so can be placed at any available index.
///
/// This object helps to track which wire indices are available to be used for intermediate values.
/// It starts placing intermediate values at a given initial index, and repeatedly increments the
/// index as more intermediate values are added.
///
/// This assumes that there are no "reserved" indices greater than the given initial value. It also
/// assumes that there is no better place to store these intermediate values (such as wires of the
/// next gate). So this model may not make sense for all gates, which is why this is provided as an
/// optional utility.
pub struct ExpandableOutputGraph<F: Field> {
pub(crate) output_graph: OutputGraph<F>,
next_unused_index: usize,
}
impl<F: Field> ExpandableOutputGraph<F> {
pub(crate) fn new(next_unused_index: usize) -> Self {
ExpandableOutputGraph {
output_graph: OutputGraph::new(),
next_unused_index,
}
}
/// Adds an intermediate value at the next available wire index, and returns a
/// `ConstraintPolynomial` pointing to the newly created wire.
pub(crate) fn add(&mut self, poly: ConstraintPolynomial<F>) -> ConstraintPolynomial<F> {
let index = self.next_unused_index;
self.next_unused_index += 1;
self.output_graph.add(GateOutputLocation::LocalWire(index), poly);
ConstraintPolynomial::local_wire(index)
}
}

View File

@ -1,4 +1,3 @@
use std::borrow::Borrow;
use std::collections::{HashMap, HashSet};
use crate::field::field::Field;

View File

@ -17,6 +17,7 @@ mod circuit_data;
mod constraint_polynomial;
mod field;
mod fri;
mod gadgets;
mod gates;
mod generator;
mod gmimc;

View File

@ -1,8 +1,4 @@
use std::convert::Infallible;
use std::marker::PhantomData;
use crate::circuit_data::CircuitConfig;
use crate::field::field::Field;
use crate::wire::Wire;
/// A location in the witness.

View File

@ -47,7 +47,11 @@ impl<F: Field> PartialWitness<F> {
}
pub fn set_target(&mut self, target: Target, value: F) {
self.target_values.insert(target, value);
let opt_old_value = self.target_values.insert(target, value);
if let Some(old_value) = opt_old_value {
assert_eq!(old_value, value,
"Target was set twice with different values: {:?}", target);
}
}
pub fn set_wire(&mut self, wire: Wire, value: F) {