Gate infra

This commit is contained in:
Daniel Lubarov 2021-02-26 13:18:41 -08:00
parent 33bd3edd11
commit 9fdff8ea08
17 changed files with 370 additions and 111 deletions

View File

@ -7,6 +7,7 @@ edition = "2018"
[dependencies] [dependencies]
unroll = "0.1.5" unroll = "0.1.5"
rayon = "1.5.0" rayon = "1.5.0"
num = "0.3"
[profile.release] [profile.release]
opt-level = 3 opt-level = 3

94
src/circuit_builder.rs Normal file
View File

@ -0,0 +1,94 @@
use std::collections::HashSet;
use crate::circuit_data::CircuitConfig;
use crate::field::field::Field;
use crate::gates::gate::{GateInstance, GateRef};
use crate::generator::{CopyGenerator, WitnessGenerator2};
use crate::target::Target;
use crate::gates::constant::ConstantGate2;
use crate::wire::Wire;
pub struct CircuitBuilder2<F: Field> {
config: CircuitConfig,
gates: HashSet<GateRef<F>>,
gate_instances: Vec<GateInstance<F>>,
generators: Vec<Box<dyn WitnessGenerator2<F>>>,
}
impl<F: Field> CircuitBuilder2<F> {
pub fn new(config: CircuitConfig) -> Self {
CircuitBuilder2 {
config,
gates: HashSet::new(),
gate_instances: Vec::new(),
generators: Vec::new(),
}
}
/// Adds a gate to the circuit, and returns its index.
pub fn add_gate(&mut self, gate_type: GateRef<F>, constants: Vec<F>) -> usize {
// If we haven't seen a gate of this type before, check that it's compatible with our
// circuit configuration, then register it.
if !self.gates.contains(&gate_type) {
self.check_gate_compatibility(&gate_type);
self.gates.insert(gate_type.clone());
}
let index = self.gate_instances.len();
self.gate_instances.push(GateInstance { gate_type, constants });
index
}
fn check_gate_compatibility(&self, gate: &GateRef<F>) {
assert!(gate.0.min_wires(self.config) <= self.config.num_wires);
}
/// Shorthand for `generate_copy` and `assert_equal`.
/// Both elements must be routable, otherwise this method will panic.
pub fn route(&mut self, src: Target, dst: Target) {
self.generate_copy(src, dst);
self.assert_equal(src, dst);
}
/// Adds a generator which will copy `src` to `dst`.
pub fn generate_copy(&mut self, src: Target, dst: Target) {
self.add_generator(CopyGenerator { src, dst });
}
/// Uses Plonk's permutation argument to require that two elements be equal.
/// Both elements must be routable, otherwise this method will panic.
pub fn assert_equal(&mut self, x: Target, y: Target) {
assert!(x.is_routable(self.config));
assert!(y.is_routable(self.config));
}
pub fn add_generator<G: WitnessGenerator2<F>>(&mut self, generator: G) {
self.generators.push(Box::new(generator));
}
/// Returns a routable target with a value of 0.
pub fn zero(&mut self) -> Target {
self.constant(F::ZERO)
}
/// Returns a routable target with a value of 1.
pub fn one(&mut self) -> Target {
self.constant(F::ONE)
}
/// Returns a routable target with a value of 2.
pub fn two(&mut self) -> Target {
self.constant(F::TWO)
}
/// Returns a routable target with a value of `ORDER - 1`.
pub fn neg_one(&mut self) -> Target {
self.constant(F::NEG_ONE)
}
/// Returns a routable target with the given constant value.
pub fn constant(&mut self, c: F) -> Target {
let gate = self.add_gate(ConstantGate2::get(), vec![c]);
Target::Wire(Wire { gate, input: ConstantGate2::WIRE_OUTPUT })
}
}

View File

