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