diff --git a/.gitignore b/.gitignore index 6936990..173b951 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk Cargo.lock +.vscode 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/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 ae99c24..f85b75e 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([ + 0x48796f6fde98a425, + 0xa99d8b67e918805e, + 0x671383de08b5fe3c, + 0x1e9372724e80300d, +]); + 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, @@ -640,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 29babce..1005263 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([ + 0x83d2833d15f2bbf9, + 0x5127e2ce24a8e69c, + 0x4243423589e0a9b5, + 0x20daec44973be920, +]); + 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, @@ -654,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])); +} diff --git a/src/plonk.rs b/src/plonk.rs index 7de9c61..6bdf8f4 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -29,9 +29,14 @@ use domain::EvaluationDomain; #[derive(Debug)] pub struct SRS { domain: EvaluationDomain, + l0: Vec, fixed_commitments: Vec, fixed_polys: Vec>, fixed_cosets: Vec>, + permutation_commitments: Vec>, + permutations: Vec>>, + permutation_polys: Vec>>, + permutation_cosets: Vec>>, meta: MetaCircuit, } @@ -41,6 +46,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, @@ -87,6 +96,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); @@ -94,15 +107,17 @@ fn test_proving() { a: AdviceWire, b: AdviceWire, c: AdviceWire, + d: AdviceWire, + e: AdviceWire, sa: FixedWire, sb: FixedWire, sc: FixedWire, sm: FixedWire, - } - #[derive(Copy, Clone)] - struct Variable(AdviceWire, usize); + perm: usize, + perm2: usize, + } trait StandardCS { fn raw_multiply(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> @@ -111,6 +126,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 { @@ -147,9 +163,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) })?; @@ -179,9 +201,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) })?; @@ -200,23 +228,51 @@ fn test_proving() { Variable(self.config.c, index), )) } + 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 right.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, left.1, right_wire, right.1)?; + self.cs + .copy(self.config.perm2, left_wire, left.1, right_wire, right.1) + } } impl Circuit for MyCircuit { 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 perm2 = 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); @@ -225,17 +281,21 @@ 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, sm, + perm, + perm2, } } @@ -248,7 +308,7 @@ fn test_proving() { for _ in 0..10 { let mut a_squared = None; - let (_, _, _) = cs.raw_multiply(|| { + let (a0, _, c0) = cs.raw_multiply(|| { a_squared = self.a.map(|a| a.square()); Ok(( self.a.ok_or(Error::SynthesisError)?, @@ -256,7 +316,7 @@ fn test_proving() { a_squared.ok_or(Error::SynthesisError)?, )) })?; - let (_, _, _) = 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)?, @@ -264,6 +324,8 @@ fn test_proving() { fin.ok_or(Error::SynthesisError)?, )) })?; + cs.copy(a0, a1)?; + cs.copy(b1, c0)?; } Ok(()) @@ -271,7 +333,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 }; @@ -279,9 +341,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/circuit.rs b/src/plonk/circuit.rs index d1923b6..89a5f57 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; @@ -33,7 +33,15 @@ 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 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 @@ -147,18 +155,26 @@ 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)>, // 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 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 { fn default() -> MetaCircuit { - let mut rotations = HashMap::new(); + let mut rotations = BTreeMap::new(); rotations.insert(Rotation::default(), PointIndex(0)); MetaCircuit { @@ -168,39 +184,79 @@ impl Default for MetaCircuit { fixed_queries: Vec::new(), advice_queries: Vec::new(), rotations, + permutations: Vec::new(), } } } impl MetaCircuit { - /// Query a fixed wire at a relative position - pub fn query_fixed(&mut self, wire: FixedWire, at: i32) -> Polynomial { + /// Add a permutation argument for some advice wires + pub fn permutation(&mut self, wires: &[AdviceWire]) -> usize { + let index = self.permutations.len(); + if index == 0 { + let at = Rotation(-1); + let len = self.rotations.len(); + self.rotations.entry(at).or_insert(PointIndex(len)); + } + let wires = wires + .iter() + .map(|&wire| (wire, self.query_advice_index(wire, 0))) + .collect(); + self.permutations.push(wires); + + index + } + + 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 { + let at = Rotation(at); + { + let len = self.rotations.len(); + self.rotations.entry(at).or_insert(PointIndex(len)); + } + + // 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)); + + index } /// Query an advice wire at a relative position pub fn query_advice(&mut self, wire: AdviceWire, at: i32) -> Polynomial { - 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 - let index = self.advice_queries.len(); - self.advice_queries.push((wire, at)); - - Polynomial::Advice(index) + 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..52a79b0 100644 --- a/src/plonk/domain.rs +++ b/src/plonk/domain.rs @@ -1,8 +1,8 @@ -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. -#[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 { @@ -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 { @@ -56,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. @@ -96,9 +93,26 @@ 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 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, k, @@ -113,6 +127,7 @@ impl EvaluationDomain { ifft_divisor, extended_ifft_divisor, t_evaluations, + barycentric_weight, } } @@ -245,6 +260,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 } @@ -260,4 +279,8 @@ impl EvaluationDomain { } point } + + pub fn get_barycentric_weight(&self) -> G::Scalar { + self.barycentric_weight + } } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 70f006c..7da6fe8 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; @@ -53,6 +53,19 @@ impl Proof { Ok(()) } + + fn copy( + &mut self, + _: usize, + _: usize, + _: usize, + _: usize, + _: usize, + ) -> Result<(), Error> { + // We only care about advice wires here + + Ok(()) + } } let mut meta = MetaCircuit::default(); @@ -65,17 +78,23 @@ 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()); // 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)?; @@ -85,6 +104,7 @@ impl Proof { let advice_polys: Vec<_> = witness .advice + .clone() .into_iter() .map(|poly| domain.obtain_poly(poly)) .collect(); @@ -98,6 +118,121 @@ 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())); + + // 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_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 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![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()) { + 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); + } + + // Batch invert to obtain the denominators for the permutation product + // polynomials + permutation_modified_advice + .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(); + 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 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 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 + let mut z = vec![C::Scalar::one()]; + for row in 1..(params.n as usize) { + let mut tmp = z[row - 1]; + + tmp *= &modified_advice[row]; + z.push(tmp); + } + + let blind = C::Scalar::random(); + + 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 { + 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())); @@ -105,9 +240,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 { - 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( @@ -144,12 +281,79 @@ impl Proof { if i == 0 { h_poly = evaluation; } else { - 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 + for coset in permutation_product_cosets.iter() { + 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() { + 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 + .iter() + .map(|&(_, index)| &advice_cosets[index]) + .zip(srs.permutation_cosets[permutation_index].iter()) + { + 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(|&(_, 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()) { + *right *= &(*advice + &beta_term + &x_1); + beta_term *= &step; + } + }); + current_delta *= &C::Scalar::DELTA; + } + + 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. let h_poly = domain.divide_by_vanishing_poly(h_poly); @@ -165,11 +369,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() { @@ -193,6 +401,27 @@ impl Proof { }) .collect(); + 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, domain.rotate_omega(x_3, Rotation(-1)))) + .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() .map(|poly| eval_polynomial(poly, x_3)) @@ -203,17 +432,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 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); } @@ -281,6 +507,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())); @@ -314,13 +572,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() { @@ -356,6 +612,10 @@ impl Proof { Ok(Proof { advice_commitments, h_commitments, + permutation_product_commitments, + permutation_product_evals, + permutation_product_inv_evals, + permutation_evals, advice_evals, fixed_evals, h_evals, diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 98bdc13..86567c1 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -1,6 +1,6 @@ use super::{ circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit}, - domain::EvaluationDomain, + domain::{EvaluationDomain, Rotation}, Error, SRS, }; use crate::arithmetic::{Curve, CurveAffine, Field}; @@ -15,6 +15,9 @@ impl SRS { ) -> Result { struct Assembly { fixed: Vec>, + mapping: Vec>>, + aux: Vec>>, + sizes: Vec>>, } impl ConstraintSystem for Assembly { @@ -42,31 +45,180 @@ impl SRS { Ok(()) } + + fn copy( + &mut self, + permutation: usize, + left_wire: usize, + left_row: usize, + right_wire: usize, + right_row: usize, + ) -> Result<(), Error> { + // 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(()) + } } 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); + } + + // The permutation argument will serve alongside the gates, so must be + // accounted for. + 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. + 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(); + for _ in 0..params.n { + omega_powers.push(cur); + cur *= &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; + } + } + let mut assembly: Assembly = Assembly { fixed: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_fixed_wires], + mapping: vec![], + aux: vec![], + sizes: 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 0..permutation.len() { + // Computes [(i, 0), (i, 1), ..., (i, n - 1)] + wires.push((0..params.n).map(|j| (i, j as usize)).collect()); + } + 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 circuit.synthesize(&mut assembly, config)?; + // 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 0..permutation.len() { + // 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.mapping[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 + 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); + } + 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() @@ -82,11 +234,23 @@ 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, + l0, fixed_commitments, fixed_polys, fixed_cosets, + permutation_commitments, + permutations, + permutation_polys, + permutation_cosets, meta, }) } diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index d8560d1..95743c0 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -19,6 +19,17 @@ 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())); + + // 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. let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); @@ -30,6 +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 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. @@ -40,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); } @@ -63,17 +78,61 @@ impl Proof { h_eval += &evaluation; } - let xn = x_3.pow(&[params.n as u64, 0, 0, 0]); + + // First element in each permutation product should be 1 + // l_0(X) * (1 - z(X)) = 0 + { + // TODO: bubble this error up + let denominator = (x_3 - &C::Scalar::one()).invert().unwrap(); + + for eval in self.permutation_product_evals.iter() { + h_eval *= &x_2; + + 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; + } + } + + // 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() { + h_eval *= &x_2; + + let mut left = self.permutation_product_evals[permutation_index]; + for (advice_eval, permutation_eval) in wires + .iter() + .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_inv_evals[permutation_index]; + let mut current_delta = x_0 * &x_3; + for advice_eval in wires + .iter() + .map(|&(_, query_index)| self.advice_evals[query_index]) + { + right *= &(advice_eval + ¤t_delta + &x_1); + current_delta *= &C::Scalar::DELTA; + } + + h_eval += &left; + h_eval -= &right; + } // Compute the expected h(x) value let mut expected_h_eval = C::Scalar::zero(); 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; } @@ -119,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); + } } } @@ -147,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);