halo2_axiom/plonk/lookup/
prover.rs

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