use super::super::{ circuit::Expression, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error, ProvingKey, }; use super::Argument; use crate::{ arithmetic::{eval_polynomial, parallelize, CurveAffine}, poly::{ self, commitment::{Blind, Params}, multiopen::ProverQuery, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation, }, transcript::{EncodedChallenge, TranscriptWrite}, }; use ff::WithSmallOrderMulGroup; use group::{ ff::{BatchInvert, Field}, Curve, }; use rand_core::RngCore; use std::{ collections::BTreeMap, iter, ops::{Mul, MulAssign}, }; #[derive(Debug)] pub(in crate::plonk) struct Permuted { compressed_input_expression: Polynomial, permuted_input_expression: Polynomial, compressed_input_coset: poly::Ast, permuted_input_poly: Polynomial, permuted_input_coset: poly::AstLeaf, permuted_input_blind: Blind, compressed_table_expression: Polynomial, compressed_table_coset: poly::Ast, permuted_table_expression: Polynomial, permuted_table_poly: Polynomial, permuted_table_coset: poly::AstLeaf, permuted_table_blind: Blind, } #[derive(Debug)] pub(in crate::plonk) struct Committed { permuted: Permuted, product_poly: Polynomial, product_coset: poly::AstLeaf, product_blind: Blind, } pub(in crate::plonk) struct Constructed { permuted_input_poly: Polynomial, permuted_input_blind: Blind, permuted_table_poly: Polynomial, permuted_table_blind: Blind, product_poly: Polynomial, product_blind: Blind, } pub(in crate::plonk) struct Evaluated { constructed: Constructed, } impl> Argument { /// Given a Lookup with input expressions [A_0, A_1, ..., A_{m-1}] and table expressions /// [S_0, S_1, ..., S_{m-1}], this method /// - constructs A_compressed = \theta^{m-1} A_0 + theta^{m-2} A_1 + ... + \theta A_{m-2} + A_{m-1} /// and S_compressed = \theta^{m-1} S_0 + theta^{m-2} S_1 + ... + \theta S_{m-2} + S_{m-1}, /// - permutes A_compressed and S_compressed using permute_expression_pair() helper, /// obtaining A' and S', and /// - constructs Permuted struct using permuted_input_value = A', and /// permuted_table_expression = S'. /// The Permuted struct is used to update the Lookup, and is then returned. #[allow(clippy::too_many_arguments)] pub(in crate::plonk) fn commit_permuted< 'a, C, E: EncodedChallenge, Ev: Copy + Send + Sync, Ec: Copy + Send + Sync, R: RngCore, T: TranscriptWrite, >( &self, pk: &ProvingKey, params: &Params, domain: &EvaluationDomain, value_evaluator: &poly::Evaluator, coset_evaluator: &mut poly::Evaluator, theta: ChallengeTheta, advice_values: &'a [poly::AstLeaf], fixed_values: &'a [poly::AstLeaf], instance_values: &'a [poly::AstLeaf], advice_cosets: &'a [poly::AstLeaf], fixed_cosets: &'a [poly::AstLeaf], instance_cosets: &'a [poly::AstLeaf], mut rng: R, transcript: &mut T, ) -> Result, Error> where C: CurveAffine, C::Curve: Mul + MulAssign, { // Closure to get values of expressions and compress them let compress_expressions = |expressions: &[Expression]| { // Values of input expressions involved in the lookup let unpermuted_expressions: Vec<_> = expressions .iter() .map(|expression| { expression.evaluate( &|scalar| poly::Ast::ConstantTerm(scalar), &|_| panic!("virtual selectors are removed during optimization"), &|query| { fixed_values[query.column_index] .with_rotation(query.rotation) .into() }, &|query| { advice_values[query.column_index] .with_rotation(query.rotation) .into() }, &|query| { instance_values[query.column_index] .with_rotation(query.rotation) .into() }, &|a| -a, &|a, b| a + b, &|a, b| a * b, &|a, scalar| a * scalar, ) }) .collect(); let unpermuted_cosets: Vec<_> = expressions .iter() .map(|expression| { expression.evaluate( &|scalar| poly::Ast::ConstantTerm(scalar), &|_| panic!("virtual selectors are removed during optimization"), &|query| { fixed_cosets[query.column_index] .with_rotation(query.rotation) .into() }, &|query| { advice_cosets[query.column_index] .with_rotation(query.rotation) .into() }, &|query| { instance_cosets[query.column_index] .with_rotation(query.rotation) .into() }, &|a| -a, &|a, b| a + b, &|a, b| a * b, &|a, scalar| a * scalar, ) }) .collect(); // Compressed version of expressions let compressed_expression = unpermuted_expressions.iter().fold( poly::Ast::ConstantTerm(C::Scalar::ZERO), |acc, expression| &(acc * *theta) + expression, ); // Compressed version of cosets let compressed_coset = unpermuted_cosets.iter().fold( poly::Ast::<_, _, ExtendedLagrangeCoeff>::ConstantTerm(C::Scalar::ZERO), |acc, eval| acc * poly::Ast::ConstantTerm(*theta) + eval.clone(), ); ( compressed_coset, value_evaluator.evaluate(&compressed_expression, domain), ) }; // Get values of input expressions involved in the lookup and compress them let (compressed_input_coset, compressed_input_expression) = compress_expressions(&self.input_expressions); // Get values of table expressions involved in the lookup and compress them let (compressed_table_coset, compressed_table_expression) = compress_expressions(&self.table_expressions); // Permute compressed (InputExpression, TableExpression) pair let (permuted_input_expression, permuted_table_expression) = permute_expression_pair::( pk, params, domain, &mut rng, &compressed_input_expression, &compressed_table_expression, )?; // Closure to construct commitment to vector of values let mut commit_values = |values: &Polynomial| { let poly = pk.vk.domain.lagrange_to_coeff(values.clone()); let blind = Blind(C::Scalar::random(&mut rng)); let commitment = params.commit_lagrange(values, blind).to_affine(); (poly, blind, commitment) }; // Commit to permuted input expression let (permuted_input_poly, permuted_input_blind, permuted_input_commitment) = commit_values(&permuted_input_expression); // Commit to permuted table expression let (permuted_table_poly, permuted_table_blind, permuted_table_commitment) = commit_values(&permuted_table_expression); // Hash permuted input commitment transcript.write_point(permuted_input_commitment)?; // Hash permuted table commitment transcript.write_point(permuted_table_commitment)?; let permuted_input_coset = coset_evaluator .register_poly(pk.vk.domain.coeff_to_extended(permuted_input_poly.clone())); let permuted_table_coset = coset_evaluator .register_poly(pk.vk.domain.coeff_to_extended(permuted_table_poly.clone())); Ok(Permuted { compressed_input_expression, compressed_input_coset, permuted_input_expression, permuted_input_poly, permuted_input_coset, permuted_input_blind, compressed_table_expression, compressed_table_coset, permuted_table_expression, permuted_table_poly, permuted_table_coset, permuted_table_blind, }) } } impl Permuted { /// Given a Lookup with input expressions, table expressions, and the permuted /// input expression and permuted table expression, this method constructs the /// grand product polynomial over the lookup. The grand product polynomial /// is used to populate the Product struct. The Product struct is /// added to the Lookup and finally returned by the method. #[allow(clippy::too_many_arguments)] pub(in crate::plonk) fn commit_product< E: EncodedChallenge, R: RngCore, T: TranscriptWrite, >( self, pk: &ProvingKey, params: &Params, beta: ChallengeBeta, gamma: ChallengeGamma, evaluator: &mut poly::Evaluator, mut rng: R, transcript: &mut T, ) -> Result, Error> { let blinding_factors = pk.vk.cs.blinding_factors(); // Goal is to compute the products of fractions // // Numerator: (\theta^{m-1} a_0(\omega^i) + \theta^{m-2} a_1(\omega^i) + ... + \theta a_{m-2}(\omega^i) + a_{m-1}(\omega^i) + \beta) // * (\theta^{m-1} s_0(\omega^i) + \theta^{m-2} s_1(\omega^i) + ... + \theta s_{m-2}(\omega^i) + s_{m-1}(\omega^i) + \gamma) // Denominator: (a'(\omega^i) + \beta) (s'(\omega^i) + \gamma) // // where a_j(X) is the jth input expression in this lookup, // where a'(X) is the compression of the permuted input expressions, // s_j(X) is the jth table expression in this lookup, // s'(X) is the compression of the permuted table expressions, // and i is the ith row of the expression. let mut lookup_product = vec![C::Scalar::ZERO; params.n as usize]; // Denominator uses the permuted input expression and permuted table expression parallelize(&mut lookup_product, |lookup_product, start| { for ((lookup_product, permuted_input_value), permuted_table_value) in lookup_product .iter_mut() .zip(self.permuted_input_expression[start..].iter()) .zip(self.permuted_table_expression[start..].iter()) { *lookup_product = (*beta + permuted_input_value) * &(*gamma + permuted_table_value); } }); // Batch invert to obtain the denominators for the lookup product // polynomials lookup_product.iter_mut().batch_invert(); // Finish the computation of the entire fraction by computing the numerators // (\theta^{m-1} a_0(\omega^i) + \theta^{m-2} a_1(\omega^i) + ... + \theta a_{m-2}(\omega^i) + a_{m-1}(\omega^i) + \beta) // * (\theta^{m-1} s_0(\omega^i) + \theta^{m-2} s_1(\omega^i) + ... + \theta s_{m-2}(\omega^i) + s_{m-1}(\omega^i) + \gamma) parallelize(&mut lookup_product, |product, start| { for ((product, &input_term), &table_term) in product .iter_mut() .zip(self.compressed_input_expression[start..].iter()) .zip(self.compressed_table_expression[start..].iter()) { *product *= &(input_term + &*beta); *product *= &(table_term + &*gamma); } }); // The product vector is a vector of products of fractions of the form // // Numerator: (\theta^{m-1} a_0(\omega^i) + \theta^{m-2} a_1(\omega^i) + ... + \theta a_{m-2}(\omega^i) + a_{m-1}(\omega^i) + \beta) // * (\theta^{m-1} s_0(\omega^i) + \theta^{m-2} s_1(\omega^i) + ... + \theta s_{m-2}(\omega^i) + s_{m-1}(\omega^i) + \gamma) // Denominator: (a'(\omega^i) + \beta) (s'(\omega^i) + \gamma) // // where there are m input expressions and m table expressions, // a_j(\omega^i) is the jth input expression in this lookup, // a'j(\omega^i) is the permuted input expression, // s_j(\omega^i) is the jth table expression in this lookup, // s'(\omega^i) is the permuted table expression, // and i is the ith row of the expression. // Compute the evaluations of the lookup product polynomial // over our domain, starting with z[0] = 1 let z = iter::once(C::Scalar::ONE) .chain(lookup_product) .scan(C::Scalar::ONE, |state, cur| { *state *= &cur; Some(*state) }) // Take all rows including the "last" row which should // be a boolean (and ideally 1, else soundness is broken) .take(params.n as usize - blinding_factors) // Chain random blinding factors. .chain((0..blinding_factors).map(|_| C::Scalar::random(&mut rng))) .collect::>(); assert_eq!(z.len(), params.n as usize); let z = pk.vk.domain.lagrange_from_vec(z); #[cfg(feature = "sanity-checks")] // This test works only with intermediate representations in this method. // It can be used for debugging purposes. { // While in Lagrange basis, check that product is correctly constructed let u = (params.n as usize) - (blinding_factors + 1); // l_0(X) * (1 - z(X)) = 0 assert_eq!(z[0], C::Scalar::ONE); // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) // - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma) for i in 0..u { let mut left = z[i + 1]; let permuted_input_value = &self.permuted_input_expression[i]; let permuted_table_value = &self.permuted_table_expression[i]; left *= &(*beta + permuted_input_value); left *= &(*gamma + permuted_table_value); let mut right = z[i]; let mut input_term = self.compressed_input_expression[i]; let mut table_term = self.compressed_table_expression[i]; input_term += &(*beta); table_term += &(*gamma); right *= &(input_term * &table_term); assert_eq!(left, right); } // l_last(X) * (z(X)^2 - z(X)) = 0 // Assertion will fail only when soundness is broken, in which // case this z[u] value will be zero. (bad!) assert_eq!(z[u], C::Scalar::ONE); } let product_blind = Blind(C::Scalar::random(rng)); let product_commitment = params.commit_lagrange(&z, product_blind).to_affine(); let z = pk.vk.domain.lagrange_to_coeff(z); let product_coset = evaluator.register_poly(pk.vk.domain.coeff_to_extended(z.clone())); // Hash product commitment transcript.write_point(product_commitment)?; Ok(Committed:: { permuted: self, product_poly: z, product_coset, product_blind, }) } } impl<'a, C: CurveAffine, Ev: Copy + Send + Sync + 'a> Committed { /// Given a Lookup with input expressions, table expressions, permuted input /// expression, permuted table expression, and grand product polynomial, this /// method constructs constraints that must hold between these values. /// This method returns the constraints as a vector of ASTs for polynomials in /// the extended evaluation domain. pub(in crate::plonk) fn construct( self, beta: ChallengeBeta, gamma: ChallengeGamma, l0: poly::AstLeaf, l_blind: poly::AstLeaf, l_last: poly::AstLeaf, ) -> ( Constructed, impl Iterator> + 'a, ) { let permuted = self.permuted; let active_rows = poly::Ast::one() - (poly::Ast::from(l_last) + l_blind); let beta = poly::Ast::ConstantTerm(*beta); let gamma = poly::Ast::ConstantTerm(*gamma); let expressions = iter::empty() // l_0(X) * (1 - z(X)) = 0 .chain(Some((poly::Ast::one() - self.product_coset) * l0)) // l_last(X) * (z(X)^2 - z(X)) = 0 .chain(Some( (poly::Ast::from(self.product_coset) * self.product_coset - self.product_coset) * l_last, )) // (1 - (l_last(X) + l_blind(X))) * ( // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) // - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma) // ) = 0 .chain({ // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) let left: poly::Ast<_, _, _> = poly::Ast::<_, C::Scalar, _>::from( self.product_coset.with_rotation(Rotation::next()), ) * (poly::Ast::from(permuted.permuted_input_coset) + beta.clone()) * (poly::Ast::from(permuted.permuted_table_coset) + gamma.clone()); // z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma) let right: poly::Ast<_, _, _> = poly::Ast::from(self.product_coset) * (permuted.compressed_input_coset + beta) * (permuted.compressed_table_coset + gamma); Some((left - right) * active_rows.clone()) }) // Check that the first values in the permuted input expression and permuted // fixed expression are the same. // l_0(X) * (a'(X) - s'(X)) = 0 .chain(Some( (poly::Ast::from(permuted.permuted_input_coset) - permuted.permuted_table_coset) * l0, )) // Check that each value in the permuted lookup input expression is either // equal to the value above it, or the value at the same index in the // permuted table expression. // (1 - (l_last + l_blind)) * (a′(X) − s′(X))⋅(a′(X) − a′(\omega^{-1} X)) = 0 .chain(Some( (poly::Ast::<_, C::Scalar, _>::from(permuted.permuted_input_coset) - permuted.permuted_table_coset) * (poly::Ast::from(permuted.permuted_input_coset) - permuted .permuted_input_coset .with_rotation(Rotation::prev())) * active_rows, )); ( Constructed { permuted_input_poly: permuted.permuted_input_poly, permuted_input_blind: permuted.permuted_input_blind, permuted_table_poly: permuted.permuted_table_poly, permuted_table_blind: permuted.permuted_table_blind, product_poly: self.product_poly, product_blind: self.product_blind, }, expressions, ) } } impl Constructed { pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( self, pk: &ProvingKey, x: ChallengeX, transcript: &mut T, ) -> Result, Error> { let domain = &pk.vk.domain; let x_inv = domain.rotate_omega(*x, Rotation::prev()); let x_next = domain.rotate_omega(*x, Rotation::next()); let product_eval = eval_polynomial(&self.product_poly, *x); let product_next_eval = eval_polynomial(&self.product_poly, x_next); let permuted_input_eval = eval_polynomial(&self.permuted_input_poly, *x); let permuted_input_inv_eval = eval_polynomial(&self.permuted_input_poly, x_inv); let permuted_table_eval = eval_polynomial(&self.permuted_table_poly, *x); // Hash each advice evaluation for eval in iter::empty() .chain(Some(product_eval)) .chain(Some(product_next_eval)) .chain(Some(permuted_input_eval)) .chain(Some(permuted_input_inv_eval)) .chain(Some(permuted_table_eval)) { transcript.write_scalar(eval)?; } Ok(Evaluated { constructed: self }) } } impl Evaluated { pub(in crate::plonk) fn open<'a>( &'a self, pk: &'a ProvingKey, x: ChallengeX, ) -> impl Iterator> + Clone { let x_inv = pk.vk.domain.rotate_omega(*x, Rotation::prev()); let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next()); iter::empty() // Open lookup product commitments at x .chain(Some(ProverQuery { point: *x, poly: &self.constructed.product_poly, blind: self.constructed.product_blind, })) // Open lookup input commitments at x .chain(Some(ProverQuery { point: *x, poly: &self.constructed.permuted_input_poly, blind: self.constructed.permuted_input_blind, })) // Open lookup table commitments at x .chain(Some(ProverQuery { point: *x, poly: &self.constructed.permuted_table_poly, blind: self.constructed.permuted_table_blind, })) // Open lookup input commitments at x_inv .chain(Some(ProverQuery { point: x_inv, poly: &self.constructed.permuted_input_poly, blind: self.constructed.permuted_input_blind, })) // Open lookup product commitments at x_next .chain(Some(ProverQuery { point: x_next, poly: &self.constructed.product_poly, blind: self.constructed.product_blind, })) } } type ExpressionPair = (Polynomial, Polynomial); /// Given a vector of input values A and a vector of table values S, /// this method permutes A and S to produce A' and S', such that: /// - like values in A' are vertically adjacent to each other; and /// - the first row in a sequence of like values in A' is the row /// that has the corresponding value in S'. /// This method returns (A', S') if no errors are encountered. fn permute_expression_pair( pk: &ProvingKey, params: &Params, domain: &EvaluationDomain, mut rng: R, input_expression: &Polynomial, table_expression: &Polynomial, ) -> Result, Error> { let blinding_factors = pk.vk.cs.blinding_factors(); let usable_rows = params.n as usize - (blinding_factors + 1); let mut permuted_input_expression: Vec = input_expression.to_vec(); permuted_input_expression.truncate(usable_rows); // Sort input lookup expression values permuted_input_expression.sort(); // A BTreeMap of each unique element in the table expression and its count let mut leftover_table_map: BTreeMap = table_expression .iter() .take(usable_rows) .fold(BTreeMap::new(), |mut acc, coeff| { *acc.entry(*coeff).or_insert(0) += 1; acc }); let mut permuted_table_coeffs = vec![C::Scalar::ZERO; usable_rows]; let mut repeated_input_rows = permuted_input_expression .iter() .zip(permuted_table_coeffs.iter_mut()) .enumerate() .filter_map(|(row, (input_value, table_value))| { // If this is the first occurrence of `input_value` in the input expression if row == 0 || *input_value != permuted_input_expression[row - 1] { *table_value = *input_value; // Remove one instance of input_value from leftover_table_map if let Some(count) = leftover_table_map.get_mut(input_value) { assert!(*count > 0); *count -= 1; None } else { // Return error if input_value not found Some(Err(Error::ConstraintSystemFailure)) } // If input value is repeated } else { Some(Ok(row)) } }) .collect::, _>>()?; // Populate permuted table at unfilled rows with leftover table elements for (coeff, count) in leftover_table_map.iter() { for _ in 0..*count { permuted_table_coeffs[repeated_input_rows.pop().unwrap()] = *coeff; } } assert!(repeated_input_rows.is_empty()); permuted_input_expression .extend((0..(blinding_factors + 1)).map(|_| C::Scalar::random(&mut rng))); permuted_table_coeffs.extend((0..(blinding_factors + 1)).map(|_| C::Scalar::random(&mut rng))); assert_eq!(permuted_input_expression.len(), params.n as usize); assert_eq!(permuted_table_coeffs.len(), params.n as usize); #[cfg(feature = "sanity-checks")] { let mut last = None; for (a, b) in permuted_input_expression .iter() .zip(permuted_table_coeffs.iter()) .take(usable_rows) { if *a != *b { assert_eq!(*a, last.unwrap()); } last = Some(*a); } } Ok(( domain.lagrange_from_vec(permuted_input_expression), domain.lagrange_from_vec(permuted_table_coeffs), )) }