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