Skip to main content

kimchi/
verifier.rs

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