From f42120482ac5d3a3b7f2e84441a2cfafa6b7d3ad Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 28 Mar 2021 15:36:51 -0700 Subject: [PATCH] No more polynomial programming abstraction It was too expensive. --- src/circuit_builder.rs | 43 +- src/circuit_data.rs | 57 ++- src/constraint_polynomial.rs | 566 +---------------------- src/gadgets/arithmetic.rs | 21 + src/gadgets/conditionals.rs | 13 - src/gadgets/mod.rs | 2 +- src/gadgets/split_join.rs | 27 +- src/gates/constant.rs | 84 +++- src/gates/deterministic_gate.rs | 157 ------- src/gates/fri_consistency_gate.rs | 744 +++++++++++++++--------------- src/gates/gate.rs | 66 ++- src/gates/gmimc.rs | 238 ++++++---- src/gates/gmimc_eval.rs | 81 ++++ src/gates/mod.rs | 3 +- src/gates/noop.rs | 38 +- src/gates/output_graph.rs | 113 ----- src/generator.rs | 1 + src/hash.rs | 13 +- src/main.rs | 18 +- src/plonk_common.rs | 19 + src/prover.rs | 103 ++++- src/recursive_verifier.rs | 5 + src/util.rs | 16 +- 23 files changed, 1008 insertions(+), 1420 deletions(-) create mode 100644 src/gadgets/arithmetic.rs delete mode 100644 src/gadgets/conditionals.rs delete mode 100644 src/gates/deterministic_gate.rs create mode 100644 src/gates/gmimc_eval.rs delete mode 100644 src/gates/output_graph.rs create mode 100644 src/plonk_common.rs diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index 0bb88e70..5ab7e907 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -6,17 +6,17 @@ use log::info; use crate::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, ProverCircuitData, ProverOnlyCircuitData, VerifierCircuitData, VerifierOnlyCircuitData}; use crate::field::fft::lde_multiple; use crate::field::field::Field; -use crate::gates::constant::ConstantGate2; -use crate::gates::gate::{GateInstance, GateRef}; +use crate::gates::constant::ConstantGate; +use crate::gates::gate::{Gate, GateInstance, GateRef}; +use crate::gates::noop::NoopGate; use crate::generator::{CopyGenerator, WitnessGenerator}; use crate::hash::merkle_root_bit_rev_order; use crate::target::Target; +use crate::util::{transpose, log2_strict}; use crate::wire::Wire; -use crate::gates::noop::NoopGate; -use crate::util::transpose; pub struct CircuitBuilder { - config: CircuitConfig, + pub(crate) config: CircuitConfig, /// The types of gates used in this circuit. gates: HashSet>, @@ -55,7 +55,7 @@ impl CircuitBuilder { } fn check_gate_compatibility(&self, gate: &GateRef) { - assert!(gate.0.min_wires(self.config) <= self.config.num_wires); + assert!(gate.0.num_wires() <= self.config.num_wires); } /// Shorthand for `generate_copy` and `assert_equal`. @@ -103,8 +103,8 @@ impl CircuitBuilder { /// 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 }) + let gate = self.add_gate(ConstantGate::get(), vec![c]); + Target::Wire(Wire { gate, input: ConstantGate::WIRE_OUTPUT }) } fn blind_and_pad(&mut self) { @@ -119,10 +119,9 @@ impl CircuitBuilder { self.gate_instances.iter() .enumerate() .flat_map(|(gate_index, gate_inst)| gate_inst.gate_type.0.generators( - self.config, gate_index, - gate_inst.constants.clone(), - vec![])) // TODO: Not supporting next_const for now. + &gate_inst.constants, + &[])) // TODO: Not supporting next_const for now. .collect() } @@ -157,20 +156,32 @@ impl CircuitBuilder { let constant_vecs = self.constant_vecs(); let constant_ldes = lde_multiple(constant_vecs, self.config.rate_bits); - let constants_root = merkle_root_bit_rev_order(constant_ldes); + let constant_ldes_t = transpose(&constant_ldes); + let constants_root = merkle_root_bit_rev_order(constant_ldes_t.clone()); let sigma_vecs = self.sigma_vecs(); let sigma_ldes = lde_multiple(sigma_vecs, self.config.rate_bits); - let sigmas_root = merkle_root_bit_rev_order(sigma_ldes); + let sigmas_root = merkle_root_bit_rev_order(transpose(&sigma_ldes)); let generators = self.get_generators(); - let prover_only = ProverOnlyCircuitData { generators }; + let prover_only = ProverOnlyCircuitData { generators, constant_ldes_t }; let verifier_only = VerifierOnlyCircuitData {}; + // The HashSet of gates will have a non-deterministic order. When converting to a Vec, we + // sort by ID to make the ordering deterministic. + let mut gates = self.gates.iter().cloned().collect::>(); + gates.sort_unstable_by_key(|gate| gate.0.id()); + + let num_gate_constraints = gates.iter() + .map(|gate| gate.0.num_constraints()) + .max() + .expect("No gates?"); + let common = CommonCircuitData { config: self.config, - degree, - gates: self.gates.iter().cloned().collect(), + degree_bits: log2_strict(degree), + gates, + num_gate_constraints, constants_root, sigmas_root, }; diff --git a/src/circuit_data.rs b/src/circuit_data.rs index 18e78b95..7a750118 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -5,6 +5,8 @@ use crate::prover::prove; use crate::verifier::verify; use crate::witness::PartialWitness; use crate::gates::gate::{GateRef}; +use crate::constraint_polynomial::{EvaluationVars, EvaluationTargets}; +use crate::target::Target; #[derive(Copy, Clone)] pub struct CircuitConfig { @@ -12,6 +14,20 @@ pub struct CircuitConfig { pub num_routed_wires: usize, pub security_bits: usize, pub rate_bits: usize, + /// The number of times to repeat checks that have soundness errors of (roughly) `degree / |F|`. + pub num_checks: usize, +} + +impl Default for CircuitConfig { + fn default() -> Self { + CircuitConfig { + num_wires: 3, + num_routed_wires: 3, + security_bits: 128, + rate_bits: 3, + num_checks: 3, + } + } } impl CircuitConfig { @@ -64,6 +80,7 @@ impl VerifierCircuitData { /// Circuit data required by the prover, but not the verifier. pub(crate) struct ProverOnlyCircuitData { pub generators: Vec>>, + pub constant_ldes_t: Vec>, } /// Circuit data required by the verifier, but not the prover. @@ -73,11 +90,13 @@ pub(crate) struct VerifierOnlyCircuitData {} pub(crate) struct CommonCircuitData { pub(crate) config: CircuitConfig, - pub(crate) degree: usize, + pub(crate) degree_bits: usize, /// The types of gates used in this circuit. pub(crate) gates: Vec>, + pub(crate) num_gate_constraints: usize, + /// A commitment to each constant polynomial. pub(crate) constants_root: Hash, @@ -86,10 +105,42 @@ pub(crate) struct CommonCircuitData { } impl CommonCircuitData { - pub fn constraint_degree(&self, config: CircuitConfig) -> usize { + pub fn degree(&self) -> usize { + 1 << self.degree_bits + } + + pub fn lde_size(&self) -> usize { + 1 << (self.degree_bits + self.config.rate_bits) + } + + pub fn lde_generator(&self) -> F { + F::primitive_root_of_unity(self.degree_bits + self.config.rate_bits) + } + + pub fn constraint_degree(&self) -> usize { self.gates.iter() - .map(|g| g.0.degree(config)) + .map(|g| g.0.degree()) .max() .expect("No gates?") } + + pub fn total_constraints(&self) -> usize { + // 2 constraints for each Z check. + self.config.num_checks * 2 + self.num_gate_constraints + } + + pub fn evaluate(&self, vars: EvaluationVars) -> Vec { + let mut constraints = vec![F::ZERO; self.num_gate_constraints]; + for gate in &self.gates { + let gate_constraints = gate.0.eval_filtered(vars); + for (i, c) in gate_constraints.into_iter().enumerate() { + constraints[i] += c; + } + } + constraints + } + + pub fn evaluate_recursive(&self, vars: EvaluationTargets) -> Vec { + todo!() + } } diff --git a/src/constraint_polynomial.rs b/src/constraint_polynomial.rs index 9a5e9d1c..395e092d 100644 --- a/src/constraint_polynomial.rs +++ b/src/constraint_polynomial.rs @@ -10,570 +10,20 @@ use std::rc::Rc; use num::{BigUint, FromPrimitive, One, Zero}; use crate::field::field::Field; -use crate::gates::output_graph::GateOutputLocation; +use crate::target::Target; use crate::wire::Wire; -pub(crate) struct EvaluationVars<'a, F: Field> { +#[derive(Copy, Clone)] +pub struct EvaluationVars<'a, F: Field> { pub(crate) local_constants: &'a [F], pub(crate) next_constants: &'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 -/// constants, local wire values, and next wire values). This representation does not require any -/// particular form; it permits arbitrary forms such as `(x + 1)^3 + y z`. -/// -/// This type implements `Hash` and `Eq` based on references rather -/// than content. This is useful when we want to use constraint polynomials as `HashMap` keys, but -/// we want address-based hashing for performance reasons. -#[derive(Clone)] -pub struct ConstraintPolynomial(pub(crate) Rc>); - -impl ConstraintPolynomial { - pub fn constant(c: F) -> Self { - Self::from_inner(ConstraintPolynomialInner::Constant(c)) - } - - pub fn constant_usize(c: usize) -> Self { - Self::constant(F::from_canonical_usize(c)) - } - - // TODO: const? - pub fn zero() -> Self { - Self::constant(F::ZERO) - } - - pub fn one() -> Self { - Self::constant(F::ONE) - } - - pub fn local_constant(index: usize) -> Self { - Self::from_inner(ConstraintPolynomialInner::LocalConstant(index)) - } - - pub fn next_constant(index: usize) -> Self { - Self::from_inner(ConstraintPolynomialInner::NextConstant(index)) - } - - pub fn local_wire(index: usize) -> Self { - Self::from_inner(ConstraintPolynomialInner::LocalWireValue(index)) - } - - 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(i), - GateOutputLocation::NextWire(i) => Self::next_wire(i), - } - } - - // TODO: Have these take references? - pub fn add(&self, rhs: &Self) -> Self { - // TODO: Special case for either operand being 0. - Self::from_inner(ConstraintPolynomialInner::Sum { - lhs: self.clone(), - rhs: rhs.clone(), - }) - } - - pub fn sub(&self, rhs: &Self) -> Self { - // TODO: Special case for either operand being 0. - // TODO: Faster to have a dedicated ConstraintPolynomialInner::Difference? - // TODO: `self + -rhs`? - self.add(&rhs.neg()) - } - - pub fn double(&self) -> Self { - self.clone().add(self) - } - - pub fn triple(&self) -> Self { - self * 3 - } - - pub fn mul(&self, rhs: &Self) -> Self { - // TODO: Special case for either operand being 1. - Self::from_inner(ConstraintPolynomialInner::Product { - lhs: self.clone(), - rhs: rhs.clone(), - }) - } - - pub fn exp(&self, exponent: usize) -> Self { - Self::from_inner(ConstraintPolynomialInner::Exponentiation { - base: self.clone(), - exponent, - }) - } - - pub fn square(&self) -> Self { - self * self - } - - pub fn cube(&self) -> Self { - self * self * self - } - - pub fn degree(&self) -> BigUint { - self.0.degree() - } - - /// Returns the set of wires that this constraint would depend on if it were applied at a - /// certain gate index. - pub(crate) fn dependencies(&self, gate: usize) -> Vec { - let mut deps = HashSet::new(); - self.0.add_dependencies(gate, &mut deps); - deps.into_iter().collect() - } - - /// Find the largest input index among the wires this constraint depends on. - pub(crate) fn max_wire_input_index(&self) -> Option { - self.dependencies(0) - .into_iter() - .map(|wire| wire.input) - .max() - } - - pub(crate) fn max_constant_index(&self) -> Option { - let mut indices = HashSet::new(); - self.0.add_constant_indices(&mut indices); - indices.into_iter().max() - } - - pub(crate) fn evaluate(&self, vars: EvaluationVars) -> F { - let results = Self::evaluate_all(&[self.clone()], vars); - assert_eq!(results.len(), 1); - results[0] - } - - /// Evaluate multiple constraint polynomials simultaneously. This can be more efficient than - /// evaluating them sequentially, since shared intermediate results will only be computed once. - pub(crate) fn evaluate_all( - polynomials: &[ConstraintPolynomial], - vars: EvaluationVars, - ) -> Vec { - let mut mem = HashMap::new(); - polynomials.iter() - .map(|p| p.evaluate_memoized(&vars, &mut mem)) - .collect() - } - - fn evaluate_memoized( - &self, - vars: &EvaluationVars, - mem: &mut HashMap, - ) -> F { - if let Some(&result) = mem.get(self) { - result - } else { - let result = self.0.evaluate(vars, mem); - mem.insert(self.clone(), result); - result - } - } - - /// Replace all occurrences of `from` with `to` in this polynomial graph. - pub(crate) fn replace_all( - &self, - from: Self, - to: Self, - ) -> Self { - self.replace_all_helper(from, to, &mut HashMap::new()) - } - - /// Replace all occurrences of `from` with `to` in this polynomial graph. In order to preserve - /// the structure of the graph, we keep track of any `ConstraintPolynomial`s that have been - /// replaced already. - fn replace_all_helper( - &self, from: Self, - to: Self, - replacements: &mut HashMap, - ) -> Self { - if *self == from { - return to; - } - - if let Some(replacement) = replacements.get(self) { - return replacement.clone(); - } - - match self.0.borrow() { - ConstraintPolynomialInner::Constant(_) => self.clone(), - ConstraintPolynomialInner::LocalConstant(_) => self.clone(), - ConstraintPolynomialInner::NextConstant(_) => self.clone(), - ConstraintPolynomialInner::LocalWireValue(_) => self.clone(), - ConstraintPolynomialInner::NextWireValue(_) => self.clone(), - ConstraintPolynomialInner::Sum { lhs, rhs } => { - let lhs = lhs.replace_all_helper(from.clone(), to.clone(), replacements); - let rhs = rhs.replace_all_helper(from, to, replacements); - let replacement = Self::from_inner(ConstraintPolynomialInner::Sum { lhs, rhs }); - debug_assert!(!replacements.contains_key(self)); - replacements.insert(self.clone(), replacement.clone()); - replacement - } - ConstraintPolynomialInner::Product { lhs, rhs } => { - let lhs = lhs.replace_all_helper(from.clone(), to.clone(), replacements); - let rhs = rhs.replace_all_helper(from, to, replacements); - let replacement = Self::from_inner(ConstraintPolynomialInner::Product { lhs, rhs }); - debug_assert!(!replacements.contains_key(self)); - replacements.insert(self.clone(), replacement.clone()); - replacement - } - ConstraintPolynomialInner::Exponentiation { base, exponent } => { - let base = base.replace_all_helper(from, to, replacements); - let replacement = Self::from_inner( - ConstraintPolynomialInner::Exponentiation { base, exponent: *exponent }); - debug_assert!(!replacements.contains_key(self)); - replacements.insert(self.clone(), replacement.clone()); - replacement - } - } - } - - fn from_inner(inner: ConstraintPolynomialInner) -> Self { - Self(Rc::new(inner)) - } -} - -impl Debug for ConstraintPolynomial { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Display::fmt(&self.0, f) - } -} - -impl Display for ConstraintPolynomial { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Display::fmt(&self.0, f) - } -} - -impl PartialEq for ConstraintPolynomial { - fn eq(&self, other: &Self) -> bool { - ptr::eq(&*self.0, &*other.0) - } -} - -impl Eq for ConstraintPolynomial {} - -impl Hash for ConstraintPolynomial { - fn hash(&self, state: &mut H) { - ptr::hash(&*self.0, state); - } -} - -impl Neg for ConstraintPolynomial { - type Output = Self; - - fn neg(self) -> Self { - // TODO: Faster to have a dedicated ConstraintPolynomialInner::Negation? - self * ConstraintPolynomial::constant(F::NEG_ONE) - } -} - -impl Neg for &ConstraintPolynomial { - type Output = ConstraintPolynomial; - - fn neg(self) -> ConstraintPolynomial { - self.clone().neg() - } -} - -/// Generates the following variants of a binary operation: -/// - `Self . Self` -/// - `&Self . Self` -/// - `Self . &Self` -/// - `&Self . &Self` -/// - `Self . F` -/// - `&Self . F` -/// - `Self . usize` -/// - `&Self . usize` -/// where `Self` is `ConstraintPolynomial`. -/// -/// Takes the following arguments: -/// - `$trait`: the name of the binary operation trait to implement -/// - `$method`: the name of the method in the trait. It is assumed that `ConstraintPolynomial` -/// contains a method with the same name, implementing the `&Self . &Self` variant. -macro_rules! binop_variants { - ($trait:ident, $method:ident) => { - impl $trait for ConstraintPolynomial { - type Output = Self; - - fn $method(self, rhs: Self) -> Self { - ConstraintPolynomial::$method(&self, &rhs) - } - } - - impl $trait<&Self> for ConstraintPolynomial { - type Output = Self; - - fn $method(self, rhs: &Self) -> Self { - ConstraintPolynomial::$method(&self, rhs) - } - } - - impl $trait> for &ConstraintPolynomial { - type Output = ConstraintPolynomial; - - fn $method(self, rhs: ConstraintPolynomial) -> Self::Output { - ConstraintPolynomial::$method(self, &rhs) - } - } - - impl $trait for &ConstraintPolynomial { - type Output = ConstraintPolynomial; - - fn $method(self, rhs: Self) -> Self::Output { - ConstraintPolynomial::$method(self, rhs) - } - } - - impl $trait for ConstraintPolynomial { - type Output = Self; - - fn $method(self, rhs: F) -> Self { - ConstraintPolynomial::$method(&self, &ConstraintPolynomial::constant(rhs)) - } - } - - impl $trait for &ConstraintPolynomial { - type Output = ConstraintPolynomial; - - fn $method(self, rhs: F) -> Self::Output { - ConstraintPolynomial::$method(self, &ConstraintPolynomial::constant(rhs)) - } - } - - impl $trait for ConstraintPolynomial { - type Output = Self; - - fn $method(self, rhs: usize) -> Self { - ConstraintPolynomial::$method(&self, &ConstraintPolynomial::constant_usize(rhs)) - } - } - - impl $trait for &ConstraintPolynomial { - type Output = ConstraintPolynomial; - - fn $method(self, rhs: usize) -> Self::Output { - ConstraintPolynomial::$method(self, &ConstraintPolynomial::constant_usize(rhs)) - } - } - }; -} - -/// Generates the following variants of a binary operation assignment: -/// - `.= Self` -/// - `.= &Self` -/// - `.= F` -/// - `.= usize` -/// where `Self` is `ConstraintPolynomial`. -/// -/// Takes the following arguments: -/// - `$trait`: the name of the binary operation trait to implement -/// - `$assign_method`: the name of the method in the trait -/// - `$binop_method`: the name of a method in `ConstraintPolynomial` -/// which implements the `&Self . &Self` operation. -macro_rules! binop_assign_variants { - ($trait:ident, $assign_method:ident, $binop_method:ident) => { - impl $trait for ConstraintPolynomial { - fn $assign_method(&mut self, rhs: Self) { - *self = ConstraintPolynomial::$binop_method(self, &rhs); - } - } - - impl $trait<&Self> for ConstraintPolynomial { - fn $assign_method(&mut self, rhs: &Self) { - *self = ConstraintPolynomial::$binop_method(self, rhs); - } - } - - impl $trait for ConstraintPolynomial { - fn $assign_method(&mut self, rhs: F) { - *self = ConstraintPolynomial::$binop_method(self, &ConstraintPolynomial::constant(rhs)); - } - } - - impl $trait for ConstraintPolynomial { - fn $assign_method(&mut self, rhs: usize) { - *self = ConstraintPolynomial::$binop_method(self, &ConstraintPolynomial::constant_usize(rhs)); - } - } - } -} - -binop_variants!(Add, add); -binop_variants!(Sub, sub); -binop_variants!(Mul, mul); - -binop_assign_variants!(AddAssign, add_assign, add); -binop_assign_variants!(SubAssign, sub_assign, sub); -binop_assign_variants!(MulAssign, mul_assign, mul); - -impl Sum for ConstraintPolynomial { - fn sum>(iter: I) -> Self { - iter.fold( - ConstraintPolynomial::zero(), - |sum, x| sum + x) - } -} - -impl Product for ConstraintPolynomial { - fn product>(iter: I) -> Self { - iter.fold( - ConstraintPolynomial::one(), - |product, x| product * x) - } -} - -#[derive(Clone, Debug)] -pub(crate) enum ConstraintPolynomialInner { - Constant(F), - - LocalConstant(usize), - NextConstant(usize), - LocalWireValue(usize), - NextWireValue(usize), - - Sum { - lhs: ConstraintPolynomial, - rhs: ConstraintPolynomial, - }, - Product { - lhs: ConstraintPolynomial, - rhs: ConstraintPolynomial, - }, - Exponentiation { - base: ConstraintPolynomial, - exponent: usize, - }, -} - -impl Display for ConstraintPolynomialInner { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - ConstraintPolynomialInner::Constant(c) => - write!(f, "{}", c), - ConstraintPolynomialInner::LocalConstant(i) => - write!(f, "local_const_{}", i), - ConstraintPolynomialInner::NextConstant(i) => - write!(f, "next_const_{}", i), - ConstraintPolynomialInner::LocalWireValue(i) => - write!(f, "local_wire_{}", i), - ConstraintPolynomialInner::NextWireValue(i) => - write!(f, "next_wire_{}", i), - ConstraintPolynomialInner::Sum { lhs, rhs } => - write!(f, "({} + {})", lhs, rhs), - ConstraintPolynomialInner::Product { lhs, rhs } => - write!(f, "({} * {})", lhs, rhs), - ConstraintPolynomialInner::Exponentiation { base, exponent } => - write!(f, "({} ^ {})", base, exponent), - } - } -} - -impl ConstraintPolynomialInner { - fn add_dependencies(&self, gate: usize, deps: &mut HashSet) { - match self { - ConstraintPolynomialInner::Constant(_) => (), - ConstraintPolynomialInner::LocalConstant(_) => (), - ConstraintPolynomialInner::NextConstant(_) => (), - ConstraintPolynomialInner::LocalWireValue(i) => - { deps.insert(Wire { gate, input: *i }); } - ConstraintPolynomialInner::NextWireValue(i) => - { deps.insert(Wire { gate: gate + 1, input: *i }); } - ConstraintPolynomialInner::Sum { lhs, rhs } => { - lhs.0.add_dependencies(gate, deps); - rhs.0.add_dependencies(gate, deps); - } - ConstraintPolynomialInner::Product { lhs, rhs } => { - lhs.0.add_dependencies(gate, deps); - rhs.0.add_dependencies(gate, deps); - } - ConstraintPolynomialInner::Exponentiation { base, exponent: _ } => { - base.0.add_dependencies(gate, deps); - } - } - } - - fn add_constant_indices(&self, indices: &mut HashSet) { - match self { - ConstraintPolynomialInner::Constant(_) => (), - ConstraintPolynomialInner::LocalConstant(i) => { indices.insert(*i); } - ConstraintPolynomialInner::NextConstant(i) => { indices.insert(*i); } - ConstraintPolynomialInner::LocalWireValue(_) => (), - ConstraintPolynomialInner::NextWireValue(_) => (), - ConstraintPolynomialInner::Sum { lhs, rhs } => { - lhs.0.add_constant_indices(indices); - rhs.0.add_constant_indices(indices); - } - ConstraintPolynomialInner::Product { lhs, rhs } => { - lhs.0.add_constant_indices(indices); - rhs.0.add_constant_indices(indices); - } - ConstraintPolynomialInner::Exponentiation { base, exponent: _ } => { - base.0.add_constant_indices(indices); - } - } - } - - fn evaluate( - &self, - vars: &EvaluationVars, - mem: &mut HashMap, F>, - ) -> F { - match self { - ConstraintPolynomialInner::Constant(c) => *c, - ConstraintPolynomialInner::LocalConstant(i) => vars.local_constants[*i], - ConstraintPolynomialInner::NextConstant(i) => vars.next_constants[*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); - lhs + rhs - } - ConstraintPolynomialInner::Product { lhs, rhs } => { - let lhs = lhs.evaluate_memoized(vars, mem); - let rhs = rhs.evaluate_memoized(vars, mem); - lhs * rhs - } - ConstraintPolynomialInner::Exponentiation { base, exponent } => { - let base = base.evaluate_memoized(vars, mem); - base.exp_usize(*exponent) - } - } - } - - fn degree(&self) -> BigUint { - match self { - ConstraintPolynomialInner::Constant(_) => BigUint::zero(), - ConstraintPolynomialInner::LocalConstant(_) => BigUint::one(), - ConstraintPolynomialInner::NextConstant(_) => BigUint::one(), - ConstraintPolynomialInner::LocalWireValue(_) => BigUint::one(), - ConstraintPolynomialInner::NextWireValue(_) => BigUint::one(), - ConstraintPolynomialInner::Sum { lhs, rhs } => lhs.0.degree().max(rhs.0.degree()), - ConstraintPolynomialInner::Product { lhs, rhs } => lhs.0.degree() + rhs.0.degree(), - ConstraintPolynomialInner::Exponentiation { base, exponent } => - base.0.degree() * BigUint::from_usize(*exponent).unwrap(), - } - } -} - -#[cfg(test)] -mod tests { - use crate::constraint_polynomial::ConstraintPolynomial; - use crate::field::crandall_field::CrandallField; - - #[test] - fn equality() { - type F = CrandallField; - let wire0 = ConstraintPolynomial::::local_wire(0); - // == should compare the pointers, and the clone should point to the same underlying - // ConstraintPolynomialInner. - assert_eq!(wire0.clone(), wire0); - } +pub struct EvaluationTargets<'a> { + pub(crate) local_constants: &'a [Target], + pub(crate) next_constants: &'a [Target], + pub(crate) local_wires: &'a [Target], + pub(crate) next_wires: &'a [Target], } diff --git a/src/gadgets/arithmetic.rs b/src/gadgets/arithmetic.rs new file mode 100644 index 00000000..a8991408 --- /dev/null +++ b/src/gadgets/arithmetic.rs @@ -0,0 +1,21 @@ +use crate::circuit_builder::CircuitBuilder; +use crate::field::field::Field; +use crate::target::Target; + +impl CircuitBuilder { + pub fn add(&mut self, x: Target, y: Target) -> Target { + todo!() + } + + pub fn sub(&mut self, x: Target, y: Target) -> Target { + todo!() + } + + pub fn mul(&mut self, x: Target, y: Target) -> Target { + todo!() + } + + pub fn div(&mut self, x: Target, y: Target) -> Target { + todo!() + } +} diff --git a/src/gadgets/conditionals.rs b/src/gadgets/conditionals.rs deleted file mode 100644 index fd8683be..00000000 --- a/src/gadgets/conditionals.rs +++ /dev/null @@ -1,13 +0,0 @@ -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( - x: &ConstraintPolynomial, - y: &ConstraintPolynomial, - c: &ConstraintPolynomial, -) -> ConstraintPolynomial { - let product = x * y; - let delta = product - x; - x + c * delta -} diff --git a/src/gadgets/mod.rs b/src/gadgets/mod.rs index f2de26aa..754bccfb 100644 --- a/src/gadgets/mod.rs +++ b/src/gadgets/mod.rs @@ -1,2 +1,2 @@ +pub(crate) mod arithmetic; pub(crate) mod split_join; -pub(crate) mod conditionals; diff --git a/src/gadgets/split_join.rs b/src/gadgets/split_join.rs index b017927f..3a79b084 100644 --- a/src/gadgets/split_join.rs +++ b/src/gadgets/split_join.rs @@ -1,25 +1,24 @@ 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( - integer: ConstraintPolynomial, - bits: &[ConstraintPolynomial], -) -> Vec> { - 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() -} +// /// Constraints for a little-endian split. +// pub fn split_le_constraints( +// integer: ConstraintPolynomial, +// bits: &[ConstraintPolynomial], +// ) -> Vec> { +// 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( diff --git a/src/gates/constant.rs b/src/gates/constant.rs index 8bd2c89f..03281442 100644 --- a/src/gates/constant.rs +++ b/src/gates/constant.rs @@ -1,16 +1,18 @@ -use crate::circuit_data::CircuitConfig; -use crate::constraint_polynomial::ConstraintPolynomial; +use crate::circuit_builder::CircuitBuilder; +use crate::constraint_polynomial::{EvaluationTargets, EvaluationVars}; use crate::field::field::Field; -use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter}; -use crate::gates::gate::GateRef; -use crate::gates::output_graph::{GateOutputLocation, OutputGraph}; +use crate::gates::gate::{Gate, GateRef}; +use crate::generator::{SimpleGenerator, WitnessGenerator}; +use crate::target::Target; +use crate::witness::PartialWitness; +use crate::wire::Wire; /// A gate which takes a single constant parameter and outputs that value. -pub struct ConstantGate2; +pub struct ConstantGate; -impl ConstantGate2 { +impl ConstantGate { pub fn get() -> GateRef { - GateRef::new(DeterministicGateAdapter::new(ConstantGate2)) + GateRef::new(ConstantGate) } pub const CONST_INPUT: usize = 0; @@ -18,14 +20,70 @@ impl ConstantGate2 { pub const WIRE_OUTPUT: usize = 0; } -impl DeterministicGate for ConstantGate2 { +impl Gate for ConstantGate { fn id(&self) -> String { "ConstantGate".into() } - fn outputs(&self, _config: CircuitConfig) -> OutputGraph { - let loc = GateOutputLocation::LocalWire(Self::WIRE_OUTPUT); - let out = ConstraintPolynomial::local_constant(Self::CONST_INPUT); - OutputGraph::single_output(loc, out) + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let input = vars.local_constants[Self::CONST_INPUT]; + let output = vars.local_wires[Self::WIRE_OUTPUT]; + vec![output - input] + } + + fn eval_unfiltered_recursively( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec { + let input = vars.local_constants[Self::CONST_INPUT]; + let output = vars.local_wires[Self::WIRE_OUTPUT]; + vec![builder.sub(output, input)] + } + + fn generators( + &self, + gate_index: usize, + local_constants: &[F], + next_constants: &[F], + ) -> Vec>> { + let gen = ConstantGenerator { + gate_index, + constant: local_constants[0], + }; + vec![Box::new(gen)] + } + + fn num_wires(&self) -> usize { + 1 + } + + fn num_constants(&self) -> usize { + 1 + } + + fn degree(&self) -> usize { + 1 + } + + fn num_constraints(&self) -> usize { + 1 + } +} + +#[derive(Debug)] +struct ConstantGenerator { + gate_index: usize, + constant: F, +} + +impl SimpleGenerator for ConstantGenerator { + fn dependencies(&self) -> Vec { + Vec::new() + } + + fn run_once(&self, witness: &PartialWitness) -> PartialWitness { + let wire = Wire { gate: self.gate_index, input: ConstantGate::WIRE_OUTPUT }; + PartialWitness::singleton(Target::Wire(wire), self.constant) } } diff --git a/src/gates/deterministic_gate.rs b/src/gates/deterministic_gate.rs deleted file mode 100644 index da231450..00000000 --- a/src/gates/deterministic_gate.rs +++ /dev/null @@ -1,157 +0,0 @@ -use std::marker::PhantomData; - -use crate::circuit_data::CircuitConfig; -use crate::constraint_polynomial::{ConstraintPolynomial, EvaluationVars}; -use crate::field::field::Field; -use crate::gates::gate::Gate; -use crate::generator::{SimpleGenerator, WitnessGenerator}; -use crate::target::Target; -use crate::wire::Wire; -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 -/// used to create both the constraint set and the generator set. -/// -/// `DeterministicGate`s do not automatically implement `Gate`; they should instead be wrapped in -/// `DeterministicGateAdapter`. -pub trait DeterministicGate: 'static { - /// A unique identifier for this gate. - fn id(&self) -> String; - - /// A vector of `(loc, out)` pairs, where `loc` is the location of an output and `out` is a - /// polynomial defining how that output is evaluated. - fn outputs(&self, config: CircuitConfig) -> OutputGraph; - - /// Any additional constraints to be enforced, besides the (automatically provided) ones that - /// constraint output values. - fn additional_constraints(&self, _config: CircuitConfig) -> Vec> { - Vec::new() - } - - /// Any additional generators, besides the (automatically provided) ones that generate output - /// values. - fn additional_generators( - &self, - _config: CircuitConfig, - _gate_index: usize, - local_constants: Vec, - next_constants: Vec, - ) -> Vec>> { - Vec::new() - } -} - -/// A wrapper around `DeterministicGate` which implements `Gate`. Note that a blanket implementation -/// is not possible in this context given Rust's coherence rules. -pub struct DeterministicGateAdapter + ?Sized> { - gate: Box, - _phantom: PhantomData, -} - -impl> DeterministicGateAdapter { - pub fn new(gate: DG) -> Self { - Self { gate: Box::new(gate), _phantom: PhantomData } - } -} - -impl> Gate for DeterministicGateAdapter { - fn id(&self) -> String { - self.gate.id() - } - - fn constraints(&self, config: CircuitConfig) -> Vec> { - // For each output, we add a constraint of the form `out - expression = 0`, - // then we append any additional constraints that the gate defines. - self.gate.outputs(config).outputs.into_iter() - .map(|(output_loc, out)| out - ConstraintPolynomial::from_gate_output(output_loc)) - .chain(self.gate.additional_constraints(config).into_iter()) - .collect() - } - - fn generators( - &self, - config: CircuitConfig, - gate_index: usize, - local_constants: Vec, - next_constants: Vec, - ) -> Vec>> { - self.gate.outputs(config).outputs - .into_iter() - .map(|(location, out)| { - let og = OutputGenerator { - gate_index, - location, - out, - local_constants: local_constants.clone(), - next_constants: next_constants.clone(), - }; - - // We need the type system to treat this as a boxed `WitnessGenerator2`, rather - // than a boxed `OutputGenerator`. - let b: Box::> = Box::new(og); - b - }) - .chain(self.gate.additional_generators( - config, gate_index, local_constants.clone(), next_constants.clone())) - .collect() - } -} - -#[derive(Debug)] -struct OutputGenerator { - gate_index: usize, - location: GateOutputLocation, - out: ConstraintPolynomial, - local_constants: Vec, - next_constants: Vec, -} - -impl SimpleGenerator for OutputGenerator { - fn dependencies(&self) -> Vec { - self.out.dependencies(self.gate_index) - .into_iter() - .map(Target::Wire) - .collect() - } - - fn run_once(&self, witness: &PartialWitness) -> PartialWitness { - let mut local_wire_values = Vec::new(); - let mut next_wire_values = Vec::new(); - - // Get an exclusive upper bound on the largest input index in this constraint. - let input_limit_exclusive = self.out.max_wire_input_index() - .map_or(0, |i| i + 1); - - for input in 0..input_limit_exclusive { - let local_wire = Wire { gate: self.gate_index, input }; - let next_wire = Wire { gate: self.gate_index + 1, input }; - - // 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 - // dependencies, and this generator would not have run yet.) - let local_value = witness.try_get_target(Target::Wire(local_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); - next_wire_values.push(next_value); - } - - let vars = EvaluationVars { - local_constants: &self.local_constants, - next_constants: &self.next_constants, - local_wires: &local_wire_values, - next_wires: &next_wire_values, - }; - - 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); - PartialWitness::singleton(Target::Wire(result_wire), result_value) - } -} diff --git a/src/gates/fri_consistency_gate.rs b/src/gates/fri_consistency_gate.rs index 7decc45d..6c9c8aaf 100644 --- a/src/gates/fri_consistency_gate.rs +++ b/src/gates/fri_consistency_gate.rs @@ -1,372 +1,372 @@ -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( - arity_bits: usize, - num_commits: usize, - max_path_bits: usize, - ) -> GateRef { - 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(&self, output_graph: &mut ExpandableOutputGraph) { - // 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(&self, output_graph: &mut ExpandableOutputGraph) { - 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(&self, commit_idx: usize) -> Vec> { - let coefficients = (0..self.arity()) - .map(|i| ConstraintPolynomial::local_wire(self.wire_coefficient(commit_idx, i))) - .collect::>>(); - 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( - &self, - coefficients: &[ConstraintPolynomial], - point: ConstraintPolynomial, - ) -> ConstraintPolynomial { - coefficients.iter() - .enumerate() - .map(|(i, coeff)| coeff * point.exp(i)) - .sum() - } -} - -impl DeterministicGate for FriConsistencyGate { - fn id(&self) -> String { - format!("{:?}", self) - } - - fn outputs(&self, _config: CircuitConfig) -> OutputGraph { - 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> { - 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::>(); - 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, - _next_constants: Vec, - ) -> Vec>> { - 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::>(); - 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 { - gate: FriConsistencyGate, - gate_index: usize, - generator_i: F, -} - -impl InterpolantGenerator { - /// 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 SimpleGenerator for InterpolantGenerator { - fn dependencies(&self) -> Vec { - 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) -> PartialWitness { - 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()); - } -} +// 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( +// arity_bits: usize, +// num_commits: usize, +// max_path_bits: usize, +// ) -> GateRef { +// 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(&self, output_graph: &mut ExpandableOutputGraph) { +// // 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(&self, output_graph: &mut ExpandableOutputGraph) { +// 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(&self, commit_idx: usize) -> Vec> { +// let coefficients = (0..self.arity()) +// .map(|i| ConstraintPolynomial::local_wire(self.wire_coefficient(commit_idx, i))) +// .collect::>>(); +// 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( +// &self, +// coefficients: &[ConstraintPolynomial], +// point: ConstraintPolynomial, +// ) -> ConstraintPolynomial { +// coefficients.iter() +// .enumerate() +// .map(|(i, coeff)| coeff * point.exp(i)) +// .sum() +// } +// } +// +// impl DeterministicGate for FriConsistencyGate { +// fn id(&self) -> String { +// format!("{:?}", self) +// } +// +// fn outputs(&self, _config: CircuitConfig) -> OutputGraph { +// 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> { +// 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::>(); +// 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, +// _next_constants: Vec, +// ) -> Vec>> { +// 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::>(); +// 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 { +// gate: FriConsistencyGate, +// gate_index: usize, +// generator_i: F, +// } +// +// impl InterpolantGenerator { +// /// 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 SimpleGenerator for InterpolantGenerator { +// fn dependencies(&self) -> Vec { +// 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) -> PartialWitness { +// 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()); +// } +// } diff --git a/src/gates/gate.rs b/src/gates/gate.rs index 9517d1fc..4c142b40 100644 --- a/src/gates/gate.rs +++ b/src/gates/gate.rs @@ -1,62 +1,48 @@ use std::hash::{Hash, Hasher}; use std::rc::Rc; -use crate::circuit_data::CircuitConfig; -use crate::constraint_polynomial::{ConstraintPolynomial, EvaluationVars}; -use crate::field::field::Field; -use crate::generator::WitnessGenerator; use num::ToPrimitive; +use crate::circuit_builder::CircuitBuilder; +use crate::constraint_polynomial::{EvaluationTargets, EvaluationVars}; +use crate::field::field::Field; +use crate::generator::WitnessGenerator; +use crate::target::Target; + /// A custom gate. -// TODO: Remove CircuitConfig params? Could just use fields within each struct. pub trait Gate: 'static { fn id(&self) -> String; - /// A set of expressions which must evaluate to zero. - fn constraints(&self, config: CircuitConfig) -> Vec>; + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec; - // fn eval_constraints(&self, config: CircuitConfig, vars: EvaluationVars) -> Vec { - // self.constraints(config) - // .into_iter() - // .map(|c| c.evaluate(vars)) - // .collect() - // } + fn eval_unfiltered_recursively( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec; + + fn eval_filtered(&self, vars: EvaluationVars) -> Vec { + // TODO: Filter + self.eval_unfiltered(vars) + } fn generators( &self, - config: CircuitConfig, gate_index: usize, - // TODO: Switch to slices? - local_constants: Vec, - next_constants: Vec, + local_constants: &[F], + next_constants: &[F], ) -> Vec>>; - /// The number of constants used by this gate. - fn num_constants(&self, config: CircuitConfig) -> usize { - self.constraints(config) - .into_iter() - .map(|c| c.max_constant_index().map_or(0, |i| i + 1)) - .max() - .unwrap_or(0) - } + /// The number of wires used by this gate. + fn num_wires(&self) -> usize; - /// The minimum number of wires required to use this gate. - fn min_wires(&self, config: CircuitConfig) -> usize { - self.constraints(config) - .into_iter() - .map(|c| c.max_wire_input_index().map_or(0, |i| i + 1)) - .max() - .unwrap_or(0) - } + /// The number of constants used by this gate. + fn num_constants(&self) -> usize; /// The maximum degree among this gate's constraint polynomials. - fn degree(&self, config: CircuitConfig) -> usize { - self.constraints(config) - .into_iter() - .map(|c| c.degree().to_usize().expect("degree too large")) - .max() - .unwrap_or(0) - } + fn degree(&self) -> usize; + + fn num_constraints(&self) -> usize; } /// A wrapper around an `Rc` which implements `PartialEq`, `Eq` and `Hash` based on gate IDs. diff --git a/src/gates/gmimc.rs b/src/gates/gmimc.rs index ba1a225a..6d833ba9 100644 --- a/src/gates/gmimc.rs +++ b/src/gates/gmimc.rs @@ -2,25 +2,31 @@ use std::sync::Arc; use num::{BigUint, One}; +use crate::circuit_builder::CircuitBuilder; use crate::circuit_data::CircuitConfig; -use crate::constraint_polynomial::ConstraintPolynomial; +use crate::constraint_polynomial::{EvaluationTargets, EvaluationVars}; use crate::field::field::Field; -use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter}; -use crate::gates::gate::GateRef; -use crate::gates::output_graph::{GateOutputLocation, OutputGraph}; +use crate::gates::gate::{Gate, GateRef}; +use crate::generator::{SimpleGenerator, WitnessGenerator}; +use crate::target::Target; +use crate::witness::PartialWitness; +use crate::wire::Wire; +use crate::gates::noop::NoopGate; -/// Evaluates a full GMiMC permutation, and writes the output to the next gate's first `width` -/// wires (which could be the input of another `GMiMCGate`). +/// The width of the permutation, in field elements. +const W: usize = 12; + +/// Evaluates a full GMiMC permutation with 12 state elements, and writes the output to the next +/// gate's first `width` wires (which could be the input of another `GMiMCGate`). #[derive(Debug)] -pub struct GMiMCGate { +pub struct GMiMCGate { constants: Arc<[F; R]>, } -impl GMiMCGate { +impl GMiMCGate { pub fn with_constants(constants: Arc<[F; R]>) -> GateRef { - let gate = GMiMCGate:: { constants }; - let adapter = DeterministicGateAdapter::new(gate); - GateRef::new(adapter) + let gate = GMiMCGate:: { constants }; + GateRef::new(gate) } pub fn with_automatic_constants() -> GateRef { @@ -42,79 +48,163 @@ impl GMiMCGate { i } - /// Adds a local wire output to this output graph. Returns a `ConstraintPolynomial` which - /// references the newly-created wire. - /// - /// 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, - poly: ConstraintPolynomial, - ) -> ConstraintPolynomial { - let index = outputs.max_local_output_index().map_or(W + 1, |i| i + 1); - outputs.add(GateOutputLocation::LocalWire(index), poly); - ConstraintPolynomial::local_wire(index) + /// A wire which stores the input to the `i`th cubing. + fn wire_cubing_input(i: usize) -> usize { + W + 1 + i } } -impl DeterministicGate for GMiMCGate { +impl Gate for GMiMCGate { fn id(&self) -> String { // TODO: This won't include generic params? format!("{:?}", self) } - fn outputs(&self, _config: CircuitConfig) -> OutputGraph { - let original_inputs = (0..W) - .map(|i| ConstraintPolynomial::local_wire(Self::wire_input(i))) + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let mut constraints = Vec::with_capacity(W + R); + + // Value that is implicitly added to each element. + // See https://affine.group/2020/02/starkware-challenge + let mut addition_buffer = F::ZERO; + + let switch = vars.local_wires[Self::WIRE_SWITCH]; + let mut state = Vec::with_capacity(12); + for i in 0..4 { + let a = vars.local_wires[i]; + let b = vars.local_wires[i + 4]; + state.push(a + switch * (b - a)); + } + for i in 0..4 { + let a = vars.local_wires[i + 4]; + let b = vars.local_wires[i]; + state.push(a + switch * (b - a)); + } + for i in 8..12 { + state.push(vars.local_wires[i]); + } + + for r in 0..R { + let active = r % W; + let cubing_input = state[active] + addition_buffer + self.constants[r]; + let cubing_input_wire = vars.local_wires[Self::wire_cubing_input(r)]; + constraints.push(cubing_input - cubing_input_wire); + let f = cubing_input_wire.cube(); + addition_buffer += f; + state[active] -= f; + } + + for i in 0..W { + state[i] += addition_buffer; + constraints.push(state[i] - vars.next_wires[i]); + } + + constraints + } + + fn eval_unfiltered_recursively( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec { + unimplemented!() + } + + fn generators( + &self, + gate_index: usize, + _local_constants: &[F], + _next_constants: &[F], + ) -> Vec>> { + let gen = GMiMCGenerator { + gate_index, + constants: self.constants.clone(), + }; + vec![Box::new(gen)] + } + + fn num_wires(&self) -> usize { + W + 1 + R + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 3 + } + + fn num_constraints(&self) -> usize { + R + W + } +} + +#[derive(Debug)] +struct GMiMCGenerator { + gate_index: usize, + constants: Arc<[F; R]>, +} + +impl SimpleGenerator for GMiMCGenerator { + fn dependencies(&self) -> Vec { + (0..W) + .map(|i| Target::Wire(Wire { + gate: self.gate_index, + input: GMiMCGate::::wire_input(i), + })) + .collect() + } + + fn run_once(&self, witness: &PartialWitness) -> PartialWitness { + let mut result = PartialWitness::new(); + + let mut state = (0..W) + .map(|i| witness.get_wire(Wire { + gate: self.gate_index, + input: GMiMCGate::::wire_input(i), + })) .collect::>(); - let mut outputs = OutputGraph::new(); - - // Conditionally switch inputs based on the (boolean) switch wire. - let switch = ConstraintPolynomial::local_wire(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()); + let switch_value = witness.get_wire(Wire { + gate: self.gate_index, + input: GMiMCGate::::WIRE_SWITCH, + }); + debug_assert!(switch_value == F::ZERO || switch_value == F::ONE); + if switch_value == F::ONE { + for i in 0..4 { + state.swap(i, 4 + i); + } } // Value that is implicitly added to each element. // See https://affine.group/2020/02/starkware-challenge - let mut addition_buffer = ConstraintPolynomial::zero(); + let mut addition_buffer = F::ZERO; for r in 0..R { let active = r % W; - let round_constant = ConstraintPolynomial::constant(self.constants[r]); - let mut f_input = &state[active] + &addition_buffer + round_constant; - if f_input.degree() > BigUint::one() { - f_input = Self::add_local(&mut outputs, f_input); - } - let f_output = f_input.cube(); - addition_buffer += &f_output; - state[active] -= f_output; + let cubing_input = state[active] + addition_buffer + self.constants[r]; + result.set_wire( + Wire { + gate: self.gate_index, + input: GMiMCGate::::wire_cubing_input(r), + }, + cubing_input); + let f = cubing_input.cube(); + addition_buffer += f; + state[active] -= f; } for i in 0..W { - outputs.add(GateOutputLocation::NextWire(i), &state[i] + &addition_buffer); + state[i] += addition_buffer; + result.set_wire( + Wire { + gate: self.gate_index + 1, + input: GMiMCGate::::wire_output(i), + }, + state[i]); } - outputs - } - - fn additional_constraints(&self, _config: CircuitConfig) -> Vec> { - let switch = ConstraintPolynomial::local_wire(Self::WIRE_SWITCH); - let switch_bool_constraint = &switch * (&switch - 1); - vec![switch_bool_constraint] + result } } @@ -127,40 +217,24 @@ mod tests { use crate::field::crandall_field::CrandallField; use crate::field::field::Field; use crate::gates::deterministic_gate::DeterministicGate; - use crate::gates::gmimc::GMiMCGate; + use crate::gates::gmimc::{GMiMCGate, W}; use crate::generator::generate_partial_witness; use crate::gmimc::gmimc_permute_naive; use crate::wire::Wire; use crate::witness::PartialWitness; - #[test] - fn degree() { - type F = CrandallField; - const W: usize = 12; - const R: usize = 101; - let gate = GMiMCGate:: { constants: Arc::new([F::TWO; R]) }; - let config = CircuitConfig { - num_wires: 200, - num_routed_wires: 200, - security_bits: 128, - }; - let outs = gate.outputs(config); - assert_eq!(outs.degree(), 3); - } - #[test] fn generated_output() { type F = CrandallField; - const W: usize = 12; const R: usize = 101; let constants = Arc::new([F::TWO; R]); - type Gate = GMiMCGate::; + type Gate = GMiMCGate::; let gate = Gate::with_constants(constants.clone()); let config = CircuitConfig { num_wires: 200, num_routed_wires: 200, - security_bits: 128, + ..Default::default() }; let permutation_inputs = (0..W) @@ -175,7 +249,7 @@ mod tests { permutation_inputs[i]); } - let generators = gate.0.generators(config, 0, vec![], vec![]); + let generators = gate.0.generators(0, &[], &[]); generate_partial_witness(&mut witness, &generators); let expected_outputs: [F; W] = gmimc_permute_naive( diff --git a/src/gates/gmimc_eval.rs b/src/gates/gmimc_eval.rs new file mode 100644 index 00000000..c726686e --- /dev/null +++ b/src/gates/gmimc_eval.rs @@ -0,0 +1,81 @@ +use crate::circuit_builder::CircuitBuilder; +use crate::constraint_polynomial::{EvaluationTargets, EvaluationVars}; +use crate::field::field::Field; +use crate::gates::gate::{Gate, GateRef}; +use crate::generator::{SimpleGenerator, WitnessGenerator}; +use crate::target::Target; +use crate::witness::PartialWitness; + +/// Performs some arithmetic involved in the evaluation of GMiMC's constraint polynomials for one +/// round. +#[derive(Debug)] +pub struct GMiMCEvalGate; + +impl GMiMCEvalGate { + pub fn get() -> GateRef { + GateRef::new(GMiMCEvalGate) + } +} + +impl Gate for GMiMCEvalGate { + fn id(&self) -> String { + format!("{:?}", self) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + todo!() + } + + fn eval_unfiltered_recursively( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec { + unimplemented!() + } + + fn generators( + &self, + gate_index: usize, + local_constants: &[F], + _next_constants: &[F], + ) -> Vec>> { + let gen = GMiMCEvalGenerator:: { + gate_index, + constant: local_constants[0], + }; + vec![Box::new(gen)] + } + + fn num_wires(&self) -> usize { + 6 + } + + fn num_constants(&self) -> usize { + 1 + } + + fn degree(&self) -> usize { + 3 + } + + fn num_constraints(&self) -> usize { + unimplemented!() + } +} + +#[derive(Debug)] +struct GMiMCEvalGenerator { + gate_index: usize, + constant: F, +} + +impl SimpleGenerator for GMiMCEvalGenerator { + fn dependencies(&self) -> Vec { + todo!() + } + + fn run_once(&self, witness: &PartialWitness) -> PartialWitness { + todo!() + } +} diff --git a/src/gates/mod.rs b/src/gates/mod.rs index 48c6ce38..82063dfc 100644 --- a/src/gates/mod.rs +++ b/src/gates/mod.rs @@ -1,7 +1,6 @@ pub(crate) mod constant; -pub(crate) mod deterministic_gate; pub(crate) mod fri_consistency_gate; pub(crate) mod gate; pub(crate) mod gmimc; +pub(crate) mod gmimc_eval; pub(crate) mod noop; -pub(crate) mod output_graph; diff --git a/src/gates/noop.rs b/src/gates/noop.rs index 99b87b97..1aa54317 100644 --- a/src/gates/noop.rs +++ b/src/gates/noop.rs @@ -1,9 +1,10 @@ use crate::circuit_data::CircuitConfig; -use crate::constraint_polynomial::ConstraintPolynomial; +use crate::constraint_polynomial::{EvaluationVars, EvaluationTargets}; use crate::field::field::Field; -use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter}; use crate::gates::gate::{Gate, GateRef}; use crate::generator::WitnessGenerator; +use crate::target::Target; +use crate::circuit_builder::CircuitBuilder; /// A gate which takes a single constant parameter and outputs that value. pub struct NoopGate; @@ -19,17 +20,40 @@ impl Gate for NoopGate { "NoopGate".into() } - fn constraints(&self, _config: CircuitConfig) -> Vec> { + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + Vec::new() + } + + fn eval_unfiltered_recursively( + &self, + _builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec { Vec::new() } fn generators( &self, - _config: CircuitConfig, - _gate_index: usize, - _local_constants: Vec, - _next_constants: Vec + gate_index: usize, + local_constants: &[F], + next_constants: &[F], ) -> Vec>> { Vec::new() } + + fn num_wires(&self) -> usize { + 0 + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 0 + } + + fn num_constraints(&self) -> usize { + 0 + } } diff --git a/src/gates/output_graph.rs b/src/gates/output_graph.rs deleted file mode 100644 index 75cdcd35..00000000 --- a/src/gates/output_graph.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::fmt; -use std::fmt::{Display, Formatter}; - -use num::ToPrimitive; - -use crate::constraint_polynomial::ConstraintPolynomial; -use crate::field::field::Field; - -/// Represents a set of deterministic gate outputs, expressed as polynomials over witness -/// values. -#[derive(Clone, Debug)] -pub struct OutputGraph { - pub(crate) outputs: Vec<(GateOutputLocation, ConstraintPolynomial)> -} - -impl OutputGraph { - /// Creates a new output graph with no outputs. - pub fn new() -> Self { - Self { outputs: Vec::new() } - } - - /// Creates an output graph with a single output. - pub fn single_output(loc: GateOutputLocation, out: ConstraintPolynomial) -> Self { - Self { outputs: vec![(loc, out)] } - } - - pub fn add(&mut self, location: GateOutputLocation, poly: ConstraintPolynomial) { - self.outputs.push((location, poly)) - } - - /// 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()) - .max() - .unwrap_or(0) - } - - /// The largest local wire index among this graph's output locations. - pub fn max_local_output_index(&self) -> Option { - self.outputs.iter() - .filter_map(|(loc, _out)| match loc { - GateOutputLocation::LocalWire(i) => Some(*i), - GateOutputLocation::NextWire(_) => None - }) - .max() - } -} - -impl Display for OutputGraph { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - for (loc, out) in &self.outputs { - write!(f, "{} := {}, ", loc, out)?; - } - Ok(()) - } -} - -/// Represents an output location of a deterministic gate. -#[derive(Copy, Clone, Debug)] -pub enum GateOutputLocation { - /// A wire belonging to the gate itself. - LocalWire(usize), - /// A wire belonging to the following gate. - NextWire(usize), -} - -impl Display for GateOutputLocation { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - GateOutputLocation::LocalWire(i) => write!(f, "local_wire_{}", i), - GateOutputLocation::NextWire(i) => write!(f, "next_wire_{}", i), - } - } -} - -/// 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 { - pub(crate) output_graph: OutputGraph, - next_unused_index: usize, -} - -impl ExpandableOutputGraph { - 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) -> ConstraintPolynomial { - let index = self.next_unused_index; - self.next_unused_index += 1; - - self.output_graph.add(GateOutputLocation::LocalWire(index), poly); - ConstraintPolynomial::local_wire(index) - } -} diff --git a/src/generator.rs b/src/generator.rs index 4aae83a4..b2c4ca31 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -79,6 +79,7 @@ pub trait WitnessGenerator: 'static + Debug { } /// A generator which runs once after a list of dependencies is present in the witness. +// TODO: Remove Debug. Here temporarily to debug generator issues. pub trait SimpleGenerator: 'static + Debug { fn dependencies(&self) -> Vec; diff --git a/src/hash.rs b/src/hash.rs index 57b5c6bc..1b23c0af 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -8,7 +8,7 @@ use rayon::prelude::*; use crate::field::field::Field; use crate::gmimc::{gmimc_compress, gmimc_permute_array}; use crate::proof::Hash; -use crate::util::{reverse_index_bits, transpose}; +use crate::util::{reverse_index_bits, transpose, reverse_index_bits_in_place}; const RATE: usize = 8; const CAPACITY: usize = 4; @@ -80,11 +80,9 @@ pub fn hash_n_to_1(inputs: Vec, pad: bool) -> F { } /// Like `merkle_root`, but first reorders each vector so that `new[i] = old[i.reverse_bits()]`. -pub(crate) fn merkle_root_bit_rev_order(vecs: Vec>) -> Hash { - let vecs_reordered = vecs.into_par_iter() - .map(reverse_index_bits) - .collect(); - merkle_root(vecs_reordered) +pub(crate) fn merkle_root_bit_rev_order(mut vecs: Vec>) -> Hash { + reverse_index_bits_in_place(&mut vecs); + merkle_root(vecs) } /// Given `n` vectors, each of length `l`, constructs a Merkle tree with `l` leaves, where each leaf @@ -92,8 +90,7 @@ pub(crate) fn merkle_root_bit_rev_order(vecs: Vec>) -> Hash /// is skipped, as there is no need to compress leaf data. pub(crate) fn merkle_root(vecs: Vec>) -> Hash { // TODO: Parallelize. - let mut vecs_t = transpose(&vecs); - let mut hashes = vecs_t.into_iter() + let mut hashes = vecs.into_iter() .map(|leaf_set| hash_or_noop(leaf_set)) .collect::>(); while hashes.len() > 1 { diff --git a/src/main.rs b/src/main.rs index c3e600f7..543897cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,11 +14,11 @@ use crate::util::log2_ceil; use crate::circuit_builder::CircuitBuilder; use crate::circuit_data::CircuitConfig; use crate::witness::PartialWitness; -use crate::gates::fri_consistency_gate::FriConsistencyGate; use env_logger::Env; use crate::gates::gmimc::GMiMCGate; use std::sync::Arc; use std::convert::TryInto; +use crate::gates::constant::ConstantGate; mod circuit_builder; mod circuit_data; @@ -29,6 +29,7 @@ mod gadgets; mod gates; mod generator; mod gmimc; +mod plonk_common; mod proof; mod prover; mod recursive_verifier; @@ -69,7 +70,7 @@ fn bench_prove() { for i in 0..GMIMC_ROUNDS { gmimc_constants[i] = F::from_canonical_u64(GMIMC_CONSTANTS[i]); } - let gmimc_gate = GMiMCGate::::with_constants( + let gmimc_gate = GMiMCGate::::with_constants( Arc::new(gmimc_constants)); let config = CircuitConfig { @@ -77,6 +78,7 @@ fn bench_prove() { num_routed_wires: 12, security_bits: 128, rate_bits: 3, + num_checks: 3, }; let mut builder = CircuitBuilder::::new(config); @@ -85,11 +87,13 @@ fn bench_prove() { builder.add_gate_no_constants(gmimc_gate.clone()); } - for _ in 0..(40 * 5) { - builder.add_gate( - FriConsistencyGate::new(2, 3, 13), - vec![F::primitive_root_of_unity(13)]); - } + builder.add_gate(ConstantGate::get(), vec![F::NEG_ONE]); + + // for _ in 0..(40 * 5) { + // builder.add_gate( + // FriConsistencyGate::new(2, 3, 13), + // vec![F::primitive_root_of_unity(13)]); + // } let prover = builder.build_prover(); let inputs = PartialWitness::new(); diff --git a/src/plonk_common.rs b/src/plonk_common.rs new file mode 100644 index 00000000..1852bfd2 --- /dev/null +++ b/src/plonk_common.rs @@ -0,0 +1,19 @@ +use crate::field::field::Field; +use crate::target::Target; +use crate::circuit_builder::CircuitBuilder; + +pub(crate) fn reduce_with_powers(terms: Vec, alpha: F) -> F { + let mut sum = F::ZERO; + for &term in terms.iter().rev() { + sum = sum * alpha + term; + } + sum +} + +pub(crate) fn reduce_with_powers_recursive( + builder: &mut CircuitBuilder, + terms: Vec, + alpha: Target, +) -> Target { + todo!() +} diff --git a/src/prover.rs b/src/prover.rs index 76f537ef..7187fc91 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -1,17 +1,20 @@ +use std::env::var; use std::time::Instant; use log::info; +use rayon::prelude::*; -use crate::circuit_data::{CommonCircuitData, ProverOnlyCircuitData}; -use crate::field::fft::{fft, ifft, lde}; +use crate::circuit_data::{CommonCircuitData, ProverCircuitData, ProverOnlyCircuitData}; +use crate::constraint_polynomial::EvaluationVars; +use crate::field::fft::{fft, ifft, lde, lde_multiple}; use crate::field::field::Field; use crate::generator::generate_partial_witness; use crate::hash::{compress, hash_n_to_hash, hash_n_to_m, hash_or_noop, merkle_root_bit_rev_order}; +use crate::plonk_common::reduce_with_powers; use crate::proof::{Hash, Proof2}; -use crate::util::{log2_ceil, reverse_index_bits}; +use crate::util::{log2_ceil, reverse_index_bits, transpose}; use crate::wire::Wire; use crate::witness::PartialWitness; -use rayon::prelude::*; pub(crate) fn prove( prover_data: &ProverOnlyCircuitData, @@ -31,21 +34,33 @@ pub(crate) fn prove( // TODO: Simplify using lde_multiple. // TODO: Parallelize. let wire_ldes = (0..num_wires) - .map(|i| compute_wire_lde(i, &witness, common_data.degree, config.rate_bits)) + .map(|i| compute_wire_lde(i, &witness, common_data.degree(), config.rate_bits)) .collect::>(); info!("Computing wire LDEs took {}s", start_wire_ldes.elapsed().as_secs_f32()); let start_wires_root = Instant::now(); - let wires_root = merkle_root_bit_rev_order(wire_ldes); + let wire_ldes_t = transpose(&wire_ldes); + // TODO: Could avoid cloning if it's significant? + let wires_root = merkle_root_bit_rev_order(wire_ldes_t.clone()); info!("Merklizing wire LDEs took {}s", start_wires_root.elapsed().as_secs_f32()); - let plonk_z_vecs = todo!(); - let plonk_z_ldes = todo!(); - let plonk_z_root = merkle_root_bit_rev_order(plonk_z_ldes); + let plonk_z_vecs = compute_zs(&common_data); + let plonk_z_ldes = lde_multiple(plonk_z_vecs, config.rate_bits); + let plonk_z_ldes_t = transpose(&plonk_z_ldes); + let plonk_z_root = merkle_root_bit_rev_order(plonk_z_ldes_t.clone()); - let plonk_t_vecs = todo!(); - let plonk_t_ldes = todo!(); - let plonk_t_root = merkle_root_bit_rev_order(plonk_t_ldes); + let alpha = F::ZERO; // TODO + + let start_vanishing_poly = Instant::now(); + let vanishing_poly = compute_vanishing_poly( + common_data, prover_data, wire_ldes_t, plonk_z_ldes_t, alpha); + info!("Computing vanishing poly took {}s", start_vanishing_poly.elapsed().as_secs_f32()); + + let plonk_t: Vec = todo!(); // vanishing_poly / Z_H + // Need to convert to coeff form and back? + let plonk_t_parts = todo!(); + let plonk_t_ldes = lde_multiple(plonk_t_parts, config.rate_bits); + let plonk_t_root = merkle_root_bit_rev_order(transpose(&plonk_t_ldes)); let openings = todo!(); @@ -57,6 +72,70 @@ pub(crate) fn prove( } } +fn compute_zs(common_data: &CommonCircuitData) -> Vec> { + (0..common_data.config.num_checks) + .map(|i| compute_z(common_data, i)) + .collect() +} + +fn compute_z(common_data: &CommonCircuitData, i: usize) -> Vec { + vec![F::ZERO; common_data.degree()] // TODO +} + +fn compute_vanishing_poly( + common_data: &CommonCircuitData, + prover_data: &ProverOnlyCircuitData, + wire_ldes_t: Vec>, + plonk_z_lde_t: Vec>, + alpha: F, +) -> Vec { + let lde_size = common_data.lde_size(); + let lde_gen = common_data.lde_generator(); + + let mut result = Vec::with_capacity(lde_size); + let mut point = F::ONE; + for i in 0..lde_size { + debug_assert!(point != F::ONE); + + let i_next = (i + 1) % lde_size; + let local_wires = &wire_ldes_t[i]; + let next_wires = &wire_ldes_t[i_next]; + let local_constants = &prover_data.constant_ldes_t[i]; + let next_constants = &prover_data.constant_ldes_t[i_next]; + let local_plonk_zs = &plonk_z_lde_t[i]; + let next_plonk_zs = &plonk_z_lde_t[i_next]; + + debug_assert_eq!(local_wires.len(), common_data.config.num_wires); + debug_assert_eq!(local_plonk_zs.len(), common_data.config.num_checks); + + let vars = EvaluationVars { + local_constants, + next_constants, + local_wires, + next_wires, + }; + result.push(compute_vanishing_poly_entry( + common_data, vars, local_plonk_zs, next_plonk_zs, alpha)); + + point *= lde_gen; + } + debug_assert_eq!(point, F::ONE); + result +} + +fn compute_vanishing_poly_entry( + common_data: &CommonCircuitData, + vars: EvaluationVars, + local_plonk_zs: &[F], + next_plonk_zs: &[F], + alpha: F, +) -> F { + let mut constraints = Vec::with_capacity(common_data.total_constraints()); + // TODO: Add Z constraints. + constraints.extend(common_data.evaluate(vars)); + reduce_with_powers(constraints, alpha) +} + fn compute_wire_lde( input: usize, witness: &PartialWitness, diff --git a/src/recursive_verifier.rs b/src/recursive_verifier.rs index 6d27514e..8641048e 100644 --- a/src/recursive_verifier.rs +++ b/src/recursive_verifier.rs @@ -1,5 +1,10 @@ use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; +const MIN_WIRES: usize = 120; // TODO: Double check. +const MIN_ROUTED_WIRES: usize = 12; // TODO: Double check. + pub fn add_recursive_verifier(builder: &mut CircuitBuilder) { + assert!(builder.config.num_wires >= MIN_WIRES); + assert!(builder.config.num_wires >= MIN_ROUTED_WIRES); } diff --git a/src/util.rs b/src/util.rs index 046bf55f..a6111805 100644 --- a/src/util.rs +++ b/src/util.rs @@ -30,17 +30,29 @@ pub(crate) fn transpose(matrix: &[Vec]) -> Vec> { } /// Permutes `arr` such that each index is mapped to its reverse in binary. -pub(crate) fn reverse_index_bits(arr: Vec) -> Vec { +pub(crate) fn reverse_index_bits(arr: Vec) -> Vec { let n = arr.len(); let n_power = log2_strict(n); let mut result = Vec::with_capacity(n); for i in 0..n { - result.push(arr[reverse_bits(i, n_power)]); + result.push(arr[reverse_bits(i, n_power)].clone()); } result } +pub(crate) fn reverse_index_bits_in_place(arr: &mut Vec) { + let n = arr.len(); + let n_power = log2_strict(n); + + for src in 0..n { + let dst = reverse_bits(src, n_power); + if src < dst { + arr.swap(src, dst); + } + } +} + fn reverse_bits(n: usize, num_bits: usize) -> usize { let mut result = 0; for i in 0..num_bits {