o1vm/pickles/
prover.rs

1use std::array;
2
3use ark_ff::{One, PrimeField, Zero};
4use ark_poly::{univariate::DensePolynomial, Evaluations, Polynomial, Radix2EvaluationDomain as D};
5use kimchi::{
6    circuits::{
7        berkeley_columns::BerkeleyChallenges,
8        domains::EvaluationDomains,
9        expr::{l0_1, Constants},
10    },
11    curve::KimchiCurve,
12    groupmap::GroupMap,
13    plonk_sponge::FrSponge,
14    proof::PointEvaluations,
15};
16use log::debug;
17use mina_poseidon::{sponge::ScalarChallenge, FqSponge};
18use o1_utils::ExtendedDensePolynomial;
19use poly_commitment::{
20    commitment::{absorb_commitment, PolyComm},
21    ipa::{OpeningProof, SRS},
22    utils::DensePolynomialOrEvaluations,
23    OpenProof as _, SRS as _,
24};
25use rand::{CryptoRng, RngCore};
26use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
27
28use super::{
29    column_env::ColumnEnvironment,
30    proof::{Proof, ProofInputs, WitnessColumns},
31    DEGREE_QUOTIENT_POLYNOMIAL,
32};
33use crate::{interpreters::mips::column::N_MIPS_SEL_COLS, E};
34use thiserror::Error;
35
36/// Errors that can arise when creating a proof
37#[derive(Error, Debug, Clone)]
38pub enum ProverError {
39    #[error("the provided constraint has degree {0} > allowed {1}; expr: {2}")]
40    ConstraintDegreeTooHigh(u64, u64, String),
41}
42
43/// Make a PlonKish proof for the given circuit. As inputs, we get the execution
44/// trace consisting of evaluations of polynomials over a certain domain
45/// `domain`.
46///
47/// The proof is made of the following steps:
48/// 1. For each column, we create a commitment and absorb it in the sponge.
49/// 2. We compute the quotient polynomial.
50/// 3. We evaluate each polynomial (columns + quotient) to two challenges ζ and ζω.
51/// 4. We make a batch opening proof using the IPA PCS.
52///
53/// The final proof consists of the opening proof, the commitments and the
54/// evaluations at ζ and ζω.
55pub fn prove<
56    G: KimchiCurve,
57    EFqSponge: FqSponge<G::BaseField, G, G::ScalarField> + Clone,
58    EFrSponge: FrSponge<G::ScalarField>,
59    RNG,
60>(
61    domain: EvaluationDomains<G::ScalarField>,
62    srs: &SRS<G>,
63    inputs: ProofInputs<G>,
64    constraints: &[E<G::ScalarField>],
65    rng: &mut RNG,
66) -> Result<Proof<G>, ProverError>
67where
68    G::BaseField: PrimeField,
69    RNG: RngCore + CryptoRng,
70{
71    let num_chunks = 1;
72    let omega = domain.d1.group_gen;
73
74    let mut fq_sponge = EFqSponge::new(G::other_curve_sponge_params());
75
76    ////////////////////////////////////////////////////////////////////////////
77    // Round 1: Creating and absorbing column commitments
78    ////////////////////////////////////////////////////////////////////////////
79
80    debug!("Prover: interpolating all columns, including the selectors");
81    let ProofInputs { evaluations } = inputs;
82    let polys: WitnessColumns<
83        DensePolynomial<G::ScalarField>,
84        [DensePolynomial<G::ScalarField>; N_MIPS_SEL_COLS],
85    > = {
86        let WitnessColumns {
87            scratch,
88            scratch_inverse,
89            lookup_state,
90            instruction_counter,
91            error,
92            selector,
93        } = evaluations;
94
95        let domain_size = domain.d1.size as usize;
96
97        // Build the selectors
98        let selector: [Vec<G::ScalarField>; N_MIPS_SEL_COLS] = array::from_fn(|i| {
99            let mut s_i = Vec::with_capacity(domain_size);
100            for s in &selector {
101                s_i.push(if G::ScalarField::from(i as u64) == *s {
102                    G::ScalarField::one()
103                } else {
104                    G::ScalarField::zero()
105                })
106            }
107            s_i
108        });
109
110        let eval_col = |evals: Vec<G::ScalarField>| {
111            Evaluations::<G::ScalarField, D<G::ScalarField>>::from_vec_and_domain(evals, domain.d1)
112                .interpolate()
113        };
114        // Doing in parallel
115        let scratch = scratch.into_par_iter().map(eval_col).collect::<Vec<_>>();
116        let scratch_inverse = scratch_inverse
117            .into_par_iter()
118            .map(|mut evals| {
119                ark_ff::batch_inversion(&mut evals);
120                eval_col(evals)
121            })
122            .collect::<Vec<_>>();
123        let lookup_state = lookup_state
124            .into_par_iter()
125            .map(eval_col)
126            .collect::<Vec<_>>();
127        let selector = selector.into_par_iter().map(eval_col).collect::<Vec<_>>();
128        WitnessColumns {
129            scratch: scratch.try_into().unwrap(),
130            scratch_inverse: scratch_inverse.try_into().unwrap(),
131            lookup_state,
132            instruction_counter: eval_col(instruction_counter),
133            error: eval_col(error.clone()),
134            selector: selector.try_into().unwrap(),
135        }
136    };
137
138    debug!("Prover: committing to all columns, including the selectors");
139    let commitments: WitnessColumns<PolyComm<G>, [PolyComm<G>; N_MIPS_SEL_COLS]> = {
140        let WitnessColumns {
141            scratch,
142            scratch_inverse,
143            lookup_state,
144            instruction_counter,
145            error,
146            selector,
147        } = &polys;
148
149        let comm = |poly: &DensePolynomial<G::ScalarField>| {
150            srs.commit_custom(
151                poly,
152                num_chunks,
153                &PolyComm::new(vec![G::ScalarField::one()]),
154            )
155            .unwrap()
156            .commitment
157        };
158        // Doing in parallel
159        let scratch = scratch.par_iter().map(comm).collect::<Vec<_>>();
160        let scratch_inverse = scratch_inverse.par_iter().map(comm).collect::<Vec<_>>();
161        let lookup_state = lookup_state.par_iter().map(comm).collect::<Vec<_>>();
162        let selector = selector.par_iter().map(comm).collect::<Vec<_>>();
163        WitnessColumns {
164            scratch: scratch.try_into().unwrap(),
165            scratch_inverse: scratch_inverse.try_into().unwrap(),
166            lookup_state,
167            instruction_counter: comm(instruction_counter),
168            error: comm(error),
169            selector: selector.try_into().unwrap(),
170        }
171    };
172
173    debug!("Prover: evaluating all columns, including the selectors, on d8");
174    // We evaluate on a domain higher than d1 for the quotient polynomial.
175    // Based on the regression test
176    // `test_regression_constraints_with_selectors`, the highest degree is 6.
177    // Therefore, we do evaluate on d8.
178    let evaluations_d8 = {
179        let WitnessColumns {
180            scratch,
181            scratch_inverse,
182            lookup_state,
183            instruction_counter,
184            error,
185            selector,
186        } = &polys;
187        let eval_d8 =
188            |poly: &DensePolynomial<G::ScalarField>| poly.evaluate_over_domain_by_ref(domain.d8);
189        // Doing in parallel
190        let scratch = scratch.into_par_iter().map(eval_d8).collect::<Vec<_>>();
191        let scratch_inverse = scratch_inverse
192            .into_par_iter()
193            .map(eval_d8)
194            .collect::<Vec<_>>();
195        let lookup_state = lookup_state
196            .into_par_iter()
197            .map(eval_d8)
198            .collect::<Vec<_>>();
199        let selector = selector.into_par_iter().map(eval_d8).collect::<Vec<_>>();
200        WitnessColumns {
201            scratch: scratch.try_into().unwrap(),
202            scratch_inverse: scratch_inverse.try_into().unwrap(),
203            lookup_state,
204            instruction_counter: eval_d8(instruction_counter),
205            error: eval_d8(error),
206            selector: selector.try_into().unwrap(),
207        }
208    };
209
210    // Absorbing the commitments - Fiat Shamir
211    // We do not parallelize as we need something deterministic.
212    for comm in commitments.scratch.iter() {
213        absorb_commitment(&mut fq_sponge, comm)
214    }
215    for comm in commitments.scratch_inverse.iter() {
216        absorb_commitment(&mut fq_sponge, comm)
217    }
218    for comm in commitments.lookup_state.iter() {
219        absorb_commitment(&mut fq_sponge, comm)
220    }
221    absorb_commitment(&mut fq_sponge, &commitments.instruction_counter);
222    absorb_commitment(&mut fq_sponge, &commitments.error);
223    for comm in commitments.selector.iter() {
224        absorb_commitment(&mut fq_sponge, comm)
225    }
226
227    ////////////////////////////////////////////////////////////////////////////
228    // Round 2: Creating and committing to the quotient polynomial
229    ////////////////////////////////////////////////////////////////////////////
230
231    let (_, endo_r) = G::endos();
232
233    // Constraints combiner
234    let alpha: G::ScalarField = fq_sponge.challenge();
235
236    let zk_rows = 0;
237    let column_env: ColumnEnvironment<'_, G::ScalarField> = {
238        // FIXME: use a proper Challenge structure
239        let challenges = BerkeleyChallenges {
240            alpha,
241            // No permutation argument for the moment
242            beta: G::ScalarField::zero(),
243            gamma: G::ScalarField::zero(),
244            // No lookup for the moment
245            joint_combiner: G::ScalarField::zero(),
246        };
247        ColumnEnvironment {
248            constants: Constants {
249                endo_coefficient: *endo_r,
250                mds: &G::sponge_params().mds,
251                zk_rows,
252            },
253            challenges,
254            witness: &evaluations_d8,
255            l0_1: l0_1(domain.d1),
256            domain,
257        }
258    };
259
260    debug!("Prover: computing the quotient polynomial");
261    // Hint:
262    // To debug individual constraint, you can revert the following commits that implement the
263    // check for individual constraints.
264    // ```
265    // git revert 8e87244a98d55b90d175ad389611a3c98bd16b34
266    // git revert 96d42c127ef025869c91e5fed680e0e383108706
267    // ```
268    let quotient_poly: DensePolynomial<G::ScalarField> = {
269        // Compute ∑ α^i constraint_i as an expression
270        let combined_expr =
271            E::combine_constraints(0..(constraints.len() as u32), (constraints).to_vec());
272
273        // We want to compute the quotient polynomial, i.e.
274        // t(X) = (∑ α^i constraint_i(X)) / Z_H(X).
275        // The sum of the expressions is called the "constraint polynomial".
276        // We will use the evaluations points of the individual witness
277        // columns.
278        // Note that as the constraints might be of higher degree than N, the
279        // size of the set H we want the constraints to be verified on, we must
280        // have more than N evaluations points for each columns. This is handled
281        // in the ColumnEnvironment structure.
282        // Reminder: to compute P(X) = P_{1}(X) * P_{2}(X), from the evaluations
283        // of P_{1} and P_{2}, with deg(P_{1}) = deg(P_{2}(X)) = N, we must have
284        // 2N evaluation points to compute P as deg(P(X)) <= 2N.
285        let expr_evaluation: Evaluations<G::ScalarField, D<G::ScalarField>> =
286            combined_expr.evaluations(&column_env);
287
288        // And we interpolate using the evaluations
289        let expr_evaluation_interpolated = expr_evaluation.interpolate();
290
291        let fail_remainder_not_zero =
292            || panic!("The constraints are not satisfied since the remainder is not zero");
293        // We compute the polynomial t(X) by dividing the constraints polynomial
294        // by the vanishing polynomial, i.e. Z_H(X).
295        let (quotient, rem) = expr_evaluation_interpolated.divide_by_vanishing_poly(domain.d1);
296        // As the constraints must be verified on H, the rest of the division
297        // must be equal to 0 as the constraints polynomial and Z_H(X) are both
298        // equal on H.
299        if !rem.is_zero() {
300            fail_remainder_not_zero();
301        }
302
303        quotient
304    };
305
306    let quotient_commitment = srs
307        .commit_custom(
308            &quotient_poly,
309            DEGREE_QUOTIENT_POLYNOMIAL as usize,
310            &PolyComm::new(vec![
311                G::ScalarField::one();
312                DEGREE_QUOTIENT_POLYNOMIAL as usize
313            ]),
314        )
315        .unwrap();
316    absorb_commitment(&mut fq_sponge, &quotient_commitment.commitment);
317
318    ////////////////////////////////////////////////////////////////////////////
319    // Round 3: Evaluations at ζ and ζω
320    ////////////////////////////////////////////////////////////////////////////
321
322    debug!("Prover: evaluating all columns, including the selectors, at ζ and ζω");
323    let zeta_chal = ScalarChallenge(fq_sponge.challenge());
324
325    let zeta = zeta_chal.to_field(endo_r);
326    let zeta_omega = zeta * omega;
327
328    let evals = |point| {
329        let WitnessColumns {
330            scratch,
331            scratch_inverse,
332            lookup_state,
333            instruction_counter,
334            error,
335            selector,
336        } = &polys;
337        let eval = |poly: &DensePolynomial<G::ScalarField>| poly.evaluate(point);
338        let scratch = scratch.par_iter().map(eval).collect::<Vec<_>>();
339        let scratch_inverse = scratch_inverse.par_iter().map(eval).collect::<Vec<_>>();
340        let lookup_state = lookup_state.par_iter().map(eval).collect::<Vec<_>>();
341        let selector = selector.par_iter().map(eval).collect::<Vec<_>>();
342        WitnessColumns {
343            scratch: scratch.try_into().unwrap(),
344            scratch_inverse: scratch_inverse.try_into().unwrap(),
345            lookup_state,
346            instruction_counter: eval(instruction_counter),
347            error: eval(error),
348            selector: selector.try_into().unwrap(),
349        }
350    };
351    // All evaluations at ζ
352    let zeta_evaluations: WitnessColumns<G::ScalarField, [G::ScalarField; N_MIPS_SEL_COLS]> =
353        evals(&zeta);
354
355    // All evaluations at ζω
356    let zeta_omega_evaluations: WitnessColumns<G::ScalarField, [G::ScalarField; N_MIPS_SEL_COLS]> =
357        evals(&zeta_omega);
358
359    let chunked_quotient = quotient_poly
360        .to_chunked_polynomial(DEGREE_QUOTIENT_POLYNOMIAL as usize, domain.d1.size as usize);
361    let quotient_evaluations = PointEvaluations {
362        zeta: chunked_quotient
363            .polys
364            .iter()
365            .map(|p| p.evaluate(&zeta))
366            .collect::<Vec<_>>(),
367        zeta_omega: chunked_quotient
368            .polys
369            .iter()
370            .map(|p| p.evaluate(&zeta_omega))
371            .collect(),
372    };
373
374    // Absorbing evaluations with a sponge for the other field
375    // We initialize the state with the previous state of the fq_sponge
376    let fq_sponge_before_evaluations = fq_sponge.clone();
377    let mut fr_sponge = EFrSponge::new(G::sponge_params());
378    fr_sponge.absorb(&fq_sponge.digest());
379
380    for (zeta_eval, zeta_omega_eval) in zeta_evaluations
381        .scratch
382        .iter()
383        .zip(zeta_omega_evaluations.scratch.iter())
384    {
385        fr_sponge.absorb(zeta_eval);
386        fr_sponge.absorb(zeta_omega_eval);
387    }
388    for (zeta_eval, zeta_omega_eval) in zeta_evaluations
389        .scratch_inverse
390        .iter()
391        .zip(zeta_omega_evaluations.scratch_inverse.iter())
392    {
393        fr_sponge.absorb(zeta_eval);
394        fr_sponge.absorb(zeta_omega_eval);
395    }
396
397    for (zeta_eval, zeta_omega_eval) in zeta_evaluations
398        .lookup_state
399        .iter()
400        .zip(zeta_omega_evaluations.lookup_state.iter())
401    {
402        fr_sponge.absorb(zeta_eval);
403        fr_sponge.absorb(zeta_omega_eval);
404    }
405    fr_sponge.absorb(&zeta_evaluations.instruction_counter);
406    fr_sponge.absorb(&zeta_omega_evaluations.instruction_counter);
407    fr_sponge.absorb(&zeta_evaluations.error);
408    fr_sponge.absorb(&zeta_omega_evaluations.error);
409    for (zeta_eval, zeta_omega_eval) in zeta_evaluations
410        .selector
411        .iter()
412        .zip(zeta_omega_evaluations.selector.iter())
413    {
414        fr_sponge.absorb(zeta_eval);
415        fr_sponge.absorb(zeta_omega_eval);
416    }
417    for (quotient_zeta_eval, quotient_zeta_omega_eval) in quotient_evaluations
418        .zeta
419        .iter()
420        .zip(quotient_evaluations.zeta_omega.iter())
421    {
422        fr_sponge.absorb(quotient_zeta_eval);
423        fr_sponge.absorb(quotient_zeta_omega_eval);
424    }
425    ////////////////////////////////////////////////////////////////////////////
426    // Round 4: Opening proof w/o linearization polynomial
427    ////////////////////////////////////////////////////////////////////////////
428
429    let mut polynomials: Vec<_> = polys.scratch.into_iter().collect();
430    polynomials.extend(polys.scratch_inverse);
431    polynomials.extend(polys.lookup_state);
432    polynomials.push(polys.instruction_counter);
433    polynomials.push(polys.error);
434    polynomials.extend(polys.selector);
435
436    // Preparing the polynomials for the opening proof
437    let mut polynomials: Vec<_> = polynomials
438        .iter()
439        .map(|poly| {
440            (
441                DensePolynomialOrEvaluations::DensePolynomial(poly),
442                // We do not have any blinder, therefore we set to 1.
443                PolyComm::new(vec![G::ScalarField::one()]),
444            )
445        })
446        .collect();
447    // we handle the quotient separately because the number of blinders =
448    // number of chunks, which is different for just the quotient polynomial.
449    polynomials.push((
450        DensePolynomialOrEvaluations::DensePolynomial(&quotient_poly),
451        quotient_commitment.blinders,
452    ));
453
454    // poly scale
455    let v_chal = fr_sponge.challenge();
456    let v = v_chal.to_field(endo_r);
457    // eval scale
458    let u_chal = fr_sponge.challenge();
459    let u = u_chal.to_field(endo_r);
460
461    let group_map = G::Map::setup();
462
463    debug!("Prover: computing the (batched) opening proof using the IPA PCS");
464    // Computing the opening proof for the IPA PCS
465    let opening_proof = OpeningProof::open::<_, _, D<G::ScalarField>>(
466        srs,
467        &group_map,
468        polynomials.as_slice(),
469        &[zeta, zeta_omega],
470        v,
471        u,
472        fq_sponge_before_evaluations,
473        rng,
474    );
475
476    Ok(Proof {
477        commitments,
478        zeta_evaluations,
479        zeta_omega_evaluations,
480        quotient_commitment: quotient_commitment.commitment,
481        quotient_evaluations,
482        opening_proof,
483    })
484}