@ -5,8 +5,11 @@ use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use std::ptr; use std::ptr;
use std::rc::Rc; use std::rc::Rc;
use num::{BigUint, FromPrimitive, One, Zero};
use crate::field::field::Field; use crate::field::field::Field;
use crate::wire::Wire; use crate::wire::Wire;
use crate::gates::output_graph::GateOutputLocation;
pub(crate) struct EvaluationVars<'a, F: Field> { pub(crate) struct EvaluationVars<'a, F: Field> {
pub(crate) local_constants: &'a [F], pub(crate) local_constants: &'a [F],
@ -58,6 +61,13 @@ impl<F: Field> ConstraintPolynomial<F> {
Self::from_inner(ConstraintPolynomialInner::NextWireValue(index)) 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),
}
}
// TODO: Have these take references? // TODO: Have these take references?
pub fn add(&self, rhs: &Self) -> Self { pub fn add(&self, rhs: &Self) -> Self {
// TODO: Special case for either operand being 0. // TODO: Special case for either operand being 0.
@ -105,7 +115,7 @@ impl<F: Field> ConstraintPolynomial<F> {
self * self * self self * self * self
} }
pub(crate) fn degree(&self) -> usize { pub(crate) fn degree(&self) -> BigUint {
(self.0).0.degree() (self.0).0.degree()
} }
@ -149,6 +159,15 @@ impl<F: Field> ConstraintPolynomial<F> {
.collect() .collect()
} }
/// Replace all occurrences of `from` with `to` in this polynomial graph.
pub(crate) fn replace_all(
&self,
from: Self,
to: Self,
) -> Self {
Self(self.0.replace_all(from.0, to.0))
}
fn from_inner(inner: ConstraintPolynomialInner<F>) -> Self { fn from_inner(inner: ConstraintPolynomialInner<F>) -> Self {
Self(ConstraintPolynomialRef::new(inner)) Self(ConstraintPolynomialRef::new(inner))
} }
@ -413,16 +432,17 @@ impl<F: Field> ConstraintPolynomialInner<F> {
} }
} }
fn degree(&self) -> usize { fn degree(&self) -> BigUint {
match self { match self {
ConstraintPolynomialInner::Constant(_) => 0, ConstraintPolynomialInner::Constant(_) => BigUint::zero(),
ConstraintPolynomialInner::LocalConstant(_) => 1, ConstraintPolynomialInner::LocalConstant(_) => BigUint::one(),
ConstraintPolynomialInner::NextConstant(_) => 1, ConstraintPolynomialInner::NextConstant(_) => BigUint::one(),
ConstraintPolynomialInner::LocalWireValue(_) => 1, ConstraintPolynomialInner::LocalWireValue(_) => BigUint::one(),
ConstraintPolynomialInner::NextWireValue(_) => 1, ConstraintPolynomialInner::NextWireValue(_) => BigUint::one(),
ConstraintPolynomialInner::Sum { lhs, rhs } => lhs.0.degree().max(rhs.0.degree()), ConstraintPolynomialInner::Sum { lhs, rhs } => lhs.0.degree().max(rhs.0.degree()),
ConstraintPolynomialInner::Product { lhs, rhs } => lhs.0.degree() + rhs.0.degree(), ConstraintPolynomialInner::Product { lhs, rhs } => lhs.0.degree() + rhs.0.degree(),
ConstraintPolynomialInner::Exponentiation { base, exponent } => base.0.degree() * exponent, ConstraintPolynomialInner::Exponentiation { base, exponent } =>
base.0.degree() * BigUint::from_usize(*exponent).unwrap(),
} }
} }
} }
@ -431,7 +451,7 @@ impl<F: Field> ConstraintPolynomialInner<F> {
/// than content. This is useful when we want to use constraint polynomials as `HashMap` keys, but /// than content. This is useful when we want to use constraint polynomials as `HashMap` keys, but
/// we want address-based hashing for performance reasons. /// we want address-based hashing for performance reasons.
#[derive(Clone)] #[derive(Clone)]
struct ConstraintPolynomialRef<F: Field>(Rc<ConstraintPolynomialInner<F>>); pub(crate) struct ConstraintPolynomialRef<F: Field>(Rc<ConstraintPolynomialInner<F>>);
impl<F: Field> ConstraintPolynomialRef<F> { impl<F: Field> ConstraintPolynomialRef<F> {
fn new(inner: ConstraintPolynomialInner<F>) -> Self { fn new(inner: ConstraintPolynomialInner<F>) -> Self {
@ -451,6 +471,15 @@ impl<F: Field> ConstraintPolynomialRef<F> {
result result
} }
} }
/// Replace all occurrences of `from` with `to` in this polynomial graph.
fn replace_all(
&self,
from: ConstraintPolynomialRef<F>,
to: ConstraintPolynomialRef<F>,
) -> ConstraintPolynomialRef<F> {
todo!()
}
} }
impl<F: Field> PartialEq for ConstraintPolynomialRef<F> { impl<F: Field> PartialEq for ConstraintPolynomialRef<F> {

View File

@ -38,6 +38,7 @@ impl Debug for CrandallField {
impl Field for CrandallField { impl Field for CrandallField {
const ZERO: Self = Self(0); const ZERO: Self = Self(0);
const ONE: Self = Self(1); const ONE: Self = Self(1);
const TWO: Self = Self(2);
const NEG_ONE: Self = Self(P - 1); const NEG_ONE: Self = Self(P - 1);
#[inline(always)] #[inline(always)]

View File

@ -20,6 +20,7 @@ pub trait Field: 'static
+ Sync { + Sync {
const ZERO: Self; const ZERO: Self;
const ONE: Self; const ONE: Self;
const TWO: Self;
const NEG_ONE: Self; const NEG_ONE: Self;
fn sq(&self) -> Self; fn sq(&self) -> Self;

31
src/gates/constant.rs Normal file
View File

@ -0,0 +1,31 @@
use crate::circuit_data::CircuitConfig;
use crate::constraint_polynomial::ConstraintPolynomial;
use crate::field::field::Field;
use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter};
use crate::gates::gate::GateRef;
use crate::gates::output_graph::{GateOutputLocation, OutputGraph};
/// A gate which takes a single constant parameter and outputs that value.
pub struct ConstantGate2;
impl ConstantGate2 {
pub fn get<F: Field>() -> GateRef<F> {
GateRef::new(DeterministicGateAdapter::new(ConstantGate2))
}
pub const CONST_INPUT: usize = 0;
pub const WIRE_OUTPUT: usize = 0;
}
impl<F: Field> DeterministicGate<F> for ConstantGate2 {
fn id(&self) -> String {
"ConstantGate".into()
}
fn outputs(&self, _config: CircuitConfig) -> OutputGraph<F> {
let loc = GateOutputLocation::LocalWire(Self::WIRE_OUTPUT);
let out = ConstraintPolynomial::local_constant(Self::CONST_INPUT);
OutputGraph::single_output(loc, out)
}
}

View File

@ -5,9 +5,10 @@ use crate::constraint_polynomial::{ConstraintPolynomial, EvaluationVars};
use crate::field::field::Field; use crate::field::field::Field;
use crate::gates::gate::Gate; use crate::gates::gate::Gate;
use crate::generator::{SimpleGenerator, WitnessGenerator2}; use crate::generator::{SimpleGenerator, WitnessGenerator2};
use crate::target::Target2; use crate::target::Target;
use crate::wire::Wire; use crate::wire::Wire;
use crate::witness::PartialWitness; use crate::witness::PartialWitness;
use crate::gates::output_graph::{OutputGraph, GateOutputLocation};
/// A deterministic gate. Each entry in `outputs()` describes how that output is evaluated; this is /// A deterministic gate. Each entry in `outputs()` describes how that output is evaluated; this is
/// used to create both the constraint set and the generator set. /// used to create both the constraint set and the generator set.
@ -18,9 +19,9 @@ pub trait DeterministicGate<F: Field>: 'static {
/// A unique identifier for this gate. /// A unique identifier for this gate.
fn id(&self) -> String; fn id(&self) -> String;
/// A vector of `(i, c)` pairs, where `i` is the index of an output and `c` is the polynomial /// A vector of `(loc, out)` pairs, where `loc` is the location of an output and `out` is a
/// defining how that output is evaluated. /// polynomial defining how that output is evaluated.
fn outputs(&self, config: CircuitConfig) -> Vec<(usize, ConstraintPolynomial<F>)>; fn outputs(&self, config: CircuitConfig) -> OutputGraph<F>;
/// Any additional constraints to be enforced, besides the (automatically provided) ones that /// Any additional constraints to be enforced, besides the (automatically provided) ones that
/// constraint output values. /// constraint output values.
@ -60,8 +61,8 @@ impl<F: Field, DG: DeterministicGate<F>> Gate<F> for DeterministicGateAdapter<F,
fn constraints(&self, config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> { fn constraints(&self, config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> {
// For each output, we add a constraint of the form `out - expression = 0`, // For each output, we add a constraint of the form `out - expression = 0`,
// then we append any additional constraints that the gate defines. // then we append any additional constraints that the gate defines.
self.gate.outputs(config).into_iter() self.gate.outputs(config).outputs.into_iter()
.map(|(i, out)| out - ConstraintPolynomial::local_wire_value(i)) .map(|(output_loc, out)| out - ConstraintPolynomial::from_gate_output(output_loc))
.chain(self.gate.additional_constraints(config).into_iter()) .chain(self.gate.additional_constraints(config).into_iter())
.collect() .collect()
} }
@ -73,12 +74,12 @@ impl<F: Field, DG: DeterministicGate<F>> Gate<F> for DeterministicGateAdapter<F,
local_constants: Vec<F>, local_constants: Vec<F>,
next_constants: Vec<F>, next_constants: Vec<F>,
) -> Vec<Box<dyn WitnessGenerator2<F>>> { ) -> Vec<Box<dyn WitnessGenerator2<F>>> {
self.gate.outputs(config) self.gate.outputs(config).outputs
.into_iter() .into_iter()
.map(|(input_index, out)| { .map(|(location, out)| {
let og = OutputGenerator { let og = OutputGenerator {
gate_index, gate_index,
input_index, location,
out, out,
local_constants: local_constants.clone(), local_constants: local_constants.clone(),
next_constants: next_constants.clone(), next_constants: next_constants.clone(),
@ -96,17 +97,17 @@ impl<F: Field, DG: DeterministicGate<F>> Gate<F> for DeterministicGateAdapter<F,
struct OutputGenerator<F: Field> { struct OutputGenerator<F: Field> {
gate_index: usize, gate_index: usize,
input_index: usize, location: GateOutputLocation,
out: ConstraintPolynomial<F>, out: ConstraintPolynomial<F>,
local_constants: Vec<F>, local_constants: Vec<F>,
next_constants: Vec<F>, next_constants: Vec<F>,
} }
impl<F: Field> SimpleGenerator<F> for OutputGenerator<F> { impl<F: Field> SimpleGenerator<F> for OutputGenerator<F> {
fn dependencies(&self) -> Vec<Target2> { fn dependencies(&self) -> Vec<Target> {
self.out.dependencies(self.gate_index) self.out.dependencies(self.gate_index)
.into_iter() .into_iter()
.map(Target2::Wire) .map(Target::Wire)
.collect() .collect()
} }
@ -125,8 +126,8 @@ impl<F: Field> SimpleGenerator<F> for OutputGenerator<F> {
// Lookup the values if they exist. If not, we can just insert a zero, knowing // Lookup the values if they exist. If not, we can just insert a zero, knowing
// that it will not be used. (If it was used, it would have been included in our // that it will not be used. (If it was used, it would have been included in our
// dependencies, and this generator would not have run yet.) // dependencies, and this generator would not have run yet.)
let local_value = witness.try_get_target(Target2::Wire(local_wire)).unwrap_or(F::ZERO); let local_value = witness.try_get_target(Target::Wire(local_wire)).unwrap_or(F::ZERO);
let next_value = witness.try_get_target(Target2::Wire(next_wire)).unwrap_or(F::ZERO); let next_value = witness.try_get_target(Target::Wire(next_wire)).unwrap_or(F::ZERO);
local_wire_values.push(local_value); local_wire_values.push(local_value);
next_wire_values.push(next_value); next_wire_values.push(next_value);
@ -139,8 +140,14 @@ impl<F: Field> SimpleGenerator<F> for OutputGenerator<F> {
next_wire_values: &next_wire_values, next_wire_values: &next_wire_values,
}; };
let result_wire = Wire { gate: self.gate_index, input: self.input_index }; let result_wire = match self.location {
GateOutputLocation::LocalWire(input) =>
Wire { gate: self.gate_index, input },
GateOutputLocation::NextWire(input) =>
Wire { gate: self.gate_index + 1, input },
};
let result_value = self.out.evaluate(vars); let result_value = self.out.evaluate(vars);
PartialWitness::singleton(Target2::Wire(result_wire), result_value) PartialWitness::singleton(Target::Wire(result_wire), result_value)
} }
} }

View File

@ -5,6 +5,7 @@ use crate::circuit_data::CircuitConfig;
use crate::constraint_polynomial::ConstraintPolynomial; use crate::constraint_polynomial::ConstraintPolynomial;
use crate::field::field::Field; use crate::field::field::Field;
use crate::generator::WitnessGenerator2; use crate::generator::WitnessGenerator2;
use num::ToPrimitive;
/// A custom gate. /// A custom gate.
// TODO: Remove CircuitConfig params? Could just use fields within each struct. // TODO: Remove CircuitConfig params? Could just use fields within each struct.
@ -44,7 +45,7 @@ pub trait Gate<F: Field>: 'static {
fn degree(&self, config: CircuitConfig) -> usize { fn degree(&self, config: CircuitConfig) -> usize {
self.constraints(config) self.constraints(config)
.into_iter() .into_iter()
.map(|c| c.degree()) .map(|c| c.degree().to_usize().expect("degree too large"))
.max() .max()
.unwrap_or(0) .unwrap_or(0)
} }

View File

@ -4,11 +4,12 @@ use std::sync::Arc;
use crate::circuit_data::CircuitConfig; use crate::circuit_data::CircuitConfig;
use crate::constraint_polynomial::ConstraintPolynomial; use crate::constraint_polynomial::ConstraintPolynomial;
use crate::field::field::Field; use crate::field::field::Field;
use crate::gates::deterministic_gate::DeterministicGate; use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter};
use crate::gates::gate::{Gate, GateRef}; use crate::gates::gate::{Gate, GateRef};
use crate::gates::output_graph::{GateOutputLocation, OutputGraph};
use crate::generator::{SimpleGenerator, WitnessGenerator2}; use crate::generator::{SimpleGenerator, WitnessGenerator2};
use crate::gmimc::{gmimc_permute_array, gmimc_permute}; use crate::gmimc::{gmimc_permute, gmimc_permute_array};
use crate::target::Target2; use crate::target::Target;
use crate::wire::Wire; use crate::wire::Wire;
use crate::witness::PartialWitness; use crate::witness::PartialWitness;
@ -21,25 +22,60 @@ pub struct GMiMCGate<F: Field, const W: usize, const R: usize> {
impl<F: Field, const W: usize, const R: usize> GMiMCGate<F, W, R> { impl<F: Field, const W: usize, const R: usize> GMiMCGate<F, W, R> {
pub fn with_constants(constants: Arc<[F; R]>) -> GateRef<F> { pub fn with_constants(constants: Arc<[F; R]>) -> GateRef<F> {
GateRef::new(GMiMCGate::<F, W, R> { constants }) let gate = GMiMCGate::<F, W, R> { constants };
let adapter = DeterministicGateAdapter::new(gate);
GateRef::new(adapter)
} }
pub fn with_automatic_constants() -> GateRef<F> { pub fn with_automatic_constants() -> GateRef<F> {
todo!() todo!()
} }
/// 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 = 0;
/// The wire index for the i'th input to the permutation.
pub fn wire_input(i: usize) -> usize {
i + 1
}
/// The wire index for the i'th output to the permutation.
/// Note that outputs are written to the next gate's wires.
pub fn wire_output(i: usize) -> usize {
i + 1
}
} }
impl<F: Field, const W: usize, const R: usize> Gate<F> for GMiMCGate<F, W, R> { impl<F: Field, const W: usize, const R: usize> DeterministicGate<F> for GMiMCGate<F, W, R> {
fn id(&self) -> String { fn id(&self) -> String {
// TODO: Add W/R // TODO: This won't include generic params?
format!("{:?}", self) format!("{:?}", self)
} }
fn constraints(&self, config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> { fn outputs(&self, config: CircuitConfig) -> OutputGraph<F> {
let mut state = (0..W) let original_inputs = (0..W)
.map(|i| ConstraintPolynomial::local_wire_value(i)) .map(|i| ConstraintPolynomial::local_wire_value(Self::wire_input(i)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Conditionally switch inputs based on the (boolean) switch wire.
let switch = ConstraintPolynomial::local_wire_value(Self::WIRE_SWITCH);
let mut state = Vec::new();
for i in 0..4 {
let a = &original_inputs[i];
let b = &original_inputs[i + 4];
state.push(a + &switch * (b - a));
}
for i in 0..4 {
let a = &original_inputs[i + 4];
let b = &original_inputs[i];
state.push(a + &switch * (b - a));
}
for i in 8..W {
state.push(original_inputs[i].clone());
}
// Value that is implicitly added to each element. // Value that is implicitly added to each element.
// See https://affine.group/2020/02/starkware-challenge // See https://affine.group/2020/02/starkware-challenge
let mut addition_buffer = ConstraintPolynomial::zero(); let mut addition_buffer = ConstraintPolynomial::zero();
@ -56,52 +92,19 @@ impl<F: Field, const W: usize, const R: usize> Gate<F> for GMiMCGate<F, W, R> {
state[i] += &addition_buffer; state[i] += &addition_buffer;
} }
state let outputs = state.into_iter()
.enumerate()
.map(|(i, out)| (GateOutputLocation::NextWire(Self::wire_output(i)), out))
.collect();
// A degree of 9 is reasonable for most circuits, and it means that we only need wires for
// every other addition buffer state.
OutputGraph { outputs }.shrink_degree(9)
} }
fn generators( fn additional_constraints(&self, _config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> {
&self, let switch = ConstraintPolynomial::local_wire_value(Self::WIRE_SWITCH);
config: CircuitConfig, let switch_bool_constraint = &switch * (&switch - 1);
gate_index: usize, vec![switch_bool_constraint]
local_constants: Vec<F>,
next_constants: Vec<F>,
) -> Vec<Box<dyn WitnessGenerator2<F>>> {
let generator = GMiMCGenerator::<F, W, R> {
round_constants: self.constants.clone(),
gate_index,
};
vec![Box::new(generator)]
}
}
struct GMiMCGenerator<F: Field, const W: usize, const R: usize> {
round_constants: Arc<[F; R]>,
gate_index: usize,
}
impl<F: Field, const W: usize, const R: usize> SimpleGenerator<F> for GMiMCGenerator<F, W, R> {
fn dependencies(&self) -> Vec<Target2> {
(0..W)
.map(|i| Target2::Wire(
Wire { gate: self.gate_index, input: i }))
.collect()
}
fn run_once(&mut self, witness: &PartialWitness<F>) -> PartialWitness<F> {
let mut inputs: [F; W] = [F::ZERO; W];
for i in 0..W {
inputs[i] = witness.get_wire(
Wire { gate: self.gate_index, input: i });
}
let outputs = gmimc_permute::<F, W, R>(inputs, self.round_constants.clone());
let mut result = PartialWitness::new();
for i in 0..W {
result.set_wire(
Wire { gate: self.gate_index + 1, input: i },
outputs[i]);
}
result
} }
} }

View File

@ -1,3 +1,5 @@
pub(crate) mod constant;
pub(crate) mod deterministic_gate; pub(crate) mod deterministic_gate;
pub(crate) mod gate; pub(crate) mod gate;
pub(crate) mod gmimc; pub(crate) mod gmimc;
pub(crate) mod output_graph;

66
src/gates/output_graph.rs Normal file
View File

@ -0,0 +1,66 @@
use std::iter;
use crate::constraint_polynomial::{ConstraintPolynomial, ConstraintPolynomialRef};
use crate::field::field::Field;
/// Represents a set of deterministic gate outputs, expressed as polynomials over witness
/// values.
pub struct OutputGraph<F: Field> {
pub(crate) outputs: Vec<(GateOutputLocation, ConstraintPolynomial<F>)>
}
/// Represents an output location of a deterministic gate.
#[derive(Copy, Clone)]
pub enum GateOutputLocation {
/// A wire belonging to the gate itself.
LocalWire(usize),
/// A wire belonging to the following gate.
NextWire(usize),
}
impl<F: Field> OutputGraph<F> {
/// Creates an output graph with a single output.
pub fn single_output(loc: GateOutputLocation, out: ConstraintPolynomial<F>) -> Self {
Self { outputs: vec![(loc, out)] }
}
/// Compiles an output graph with potentially high-degree polynomials to one with low-degree
/// polynomials by introducing extra wires for some intermediate values.
///
/// Note that this uses a simple greedy algorithm, so the result may not be optimal in terms of wire
/// count.
pub fn shrink_degree(&self, new_degree: usize) -> Self {
todo!()
}
/// Allocate a new wire for the given target polynomial, and return a new output graph with
/// references to the target polynomial replaced with references to that wire.
fn allocate_wire(&self, target: ConstraintPolynomial<F>) -> Self {
let new_wire_index = self.outputs.iter()
.flat_map(|(loc, out)| out.max_wire_input_index())
.max()
.map_or(0, |i| i + 1);
let new_wire = ConstraintPolynomial::local_wire_value(new_wire_index);
let outputs = self.outputs.iter()
.map(|(loc, out)| (*loc, out.replace_all(target.clone(), new_wire.clone())))
.chain(iter::once((GateOutputLocation::LocalWire(new_wire_index), target.clone())))
.collect();
Self { outputs }
}
}
#[cfg(test)]
mod tests {
use crate::constraint_polynomial::ConstraintPolynomial;
use crate::gates::output_graph::shrink_degree;
#[test]
fn shrink_exp() {
let original = ConstraintPolynomial::local_wire_value(0).exp(10);
let shrunk = shrink_degree(original, 3);
// `shrunk` should be something similar to (wire0^3)^3 * wire0.
assert_eq!(shrunk.max_wire_input_index(), Some(2))
}
}

View File

@ -1,12 +1,12 @@
use crate::field::field::Field; use crate::field::field::Field;
use crate::target::Target2; use crate::target::Target;
use crate::witness::PartialWitness; use crate::witness::PartialWitness;
/// A generator participates in the generation of the witness. /// A generator participates in the generation of the witness.
pub trait WitnessGenerator2<F: Field>: 'static { pub trait WitnessGenerator2<F: Field>: 'static {
/// Targets to be "watched" by this generator. Whenever a target in the watch list is populated, /// Targets to be "watched" by this generator. Whenever a target in the watch list is populated,
/// the generator will be queued to run. /// the generator will be queued to run.
fn watch_list(&self) -> Vec<Target2>; fn watch_list(&self) -> Vec<Target>;
/// Run this generator, returning a `PartialWitness` containing any new witness elements, and a /// Run this generator, returning a `PartialWitness` containing any new witness elements, and a
/// flag indicating whether the generator is finished. If the flag is true, the generator will /// flag indicating whether the generator is finished. If the flag is true, the generator will
@ -17,13 +17,13 @@ pub trait WitnessGenerator2<F: Field>: 'static {
/// A generator which runs once after a list of dependencies is present in the witness. /// A generator which runs once after a list of dependencies is present in the witness.
pub trait SimpleGenerator<F: Field>: 'static { pub trait SimpleGenerator<F: Field>: 'static {
fn dependencies(&self) -> Vec<Target2>; fn dependencies(&self) -> Vec<Target>;
fn run_once(&mut self, witness: &PartialWitness<F>) -> PartialWitness<F>; fn run_once(&mut self, witness: &PartialWitness<F>) -> PartialWitness<F>;
} }
impl<F: Field, SG: SimpleGenerator<F>> WitnessGenerator2<F> for SG { impl<F: Field, SG: SimpleGenerator<F>> WitnessGenerator2<F> for SG {
fn watch_list(&self) -> Vec<Target2> { fn watch_list(&self) -> Vec<Target> {
self.dependencies() self.dependencies()
} }
@ -38,12 +38,12 @@ impl<F: Field, SG: SimpleGenerator<F>> WitnessGenerator2<F> for SG {
/// A generator which copies one wire to another. /// A generator which copies one wire to another.
pub(crate) struct CopyGenerator { pub(crate) struct CopyGenerator {
pub(crate) src: Target2, pub(crate) src: Target,
pub(crate) dst: Target2, pub(crate) dst: Target,
} }
impl<F: Field> SimpleGenerator<F> for CopyGenerator { impl<F: Field> SimpleGenerator<F> for CopyGenerator {
fn dependencies(&self) -> Vec<Target2> { fn dependencies(&self) -> Vec<Target> {
vec![self.src] vec![self.src]
} }

View File

@ -11,8 +11,8 @@ use field::fft::fft_precompute;
use crate::field::field::Field; use crate::field::field::Field;
use crate::util::log2_ceil; use crate::util::log2_ceil;
use std::sync::Arc;
mod circuit_builder;
mod circuit_data; mod circuit_data;
mod constraint_polynomial; mod constraint_polynomial;
mod field; mod field;
@ -22,6 +22,7 @@ mod generator;
mod gmimc; mod gmimc;
mod proof; mod proof;
mod prover; mod prover;
mod recursive_verifier;
mod rescue; mod rescue;
mod target; mod target;
mod util; mod util;

View File

@ -1,12 +1,12 @@
use crate::field::field::Field; use crate::field::field::Field;
use crate::target::Target2; use crate::target::Target;
pub struct Hash<F: Field> { pub struct Hash<F: Field> {
elements: Vec<F>, elements: Vec<F>,
} }
pub struct HashTarget { pub struct HashTarget {
elements: Vec<Target2>, elements: Vec<Target>,
} }
pub struct Proof2<F: Field> { pub struct Proof2<F: Field> {
@ -34,7 +34,24 @@ pub struct ProofTarget2 {
/// Purported values of each polynomial at each challenge point. /// Purported values of each polynomial at each challenge point.
pub openings: Vec<OpeningSetTarget>, pub openings: Vec<OpeningSetTarget>,
// TODO: FRI Merkle proofs. /// A FRI argument for each FRI query.
pub fri_proofs: Vec<FriProofTarget>,
}
/// Represents a single FRI query, i.e. a path through the reduction tree.
pub struct FriProofTarget {
/// Merkle proofs for the original purported codewords, i.e. the subject of the LDT.
pub initial_merkle_proofs: Vec<MerkleProofTarget>,
/// Merkle proofs for the reduced polynomials that were sent in the commit phase.
pub intermediate_merkle_proofs: Vec<MerkleProofTarget>,
/// The final polynomial in point-value form.
pub final_poly: Vec<Target>,
}
pub struct MerkleProofTarget {
pub leaf: Vec<Target>,
pub siblings: Vec<Target>,
// TODO: Also need left/right turn info.
} }
/// The purported values of each polynomial at a single point. /// The purported values of each polynomial at a single point.
@ -49,10 +66,10 @@ pub struct OpeningSet<F: Field> {
/// The purported values of each polynomial at a single point. /// The purported values of each polynomial at a single point.
pub struct OpeningSetTarget { pub struct OpeningSetTarget {
pub constants: Vec<Target2>, pub constants: Vec<Target>,
pub plonk_sigmas: Vec<Target2>, pub plonk_sigmas: Vec<Target>,
pub wires: Vec<Target2>, pub wires: Vec<Target>,
// TODO: One or multiple? // TODO: One or multiple?
pub plonk_z: Vec<Target2>, pub plonk_z: Vec<Target>,
pub plonk_t: Vec<Target2>, pub plonk_t: Vec<Target>,
} }

View File

@ -0,0 +1,5 @@
use crate::circuit_builder::CircuitBuilder2;
use crate::field::field::Field;
pub fn add_recursive_verifier<F: Field>(builder: &mut CircuitBuilder2<F>) {
}

View File

@ -7,22 +7,22 @@ use crate::wire::Wire;
/// A location in the witness. /// A location in the witness.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum Target2 { pub enum Target {
Wire(Wire), Wire(Wire),
PublicInput { index: usize }, PublicInput { index: usize },
VirtualAdviceTarget { index: usize }, VirtualAdviceTarget { index: usize },
} }
impl Target2 { impl Target {
pub fn wire(gate: usize, input: usize) -> Self { pub fn wire(gate: usize, input: usize) -> Self {
Self::Wire(Wire { gate, input }) Self::Wire(Wire { gate, input })
} }
pub fn is_routable(&self, config: CircuitConfig) -> bool { pub fn is_routable(&self, config: CircuitConfig) -> bool {
match self { match self {
Target2::Wire(wire) => wire.is_routable(config), Target::Wire(wire) => wire.is_routable(config),
Target2::PublicInput { .. } => true, Target::PublicInput { .. } => true,
Target2::VirtualAdviceTarget { .. } => false, Target::VirtualAdviceTarget { .. } => false,
} }
} }
} }

View File

@ -1,12 +1,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::field::field::Field; use crate::field::field::Field;
use crate::target::Target2; use crate::target::Target;
use crate::wire::Wire; use crate::wire::Wire;
#[derive(Debug)] #[derive(Debug)]
pub struct PartialWitness<F: Field> { pub struct PartialWitness<F: Field> {
target_values: HashMap<Target2, F>, target_values: HashMap<Target, F>,
} }
impl<F: Field> PartialWitness<F> { impl<F: Field> PartialWitness<F> {
@ -16,7 +16,7 @@ impl<F: Field> PartialWitness<F> {
} }
} }
pub fn singleton(target: Target2, value: F) -> Self { pub fn singleton(target: Target, value: F) -> Self {
let mut witness = PartialWitness::new(); let mut witness = PartialWitness::new();
witness.set_target(target, value); witness.set_target(target, value);
witness witness
@ -26,31 +26,31 @@ impl<F: Field> PartialWitness<F> {
self.target_values.is_empty() self.target_values.is_empty()
} }
pub fn get_target(&self, target: Target2) -> F { pub fn get_target(&self, target: Target) -> F {
self.target_values[&target] self.target_values[&target]
} }
pub fn try_get_target(&self, target: Target2) -> Option<F> { pub fn try_get_target(&self, target: Target) -> Option<F> {
self.target_values.get(&target).cloned() self.target_values.get(&target).cloned()
} }
pub fn get_wire(&self, wire: Wire) -> F { pub fn get_wire(&self, wire: Wire) -> F {
self.get_target(Target2::Wire(wire)) self.get_target(Target::Wire(wire))
} }
pub fn contains(&self, target: Target2) -> bool { pub fn contains(&self, target: Target) -> bool {
self.target_values.contains_key(&target) self.target_values.contains_key(&target)
} }
pub fn contains_all(&self, targets: &[Target2]) -> bool { pub fn contains_all(&self, targets: &[Target]) -> bool {
targets.iter().all(|&t| self.contains(t)) targets.iter().all(|&t| self.contains(t))
} }
pub fn set_target(&mut self, target: Target2, value: F) { pub fn set_target(&mut self, target: Target, value: F) {
self.target_values.insert(target, value); self.target_values.insert(target, value);
} }
pub fn set_wire(&mut self, wire: Wire, value: F) { pub fn set_wire(&mut self, wire: Wire, value: F) {
self.set_target(Target2::Wire(wire), value) self.set_target(Target::Wire(wire), value)
} }
} }