From 4f8570db95c72ec9426935ca3c4586e31790c5c4 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 29 Aug 2020 13:51:42 +0800 Subject: [PATCH 01/38] Add DELTA generator of t-order multiplicative subgroup to Fp, Fq --- src/arithmetic/fields.rs | 3 +++ src/arithmetic/fields/fp.rs | 15 +++++++++++++++ src/arithmetic/fields/fq.rs | 15 +++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index bc75289..45b9f7b 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -64,6 +64,9 @@ pub trait Field: /// The value $t^{-1} \mod 2^S$. const UNROLL_S_EXPONENT: u64; + /// Generator of the $t-order$ multiplicative subgroup + const DELTA: Self; + /// Inverse of $2$ in the field. const TWO_INV: Self; diff --git a/src/arithmetic/fields/fp.rs b/src/arithmetic/fields/fp.rs index f79fb07..a7e0cb9 100644 --- a/src/arithmetic/fields/fp.rs +++ b/src/arithmetic/fields/fp.rs @@ -173,6 +173,20 @@ const ROOT_OF_UNITY: Fp = Fp::from_raw([ 0x2ae45117890ee2fc, ]); +/// GENERATOR^{2^s} where t * 2^s + 1 = p +/// with t odd. In other words, this +/// is a t root of unity. +/// +/// `GENERATOR = 5 mod p` is a generator +/// of the p - 1 order multiplicative +/// subgroup. +const DELTA: Fp = Fp::from_raw([ + 0x1e9372724e80300d, + 0x671383de08b5fe3c, + 0xa99d8b67e918805e, + 0x48796f6fde98a425, +]); + impl Default for Fp { #[inline] fn default() -> Self { @@ -429,6 +443,7 @@ impl Field for Fp { 0x0000000000000000, 0x20000000, ]; + const DELTA: Self = DELTA; const UNROLL_S_EXPONENT: u64 = 0x11cb54e91; const TWO_INV: Self = Fp::from_raw([ 0xd0a0327100000001, diff --git a/src/arithmetic/fields/fq.rs b/src/arithmetic/fields/fq.rs index 24358e3..24fafcc 100644 --- a/src/arithmetic/fields/fq.rs +++ b/src/arithmetic/fields/fq.rs @@ -173,6 +173,20 @@ const ROOT_OF_UNITY: Fq = Fq::from_raw([ 0x113efc510dc03c0b, ]); +/// GENERATOR^{2^s} where t * 2^s + 1 = q +/// with t odd. In other words, this +/// is a t root of unity. +/// +/// `GENERATOR = 5 mod q` is a generator +/// of the q - 1 order multiplicative +/// subgroup. +const DELTA: Fq = Fq::from_raw([ + 0x20daec44973be920, + 0x4243423589e0a9b5, + 0x5127e2ce24a8e69c, + 0x83d2833d15f2bbf9, +]); + impl Default for Fq { #[inline] fn default() -> Self { @@ -444,6 +458,7 @@ impl Field for Fq { 0x0000000000000000, 0x10000000, ]; + const DELTA: Self = DELTA; const UNROLL_S_EXPONENT: u64 = 0x344cfe85d; const TWO_INV: Self = Fq::from_raw([ 0xc21657ea00000001, From 85fd924b15a4fac0725db05b20ca05b87529966c Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 31 Aug 2020 13:58:00 +0800 Subject: [PATCH 02/38] WIP implement copy() on Variables --- src/plonk.rs | 20 +++++++++----------- src/plonk/circuit.rs | 15 +++++++++++++-- src/plonk/prover.rs | 8 +++++++- src/plonk/srs.rs | 17 ++++++++++++++++- 4 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index 7de9c61..345d39e 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -101,9 +101,6 @@ fn test_proving() { sm: FixedWire, } - #[derive(Copy, Clone)] - struct Variable(AdviceWire, usize); - trait StandardCS { fn raw_multiply(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> where @@ -163,9 +160,9 @@ fn test_proving() { self.cs .assign_fixed(self.config.sm, index, || Ok(FF::one()))?; Ok(( - Variable(self.config.a, index), - Variable(self.config.b, index), - Variable(self.config.c, index), + Variable::new(self.config.a, index), + Variable::new(self.config.b, index), + Variable::new(self.config.c, index), )) } fn raw_add(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> @@ -195,9 +192,9 @@ fn test_proving() { self.cs .assign_fixed(self.config.sm, index, || Ok(FF::zero()))?; Ok(( - Variable(self.config.a, index), - Variable(self.config.b, index), - Variable(self.config.c, index), + Variable::new(self.config.a, index), + Variable::new(self.config.b, index), + Variable::new(self.config.c, index), )) } } @@ -248,7 +245,7 @@ fn test_proving() { for _ in 0..10 { let mut a_squared = None; - let (_, _, _) = cs.raw_multiply(|| { + let (_, _, c0) = cs.raw_multiply(|| { a_squared = self.a.map(|a| a.square()); Ok(( self.a.ok_or(Error::SynthesisError)?, @@ -256,7 +253,7 @@ fn test_proving() { a_squared.ok_or(Error::SynthesisError)?, )) })?; - let (_, _, _) = cs.raw_add(|| { + let (a1, _, _) = cs.raw_add(|| { let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2)); Ok(( self.a.ok_or(Error::SynthesisError)?, @@ -264,6 +261,7 @@ fn test_proving() { fin.ok_or(Error::SynthesisError)?, )) })?; + cs.cs.assign_copy(a1, c0)?; } Ok(()) diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index d1923b6..b190d29 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -14,6 +14,17 @@ pub struct FixedWire(pub usize); #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct AdviceWire(pub usize); +/// This represents an advice wire at a certain row in the MetaCircuit +#[derive(Copy, Clone, Debug)] +pub struct Variable(pub AdviceWire, pub usize); + +impl Variable { + /// Construct a Variable + pub fn new(wire: AdviceWire, index: usize) -> Variable { + Variable(wire, index) + } +} + /// This trait allows a [`Circuit`] to direct some backend to assign a witness /// for a constraint system. pub trait ConstraintSystem { @@ -33,7 +44,8 @@ pub trait ConstraintSystem { to: impl FnOnce() -> Result, ) -> Result<(), Error>; - // fn copy(&mut self, left: Wire, right: Wire); + /// Assign two advice wires to have the same value + fn assign_copy(&mut self, left: Variable, right: Variable) -> Result<(), Error>; } /// This is a trait that circuits provide implementations for so that the @@ -147,7 +159,6 @@ pub struct PointIndex(pub usize); pub struct MetaCircuit { pub(crate) num_fixed_wires: usize, pub(crate) num_advice_wires: usize, - // permutations: Vec>, pub(crate) gates: Vec>, pub(crate) advice_queries: Vec<(AdviceWire, Rotation)>, pub(crate) fixed_queries: Vec<(FixedWire, Rotation)>, diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 70f006c..8cc1e72 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -1,5 +1,5 @@ use super::{ - circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit}, + circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit, Variable}, domain::Rotation, hash_point, Error, Proof, SRS, }; @@ -53,6 +53,12 @@ impl Proof { Ok(()) } + + fn assign_copy(&mut self, _: Variable, _: Variable) -> Result<(), Error> { + // We only care about advice wires here + + Ok(()) + } } let mut meta = MetaCircuit::default(); diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 98bdc13..82ca2c9 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -1,5 +1,5 @@ use super::{ - circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit}, + circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit, Variable}, domain::EvaluationDomain, Error, SRS, }; @@ -15,6 +15,7 @@ impl SRS { ) -> Result { struct Assembly { fixed: Vec>, + copy: Vec>, } impl ConstraintSystem for Assembly { @@ -42,6 +43,16 @@ impl SRS { Ok(()) } + + fn assign_copy(&mut self, left: Variable, right: Variable) -> Result<(), Error> { + *self + .copy + .get_mut((left.0).0) + .and_then(|v| v.get_mut(left.1)) + .ok_or(Error::BoundsFailure)? = right; + + Ok(()) + } } let mut meta = MetaCircuit::default(); @@ -49,6 +60,10 @@ impl SRS { let mut assembly: Assembly = Assembly { fixed: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_fixed_wires], + copy: vec![ + vec![Variable::new(AdviceWire(0), 0); params.n as usize]; + meta.num_advice_wires + ], }; // Synthesize the circuit to obtain SRS From dc5df10832931f29cbf039663f48cef7abbd188d Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 31 Aug 2020 10:01:09 -0600 Subject: [PATCH 03/38] Update structured reference string and API for permutation argument. --- src/plonk.rs | 46 ++++++++++++--- src/plonk/circuit.rs | 36 ++++++++---- src/plonk/prover.rs | 11 +++- src/plonk/srs.rs | 135 ++++++++++++++++++++++++++++++++++++------- 4 files changed, 186 insertions(+), 42 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index 345d39e..f71ecf7 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -32,6 +32,9 @@ pub struct SRS { fixed_commitments: Vec, fixed_polys: Vec>, fixed_cosets: Vec>, + permutation_commitments: Vec>, + permutation_polys: Vec>>, + permutation_cosets: Vec>>, meta: MetaCircuit, } @@ -87,6 +90,10 @@ fn test_proving() { use std::marker::PhantomData; const K: u32 = 5; + /// This represents an advice wire at a certain row in the MetaCircuit + #[derive(Copy, Clone, Debug)] + pub struct Variable(AdviceWire, usize); + // Initialize the polynomial commitment parameters let params: Params = Params::new::>(K); @@ -99,6 +106,8 @@ fn test_proving() { sb: FixedWire, sc: FixedWire, sm: FixedWire, + + perm: usize, } trait StandardCS { @@ -108,6 +117,7 @@ fn test_proving() { fn raw_add(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> where F: FnOnce() -> Result<(FF, FF, FF), Error>; + fn copy(&mut self, a: Variable, b: Variable) -> Result<(), Error>; } struct MyCircuit { @@ -160,9 +170,9 @@ fn test_proving() { self.cs .assign_fixed(self.config.sm, index, || Ok(FF::one()))?; Ok(( - Variable::new(self.config.a, index), - Variable::new(self.config.b, index), - Variable::new(self.config.c, index), + Variable(self.config.a, index), + Variable(self.config.b, index), + Variable(self.config.c, index), )) } fn raw_add(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> @@ -192,11 +202,28 @@ fn test_proving() { self.cs .assign_fixed(self.config.sm, index, || Ok(FF::zero()))?; Ok(( - Variable::new(self.config.a, index), - Variable::new(self.config.b, index), - Variable::new(self.config.c, index), + Variable(self.config.a, index), + Variable(self.config.b, index), + Variable(self.config.c, index), )) } + fn copy(&mut self, a: Variable, b: Variable) -> Result<(), Error> { + let left_wire = match a.0 { + x if x == self.config.a => 0, + x if x == self.config.b => 1, + x if x == self.config.c => 2, + _ => unreachable!(), + }; + let right_wire = match b.0 { + x if x == self.config.a => 0, + x if x == self.config.b => 1, + x if x == self.config.c => 2, + _ => unreachable!(), + }; + + self.cs + .copy(self.config.perm, left_wire, a.1, right_wire, b.1) + } } impl Circuit for MyCircuit { @@ -207,6 +234,8 @@ fn test_proving() { let b = meta.advice_wire(); let c = meta.advice_wire(); + let perm = meta.permutation(&[a, b, c]); + let sa = meta.fixed_wire(); let sb = meta.fixed_wire(); let sc = meta.fixed_wire(); @@ -233,6 +262,7 @@ fn test_proving() { sb, sc, sm, + perm, } } @@ -253,7 +283,7 @@ fn test_proving() { a_squared.ok_or(Error::SynthesisError)?, )) })?; - let (a1, _, _) = cs.raw_add(|| { + let (_, b1, _) = cs.raw_add(|| { let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2)); Ok(( self.a.ok_or(Error::SynthesisError)?, @@ -261,7 +291,7 @@ fn test_proving() { fin.ok_or(Error::SynthesisError)?, )) })?; - cs.cs.assign_copy(a1, c0)?; + cs.copy(b1, c0)?; } Ok(()) diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index b190d29..26aca04 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -14,17 +14,6 @@ pub struct FixedWire(pub usize); #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct AdviceWire(pub usize); -/// This represents an advice wire at a certain row in the MetaCircuit -#[derive(Copy, Clone, Debug)] -pub struct Variable(pub AdviceWire, pub usize); - -impl Variable { - /// Construct a Variable - pub fn new(wire: AdviceWire, index: usize) -> Variable { - Variable(wire, index) - } -} - /// This trait allows a [`Circuit`] to direct some backend to assign a witness /// for a constraint system. pub trait ConstraintSystem { @@ -45,7 +34,14 @@ pub trait ConstraintSystem { ) -> Result<(), Error>; /// Assign two advice wires to have the same value - fn assign_copy(&mut self, left: Variable, right: Variable) -> Result<(), Error>; + fn copy( + &mut self, + permutation: usize, + left_wire: usize, + left_row: usize, + right_wire: usize, + right_row: usize, + ) -> Result<(), Error>; } /// This is a trait that circuits provide implementations for so that the @@ -165,6 +161,14 @@ pub struct MetaCircuit { // Mapping from a witness vector rotation to the index in the point vector. pub(crate) rotations: HashMap, + + // Vector of permutation arguments, where each corresponds to a set of wires + // that are involved in a permutation argument. As an example, we could have + // a permutation argument between wires (A, B, C) which allows copy + // constraints to be enforced between advice wire values in A, B and C, and + // another permutation between wires (B, C, D) which allows the same with D + // instead of A. + pub(crate) permutations: Vec>, } impl Default for MetaCircuit { @@ -179,11 +183,19 @@ impl Default for MetaCircuit { fixed_queries: Vec::new(), advice_queries: Vec::new(), rotations, + permutations: Vec::new(), } } } impl MetaCircuit { + /// Add a permutation argument for some advice wires + pub fn permutation(&mut self, wires: &[AdviceWire]) -> usize { + let index = self.permutations.len(); + self.permutations.push(wires.to_vec()); + index + } + /// Query a fixed wire at a relative position pub fn query_fixed(&mut self, wire: FixedWire, at: i32) -> Polynomial { let at = Rotation(at); diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 8cc1e72..8ac627e 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -1,5 +1,5 @@ use super::{ - circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit, Variable}, + circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit}, domain::Rotation, hash_point, Error, Proof, SRS, }; @@ -54,7 +54,14 @@ impl Proof { Ok(()) } - fn assign_copy(&mut self, _: Variable, _: Variable) -> Result<(), Error> { + fn copy( + &mut self, + _: usize, + _: usize, + _: usize, + _: usize, + _: usize, + ) -> Result<(), Error> { // We only care about advice wires here Ok(()) diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 82ca2c9..13c7992 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -1,6 +1,6 @@ use super::{ - circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit, Variable}, - domain::EvaluationDomain, + circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit}, + domain::{EvaluationDomain, Rotation}, Error, SRS, }; use crate::arithmetic::{Curve, CurveAffine, Field}; @@ -15,7 +15,7 @@ impl SRS { ) -> Result { struct Assembly { fixed: Vec>, - copy: Vec>, + copy: Vec>>, } impl ConstraintSystem for Assembly { @@ -44,12 +44,38 @@ impl SRS { Ok(()) } - fn assign_copy(&mut self, left: Variable, right: Variable) -> Result<(), Error> { - *self - .copy - .get_mut((left.0).0) - .and_then(|v| v.get_mut(left.1)) - .ok_or(Error::BoundsFailure)? = right; + fn copy( + &mut self, + permutation: usize, + left_wire: usize, + left_row: usize, + right_wire: usize, + right_row: usize, + ) -> Result<(), Error> { + let left: (usize, usize) = *self.copy[permutation] + .get_mut(left_wire) + .and_then(|wire| wire.get_mut(left_row)) + .ok_or(Error::BoundsFailure)?; + + let right: (usize, usize) = *self.copy[permutation] + .get_mut(right_wire) + .and_then(|wire| wire.get_mut(right_row)) + .ok_or(Error::BoundsFailure)?; + + if left == (left_wire, left_row) || right == (right_wire, right_row) { + // Don't perform the copy constraint because it will undo + // the effect of the permutation. + } else { + *self.copy[permutation] + .get_mut(left_wire) + .and_then(|wire| wire.get_mut(left_row)) + .ok_or(Error::BoundsFailure)? = right; + + *self.copy[permutation] + .get_mut(right_wire) + .and_then(|wire| wire.get_mut(right_row)) + .ok_or(Error::BoundsFailure)? = left; + } Ok(()) } @@ -58,30 +84,96 @@ impl SRS { let mut meta = MetaCircuit::default(); let config = ConcreteCircuit::configure(&mut meta); + let mut degree = 1; + for poly in meta.gates.iter() { + degree = std::cmp::max(degree, poly.degree()); + } + for permutation in &meta.permutations { + degree = std::cmp::max(degree, permutation.len() + 1); + } + + let domain = EvaluationDomain::new(degree as u32, params.k); + + let mut largest_permutation_length = 0; + for permutation in &meta.permutations { + largest_permutation_length = + std::cmp::max(permutation.len(), largest_permutation_length); + } + + let mut omega_powers = Vec::with_capacity(params.n as usize); + { + let mut cur = C::Scalar::one(); + for _ in 0..params.n { + omega_powers.push(cur); + cur *= &domain.get_omega(); + } + } + + let mut deltaomega = Vec::with_capacity(largest_permutation_length); + { + let mut cur = C::Scalar::one(); + for _ in 0..largest_permutation_length { + let mut omega_powers = omega_powers.clone(); + for o in &mut omega_powers { + *o *= &cur; + } + + deltaomega.push(omega_powers); + + cur *= &C::Scalar::DELTA; + } + } + let mut assembly: Assembly = Assembly { fixed: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_fixed_wires], - copy: vec![ - vec![Variable::new(AdviceWire(0), 0); params.n as usize]; - meta.num_advice_wires - ], + copy: vec![], }; + for permutation in &meta.permutations { + let mut wires = vec![]; + for (i, _) in permutation.iter().enumerate() { + wires.push((0..params.n).map(|j| (i, j as usize)).collect()); + } + assembly.copy.push(wires); + } + // Synthesize the circuit to obtain SRS circuit.synthesize(&mut assembly, config)?; + // Compute permutation polynomials + let mut permutation_commitments = vec![]; + let mut permutation_polys = vec![]; + let mut permutation_cosets = vec![]; + for (permutation_index, permutation) in meta.permutations.iter().enumerate() { + let mut commitments = vec![]; + let mut polys = vec![]; + let mut cosets = vec![]; + for (i, _) in permutation.iter().enumerate() { + let permutation_poly: Vec<_> = (0..params.n as usize) + .map(|j| { + let (permuted_i, permuted_j) = assembly.copy[permutation_index][i][j]; + deltaomega[permuted_i][permuted_j] + }) + .collect(); + commitments.push( + params + .commit_lagrange(&permutation_poly, C::Scalar::one()) + .to_affine(), + ); + polys.push(permutation_poly.clone()); + cosets.push(domain.obtain_coset(permutation_poly, Rotation::default())); + } + permutation_commitments.push(commitments); + permutation_polys.push(polys); + permutation_cosets.push(cosets); + } + let fixed_commitments = assembly .fixed .iter() .map(|poly| params.commit_lagrange(poly, C::Scalar::one()).to_affine()) .collect(); - let mut degree = 1; - for poly in meta.gates.iter() { - degree = std::cmp::max(degree, poly.degree()); - } - - let domain = EvaluationDomain::new(degree as u32, params.k); - let fixed_polys: Vec<_> = assembly .fixed .into_iter() @@ -102,6 +194,9 @@ impl SRS { fixed_commitments, fixed_polys, fixed_cosets, + permutation_commitments, + permutation_polys, + permutation_cosets, meta, }) } From c427795bf550f88ac6155fe7ca5a95758ffa7406 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 31 Aug 2020 10:10:05 -0600 Subject: [PATCH 04/38] Reverse endianness of delta constants --- src/arithmetic/fields/fp.rs | 11 ++++++++--- src/arithmetic/fields/fq.rs | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/arithmetic/fields/fp.rs b/src/arithmetic/fields/fp.rs index a7e0cb9..11c51fa 100644 --- a/src/arithmetic/fields/fp.rs +++ b/src/arithmetic/fields/fp.rs @@ -181,10 +181,10 @@ const ROOT_OF_UNITY: Fp = Fp::from_raw([ /// of the p - 1 order multiplicative /// subgroup. const DELTA: Fp = Fp::from_raw([ - 0x1e9372724e80300d, - 0x671383de08b5fe3c, - 0xa99d8b67e918805e, 0x48796f6fde98a425, + 0xa99d8b67e918805e, + 0x671383de08b5fe3c, + 0x1e9372724e80300d, ]); impl Default for Fp { @@ -655,3 +655,8 @@ fn test_inv_root_of_unity() { fn test_inv_2() { assert_eq!(Fp::TWO_INV, Fp::from(2).invert().unwrap()); } + +#[test] +fn test_delta() { + assert_eq!(Fp::DELTA, Fp::from(5).pow(&[1u64 << Fp::S, 0, 0, 0])); +} diff --git a/src/arithmetic/fields/fq.rs b/src/arithmetic/fields/fq.rs index 24fafcc..5a43206 100644 --- a/src/arithmetic/fields/fq.rs +++ b/src/arithmetic/fields/fq.rs @@ -181,10 +181,10 @@ const ROOT_OF_UNITY: Fq = Fq::from_raw([ /// of the q - 1 order multiplicative /// subgroup. const DELTA: Fq = Fq::from_raw([ - 0x20daec44973be920, - 0x4243423589e0a9b5, - 0x5127e2ce24a8e69c, 0x83d2833d15f2bbf9, + 0x5127e2ce24a8e69c, + 0x4243423589e0a9b5, + 0x20daec44973be920, ]); impl Default for Fq { @@ -669,3 +669,8 @@ fn test_inv_root_of_unity() { fn test_inv_2() { assert_eq!(Fq::TWO_INV, Fq::from(2).invert().unwrap()); } + +#[test] +fn test_delta() { + assert_eq!(Fq::DELTA, Fq::from(5).pow(&[1u64 << Fq::S, 0, 0, 0])); +} From a2fca8a02d1b23804ec84aac8c6e62d36686a85a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 31 Aug 2020 10:18:55 -0600 Subject: [PATCH 05/38] Add comments to clarify implementation of permutation argument in SRS generator. --- src/plonk/srs.rs | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 13c7992..d878e32 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -84,22 +84,27 @@ impl SRS { let mut meta = MetaCircuit::default(); let config = ConcreteCircuit::configure(&mut meta); - let mut degree = 1; - for poly in meta.gates.iter() { - degree = std::cmp::max(degree, poly.degree()); - } - for permutation in &meta.permutations { - degree = std::cmp::max(degree, permutation.len() + 1); - } - - let domain = EvaluationDomain::new(degree as u32, params.k); - + // Get the largest permutation argument length in terms of the number of + // advice wires involved. let mut largest_permutation_length = 0; for permutation in &meta.permutations { largest_permutation_length = std::cmp::max(permutation.len(), largest_permutation_length); } + // The permutation argument will serve alongside the gates, so must be + // accounted for. + let mut degree = largest_permutation_length; + + // Account for each gate to ensure our quotient polynomial is the + // correct degree and that our extended domain is the right size. + for poly in meta.gates.iter() { + degree = std::cmp::max(degree, poly.degree()); + } + + let domain = EvaluationDomain::new(degree as u32, params.k); + + // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] let mut omega_powers = Vec::with_capacity(params.n as usize); { let mut cur = C::Scalar::one(); @@ -109,6 +114,7 @@ impl SRS { } } + // Compute [omega_powers * \delta^0, omega_powers * \delta^1, ..., omega_powers * \delta^m] let mut deltaomega = Vec::with_capacity(largest_permutation_length); { let mut cur = C::Scalar::one(); @@ -129,9 +135,12 @@ impl SRS { copy: vec![], }; + // Initialize the copy vector to keep track of copy constraints in all + // the permutation arguments. for permutation in &meta.permutations { let mut wires = vec![]; for (i, _) in permutation.iter().enumerate() { + // Computes [(i, 0), (i, 1), ..., (i, n - 1)] wires.push((0..params.n).map(|j| (i, j as usize)).collect()); } assembly.copy.push(wires); @@ -140,7 +149,8 @@ impl SRS { // Synthesize the circuit to obtain SRS circuit.synthesize(&mut assembly, config)?; - // Compute permutation polynomials + // Compute permutation polynomials, convert to coset form and + // pre-compute commitments for the SRS. let mut permutation_commitments = vec![]; let mut permutation_polys = vec![]; let mut permutation_cosets = vec![]; @@ -149,17 +159,25 @@ impl SRS { let mut polys = vec![]; let mut cosets = vec![]; for (i, _) in permutation.iter().enumerate() { + // Computes the permutation polynomial based on the permutation + // description in the assembly. let permutation_poly: Vec<_> = (0..params.n as usize) .map(|j| { + // assembly.copy[permutation_index] is indexed by wire + // i, and then indexed by row j, obtaining the index of + // the permuted value in deltaomega. let (permuted_i, permuted_j) = assembly.copy[permutation_index][i][j]; deltaomega[permuted_i][permuted_j] }) .collect(); + + // Compute commitment to permutation polynomial commitments.push( params .commit_lagrange(&permutation_poly, C::Scalar::one()) .to_affine(), ); + // Store permutation polynomial and precompute its coset evaluation polys.push(permutation_poly.clone()); cosets.push(domain.obtain_coset(permutation_poly, Rotation::default())); } From 0bf73c5d085466fe74fa1db75750109b26ed84d3 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 2 Sep 2020 23:18:43 +0800 Subject: [PATCH 06/38] Minor fixes to srs.rs --- src/plonk/srs.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index d878e32..d22ac77 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -66,15 +66,9 @@ impl SRS { // Don't perform the copy constraint because it will undo // the effect of the permutation. } else { - *self.copy[permutation] - .get_mut(left_wire) - .and_then(|wire| wire.get_mut(left_row)) - .ok_or(Error::BoundsFailure)? = right; + self.copy[permutation][left_wire][left_row] = right; - *self.copy[permutation] - .get_mut(right_wire) - .and_then(|wire| wire.get_mut(right_row)) - .ok_or(Error::BoundsFailure)? = left; + self.copy[permutation][right_wire][right_row] = left; } Ok(()) @@ -94,7 +88,7 @@ impl SRS { // The permutation argument will serve alongside the gates, so must be // accounted for. - let mut degree = largest_permutation_length; + let mut degree = largest_permutation_length + 1; // Account for each gate to ensure our quotient polynomial is the // correct degree and that our extended domain is the right size. From 2472ec32910d34fcfb660be79bd68b5b621bc5ce Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 1 Sep 2020 13:06:25 +0800 Subject: [PATCH 07/38] WIP permutation checks in verifier --- src/plonk.rs | 4 ++++ src/plonk/prover.rs | 10 ++++++++++ src/plonk/verifier.rs | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/src/plonk.rs b/src/plonk.rs index f71ecf7..fd72bde 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -44,6 +44,10 @@ pub struct SRS { pub struct Proof { advice_commitments: Vec, h_commitments: Vec, + permutation_product_commitments: Vec, + permutation_product_evals: Vec, + permutation_product_inv_evals: Vec, + permutation_evals: Vec, advice_evals: Vec, fixed_evals: Vec, h_evals: Vec, diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 8ac627e..f4081a9 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -111,6 +111,12 @@ impl Proof { }) .collect(); + // Sample x_0 challenge + let x_0: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Sample x_1 challenge + let x_1: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + // Obtain challenge for keeping all separate gates linearly independent let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); @@ -369,6 +375,10 @@ impl Proof { Ok(Proof { advice_commitments, h_commitments, + permutation_product_commitments: vec![C::default(); params.n as usize], + permutation_product_evals: vec![C::Scalar::one(); params.n as usize], + permutation_product_inv_evals: vec![C::Scalar::one(); params.n as usize], + permutation_evals: vec![C::Scalar::one(); params.n as usize], advice_evals, fixed_evals, h_evals, diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index d8560d1..37083f0 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -19,6 +19,41 @@ impl Proof { .expect("proof cannot contain points at infinity"); } + // Sample x_0 challenge + let x_0: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Sample x_1 challenge + let x_1: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Check permutations + // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] + let mut omega_powers = Vec::with_capacity(params.n as usize); + { + let mut cur = C::Scalar::one(); + for _ in 0..params.n { + omega_powers.push(cur); + cur *= &srs.domain.get_omega(); + } + } + + // For each permutation + for perm in &srs.meta.permutations { + // Check permutation condition on all points + for i in 0..params.n as usize { + let left_perm_eval = self.permutation_product_inv_evals[i]; + let right_perm_eval = self.permutation_product_evals[i]; + + for wire in perm { + // z(\omega^{-1} X) (a(X) + \beta X + \gamma) (b(X) + \delta \beta X + \gamma) (c(X) + \delta^2 \beta X + \gamma) + + // z(X) (a(X) + \beta s_a(X) + \gamma) (b(X) + \beta s_b(X) + \gamma) (c(X) + \beta s_c(X) + \gamma) + } + if left_perm_eval != right_perm_eval { + return false; + } + } + } + // Sample x_2 challenge, which keeps the gates linearly independent. let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); From bdd48f6037750c66e75ca03500e42520fdc73764 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 2 Sep 2020 16:45:34 +0800 Subject: [PATCH 08/38] Add advice_shifted_evals to Proof struct --- src/plonk.rs | 1 + src/plonk/prover.rs | 28 ++++++++++++++++++++++++++++ src/plonk/srs.rs | 2 +- src/plonk/verifier.rs | 24 ++++++++++++++++++------ 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index fd72bde..adaf412 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -48,6 +48,7 @@ pub struct Proof { permutation_product_evals: Vec, permutation_product_inv_evals: Vec, permutation_evals: Vec, + advice_shifted_evals: Vec>>, advice_evals: Vec, fixed_evals: Vec, h_evals: Vec, diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index f4081a9..dd1ba5c 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -117,6 +117,33 @@ impl Proof { // Sample x_1 challenge let x_1: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] + let mut omega_powers = Vec::with_capacity(params.n as usize); + { + let mut cur = C::Scalar::one(); + for _ in 0..params.n { + omega_powers.push(cur); + cur *= &srs.domain.get_omega(); + } + } + + let mut advice_shifted_evals = + vec![ + vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_advice_wires]; + meta.permutations.len() + ]; + + for perm_idx in 0..meta.permutations.len() { + for wire_idx in 0..meta.permutations[perm_idx].len() { + for point_idx in 0..params.n { + let mut eval = + eval_polynomial(&advice_polys[wire_idx], omega_powers[point_idx as usize]); + eval += &x_1; + advice_shifted_evals[perm_idx][wire_idx as usize][point_idx as usize] = eval; + } + } + } + // Obtain challenge for keeping all separate gates linearly independent let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); @@ -379,6 +406,7 @@ impl Proof { permutation_product_evals: vec![C::Scalar::one(); params.n as usize], permutation_product_inv_evals: vec![C::Scalar::one(); params.n as usize], permutation_evals: vec![C::Scalar::one(); params.n as usize], + advice_shifted_evals, advice_evals, fixed_evals, h_evals, diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index d22ac77..8e7a609 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -164,7 +164,7 @@ impl SRS { deltaomega[permuted_i][permuted_j] }) .collect(); - + // Compute commitment to permutation polynomial commitments.push( params diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 37083f0..635e180 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -37,16 +37,28 @@ impl Proof { } // For each permutation - for perm in &srs.meta.permutations { - // Check permutation condition on all points - for i in 0..params.n as usize { - let left_perm_eval = self.permutation_product_inv_evals[i]; - let right_perm_eval = self.permutation_product_evals[i]; + for perm_idx in 0..srs.meta.permutations.len() { + // For each X in evaluation domain + for point_idx in 0..params.n as usize { + let point = omega_powers[point_idx]; - for wire in perm { + let mut left_perm_eval = self.permutation_product_inv_evals[point_idx]; + let mut right_perm_eval = self.permutation_product_evals[point_idx]; + let mut cur_delta = C::Scalar::one(); + + for wire_idx in 0..srs.meta.permutations[perm_idx].len() { // z(\omega^{-1} X) (a(X) + \beta X + \gamma) (b(X) + \delta \beta X + \gamma) (c(X) + \delta^2 \beta X + \gamma) + let left_tmp = &(self.advice_shifted_evals[perm_idx][wire_idx][point_idx] + + &(x_0 * &(cur_delta * &point))); + left_perm_eval *= &left_tmp; + + cur_delta *= &C::Scalar::DELTA; // z(X) (a(X) + \beta s_a(X) + \gamma) (b(X) + \beta s_b(X) + \gamma) (c(X) + \beta s_c(X) + \gamma) + let perm_eval = srs.permutation_polys[perm_idx][wire_idx][point_idx]; + let right_tmp = &(self.advice_shifted_evals[perm_idx][wire_idx][point_idx] + + &(x_0 * &perm_eval)); + right_perm_eval *= &right_tmp; } if left_perm_eval != right_perm_eval { return false; From c44a020de75c0f4ab04c0727d5a27a27abbb85f6 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 3 Sep 2020 00:45:03 +0800 Subject: [PATCH 09/38] Permutation checks in verifier --- src/plonk.rs | 3 +- src/plonk/circuit.rs | 14 ++++++++ src/plonk/prover.rs | 3 +- src/plonk/verifier.rs | 78 ++++++++++++++++++++++++++----------------- 4 files changed, 63 insertions(+), 35 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index adaf412..86b173d 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -47,8 +47,7 @@ pub struct Proof { permutation_product_commitments: Vec, permutation_product_evals: Vec, permutation_product_inv_evals: Vec, - permutation_evals: Vec, - advice_shifted_evals: Vec>>, + permutation_evals: Vec>, advice_evals: Vec, fixed_evals: Vec, h_evals: Vec, diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 26aca04..dae4e07 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -169,6 +169,7 @@ pub struct MetaCircuit { // another permutation between wires (B, C, D) which allows the same with D // instead of A. pub(crate) permutations: Vec>, + pub(crate) permutation_queries: Vec>>, } impl Default for MetaCircuit { @@ -184,6 +185,7 @@ impl Default for MetaCircuit { advice_queries: Vec::new(), rotations, permutations: Vec::new(), + permutation_queries: Vec::new(), } } } @@ -192,7 +194,19 @@ impl MetaCircuit { /// Add a permutation argument for some advice wires pub fn permutation(&mut self, wires: &[AdviceWire]) -> usize { let index = self.permutations.len(); + if index == 0 { + // no permutations + let point_idx = self.rotations.len(); + self.rotations.insert(Rotation(-1), PointIndex(point_idx)); + } self.permutations.push(wires.to_vec()); + + let mut queries = vec![]; + for wire in wires { + queries.push(self.query_advice(*wire, 0)); + } + self.permutation_queries.push(queries); + index } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index dd1ba5c..d15d567 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -405,8 +405,7 @@ impl Proof { permutation_product_commitments: vec![C::default(); params.n as usize], permutation_product_evals: vec![C::Scalar::one(); params.n as usize], permutation_product_inv_evals: vec![C::Scalar::one(); params.n as usize], - permutation_evals: vec![C::Scalar::one(); params.n as usize], - advice_shifted_evals, + permutation_evals: vec![vec![C::Scalar::one(); params.n as usize]], advice_evals, fixed_evals, h_evals, diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 635e180..a8347b5 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -36,36 +36,6 @@ impl Proof { } } - // For each permutation - for perm_idx in 0..srs.meta.permutations.len() { - // For each X in evaluation domain - for point_idx in 0..params.n as usize { - let point = omega_powers[point_idx]; - - let mut left_perm_eval = self.permutation_product_inv_evals[point_idx]; - let mut right_perm_eval = self.permutation_product_evals[point_idx]; - let mut cur_delta = C::Scalar::one(); - - for wire_idx in 0..srs.meta.permutations[perm_idx].len() { - // z(\omega^{-1} X) (a(X) + \beta X + \gamma) (b(X) + \delta \beta X + \gamma) (c(X) + \delta^2 \beta X + \gamma) - let left_tmp = &(self.advice_shifted_evals[perm_idx][wire_idx][point_idx] - + &(x_0 * &(cur_delta * &point))); - left_perm_eval *= &left_tmp; - - cur_delta *= &C::Scalar::DELTA; - - // z(X) (a(X) + \beta s_a(X) + \gamma) (b(X) + \beta s_b(X) + \gamma) (c(X) + \beta s_c(X) + \gamma) - let perm_eval = srs.permutation_polys[perm_idx][wire_idx][point_idx]; - let right_tmp = &(self.advice_shifted_evals[perm_idx][wire_idx][point_idx] - + &(x_0 * &perm_eval)); - right_perm_eval *= &right_tmp; - } - if left_perm_eval != right_perm_eval { - return false; - } - } - } - // Sample x_2 challenge, which keeps the gates linearly independent. let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); @@ -77,6 +47,7 @@ impl Proof { // Sample x_3 challenge, which is used to ensure the circuit is // satisfied with high probability. let x_3: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + let xn = x_3.pow(&[params.n as u64, 0, 0, 0]); // Hash together all the openings provided by the prover into a new // transcript on the scalar field. @@ -110,7 +81,52 @@ impl Proof { h_eval += &evaluation; } - let xn = x_3.pow(&[params.n as u64, 0, 0, 0]); + + // Evaluate permutation polynomial at first point + // l_0(X) * (1 - z(X)) = 0 + for eval in self.permutation_product_evals.iter() { + h_eval *= &x_2; + + let mut l0_eval = (C::Scalar::from_u64(params.n) * &(xn * &x_3 - &C::Scalar::one())) + * &(x_3 - &C::Scalar::one()).invert().unwrap(); + l0_eval *= &(C::Scalar::one() - &eval); + + h_eval += &l0_eval; + } + + // Evaluate permutation polynomial at subsequent points + for (perm_idx, queries) in srs.meta.permutation_queries.iter().enumerate() { + h_eval *= &x_2; + + // queries is a vector of polynomials + let evals: Vec = queries + .iter() + .map(|poly| { + poly.evaluate( + &|index| self.fixed_evals[index], + &|index| self.advice_evals[index], + &|a, b| a + &b, + &|a, b| a * &b, + &|a, scalar| a * &scalar, + ) + }) + .collect(); + + let mut left = self.permutation_product_inv_evals[perm_idx]; + let mut cur_delta = x_0 * &x_3; + for eval in evals.iter() { + left *= &(*eval + &cur_delta + &x_1); + cur_delta *= &C::Scalar::DELTA; + } + + let mut right = self.permutation_product_evals[perm_idx]; + for (perm_eval, eval) in self.permutation_evals[perm_idx].iter().zip(evals.iter()) { + right *= &(*eval + &(x_0 * perm_eval) + &x_1); + } + + h_eval += &left; + h_eval -= &right; + } // Compute the expected h(x) value let mut expected_h_eval = C::Scalar::zero(); From 160dabe9c5dfd9109b1dbb746aa638dd74e0295c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 2 Sep 2020 13:15:40 -0600 Subject: [PATCH 10/38] Cleanups for verifier of permutation argument --- src/plonk/circuit.rs | 14 +++-- src/plonk/domain.rs | 10 ++++ src/plonk/verifier.rs | 116 ++++++++++++++++++++++++++---------------- 3 files changed, 90 insertions(+), 50 deletions(-) diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index dae4e07..3c300fd 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -169,7 +169,7 @@ pub struct MetaCircuit { // another permutation between wires (B, C, D) which allows the same with D // instead of A. pub(crate) permutations: Vec>, - pub(crate) permutation_queries: Vec>>, + pub(crate) permutation_queries: Vec>, } impl Default for MetaCircuit { @@ -203,7 +203,7 @@ impl MetaCircuit { let mut queries = vec![]; for wire in wires { - queries.push(self.query_advice(*wire, 0)); + queries.push(self.query_advice_index(*wire, 0)); } self.permutation_queries.push(queries); @@ -225,8 +225,7 @@ impl MetaCircuit { Polynomial::Fixed(index) } - /// Query an advice wire at a relative position - pub fn query_advice(&mut self, wire: AdviceWire, at: i32) -> Polynomial { + fn query_advice_index(&mut self, wire: AdviceWire, at: i32) -> usize { let at = Rotation(at); { let len = self.rotations.len(); @@ -237,7 +236,12 @@ impl MetaCircuit { let index = self.advice_queries.len(); self.advice_queries.push((wire, at)); - Polynomial::Advice(index) + index + } + + /// Query an advice wire at a relative position + pub fn query_advice(&mut self, wire: AdviceWire, at: i32) -> Polynomial { + Polynomial::Advice(self.query_advice_index(wire, at)) } /// Create a new gate diff --git a/src/plonk/domain.rs b/src/plonk/domain.rs index 6ebfb5c..3203fb2 100644 --- a/src/plonk/domain.rs +++ b/src/plonk/domain.rs @@ -29,6 +29,7 @@ pub struct EvaluationDomain { ifft_divisor: G::Scalar, extended_ifft_divisor: G::Scalar, t_evaluations: Vec, + barycentric_weight: G::Scalar, } impl EvaluationDomain { @@ -99,6 +100,10 @@ impl EvaluationDomain { G::Scalar::batch_invert(&mut t_evaluations); } + // The barycentric weight of 1 over the evaluation domain + // 1 / \prod_{i != 0} (1 - omega^i) + let barycentric_weight = G::Scalar::from(n).invert().unwrap(); + EvaluationDomain { n, k, @@ -113,6 +118,7 @@ impl EvaluationDomain { ifft_divisor, extended_ifft_divisor, t_evaluations, + barycentric_weight, } } @@ -260,4 +266,8 @@ impl EvaluationDomain { } point } + + pub fn get_barycentric_weight(&self) -> G::Scalar { + self.barycentric_weight + } } diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index a8347b5..76cbd62 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -25,15 +25,9 @@ impl Proof { // Sample x_1 challenge let x_1: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - // Check permutations - // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] - let mut omega_powers = Vec::with_capacity(params.n as usize); - { - let mut cur = C::Scalar::one(); - for _ in 0..params.n { - omega_powers.push(cur); - cur *= &srs.domain.get_omega(); - } + // Hash each permutation product commitment + for c in &self.permutation_product_commitments { + hash_point(&mut transcript, c).expect("proof cannot contain points at infinity"); } // Sample x_2 challenge, which keeps the gates linearly independent. @@ -47,7 +41,7 @@ impl Proof { // Sample x_3 challenge, which is used to ensure the circuit is // satisfied with high probability. let x_3: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - let xn = x_3.pow(&[params.n as u64, 0, 0, 0]); + let x_3n = x_3.pow(&[params.n as u64, 0, 0, 0]); // Hash together all the openings provided by the prover into a new // transcript on the scalar field. @@ -58,6 +52,9 @@ impl Proof { .iter() .chain(self.fixed_evals.iter()) .chain(self.h_evals.iter()) + .chain(self.permutation_product_evals.iter()) + .chain(self.permutation_product_inv_evals.iter()) + .chain(self.permutation_evals.iter().flat_map(|evals| evals.iter())) { transcript_scalar.absorb(*eval); } @@ -82,46 +79,45 @@ impl Proof { h_eval += &evaluation; } - // Evaluate permutation polynomial at first point + // First element in each permutation product should be 1 // l_0(X) * (1 - z(X)) = 0 - for eval in self.permutation_product_evals.iter() { - h_eval *= &x_2; + { + // TODO: bubble this error up + let denominator = (x_3 - &C::Scalar::one()).invert().unwrap(); - let mut l0_eval = (C::Scalar::from_u64(params.n) * &(xn * &x_3 - &C::Scalar::one())) - * &(x_3 - &C::Scalar::one()).invert().unwrap(); - l0_eval *= &(C::Scalar::one() - &eval); + for eval in self.permutation_product_evals.iter() { + h_eval *= &x_2; - h_eval += &l0_eval; + let mut tmp = denominator; // 1 / (x_3 - 1) + tmp *= &(x_3n - &C::Scalar::one()); // (x_3^n - 1) / (x_3 - 1) + tmp *= &srs.domain.get_barycentric_weight(); // l_0(x_3) + tmp *= &(C::Scalar::one() - &eval); // l_0(X) * (1 - z(X)) + + h_eval += &tmp; + } } - // Evaluate permutation polynomial at subsequent points - for (perm_idx, queries) in srs.meta.permutation_queries.iter().enumerate() { + // z(X) \prod (p(X) + \beta s_i(X) + \gamma) - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) + for (permutation_index, queries) in srs.meta.permutation_queries.iter().enumerate() { h_eval *= &x_2; - // queries is a vector of polynomials - let evals: Vec = queries + let mut left = self.permutation_product_evals[permutation_index]; + for (advice_eval, permutation_eval) in queries .iter() - .map(|poly| { - poly.evaluate( - &|index| self.fixed_evals[index], - &|index| self.advice_evals[index], - &|a, b| a + &b, - &|a, b| a * &b, - &|a, scalar| a * &scalar, - ) - }) - .collect(); - - let mut left = self.permutation_product_inv_evals[perm_idx]; - let mut cur_delta = x_0 * &x_3; - for eval in evals.iter() { - left *= &(*eval + &cur_delta + &x_1); - cur_delta *= &C::Scalar::DELTA; + .map(|&query_index| self.advice_evals[query_index]) + .zip(self.permutation_evals[permutation_index].iter()) + { + left *= &(advice_eval + &(x_0 * permutation_eval) + &x_1); } - let mut right = self.permutation_product_evals[perm_idx]; - for (perm_eval, eval) in self.permutation_evals[perm_idx].iter().zip(evals.iter()) { - right *= &(*eval + &(x_0 * perm_eval) + &x_1); + let mut right = self.permutation_product_inv_evals[permutation_index]; + let mut current_delta = x_0; + for advice_eval in queries + .iter() + .map(|&query_index| self.advice_evals[query_index]) + { + right *= &(advice_eval + ¤t_delta + &x_1); + current_delta *= &C::Scalar::DELTA; } h_eval += &left; @@ -133,10 +129,10 @@ impl Proof { let mut cur = C::Scalar::one(); for eval in &self.h_evals { expected_h_eval += &(cur * eval); - cur *= &xn; + cur *= &x_3n; } - if h_eval != (expected_h_eval * &(xn - &C::Scalar::one())) { + if h_eval != (expected_h_eval * &(x_3n - &C::Scalar::one())) { return false; } @@ -182,8 +178,38 @@ impl Proof { } let current_index = (*srs.meta.rotations.get(&Rotation::default()).unwrap()).0; - for (h_commitment, h_eval) in self.h_commitments.iter().zip(self.h_evals.iter()) { - accumulate(current_index, *h_commitment, *h_eval); + for (commitment, eval) in self.h_commitments.iter().zip(self.h_evals.iter()) { + accumulate(current_index, *commitment, *eval); + } + + // Handle permutation arguments, if any exist + if !srs.meta.permutations.is_empty() { + // Open permutation product commitments at x_3 + for (commitment, eval) in self + .permutation_product_commitments + .iter() + .zip(self.permutation_product_evals.iter()) + { + accumulate(current_index, *commitment, *eval); + } + // Open permutation commitments for each permutation argument at x_3 + for (commitment, eval) in srs + .permutation_commitments + .iter() + .zip(self.permutation_evals.iter()) + .flat_map(|(commitments, evals)| commitments.iter().zip(evals.iter())) + { + accumulate(current_index, *commitment, *eval); + } + let current_index = (*srs.meta.rotations.get(&Rotation(-1)).unwrap()).0; + // Open permutation product commitments at \omega^{-1} x_3 + for (commitment, eval) in self + .permutation_product_commitments + .iter() + .zip(self.permutation_product_inv_evals.iter()) + { + accumulate(current_index, *commitment, *eval); + } } } @@ -210,7 +236,7 @@ impl Proof { // We can compute the expected f_eval at x_6 using the q_evals provided // by the prover and from x_5 let mut f_eval = C::Scalar::zero(); - for (&row, &point_index) in srs.meta.rotations.iter() { + for (&row, point_index) in srs.meta.rotations.iter() { let mut eval = self.q_evals[point_index.0]; let point = srs.domain.rotate_omega(x_3, row); From 1bc90c4fec493144bb6b38437df94a5236e47456 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 3 Sep 2020 12:25:55 +0800 Subject: [PATCH 11/38] Remove advice_shifted_evals from prover --- src/plonk/prover.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index d15d567..e47dc7e 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -127,23 +127,6 @@ impl Proof { } } - let mut advice_shifted_evals = - vec![ - vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_advice_wires]; - meta.permutations.len() - ]; - - for perm_idx in 0..meta.permutations.len() { - for wire_idx in 0..meta.permutations[perm_idx].len() { - for point_idx in 0..params.n { - let mut eval = - eval_polynomial(&advice_polys[wire_idx], omega_powers[point_idx as usize]); - eval += &x_1; - advice_shifted_evals[perm_idx][wire_idx as usize][point_idx as usize] = eval; - } - } - } - // Obtain challenge for keeping all separate gates linearly independent let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); From 441dcf0ecc2c28be7ad0302ffa9f42908be14f71 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 3 Sep 2020 12:29:38 +0800 Subject: [PATCH 12/38] Compute permutation_evals in prover --- src/plonk/prover.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index e47dc7e..b883416 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -222,6 +222,18 @@ impl Proof { }) .collect(); + let mut permutation_evals: Vec> = + Vec::with_capacity(meta.permutation_queries.len()); + for (permutation_idx, queries) in meta.permutation_queries.iter().enumerate() { + let query_evals: Vec = queries + .iter() + .map(|&query_index| { + eval_polynomial(&srs.permutation_polys[permutation_idx][query_index], x_3) + }) + .collect(); + permutation_evals.push(query_evals); + } + let h_evals: Vec<_> = h_pieces .iter() .map(|poly| eval_polynomial(poly, x_3)) @@ -241,6 +253,13 @@ impl Proof { transcript_scalar.absorb(*eval); } + // Hash each permutation evaluation + for permutation in permutation_evals.iter() { + for eval in permutation.iter() { + transcript_scalar.absorb(*eval); + } + } + // Hash each h(x) piece evaluation for eval in h_evals.iter() { transcript_scalar.absorb(*eval); @@ -388,7 +407,7 @@ impl Proof { permutation_product_commitments: vec![C::default(); params.n as usize], permutation_product_evals: vec![C::Scalar::one(); params.n as usize], permutation_product_inv_evals: vec![C::Scalar::one(); params.n as usize], - permutation_evals: vec![vec![C::Scalar::one(); params.n as usize]], + permutation_evals, advice_evals, fixed_evals, h_evals, From d601533bd7437fa4cdad6febce1bd38867f5e05d Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 3 Sep 2020 10:58:48 -0600 Subject: [PATCH 13/38] Commit to permutation product polynomial in the prover. --- src/plonk/prover.rs | 128 +++++++++++++++++++++++++++++++++++++++++++- src/plonk/srs.rs | 1 + 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index b883416..3f033de 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -71,6 +71,14 @@ impl Proof { let mut meta = MetaCircuit::default(); let config = ConcreteCircuit::configure(&mut meta); + // Get the largest permutation argument length in terms of the number of + // advice wires involved. + let mut largest_permutation_length = 0; + for permutation in &meta.permutations { + largest_permutation_length = + std::cmp::max(permutation.len(), largest_permutation_length); + } + let mut witness = WitnessCollection { advice: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_advice_wires], }; @@ -98,6 +106,7 @@ impl Proof { let advice_polys: Vec<_> = witness .advice + .clone() .into_iter() .map(|poly| domain.obtain_poly(poly)) .collect(); @@ -117,6 +126,7 @@ impl Proof { // Sample x_1 challenge let x_1: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + // TODO: maybe put this in SRS? // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] let mut omega_powers = Vec::with_capacity(params.n as usize); { @@ -127,6 +137,121 @@ impl Proof { } } + // Compute [omega_powers * \delta^0, omega_powers * \delta^1, ..., omega_powers * \delta^m] + let mut deltaomega = Vec::with_capacity(largest_permutation_length); + { + let mut cur = C::Scalar::one(); + for _ in 0..largest_permutation_length { + let mut omega_powers = omega_powers.clone(); + for o in &mut omega_powers { + *o *= &cur; + } + + deltaomega.push(omega_powers); + + cur *= &C::Scalar::DELTA; + } + } + + // Compute permutation product polynomial commitment + let mut permutation_product_commitments = vec![]; + let mut permutation_product_blinds = vec![]; + + // Iterate over each permutation + for (wires, permutations) in srs.meta.permutations.iter().zip(srs.permutation_polys) { + // Goal is to compute the fraction + // + // (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) / + // (p_j(\omega^i) + \beta s_j(\omega^i) + \gamma) + // + // where p_j(X) is the jth advice wire in this permutation, + // and i is the ith row of the wire. + let mut modified_advice = Vec::with_capacity(wires.len()); + + // Iterate over each wire of the permutation + for (wire, permutation) in wires.iter().zip(permutations.iter()) { + // Grab the advice wire's values from the witness + let mut tmp = witness.advice[wire.0].clone(); + + // For each row i, compute + // p_j(\omega^i) + \beta s_j(\omega^i) + \gamma + // where p_j(omega^i) = tmp[i] + for (tmp, permutation) in tmp.iter_mut().zip(permutation.iter()) { + *tmp += &(x_0 * permutation); + *tmp += &x_1; + } + modified_advice.push(tmp); + } + + // Batch invert to obtain the denominators for the permutation product + // polynomial + for v in &mut modified_advice { + C::Scalar::batch_invert(v); + } + + // Iterate over each wire again, this time finishing the computation + // of the entire fraction by computing the numerators + for ((wire, modified_advice), deltaomega) in wires + .iter() + .zip(modified_advice.iter_mut()) + .zip(deltaomega.iter()) + { + // For each row i, we compute + // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma + // for the jth wire of the permutation + for ((wire, modified_advice), deltaomega) in witness.advice[wire.0] + .iter_mut() + .zip(modified_advice.iter_mut()) + .zip(deltaomega.iter()) + { + let mut tmp = *deltaomega; // \delta^j \omega^i + tmp *= &x_0; // \delta^j \omega^i \beta + tmp += &x_1; // \delta^j \omega^i \beta + \gamma + tmp += wire; // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma + *modified_advice *= &tmp; + } + } + + // The modified_advice vector is a vector of vectors of fractions of + // the form + // + // (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) / + // (p_j(\omega^i) + \beta s_j(\omega^i) + \gamma) + // + // where j is the index into modified_advice, and i is the index + // into modified_advice[j], for the jth wire in the permutation + + // Compute the evaluations of the permutation product polynomial + // over our domain, starting with z[0] = 1 + let mut z = vec![C::Scalar::one()]; + for i in 1..(params.n as usize) { + let mut tmp = z[i - 1]; + + // Iterate over each wire's modified advice, where for the jth + // wire we obtain the fraction + // + // (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) / + // (p_j(\omega^i) + \beta s_j(\omega^i) + \gamma) + // + // where i is the row of the permutation product polynomial + // evaluation vector that we are currently evaluating. + for modified_advice in modified_advice.iter() { + tmp *= &modified_advice[i]; + } + z.push(tmp); + } + + let blind = C::Scalar::random(); + + permutation_product_commitments.push(params.commit_lagrange(&z, blind).to_affine()); + permutation_product_blinds.push(blind); + } + + // Hash each permutation product commitment + for c in &permutation_product_commitments { + hash_point(&mut transcript, c)?; + } + // Obtain challenge for keeping all separate gates linearly independent let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); @@ -206,6 +331,7 @@ impl Proof { } let x_3: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + let x_3n = x_3.pow(&[params.n as u64, 0, 0, 0]); // Evaluate polynomials at omega^i x_3 let advice_evals: Vec<_> = meta @@ -404,7 +530,7 @@ impl Proof { Ok(Proof { advice_commitments, h_commitments, - permutation_product_commitments: vec![C::default(); params.n as usize], + permutation_product_commitments, permutation_product_evals: vec![C::Scalar::one(); params.n as usize], permutation_product_inv_evals: vec![C::Scalar::one(); params.n as usize], permutation_evals, diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 8e7a609..739a70f 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -173,6 +173,7 @@ impl SRS { ); // Store permutation polynomial and precompute its coset evaluation polys.push(permutation_poly.clone()); + let permutation_poly = domain.obtain_poly(permutation_poly); cosets.push(domain.obtain_coset(permutation_poly, Rotation::default())); } permutation_commitments.push(commitments); From 4a88d52457c00e5b707785130d635c7e0faaad42 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 3 Sep 2020 14:21:13 -0600 Subject: [PATCH 14/38] Use the correct permutation values from the SRS. --- src/plonk.rs | 1 + src/plonk/prover.rs | 2 +- src/plonk/srs.rs | 11 ++++++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index 86b173d..1372157 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -33,6 +33,7 @@ pub struct SRS { fixed_polys: Vec>, fixed_cosets: Vec>, permutation_commitments: Vec>, + permutations: Vec>>, permutation_polys: Vec>>, permutation_cosets: Vec>>, meta: MetaCircuit, diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 3f033de..e2c15e4 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -158,7 +158,7 @@ impl Proof { let mut permutation_product_blinds = vec![]; // Iterate over each permutation - for (wires, permutations) in srs.meta.permutations.iter().zip(srs.permutation_polys) { + for (wires, permutations) in srs.meta.permutations.iter().zip(srs.permutations.iter()) { // Goal is to compute the fraction // // (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) / diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 739a70f..43b2199 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -146,10 +146,12 @@ impl SRS { // Compute permutation polynomials, convert to coset form and // pre-compute commitments for the SRS. let mut permutation_commitments = vec![]; + let mut permutations = vec![]; let mut permutation_polys = vec![]; let mut permutation_cosets = vec![]; for (permutation_index, permutation) in meta.permutations.iter().enumerate() { let mut commitments = vec![]; + let mut inner_permutations = vec![]; let mut polys = vec![]; let mut cosets = vec![]; for (i, _) in permutation.iter().enumerate() { @@ -172,11 +174,13 @@ impl SRS { .to_affine(), ); // Store permutation polynomial and precompute its coset evaluation - polys.push(permutation_poly.clone()); - let permutation_poly = domain.obtain_poly(permutation_poly); - cosets.push(domain.obtain_coset(permutation_poly, Rotation::default())); + inner_permutations.push(permutation_poly.clone()); + let poly = domain.obtain_poly(permutation_poly); + polys.push(poly.clone()); + cosets.push(domain.obtain_coset(poly, Rotation::default())); } permutation_commitments.push(commitments); + permutations.push(inner_permutations); permutation_polys.push(polys); permutation_cosets.push(cosets); } @@ -208,6 +212,7 @@ impl SRS { fixed_polys, fixed_cosets, permutation_commitments, + permutations, permutation_polys, permutation_cosets, meta, From 335b629724a072aeadb87cac02e971e37881e32e Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 3 Sep 2020 14:26:00 -0600 Subject: [PATCH 15/38] Avoid redundant wire queries by searching for an existing query. --- src/plonk/circuit.rs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 3c300fd..e6b71b1 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -210,19 +210,30 @@ impl MetaCircuit { index } - /// Query a fixed wire at a relative position - pub fn query_fixed(&mut self, wire: FixedWire, at: i32) -> Polynomial { + fn query_fixed_index(&mut self, wire: FixedWire, at: i32) -> usize { let at = Rotation(at); { let len = self.rotations.len(); self.rotations.entry(at).or_insert(PointIndex(len)); } - // TODO: check for existing query so we don't make redundant queries + // Return existing query, if it exists + for (index, fixed_query) in self.fixed_queries.iter().enumerate() { + if fixed_query == &(wire, at) { + return index; + } + } + + // Make a new query let index = self.fixed_queries.len(); self.fixed_queries.push((wire, at)); - Polynomial::Fixed(index) + index + } + + /// Query a fixed wire at a relative position + pub fn query_fixed(&mut self, wire: FixedWire, at: i32) -> Polynomial { + Polynomial::Fixed(self.query_fixed_index(wire, at)) } fn query_advice_index(&mut self, wire: AdviceWire, at: i32) -> usize { @@ -232,7 +243,14 @@ impl MetaCircuit { self.rotations.entry(at).or_insert(PointIndex(len)); } - // TODO: check for existing query so we don't make redundant queries + // Return existing query, if it exists + for (index, advice_query) in self.advice_queries.iter().enumerate() { + if advice_query == &(wire, at) { + return index; + } + } + + // Make a new query let index = self.advice_queries.len(); self.advice_queries.push((wire, at)); From 36d37002fe2580a613161591af76daeb762d7669 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 3 Sep 2020 14:28:22 -0600 Subject: [PATCH 16/38] Remove unneeded exponentiation of x_3 --- src/plonk/prover.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index e2c15e4..9a169ed 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -331,7 +331,6 @@ impl Proof { } let x_3: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - let x_3n = x_3.pow(&[params.n as u64, 0, 0, 0]); // Evaluate polynomials at omega^i x_3 let advice_evals: Vec<_> = meta From 6b9ea1dbebbe557d99682a53669129d281a8afdf Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 3 Sep 2020 14:31:57 -0600 Subject: [PATCH 17/38] Precompute deltaomega vector. --- src/plonk.rs | 1 + src/plonk/prover.rs | 29 +---------------------------- src/plonk/srs.rs | 1 + 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index 1372157..3ba41ae 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -29,6 +29,7 @@ use domain::EvaluationDomain; #[derive(Debug)] pub struct SRS { domain: EvaluationDomain, + deltaomega: Vec>, fixed_commitments: Vec, fixed_polys: Vec>, fixed_cosets: Vec>, diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 9a169ed..86c7bde 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -126,33 +126,6 @@ impl Proof { // Sample x_1 challenge let x_1: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - // TODO: maybe put this in SRS? - // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] - let mut omega_powers = Vec::with_capacity(params.n as usize); - { - let mut cur = C::Scalar::one(); - for _ in 0..params.n { - omega_powers.push(cur); - cur *= &srs.domain.get_omega(); - } - } - - // Compute [omega_powers * \delta^0, omega_powers * \delta^1, ..., omega_powers * \delta^m] - let mut deltaomega = Vec::with_capacity(largest_permutation_length); - { - let mut cur = C::Scalar::one(); - for _ in 0..largest_permutation_length { - let mut omega_powers = omega_powers.clone(); - for o in &mut omega_powers { - *o *= &cur; - } - - deltaomega.push(omega_powers); - - cur *= &C::Scalar::DELTA; - } - } - // Compute permutation product polynomial commitment let mut permutation_product_commitments = vec![]; let mut permutation_product_blinds = vec![]; @@ -194,7 +167,7 @@ impl Proof { for ((wire, modified_advice), deltaomega) in wires .iter() .zip(modified_advice.iter_mut()) - .zip(deltaomega.iter()) + .zip(srs.deltaomega.iter()) { // For each row i, we compute // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 43b2199..ff26ba4 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -208,6 +208,7 @@ impl SRS { Ok(SRS { domain, + deltaomega, fixed_commitments, fixed_polys, fixed_cosets, From 0651359cb88d547b56903d9f1a8b84259f8f237a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 3 Sep 2020 17:21:44 -0600 Subject: [PATCH 18/38] [WIP] Finish prover --- src/plonk.rs | 1 + src/plonk/prover.rs | 169 ++++++++++++++++++++++++++++++++++-------- src/plonk/srs.rs | 8 ++ src/plonk/verifier.rs | 3 +- 4 files changed, 149 insertions(+), 32 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index 3ba41ae..dd4f8f3 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -30,6 +30,7 @@ use domain::EvaluationDomain; pub struct SRS { domain: EvaluationDomain, deltaomega: Vec>, + l0: Vec, fixed_commitments: Vec, fixed_polys: Vec>, fixed_cosets: Vec>, diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 86c7bde..d5a67e6 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -127,6 +127,9 @@ impl Proof { let x_1: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); // Compute permutation product polynomial commitment + let mut permutation_product_polys = vec![]; + let mut permutation_product_cosets = vec![]; + let mut permutation_product_cosets_inv = vec![]; let mut permutation_product_commitments = vec![]; let mut permutation_product_blinds = vec![]; @@ -218,6 +221,9 @@ impl Proof { permutation_product_commitments.push(params.commit_lagrange(&z, blind).to_affine()); permutation_product_blinds.push(blind); + permutation_product_polys.push(z.clone()); + permutation_product_cosets.push(domain.obtain_coset(z.clone(), Rotation::default())); + permutation_product_cosets_inv.push(domain.obtain_coset(z, Rotation(-1))); } // Hash each permutation product commitment @@ -232,6 +238,7 @@ impl Proof { let mut h_poly = vec![C::Scalar::zero(); domain.coset_len()]; for (i, poly) in meta.gates.iter().enumerate() { if i != 0 { + // TODO: parallelize for h in h_poly.iter_mut() { *h *= &x_2; } @@ -271,12 +278,81 @@ impl Proof { if i == 0 { h_poly = evaluation; } else { + // TODO: parallelize for (h, e) in h_poly.iter_mut().zip(evaluation.into_iter()) { *h += &e; } } } + // l_0(X) * (1 - z(X)) = 0 + // => l_0(X) - l_0(X) z(X) = 0 + // We negate, so + // => l_0(X) z(X) - l_0(X) = 0 + // TODO: parallelize + for coset in permutation_product_cosets.iter() { + for h in h_poly.iter_mut() { + *h *= &x_2; + } + + let mut tmp = srs.l0.clone(); + for (t, c) in tmp.iter_mut().zip(coset.iter()) { + *t *= c; + } + for (t, c) in tmp.iter_mut().zip(srs.l0.iter()) { + *t -= c; + } + + for (h, e) in h_poly.iter_mut().zip(tmp.into_iter()) { + *h += &e; + } + } + + // z(X) \prod (p(X) + \beta s_i(X) + \gamma) - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) + for (permutation_index, queries) in srs.meta.permutation_queries.iter().enumerate() { + for h in h_poly.iter_mut() { + *h *= &x_2; + } + + let mut left = permutation_product_cosets[permutation_index].clone(); + for (advice, permutation) in queries + .iter() + .map(|&query_index| &advice_cosets[query_index]) + .zip(srs.permutation_cosets[permutation_index].iter()) + { + // TODO: parallelize + for ((left, advice), permutation) in + left.iter_mut().zip(advice.iter()).zip(permutation.iter()) + { + *left *= &(*advice + &(x_0 * permutation) + &x_1); + } + } + + let mut right = permutation_product_cosets_inv[permutation_index].clone(); + let mut current_delta = x_0 * &C::Scalar::ZETA; + let step = domain.get_omega(); + for advice in queries + .iter() + .map(|&query_index| &advice_cosets[query_index]) + { + // TODO: parallelize + let mut beta_term = current_delta; + for (right, advice) in right.iter_mut().zip(advice.iter()) { + *right *= &(*advice + &beta_term + &x_1); + beta_term *= &step; + } + current_delta *= &C::Scalar::DELTA; + } + + for (h, e) in h_poly.iter_mut().zip(left.into_iter()) { + *h += &e; + } + + for (h, e) in h_poly.iter_mut().zip(right.into_iter()) { + *h -= &e; + } + } + // Divide by t(X) = X^{params.n} - 1. let h_poly = domain.divide_by_vanishing_poly(h_poly); @@ -320,17 +396,26 @@ impl Proof { }) .collect(); - let mut permutation_evals: Vec> = - Vec::with_capacity(meta.permutation_queries.len()); - for (permutation_idx, queries) in meta.permutation_queries.iter().enumerate() { - let query_evals: Vec = queries - .iter() - .map(|&query_index| { - eval_polynomial(&srs.permutation_polys[permutation_idx][query_index], x_3) - }) - .collect(); - permutation_evals.push(query_evals); - } + let permutation_product_evals: Vec = permutation_product_polys + .iter() + .map(|poly| eval_polynomial(poly, x_3)) + .collect(); + + let permutation_product_inv_evals: Vec = permutation_product_polys + .iter() + .map(|poly| eval_polynomial(poly, srs.domain.get_omega_inv() * &x_3)) + .collect(); + + let permutation_evals: Vec> = srs + .permutation_polys + .iter() + .map(|polys| { + polys + .iter() + .map(|poly| eval_polynomial(poly, x_3)) + .collect() + }) + .collect(); let h_evals: Vec<_> = h_pieces .iter() @@ -342,24 +427,14 @@ impl Proof { let mut transcript_scalar = HScalar::init(C::Scalar::one()); // Hash each advice evaluation - for eval in advice_evals.iter() { - transcript_scalar.absorb(*eval); - } - - // Hash each fixed evaluation - for eval in fixed_evals.iter() { - transcript_scalar.absorb(*eval); - } - - // Hash each permutation evaluation - for permutation in permutation_evals.iter() { - for eval in permutation.iter() { - transcript_scalar.absorb(*eval); - } - } - - // Hash each h(x) piece evaluation - for eval in h_evals.iter() { + for eval in advice_evals + .iter() + .chain(fixed_evals.iter()) + .chain(h_evals.iter()) + .chain(permutation_product_evals.iter()) + .chain(permutation_product_inv_evals.iter()) + .chain(permutation_evals.iter().flat_map(|evals| evals.iter())) + { transcript_scalar.absorb(*eval); } @@ -427,6 +502,38 @@ impl Proof { { accumulate(current_index, &h_poly, *h_blind, *h_eval); } + + // Handle permutation arguments, if any exist + if !srs.meta.permutations.is_empty() { + // Open permutation product commitments at x_3 + for ((poly, blind), eval) in permutation_product_polys + .iter() + .zip(permutation_product_blinds.iter()) + .zip(permutation_product_evals.iter()) + { + accumulate(current_index, poly, *blind, *eval); + } + + // Open permutation polynomial commitments at x_3 + for (poly, eval) in srs + .permutation_polys + .iter() + .zip(permutation_evals.iter()) + .flat_map(|(polys, evals)| polys.iter().zip(evals.iter())) + { + accumulate(current_index, poly, C::Scalar::one(), *eval); + } + + let current_index = (*srs.meta.rotations.get(&Rotation(-1)).unwrap()).0; + // Open permutation product commitments at \omega^{-1} x_3 + for ((poly, blind), eval) in permutation_product_polys + .iter() + .zip(permutation_product_blinds.iter()) + .zip(permutation_product_inv_evals.iter()) + { + accumulate(current_index, poly, *blind, *eval); + } + } } let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); @@ -503,8 +610,8 @@ impl Proof { advice_commitments, h_commitments, permutation_product_commitments, - permutation_product_evals: vec![C::Scalar::one(); params.n as usize], - permutation_product_inv_evals: vec![C::Scalar::one(); params.n as usize], + permutation_product_evals, + permutation_product_inv_evals, permutation_evals, advice_evals, fixed_evals, diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index ff26ba4..d6c69ba 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -206,9 +206,17 @@ impl SRS { }) .collect(); + // Compute l_0(X) + // TODO: this can be done more efficiently + let mut l0 = vec![C::Scalar::zero(); params.n as usize]; + l0[0] = C::Scalar::one(); + let l0 = domain.obtain_poly(l0); + let l0 = domain.obtain_coset(l0, Rotation::default()); + Ok(SRS { domain, deltaomega, + l0, fixed_commitments, fixed_polys, fixed_cosets, diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 76cbd62..3dcb429 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -93,7 +93,8 @@ impl Proof { tmp *= &srs.domain.get_barycentric_weight(); // l_0(x_3) tmp *= &(C::Scalar::one() - &eval); // l_0(X) * (1 - z(X)) - h_eval += &tmp; + // We negate this (with no effect on the argument) to simplify the prover. + h_eval -= &tmp; } } From 06a4cfe13b0f7938784188222b8e7a6c58f141be Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 4 Sep 2020 04:49:59 -0600 Subject: [PATCH 19/38] Use extended omega for coset in prover. --- src/plonk/domain.rs | 4 ++++ src/plonk/prover.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/plonk/domain.rs b/src/plonk/domain.rs index 3203fb2..62ec88d 100644 --- a/src/plonk/domain.rs +++ b/src/plonk/domain.rs @@ -251,6 +251,10 @@ impl EvaluationDomain { self.omega } + pub fn get_extended_omega(&self) -> G::Scalar { + self.extended_omega + } + pub fn get_omega_inv(&self) -> G::Scalar { self.omega_inv } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index d5a67e6..b4f2e07 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -330,7 +330,7 @@ impl Proof { let mut right = permutation_product_cosets_inv[permutation_index].clone(); let mut current_delta = x_0 * &C::Scalar::ZETA; - let step = domain.get_omega(); + let step = domain.get_extended_omega(); for advice in queries .iter() .map(|&query_index| &advice_cosets[query_index]) From 10a4b4252cceb4819f1720b3e32ce11b35b5a370 Mon Sep 17 00:00:00 2001 From: ying tong Date: Fri, 4 Sep 2020 19:05:08 +0800 Subject: [PATCH 20/38] Fix current_delta initialisation in verifier --- src/plonk/verifier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 3dcb429..aaa7520 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -112,7 +112,7 @@ impl Proof { } let mut right = self.permutation_product_inv_evals[permutation_index]; - let mut current_delta = x_0; + let mut current_delta = x_0 * &x_3; for advice_eval in queries .iter() .map(|&query_index| self.advice_evals[query_index]) From c7c5cf4db60f75fda910f85503418a90bd484204 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 4 Sep 2020 13:51:50 +0800 Subject: [PATCH 21/38] Rename tmp variables --- src/plonk/prover.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index b4f2e07..374998c 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -134,7 +134,7 @@ impl Proof { let mut permutation_product_blinds = vec![]; // Iterate over each permutation - for (wires, permutations) in srs.meta.permutations.iter().zip(srs.permutations.iter()) { + for (wires, permuted_values) in srs.meta.permutations.iter().zip(srs.permutations.iter()) { // Goal is to compute the fraction // // (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) / @@ -145,18 +145,18 @@ impl Proof { let mut modified_advice = Vec::with_capacity(wires.len()); // Iterate over each wire of the permutation - for (wire, permutation) in wires.iter().zip(permutations.iter()) { + for (wire, permuted_wire_values) in wires.iter().zip(permuted_values.iter()) { // Grab the advice wire's values from the witness - let mut tmp = witness.advice[wire.0].clone(); + let mut tmp_advice_values = witness.advice[wire.0].clone(); // For each row i, compute // p_j(\omega^i) + \beta s_j(\omega^i) + \gamma // where p_j(omega^i) = tmp[i] - for (tmp, permutation) in tmp.iter_mut().zip(permutation.iter()) { - *tmp += &(x_0 * permutation); - *tmp += &x_1; + for (tmp_advice_value, permuted_advice_value) in tmp_advice_values.iter_mut().zip(permuted_wire_values.iter()) { + *tmp_advice_value += &(x_0 * permuted_advice_value); + *tmp_advice_value += &x_1; } - modified_advice.push(tmp); + modified_advice.push(tmp_advice_values); } // Batch invert to obtain the denominators for the permutation product @@ -175,7 +175,7 @@ impl Proof { // For each row i, we compute // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma // for the jth wire of the permutation - for ((wire, modified_advice), deltaomega) in witness.advice[wire.0] + for ((advice_value, modified_advice), deltaomega) in witness.advice[wire.0] .iter_mut() .zip(modified_advice.iter_mut()) .zip(deltaomega.iter()) @@ -183,7 +183,7 @@ impl Proof { let mut tmp = *deltaomega; // \delta^j \omega^i tmp *= &x_0; // \delta^j \omega^i \beta tmp += &x_1; // \delta^j \omega^i \beta + \gamma - tmp += wire; // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma + tmp += advice_value; // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma *modified_advice *= &tmp; } } @@ -200,8 +200,8 @@ impl Proof { // Compute the evaluations of the permutation product polynomial // over our domain, starting with z[0] = 1 let mut z = vec![C::Scalar::one()]; - for i in 1..(params.n as usize) { - let mut tmp = z[i - 1]; + for row in 1..(params.n as usize) { + let mut tmp = z[row - 1]; // Iterate over each wire's modified advice, where for the jth // wire we obtain the fraction @@ -211,8 +211,8 @@ impl Proof { // // where i is the row of the permutation product polynomial // evaluation vector that we are currently evaluating. - for modified_advice in modified_advice.iter() { - tmp *= &modified_advice[i]; + for wire_modified_advice in modified_advice.iter() { + tmp *= &wire_modified_advice[row]; } z.push(tmp); } From a128d5d9b3e001c37a30a5288f3a9e56b9ff00d5 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 4 Sep 2020 14:25:16 -0600 Subject: [PATCH 22/38] Undo unnecessarily complicated negation thing. --- src/plonk/prover.rs | 8 +------- src/plonk/verifier.rs | 3 +-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 374998c..65624f9 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -286,9 +286,6 @@ impl Proof { } // l_0(X) * (1 - z(X)) = 0 - // => l_0(X) - l_0(X) z(X) = 0 - // We negate, so - // => l_0(X) z(X) - l_0(X) = 0 // TODO: parallelize for coset in permutation_product_cosets.iter() { for h in h_poly.iter_mut() { @@ -297,10 +294,7 @@ impl Proof { let mut tmp = srs.l0.clone(); for (t, c) in tmp.iter_mut().zip(coset.iter()) { - *t *= c; - } - for (t, c) in tmp.iter_mut().zip(srs.l0.iter()) { - *t -= c; + *t *= &(C::Scalar::one() - c); } for (h, e) in h_poly.iter_mut().zip(tmp.into_iter()) { diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index aaa7520..3a03393 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -93,8 +93,7 @@ impl Proof { tmp *= &srs.domain.get_barycentric_weight(); // l_0(x_3) tmp *= &(C::Scalar::one() - &eval); // l_0(X) * (1 - z(X)) - // We negate this (with no effect on the argument) to simplify the prover. - h_eval -= &tmp; + h_eval += &tmp; } } From 114653f3664d7e608afcbc349e23e2fe060a015b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 4 Sep 2020 14:45:05 -0600 Subject: [PATCH 23/38] Fix indexing for permutation argument. --- src/plonk/prover.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 65624f9..dcf319e 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -303,15 +303,15 @@ impl Proof { } // z(X) \prod (p(X) + \beta s_i(X) + \gamma) - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) - for (permutation_index, queries) in srs.meta.permutation_queries.iter().enumerate() { + for (permutation_index, wires) in srs.meta.permutations.iter().enumerate() { for h in h_poly.iter_mut() { *h *= &x_2; } let mut left = permutation_product_cosets[permutation_index].clone(); - for (advice, permutation) in queries + for (advice, permutation) in wires .iter() - .map(|&query_index| &advice_cosets[query_index]) + .map(|&wire_index| &advice_cosets[wire_index.0]) .zip(srs.permutation_cosets[permutation_index].iter()) { // TODO: parallelize @@ -325,9 +325,9 @@ impl Proof { let mut right = permutation_product_cosets_inv[permutation_index].clone(); let mut current_delta = x_0 * &C::Scalar::ZETA; let step = domain.get_extended_omega(); - for advice in queries + for advice in wires .iter() - .map(|&query_index| &advice_cosets[query_index]) + .map(|&wire_index| &advice_cosets[wire_index.0]) { // TODO: parallelize let mut beta_term = current_delta; From da9c24bcfad1c4b837dfe331c4b776390b619a3e Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 5 Sep 2020 10:52:40 -0600 Subject: [PATCH 24/38] Obtain permutation product polynomial correctly. --- src/plonk/prover.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index dcf319e..9a3294b 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -221,6 +221,7 @@ impl Proof { permutation_product_commitments.push(params.commit_lagrange(&z, blind).to_affine()); permutation_product_blinds.push(blind); + let z = domain.obtain_poly(z); permutation_product_polys.push(z.clone()); permutation_product_cosets.push(domain.obtain_coset(z.clone(), Rotation::default())); permutation_product_cosets_inv.push(domain.obtain_coset(z, Rotation(-1))); From 869aba389a52002587f017ba20848b07759a2804 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 5 Sep 2020 11:40:25 -0600 Subject: [PATCH 25/38] Cleanups --- src/plonk.rs | 10 ++++++---- src/plonk/prover.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index dd4f8f3..369c30e 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -314,9 +314,11 @@ fn test_proving() { // Initialize the SRS let srs = SRS::generate(¶ms, &empty_circuit).expect("SRS generation should not fail"); - // Create a proof - let proof = Proof::create::, DummyHash, _>(¶ms, &srs, &circuit) - .expect("proof generation should not fail"); + for _ in 0..100 { + // Create a proof + let proof = Proof::create::, DummyHash, _>(¶ms, &srs, &circuit) + .expect("proof generation should not fail"); - assert!(proof.verify::, DummyHash>(¶ms, &srs)); + assert!(proof.verify::, DummyHash>(¶ms, &srs)); + } } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 9a3294b..861fa8c 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -152,7 +152,10 @@ impl Proof { // For each row i, compute // p_j(\omega^i) + \beta s_j(\omega^i) + \gamma // where p_j(omega^i) = tmp[i] - for (tmp_advice_value, permuted_advice_value) in tmp_advice_values.iter_mut().zip(permuted_wire_values.iter()) { + for (tmp_advice_value, permuted_advice_value) in tmp_advice_values + .iter_mut() + .zip(permuted_wire_values.iter()) + { *tmp_advice_value += &(x_0 * permuted_advice_value); *tmp_advice_value += &x_1; } @@ -326,10 +329,7 @@ impl Proof { let mut right = permutation_product_cosets_inv[permutation_index].clone(); let mut current_delta = x_0 * &C::Scalar::ZETA; let step = domain.get_extended_omega(); - for advice in wires - .iter() - .map(|&wire_index| &advice_cosets[wire_index.0]) - { + for advice in wires.iter().map(|&wire_index| &advice_cosets[wire_index.0]) { // TODO: parallelize let mut beta_term = current_delta; for (right, advice) in right.iter_mut().zip(advice.iter()) { @@ -398,7 +398,7 @@ impl Proof { let permutation_product_inv_evals: Vec = permutation_product_polys .iter() - .map(|poly| eval_polynomial(poly, srs.domain.get_omega_inv() * &x_3)) + .map(|poly| eval_polynomial(poly, domain.rotate_omega(x_3, Rotation(-1)))) .collect(); let permutation_evals: Vec> = srs From d7132404bac687608e3266ad6bf4d5a616cfc463 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 5 Sep 2020 12:08:56 -0600 Subject: [PATCH 26/38] Index into q_evals consistently between prover and verifier. --- src/plonk/circuit.rs | 12 ++++++------ src/plonk/domain.rs | 2 +- src/plonk/prover.rs | 8 +++----- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index e6b71b1..eab4517 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -1,6 +1,6 @@ use core::cmp::max; use core::ops::{Add, Mul}; -use std::collections::HashMap; +use std::collections::BTreeMap; use super::Error; use crate::arithmetic::Field; @@ -160,7 +160,7 @@ pub struct MetaCircuit { pub(crate) fixed_queries: Vec<(FixedWire, Rotation)>, // Mapping from a witness vector rotation to the index in the point vector. - pub(crate) rotations: HashMap, + pub(crate) rotations: BTreeMap, // Vector of permutation arguments, where each corresponds to a set of wires // that are involved in a permutation argument. As an example, we could have @@ -174,7 +174,7 @@ pub struct MetaCircuit { impl Default for MetaCircuit { fn default() -> MetaCircuit { - let mut rotations = HashMap::new(); + let mut rotations = BTreeMap::new(); rotations.insert(Rotation::default(), PointIndex(0)); MetaCircuit { @@ -195,9 +195,9 @@ impl MetaCircuit { pub fn permutation(&mut self, wires: &[AdviceWire]) -> usize { let index = self.permutations.len(); if index == 0 { - // no permutations - let point_idx = self.rotations.len(); - self.rotations.insert(Rotation(-1), PointIndex(point_idx)); + let at = Rotation(-1); + let len = self.rotations.len(); + self.rotations.entry(at).or_insert(PointIndex(len)); } self.permutations.push(wires.to_vec()); diff --git a/src/plonk/domain.rs b/src/plonk/domain.rs index 62ec88d..81b74b6 100644 --- a/src/plonk/domain.rs +++ b/src/plonk/domain.rs @@ -2,7 +2,7 @@ use crate::arithmetic::{best_fft, parallelize, Field, Group}; /// Describes a relative location in the evaluation domain; applying a rotation /// by i will rotate the vector in the evaluation domain by i. -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd)] pub struct Rotation(pub i32); impl Default for Rotation { diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 861fa8c..03939fd 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -562,13 +562,11 @@ impl Proof { let x_6: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - let mut q_evals = vec![]; + let mut q_evals = vec![C::Scalar::zero(); meta.rotations.len()]; for (_, &point_index) in meta.rotations.iter() { - q_evals.push(eval_polynomial( - &q_polys[point_index.0].as_ref().unwrap(), - x_6, - )); + q_evals[point_index.0] = + eval_polynomial(&q_polys[point_index.0].as_ref().unwrap(), x_6); } for eval in q_evals.iter() { From 937861c0b84568006848184856baf16ea99c98da Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 5 Sep 2020 12:56:45 -0600 Subject: [PATCH 27/38] Add implementation of daira's algorithm for copy constraint enforcement. --- src/plonk.rs | 7 ++--- src/plonk/srs.rs | 70 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index 369c30e..d890d3f 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -282,7 +282,7 @@ fn test_proving() { for _ in 0..10 { let mut a_squared = None; - let (_, _, c0) = cs.raw_multiply(|| { + let (a0, _, c0) = cs.raw_multiply(|| { a_squared = self.a.map(|a| a.square()); Ok(( self.a.ok_or(Error::SynthesisError)?, @@ -290,7 +290,7 @@ fn test_proving() { a_squared.ok_or(Error::SynthesisError)?, )) })?; - let (_, b1, _) = cs.raw_add(|| { + let (a1, b1, _) = cs.raw_add(|| { let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2)); Ok(( self.a.ok_or(Error::SynthesisError)?, @@ -298,6 +298,7 @@ fn test_proving() { fin.ok_or(Error::SynthesisError)?, )) })?; + cs.copy(a0, a1)?; cs.copy(b1, c0)?; } @@ -306,7 +307,7 @@ fn test_proving() { } let circuit: MyCircuit = MyCircuit { - a: Some((-Fp::from_u64(2) + Fp::ROOT_OF_UNITY).pow(&[100, 0, 0, 0])), + a: Some(Fp::random()), }; let empty_circuit: MyCircuit = MyCircuit { a: None }; diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index d6c69ba..9b2b997 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -15,7 +15,9 @@ impl SRS { ) -> Result { struct Assembly { fixed: Vec>, - copy: Vec>>, + mapping: Vec>>, + aux: Vec>>, + sizes: Vec>>, } impl ConstraintSystem for Assembly { @@ -52,25 +54,45 @@ impl SRS { right_wire: usize, right_row: usize, ) -> Result<(), Error> { - let left: (usize, usize) = *self.copy[permutation] - .get_mut(left_wire) - .and_then(|wire| wire.get_mut(left_row)) - .ok_or(Error::BoundsFailure)?; - - let right: (usize, usize) = *self.copy[permutation] - .get_mut(right_wire) - .and_then(|wire| wire.get_mut(right_row)) - .ok_or(Error::BoundsFailure)?; - - if left == (left_wire, left_row) || right == (right_wire, right_row) { - // Don't perform the copy constraint because it will undo - // the effect of the permutation. - } else { - self.copy[permutation][left_wire][left_row] = right; - - self.copy[permutation][right_wire][right_row] = left; + // Check bounds first + if permutation >= self.mapping.len() + || left_wire >= self.mapping[permutation].len() + || left_row >= self.mapping[permutation][left_wire].len() + || right_wire >= self.mapping[permutation].len() + || right_row >= self.mapping[permutation][right_wire].len() + { + return Err(Error::BoundsFailure); } + let mut left_cycle = self.aux[permutation][left_wire][left_row]; + let mut right_cycle = self.aux[permutation][right_wire][right_row]; + + if left_cycle == right_cycle { + return Ok(()); + } + + if self.sizes[permutation][left_cycle.0][left_cycle.1] + < self.sizes[permutation][right_cycle.0][right_cycle.1] + { + std::mem::swap(&mut left_cycle, &mut right_cycle); + } + + self.sizes[permutation][left_cycle.0][left_cycle.1] += + self.sizes[permutation][right_cycle.0][right_cycle.1]; + let mut i = right_cycle; + loop { + self.aux[permutation][i.0][i.1] = left_cycle; + i = self.mapping[permutation][i.0][i.1]; + if i == right_cycle { + break; + } + } + + let tmp = self.mapping[permutation][left_wire][left_row]; + self.mapping[permutation][left_wire][left_row] = + self.mapping[permutation][right_wire][right_row]; + self.mapping[permutation][right_wire][right_row] = tmp; + Ok(()) } } @@ -126,7 +148,9 @@ impl SRS { let mut assembly: Assembly = Assembly { fixed: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_fixed_wires], - copy: vec![], + mapping: vec![], + aux: vec![], + sizes: vec![], }; // Initialize the copy vector to keep track of copy constraints in all @@ -137,7 +161,11 @@ impl SRS { // Computes [(i, 0), (i, 1), ..., (i, n - 1)] wires.push((0..params.n).map(|j| (i, j as usize)).collect()); } - assembly.copy.push(wires); + assembly.mapping.push(wires.clone()); + assembly.aux.push(wires); + assembly + .sizes + .push(vec![vec![1usize; params.n as usize]; permutation.len()]); } // Synthesize the circuit to obtain SRS @@ -162,7 +190,7 @@ impl SRS { // assembly.copy[permutation_index] is indexed by wire // i, and then indexed by row j, obtaining the index of // the permuted value in deltaomega. - let (permuted_i, permuted_j) = assembly.copy[permutation_index][i][j]; + let (permuted_i, permuted_j) = assembly.mapping[permutation_index][i][j]; deltaomega[permuted_i][permuted_j] }) .collect(); From 965362c1f5f75818868e28421c786fca9021b4a4 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 5 Sep 2020 14:44:13 -0600 Subject: [PATCH 28/38] Don't precompute deltaomega; inline its computation. --- src/plonk.rs | 1 - src/plonk/prover.rs | 14 ++++++-------- src/plonk/srs.rs | 1 - 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index d890d3f..0aa9cb1 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -29,7 +29,6 @@ use domain::EvaluationDomain; #[derive(Debug)] pub struct SRS { domain: EvaluationDomain, - deltaomega: Vec>, l0: Vec, fixed_commitments: Vec, fixed_polys: Vec>, diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 03939fd..609a4ed 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -170,25 +170,23 @@ impl Proof { // Iterate over each wire again, this time finishing the computation // of the entire fraction by computing the numerators - for ((wire, modified_advice), deltaomega) in wires - .iter() - .zip(modified_advice.iter_mut()) - .zip(srs.deltaomega.iter()) - { + let mut deltaomega = C::Scalar::one(); + for (wire, modified_advice) in wires.iter().zip(modified_advice.iter_mut()) { // For each row i, we compute // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma // for the jth wire of the permutation - for ((advice_value, modified_advice), deltaomega) in witness.advice[wire.0] + for (advice_value, modified_advice) in witness.advice[wire.0] .iter_mut() .zip(modified_advice.iter_mut()) - .zip(deltaomega.iter()) { - let mut tmp = *deltaomega; // \delta^j \omega^i + let mut tmp = deltaomega; // \delta^j \omega^i tmp *= &x_0; // \delta^j \omega^i \beta tmp += &x_1; // \delta^j \omega^i \beta + \gamma tmp += advice_value; // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma *modified_advice *= &tmp; + deltaomega *= &domain.get_omega(); } + deltaomega *= &C::Scalar::DELTA; } // The modified_advice vector is a vector of vectors of fractions of diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 9b2b997..4d73417 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -243,7 +243,6 @@ impl SRS { Ok(SRS { domain, - deltaomega, l0, fixed_commitments, fixed_polys, From 503939db057f92e79b550de69f4b74e68bf5064e Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sun, 6 Sep 2020 06:34:29 +0800 Subject: [PATCH 29/38] Minor cleanups --- src/plonk.rs | 8 ++++---- src/plonk/prover.rs | 14 ++++++-------- src/plonk/srs.rs | 4 ++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index 0aa9cb1..3df2689 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -213,14 +213,14 @@ fn test_proving() { Variable(self.config.c, index), )) } - fn copy(&mut self, a: Variable, b: Variable) -> Result<(), Error> { - let left_wire = match a.0 { + fn copy(&mut self, left: Variable, right: Variable) -> Result<(), Error> { + let left_wire = match left.0 { x if x == self.config.a => 0, x if x == self.config.b => 1, x if x == self.config.c => 2, _ => unreachable!(), }; - let right_wire = match b.0 { + let right_wire = match right.0 { x if x == self.config.a => 0, x if x == self.config.b => 1, x if x == self.config.c => 2, @@ -228,7 +228,7 @@ fn test_proving() { }; self.cs - .copy(self.config.perm, left_wire, a.1, right_wire, b.1) + .copy(self.config.perm, left_wire, left.1, right_wire, right.1) } } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 609a4ed..1a1b709 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -156,8 +156,8 @@ impl Proof { .iter_mut() .zip(permuted_wire_values.iter()) { - *tmp_advice_value += &(x_0 * permuted_advice_value); - *tmp_advice_value += &x_1; + *tmp_advice_value += &(x_0 * permuted_advice_value); // p_j(\omega^i) + \beta s_j(\omega^i) + *tmp_advice_value += &x_1; // p_j(\omega^i) + \beta s_j(\omega^i) + \gamma } modified_advice.push(tmp_advice_values); } @@ -175,15 +175,13 @@ impl Proof { // For each row i, we compute // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma // for the jth wire of the permutation - for (advice_value, modified_advice) in witness.advice[wire.0] + for (tmp_advice_value, modified_advice) in witness.advice[wire.0] .iter_mut() .zip(modified_advice.iter_mut()) { - let mut tmp = deltaomega; // \delta^j \omega^i - tmp *= &x_0; // \delta^j \omega^i \beta - tmp += &x_1; // \delta^j \omega^i \beta + \gamma - tmp += advice_value; // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma - *modified_advice *= &tmp; + *tmp_advice_value += &(deltaomega * &x_0); // p_j(\omega^i) + \delta^j \omega^i \beta + *tmp_advice_value += &x_1; // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma + *modified_advice *= tmp_advice_value; deltaomega *= &domain.get_omega(); } deltaomega *= &C::Scalar::DELTA; diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 4d73417..86567c1 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -157,7 +157,7 @@ impl SRS { // the permutation arguments. for permutation in &meta.permutations { let mut wires = vec![]; - for (i, _) in permutation.iter().enumerate() { + for i in 0..permutation.len() { // Computes [(i, 0), (i, 1), ..., (i, n - 1)] wires.push((0..params.n).map(|j| (i, j as usize)).collect()); } @@ -182,7 +182,7 @@ impl SRS { let mut inner_permutations = vec![]; let mut polys = vec![]; let mut cosets = vec![]; - for (i, _) in permutation.iter().enumerate() { + for i in 0..permutation.len() { // Computes the permutation polynomial based on the permutation // description in the assembly. let permutation_poly: Vec<_> = (0..params.n as usize) From 624eb6a421981b9f3679d2e08483b70d5ccc1966 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 6 Sep 2020 11:33:09 -0600 Subject: [PATCH 30/38] Remove unnecessary computation of permutation length in prover. --- src/plonk/prover.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 1a1b709..42367b4 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -71,14 +71,6 @@ impl Proof { let mut meta = MetaCircuit::default(); let config = ConcreteCircuit::configure(&mut meta); - // Get the largest permutation argument length in terms of the number of - // advice wires involved. - let mut largest_permutation_length = 0; - for permutation in &meta.permutations { - largest_permutation_length = - std::cmp::max(permutation.len(), largest_permutation_length); - } - let mut witness = WitnessCollection { advice: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_advice_wires], }; From 45491a21c9ce9aaef2eb714d7694e71c83f03462 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 6 Sep 2020 11:33:47 -0600 Subject: [PATCH 31/38] Add .vscode to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6936990..173b951 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk Cargo.lock +.vscode From ff8f9eb20ecc04893f4d75705c4a1cab84ec3ea3 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 6 Sep 2020 12:24:55 -0600 Subject: [PATCH 32/38] Reduce number of inversions by batch inverting when possible. --- src/arithmetic.rs | 37 +++++++++++++++++++++++++++++++ src/plonk/prover.rs | 54 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/src/arithmetic.rs b/src/arithmetic.rs index 49f13c8..312cfe5 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -32,6 +32,43 @@ pub trait Group: Copy + Clone + Send + Sync + 'static { fn group_scale(&mut self, by: &Self::Scalar); } +/// Extension trait for iterators over mutable field elements which allows those +/// field elements to be inverted in a batch. +pub trait BatchInvert { + /// Consume this iterator and invert each field element (when nonzero), + /// returning the inverse of all nonzero field elements. + fn batch_invert(self) -> F; +} + +impl<'a, F, I> BatchInvert for I +where + F: Field, + I: IntoIterator, +{ + fn batch_invert(self) -> F { + let mut acc = F::one(); + let mut iter = self.into_iter(); + let mut tmp = Vec::with_capacity(iter.size_hint().0); + while let Some(p) = iter.next() { + let q = *p; + tmp.push((acc, p)); + acc = F::conditional_select(&(acc * q), &acc, q.is_zero()); + } + acc = acc.invert().unwrap(); + let allinv = acc; + + for (tmp, p) in tmp.into_iter().rev() { + let skip = p.is_zero(); + + let tmp = tmp * acc; + acc = F::conditional_select(&(acc * *p), &acc, skip); + *p = F::conditional_select(&tmp, p, skip); + } + + allinv + } +} + /// This is a 128-bit verifier challenge. #[derive(Copy, Clone, Debug)] pub struct Challenge(pub(crate) u128); diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 42367b4..5eb4921 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -4,8 +4,8 @@ use super::{ hash_point, Error, Proof, SRS, }; use crate::arithmetic::{ - eval_polynomial, get_challenge_scalar, kate_division, parallelize, Challenge, Curve, - CurveAffine, Field, + eval_polynomial, get_challenge_scalar, kate_division, parallelize, BatchInvert, Challenge, + Curve, CurveAffine, Field, }; use crate::polycommit::Params; use crate::transcript::Hasher; @@ -83,12 +83,16 @@ impl Proof { // Compute commitments to advice wire polynomials let advice_blinds: Vec<_> = witness.advice.iter().map(|_| C::Scalar::random()).collect(); - let advice_commitments = witness + let advice_commitments_projective: Vec<_> = witness .advice .iter() .zip(advice_blinds.iter()) - .map(|(poly, blind)| params.commit_lagrange(poly, *blind).to_affine()) + .map(|(poly, blind)| params.commit_lagrange(poly, *blind)) .collect(); + let mut advice_commitments = vec![C::zero(); advice_commitments_projective.len()]; + C::Projective::batch_to_affine(&advice_commitments_projective, &mut advice_commitments); + let advice_commitments = advice_commitments; + drop(advice_commitments_projective); for commitment in &advice_commitments { hash_point(&mut transcript, commitment)?; @@ -122,10 +126,11 @@ impl Proof { let mut permutation_product_polys = vec![]; let mut permutation_product_cosets = vec![]; let mut permutation_product_cosets_inv = vec![]; - let mut permutation_product_commitments = vec![]; + let mut permutation_product_commitments_projective = vec![]; let mut permutation_product_blinds = vec![]; // Iterate over each permutation + let mut permutation_modified_advice = vec![]; for (wires, permuted_values) in srs.meta.permutations.iter().zip(srs.permutations.iter()) { // Goal is to compute the fraction // @@ -154,12 +159,23 @@ impl Proof { modified_advice.push(tmp_advice_values); } - // Batch invert to obtain the denominators for the permutation product - // polynomial - for v in &mut modified_advice { - C::Scalar::batch_invert(v); - } + permutation_modified_advice.push(modified_advice); + } + // Batch invert to obtain the denominators for the permutation product + // polynomials + permutation_modified_advice + .iter_mut() + .flat_map(|v| v.iter_mut()) + .flat_map(|v| v.iter_mut()) + .batch_invert(); + + for (wires, mut modified_advice) in srs + .meta + .permutations + .iter() + .zip(permutation_modified_advice.into_iter()) + { // Iterate over each wire again, this time finishing the computation // of the entire fraction by computing the numerators let mut deltaomega = C::Scalar::one(); @@ -210,13 +226,21 @@ impl Proof { let blind = C::Scalar::random(); - permutation_product_commitments.push(params.commit_lagrange(&z, blind).to_affine()); + permutation_product_commitments_projective.push(params.commit_lagrange(&z, blind)); permutation_product_blinds.push(blind); let z = domain.obtain_poly(z); permutation_product_polys.push(z.clone()); permutation_product_cosets.push(domain.obtain_coset(z.clone(), Rotation::default())); permutation_product_cosets_inv.push(domain.obtain_coset(z, Rotation(-1))); } + let mut permutation_product_commitments = + vec![C::zero(); permutation_product_commitments_projective.len()]; + C::Projective::batch_to_affine( + &permutation_product_commitments_projective, + &mut permutation_product_commitments, + ); + let permutation_product_commitments = permutation_product_commitments; + drop(permutation_product_commitments_projective); // Hash each permutation product commitment for c in &permutation_product_commitments { @@ -351,11 +375,15 @@ impl Proof { let h_blinds: Vec<_> = h_pieces.iter().map(|_| C::Scalar::random()).collect(); // Compute commitments to each h(X) piece - let h_commitments: Vec<_> = h_pieces + let h_commitments_projective: Vec<_> = h_pieces .iter() .zip(h_blinds.iter()) - .map(|(h_piece, blind)| params.commit(&h_piece, *blind).to_affine()) + .map(|(h_piece, blind)| params.commit(&h_piece, *blind)) .collect(); + let mut h_commitments = vec![C::zero(); h_commitments_projective.len()]; + C::Projective::batch_to_affine(&h_commitments_projective, &mut h_commitments); + let h_commitments = h_commitments; + drop(h_commitments_projective); // Hash each h(X) piece for c in h_commitments.iter() { From 3157fdd7d05b7738bb1752d2774e573deec50bf5 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 6 Sep 2020 12:44:36 -0600 Subject: [PATCH 33/38] Batch inversions during domain setup. --- src/plonk/domain.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/plonk/domain.rs b/src/plonk/domain.rs index 81b74b6..52a79b0 100644 --- a/src/plonk/domain.rs +++ b/src/plonk/domain.rs @@ -1,4 +1,4 @@ -use crate::arithmetic::{best_fft, parallelize, Field, Group}; +use crate::arithmetic::{best_fft, parallelize, BatchInvert, Field, Group}; /// Describes a relative location in the evaluation domain; applying a rotation /// by i will rotate the vector in the evaluation domain by i. @@ -57,24 +57,20 @@ impl EvaluationDomain { extended_omega = extended_omega.square(); } let extended_omega = extended_omega; // 2^{j+k}'th root of unity - let extended_omega_inv = extended_omega.invert().unwrap(); + let mut extended_omega_inv = extended_omega; // Inversion computed later let mut omega = extended_omega; for _ in k..extended_k { omega = omega.square(); } let omega = omega; // 2^{k}'th root of unity - let omega_inv = omega.invert().unwrap(); + let mut omega_inv = omega; // Inversion computed later // We use zeta here because we know it generates a coset, and it's available // already. let g_coset = G::Scalar::ZETA; let g_coset_inv = g_coset.square(); - // TODO: merge these inversions together with t_evaluations batch inversion? - let ifft_divisor = G::Scalar::from_u64(1 << k).invert().unwrap(); - let extended_ifft_divisor = G::Scalar::from_u64(1 << extended_k).invert().unwrap(); - let mut t_evaluations = Vec::with_capacity(1 << (extended_k - k)); { // Compute the evaluations of t(X) in the coset evaluation domain. @@ -97,12 +93,25 @@ impl EvaluationDomain { } // Invert, because we're dividing by this polynomial. - G::Scalar::batch_invert(&mut t_evaluations); + // We invert in a batch, below. } + let mut ifft_divisor = G::Scalar::from_u64(1 << k); // Inversion computed later + let mut extended_ifft_divisor = G::Scalar::from_u64(1 << extended_k); // Inversion computed later + // The barycentric weight of 1 over the evaluation domain // 1 / \prod_{i != 0} (1 - omega^i) - let barycentric_weight = G::Scalar::from(n).invert().unwrap(); + let mut barycentric_weight = G::Scalar::from(n); // Inversion computed later + + // Compute batch inversion + t_evaluations + .iter_mut() + .chain(Some(&mut ifft_divisor)) + .chain(Some(&mut extended_ifft_divisor)) + .chain(Some(&mut barycentric_weight)) + .chain(Some(&mut extended_omega_inv)) + .chain(Some(&mut omega_inv)) + .batch_invert(); EvaluationDomain { n, From e37d0c946be5896f313bb16223a441308470fd24 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 6 Sep 2020 13:40:06 -0600 Subject: [PATCH 34/38] Add parallelism in various locations in the prover. --- src/plonk/prover.rs | 93 +++++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 5eb4921..db8d59e 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -254,10 +254,11 @@ impl Proof { let mut h_poly = vec![C::Scalar::zero(); domain.coset_len()]; for (i, poly) in meta.gates.iter().enumerate() { if i != 0 { - // TODO: parallelize - for h in h_poly.iter_mut() { - *h *= &x_2; - } + parallelize(&mut h_poly, |a, _| { + for a in a.iter_mut() { + *a *= &x_2; + } + }); } let evaluation: Vec = poly.evaluate( @@ -294,35 +295,36 @@ impl Proof { if i == 0 { h_poly = evaluation; } else { - // TODO: parallelize - for (h, e) in h_poly.iter_mut().zip(evaluation.into_iter()) { - *h += &e; - } + parallelize(&mut h_poly, |a, start| { + for (a, b) in a.iter_mut().zip(evaluation[start..].iter()) { + *a += b; + } + }); } } // l_0(X) * (1 - z(X)) = 0 // TODO: parallelize for coset in permutation_product_cosets.iter() { - for h in h_poly.iter_mut() { - *h *= &x_2; - } - - let mut tmp = srs.l0.clone(); - for (t, c) in tmp.iter_mut().zip(coset.iter()) { - *t *= &(C::Scalar::one() - c); - } - - for (h, e) in h_poly.iter_mut().zip(tmp.into_iter()) { - *h += &e; - } + parallelize(&mut h_poly, |h, start| { + for ((h, c), l0) in h + .iter_mut() + .zip(coset[start..].iter()) + .zip(srs.l0[start..].iter()) + { + *h *= &x_2; + *h += &(*l0 * &(C::Scalar::one() - c)); + } + }); } // z(X) \prod (p(X) + \beta s_i(X) + \gamma) - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) for (permutation_index, wires) in srs.meta.permutations.iter().enumerate() { - for h in h_poly.iter_mut() { - *h *= &x_2; - } + parallelize(&mut h_poly, |a, _| { + for a in a.iter_mut() { + *a *= &x_2; + } + }); let mut left = permutation_product_cosets[permutation_index].clone(); for (advice, permutation) in wires @@ -330,34 +332,41 @@ impl Proof { .map(|&wire_index| &advice_cosets[wire_index.0]) .zip(srs.permutation_cosets[permutation_index].iter()) { - // TODO: parallelize - for ((left, advice), permutation) in - left.iter_mut().zip(advice.iter()).zip(permutation.iter()) - { - *left *= &(*advice + &(x_0 * permutation) + &x_1); - } + parallelize(&mut left, |left, start| { + for ((left, advice), permutation) in left + .iter_mut() + .zip(advice[start..].iter()) + .zip(permutation[start..].iter()) + { + *left *= &(*advice + &(x_0 * permutation) + &x_1); + } + }); } let mut right = permutation_product_cosets_inv[permutation_index].clone(); let mut current_delta = x_0 * &C::Scalar::ZETA; let step = domain.get_extended_omega(); for advice in wires.iter().map(|&wire_index| &advice_cosets[wire_index.0]) { - // TODO: parallelize - let mut beta_term = current_delta; - for (right, advice) in right.iter_mut().zip(advice.iter()) { - *right *= &(*advice + &beta_term + &x_1); - beta_term *= &step; - } + parallelize(&mut right, move |right, start| { + let mut beta_term = current_delta * &step.pow_vartime(&[start as u64, 0, 0, 0]); + for (right, advice) in right.iter_mut().zip(advice[start..].iter()) { + *right *= &(*advice + &beta_term + &x_1); + beta_term *= &step; + } + }); current_delta *= &C::Scalar::DELTA; } - for (h, e) in h_poly.iter_mut().zip(left.into_iter()) { - *h += &e; - } - - for (h, e) in h_poly.iter_mut().zip(right.into_iter()) { - *h -= &e; - } + parallelize(&mut h_poly, |a, start| { + for ((h, left), right) in a + .iter_mut() + .zip(left[start..].iter()) + .zip(right[start..].iter()) + { + *h += &left; + *h -= &right; + } + }); } // Divide by t(X) = X^{params.n} - 1. From eff149e7346cf4c897c6c341a763d1da7caebbc2 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 6 Sep 2020 14:10:25 -0600 Subject: [PATCH 35/38] Fix incorrect indexing into advice_cosets during proving. --- src/plonk.rs | 26 ++++++++++++++++++++++++-- src/plonk/prover.rs | 6 +++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index 3df2689..cd83bb1 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -107,6 +107,8 @@ fn test_proving() { a: AdviceWire, b: AdviceWire, c: AdviceWire, + d: AdviceWire, + e: AdviceWire, sa: FixedWire, sb: FixedWire, @@ -160,9 +162,15 @@ fn test_proving() { value = Some(f()?); Ok(value.ok_or(Error::SynthesisError)?.0) })?; + self.cs.assign_advice(self.config.d, index, || { + Ok(value.ok_or(Error::SynthesisError)?.0.square().square()) + })?; self.cs.assign_advice(self.config.b, index, || { Ok(value.ok_or(Error::SynthesisError)?.1) })?; + self.cs.assign_advice(self.config.e, index, || { + Ok(value.ok_or(Error::SynthesisError)?.1.square().square()) + })?; self.cs.assign_advice(self.config.c, index, || { Ok(value.ok_or(Error::SynthesisError)?.2) })?; @@ -192,9 +200,15 @@ fn test_proving() { value = Some(f()?); Ok(value.ok_or(Error::SynthesisError)?.0) })?; + self.cs.assign_advice(self.config.d, index, || { + Ok(value.ok_or(Error::SynthesisError)?.0.square().square()) + })?; self.cs.assign_advice(self.config.b, index, || { Ok(value.ok_or(Error::SynthesisError)?.1) })?; + self.cs.assign_advice(self.config.e, index, || { + Ok(value.ok_or(Error::SynthesisError)?.1.square().square()) + })?; self.cs.assign_advice(self.config.c, index, || { Ok(value.ok_or(Error::SynthesisError)?.2) })?; @@ -236,19 +250,25 @@ fn test_proving() { type Config = PLONKConfig; fn configure(meta: &mut MetaCircuit) -> PLONKConfig { + let e = meta.advice_wire(); let a = meta.advice_wire(); let b = meta.advice_wire(); + let sf = meta.fixed_wire(); let c = meta.advice_wire(); + let d = meta.advice_wire(); let perm = meta.permutation(&[a, b, c]); + let sm = meta.fixed_wire(); let sa = meta.fixed_wire(); let sb = meta.fixed_wire(); let sc = meta.fixed_wire(); - let sm = meta.fixed_wire(); meta.create_gate(|meta| { + let d = meta.query_advice(d, 1); let a = meta.query_advice(a, 0); + let sf = meta.query_fixed(sf, 0); + let e = meta.query_advice(e, -1); let b = meta.query_advice(b, 0); let c = meta.query_advice(c, 0); @@ -257,13 +277,15 @@ fn test_proving() { let sc = meta.query_fixed(sc, 0); let sm = meta.query_fixed(sm, 0); - a.clone() * sa + b.clone() * sb + a * b * sm + (c * sc * (-F::one())) + a.clone() * sa + b.clone() * sb + a * b * sm + (c * sc * (-F::one())) + sf * (d * e) }); PLONKConfig { a, b, c, + d, + e, sa, sb, sc, diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index db8d59e..acc1a5b 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -319,7 +319,7 @@ impl Proof { } // z(X) \prod (p(X) + \beta s_i(X) + \gamma) - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) - for (permutation_index, wires) in srs.meta.permutations.iter().enumerate() { + for (permutation_index, wires) in srs.meta.permutation_queries.iter().enumerate() { parallelize(&mut h_poly, |a, _| { for a in a.iter_mut() { *a *= &x_2; @@ -329,7 +329,7 @@ impl Proof { let mut left = permutation_product_cosets[permutation_index].clone(); for (advice, permutation) in wires .iter() - .map(|&wire_index| &advice_cosets[wire_index.0]) + .map(|&wire| &advice_cosets[wire]) .zip(srs.permutation_cosets[permutation_index].iter()) { parallelize(&mut left, |left, start| { @@ -346,7 +346,7 @@ impl Proof { let mut right = permutation_product_cosets_inv[permutation_index].clone(); let mut current_delta = x_0 * &C::Scalar::ZETA; let step = domain.get_extended_omega(); - for advice in wires.iter().map(|&wire_index| &advice_cosets[wire_index.0]) { + for advice in wires.iter().map(|&wire| &advice_cosets[wire]) { parallelize(&mut right, move |right, start| { let mut beta_term = current_delta * &step.pow_vartime(&[start as u64, 0, 0, 0]); for (right, advice) in right.iter_mut().zip(advice[start..].iter()) { From 190242a4e9241126cad8b4b5dd06434a31725e29 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 6 Sep 2020 14:18:05 -0600 Subject: [PATCH 36/38] Remove redundant permutation_queries vector. --- src/plonk/circuit.rs | 27 ++++++++++++--------------- src/plonk/prover.rs | 10 +++++----- src/plonk/verifier.rs | 10 +++++----- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index eab4517..89a5f57 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -163,13 +163,13 @@ pub struct MetaCircuit { pub(crate) rotations: BTreeMap, // Vector of permutation arguments, where each corresponds to a set of wires - // that are involved in a permutation argument. As an example, we could have - // a permutation argument between wires (A, B, C) which allows copy - // constraints to be enforced between advice wire values in A, B and C, and - // another permutation between wires (B, C, D) which allows the same with D - // instead of A. - pub(crate) permutations: Vec>, - pub(crate) permutation_queries: Vec>, + // that are involved in a permutation argument, as well as the corresponding + // query index for each wire. As an example, we could have a permutation + // argument between wires (A, B, C) which allows copy constraints to be + // enforced between advice wire values in A, B and C, and another + // permutation between wires (B, C, D) which allows the same with D instead + // of A. + pub(crate) permutations: Vec>, } impl Default for MetaCircuit { @@ -185,7 +185,6 @@ impl Default for MetaCircuit { advice_queries: Vec::new(), rotations, permutations: Vec::new(), - permutation_queries: Vec::new(), } } } @@ -199,13 +198,11 @@ impl MetaCircuit { let len = self.rotations.len(); self.rotations.entry(at).or_insert(PointIndex(len)); } - self.permutations.push(wires.to_vec()); - - let mut queries = vec![]; - for wire in wires { - queries.push(self.query_advice_index(*wire, 0)); - } - self.permutation_queries.push(queries); + let wires = wires + .iter() + .map(|&wire| (wire, self.query_advice_index(wire, 0))) + .collect(); + self.permutations.push(wires); index } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index acc1a5b..89ea5ee 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -142,7 +142,7 @@ impl Proof { let mut modified_advice = Vec::with_capacity(wires.len()); // Iterate over each wire of the permutation - for (wire, permuted_wire_values) in wires.iter().zip(permuted_values.iter()) { + for (&(wire, _), permuted_wire_values) in wires.iter().zip(permuted_values.iter()) { // Grab the advice wire's values from the witness let mut tmp_advice_values = witness.advice[wire.0].clone(); @@ -179,7 +179,7 @@ impl Proof { // Iterate over each wire again, this time finishing the computation // of the entire fraction by computing the numerators let mut deltaomega = C::Scalar::one(); - for (wire, modified_advice) in wires.iter().zip(modified_advice.iter_mut()) { + for (&(wire, _), modified_advice) in wires.iter().zip(modified_advice.iter_mut()) { // For each row i, we compute // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma // for the jth wire of the permutation @@ -319,7 +319,7 @@ impl Proof { } // z(X) \prod (p(X) + \beta s_i(X) + \gamma) - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) - for (permutation_index, wires) in srs.meta.permutation_queries.iter().enumerate() { + for (permutation_index, wires) in srs.meta.permutations.iter().enumerate() { parallelize(&mut h_poly, |a, _| { for a in a.iter_mut() { *a *= &x_2; @@ -329,7 +329,7 @@ impl Proof { let mut left = permutation_product_cosets[permutation_index].clone(); for (advice, permutation) in wires .iter() - .map(|&wire| &advice_cosets[wire]) + .map(|&(_, index)| &advice_cosets[index]) .zip(srs.permutation_cosets[permutation_index].iter()) { parallelize(&mut left, |left, start| { @@ -346,7 +346,7 @@ impl Proof { let mut right = permutation_product_cosets_inv[permutation_index].clone(); let mut current_delta = x_0 * &C::Scalar::ZETA; let step = domain.get_extended_omega(); - for advice in wires.iter().map(|&wire| &advice_cosets[wire]) { + for advice in wires.iter().map(|&(_, index)| &advice_cosets[index]) { parallelize(&mut right, move |right, start| { let mut beta_term = current_delta * &step.pow_vartime(&[start as u64, 0, 0, 0]); for (right, advice) in right.iter_mut().zip(advice[start..].iter()) { diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 3a03393..95743c0 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -98,13 +98,13 @@ impl Proof { } // z(X) \prod (p(X) + \beta s_i(X) + \gamma) - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) - for (permutation_index, queries) in srs.meta.permutation_queries.iter().enumerate() { + for (permutation_index, wires) in srs.meta.permutations.iter().enumerate() { h_eval *= &x_2; let mut left = self.permutation_product_evals[permutation_index]; - for (advice_eval, permutation_eval) in queries + for (advice_eval, permutation_eval) in wires .iter() - .map(|&query_index| self.advice_evals[query_index]) + .map(|&(_, query_index)| self.advice_evals[query_index]) .zip(self.permutation_evals[permutation_index].iter()) { left *= &(advice_eval + &(x_0 * permutation_eval) + &x_1); @@ -112,9 +112,9 @@ impl Proof { let mut right = self.permutation_product_inv_evals[permutation_index]; let mut current_delta = x_0 * &x_3; - for advice_eval in queries + for advice_eval in wires .iter() - .map(|&query_index| self.advice_evals[query_index]) + .map(|&(_, query_index)| self.advice_evals[query_index]) { right *= &(advice_eval + ¤t_delta + &x_1); current_delta *= &C::Scalar::DELTA; From b65e75921b23e38aa8c1e3f60d0141349b5a62ce Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 6 Sep 2020 14:21:28 -0600 Subject: [PATCH 37/38] Remove stale comment --- src/plonk/prover.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 89ea5ee..527885c 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -304,7 +304,6 @@ impl Proof { } // l_0(X) * (1 - z(X)) = 0 - // TODO: parallelize for coset in permutation_product_cosets.iter() { parallelize(&mut h_poly, |h, start| { for ((h, c), l0) in h From 21f02a73c2bc39ca99a437c23c57f76a51c43b1a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 7 Sep 2020 09:37:49 -0600 Subject: [PATCH 38/38] Don't mutate the witness during permutation argument. Also, adds parallelism and reduces state/multiplications. --- src/plonk.rs | 7 ++++- src/plonk/prover.rs | 76 ++++++++++++++++++--------------------------- 2 files changed, 37 insertions(+), 46 deletions(-) diff --git a/src/plonk.rs b/src/plonk.rs index cd83bb1..6bdf8f4 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -116,6 +116,7 @@ fn test_proving() { sm: FixedWire, perm: usize, + perm2: usize, } trait StandardCS { @@ -242,7 +243,9 @@ fn test_proving() { }; self.cs - .copy(self.config.perm, left_wire, left.1, right_wire, right.1) + .copy(self.config.perm, left_wire, left.1, right_wire, right.1)?; + self.cs + .copy(self.config.perm2, left_wire, left.1, right_wire, right.1) } } @@ -258,6 +261,7 @@ fn test_proving() { let d = meta.advice_wire(); let perm = meta.permutation(&[a, b, c]); + let perm2 = meta.permutation(&[a, b, c]); let sm = meta.fixed_wire(); let sa = meta.fixed_wire(); @@ -291,6 +295,7 @@ fn test_proving() { sc, sm, perm, + perm2, } } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 527885c..7da6fe8 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -78,6 +78,8 @@ impl Proof { // Synthesize the circuit to obtain the witness and other information. circuit.synthesize(&mut witness, config)?; + let witness = witness; + // Create a transcript for obtaining Fiat-Shamir challenges. let mut transcript = HBase::init(C::Base::one()); @@ -132,31 +134,26 @@ impl Proof { // Iterate over each permutation let mut permutation_modified_advice = vec![]; for (wires, permuted_values) in srs.meta.permutations.iter().zip(srs.permutations.iter()) { - // Goal is to compute the fraction + // Goal is to compute the products of fractions // // (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) / // (p_j(\omega^i) + \beta s_j(\omega^i) + \gamma) // // where p_j(X) is the jth advice wire in this permutation, // and i is the ith row of the wire. - let mut modified_advice = Vec::with_capacity(wires.len()); + let mut modified_advice = vec![C::Scalar::one(); params.n as usize]; // Iterate over each wire of the permutation for (&(wire, _), permuted_wire_values) in wires.iter().zip(permuted_values.iter()) { - // Grab the advice wire's values from the witness - let mut tmp_advice_values = witness.advice[wire.0].clone(); - - // For each row i, compute - // p_j(\omega^i) + \beta s_j(\omega^i) + \gamma - // where p_j(omega^i) = tmp[i] - for (tmp_advice_value, permuted_advice_value) in tmp_advice_values - .iter_mut() - .zip(permuted_wire_values.iter()) - { - *tmp_advice_value += &(x_0 * permuted_advice_value); // p_j(\omega^i) + \beta s_j(\omega^i) - *tmp_advice_value += &x_1; // p_j(\omega^i) + \beta s_j(\omega^i) + \gamma - } - modified_advice.push(tmp_advice_values); + parallelize(&mut modified_advice, |modified_advice, start| { + for ((modified_advice, advice_value), permuted_advice_value) in modified_advice + .iter_mut() + .zip(witness.advice[wire.0][start..].iter()) + .zip(permuted_wire_values[start..].iter()) + { + *modified_advice *= &(x_0 * permuted_advice_value + &x_1 + advice_value); + } + }); } permutation_modified_advice.push(modified_advice); @@ -167,7 +164,6 @@ impl Proof { permutation_modified_advice .iter_mut() .flat_map(|v| v.iter_mut()) - .flat_map(|v| v.iter_mut()) .batch_invert(); for (wires, mut modified_advice) in srs @@ -179,30 +175,30 @@ impl Proof { // Iterate over each wire again, this time finishing the computation // of the entire fraction by computing the numerators let mut deltaomega = C::Scalar::one(); - for (&(wire, _), modified_advice) in wires.iter().zip(modified_advice.iter_mut()) { - // For each row i, we compute - // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma - // for the jth wire of the permutation - for (tmp_advice_value, modified_advice) in witness.advice[wire.0] - .iter_mut() - .zip(modified_advice.iter_mut()) - { - *tmp_advice_value += &(deltaomega * &x_0); // p_j(\omega^i) + \delta^j \omega^i \beta - *tmp_advice_value += &x_1; // p_j(\omega^i) + \delta^j \omega^i \beta + \gamma - *modified_advice *= tmp_advice_value; - deltaomega *= &domain.get_omega(); - } + for &(wire, _) in wires.iter() { + let omega = domain.get_omega(); + parallelize(&mut modified_advice, |modified_advice, start| { + let mut deltaomega = deltaomega * &omega.pow_vartime(&[start as u64, 0, 0, 0]); + for (modified_advice, advice_value) in modified_advice + .iter_mut() + .zip(witness.advice[wire.0][start..].iter()) + { + // Multiply by p_j(\omega^i) + \delta^j \omega^i \beta + *modified_advice *= &(deltaomega * &x_0 + &x_1 + advice_value); + deltaomega *= ω + } + }); deltaomega *= &C::Scalar::DELTA; } - // The modified_advice vector is a vector of vectors of fractions of - // the form + // The modified_advice vector is a vector of products of fractions + // of the form // // (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) / // (p_j(\omega^i) + \beta s_j(\omega^i) + \gamma) // - // where j is the index into modified_advice, and i is the index - // into modified_advice[j], for the jth wire in the permutation + // where i is the index into modified_advice, for the jth wire in + // the permutation // Compute the evaluations of the permutation product polynomial // over our domain, starting with z[0] = 1 @@ -210,17 +206,7 @@ impl Proof { for row in 1..(params.n as usize) { let mut tmp = z[row - 1]; - // Iterate over each wire's modified advice, where for the jth - // wire we obtain the fraction - // - // (p_j(\omega^i) + \delta^j \omega^i \beta + \gamma) / - // (p_j(\omega^i) + \beta s_j(\omega^i) + \gamma) - // - // where i is the row of the permutation product polynomial - // evaluation vector that we are currently evaluating. - for wire_modified_advice in modified_advice.iter() { - tmp *= &wire_modified_advice[row]; - } + tmp *= &modified_advice[row]; z.push(tmp); }