kimchi/
verifier.rs

1//! This module implements zk-proof batch verifier functionality.
2
3use crate::{
4    circuits::{
5        argument::ArgumentType,
6        berkeley_columns::{BerkeleyChallenges, Column},
7        constraints::ConstraintSystem,
8        expr::{Constants, PolishToken},
9        gate::GateType,
10        lookup::{lookups::LookupPattern, tables::combine_table},
11        polynomials::permutation,
12        scalars::RandomOracles,
13        wires::{COLUMNS, PERMUTS},
14    },
15    curve::KimchiCurve,
16    error::VerifyError,
17    oracles::OraclesResult,
18    plonk_sponge::FrSponge,
19    proof::{PointEvaluations, ProofEvaluations, ProverProof, RecursionChallenge},
20    verifier_index::VerifierIndex,
21};
22use ark_ec::AffineRepr;
23use ark_ff::{Field, One, PrimeField, Zero};
24use ark_poly::{univariate::DensePolynomial, EvaluationDomain, Polynomial};
25use mina_poseidon::{sponge::ScalarChallenge, FqSponge};
26use o1_utils::ExtendedDensePolynomial;
27use poly_commitment::{
28    commitment::{
29        absorb_commitment, combined_inner_product, BatchEvaluationProof, Evaluation, PolyComm,
30    },
31    OpenProof, SRS as _,
32};
33use rand::thread_rng;
34
35/// The result of a proof verification.
36pub type Result<T> = core::result::Result<T, VerifyError>;
37
38#[derive(Debug)]
39pub struct Context<'a, G: KimchiCurve, OpeningProof: OpenProof<G>> {
40    /// The [VerifierIndex] associated to the proof
41    pub verifier_index: &'a VerifierIndex<G, OpeningProof>,
42
43    /// The proof to verify
44    pub proof: &'a ProverProof<G, OpeningProof>,
45
46    /// The public input used in the creation of the proof
47    pub public_input: &'a [G::ScalarField],
48}
49
50impl<'a, G: KimchiCurve, OpeningProof: OpenProof<G>> Context<'a, G, OpeningProof> {
51    pub fn get_column(&self, col: Column) -> Option<&'a PolyComm<G>> {
52        use Column::*;
53        match col {
54            Witness(i) => Some(&self.proof.commitments.w_comm[i]),
55            Coefficient(i) => Some(&self.verifier_index.coefficients_comm[i]),
56            Permutation(i) => Some(&self.verifier_index.sigma_comm[i]),
57            Z => Some(&self.proof.commitments.z_comm),
58            LookupSorted(i) => Some(&self.proof.commitments.lookup.as_ref()?.sorted[i]),
59            LookupAggreg => Some(&self.proof.commitments.lookup.as_ref()?.aggreg),
60            LookupKindIndex(i) => {
61                Some(self.verifier_index.lookup_index.as_ref()?.lookup_selectors[i].as_ref()?)
62            }
63            LookupTable => None,
64            LookupRuntimeSelector => Some(
65                self.verifier_index
66                    .lookup_index
67                    .as_ref()?
68                    .runtime_tables_selector
69                    .as_ref()?,
70            ),
71            LookupRuntimeTable => self.proof.commitments.lookup.as_ref()?.runtime.as_ref(),
72            Index(t) => {
73                use GateType::*;
74                match t {
75                    Zero => None,
76                    Generic => Some(&self.verifier_index.generic_comm),
77                    Lookup => None,
78                    CompleteAdd => Some(&self.verifier_index.complete_add_comm),
79                    VarBaseMul => Some(&self.verifier_index.mul_comm),
80                    EndoMul => Some(&self.verifier_index.emul_comm),
81                    EndoMulScalar => Some(&self.verifier_index.endomul_scalar_comm),
82                    Poseidon => Some(&self.verifier_index.psm_comm),
83                    CairoClaim | CairoInstruction | CairoFlags | CairoTransition => None,
84                    RangeCheck0 => Some(self.verifier_index.range_check0_comm.as_ref()?),
85                    RangeCheck1 => Some(self.verifier_index.range_check1_comm.as_ref()?),
86                    ForeignFieldAdd => Some(self.verifier_index.foreign_field_add_comm.as_ref()?),
87                    ForeignFieldMul => Some(self.verifier_index.foreign_field_mul_comm.as_ref()?),
88                    Xor16 => Some(self.verifier_index.xor_comm.as_ref()?),
89                    Rot64 => Some(self.verifier_index.rot_comm.as_ref()?),
90                }
91            }
92        }
93    }
94}
95
96impl<G: KimchiCurve, OpeningProof: OpenProof<G>> ProverProof<G, OpeningProof>
97where
98    G::BaseField: PrimeField,
99{
100    /// This function runs the random oracle argument
101    ///
102    /// # Errors
103    ///
104    /// Will give error if `commitment(s)` are invalid(missing or wrong length), or `proof` is verified as invalid.
105    ///
106    /// # Panics
107    ///
108    /// Will panic if `PolishToken` evaluation is invalid.
109    pub fn oracles<
110        EFqSponge: Clone + FqSponge<G::BaseField, G, G::ScalarField>,
111        EFrSponge: FrSponge<G::ScalarField>,
112    >(
113        &self,
114        index: &VerifierIndex<G, OpeningProof>,
115        public_comm: &PolyComm<G>,
116        public_input: Option<&[G::ScalarField]>,
117    ) -> Result<OraclesResult<G, EFqSponge>> {
118        //~
119        //~ #### Fiat-Shamir argument
120        //~
121        //~ We run the following algorithm:
122        //~
123        let n = index.domain.size;
124        let (_, endo_r) = G::endos();
125
126        let chunk_size = {
127            let d1_size = index.domain.size();
128            if d1_size < index.max_poly_size {
129                1
130            } else {
131                d1_size / index.max_poly_size
132            }
133        };
134
135        let zk_rows = index.zk_rows;
136
137        //~ 1. Setup the Fq-Sponge. This sponge mostly absorbs group
138        // elements (points as tuples over the base field), but it
139        // squeezes out elements of the group's scalar field.
140        let mut fq_sponge = EFqSponge::new(G::other_curve_sponge_params());
141
142        //~ 1. Absorb the digest of the VerifierIndex.
143        let verifier_index_digest = index.digest::<EFqSponge>();
144        fq_sponge.absorb_fq(&[verifier_index_digest]);
145
146        //~ 1. Absorb the commitments of the previous challenges with the Fq-sponge.
147        for RecursionChallenge { comm, .. } in &self.prev_challenges {
148            absorb_commitment(&mut fq_sponge, comm);
149        }
150
151        //~ 1. Absorb the commitment of the public input polynomial with the Fq-Sponge.
152        absorb_commitment(&mut fq_sponge, public_comm);
153
154        //~ 1. Absorb the commitments to the registers / witness columns with the Fq-Sponge.
155        self.commitments
156            .w_comm
157            .iter()
158            .for_each(|c| absorb_commitment(&mut fq_sponge, c));
159
160        //~ 1. If lookup is used:
161        if let Some(l) = &index.lookup_index {
162            let lookup_commits = self
163                .commitments
164                .lookup
165                .as_ref()
166                .ok_or(VerifyError::LookupCommitmentMissing)?;
167
168            // if runtime is used, absorb the commitment
169            if l.runtime_tables_selector.is_some() {
170                let runtime_commit = lookup_commits
171                    .runtime
172                    .as_ref()
173                    .ok_or(VerifyError::IncorrectRuntimeProof)?;
174                absorb_commitment(&mut fq_sponge, runtime_commit);
175            }
176        }
177
178        let joint_combiner = if let Some(l) = &index.lookup_index {
179            //~~ * If it involves queries to a multiple-column lookup table,
180            //~~   then squeeze the Fq-Sponge to obtain the joint combiner challenge $j'$,
181            //~~   otherwise set the joint combiner challenge $j'$ to $0$.
182            let joint_combiner = if l.joint_lookup_used {
183                fq_sponge.challenge()
184            } else {
185                G::ScalarField::zero()
186            };
187
188            //~~ * Derive the scalar joint combiner challenge $j$ from $j'$ using the endomorphism.
189            //~~   (TODO: specify endomorphism)
190            let joint_combiner = ScalarChallenge(joint_combiner);
191            let joint_combiner_field = joint_combiner.to_field(endo_r);
192            let joint_combiner = (joint_combiner, joint_combiner_field);
193
194            Some(joint_combiner)
195        } else {
196            None
197        };
198
199        if index.lookup_index.is_some() {
200            let lookup_commits = self
201                .commitments
202                .lookup
203                .as_ref()
204                .ok_or(VerifyError::LookupCommitmentMissing)?;
205
206            //~~ * absorb the commitments to the sorted polynomials.
207            for com in &lookup_commits.sorted {
208                absorb_commitment(&mut fq_sponge, com);
209            }
210        }
211
212        // --- PlonK - Round 2
213        //~ 1. Sample the first permutation challenge $\beta$ with the Fq-Sponge.
214        let beta = fq_sponge.challenge();
215
216        //~ 1. Sample the second permutation challenge $\gamma$ with the Fq-Sponge.
217        let gamma = fq_sponge.challenge();
218
219        //~ 1. If using lookup, absorb the commitment to the aggregation lookup polynomial.
220        if index.lookup_index.is_some() {
221            // Should not fail, as the lookup index is present
222            let lookup_commits = self
223                .commitments
224                .lookup
225                .as_ref()
226                .ok_or(VerifyError::LookupCommitmentMissing)?;
227            absorb_commitment(&mut fq_sponge, &lookup_commits.aggreg);
228        }
229
230        //~ 1. Absorb the commitment to the permutation trace with the Fq-Sponge.
231        absorb_commitment(&mut fq_sponge, &self.commitments.z_comm);
232
233        // --- PlonK - Round 3
234        //~ 1. Sample the quotient challenge $\alpha'$ with the Fq-Sponge.
235        let alpha_chal = ScalarChallenge(fq_sponge.challenge());
236
237        //~ 1. Derive $\alpha$ from $\alpha'$ using the endomorphism (TODO: details).
238        let alpha = alpha_chal.to_field(endo_r);
239
240        //~ 1. Enforce that the length of the $t$ commitment is of size 7.
241        if self.commitments.t_comm.len() > chunk_size * 7 {
242            return Err(VerifyError::IncorrectCommitmentLength(
243                "t",
244                chunk_size * 7,
245                self.commitments.t_comm.len(),
246            ));
247        }
248
249        //~ 1. Absorb the commitment to the quotient polynomial $t$ into the argument.
250        absorb_commitment(&mut fq_sponge, &self.commitments.t_comm);
251
252        // --- PlonK - Round 4
253        //~ 1. Sample $\zeta'$ with the Fq-Sponge.
254        let zeta_chal = ScalarChallenge(fq_sponge.challenge());
255
256        //~ 1. Derive $\zeta$ from $\zeta'$ using the endomorphism (TODO: specify).
257        let zeta = zeta_chal.to_field(endo_r);
258
259        //~ 1. Setup the Fr-Sponge. This sponge absorbs elements from
260        // the scalar field of the curve (equal to the base field of
261        // the previous recursion round), and squeezes scalar elements
262        // of the field. The squeeze result is the same as with the
263        // `fq_sponge`.
264        let digest = fq_sponge.clone().digest();
265        let mut fr_sponge = EFrSponge::new(G::sponge_params());
266
267        //~ 1. Squeeze the Fq-sponge and absorb the result with the Fr-Sponge.
268        fr_sponge.absorb(&digest);
269
270        //~ 1. Absorb the previous recursion challenges.
271        let prev_challenge_digest = {
272            // Note: we absorb in a new sponge here to limit the scope in which we need the
273            // more-expensive 'optional sponge'.
274            let mut fr_sponge = EFrSponge::new(G::sponge_params());
275            for RecursionChallenge { chals, .. } in &self.prev_challenges {
276                fr_sponge.absorb_multiple(chals);
277            }
278            fr_sponge.digest()
279        };
280        fr_sponge.absorb(&prev_challenge_digest);
281
282        // prepare some often used values
283        let zeta1 = zeta.pow([n]);
284        let zetaw = zeta * index.domain.group_gen;
285        let evaluation_points = [zeta, zetaw];
286        let powers_of_eval_points_for_chunks = PointEvaluations {
287            zeta: zeta.pow([index.max_poly_size as u64]),
288            zeta_omega: zetaw.pow([index.max_poly_size as u64]),
289        };
290
291        //~ 1. Compute evaluations for the previous recursion challenges.
292        let polys: Vec<(PolyComm<G>, _)> = self
293            .prev_challenges
294            .iter()
295            .map(|challenge| {
296                let evals = challenge.evals(
297                    index.max_poly_size,
298                    &evaluation_points,
299                    &[
300                        powers_of_eval_points_for_chunks.zeta,
301                        powers_of_eval_points_for_chunks.zeta_omega,
302                    ],
303                );
304                let RecursionChallenge { chals: _, comm } = challenge;
305                (comm.clone(), evals)
306            })
307            .collect();
308
309        // retrieve ranges for the powers of alphas
310        let mut all_alphas = index.powers_of_alpha.clone();
311        all_alphas.instantiate(alpha);
312
313        let public_evals = if let Some(public_evals) = &self.evals.public {
314            [public_evals.zeta.clone(), public_evals.zeta_omega.clone()]
315        } else if chunk_size > 1 {
316            return Err(VerifyError::MissingPublicInputEvaluation);
317        } else if let Some(public_input) = public_input {
318            // compute Lagrange base evaluation denominators
319            let w: Vec<_> = index.domain.elements().take(public_input.len()).collect();
320
321            let mut zeta_minus_x: Vec<_> = w.iter().map(|w| zeta - w).collect();
322
323            w.iter()
324                .take(public_input.len())
325                .for_each(|w| zeta_minus_x.push(zetaw - w));
326
327            ark_ff::fields::batch_inversion::<G::ScalarField>(&mut zeta_minus_x);
328
329            //~ 1. Evaluate the negated public polynomial (if present) at $\zeta$ and $\zeta\omega$.
330            //~
331            //~    NOTE: this works only in the case when the poly segment size is not smaller than that of the domain.
332            if public_input.is_empty() {
333                [vec![G::ScalarField::zero()], vec![G::ScalarField::zero()]]
334            } else {
335                [
336                    vec![
337                        (public_input
338                            .iter()
339                            .zip(zeta_minus_x.iter())
340                            .zip(index.domain.elements())
341                            .map(|((p, l), w)| -*l * p * w)
342                            .fold(G::ScalarField::zero(), |x, y| x + y))
343                            * (zeta1 - G::ScalarField::one())
344                            * index.domain.size_inv,
345                    ],
346                    vec![
347                        (public_input
348                            .iter()
349                            .zip(zeta_minus_x[public_input.len()..].iter())
350                            .zip(index.domain.elements())
351                            .map(|((p, l), w)| -*l * p * w)
352                            .fold(G::ScalarField::zero(), |x, y| x + y))
353                            * index.domain.size_inv
354                            * (zetaw.pow([n]) - G::ScalarField::one()),
355                    ],
356                ]
357            }
358        } else {
359            return Err(VerifyError::MissingPublicInputEvaluation);
360        };
361
362        //~ 1. Absorb the unique evaluation of ft: $ft(\zeta\omega)$.
363        fr_sponge.absorb(&self.ft_eval1);
364
365        //~ 1. Absorb all the polynomial evaluations in $\zeta$ and $\zeta\omega$:
366        //~~ * the public polynomial
367        //~~ * z
368        //~~ * generic selector
369        //~~ * poseidon selector
370        //~~ * the 15 register/witness
371        //~~ * 6 sigmas evaluations (the last one is not evaluated)
372        fr_sponge.absorb_multiple(&public_evals[0]);
373        fr_sponge.absorb_multiple(&public_evals[1]);
374        fr_sponge.absorb_evaluations(&self.evals);
375
376        //~ 1. Sample the "polyscale" $v'$ with the Fr-Sponge.
377        let v_chal = fr_sponge.challenge();
378
379        //~ 1. Derive $v$ from $v'$ using the endomorphism (TODO: specify).
380        let v = v_chal.to_field(endo_r);
381
382        //~ 1. Sample the "evalscale" $u'$ with the Fr-Sponge.
383        let u_chal = fr_sponge.challenge();
384
385        //~ 1. Derive $u$ from $u'$ using the endomorphism (TODO: specify).
386        let u = u_chal.to_field(endo_r);
387
388        //~ 1. Create a list of all polynomials that have an evaluation proof.
389
390        let evals = self.evals.combine(&powers_of_eval_points_for_chunks);
391
392        //~ 1. Compute the evaluation of $ft(\zeta)$.
393        let ft_eval0 = {
394            let permutation_vanishing_polynomial =
395                index.permutation_vanishing_polynomial_m().evaluate(&zeta);
396            let zeta1m1 = zeta1 - G::ScalarField::one();
397
398            let mut alpha_powers =
399                all_alphas.get_alphas(ArgumentType::Permutation, permutation::CONSTRAINTS);
400            let alpha0 = alpha_powers
401                .next()
402                .expect("missing power of alpha for permutation");
403            let alpha1 = alpha_powers
404                .next()
405                .expect("missing power of alpha for permutation");
406            let alpha2 = alpha_powers
407                .next()
408                .expect("missing power of alpha for permutation");
409
410            let init = (evals.w[PERMUTS - 1].zeta + gamma)
411                * evals.z.zeta_omega
412                * alpha0
413                * permutation_vanishing_polynomial;
414            let mut ft_eval0 = evals
415                .w
416                .iter()
417                .zip(evals.s.iter())
418                .map(|(w, s)| (beta * s.zeta) + w.zeta + gamma)
419                .fold(init, |x, y| x * y);
420
421            ft_eval0 -= DensePolynomial::eval_polynomial(
422                &public_evals[0],
423                powers_of_eval_points_for_chunks.zeta,
424            );
425
426            ft_eval0 -= evals
427                .w
428                .iter()
429                .zip(index.shift.iter())
430                .map(|(w, s)| gamma + (beta * zeta * s) + w.zeta)
431                .fold(
432                    alpha0 * permutation_vanishing_polynomial * evals.z.zeta,
433                    |x, y| x * y,
434                );
435
436            let numerator = ((zeta1m1 * alpha1 * (zeta - index.w()))
437                + (zeta1m1 * alpha2 * (zeta - G::ScalarField::one())))
438                * (G::ScalarField::one() - evals.z.zeta);
439
440            let denominator = (zeta - index.w()) * (zeta - G::ScalarField::one());
441            let denominator = denominator.inverse().expect("negligible probability");
442
443            ft_eval0 += numerator * denominator;
444
445            let constants = Constants {
446                endo_coefficient: index.endo,
447                mds: &G::sponge_params().mds,
448                zk_rows,
449            };
450            let challenges = BerkeleyChallenges {
451                alpha,
452                beta,
453                gamma,
454                joint_combiner: joint_combiner
455                    .as_ref()
456                    .map(|j| j.1)
457                    .unwrap_or(G::ScalarField::zero()),
458            };
459
460            ft_eval0 -= PolishToken::evaluate(
461                &index.linearization.constant_term,
462                index.domain,
463                zeta,
464                &evals,
465                &constants,
466                &challenges,
467            )
468            .unwrap();
469
470            ft_eval0
471        };
472
473        let combined_inner_product =
474            {
475                let ft_eval0 = vec![ft_eval0];
476                let ft_eval1 = vec![self.ft_eval1];
477
478                #[allow(clippy::type_complexity)]
479                let mut es: Vec<Vec<Vec<G::ScalarField>>> =
480                    polys.iter().map(|(_, e)| e.clone()).collect();
481                es.push(public_evals.to_vec());
482                es.push(vec![ft_eval0, ft_eval1]);
483                for col in [
484                    Column::Z,
485                    Column::Index(GateType::Generic),
486                    Column::Index(GateType::Poseidon),
487                    Column::Index(GateType::CompleteAdd),
488                    Column::Index(GateType::VarBaseMul),
489                    Column::Index(GateType::EndoMul),
490                    Column::Index(GateType::EndoMulScalar),
491                ]
492                .into_iter()
493                .chain((0..COLUMNS).map(Column::Witness))
494                .chain((0..COLUMNS).map(Column::Coefficient))
495                .chain((0..PERMUTS - 1).map(Column::Permutation))
496                .chain(
497                    index
498                        .range_check0_comm
499                        .as_ref()
500                        .map(|_| Column::Index(GateType::RangeCheck0)),
501                )
502                .chain(
503                    index
504                        .range_check1_comm
505                        .as_ref()
506                        .map(|_| Column::Index(GateType::RangeCheck1)),
507                )
508                .chain(
509                    index
510                        .foreign_field_add_comm
511                        .as_ref()
512                        .map(|_| Column::Index(GateType::ForeignFieldAdd)),
513                )
514                .chain(
515                    index
516                        .foreign_field_mul_comm
517                        .as_ref()
518                        .map(|_| Column::Index(GateType::ForeignFieldMul)),
519                )
520                .chain(
521                    index
522                        .xor_comm
523                        .as_ref()
524                        .map(|_| Column::Index(GateType::Xor16)),
525                )
526                .chain(
527                    index
528                        .rot_comm
529                        .as_ref()
530                        .map(|_| Column::Index(GateType::Rot64)),
531                )
532                .chain(
533                    index
534                        .lookup_index
535                        .as_ref()
536                        .map(|li| {
537                            (0..li.lookup_info.max_per_row + 1)
538                                .map(Column::LookupSorted)
539                                .chain([Column::LookupAggreg, Column::LookupTable].into_iter())
540                                .chain(
541                                    li.runtime_tables_selector
542                                        .as_ref()
543                                        .map(|_| [Column::LookupRuntimeTable].into_iter())
544                                        .into_iter()
545                                        .flatten(),
546                                )
547                                .chain(
548                                    self.evals
549                                        .runtime_lookup_table_selector
550                                        .as_ref()
551                                        .map(|_| Column::LookupRuntimeSelector),
552                                )
553                                .chain(
554                                    self.evals
555                                        .xor_lookup_selector
556                                        .as_ref()
557                                        .map(|_| Column::LookupKindIndex(LookupPattern::Xor)),
558                                )
559                                .chain(
560                                    self.evals
561                                        .lookup_gate_lookup_selector
562                                        .as_ref()
563                                        .map(|_| Column::LookupKindIndex(LookupPattern::Lookup)),
564                                )
565                                .chain(
566                                    self.evals.range_check_lookup_selector.as_ref().map(|_| {
567                                        Column::LookupKindIndex(LookupPattern::RangeCheck)
568                                    }),
569                                )
570                                .chain(self.evals.foreign_field_mul_lookup_selector.as_ref().map(
571                                    |_| Column::LookupKindIndex(LookupPattern::ForeignFieldMul),
572                                ))
573                        })
574                        .into_iter()
575                        .flatten(),
576                ) {
577                    es.push({
578                        let evals = self
579                            .evals
580                            .get_column(col)
581                            .ok_or(VerifyError::MissingEvaluation(col))?;
582                        vec![evals.zeta.clone(), evals.zeta_omega.clone()]
583                    })
584                }
585
586                combined_inner_product(&v, &u, &es)
587            };
588
589        let oracles = RandomOracles {
590            joint_combiner,
591            beta,
592            gamma,
593            alpha_chal,
594            alpha,
595            zeta,
596            v,
597            u,
598            zeta_chal,
599            v_chal,
600            u_chal,
601        };
602
603        Ok(OraclesResult {
604            fq_sponge,
605            digest,
606            oracles,
607            all_alphas,
608            public_evals,
609            powers_of_eval_points_for_chunks,
610            polys,
611            zeta1,
612            ft_eval0,
613            combined_inner_product,
614        })
615    }
616}
617
618/// Enforce the length of evaluations inside [`ProverProof`].
619/// Atm, the length of evaluations(both `zeta` and `zeta_omega`) SHOULD be 1.
620/// The length value is prone to future change.
621fn check_proof_evals_len<G, OpeningProof>(
622    proof: &ProverProof<G, OpeningProof>,
623    expected_size: usize,
624) -> Result<()>
625where
626    G: KimchiCurve,
627    G::BaseField: PrimeField,
628{
629    let ProofEvaluations {
630        public,
631        w,
632        z,
633        s,
634        coefficients,
635        generic_selector,
636        poseidon_selector,
637        complete_add_selector,
638        mul_selector,
639        emul_selector,
640        endomul_scalar_selector,
641        range_check0_selector,
642        range_check1_selector,
643        foreign_field_add_selector,
644        foreign_field_mul_selector,
645        xor_selector,
646        rot_selector,
647        lookup_aggregation,
648        lookup_table,
649        lookup_sorted,
650        runtime_lookup_table,
651        runtime_lookup_table_selector,
652        xor_lookup_selector,
653        lookup_gate_lookup_selector,
654        range_check_lookup_selector,
655        foreign_field_mul_lookup_selector,
656    } = &proof.evals;
657
658    let check_eval_len = |eval: &PointEvaluations<Vec<_>>, str: &'static str| -> Result<()> {
659        if eval.zeta.len() != expected_size {
660            Err(VerifyError::IncorrectEvaluationsLength(
661                expected_size,
662                eval.zeta.len(),
663                str,
664            ))
665        } else if eval.zeta_omega.len() != expected_size {
666            Err(VerifyError::IncorrectEvaluationsLength(
667                expected_size,
668                eval.zeta_omega.len(),
669                str,
670            ))
671        } else {
672            Ok(())
673        }
674    };
675
676    if let Some(public) = public {
677        check_eval_len(public, "public input")?;
678    }
679
680    for w_i in w {
681        check_eval_len(w_i, "witness")?;
682    }
683    check_eval_len(z, "permutation accumulator")?;
684    for s_i in s {
685        check_eval_len(s_i, "permutation shifts")?;
686    }
687    for coeff in coefficients {
688        check_eval_len(coeff, "coefficients")?;
689    }
690
691    // Lookup evaluations
692    for sorted in lookup_sorted.iter().flatten() {
693        check_eval_len(sorted, "lookup sorted")?
694    }
695
696    if let Some(lookup_aggregation) = lookup_aggregation {
697        check_eval_len(lookup_aggregation, "lookup aggregation")?;
698    }
699    if let Some(lookup_table) = lookup_table {
700        check_eval_len(lookup_table, "lookup table")?;
701    }
702    if let Some(runtime_lookup_table) = runtime_lookup_table {
703        check_eval_len(runtime_lookup_table, "runtime lookup table")?;
704    }
705
706    check_eval_len(generic_selector, "generic selector")?;
707    check_eval_len(poseidon_selector, "poseidon selector")?;
708    check_eval_len(complete_add_selector, "complete add selector")?;
709    check_eval_len(mul_selector, "mul selector")?;
710    check_eval_len(emul_selector, "endomul selector")?;
711    check_eval_len(endomul_scalar_selector, "endomul scalar selector")?;
712
713    // Optional gates
714
715    if let Some(range_check0_selector) = range_check0_selector {
716        check_eval_len(range_check0_selector, "range check 0 selector")?
717    }
718    if let Some(range_check1_selector) = range_check1_selector {
719        check_eval_len(range_check1_selector, "range check 1 selector")?
720    }
721    if let Some(foreign_field_add_selector) = foreign_field_add_selector {
722        check_eval_len(foreign_field_add_selector, "foreign field add selector")?
723    }
724    if let Some(foreign_field_mul_selector) = foreign_field_mul_selector {
725        check_eval_len(foreign_field_mul_selector, "foreign field mul selector")?
726    }
727    if let Some(xor_selector) = xor_selector {
728        check_eval_len(xor_selector, "xor selector")?
729    }
730    if let Some(rot_selector) = rot_selector {
731        check_eval_len(rot_selector, "rot selector")?
732    }
733
734    // Lookup selectors
735
736    if let Some(runtime_lookup_table_selector) = runtime_lookup_table_selector {
737        check_eval_len(
738            runtime_lookup_table_selector,
739            "runtime lookup table selector",
740        )?
741    }
742    if let Some(xor_lookup_selector) = xor_lookup_selector {
743        check_eval_len(xor_lookup_selector, "xor lookup selector")?
744    }
745    if let Some(lookup_gate_lookup_selector) = lookup_gate_lookup_selector {
746        check_eval_len(lookup_gate_lookup_selector, "lookup gate lookup selector")?
747    }
748    if let Some(range_check_lookup_selector) = range_check_lookup_selector {
749        check_eval_len(range_check_lookup_selector, "range check lookup selector")?
750    }
751    if let Some(foreign_field_mul_lookup_selector) = foreign_field_mul_lookup_selector {
752        check_eval_len(
753            foreign_field_mul_lookup_selector,
754            "foreign field mul lookup selector",
755        )?
756    }
757
758    Ok(())
759}
760
761fn to_batch<'a, G, EFqSponge, EFrSponge, OpeningProof: OpenProof<G>>(
762    verifier_index: &VerifierIndex<G, OpeningProof>,
763    proof: &'a ProverProof<G, OpeningProof>,
764    public_input: &'a [<G as AffineRepr>::ScalarField],
765) -> Result<BatchEvaluationProof<'a, G, EFqSponge, OpeningProof>>
766where
767    G: KimchiCurve,
768    G::BaseField: PrimeField,
769    EFqSponge: Clone + FqSponge<G::BaseField, G, G::ScalarField>,
770    EFrSponge: FrSponge<G::ScalarField>,
771{
772    //~
773    //~ #### Partial verification
774    //~
775    //~ For every proof we want to verify, we defer the proof opening to the very end.
776    //~ This allows us to potentially batch verify a number of partially verified proofs.
777    //~ Essentially, this steps verifies that $f(\zeta) = t(\zeta) * Z_H(\zeta)$.
778    //~
779
780    let zk_rows = verifier_index.zk_rows;
781
782    if proof.prev_challenges.len() != verifier_index.prev_challenges {
783        return Err(VerifyError::IncorrectPrevChallengesLength(
784            verifier_index.prev_challenges,
785            proof.prev_challenges.len(),
786        ));
787    }
788    if public_input.len() != verifier_index.public {
789        return Err(VerifyError::IncorrectPubicInputLength(
790            verifier_index.public,
791        ));
792    }
793
794    //~ 1. Check the length of evaluations inside the proof.
795    let chunk_size = {
796        let d1_size = verifier_index.domain.size();
797        if d1_size < verifier_index.max_poly_size {
798            1
799        } else {
800            d1_size / verifier_index.max_poly_size
801        }
802    };
803    check_proof_evals_len(proof, chunk_size)?;
804
805    //~ 1. Commit to the negated public input polynomial.
806    let public_comm = {
807        if public_input.len() != verifier_index.public {
808            return Err(VerifyError::IncorrectPubicInputLength(
809                verifier_index.public,
810            ));
811        }
812        let lgr_comm = verifier_index
813            .srs()
814            .get_lagrange_basis(verifier_index.domain);
815        let com: Vec<_> = lgr_comm.iter().take(verifier_index.public).collect();
816        if public_input.is_empty() {
817            PolyComm::new(vec![verifier_index.srs().blinding_commitment(); chunk_size])
818        } else {
819            let elm: Vec<_> = public_input.iter().map(|s| -*s).collect();
820            let public_comm = PolyComm::<G>::multi_scalar_mul(&com, &elm);
821            verifier_index
822                .srs()
823                .mask_custom(
824                    public_comm.clone(),
825                    &public_comm.map(|_| G::ScalarField::one()),
826                )
827                .unwrap()
828                .commitment
829        }
830    };
831
832    //~ 1. Run the [Fiat-Shamir argument](#fiat-shamir-argument).
833    let OraclesResult {
834        fq_sponge,
835        oracles,
836        all_alphas,
837        public_evals,
838        powers_of_eval_points_for_chunks,
839        polys,
840        zeta1: zeta_to_domain_size,
841        ft_eval0,
842        combined_inner_product,
843        ..
844    } = proof.oracles::<EFqSponge, EFrSponge>(verifier_index, &public_comm, Some(public_input))?;
845
846    //~ 1. Combine the chunked polynomials' evaluations
847    //~    (TODO: most likely only the quotient polynomial is chunked)
848    //~    with the right powers of $\zeta^n$ and $(\zeta * \omega)^n$.
849    let evals = proof.evals.combine(&powers_of_eval_points_for_chunks);
850
851    let context = Context {
852        verifier_index,
853        proof,
854        public_input,
855    };
856
857    //~ 1. Compute the commitment to the linearized polynomial $f$.
858    //~    To do this, add the constraints of all of the gates, of the permutation,
859    //~    and optionally of the lookup.
860    //~    (See the separate sections in the [constraints](#constraints) section.)
861    //~    Any polynomial should be replaced by its associated commitment,
862    //~    contained in the verifier index or in the proof,
863    //~    unless a polynomial has its evaluation provided by the proof
864    //~    in which case the evaluation should be used in place of the commitment.
865    let f_comm = {
866        // the permutation is written manually (not using the expr framework)
867        let permutation_vanishing_polynomial = verifier_index
868            .permutation_vanishing_polynomial_m()
869            .evaluate(&oracles.zeta);
870
871        let alphas = all_alphas.get_alphas(ArgumentType::Permutation, permutation::CONSTRAINTS);
872
873        let mut commitments = vec![&verifier_index.sigma_comm[PERMUTS - 1]];
874        let mut scalars = vec![ConstraintSystem::<G::ScalarField>::perm_scalars(
875            &evals,
876            oracles.beta,
877            oracles.gamma,
878            alphas,
879            permutation_vanishing_polynomial,
880        )];
881
882        // other gates are implemented using the expression framework
883        {
884            // TODO: Reuse constants and challenges from oracles function
885            let constants = Constants {
886                endo_coefficient: verifier_index.endo,
887                mds: &G::sponge_params().mds,
888                zk_rows,
889            };
890            let challenges = BerkeleyChallenges {
891                alpha: oracles.alpha,
892                beta: oracles.beta,
893                gamma: oracles.gamma,
894                joint_combiner: oracles
895                    .joint_combiner
896                    .as_ref()
897                    .map(|j| j.1)
898                    .unwrap_or(G::ScalarField::zero()),
899            };
900
901            for (col, tokens) in &verifier_index.linearization.index_terms {
902                let scalar = PolishToken::evaluate(
903                    tokens,
904                    verifier_index.domain,
905                    oracles.zeta,
906                    &evals,
907                    &constants,
908                    &challenges,
909                )
910                .expect("should evaluate");
911
912                let col = *col;
913                scalars.push(scalar);
914                commitments.push(
915                    context
916                        .get_column(col)
917                        .ok_or(VerifyError::MissingCommitment(col))?,
918                );
919            }
920        }
921
922        // MSM
923        PolyComm::multi_scalar_mul(&commitments, &scalars)
924    };
925
926    //~ 1. Compute the (chuncked) commitment of $ft$
927    //~    (see [Maller's optimization](../kimchi/maller_15.md)).
928    let ft_comm = {
929        let zeta_to_srs_len = oracles.zeta.pow([verifier_index.max_poly_size as u64]);
930        let chunked_f_comm = f_comm.chunk_commitment(zeta_to_srs_len);
931        let chunked_t_comm = &proof.commitments.t_comm.chunk_commitment(zeta_to_srs_len);
932        &chunked_f_comm - &chunked_t_comm.scale(zeta_to_domain_size - G::ScalarField::one())
933    };
934
935    //~ 1. List the polynomial commitments, and their associated evaluations,
936    //~    that are associated to the aggregated evaluation proof in the proof:
937    let mut evaluations = vec![];
938
939    //~~ * recursion
940    evaluations.extend(polys.into_iter().map(|(c, e)| Evaluation {
941        commitment: c,
942        evaluations: e,
943    }));
944
945    //~~ * public input commitment
946    evaluations.push(Evaluation {
947        commitment: public_comm,
948        evaluations: public_evals.to_vec(),
949    });
950
951    //~~ * ft commitment (chunks of it)
952    evaluations.push(Evaluation {
953        commitment: ft_comm,
954        evaluations: vec![vec![ft_eval0], vec![proof.ft_eval1]],
955    });
956
957    for col in [
958        //~~ * permutation commitment
959        Column::Z,
960        //~~ * index commitments that use the coefficients
961        Column::Index(GateType::Generic),
962        Column::Index(GateType::Poseidon),
963        Column::Index(GateType::CompleteAdd),
964        Column::Index(GateType::VarBaseMul),
965        Column::Index(GateType::EndoMul),
966        Column::Index(GateType::EndoMulScalar),
967    ]
968    .into_iter()
969    //~~ * witness commitments
970    .chain((0..COLUMNS).map(Column::Witness))
971    //~~ * coefficient commitments
972    .chain((0..COLUMNS).map(Column::Coefficient))
973    //~~ * sigma commitments
974    .chain((0..PERMUTS - 1).map(Column::Permutation))
975    //~~ * optional gate commitments
976    .chain(
977        verifier_index
978            .range_check0_comm
979            .as_ref()
980            .map(|_| Column::Index(GateType::RangeCheck0)),
981    )
982    .chain(
983        verifier_index
984            .range_check1_comm
985            .as_ref()
986            .map(|_| Column::Index(GateType::RangeCheck1)),
987    )
988    .chain(
989        verifier_index
990            .foreign_field_add_comm
991            .as_ref()
992            .map(|_| Column::Index(GateType::ForeignFieldAdd)),
993    )
994    .chain(
995        verifier_index
996            .foreign_field_mul_comm
997            .as_ref()
998            .map(|_| Column::Index(GateType::ForeignFieldMul)),
999    )
1000    .chain(
1001        verifier_index
1002            .xor_comm
1003            .as_ref()
1004            .map(|_| Column::Index(GateType::Xor16)),
1005    )
1006    .chain(
1007        verifier_index
1008            .rot_comm
1009            .as_ref()
1010            .map(|_| Column::Index(GateType::Rot64)),
1011    )
1012    //~~ * lookup commitments
1013    //~
1014    .chain(
1015        verifier_index
1016            .lookup_index
1017            .as_ref()
1018            .map(|li| {
1019                // add evaluations of sorted polynomials
1020                (0..li.lookup_info.max_per_row + 1)
1021                    .map(Column::LookupSorted)
1022                    // add evaluations of the aggreg polynomial
1023                    .chain([Column::LookupAggreg].into_iter())
1024            })
1025            .into_iter()
1026            .flatten(),
1027    ) {
1028        let evals = proof
1029            .evals
1030            .get_column(col)
1031            .ok_or(VerifyError::MissingEvaluation(col))?;
1032        evaluations.push(Evaluation {
1033            commitment: context
1034                .get_column(col)
1035                .ok_or(VerifyError::MissingCommitment(col))?
1036                .clone(),
1037            evaluations: vec![evals.zeta.clone(), evals.zeta_omega.clone()],
1038        });
1039    }
1040
1041    if let Some(li) = &verifier_index.lookup_index {
1042        let lookup_comms = proof
1043            .commitments
1044            .lookup
1045            .as_ref()
1046            .ok_or(VerifyError::LookupCommitmentMissing)?;
1047
1048        let lookup_table = proof
1049            .evals
1050            .lookup_table
1051            .as_ref()
1052            .ok_or(VerifyError::LookupEvalsMissing)?;
1053        let runtime_lookup_table = proof.evals.runtime_lookup_table.as_ref();
1054
1055        // compute table commitment
1056        let table_comm = {
1057            let joint_combiner = oracles
1058                .joint_combiner
1059                .expect("joint_combiner should be present if lookups are used");
1060            // The table ID is added as the last column of the vector.
1061            // Therefore, the exponent for the combiner for the table ID is the
1062            // width of the concatenated table, i.e. max_joint_size.
1063            let table_id_combiner = joint_combiner
1064                .1
1065                .pow([u64::from(li.lookup_info.max_joint_size)]);
1066            let lookup_table: Vec<_> = li.lookup_table.iter().collect();
1067            let runtime = lookup_comms.runtime.as_ref();
1068
1069            combine_table(
1070                &lookup_table,
1071                joint_combiner.1,
1072                table_id_combiner,
1073                li.table_ids.as_ref(),
1074                runtime,
1075            )
1076        };
1077
1078        // add evaluation of the table polynomial
1079        evaluations.push(Evaluation {
1080            commitment: table_comm,
1081            evaluations: vec![lookup_table.zeta.clone(), lookup_table.zeta_omega.clone()],
1082        });
1083
1084        // add evaluation of the runtime table polynomial
1085        if li.runtime_tables_selector.is_some() {
1086            let runtime = lookup_comms
1087                .runtime
1088                .as_ref()
1089                .ok_or(VerifyError::IncorrectRuntimeProof)?;
1090            let runtime_eval = runtime_lookup_table
1091                .as_ref()
1092                .map(|x| x.map_ref(&|x| x.clone()))
1093                .ok_or(VerifyError::IncorrectRuntimeProof)?;
1094
1095            evaluations.push(Evaluation {
1096                commitment: runtime.clone(),
1097                evaluations: vec![runtime_eval.zeta, runtime_eval.zeta_omega],
1098            });
1099        }
1100    }
1101
1102    for col in verifier_index
1103        .lookup_index
1104        .as_ref()
1105        .map(|li| {
1106            (li.runtime_tables_selector
1107                .as_ref()
1108                .map(|_| Column::LookupRuntimeSelector))
1109            .into_iter()
1110            .chain(
1111                li.lookup_selectors
1112                    .xor
1113                    .as_ref()
1114                    .map(|_| Column::LookupKindIndex(LookupPattern::Xor)),
1115            )
1116            .chain(
1117                li.lookup_selectors
1118                    .lookup
1119                    .as_ref()
1120                    .map(|_| Column::LookupKindIndex(LookupPattern::Lookup)),
1121            )
1122            .chain(
1123                li.lookup_selectors
1124                    .range_check
1125                    .as_ref()
1126                    .map(|_| Column::LookupKindIndex(LookupPattern::RangeCheck)),
1127            )
1128            .chain(
1129                li.lookup_selectors
1130                    .ffmul
1131                    .as_ref()
1132                    .map(|_| Column::LookupKindIndex(LookupPattern::ForeignFieldMul)),
1133            )
1134        })
1135        .into_iter()
1136        .flatten()
1137    {
1138        let evals = proof
1139            .evals
1140            .get_column(col)
1141            .ok_or(VerifyError::MissingEvaluation(col))?;
1142        evaluations.push(Evaluation {
1143            commitment: context
1144                .get_column(col)
1145                .ok_or(VerifyError::MissingCommitment(col))?
1146                .clone(),
1147            evaluations: vec![evals.zeta.clone(), evals.zeta_omega.clone()],
1148        });
1149    }
1150
1151    // prepare for the opening proof verification
1152    let evaluation_points = vec![oracles.zeta, oracles.zeta * verifier_index.domain.group_gen];
1153    Ok(BatchEvaluationProof {
1154        sponge: fq_sponge,
1155        evaluations,
1156        evaluation_points,
1157        polyscale: oracles.v,
1158        evalscale: oracles.u,
1159        opening: &proof.proof,
1160        combined_inner_product,
1161    })
1162}
1163
1164/// Verify a proof [`ProverProof`] using a [`VerifierIndex`] and a `group_map`.
1165///
1166/// # Errors
1167///
1168/// Will give error if `proof(s)` are not verified as valid.
1169pub fn verify<G, EFqSponge, EFrSponge, OpeningProof: OpenProof<G>>(
1170    group_map: &G::Map,
1171    verifier_index: &VerifierIndex<G, OpeningProof>,
1172    proof: &ProverProof<G, OpeningProof>,
1173    public_input: &[G::ScalarField],
1174) -> Result<()>
1175where
1176    G: KimchiCurve,
1177    G::BaseField: PrimeField,
1178    EFqSponge: Clone + FqSponge<G::BaseField, G, G::ScalarField>,
1179    EFrSponge: FrSponge<G::ScalarField>,
1180{
1181    let proofs = vec![Context {
1182        verifier_index,
1183        proof,
1184        public_input,
1185    }];
1186    batch_verify::<G, EFqSponge, EFrSponge, OpeningProof>(group_map, &proofs)
1187}
1188
1189/// This function verifies the batch of zk-proofs
1190///     proofs: vector of Plonk proofs
1191///     RETURN: verification status
1192///
1193/// # Errors
1194///
1195/// Will give error if `srs` of `proof` is invalid or `verify` process fails.
1196pub fn batch_verify<G, EFqSponge, EFrSponge, OpeningProof: OpenProof<G>>(
1197    group_map: &G::Map,
1198    proofs: &[Context<G, OpeningProof>],
1199) -> Result<()>
1200where
1201    G: KimchiCurve,
1202    G::BaseField: PrimeField,
1203    EFqSponge: Clone + FqSponge<G::BaseField, G, G::ScalarField>,
1204    EFrSponge: FrSponge<G::ScalarField>,
1205{
1206    //~ #### Batch verification of proofs
1207    //~
1208    //~ Below, we define the steps to verify a number of proofs
1209    //~ (each associated to a [verifier index](#verifier-index)).
1210    //~ You can, of course, use it to verify a single proof.
1211    //~
1212
1213    //~ 1. If there's no proof to verify, the proof validates trivially.
1214    if proofs.is_empty() {
1215        return Ok(());
1216    }
1217
1218    //~ 1. Ensure that all the proof's verifier index have a URS of the same length. (TODO: do they have to be the same URS though? should we check for that?)
1219    // TODO: Account for the different SRS lengths
1220    let srs = proofs[0].verifier_index.srs();
1221    for &Context { verifier_index, .. } in proofs {
1222        if verifier_index.srs().max_poly_size() != srs.max_poly_size() {
1223            return Err(VerifyError::DifferentSRS);
1224        }
1225    }
1226
1227    //~ 1. Validate each proof separately following the [partial verification](#partial-verification) steps.
1228    let mut batch = vec![];
1229    for &Context {
1230        verifier_index,
1231        proof,
1232        public_input,
1233    } in proofs
1234    {
1235        batch.push(to_batch::<G, EFqSponge, EFrSponge, OpeningProof>(
1236            verifier_index,
1237            proof,
1238            public_input,
1239        )?);
1240    }
1241
1242    //~ 1. Use the [`PolyCom.verify`](#polynomial-commitments) to verify the partially evaluated proofs.
1243    if OpeningProof::verify(srs, group_map, &mut batch, &mut thread_rng()) {
1244        Ok(())
1245    } else {
1246        Err(VerifyError::OpenProof)
1247    }
1248}