use group::{ ff::{BatchInvert, Field, PrimeField}, Curve, }; use rand_core::RngCore; use std::iter::{self, ExactSizeIterator}; use super::super::{circuit::Any, ChallengeBeta, ChallengeGamma, ChallengeX}; use super::{Argument, ProvingKey}; use crate::{ arithmetic::{eval_polynomial, parallelize, CurveAffine}, plonk::{self, Error}, poly::{ self, commitment::{Blind, Params}, multiopen::ProverQuery, Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation, }, transcript::{EncodedChallenge, TranscriptWrite}, }; pub struct CommittedSet { permutation_product_poly: Polynomial, permutation_product_coset: poly::AstLeaf, permutation_product_blind: Blind, } pub(crate) struct Committed { sets: Vec>, } pub struct ConstructedSet { permutation_product_poly: Polynomial, permutation_product_blind: Blind, } pub(crate) struct Constructed { sets: Vec>, } pub(crate) struct Evaluated { constructed: Constructed, } impl Argument { #[allow(clippy::too_many_arguments)] pub(in crate::plonk) fn commit< C: CurveAffine, E: EncodedChallenge, Ev: Copy + Send + Sync, R: RngCore, T: TranscriptWrite, >( &self, params: &Params, pk: &plonk::ProvingKey, pkey: &ProvingKey, advice: &[Polynomial], fixed: &[Polynomial], instance: &[Polynomial], beta: ChallengeBeta, gamma: ChallengeGamma, evaluator: &mut poly::Evaluator, mut rng: R, transcript: &mut T, ) -> Result, Error> { let domain = &pk.vk.domain; // How many columns can be included in a single permutation polynomial? // We need to multiply by z(X) and (1 - (l_last(X) + l_blind(X))). This // will never underflow because of the requirement of at least a degree // 3 circuit for the permutation argument. assert!(pk.vk.cs_degree >= 3); let chunk_len = pk.vk.cs_degree - 2; let blinding_factors = pk.vk.cs.blinding_factors(); // Each column gets its own delta power. let mut deltaomega = C::Scalar::ONE; // Track the "last" value from the previous column set let mut last_z = C::Scalar::ONE; let mut sets = vec![]; for (columns, permutations) in self .columns .chunks(chunk_len) .zip(pkey.permutations.chunks(chunk_len)) { // 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 column in this permutation, // and i is the ith row of the column. let mut modified_values = vec![C::Scalar::ONE; params.n as usize]; // Iterate over each column of the permutation for (&column, permuted_column_values) in columns.iter().zip(permutations.iter()) { let values = match column.column_type() { Any::Advice => advice, Any::Fixed => fixed, Any::Instance => instance, }; parallelize(&mut modified_values, |modified_values, start| { for ((modified_values, value), permuted_value) in modified_values .iter_mut() .zip(values[column.index()][start..].iter()) .zip(permuted_column_values[start..].iter()) { *modified_values *= &(*beta * permuted_value + &*gamma + value); } }); } // Invert to obtain the denominator for the permutation product polynomial modified_values.batch_invert(); // Iterate over each column again, this time finishing the computation // of the entire fraction by computing the numerators for &column in columns.iter() { let omega = domain.get_omega(); let values = match column.column_type() { Any::Advice => advice, Any::Fixed => fixed, Any::Instance => instance, }; parallelize(&mut modified_values, |modified_values, start| { let mut deltaomega = deltaomega * &omega.pow_vartime([start as u64, 0, 0, 0]); for (modified_values, value) in modified_values .iter_mut() .zip(values[column.index()][start..].iter()) { // Multiply by p_j(\omega^i) + \delta^j \omega^i \beta *modified_values *= &(deltaomega * &*beta + &*gamma + value); deltaomega *= ω } }); deltaomega *= &C::Scalar::DELTA; } // The modified_values 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_values, 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![last_z]; for row in 1..(params.n as usize) { let mut tmp = z[row - 1]; tmp *= &modified_values[row - 1]; z.push(tmp); } let mut z = domain.lagrange_from_vec(z); // Set blinding factors for z in &mut z[params.n as usize - blinding_factors..] { *z = C::Scalar::random(&mut rng); } // Set new last_z last_z = z[params.n as usize - (blinding_factors + 1)]; let blind = Blind(C::Scalar::random(&mut rng)); let permutation_product_commitment_projective = params.commit_lagrange(&z, blind); let permutation_product_blind = blind; let z = domain.lagrange_to_coeff(z); let permutation_product_poly = z.clone(); let permutation_product_coset = evaluator.register_poly(domain.coeff_to_extended(z.clone())); let permutation_product_commitment = permutation_product_commitment_projective.to_affine(); // Hash the permutation product commitment transcript.write_point(permutation_product_commitment)?; sets.push(CommittedSet { permutation_product_poly, permutation_product_coset, permutation_product_blind, }); } Ok(Committed { sets }) } } impl Committed { #[allow(clippy::too_many_arguments)] pub(in crate::plonk) fn construct<'a>( self, pk: &'a plonk::ProvingKey, p: &'a Argument, advice_cosets: &'a [poly::AstLeaf], fixed_cosets: &'a [poly::AstLeaf], instance_cosets: &'a [poly::AstLeaf], permutation_cosets: &'a [poly::AstLeaf], l0: poly::AstLeaf, l_blind: poly::AstLeaf, l_last: poly::AstLeaf, beta: ChallengeBeta, gamma: ChallengeGamma, ) -> ( Constructed, impl Iterator> + 'a, ) { let chunk_len = pk.vk.cs_degree - 2; let blinding_factors = pk.vk.cs.blinding_factors(); let last_rotation = Rotation(-((blinding_factors + 1) as i32)); let constructed = Constructed { sets: self .sets .iter() .map(|set| ConstructedSet { permutation_product_poly: set.permutation_product_poly.clone(), permutation_product_blind: set.permutation_product_blind, }) .collect(), }; let expressions = iter::empty() // Enforce only for the first set. // l_0(X) * (1 - z_0(X)) = 0 .chain( self.sets .first() .map(|first_set| (poly::Ast::one() - first_set.permutation_product_coset) * l0), ) // Enforce only for the last set. // l_last(X) * (z_l(X)^2 - z_l(X)) = 0 .chain(self.sets.last().map(|last_set| { ((poly::Ast::from(last_set.permutation_product_coset) * last_set.permutation_product_coset) - last_set.permutation_product_coset) * l_last })) // Except for the first set, enforce. // l_0(X) * (z_i(X) - z_{i-1}(\omega^(last) X)) = 0 .chain( self.sets .iter() .skip(1) .zip(self.sets.iter()) .map(|(set, last_set)| { (poly::Ast::from(set.permutation_product_coset) - last_set .permutation_product_coset .with_rotation(last_rotation)) * l0 }) .collect::>(), ) // And for all the sets we enforce: // (1 - (l_last(X) + l_blind(X))) * ( // z_i(\omega X) \prod_j (p(X) + \beta s_j(X) + \gamma) // - z_i(X) \prod_j (p(X) + \delta^j \beta X + \gamma) // ) .chain( self.sets .into_iter() .zip(p.columns.chunks(chunk_len)) .zip(permutation_cosets.chunks(chunk_len)) .enumerate() .map(move |(chunk_index, ((set, columns), cosets))| { let mut left = poly::Ast::<_, C::Scalar, _>::from( set.permutation_product_coset .with_rotation(Rotation::next()), ); for (values, permutation) in columns .iter() .map(|&column| match column.column_type() { Any::Advice => &advice_cosets[column.index()], Any::Fixed => &fixed_cosets[column.index()], Any::Instance => &instance_cosets[column.index()], }) .zip(cosets.iter()) { left *= poly::Ast::<_, C::Scalar, _>::from(*values) + (poly::Ast::ConstantTerm(*beta) * poly::Ast::from(*permutation)) + poly::Ast::ConstantTerm(*gamma); } let mut right = poly::Ast::from(set.permutation_product_coset); let mut current_delta = *beta * &(C::Scalar::DELTA.pow_vartime([(chunk_index * chunk_len) as u64])); for values in columns.iter().map(|&column| match column.column_type() { Any::Advice => &advice_cosets[column.index()], Any::Fixed => &fixed_cosets[column.index()], Any::Instance => &instance_cosets[column.index()], }) { right *= poly::Ast::from(*values) + poly::Ast::LinearTerm(current_delta) + poly::Ast::ConstantTerm(*gamma); current_delta *= &C::Scalar::DELTA; } (left - right) * (poly::Ast::one() - (poly::Ast::from(l_last) + l_blind)) }), ); (constructed, expressions) } } impl super::ProvingKey { pub(in crate::plonk) fn open( &self, x: ChallengeX, ) -> impl Iterator> + Clone { self.polys.iter().map(move |poly| ProverQuery { point: *x, poly, blind: Blind::default(), }) } pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( &self, x: ChallengeX, transcript: &mut T, ) -> Result<(), Error> { // Hash permutation evals for eval in self.polys.iter().map(|poly| eval_polynomial(poly, *x)) { transcript.write_scalar(eval)?; } Ok(()) } } impl Constructed { pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( self, pk: &plonk::ProvingKey, x: ChallengeX, transcript: &mut T, ) -> Result, Error> { let domain = &pk.vk.domain; let blinding_factors = pk.vk.cs.blinding_factors(); { let mut sets = self.sets.iter(); while let Some(set) = sets.next() { let permutation_product_eval = eval_polynomial(&set.permutation_product_poly, *x); let permutation_product_next_eval = eval_polynomial( &set.permutation_product_poly, domain.rotate_omega(*x, Rotation::next()), ); // Hash permutation product evals for eval in iter::empty() .chain(Some(&permutation_product_eval)) .chain(Some(&permutation_product_next_eval)) { transcript.write_scalar(*eval)?; } // If we have any remaining sets to process, evaluate this set at omega^u // so we can constrain the last value of its running product to equal the // first value of the next set's running product, chaining them together. if sets.len() > 0 { let permutation_product_last_eval = eval_polynomial( &set.permutation_product_poly, domain.rotate_omega(*x, Rotation(-((blinding_factors + 1) as i32))), ); transcript.write_scalar(permutation_product_last_eval)?; } } } Ok(Evaluated { constructed: self }) } } impl Evaluated { pub(in crate::plonk) fn open<'a>( &'a self, pk: &'a plonk::ProvingKey, x: ChallengeX, ) -> impl Iterator> + Clone { let blinding_factors = pk.vk.cs.blinding_factors(); let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next()); let x_last = pk .vk .domain .rotate_omega(*x, Rotation(-((blinding_factors + 1) as i32))); iter::empty() .chain(self.constructed.sets.iter().flat_map(move |set| { iter::empty() // Open permutation product commitments at x and \omega x .chain(Some(ProverQuery { point: *x, poly: &set.permutation_product_poly, blind: set.permutation_product_blind, })) .chain(Some(ProverQuery { point: x_next, poly: &set.permutation_product_poly, blind: set.permutation_product_blind, })) })) // Open it at \omega^{last} x for all but the last set. This rotation is only // sensical for the first row, but we only use this rotation in a constraint // that is gated on l_0. .chain( self.constructed .sets .iter() .rev() .skip(1) .flat_map(move |set| { Some(ProverQuery { point: x_last, poly: &set.permutation_product_poly, blind: set.permutation_product_blind, }) }), ) } }