From 995e4c1c7ce6cd1d79fee78257f8e604ce6372b3 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 9 Nov 2020 17:10:00 -0800 Subject: [PATCH] DeterministicGate etc. --- src/gates2/arithmetic_gate.rs | 102 +++++++++++++++++++--------- src/gates2/mod.rs | 100 +++++++++++++++++++++++---- src/plonk2/constraint_polynomial.rs | 71 +++++++++++++------ src/plonk2/generator.rs | 2 +- src/plonk2/mod.rs | 16 ++++- src/plonk2/witness.rs | 8 +++ 6 files changed, 230 insertions(+), 69 deletions(-) diff --git a/src/gates2/arithmetic_gate.rs b/src/gates2/arithmetic_gate.rs index 7c94b65..a58f241 100644 --- a/src/gates2/arithmetic_gate.rs +++ b/src/gates2/arithmetic_gate.rs @@ -1,56 +1,96 @@ -use std::marker::PhantomData; use std::rc::Rc; use crate::{CircuitBuilder2, ConstraintPolynomial, DeterministicGate, Field, Gate2, GateInstance, Target2}; -pub const ID: &'static str = "ARITHMETIC"; -pub const CONST_PRODUCT_WEIGHT: usize = 0; -pub const CONST_ADDEND_WEIGHT: usize = 1; -pub const WIRE_MULTIPLICAND_0: usize = 0; -pub const WIRE_MULTIPLICAND_1: usize = 1; -pub const WIRE_ADDEND: usize = 2; -pub const WIRE_OUTPUT: usize = 3; - /// A gate which can be configured to perform various arithmetic. In particular, it computes /// /// ```text -/// output := const_product_weight * multiplicand_0 * multiplicand_1 -/// + const_addend_weight * addend +/// output := product_weight * multiplicand_0 * multiplicand_1 +/// + addend_weight * addend /// ``` -struct ArithmeticGate { - _phantom: PhantomData +/// +/// where `product_weight` and `addend_weight` are constants, and the other variables are wires. +struct ArithmeticGate2 { +} + +impl ArithmeticGate2 { + pub const ID: &'static str = "ArithmeticGate"; + pub const CONST_PRODUCT_WEIGHT: usize = 0; + pub const CONST_ADDEND_WEIGHT: usize = 1; + pub const WIRE_MULTIPLICAND_0: usize = 0; + pub const WIRE_MULTIPLICAND_1: usize = 1; + pub const WIRE_ADDEND: usize = 2; + pub const WIRE_OUTPUT: usize = 3; + + /// Computes `x y + z`. + pub fn mul_add( + builder: &mut CircuitBuilder2, + x: Target2, + y: Target2, + z: Target2, + ) -> Target2 { + let gate_type = gate_type(); + let constants = vec![F::ONE, F::ONE]; + let gate = builder.add_gate(GateInstance { gate_type, constants }); + + builder.copy(x, Target2::wire(gate, Self::WIRE_MULTIPLICAND_0)); + builder.copy(y, Target2::wire(gate, Self::WIRE_MULTIPLICAND_1)); + builder.copy(z, Target2::wire(gate, Self::WIRE_ADDEND)); + + Target2::wire(gate, Self::WIRE_OUTPUT) + } + + /// Computes `x y`. + pub fn mul( + builder: &mut CircuitBuilder2, + x: Target2, + y: Target2, + ) -> Target2 { + let zero = builder.zero(); + Self::mul_add(builder, x, y, zero) + } + + /// Computes `x + y`. + pub fn add( + builder: &mut CircuitBuilder2, + x: Target2, + y: Target2, + ) -> Target2 { + let one = builder.one(); + Self::mul_add(builder, x, one, y) + } } -impl DeterministicGate for ArithmeticGate { +impl DeterministicGate for ArithmeticGate2 { fn id(&self) -> String { "ArithmeticGate".into() } fn outputs(&self) -> Vec<(usize, ConstraintPolynomial)> { - let const_0 = ConstraintPolynomial::local_constant(CONST_PRODUCT_WEIGHT); - let const_1 = ConstraintPolynomial::local_constant(CONST_ADDEND_WEIGHT); - let multiplicand_0 = ConstraintPolynomial::local_wire_value(WIRE_MULTIPLICAND_0); - let multiplicand_1 = ConstraintPolynomial::local_wire_value(WIRE_MULTIPLICAND_1); - let addend = ConstraintPolynomial::local_wire_value(WIRE_ADDEND); + let const_0 = ConstraintPolynomial::local_constant(Self::CONST_PRODUCT_WEIGHT); + let const_1 = ConstraintPolynomial::local_constant(Self::CONST_ADDEND_WEIGHT); + let multiplicand_0 = ConstraintPolynomial::local_wire_value(Self::WIRE_MULTIPLICAND_0); + let multiplicand_1 = ConstraintPolynomial::local_wire_value(Self::WIRE_MULTIPLICAND_1); + let addend = ConstraintPolynomial::local_wire_value(Self::WIRE_ADDEND); let out = const_0 * multiplicand_0 * &multiplicand_1 + const_1 * &addend; - vec![(WIRE_OUTPUT, out)] + vec![(Self::WIRE_OUTPUT, out)] } } -fn gate_type() -> Rc> { +fn gate_type() -> Rc> { todo!() } -fn add(builder: &mut CircuitBuilder2, x: Target2, y: Target2) -> Target2 { - let gate_type = gate_type(); - let constants = vec![F::ONE, F::ONE]; - let gate = builder.add_gate(GateInstance { gate_type, constants }); - let one = builder.one(); - - builder.copy(x, Target2::wire(gate, WIRE_MULTIPLICAND_0)); - builder.copy(one, Target2::wire(gate, WIRE_MULTIPLICAND_1)); - builder.copy(y, Target2::wire(gate, WIRE_ADDEND)); +#[cfg(test)] +mod tests { + use crate::{CircuitBuilder2, TweedledumBase}; + use crate::gates2::arithmetic_gate::ArithmeticGate2; - Target2::wire(gate, WIRE_OUTPUT) + fn add() { + let mut builder = CircuitBuilder2::::new(); + let one = builder.one(); + let two = builder.two(); + let sum = ArithmeticGate2::add(&mut builder, one, one); + } } diff --git a/src/gates2/mod.rs b/src/gates2/mod.rs index 6ead792..2548426 100644 --- a/src/gates2/mod.rs +++ b/src/gates2/mod.rs @@ -1,6 +1,7 @@ use std::rc::Rc; -use crate::{Field, ConstraintPolynomial, WitnessGenerator2, Target2, PartialWitness2}; +use crate::{Field, ConstraintPolynomial, WitnessGenerator2, Target2, PartialWitness2, EvaluationVars, SimpleGenerator, Wire}; +use std::iter; pub mod arithmetic_gate; @@ -9,7 +10,16 @@ pub trait Gate2 { fn constraints(&self) -> Vec>; - fn generators(&self, index: usize) -> Vec>>; + fn generators( + &self, + gate_index: usize, + local_constants: Vec, + next_constants: Vec, + ) -> Vec>>; + + fn max_constant_index(&self) -> Option; + + fn max_wire_input_index(&self) -> Option; } /// A deterministic gate. Each entry in `outputs()` describes how that output is evaluated; this is @@ -31,32 +41,96 @@ impl Gate2 for dyn DeterministicGate { .collect() } - fn generators(&self, index: usize) -> Vec>> { + fn generators( + &self, + gate_index: usize, + local_constants: Vec, + next_constants: Vec, + ) -> Vec>> { struct OutputGenerator { - i: usize, + gate_index: usize, + input_index: usize, out: ConstraintPolynomial, + local_constants: Vec, + next_constants: Vec, } - impl WitnessGenerator2 for OutputGenerator { - fn watch_list(&self) -> Vec> { - todo!() + impl SimpleGenerator for OutputGenerator { + fn dependencies(&self) -> Vec> { + self.out.dependencies(self.gate_index) + .into_iter() + .map(Target2::Wire) + .collect() } - fn run(&mut self, witness: &PartialWitness2) -> (PartialWitness2, bool) { - // ConstraintPolynomial::evaluate_all(&self.outputs) - todo!() + fn run_once(&mut self, witness: &PartialWitness2) -> PartialWitness2 { + 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(Target2::Wire(local_wire)).unwrap_or(F::ZERO); + let next_value = witness.try_get(Target2::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_wire_values: &local_wire_values, + next_wire_values: &next_wire_values, + }; + + let result_wire = Wire { gate: self.gate_index, input: self.input_index }; + let result_value = self.out.evaluate(vars); + let mut witness = PartialWitness2::new(); + witness.set(Target2::Wire(result_wire), result_value); + witness } } self.outputs() .into_iter() - .map(|(i, out)| { - let b: Box:: + 'static> = - Box::new(OutputGenerator { i, out }); + .map(|(input_index, out)| { + let og = OutputGenerator { + gate_index, + input_index, + out, + local_constants: local_constants.clone(), + next_constants: next_constants.clone(), + }; + let b: Box:: + 'static> = Box::new(og); b }) .collect() } + + fn max_constant_index(&self) -> Option { + self.outputs().into_iter() + .map(|(i, out)| out.max_constant_index()) + .filter_map(|out_max| out_max) + .max() + } + + fn max_wire_input_index(&self) -> Option { + self.outputs().into_iter() + // For each output, we consider both the output wire and the wires it depends on. + .flat_map(|(i, out)| out.max_wire_input_index() + .into_iter() + .chain(iter::once(i))) + .max() + } } pub struct GateInstance { diff --git a/src/plonk2/constraint_polynomial.rs b/src/plonk2/constraint_polynomial.rs index aa3a02e..a4cb4b7 100644 --- a/src/plonk2/constraint_polynomial.rs +++ b/src/plonk2/constraint_polynomial.rs @@ -1,15 +1,15 @@ use std::hash::{Hash, Hasher}; use std::ptr; use std::rc::Rc; -use crate::Field; +use crate::{Field, Wire}; use std::collections::HashMap; -use std::ops::{Add, Sub, Mul}; +use std::ops::{Add, Sub, Mul, Neg}; -struct EvaluationVars<'a, F: Field> { - local_constants: &'a [F], - next_constants: &'a [F], - local_wire_values: &'a [F], - next_wire_values: &'a [F], +pub(crate) struct EvaluationVars<'a, F: Field> { + pub(crate) local_constants: &'a [F], + pub(crate) next_constants: &'a [F], + pub(crate) local_wire_values: &'a [F], + pub(crate) next_wire_values: &'a [F], } /// A polynomial over all the variables that are subject to constraints (local constants, next @@ -43,10 +43,6 @@ impl ConstraintPolynomial { Self::from_inner(ConstraintPolynomialInner::NextWireValue(index)) } - fn neg(self) -> Self { - todo!() - } - fn add(self, rhs: Self) -> Self { Self::from_inner(ConstraintPolynomialInner::Sum { lhs: self.0, @@ -76,20 +72,35 @@ impl ConstraintPolynomial { (self.0).0.degree() } + /// Returns the set of wires that this constraint would depend on if it were applied at `index`. + pub(crate) fn dependencies(&self, index: usize) -> Vec { + todo!() + } + + /// 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 { + todo!() + } + + 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], - local_constants: &[F], - next_constants: &[F], - local_wire_values: &[F], - next_wire_values: &[F], + vars: EvaluationVars, ) -> Vec { - let vars = EvaluationVars { - local_constants, - next_constants, - local_wire_values, - next_wire_values, - }; - let mut mem = HashMap::new(); polynomials.iter() .map(|p| p.0.evaluate_memoized(&vars, &mut mem)) @@ -101,6 +112,22 @@ impl ConstraintPolynomial { } } +impl Neg for ConstraintPolynomial { + type Output = Self; + + fn neg(self) -> Self { + 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` diff --git a/src/plonk2/generator.rs b/src/plonk2/generator.rs index eedd858..e6841d0 100644 --- a/src/plonk2/generator.rs +++ b/src/plonk2/generator.rs @@ -20,7 +20,7 @@ pub trait SimpleGenerator { fn run_once(&mut self, witness: &PartialWitness2) -> PartialWitness2; } -impl WitnessGenerator2 for dyn SimpleGenerator { +impl> WitnessGenerator2 for SG { fn watch_list(&self) -> Vec> { self.dependencies() } diff --git a/src/plonk2/mod.rs b/src/plonk2/mod.rs index 2800229..791e5b8 100644 --- a/src/plonk2/mod.rs +++ b/src/plonk2/mod.rs @@ -26,6 +26,13 @@ pub struct CircuitBuilder2 { } impl CircuitBuilder2 { + pub fn new() -> Self { + CircuitBuilder2 { + gates: Vec::new(), + gate_instances: Vec::new(), + } + } + /// Adds a gate to the circuit, and returns its index. pub fn add_gate(&mut self, gate_instance: GateInstance) -> usize { let index = self.gate_instances.len(); @@ -52,16 +59,21 @@ impl CircuitBuilder2 { assert!(y.is_routable()); } - /// Returns a routable target with a value of zero. + /// Returns a routable target with a value of 0. pub fn zero(&mut self) -> Target2 { self.constant(F::ZERO) } - /// Returns a routable target with a value of one. + /// Returns a routable target with a value of 1. pub fn one(&mut self) -> Target2 { self.constant(F::ONE) } + /// Returns a routable target with a value of 2. + pub fn two(&mut self) -> Target2 { + self.constant(F::TWO) + } + /// Returns a routable target with a value of `ORDER - 1`. pub fn neg_one(&mut self) -> Target2 { self.constant(F::NEG_ONE) diff --git a/src/plonk2/witness.rs b/src/plonk2/witness.rs index cb39c28..26a63a8 100644 --- a/src/plonk2/witness.rs +++ b/src/plonk2/witness.rs @@ -18,6 +18,10 @@ impl PartialWitness2 { self.wire_values.is_empty() } + pub fn try_get(&self, target: Target2) -> Option { + self.wire_values.get(&target).cloned() + } + pub fn contains(&self, target: Target2) -> bool { self.wire_values.contains_key(&target) } @@ -25,4 +29,8 @@ impl PartialWitness2 { pub fn contains_all(&self, targets: &[Target2]) -> bool { targets.iter().all(|&t| self.contains(t)) } + + pub fn set(&mut self, target: Target2, value: F) { + self.wire_values.insert(target, value); + } }