diff --git a/src/plonk/lookup/prover.rs b/src/plonk/lookup/prover.rs index 260f0305..25f807d1 100644 --- a/src/plonk/lookup/prover.rs +++ b/src/plonk/lookup/prover.rs @@ -176,6 +176,200 @@ impl Argument { permuted_table_commitment, }) } + + /// Given a Lookup with input columns, table columns, and the permuted + /// input column and permuted table column, 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. + pub(in crate::plonk) fn commit_product< + C: CurveAffine, + HBase: Hasher, + HScalar: Hasher, + >( + &self, + permuted: &Permuted, + pk: &ProvingKey, + params: &Params, + theta: C::Scalar, + beta: C::Scalar, + gamma: C::Scalar, + advice_values: &[Polynomial], + fixed_values: &[Polynomial], + aux_values: &[Polynomial], + transcript: &mut Transcript, + ) -> Result, Error> { + let permuted = permuted.clone(); + 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(); + + 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(); + + // Goal is to compute the products of fractions + // + // (a_1(\omega^i) + \theta a_2(\omega^i) + ... + beta)(s_1(\omega^i) + \theta(\omega^i) + ... + \gamma) / + // (a'(\omega^i) + \beta)(s'(\omega^i) + \gamma) + // + // where a_j(X) is the jth input column in this lookup, + // where a'(X) is the compression of the permuted input columns, + // s_j(X) is the jth table column in this lookup, + // s'(X) is the compression of the permuted table columns, + // and i is the ith row of the column. + let mut lookup_product = vec![C::Scalar::one(); params.n as usize]; + + // Denominator uses the permuted input column and permuted table column + parallelize(&mut lookup_product, |lookup_product, start| { + for ((lookup_product, permuted_input_value), permuted_table_value) in lookup_product + .iter_mut() + .zip(permuted.permuted_input_value[start..].iter()) + .zip(permuted.permuted_table_value[start..].iter()) + { + *lookup_product *= &(beta + permuted_input_value); + *lookup_product *= &(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 + // (a_1(X) + \theta a_2(X) + ... + \beta) (s_1(X) + \theta s_2(X) + ... + \gamma) + // Compress unpermuted input columns + let mut input_term = vec![C::Scalar::zero(); params.n as usize]; + for unpermuted_input_value in unpermuted_input_values.iter() { + parallelize(&mut input_term, |input_term, start| { + for (input_term, input_value) in input_term + .iter_mut() + .zip(unpermuted_input_value[start..].iter()) + { + *input_term *= θ + *input_term += input_value; + } + }); + } + + // Compress unpermuted table columns + let mut table_term = vec![C::Scalar::zero(); params.n as usize]; + for unpermuted_table_value in unpermuted_table_values.iter() { + parallelize(&mut table_term, |table_term, start| { + for (table_term, fixed_value) in table_term + .iter_mut() + .zip(unpermuted_table_value[start..].iter()) + { + *table_term *= θ + *table_term += fixed_value; + } + }); + } + + // Add \beta and \gamma offsets + parallelize(&mut lookup_product, |product, start| { + for ((product, input_term), table_term) in product + .iter_mut() + .zip(input_term[start..].iter()) + .zip(table_term[start..].iter()) + { + *product *= &(*input_term + &beta); + *product *= &(*table_term + &gamma); + } + }); + + // The product vector is a vector of products of fractions of the form + // + // (a_1(\omega^i) + \theta a_2(\omega^i) + ... + \beta)(s_1(\omega^i) + \theta s_2(\omega^i) + ... + \gamma)/ + // (a'(\omega^i) + \beta) (s'(\omega^i) + \gamma) + // + // where a_j(\omega^i) is the jth input column in this lookup, + // a'j(\omega^i) is the permuted input column, + // s_j(\omega^i) is the jth table column in this lookup, + // s'(\omega^i) is the permuted table column, + // and i is the ith row of the column. + + // Compute the evaluations of the lookup 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 *= &lookup_product[row]; + z.push(tmp); + } + 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 n = params.n as usize; + + // z'(X) (a'(X) + \beta) (s'(X) + \gamma) + // - z'(\omega^{-1} X) (a_1(X) + \theta a_2(X) + ... + \beta) (s_1(X) + \theta s_2(X) + ... + \gamma) + for i in 0..n { + let prev_idx = (n + i - 1) % n; + + let mut left = z[i]; + let permuted_input_value = &permuted.permuted_input_value[i]; + + let permuted_table_value = &permuted.permuted_table_value[i]; + + left *= &(*beta + permuted_input_value); + left *= &(*gamma + permuted_table_value); + + let mut right = z[prev_idx]; + let mut input_term = unpermuted_input_values + .iter() + .fold(C::Scalar::zero(), |acc, input| acc * &theta + &input[i]); + + let mut table_term = unpermuted_table_values + .iter() + .fold(C::Scalar::zero(), |acc, table| acc * &theta + &table[i]); + + input_term += &(*beta); + table_term += &(*gamma); + right *= &(input_term * &table_term); + + assert_eq!(left, right); + } + } + + let product_blind = Blind(C::Scalar::rand()); + let product_commitment = params.commit_lagrange(&z, product_blind).to_affine(); + let z = pk.vk.domain.lagrange_to_coeff(z); + let product_coset = pk + .vk + .domain + .coeff_to_extended(z.clone(), Rotation::default()); + let product_inv_coset = pk.vk.domain.coeff_to_extended(z.clone(), Rotation(-1)); + + // Hash each product commitment + transcript + .absorb_point(&product_commitment) + .map_err(|_| Error::TranscriptError)?; + + Ok(Product:: { + product_poly: z, + product_coset, + product_inv_coset, + product_commitment, + product_blind, + }) + } } /// Given a column of input values A and a column of table values S,