use super::super::{ circuit::{Advice, Any, Aux, Column, Fixed}, Error, ProvingKey, }; use super::Argument; use crate::{ arithmetic::{eval_polynomial, parallelize, BatchInvert, Curve, CurveAffine, FieldExt}, poly::{ commitment::{Blind, Params}, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation, }, transcript::{Hasher, Transcript}, }; use ff::Field; use std::collections::BTreeMap; #[derive(Clone, Debug)] pub(crate) struct Permuted { permuted_input_value: Polynomial, permuted_input_poly: Polynomial, permuted_input_coset: Polynomial, permuted_input_inv_coset: Polynomial, permuted_input_blind: Blind, permuted_input_commitment: C, permuted_table_value: Polynomial, permuted_table_poly: Polynomial, permuted_table_coset: Polynomial, permuted_table_blind: Blind, permuted_table_commitment: C, } #[derive(Clone, Debug)] pub(crate) struct Product { product_poly: Polynomial, product_coset: Polynomial, product_inv_coset: Polynomial, product_blind: Blind, product_commitment: C, } #[derive(Clone, Debug)] pub(crate) struct Committed { permuted: Permuted, product: Product, } pub(crate) struct Constructed { permuted_input_poly: Polynomial, permuted_input_blind: Blind, permuted_input_commitment: C, permuted_table_poly: Polynomial, permuted_table_blind: Blind, permuted_table_commitment: C, product_poly: Polynomial, product_blind: Blind, product_commitment: C, } pub(crate) struct Evaluated { constructed: Constructed, pub product_eval: C::Scalar, pub product_inv_eval: C::Scalar, pub permuted_input_eval: C::Scalar, pub permuted_input_inv_eval: C::Scalar, pub permuted_table_eval: C::Scalar, } impl Argument { /// Given a Lookup with input columns [A_0, A_1, ..., A_m] and table columns /// [S_0, S_1, ..., S_m], this method /// - constructs A_compressed = A_0 + theta A_1 + theta^2 A_2 + ... and /// S_compressed = S_0 + theta S_1 + theta^2 S_2 + ..., /// - permutes A_compressed and S_compressed using permute_column_pair() helper, /// obtaining A' and S', and /// - constructs Permuted struct using permuted_input_value = A', and /// permuted_table_value = S'. /// The Permuted struct is used to update the Lookup, and is then returned. pub(in crate::plonk) fn commit_permuted< C: CurveAffine, HBase: Hasher, HScalar: Hasher, >( &self, pk: &ProvingKey, params: &Params, domain: &EvaluationDomain, theta: C::Scalar, advice_values: &[Polynomial], fixed_values: &[Polynomial], aux_values: &[Polynomial], transcript: &mut Transcript, ) -> Result, Error> { // Values of input columns involved in the lookup let unpermuted_input_values: Vec> = self .input_columns .iter() .map(|&input| match input.column_type() { Any::Advice => advice_values[input.index()].clone(), Any::Fixed => fixed_values[input.index()].clone(), Any::Aux => aux_values[input.index()].clone(), }) .collect(); // Compressed version of input columns let compressed_input_value = unpermuted_input_values .iter() .fold(domain.empty_lagrange(), |acc, input| acc * theta + input); // Values of table columns involved in the lookup let unpermuted_table_values: Vec> = self .table_columns .iter() .map(|&table| match table.column_type() { Any::Advice => advice_values[table.index()].clone(), Any::Fixed => fixed_values[table.index()].clone(), Any::Aux => aux_values[table.index()].clone(), }) .collect(); // Compressed version of table columns let compressed_table_value = unpermuted_table_values .iter() .fold(domain.empty_lagrange(), |acc, table| acc * theta + table); // Permute compressed (InputColumn, TableColumn) pair let (permuted_input_value, permuted_table_value) = permute_column_pair::(domain, &compressed_input_value, &compressed_table_value)?; // Construct Permuted struct let permuted_input_poly = pk.vk.domain.lagrange_to_coeff(permuted_input_value.clone()); let permuted_input_coset = pk .vk .domain .coeff_to_extended(permuted_input_poly.clone(), Rotation::default()); let permuted_input_inv_coset = pk .vk .domain .coeff_to_extended(permuted_input_poly.clone(), Rotation(-1)); let permuted_input_blind = Blind(C::Scalar::rand()); let permuted_input_commitment = params .commit_lagrange(&permuted_input_value, permuted_input_blind) .to_affine(); let permuted_table_poly = pk.vk.domain.lagrange_to_coeff(permuted_table_value.clone()); let permuted_table_coset = pk .vk .domain .coeff_to_extended(permuted_table_poly.clone(), Rotation::default()); let permuted_table_blind = Blind(C::Scalar::rand()); let permuted_table_commitment = params .commit_lagrange(&permuted_table_value, permuted_table_blind) .to_affine(); // Hash each permuted input commitment transcript .absorb_point(&permuted_input_commitment) .map_err(|_| Error::TranscriptError)?; // Hash each permuted table commitment transcript .absorb_point(&permuted_table_commitment) .map_err(|_| Error::TranscriptError)?; Ok(Permuted { permuted_input_value, permuted_input_poly, permuted_input_coset, permuted_input_inv_coset, permuted_input_blind, permuted_input_commitment, permuted_table_value, permuted_table_poly, permuted_table_coset, permuted_table_blind, permuted_table_commitment, }) } } /// Given a column of input values A and a column 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_column_pair( domain: &EvaluationDomain, input_column: &Polynomial, table_column: &Polynomial, ) -> Result< ( Polynomial, Polynomial, ), Error, > { let mut permuted_input_column = input_column.clone(); // Sort input lookup column values permuted_input_column.sort(); // A BTreeMap of each unique element in the table column and its count let mut leftover_table_map: BTreeMap = table_column.iter().fold(BTreeMap::new(), |mut acc, coeff| { *acc.entry(*coeff).or_insert(0) += 1; acc }); let mut repeated_input_rows = vec![]; let mut permuted_table_coeffs = vec![C::Scalar::zero(); table_column.len()]; for row in 0..permuted_input_column.len() { let input_value = permuted_input_column[row]; // If this is the first occurence of `input_value` in the input column if row == 0 || input_value != permuted_input_column[row - 1] { permuted_table_coeffs[row] = 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; } else { // Return error if input_value not found return Err(Error::ConstraintSystemFailure); } // If input value is repeated } else { repeated_input_rows.push(row); } } // 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() as usize] = *coeff; } } assert!(repeated_input_rows.is_empty()); let mut permuted_table_column = domain.empty_lagrange(); parallelize( &mut permuted_table_column, |permuted_table_column, start| { for (permuted_table_value, permuted_table_coeff) in permuted_table_column .iter_mut() .zip(permuted_table_coeffs[start..].iter()) { *permuted_table_value += permuted_table_coeff; } }, ); Ok((permuted_input_column, permuted_table_column)) }