Skip to main content

kimchi/
prover.rs

1//! This module implements prover's zk-proof primitive.
2
3use crate::{
4    circuits::{
5        argument::{Argument, ArgumentType},
6        berkeley_columns::{BerkeleyChallenges, Environment, LookupEnvironment},
7        constraints::zk_rows_strict_lower_bound,
8        expr::{self, l0_1, Constants},
9        gate::GateType,
10        lookup::{self, runtime_tables::RuntimeTable, tables::combine_table_entry},
11        polynomials::{
12            complete_add::CompleteAdd,
13            endomul_scalar::EndomulScalar,
14            endosclmul::EndosclMul,
15            foreign_field_add::circuitgates::ForeignFieldAdd,
16            foreign_field_mul::{self, circuitgates::ForeignFieldMul},
17            generic, permutation,
18            poseidon::Poseidon,
19            range_check::circuitgates::{RangeCheck0, RangeCheck1},
20            rot::Rot64,
21            varbasemul::VarbaseMul,
22            xor::Xor16,
23        },
24        wires::{COLUMNS, PERMUTS},
25    },
26    collections::HashMap,
27    curve::KimchiCurve,
28    error::ProverError,
29    lagrange_basis_evaluations::LagrangeBasisEvaluations,
30    plonk_sponge::FrSponge,
31    proof::{
32        LookupCommitments, PointEvaluations, ProofEvaluations, ProverCommitments, ProverProof,
33        RecursionChallenge,
34    },
35    prover_index::ProverIndex,
36    verifier_index::VerifierIndex,
37};
38use ark_ff::{FftField, Field, One, PrimeField, UniformRand, Zero};
39use ark_poly::{
40    univariate::DensePolynomial, DenseUVPolynomial, EvaluationDomain, Evaluations, Polynomial,
41    Radix2EvaluationDomain as D,
42};
43use core::array;
44use itertools::Itertools;
45use mina_poseidon::{poseidon::ArithmeticSpongeParams, sponge::ScalarChallenge, FqSponge};
46use o1_utils::ExtendedDensePolynomial as _;
47use poly_commitment::{
48    commitment::{
49        absorb_commitment, b_poly_coefficients, BlindedCommitment, CommitmentCurve, PolyComm,
50    },
51    utils::DensePolynomialOrEvaluations,
52    OpenProof, SRS as _,
53};
54use rand_core::{CryptoRng, RngCore};
55use rayon::prelude::*;
56
57/// The result of a proof creation or verification.
58type Result<T> = core::result::Result<T, ProverError>;
59
60/// Helper to quickly test if a witness satisfies a constraint
61macro_rules! check_constraint {
62    ($index:expr, $evaluation:expr) => {{
63        check_constraint!($index, stringify!($evaluation), $evaluation);
64    }};
65    ($index:expr, $label:expr, $evaluation:expr) => {{
66        if cfg!(debug_assertions) {
67            let (_, res) = $evaluation
68                .interpolate_by_ref()
69                .divide_by_vanishing_poly($index.cs.domain.d1);
70            if !res.is_zero() {
71                panic!("couldn't divide by vanishing polynomial: {}", $label);
72            }
73        }
74    }};
75}
76
77/// The blinders masking one commitment: one per chunk, drawn independently.
78///
79/// Independently, because a commitment is masked chunk by chunk. Two chunks
80/// masked by the same blinder leave their difference unmasked — the shared term
81/// cancels — so a single blinder repeated across the chunks hides less than it
82/// appears to. With one chunk the distinction does not arise, which is why this
83/// is easy to get wrong and impossible to observe in a passing test.
84fn blinder<F: UniformRand, RNG: RngCore + CryptoRng>(
85    num_chunks: usize,
86    rng: &mut RNG,
87) -> PolyComm<F> {
88    PolyComm::new((0..num_chunks).map(|_| F::rand(rng)).collect())
89}
90
91/// Contains variables needed for lookup in the prover algorithm.
92#[derive(Default)]
93struct LookupContext<G, F>
94where
95    G: CommitmentCurve,
96    F: FftField,
97{
98    /// The joint combiner used to join the columns of lookup tables
99    joint_combiner: Option<F>,
100
101    /// The power of the joint_combiner that can be used to add a table_id column
102    /// to the concatenated lookup tables.
103    table_id_combiner: Option<F>,
104
105    /// The combined lookup entry that can be used as dummy value
106    dummy_lookup_value: Option<F>,
107
108    /// The combined lookup table
109    joint_lookup_table: Option<DensePolynomial<F>>,
110    joint_lookup_table_d8: Option<Evaluations<F, D<F>>>,
111
112    /// The sorted polynomials `s` in different forms
113    sorted: Option<Vec<Evaluations<F, D<F>>>>,
114    sorted_coeffs: Option<Vec<DensePolynomial<F>>>,
115    sorted_comms: Option<Vec<BlindedCommitment<G>>>,
116    sorted8: Option<Vec<Evaluations<F, D<F>>>>,
117
118    /// The aggregation polynomial in different forms
119    aggreg_coeffs: Option<DensePolynomial<F>>,
120    aggreg_comm: Option<BlindedCommitment<G>>,
121    aggreg8: Option<Evaluations<F, D<F>>>,
122
123    // lookup-related evaluations
124    /// evaluation of lookup aggregation polynomial
125    pub lookup_aggregation_eval: Option<PointEvaluations<Vec<F>>>,
126    /// evaluation of lookup table polynomial
127    pub lookup_table_eval: Option<PointEvaluations<Vec<F>>>,
128    /// evaluation of lookup sorted polynomials
129    pub lookup_sorted_eval: [Option<PointEvaluations<Vec<F>>>; 5],
130    /// evaluation of runtime lookup table polynomial
131    pub runtime_lookup_table_eval: Option<PointEvaluations<Vec<F>>>,
132
133    /// Runtime table
134    runtime_table: Option<DensePolynomial<F>>,
135    runtime_table_d8: Option<Evaluations<F, D<F>>>,
136    runtime_table_comm: Option<BlindedCommitment<G>>,
137}
138
139impl<G, OpeningProof, const FULL_ROUNDS: usize> ProverProof<G, OpeningProof, FULL_ROUNDS>
140where
141    G: KimchiCurve<FULL_ROUNDS>,
142    G::BaseField: PrimeField,
143    OpeningProof: OpenProof<G, FULL_ROUNDS>,
144{
145    /// This function constructs prover's zk-proof from the witness & the `ProverIndex` against SRS instance
146    ///
147    /// # Errors
148    ///
149    /// Will give error if `create_recursive` process fails.
150    pub fn create<EFqSponge, EFrSponge, RNG>(
151        groupmap: &G::Map,
152        witness: [Vec<G::ScalarField>; COLUMNS],
153        runtime_tables: &[RuntimeTable<G::ScalarField>],
154        index: &ProverIndex<FULL_ROUNDS, G, OpeningProof::SRS>,
155        rng: &mut RNG,
156    ) -> Result<Self>
157    where
158        EFqSponge: Clone + FqSponge<G::BaseField, G, G::ScalarField, FULL_ROUNDS>,
159        EFrSponge: FrSponge<G::ScalarField>,
160        EFrSponge: From<&'static ArithmeticSpongeParams<G::ScalarField, FULL_ROUNDS>>,
161        RNG: RngCore + CryptoRng,
162        VerifierIndex<FULL_ROUNDS, G, OpeningProof::SRS>: Clone,
163    {
164        Self::create_recursive::<EFqSponge, EFrSponge, RNG>(
165            groupmap,
166            witness,
167            runtime_tables,
168            index,
169            Vec::new(),
170            None,
171            rng,
172        )
173    }
174
175    /// This function constructs prover's recursive zk-proof from the witness &
176    /// the `ProverIndex` against SRS instance
177    ///
178    /// # Errors
179    ///
180    /// Will give error if inputs(like `lookup_context.joint_lookup_table_d8`)
181    /// are None.
182    ///
183    /// # Panics
184    ///
185    /// Will panic if `lookup_context.joint_lookup_table_d8` is None.
186    pub fn create_recursive<EFqSponge, EFrSponge, RNG>(
187        group_map: &G::Map,
188        mut witness: [Vec<G::ScalarField>; COLUMNS],
189        runtime_tables: &[RuntimeTable<G::ScalarField>],
190        index: &ProverIndex<FULL_ROUNDS, G, OpeningProof::SRS>,
191        prev_challenges: Vec<RecursionChallenge<G>>,
192        blinders: Option<[Option<PolyComm<G::ScalarField>>; COLUMNS]>,
193        rng: &mut RNG,
194    ) -> Result<Self>
195    where
196        EFqSponge: Clone + FqSponge<G::BaseField, G, G::ScalarField, FULL_ROUNDS>,
197        EFrSponge: FrSponge<G::ScalarField>,
198        EFrSponge: From<&'static ArithmeticSpongeParams<G::ScalarField, FULL_ROUNDS>>,
199        RNG: RngCore + CryptoRng,
200        VerifierIndex<FULL_ROUNDS, G, OpeningProof::SRS>: Clone,
201    {
202        internal_tracing::checkpoint!(internal_traces; create_recursive);
203        let d1_size = index.cs.domain.d1.size();
204
205        let (_, endo_r) = G::endos();
206
207        let num_chunks = if d1_size < index.max_poly_size {
208            1
209        } else {
210            d1_size / index.max_poly_size
211        };
212
213        // Verify the circuit satisfiability by the computed witness (baring plookup constraints)
214        // Catch mistakes before proof generation.
215        if cfg!(debug_assertions) && !index.cs.disable_gates_checks {
216            let public = witness[0][0..index.cs.public].to_vec();
217            index.verify(&witness, &public).expect("incorrect witness");
218        }
219
220        //~ 1. Ensure we have room in the witness for the zero-knowledge rows.
221        //~    We currently expect the witness not to be of the same length as the domain,
222        //~    but instead be of the length of the (smaller) circuit.
223        //~    If we cannot add `zk_rows` rows to the columns of the witness before reaching
224        //~    the size of the domain, abort.
225        let length_witness = witness[0].len();
226        let length_padding = d1_size
227            .checked_sub(length_witness)
228            .ok_or(ProverError::NoRoomForZkInWitness)?;
229
230        let zero_knowledge_limit = zk_rows_strict_lower_bound(num_chunks);
231        // Because the lower bound is strict, the result of the function above
232        // is not a sufficient number of zero knowledge rows, so the error must
233        // be raised anytime the number of zero knowledge rows is not greater
234        // than the strict lower bound.
235        // Example:
236        //   for 1 chunk, `zero_knowledge_limit` is 2, and we need at least 3,
237        //   thus the error should be raised and the message should say that the
238        //   expected number of zero knowledge rows is 3 (hence the + 1).
239        if (index.cs.zk_rows as usize) <= zero_knowledge_limit {
240            return Err(ProverError::NotZeroKnowledge(
241                zero_knowledge_limit + 1,
242                index.cs.zk_rows as usize,
243            ));
244        }
245
246        if length_padding < index.cs.zk_rows as usize {
247            return Err(ProverError::NoRoomForZkInWitness);
248        }
249
250        //~ 1. Pad the witness columns with Zero gates to make them the same length as the domain.
251        //~    Then, randomize the last `zk_rows` of each columns.
252        internal_tracing::checkpoint!(internal_traces; pad_witness);
253        for w in &mut witness {
254            if w.len() != length_witness {
255                return Err(ProverError::WitnessCsInconsistent);
256            }
257
258            // padding
259            w.extend(std::iter::repeat_n(G::ScalarField::zero(), length_padding));
260
261            // zk-rows
262            for row in w.iter_mut().rev().take(index.cs.zk_rows as usize) {
263                *row = <G::ScalarField as UniformRand>::rand(rng);
264            }
265        }
266
267        //~ 1. Setup the Fq-Sponge.
268        internal_tracing::checkpoint!(internal_traces; set_up_fq_sponge);
269        let mut fq_sponge = EFqSponge::new(G::other_curve_sponge_params());
270
271        //~ 1. Absorb the digest of the VerifierIndex.
272        let verifier_index_digest = index.verifier_index_digest::<EFqSponge>();
273        fq_sponge.absorb_fq(&[verifier_index_digest]);
274
275        //~ 1. Absorb the commitments of the previous challenges with the Fq-sponge.
276        for RecursionChallenge { comm, .. } in &prev_challenges {
277            absorb_commitment(&mut fq_sponge, comm)
278        }
279
280        //~ 1. Compute the negated public input polynomial as
281        //~    the polynomial that evaluates to $-p_i$ for the first `public_input_size` values of the domain,
282        //~    and $0$ for the rest.
283        let public = witness[0][0..index.cs.public].to_vec();
284        let public_poly = -Evaluations::<G::ScalarField, D<G::ScalarField>>::from_vec_and_domain(
285            public,
286            index.cs.domain.d1,
287        )
288        .interpolate();
289
290        //~ 1. Commit (non-hiding) to the negated public input polynomial.
291        let public_comm = index.srs.commit_non_hiding(&public_poly, num_chunks);
292        let public_comm = {
293            index
294                .srs
295                .mask_custom(
296                    public_comm.clone(),
297                    &public_comm.map(|_| G::ScalarField::one()),
298                )
299                .unwrap()
300                .commitment
301        };
302
303        //~ 1. Absorb the commitment to the public polynomial with the Fq-Sponge.
304        //~
305        //~    Note: unlike the original PLONK protocol,
306        //~    the prover also provides evaluations of the public polynomial to help the verifier circuit.
307        //~    This is why we need to absorb the commitment to the public polynomial at this point.
308        absorb_commitment(&mut fq_sponge, &public_comm);
309
310        //~ 1. Commit to the witness columns by creating `COLUMNS` hiding commitments.
311        //~
312        //~    Note: since the witness is in evaluation form,
313        //~    we can use the `commit_evaluation` optimization.
314        internal_tracing::checkpoint!(internal_traces; commit_to_witness_columns);
315        // generate blinders if not given externally
316        let blinders_final: Vec<PolyComm<G::ScalarField>> = match blinders {
317            None => (0..COLUMNS)
318                .map(|_| blinder::<G::ScalarField, _>(num_chunks, rng))
319                .collect(),
320            Some(blinders_arr) => blinders_arr
321                .into_iter()
322                .map(|blinder_el| match blinder_el {
323                    None => blinder::<G::ScalarField, _>(num_chunks, rng),
324                    Some(blinder_el_some) => blinder_el_some,
325                })
326                .collect(),
327        };
328        let w_comm_opt_res: Vec<Result<_>> = witness
329            .clone()
330            .into_par_iter()
331            .zip(blinders_final.into_par_iter())
332            .map(|(witness, blinder)| {
333                let witness_eval =
334                    Evaluations::<G::ScalarField, D<G::ScalarField>>::from_vec_and_domain(
335                        witness,
336                        index.cs.domain.d1,
337                    );
338
339                // TODO: make this a function rather no? mask_with_custom()
340                let witness_com = index
341                    .srs
342                    .commit_evaluations_non_hiding(index.cs.domain.d1, &witness_eval);
343                let com = index
344                    .srs
345                    .mask_custom(witness_com, &blinder)
346                    .map_err(ProverError::WrongBlinders)?;
347
348                Ok(com)
349            })
350            .collect();
351
352        let w_comm_res: Result<Vec<BlindedCommitment<G>>> = w_comm_opt_res.into_iter().collect();
353
354        let w_comm = w_comm_res?;
355
356        let w_comm: [BlindedCommitment<G>; COLUMNS] = w_comm
357            .try_into()
358            .expect("previous loop is of the correct length");
359
360        //~ 1. Absorb the witness commitments with the Fq-Sponge.
361        w_comm
362            .iter()
363            .for_each(|c| absorb_commitment(&mut fq_sponge, &c.commitment));
364
365        //~ 1. Compute the witness polynomials by interpolating each `COLUMNS` of the witness.
366        //~    As mentioned above, we commit using the evaluations form rather than the coefficients
367        //~    form so we can take advantage of the sparsity of the evaluations (i.e., there are many
368        //~    0 entries and entries that have less-than-full-size field elemnts.)
369        let witness_poly: [DensePolynomial<G::ScalarField>; COLUMNS] = (0..COLUMNS)
370            .into_par_iter()
371            .map(|i| {
372                Evaluations::<G::ScalarField, D<G::ScalarField>>::from_vec_and_domain(
373                    witness[i].clone(),
374                    index.cs.domain.d1,
375                )
376                .interpolate()
377            })
378            .collect::<Vec<_>>()
379            .try_into()
380            .unwrap();
381
382        let mut lookup_context = LookupContext::default();
383
384        //~ 1. If using lookup:
385        let lookup_constraint_system = index
386            .cs
387            .lookup_constraint_system
388            .try_get_or_err()
389            .map_err(ProverError::from)?;
390        if let Some(lcs) = lookup_constraint_system {
391            internal_tracing::checkpoint!(internal_traces; use_lookup, {
392                "uses_lookup": true,
393                "uses_runtime_tables": lcs.runtime_tables.is_some(),
394            });
395            //~~ * if using runtime table:
396            let runtime_second_col_d8 = if let Some(cfg_runtime_tables) = &lcs.runtime_tables {
397                //~~~ * check that all the provided runtime tables have length and IDs that match the runtime table configuration of the index
398                //~~~   we expect the given runtime tables to be sorted as configured, this makes it easier afterwards
399                let expected_runtime: Vec<_> = cfg_runtime_tables
400                    .iter()
401                    .map(|rt| (rt.id, rt.len))
402                    .collect();
403                let runtime: Vec<_> = runtime_tables
404                    .iter()
405                    .map(|rt| (rt.id, rt.data.len()))
406                    .collect();
407                if expected_runtime != runtime {
408                    return Err(ProverError::RuntimeTablesInconsistent);
409                }
410
411                //~~~ * calculate the contribution to the second column of the lookup table
412                //~~~   (the runtime vector)
413                let (runtime_table_contribution, runtime_table_contribution_d8) = {
414                    let mut offset = lcs
415                        .runtime_table_offset
416                        .expect("runtime configuration missing offset");
417
418                    let mut evals = vec![G::ScalarField::zero(); d1_size];
419                    for rt in runtime_tables {
420                        let range = offset..(offset + rt.data.len());
421                        evals[range].copy_from_slice(&rt.data);
422                        offset += rt.data.len();
423                    }
424
425                    // zero-knowledge
426                    for e in evals.iter_mut().rev().take(index.cs.zk_rows as usize) {
427                        *e = <G::ScalarField as UniformRand>::rand(rng);
428                    }
429
430                    // get coeff and evaluation form
431                    let runtime_table_contribution =
432                        Evaluations::from_vec_and_domain(evals, index.cs.domain.d1).interpolate();
433
434                    let runtime_table_contribution_d8 =
435                        runtime_table_contribution.evaluate_over_domain_by_ref(index.cs.domain.d8);
436
437                    (runtime_table_contribution, runtime_table_contribution_d8)
438                };
439
440                // commit the runtime polynomial
441                // (and save it to the proof)
442                let runtime_table_comm =
443                    index
444                        .srs
445                        .commit(&runtime_table_contribution, num_chunks, rng);
446
447                // absorb the commitment
448                absorb_commitment(&mut fq_sponge, &runtime_table_comm.commitment);
449
450                // pre-compute the updated second column of the lookup table
451                let mut second_column_d8 = runtime_table_contribution_d8.clone();
452                second_column_d8
453                    .evals
454                    .par_iter_mut()
455                    .enumerate()
456                    .for_each(|(row, e)| {
457                        *e += lcs.lookup_table8[1][row];
458                    });
459
460                lookup_context.runtime_table = Some(runtime_table_contribution);
461                lookup_context.runtime_table_d8 = Some(runtime_table_contribution_d8);
462                lookup_context.runtime_table_comm = Some(runtime_table_comm);
463                Some(second_column_d8)
464            } else {
465                None
466            };
467
468            //~~ * If queries involve a lookup table with multiple columns
469            //~~   then squeeze the Fq-Sponge to obtain the joint combiner challenge $j'$,
470            //~~   otherwise set the joint combiner challenge $j'$ to $0$.
471            let joint_combiner = if lcs.configuration.lookup_info.features.joint_lookup_used {
472                fq_sponge.challenge()
473            } else {
474                G::ScalarField::zero()
475            };
476
477            //~~ * Derive the scalar joint combiner $j$ from $j'$ using the endomorphism (TODO: specify)
478            let joint_combiner: G::ScalarField =
479                ScalarChallenge::new(joint_combiner).to_field(endo_r);
480
481            //~~ * If multiple lookup tables are involved,
482            //~~   set the `table_id_combiner` as the $j^i$ with $i$ the maximum width of any used table.
483            //~~   Essentially, this is to add a last column of table ids to the concatenated lookup tables.
484            let table_id_combiner: G::ScalarField = if lcs.table_ids8.as_ref().is_some() {
485                joint_combiner.pow([lcs.configuration.lookup_info.max_joint_size as u64])
486            } else {
487                // TODO: just set this to None in case multiple tables are not used
488                G::ScalarField::zero()
489            };
490            lookup_context.table_id_combiner = Some(table_id_combiner);
491
492            //~~ * Compute the dummy lookup value as the combination of the last entry of the XOR table (so `(0, 0, 0)`).
493            //~~   Warning: This assumes that we always use the XOR table when using lookups.
494            let dummy_lookup_value = lcs
495                .configuration
496                .dummy_lookup
497                .evaluate(&joint_combiner, &table_id_combiner);
498            lookup_context.dummy_lookup_value = Some(dummy_lookup_value);
499
500            //~~ * Compute the lookup table values as the combination of the lookup table entries.
501            let joint_lookup_table_d8 = {
502                // Each row combines its own table entry independently of every
503                // other row, so this is a pure map over the d8 domain.
504                let evals = (0..(d1_size * 8))
505                    .into_par_iter()
506                    .map(|idx| {
507                        let table_id = match lcs.table_ids8.as_ref() {
508                            Some(table_ids8) => table_ids8.evals[idx],
509                            None =>
510                            // If there is no `table_ids8` in the constraint system,
511                            // every table ID is identically 0.
512                            {
513                                G::ScalarField::zero()
514                            }
515                        };
516
517                        if !lcs.configuration.lookup_info.features.uses_runtime_tables {
518                            let table_row = lcs.lookup_table8.iter().map(|e| &e.evals[idx]);
519
520                            combine_table_entry(
521                                &joint_combiner,
522                                &table_id_combiner,
523                                table_row,
524                                &table_id,
525                            )
526                        } else {
527                            // if runtime table are used, the second row is modified
528                            let second_col = runtime_second_col_d8.as_ref().unwrap();
529
530                            let table_row = lcs.lookup_table8.iter().enumerate().map(|(col, e)| {
531                                if col == 1 {
532                                    &second_col.evals[idx]
533                                } else {
534                                    &e.evals[idx]
535                                }
536                            });
537
538                            combine_table_entry(
539                                &joint_combiner,
540                                &table_id_combiner,
541                                table_row,
542                                &table_id,
543                            )
544                        }
545                    })
546                    .collect();
547
548                Evaluations::from_vec_and_domain(evals, index.cs.domain.d8)
549            };
550
551            // Recover the d1 coefficient form of the joint table.
552            //
553            // `combine_table_entry` is a Horner fold over the table columns with
554            // constant (challenge) coefficients, so the joint table is a fixed
555            // linear combination of the columns -- each of which is the d8
556            // evaluation of a polynomial of degree < d1 -- and so has degree < d1
557            // itself. d1 is a subgroup of d8 (d8 = 8*d1, Radix2), so the joint
558            // table's d1 evaluations are exactly every 8th d8 evaluation, and a
559            // d1-sized iFFT over them recovers the coefficients exactly. This
560            // avoids the full d8 iFFT the interpolation here used to perform.
561            let joint_lookup_table = {
562                let d1_evals: Vec<G::ScalarField> = joint_lookup_table_d8
563                    .evals
564                    .iter()
565                    .step_by(8)
566                    .copied()
567                    .collect();
568                Evaluations::from_vec_and_domain(d1_evals, index.cs.domain.d1).interpolate()
569            };
570
571            //~~ * Compute the sorted evaluations.
572            // TODO: Once we switch to committing using lagrange commitments,
573            // `witness` will be consumed when we interpolate, so interpolation will
574            // have to moved below this.
575            let sorted: Vec<_> = lookup::constraints::sorted(
576                dummy_lookup_value,
577                &joint_lookup_table_d8,
578                index.cs.domain.d1,
579                &index.cs.gates,
580                &witness,
581                joint_combiner,
582                table_id_combiner,
583                &lcs.configuration.lookup_info,
584                index.cs.zk_rows as usize,
585            )?;
586
587            //~~ * Randomize the last `EVALS` rows in each of the sorted polynomials
588            //~~   in order to add zero-knowledge to the protocol.
589            let sorted: Vec<_> = sorted
590                .into_iter()
591                .map(|chunk| {
592                    lookup::constraints::zk_patch(
593                        chunk,
594                        index.cs.domain.d1,
595                        index.cs.zk_rows as usize,
596                        rng,
597                    )
598                })
599                .collect();
600
601            //~~ * Commit each of the sorted polynomials.
602            let sorted_comms: Vec<_> = sorted
603                .iter()
604                .map(|v| index.srs.commit_evaluations(index.cs.domain.d1, v, rng))
605                .collect();
606
607            //~~ * Absorb each commitments to the sorted polynomials.
608            sorted_comms
609                .iter()
610                .for_each(|c| absorb_commitment(&mut fq_sponge, &c.commitment));
611
612            // precompute different forms of the sorted polynomials for later
613            // TODO: We can avoid storing these coefficients.
614            let sorted_coeffs: Vec<_> =
615                sorted.par_iter().map(|e| e.clone().interpolate()).collect();
616            let sorted8: Vec<_> = sorted_coeffs
617                .par_iter()
618                .map(|v| v.evaluate_over_domain_by_ref(index.cs.domain.d8))
619                .collect();
620
621            lookup_context.joint_combiner = Some(joint_combiner);
622            lookup_context.sorted = Some(sorted);
623            lookup_context.sorted_coeffs = Some(sorted_coeffs);
624            lookup_context.sorted_comms = Some(sorted_comms);
625            lookup_context.sorted8 = Some(sorted8);
626            lookup_context.joint_lookup_table_d8 = Some(joint_lookup_table_d8);
627            lookup_context.joint_lookup_table = Some(joint_lookup_table);
628        }
629
630        //~ 1. Sample $\beta$ with the Fq-Sponge.
631        let beta = fq_sponge.challenge();
632
633        //~ 1. Sample $\gamma$ with the Fq-Sponge.
634        let gamma = fq_sponge.challenge();
635
636        //~ 1. If using lookup:
637        if let Some(lcs) = lookup_constraint_system {
638            //~~ * Compute the lookup aggregation polynomial.
639            let joint_lookup_table_d8 = lookup_context.joint_lookup_table_d8.as_ref().unwrap();
640            let sorted = lookup_context.sorted.take().unwrap();
641
642            let aggreg = lookup::constraints::aggregation::<_, G::ScalarField>(
643                lookup_context.dummy_lookup_value.unwrap(),
644                joint_lookup_table_d8,
645                index.cs.domain.d1,
646                &index.cs.gates,
647                &witness,
648                &lookup_context.joint_combiner.unwrap(),
649                &lookup_context.table_id_combiner.unwrap(),
650                beta,
651                gamma,
652                &sorted,
653                rng,
654                &lcs.configuration.lookup_info,
655                index.cs.zk_rows as usize,
656            )?;
657
658            //~~ * Commit to the aggregation polynomial.
659            let aggreg_comm = index
660                .srs
661                .commit_evaluations(index.cs.domain.d1, &aggreg, rng);
662
663            //~~ * Absorb the commitment to the aggregation polynomial with the Fq-Sponge.
664            absorb_commitment(&mut fq_sponge, &aggreg_comm.commitment);
665
666            // precompute different forms of the aggregation polynomial for later
667            let aggreg_coeffs = aggreg.interpolate();
668            // TODO: There's probably a clever way to expand the domain without
669            // interpolating
670            let aggreg8 = aggreg_coeffs.evaluate_over_domain_by_ref(index.cs.domain.d8);
671
672            lookup_context.aggreg_comm = Some(aggreg_comm);
673            lookup_context.aggreg_coeffs = Some(aggreg_coeffs);
674            lookup_context.aggreg8 = Some(aggreg8);
675        }
676
677        let column_evaluations = index.column_evaluations.get();
678
679        //~ 1. Compute the permutation aggregation polynomial $z$.
680        internal_tracing::checkpoint!(internal_traces; z_permutation_aggregation_polynomial);
681        let z_poly = index.perm_aggreg(&witness, &beta, &gamma, rng)?;
682        drop(witness);
683
684        //~ 1. Commit (hiding) to the permutation aggregation polynomial $z$.
685        let z_comm = index.srs.commit(&z_poly, num_chunks, rng);
686
687        //~ 1. Absorb the permutation aggregation polynomial $z$ with the Fq-Sponge.
688        absorb_commitment(&mut fq_sponge, &z_comm.commitment);
689
690        //~ 1. Sample $\alpha'$ with the Fq-Sponge.
691        let alpha_chal = ScalarChallenge::new(fq_sponge.challenge());
692
693        //~ 1. Derive $\alpha$ from $\alpha'$ using the endomorphism (TODO: details)
694        let alpha: G::ScalarField = alpha_chal.to_field(endo_r);
695
696        //~ 1. TODO: instantiate alpha?
697        let mut all_alphas = index.powers_of_alpha.clone();
698        all_alphas.instantiate(alpha);
699
700        //~ 1. Compute the quotient polynomial (the $t$ in $f = Z_H \cdot t$).
701        //~    The quotient polynomial is computed by adding all these polynomials together:
702        //~~ * the combined constraints for all the gates
703        //~~ * the combined constraints for the permutation
704        //~~ * TODO: lookup
705        //~~ * the negated public polynomial
706        //~    and by then dividing the resulting polynomial with the vanishing polynomial $Z_H$.
707        //~    TODO: specify the split of the permutation polynomial into perm and bnd?
708        let (t_comm, zeta, zeta_omega, chunked_evals, ft, blinding_ft) = {
709            let joint_lookup_table_d8 = lookup_context.joint_lookup_table_d8.take();
710            let sorted8 = lookup_context.sorted8.take();
711            let aggreg8 = lookup_context.aggreg8.take();
712            let runtime_table_d8 = lookup_context.runtime_table_d8.take();
713
714            let lookup_env = if let Some(lcs) = lookup_constraint_system {
715                Some(LookupEnvironment {
716                    aggreg: aggreg8.as_ref().unwrap(),
717                    sorted: sorted8.as_ref().unwrap(),
718                    selectors: &lcs.lookup_selectors,
719                    table: joint_lookup_table_d8.as_ref().unwrap(),
720                    runtime_selector: lcs.runtime_selector.as_ref(),
721                    runtime_table: runtime_table_d8.as_ref(),
722                })
723            } else {
724                None
725            };
726
727            internal_tracing::checkpoint!(internal_traces; eval_witness_polynomials_over_domains);
728            let lagrange = index.cs.evaluate(&witness_poly, &z_poly);
729            internal_tracing::checkpoint!(internal_traces; compute_index_evals);
730            let env = {
731                let mut index_evals = HashMap::new();
732                use GateType::*;
733                index_evals.insert(Generic, &column_evaluations.generic_selector4);
734                index_evals.insert(Poseidon, &column_evaluations.poseidon_selector8);
735                index_evals.insert(CompleteAdd, &column_evaluations.complete_add_selector4);
736                index_evals.insert(VarBaseMul, &column_evaluations.mul_selector8);
737                index_evals.insert(EndoMul, &column_evaluations.emul_selector8);
738                index_evals.insert(EndoMulScalar, &column_evaluations.endomul_scalar_selector8);
739
740                if let Some(selector) = &column_evaluations.range_check0_selector8 {
741                    index_evals.insert(GateType::RangeCheck0, selector);
742                }
743
744                if let Some(selector) = &column_evaluations.range_check1_selector8 {
745                    index_evals.insert(GateType::RangeCheck1, selector);
746                }
747
748                if let Some(selector) = &column_evaluations.foreign_field_add_selector8 {
749                    index_evals.insert(GateType::ForeignFieldAdd, selector);
750                }
751
752                if let Some(selector) = &column_evaluations.foreign_field_mul_selector8 {
753                    index_evals.extend(
754                        foreign_field_mul::gadget::circuit_gates()
755                            .iter()
756                            .map(|gate_type| (*gate_type, selector)),
757                    );
758                }
759
760                if let Some(selector) = &column_evaluations.xor_selector8 {
761                    index_evals.insert(GateType::Xor16, selector);
762                }
763
764                if let Some(selector) = &column_evaluations.rot_selector8 {
765                    index_evals.insert(GateType::Rot64, selector);
766                }
767
768                let mds = &G::sponge_params().mds;
769                Environment {
770                    constants: Constants {
771                        endo_coefficient: index.cs.endo,
772                        mds,
773                        zk_rows: index.cs.zk_rows,
774                    },
775                    challenges: BerkeleyChallenges {
776                        alpha,
777                        beta,
778                        gamma,
779                        joint_combiner: lookup_context
780                            .joint_combiner
781                            .unwrap_or(G::ScalarField::zero()),
782                    },
783                    witness: &lagrange.this.w,
784                    coefficient: &column_evaluations.coefficients8,
785                    vanishes_on_zero_knowledge_and_previous_rows: &index
786                        .cs
787                        .precomputations()
788                        .vanishes_on_zero_knowledge_and_previous_rows,
789                    z: &lagrange.this.z,
790                    l0_1: l0_1(index.cs.domain.d1),
791                    domain: index.cs.domain,
792                    index: index_evals,
793                    lookup: lookup_env,
794                }
795            };
796
797            let mut cache = expr::Cache::default();
798
799            internal_tracing::checkpoint!(internal_traces; compute_quotient_poly);
800
801            let quotient_poly = {
802                // generic
803                let mut t4 = {
804                    let generic_constraint =
805                        generic::Generic::combined_constraints(&all_alphas, &mut cache);
806                    let generic4 = generic_constraint.evaluations(&env);
807
808                    if cfg!(debug_assertions) {
809                        let p4 = public_poly.evaluate_over_domain_by_ref(index.cs.domain.d4);
810                        let gen_minus_pub = &generic4 + &p4;
811
812                        check_constraint!(index, gen_minus_pub);
813                    }
814
815                    generic4
816                };
817
818                // permutation
819                let (mut t8, bnd) = {
820                    let alphas =
821                        all_alphas.get_alphas(ArgumentType::Permutation, permutation::CONSTRAINTS);
822                    let (perm, bnd) = index.perm_quot(&lagrange, beta, gamma, &z_poly, alphas)?;
823
824                    check_constraint!(index, perm);
825
826                    (perm, bnd)
827                };
828
829                {
830                    use crate::circuits::argument::DynArgument;
831
832                    let range_check0_enabled = column_evaluations.range_check0_selector8.is_some();
833                    let range_check1_enabled = column_evaluations.range_check1_selector8.is_some();
834                    let foreign_field_addition_enabled =
835                        column_evaluations.foreign_field_add_selector8.is_some();
836                    let foreign_field_multiplication_enabled =
837                        column_evaluations.foreign_field_mul_selector8.is_some();
838                    let xor_enabled = column_evaluations.xor_selector8.is_some();
839                    let rot_enabled = column_evaluations.rot_selector8.is_some();
840
841                    for gate in [
842                        (
843                            (&CompleteAdd::default() as &dyn DynArgument<G::ScalarField>),
844                            true,
845                        ),
846                        (&VarbaseMul::default(), true),
847                        (&EndosclMul::default(), true),
848                        (&EndomulScalar::default(), true),
849                        (&Poseidon::default(), true),
850                        // Range check gates
851                        (&RangeCheck0::default(), range_check0_enabled),
852                        (&RangeCheck1::default(), range_check1_enabled),
853                        // Foreign field addition gate
854                        (&ForeignFieldAdd::default(), foreign_field_addition_enabled),
855                        // Foreign field multiplication gate
856                        (
857                            &ForeignFieldMul::default(),
858                            foreign_field_multiplication_enabled,
859                        ),
860                        // Xor gate
861                        (&Xor16::default(), xor_enabled),
862                        // Rot gate
863                        (&Rot64::default(), rot_enabled),
864                    ]
865                    .into_iter()
866                    .filter_map(|(gate, is_enabled)| if is_enabled { Some(gate) } else { None })
867                    {
868                        let constraint = gate.combined_constraints(&all_alphas, &mut cache);
869                        let eval = constraint.evaluations(&env);
870                        if eval.domain().size == t4.domain().size {
871                            t4 += &eval;
872                        } else if eval.domain().size == t8.domain().size {
873                            t8 += &eval;
874                        } else {
875                            panic!("Bad evaluation")
876                        }
877                        check_constraint!(index, format!("{:?}", gate.argument_type()), eval);
878                    }
879                };
880
881                // lookup
882                {
883                    if let Some(lcs) = lookup_constraint_system {
884                        let constraints =
885                            lookup::constraints::constraints(&lcs.configuration, false);
886                        let constraints_len = u32::try_from(constraints.len())
887                            .expect("not expecting a large amount of constraints");
888                        let lookup_alphas =
889                            all_alphas.get_alphas(ArgumentType::Lookup, constraints_len);
890
891                        // as lookup constraints are computed with the expression framework,
892                        // each of them can result in Evaluations of different domains
893                        for (ii, (constraint, alpha_pow)) in
894                            constraints.into_iter().zip_eq(lookup_alphas).enumerate()
895                        {
896                            let mut eval = constraint.evaluations(&env);
897                            eval.evals.par_iter_mut().for_each(|x| *x *= alpha_pow);
898
899                            if eval.domain().size == t4.domain().size {
900                                t4 += &eval;
901                            } else if eval.domain().size == t8.domain().size {
902                                t8 += &eval;
903                            } else if eval.evals.iter().all(|x| x.is_zero()) {
904                                // Skip any 0-valued evaluations
905                            } else {
906                                panic!("Bad evaluation")
907                            }
908
909                            check_constraint!(index, format!("lookup constraint #{ii}"), eval);
910                        }
911                    }
912                }
913
914                // public polynomial
915                let mut f = t4.interpolate() + t8.interpolate();
916                f += &public_poly;
917
918                // divide contributions with vanishing polynomial
919                let (mut quotient, res) = f.divide_by_vanishing_poly(index.cs.domain.d1);
920                if !res.is_zero() {
921                    return Err(ProverError::Prover(
922                        "rest of division by vanishing polynomial",
923                    ));
924                }
925
926                quotient += &bnd; // already divided by Z_H
927                quotient
928            };
929
930            //~ 1. commit (hiding) to the quotient polynomial $t$
931            let t_comm = { index.srs.commit(&quotient_poly, 7 * num_chunks, rng) };
932
933            //~ 1. Absorb the commitment of the quotient polynomial with the Fq-Sponge.
934            absorb_commitment(&mut fq_sponge, &t_comm.commitment);
935
936            //~ 1. Sample $\zeta'$ with the Fq-Sponge.
937            let zeta_chal = ScalarChallenge::new(fq_sponge.challenge());
938
939            //~ 1. Derive $\zeta$ from $\zeta'$ using the endomorphism (TODO: specify)
940            let zeta = zeta_chal.to_field(endo_r);
941
942            let omega = index.cs.domain.d1.group_gen;
943            let zeta_omega = zeta * omega;
944
945            //~ 1. If lookup is used, evaluate the following polynomials at $\zeta$ and $\zeta \omega$:
946            if lookup_constraint_system.is_some() {
947                //~~ * the aggregation polynomial
948                let aggreg = lookup_context
949                    .aggreg_coeffs
950                    .as_ref()
951                    .unwrap()
952                    .to_chunked_polynomial(num_chunks, index.max_poly_size);
953
954                //~~ * the sorted polynomials
955                let sorted = lookup_context
956                    .sorted_coeffs
957                    .as_ref()
958                    .unwrap()
959                    .iter()
960                    .map(|c| c.to_chunked_polynomial(num_chunks, index.max_poly_size))
961                    .collect::<Vec<_>>();
962
963                //~~ * the table polynonial
964                let joint_table = lookup_context.joint_lookup_table.as_ref().unwrap();
965                let joint_table =
966                    joint_table.to_chunked_polynomial(num_chunks, index.max_poly_size);
967
968                lookup_context.lookup_aggregation_eval = Some(PointEvaluations {
969                    zeta: aggreg.evaluate_chunks(zeta),
970                    zeta_omega: aggreg.evaluate_chunks(zeta_omega),
971                });
972                lookup_context.lookup_table_eval = Some(PointEvaluations {
973                    zeta: joint_table.evaluate_chunks(zeta),
974                    zeta_omega: joint_table.evaluate_chunks(zeta_omega),
975                });
976                lookup_context.lookup_sorted_eval = array::from_fn(|i| {
977                    if i < sorted.len() {
978                        let sorted = &sorted[i];
979                        Some(PointEvaluations {
980                            zeta: sorted.evaluate_chunks(zeta),
981                            zeta_omega: sorted.evaluate_chunks(zeta_omega),
982                        })
983                    } else {
984                        None
985                    }
986                });
987                lookup_context.runtime_lookup_table_eval =
988                    lookup_context.runtime_table.as_ref().map(|runtime_table| {
989                        let runtime_table =
990                            runtime_table.to_chunked_polynomial(num_chunks, index.max_poly_size);
991                        PointEvaluations {
992                            zeta: runtime_table.evaluate_chunks(zeta),
993                            zeta_omega: runtime_table.evaluate_chunks(zeta_omega),
994                        }
995                    });
996            }
997
998            //~ 1. Chunk evaluate the following polynomials at both $\zeta$ and $\zeta \omega$:
999            //~~ * $s_i$
1000            //~~ * $w_i$
1001            //~~ * $z$
1002            //~~ * lookup (TODO, see [this issue](https://github.com/MinaProtocol/mina/issues/13886))
1003            //~~ * generic selector
1004            //~~ * poseidon selector
1005            //~
1006            //~    By "chunk evaluate" we mean that the evaluation of each polynomial can potentially be a vector of values.
1007            //~    This is because the index's `max_poly_size` parameter dictates the maximum size of a polynomial in the protocol.
1008            //~    If a polynomial $f$ exceeds this size, it must be split into several polynomials like so:
1009            //~    $$f(x) = f_0(x) + x^n f_1(x) + x^{2n} f_2(x) + \cdots$$
1010            //~
1011            //~    And the evaluation of such a polynomial is the following list for $x \in {\zeta, \zeta\omega}$:
1012            //~
1013            //~    $$(f_0(x), f_1(x), f_2(x), \ldots)$$
1014            //~
1015            //~    TODO: do we want to specify more on that? It seems unnecessary except for the t polynomial (or if for some reason someone sets that to a low value)
1016
1017            internal_tracing::checkpoint!(internal_traces; lagrange_basis_eval_zeta_poly);
1018            let zeta_evals =
1019                LagrangeBasisEvaluations::new(index.max_poly_size, index.cs.domain.d1, zeta);
1020            internal_tracing::checkpoint!(internal_traces; lagrange_basis_eval_zeta_omega_poly);
1021            let zeta_omega_evals =
1022                LagrangeBasisEvaluations::new(index.max_poly_size, index.cs.domain.d1, zeta_omega);
1023
1024            let chunked_evals_for_selector =
1025                |p: &Evaluations<G::ScalarField, D<G::ScalarField>>| PointEvaluations {
1026                    zeta: zeta_evals.evaluate_boolean(p),
1027                    zeta_omega: zeta_omega_evals.evaluate_boolean(p),
1028                };
1029
1030            let chunked_evals_for_evaluations =
1031                |p: &Evaluations<G::ScalarField, D<G::ScalarField>>| PointEvaluations {
1032                    zeta: zeta_evals.evaluate(p),
1033                    zeta_omega: zeta_omega_evals.evaluate(p),
1034                };
1035
1036            internal_tracing::checkpoint!(internal_traces; chunk_eval_zeta_omega_poly);
1037            let chunked_evals = ProofEvaluations::<PointEvaluations<Vec<G::ScalarField>>> {
1038                public: {
1039                    let chunked =
1040                        public_poly.to_chunked_polynomial(num_chunks, index.max_poly_size);
1041                    Some(PointEvaluations {
1042                        zeta: chunked.evaluate_chunks(zeta),
1043                        zeta_omega: chunked.evaluate_chunks(zeta_omega),
1044                    })
1045                },
1046                s: array::from_fn(|i| {
1047                    chunked_evals_for_evaluations(&column_evaluations.permutation_coefficients8[i])
1048                }),
1049                coefficients: array::from_fn(|i| {
1050                    chunked_evals_for_evaluations(&column_evaluations.coefficients8[i])
1051                }),
1052                w: array::from_fn(|i| {
1053                    let chunked =
1054                        witness_poly[i].to_chunked_polynomial(num_chunks, index.max_poly_size);
1055                    PointEvaluations {
1056                        zeta: chunked.evaluate_chunks(zeta),
1057                        zeta_omega: chunked.evaluate_chunks(zeta_omega),
1058                    }
1059                }),
1060
1061                z: {
1062                    let chunked = z_poly.to_chunked_polynomial(num_chunks, index.max_poly_size);
1063                    PointEvaluations {
1064                        zeta: chunked.evaluate_chunks(zeta),
1065                        zeta_omega: chunked.evaluate_chunks(zeta_omega),
1066                    }
1067                },
1068
1069                lookup_aggregation: lookup_context.lookup_aggregation_eval.take(),
1070                lookup_table: lookup_context.lookup_table_eval.take(),
1071                lookup_sorted: array::from_fn(|i| lookup_context.lookup_sorted_eval[i].take()),
1072                runtime_lookup_table: lookup_context.runtime_lookup_table_eval.take(),
1073                generic_selector: chunked_evals_for_selector(&column_evaluations.generic_selector4),
1074                poseidon_selector: chunked_evals_for_selector(
1075                    &column_evaluations.poseidon_selector8,
1076                ),
1077                complete_add_selector: chunked_evals_for_selector(
1078                    &column_evaluations.complete_add_selector4,
1079                ),
1080                mul_selector: chunked_evals_for_selector(&column_evaluations.mul_selector8),
1081                emul_selector: chunked_evals_for_selector(&column_evaluations.emul_selector8),
1082                endomul_scalar_selector: chunked_evals_for_selector(
1083                    &column_evaluations.endomul_scalar_selector8,
1084                ),
1085
1086                range_check0_selector: column_evaluations
1087                    .range_check0_selector8
1088                    .as_ref()
1089                    .map(chunked_evals_for_selector),
1090                range_check1_selector: column_evaluations
1091                    .range_check1_selector8
1092                    .as_ref()
1093                    .map(chunked_evals_for_selector),
1094                foreign_field_add_selector: column_evaluations
1095                    .foreign_field_add_selector8
1096                    .as_ref()
1097                    .map(chunked_evals_for_selector),
1098                foreign_field_mul_selector: column_evaluations
1099                    .foreign_field_mul_selector8
1100                    .as_ref()
1101                    .map(chunked_evals_for_selector),
1102                xor_selector: column_evaluations
1103                    .xor_selector8
1104                    .as_ref()
1105                    .map(chunked_evals_for_selector),
1106                rot_selector: column_evaluations
1107                    .rot_selector8
1108                    .as_ref()
1109                    .map(chunked_evals_for_selector),
1110
1111                runtime_lookup_table_selector: lookup_constraint_system.as_ref().and_then(|lcs| {
1112                    lcs.runtime_selector
1113                        .as_ref()
1114                        .map(chunked_evals_for_selector)
1115                }),
1116                xor_lookup_selector: lookup_constraint_system.as_ref().and_then(|lcs| {
1117                    lcs.lookup_selectors
1118                        .xor
1119                        .as_ref()
1120                        .map(chunked_evals_for_selector)
1121                }),
1122                lookup_gate_lookup_selector: lookup_constraint_system.as_ref().and_then(|lcs| {
1123                    lcs.lookup_selectors
1124                        .lookup
1125                        .as_ref()
1126                        .map(chunked_evals_for_selector)
1127                }),
1128                range_check_lookup_selector: lookup_constraint_system.as_ref().and_then(|lcs| {
1129                    lcs.lookup_selectors
1130                        .range_check
1131                        .as_ref()
1132                        .map(chunked_evals_for_selector)
1133                }),
1134                foreign_field_mul_lookup_selector: lookup_constraint_system.as_ref().and_then(
1135                    |lcs| {
1136                        lcs.lookup_selectors
1137                            .ffmul
1138                            .as_ref()
1139                            .map(chunked_evals_for_selector)
1140                    },
1141                ),
1142            };
1143
1144            let zeta_to_srs_len = zeta.pow([index.max_poly_size as u64]);
1145            let zeta_omega_to_srs_len = zeta_omega.pow([index.max_poly_size as u64]);
1146            let zeta_to_domain_size = zeta.pow([d1_size as u64]);
1147
1148            //~ 1. Evaluate the same polynomials without chunking them
1149            //~    (so that each polynomial should correspond to a single value this time).
1150            let evals: ProofEvaluations<PointEvaluations<G::ScalarField>> = {
1151                let powers_of_eval_points_for_chunks = PointEvaluations {
1152                    zeta: zeta_to_srs_len,
1153                    zeta_omega: zeta_omega_to_srs_len,
1154                };
1155                chunked_evals.combine(&powers_of_eval_points_for_chunks)
1156            };
1157
1158            //~ 1. Compute the ft polynomial.
1159            //~    This is to implement [Maller's optimization](https://o1-labs.github.io/proof-systems/kimchi/maller_15.html).
1160            internal_tracing::checkpoint!(internal_traces; compute_ft_poly);
1161            let ft: DensePolynomial<G::ScalarField> = {
1162                let f_chunked = {
1163                    // TODO: compute the linearization polynomial in evaluation form so
1164                    // that we can drop the coefficient forms of the index polynomials from
1165                    // the constraint system struct
1166
1167                    // permutation (not part of linearization yet)
1168                    let alphas =
1169                        all_alphas.get_alphas(ArgumentType::Permutation, permutation::CONSTRAINTS);
1170                    let f = index.perm_lnrz(&evals, zeta, beta, gamma, alphas);
1171
1172                    // the circuit polynomial
1173                    let f = {
1174                        let (_lin_constant, mut lin) =
1175                            index.linearization.to_polynomial(&env, zeta, &evals);
1176                        lin += &f;
1177                        lin.interpolate()
1178                    };
1179
1180                    // see https://o1-labs.github.io/proof-systems/kimchi/maller_15.html#the-prover-side
1181                    f.to_chunked_polynomial(num_chunks, index.max_poly_size)
1182                        .linearize(zeta_to_srs_len)
1183                };
1184
1185                let t_chunked = quotient_poly
1186                    .to_chunked_polynomial(7 * num_chunks, index.max_poly_size)
1187                    .linearize(zeta_to_srs_len);
1188
1189                &f_chunked - &t_chunked.scale(zeta_to_domain_size - G::ScalarField::one())
1190            };
1191
1192            //~ 1. construct the blinding part of the ft polynomial commitment
1193            //~    [see this section](https://o1-labs.github.io/proof-systems/kimchi/maller_15.html#evaluation-proof-and-blinding-factors)
1194            let blinding_ft = {
1195                let blinding_t = t_comm.blinders.chunk_blinding(zeta_to_srs_len);
1196                let blinding_f = G::ScalarField::zero();
1197
1198                PolyComm {
1199                    // blinding_f - Z_H(zeta) * blinding_t
1200                    chunks: vec![
1201                        blinding_f - (zeta_to_domain_size - G::ScalarField::one()) * blinding_t,
1202                    ],
1203                }
1204            };
1205
1206            (t_comm, zeta, zeta_omega, chunked_evals, ft, blinding_ft)
1207        };
1208
1209        //~ 1. Evaluate the ft polynomial at $\zeta\omega$ only.
1210        internal_tracing::checkpoint!(internal_traces; ft_eval_zeta_omega);
1211        let ft_eval1 = ft.evaluate(&zeta_omega);
1212
1213        //~ 1. Setup the Fr-Sponge
1214        let fq_sponge_before_evaluations = fq_sponge.clone();
1215        let mut fr_sponge = EFrSponge::from(G::sponge_params());
1216
1217        //~ 1. Squeeze the Fq-sponge and absorb the result with the Fr-Sponge.
1218        fr_sponge.absorb(&fq_sponge.digest());
1219
1220        //~ 1. Absorb the previous recursion challenges.
1221        let prev_challenge_digest = {
1222            // Note: we absorb in a new sponge here to limit the scope in which we need the
1223            // more-expensive 'optional sponge'.
1224            let mut fr_sponge = EFrSponge::from(G::sponge_params());
1225            for RecursionChallenge { chals, .. } in &prev_challenges {
1226                fr_sponge.absorb_multiple(chals);
1227            }
1228            fr_sponge.digest()
1229        };
1230        fr_sponge.absorb(&prev_challenge_digest);
1231
1232        //~ 1. Compute evaluations for the previous recursion challenges.
1233        internal_tracing::checkpoint!(internal_traces; build_polynomials);
1234        let polys = prev_challenges
1235            .iter()
1236            .map(|RecursionChallenge { chals, comm }| {
1237                (
1238                    DensePolynomial::from_coefficients_vec(b_poly_coefficients(chals)),
1239                    comm.len(),
1240                )
1241            })
1242            .collect::<Vec<_>>();
1243
1244        //~ 1. Absorb the unique evaluation of ft: $ft(\zeta\omega)$.
1245        fr_sponge.absorb(&ft_eval1);
1246
1247        //~ 1. Absorb all the polynomial evaluations in $\zeta$ and $\zeta\omega$:
1248        //~~ * the public polynomial
1249        //~~ * z
1250        //~~ * generic selector
1251        //~~ * poseidon selector
1252        //~~ * the 15 register/witness
1253        //~~ * 6 sigmas evaluations (the last one is not evaluated)
1254        fr_sponge.absorb_multiple(&chunked_evals.public.as_ref().unwrap().zeta);
1255        fr_sponge.absorb_multiple(&chunked_evals.public.as_ref().unwrap().zeta_omega);
1256        fr_sponge.absorb_evaluations(&chunked_evals);
1257
1258        //~ 1. Sample $v'$ with the Fr-Sponge
1259        let v_chal = fr_sponge.challenge();
1260
1261        //~ 1. Derive $v$ from $v'$ using the endomorphism (TODO: specify)
1262        let v = v_chal.to_field(endo_r);
1263
1264        //~ 1. Sample $u'$ with the Fr-Sponge
1265        let u_chal = fr_sponge.challenge();
1266
1267        //~ 1. Derive $u$ from $u'$ using the endomorphism (TODO: specify)
1268        let u = u_chal.to_field(endo_r);
1269
1270        //~ 1. Create a list of all polynomials that will require evaluations
1271        //~    (and evaluation proofs) in the protocol.
1272        //~    First, include the previous challenges, in case we are in a recursive prover.
1273        let non_hiding = |n_chunks: usize| PolyComm {
1274            chunks: vec![G::ScalarField::zero(); n_chunks],
1275        };
1276
1277        let fixed_hiding = |n_chunks: usize| PolyComm {
1278            chunks: vec![G::ScalarField::one(); n_chunks],
1279        };
1280
1281        let coefficients_form = DensePolynomialOrEvaluations::DensePolynomial;
1282        let evaluations_form = |e| DensePolynomialOrEvaluations::Evaluations(e, index.cs.domain.d1);
1283
1284        let mut polynomials = polys
1285            .iter()
1286            .map(|(p, n_chunks)| (coefficients_form(p), non_hiding(*n_chunks)))
1287            .collect::<Vec<_>>();
1288
1289        //~ 1. Then, include:
1290        //~~ * the negated public polynomial
1291        //~~ * the ft polynomial
1292        //~~ * the permutation aggregation polynomial z polynomial
1293        //~~ * the generic selector
1294        //~~ * the poseidon selector
1295        //~~ * the 15 registers/witness columns
1296        //~~ * the 6 sigmas
1297        polynomials.push((coefficients_form(&public_poly), fixed_hiding(num_chunks)));
1298        polynomials.push((coefficients_form(&ft), blinding_ft));
1299        polynomials.push((coefficients_form(&z_poly), z_comm.blinders));
1300        polynomials.push((
1301            evaluations_form(&column_evaluations.generic_selector4),
1302            fixed_hiding(num_chunks),
1303        ));
1304        polynomials.push((
1305            evaluations_form(&column_evaluations.poseidon_selector8),
1306            fixed_hiding(num_chunks),
1307        ));
1308        polynomials.push((
1309            evaluations_form(&column_evaluations.complete_add_selector4),
1310            fixed_hiding(num_chunks),
1311        ));
1312        polynomials.push((
1313            evaluations_form(&column_evaluations.mul_selector8),
1314            fixed_hiding(num_chunks),
1315        ));
1316        polynomials.push((
1317            evaluations_form(&column_evaluations.emul_selector8),
1318            fixed_hiding(num_chunks),
1319        ));
1320        polynomials.push((
1321            evaluations_form(&column_evaluations.endomul_scalar_selector8),
1322            fixed_hiding(num_chunks),
1323        ));
1324        polynomials.extend(
1325            witness_poly
1326                .iter()
1327                .zip(w_comm.iter())
1328                .map(|(w, c)| (coefficients_form(w), c.blinders.clone()))
1329                .collect::<Vec<_>>(),
1330        );
1331        polynomials.extend(
1332            column_evaluations
1333                .coefficients8
1334                .iter()
1335                .map(|coefficientm| (evaluations_form(coefficientm), non_hiding(num_chunks)))
1336                .collect::<Vec<_>>(),
1337        );
1338        polynomials.extend(
1339            column_evaluations.permutation_coefficients8[0..PERMUTS - 1]
1340                .iter()
1341                .map(|w| (evaluations_form(w), non_hiding(num_chunks)))
1342                .collect::<Vec<_>>(),
1343        );
1344
1345        //~~ * the optional gates
1346        if let Some(range_check0_selector8) = &column_evaluations.range_check0_selector8 {
1347            polynomials.push((
1348                evaluations_form(range_check0_selector8),
1349                non_hiding(num_chunks),
1350            ));
1351        }
1352        if let Some(range_check1_selector8) = &column_evaluations.range_check1_selector8 {
1353            polynomials.push((
1354                evaluations_form(range_check1_selector8),
1355                non_hiding(num_chunks),
1356            ));
1357        }
1358        if let Some(foreign_field_add_selector8) = &column_evaluations.foreign_field_add_selector8 {
1359            polynomials.push((
1360                evaluations_form(foreign_field_add_selector8),
1361                non_hiding(num_chunks),
1362            ));
1363        }
1364        if let Some(foreign_field_mul_selector8) = &column_evaluations.foreign_field_mul_selector8 {
1365            polynomials.push((
1366                evaluations_form(foreign_field_mul_selector8),
1367                non_hiding(num_chunks),
1368            ));
1369        }
1370        if let Some(xor_selector8) = &column_evaluations.xor_selector8 {
1371            polynomials.push((evaluations_form(xor_selector8), non_hiding(num_chunks)));
1372        }
1373        if let Some(rot_selector8) = &column_evaluations.rot_selector8 {
1374            polynomials.push((evaluations_form(rot_selector8), non_hiding(num_chunks)));
1375        }
1376
1377        //~~ * optionally, the runtime table
1378        //~ 1. if using lookup:
1379        if let Some(lcs) = lookup_constraint_system {
1380            //~~ * add the lookup sorted polynomials
1381            let sorted_poly = lookup_context.sorted_coeffs.as_ref().unwrap();
1382            let sorted_comms = lookup_context.sorted_comms.as_ref().unwrap();
1383
1384            for (poly, comm) in sorted_poly.iter().zip(sorted_comms) {
1385                polynomials.push((coefficients_form(poly), comm.blinders.clone()));
1386            }
1387
1388            //~~ * add the lookup aggreg polynomial
1389            let aggreg_poly = lookup_context.aggreg_coeffs.as_ref().unwrap();
1390            let aggreg_comm = lookup_context.aggreg_comm.as_ref().unwrap();
1391            polynomials.push((coefficients_form(aggreg_poly), aggreg_comm.blinders.clone()));
1392
1393            //~~ * add the combined table polynomial
1394            let table_blinding = {
1395                let joint_combiner = lookup_context.joint_combiner.as_ref().unwrap();
1396                let table_id_combiner = lookup_context.table_id_combiner.as_ref().unwrap();
1397                let max_fixed_lookup_table_size = {
1398                    // CAUTION: This is not `lcs.configuration.lookup_info.max_joint_size` because
1399                    // the lookup table may be strictly narrower, and as such will not contribute
1400                    // the associated blinders.
1401                    // For example, using a runtime table with the lookup gate (width 2), but only
1402                    // width-1 fixed tables (e.g. range check), it would be incorrect to use the
1403                    // wider width (2) because there are no such contributing commitments!
1404                    // Note that lookup_table8 is a list of polynomials
1405                    lcs.lookup_table8.len()
1406                };
1407                let base_blinding = {
1408                    let fixed_table_blinding = if max_fixed_lookup_table_size == 0 {
1409                        G::ScalarField::zero()
1410                    } else {
1411                        (1..max_fixed_lookup_table_size).fold(G::ScalarField::one(), |acc, _| {
1412                            G::ScalarField::one() + *joint_combiner * acc
1413                        })
1414                    };
1415                    fixed_table_blinding + *table_id_combiner
1416                };
1417                if lcs.runtime_selector.is_some() {
1418                    let runtime_comm = lookup_context.runtime_table_comm.as_ref().unwrap();
1419
1420                    let chunks = runtime_comm
1421                        .blinders
1422                        .into_iter()
1423                        .map(|blinding| *joint_combiner * *blinding + base_blinding)
1424                        .collect();
1425
1426                    PolyComm::new(chunks)
1427                } else {
1428                    let chunks = vec![base_blinding; num_chunks];
1429                    PolyComm::new(chunks)
1430                }
1431            };
1432
1433            let joint_lookup_table = lookup_context.joint_lookup_table.as_ref().unwrap();
1434
1435            polynomials.push((coefficients_form(joint_lookup_table), table_blinding));
1436
1437            //~~ * if present, add the runtime table polynomial
1438            if lcs.runtime_selector.is_some() {
1439                let runtime_table_comm = lookup_context.runtime_table_comm.as_ref().unwrap();
1440                let runtime_table = lookup_context.runtime_table.as_ref().unwrap();
1441
1442                polynomials.push((
1443                    coefficients_form(runtime_table),
1444                    runtime_table_comm.blinders.clone(),
1445                ));
1446            }
1447
1448            //~~ * the lookup selectors
1449
1450            if let Some(runtime_lookup_table_selector) = &lcs.runtime_selector {
1451                polynomials.push((
1452                    evaluations_form(runtime_lookup_table_selector),
1453                    non_hiding(num_chunks),
1454                ))
1455            }
1456            if let Some(xor_lookup_selector) = &lcs.lookup_selectors.xor {
1457                polynomials.push((
1458                    evaluations_form(xor_lookup_selector),
1459                    non_hiding(num_chunks),
1460                ))
1461            }
1462            if let Some(lookup_gate_selector) = &lcs.lookup_selectors.lookup {
1463                polynomials.push((
1464                    evaluations_form(lookup_gate_selector),
1465                    non_hiding(num_chunks),
1466                ))
1467            }
1468            if let Some(range_check_lookup_selector) = &lcs.lookup_selectors.range_check {
1469                polynomials.push((
1470                    evaluations_form(range_check_lookup_selector),
1471                    non_hiding(num_chunks),
1472                ))
1473            }
1474            if let Some(foreign_field_mul_lookup_selector) = &lcs.lookup_selectors.ffmul {
1475                polynomials.push((
1476                    evaluations_form(foreign_field_mul_lookup_selector),
1477                    non_hiding(num_chunks),
1478                ))
1479            }
1480        }
1481
1482        //~ 1. Create an aggregated evaluation proof for all of these polynomials at $\zeta$ and $\zeta\omega$ using $u$ and $v$.
1483        internal_tracing::checkpoint!(internal_traces; create_aggregated_ipa);
1484        let proof = OpenProof::open(
1485            &*index.srs,
1486            group_map,
1487            &polynomials,
1488            &[zeta, zeta_omega],
1489            v,
1490            u,
1491            fq_sponge_before_evaluations,
1492            rng,
1493        );
1494
1495        let lookup = lookup_context
1496            .aggreg_comm
1497            .zip(lookup_context.sorted_comms)
1498            .map(|(a, s)| LookupCommitments {
1499                aggreg: a.commitment,
1500                sorted: s.iter().map(|c| c.commitment.clone()).collect(),
1501                runtime: lookup_context.runtime_table_comm.map(|x| x.commitment),
1502            });
1503
1504        let proof = Self {
1505            commitments: ProverCommitments {
1506                w_comm: array::from_fn(|i| w_comm[i].commitment.clone()),
1507                z_comm: z_comm.commitment,
1508                t_comm: t_comm.commitment,
1509                lookup,
1510            },
1511            proof,
1512            evals: chunked_evals,
1513            ft_eval1,
1514            prev_challenges,
1515        };
1516
1517        internal_tracing::checkpoint!(internal_traces; create_recursive_done);
1518
1519        Ok(proof)
1520    }
1521}
1522
1523internal_tracing::decl_traces!(internal_traces;
1524    pasta_fp_plonk_proof_create,
1525    pasta_fq_plonk_proof_create,
1526    create_recursive,
1527    pad_witness,
1528    set_up_fq_sponge,
1529    commit_to_witness_columns,
1530    use_lookup,
1531    z_permutation_aggregation_polynomial,
1532    eval_witness_polynomials_over_domains,
1533    compute_index_evals,
1534    compute_quotient_poly,
1535    lagrange_basis_eval_zeta_poly,
1536    lagrange_basis_eval_zeta_omega_poly,
1537    chunk_eval_zeta_omega_poly,
1538    compute_ft_poly,
1539    ft_eval_zeta_omega,
1540    build_polynomials,
1541    create_aggregated_ipa,
1542    create_recursive_done);
1543
1544#[cfg(feature = "ocaml_types")]
1545pub mod caml {
1546    use super::*;
1547    use crate::proof::caml::{CamlProofEvaluations, CamlRecursionChallenge};
1548    use ark_ec::AffineRepr;
1549    use poly_commitment::{
1550        commitment::caml::CamlPolyComm,
1551        ipa::{caml::CamlOpeningProof, OpeningProof},
1552    };
1553
1554    #[cfg(feature = "internal_tracing")]
1555    pub use internal_traces::caml::CamlTraces as CamlProverTraces;
1556
1557    #[derive(ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)]
1558    pub struct CamlProofWithPublic<CamlG, CamlF> {
1559        pub public_evals: Option<PointEvaluations<Vec<CamlF>>>,
1560        pub proof: CamlProverProof<CamlG, CamlF>,
1561    }
1562
1563    //
1564    // CamlProverProof<CamlG, CamlF>
1565    //
1566
1567    #[derive(ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)]
1568    pub struct CamlProverProof<CamlG, CamlF> {
1569        pub commitments: CamlProverCommitments<CamlG>,
1570        pub proof: CamlOpeningProof<CamlG, CamlF>,
1571        // OCaml doesn't have sized arrays, so we have to convert to a tuple..
1572        pub evals: CamlProofEvaluations<CamlF>,
1573        pub ft_eval1: CamlF,
1574        pub public: Vec<CamlF>,
1575        //Vec<(Vec<CamlF>, CamlPolyComm<CamlG>)>,
1576        pub prev_challenges: Vec<CamlRecursionChallenge<CamlG, CamlF>>,
1577    }
1578
1579    //
1580    // CamlProverCommitments<CamlG>
1581    //
1582
1583    #[derive(Clone, ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)]
1584    pub struct CamlLookupCommitments<CamlG> {
1585        pub sorted: Vec<CamlPolyComm<CamlG>>,
1586        pub aggreg: CamlPolyComm<CamlG>,
1587        pub runtime: Option<CamlPolyComm<CamlG>>,
1588    }
1589
1590    #[allow(clippy::type_complexity)]
1591    #[derive(Clone, ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)]
1592    pub struct CamlProverCommitments<CamlG> {
1593        // polynomial commitments
1594        pub w_comm: (
1595            CamlPolyComm<CamlG>,
1596            CamlPolyComm<CamlG>,
1597            CamlPolyComm<CamlG>,
1598            CamlPolyComm<CamlG>,
1599            CamlPolyComm<CamlG>,
1600            CamlPolyComm<CamlG>,
1601            CamlPolyComm<CamlG>,
1602            CamlPolyComm<CamlG>,
1603            CamlPolyComm<CamlG>,
1604            CamlPolyComm<CamlG>,
1605            CamlPolyComm<CamlG>,
1606            CamlPolyComm<CamlG>,
1607            CamlPolyComm<CamlG>,
1608            CamlPolyComm<CamlG>,
1609            CamlPolyComm<CamlG>,
1610        ),
1611        pub z_comm: CamlPolyComm<CamlG>,
1612        pub t_comm: CamlPolyComm<CamlG>,
1613        pub lookup: Option<CamlLookupCommitments<CamlG>>,
1614    }
1615
1616    // These implementations are handy for conversions such as:
1617    // InternalType <-> Ocaml::Value
1618    //
1619    // It does this by hiding the required middle conversion step:
1620    // InternalType <-> CamlInternalType <-> Ocaml::Value
1621    //
1622    // Note that some conversions are not always possible to shorten,
1623    // because we don't always know how to convert the types.
1624    // For example, to implement the conversion
1625    // ProverCommitments<G> -> CamlProverCommitments<CamlG>
1626    // we need to know how to convert G to CamlG.
1627    // we don't know that information, unless we implemented some trait (e.g. ToCaml)
1628    // we can do that, but instead we implemented the From trait for the reverse
1629    // operations (From<G> for CamlG).
1630    // it reduces the complexity, but forces us to do the conversion in two
1631    // phases instead of one.
1632
1633    //
1634    // CamlLookupCommitments<CamlG> <-> LookupCommitments<G>
1635    //
1636
1637    impl<G, CamlG> From<LookupCommitments<G>> for CamlLookupCommitments<CamlG>
1638    where
1639        G: AffineRepr,
1640        CamlPolyComm<CamlG>: From<PolyComm<G>>,
1641    {
1642        fn from(
1643            LookupCommitments {
1644                aggreg,
1645                sorted,
1646                runtime,
1647            }: LookupCommitments<G>,
1648        ) -> Self {
1649            Self {
1650                aggreg: aggreg.into(),
1651                sorted: sorted.into_iter().map(Into::into).collect(),
1652                runtime: runtime.map(Into::into),
1653            }
1654        }
1655    }
1656
1657    impl<G, CamlG> From<CamlLookupCommitments<CamlG>> for LookupCommitments<G>
1658    where
1659        G: AffineRepr,
1660        PolyComm<G>: From<CamlPolyComm<CamlG>>,
1661    {
1662        fn from(
1663            CamlLookupCommitments {
1664                aggreg,
1665                sorted,
1666                runtime,
1667            }: CamlLookupCommitments<CamlG>,
1668        ) -> LookupCommitments<G> {
1669            LookupCommitments {
1670                aggreg: aggreg.into(),
1671                sorted: sorted.into_iter().map(Into::into).collect(),
1672                runtime: runtime.map(Into::into),
1673            }
1674        }
1675    }
1676
1677    //
1678    // CamlProverCommitments<CamlG> <-> ProverCommitments<G>
1679    //
1680
1681    impl<G, CamlG> From<ProverCommitments<G>> for CamlProverCommitments<CamlG>
1682    where
1683        G: AffineRepr,
1684        CamlPolyComm<CamlG>: From<PolyComm<G>>,
1685    {
1686        fn from(prover_comm: ProverCommitments<G>) -> Self {
1687            let [w_comm0, w_comm1, w_comm2, w_comm3, w_comm4, w_comm5, w_comm6, w_comm7, w_comm8, w_comm9, w_comm10, w_comm11, w_comm12, w_comm13, w_comm14] =
1688                prover_comm.w_comm;
1689            Self {
1690                w_comm: (
1691                    w_comm0.into(),
1692                    w_comm1.into(),
1693                    w_comm2.into(),
1694                    w_comm3.into(),
1695                    w_comm4.into(),
1696                    w_comm5.into(),
1697                    w_comm6.into(),
1698                    w_comm7.into(),
1699                    w_comm8.into(),
1700                    w_comm9.into(),
1701                    w_comm10.into(),
1702                    w_comm11.into(),
1703                    w_comm12.into(),
1704                    w_comm13.into(),
1705                    w_comm14.into(),
1706                ),
1707                z_comm: prover_comm.z_comm.into(),
1708                t_comm: prover_comm.t_comm.into(),
1709                lookup: prover_comm.lookup.map(Into::into),
1710            }
1711        }
1712    }
1713
1714    impl<G, CamlG> From<CamlProverCommitments<CamlG>> for ProverCommitments<G>
1715    where
1716        G: AffineRepr,
1717        PolyComm<G>: From<CamlPolyComm<CamlG>>,
1718    {
1719        fn from(caml_prover_comm: CamlProverCommitments<CamlG>) -> ProverCommitments<G> {
1720            let (
1721                w_comm0,
1722                w_comm1,
1723                w_comm2,
1724                w_comm3,
1725                w_comm4,
1726                w_comm5,
1727                w_comm6,
1728                w_comm7,
1729                w_comm8,
1730                w_comm9,
1731                w_comm10,
1732                w_comm11,
1733                w_comm12,
1734                w_comm13,
1735                w_comm14,
1736            ) = caml_prover_comm.w_comm;
1737            ProverCommitments {
1738                w_comm: [
1739                    w_comm0.into(),
1740                    w_comm1.into(),
1741                    w_comm2.into(),
1742                    w_comm3.into(),
1743                    w_comm4.into(),
1744                    w_comm5.into(),
1745                    w_comm6.into(),
1746                    w_comm7.into(),
1747                    w_comm8.into(),
1748                    w_comm9.into(),
1749                    w_comm10.into(),
1750                    w_comm11.into(),
1751                    w_comm12.into(),
1752                    w_comm13.into(),
1753                    w_comm14.into(),
1754                ],
1755                z_comm: caml_prover_comm.z_comm.into(),
1756                t_comm: caml_prover_comm.t_comm.into(),
1757                lookup: caml_prover_comm.lookup.map(Into::into),
1758            }
1759        }
1760    }
1761
1762    //
1763    // ProverProof<G> <-> CamlProofWithPublic<CamlG, CamlF>
1764    //
1765
1766    // this is just used to simplify the type signatures below
1767    // the bound does not need to be enforced
1768    #[allow(type_alias_bounds)]
1769    type ProofTuple<const FULL_ROUNDS: usize, G: AffineRepr> = (
1770        ProverProof<G, OpeningProof<G, FULL_ROUNDS>, FULL_ROUNDS>,
1771        Vec<<G as AffineRepr>::ScalarField>,
1772    );
1773
1774    impl<const FULL_ROUNDS: usize, G, CamlG, CamlF> From<ProofTuple<FULL_ROUNDS, G>>
1775        for CamlProofWithPublic<CamlG, CamlF>
1776    where
1777        G: AffineRepr,
1778        G::BaseField: PrimeField,
1779        G: poly_commitment::commitment::EndoCurve,
1780        CamlG: From<G>,
1781        CamlF: From<G::ScalarField>,
1782    {
1783        fn from(pp: ProofTuple<FULL_ROUNDS, G>) -> Self {
1784            let (public_evals, evals) = pp.0.evals.into();
1785            CamlProofWithPublic {
1786                public_evals,
1787                proof: CamlProverProof {
1788                    commitments: pp.0.commitments.into(),
1789                    proof: pp.0.proof.into(),
1790                    evals,
1791                    ft_eval1: pp.0.ft_eval1.into(),
1792                    public: pp.1.into_iter().map(Into::into).collect(),
1793                    prev_challenges: pp.0.prev_challenges.into_iter().map(Into::into).collect(),
1794                },
1795            }
1796        }
1797    }
1798
1799    impl<const FULL_ROUNDS: usize, G, CamlG, CamlF> From<CamlProofWithPublic<CamlG, CamlF>>
1800        for ProofTuple<FULL_ROUNDS, G>
1801    where
1802        CamlF: Clone,
1803        G: AffineRepr + From<CamlG>,
1804        G::BaseField: PrimeField,
1805        G: poly_commitment::commitment::EndoCurve,
1806        G::ScalarField: From<CamlF>,
1807    {
1808        fn from(caml_pp: CamlProofWithPublic<CamlG, CamlF>) -> Self {
1809            let CamlProofWithPublic {
1810                public_evals,
1811                proof: caml_pp,
1812            } = caml_pp;
1813            let proof = ProverProof {
1814                commitments: caml_pp.commitments.into(),
1815                proof: caml_pp.proof.into(),
1816                evals: (public_evals, caml_pp.evals).into(),
1817                ft_eval1: caml_pp.ft_eval1.into(),
1818                prev_challenges: caml_pp
1819                    .prev_challenges
1820                    .into_iter()
1821                    .map(Into::into)
1822                    .collect(),
1823            };
1824
1825            (proof, caml_pp.public.into_iter().map(Into::into).collect())
1826        }
1827    }
1828}
1829
1830#[cfg(test)]
1831mod tests {
1832    use super::*;
1833    use mina_curves::pasta::Fp;
1834
1835    /// Each chunk gets its own blinder. A commitment is masked chunk by chunk,
1836    /// so repeating one blinder across the chunks would leave their differences
1837    /// unmasked.
1838    #[test]
1839    fn blinders_are_drawn_per_chunk() {
1840        let mut rng = o1_utils::tests::make_test_rng(None);
1841        let blinder = blinder::<Fp, _>(4, &mut rng);
1842
1843        assert_eq!(blinder.chunks.len(), 4);
1844        for (i, a) in blinder.chunks.iter().enumerate() {
1845            for b in blinder.chunks.iter().skip(i + 1) {
1846                assert_ne!(a, b, "two chunks share a blinder");
1847            }
1848        }
1849    }
1850}