arnaucube
arnaucube/hyperplonkPublic Self-hosted repository
hyperplonk/pcs/src/multilinear_kzg/mod.rs
pcs/src/multilinear_kzg/mod.rsRaw
  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//! Main module for multilinear KZG commitment scheme
  8
  9mod batching;
 10pub(crate) mod srs;
 11pub(crate) mod util;
 12
 13use self::batching::{
 14    batch_verify_internal, batch_verify_same_poly_internal, multi_open_internal,
 15    multi_open_same_poly_internal,
 16};
 17use crate::{
 18    prelude::{
 19        Commitment, UnivariateProverParam, UnivariateUniversalParams, UnivariateVerifierParam,
 20    },
 21    univariate_kzg::UnivariateKzgProof,
 22    PCSError, PolynomialCommitmentScheme, StructuredReferenceString,
 23};
 24use arithmetic::{evaluate_opt, merge_polynomials};
 25use ark_ec::{
 26    msm::{FixedBaseMSM, VariableBaseMSM},
 27    AffineCurve, PairingEngine, ProjectiveCurve,
 28};
 29use ark_ff::PrimeField;
 30use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
 31use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
 32use ark_std::{
 33    borrow::Borrow,
 34    end_timer, format,
 35    marker::PhantomData,
 36    rand::{CryptoRng, RngCore},
 37    rc::Rc,
 38    start_timer,
 39    string::ToString,
 40    vec,
 41    vec::Vec,
 42    One, Zero,
 43};
 44// use batching::{batch_verify_internal, multi_open_internal};
 45use srs::{MultilinearProverParam, MultilinearUniversalParams, MultilinearVerifierParam};
 46
 47/// KZG Polynomial Commitment Scheme on multilinear polynomials.
 48pub struct MultilinearKzgPCS<E: PairingEngine> {
 49    #[doc(hidden)]
 50    phantom: PhantomData<E>,
 51}
 52
 53#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
 54/// proof of opening
 55pub struct MultilinearKzgProof<E: PairingEngine> {
 56    /// Evaluation of quotients
 57    pub proofs: Vec<E::G1Affine>,
 58}
 59
 60#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
 61/// proof of batch opening
 62pub struct MultilinearKzgBatchProof<E: PairingEngine> {
 63    /// The actual proof
 64    pub proof: MultilinearKzgProof<E>,
 65    /// Commitment to q(x):= w(l(x)) where
 66    /// - `w` is the merged MLE
 67    /// - `l` is the list of univariate polys that goes through all points
 68    pub q_x_commit: Commitment<E>,
 69    /// openings of q(x) at 1, omega, ..., and r
 70    pub q_x_opens: Vec<UnivariateKzgProof<E>>,
 71}
 72
 73impl<E: PairingEngine> PolynomialCommitmentScheme<E> for MultilinearKzgPCS<E> {
 74    // Parameters
 75    type ProverParam = (
 76        MultilinearProverParam<E>,
 77        UnivariateProverParam<E::G1Affine>,
 78    );
 79    type VerifierParam = (MultilinearVerifierParam<E>, UnivariateVerifierParam<E>);
 80    type SRS = (MultilinearUniversalParams<E>, UnivariateUniversalParams<E>);
 81    // Polynomial and its associated types
 82    type Polynomial = Rc<DenseMultilinearExtension<E::Fr>>;
 83    type Point = Vec<E::Fr>;
 84    type Evaluation = E::Fr;
 85    // Commitments and proofs
 86    type Commitment = Commitment<E>;
 87    type BatchCommitment = Commitment<E>;
 88    type Proof = MultilinearKzgProof<E>;
 89    type BatchProof = MultilinearKzgBatchProof<E>;
 90
 91    /// Build SRS for testing.
 92    ///
 93    /// - For univariate polynomials, `log_size` is the log of maximum degree.
 94    /// - For multilinear polynomials, `log_size` is the number of variables.
 95    ///
 96    /// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY.
 97    /// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION.
 98    fn gen_srs_for_testing<R: RngCore + CryptoRng>(
 99        rng: &mut R,
100        log_size: usize,
101    ) -> Result<Self::SRS, PCSError> {
102        Ok((
103            MultilinearUniversalParams::<E>::gen_srs_for_testing(rng, log_size)?,
104            UnivariateUniversalParams::<E>::gen_srs_for_testing(rng, 1 << log_size)?,
105        ))
106    }
107
108    /// Trim the universal parameters to specialize the public parameters.
109    /// Input both `supported_log_degree` for univariate and
110    /// `supported_num_vars` for multilinear.
111    fn trim(
112        srs: impl Borrow<Self::SRS>,
113        supported_degree: usize,
114        supported_num_vars: Option<usize>,
115    ) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError> {
116        let supported_num_vars = match supported_num_vars {
117            Some(p) => p,
118            None => {
119                return Err(PCSError::InvalidParameters(
120                    "multilinear should receive a num_var param".to_string(),
121                ))
122            },
123        };
124        let (uni_ck, uni_vk) = srs.borrow().1.trim(supported_degree)?;
125        let (ml_ck, ml_vk) = srs.borrow().0.trim(supported_num_vars)?;
126
127        Ok(((ml_ck, uni_ck), (ml_vk, uni_vk)))
128    }
129
130    /// Generate a commitment for a polynomial.
131    ///
132    /// This function takes `2^num_vars` number of scalar multiplications over
133    /// G1.
134    fn commit(
135        prover_param: impl Borrow<Self::ProverParam>,
136        poly: &Self::Polynomial,
137    ) -> Result<Self::Commitment, PCSError> {
138        let prover_param = prover_param.borrow();
139        let commit_timer = start_timer!(|| "commit");
140        if prover_param.0.num_vars < poly.num_vars {
141            return Err(PCSError::InvalidParameters(format!(
142                "MlE length ({}) exceeds param limit ({})",
143                poly.num_vars, prover_param.0.num_vars
144            )));
145        }
146        let ignored = prover_param.0.num_vars - poly.num_vars;
147        let scalars: Vec<_> = poly
148            .to_evaluations()
149            .into_iter()
150            .map(|x| x.into_repr())
151            .collect();
152        let commitment = VariableBaseMSM::multi_scalar_mul(
153            &prover_param.0.powers_of_g[ignored].evals,
154            scalars.as_slice(),
155        )
156        .into_affine();
157
158        end_timer!(commit_timer);
159        Ok(Commitment(commitment))
160    }
161
162    /// Generate a commitment for a list of polynomials.
163    ///
164    /// This function takes `2^(num_vars + log(polys.len())` number of scalar
165    /// multiplications over G1.
166    fn multi_commit(
167        prover_param: impl Borrow<Self::ProverParam>,
168        polys: &[Self::Polynomial],
169    ) -> Result<Self::Commitment, PCSError> {
170        let prover_param = prover_param.borrow();
171        let commit_timer = start_timer!(|| "multi commit");
172        let poly = merge_polynomials(polys)?;
173
174        let scalars: Vec<_> = poly
175            .to_evaluations()
176            .iter()
177            .map(|x| x.into_repr())
178            .collect();
179
180        let commitment = VariableBaseMSM::multi_scalar_mul(
181            &prover_param.0.powers_of_g[0].evals,
182            scalars.as_slice(),
183        )
184        .into_affine();
185
186        end_timer!(commit_timer);
187        Ok(Commitment(commitment))
188    }
189
190    /// On input a polynomial `p` and a point `point`, outputs a proof for the
191    /// same. This function does not need to take the evaluation value as an
192    /// input.
193    ///
194    /// This function takes 2^{num_var +1} number of scalar multiplications over
195    /// G1:
196    /// - it prodceeds with `num_var` number of rounds,
197    /// - at round i, we compute an MSM for `2^{num_var - i + 1}` number of G2
198    ///   elements.
199    fn open(
200        prover_param: impl Borrow<Self::ProverParam>,
201        polynomial: &Self::Polynomial,
202        point: &Self::Point,
203    ) -> Result<(Self::Proof, Self::Evaluation), PCSError> {
204        open_internal(&prover_param.borrow().0, polynomial, point)
205    }
206
207    /// Input
208    /// - the prover parameters for univariate KZG,
209    /// - the prover parameters for multilinear KZG,
210    /// - a list of multilinear extensions (MLEs),
211    /// - a commitment to all multilinear extensions,
212    /// - and a same number of points,
213    /// compute a multi-opening for all the polynomials.
214    ///
215    /// For simplicity, this API requires each MLE to have only one point. If
216    /// the caller wish to use more than one points per MLE, it should be
217    /// handled at the caller layer.
218    ///
219    /// Returns an error if the lengths do not match.
220    ///
221    /// Returns the proof, consists of
222    /// - the multilinear KZG opening
223    /// - the univariate KZG commitment to q(x)
224    /// - the openings and evaluations of q(x) at omega^i and r
225    ///
226    /// Steps:
227    /// 1. build `l(points)` which is a list of univariate polynomials that goes
228    /// through the points
229    /// 2. build MLE `w` which is the merge of all MLEs.
230    /// 3. build `q(x)` which is a univariate polynomial `W circ l`
231    /// 4. commit to q(x) and sample r from transcript
232    /// transcript contains: w commitment, points, q(x)'s commitment
233    /// 5. build q(omega^i) and their openings
234    /// 6. build q(r) and its opening
235    /// 7. get a point `p := l(r)`
236    /// 8. output an opening of `w` over point `p`
237    /// 9. output `w(p)`
238    fn multi_open(
239        prover_param: impl Borrow<Self::ProverParam>,
240        multi_commitment: &Self::BatchCommitment,
241        polynomials: &[Self::Polynomial],
242        points: &[Self::Point],
243    ) -> Result<(Self::BatchProof, Vec<Self::Evaluation>), PCSError> {
244        multi_open_internal::<E>(
245            &prover_param.borrow().1,
246            &prover_param.borrow().0,
247            polynomials,
248            multi_commitment,
249            points,
250        )
251    }
252
253    /// Input a multilinear extension, and a number of points, and
254    /// a transcript, compute a multi-opening for all the polynomials.
255    fn multi_open_single_poly(
256        prover_param: impl Borrow<Self::ProverParam>,
257        commitment: &Self::Commitment,
258        polynomial: &Self::Polynomial,
259        points: &[Self::Point],
260    ) -> Result<(Self::BatchProof, Vec<Self::Evaluation>), PCSError> {
261        multi_open_same_poly_internal::<E>(
262            &prover_param.borrow().1,
263            &prover_param.borrow().0,
264            polynomial,
265            commitment,
266            points,
267        )
268    }
269
270    /// Verifies that `value` is the evaluation at `x` of the polynomial
271    /// committed inside `comm`.
272    ///
273    /// This function takes
274    /// - num_var number of pairing product.
275    /// - num_var number of MSM
276    fn verify(
277        verifier_param: &Self::VerifierParam,
278        commitment: &Self::Commitment,
279        point: &Self::Point,
280        value: &E::Fr,
281        proof: &Self::Proof,
282    ) -> Result<bool, PCSError> {
283        verify_internal(&verifier_param.0, commitment, point, value, proof)
284    }
285
286    /// Verifies that `value` is the evaluation at `x_i` of the polynomial
287    /// `poly_i` committed inside `comm`.
288    /// steps:
289    ///
290    /// 1. put `q(x)`'s evaluations over `(1, omega,...)` into transcript
291    /// 2. sample `r` from transcript
292    /// 3. check `q(r) == value`
293    /// 4. build `l(points)` which is a list of univariate polynomials that goes
294    /// through the points
295    /// 5. get a point `p := l(r)`
296    /// 6. verifies `p` is verifies against proof
297    fn batch_verify<R: RngCore + CryptoRng>(
298        verifier_param: &Self::VerifierParam,
299        multi_commitment: &Self::BatchCommitment,
300        points: &[Self::Point],
301        values: &[E::Fr],
302        batch_proof: &Self::BatchProof,
303        _rng: &mut R,
304    ) -> Result<bool, PCSError> {
305        batch_verify_internal(
306            &verifier_param.1,
307            &verifier_param.0,
308            multi_commitment,
309            points,
310            values,
311            batch_proof,
312        )
313    }
314
315    /// Verifies that `value_i` is the evaluation at `x_i` of the polynomial
316    /// `poly` committed inside `comm`.
317    fn batch_verify_single_poly(
318        verifier_param: &Self::VerifierParam,
319        commitment: &Self::Commitment,
320        points: &[Self::Point],
321        values: &[E::Fr],
322        batch_proof: &Self::BatchProof,
323    ) -> Result<bool, PCSError> {
324        batch_verify_same_poly_internal(
325            &verifier_param.1,
326            &verifier_param.0,
327            commitment,
328            points,
329            values,
330            batch_proof,
331        )
332    }
333}
334
335/// On input a polynomial `p` and a point `point`, outputs a proof for the
336/// same. This function does not need to take the evaluation value as an
337/// input.
338///
339/// This function takes 2^{num_var +1} number of scalar multiplications over
340/// G1:
341/// - it proceeds with `num_var` number of rounds,
342/// - at round i, we compute an MSM for `2^{num_var - i + 1}` number of G2
343///   elements.
344fn open_internal<E: PairingEngine>(
345    prover_param: &MultilinearProverParam<E>,
346    polynomial: &DenseMultilinearExtension<E::Fr>,
347    point: &[E::Fr],
348) -> Result<(MultilinearKzgProof<E>, E::Fr), PCSError> {
349    let open_timer = start_timer!(|| format!("open mle with {} variable", polynomial.num_vars));
350
351    if polynomial.num_vars() > prover_param.num_vars {
352        return Err(PCSError::InvalidParameters(format!(
353            "Polynomial num_vars {} exceed the limit {}",
354            polynomial.num_vars, prover_param.num_vars
355        )));
356    }
357
358    if polynomial.num_vars() != point.len() {
359        return Err(PCSError::InvalidParameters(format!(
360            "Polynomial num_vars {} does not match point len {}",
361            polynomial.num_vars,
362            point.len()
363        )));
364    }
365
366    let nv = polynomial.num_vars();
367    let ignored = prover_param.num_vars - nv;
368    let mut r: Vec<Vec<E::Fr>> = (0..nv + 1).map(|_| Vec::new()).collect();
369    let mut q: Vec<Vec<E::Fr>> = (0..nv + 1).map(|_| Vec::new()).collect();
370
371    r[nv] = polynomial.to_evaluations();
372
373    let mut proofs = Vec::new();
374
375    for (i, (&point_at_k, gi)) in point
376        .iter()
377        .zip(prover_param.powers_of_g[ignored..].iter())
378        .take(nv)
379        .enumerate()
380    {
381        let ith_round = start_timer!(|| format!("{}-th round", i));
382
383        let k = nv - i;
384        let cur_dim = 1 << (k - 1);
385        let mut cur_q = vec![E::Fr::zero(); cur_dim];
386        let mut cur_r = vec![E::Fr::zero(); cur_dim];
387        let one_minus_point_at_k = E::Fr::one() - point_at_k;
388
389        let ith_round_eval = start_timer!(|| format!("{}-th round eval", i));
390        for b in 0..(1 << (k - 1)) {
391            // q_b = pre_r [2^b + 1] - pre_r [2^b]
392            cur_q[b] = r[k][(b << 1) + 1] - r[k][b << 1];
393
394            // r_b = pre_r [2^b]*(1-p) + pre_r [2^b + 1] * p
395            cur_r[b] = r[k][b << 1] * one_minus_point_at_k + (r[k][(b << 1) + 1] * point_at_k);
396        }
397        end_timer!(ith_round_eval);
398        let scalars: Vec<_> = (0..(1 << k)).map(|x| cur_q[x >> 1].into_repr()).collect();
399
400        q[k] = cur_q;
401        r[k - 1] = cur_r;
402
403        // this is a MSM over G1 and is likely to be the bottleneck
404        proofs.push(VariableBaseMSM::multi_scalar_mul(&gi.evals, &scalars).into_affine());
405        end_timer!(ith_round);
406    }
407    let eval = evaluate_opt(polynomial, point);
408    end_timer!(open_timer);
409    Ok((MultilinearKzgProof { proofs }, eval))
410}
411
412/// Verifies that `value` is the evaluation at `x` of the polynomial
413/// committed inside `comm`.
414///
415/// This function takes
416/// - num_var number of pairing product.
417/// - num_var number of MSM
418fn verify_internal<E: PairingEngine>(
419    verifier_param: &MultilinearVerifierParam<E>,
420    commitment: &Commitment<E>,
421    point: &[E::Fr],
422    value: &E::Fr,
423    proof: &MultilinearKzgProof<E>,
424) -> Result<bool, PCSError> {
425    let verify_timer = start_timer!(|| "verify");
426    let num_var = point.len();
427
428    if num_var > verifier_param.num_vars {
429        return Err(PCSError::InvalidParameters(format!(
430            "point length ({}) exceeds param limit ({})",
431            num_var, verifier_param.num_vars
432        )));
433    }
434
435    let ignored = verifier_param.num_vars - num_var;
436    let prepare_inputs_timer = start_timer!(|| "prepare pairing inputs");
437
438    let scalar_size = E::Fr::size_in_bits();
439    let window_size = FixedBaseMSM::get_mul_window_size(num_var);
440
441    let h_table = FixedBaseMSM::get_window_table(
442        scalar_size,
443        window_size,
444        verifier_param.h.into_projective(),
445    );
446    let h_mul: Vec<E::G2Projective> =
447        FixedBaseMSM::multi_scalar_mul(scalar_size, window_size, &h_table, point);
448
449    let h_vec: Vec<_> = (0..num_var)
450        .map(|i| verifier_param.h_mask[ignored + i].into_projective() - h_mul[i])
451        .collect();
452    let h_vec: Vec<E::G2Affine> = E::G2Projective::batch_normalization_into_affine(&h_vec);
453    end_timer!(prepare_inputs_timer);
454
455    let pairing_product_timer = start_timer!(|| "pairing product");
456
457    let mut pairings: Vec<_> = proof
458        .proofs
459        .iter()
460        .map(|&x| E::G1Prepared::from(x))
461        .zip(h_vec.into_iter().take(num_var).map(E::G2Prepared::from))
462        .collect();
463
464    pairings.push((
465        E::G1Prepared::from(
466            (verifier_param.g.mul(*value) - commitment.0.into_projective()).into_affine(),
467        ),
468        E::G2Prepared::from(verifier_param.h),
469    ));
470
471    let res = E::product_of_pairings(pairings.iter()) == E::Fqk::one();
472
473    end_timer!(pairing_product_timer);
474    end_timer!(verify_timer);
475    Ok(res)
476}
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481    use crate::multilinear_kzg::util::compute_qx_degree;
482    use arithmetic::get_batched_nv;
483    use ark_bls12_381::Bls12_381;
484    use ark_ec::PairingEngine;
485    use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
486    use ark_std::{log2, rand::RngCore, test_rng, vec::Vec, UniformRand};
487
488    type E = Bls12_381;
489    type Fr = <E as PairingEngine>::Fr;
490
491    fn test_single_helper<R: RngCore + CryptoRng>(
492        params: &(MultilinearUniversalParams<E>, UnivariateUniversalParams<E>),
493        poly: &Rc<DenseMultilinearExtension<Fr>>,
494        rng: &mut R,
495    ) -> Result<(), PCSError> {
496        let nv = poly.num_vars();
497        assert_ne!(nv, 0);
498        let uni_degree = 1;
499        let (ck, vk) = MultilinearKzgPCS::trim(params, uni_degree, Some(nv + 1))?;
500        let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect();
501        let com = MultilinearKzgPCS::commit(&ck, poly)?;
502        let (proof, value) = MultilinearKzgPCS::open(&ck, poly, &point)?;
503
504        assert!(MultilinearKzgPCS::verify(
505            &vk, &com, &point, &value, &proof
506        )?);
507
508        let value = Fr::rand(rng);
509        assert!(!MultilinearKzgPCS::verify(
510            &vk, &com, &point, &value, &proof
511        )?);
512
513        Ok(())
514    }
515
516    #[test]
517    fn test_single_commit() -> Result<(), PCSError> {
518        let mut rng = test_rng();
519
520        let params = MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 10)?;
521
522        // normal polynomials
523        let poly1 = Rc::new(DenseMultilinearExtension::rand(8, &mut rng));
524        test_single_helper(&params, &poly1, &mut rng)?;
525
526        // single-variate polynomials
527        let poly2 = Rc::new(DenseMultilinearExtension::rand(1, &mut rng));
528        test_single_helper(&params, &poly2, &mut rng)?;
529
530        Ok(())
531    }
532
533    #[test]
534    fn setup_commit_verify_constant_polynomial() {
535        let mut rng = test_rng();
536
537        // normal polynomials
538        assert!(MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 0).is_err());
539    }
540
541    fn test_multi_open_single_poly_helper<R: RngCore + CryptoRng>(
542        params: &(MultilinearUniversalParams<E>, UnivariateUniversalParams<E>),
543        poly: Rc<DenseMultilinearExtension<Fr>>,
544        num_open: usize,
545        rng: &mut R,
546    ) -> Result<(), PCSError> {
547        let nv = poly.num_vars();
548        assert_ne!(nv, 0);
549        let uni_degree = 1024;
550        let (ck, vk) = MultilinearKzgPCS::trim(params, uni_degree, Some(nv + 1))?;
551        let mut points = vec![];
552        for _ in 0..num_open {
553            let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect();
554            points.push(point)
555        }
556        let com = MultilinearKzgPCS::commit(&ck, &poly)?;
557        let (proof, mut values) =
558            MultilinearKzgPCS::multi_open_single_poly(&ck, &com, &poly, &points)?;
559        for (a, b) in values.iter().zip(points.iter()) {
560            let p = poly.evaluate(&b).unwrap();
561            assert_eq!(*a, p);
562        }
563
564        assert!(MultilinearKzgPCS::batch_verify_single_poly(
565            &vk, &com, &points, &values, &proof
566        )?);
567
568        values[0] = Fr::rand(rng);
569        assert!(!MultilinearKzgPCS::batch_verify_single_poly(
570            &vk, &com, &points, &values, &proof
571        )?);
572        Ok(())
573    }
574
575    #[test]
576    fn test_multi_open_single_poly() -> Result<(), PCSError> {
577        let mut rng = test_rng();
578
579        let params = MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 15)?;
580
581        for nv in 1..10 {
582            for num_open in 2..10 {
583                let poly1 = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng));
584                test_multi_open_single_poly_helper(&params, poly1, num_open, &mut rng)?;
585            }
586        }
587
588        Ok(())
589    }
590
591    fn test_multi_open_helper<R: RngCore + CryptoRng>(
592        params: &(MultilinearUniversalParams<E>, UnivariateUniversalParams<E>),
593        polys: &[Rc<DenseMultilinearExtension<Fr>>],
594        num_open: usize,
595        rng: &mut R,
596    ) -> Result<(), PCSError> {
597        let nv = polys[0].num_vars();
598        assert_ne!(nv, 0);
599        let merged_nv = get_batched_nv(nv, polys.len());
600        let qx_degree = compute_qx_degree(merged_nv, polys.len());
601        let padded_qx_degree = 1usize << log2(qx_degree);
602
603        let (ck, vk) = MultilinearKzgPCS::trim(params, padded_qx_degree, Some(merged_nv))?;
604        let mut points = vec![];
605        for _ in 0..num_open {
606            let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect();
607            points.push(point)
608        }
609        let com = MultilinearKzgPCS::multi_commit(&ck, &polys)?;
610        let (proof, mut values) = MultilinearKzgPCS::multi_open(&ck, &com, polys, &points)?;
611
612        assert!(MultilinearKzgPCS::batch_verify(
613            &vk, &com, &points, &values, &proof, rng
614        )?);
615
616        values[0] = Fr::rand(rng);
617        assert!(!MultilinearKzgPCS::batch_verify_single_poly(
618            &vk, &com, &points, &values, &proof
619        )?);
620
621        Ok(())
622    }
623
624    #[test]
625    fn test_multi_open() -> Result<(), PCSError> {
626        let mut rng = test_rng();
627
628        let params = MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 15)?;
629
630        // normal polynomials
631        for nv in 1..10 {
632            for num_open in 1..4 {
633                let mut polys = vec![];
634                for _ in 0..num_open {
635                    let poly = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng));
636                    polys.push(poly)
637                }
638
639                test_multi_open_helper(&params, &polys, num_open, &mut rng)?;
640            }
641        }
642        Ok(())
643    }
644}