From cf5542319530cbd59262548803d846d254cba74e Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 9 Oct 2020 15:25:15 -0700 Subject: [PATCH] Overhaul --- Cargo.toml | 2 +- src/lib.rs | 4 + src/plonk2/constraint_polynomial.rs | 294 ++++++++++++++++++++++++++++ src/plonk2/gate.rs | 0 src/plonk2/generator.rs | 35 ++++ src/plonk2/mod.rs | 99 ++++++++++ src/plonk2/partitions.rs | 49 +++++ src/plonk2/prover.rs | 3 + src/plonk2/verifier.rs | 3 + src/plonk2/witness.rs | 28 +++ 10 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 src/plonk2/constraint_polynomial.rs create mode 100644 src/plonk2/gate.rs create mode 100644 src/plonk2/generator.rs create mode 100644 src/plonk2/mod.rs create mode 100644 src/plonk2/partitions.rs create mode 100644 src/plonk2/prover.rs create mode 100644 src/plonk2/verifier.rs create mode 100644 src/plonk2/witness.rs diff --git a/Cargo.toml b/Cargo.toml index 56bbf9e..9754f32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "plonky" description = "Recursive SNARKs based on Plonk and Halo" version = "0.1.0" -authors = ["Daniel Lubarov"] +authors = ["Daniel Lubarov ", "William Borgeaud "] readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/mir-protocol/plonky" diff --git a/src/lib.rs b/src/lib.rs index 42cb6e4..017d542 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,10 +24,12 @@ pub use curve::*; pub use fft::*; pub use field::*; pub use gates::*; +pub use gates2::*; pub use hash_to_curve::*; pub use mds::*; pub use partition::*; pub use plonk::*; +pub use plonk2::*; pub use plonk_proof::*; pub use plonk_recursion::*; pub use poly_commit::*; @@ -50,11 +52,13 @@ mod curve; mod fft; mod field; mod gates; +mod gates2; pub mod halo; mod hash_to_curve; mod mds; mod partition; mod plonk; +mod plonk2; pub mod plonk_challenger; mod plonk_proof; mod plonk_recursion; diff --git a/src/plonk2/constraint_polynomial.rs b/src/plonk2/constraint_polynomial.rs new file mode 100644 index 0000000..aa3a02e --- /dev/null +++ b/src/plonk2/constraint_polynomial.rs @@ -0,0 +1,294 @@ +use std::hash::{Hash, Hasher}; +use std::ptr; +use std::rc::Rc; +use crate::Field; +use std::collections::HashMap; +use std::ops::{Add, Sub, Mul}; + +struct EvaluationVars<'a, F: Field> { + local_constants: &'a [F], + next_constants: &'a [F], + local_wire_values: &'a [F], + next_wire_values: &'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`. +// Implementation note: This is a wrapper because we want to hide complexity behind +// `ConstraintPolynomialInner` and `ConstraintPolynomialRef`. In particular, the caller shouldn't +// need to know that we use reference counting internally, and shouldn't have to deal with wrapper +// types related to reference counting. +#[derive(Clone)] +pub struct ConstraintPolynomial(ConstraintPolynomialRef); + +impl ConstraintPolynomial { + pub fn constant(c: F) -> Self { + Self::from_inner(ConstraintPolynomialInner::Constant(c)) + } + + 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_value(index: usize) -> Self { + Self::from_inner(ConstraintPolynomialInner::LocalWireValue(index)) + } + + pub fn next_wire_value(index: usize) -> Self { + 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, + rhs: rhs.0, + }) + } + + fn sub(self, rhs: Self) -> Self { + self.add(rhs.neg()) + } + + fn mul(self, rhs: Self) -> Self { + Self::from_inner(ConstraintPolynomialInner::Product { + lhs: self.0, + rhs: rhs.0, + }) + } + + pub fn exp(&self, exponent: usize) -> Self { + Self::from_inner(ConstraintPolynomialInner::Exponentiation { + base: self.0.clone(), + exponent, + }) + } + + pub(crate) fn degree(&self) -> usize { + (self.0).0.degree() + } + + pub(crate) fn evaluate_all( + polynomials: &[ConstraintPolynomial], + local_constants: &[F], + next_constants: &[F], + local_wire_values: &[F], + next_wire_values: &[F], + ) -> 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)) + .collect() + } + + fn from_inner(inner: ConstraintPolynomialInner) -> Self { + Self(ConstraintPolynomialRef::new(inner)) + } +} + +/// 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.clone()) + } + } + + impl $trait> for &ConstraintPolynomial { + type Output = ConstraintPolynomial; + + fn $method(self, rhs: ConstraintPolynomial) -> Self::Output { + ConstraintPolynomial::$method(self.clone(), rhs) + } + } + + impl $trait for &ConstraintPolynomial { + type Output = ConstraintPolynomial; + + fn $method(self, rhs: Self) -> Self::Output { + ConstraintPolynomial::$method(self.clone(), rhs.clone()) + } + } + + 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.clone(), ConstraintPolynomial::constant(rhs)) + } + } + + impl $trait for ConstraintPolynomial { + type Output = Self; + + fn $method(self, rhs: usize) -> Self { + ConstraintPolynomial::$method(self, ConstraintPolynomial::constant(F::from_canonical_usize(rhs))) + } + } + + impl $trait for &ConstraintPolynomial { + type Output = ConstraintPolynomial; + + fn $method(self, rhs: usize) -> Self::Output { + ConstraintPolynomial::$method(self.clone(), ConstraintPolynomial::constant(F::from_canonical_usize(rhs))) + } + } + }; +} + +binop_variants!(Add, add); +binop_variants!(Sub, sub); +binop_variants!(Mul, mul); + +enum ConstraintPolynomialInner { + Constant(F), + + LocalConstant(usize), + NextConstant(usize), + LocalWireValue(usize), + NextWireValue(usize), + + Sum { + lhs: ConstraintPolynomialRef, + rhs: ConstraintPolynomialRef, + }, + Product { + lhs: ConstraintPolynomialRef, + rhs: ConstraintPolynomialRef, + }, + Exponentiation { + base: ConstraintPolynomialRef, + exponent: usize, + }, +} + +impl ConstraintPolynomialInner { + 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_wire_values[*i], + ConstraintPolynomialInner::NextWireValue(i) => vars.next_wire_values[*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) -> usize { + match self { + ConstraintPolynomialInner::Constant(_) => 0, + ConstraintPolynomialInner::LocalConstant(_) => 1, + ConstraintPolynomialInner::NextConstant(_) => 1, + ConstraintPolynomialInner::LocalWireValue(_) => 1, + ConstraintPolynomialInner::NextWireValue(_) => 1, + 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() * exponent, + } + } +} + +/// Wraps `Rc`, and 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)] +struct ConstraintPolynomialRef(Rc>); + +impl ConstraintPolynomialRef { + fn new(inner: ConstraintPolynomialInner) -> Self { + Self(Rc::new(inner)) + } + + 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 + } + } +} + +impl PartialEq for ConstraintPolynomialRef { + fn eq(&self, other: &Self) -> bool { + ptr::eq(&*self.0, &*other.0) + } +} + +impl Eq for ConstraintPolynomialRef {} + +impl Hash for ConstraintPolynomialRef { + fn hash(&self, state: &mut H) { + ptr::hash(&*self.0, state); + } +} diff --git a/src/plonk2/gate.rs b/src/plonk2/gate.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/plonk2/generator.rs b/src/plonk2/generator.rs new file mode 100644 index 0000000..eedd858 --- /dev/null +++ b/src/plonk2/generator.rs @@ -0,0 +1,35 @@ +use crate::{Field, Target2, PartialWitness2}; + +/// A generator participates in the generation of the witness. +pub trait WitnessGenerator2 { + /// Targets to be "watched" by this generator. Whenever a target in the watch list is populated, + /// the generator will be queued to run. + fn watch_list(&self) -> Vec>; + + /// Run this generator, returning a `PartialWitness` containing any new witness elements, and a + /// flag indicating whether the generator is finished. If the flag is true, the generator will + /// never be run again, otherwise it will be queued for another run next time a target in its + /// watch list is populated. + fn run(&mut self, witness: &PartialWitness2) -> (PartialWitness2, bool); +} + +/// A generator which runs once after a list of dependencies is present in the witness. +pub trait SimpleGenerator { + fn dependencies(&self) -> Vec>; + + fn run_once(&mut self, witness: &PartialWitness2) -> PartialWitness2; +} + +impl WitnessGenerator2 for dyn SimpleGenerator { + fn watch_list(&self) -> Vec> { + self.dependencies() + } + + fn run(&mut self, witness: &PartialWitness2) -> (PartialWitness2, bool) { + if witness.contains_all(&self.dependencies()) { + (self.run_once(witness), true) + } else { + (PartialWitness2::new(), false) + } + } +} diff --git a/src/plonk2/mod.rs b/src/plonk2/mod.rs new file mode 100644 index 0000000..9689cdb --- /dev/null +++ b/src/plonk2/mod.rs @@ -0,0 +1,99 @@ +use std::convert::Infallible; +use std::hash::Hash; +use std::marker::PhantomData; +use std::rc::Rc; + +pub use constraint_polynomial::*; +pub use gate::*; +pub use generator::*; +pub use prover::*; +pub use verifier::*; +pub use witness::*; + +use crate::{Field, GateInstance, GateType, Wire}; + +mod constraint_polynomial; +mod gate; +mod generator; +mod partitions; +mod prover; +mod witness; +mod verifier; + +pub struct CircuitBuilder2 { + gates: Vec>>, + gate_instances: Vec>, +} + +impl CircuitBuilder2 { + /// 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(); + self.gate_instances.push(gate_instance); + index + } + + /// Shorthand for `generate_copy` and `assert_equal`. + /// Both elements must be routable, otherwise this method will panic. + pub fn copy(&mut self, x: Target2, y: Target2) { + self.generate_copy(x, y); + self.assert_equal(x, y); + } + + /// Adds a generator which will copy `x` to `y`. + pub fn generate_copy(&mut self, x: Target2, y: Target2) { + todo!(); + } + + /// Uses Plonk's permutation argument to require that two elements be equal. + /// Both elements must be routable, otherwise this method will panic. + pub fn assert_equal(&mut self, x: Target2, y: Target2) { + assert!(x.is_routable()); + assert!(y.is_routable()); + } + + /// Returns a routable target with a value of zero. + pub fn zero(&mut self) -> Target2 { + self.constant(F::ZERO) + } + + /// Returns a routable target with a value of one. + pub fn one(&mut self) -> Target2 { + self.constant(F::ONE) + } + + /// Returns a routable target with a value of `ORDER - 1`. + pub fn neg_one(&mut self) -> Target2 { + self.constant(F::NEG_ONE) + } + + /// Returns a routable target with the given constant value. + pub fn constant(&mut self, c: F) -> Target2 { + todo!() + } +} + +/// A location in the witness. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum Target2 { + Wire(Wire), + PublicInput { index: usize }, + VirtualAdviceTarget { index: usize }, + // Trick taken from https://github.com/rust-lang/rust/issues/32739#issuecomment-627765543. + _Field(Infallible, PhantomData), +} + +impl Target2 { + pub fn wire(gate: usize, input: usize) -> Self { + Self::Wire(Wire { gate, input }) + } + + pub fn is_routable(&self) -> bool { + match self { + Target2::Wire(wire) => wire.is_routable(), + Target2::PublicInput { .. } => true, + Target2::VirtualAdviceTarget { .. } => false, + Target2::_Field(_, _) => unreachable!(), + } + } +} diff --git a/src/plonk2/partitions.rs b/src/plonk2/partitions.rs new file mode 100644 index 0000000..f73905c --- /dev/null +++ b/src/plonk2/partitions.rs @@ -0,0 +1,49 @@ +use std::collections::HashMap; + +use crate::{Field, Target2}; + +#[derive(Debug)] +pub(crate) struct Partitions2 { + partitions: Vec>>, + indices: HashMap, usize>, +} + +impl Partitions2 { + pub fn new() -> Self { + Self { + partitions: Vec::new(), + indices: HashMap::new(), + } + } + + /// Adds the targets as new singleton partitions if they are not already present, then merges + /// their partitions if the targets are not already in the same partition. + pub fn merge(&mut self, a: Target2, b: Target2) { + let a_index = self.get_index(a); + let b_index = self.get_index(b); + + if a_index != b_index { + // Merge a's partition into b's partition, leaving a's partition empty. + // We have to clone because Rust's borrow checker doesn't know that + // self.partitions[b_index] and self.partitions[b_index] are disjoint. + let mut a_partition = self.partitions[a_index].clone(); + let b_partition = &mut self.partitions[b_index]; + for a_sibling in &a_partition { + *self.indices.get_mut(a_sibling).unwrap() = b_index; + } + b_partition.append(&mut a_partition); + } + } + + /// Gets the partition index of a given target. If the target is not present, adds it as a new + /// singleton partition and returns the new partition's index. + fn get_index(&mut self, target: Target2) -> usize { + if let Some(&index) = self.indices.get(&target) { + index + } else { + let index = self.partitions.len(); + self.partitions.push(vec![target]); + index + } + } +} diff --git a/src/plonk2/prover.rs b/src/plonk2/prover.rs new file mode 100644 index 0000000..40fd6e5 --- /dev/null +++ b/src/plonk2/prover.rs @@ -0,0 +1,3 @@ +// use crate::Curve; +// +// pub struct Prover {} diff --git a/src/plonk2/verifier.rs b/src/plonk2/verifier.rs new file mode 100644 index 0000000..a4295c8 --- /dev/null +++ b/src/plonk2/verifier.rs @@ -0,0 +1,3 @@ +// use crate::Curve; +// +// pub struct Verifier {} diff --git a/src/plonk2/witness.rs b/src/plonk2/witness.rs new file mode 100644 index 0000000..cb39c28 --- /dev/null +++ b/src/plonk2/witness.rs @@ -0,0 +1,28 @@ +use std::collections::HashMap; + +use crate::{Field, Target2}; + +#[derive(Debug)] +pub struct PartialWitness2 { + wire_values: HashMap, F>, +} + +impl PartialWitness2 { + pub fn new() -> Self { + PartialWitness2 { + wire_values: HashMap::new(), + } + } + + pub fn is_empty(&self) -> bool { + self.wire_values.is_empty() + } + + pub fn contains(&self, target: Target2) -> bool { + self.wire_values.contains_key(&target) + } + + pub fn contains_all(&self, targets: &[Target2]) -> bool { + targets.iter().all(|&t| self.contains(t)) + } +}