jf_plonk/circuit/plonk_verifier/
gadgets.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 building blocks in Plonk verifiers.
8use super::{
9    challenge_var_to_fp_elem_var, poly, BatchProofVar, ChallengesFpElemVar, ChallengesVar,
10    NonNativeFieldInfo, PcsInfoVar, ProofEvaluationsVar, ScalarsAndBasesVar, VerifyingKeyVar,
11};
12use crate::{
13    circuit::transcript::RescueTranscriptVar, constants::EXTRA_TRANSCRIPT_MSG_LABEL,
14    errors::PlonkError,
15};
16use ark_ec::{
17    pairing::Pairing,
18    short_weierstrass::{Affine, SWCurveConfig as SWParam},
19};
20use ark_ff::PrimeField;
21use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
22use ark_std::{format, vec, vec::Vec};
23use jf_relation::{
24    gadgets::{
25        ecc::{PointVariable, SWToTEConParam},
26        ultraplonk::mod_arith::{FpElem, FpElemVar},
27    },
28    Circuit, CircuitError,
29    CircuitError::ParameterError,
30    PlonkCircuit,
31};
32use jf_rescue::RescueParameter;
33use jf_utils::{bytes_to_field_elements, field_switching};
34
35/// Aggregate polynomial commitments into a single commitment (in the
36/// ScalarsAndBases form). Useful in batch opening.
37///
38/// The verification key type is guaranteed to match the Plonk proof type.
39///
40/// The returned commitment is a generalization of `[F]1` described
41/// in Sec 8.3, step 10 of https://eprint.iacr.org/2019/953.pdf
42///
43/// Input:
44/// - vks: verification key variable
45/// - challenges: challenge variable in FpElemVar form
46/// - poly_evals: zeta^n, zeta^n-1 and Lagrange evaluated at 1
47/// - batch_proof: batched proof inputs
48/// - non_native_field_info: aux information for non-native field
49///
50/// Output:
51/// - scalar and bases prepared for MSM
52/// - buffer info for u and v powers
53pub(super) fn aggregate_poly_commitments_circuit<E, F>(
54    circuit: &mut PlonkCircuit<F>,
55    vks: &[&VerifyingKeyVar<E>],
56    challenges: &ChallengesFpElemVar<F>,
57    poly_evals: &[FpElemVar<F>; 3],
58    batch_proof: &BatchProofVar<F>,
59    alpha_bases: &[FpElemVar<F>],
60    non_native_field_info: NonNativeFieldInfo<F>,
61) -> Result<(ScalarsAndBasesVar<F>, Vec<FpElemVar<F>>), CircuitError>
62where
63    E: Pairing<BaseField = F>,
64    F: PrimeField,
65{
66    if vks.len() != batch_proof.len() {
67        return Err(ParameterError(format!(
68            "the number of verification keys {} != the number of instances {}",
69            vks.len(),
70            batch_proof.len()
71        )));
72    }
73
74    // 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
75    let mut scalars_and_bases = poly::linearization_scalars_and_bases_circuit(
76        circuit,
77        vks,
78        challenges,
79        poly_evals,
80        batch_proof,
81        alpha_bases,
82        non_native_field_info,
83    )?;
84    // the random combiner term for the polynomials evaluated at point `zeta`
85    let mut v_base = challenges.v;
86    // the random combiner term for the polynomials evaluated at point `zeta * g`
87    let mut uv_base = challenges.u;
88
89    // return the buffer data for aggregate_evaluations_circuit
90    let mut v_and_uv_basis = vec![];
91
92    for (i, vk) in vks.iter().enumerate() {
93        // Add poly commitments to be evaluated at point `zeta`.
94        // Add wire witness polynomial commitments.
95        for &poly_comm in batch_proof.wires_poly_comms_vec[i].iter() {
96            v_and_uv_basis.push(v_base);
97            add_poly_comm_circuit(
98                circuit,
99                &mut scalars_and_bases,
100                &mut v_base,
101                &poly_comm,
102                &challenges.v,
103                &non_native_field_info.modulus_fp_elem,
104            )?;
105        }
106        // Add wire sigma polynomial commitments. The last sigma commitment is excluded.
107        let num_wire_types = batch_proof.wires_poly_comms_vec[i].len();
108        for &poly_comm in vk.sigma_comms.iter().take(num_wire_types - 1) {
109            v_and_uv_basis.push(v_base);
110            add_poly_comm_circuit(
111                circuit,
112                &mut scalars_and_bases,
113                &mut v_base,
114                &poly_comm,
115                &challenges.v,
116                &non_native_field_info.modulus_fp_elem,
117            )?;
118        }
119
120        // Add poly commitments to be evaluated at point `zeta * g`.
121        v_and_uv_basis.push(uv_base);
122        add_poly_comm_circuit(
123            circuit,
124            &mut scalars_and_bases,
125            &mut uv_base,
126            &batch_proof.prod_perm_poly_comms_vec[i],
127            &challenges.v,
128            &non_native_field_info.modulus_fp_elem,
129        )?;
130    }
131
132    Ok((scalars_and_bases, v_and_uv_basis))
133}
134
135/// Combine the polynomial evaluations into a single evaluation. Useful in
136/// batch opening.
137/// The returned value is the scalar in `[E]1` described in Sec 8.3, step 11 of https://eprint.iacr.org/2019/953.pdf
138pub(super) fn aggregate_evaluations_circuit<F>(
139    circuit: &mut PlonkCircuit<F>,
140    lin_poly_constant: &FpElemVar<F>,
141    poly_evals_vec: &[ProofEvaluationsVar<F>],
142    non_native_field_info: NonNativeFieldInfo<F>,
143    buffer_v_and_uv_basis: &[FpElemVar<F>],
144) -> Result<FpElemVar<F>, CircuitError>
145where
146    F: PrimeField,
147{
148    let mut result = circuit.mod_negate(lin_poly_constant, &non_native_field_info.modulus_in_f)?;
149    let mut v_and_uv_basis = buffer_v_and_uv_basis.iter();
150
151    for poly_evals in poly_evals_vec.iter() {
152        // evaluations at point `zeta`
153        for wire_eval in poly_evals.wires_evals.iter() {
154            add_pcs_eval_circuit(
155                circuit,
156                &mut result,
157                v_and_uv_basis
158                    .next()
159                    .ok_or(PlonkError::IteratorOutOfRange)?,
160                wire_eval,
161                &non_native_field_info.modulus_fp_elem,
162            )?;
163        }
164        for sigma_eval in poly_evals.wire_sigma_evals.iter() {
165            add_pcs_eval_circuit(
166                circuit,
167                &mut result,
168                v_and_uv_basis
169                    .next()
170                    .ok_or(PlonkError::IteratorOutOfRange)?,
171                sigma_eval,
172                &non_native_field_info.modulus_fp_elem,
173            )?;
174        }
175        // evaluations at point `zeta * g`
176        add_pcs_eval_circuit(
177            circuit,
178            &mut result,
179            v_and_uv_basis
180                .next()
181                .ok_or(PlonkError::IteratorOutOfRange)?,
182            &poly_evals.perm_next_eval,
183            &non_native_field_info.modulus_fp_elem,
184        )?;
185    }
186    // ensure all the buffer has been consumed
187    if v_and_uv_basis.next().is_some() {
188        Err(PlonkError::IteratorOutOfRange)?;
189    }
190    Ok(result)
191}
192
193/// Compute verifier challenges `beta`, `gamma`, `alpha`, `zeta`, 'v', 'u'.
194/// also compute the `alpha^2` and `alpha^3`.
195pub(super) fn compute_challenges_vars<E, F, P>(
196    circuit: &mut PlonkCircuit<F>,
197    verify_keys: &[&VerifyingKeyVar<E>],
198    public_inputs: &[&[FpElemVar<F>]],
199    batch_proof: &BatchProofVar<F>,
200    extra_transcript_init_msg: &Option<Vec<u8>>,
201    non_native_field_info: NonNativeFieldInfo<F>,
202) -> Result<ChallengesFpElemVar<F>, CircuitError>
203where
204    E: Pairing<BaseField = F, G1Affine = Affine<P>>,
205    F: RescueParameter + SWToTEConParam,
206    P: SWParam<BaseField = F>,
207{
208    if verify_keys.len() != batch_proof.len() || verify_keys.len() != public_inputs.len() {
209        return Err(ParameterError(format!(
210                "the number of verification keys = {}; the number of instances = {}; the number of public inputs = {}",
211                verify_keys.len(),
212                batch_proof.len(),
213                public_inputs.len(),
214            )));
215    }
216    let mut transcript_var = RescueTranscriptVar::new(circuit);
217    if let Some(msg) = extra_transcript_init_msg {
218        let msg_fs = bytes_to_field_elements::<_, F>(msg.as_ref());
219        let msg_vars = msg_fs
220            .iter()
221            .map(|x| circuit.create_variable(*x))
222            .collect::<Result<Vec<_>, _>>()?;
223        transcript_var.append_message_vars(EXTRA_TRANSCRIPT_MSG_LABEL, &msg_vars)?;
224    }
225    for (&vk, &pi) in verify_keys.iter().zip(public_inputs.iter()) {
226        transcript_var.append_vk_and_pub_input_vars::<E>(circuit, vk, pi)?;
227    }
228    for wires_poly_comms in batch_proof.wires_poly_comms_vec.iter() {
229        transcript_var.append_commitments_vars(b"witness_poly_comms", wires_poly_comms)?;
230    }
231
232    let beta = transcript_var.get_challenge_var::<E>(b"beta", circuit)?;
233    let gamma = transcript_var.get_challenge_var::<E>(b"gamma", circuit)?;
234    for prod_perm_poly_comm in batch_proof.prod_perm_poly_comms_vec.iter() {
235        transcript_var.append_commitment_var(b"perm_poly_comms", prod_perm_poly_comm)?;
236    }
237
238    let alpha = transcript_var.get_challenge_var::<E>(b"alpha", circuit)?;
239    transcript_var
240        .append_commitments_vars(b"quot_poly_comms", &batch_proof.split_quot_poly_comms)?;
241    let zeta = transcript_var.get_challenge_var::<E>(b"zeta", circuit)?;
242    for poly_evals in batch_proof.poly_evals_vec.iter() {
243        transcript_var.append_proof_evaluations_vars(circuit, poly_evals)?;
244    }
245
246    let v = transcript_var.get_challenge_var::<E>(b"v", circuit)?;
247    transcript_var.append_commitment_var(b"open_proof", &batch_proof.opening_proof)?;
248    transcript_var
249        .append_commitment_var(b"shifted_open_proof", &batch_proof.shifted_opening_proof)?;
250    let u = transcript_var.get_challenge_var::<E>(b"u", circuit)?;
251
252    // convert challenge vars into FpElemVars
253    let challenge_var = ChallengesVar {
254        alpha,
255        beta,
256        gamma,
257        zeta,
258        v,
259        u,
260    };
261
262    let challenge_fp_elem_var =
263        challenge_var_to_fp_elem_var(circuit, &challenge_var, &non_native_field_info)?;
264    Ok(challenge_fp_elem_var)
265}
266
267/// Prepare the (aggregated) polynomial commitment evaluation information.
268#[allow(clippy::too_many_arguments)]
269pub(super) fn prepare_pcs_info_var<E, F, P>(
270    circuit: &mut PlonkCircuit<F>,
271    verify_keys: &[&VerifyingKeyVar<E>],
272    public_inputs: &[&[FpElemVar<F>]],
273    batch_proof: &BatchProofVar<F>,
274    extra_transcript_init_msg: &Option<Vec<u8>>,
275
276    domain: Radix2EvaluationDomain<E::ScalarField>,
277    non_native_field_info: NonNativeFieldInfo<F>,
278) -> Result<PcsInfoVar<F>, CircuitError>
279where
280    E: Pairing<BaseField = F, G1Affine = Affine<P>>,
281    F: RescueParameter + SWToTEConParam,
282    P: SWParam<BaseField = F>,
283{
284    if verify_keys.len() != batch_proof.len() || verify_keys.len() != public_inputs.len() {
285        return Err(ParameterError(format!(
286                "the number of verification keys = {}; the number of instances =  {}; the number of public inputs = {}",           
287                verify_keys.len(),
288                batch_proof.len(),
289                public_inputs.len(),
290            )));
291    }
292
293    for (i, (&pub_input, &vk)) in public_inputs.iter().zip(verify_keys.iter()).enumerate() {
294        if pub_input.len() != vk.num_inputs {
295            return Err(ParameterError(format!(
296                    "the circuit pub_input length {} != the {}-th verification key's pub_input length {}",
297                    pub_input.len(),
298                    i,
299                    vk.num_inputs,
300                )));
301        }
302
303        if vk.domain_size != domain.size() {
304            return Err(ParameterError(format!(
305                "the domain size {} of the {}-th verification key is different from {}",
306                vk.domain_size,
307                i,
308                domain.size(),
309            )));
310        }
311    }
312
313    // compute challenges and evaluations
314    let challenges_fp_elem_var = compute_challenges_vars::<E, F, P>(
315        circuit,
316        verify_keys,
317        public_inputs,
318        batch_proof,
319        extra_transcript_init_msg,
320        non_native_field_info,
321    )?;
322
323    // pre-compute alpha_bases: [1, alpha^3, alpha^6, alpha^(3* (vks.len()-1))]
324    let alpha_bases = compute_alpha_basis(
325        circuit,
326        challenges_fp_elem_var.alphas[2],
327        verify_keys.len(),
328        non_native_field_info,
329    )?;
330
331    // the outputs are: zeta^n, vanish_eval, lagrange_1_eval, lagrange_n_eval
332    let evals = poly::evaluate_poly_helper::<E, F>(
333        circuit,
334        &challenges_fp_elem_var.zeta,
335        domain.size(),
336        non_native_field_info,
337    )?;
338
339    // compute the constant term of the linearization polynomial
340    let lin_poly_constant = poly::compute_lin_poly_constant_term_circuit(
341        circuit,
342        domain.size(),
343        &challenges_fp_elem_var,
344        verify_keys,
345        public_inputs,
346        batch_proof,
347        &evals,
348        &alpha_bases,
349        non_native_field_info,
350    )?;
351
352    // build the (aggregated) polynomial commitment/evaluation instance
353    let (comm_scalars_and_bases, v_and_uv_basis) = aggregate_poly_commitments_circuit(
354        circuit,
355        verify_keys,
356        &challenges_fp_elem_var,
357        &evals,
358        batch_proof,
359        &alpha_bases,
360        non_native_field_info,
361    )?;
362    let eval = aggregate_evaluations_circuit::<_>(
363        circuit,
364        &lin_poly_constant,
365        &batch_proof.poly_evals_vec,
366        non_native_field_info,
367        &v_and_uv_basis,
368    )?;
369
370    // next_eval_point: challenges.zeta * domain.group_gen
371    let group_gen = FpElem::new(
372        &field_switching(&domain.group_gen),
373        non_native_field_info.m,
374        non_native_field_info.two_power_m,
375    )?;
376    let next_point = circuit.mod_mul_constant(
377        &challenges_fp_elem_var.zeta,
378        &group_gen,
379        &non_native_field_info.modulus_fp_elem,
380    )?;
381
382    Ok(PcsInfoVar {
383        u: challenges_fp_elem_var.u,
384        eval_point: challenges_fp_elem_var.zeta,
385        next_eval_point: next_point,
386        eval,
387        comm_scalars_and_bases,
388        opening_proof: batch_proof.opening_proof,
389        shifted_opening_proof: batch_proof.shifted_opening_proof,
390    })
391}
392
393/// Merge a polynomial commitment into the aggregated polynomial commitment
394/// (in the ScalarAndBases form), update the random combiner afterward.
395#[inline]
396fn add_poly_comm_circuit<F>(
397    circuit: &mut PlonkCircuit<F>,
398    scalars_and_bases: &mut ScalarsAndBasesVar<F>,
399    random_combiner: &mut FpElemVar<F>,
400    comm: &PointVariable,
401    r: &FpElemVar<F>,
402    p: &FpElem<F>,
403) -> Result<(), CircuitError>
404where
405    F: PrimeField,
406{
407    scalars_and_bases.scalars.push(*random_combiner);
408    scalars_and_bases.bases.push(*comm);
409    *random_combiner = circuit.mod_mul(random_combiner, r, p)?;
410    Ok(())
411}
412
413/// Add a polynomial commitment evaluation value to the aggregated
414/// polynomial evaluation, update the random combiner afterward.
415#[inline]
416fn add_pcs_eval_circuit<F>(
417    circuit: &mut PlonkCircuit<F>,
418    result: &mut FpElemVar<F>,
419    random_combiner: &FpElemVar<F>,
420    eval: &FpElemVar<F>,
421    p: &FpElem<F>,
422) -> Result<(), CircuitError>
423where
424    F: PrimeField,
425{
426    let tmp = circuit.mod_mul(random_combiner, eval, p)?;
427    *result = circuit.mod_add(result, &tmp, p)?;
428
429    Ok(())
430}
431
432// pre-compute alpha_bases: [1, alpha^3, alpha^6, alpha^(3*(len-1))]
433#[inline]
434fn compute_alpha_basis<F: PrimeField>(
435    circuit: &mut PlonkCircuit<F>,
436    alpha_to_3: FpElemVar<F>,
437    len: usize,
438    non_native_field_info: NonNativeFieldInfo<F>,
439) -> Result<Vec<FpElemVar<F>>, CircuitError> {
440    let mut res = Vec::new();
441    let mut alpha_base_elem_var = FpElemVar::<F>::one(
442        circuit,
443        non_native_field_info.m,
444        non_native_field_info.two_power_m,
445    );
446    res.push(alpha_base_elem_var);
447    for _ in 0..len - 1 {
448        alpha_base_elem_var = circuit.mod_mul(
449            &alpha_base_elem_var,
450            &alpha_to_3,
451            &non_native_field_info.modulus_fp_elem,
452        )?;
453        res.push(alpha_base_elem_var);
454    }
455    Ok(res)
456}
457
458#[cfg(test)]
459mod test {
460    use super::*;
461    use crate::{
462        proof_system::{
463            batch_arg::{new_mergeable_circuit_for_test, BatchArgument},
464            structs::VerifyingKey,
465            PlonkKzgSnark, UniversalSNARK,
466        },
467        transcript::{PlonkTranscript, RescueTranscript},
468    };
469    use ark_bls12_377::Bls12_377;
470    use ark_ec::{short_weierstrass::SWCurveConfig, twisted_edwards::TECurveConfig};
471    use ark_ff::BigInteger as _;
472    use ark_std::{vec, UniformRand};
473    use jf_relation::MergeableCircuitType;
474    use jf_utils::test_rng;
475
476    const RANGE_BIT_LEN_FOR_TEST: usize = 16;
477    #[test]
478    fn test_compute_challenges_vars_circuit() -> Result<(), CircuitError> {
479        test_compute_challenges_vars_circuit_helper::<Bls12_377, _, _, RescueTranscript<_>>()
480    }
481
482    fn test_compute_challenges_vars_circuit_helper<E, F, P, T>() -> Result<(), CircuitError>
483    where
484        E: Pairing<BaseField = F, G1Affine = Affine<P>>,
485        F: RescueParameter + SWToTEConParam,
486        P: SWCurveConfig<BaseField = F> + TECurveConfig,
487        T: PlonkTranscript<F>,
488    {
489        // 1. Simulate universal setup
490        let rng = &mut test_rng();
491        let n = 128;
492        let max_degree = n + 2;
493        let srs = PlonkKzgSnark::<E>::universal_setup_for_testing(max_degree, rng)?;
494
495        // 2. Setup instances
496        let shared_public_input = E::ScalarField::rand(rng);
497        let mut instances_type_a = vec![];
498        let mut instances_type_b = vec![];
499        for i in 32..50 {
500            let circuit = new_mergeable_circuit_for_test::<E>(
501                shared_public_input,
502                i,
503                MergeableCircuitType::TypeA,
504            )?;
505            let instance =
506                BatchArgument::setup_instance(&srs, circuit, MergeableCircuitType::TypeA)?;
507            instances_type_a.push(instance);
508
509            let circuit = new_mergeable_circuit_for_test::<E>(
510                shared_public_input,
511                i,
512                MergeableCircuitType::TypeB,
513            )?;
514            let instance =
515                BatchArgument::setup_instance(&srs, circuit, MergeableCircuitType::TypeB)?;
516            instances_type_b.push(instance);
517        }
518        // 3. Batch Proving
519        let batch_proof =
520            BatchArgument::batch_prove::<_, T>(rng, &instances_type_a, &instances_type_b)?;
521
522        // 4. Aggregate verification keys
523        let vks_type_a: Vec<&VerifyingKey<E>> = instances_type_a
524            .iter()
525            .map(|pred| pred.verify_key_ref())
526            .collect();
527        let vks_type_b: Vec<&VerifyingKey<E>> = instances_type_b
528            .iter()
529            .map(|pred| pred.verify_key_ref())
530            .collect();
531        let merged_vks = BatchArgument::aggregate_verify_keys(&vks_type_a, &vks_type_b)?;
532        // error path: inconsistent length between vks_type_a and vks_type_b
533        assert!(BatchArgument::aggregate_verify_keys(&vks_type_a[1..], &vks_type_b).is_err());
534
535        // 5. Verification
536        let open_key_ref = &vks_type_a[0].open_key;
537        let beta_g_ref = &srs.powers_of_g[1];
538        let blinding_factor = E::ScalarField::rand(rng);
539        let (inner1, inner2) = BatchArgument::partial_verify::<T>(
540            beta_g_ref,
541            &open_key_ref.g,
542            &merged_vks,
543            &[shared_public_input],
544            &batch_proof,
545            blinding_factor,
546        )?;
547        assert!(BatchArgument::decide(open_key_ref, inner1, inner2)?);
548
549        // =======================================
550        // begin challenge circuit
551        // =======================================
552        let mut circuit = PlonkCircuit::<E::BaseField>::new_ultra_plonk(RANGE_BIT_LEN_FOR_TEST);
553
554        // constants
555        let m = 128;
556        let two_power_m = Some(E::BaseField::from(2u8).pow([m as u64]));
557
558        let fr_modulus_bits = <E::ScalarField as PrimeField>::MODULUS.to_bytes_le();
559        let modulus_in_f = F::from_le_bytes_mod_order(&fr_modulus_bits);
560        let modulus_fp_elem = FpElem::new(&modulus_in_f, m, two_power_m)?;
561        let non_native_field_info = NonNativeFieldInfo::<F> {
562            m,
563            two_power_m,
564            modulus_in_f,
565            modulus_fp_elem,
566        };
567
568        // vk
569        let vk_vars = merged_vks
570            .iter()
571            .map(|x| VerifyingKeyVar::new(&mut circuit, x))
572            .collect::<Result<Vec<_>, _>>()?;
573        let merged_vks_ref: Vec<&VerifyingKeyVar<E>> = vk_vars.iter().collect();
574
575        let shared_public_input_var =
576            circuit.create_public_variable(field_switching(&shared_public_input))?;
577        let shared_public_input_fp_elem_var =
578            [FpElemVar::new_unchecked(&mut circuit, shared_public_input_var, m, two_power_m)?; 1];
579        let shared_public_input_fp_elem_var_ref = shared_public_input_fp_elem_var.as_ref();
580
581        // proof
582        let batch_proof_vars = batch_proof.create_variables(&mut circuit, m, two_power_m)?;
583
584        let _challenges_fp_elem_var = compute_challenges_vars::<E, F, P>(
585            &mut circuit,
586            &merged_vks_ref,
587            &[shared_public_input_fp_elem_var_ref; 18],
588            &batch_proof_vars,
589            &None,
590            non_native_field_info,
591        )?;
592
593        let tmp = field_switching(&shared_public_input);
594        let public_inputs = [tmp];
595
596        assert!(
597            circuit
598                .check_circuit_satisfiability(public_inputs.as_ref())
599                .is_ok(),
600            "{:?}",
601            circuit.check_circuit_satisfiability(public_inputs.as_ref())
602        );
603
604        Ok(())
605    }
606}