jf_plonk/circuit/plonk_verifier/
poly.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
7//! Circuits for the polynomial evaluations within Plonk verifiers.
8use super::{
9    BatchProofVar, ChallengesFpElemVar, NonNativeFieldInfo, ScalarsAndBasesVar, VerifyingKeyVar,
10};
11use crate::errors::PlonkError;
12use ark_ec::pairing::Pairing;
13use ark_ff::PrimeField;
14use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
15use ark_std::{format, string::ToString, vec, vec::Vec, One};
16use jf_relation::{
17    constants::GATE_WIDTH,
18    gadgets::ultraplonk::mod_arith::{FpElem, FpElemVar},
19    CircuitError,
20    CircuitError::ParameterError,
21    PlonkCircuit,
22};
23use jf_utils::field_switching;
24
25/// This helper function generate the variables for the following data
26/// - Circuit evaluation of vanishing polynomial at point `zeta` i.e., output =
27///   zeta ^ domain_size - 1 mod Fr::modulus
28/// - Evaluations of the first and the last lagrange polynomial at point `zeta`
29///
30/// Note that outputs and zeta are both Fr element
31/// so this needs to be carried out over a non-native circuit
32/// using parameter m.
33/// The output is lifted to Fq and in the FpElemVar form for:
34///
35/// - zeta^n
36/// - zeta^n - 1
37/// - lagrange evaluation at 1
38///
39/// Note that evaluation at n is commented out as we don't need it for
40/// partial verification circuit.
41pub(super) fn evaluate_poly_helper<E, F>(
42    circuit: &mut PlonkCircuit<F>,
43    zeta_fp_elem_var: &FpElemVar<F>,
44    domain_size: usize,
45    non_native_field_info: NonNativeFieldInfo<F>,
46) -> Result<[FpElemVar<F>; 3], CircuitError>
47where
48    E: Pairing<BaseField = F>,
49    F: PrimeField,
50{
51    // constants
52    let domain_size_fp_elem = FpElem::new(
53        &F::from(domain_size as u64),
54        non_native_field_info.m,
55        non_native_field_info.two_power_m,
56    )?;
57
58    // zeta
59    let zeta = zeta_fp_elem_var.witness(circuit)?;
60    let zeta_fr = field_switching::<_, E::ScalarField>(&zeta);
61
62    // ================================
63    // compute zeta^n - 1
64    // ================================
65
66    // compute zeta^n for n = domain_size a power of 2
67    let mut ctr = 1;
68    let mut zeta_n_fp_elem_var = *zeta_fp_elem_var;
69    while ctr < domain_size {
70        ctr <<= 1;
71        zeta_n_fp_elem_var = circuit.mod_mul(
72            &zeta_n_fp_elem_var,
73            &zeta_n_fp_elem_var,
74            &non_native_field_info.modulus_fp_elem,
75        )?;
76    }
77
78    // to compute zeta^n -1 we need to compute it over Fr
79    // we cannot simply do
80    //  let zeta_n_minus_one_var = circuit.sub(zeta_n_var, circuit.one())?;
81    // since it may be overflowing if zeta_n = 0
82    //
83    // Option 1: to write the subtraction in non-native field
84    //
85    // Option 2, which is what is implemented here
86    // - if zeta_n = 0, output Fr::modulus - 1
87    // - else output zeta_n -1
88    // this circuit should still be cheaper than non-native circuit.
89    //
90    //
91    // Question(ZZ): second thought, this should be fine since we know that
92    // zeta !=0 mod fr with 1 - 1/|Fr| probability as zeta is a output from
93    // RO. Nonetheless, I am implementing it with non-native field first.
94    // We may switch to native field if this is fine...
95    //
96
97    // zeta^n = zeta_n_minus_1 + 1
98    let zeta_n = zeta_n_fp_elem_var.witness(circuit)?;
99    let zeta_n_minus_one = field_switching::<_, F>(
100        &(field_switching::<_, E::ScalarField>(&zeta_n) - E::ScalarField::one()),
101    );
102    let zeta_n_minus_one_fp_elem_var = FpElemVar::new_from_field_element(
103        circuit,
104        &zeta_n_minus_one,
105        non_native_field_info.m,
106        non_native_field_info.two_power_m,
107    )?;
108    let one_fp_elem = FpElem::new(
109        &F::one(),
110        non_native_field_info.m,
111        non_native_field_info.two_power_m,
112    )?;
113    let zeta_n_fp_elem_var_rec = circuit.mod_add_constant(
114        &zeta_n_minus_one_fp_elem_var,
115        &one_fp_elem,
116        &non_native_field_info.modulus_fp_elem,
117    )?;
118    zeta_n_fp_elem_var.enforce_equal(circuit, &zeta_n_fp_elem_var_rec)?;
119
120    // ================================
121    // evaluate lagrange at 1
122    //  lagrange_1_eval = (zeta^n - 1) / (zeta - 1) / domain_size
123    //
124    // which is proven via
125    //  domain_size * lagrange_1_eval * (zeta - 1) = zeta^n - 1 mod Fr::modulus
126    // ================================
127
128    // lagrange_1_eval
129    let zeta_n_minus_one = field_switching::<_, E::ScalarField>(&zeta_n_minus_one);
130    let divisor = E::ScalarField::from(domain_size as u64) * (zeta_fr - E::ScalarField::one());
131    let lagrange_1_eval = zeta_n_minus_one / divisor;
132    let lagrange_1_eval_fp_elem_var = FpElemVar::new_from_field_element(
133        circuit,
134        &field_switching(&lagrange_1_eval),
135        non_native_field_info.m,
136        non_native_field_info.two_power_m,
137    )?;
138
139    // zeta - 1
140    let zeta_minus_one_fr = zeta_fr - E::ScalarField::one();
141    let zeta_minus_one_fp_elem_var = FpElemVar::new_from_field_element(
142        circuit,
143        &field_switching(&zeta_minus_one_fr),
144        non_native_field_info.m,
145        non_native_field_info.two_power_m,
146    )?;
147    let zeta_fp_elem_var_rec = circuit.mod_add_constant(
148        &zeta_minus_one_fp_elem_var,
149        &one_fp_elem,
150        &non_native_field_info.modulus_fp_elem,
151    )?;
152    zeta_fp_elem_var.enforce_equal(circuit, &zeta_fp_elem_var_rec)?;
153
154    // left
155    let mut left = circuit.mod_mul_constant(
156        &lagrange_1_eval_fp_elem_var,
157        &domain_size_fp_elem,
158        &non_native_field_info.modulus_fp_elem,
159    )?;
160    left = circuit.mod_mul(
161        &left,
162        &zeta_minus_one_fp_elem_var,
163        &non_native_field_info.modulus_fp_elem,
164    )?;
165    left.enforce_equal(circuit, &zeta_n_minus_one_fp_elem_var)?;
166
167    Ok([
168        zeta_n_fp_elem_var,
169        zeta_n_minus_one_fp_elem_var,
170        lagrange_1_eval_fp_elem_var,
171    ])
172}
173
174/// Evaluate public input polynomial at point `z`.
175/// Define the following as
176/// - H: The domain with generator g
177/// - n: The size of the domain H
178/// - Z_H: The vanishing polynomial for H.
179/// - v_i: A sequence of values, where v_i = g^i / n
180///
181/// We then compute L_{i,H}(z) as `L_{i,H}(z) = Z_H(z) * v_i / (z - g^i)`
182/// The public input polynomial evaluation for the merged circuit is:
183///
184/// \sum_{i=0..l/2} L_{i,H}(z) * pub_input[i] +
185/// \sum_{i=0..l/2} L_{n-i,H}(z) * pub_input[l/2+i]
186pub(super) fn evaluate_pi_poly_circuit<E, F>(
187    circuit: &mut PlonkCircuit<F>,
188    domain_size: usize,
189    pub_inputs_fp_elem_var: &[FpElemVar<F>],
190    zeta_fp_elem_var: &FpElemVar<F>,
191    vanish_eval_fp_elem_var: &FpElemVar<F>,
192    circuit_is_merged: bool,
193    non_native_field_info: NonNativeFieldInfo<F>,
194) -> Result<FpElemVar<F>, CircuitError>
195where
196    E: Pairing<BaseField = F>,
197    F: PrimeField,
198{
199    // the circuit is already merged
200    if !circuit_is_merged {
201        return Err(CircuitError::ParameterError(
202            "Circuit should already been merged".to_string(),
203        ));
204    }
205    let len = pub_inputs_fp_elem_var.len() >> 1;
206
207    // constants
208    let zeta = field_switching::<_, E::ScalarField>(&zeta_fp_elem_var.witness(circuit)?);
209    let vanish_eval =
210        field_switching::<_, E::ScalarField>(&vanish_eval_fp_elem_var.witness(circuit)?);
211
212    // compute v_i = g^i / n in the clear
213    let domain = Radix2EvaluationDomain::<E::ScalarField>::new(domain_size).unwrap();
214    let v_i: Vec<E::ScalarField> = (0..domain_size)
215        .map(|x| domain.element(x) / E::ScalarField::from(domain_size as u64))
216        .collect();
217
218    // compute L_{i,H}(zeta) = Z_H(zeta) * v_i / (zeta - g^i)
219    // where Z_H(z) is the vanishing evaluation
220    // compute for both i in [0, len) and [domain_size-len, domain_size)
221    let mut lagrange_eval_fp_elem_var: Vec<FpElemVar<F>> = Vec::new();
222    let range = (0..len).chain(domain_size - len..domain_size);
223
224    for i in range {
225        // compute L_{i,H}(zeta) and related values in the clear
226        let v_i_fp_elem = FpElem::<F>::new(
227            &field_switching(&v_i[i]),
228            non_native_field_info.m,
229            non_native_field_info.two_power_m,
230        )?;
231        let g_i_fp_elem = FpElem::<F>::new(
232            &field_switching(&domain.element(i)),
233            non_native_field_info.m,
234            non_native_field_info.two_power_m,
235        )?;
236        let zeta_minus_gi = zeta - domain.element(i);
237        let eval_i = vanish_eval * v_i[i] / zeta_minus_gi;
238
239        // prove zeta_minus_gi = zeta - g^i
240        let zeta_minus_gi_elem_var = FpElemVar::new_from_field_element(
241            circuit,
242            &field_switching(&zeta_minus_gi),
243            non_native_field_info.m,
244            non_native_field_info.two_power_m,
245        )?;
246        let zeta_fp_elem_var_rec = circuit.mod_add_constant(
247            &zeta_minus_gi_elem_var,
248            &g_i_fp_elem,
249            &non_native_field_info.modulus_fp_elem,
250        )?;
251        zeta_fp_elem_var.enforce_equal(circuit, &zeta_fp_elem_var_rec)?;
252
253        // prove L_{i,H}(zeta) * zeta_minus_gi = Z_H(zeta) * v_i
254        let eval_i_fp_elem_var = FpElemVar::new_from_field_element(
255            circuit,
256            &field_switching(&eval_i),
257            non_native_field_info.m,
258            non_native_field_info.two_power_m,
259        )?;
260        let left = circuit.mod_mul(
261            &eval_i_fp_elem_var,
262            &zeta_minus_gi_elem_var,
263            &non_native_field_info.modulus_fp_elem,
264        )?;
265        let right = circuit.mod_mul_constant(
266            vanish_eval_fp_elem_var,
267            &v_i_fp_elem,
268            &non_native_field_info.modulus_fp_elem,
269        )?;
270        left.enforce_equal(circuit, &right)?;
271
272        // finish
273        lagrange_eval_fp_elem_var.push(eval_i_fp_elem_var);
274    }
275
276    // \sum_{i=0..l/2} L_{i,H}(z) * pub_input[i] + \sum_{i=0..l/2} L_{n-i,H}(z)
277    // * pub_input[l/2+i]
278    let mut res_i_fp_elem_var = Vec::new();
279    for i in 0..len {
280        let first_term = circuit.mod_mul(
281            &lagrange_eval_fp_elem_var[i],
282            &pub_inputs_fp_elem_var[i],
283            &non_native_field_info.modulus_fp_elem,
284        )?;
285        let second_term = circuit.mod_mul(
286            &lagrange_eval_fp_elem_var[(len << 1) - i - 1],
287            &pub_inputs_fp_elem_var[len + i],
288            &non_native_field_info.modulus_fp_elem,
289        )?;
290        res_i_fp_elem_var.push(first_term);
291        res_i_fp_elem_var.push(second_term);
292    }
293    let res = circuit.mod_add_vec(&res_i_fp_elem_var, &non_native_field_info.modulus_fp_elem)?;
294
295    Ok(res)
296}
297
298/// Compute the constant term of the linearization polynomial:
299/// For each instance j:
300///
301/// r_plonk_j
302///  = PI - L1(x) * alpha^2 - alpha *
303///  \prod_i=1..m-1 (w_{j,i} + beta * sigma_{j,i} + gamma)
304///  * (w_{j,m} + gamma) * z_j(xw)
305///
306/// return r_0 = \sum_{j=1..m} alpha^{k_j} * r_plonk_j
307/// where m is the number of instances, and k_j is the number of alpha power
308/// terms added to the first j-1 instances.
309///
310/// - input evals: zeta^n, zeta^n-1 and Lagrange evaluated at 1
311///
312/// Note that this function cannot evaluate plookup verification circuits.
313#[allow(clippy::too_many_arguments)]
314pub(super) fn compute_lin_poly_constant_term_circuit<E, F>(
315    circuit: &mut PlonkCircuit<F>,
316    domain_size: usize,
317    challenges: &ChallengesFpElemVar<F>,
318    verify_keys: &[&VerifyingKeyVar<E>],
319    public_inputs: &[&[FpElemVar<F>]],
320    batch_proof: &BatchProofVar<F>,
321    evals: &[FpElemVar<F>; 3],
322    alpha_bases: &[FpElemVar<F>],
323    non_native_field_info: NonNativeFieldInfo<F>,
324) -> Result<FpElemVar<F>, CircuitError>
325where
326    E: Pairing<BaseField = F>,
327    F: PrimeField,
328{
329    if verify_keys.len() != batch_proof.len() || verify_keys.len() != public_inputs.len() {
330        return Err(ParameterError(format!(
331            "the number of verification keys = {}; the number of instances = {}; the number of public inputs = {}",
332            verify_keys.len(),
333            batch_proof.len(),
334            public_inputs.len(),
335        )));
336    }
337
338    let zeta_fp_elem_var = challenges.zeta;
339
340    let mut alpha_bases_elem_var = alpha_bases.iter();
341    let mut r_0_components = Vec::new();
342
343    // making sure the public inputs are the same for all instances
344    let pi = public_inputs[0];
345    for &pi_i in public_inputs.iter().skip(1) {
346        if pi != pi_i {
347            Err(PlonkError::PublicInputsDoNotMatch)?;
348        }
349    }
350
351    // compute public inputs
352    let pi_fp_elem_var = evaluate_pi_poly_circuit::<E, F>(
353        circuit,
354        domain_size,
355        pi,
356        &zeta_fp_elem_var,
357        &evals[1],
358        true,
359        non_native_field_info,
360    )?;
361    let pi_fr = field_switching::<_, E::ScalarField>(&pi_fp_elem_var.witness(circuit)?);
362
363    // L1(x)*alpha_2
364    let l1_mul_alpha_2_fp_elem_var = circuit.mod_mul(
365        &evals[2],
366        &challenges.alphas[1],
367        &non_native_field_info.modulus_fp_elem,
368    )?;
369
370    let l1_mul_alpha_2_fr =
371        field_switching::<_, E::ScalarField>(&l1_mul_alpha_2_fp_elem_var.witness(circuit)?);
372
373    // the big loop to compute r_0[j]
374    //
375    // For each instance j:
376    //
377    // r_plonk_j
378    //  = PI - L1(x) * alpha^2 - alpha *
379    //  \prod_i=1..m-1 (w_{j,i} + beta * sigma_{j,i} + gamma)
380    //  * (w_{j,m} + gamma) * z_j(xw)
381    //
382    // r_0[j] = alpha^{k_j} * r_plonk_j
383    // where m is the number of instances, and k_j is the number of alpha power
384    // terms added to the first j-1 instances.
385    for poly_evals in batch_proof.poly_evals_vec.iter() {
386        // =====================================================
387        // r_plonk_j
388        //  = PI - L1(x) * alpha^2 - alpha *
389        //  \prod_i=1..m-1 (w_{j,i} + beta * sigma_{j,i} + gamma)
390        //  * (w_{j,m} + gamma) * z_j(xw)
391        // =====================================================
392
393        // \prod_i=1..m-1 (w_{j,i} + beta * sigma_{j,i} + gamma)
394        let mut prod = FpElemVar::one(
395            circuit,
396            non_native_field_info.m,
397            non_native_field_info.two_power_m,
398        );
399        for (w_j_i_var, sigma_j_i_var) in poly_evals.wires_evals[..GATE_WIDTH]
400            .iter()
401            .zip(poly_evals.wire_sigma_evals.iter())
402        {
403            let beta_sigma_j_i = circuit.mod_mul(
404                &challenges.beta,
405                sigma_j_i_var,
406                &non_native_field_info.modulus_fp_elem,
407            )?;
408            let sum = circuit.mod_add_vec(
409                &[*w_j_i_var, beta_sigma_j_i, challenges.gamma],
410                &non_native_field_info.modulus_fp_elem,
411            )?;
412            prod = circuit.mod_mul(&prod, &sum, &non_native_field_info.modulus_fp_elem)?;
413        }
414
415        // tmp = (w_{j,m} + gamma) * z_j(xw)
416        let mut tmp = circuit.mod_add(
417            &poly_evals.wires_evals[GATE_WIDTH],
418            &challenges.gamma,
419            &non_native_field_info.modulus_fp_elem,
420        )?;
421        tmp = circuit.mod_mul(
422            &tmp,
423            &poly_evals.perm_next_eval,
424            &non_native_field_info.modulus_fp_elem,
425        )?;
426
427        // tmp = alpha *
428        //  \prod_i=1..m-1 (w_{j,i} + beta * sigma_{j,i} + gamma)
429        //  * (w_{j,m} + gamma) * z_j(xw)
430        tmp = circuit.mod_mul(
431            &tmp,
432            &challenges.alphas[0],
433            &non_native_field_info.modulus_fp_elem,
434        )?;
435        tmp = circuit.mod_mul(&tmp, &prod, &non_native_field_info.modulus_fp_elem)?;
436        let tmp_fr = field_switching::<_, E::ScalarField>(&tmp.witness(circuit)?);
437
438        // r_plonk_j
439        let r_plonk_j_fr = pi_fr - l1_mul_alpha_2_fr - tmp_fr;
440        let r_plonk_j_fp_elem_var = FpElemVar::new_from_field_element(
441            circuit,
442            &field_switching(&r_plonk_j_fr),
443            non_native_field_info.m,
444            non_native_field_info.two_power_m,
445        )?;
446
447        // proving r_plonk_j + L1(x)*alpha_2 + tmp = PI
448        let mut left = circuit.mod_add(
449            &r_plonk_j_fp_elem_var,
450            &l1_mul_alpha_2_fp_elem_var,
451            &non_native_field_info.modulus_fp_elem,
452        )?;
453        left = circuit.mod_add(&left, &tmp, &non_native_field_info.modulus_fp_elem)?;
454        left.enforce_equal(circuit, &pi_fp_elem_var)?;
455
456        // preparing data for second statement
457        let r_0_component = circuit.mod_mul(
458            alpha_bases_elem_var
459                .next()
460                .ok_or(PlonkError::IteratorOutOfRange)?,
461            &r_plonk_j_fp_elem_var,
462            &non_native_field_info.modulus_fp_elem,
463        )?;
464
465        r_0_components.push(r_0_component);
466    }
467    // ensure all the buffer has been consumed
468    if alpha_bases_elem_var.next().is_some() {
469        Err(PlonkError::IteratorOutOfRange)?;
470    }
471    // =====================================================
472    // second statement
473    // r_0 = \sum_{j=1..m} alpha^{k_j} * r_plonk_j
474    // =====================================================
475    let res_elem_var =
476        circuit.mod_add_vec(&r_0_components, &non_native_field_info.modulus_fp_elem)?;
477
478    Ok(res_elem_var)
479}
480
481/// Compute the bases and scalars in the batched polynomial commitment,
482/// which is a generalization of `[D]1` specified in Sec 8.3, Verifier
483/// algorithm step 9 of https://eprint.iacr.org/2019/953.pdf.
484///
485/// - input evals: zeta^n, zeta^n-1 and Lagrange evaluated at 1
486///
487/// Do not compute plookup related variables.
488pub(super) fn linearization_scalars_and_bases_circuit<E, F>(
489    circuit: &mut PlonkCircuit<F>,
490    vks: &[&VerifyingKeyVar<E>],
491    challenges: &ChallengesFpElemVar<F>,
492    poly_evals: &[FpElemVar<F>; 3],
493    batch_proof: &BatchProofVar<F>,
494    alpha_bases: &[FpElemVar<F>],
495    non_native_field_info: NonNativeFieldInfo<F>,
496) -> Result<ScalarsAndBasesVar<F>, CircuitError>
497where
498    E: Pairing<BaseField = F>,
499    F: PrimeField,
500{
501    let beta_times_zeta_fp_elem_var = circuit.mod_mul(
502        &challenges.beta,
503        &challenges.zeta,
504        &non_native_field_info.modulus_fp_elem,
505    )?;
506    let alpha_times_beta_fp_elem_var = circuit.mod_mul(
507        &challenges.alphas[0],
508        &challenges.beta,
509        &non_native_field_info.modulus_fp_elem,
510    )?;
511
512    let alpha_2_mul_l1 = circuit.mod_mul(
513        &challenges.alphas[1],
514        &poly_evals[2],
515        &non_native_field_info.modulus_fp_elem,
516    )?;
517
518    let mut alpha_bases_elem_var = alpha_bases.iter();
519
520    let mut scalars_and_bases = ScalarsAndBasesVar::new();
521    for (i, vk) in vks.iter().enumerate() {
522        // ============================================
523        // Compute coefficient for the permutation product polynomial commitment.
524        // coeff = [z]_1 *
525        //       ( L1(zeta) * alpha^2
526        //          + alpha
527        //              * (beta * zeta      + a_bar + gamma)    <- computed via the loop
528        //              * (beta * k1 * zeta + b_bar + gamma)    <- computed via the loop
529        //              * (beta * k2 * zeta + c_bar + gamma)    <- computed via the loop
530        //       )
531        // where a_bar, b_bar and c_bar are in w_evals
532        // ============================================
533
534        let current_alpha_bases = alpha_bases_elem_var
535            .next()
536            .ok_or(PlonkError::IteratorOutOfRange)?;
537
538        let mut coeff_fp_elem_var = alpha_2_mul_l1;
539        let w_evals = &batch_proof.poly_evals_vec[i].wires_evals;
540        let mut prod = challenges.alphas[0];
541        for (&x_bar, k_i) in w_evals.iter().zip(vk.k.iter()) {
542            let beta_k_zeta_fp_elem_var = circuit.mod_mul_constant(
543                &beta_times_zeta_fp_elem_var,
544                &FpElem::new(
545                    &field_switching::<_, F>(k_i),
546                    non_native_field_info.m,
547                    non_native_field_info.two_power_m,
548                )?,
549                &non_native_field_info.modulus_fp_elem,
550            )?;
551
552            let sum = circuit.mod_add_vec(
553                &[beta_k_zeta_fp_elem_var, x_bar, challenges.gamma],
554                &non_native_field_info.modulus_fp_elem,
555            )?;
556            prod = circuit.mod_mul(&prod, &sum, &non_native_field_info.modulus_fp_elem)?;
557        }
558        coeff_fp_elem_var = circuit.mod_add(
559            &coeff_fp_elem_var,
560            &prod,
561            &non_native_field_info.modulus_fp_elem,
562        )?;
563        // multiply the final results with alpha_base
564        coeff_fp_elem_var = circuit.mod_mul(
565            &coeff_fp_elem_var,
566            current_alpha_bases,
567            &non_native_field_info.modulus_fp_elem,
568        )?;
569        // Add permutation product polynomial commitment.
570        scalars_and_bases.scalars.push(coeff_fp_elem_var);
571        scalars_and_bases
572            .bases
573            .push(batch_proof.prod_perm_poly_comms_vec[i]);
574
575        // ============================================
576        // Compute coefficient for the last wire sigma polynomial commitment.
577        // coeff = alpha * beta * z_w * [s_sigma_3]_1
578        //       * (a_bar + gamma + beta * s_bar_sigma_1)
579        //       * (b_bar + gamma + beta * s_bar_sigma_2)
580        // ============================================
581        let num_wire_types = batch_proof.wires_poly_comms_vec[i].len();
582        let sigma_evals = &batch_proof.poly_evals_vec[i].wire_sigma_evals;
583        let mut coeff_fp_elem_var = circuit.mod_mul(
584            &alpha_times_beta_fp_elem_var,
585            &batch_proof.poly_evals_vec[i].perm_next_eval,
586            &non_native_field_info.modulus_fp_elem,
587        )?;
588
589        for (&x_bar, sigma_i) in w_evals
590            .iter()
591            .take(num_wire_types - 1)
592            .zip(sigma_evals.iter())
593        {
594            let beta_times_s_bar_sigma_1 = circuit.mod_mul(
595                &challenges.beta,
596                sigma_i,
597                &non_native_field_info.modulus_fp_elem,
598            )?;
599            let sum = circuit.mod_add_vec(
600                &[x_bar, challenges.gamma, beta_times_s_bar_sigma_1],
601                &non_native_field_info.modulus_fp_elem,
602            )?;
603
604            coeff_fp_elem_var = circuit.mod_mul(
605                &coeff_fp_elem_var,
606                &sum,
607                &non_native_field_info.modulus_fp_elem,
608            )?;
609        }
610
611        // multiply the final results with alpha_base
612        coeff_fp_elem_var = circuit.mod_mul(
613            &coeff_fp_elem_var,
614            current_alpha_bases,
615            &non_native_field_info.modulus_fp_elem,
616        )?;
617
618        // Add output wire sigma polynomial commitment.
619        scalars_and_bases.scalars.push(coeff_fp_elem_var);
620        let tmp = circuit.inverse_point(vk.sigma_comms.last().ok_or(CircuitError::IndexError)?)?;
621
622        scalars_and_bases.bases.push(tmp);
623
624        // ============================================
625        // Add selector polynomial commitments.
626        // Compute coefficients for selector polynomial commitments.
627        // The order: q_lc, q_mul, q_hash, q_o, q_c, q_ecc
628        // ============================================
629        // q_scalars[0..3]
630        let mut q_scalars_fp_elem_vars = vec![w_evals[0], w_evals[1], w_evals[2], w_evals[3]];
631        // q_scalars[4] = w_evals[0] * w_evals[1];
632        q_scalars_fp_elem_vars.push(circuit.mod_mul(
633            &w_evals[0],
634            &w_evals[1],
635            &non_native_field_info.modulus_fp_elem,
636        )?);
637        // q_scalars[5] = w_evals[2] * w_evals[3];
638        q_scalars_fp_elem_vars.push(circuit.mod_mul(
639            &w_evals[2],
640            &w_evals[3],
641            &non_native_field_info.modulus_fp_elem,
642        )?);
643        // q_scalars[6] = w_evals[0].pow([5]);
644        q_scalars_fp_elem_vars.push(circuit.non_native_power_5_gen::<E::ScalarField>(&w_evals[0])?);
645        // q_scalars[7] = w_evals[1].pow([5]);
646        q_scalars_fp_elem_vars.push(circuit.non_native_power_5_gen::<E::ScalarField>(&w_evals[1])?);
647        // q_scalars[8] = w_evals[2].pow([5]);
648        q_scalars_fp_elem_vars.push(circuit.non_native_power_5_gen::<E::ScalarField>(&w_evals[2])?);
649        // q_scalars[9] = w_evals[3].pow([5]);
650        q_scalars_fp_elem_vars.push(circuit.non_native_power_5_gen::<E::ScalarField>(&w_evals[3])?);
651        // q_scalars[10] = -w_evals[4];
652        // note that we push w_eval to the buffer, so we will need to inverse the basis
653        q_scalars_fp_elem_vars.push(w_evals[4]);
654        // q_scalars[11] = E::ScalarField::one();
655        // TODO(optimization): public wire?
656        q_scalars_fp_elem_vars.push(FpElemVar::one(
657            circuit,
658            non_native_field_info.m,
659            non_native_field_info.two_power_m,
660        ));
661        // q_scalars[12]
662        // = w_evals[0] * w_evals[1] * w_evals[2] * w_evals[3] * w_evals[4];
663        let mut tmp = circuit.mod_mul(
664            &w_evals[0],
665            &w_evals[1],
666            &non_native_field_info.modulus_fp_elem,
667        )?;
668        tmp = circuit.mod_mul(&tmp, &w_evals[2], &non_native_field_info.modulus_fp_elem)?;
669        tmp = circuit.mod_mul(&tmp, &w_evals[3], &non_native_field_info.modulus_fp_elem)?;
670        tmp = circuit.mod_mul(&tmp, &w_evals[4], &non_native_field_info.modulus_fp_elem)?;
671        q_scalars_fp_elem_vars.push(tmp);
672
673        for (i, (s, &poly)) in q_scalars_fp_elem_vars
674            .iter()
675            .zip(vk.selector_comms.iter())
676            .enumerate()
677        {
678            // inverse the bases for w_eval[10]
679            let bases = if i == 10 {
680                circuit.inverse_point(&poly)?
681            } else {
682                poly
683            };
684
685            let tmp = circuit.mod_mul(
686                s,
687                current_alpha_bases,
688                &non_native_field_info.modulus_fp_elem,
689            )?;
690            scalars_and_bases.scalars.push(tmp);
691            scalars_and_bases.bases.push(bases);
692        }
693    }
694
695    // ensure all the buffer has been consumed
696    if alpha_bases_elem_var.next().is_some() {
697        Err(PlonkError::IteratorOutOfRange)?;
698    }
699    // ============================================
700    // Add split quotient commitments
701    // ============================================
702    let zeta_square_fp_elem_var = circuit.mod_mul(
703        &challenges.zeta,
704        &challenges.zeta,
705        &non_native_field_info.modulus_fp_elem,
706    )?;
707    let zeta_to_n_plus_2 = circuit.mod_mul(
708        &zeta_square_fp_elem_var,
709        &poly_evals[0],
710        &non_native_field_info.modulus_fp_elem,
711    )?;
712
713    let mut coeff = poly_evals[1];
714    let tmp = circuit.inverse_point(
715        batch_proof
716            .split_quot_poly_comms
717            .first()
718            .ok_or(CircuitError::IndexError)?,
719    )?;
720    scalars_and_bases.scalars.push(poly_evals[1]);
721    scalars_and_bases.bases.push(tmp);
722
723    for &poly in batch_proof.split_quot_poly_comms.iter().skip(1) {
724        coeff = circuit.mod_mul(
725            &coeff,
726            &zeta_to_n_plus_2,
727            &non_native_field_info.modulus_fp_elem,
728        )?;
729        let poly = circuit.inverse_point(&poly)?;
730        scalars_and_bases.scalars.push(coeff);
731        scalars_and_bases.bases.push(poly);
732    }
733
734    Ok(scalars_and_bases)
735}
736
737#[cfg(test)]
738mod test {
739    use super::*;
740    use ark_bls12_377::Bls12_377;
741    use ark_ff::{BigInteger as _, Field};
742    use ark_std::UniformRand;
743    use jf_relation::Circuit;
744    use jf_utils::test_rng;
745
746    const RANGE_BIT_LEN_FOR_TEST: usize = 16;
747
748    #[test]
749    fn test_evaluate_poly() {
750        test_evaluate_poly_helper::<Bls12_377>();
751    }
752
753    fn test_evaluate_poly_helper<E: Pairing>() {
754        let mut rng = test_rng();
755
756        let mut circuit = PlonkCircuit::<E::BaseField>::new_ultra_plonk(RANGE_BIT_LEN_FOR_TEST);
757        let zeta = E::ScalarField::rand(&mut rng);
758        let zeta_var = circuit.create_variable(field_switching(&zeta)).unwrap();
759
760        for domain_size in [64, 128, 256, 512, 1024] {
761            // compute the result in the clear
762            let domain = Radix2EvaluationDomain::<E::ScalarField>::new(domain_size).unwrap();
763            let vanish_eval = domain.evaluate_vanishing_polynomial(zeta);
764            let zeta_n = vanish_eval + E::ScalarField::one();
765            let divisor = E::ScalarField::from(domain_size as u32) * (zeta - E::ScalarField::one());
766            let lagrange_1_eval = vanish_eval / divisor;
767
768            // compute the variables
769            let m = 128;
770            // constants
771            let two_power_m = Some(E::BaseField::from(2u8).pow([m as u64]));
772
773            let fr_modulus_bits = <E::ScalarField as PrimeField>::MODULUS.to_bytes_le();
774            let modulus_in_f = E::BaseField::from_le_bytes_mod_order(&fr_modulus_bits);
775            let modulus_fp_elem = FpElem::new(&modulus_in_f, m, two_power_m).unwrap();
776
777            let non_native_field_info = NonNativeFieldInfo::<E::BaseField> {
778                m,
779                two_power_m,
780                modulus_in_f,
781                modulus_fp_elem,
782            };
783
784            let zeta_fp_elem_var =
785                FpElemVar::new_unchecked(&mut circuit, zeta_var, m, None).unwrap();
786            let eval_results = evaluate_poly_helper::<E, _>(
787                &mut circuit,
788                &zeta_fp_elem_var,
789                domain_size,
790                non_native_field_info,
791            )
792            .unwrap();
793
794            // check the correctness
795            let tmp = eval_results[0].convert_to_var(&mut circuit).unwrap();
796            assert_eq!(
797                field_switching::<_, E::BaseField>(&zeta_n),
798                circuit.witness(tmp).unwrap(),
799            );
800
801            let tmp = eval_results[1].convert_to_var(&mut circuit).unwrap();
802            assert_eq!(
803                field_switching::<_, E::BaseField>(&vanish_eval),
804                circuit.witness(tmp).unwrap(),
805            );
806
807            let tmp = eval_results[2].convert_to_var(&mut circuit).unwrap();
808            assert_eq!(
809                field_switching::<_, E::BaseField>(&lagrange_1_eval),
810                circuit.witness(tmp).unwrap(),
811            );
812        }
813    }
814}