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            let fail_q_division =
269                ProverError::ConstraintNotSatisfied(format!("Unsatisfied expression: {:}", expr));
270            // Check this expression are witness satisfied
271            let (_, res) = expr
272                .evaluations(&column_env)
273                .interpolate_by_ref()
274                .divide_by_vanishing_poly(domain.d1)
275                .ok_or(fail_q_division.clone())?;
276            if !res.is_zero() {
277                eprintln!("Unsatisfied expression: {}", expr);
278                //return Err(fail_q_division);
279                last_constraint_failed = Some(expr.clone());
280            }
281        }
282        if let Some(expr) = last_constraint_failed {
283            return Err(ProverError::ConstraintNotSatisfied(format!(
284                "Unsatisfied expression: {:}",
285                expr
286            )));
287        }
288
289        // Compute ∑ α^i constraint_i as an expression
290        let combined_expr =
291            Expr::combine_constraints(0..(constraints.len() as u32), constraints.to_vec());
292
293        // We want to compute the quotient polynomial, i.e.
294        // t(X) = (∑ α^i constraint_i(X)) / Z_H(X).
295        // The sum of the expressions is called the "constraint polynomial".
296        // We will use the evaluations points of the individual witness and
297        // lookup columns.
298        // Note that as the constraints might be of higher degree than N, the
299        // size of the set H we want the constraints to be verified on, we must
300        // have more than N evaluations points for each columns. This is handled
301        // in the ColumnEnvironment structure.
302        // Reminder: to compute P(X) = P_{1}(X) * P_{2}(X), from the evaluations
303        // of P_{1} and P_{2}, with deg(P_{1}) = deg(P_{2}(X)) = N, we must have
304        // 2N evaluation points to compute P as deg(P(X)) <= 2N.
305        let expr_evaluation: Evaluations<G::ScalarField, R2D<G::ScalarField>> =
306            combined_expr.evaluations(&column_env);
307
308        // And we interpolate using the evaluations
309        let expr_evaluation_interpolated = expr_evaluation.interpolate();
310
311        let fail_final_q_division = || {
312            panic!("Division by vanishing poly must not fail at this point, we checked it before")
313        };
314        // We compute the polynomial t(X) by dividing the constraints polynomial
315        // by the vanishing polynomial, i.e. Z_H(X).
316        let (quotient, res) = expr_evaluation_interpolated
317            .divide_by_vanishing_poly(domain.d1)
318            .unwrap_or_else(fail_final_q_division);
319        // As the constraints must be verified on H, the rest of the division
320        // must be equal to 0 as the constraints polynomial and Z_H(X) are both
321        // equal on H.
322        if !res.is_zero() {
323            fail_final_q_division();
324        }
325
326        quotient
327    };
328
329    let num_chunks: usize = if max_degree == 1 {
330        1
331    } else {
332        (max_degree - 1) as usize
333    };
334
335    //~ 1. commit to the quotient polynomial $t$.
336    let t_comm = srs.commit_non_hiding(&quotient_poly, num_chunks);
337
338    ////////////////////////////////////////////////////////////////////////////
339    // Round 3: Evaluations at ζ and ζω
340    ////////////////////////////////////////////////////////////////////////////
341
342    //~ 1. Absorb the commitment of the quotient polynomial with the Fq-Sponge.
343    absorb_commitment(&mut fq_sponge, &t_comm);
344
345    //~ 1. Sample ζ with the Fq-Sponge.
346    let zeta_chal = ScalarChallenge(fq_sponge.challenge());
347
348    let zeta = zeta_chal.to_field(endo_r);
349
350    let omega = domain.d1.group_gen;
351    // We will also evaluate at ζω as lookups do require to go to the next row.
352    let zeta_omega = zeta * omega;
353
354    // Evaluate the polynomials at ζ and ζω -- Columns
355    let witness_evals: Witness<N_WIT, PointEvaluations<_>> = {
356        let eval = |p: &DensePolynomial<_>| PointEvaluations {
357            zeta: p.evaluate(&zeta),
358            zeta_omega: p.evaluate(&zeta_omega),
359        };
360        (&witness_polys)
361            .into_par_iter()
362            .map(eval)
363            .collect::<Witness<N_WIT, PointEvaluations<_>>>()
364    };
365
366    let fixed_selectors_evals: Box<[PointEvaluations<_>; N_FSEL]> = {
367        let eval = |p: &DensePolynomial<_>| PointEvaluations {
368            zeta: p.evaluate(&zeta),
369            zeta_omega: p.evaluate(&zeta_omega),
370        };
371        o1_utils::array::vec_to_boxed_array(
372            fixed_selectors_polys
373                .as_ref()
374                .into_par_iter()
375                .map(eval)
376                .collect::<_>(),
377        )
378    };
379
380    // IMPROVEME: move this into the logup module
381    let logup_evals = lookup_env.as_ref().map(|lookup_env| LookupProof {
382        m: lookup_env
383            .lookup_counters_poly_d1
384            .iter()
385            .map(|(id, polys)| {
386                (
387                    *id,
388                    polys
389                        .iter()
390                        .map(|poly| {
391                            let zeta = poly.evaluate(&zeta);
392                            let zeta_omega = poly.evaluate(&zeta_omega);
393                            PointEvaluations { zeta, zeta_omega }
394                        })
395                        .collect(),
396                )
397            })
398            .collect(),
399        h: lookup_env
400            .lookup_terms_poly_d1
401            .iter()
402            .map(|(id, polys)| {
403                let polys_evals: Vec<_> = polys
404                    .iter()
405                    .map(|poly| PointEvaluations {
406                        zeta: poly.evaluate(&zeta),
407                        zeta_omega: poly.evaluate(&zeta_omega),
408                    })
409                    .collect();
410                (*id, polys_evals)
411            })
412            .collect(),
413        sum: PointEvaluations {
414            zeta: lookup_env.lookup_aggregation_poly_d1.evaluate(&zeta),
415            zeta_omega: lookup_env.lookup_aggregation_poly_d1.evaluate(&zeta_omega),
416        },
417        fixed_tables: {
418            lookup_env
419                .fixed_lookup_tables_poly_d1
420                .iter()
421                .map(|(id, poly)| {
422                    let zeta = poly.evaluate(&zeta);
423                    let zeta_omega = poly.evaluate(&zeta_omega);
424                    (*id, PointEvaluations { zeta, zeta_omega })
425                })
426                .collect()
427        },
428    });
429
430    ////////////////////////////////////////////////////////////////////////////
431    // Round 4: Opening proof w/o linearization polynomial
432    ////////////////////////////////////////////////////////////////////////////
433
434    // Fiat Shamir - absorbing evaluations
435    let fq_sponge_before_evaluations = fq_sponge.clone();
436    let mut fr_sponge = EFrSponge::new(G::sponge_params());
437    fr_sponge.absorb(&fq_sponge.digest());
438
439    for PointEvaluations { zeta, zeta_omega } in (&witness_evals).into_iter() {
440        fr_sponge.absorb(zeta);
441        fr_sponge.absorb(zeta_omega);
442    }
443
444    for PointEvaluations { zeta, zeta_omega } in fixed_selectors_evals.as_ref().iter() {
445        fr_sponge.absorb(zeta);
446        fr_sponge.absorb(zeta_omega);
447    }
448
449    if lookup_env.is_some() {
450        for PointEvaluations { zeta, zeta_omega } in logup_evals.as_ref().unwrap().into_iter() {
451            fr_sponge.absorb(zeta);
452            fr_sponge.absorb(zeta_omega);
453        }
454    }
455
456    // Compute ft(X) = \
457    //   (1 - ζ^n) \
458    //    (t_0(X) + ζ^n t_1(X) + ... + ζ^{kn} t_{k}(X))
459    // where \sum_i t_i(X) X^{i n} = t(X), and t(X) is the quotient polynomial.
460    // At the end, we get the (partial) evaluation of the constraint polynomial
461    // in ζ.
462    let ft: DensePolynomial<G::ScalarField> = {
463        let evaluation_point_to_domain_size = zeta.pow([domain.d1.size]);
464        // Compute \sum_i t_i(X) ζ^{i n}
465        // First we split t in t_i, and we reduce to degree (n - 1) after using `linearize`
466        let t_chunked: DensePolynomial<G::ScalarField> = quotient_poly
467            .to_chunked_polynomial(num_chunks, domain.d1.size as usize)
468            .linearize(evaluation_point_to_domain_size);
469        // -Z_H = (1 - ζ^n)
470        let minus_vanishing_poly_at_zeta = -domain.d1.vanishing_polynomial().evaluate(&zeta);
471        // Multiply the polynomial \sum_i t_i(X) ζ^{i n} by -Z_H(ζ)
472        // (the evaluation in ζ of the vanishing polynomial)
473        t_chunked.scale(minus_vanishing_poly_at_zeta)
474    };
475
476    // We only evaluate at ζω as the verifier can compute the
477    // evaluation at ζ from the independent evaluations at ζ of the
478    // witness columns because ft(X) is the constraint polynomial, built from
479    // the public constraints.
480    // We evaluate at ζω because the lookup argument requires to compute
481    // \phi(Xω) - \phi(X).
482    let ft_eval1 = ft.evaluate(&zeta_omega);
483
484    // Absorb ft(ζω)
485    fr_sponge.absorb(&ft_eval1);
486
487    let v_chal = fr_sponge.challenge();
488    let v = v_chal.to_field(endo_r);
489    let u_chal = fr_sponge.challenge();
490    let u = u_chal.to_field(endo_r);
491
492    let coefficients_form = DensePolynomialOrEvaluations::DensePolynomial;
493    let non_hiding = |n_chunks| PolyComm {
494        chunks: vec![G::ScalarField::zero(); n_chunks],
495    };
496    let hiding = |n_chunks| PolyComm {
497        chunks: vec![G::ScalarField::one(); n_chunks],
498    };
499
500    // Gathering all polynomials to use in the opening proof
501    let mut polynomials: Vec<_> = (&witness_polys)
502        .into_par_iter()
503        .map(|poly| (coefficients_form(poly), hiding(1)))
504        .collect();
505
506    // @volhovm: I'm not sure we need to prove opening of fixed
507    // selectors in the commitment.
508    polynomials.extend(
509        fixed_selectors_polys
510            .as_ref()
511            .into_par_iter()
512            .map(|poly| (coefficients_form(poly), non_hiding(1)))
513            .collect::<Vec<_>>(),
514    );
515
516    // Adding Logup
517    if let Some(ref lookup_env) = lookup_env {
518        // -- first m(X)
519        polynomials.extend(
520            lookup_env
521                .lookup_counters_poly_d1
522                .values()
523                .flat_map(|polys| {
524                    polys
525                        .iter()
526                        .map(|poly| (coefficients_form(poly), non_hiding(1)))
527                        .collect::<Vec<_>>()
528                })
529                .collect::<Vec<_>>(),
530        );
531        // -- after that the partial sums
532        polynomials.extend({
533            let polys = lookup_env.lookup_terms_poly_d1.values().map(|polys| {
534                polys
535                    .iter()
536                    .map(|poly| (coefficients_form(poly), non_hiding(1)))
537                    .collect::<Vec<_>>()
538            });
539            let polys: Vec<_> = polys.flatten().collect();
540            polys
541        });
542        // -- after that the running sum
543        polynomials.push((
544            coefficients_form(&lookup_env.lookup_aggregation_poly_d1),
545            non_hiding(1),
546        ));
547        // -- Adding fixed lookup tables
548        polynomials.extend(
549            lookup_env
550                .fixed_lookup_tables_poly_d1
551                .values()
552                .map(|poly| (coefficients_form(poly), non_hiding(1)))
553                .collect::<Vec<_>>(),
554        );
555    }
556    polynomials.push((coefficients_form(&ft), non_hiding(1)));
557
558    let opening_proof = OpenProof::open::<_, _, R2D<G::ScalarField>>(
559        srs,
560        &group_map,
561        polynomials.as_slice(),
562        &[zeta, zeta_omega],
563        v,
564        u,
565        fq_sponge_before_evaluations,
566        rng,
567    );
568
569    let proof_evals: ProofEvaluations<N_WIT, N_REL, N_DSEL, N_FSEL, G::ScalarField, ID> = {
570        ProofEvaluations {
571            witness_evals,
572            fixed_selectors_evals,
573            logup_evals,
574            ft_eval1,
575        }
576    };
577
578    Ok(Proof {
579        proof_comms: ProofCommitments {
580            witness_comms,
581            logup_comms,
582            t_comm,
583        },
584        proof_evals,
585        opening_proof,
586    })
587}