kimchi_msm/
prover.rs

1#![allow(clippy::type_complexity)]
2#![allow(clippy::boxed_local)]
3
4use crate::{
5    column_env::ColumnEnvironment,
6    expr::E,
7    logup,
8    logup::{prover::Env, LookupProof, LookupTableID},
9    proof::{Proof, ProofCommitments, ProofEvaluations, ProofInputs},
10    witness::Witness,
11    MAX_SUPPORTED_DEGREE,
12};
13use ark_ff::{Field, One, Zero};
14use ark_poly::{
15    univariate::DensePolynomial, EvaluationDomain, Evaluations, Polynomial,
16    Radix2EvaluationDomain as R2D,
17};
18use kimchi::{
19    circuits::{
20        berkeley_columns::BerkeleyChallenges,
21        domains::EvaluationDomains,
22        expr::{l0_1, Constants, Expr},
23    },
24    curve::KimchiCurve,
25    groupmap::GroupMap,
26    plonk_sponge::FrSponge,
27    proof::PointEvaluations,
28};
29use mina_poseidon::{sponge::ScalarChallenge, FqSponge};
30use o1_utils::ExtendedDensePolynomial;
31use poly_commitment::{
32    commitment::{absorb_commitment, PolyComm},
33    utils::DensePolynomialOrEvaluations,
34    OpenProof, SRS,
35};
36use rand::{CryptoRng, RngCore};
37use rayon::iter::{IntoParallelIterator, ParallelIterator};
38use thiserror::Error;
39
40/// Errors that can arise when creating a proof
41#[derive(Error, Debug, Clone)]
42pub enum ProverError {
43    #[error("the proof could not be constructed: {0}")]
44    Generic(&'static str),
45
46    #[error("the provided (witness) constraints was not satisfied: {0}")]
47    ConstraintNotSatisfied(String),
48
49    #[error("the provided (witness) constraint has degree {0} > allowed {1}; expr: {2}")]
50    ConstraintDegreeTooHigh(u64, u64, String),
51}
52
53pub fn prove<
54    G: KimchiCurve,
55    OpeningProof: OpenProof<G>,
56    EFqSponge: Clone + FqSponge<G::BaseField, G, G::ScalarField>,
57    EFrSponge: FrSponge<G::ScalarField>,
58    RNG,
59    const N_WIT: usize,
60    const N_REL: usize,
61    const N_DSEL: usize,
62    const N_FSEL: usize,
63    ID: LookupTableID,
64>(
65    domain: EvaluationDomains<G::ScalarField>,
66    srs: &OpeningProof::SRS,
67    constraints: &[E<G::ScalarField>],
68    fixed_selectors: Box<[Vec<G::ScalarField>; N_FSEL]>,
69    inputs: ProofInputs<N_WIT, G::ScalarField, ID>,
70    rng: &mut RNG,
71) -> Result<Proof<N_WIT, N_REL, N_DSEL, N_FSEL, G, OpeningProof, ID>, ProverError>
72where
73    OpeningProof::SRS: Sync,
74    RNG: RngCore + CryptoRng,
75{
76    ////////////////////////////////////////////////////////////////////////////
77    // Setting up the protocol
78    ////////////////////////////////////////////////////////////////////////////
79
80    let group_map = G::Map::setup();
81
82    ////////////////////////////////////////////////////////////////////////////
83    // Round 1: Creating and absorbing column commitments
84    ////////////////////////////////////////////////////////////////////////////
85
86    let mut fq_sponge = EFqSponge::new(G::other_curve_sponge_params());
87
88    let fixed_selectors_evals_d1: Box<[Evaluations<G::ScalarField, R2D<G::ScalarField>>; N_FSEL]> =
89        o1_utils::array::vec_to_boxed_array(
90            fixed_selectors
91                .into_par_iter()
92                .map(|evals| Evaluations::from_vec_and_domain(evals, domain.d1))
93                .collect(),
94        );
95
96    let fixed_selectors_polys: Box<[DensePolynomial<G::ScalarField>; N_FSEL]> =
97        o1_utils::array::vec_to_boxed_array(
98            fixed_selectors_evals_d1
99                .into_par_iter()
100                .map(|evals| evals.interpolate())
101                .collect(),
102        );
103
104    let fixed_selectors_comms: Box<[PolyComm<G>; N_FSEL]> = {
105        let comm = |poly: &DensePolynomial<G::ScalarField>| srs.commit_non_hiding(poly, 1);
106        o1_utils::array::vec_to_boxed_array(
107            fixed_selectors_polys
108                .as_ref()
109                .into_par_iter()
110                .map(comm)
111                .collect(),
112        )
113    };
114
115    // Do not use parallelism
116    (fixed_selectors_comms)
117        .into_iter()
118        .for_each(|comm| absorb_commitment(&mut fq_sponge, &comm));
119
120    // Interpolate all columns on d1, using trait Into.
121    let witness_evals_d1: Witness<N_WIT, Evaluations<G::ScalarField, R2D<G::ScalarField>>> = inputs
122        .evaluations
123        .into_par_iter()
124        .map(|evals| {
125            Evaluations::<G::ScalarField, R2D<G::ScalarField>>::from_vec_and_domain(
126                evals, domain.d1,
127            )
128        })
129        .collect::<Witness<N_WIT, Evaluations<G::ScalarField, R2D<G::ScalarField>>>>();
130
131    let witness_polys: Witness<N_WIT, DensePolynomial<G::ScalarField>> = {
132        let interpolate =
133            |evals: Evaluations<G::ScalarField, R2D<G::ScalarField>>| evals.interpolate();
134        witness_evals_d1
135            .into_par_iter()
136            .map(interpolate)
137            .collect::<Witness<N_WIT, DensePolynomial<G::ScalarField>>>()
138    };
139
140    let witness_comms: Witness<N_WIT, PolyComm<G>> = {
141        let blinders = PolyComm {
142            chunks: vec![G::ScalarField::one()],
143        };
144        let comm = {
145            |poly: &DensePolynomial<G::ScalarField>| {
146                // In case the column polynomial is all zeroes, we want to mask the commitment
147                let comm = srs.commit_custom(poly, 1, &blinders).unwrap();
148                comm.commitment
149            }
150        };
151        (&witness_polys)
152            .into_par_iter()
153            .map(comm)
154            .collect::<Witness<N_WIT, PolyComm<G>>>()
155    };
156
157    // Do not use parallelism
158    (&witness_comms)
159        .into_iter()
160        .for_each(|comm| absorb_commitment(&mut fq_sponge, comm));
161
162    // -- Start Logup
163    let lookup_env = if !inputs.logups.is_empty() {
164        Some(Env::create::<OpeningProof, EFqSponge>(
165            inputs.logups,
166            domain,
167            &mut fq_sponge,
168            srs,
169        ))
170    } else {
171        None
172    };
173
174    let max_degree = {
175        if lookup_env.is_none() {
176            constraints
177                .iter()
178                .map(|expr| expr.degree(1, 0))
179                .max()
180                .unwrap_or(0)
181        } else {
182            8
183        }
184    };
185
186    // Don't need to be absorbed. Already absorbed in logup::prover::Env::create
187    // FIXME: remove clone
188    let logup_comms = Option::map(lookup_env.as_ref(), |lookup_env| LookupProof {
189        m: lookup_env.lookup_counters_comm_d1.clone(),
190        h: lookup_env.lookup_terms_comms_d1.clone(),
191        sum: lookup_env.lookup_aggregation_comm_d1.clone(),
192        fixed_tables: lookup_env.fixed_lookup_tables_comms_d1.clone(),
193    });
194
195    // -- end computing the running sum in lookup_aggregation
196    // -- End of Logup
197
198    let domain_eval = if max_degree <= 4 {
199        domain.d4
200    } else if max_degree as usize <= MAX_SUPPORTED_DEGREE {
201        domain.d8
202    } else {
203        panic!("We do support constraints up to {:?}", MAX_SUPPORTED_DEGREE)
204    };
205
206    let witness_evals: Witness<N_WIT, Evaluations<G::ScalarField, R2D<G::ScalarField>>> = {
207        (&witness_polys)
208            .into_par_iter()
209            .map(|evals| evals.evaluate_over_domain_by_ref(domain_eval))
210            .collect::<Witness<N_WIT, Evaluations<G::ScalarField, R2D<G::ScalarField>>>>()
211    };
212
213    let fixed_selectors_evals: Box<[Evaluations<G::ScalarField, R2D<G::ScalarField>>; N_FSEL]> = {
214        o1_utils::array::vec_to_boxed_array(
215            (fixed_selectors_polys.as_ref())
216                .into_par_iter()
217                .map(|evals| evals.evaluate_over_domain_by_ref(domain_eval))
218                .collect(),
219        )
220    };
221
222    ////////////////////////////////////////////////////////////////////////////
223    // Round 2: Creating and committing to the quotient polynomial
224    ////////////////////////////////////////////////////////////////////////////
225
226    let (_, endo_r) = G::endos();
227
228    // Sample α with the Fq-Sponge.
229    let alpha: G::ScalarField = fq_sponge.challenge();
230
231    let zk_rows = 0;
232    let column_env: ColumnEnvironment<'_, N_WIT, N_REL, N_DSEL, N_FSEL, _, _> = {
233        let challenges = BerkeleyChallenges {
234            alpha,
235            // NB: as there is no permutation argument, we do use the beta
236            // field instead of a new one for the evaluation point.
237            beta: Option::map(lookup_env.as_ref(), |x| x.beta).unwrap_or(G::ScalarField::zero()),
238            gamma: G::ScalarField::zero(),
239            joint_combiner: Option::map(lookup_env.as_ref(), |x| x.joint_combiner)
240                .unwrap_or(G::ScalarField::zero()),
241        };
242        ColumnEnvironment {
243            constants: Constants {
244                endo_coefficient: *endo_r,
245                mds: &G::sponge_params().mds,
246                zk_rows,
247            },
248            challenges,
249            witness: &witness_evals,
250            fixed_selectors: &fixed_selectors_evals,
251            l0_1: l0_1(domain.d1),
252            lookup: Option::map(lookup_env.as_ref(), |lookup_env| {
253                logup::prover::QuotientPolynomialEnvironment {
254                    lookup_terms_evals_d8: &lookup_env.lookup_terms_evals_d8,
255                    lookup_aggregation_evals_d8: &lookup_env.lookup_aggregation_evals_d8,
256                    lookup_counters_evals_d8: &lookup_env.lookup_counters_evals_d8,
257                    fixed_tables_evals_d8: &lookup_env.fixed_lookup_tables_evals_d8,
258                }
259            }),
260            domain,
261        }
262    };
263
264    let quotient_poly: DensePolynomial<G::ScalarField> = {
265        let mut last_constraint_failed = None;
266        // Only for debugging purposes
267        for expr in constraints.iter() {
268            // Check this expression are witness satisfied
269            let (_, res) = expr
270                .evaluations(&column_env)
271                .interpolate_by_ref()
272                .divide_by_vanishing_poly(domain.d1);
273            if !res.is_zero() {
274                eprintln!("Unsatisfied expression: {}", expr);
275                //return Err(fail_q_division);
276                last_constraint_failed = Some(expr.clone());
277            }
278        }
279        if let Some(expr) = last_constraint_failed {
280            return Err(ProverError::ConstraintNotSatisfied(format!(
281                "Unsatisfied expression: {:}",
282                expr
283            )));
284        }
285
286        // Compute ∑ α^i constraint_i as an expression
287        let combined_expr =
288            Expr::combine_constraints(0..(constraints.len() as u32), constraints.to_vec());
289
290        // We want to compute the quotient polynomial, i.e.
291        // t(X) = (∑ α^i constraint_i(X)) / Z_H(X).
292        // The sum of the expressions is called the "constraint polynomial".
293        // We will use the evaluations points of the individual witness and
294        // lookup columns.
295        // Note that as the constraints might be of higher degree than N, the
296        // size of the set H we want the constraints to be verified on, we must
297        // have more than N evaluations points for each columns. This is handled
298        // in the ColumnEnvironment structure.
299        // Reminder: to compute P(X) = P_{1}(X) * P_{2}(X), from the evaluations
300        // of P_{1} and P_{2}, with deg(P_{1}) = deg(P_{2}(X)) = N, we must have
301        // 2N evaluation points to compute P as deg(P(X)) <= 2N.
302        let expr_evaluation: Evaluations<G::ScalarField, R2D<G::ScalarField>> =
303            combined_expr.evaluations(&column_env);
304
305        // And we interpolate using the evaluations
306        let expr_evaluation_interpolated = expr_evaluation.interpolate();
307
308        let fail_final_q_division = || {
309            panic!("Division by vanishing poly must not fail at this point, we checked it before")
310        };
311        // We compute the polynomial t(X) by dividing the constraints polynomial
312        // by the vanishing polynomial, i.e. Z_H(X).
313        let (quotient, res) = expr_evaluation_interpolated.divide_by_vanishing_poly(domain.d1);
314        // As the constraints must be verified on H, the rest of the division
315        // must be equal to 0 as the constraints polynomial and Z_H(X) are both
316        // equal on H.
317        if !res.is_zero() {
318            fail_final_q_division();
319        }
320
321        quotient
322    };
323
324    let num_chunks: usize = if max_degree == 1 {
325        1
326    } else {
327        (max_degree - 1) as usize
328    };
329
330    //~ 1. commit to the quotient polynomial $t$.
331    let t_comm = srs.commit_non_hiding(&quotient_poly, num_chunks);
332
333    ////////////////////////////////////////////////////////////////////////////
334    // Round 3: Evaluations at ζ and ζω
335    ////////////////////////////////////////////////////////////////////////////
336
337    //~ 1. Absorb the commitment of the quotient polynomial with the Fq-Sponge.
338    absorb_commitment(&mut fq_sponge, &t_comm);
339
340    //~ 1. Sample ζ with the Fq-Sponge.
341    let zeta_chal = ScalarChallenge(fq_sponge.challenge());
342
343    let zeta = zeta_chal.to_field(endo_r);
344
345    let omega = domain.d1.group_gen;
346    // We will also evaluate at ζω as lookups do require to go to the next row.
347    let zeta_omega = zeta * omega;
348
349    // Evaluate the polynomials at ζ and ζω -- Columns
350    let witness_evals: Witness<N_WIT, PointEvaluations<_>> = {
351        let eval = |p: &DensePolynomial<_>| PointEvaluations {
352            zeta: p.evaluate(&zeta),
353            zeta_omega: p.evaluate(&zeta_omega),
354        };
355        (&witness_polys)
356            .into_par_iter()
357            .map(eval)
358            .collect::<Witness<N_WIT, PointEvaluations<_>>>()
359    };
360
361    let fixed_selectors_evals: Box<[PointEvaluations<_>; N_FSEL]> = {
362        let eval = |p: &DensePolynomial<_>| PointEvaluations {
363            zeta: p.evaluate(&zeta),
364            zeta_omega: p.evaluate(&zeta_omega),
365        };
366        o1_utils::array::vec_to_boxed_array(
367            fixed_selectors_polys
368                .as_ref()
369                .into_par_iter()
370                .map(eval)
371                .collect::<_>(),
372        )
373    };
374
375    // IMPROVEME: move this into the logup module
376    let logup_evals = lookup_env.as_ref().map(|lookup_env| LookupProof {
377        m: lookup_env
378            .lookup_counters_poly_d1
379            .iter()
380            .map(|(id, polys)| {
381                (
382                    *id,
383                    polys
384                        .iter()
385                        .map(|poly| {
386                            let zeta = poly.evaluate(&zeta);
387                            let zeta_omega = poly.evaluate(&zeta_omega);
388                            PointEvaluations { zeta, zeta_omega }
389                        })
390                        .collect(),
391                )
392            })
393            .collect(),
394        h: lookup_env
395            .lookup_terms_poly_d1
396            .iter()
397            .map(|(id, polys)| {
398                let polys_evals: Vec<_> = polys
399                    .iter()
400                    .map(|poly| PointEvaluations {
401                        zeta: poly.evaluate(&zeta),
402                        zeta_omega: poly.evaluate(&zeta_omega),
403                    })
404                    .collect();
405                (*id, polys_evals)
406            })
407            .collect(),
408        sum: PointEvaluations {
409            zeta: lookup_env.lookup_aggregation_poly_d1.evaluate(&zeta),
410            zeta_omega: lookup_env.lookup_aggregation_poly_d1.evaluate(&zeta_omega),
411        },
412        fixed_tables: {
413            lookup_env
414                .fixed_lookup_tables_poly_d1
415                .iter()
416                .map(|(id, poly)| {
417                    let zeta = poly.evaluate(&zeta);
418                    let zeta_omega = poly.evaluate(&zeta_omega);
419                    (*id, PointEvaluations { zeta, zeta_omega })
420                })
421                .collect()
422        },
423    });
424
425    ////////////////////////////////////////////////////////////////////////////
426    // Round 4: Opening proof w/o linearization polynomial
427    ////////////////////////////////////////////////////////////////////////////
428
429    // Fiat Shamir - absorbing evaluations
430    let fq_sponge_before_evaluations = fq_sponge.clone();
431    let mut fr_sponge = EFrSponge::new(G::sponge_params());
432    fr_sponge.absorb(&fq_sponge.digest());
433
434    for PointEvaluations { zeta, zeta_omega } in (&witness_evals).into_iter() {
435        fr_sponge.absorb(zeta);
436        fr_sponge.absorb(zeta_omega);
437    }
438
439    for PointEvaluations { zeta, zeta_omega } in fixed_selectors_evals.as_ref().iter() {
440        fr_sponge.absorb(zeta);
441        fr_sponge.absorb(zeta_omega);
442    }
443
444    if lookup_env.is_some() {
445        for PointEvaluations { zeta, zeta_omega } in logup_evals.as_ref().unwrap().into_iter() {
446            fr_sponge.absorb(zeta);
447            fr_sponge.absorb(zeta_omega);
448        }
449    }
450
451    // Compute ft(X) = \
452    //   (1 - ζ^n) \
453    //    (t_0(X) + ζ^n t_1(X) + ... + ζ^{kn} t_{k}(X))
454    // where \sum_i t_i(X) X^{i n} = t(X), and t(X) is the quotient polynomial.
455    // At the end, we get the (partial) evaluation of the constraint polynomial
456    // in ζ.
457    let ft: DensePolynomial<G::ScalarField> = {
458        let evaluation_point_to_domain_size = zeta.pow([domain.d1.size]);
459        // Compute \sum_i t_i(X) ζ^{i n}
460        // First we split t in t_i, and we reduce to degree (n - 1) after using `linearize`
461        let t_chunked: DensePolynomial<G::ScalarField> = quotient_poly
462            .to_chunked_polynomial(num_chunks, domain.d1.size as usize)
463            .linearize(evaluation_point_to_domain_size);
464        // -Z_H = (1 - ζ^n)
465        let minus_vanishing_poly_at_zeta = -domain.d1.vanishing_polynomial().evaluate(&zeta);
466        // Multiply the polynomial \sum_i t_i(X) ζ^{i n} by -Z_H(ζ)
467        // (the evaluation in ζ of the vanishing polynomial)
468        t_chunked.scale(minus_vanishing_poly_at_zeta)
469    };
470
471    // We only evaluate at ζω as the verifier can compute the
472    // evaluation at ζ from the independent evaluations at ζ of the
473    // witness columns because ft(X) is the constraint polynomial, built from
474    // the public constraints.
475    // We evaluate at ζω because the lookup argument requires to compute
476    // \phi(Xω) - \phi(X).
477    let ft_eval1 = ft.evaluate(&zeta_omega);
478
479    // Absorb ft(ζω)
480    fr_sponge.absorb(&ft_eval1);
481
482    let v_chal = fr_sponge.challenge();
483    let v = v_chal.to_field(endo_r);
484    let u_chal = fr_sponge.challenge();
485    let u = u_chal.to_field(endo_r);
486
487    let coefficients_form = DensePolynomialOrEvaluations::DensePolynomial;
488    let non_hiding = |n_chunks| PolyComm {
489        chunks: vec![G::ScalarField::zero(); n_chunks],
490    };
491    let hiding = |n_chunks| PolyComm {
492        chunks: vec![G::ScalarField::one(); n_chunks],
493    };
494
495    // Gathering all polynomials to use in the opening proof
496    let mut polynomials: Vec<_> = (&witness_polys)
497        .into_par_iter()
498        .map(|poly| (coefficients_form(poly), hiding(1)))
499        .collect();
500
501    // @volhovm: I'm not sure we need to prove opening of fixed
502    // selectors in the commitment.
503    polynomials.extend(
504        fixed_selectors_polys
505            .as_ref()
506            .into_par_iter()
507            .map(|poly| (coefficients_form(poly), non_hiding(1)))
508            .collect::<Vec<_>>(),
509    );
510
511    // Adding Logup
512    if let Some(ref lookup_env) = lookup_env {
513        // -- first m(X)
514        polynomials.extend(
515            lookup_env
516                .lookup_counters_poly_d1
517                .values()
518                .flat_map(|polys| {
519                    polys
520                        .iter()
521                        .map(|poly| (coefficients_form(poly), non_hiding(1)))
522                        .collect::<Vec<_>>()
523                })
524                .collect::<Vec<_>>(),
525        );
526        // -- after that the partial sums
527        polynomials.extend({
528            let polys = lookup_env.lookup_terms_poly_d1.values().map(|polys| {
529                polys
530                    .iter()
531                    .map(|poly| (coefficients_form(poly), non_hiding(1)))
532                    .collect::<Vec<_>>()
533            });
534            let polys: Vec<_> = polys.flatten().collect();
535            polys
536        });
537        // -- after that the running sum
538        polynomials.push((
539            coefficients_form(&lookup_env.lookup_aggregation_poly_d1),
540            non_hiding(1),
541        ));
542        // -- Adding fixed lookup tables
543        polynomials.extend(
544            lookup_env
545                .fixed_lookup_tables_poly_d1
546                .values()
547                .map(|poly| (coefficients_form(poly), non_hiding(1)))
548                .collect::<Vec<_>>(),
549        );
550    }
551    polynomials.push((coefficients_form(&ft), non_hiding(1)));
552
553    let opening_proof = OpenProof::open::<_, _, R2D<G::ScalarField>>(
554        srs,
555        &group_map,
556        polynomials.as_slice(),
557        &[zeta, zeta_omega],
558        v,
559        u,
560        fq_sponge_before_evaluations,
561        rng,
562    );
563
564    let proof_evals: ProofEvaluations<N_WIT, N_REL, N_DSEL, N_FSEL, G::ScalarField, ID> = {
565        ProofEvaluations {
566            witness_evals,
567            fixed_selectors_evals,
568            logup_evals,
569            ft_eval1,
570        }
571    };
572
573    Ok(Proof {
574        proof_comms: ProofCommitments {
575            witness_comms,
576            logup_comms,
577            t_comm,
578        },
579        proof_evals,
580        opening_proof,
581    })
582}