diff --git a/src/plonk.rs b/src/plonk.rs index 2388e43..4d67c7d 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -8,7 +8,7 @@ use crate::arithmetic::CurveAffine; use crate::poly::{ commitment::ChallengeScalar, multiopen, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, - LagrangeCoeff, Polynomial, + Polynomial, }; mod circuit; @@ -28,7 +28,7 @@ pub use verifier::*; pub struct VerifyingKey { domain: EvaluationDomain, fixed_commitments: Vec, - permutation_commitments: Vec>, + permutations: Vec>, cs: ConstraintSystem, } @@ -41,9 +41,7 @@ pub struct ProvingKey { l0: Polynomial, fixed_polys: Vec>, fixed_cosets: Vec>, - permutations: Vec>>, - permutation_polys: Vec>>, - permutation_cosets: Vec>>, + permutations: Vec>, } /// This is an object which represents a (Turbo)PLONK proof. diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index c5447c5..151ac67 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -4,7 +4,7 @@ use ff::Field; use std::collections::BTreeMap; use std::convert::TryFrom; -use super::Error; +use super::{permutation, Error}; use crate::poly::Rotation; /// A column type @@ -312,7 +312,7 @@ pub struct ConstraintSystem { // Vector of permutation arguments, where each corresponds to a sequence of columns // that are involved in a permutation argument. - pub(crate) permutations: Vec>>, + pub(crate) permutations: Vec, } impl Default for ConstraintSystem { @@ -347,7 +347,8 @@ impl ConstraintSystem { for column in columns { self.query_advice_index(*column, 0); } - self.permutations.push(columns.to_vec()); + self.permutations + .push(permutation::Argument::new(columns.to_vec())); index } diff --git a/src/plonk/keygen.rs b/src/plonk/keygen.rs index 5b7af79..1b72625 100644 --- a/src/plonk/keygen.rs +++ b/src/plonk/keygen.rs @@ -2,9 +2,9 @@ use ff::Field; use super::{ circuit::{Advice, Assignment, Circuit, Column, ConstraintSystem, Fixed}, - Error, ProvingKey, VerifyingKey, + permutation, Error, ProvingKey, VerifyingKey, }; -use crate::arithmetic::{Curve, CurveAffine, FieldExt}; +use crate::arithmetic::{Curve, CurveAffine}; use crate::poly::{ commitment::{Blind, Params}, EvaluationDomain, LagrangeCoeff, Polynomial, Rotation, @@ -21,9 +21,7 @@ where { struct Assembly { fixed: Vec>, - mapping: Vec>>, - aux: Vec>>, - sizes: Vec>>, + permutations: permutation::keygen::Assembly, _marker: std::marker::PhantomData, } @@ -61,62 +59,22 @@ where right_column: usize, right_row: usize, ) -> Result<(), Error> { - // Check bounds first - if permutation >= self.mapping.len() - || left_column >= self.mapping[permutation].len() - || left_row >= self.mapping[permutation][left_column].len() - || right_column >= self.mapping[permutation].len() - || right_row >= self.mapping[permutation][right_column].len() - { - return Err(Error::BoundsFailure); - } - - let mut left_cycle = self.aux[permutation][left_column][left_row]; - let mut right_cycle = self.aux[permutation][right_column][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_column][left_row]; - self.mapping[permutation][left_column][left_row] = - self.mapping[permutation][right_column][right_row]; - self.mapping[permutation][right_column][right_row] = tmp; - - Ok(()) + self.permutations + .copy(permutation, left_column, left_row, right_column, right_row) } } let mut cs = ConstraintSystem::default(); let config = ConcreteCircuit::configure(&mut cs); - // Get the largest permutation argument length in terms of the number of - // advice columns involved. - let mut largest_permutation_length = 0; - for permutation in &cs.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; + let mut degree = cs + .permutations + .iter() + .map(|p| p.required_degree()) + .max() + .unwrap_or(1); // Account for each gate to ensure our quotient polynomial is the // correct degree and that our extended domain is the right size. @@ -126,95 +84,16 @@ where 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![domain.empty_lagrange(); cs.num_fixed_columns], - mapping: vec![], - aux: vec![], - sizes: vec![], + permutations: permutation::keygen::Assembly::new(params, &cs), _marker: std::marker::PhantomData, }; - // Initialize the copy vector to keep track of copy constraints in all - // the permutation arguments. - for permutation in &cs.permutations { - let mut columns = vec![]; - for i in 0..permutation.len() { - // Computes [(i, 0), (i, 1), ..., (i, n - 1)] - columns.push((0..params.n).map(|j| (i, j as usize)).collect()); - } - assembly.mapping.push(columns.clone()); - assembly.aux.push(columns); - 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 cs.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 mut permutation_poly = domain.empty_lagrange(); - for (j, p) in permutation_poly.iter_mut().enumerate() { - let (permuted_i, permuted_j) = assembly.mapping[permutation_index][i][j]; - *p = deltaomega[permuted_i][permuted_j]; - } - - // Compute commitment to permutation polynomial - commitments.push( - params - .commit_lagrange(&permutation_poly, Blind::default()) - .to_affine(), - ); - // Store permutation polynomial and precompute its coset evaluation - inner_permutations.push(permutation_poly.clone()); - let poly = domain.lagrange_to_coeff(permutation_poly); - polys.push(poly.clone()); - cosets.push(domain.coeff_to_extended(poly, Rotation::default())); - } - permutation_commitments.push(commitments); - permutations.push(inner_permutations); - permutation_polys.push(polys); - permutation_cosets.push(cosets); - } + let (permutation_pks, permutation_vks) = assembly.permutations.build_keys(params, &cs, &domain); let fixed_commitments = assembly .fixed @@ -248,14 +127,12 @@ where vk: VerifyingKey { domain, fixed_commitments, - permutation_commitments, + permutations: permutation_vks, cs, }, l0, fixed_polys, fixed_cosets, - permutations, - permutation_polys, - permutation_cosets, + permutations: permutation_pks, }) } diff --git a/src/plonk/permutation.rs b/src/plonk/permutation.rs index 74a4714..deda02b 100644 --- a/src/plonk/permutation.rs +++ b/src/plonk/permutation.rs @@ -1,10 +1,48 @@ //! Implementation of a PLONK permutation argument. -use crate::arithmetic::CurveAffine; +use super::circuit::{Advice, Column}; +use crate::{ + arithmetic::CurveAffine, + poly::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial}, +}; +pub(crate) mod keygen; mod prover; mod verifier; +/// A permutation argument. +#[derive(Debug, Clone)] +pub(crate) struct Argument { + /// A sequence of columns involved in the argument. + columns: Vec>, +} + +impl Argument { + pub(crate) fn new(columns: Vec>) -> Self { + Argument { columns } + } + + pub(crate) fn required_degree(&self) -> usize { + // The permutation argument will serve alongside the gates, so must be + // accounted for. + self.columns.len() + 1 + } +} + +/// The verifying key for a single permutation argument. +#[derive(Debug)] +pub(crate) struct VerifyingKey { + commitments: Vec, +} + +/// The proving key for a single permutation argument. +#[derive(Debug)] +pub(crate) struct ProvingKey { + permutations: Vec>, + polys: Vec>, + cosets: Vec>, +} + #[derive(Debug, Clone)] pub(crate) struct Proof { permutation_product_commitments: Vec, diff --git a/src/plonk/permutation/keygen.rs b/src/plonk/permutation/keygen.rs new file mode 100644 index 0000000..960cb04 --- /dev/null +++ b/src/plonk/permutation/keygen.rs @@ -0,0 +1,179 @@ +use ff::Field; + +use super::{ProvingKey, VerifyingKey}; +use crate::{ + arithmetic::{Curve, CurveAffine, FieldExt}, + plonk::{circuit::ConstraintSystem, Error}, + poly::{ + commitment::{Blind, Params}, + EvaluationDomain, Rotation, + }, +}; + +pub(crate) struct Assembly { + mapping: Vec>>, + aux: Vec>>, + sizes: Vec>>, +} + +impl Assembly { + pub(crate) fn new( + params: &Params, + cs: &ConstraintSystem, + ) -> Self { + let mut assembly = Assembly { + mapping: vec![], + aux: vec![], + sizes: vec![], + }; + + // Initialize the copy vector to keep track of copy constraints in all + // the permutation arguments. + for p in &cs.permutations { + let mut columns = vec![]; + for i in 0..p.columns.len() { + // Computes [(i, 0), (i, 1), ..., (i, n - 1)] + columns.push((0..params.n).map(|j| (i, j as usize)).collect()); + } + assembly.mapping.push(columns.clone()); + assembly.aux.push(columns); + assembly + .sizes + .push(vec![vec![1usize; params.n as usize]; p.columns.len()]); + } + + assembly + } + + pub(crate) fn copy( + &mut self, + permutation: usize, + left_column: usize, + left_row: usize, + right_column: usize, + right_row: usize, + ) -> Result<(), Error> { + // Check bounds first + if permutation >= self.mapping.len() + || left_column >= self.mapping[permutation].len() + || left_row >= self.mapping[permutation][left_column].len() + || right_column >= self.mapping[permutation].len() + || right_row >= self.mapping[permutation][right_column].len() + { + return Err(Error::BoundsFailure); + } + + let mut left_cycle = self.aux[permutation][left_column][left_row]; + let mut right_cycle = self.aux[permutation][right_column][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_column][left_row]; + self.mapping[permutation][left_column][left_row] = + self.mapping[permutation][right_column][right_row]; + self.mapping[permutation][right_column][right_row] = tmp; + + Ok(()) + } + + pub(crate) fn build_keys( + self, + params: &Params, + cs: &ConstraintSystem, + domain: &EvaluationDomain, + ) -> (Vec>, Vec>) { + // Get the largest permutation argument length in terms of the number of + // advice columns involved. + let largest_permutation_length = cs + .permutations + .iter() + .map(|p| p.columns.len()) + .max() + .unwrap_or_default(); + + // 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; + } + } + + // Compute permutation polynomials, convert to coset form and + // pre-compute commitments for the SRS. + let mut pks = vec![]; + let mut vks = vec![]; + for (p, mapping) in cs.permutations.iter().zip(self.mapping.iter()) { + let mut commitments = vec![]; + let mut permutations = vec![]; + let mut polys = vec![]; + let mut cosets = vec![]; + for i in 0..p.columns.len() { + // Computes the permutation polynomial based on the permutation + // description in the assembly. + let mut permutation_poly = domain.empty_lagrange(); + for (j, p) in permutation_poly.iter_mut().enumerate() { + let (permuted_i, permuted_j) = mapping[i][j]; + *p = deltaomega[permuted_i][permuted_j]; + } + + // Compute commitment to permutation polynomial + commitments.push( + params + .commit_lagrange(&permutation_poly, Blind::default()) + .to_affine(), + ); + // Store permutation polynomial and precompute its coset evaluation + permutations.push(permutation_poly.clone()); + let poly = domain.lagrange_to_coeff(permutation_poly); + polys.push(poly.clone()); + cosets.push(domain.coeff_to_extended(poly, Rotation::default())); + } + vks.push(VerifyingKey { commitments }); + pks.push(ProvingKey { + permutations, + polys, + cosets, + }); + } + + (pks, vks) + } +} diff --git a/src/plonk/permutation/prover.rs b/src/plonk/permutation/prover.rs index 6b4f752..bd74a49 100644 --- a/src/plonk/permutation/prover.rs +++ b/src/plonk/permutation/prover.rs @@ -67,11 +67,12 @@ impl Proof { // // where p_j(X) is the jth advice column in this permutation, // and i is the ith row of the column. - .map(|(columns, permuted_values)| { + .map(|(p, pkey)| { let mut modified_advice = vec![C::Scalar::one(); params.n as usize]; // Iterate over each column of the permutation - for (&column, permuted_column_values) in columns.iter().zip(permuted_values.iter()) + for (&column, permuted_column_values) in + p.columns.iter().zip(pkey.permutations.iter()) { parallelize(&mut modified_advice, |modified_advice, start| { for ((modified_advice, advice_value), permuted_advice_value) in @@ -97,7 +98,7 @@ impl Proof { .flat_map(|v| v.iter_mut()) .batch_invert(); - for (columns, mut modified_advice) in pk + for (p, mut modified_advice) in pk .vk .cs .permutations @@ -107,7 +108,7 @@ impl Proof { // Iterate over each column again, this time finishing the computation // of the entire fraction by computing the numerators let mut deltaomega = C::Scalar::one(); - for &column in columns.iter() { + for &column in p.columns.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]); @@ -195,8 +196,8 @@ impl Committed { > { let domain = &pk.vk.domain; let permutation_product_cosets_owned = self.permutation_product_cosets.clone(); - let permutation_product_cosets = self.permutation_product_cosets; - let permutation_product_cosets_inv = self.permutation_product_cosets_inv; + let permutation_product_cosets = self.permutation_product_cosets.clone(); + let permutation_product_cosets_inv = self.permutation_product_cosets_inv.clone(); let expressions = iter::empty() // l_0(X) * (1 - z(X)) = 0 @@ -206,46 +207,56 @@ impl Committed { .map(move |coset| Polynomial::one_minus(coset) * &pk.l0), ) // z(X) \prod (p(X) + \beta s_i(X) + \gamma) - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) - .chain(pk.vk.cs.permutations.iter().enumerate().map( - move |(permutation_index, columns)| { - let mut left = permutation_product_cosets[permutation_index].clone(); - for (advice, permutation) in columns - .iter() - .map(|&column| &advice_cosets[pk.vk.cs.get_advice_query_index(column, 0)]) - .zip(pk.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 + &(*beta * permutation) + &gamma); - } - }); - } + .chain( + pk.vk + .cs + .permutations + .iter() + .zip(pk.permutations.iter()) + .zip(permutation_product_cosets.into_iter()) + .zip(permutation_product_cosets_inv.into_iter()) + .map(move |(((p, pkey), cosets), cosets_inv)| { + let mut left = cosets; + for (advice, permutation) in p + .columns + .iter() + .map(|&column| { + &advice_cosets[pk.vk.cs.get_advice_query_index(column, 0)] + }) + .zip(pkey.cosets.iter()) + { + parallelize(&mut left, |left, start| { + for ((left, advice), permutation) in left + .iter_mut() + .zip(advice[start..].iter()) + .zip(permutation[start..].iter()) + { + *left *= &(*advice + &(*beta * permutation) + &gamma); + } + }); + } - let mut right = permutation_product_cosets_inv[permutation_index].clone(); - let mut current_delta = *beta * &C::Scalar::ZETA; - let step = domain.get_extended_omega(); - for advice in columns - .iter() - .map(|&column| &advice_cosets[pk.vk.cs.get_advice_query_index(column, 0)]) - { - 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 + &gamma); - beta_term *= &step; - } - }); - current_delta *= &C::Scalar::DELTA; - } + let mut right = cosets_inv; + let mut current_delta = *beta * &C::Scalar::ZETA; + let step = domain.get_extended_omega(); + for advice in p.columns.iter().map(|&column| { + &advice_cosets[pk.vk.cs.get_advice_query_index(column, 0)] + }) { + 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 + &gamma); + beta_term *= &step; + } + }); + current_delta *= &C::Scalar::DELTA; + } - left - &right - }, - )); + left - &right + }), + ); Ok(( Constructed { @@ -258,6 +269,31 @@ impl Committed { } } +impl super::ProvingKey { + fn evaluate(&self, x: ChallengeX) -> Vec { + self.polys + .iter() + .map(|poly| eval_polynomial(poly, *x)) + .collect() + } + + fn open<'a>( + &'a self, + evals: &'a [C::Scalar], + x: ChallengeX, + ) -> impl Iterator> + Clone { + self.polys + .iter() + .zip(evals.iter()) + .map(move |(poly, eval)| ProverQuery { + point: *x, + poly, + blind: Blind::default(), + eval: *eval, + }) + } +} + impl Constructed { pub(crate) fn evaluate, HScalar: Hasher>( self, @@ -279,11 +315,7 @@ impl Constructed { .map(|poly| eval_polynomial(poly, domain.rotate_omega(*x, Rotation(-1)))) .collect(); - let permutation_evals: Vec> = pk - .permutation_polys - .iter() - .map(|polys| polys.iter().map(|poly| eval_polynomial(poly, *x)).collect()) - .collect(); + let permutation_evals: Vec<_> = pk.permutations.iter().map(|p| p.evaluate(x)).collect(); // Hash each advice evaluation for eval in permutation_product_evals @@ -328,16 +360,10 @@ impl Evaluated { ) // Open permutation polynomial commitments at x .chain( - pk.permutation_polys + pk.permutations .iter() .zip(self.permutation_evals.iter()) - .flat_map(|(polys, evals)| polys.iter().zip(evals.iter())) - .map(move |(poly, eval)| ProverQuery { - point: *x, - poly, - blind: Blind::default(), - eval: *eval, - }), + .flat_map(move |(permutation, evals)| permutation.open(evals, x)), ) // Open permutation product commitments at \omega^{-1} x .chain( diff --git a/src/plonk/permutation/verifier.rs b/src/plonk/permutation/verifier.rs index a5af78e..63fb439 100644 --- a/src/plonk/permutation/verifier.rs +++ b/src/plonk/permutation/verifier.rs @@ -15,10 +15,8 @@ impl Proof { return Err(Error::IncompatibleParams); } - for (permutation_evals, permutation) in - self.permutation_evals.iter().zip(vk.cs.permutations.iter()) - { - if permutation_evals.len() != permutation.len() { + for (permutation_evals, p) in self.permutation_evals.iter().zip(vk.cs.permutations.iter()) { + if permutation_evals.len() != p.columns.len() { return Err(Error::IncompatibleParams); } } @@ -76,9 +74,10 @@ impl Proof { .zip(self.permutation_product_evals.iter()) .zip(self.permutation_product_inv_evals.iter()) .map( - move |(((columns, permutation_evals), product_eval), product_inv_eval)| { + move |(((p, permutation_evals), product_eval), product_inv_eval)| { let mut left = *product_eval; - for (advice_eval, permutation_eval) in columns + for (advice_eval, permutation_eval) in p + .columns .iter() .map(|&column| { advice_evals[vk.cs.get_advice_query_index(column, 0)] @@ -90,7 +89,7 @@ impl Proof { let mut right = *product_inv_eval; let mut current_delta = *beta * &x; - for advice_eval in columns.iter().map(|&column| { + for advice_eval in p.columns.iter().map(|&column| { advice_evals[vk.cs.get_advice_query_index(column, 0)] }) { right *= &(advice_eval + ¤t_delta + &gamma); @@ -132,12 +131,12 @@ impl Proof { ) // Open permutation commitments for each permutation argument at x .chain( - (0..vk.permutation_commitments.len()) + (0..vk.permutations.len()) .map(move |outer_idx| { - let inner_len = vk.permutation_commitments[outer_idx].len(); + let inner_len = vk.permutations[outer_idx].commitments.len(); (0..inner_len).map(move |inner_idx| VerifierQuery { point: *x, - commitment: &vk.permutation_commitments[outer_idx][inner_idx], + commitment: &vk.permutations[outer_idx].commitments[inner_idx], eval: self.permutation_evals[outer_idx][inner_idx], }) })