halo2_proofs/plonk/lookup/
prover.rs

1use super::super::{
2    circuit::Expression, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error,
3    ProvingKey,
4};
5use super::Argument;
6use crate::{
7    arithmetic::{eval_polynomial, parallelize, CurveAffine, FieldExt},
8    poly::{
9        self,
10        commitment::{Blind, Params},
11        multiopen::ProverQuery,
12        Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation,
13    },
14    transcript::{EncodedChallenge, TranscriptWrite},
15};
16use group::{
17    ff::{BatchInvert, Field},
18    Curve,
19};
20use rand_core::RngCore;
21use std::{
22    collections::BTreeMap,
23    iter,
24    ops::{Mul, MulAssign},
25};
26
27#[derive(Debug)]
28pub(in crate::plonk) struct Permuted<C: CurveAffine, Ev> {
29    unpermuted_input_expressions: Vec<Polynomial<C::Scalar, LagrangeCoeff>>,
30    unpermuted_input_cosets: Vec<poly::Ast<Ev, C::Scalar, ExtendedLagrangeCoeff>>,
31    permuted_input_expression: Polynomial<C::Scalar, LagrangeCoeff>,
32    permuted_input_poly: Polynomial<C::Scalar, Coeff>,
33    permuted_input_coset: poly::AstLeaf<Ev, ExtendedLagrangeCoeff>,
34    permuted_input_blind: Blind<C::Scalar>,
35    unpermuted_table_expressions: Vec<Polynomial<C::Scalar, LagrangeCoeff>>,
36    unpermuted_table_cosets: Vec<poly::Ast<Ev, C::Scalar, ExtendedLagrangeCoeff>>,
37    permuted_table_expression: Polynomial<C::Scalar, LagrangeCoeff>,
38    permuted_table_poly: Polynomial<C::Scalar, Coeff>,
39    permuted_table_coset: poly::AstLeaf<Ev, ExtendedLagrangeCoeff>,
40    permuted_table_blind: Blind<C::Scalar>,
41}
42
43#[derive(Debug)]
44pub(in crate::plonk) struct Committed<C: CurveAffine, Ev> {
45    permuted: Permuted<C, Ev>,
46    product_poly: Polynomial<C::Scalar, Coeff>,
47    product_coset: poly::AstLeaf<Ev, ExtendedLagrangeCoeff>,
48    product_blind: Blind<C::Scalar>,
49}
50
51pub(in crate::plonk) struct Constructed<C: CurveAffine> {
52    permuted_input_poly: Polynomial<C::Scalar, Coeff>,
53    permuted_input_blind: Blind<C::Scalar>,
54    permuted_table_poly: Polynomial<C::Scalar, Coeff>,
55    permuted_table_blind: Blind<C::Scalar>,
56    product_poly: Polynomial<C::Scalar, Coeff>,
57    product_blind: Blind<C::Scalar>,
58}
59
60pub(in crate::plonk) struct Evaluated<C: CurveAffine> {
61    constructed: Constructed<C>,
62}
63
64impl<F: FieldExt> Argument<F> {
65    /// Given a Lookup with input expressions [A_0, A_1, ..., A_{m-1}] and table expressions
66    /// [S_0, S_1, ..., S_{m-1}], this method
67    /// - constructs A_compressed = \theta^{m-1} A_0 + theta^{m-2} A_1 + ... + \theta A_{m-2} + A_{m-1}
68    ///   and S_compressed = \theta^{m-1} S_0 + theta^{m-2} S_1 + ... + \theta S_{m-2} + S_{m-1},
69    /// - permutes A_compressed and S_compressed using permute_expression_pair() helper,
70    ///   obtaining A' and S', and
71    /// - constructs Permuted<C> struct using permuted_input_value = A', and
72    ///   permuted_table_expression = S'.
73    /// The Permuted<C> struct is used to update the Lookup, and is then returned.
74    pub(in crate::plonk) fn commit_permuted<
75        'a,
76        C,
77        E: EncodedChallenge<C>,
78        Ev: Copy + Send + Sync,
79        Ec: Copy + Send + Sync,
80        R: RngCore,
81        T: TranscriptWrite<C, E>,
82    >(
83        &self,
84        pk: &ProvingKey<C>,
85        params: &Params<C>,
86        domain: &EvaluationDomain<C::Scalar>,
87        value_evaluator: &poly::Evaluator<Ev, C::Scalar, LagrangeCoeff>,
88        coset_evaluator: &mut poly::Evaluator<Ec, C::Scalar, ExtendedLagrangeCoeff>,
89        theta: ChallengeTheta<C>,
90        advice_values: &'a [poly::AstLeaf<Ev, LagrangeCoeff>],
91        fixed_values: &'a [poly::AstLeaf<Ev, LagrangeCoeff>],
92        instance_values: &'a [poly::AstLeaf<Ev, LagrangeCoeff>],
93        advice_cosets: &'a [poly::AstLeaf<Ec, ExtendedLagrangeCoeff>],
94        fixed_cosets: &'a [poly::AstLeaf<Ec, ExtendedLagrangeCoeff>],
95        instance_cosets: &'a [poly::AstLeaf<Ec, ExtendedLagrangeCoeff>],
96        mut rng: R,
97        transcript: &mut T,
98    ) -> Result<Permuted<C, Ec>, Error>
99    where
100        C: CurveAffine<ScalarExt = F>,
101        C::Curve: Mul<F, Output = C::Curve> + MulAssign<F>,
102    {
103        // Closure to get values of expressions and compress them
104        let compress_expressions = |expressions: &[Expression<C::Scalar>]| {
105            // Values of input expressions involved in the lookup
106            let unpermuted_expressions: Vec<_> = expressions
107                .iter()
108                .map(|expression| {
109                    expression.evaluate(
110                        &|scalar| poly::Ast::ConstantTerm(scalar),
111                        &|_| panic!("virtual selectors are removed during optimization"),
112                        &|_, column_index, rotation| {
113                            fixed_values[column_index].with_rotation(rotation).into()
114                        },
115                        &|_, column_index, rotation| {
116                            advice_values[column_index].with_rotation(rotation).into()
117                        },
118                        &|_, column_index, rotation| {
119                            instance_values[column_index].with_rotation(rotation).into()
120                        },
121                        &|a| -a,
122                        &|a, b| a + b,
123                        &|a, b| a * b,
124                        &|a, scalar| a * scalar,
125                    )
126                })
127                .collect();
128
129            let unpermuted_cosets: Vec<_> = expressions
130                .iter()
131                .map(|expression| {
132                    expression.evaluate(
133                        &|scalar| poly::Ast::ConstantTerm(scalar),
134                        &|_| panic!("virtual selectors are removed during optimization"),
135                        &|_, column_index, rotation| {
136                            fixed_cosets[column_index].with_rotation(rotation).into()
137                        },
138                        &|_, column_index, rotation| {
139                            advice_cosets[column_index].with_rotation(rotation).into()
140                        },
141                        &|_, column_index, rotation| {
142                            instance_cosets[column_index].with_rotation(rotation).into()
143                        },
144                        &|a| -a,
145                        &|a, b| a + b,
146                        &|a, b| a * b,
147                        &|a, scalar| a * scalar,
148                    )
149                })
150                .collect();
151
152            // Compressed version of expressions
153            let compressed_expression = unpermuted_expressions.iter().fold(
154                poly::Ast::ConstantTerm(C::Scalar::zero()),
155                |acc, expression| &(acc * *theta) + expression,
156            );
157
158            (
159                unpermuted_expressions
160                    .iter()
161                    .map(|ast| value_evaluator.evaluate(ast, domain))
162                    .collect(),
163                unpermuted_cosets,
164                value_evaluator.evaluate(&compressed_expression, domain),
165            )
166        };
167
168        // Get values of input expressions involved in the lookup and compress them
169        let (unpermuted_input_expressions, unpermuted_input_cosets, compressed_input_expression) =
170            compress_expressions(&self.input_expressions);
171
172        // Get values of table expressions involved in the lookup and compress them
173        let (unpermuted_table_expressions, unpermuted_table_cosets, compressed_table_expression) =
174            compress_expressions(&self.table_expressions);
175
176        // Permute compressed (InputExpression, TableExpression) pair
177        let (permuted_input_expression, permuted_table_expression) = permute_expression_pair::<C, _>(
178            pk,
179            params,
180            domain,
181            &mut rng,
182            &compressed_input_expression,
183            &compressed_table_expression,
184        )?;
185
186        // Closure to construct commitment to vector of values
187        let mut commit_values = |values: &Polynomial<C::Scalar, LagrangeCoeff>| {
188            let poly = pk.vk.domain.lagrange_to_coeff(values.clone());
189            let blind = Blind(C::Scalar::random(&mut rng));
190            let commitment = params.commit_lagrange(values, blind).to_affine();
191            (poly, blind, commitment)
192        };
193
194        // Commit to permuted input expression
195        let (permuted_input_poly, permuted_input_blind, permuted_input_commitment) =
196            commit_values(&permuted_input_expression);
197
198        // Commit to permuted table expression
199        let (permuted_table_poly, permuted_table_blind, permuted_table_commitment) =
200            commit_values(&permuted_table_expression);
201
202        // Hash permuted input commitment
203        transcript.write_point(permuted_input_commitment)?;
204
205        // Hash permuted table commitment
206        transcript.write_point(permuted_table_commitment)?;
207
208        let permuted_input_coset = coset_evaluator
209            .register_poly(pk.vk.domain.coeff_to_extended(permuted_input_poly.clone()));
210        let permuted_table_coset = coset_evaluator
211            .register_poly(pk.vk.domain.coeff_to_extended(permuted_table_poly.clone()));
212
213        Ok(Permuted {
214            unpermuted_input_expressions,
215            unpermuted_input_cosets,
216            permuted_input_expression,
217            permuted_input_poly,
218            permuted_input_coset,
219            permuted_input_blind,
220            unpermuted_table_expressions,
221            unpermuted_table_cosets,
222            permuted_table_expression,
223            permuted_table_poly,
224            permuted_table_coset,
225            permuted_table_blind,
226        })
227    }
228}
229
230impl<C: CurveAffine, Ev: Copy + Send + Sync> Permuted<C, Ev> {
231    /// Given a Lookup with input expressions, table expressions, and the permuted
232    /// input expression and permuted table expression, this method constructs the
233    /// grand product polynomial over the lookup. The grand product polynomial
234    /// is used to populate the Product<C> struct. The Product<C> struct is
235    /// added to the Lookup and finally returned by the method.
236    pub(in crate::plonk) fn commit_product<
237        E: EncodedChallenge<C>,
238        R: RngCore,
239        T: TranscriptWrite<C, E>,
240    >(
241        self,
242        pk: &ProvingKey<C>,
243        params: &Params<C>,
244        theta: ChallengeTheta<C>,
245        beta: ChallengeBeta<C>,
246        gamma: ChallengeGamma<C>,
247        evaluator: &mut poly::Evaluator<Ev, C::Scalar, ExtendedLagrangeCoeff>,
248        mut rng: R,
249        transcript: &mut T,
250    ) -> Result<Committed<C, Ev>, Error> {
251        let blinding_factors = pk.vk.cs.blinding_factors();
252        // Goal is to compute the products of fractions
253        //
254        // 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)
255        //            * (\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)
256        // Denominator: (a'(\omega^i) + \beta) (s'(\omega^i) + \gamma)
257        //
258        // where a_j(X) is the jth input expression in this lookup,
259        // where a'(X) is the compression of the permuted input expressions,
260        // s_j(X) is the jth table expression in this lookup,
261        // s'(X) is the compression of the permuted table expressions,
262        // and i is the ith row of the expression.
263        let mut lookup_product = vec![C::Scalar::zero(); params.n as usize];
264        // Denominator uses the permuted input expression and permuted table expression
265        parallelize(&mut lookup_product, |lookup_product, start| {
266            for ((lookup_product, permuted_input_value), permuted_table_value) in lookup_product
267                .iter_mut()
268                .zip(self.permuted_input_expression[start..].iter())
269                .zip(self.permuted_table_expression[start..].iter())
270            {
271                *lookup_product = (*beta + permuted_input_value) * &(*gamma + permuted_table_value);
272            }
273        });
274
275        // Batch invert to obtain the denominators for the lookup product
276        // polynomials
277        lookup_product.iter_mut().batch_invert();
278
279        // Finish the computation of the entire fraction by computing the numerators
280        // (\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)
281        // * (\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)
282        parallelize(&mut lookup_product, |product, start| {
283            for (i, product) in product.iter_mut().enumerate() {
284                let i = i + start;
285
286                // Compress unpermuted input expressions
287                let mut input_term = C::Scalar::zero();
288                for unpermuted_input_expression in self.unpermuted_input_expressions.iter() {
289                    input_term *= &*theta;
290                    input_term += &unpermuted_input_expression[i];
291                }
292
293                // Compress unpermuted table expressions
294                let mut table_term = C::Scalar::zero();
295                for unpermuted_table_expression in self.unpermuted_table_expressions.iter() {
296                    table_term *= &*theta;
297                    table_term += &unpermuted_table_expression[i];
298                }
299
300                *product *= &(input_term + &*beta);
301                *product *= &(table_term + &*gamma);
302            }
303        });
304
305        // The product vector is a vector of products of fractions of the form
306        //
307        // 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)
308        //            * (\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)
309        // Denominator: (a'(\omega^i) + \beta) (s'(\omega^i) + \gamma)
310        //
311        // where there are m input expressions and m table expressions,
312        // a_j(\omega^i) is the jth input expression in this lookup,
313        // a'j(\omega^i) is the permuted input expression,
314        // s_j(\omega^i) is the jth table expression in this lookup,
315        // s'(\omega^i) is the permuted table expression,
316        // and i is the ith row of the expression.
317
318        // Compute the evaluations of the lookup product polynomial
319        // over our domain, starting with z[0] = 1
320        let z = iter::once(C::Scalar::one())
321            .chain(lookup_product)
322            .scan(C::Scalar::one(), |state, cur| {
323                *state *= &cur;
324                Some(*state)
325            })
326            // Take all rows including the "last" row which should
327            // be a boolean (and ideally 1, else soundness is broken)
328            .take(params.n as usize - blinding_factors)
329            // Chain random blinding factors.
330            .chain((0..blinding_factors).map(|_| C::Scalar::random(&mut rng)))
331            .collect::<Vec<_>>();
332        assert_eq!(z.len(), params.n as usize);
333        let z = pk.vk.domain.lagrange_from_vec(z);
334
335        #[cfg(feature = "sanity-checks")]
336        // This test works only with intermediate representations in this method.
337        // It can be used for debugging purposes.
338        {
339            // While in Lagrange basis, check that product is correctly constructed
340            let u = (params.n as usize) - (blinding_factors + 1);
341
342            // l_0(X) * (1 - z(X)) = 0
343            assert_eq!(z[0], C::Scalar::one());
344
345            // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma)
346            // - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma)
347            for i in 0..u {
348                let mut left = z[i + 1];
349                let permuted_input_value = &self.permuted_input_expression[i];
350
351                let permuted_table_value = &self.permuted_table_expression[i];
352
353                left *= &(*beta + permuted_input_value);
354                left *= &(*gamma + permuted_table_value);
355
356                let mut right = z[i];
357                let mut input_term = self
358                    .unpermuted_input_expressions
359                    .iter()
360                    .fold(C::Scalar::zero(), |acc, input| acc * &*theta + &input[i]);
361
362                let mut table_term = self
363                    .unpermuted_table_expressions
364                    .iter()
365                    .fold(C::Scalar::zero(), |acc, table| acc * &*theta + &table[i]);
366
367                input_term += &(*beta);
368                table_term += &(*gamma);
369                right *= &(input_term * &table_term);
370
371                assert_eq!(left, right);
372            }
373
374            // l_last(X) * (z(X)^2 - z(X)) = 0
375            // Assertion will fail only when soundness is broken, in which
376            // case this z[u] value will be zero. (bad!)
377            assert_eq!(z[u], C::Scalar::one());
378        }
379
380        let product_blind = Blind(C::Scalar::random(rng));
381        let product_commitment = params.commit_lagrange(&z, product_blind).to_affine();
382        let z = pk.vk.domain.lagrange_to_coeff(z);
383        let product_coset = evaluator.register_poly(pk.vk.domain.coeff_to_extended(z.clone()));
384
385        // Hash product commitment
386        transcript.write_point(product_commitment)?;
387
388        Ok(Committed::<C, _> {
389            permuted: self,
390            product_poly: z,
391            product_coset,
392            product_blind,
393        })
394    }
395}
396
397impl<'a, C: CurveAffine, Ev: Copy + Send + Sync + 'a> Committed<C, Ev> {
398    /// Given a Lookup with input expressions, table expressions, permuted input
399    /// expression, permuted table expression, and grand product polynomial, this
400    /// method constructs constraints that must hold between these values.
401    /// This method returns the constraints as a vector of ASTs for polynomials in
402    /// the extended evaluation domain.
403    pub(in crate::plonk) fn construct(
404        self,
405        theta: ChallengeTheta<C>,
406        beta: ChallengeBeta<C>,
407        gamma: ChallengeGamma<C>,
408        l0: poly::AstLeaf<Ev, ExtendedLagrangeCoeff>,
409        l_blind: poly::AstLeaf<Ev, ExtendedLagrangeCoeff>,
410        l_last: poly::AstLeaf<Ev, ExtendedLagrangeCoeff>,
411    ) -> (
412        Constructed<C>,
413        impl Iterator<Item = poly::Ast<Ev, C::Scalar, ExtendedLagrangeCoeff>> + 'a,
414    ) {
415        let permuted = self.permuted;
416
417        let active_rows = poly::Ast::one() - (poly::Ast::from(l_last) + l_blind);
418        let beta = poly::Ast::ConstantTerm(*beta);
419        let gamma = poly::Ast::ConstantTerm(*gamma);
420
421        let expressions = iter::empty()
422            // l_0(X) * (1 - z(X)) = 0
423            .chain(Some((poly::Ast::one() - self.product_coset) * l0))
424            // l_last(X) * (z(X)^2 - z(X)) = 0
425            .chain(Some(
426                (poly::Ast::from(self.product_coset) * self.product_coset - self.product_coset)
427                    * l_last,
428            ))
429            // (1 - (l_last(X) + l_blind(X))) * (
430            //   z(\omega X) (a'(X) + \beta) (s'(X) + \gamma)
431            //   - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma)
432            // ) = 0
433            .chain({
434                // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma)
435                let left: poly::Ast<_, _, _> = poly::Ast::<_, C::Scalar, _>::from(
436                    self.product_coset.with_rotation(Rotation::next()),
437                ) * (poly::Ast::from(permuted.permuted_input_coset)
438                    + beta.clone())
439                    * (poly::Ast::from(permuted.permuted_table_coset) + gamma.clone());
440
441                //  z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma)
442                let compress_cosets = |cosets: &[poly::Ast<_, _, _>]| {
443                    cosets.iter().fold(
444                        poly::Ast::<_, _, ExtendedLagrangeCoeff>::ConstantTerm(C::Scalar::zero()),
445                        |acc, eval| acc * poly::Ast::ConstantTerm(*theta) + eval.clone(),
446                    )
447                };
448                let right: poly::Ast<_, _, _> = poly::Ast::from(self.product_coset)
449                    * (compress_cosets(&permuted.unpermuted_input_cosets) + beta)
450                    * (compress_cosets(&permuted.unpermuted_table_cosets) + gamma);
451
452                Some((left - right) * active_rows.clone())
453            })
454            // Check that the first values in the permuted input expression and permuted
455            // fixed expression are the same.
456            // l_0(X) * (a'(X) - s'(X)) = 0
457            .chain(Some(
458                (poly::Ast::from(permuted.permuted_input_coset) - permuted.permuted_table_coset)
459                    * l0,
460            ))
461            // Check that each value in the permuted lookup input expression is either
462            // equal to the value above it, or the value at the same index in the
463            // permuted table expression.
464            // (1 - (l_last + l_blind)) * (a′(X) − s′(X))⋅(a′(X) − a′(\omega^{-1} X)) = 0
465            .chain(Some(
466                (poly::Ast::<_, C::Scalar, _>::from(permuted.permuted_input_coset)
467                    - permuted.permuted_table_coset)
468                    * (poly::Ast::from(permuted.permuted_input_coset)
469                        - permuted
470                            .permuted_input_coset
471                            .with_rotation(Rotation::prev()))
472                    * active_rows,
473            ));
474
475        (
476            Constructed {
477                permuted_input_poly: permuted.permuted_input_poly,
478                permuted_input_blind: permuted.permuted_input_blind,
479                permuted_table_poly: permuted.permuted_table_poly,
480                permuted_table_blind: permuted.permuted_table_blind,
481                product_poly: self.product_poly,
482                product_blind: self.product_blind,
483            },
484            expressions,
485        )
486    }
487}
488
489impl<C: CurveAffine> Constructed<C> {
490    pub(in crate::plonk) fn evaluate<E: EncodedChallenge<C>, T: TranscriptWrite<C, E>>(
491        self,
492        pk: &ProvingKey<C>,
493        x: ChallengeX<C>,
494        transcript: &mut T,
495    ) -> Result<Evaluated<C>, Error> {
496        let domain = &pk.vk.domain;
497        let x_inv = domain.rotate_omega(*x, Rotation::prev());
498        let x_next = domain.rotate_omega(*x, Rotation::next());
499
500        let product_eval = eval_polynomial(&self.product_poly, *x);
501        let product_next_eval = eval_polynomial(&self.product_poly, x_next);
502        let permuted_input_eval = eval_polynomial(&self.permuted_input_poly, *x);
503        let permuted_input_inv_eval = eval_polynomial(&self.permuted_input_poly, x_inv);
504        let permuted_table_eval = eval_polynomial(&self.permuted_table_poly, *x);
505
506        // Hash each advice evaluation
507        for eval in iter::empty()
508            .chain(Some(product_eval))
509            .chain(Some(product_next_eval))
510            .chain(Some(permuted_input_eval))
511            .chain(Some(permuted_input_inv_eval))
512            .chain(Some(permuted_table_eval))
513        {
514            transcript.write_scalar(eval)?;
515        }
516
517        Ok(Evaluated { constructed: self })
518    }
519}
520
521impl<C: CurveAffine> Evaluated<C> {
522    pub(in crate::plonk) fn open<'a>(
523        &'a self,
524        pk: &'a ProvingKey<C>,
525        x: ChallengeX<C>,
526    ) -> impl Iterator<Item = ProverQuery<'a, C>> + Clone {
527        let x_inv = pk.vk.domain.rotate_omega(*x, Rotation::prev());
528        let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next());
529
530        iter::empty()
531            // Open lookup product commitments at x
532            .chain(Some(ProverQuery {
533                point: *x,
534                poly: &self.constructed.product_poly,
535                blind: self.constructed.product_blind,
536            }))
537            // Open lookup input commitments at x
538            .chain(Some(ProverQuery {
539                point: *x,
540                poly: &self.constructed.permuted_input_poly,
541                blind: self.constructed.permuted_input_blind,
542            }))
543            // Open lookup table commitments at x
544            .chain(Some(ProverQuery {
545                point: *x,
546                poly: &self.constructed.permuted_table_poly,
547                blind: self.constructed.permuted_table_blind,
548            }))
549            // Open lookup input commitments at x_inv
550            .chain(Some(ProverQuery {
551                point: x_inv,
552                poly: &self.constructed.permuted_input_poly,
553                blind: self.constructed.permuted_input_blind,
554            }))
555            // Open lookup product commitments at x_next
556            .chain(Some(ProverQuery {
557                point: x_next,
558                poly: &self.constructed.product_poly,
559                blind: self.constructed.product_blind,
560            }))
561    }
562}
563
564type ExpressionPair<F> = (Polynomial<F, LagrangeCoeff>, Polynomial<F, LagrangeCoeff>);
565
566/// Given a vector of input values A and a vector of table values S,
567/// this method permutes A and S to produce A' and S', such that:
568/// - like values in A' are vertically adjacent to each other; and
569/// - the first row in a sequence of like values in A' is the row
570///   that has the corresponding value in S'.
571/// This method returns (A', S') if no errors are encountered.
572fn permute_expression_pair<C: CurveAffine, R: RngCore>(
573    pk: &ProvingKey<C>,
574    params: &Params<C>,
575    domain: &EvaluationDomain<C::Scalar>,
576    mut rng: R,
577    input_expression: &Polynomial<C::Scalar, LagrangeCoeff>,
578    table_expression: &Polynomial<C::Scalar, LagrangeCoeff>,
579) -> Result<ExpressionPair<C::Scalar>, Error> {
580    let blinding_factors = pk.vk.cs.blinding_factors();
581    let usable_rows = params.n as usize - (blinding_factors + 1);
582
583    let mut permuted_input_expression: Vec<C::Scalar> = input_expression.to_vec();
584    permuted_input_expression.truncate(usable_rows);
585
586    // Sort input lookup expression values
587    permuted_input_expression.sort();
588
589    // A BTreeMap of each unique element in the table expression and its count
590    let mut leftover_table_map: BTreeMap<C::Scalar, u32> = table_expression
591        .iter()
592        .take(usable_rows)
593        .fold(BTreeMap::new(), |mut acc, coeff| {
594            *acc.entry(*coeff).or_insert(0) += 1;
595            acc
596        });
597    let mut permuted_table_coeffs = vec![C::Scalar::zero(); usable_rows];
598
599    let mut repeated_input_rows = permuted_input_expression
600        .iter()
601        .zip(permuted_table_coeffs.iter_mut())
602        .enumerate()
603        .filter_map(|(row, (input_value, table_value))| {
604            // If this is the first occurrence of `input_value` in the input expression
605            if row == 0 || *input_value != permuted_input_expression[row - 1] {
606                *table_value = *input_value;
607                // Remove one instance of input_value from leftover_table_map
608                if let Some(count) = leftover_table_map.get_mut(input_value) {
609                    assert!(*count > 0);
610                    *count -= 1;
611                    None
612                } else {
613                    // Return error if input_value not found
614                    Some(Err(Error::ConstraintSystemFailure))
615                }
616            // If input value is repeated
617            } else {
618                Some(Ok(row))
619            }
620        })
621        .collect::<Result<Vec<_>, _>>()?;
622
623    // Populate permuted table at unfilled rows with leftover table elements
624    for (coeff, count) in leftover_table_map.iter() {
625        for _ in 0..*count {
626            permuted_table_coeffs[repeated_input_rows.pop().unwrap() as usize] = *coeff;
627        }
628    }
629    assert!(repeated_input_rows.is_empty());
630
631    permuted_input_expression
632        .extend((0..(blinding_factors + 1)).map(|_| C::Scalar::random(&mut rng)));
633    permuted_table_coeffs.extend((0..(blinding_factors + 1)).map(|_| C::Scalar::random(&mut rng)));
634    assert_eq!(permuted_input_expression.len(), params.n as usize);
635    assert_eq!(permuted_table_coeffs.len(), params.n as usize);
636
637    #[cfg(feature = "sanity-checks")]
638    {
639        let mut last = None;
640        for (a, b) in permuted_input_expression
641            .iter()
642            .zip(permuted_table_coeffs.iter())
643            .take(usable_rows)
644        {
645            if *a != *b {
646                assert_eq!(*a, last.unwrap());
647            }
648            last = Some(*a);
649        }
650    }
651
652    Ok((
653        domain.lagrange_from_vec(permuted_input_expression),
654        domain.lagrange_from_vec(permuted_table_coeffs),
655    ))
656}