From 1b35fab453a62f28f3e399c1c68ee4e69aa88874 Mon Sep 17 00:00:00 2001 From: "Eduard S." Date: Thu, 14 Dec 2023 17:24:41 +0100 Subject: [PATCH] Complete create_proof refactor for fe-be split --- halo2_proofs/src/plonk/circuit.rs | 2 +- halo2_proofs/src/plonk/evaluation.rs | 313 +++++++++++++- halo2_proofs/src/plonk/lookup/prover.rs | 418 ++++++++++++++++++- halo2_proofs/src/plonk/permutation/prover.rs | 240 +++++++++++ halo2_proofs/src/plonk/prover.rs | 378 ++++++++++++++--- halo2_proofs/src/plonk/shuffle/prover.rs | 213 ++++++++++ 6 files changed, 1506 insertions(+), 58 deletions(-) diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index 1526873a08..ae6ec15703 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -1704,8 +1704,8 @@ impl ConstraintSystemV2Backend { let max_phase = self .advice_column_phase .iter() + .cloned() .max() - .map(|phase| phase.0) .unwrap_or_default(); (0..=max_phase).collect() } diff --git a/halo2_proofs/src/plonk/evaluation.rs b/halo2_proofs/src/plonk/evaluation.rs index 43958e7c9e..efd4f6081f 100644 --- a/halo2_proofs/src/plonk/evaluation.rs +++ b/halo2_proofs/src/plonk/evaluation.rs @@ -1,5 +1,5 @@ use crate::multicore; -use crate::plonk::{lookup, permutation, Any, ProvingKey}; +use crate::plonk::{lookup, permutation, Any, ProvingKey, ProvingKeyV2}; use crate::poly::Basis; use crate::{ arithmetic::{parallelize, CurveAffine}, @@ -383,6 +383,317 @@ impl Evaluator { ev } + /// Evaluate h poly + // NOTE: Copy of evaluate_h with ProvingKeyV2 + #[allow(clippy::too_many_arguments)] + pub(in crate::plonk) fn evaluate_h_v2( + &self, + pk: &ProvingKeyV2, + advice_polys: &[&[Polynomial]], + instance_polys: &[&[Polynomial]], + challenges: &[C::ScalarExt], + y: C::ScalarExt, + beta: C::ScalarExt, + gamma: C::ScalarExt, + theta: C::ScalarExt, + lookups: &[Vec>], + shuffles: &[Vec>], + permutations: &[permutation::prover::Committed], + ) -> Polynomial { + let domain = &pk.vk.domain; + let size = domain.extended_len(); + let rot_scale = 1 << (domain.extended_k() - domain.k()); + let fixed = &pk.fixed_cosets[..]; + let extended_omega = domain.get_extended_omega(); + let isize = size as i32; + let one = C::ScalarExt::ONE; + let l0 = &pk.l0; + let l_last = &pk.l_last; + let l_active_row = &pk.l_active_row; + let p = &pk.vk.cs.permutation; + + // Calculate the advice and instance cosets + let advice: Vec>> = advice_polys + .iter() + .map(|advice_polys| { + advice_polys + .iter() + .map(|poly| domain.coeff_to_extended(poly.clone())) + .collect() + }) + .collect(); + let instance: Vec>> = instance_polys + .iter() + .map(|instance_polys| { + instance_polys + .iter() + .map(|poly| domain.coeff_to_extended(poly.clone())) + .collect() + }) + .collect(); + + let mut values = domain.empty_extended(); + + // Core expression evaluations + let num_threads = multicore::current_num_threads(); + for ((((advice, instance), lookups), shuffles), permutation) in advice + .iter() + .zip(instance.iter()) + .zip(lookups.iter()) + .zip(shuffles.iter()) + .zip(permutations.iter()) + { + // Custom gates + multicore::scope(|scope| { + let chunk_size = (size + num_threads - 1) / num_threads; + for (thread_idx, values) in values.chunks_mut(chunk_size).enumerate() { + let start = thread_idx * chunk_size; + scope.spawn(move |_| { + let mut eval_data = self.custom_gates.instance(); + for (i, value) in values.iter_mut().enumerate() { + let idx = start + i; + *value = self.custom_gates.evaluate( + &mut eval_data, + fixed, + advice, + instance, + challenges, + &beta, + &gamma, + &theta, + &y, + value, + idx, + rot_scale, + isize, + ); + } + }); + } + }); + + // Permutations + let sets = &permutation.sets; + if !sets.is_empty() { + let blinding_factors = pk.vk.cs.blinding_factors(); + let last_rotation = Rotation(-((blinding_factors + 1) as i32)); + let chunk_len = pk.vk.cs.degree() - 2; + let delta_start = beta * &C::Scalar::ZETA; + + let first_set = sets.first().unwrap(); + let last_set = sets.last().unwrap(); + + // Permutation constraints + parallelize(&mut values, |values, start| { + let mut beta_term = extended_omega.pow_vartime([start as u64, 0, 0, 0]); + for (i, value) in values.iter_mut().enumerate() { + let idx = start + i; + let r_next = get_rotation_idx(idx, 1, rot_scale, isize); + let r_last = get_rotation_idx(idx, last_rotation.0, rot_scale, isize); + + // Enforce only for the first set. + // l_0(X) * (1 - z_0(X)) = 0 + *value = *value * y + + ((one - first_set.permutation_product_coset[idx]) * l0[idx]); + // Enforce only for the last set. + // l_last(X) * (z_l(X)^2 - z_l(X)) = 0 + *value = *value * y + + ((last_set.permutation_product_coset[idx] + * last_set.permutation_product_coset[idx] + - last_set.permutation_product_coset[idx]) + * l_last[idx]); + // Except for the first set, enforce. + // l_0(X) * (z_i(X) - z_{i-1}(\omega^(last) X)) = 0 + for (set_idx, set) in sets.iter().enumerate() { + if set_idx != 0 { + *value = *value * y + + ((set.permutation_product_coset[idx] + - permutation.sets[set_idx - 1].permutation_product_coset + [r_last]) + * l0[idx]); + } + } + // 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) + // ) + let mut current_delta = delta_start * beta_term; + for ((set, columns), cosets) in sets + .iter() + .zip(p.columns.chunks(chunk_len)) + .zip(pk.permutation.cosets.chunks(chunk_len)) + { + let mut left = set.permutation_product_coset[r_next]; + for (values, permutation) in columns + .iter() + .map(|&column| match column.column_type() { + Any::Advice(_) => &advice[column.index()], + Any::Fixed => &fixed[column.index()], + Any::Instance => &instance[column.index()], + }) + .zip(cosets.iter()) + { + left *= values[idx] + beta * permutation[idx] + gamma; + } + + let mut right = set.permutation_product_coset[idx]; + for values in columns.iter().map(|&column| match column.column_type() { + Any::Advice(_) => &advice[column.index()], + Any::Fixed => &fixed[column.index()], + Any::Instance => &instance[column.index()], + }) { + right *= values[idx] + current_delta + gamma; + current_delta *= &C::Scalar::DELTA; + } + + *value = *value * y + ((left - right) * l_active_row[idx]); + } + beta_term *= &extended_omega; + } + }); + } + + // Lookups + for (n, lookup) in lookups.iter().enumerate() { + // Polynomials required for this lookup. + // Calculated here so these only have to be kept in memory for the short time + // they are actually needed. + let product_coset = pk.vk.domain.coeff_to_extended(lookup.product_poly.clone()); + let permuted_input_coset = pk + .vk + .domain + .coeff_to_extended(lookup.permuted_input_poly.clone()); + let permuted_table_coset = pk + .vk + .domain + .coeff_to_extended(lookup.permuted_table_poly.clone()); + + // Lookup constraints + parallelize(&mut values, |values, start| { + let lookup_evaluator = &self.lookups[n]; + let mut eval_data = lookup_evaluator.instance(); + for (i, value) in values.iter_mut().enumerate() { + let idx = start + i; + + let table_value = lookup_evaluator.evaluate( + &mut eval_data, + fixed, + advice, + instance, + challenges, + &beta, + &gamma, + &theta, + &y, + &C::ScalarExt::ZERO, + idx, + rot_scale, + isize, + ); + + let r_next = get_rotation_idx(idx, 1, rot_scale, isize); + let r_prev = get_rotation_idx(idx, -1, rot_scale, isize); + + let a_minus_s = permuted_input_coset[idx] - permuted_table_coset[idx]; + // l_0(X) * (1 - z(X)) = 0 + *value = *value * y + ((one - product_coset[idx]) * l0[idx]); + // l_last(X) * (z(X)^2 - z(X)) = 0 + *value = *value * y + + ((product_coset[idx] * product_coset[idx] - product_coset[idx]) + * l_last[idx]); + // (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 + *value = *value * y + + ((product_coset[r_next] + * (permuted_input_coset[idx] + beta) + * (permuted_table_coset[idx] + gamma) + - product_coset[idx] * table_value) + * l_active_row[idx]); + // 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 + *value = *value * y + (a_minus_s * l0[idx]); + // 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 + *value = *value * y + + (a_minus_s + * (permuted_input_coset[idx] - permuted_input_coset[r_prev]) + * l_active_row[idx]); + } + }); + } + + // Shuffle constraints + for (n, shuffle) in shuffles.iter().enumerate() { + let product_coset = pk.vk.domain.coeff_to_extended(shuffle.product_poly.clone()); + + // Shuffle constraints + parallelize(&mut values, |values, start| { + let input_evaluator = &self.shuffles[2 * n]; + let shuffle_evaluator = &self.shuffles[2 * n + 1]; + let mut eval_data_input = shuffle_evaluator.instance(); + let mut eval_data_shuffle = shuffle_evaluator.instance(); + for (i, value) in values.iter_mut().enumerate() { + let idx = start + i; + + let input_value = input_evaluator.evaluate( + &mut eval_data_input, + fixed, + advice, + instance, + challenges, + &beta, + &gamma, + &theta, + &y, + &C::ScalarExt::ZERO, + idx, + rot_scale, + isize, + ); + + let shuffle_value = shuffle_evaluator.evaluate( + &mut eval_data_shuffle, + fixed, + advice, + instance, + challenges, + &beta, + &gamma, + &theta, + &y, + &C::ScalarExt::ZERO, + idx, + rot_scale, + isize, + ); + + let r_next = get_rotation_idx(idx, 1, rot_scale, isize); + + // l_0(X) * (1 - z(X)) = 0 + *value = *value * y + ((one - product_coset[idx]) * l0[idx]); + // l_last(X) * (z(X)^2 - z(X)) = 0 + *value = *value * y + + ((product_coset[idx] * product_coset[idx] - product_coset[idx]) + * l_last[idx]); + // (1 - (l_last(X) + l_blind(X))) * (z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma)) = 0 + *value = *value * y + + l_active_row[idx] + * (product_coset[r_next] * shuffle_value + - product_coset[idx] * input_value) + } + }); + } + } + values + } + /// Evaluate h poly #[allow(clippy::too_many_arguments)] pub(in crate::plonk) fn evaluate_h( diff --git a/halo2_proofs/src/plonk/lookup/prover.rs b/halo2_proofs/src/plonk/lookup/prover.rs index 028b298853..203b554939 100644 --- a/halo2_proofs/src/plonk/lookup/prover.rs +++ b/halo2_proofs/src/plonk/lookup/prover.rs @@ -1,6 +1,6 @@ use super::super::{ circuit::Expression, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error, - ProvingKey, + ProvingKey, ProvingKeyV2, }; use super::Argument; use crate::plonk::evaluation::evaluate; @@ -51,6 +51,112 @@ pub(in crate::plonk) struct Evaluated { } 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. + // NOTE: Copy of commit_permuted that uses ProvingKeyV2 + #[allow(clippy::too_many_arguments)] + pub(in crate::plonk) fn commit_permuted_v2< + 'a, + 'params: 'a, + C, + P: Params<'params, C>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWrite, + >( + &self, + pk: &ProvingKeyV2, + params: &P, + domain: &EvaluationDomain, + theta: ChallengeTheta, + advice_values: &'a [Polynomial], + fixed_values: &'a [Polynomial], + instance_values: &'a [Polynomial], + challenges: &'a [C::Scalar], + 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]| { + let compressed_expression = expressions + .iter() + .map(|expression| { + pk.vk.domain.lagrange_from_vec(evaluate( + expression, + params.n() as usize, + 1, + fixed_values, + advice_values, + instance_values, + challenges, + )) + }) + .fold(domain.empty_lagrange(), |acc, expression| { + acc * *theta + &expression + }); + compressed_expression + }; + + // Get values of input expressions involved in the lookup and compress them + let compressed_input_expression = compress_expressions(&self.input_expressions); + + // Get values of table expressions involved in the lookup and compress them + let compressed_table_expression = compress_expressions(&self.table_expressions); + + // Permute compressed (InputExpression, TableExpression) pair + let (permuted_input_expression, permuted_table_expression) = permute_expression_pair_v2( + 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)?; + + Ok(Permuted { + compressed_input_expression, + permuted_input_expression, + permuted_input_poly, + permuted_input_blind, + compressed_table_expression, + permuted_table_expression, + permuted_table_poly, + permuted_table_blind, + }) + } /// 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} @@ -159,6 +265,151 @@ impl> Argument { } 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. + // NOTE: Copy of commit_permuted with ProvingKeyV2 + pub(in crate::plonk) fn commit_product_v2< + 'params, + P: Params<'params, C>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWrite, + >( + self, + pk: &ProvingKeyV2, + params: &P, + beta: ChallengeBeta, + gamma: ChallengeGamma, + 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 (i, product) in product.iter_mut().enumerate() { + let i = i + start; + + *product *= &(self.compressed_input_expression[i] + &*beta); + *product *= &(self.compressed_table_expression[i] + &*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); + + // Hash product commitment + transcript.write_point(product_commitment)?; + + Ok(Committed:: { + permuted_input_poly: self.permuted_input_poly, + permuted_input_blind: self.permuted_input_blind, + permuted_table_poly: self.permuted_table_poly, + permuted_table_blind: self.permuted_table_blind, + product_poly: z, + product_blind, + }) + } /// 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 @@ -306,6 +557,36 @@ impl Permuted { } impl Committed { + pub(in crate::plonk) fn evaluate_v2, T: TranscriptWrite>( + self, + pk: &ProvingKeyV2, + 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 }) + } + pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( self, pk: &ProvingKey, @@ -338,6 +619,48 @@ impl Committed { } impl Evaluated { + // NOTE: Copy of open with ProvingKeyV2 + pub(in crate::plonk) fn open_v2<'a>( + &'a self, + pk: &'a ProvingKeyV2, + 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, + })) + } + pub(in crate::plonk) fn open<'a>( &'a self, pk: &'a ProvingKey, @@ -382,6 +705,99 @@ impl Evaluated { 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. +// NOTE: Copy of permute_expression_pair that uses ProvingKeyV2 +fn permute_expression_pair_v2<'params, C: CurveAffine, P: Params<'params, C>, R: RngCore>( + pk: &ProvingKeyV2, + params: &P, + 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), + )) +} + /// 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 diff --git a/halo2_proofs/src/plonk/permutation/prover.rs b/halo2_proofs/src/plonk/permutation/prover.rs index d6b108554d..5bc3924708 100644 --- a/halo2_proofs/src/plonk/permutation/prover.rs +++ b/halo2_proofs/src/plonk/permutation/prover.rs @@ -42,6 +42,155 @@ pub(crate) struct Evaluated { } impl Argument { + // NOTE: Copy of commit with ProvingKeyV2 + #[allow(clippy::too_many_arguments)] + pub(in crate::plonk) fn commit_v2< + 'params, + C: CurveAffine, + P: Params<'params, C>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWrite, + >( + &self, + params: &P, + pk: &plonk::ProvingKeyV2, + pkey: &ProvingKey, + advice: &[Polynomial], + fixed: &[Polynomial], + instance: &[Polynomial], + beta: ChallengeBeta, + gamma: ChallengeGamma, + 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 *= &::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 = 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 }) + } #[allow(clippy::too_many_arguments)] pub(in crate::plonk) fn commit< 'params, @@ -234,6 +383,51 @@ impl super::ProvingKey { } impl Constructed { + // NOTE: Copy of evaluate with ProvingKeyV2 + pub(in crate::plonk) fn evaluate_v2, T: TranscriptWrite>( + self, + pk: &plonk::ProvingKeyV2, + 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 }) + } pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( self, pk: &plonk::ProvingKey, @@ -281,6 +475,52 @@ impl Constructed { } impl Evaluated { + // NOTE: Copy of open with ProvingKeyV2 + pub(in crate::plonk) fn open_v2<'a>( + &'a self, + pk: &'a plonk::ProvingKeyV2, + 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, + }) + }), + ) + } pub(in crate::plonk) fn open<'a>( &'a self, pk: &'a plonk::ProvingKey, diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index a53a37cc74..3fa1e46bd5 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -42,6 +42,7 @@ struct AdviceSingle { pub advice_blinds: Vec>, } +// TODO: Rewrite as multi-instance prover, and make a wraper for signle-instance case. /// The prover object used to create proofs interactively by passing the witnesses to commit at /// each phase. #[derive(Debug)] @@ -61,7 +62,7 @@ pub struct ProverV2< instance_queries: Vec<(Column, Rotation)>, fixed_queries: Vec<(Column, Rotation)>, phases: Vec, - instance: Vec>, + instance: InstanceSingle, rng: R, transcript: T, advice: AdviceSingle, @@ -89,9 +90,12 @@ impl< rng: R, mut transcript: T, ) -> Result + // TODO: Can I move this `where` to the struct definition? where Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, { + // TODO: We have cs duplicated in circuit.cs and pk.vk.cs. Can we dedup them? + if instance.len() != pk.vk.cs.num_instance_columns { return Err(Error::InvalidInstances); } @@ -107,59 +111,57 @@ impl< let domain = &pk.vk.domain; - let instance: Vec> = iter::once(instance) - .map(|instance| -> Result, Error> { - let instance_values = instance - .iter() - .map(|values| { - let mut poly = domain.empty_lagrange(); - assert_eq!(poly.len(), params.n() as usize); - if values.len() > (poly.len() - (meta.blinding_factors() + 1)) { - return Err(Error::InstanceTooLarge); - } - for (poly, value) in poly.iter_mut().zip(values.iter()) { - if !P::QUERY_INSTANCE { - transcript.common_scalar(*value)?; - } - *poly = *value; + let instance: InstanceSingle = { + let instance_values = instance + .iter() + .map(|values| { + let mut poly = domain.empty_lagrange(); + assert_eq!(poly.len(), params.n() as usize); + if values.len() > (poly.len() - (meta.blinding_factors() + 1)) { + return Err(Error::InstanceTooLarge); + } + for (poly, value) in poly.iter_mut().zip(values.iter()) { + if !P::QUERY_INSTANCE { + transcript.common_scalar(*value)?; } - Ok(poly) - }) - .collect::, _>>()?; - - if P::QUERY_INSTANCE { - let instance_commitments_projective: Vec<_> = instance_values - .iter() - .map(|poly| params.commit_lagrange(poly, Blind::default())) - .collect(); - let mut instance_commitments = - vec![Scheme::Curve::identity(); instance_commitments_projective.len()]; - ::CurveExt::batch_normalize( - &instance_commitments_projective, - &mut instance_commitments, - ); - let instance_commitments = instance_commitments; - drop(instance_commitments_projective); - - for commitment in &instance_commitments { - transcript.common_point(*commitment)?; + *poly = *value; } - } + Ok(poly) + }) + .collect::, _>>()?; - let instance_polys: Vec<_> = instance_values + if P::QUERY_INSTANCE { + let instance_commitments_projective: Vec<_> = instance_values .iter() - .map(|poly| { - let lagrange_vec = domain.lagrange_from_vec(poly.to_vec()); - domain.lagrange_to_coeff(lagrange_vec) - }) + .map(|poly| params.commit_lagrange(poly, Blind::default())) .collect(); + let mut instance_commitments = + vec![Scheme::Curve::identity(); instance_commitments_projective.len()]; + ::CurveExt::batch_normalize( + &instance_commitments_projective, + &mut instance_commitments, + ); + let instance_commitments = instance_commitments; + drop(instance_commitments_projective); - Ok(InstanceSingle { - instance_values, - instance_polys, + for commitment in &instance_commitments { + transcript.common_point(*commitment)?; + } + } + + let instance_polys: Vec<_> = instance_values + .iter() + .map(|poly| { + let lagrange_vec = domain.lagrange_from_vec(poly.to_vec()); + domain.lagrange_to_coeff(lagrange_vec) }) - }) - .collect::, _>>()?; + .collect(); + + InstanceSingle { + instance_values, + instance_polys, + } + }; let advice = AdviceSingle:: { advice_polys: vec![domain.empty_lagrange(); meta.num_advice_columns], @@ -208,13 +210,12 @@ impl< let params = self.params; let meta = self.cs; - let domain = self.pk.vk.domain; - let mut transcript = self.transcript; - let mut rng = self.rng; + let transcript = &mut self.transcript; + let mut rng = &mut self.rng; - let mut advice = self.advice; - let mut challenges = self.challenges; + let advice = &mut self.advice; + let challenges = &mut self.challenges; let column_indices = meta .advice_column_phase @@ -242,7 +243,8 @@ impl< } let mut advice_values = batch_invert_assigned::(witness.into_iter().flatten().collect()); - let unblinded_advice = HashSet::from_iter(meta.unblinded_advice_columns.clone()); + let unblinded_advice: HashSet = + HashSet::from_iter(meta.unblinded_advice_columns.clone()); let unusable_rows_start = params.n() as usize - (meta.blinding_factors() + 1); // Add blinding factors to advice columns @@ -306,8 +308,274 @@ impl< Ok(challenges.clone()) } - pub fn create_proof(self) -> Result { - todo!() + /// Finalizes the proof creation. + pub fn create_proof(mut self) -> Result + where + Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + { + let params = self.params; + let meta = self.cs; + let pk = self.pk; + let domain = &self.pk.vk.domain; + + let mut transcript = self.transcript; + let mut rng = self.rng; + + let instance = std::mem::replace( + &mut self.instance, + InstanceSingle { + instance_values: Vec::new(), + instance_polys: Vec::new(), + }, + ); + let advice = std::mem::replace( + &mut self.advice, + AdviceSingle { + advice_polys: Vec::new(), + advice_blinds: Vec::new(), + }, + ); + let mut challenges = self.challenges; + + assert_eq!(challenges.len(), meta.num_challenges); + let challenges = (0..meta.num_challenges) + .map(|index| challenges.remove(&index).unwrap()) + .collect::>(); + + // Sample theta challenge for keeping lookup columns linearly independent + let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar(); + + // Construct and commit to permuted values for each lookup + let lookups: Vec> = pk + .vk + .cs + .lookups + .iter() + .map(|lookup| { + lookup.commit_permuted_v2( + pk, + params, + &domain, + theta, + &advice.advice_polys, + &pk.fixed_values, + &instance.instance_values, + &challenges, + &mut rng, + &mut transcript, + ) + }) + .collect::, _>>()?; + + // Sample beta challenge + let beta: ChallengeBeta<_> = transcript.squeeze_challenge_scalar(); + + // Sample gamma challenge + let gamma: ChallengeGamma<_> = transcript.squeeze_challenge_scalar(); + + // Commit to permutation. + let permutation = [pk.vk.cs.permutation.commit_v2( + params, + pk, + &pk.permutation, + &advice.advice_polys, + &pk.fixed_values, + &instance.instance_values, + beta, + gamma, + &mut rng, + &mut transcript, + )?]; + + // Construct and commit to products for each lookup + let lookups: [Vec>; 1] = [lookups + .into_iter() + .map(|lookup| { + lookup.commit_product_v2(pk, params, beta, gamma, &mut rng, &mut transcript) + }) + .collect::, _>>()?]; + + // Compress expressions for each shuffle + let shuffles: [Vec>; 1] = [pk + .vk + .cs + .shuffles + .iter() + .map(|shuffle| { + shuffle.commit_product_v2( + pk, + params, + domain, + theta, + gamma, + &advice.advice_polys, + &pk.fixed_values, + &instance.instance_values, + &challenges, + &mut rng, + &mut transcript, + ) + }) + .collect::, _>>()?]; + + // Commit to the vanishing argument's random polynomial for blinding h(x_3) + let vanishing = vanishing::Argument::commit(params, domain, &mut rng, &mut transcript)?; + + // Obtain challenge for keeping all separate gates linearly independent + let y: ChallengeY<_> = transcript.squeeze_challenge_scalar(); + + // Calculate the advice polys + let advice: AdviceSingle = AdviceSingle { + advice_polys: advice + .advice_polys + .into_iter() + .map(|poly| domain.lagrange_to_coeff(poly)) + .collect::>(), + advice_blinds: advice.advice_blinds, + }; + + // Evaluate the h(X) polynomial + let h_poly = pk.ev.evaluate_h_v2( + pk, + &[advice.advice_polys.as_slice()], + &[instance.instance_polys.as_slice()], + &challenges, + *y, + *beta, + *gamma, + *theta, + &lookups, + &shuffles, + &permutation, + ); + + // Construct the vanishing argument's h(X) commitments + let vanishing = vanishing.construct(params, domain, h_poly, &mut rng, &mut transcript)?; + + let x: ChallengeX<_> = transcript.squeeze_challenge_scalar(); + let xn = x.pow([params.n()]); + + if P::QUERY_INSTANCE { + // Compute and hash instance evals for the circuit instance + // Evaluate polynomials at omega^i x + let instance_evals: Vec<_> = self + .instance_queries + .iter() + .map(|&(column, at)| { + eval_polynomial( + &instance.instance_polys[column.index()], + domain.rotate_omega(*x, at), + ) + }) + .collect(); + + // Hash each instance column evaluation + for eval in instance_evals.iter() { + transcript.write_scalar(*eval)?; + } + } + + // Compute and hash advice evals for the circuit instance + // Evaluate polynomials at omega^i x + let advice_evals: Vec<_> = self + .advice_queries + .iter() + .map(|&(column, at)| { + eval_polynomial( + &advice.advice_polys[column.index()], + domain.rotate_omega(*x, at), + ) + }) + .collect(); + + // Hash each advice column evaluation + for eval in advice_evals.iter() { + transcript.write_scalar(*eval)?; + } + + // Compute and hash fixed evals + let fixed_evals: Vec<_> = self + .fixed_queries + .iter() + .map(|&(column, at)| { + eval_polynomial(&pk.fixed_polys[column.index()], domain.rotate_omega(*x, at)) + }) + .collect(); + + // Hash each fixed column evaluation + for eval in fixed_evals.iter() { + transcript.write_scalar(*eval)?; + } + + let vanishing = vanishing.evaluate(x, xn, domain, &mut transcript)?; + + // Evaluate common permutation data + pk.permutation.evaluate(x, &mut transcript)?; + + let [permutation] = permutation; + let [lookups] = lookups; + let [shuffles] = shuffles; + + // Evaluate the permutations, if any, at omega^i x. + let permutation = permutation + .construct() + .evaluate_v2(pk, x, &mut transcript)?; + + // Evaluate the lookups, if any, at omega^i x. + let lookups: Vec> = lookups + .into_iter() + .map(|p| p.evaluate_v2(pk, x, &mut transcript)) + .collect::, _>>()?; + + // Evaluate the shuffles, if any, at omega^i x. + let shuffles: Vec> = shuffles + .into_iter() + .map(|p| p.evaluate_v2(pk, x, &mut transcript)) + .collect::, _>>()?; + + let instance_ref = &instance; + let advice_ref = &advice; + let instances = + iter::empty() + .chain( + P::QUERY_INSTANCE + .then_some(self.instance_queries.iter().map(move |&(column, at)| { + ProverQuery { + point: domain.rotate_omega(*x, at), + poly: &instance_ref.instance_polys[column.index()], + blind: Blind::default(), + } + })) + .into_iter() + .flatten(), + ) + .chain( + self.advice_queries + .iter() + .map(move |&(column, at)| ProverQuery { + point: domain.rotate_omega(*x, at), + poly: &advice_ref.advice_polys[column.index()], + blind: advice_ref.advice_blinds[column.index()], + }), + ) + .chain(permutation.open_v2(pk, x)) + .chain(lookups.iter().flat_map(move |p| p.open_v2(pk, x))) + .chain(shuffles.iter().flat_map(move |p| p.open_v2(pk, x))) + .chain(self.fixed_queries.iter().map(|&(column, at)| ProverQuery { + point: domain.rotate_omega(*x, at), + poly: &pk.fixed_polys[column.index()], + blind: Blind::default(), + })) + .chain(pk.permutation.open(x)) + // We query the h(X) polynomial at x + .chain(vanishing.open(x)); + + let prover = P::new(params); + prover + .create_proof(rng, &mut transcript, instances) + .map_err(|_| Error::ConstraintSystemFailure)?; + + Ok(transcript) } } diff --git a/halo2_proofs/src/plonk/shuffle/prover.rs b/halo2_proofs/src/plonk/shuffle/prover.rs index fd30436a47..30b9768203 100644 --- a/halo2_proofs/src/plonk/shuffle/prover.rs +++ b/halo2_proofs/src/plonk/shuffle/prover.rs @@ -1,5 +1,6 @@ use super::super::{ circuit::Expression, ChallengeGamma, ChallengeTheta, ChallengeX, Error, ProvingKey, + ProvingKeyV2, }; use super::Argument; use crate::plonk::evaluation::evaluate; @@ -36,6 +37,60 @@ pub(in crate::plonk) struct Evaluated { } impl> Argument { + /// Given a Shuffle 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}, + // NOTE: Copy of compress with ProvingKeyV2 + #[allow(clippy::too_many_arguments)] + fn compress_v2<'a, 'params: 'a, C, P: Params<'params, C>>( + &self, + pk: &ProvingKeyV2, + params: &P, + domain: &EvaluationDomain, + theta: ChallengeTheta, + advice_values: &'a [Polynomial], + fixed_values: &'a [Polynomial], + instance_values: &'a [Polynomial], + challenges: &'a [C::Scalar], + ) -> Compressed + where + C: CurveAffine, + C::Curve: Mul + MulAssign, + { + // Closure to get values of expressions and compress them + let compress_expressions = |expressions: &[Expression]| { + let compressed_expression = expressions + .iter() + .map(|expression| { + pk.vk.domain.lagrange_from_vec(evaluate( + expression, + params.n() as usize, + 1, + fixed_values, + advice_values, + instance_values, + challenges, + )) + }) + .fold(domain.empty_lagrange(), |acc, expression| { + acc * *theta + &expression + }); + compressed_expression + }; + + // Get values of input expressions involved in the shuffle and compress them + let input_expression = compress_expressions(&self.input_expressions); + + // Get values of table expressions involved in the shuffle and compress them + let shuffle_expression = compress_expressions(&self.shuffle_expressions); + + Compressed { + input_expression, + shuffle_expression, + } + } + /// Given a Shuffle 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} @@ -89,6 +144,117 @@ impl> Argument { } } + /// Given a Shuffle with input expressions and table expressions this method + /// constructs the grand product polynomial over the shuffle. + /// The grand product polynomial is used to populate the Product struct. + /// The Product struct is added to the Shuffle and finally returned by the method. + // NOTE: Copy of commit_product with ProvingKeyV2 + #[allow(clippy::too_many_arguments)] + pub(in crate::plonk) fn commit_product_v2< + 'a, + 'params: 'a, + C, + P: Params<'params, C>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWrite, + >( + &self, + pk: &ProvingKeyV2, + params: &P, + domain: &EvaluationDomain, + theta: ChallengeTheta, + gamma: ChallengeGamma, + advice_values: &'a [Polynomial], + fixed_values: &'a [Polynomial], + instance_values: &'a [Polynomial], + challenges: &'a [C::Scalar], + mut rng: R, + transcript: &mut T, + ) -> Result, Error> + where + C: CurveAffine, + C::Curve: Mul + MulAssign, + { + let compressed = self.compress_v2( + pk, + params, + domain, + theta, + advice_values, + fixed_values, + instance_values, + challenges, + ); + + let blinding_factors = pk.vk.cs.blinding_factors(); + + let mut shuffle_product = vec![C::Scalar::ZERO; params.n() as usize]; + parallelize(&mut shuffle_product, |shuffle_product, start| { + for (shuffle_product, shuffle_value) in shuffle_product + .iter_mut() + .zip(compressed.shuffle_expression[start..].iter()) + { + *shuffle_product = *gamma + shuffle_value; + } + }); + + shuffle_product.iter_mut().batch_invert(); + + parallelize(&mut shuffle_product, |product, start| { + for (i, product) in product.iter_mut().enumerate() { + let i = i + start; + *product *= &(*gamma + compressed.input_expression[i]); + } + }); + + // Compute the evaluations of the shuffle product polynomial + // over our domain, starting with z[0] = 1 + let z = iter::once(C::Scalar::ONE) + .chain(shuffle_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")] + { + // While in Lagrange basis, check that product is correctly constructed + let u = (params.n() as usize) - (blinding_factors + 1); + assert_eq!(z[0], C::Scalar::ONE); + for i in 0..u { + let mut left = z[i + 1]; + let input_value = &compressed.input_expression[i]; + let shuffle_value = &compressed.shuffle_expression[i]; + left *= &(*gamma + shuffle_value); + let mut right = z[i]; + right *= &(*gamma + input_value); + assert_eq!(left, right); + } + 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); + + // Hash product commitment + transcript.write_point(product_commitment)?; + + Ok(Committed:: { + product_poly: z, + product_blind, + }) + } + /// Given a Shuffle with input expressions and table expressions this method /// constructs the grand product polynomial over the shuffle. /// The grand product polynomial is used to populate the Product struct. @@ -201,6 +367,30 @@ impl> Argument { } impl Committed { + // NOTE: Copy of evaluate with ProvingKeyV2 + pub(in crate::plonk) fn evaluate_v2, T: TranscriptWrite>( + self, + pk: &ProvingKeyV2, + x: ChallengeX, + transcript: &mut T, + ) -> Result, Error> { + let domain = &pk.vk.domain; + 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); + + // Hash each advice evaluation + for eval in iter::empty() + .chain(Some(product_eval)) + .chain(Some(product_next_eval)) + { + transcript.write_scalar(eval)?; + } + + Ok(Evaluated { constructed: self }) + } + pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( self, pk: &ProvingKey, @@ -226,6 +416,29 @@ impl Committed { } impl Evaluated { + // NOTE: Copy of open with ProvingKeyV2 + pub(in crate::plonk) fn open_v2<'a>( + &'a self, + pk: &'a ProvingKeyV2, + x: ChallengeX, + ) -> impl Iterator> + Clone { + let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next()); + + iter::empty() + // Open shuffle product commitments at x + .chain(Some(ProverQuery { + point: *x, + poly: &self.constructed.product_poly, + blind: self.constructed.product_blind, + })) + // Open shuffle product commitments at x_next + .chain(Some(ProverQuery { + point: x_next, + poly: &self.constructed.product_poly, + blind: self.constructed.product_blind, + })) + } + pub(in crate::plonk) fn open<'a>( &'a self, pk: &'a ProvingKey,