jf_plonk/proof_system/
verifier.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the Jellyfish library.
3
4// You should have received a copy of the MIT License
5// along with the Jellyfish library. If not, see <https://mit-license.org/>.
6
7use super::structs::{
8    BatchProof, Challenges, PlookupProof, ProofEvaluations, ScalarsAndBases, VerifyingKey,
9};
10use crate::{
11    constants::*,
12    errors::{PlonkError, SnarkError::ParameterError},
13    lagrange::LagrangeCoeffs,
14    proof_system::structs::{eval_merged_lookup_witness, eval_merged_table, OpenKey},
15    transcript::*,
16};
17use ark_ec::{
18    pairing::Pairing,
19    short_weierstrass::{Affine, SWCurveConfig},
20};
21use ark_ff::{Field, One, Zero};
22use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
23use ark_std::{format, vec, vec::Vec};
24use core::ops::Neg;
25use jf_pcs::prelude::Commitment;
26use jf_relation::{constants::GATE_WIDTH, gadgets::ecc::SWToTEConParam};
27use jf_rescue::RescueParameter;
28use jf_utils::multi_pairing;
29
30/// (Aggregated) polynomial commitment evaluation info.
31/// * `u` - a random combiner that was used to combine evaluations at point
32///   `eval_point` and `next_eval_point`.
33/// * `eval_point` - the point to be evaluated at.
34/// * `next_eval_point` - the shifted point to be evaluated at.
35/// * `eval` - the (aggregated) polynomial evaluation value.
36/// * `comm_scalars_and_bases` - the scalars-and-bases form of the (aggregated)
37///   polynomial commitment.
38/// * `opening_proof` - (aggregated) proof of evaluations at point `eval_point`.
39/// * `shifted_opening_proof` - (aggregated) proof of evaluations at point
40///   `next_eval_point`.
41#[derive(Debug)]
42pub(crate) struct PcsInfo<E: Pairing> {
43    pub(crate) u: E::ScalarField,
44    pub(crate) eval_point: E::ScalarField,
45    pub(crate) next_eval_point: E::ScalarField,
46    pub(crate) eval: E::ScalarField,
47    pub(crate) comm_scalars_and_bases: ScalarsAndBases<E>,
48    pub(crate) opening_proof: Commitment<E>,
49    pub(crate) shifted_opening_proof: Commitment<E>,
50}
51
52pub(crate) struct Verifier<E: Pairing> {
53    pub(crate) domain: Radix2EvaluationDomain<E::ScalarField>,
54}
55
56impl<E, F, P> Verifier<E>
57where
58    E: Pairing<BaseField = F, G1Affine = Affine<P>>,
59    F: RescueParameter + SWToTEConParam,
60    P: SWCurveConfig<BaseField = F>,
61{
62    /// Construct a Plonk verifier that uses a domain with size `domain_size`.
63    pub(crate) fn new(domain_size: usize) -> Result<Self, PlonkError> {
64        let domain = Radix2EvaluationDomain::<E::ScalarField>::new(domain_size)
65            .ok_or(PlonkError::DomainCreationError)?;
66        Ok(Self { domain })
67    }
68
69    /// Prepare the (aggregated) polynomial commitment evaluation information.
70    pub(crate) fn prepare_pcs_info<T>(
71        &self,
72        verify_keys: &[&VerifyingKey<E>],
73        public_inputs: &[&[E::ScalarField]],
74        batch_proof: &BatchProof<E>,
75        extra_transcript_init_msg: &Option<Vec<u8>>,
76    ) -> Result<PcsInfo<E>, PlonkError>
77    where
78        T: PlonkTranscript<F>,
79    {
80        if verify_keys.len() != batch_proof.len()
81            || verify_keys.len() != public_inputs.len()
82            || verify_keys.is_empty()
83        {
84            return Err(ParameterError(format!(
85                "the number of verification keys = {}; the number of instances = {}; the number of public inputs = {}",
86                verify_keys.len(),
87                batch_proof.len(),
88                public_inputs.len(),
89            ))
90            .into());
91        }
92        for (i, (&pub_input, &vk)) in public_inputs.iter().zip(verify_keys.iter()).enumerate() {
93            if pub_input.len() != vk.num_inputs {
94                return Err(ParameterError(
95                    format!("the circuit public input length {} != the {}-th verification key public input length {}", pub_input.len(), i, vk.num_inputs)
96                )
97                .into());
98            }
99            if vk.plookup_vk.is_some() != batch_proof.plookup_proofs_vec[i].is_some() {
100                return Err(ParameterError(format!(
101                    "Mismatched proof type and verification key type for the {i}-th instance",
102                ))
103                .into());
104            }
105            if vk.domain_size != self.domain.size() {
106                return Err(ParameterError(format!(
107                    "the domain size {} of the {}-th verification key is different from {}",
108                    vk.domain_size,
109                    i,
110                    self.domain.size(),
111                ))
112                .into());
113            }
114        }
115
116        // compute challenges and evaluations
117        let challenges = Self::compute_challenges::<T>(
118            verify_keys,
119            public_inputs,
120            batch_proof,
121            extra_transcript_init_msg,
122        )?;
123
124        // pre-compute alpha related values
125        let alpha_2 = challenges.alpha.square();
126        let alpha_3 = alpha_2 * challenges.alpha;
127        let alpha_4 = alpha_2 * alpha_2;
128        let alpha_5 = alpha_2 * alpha_3;
129        let alpha_6 = alpha_4 * alpha_2;
130        let alpha_7 = alpha_3 * alpha_4;
131        let alpha_powers = vec![alpha_2, alpha_3, alpha_4, alpha_5, alpha_6];
132        let mut alpha_bases = vec![E::ScalarField::one()];
133
134        let mut tmp = if verify_keys[0].plookup_vk.is_some() {
135            alpha_7
136        } else {
137            alpha_3
138        };
139        if verify_keys.len() > 1 {
140            for _ in 0..verify_keys.len() - 1 {
141                alpha_bases.push(tmp);
142                tmp *= alpha_bases[1];
143            }
144        }
145
146        let vanish_eval = self.evaluate_vanishing_poly(&challenges.zeta);
147        let (lagrange_1_eval, lagrange_n_eval) =
148            self.domain.first_and_last_lagrange_coeffs(challenges.zeta);
149
150        // compute the constant term of the linearization polynomial
151        let lin_poly_constant = self.compute_lin_poly_constant_term(
152            &challenges,
153            verify_keys,
154            public_inputs,
155            batch_proof,
156            &lagrange_1_eval,
157            &lagrange_n_eval,
158            &alpha_powers,
159            &alpha_bases,
160        )?;
161
162        // build the (aggregated) polynomial commitment/evaluation instance
163        let (comm_scalars_and_bases, buffer_v_and_uv_basis) = self.aggregate_poly_commitments(
164            verify_keys,
165            &challenges,
166            &vanish_eval,
167            &lagrange_1_eval,
168            &lagrange_n_eval,
169            batch_proof,
170            &alpha_powers,
171            &alpha_bases,
172        )?;
173        let eval = Self::aggregate_evaluations(
174            &lin_poly_constant,
175            &batch_proof.poly_evals_vec,
176            &batch_proof.plookup_proofs_vec,
177            &buffer_v_and_uv_basis,
178        )?;
179
180        Ok(PcsInfo {
181            u: challenges.u,
182            eval_point: challenges.zeta,
183            next_eval_point: challenges.zeta * self.domain.group_gen,
184            comm_scalars_and_bases,
185            eval,
186            opening_proof: batch_proof.opening_proof,
187            shifted_opening_proof: batch_proof.shifted_opening_proof,
188        })
189    }
190
191    /// Batchly verify multiple (aggregated) PCS opening proofs.
192    ///
193    /// We need to verify that:
194    /// - `e(Ai, [x]2) = e(Bi, [1]2) for i \in {0, .., m-1}`, where
195    /// - `Ai = [open_proof_i] + u_i * [shifted_open_proof_i]` and
196    /// - `Bi = eval_point_i * [open_proof_i] + u_i * next_eval_point_i *
197    ///   [shifted_open_proof_i] + comm_i - eval_i * [1]1`.
198    ///
199    /// By Schwartz-Zippel lemma, it's equivalent to check that for a random r:
200    /// - `e(A0 + ... + r^{m-1} * Am, [x]2) = e(B0 + ... + r^{m-1} * Bm, [1]2)`.
201    pub(crate) fn batch_verify_opening_proofs<T>(
202        open_key: &OpenKey<E>,
203        pcs_infos: &[PcsInfo<E>],
204    ) -> Result<bool, PlonkError>
205    where
206        T: PlonkTranscript<F>,
207    {
208        // Compute a pseudorandom challenge from the instances
209        let r = if pcs_infos.len() == 1 {
210            // No need to use `r` when there is only a single proof.
211            E::ScalarField::one()
212        } else {
213            let mut transcript = T::new(b"batch verify");
214            // r := hash(u1||u2||...||u_m), where u_i is the hash output of the i-th Plonk
215            // protocol transcript. This approach is more secure as `r` depends not only
216            // on the proofs, but also the list of public inputs and verifying keys.
217            for pcs_info in pcs_infos {
218                transcript.append_field_elem::<E>(b"u", &pcs_info.u)?;
219            }
220            transcript.get_challenge::<E>(b"r")?
221        };
222
223        // Compute A := A0 + r * A1 + ... + r^{m-1} * Am
224        let mut inners = ScalarsAndBases::<E>::new();
225        let mut r_base = E::ScalarField::one();
226        for pcs_info in pcs_infos.iter() {
227            inners.push(r_base, pcs_info.opening_proof.0);
228            inners.push(r_base * pcs_info.u, pcs_info.shifted_opening_proof.0);
229            r_base *= r;
230        }
231        let inner = inners.multi_scalar_mul();
232        // Add (A, [x]2) to the product pairing list
233        let mut g1_elems: Vec<<E as Pairing>::G1Affine> = vec![inner.into()];
234        let mut g2_elems = vec![open_key.beta_h];
235
236        // Compute B := B0 + r * B1 + ... + r^{m-1} * Bm
237        let mut inners = ScalarsAndBases::new();
238        let mut r_base = E::ScalarField::one();
239        let mut sum_evals = E::ScalarField::zero();
240        for pcs_info in pcs_infos.iter() {
241            inners.merge(r_base, &pcs_info.comm_scalars_and_bases);
242            inners.push(r_base * pcs_info.eval_point, pcs_info.opening_proof.0);
243            inners.push(
244                r_base * pcs_info.u * pcs_info.next_eval_point,
245                pcs_info.shifted_opening_proof.0,
246            );
247            sum_evals += r_base * pcs_info.eval;
248            r_base *= r;
249        }
250        inners.push(-sum_evals, open_key.g);
251        let inner = inners.multi_scalar_mul();
252        // Add (-B, [1]2) to the product pairing list
253        g1_elems.push(-inner.into());
254        g2_elems.push(open_key.h);
255        // Check e(A, [x]2) ?= e(B, [1]2)
256        Ok(multi_pairing::<E>(&g1_elems, &g2_elems).0 == E::TargetField::one())
257    }
258
259    /// Compute verifier challenges `tau`, `beta`, `gamma`, `alpha`, `zeta`,
260    /// 'v', 'u'.
261    #[inline]
262    pub(crate) fn compute_challenges<T>(
263        verify_keys: &[&VerifyingKey<E>],
264        public_inputs: &[&[E::ScalarField]],
265        batch_proof: &BatchProof<E>,
266        extra_transcript_init_msg: &Option<Vec<u8>>,
267    ) -> Result<Challenges<E::ScalarField>, PlonkError>
268    where
269        T: PlonkTranscript<F>,
270    {
271        if verify_keys.len() != batch_proof.len() || verify_keys.len() != public_inputs.len() {
272            return Err(ParameterError(format!(
273                "the number of verification keys = {}; the number of instances = {}; the number of public inputs = {}",
274                verify_keys.len(),
275                batch_proof.len(),
276                public_inputs.len(),
277            ))
278            .into());
279        }
280        let mut transcript = T::new(b"PlonkProof");
281        if let Some(msg) = extra_transcript_init_msg {
282            transcript.append_message(EXTRA_TRANSCRIPT_MSG_LABEL, msg)?;
283        }
284        for (&vk, &pi) in verify_keys.iter().zip(public_inputs.iter()) {
285            transcript.append_vk_and_pub_input(vk, pi)?;
286        }
287        for wires_poly_comms in batch_proof.wires_poly_comms_vec.iter() {
288            transcript.append_commitments(b"witness_poly_comms", wires_poly_comms)?;
289        }
290        let tau = if verify_keys.iter().any(|vk| vk.plookup_vk.is_some()) {
291            Some(transcript.get_challenge::<E>(b"tau")?)
292        } else {
293            None
294        };
295
296        for plookup_proof in batch_proof.plookup_proofs_vec.iter() {
297            if let Some(proof_lkup) = plookup_proof.as_ref() {
298                transcript.append_commitments(b"h_poly_comms", &proof_lkup.h_poly_comms)?;
299            }
300        }
301
302        let beta = transcript.get_challenge::<E>(b"beta")?;
303        let gamma = transcript.get_challenge::<E>(b"gamma")?;
304        for prod_perm_poly_comm in batch_proof.prod_perm_poly_comms_vec.iter() {
305            transcript.append_commitment(b"perm_poly_comms", prod_perm_poly_comm)?;
306        }
307        for plookup_proof in batch_proof.plookup_proofs_vec.iter() {
308            if let Some(proof_lkup) = plookup_proof.as_ref() {
309                transcript
310                    .append_commitment(b"plookup_poly_comms", &proof_lkup.prod_lookup_poly_comm)?;
311            }
312        }
313
314        let alpha = transcript.get_challenge::<E>(b"alpha")?;
315        transcript.append_commitments(b"quot_poly_comms", &batch_proof.split_quot_poly_comms)?;
316        let zeta = transcript.get_challenge::<E>(b"zeta")?;
317        for poly_evals in batch_proof.poly_evals_vec.iter() {
318            transcript.append_proof_evaluations::<E>(poly_evals)?;
319        }
320        for plookup_proof in batch_proof.plookup_proofs_vec.iter() {
321            if let Some(proof_lkup) = plookup_proof.as_ref() {
322                transcript.append_plookup_evaluations::<E>(&proof_lkup.poly_evals)?;
323            }
324        }
325
326        let v = transcript.get_challenge::<E>(b"v")?;
327        transcript.append_commitment(b"open_proof", &batch_proof.opening_proof)?;
328        transcript.append_commitment(b"shifted_open_proof", &batch_proof.shifted_opening_proof)?;
329        let u = transcript.get_challenge::<E>(b"u")?;
330        Ok(Challenges {
331            tau,
332            alpha,
333            beta,
334            gamma,
335            zeta,
336            v,
337            u,
338        })
339    }
340
341    /// Compute the constant term of the linearization polynomial:
342    /// For each instance j:
343    ///
344    /// r_plonk_j = PI - L1(x) * alpha^2 -
345    ///             alpha * \prod_i=1..m-1 (w_{j,i} + beta * sigma_{j,i} +
346    /// gamma) * (w_{j,m} + gamma) * z_j(xw)
347    ///
348    /// r_lookup_j = alpha^3 * Ln(x) * (h1_x_j - h2_wx_j) -
349    ///              alpha^4 * L1(x) * alpha -
350    ///              alpha^5 * Ln(x) -
351    ///              alpha^6 * (x - g^{n-1}) * prod_poly_wx_j * [gamma(1+beta) +
352    /// h1_x_j + beta * h1_wx_j] * [gamma(1+beta) + beta * h2_wx_j]
353    ///
354    /// r_0 = \sum_{j=1..m} alpha^{k_j} * (r_plonk_j + (r_lookup_j))
355    /// where m is the number of instances, and k_j is the number of alpha power
356    /// terms added to the first j-1 instances.
357    #[allow(clippy::too_many_arguments)]
358    pub(crate) fn compute_lin_poly_constant_term(
359        &self,
360        challenges: &Challenges<E::ScalarField>,
361        verify_keys: &[&VerifyingKey<E>],
362        public_inputs: &[&[E::ScalarField]],
363        batch_proof: &BatchProof<E>,
364        lagrange_1_eval: &E::ScalarField,
365        lagrange_n_eval: &E::ScalarField,
366        alpha_powers: &[E::ScalarField],
367        alpha_bases: &[E::ScalarField],
368    ) -> Result<E::ScalarField, PlonkError> {
369        if verify_keys.len() != batch_proof.len()
370            || verify_keys.len() != public_inputs.len()
371            || verify_keys.len() != alpha_bases.len()
372        {
373            return Err(ParameterError(format!(
374                "the number of verification keys = {}; the number of instances = {}; the number of public inputs = {}; the number of alpha bases = {}",
375                verify_keys.len(),
376                batch_proof.len(),
377                public_inputs.len(),
378                alpha_bases.len()
379            ))
380            .into());
381        }
382
383        let mut result = E::ScalarField::zero();
384        for (poly_evals, (plookup_proof, (&pi, (&vk, &current_alpha_bases)))) in
385            batch_proof.poly_evals_vec.iter().zip(
386                batch_proof.plookup_proofs_vec.iter().zip(
387                    public_inputs
388                        .iter()
389                        .zip(verify_keys.iter().zip(alpha_bases.iter())),
390                ),
391            )
392        {
393            let mut tmp = self.evaluate_pi_poly(pi, &challenges.zeta, vk.is_merged)?
394                - alpha_powers[0] * lagrange_1_eval;
395            let num_wire_types = GATE_WIDTH
396                + 1
397                + match plookup_proof.is_some() {
398                    true => 1,
399                    false => 0,
400                };
401            let first_w_evals = &poly_evals.wires_evals[..num_wire_types - 1];
402            let last_w_eval = &poly_evals.wires_evals[num_wire_types - 1];
403            let sigma_evals = &poly_evals.wire_sigma_evals[..];
404            tmp -= first_w_evals.iter().zip(sigma_evals.iter()).fold(
405                challenges.alpha * poly_evals.perm_next_eval * (challenges.gamma + last_w_eval),
406                |acc, (w_eval, sigma_eval)| {
407                    acc * (challenges.gamma + w_eval + challenges.beta * sigma_eval)
408                },
409            );
410
411            if let Some(proof_lk) = plookup_proof {
412                let gamma_mul_beta_plus_one =
413                    challenges.gamma * (E::ScalarField::one() + challenges.beta);
414                let evals = &proof_lk.poly_evals;
415
416                let plookup_constant = *lagrange_n_eval
417                    * (evals.h_1_eval - evals.h_2_next_eval - alpha_powers[0])
418                    - challenges.alpha * lagrange_1_eval
419                    - alpha_powers[1]
420                        * (challenges.zeta - self.domain.group_gen_inv)
421                        * evals.prod_next_eval
422                        * (gamma_mul_beta_plus_one
423                            + evals.h_1_eval
424                            + challenges.beta * evals.h_1_next_eval)
425                        * (gamma_mul_beta_plus_one + challenges.beta * evals.h_2_next_eval);
426                tmp += alpha_powers[1] * plookup_constant;
427            }
428
429            result += current_alpha_bases * tmp;
430        }
431        Ok(result)
432    }
433
434    /// Aggregate polynomial commitments into a single commitment (in the
435    /// ScalarsAndBases form). Useful in batch opening.
436    /// The verification key type is guaranteed to match the Plonk proof type.
437    /// The returned commitment is a generalization of `[F]1` described in Sec 8.4, step 10 of https://eprint.iacr.org/2019/953.pdf
438    #[allow(clippy::too_many_arguments)]
439    pub(crate) fn aggregate_poly_commitments(
440        &self,
441        vks: &[&VerifyingKey<E>],
442        challenges: &Challenges<E::ScalarField>,
443        vanish_eval: &E::ScalarField,
444        lagrange_1_eval: &E::ScalarField,
445        lagrange_n_eval: &E::ScalarField,
446        batch_proof: &BatchProof<E>,
447        alpha_powers: &[E::ScalarField],
448        alpha_bases: &[E::ScalarField],
449    ) -> Result<(ScalarsAndBases<E>, Vec<E::ScalarField>), PlonkError> {
450        if vks.len() != batch_proof.len() {
451            return Err(ParameterError(format!(
452                "the number of verification keys {} != the number of instances {}",
453                vks.len(),
454                batch_proof.len()
455            ))
456            .into());
457        }
458
459        // Compute the first part of the batched polynomial commitment `[D]1` described in Sec 8.4, step 9 of https://eprint.iacr.org/2019/953.pdf
460        let mut scalars_and_bases = self.linearization_scalars_and_bases(
461            vks,
462            challenges,
463            vanish_eval,
464            lagrange_1_eval,
465            lagrange_n_eval,
466            batch_proof,
467            alpha_powers,
468            alpha_bases,
469        )?;
470
471        // the random combiner term for the polynomials evaluated at point `zeta`
472        let mut v_base = challenges.v;
473        // the random combiner term for the polynomials evaluated at point `zeta * g`
474        let mut uv_base = challenges.u;
475
476        // return buffer for aggregate_evaluations computation
477        let mut buffer_v_and_uv_basis = vec![];
478
479        for (i, vk) in vks.iter().enumerate() {
480            // Add poly commitments to be evaluated at point `zeta`.
481            // Add wire witness polynomial commitments.
482            for &poly_comm in batch_proof.wires_poly_comms_vec[i].iter() {
483                buffer_v_and_uv_basis.push(v_base);
484                Self::add_poly_comm(
485                    &mut scalars_and_bases,
486                    &mut v_base,
487                    poly_comm.0,
488                    challenges.v,
489                );
490            }
491            // Add wire sigma polynomial commitments. The last sigma commitment is excluded.
492            let num_wire_types = batch_proof.wires_poly_comms_vec[i].len();
493            for &poly_comm in vk.sigma_comms.iter().take(num_wire_types - 1) {
494                buffer_v_and_uv_basis.push(v_base);
495                Self::add_poly_comm(
496                    &mut scalars_and_bases,
497                    &mut v_base,
498                    poly_comm.0,
499                    challenges.v,
500                );
501            }
502
503            // Add poly commitments to be evaluated at point `zeta * g`.
504            buffer_v_and_uv_basis.push(uv_base);
505            Self::add_poly_comm(
506                &mut scalars_and_bases,
507                &mut uv_base,
508                batch_proof.prod_perm_poly_comms_vec[i].0,
509                challenges.v,
510            );
511
512            // Add Plookup polynomial commitments
513            if let Some(proof_lkup) = batch_proof.plookup_proofs_vec[i].as_ref() {
514                // add commitments to be evaluated at point `zeta`
515                let plookup_comms = Self::plookup_open_poly_comms(proof_lkup, vk)?;
516                for &comm in plookup_comms.iter() {
517                    buffer_v_and_uv_basis.push(v_base);
518                    Self::add_poly_comm(&mut scalars_and_bases, &mut v_base, comm.0, challenges.v);
519                }
520
521                // add commitments to be evaluated at point `zeta * g`
522                let plookup_shifted_comms = Self::plookup_shifted_open_poly_comms(
523                    proof_lkup,
524                    vk,
525                    &batch_proof.wires_poly_comms_vec[i],
526                )?;
527                for &comm in plookup_shifted_comms.iter() {
528                    buffer_v_and_uv_basis.push(uv_base);
529                    Self::add_poly_comm(&mut scalars_and_bases, &mut uv_base, comm.0, challenges.v);
530                }
531            }
532        }
533
534        Ok((scalars_and_bases, buffer_v_and_uv_basis))
535    }
536
537    /// Compute the bases and scalars in the batched polynomial commitment,
538    /// which is a generalization of `[D]1` specified in Sec 8.3, Verifier
539    /// algorithm step 9 of https://eprint.iacr.org/2019/953.pdf.
540    #[allow(clippy::too_many_arguments)]
541    pub(crate) fn linearization_scalars_and_bases(
542        &self,
543        vks: &[&VerifyingKey<E>],
544        challenges: &Challenges<E::ScalarField>,
545        vanish_eval: &E::ScalarField,
546        lagrange_1_eval: &E::ScalarField,
547        lagrange_n_eval: &E::ScalarField,
548        batch_proof: &BatchProof<E>,
549        alpha_powers: &[E::ScalarField],
550        alpha_bases: &[E::ScalarField],
551    ) -> Result<ScalarsAndBases<E>, PlonkError> {
552        if vks.len() != batch_proof.len() || alpha_bases.len() != vks.len() {
553            return Err(ParameterError(format!(
554                "the number of verification keys {} != the number of instances {} or alpha bases {}",
555                vks.len(),
556                batch_proof.len(),
557                alpha_bases.len()
558            ))
559            .into());
560        }
561
562        // compute constants that are being reused
563        let beta_plus_one = E::ScalarField::one() + challenges.beta;
564        let gamma_mul_beta_plus_one = beta_plus_one * challenges.gamma;
565
566        let mut scalars_and_bases = ScalarsAndBases::new();
567
568        for (i, (vk, &current_alpha_bases)) in vks.iter().zip(alpha_bases).enumerate() {
569            // Compute coefficient for the permutation product polynomial commitment.
570            // coeff = L1(zeta) * alpha^2
571            //       + alpha
572            //       * (beta * zeta      + a_bar + gamma)
573            //       * (beta * k1 * zeta + b_bar + gamma)
574            //       * (beta * k2 * zeta + c_bar + gamma)
575            // where a_bar, b_bar and c_bar are in w_evals
576            let mut coeff = alpha_powers[0] * lagrange_1_eval;
577            let w_evals = &batch_proof.poly_evals_vec[i].wires_evals;
578            coeff += w_evals
579                .iter()
580                .zip(vk.k.iter())
581                .fold(challenges.alpha, |acc, (w_eval, k)| {
582                    acc * (challenges.beta * k * challenges.zeta + challenges.gamma + w_eval)
583                });
584            coeff *= current_alpha_bases;
585            // Add permutation product polynomial commitment.
586            scalars_and_bases.push(coeff, batch_proof.prod_perm_poly_comms_vec[i].0);
587
588            // Compute coefficient for the last wire sigma polynomial commitment.
589            let num_wire_types = batch_proof.wires_poly_comms_vec[i].len();
590            let sigma_evals = &batch_proof.poly_evals_vec[i].wire_sigma_evals;
591            let coeff = w_evals
592                .iter()
593                .take(num_wire_types - 1)
594                .zip(sigma_evals.iter())
595                .fold(
596                    challenges.alpha
597                        * challenges.beta
598                        * batch_proof.poly_evals_vec[i].perm_next_eval,
599                    |acc, (w_eval, sigma_eval)| {
600                        acc * (challenges.beta * sigma_eval + challenges.gamma + w_eval)
601                    },
602                )
603                * current_alpha_bases;
604            // Add output wire sigma polynomial commitment.
605            scalars_and_bases.push(
606                -coeff,
607                vk.sigma_comms.last().ok_or(PlonkError::IndexError)?.0,
608            );
609
610            // Add selector polynomial commitments.
611            // Compute coefficients for selector polynomial commitments.
612            // The order: q_lc, q_mul, q_hash, q_o, q_c, q_ecc
613            // TODO(binyi): get the order from a function.
614            let mut q_scalars = [E::ScalarField::zero(); 2 * GATE_WIDTH + 5];
615            q_scalars[0] = w_evals[0];
616            q_scalars[1] = w_evals[1];
617            q_scalars[2] = w_evals[2];
618            q_scalars[3] = w_evals[3];
619            q_scalars[4] = w_evals[0] * w_evals[1];
620            q_scalars[5] = w_evals[2] * w_evals[3];
621            q_scalars[6] = w_evals[0].pow([5]);
622            q_scalars[7] = w_evals[1].pow([5]);
623            q_scalars[8] = w_evals[2].pow([5]);
624            q_scalars[9] = w_evals[3].pow([5]);
625            q_scalars[10] = -w_evals[4];
626            q_scalars[11] = E::ScalarField::one();
627            q_scalars[12] = w_evals[0] * w_evals[1] * w_evals[2] * w_evals[3] * w_evals[4];
628            for (&s, poly) in q_scalars.iter().zip(vk.selector_comms.iter()) {
629                scalars_and_bases.push(s * current_alpha_bases, poly.0);
630            }
631
632            // Add Plookup related commitments
633            if let Some(lookup_proof) = batch_proof.plookup_proofs_vec[i].as_ref() {
634                let lookup_evals = &lookup_proof.poly_evals;
635                let merged_lookup_x = eval_merged_lookup_witness::<E>(
636                    challenges.tau.unwrap(),
637                    w_evals[5],
638                    w_evals[0],
639                    w_evals[1],
640                    w_evals[2],
641                    lookup_evals.q_lookup_eval,
642                    lookup_evals.q_dom_sep_eval,
643                );
644                let merged_table_x = eval_merged_table::<E>(
645                    challenges.tau.unwrap(),
646                    lookup_evals.range_table_eval,
647                    lookup_evals.key_table_eval,
648                    lookup_evals.q_lookup_eval,
649                    w_evals[3],
650                    w_evals[4],
651                    lookup_evals.table_dom_sep_eval,
652                );
653                let merged_table_xw = eval_merged_table::<E>(
654                    challenges.tau.unwrap(),
655                    lookup_evals.range_table_next_eval,
656                    lookup_evals.key_table_next_eval,
657                    lookup_evals.q_lookup_next_eval,
658                    lookup_evals.w_3_next_eval,
659                    lookup_evals.w_4_next_eval,
660                    lookup_evals.table_dom_sep_next_eval,
661                );
662
663                // coefficient for prod_lookup_poly(X):
664                // coeff_lin_poly = alpha^4 * L1(x) +
665                //                  alpha^5 * Ln(x) +
666                //                  alpha^6 * (x - w^{n-1}) * (1+beta) * (gamma + lookup_w_eval)
667                //                  * (gamma(1+beta) + table_x + beta * table_xw),
668                let mut coeff = alpha_powers[2] * lagrange_1_eval
669                    + alpha_powers[3] * lagrange_n_eval
670                    + alpha_powers[4]
671                        * (challenges.zeta - self.domain.group_gen_inv)
672                        * beta_plus_one
673                        * (challenges.gamma + merged_lookup_x)
674                        * (gamma_mul_beta_plus_one
675                            + merged_table_x
676                            + challenges.beta * merged_table_xw);
677                coeff = current_alpha_bases * coeff;
678                scalars_and_bases.push(coeff, lookup_proof.prod_lookup_poly_comm.0);
679
680                // coefficient for h2(X):
681                // coeff_lin_poly = alpha_base * alpha^6 * (w^{n-1} - x)
682                //                  * prod_lookup_poly_xw
683                //                  * [gamma(1+beta) + h1_x + beta * h1_xw]
684                let coeff = current_alpha_bases
685                    * alpha_powers[4]
686                    * (self.domain.group_gen_inv - challenges.zeta)
687                    * lookup_evals.prod_next_eval
688                    * (gamma_mul_beta_plus_one
689                        + lookup_evals.h_1_eval
690                        + challenges.beta * lookup_evals.h_1_next_eval);
691                scalars_and_bases.push(coeff, lookup_proof.h_poly_comms[1].0);
692            }
693        }
694
695        // Add split quotient commitments
696        let zeta_to_n_plus_2 =
697            (E::ScalarField::one() + vanish_eval) * challenges.zeta * challenges.zeta;
698        let mut coeff = vanish_eval.neg();
699        scalars_and_bases.push(
700            coeff,
701            batch_proof
702                .split_quot_poly_comms
703                .first()
704                .ok_or(PlonkError::IndexError)?
705                .0,
706        );
707        for poly in batch_proof.split_quot_poly_comms.iter().skip(1) {
708            coeff *= zeta_to_n_plus_2;
709            scalars_and_bases.push(coeff, poly.0);
710        }
711
712        Ok(scalars_and_bases)
713    }
714
715    /// Combine the polynomial evaluations into a single evaluation. Useful in
716    /// batch opening.
717    /// The returned value is the scalar in `[E]1` described in Sec 8.4, step 11 of https://eprint.iacr.org/2019/953.pdf
718    pub(crate) fn aggregate_evaluations(
719        lin_poly_constant: &E::ScalarField,
720        poly_evals_vec: &[ProofEvaluations<E::ScalarField>],
721        plookup_proofs_vec: &[Option<PlookupProof<E>>],
722        buffer_v_and_uv_basis: &[E::ScalarField],
723    ) -> Result<E::ScalarField, PlonkError> {
724        assert_eq!(poly_evals_vec.len(), plookup_proofs_vec.len());
725
726        let mut result: E::ScalarField = lin_poly_constant.neg();
727        let mut v_and_uv_basis = buffer_v_and_uv_basis.iter();
728
729        for (poly_evals, plookup_proof) in poly_evals_vec.iter().zip(plookup_proofs_vec.iter()) {
730            // evaluations at point `zeta`
731            for &wire_eval in poly_evals.wires_evals.iter() {
732                Self::add_pcs_eval(
733                    &mut result,
734                    v_and_uv_basis
735                        .next()
736                        .ok_or(PlonkError::IteratorOutOfRange)?,
737                    wire_eval,
738                );
739            }
740            for &sigma_eval in poly_evals.wire_sigma_evals.iter() {
741                Self::add_pcs_eval(
742                    &mut result,
743                    v_and_uv_basis
744                        .next()
745                        .ok_or(PlonkError::IteratorOutOfRange)?,
746                    sigma_eval,
747                );
748            }
749            // evaluations at point `zeta * g`
750            Self::add_pcs_eval(
751                &mut result,
752                v_and_uv_basis
753                    .next()
754                    .ok_or(PlonkError::IteratorOutOfRange)?,
755                poly_evals.perm_next_eval,
756            );
757
758            // add Plookup related polynomial evaluations
759            if let Some(proof_lk) = plookup_proof {
760                let evals = &proof_lk.poly_evals;
761                // evaluations at point `zeta`
762                for &eval in evals.evals_vec().iter() {
763                    Self::add_pcs_eval(
764                        &mut result,
765                        v_and_uv_basis
766                            .next()
767                            .ok_or(PlonkError::IteratorOutOfRange)?,
768                        eval,
769                    );
770                }
771                // evaluations at point `zeta * g`
772                for &next_eval in evals.next_evals_vec().iter() {
773                    Self::add_pcs_eval(
774                        &mut result,
775                        v_and_uv_basis
776                            .next()
777                            .ok_or(PlonkError::IteratorOutOfRange)?,
778                        next_eval,
779                    );
780                }
781            }
782        }
783        // ensure all the buffer has been consumed
784        if v_and_uv_basis.next().is_some() {
785            return Err(PlonkError::IteratorOutOfRange);
786        }
787        Ok(result)
788    }
789}
790
791/// Private helper methods
792impl<E, F, P> Verifier<E>
793where
794    E: Pairing<BaseField = F, G1Affine = Affine<P>>,
795    F: RescueParameter + SWToTEConParam,
796    P: SWCurveConfig<BaseField = F>,
797{
798    /// Merge a polynomial commitment into the aggregated polynomial commitment
799    /// (in the ScalarAndBases form), update the random combiner afterward.
800    #[inline]
801    fn add_poly_comm(
802        scalar_and_bases: &mut ScalarsAndBases<E>,
803        random_combiner: &mut E::ScalarField,
804        comm: E::G1Affine,
805        r: E::ScalarField,
806    ) {
807        scalar_and_bases.push(*random_combiner, comm);
808        *random_combiner *= r;
809    }
810
811    /// Add a polynomial commitment evaluation value to the aggregated
812    /// polynomial evaluation, update the random combiner afterward.
813    #[inline]
814    fn add_pcs_eval(
815        result: &mut E::ScalarField,
816        random_combiner: &E::ScalarField,
817        eval: E::ScalarField,
818    ) {
819        *result += eval * (*random_combiner);
820    }
821
822    /// Evaluate vanishing polynomial at point `zeta`
823    #[inline]
824    pub(crate) fn evaluate_vanishing_poly(&self, zeta: &E::ScalarField) -> E::ScalarField {
825        self.domain.evaluate_vanishing_polynomial(*zeta)
826    }
827
828    #[inline]
829    /// Return the list of polynomial commitments to be opened at point `zeta`.
830    /// The order should be consistent with the prover side.
831    fn plookup_open_poly_comms(
832        proof: &PlookupProof<E>,
833        vk: &VerifyingKey<E>,
834    ) -> Result<Vec<Commitment<E>>, PlonkError> {
835        Ok(vec![
836            vk.plookup_vk.as_ref().unwrap().range_table_comm,
837            vk.plookup_vk.as_ref().unwrap().key_table_comm,
838            proof.h_poly_comms[0],
839            *vk.q_lookup_comm()?,
840            vk.plookup_vk.as_ref().unwrap().table_dom_sep_comm,
841            vk.plookup_vk.as_ref().unwrap().q_dom_sep_comm,
842        ])
843    }
844
845    #[inline]
846    /// Return the list of polynomial commitments to be opened at point `zeta *
847    /// g`. The order should be consistent with the prover side.
848    fn plookup_shifted_open_poly_comms(
849        proof: &PlookupProof<E>,
850        vk: &VerifyingKey<E>,
851        wires_poly_comms: &[Commitment<E>],
852    ) -> Result<Vec<Commitment<E>>, PlonkError> {
853        Ok(vec![
854            proof.prod_lookup_poly_comm,
855            vk.plookup_vk.as_ref().unwrap().range_table_comm,
856            vk.plookup_vk.as_ref().unwrap().key_table_comm,
857            proof.h_poly_comms[0],
858            proof.h_poly_comms[1],
859            *vk.q_lookup_comm()?,
860            wires_poly_comms[3],
861            wires_poly_comms[4],
862            vk.plookup_vk.as_ref().unwrap().table_dom_sep_comm,
863        ])
864    }
865
866    /// Evaluate public input polynomial at point `z`.
867    /// Define the following as
868    /// - H: The domain with generator g
869    /// - n: The size of the domain H
870    /// - Z_H: The vanishing polynomial for H.
871    /// - v_i: A sequence of values, where v_i = g^i / n
872    ///
873    /// We then compute L_{i,H}(z) as `L_{i,H}(z) = Z_H(z) * v_i / (z - g^i)`
874    /// The public input polynomial evaluation is:
875    ///
876    /// \sum_{i=0..l} L_{i,H}(z) * pub_input[i].
877    ///
878    /// For merged circuits, the evaluation is:
879    /// \sum_{i=0..l/2} L_{i,H}(z) * pub_input[i] + \sum_{i=0..l/2} L_{n-i,H}(z)
880    /// * pub_input[l/2+i]
881    ///
882    /// TODO: reuse the lagrange values
883    pub(crate) fn evaluate_pi_poly(
884        &self,
885        pub_input: &[E::ScalarField],
886        z: &E::ScalarField,
887        circuit_is_merged: bool,
888    ) -> Result<E::ScalarField, PlonkError> {
889        let len = match circuit_is_merged {
890            false => pub_input.len(),
891            true => pub_input.len() / 2,
892        };
893
894        let mut result = E::ScalarField::zero();
895        let lagrange_coeffs = self.domain.lagrange_coeffs_for_range(0..len, *z);
896        for (val, lagrange_i) in pub_input.iter().take(len).zip(lagrange_coeffs) {
897            result += lagrange_i * val;
898        }
899
900        if circuit_is_merged {
901            let n = self.domain.size();
902            let last_l_lagrange_coeff = self.domain.lagrange_coeffs_for_range(n - len..n, *z);
903            for (val, lagrange_n_minus_i) in pub_input
904                .iter()
905                .skip(len)
906                .zip(last_l_lagrange_coeff.iter().rev())
907            {
908                result += *lagrange_n_minus_i * *val;
909            }
910        }
911        Ok(result)
912    }
913}