diff --git a/src/plonk.rs b/src/plonk.rs index 7953cba..e938d71 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -12,6 +12,7 @@ use crate::poly::{ mod circuit; mod keygen; +mod permutation; mod prover; mod verifier; @@ -50,10 +51,7 @@ pub struct ProvingKey { 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>, + permutations: Option>, advice_evals: Vec, aux_evals: Vec, fixed_evals: Vec, diff --git a/src/plonk/permutation.rs b/src/plonk/permutation.rs new file mode 100644 index 0000000..74a4714 --- /dev/null +++ b/src/plonk/permutation.rs @@ -0,0 +1,14 @@ +//! Implementation of a PLONK permutation argument. + +use crate::arithmetic::CurveAffine; + +mod prover; +mod verifier; + +#[derive(Debug, Clone)] +pub(crate) struct Proof { + permutation_product_commitments: Vec, + permutation_product_evals: Vec, + permutation_product_inv_evals: Vec, + permutation_evals: Vec>, +} diff --git a/src/plonk/permutation/prover.rs b/src/plonk/permutation/prover.rs new file mode 100644 index 0000000..4970212 --- /dev/null +++ b/src/plonk/permutation/prover.rs @@ -0,0 +1,371 @@ +use ff::Field; +use std::iter; + +use super::Proof; +use crate::{ + arithmetic::{eval_polynomial, parallelize, BatchInvert, Curve, CurveAffine, FieldExt}, + plonk::{Error, ProvingKey}, + poly::{ + commitment::{Blind, Params}, + multiopen::ProverQuery, + Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation, + }, + transcript::{Hasher, Transcript}, +}; + +#[derive(Clone)] +pub(crate) struct Committed { + permutation_product_polys: Vec>, + permutation_product_cosets: Vec>, + permutation_product_cosets_inv: Vec>, + permutation_product_blinds: Vec>, + permutation_product_commitments: Vec, +} + +pub(crate) struct Constructed { + permutation_product_polys: Vec>, + permutation_product_blinds: Vec>, + permutation_product_commitments: Vec, +} + +pub(crate) struct Evaluated { + constructed: Constructed, + permutation_product_evals: Vec, + permutation_product_inv_evals: Vec, + permutation_evals: Vec>, +} + +impl Proof { + pub(crate) fn commit, HScalar: Hasher>( + params: &Params, + pk: &ProvingKey, + advice: &[Polynomial], + x_0: C::Scalar, + x_1: C::Scalar, + transcript: &mut Transcript, + ) -> Result, Error> { + let domain = &pk.vk.domain; + + // 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 = pk + .vk + .cs + .permutations + .iter() + .zip(pk.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 column in this permutation, + // and i is the ith row of the column. + .map(|(columns, permuted_values)| { + 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()) + { + parallelize(&mut modified_advice, |modified_advice, start| { + for ((modified_advice, advice_value), permuted_advice_value) in + modified_advice + .iter_mut() + .zip(advice[column.index()][start..].iter()) + .zip(permuted_column_values[start..].iter()) + { + *modified_advice *= + &(x_0 * permuted_advice_value + &x_1 + advice_value); + } + }); + } + + modified_advice + }) + .collect::>(); + + // 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 (columns, mut modified_advice) in pk + .vk + .cs + .permutations + .iter() + .zip(permutation_modified_advice.into_iter()) + { + // 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() { + 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(advice[column.index()][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 column 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 z = domain.lagrange_from_vec(z); + + let blind = Blind(C::Scalar::rand()); + + permutation_product_commitments_projective.push(params.commit_lagrange(&z, blind)); + permutation_product_blinds.push(blind); + let z = domain.lagrange_to_coeff(z); + permutation_product_polys.push(z.clone()); + permutation_product_cosets + .push(domain.coeff_to_extended(z.clone(), Rotation::default())); + permutation_product_cosets_inv.push(domain.coeff_to_extended(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 { + transcript + .absorb_point(c) + .map_err(|_| Error::TranscriptError)?; + } + + Ok(Committed { + permutation_product_polys, + permutation_product_cosets, + permutation_product_cosets_inv, + permutation_product_blinds, + permutation_product_commitments, + }) + } +} + +impl Committed { + pub(crate) fn construct<'a>( + self, + pk: &'a ProvingKey, + advice_cosets: &'a [Polynomial], + x_0: C::Scalar, + x_1: C::Scalar, + ) -> Result< + ( + Constructed, + impl Iterator> + 'a, + ), + Error, + > { + 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 expressions = iter::empty() + // l_0(X) * (1 - z(X)) = 0 + .chain( + permutation_product_cosets_owned + .into_iter() + .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 + &(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 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 + &x_1); + beta_term *= &step; + } + }); + current_delta *= &C::Scalar::DELTA; + } + + left - &right + }, + )); + + Ok(( + Constructed { + permutation_product_polys: self.permutation_product_polys, + permutation_product_blinds: self.permutation_product_blinds, + permutation_product_commitments: self.permutation_product_commitments, + }, + expressions, + )) + } +} + +impl Constructed { + pub(crate) fn evaluate, HScalar: Hasher>( + self, + pk: &ProvingKey, + x_3: C::Scalar, + transcript: &mut Transcript, + ) -> Evaluated { + let domain = &pk.vk.domain; + + let permutation_product_evals: Vec = self + .permutation_product_polys + .iter() + .map(|poly| eval_polynomial(poly, x_3)) + .collect(); + + let permutation_product_inv_evals: Vec = self + .permutation_product_polys + .iter() + .map(|poly| eval_polynomial(poly, domain.rotate_omega(x_3, Rotation(-1)))) + .collect(); + + let permutation_evals: Vec> = pk + .permutation_polys + .iter() + .map(|polys| { + polys + .iter() + .map(|poly| eval_polynomial(poly, x_3)) + .collect() + }) + .collect(); + + // Hash each advice evaluation + for eval in permutation_product_evals + .iter() + .chain(permutation_product_inv_evals.iter()) + .chain(permutation_evals.iter().flat_map(|evals| evals.iter())) + { + transcript.absorb_scalar(*eval); + } + + Evaluated { + constructed: self, + permutation_product_evals, + permutation_product_inv_evals, + permutation_evals, + } + } +} + +impl Evaluated { + pub fn open<'a>( + &'a self, + pk: &'a ProvingKey, + x_3: C::Scalar, + ) -> impl Iterator> + Clone { + let x_3_inv = pk.vk.domain.rotate_omega(x_3, Rotation(-1)); + + iter::empty() + // Open permutation product commitments at x_3 + .chain( + self.constructed + .permutation_product_polys + .iter() + .zip(self.constructed.permutation_product_blinds.iter()) + .zip(self.permutation_product_evals.iter()) + .map(move |((poly, blind), eval)| ProverQuery { + point: x_3, + poly, + blind: *blind, + eval: *eval, + }), + ) + // Open permutation polynomial commitments at x_3 + .chain( + pk.permutation_polys + .iter() + .zip(self.permutation_evals.iter()) + .flat_map(|(polys, evals)| polys.iter().zip(evals.iter())) + .map(move |(poly, eval)| ProverQuery { + point: x_3, + poly, + blind: Blind::default(), + eval: *eval, + }), + ) + // Open permutation product commitments at \omega^{-1} x_3 + .chain( + self.constructed + .permutation_product_polys + .iter() + .zip(self.constructed.permutation_product_blinds.iter()) + .zip(self.permutation_product_inv_evals.iter()) + .map(move |((poly, blind), eval)| ProverQuery { + point: x_3_inv, + poly, + blind: *blind, + eval: *eval, + }), + ) + } + + pub(crate) fn build(self) -> Proof { + Proof { + permutation_product_commitments: self.constructed.permutation_product_commitments, + permutation_product_evals: self.permutation_product_evals, + permutation_product_inv_evals: self.permutation_product_inv_evals, + permutation_evals: self.permutation_evals, + } + } +} diff --git a/src/plonk/permutation/verifier.rs b/src/plonk/permutation/verifier.rs new file mode 100644 index 0000000..afd6801 --- /dev/null +++ b/src/plonk/permutation/verifier.rs @@ -0,0 +1,159 @@ +use ff::Field; +use std::iter; + +use super::Proof; +use crate::{ + arithmetic::{CurveAffine, FieldExt}, + plonk::{Error, VerifyingKey}, + poly::{multiopen::VerifierQuery, Rotation}, + transcript::{Hasher, Transcript}, +}; + +impl Proof { + pub(crate) fn check_lengths(&self, vk: &VerifyingKey) -> Result<(), Error> { + if self.permutation_evals.len() != vk.cs.permutations.len() { + return Err(Error::IncompatibleParams); + } + + for (permutation_evals, permutation) in + self.permutation_evals.iter().zip(vk.cs.permutations.iter()) + { + if permutation_evals.len() != permutation.len() { + return Err(Error::IncompatibleParams); + } + } + + if self.permutation_product_inv_evals.len() != vk.cs.permutations.len() { + return Err(Error::IncompatibleParams); + } + + if self.permutation_product_evals.len() != vk.cs.permutations.len() { + return Err(Error::IncompatibleParams); + } + + if self.permutation_product_commitments.len() != vk.cs.permutations.len() { + return Err(Error::IncompatibleParams); + } + + Ok(()) + } + + pub(crate) fn absorb_commitments, HScalar: Hasher>( + &self, + transcript: &mut Transcript, + ) -> Result<(), Error> { + for c in &self.permutation_product_commitments { + transcript + .absorb_point(c) + .map_err(|_| Error::TranscriptError)?; + } + Ok(()) + } + + pub(crate) fn expressions<'a>( + &'a self, + vk: &'a VerifyingKey, + advice_evals: &'a [C::Scalar], + l_0: C::Scalar, + x_0: C::Scalar, + x_1: C::Scalar, + x_3: C::Scalar, + ) -> impl Iterator + 'a { + iter::empty() + // l_0(X) * (1 - z(X)) = 0 + .chain( + self.permutation_product_evals + .iter() + .map(move |product_eval| l_0 * &(C::Scalar::one() - product_eval)), + ) + // z(X) \prod (p(X) + \beta s_i(X) + \gamma) + // - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) + .chain( + vk.cs + .permutations + .iter() + .zip(self.permutation_evals.iter()) + .zip(self.permutation_product_evals.iter()) + .zip(self.permutation_product_inv_evals.iter()) + .map( + move |(((columns, permutation_evals), product_eval), product_inv_eval)| { + let mut left = *product_eval; + for (advice_eval, permutation_eval) in columns + .iter() + .map(|&column| { + advice_evals[vk.cs.get_advice_query_index(column, 0)] + }) + .zip(permutation_evals.iter()) + { + left *= &(advice_eval + &(x_0 * permutation_eval) + &x_1); + } + + let mut right = *product_inv_eval; + let mut current_delta = x_0 * &x_3; + for advice_eval in columns.iter().map(|&column| { + advice_evals[vk.cs.get_advice_query_index(column, 0)] + }) { + right *= &(advice_eval + ¤t_delta + &x_1); + current_delta *= &C::Scalar::DELTA; + } + + left - &right + }, + ), + ) + } + + pub(crate) fn evals(&self) -> impl Iterator { + self.permutation_product_evals + .iter() + .chain(self.permutation_product_inv_evals.iter()) + .chain(self.permutation_evals.iter().flat_map(|evals| evals.iter())) + } + + pub(crate) fn queries<'a>( + &'a self, + vk: &'a VerifyingKey, + x_3: C::Scalar, + ) -> impl Iterator> + Clone { + let x_3_inv = vk.domain.rotate_omega(x_3, Rotation(-1)); + + iter::empty() + // Open permutation product commitments at x_3 + .chain( + self.permutation_product_commitments + .iter() + .enumerate() + .zip(self.permutation_product_evals.iter()) + .map(move |((idx, _), &eval)| VerifierQuery { + point: x_3, + commitment: &self.permutation_product_commitments[idx], + eval, + }), + ) + // Open permutation commitments for each permutation argument at x_3 + .chain( + (0..vk.permutation_commitments.len()) + .map(move |outer_idx| { + let inner_len = vk.permutation_commitments[outer_idx].len(); + (0..inner_len).map(move |inner_idx| VerifierQuery { + point: x_3, + commitment: &vk.permutation_commitments[outer_idx][inner_idx], + eval: self.permutation_evals[outer_idx][inner_idx], + }) + }) + .flatten(), + ) + // Open permutation product commitments at \omega^{-1} x_3 + .chain( + self.permutation_product_commitments + .iter() + .enumerate() + .zip(self.permutation_product_inv_evals.iter()) + .map(move |((idx, _), &eval)| VerifierQuery { + point: x_3_inv, + commitment: &self.permutation_product_commitments[idx], + eval, + }), + ) + } +} diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 8d75168..cc8827a 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -3,16 +3,15 @@ use std::iter; use super::{ circuit::{Advice, Assignment, Circuit, Column, ConstraintSystem, Fixed}, - Error, Proof, ProvingKey, + permutation, Error, Proof, ProvingKey, }; use crate::arithmetic::{ - eval_polynomial, get_challenge_scalar, parallelize, BatchInvert, Challenge, Curve, CurveAffine, - FieldExt, + eval_polynomial, get_challenge_scalar, Challenge, Curve, CurveAffine, FieldExt, }; use crate::poly::{ commitment::{Blind, Params}, multiopen::{self, ProverQuery}, - LagrangeCoeff, Polynomial, Rotation, + LagrangeCoeff, Polynomial, }; use crate::transcript::{Hasher, Transcript}; @@ -177,197 +176,46 @@ impl Proof { // 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 = pk - .vk - .cs - .permutations - .iter() - .zip(pk.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 column in this permutation, - // and i is the ith row of the column. - .map(|(columns, permuted_values)| { - 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()) - { - parallelize(&mut modified_advice, |modified_advice, start| { - for ((modified_advice, advice_value), permuted_advice_value) in - modified_advice - .iter_mut() - .zip(witness.advice[column.index()][start..].iter()) - .zip(permuted_column_values[start..].iter()) - { - *modified_advice *= - &(x_0 * permuted_advice_value + &x_1 + advice_value); - } - }); - } - - modified_advice - }) - .collect::>(); - - // 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 (columns, mut modified_advice) in pk - .vk - .cs - .permutations - .iter() - .zip(permutation_modified_advice.into_iter()) - { - // 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() { - 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[column.index()][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 column 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 z = domain.lagrange_from_vec(z); - - let blind = Blind(C::Scalar::rand()); - - permutation_product_commitments_projective.push(params.commit_lagrange(&z, blind)); - permutation_product_blinds.push(blind); - let z = domain.lagrange_to_coeff(z); - permutation_product_polys.push(z.clone()); - permutation_product_cosets - .push(domain.coeff_to_extended(z.clone(), Rotation::default())); - permutation_product_cosets_inv.push(domain.coeff_to_extended(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 { - transcript - .absorb_point(c) - .map_err(|_| Error::TranscriptError)?; - } + // Commit to permutations, if any. + let permutations = if !pk.vk.cs.permutations.is_empty() { + Some(permutation::Proof::commit( + params, + pk, + &witness.advice, + x_0, + x_1, + &mut transcript, + )?) + } else { + None + }; // Obtain challenge for keeping all separate gates linearly independent let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + // Evaluate the h(X) polynomial's constraint system expressions for the permutation constraints, if any. + let (permutations, permutation_expressions) = permutations + .map(|p| p.construct(pk, &advice_cosets, x_0, x_1)) + .transpose()? + .map(|(p, expressions)| (Some(p), Some(expressions))) + .unwrap_or_default(); + // Evaluate the h(X) polynomial's constraint system expressions for the constraints provided - let h_poly = - iter::empty() - // Custom constraints - .chain(meta.gates.iter().map(|poly| { - poly.evaluate( - &|index| pk.fixed_cosets[index].clone(), - &|index| advice_cosets[index].clone(), - &|index| aux_cosets[index].clone(), - &|a, b| a + &b, - &|a, b| a * &b, - &|a, scalar| a * scalar, - ) - })) - // l_0(X) * (1 - z(X)) = 0 - .chain( - permutation_product_cosets - .iter() - .cloned() - .map(|coset| Polynomial::one_minus(coset) * &pk.l0), + let h_poly = iter::empty() + // Custom constraints + .chain(meta.gates.iter().map(|poly| { + poly.evaluate( + &|index| pk.fixed_cosets[index].clone(), + &|index| advice_cosets[index].clone(), + &|index| aux_cosets[index].clone(), + &|a, b| a + &b, + &|a, b| a * &b, + &|a, scalar| a * scalar, ) - // 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( - |(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 + &(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 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 + &x_1); - beta_term *= &step; - } - }); - current_delta *= &C::Scalar::DELTA; - } - - left - &right - }, - )) - .fold(domain.empty_extended(), |h_poly, v| h_poly * x_2 + &v); + })) + // Permutation constraints, if any. + .chain(permutation_expressions.into_iter().flatten()) + .fold(domain.empty_extended(), |h_poly, v| h_poly * x_2 + &v); // Divide by t(X) = X^{params.n} - 1. let h_poly = domain.divide_by_vanishing_poly(h_poly); @@ -402,7 +250,6 @@ impl Proof { } let x_3: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - let x_3_inv = domain.rotate_omega(x_3, Rotation(-1)); // Evaluate polynomials at omega^i x_3 let advice_evals: Vec<_> = meta @@ -432,27 +279,6 @@ 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> = pk - .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)) @@ -464,13 +290,13 @@ impl Proof { .chain(aux_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.absorb_scalar(*eval); } + // Evaluate the permutations, if any, at omega^i x_3. + let permutations = permutations.map(|p| p.evaluate(pk, x_3, &mut transcript)); + let instances = iter::empty() .chain(pk.vk.cs.advice_queries.iter().enumerate().map( @@ -511,68 +337,23 @@ impl Proof { }), ); - // Handle permutation arguments, if any exist - let permutation_instances = if !pk.vk.cs.permutations.is_empty() { - Some( - iter::empty() - // Open permutation product commitments at x_3 - .chain( - permutation_product_polys - .iter() - .zip(permutation_product_blinds.iter()) - .zip(permutation_product_evals.iter()) - .map(|((poly, blind), eval)| ProverQuery { - point: x_3, - poly, - blind: *blind, - eval: *eval, - }), - ) - // Open permutation polynomial commitments at x_3 - .chain( - pk.permutation_polys - .iter() - .zip(permutation_evals.iter()) - .flat_map(|(polys, evals)| polys.iter().zip(evals.iter())) - .map(|(poly, eval)| ProverQuery { - point: x_3, - poly, - blind: Blind::default(), - eval: *eval, - }), - ) - // Open permutation product commitments at \omega^{-1} x_3 - .chain( - permutation_product_polys - .iter() - .zip(permutation_product_blinds.iter()) - .zip(permutation_product_inv_evals.iter()) - .map(|((poly, blind), eval)| ProverQuery { - point: x_3_inv, - poly, - blind: *blind, - eval: *eval, - }), - ), - ) - } else { - None - }; - let multiopening = multiopen::Proof::create( params, &mut transcript, - instances.chain(permutation_instances.into_iter().flatten()), + instances.chain( + permutations + .as_ref() + .map(|p| p.open(pk, x_3)) + .into_iter() + .flatten(), + ), ) .map_err(|_| Error::OpeningError)?; Ok(Proof { advice_commitments, h_commitments, - permutation_product_commitments, - permutation_product_evals, - permutation_product_inv_evals, - permutation_evals, + permutations: permutations.map(|p| p.build()), advice_evals, fixed_evals, aux_evals, diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index ce5eb3c..4450d28 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -6,7 +6,6 @@ use crate::arithmetic::{get_challenge_scalar, Challenge, CurveAffine, FieldExt}; use crate::poly::{ commitment::{Guard, Params, MSM}, multiopen::VerifierQuery, - Rotation, }; use crate::transcript::{Hasher, Transcript}; @@ -53,10 +52,8 @@ impl<'a, C: CurveAffine> Proof { 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 { - transcript - .absorb_point(c) - .map_err(|_| Error::TranscriptError)?; + if let Some(p) = &self.permutations { + p.absorb_commitments(&mut transcript)?; } // Sample x_2 challenge, which keeps the gates linearly independent. @@ -72,7 +69,6 @@ impl<'a, C: CurveAffine> 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_3_inv = vk.domain.rotate_omega(x_3, Rotation(-1)); // This check ensures the circuit is satisfied so long as the polynomial // commitments open to the correct values. @@ -84,9 +80,13 @@ impl<'a, C: CurveAffine> Proof { .chain(self.aux_evals.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())) + .chain( + self.permutations + .as_ref() + .map(|p| p.evals()) + .into_iter() + .flatten(), + ) { transcript.absorb_scalar(*eval); } @@ -130,59 +130,19 @@ impl<'a, C: CurveAffine> Proof { }), ); - // Handle permutation arguments, if any exist - let permutation_queries = if !vk.cs.permutations.is_empty() { - Some( - iter::empty() - // Open permutation product commitments at x_3 - .chain( - self.permutation_product_commitments - .iter() - .enumerate() - .zip(self.permutation_product_evals.iter()) - .map(|((idx, _), &eval)| VerifierQuery { - point: x_3, - commitment: &self.permutation_product_commitments[idx], - eval, - }), - ) - // Open permutation commitments for each permutation argument at x_3 - .chain( - (0..vk.permutation_commitments.len()) - .map(|outer_idx| { - let inner_len = vk.permutation_commitments[outer_idx].len(); - (0..inner_len).map(move |inner_idx| VerifierQuery { - point: x_3, - commitment: &vk.permutation_commitments[outer_idx][inner_idx], - eval: self.permutation_evals[outer_idx][inner_idx], - }) - }) - .flatten(), - ) - // Open permutation product commitments at \omega^{-1} x_3 - .chain( - self.permutation_product_commitments - .iter() - .enumerate() - .zip(self.permutation_product_inv_evals.iter()) - .map(|((idx, _), &eval)| VerifierQuery { - point: x_3_inv, - commitment: &self.permutation_product_commitments[idx], - eval, - }), - ), - ) - } else { - None - }; - // We are now convinced the circuit is satisfied so long as the // polynomial commitments open to the correct values. self.multiopening .verify( params, &mut transcript, - queries.chain(permutation_queries.into_iter().flatten()), + queries.chain( + self.permutations + .as_ref() + .map(|p| p.queries(vk, x_3)) + .into_iter() + .flatten(), + ), msm, ) .map_err(|_| Error::OpeningError) @@ -209,29 +169,10 @@ impl<'a, C: CurveAffine> Proof { return Err(Error::IncompatibleParams); } - if self.permutation_evals.len() != vk.cs.permutations.len() { - return Err(Error::IncompatibleParams); - } - - for (permutation_evals, permutation) in - self.permutation_evals.iter().zip(vk.cs.permutations.iter()) - { - if permutation_evals.len() != permutation.len() { - return Err(Error::IncompatibleParams); - } - } - - if self.permutation_product_inv_evals.len() != vk.cs.permutations.len() { - return Err(Error::IncompatibleParams); - } - - if self.permutation_product_evals.len() != vk.cs.permutations.len() { - return Err(Error::IncompatibleParams); - } - - if self.permutation_product_commitments.len() != vk.cs.permutations.len() { - return Err(Error::IncompatibleParams); - } + self.permutations + .as_ref() + .map(|p| p.check_lengths(vk)) + .transpose()?; // TODO: check h_commitments @@ -275,46 +216,12 @@ impl<'a, C: CurveAffine> Proof { &|a, scalar| a * &scalar, ) })) - // l_0(X) * (1 - z(X)) = 0 .chain( - self.permutation_product_evals - .iter() - .map(|product_eval| l_0 * &(C::Scalar::one() - product_eval)), - ) - // z(X) \prod (p(X) + \beta s_i(X) + \gamma) - // - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) - .chain( - vk.cs - .permutations - .iter() - .zip(self.permutation_evals.iter()) - .zip(self.permutation_product_evals.iter()) - .zip(self.permutation_product_inv_evals.iter()) - .map( - |(((columns, permutation_evals), product_eval), product_inv_eval)| { - let mut left = *product_eval; - for (advice_eval, permutation_eval) in columns - .iter() - .map(|&column| { - self.advice_evals[vk.cs.get_advice_query_index(column, 0)] - }) - .zip(permutation_evals.iter()) - { - left *= &(advice_eval + &(x_0 * permutation_eval) + &x_1); - } - - let mut right = *product_inv_eval; - let mut current_delta = x_0 * &x_3; - for advice_eval in columns.iter().map(|&column| { - self.advice_evals[vk.cs.get_advice_query_index(column, 0)] - }) { - right *= &(advice_eval + ¤t_delta + &x_1); - current_delta *= &C::Scalar::DELTA; - } - - left - &right - }, - ), + self.permutations + .as_ref() + .map(|p| p.expressions(vk, &self.advice_evals, l_0, x_0, x_1, x_3)) + .into_iter() + .flatten(), ) .fold(C::Scalar::zero(), |h_eval, v| h_eval * &x_2 + &v);