jf_pcs/univariate_kzg/
mod.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//! Main module for univariate KZG commitment scheme
8
9use crate::{
10    poly::GeneralDensePolynomial, prelude::Commitment, toeplitz::ToeplitzMatrix, PCSError,
11    PolynomialCommitmentScheme, StructuredReferenceString, UnivariatePCS,
12};
13use ark_ec::{
14    pairing::Pairing, scalar_mul::variable_base::VariableBaseMSM, AffineRepr, CurveGroup,
15};
16use ark_ff::{FftField, Field, PrimeField};
17#[cfg(not(feature = "seq-fk-23"))]
18use ark_poly::EvaluationDomain;
19use ark_poly::{
20    univariate::DensePolynomial, DenseUVPolynomial, Polynomial, Radix2EvaluationDomain,
21};
22use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
23use ark_std::{
24    borrow::Borrow,
25    end_timer, format,
26    marker::PhantomData,
27    ops::Mul,
28    rand::{CryptoRng, RngCore},
29    start_timer,
30    string::ToString,
31    vec,
32    vec::Vec,
33    One, UniformRand, Zero,
34};
35use jf_utils::par_utils::parallelizable_slice_iter;
36#[cfg(feature = "parallel")]
37use rayon::prelude::*;
38use srs::{UnivariateProverParam, UnivariateUniversalParams, UnivariateVerifierParam};
39
40pub(crate) mod srs;
41
42/// KZG Polynomial Commitment Scheme on univariate polynomial.
43pub struct UnivariateKzgPCS<E> {
44    #[doc(hidden)]
45    phantom: PhantomData<E>,
46}
47
48#[derive(Derivative, CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
49#[derivative(Hash)]
50/// proof of opening
51pub struct UnivariateKzgProof<E: Pairing> {
52    /// Evaluation of quotients
53    pub proof: E::G1Affine,
54}
55/// batch proof
56pub type UnivariateKzgBatchProof<E> = Vec<UnivariateKzgProof<E>>;
57
58impl<E: Pairing> PolynomialCommitmentScheme for UnivariateKzgPCS<E> {
59    // Config
60    type SRS = UnivariateUniversalParams<E>;
61    // Polynomial and its associated types
62    type Polynomial = DensePolynomial<E::ScalarField>;
63    type Point = E::ScalarField;
64    type Evaluation = E::ScalarField;
65    // Polynomial and its associated types
66    type Commitment = Commitment<E>;
67    type BatchCommitment = Vec<Self::Commitment>;
68    type Proof = UnivariateKzgProof<E>;
69    type BatchProof = UnivariateKzgBatchProof<E>;
70
71    /// Trim the universal parameters to specialize the public parameters.
72    /// Input `max_degree` for univariate.
73    /// `supported_num_vars` must be None or an error is returned.
74    fn trim(
75        srs: impl Borrow<Self::SRS>,
76        supported_degree: usize,
77        supported_num_vars: Option<usize>,
78    ) -> Result<(UnivariateProverParam<E>, UnivariateVerifierParam<E>), PCSError> {
79        if supported_num_vars.is_some() {
80            return Err(PCSError::InvalidParameters(
81                "univariate should not receive a num_var param".to_string(),
82            ));
83        }
84        srs.borrow().trim(supported_degree)
85    }
86
87    /// Generate a commitment for a polynomial
88    /// Note that the scheme is not hiding
89    fn commit(
90        prover_param: impl Borrow<UnivariateProverParam<E>>,
91        poly: &Self::Polynomial,
92    ) -> Result<Self::Commitment, PCSError> {
93        let prover_param = prover_param.borrow();
94
95        #[cfg(feature = "kzg-print-trace")]
96        let commit_time =
97            start_timer!(|| format!("Committing to polynomial of degree {} ", poly.degree()));
98
99        if poly.degree() > prover_param.powers_of_g.len() {
100            return Err(PCSError::InvalidParameters(format!(
101                "poly degree {} is larger than allowed {}",
102                poly.degree(),
103                prover_param.powers_of_g.len()
104            )));
105        }
106
107        let (num_leading_zeros, plain_coeffs) = skip_leading_zeros_and_convert_to_bigints(poly);
108
109        #[cfg(feature = "kzg-print-trace")]
110        let msm_time = start_timer!(|| "MSM to compute commitment to plaintext
111        poly");
112
113        let commitment = E::G1::msm_bigint(
114            &prover_param.powers_of_g[num_leading_zeros..],
115            &plain_coeffs,
116        )
117        .into_affine();
118
119        #[cfg(feature = "kzg-print-trace")]
120        end_timer!(msm_time);
121        #[cfg(feature = "kzg-print-trace")]
122        end_timer!(commit_time);
123        Ok(Commitment(commitment))
124    }
125
126    /// Generate a commitment for a list of polynomials
127    fn batch_commit(
128        prover_param: impl Borrow<UnivariateProverParam<E>>,
129        polys: &[Self::Polynomial],
130    ) -> Result<Self::BatchCommitment, PCSError> {
131        let prover_param = prover_param.borrow();
132        let commit_time = start_timer!(|| format!("batch commit {} polynomials", polys.len()));
133        let res = parallelizable_slice_iter(polys)
134            .map(|poly| Self::commit(prover_param, poly))
135            .collect::<Result<Vec<Self::Commitment>, PCSError>>()?;
136
137        end_timer!(commit_time);
138        Ok(res)
139    }
140
141    /// On input a polynomial `p` and a point `point`, outputs a proof for the
142    /// same.
143    fn open(
144        prover_param: impl Borrow<UnivariateProverParam<E>>,
145        polynomial: &Self::Polynomial,
146        point: &Self::Point,
147    ) -> Result<(Self::Proof, Self::Evaluation), PCSError> {
148        #[cfg(feature = "kzg-print-trace")]
149        let open_time =
150            start_timer!(|| format!("Opening polynomial of degree {}", polynomial.degree()));
151
152        let divisor = Self::Polynomial::from_coefficients_vec(vec![-*point, E::ScalarField::one()]);
153
154        #[cfg(feature = "kzg-print-trace")]
155        let witness_time = start_timer!(|| "Computing witness polynomial");
156
157        let witness_polynomial = polynomial / &divisor;
158
159        #[cfg(feature = "kzg-print-trace")]
160        end_timer!(witness_time);
161
162        let (num_leading_zeros, witness_coeffs) =
163            skip_leading_zeros_and_convert_to_bigints(&witness_polynomial);
164
165        let proof: E::G1Affine = E::G1::msm_bigint(
166            &prover_param.borrow().powers_of_g[num_leading_zeros..],
167            &witness_coeffs,
168        )
169        .into_affine();
170
171        // TODO offer an `open()` that doesn't also evaluate
172        // https://github.com/EspressoSystems/jellyfish/issues/426
173        let eval = polynomial.evaluate(point);
174
175        #[cfg(feature = "kzg-print-trace")]
176        end_timer!(open_time);
177
178        Ok((Self::Proof { proof }, eval))
179    }
180
181    /// Input a list of polynomials, and the same number of points,
182    /// compute a multi-opening for all the polynomials.
183    // This is a naive approach
184    // TODO: to implement the more efficient batch opening algorithm
185    // (e.g., the appendix C.4 in https://eprint.iacr.org/2020/1536.pdf)
186    fn batch_open(
187        prover_param: impl Borrow<UnivariateProverParam<E>>,
188        _multi_commitment: &Self::BatchCommitment,
189        polynomials: &[Self::Polynomial],
190        points: &[Self::Point],
191    ) -> Result<(Self::BatchProof, Vec<Self::Evaluation>), PCSError> {
192        let open_time = start_timer!(|| format!("batch opening {} polynomials", polynomials.len()));
193        if polynomials.len() != points.len() {
194            return Err(PCSError::InvalidParameters(format!(
195                "poly length {} is different from points length {}",
196                polynomials.len(),
197                points.len()
198            )));
199        }
200        let mut batch_proof = vec![];
201        let mut evals = vec![];
202        for (poly, point) in polynomials.iter().zip(points.iter()) {
203            let (proof, eval) = Self::open(prover_param.borrow(), poly, point)?;
204            batch_proof.push(proof);
205            evals.push(eval);
206        }
207
208        end_timer!(open_time);
209        Ok((batch_proof, evals))
210    }
211    /// Verifies that `value` is the evaluation at `x` of the polynomial
212    /// committed inside `comm`.
213    fn verify(
214        verifier_param: &UnivariateVerifierParam<E>,
215        commitment: &Self::Commitment,
216        point: &Self::Point,
217        value: &E::ScalarField,
218        proof: &Self::Proof,
219    ) -> Result<bool, PCSError> {
220        let check_time = start_timer!(|| "Checking evaluation");
221        let pairing_inputs_l: Vec<E::G1Prepared> = vec![
222            (verifier_param.g * value - proof.proof * point - commitment.0.into_group())
223                .into_affine()
224                .into(),
225            proof.proof.into(),
226        ];
227        let pairing_inputs_r: Vec<E::G2Prepared> =
228            vec![verifier_param.h.into(), verifier_param.beta_h.into()];
229
230        let res = E::multi_pairing(pairing_inputs_l, pairing_inputs_r)
231            .0
232            .is_one();
233
234        end_timer!(check_time, || format!("Result: {res}"));
235        Ok(res)
236    }
237
238    /// Verifies that `value_i` is the evaluation at `x_i` of the polynomial
239    /// `poly_i` committed inside `comm`.
240    // This is a naive approach
241    // TODO: to implement the more efficient batch verification algorithm
242    // (e.g., the appendix C.4 in https://eprint.iacr.org/2020/1536.pdf)
243    fn batch_verify<R: RngCore + CryptoRng>(
244        verifier_param: &UnivariateVerifierParam<E>,
245        multi_commitment: &Self::BatchCommitment,
246        points: &[Self::Point],
247        values: &[E::ScalarField],
248        batch_proof: &Self::BatchProof,
249        rng: &mut R,
250    ) -> Result<bool, PCSError> {
251        let check_time =
252            start_timer!(|| format!("Checking {} evaluation proofs", multi_commitment.len()));
253
254        let mut total_c = <E::G1>::zero();
255        let mut total_w = <E::G1>::zero();
256
257        let combination_time = start_timer!(|| "Combining commitments and proofs");
258        let mut randomizer = E::ScalarField::one();
259        // Instead of multiplying g and gamma_g in each turn, we simply accumulate
260        // their coefficients and perform a final multiplication at the end.
261        let mut g_multiplier = E::ScalarField::zero();
262        for (((c, z), v), proof) in multi_commitment
263            .iter()
264            .zip(points)
265            .zip(values)
266            .zip(batch_proof)
267        {
268            let w = proof.proof;
269            let mut temp = w.mul(*z);
270            temp += &c.0;
271            let c = temp;
272            g_multiplier += &(randomizer * v);
273            total_c += c * randomizer;
274            total_w += w * randomizer;
275            // We don't need to sample randomizers from the full field,
276            // only from 128-bit strings.
277            randomizer = u128::rand(rng).into();
278        }
279        total_c -= &verifier_param.g.mul(g_multiplier);
280        end_timer!(combination_time);
281
282        let to_affine_time = start_timer!(|| "Converting results to affine for pairing");
283        let affine_points = E::G1::normalize_batch(&[-total_w, total_c]);
284        let (total_w, total_c) = (affine_points[0], affine_points[1]);
285        end_timer!(to_affine_time);
286
287        let pairing_time = start_timer!(|| "Performing product of pairings");
288        let result = E::multi_pairing(
289            [total_w, total_c],
290            [verifier_param.beta_h, verifier_param.h],
291        )
292        .0
293        .is_one();
294        end_timer!(pairing_time);
295        end_timer!(check_time, || format!("Result: {result}"));
296        Ok(result)
297    }
298
299    /// Fast computation of batch opening for a single polynomial at multiple
300    /// arbitrary points.
301    /// Details see Sec 2.1~2.3 of [FK23](https://eprint.iacr.org/2023/033.pdf).
302    ///
303    /// Only accept `polynomial` with power-of-two degree, no constraint on the
304    /// size of `points`
305    fn multi_open(
306        prover_param: impl Borrow<UnivariateProverParam<E>>,
307        polynomial: &Self::Polynomial,
308        points: &[Self::Point],
309    ) -> Result<(Vec<Self::Proof>, Vec<Self::Evaluation>), PCSError> {
310        let h_poly = Self::compute_h_poly_in_fk23(prover_param, &polynomial.coeffs)?;
311        let proofs: Vec<_> = h_poly
312            .batch_evaluate(points)
313            .into_iter()
314            .map(|g| UnivariateKzgProof {
315                proof: g.into_affine(),
316            })
317            .collect();
318
319        // Evaluate at all points
320        let evals =
321            GeneralDensePolynomial::from_coeff_slice(&polynomial.coeffs).batch_evaluate(points);
322        Ok((proofs, evals))
323    }
324}
325
326impl<E: Pairing> UnivariatePCS for UnivariateKzgPCS<E> {
327    fn multi_open_rou_proofs(
328        prover_param: impl Borrow<<Self::SRS as StructuredReferenceString>::ProverParam>,
329        polynomial: &Self::Polynomial,
330        num_points: usize,
331        domain: &Radix2EvaluationDomain<Self::Evaluation>,
332    ) -> Result<Vec<Self::Proof>, PCSError> {
333        #[cfg(not(feature = "seq-fk-23"))]
334        {
335            let h_poly_timer = start_timer!(|| "compute h_poly");
336            let h_poly = Self::compute_h_poly_parallel(prover_param, &polynomial.coeffs)?;
337            end_timer!(h_poly_timer);
338            let small_domain: Radix2EvaluationDomain<Self::Evaluation> =
339                Radix2EvaluationDomain::new(h_poly.degree() + 1).ok_or_else(|| {
340                    PCSError::InvalidParameters(format!(
341                        "failed to create a domain of size {}",
342                        h_poly.degree() + 1,
343                    ))
344                })?;
345            let parallel_factor = domain.size() / small_domain.size();
346
347            let mut offsets = Vec::with_capacity(parallel_factor);
348            offsets.push(Self::Evaluation::one());
349            for _ in 1..parallel_factor {
350                offsets.push(domain.group_gen() * offsets.last().unwrap());
351            }
352            let proofs_timer = start_timer!(|| format!(
353                "gen eval proofs with parallel_factor {} and num_points {}",
354                parallel_factor, num_points,
355            ));
356            let proofs: Vec<Vec<_>> = parallelizable_slice_iter(&offsets)
357                .map(|&offset| {
358                    small_domain
359                        .get_coset(offset)
360                        .unwrap()
361                        .fft(&h_poly.coeffs[..])
362                })
363                .collect();
364            end_timer!(proofs_timer);
365            let mut res = vec![];
366            for j in 0..small_domain.size() {
367                for proof in proofs.iter() {
368                    res.push(UnivariateKzgProof {
369                        proof: proof[j].into_affine(),
370                    });
371                }
372            }
373            res = res.into_iter().take(num_points).collect();
374            Ok(res)
375        }
376
377        #[cfg(feature = "seq-fk-23")]
378        {
379            let h_poly_timer = start_timer!(|| "compute h_poly");
380            let mut h_poly = Self::compute_h_poly_in_fk23(prover_param, &polynomial.coeffs)?;
381            end_timer!(h_poly_timer);
382            let proofs_timer = start_timer!(|| "gen eval proofs");
383            let proofs: Vec<_> = h_poly
384                .batch_evaluate_rou(domain)?
385                .into_iter()
386                .take(num_points)
387                .map(|g| UnivariateKzgProof {
388                    proof: g.into_affine(),
389                })
390                .collect();
391            end_timer!(proofs_timer);
392            Ok(proofs)
393        }
394    }
395
396    /// Compute the evaluations in [`Self::multi_open_rou()`].
397    fn multi_open_rou_evals(
398        polynomial: &Self::Polynomial,
399        num_points: usize,
400        domain: &Radix2EvaluationDomain<Self::Evaluation>,
401    ) -> Result<Vec<Self::Evaluation>, PCSError> {
402        let evals = GeneralDensePolynomial::from_coeff_slice(&polynomial.coeffs)
403            .batch_evaluate_rou(domain)?
404            .into_iter()
405            .take(num_points)
406            .collect();
407        Ok(evals)
408    }
409
410    /// Input a polynomial, and multiple evaluation points,
411    /// compute a batch opening proof for the multiple points of the same
412    /// polynomial.
413    ///
414    /// Warning: don't use it when `points.len()` is large
415    fn multi_point_open(
416        prover_param: impl Borrow<<Self::SRS as StructuredReferenceString>::ProverParam>,
417        polynomial: &Self::Polynomial,
418        points: &[Self::Point],
419    ) -> Result<(Self::Proof, Vec<Self::Evaluation>), PCSError> {
420        if points.is_empty() {
421            return Err(PCSError::InvalidParameters(
422                "no point to evaluate and open".to_string(),
423            ));
424        }
425        let open_time = start_timer!(|| format!(
426            "Opening polynomial of degree {} at {} points",
427            polynomial.degree(),
428            points.len()
429        ));
430
431        let evals_time = start_timer!(|| "Computing polynomial evaluations");
432        let evals: Vec<Self::Evaluation> = points
433            .iter()
434            .map(|point| polynomial.evaluate(point))
435            .collect();
436        end_timer!(evals_time);
437
438        // Compute the polynomial \prod_i (X-point_i)
439        // O(|points|^2) complexity and we assume the number of points is small
440        // TODO: optimize complexity if support large number of points
441        // https://github.com/EspressoSystems/jellyfish/issues/436
442        let vanish_poly =
443            Self::Polynomial::from_coefficients_vec(vec![-points[0], E::ScalarField::one()]);
444        let divisor: Self::Polynomial = points.iter().skip(1).fold(vanish_poly, |acc, point| {
445            &acc * &Self::Polynomial::from_coefficients_vec(vec![-*point, E::ScalarField::one()])
446        });
447
448        // Compute quotient poly
449        // Quadratic complexity as Arkworks is using naive long division
450        // TODO: using FFTs for division
451        // https://github.com/EspressoSystems/jellyfish/issues/436
452        let witness_time = start_timer!(|| "Computing witness polynomial");
453        let witness_polynomial = polynomial / &divisor;
454        end_timer!(witness_time);
455
456        let (num_leading_zeros, witness_coeffs) =
457            skip_leading_zeros_and_convert_to_bigints(&witness_polynomial);
458
459        let proof: E::G1Affine = E::G1::msm_bigint(
460            &prover_param.borrow().powers_of_g[num_leading_zeros..],
461            &witness_coeffs,
462        )
463        .into_affine();
464
465        end_timer!(open_time);
466        Ok((Self::Proof { proof }, evals))
467    }
468
469    /// Verifies that `values` are the evaluation at the `points` of the
470    /// polynomial committed inside `comm`.
471    ///
472    /// Warning: don't use it when `points.len()` is large
473    fn multi_point_verify(
474        verifier_param: impl Borrow<<Self::SRS as StructuredReferenceString>::VerifierParam>,
475        commitment: &Self::Commitment,
476        points: &[Self::Point],
477        values: &[Self::Evaluation],
478        proof: &Self::Proof,
479    ) -> Result<bool, PCSError> {
480        if points.is_empty() {
481            return Err(PCSError::InvalidParameters(
482                "no evaluation to check".to_string(),
483            ));
484        }
485        if points.len() != values.len() {
486            return Err(PCSError::InvalidParameters(format!(
487                "the number of points {} is different from the number of evaluation values {}",
488                points.len(),
489                values.len(),
490            )));
491        }
492        if verifier_param.borrow().powers_of_h.len() < points.len() + 1 {
493            return Err(PCSError::InvalidParameters(format!(
494                "the number of powers of beta times h {} in SRS <= the number of evaluation points {}",
495                verifier_param.borrow().powers_of_h.len(),
496                points.len(),
497            )));
498        }
499
500        let check_time = start_timer!(|| "Checking evaluations");
501
502        // Compute the commitment to I(X) = sum_i eval_i * L_{point_i}(X)
503        // O(|points|^2) complexity and we assume the number of points is small
504        // TODO: optimize complexity if support large number of points
505        // https://github.com/EspressoSystems/jellyfish/issues/436
506        let evals_poly = values
507            .iter()
508            .enumerate()
509            .fold(Self::Polynomial::zero(), |acc, (i, &value)| {
510                acc + lagrange_poly(points, i, value)
511            });
512
513        let (num_leading_zeros, evals_poly_coeffs) =
514            skip_leading_zeros_and_convert_to_bigints(&evals_poly);
515
516        let evals_cm: E::G1Affine = E::G1::msm_bigint(
517            &verifier_param.borrow().powers_of_g[num_leading_zeros..],
518            &evals_poly_coeffs,
519        )
520        .into_affine();
521
522        // Compute the commitment to Z(X) = prod_i (X-point_i)
523        // O(|points|^2) complexity and we assume the number of points is small
524        // TODO: optimize complexity if support large number of points
525        // https://github.com/EspressoSystems/jellyfish/issues/436
526        let vanish_poly =
527            Self::Polynomial::from_coefficients_vec(vec![-points[0], E::ScalarField::one()]);
528        let vanish_poly: Self::Polynomial =
529            points.iter().skip(1).fold(vanish_poly, |acc, point| {
530                &acc * &Self::Polynomial::from_coefficients_vec(vec![
531                    -*point,
532                    E::ScalarField::one(),
533                ])
534            });
535
536        let (num_leading_zeros, vanish_poly_coeffs) =
537            skip_leading_zeros_and_convert_to_bigints(&vanish_poly);
538
539        let vanish_cm: E::G2Affine = E::G2::msm_bigint(
540            &verifier_param.borrow().powers_of_h[num_leading_zeros..],
541            &vanish_poly_coeffs,
542        )
543        .into_affine();
544
545        // Check the pairing
546        let pairing_inputs_l: Vec<E::G1Prepared> = vec![
547            (evals_cm.into_group() - commitment.0.into_group())
548                .into_affine()
549                .into(),
550            proof.proof.into(),
551        ];
552        let pairing_inputs_r: Vec<E::G2Prepared> =
553            vec![verifier_param.borrow().h.into(), vanish_cm.into()];
554
555        let res = E::multi_pairing(pairing_inputs_l, pairing_inputs_r)
556            .0
557            .is_one();
558
559        end_timer!(check_time, || format!("Result: {res}"));
560        Ok(res)
561    }
562}
563
564impl<E, F> UnivariateKzgPCS<E>
565where
566    E: Pairing<ScalarField = F>,
567    F: FftField,
568{
569    // Computes h_poly as the matrix-vector product on page 3 of https://eprint.iacr.org/2023/033.pdf via naive row-column inner products in parallel
570    #[cfg(not(feature = "seq-fk-23"))]
571    fn compute_h_poly_parallel(
572        prover_param: impl Borrow<UnivariateProverParam<E>>,
573        poly_coeffs: &[E::ScalarField],
574    ) -> Result<GeneralDensePolynomial<E::G1, F>, PCSError> {
575        if poly_coeffs.is_empty() {
576            return Ok(GeneralDensePolynomial::from_coeff_vec(vec![]));
577        }
578        let h_poly_deg = poly_coeffs.len() - 1;
579        let srs_vec: Vec<E::G1Affine> = prover_param
580            .borrow()
581            .powers_of_g
582            .iter()
583            .take(h_poly_deg)
584            .rev()
585            .cloned()
586            .collect();
587
588        let matrix: Vec<Vec<E::ScalarField>> = (0..h_poly_deg)
589            .map(|i| {
590                poly_coeffs
591                    .iter()
592                    .rev()
593                    .take(h_poly_deg - i)
594                    .copied()
595                    .collect()
596            })
597            .collect();
598        let h_vec: Vec<E::G1> = parallelizable_slice_iter(&matrix)
599            .map(|coeffs| E::G1::msm(&srs_vec[h_poly_deg - coeffs.len()..], &coeffs[..]).unwrap())
600            .collect();
601        Ok(GeneralDensePolynomial::from_coeff_vec(h_vec))
602    }
603
604    // Sec 2.2. of <https://eprint.iacr.org/2023/033>
605    fn compute_h_poly_in_fk23(
606        prover_param: impl Borrow<UnivariateProverParam<E>>,
607        poly_coeffs: &[E::ScalarField],
608    ) -> Result<GeneralDensePolynomial<E::G1, F>, PCSError> {
609        // First, pad to power_of_two, since Toeplitz mul only works for 2^k
610        let mut padded_coeffs: Vec<F> = poly_coeffs.to_vec();
611        let padded_degree = padded_coeffs
612            .len()
613            .saturating_sub(1)
614            .checked_next_power_of_two()
615            .ok_or_else(|| {
616                PCSError::InvalidParameters(ark_std::format!(
617                    "Next power of two overflows! Got: {}",
618                    padded_coeffs.len().saturating_sub(1)
619                ))
620            })?;
621        let padded_len = padded_degree + 1;
622        padded_coeffs.resize(padded_len, F::zero());
623
624        // Step 1. compute \vec{h} using fast Toeplitz matrix multiplication
625        // 1.1 Toeplitz matrix A (named `poly_coeff_matrix` here)
626        let mut toep_col = vec![*padded_coeffs
627            .last()
628            .ok_or_else(|| PCSError::InvalidParameters("poly degree should >= 1".to_string()))?];
629        toep_col.resize(padded_degree, <<E as Pairing>::ScalarField as Field>::ZERO);
630        let toep_row = padded_coeffs.iter().skip(1).rev().cloned().collect();
631        let poly_coeff_matrix = ToeplitzMatrix::new(toep_col, toep_row)?;
632
633        // 1.2 vector s (named `srs_vec` here)
634        let srs_vec: Vec<E::G1> = prover_param
635            .borrow()
636            .powers_of_g
637            .iter()
638            .take(padded_degree)
639            .rev()
640            .cloned()
641            .map(|g| g.into_group())
642            .collect();
643
644        // 1.3 compute \vec{h}
645        let h_vec = poly_coeff_matrix.fast_vec_mul(&srs_vec)?;
646
647        Ok(GeneralDensePolynomial::from_coeff_vec(h_vec))
648    }
649}
650
651#[cfg(feature = "icicle")]
652pub(crate) mod icicle {
653    use super::*;
654    use crate::icicle_deps::{curves::*, *};
655    use itertools::Itertools;
656
657    /// Trait for GPU-accelerated PCS.commit APIs
658    pub trait GPUCommittable<E: Pairing> {
659        /// Equivalent Curve from ICICLE
660        type IC: IcicleCurve + MSM<Self::IC>;
661
662        /// The full cycle of computing poly-commit on GPU
663        fn gpu_commit(
664            prover_param: impl Borrow<UnivariateProverParam<E>>,
665            poly: &DensePolynomial<E::ScalarField>,
666        ) -> Result<Commitment<E>, PCSError> {
667            let stream = warmup_new_stream().unwrap();
668
669            #[cfg(feature = "kzg-print-trace")]
670            let commit_time =
671                start_timer!(|| format!("Committing to polynomial of degree {} ", poly.degree()));
672
673            let mut srs_on_gpu = Self::load_prover_param_to_gpu(prover_param, poly.degree())?;
674            let comm = Self::gpu_commit_with_loaded_prover_param(&mut srs_on_gpu, poly, &stream)?;
675            #[cfg(feature = "kzg-print-trace")]
676            end_timer!(commit_time);
677
678            Ok(comm)
679        }
680
681        /// Compute `PCS::commit()` with SRS already loaded on GPU
682        /// Return the commitment on CPU
683        ///
684        /// # NOTE
685        /// - we assume a stream is already prepared, you can create one if not
686        /// via `warmup_new_stream()`
687        fn gpu_commit_with_loaded_prover_param(
688            prover_param_on_gpu: &mut HostOrDeviceSlice<'_, IcicleAffine<Self::IC>>,
689            poly: &DensePolynomial<E::ScalarField>,
690            stream: &CudaStream,
691        ) -> Result<Commitment<E>, PCSError> {
692            let poly_on_gpu = Self::load_poly_to_gpu(poly)?;
693            let msm_result_on_gpu =
694                Self::commit_on_gpu(prover_param_on_gpu, &poly_on_gpu, 1, stream)?;
695            let comm = Self::load_commitments_to_host(msm_result_on_gpu, stream)?[0];
696
697            Ok(comm)
698        }
699
700        /// Similar to [`Self::gpu_commit()`] but for a batch of polys
701        fn gpu_batch_commit(
702            prover_param: impl Borrow<UnivariateProverParam<E>>,
703            polys: &[DensePolynomial<E::ScalarField>],
704        ) -> Result<Vec<Commitment<E>>, PCSError> {
705            if polys.len() == 0 {
706                return Ok(vec![]);
707            }
708
709            let stream = warmup_new_stream().unwrap();
710
711            let degree = polys.iter().map(|poly| poly.degree()).max().unwrap_or(0);
712
713            #[cfg(feature = "kzg-print-trace")]
714            let commit_time = start_timer!(|| format!(
715                "Committing to {} polys of degree {} ",
716                polys.len(),
717                degree,
718            ));
719            let mut srs_on_gpu = Self::load_prover_param_to_gpu(prover_param, degree)?;
720            let comms =
721                Self::gpu_batch_commit_with_loaded_prover_param(&mut srs_on_gpu, polys, &stream)?;
722            #[cfg(feature = "kzg-print-trace")]
723            end_timer!(commit_time);
724
725            Ok(comms)
726        }
727
728        /// Compute `PCS::commit()` with SRS already loaded on GPU
729        /// Return a vector of commitments on CPU
730        fn gpu_batch_commit_with_loaded_prover_param(
731            prover_param_on_gpu: &mut HostOrDeviceSlice<'_, IcicleAffine<Self::IC>>,
732            polys: &[DensePolynomial<E::ScalarField>],
733            stream: &CudaStream,
734        ) -> Result<Vec<Commitment<E>>, PCSError> {
735            if polys.len() == 0 {
736                return Ok(vec![]);
737            }
738
739            let poly_on_gpu = Self::load_batch_poly_to_gpu(polys)?;
740            let msm_result_on_gpu =
741                Self::commit_on_gpu(prover_param_on_gpu, &poly_on_gpu, polys.len(), stream)?;
742            let comms = Self::load_commitments_to_host(msm_result_on_gpu, stream)?;
743
744            Ok(comms)
745        }
746
747        /// type conversion (specializable/overridable for concrete types) from
748        /// field in arkworks to icicle
749        ///
750        /// # NOTE
751        /// returned icicle field is in normal affine (not montgomery) form
752        fn ark_field_to_icicle(f: E::ScalarField) -> <Self::IC as IcicleCurve>::ScalarField;
753
754        /// type conversion (specializable/overridable for concrete types) from
755        /// affine point in arkworks to icicle
756        ///
757        /// # NOTE
758        /// returned icicle field is in normal affine (not montgomery) form
759        fn ark_affine_to_icicle(p: E::G1Affine) -> IcicleAffine<Self::IC>;
760
761        /// type conversion from icicle Projective to arkworks' G1Affine
762        fn icicle_projective_to_ark(p: IcicleProjective<Self::IC>) -> E::G1Affine;
763
764        /// load SRS for prover onto GPU once, and reuse for future poly-commit
765        /// of degree<=`supported_degree`
766        fn load_prover_param_to_gpu<'srs>(
767            prover_param: impl Borrow<UnivariateProverParam<E>>,
768            supported_degree: usize,
769        ) -> Result<HostOrDeviceSlice<'srs, IcicleAffine<Self::IC>>, PCSError> {
770            let prover_param = prover_param.borrow();
771            if supported_degree > prover_param.powers_of_g.len() - 1 {
772                return Err(PCSError::InvalidParameters(format!(
773                    "supported degree {} is larger than allowed {}",
774                    supported_degree,
775                    prover_param.powers_of_g.len() - 1
776                )));
777            }
778
779            let mut bases_on_device =
780                HostOrDeviceSlice::<'_, IcicleAffine<Self::IC>>::cuda_malloc(supported_degree + 1)?;
781
782            #[cfg(feature = "kzg-print-trace")]
783            let conv_time = start_timer!(|| "Type Conversion: ark->ICICLE: Group");
784            let bases: Vec<IcicleAffine<Self::IC>> = prover_param.powers_of_g
785                [..supported_degree + 1]
786                .par_iter()
787                .map(|&p| Self::ark_affine_to_icicle(p))
788                .collect();
789            #[cfg(feature = "kzg-print-trace")]
790            end_timer!(conv_time);
791
792            #[cfg(feature = "kzg-print-trace")]
793            let load_time = start_timer!(|| "Load group elements: CPU->GPU");
794            bases_on_device.copy_from_host(&bases)?;
795            #[cfg(feature = "kzg-print-trace")]
796            end_timer!(load_time);
797
798            Ok(bases_on_device)
799        }
800
801        /// Load polynomial's coefficients onto GPU, preparing for poly-commit
802        /// on GPU later
803        fn load_poly_to_gpu<'poly>(
804            poly: &DensePolynomial<E::ScalarField>,
805        ) -> Result<HostOrDeviceSlice<'poly, <Self::IC as IcicleCurve>::ScalarField>, PCSError>
806        {
807            let size = poly.degree() + 1;
808            let mut scalars_on_device =
809                HostOrDeviceSlice::<'_, <Self::IC as IcicleCurve>::ScalarField>::cuda_malloc(size)?;
810
811            #[cfg(feature = "kzg-print-trace")]
812            let conv_time = start_timer!(|| "Type Conversion: ark->ICICLE: Scalar");
813            // We assume that two types use the same underline repr.
814            let scalars = unsafe {
815                poly.coeffs()[..size]
816                    .align_to::<<Self::IC as IcicleCurve>::ScalarField>()
817                    .1
818            };
819            #[cfg(feature = "kzg-print-trace")]
820            end_timer!(conv_time);
821
822            #[cfg(feature = "kzg-print-trace")]
823            let load_time = start_timer!(|| "Load scalars: CPU->GPU");
824            scalars_on_device.copy_from_host(scalars)?;
825            #[cfg(feature = "kzg-print-trace")]
826            end_timer!(load_time);
827
828            Ok(scalars_on_device)
829        }
830
831        /// Similar to [`Self::load_poly_to_gpu()`] but handling a batch of
832        /// polys at once
833        fn load_batch_poly_to_gpu<'poly>(
834            polys: &[DensePolynomial<E::ScalarField>],
835        ) -> Result<HostOrDeviceSlice<'poly, <Self::IC as IcicleCurve>::ScalarField>, PCSError>
836        {
837            if polys.is_empty() {
838                return Err(PCSError::InvalidParameters(
839                    "number of polys must be positive".to_string(),
840                ));
841            }
842
843            let num_coeffs = polys
844                .iter()
845                .map(|poly| poly.degree() + 1)
846                .max()
847                .unwrap_or(1);
848
849            let mut scalars_on_device = HostOrDeviceSlice::<
850                '_,
851                <Self::IC as IcicleCurve>::ScalarField,
852            >::cuda_malloc(num_coeffs * polys.len())?;
853
854            #[cfg(feature = "kzg-print-trace")]
855            let conv_time = start_timer!(|| "Type Conversion: ark->ICICLE: Scalar");
856            let zero_for_padding = E::ScalarField::zero();
857            let scalars: Vec<<Self::IC as IcicleCurve>::ScalarField> = polys
858                .iter()
859                .flat_map(|poly| {
860                    poly.coeffs()
861                        .iter()
862                        .pad_using(num_coeffs, |_| &zero_for_padding)
863                })
864                .collect::<Vec<_>>()
865                .into_par_iter()
866                .map(|&s| Self::ark_field_to_icicle(s))
867                .collect();
868            #[cfg(feature = "kzg-print-trace")]
869            end_timer!(conv_time);
870
871            #[cfg(feature = "kzg-print-trace")]
872            let load_time = start_timer!(|| "Load scalars: CPU->GPU");
873            scalars_on_device.copy_from_host(&scalars)?;
874            #[cfg(feature = "kzg-print-trace")]
875            end_timer!(load_time);
876
877            Ok(scalars_on_device)
878        }
879
880        /// Comupte PCS commit using GPU
881        /// Similar to [`Self::commit()`] but with ICICE's GPU-accelerated MSM
882        ///
883        /// - `stream` is a `CudaStream`, you should consider using
884        /// `crate::icicle_deps::warmup_new_stream()` to create one
885        /// - `batch_size`: by default is 1, during batch_commit it could be
886        ///   greater
887        ///
888        /// # NOTE
889        /// - if you pass in `HostOrDeviceSlice::Host`, then `icicle_core::msm`
890        ///   will push them onto GPU first; if they are already on GPU, i.e.
891        ///   `HostOrDeviceSlice::Device`, then msm will be executed directly
892        /// - the result is also temporarily on GPU, you can use
893        ///   `Self::load_commitments_to_host()` to load back to host CPU.
894        /// - this function is async/non-blocking, thus returns a CudaStream
895        ///   handle
896        /// - default implementation assume normal(non-montgomery) affine for
897        ///   bases and scalars, consider overwrite this function if you want
898        ///   otherwise
899        fn commit_on_gpu<'comm>(
900            prover_param: &mut HostOrDeviceSlice<'_, IcicleAffine<Self::IC>>,
901            poly: &HostOrDeviceSlice<'_, <Self::IC as IcicleCurve>::ScalarField>,
902            batch_size: usize,
903            stream: &CudaStream,
904        ) -> Result<HostOrDeviceSlice<'comm, IcicleProjective<Self::IC>>, PCSError> {
905            let trimmed_prover_param = match prover_param {
906                HostOrDeviceSlice::Device(ck, device_id) => {
907                    HostOrDeviceSlice::Device(&mut ck[..poly.len()], *device_id)
908                },
909                HostOrDeviceSlice::Host(ck) => HostOrDeviceSlice::Host(ck[..poly.len()].to_vec()),
910            };
911
912            let mut msm_result =
913                HostOrDeviceSlice::<'_, IcicleProjective<Self::IC>>::cuda_malloc(batch_size)?;
914
915            let mut cfg = MSMConfig::default();
916            cfg.ctx.stream = &stream;
917            cfg.is_async = true; // non-blocking
918
919            #[cfg(feature = "kzg-print-trace")]
920            let msm_time = start_timer!(|| "GPU-accelerated MSM dispatched");
921
922            icicle_core::msm::msm(poly, &trimmed_prover_param, &cfg, &mut msm_result)?;
923
924            #[cfg(feature = "kzg-print-trace")]
925            end_timer!(msm_time);
926
927            ark_std::mem::forget(trimmed_prover_param);
928            Ok(msm_result)
929        }
930
931        /// After `Self::commit_on_gpu()`, you can choose to load the result
932        /// back to host CPU
933        fn load_commitments_to_host(
934            commitments_on_gpu: HostOrDeviceSlice<'_, IcicleProjective<Self::IC>>,
935            stream: &CudaStream,
936        ) -> Result<Vec<Commitment<E>>, PCSError> {
937            #[cfg(feature = "kzg-print-trace")]
938            let sync_time = start_timer!(|| "Sync MSM result");
939            // Since `commit_on_gpu()` is conducting the MSM in async way, we need to
940            // synchronize it first.
941            stream.synchronize()?;
942            #[cfg(feature = "kzg-print-trace")]
943            end_timer!(sync_time);
944
945            #[cfg(feature = "kzg-print-trace")]
946            let load_time = start_timer!(|| "Load MSM result GPU->CPU");
947            let mut msm_host_result =
948                vec![IcicleProjective::<Self::IC>::zero(); commitments_on_gpu.len()];
949            commitments_on_gpu.copy_to_host(&mut msm_host_result[..])?;
950            #[cfg(feature = "kzg-print-trace")]
951            end_timer!(load_time);
952
953            #[cfg(feature = "kzg-print-trace")]
954            let conv_time = start_timer!(|| "Type Conversion: ICICLE->ark: Group");
955            let comms = msm_host_result
956                .par_iter()
957                .map(|&p| Commitment(Self::icicle_projective_to_ark(p)))
958                .collect();
959            #[cfg(feature = "kzg-print-trace")]
960            end_timer!(conv_time);
961
962            Ok(comms)
963        }
964    }
965
966    impl GPUCommittable<Bn254> for UnivariateKzgPCS<Bn254> {
967        type IC = IcicleBn254;
968        // NOTE: we are directly using montgomery form, different from default!
969        fn ark_field_to_icicle(f: ark_bn254::Fr) -> icicle_bn254::curve::ScalarField {
970            icicle_bn254::curve::ScalarField::from(f.0 .0)
971        }
972
973        // NOTE: we are directly using montgomery form, different from default!
974        fn ark_affine_to_icicle(p: ark_bn254::G1Affine) -> icicle_bn254::curve::G1Affine {
975            icicle_bn254::curve::G1Affine {
976                x: icicle_bn254::curve::BaseField::from(p.x.0 .0),
977                y: icicle_bn254::curve::BaseField::from(p.y.0 .0),
978            }
979        }
980
981        fn icicle_projective_to_ark(p: icicle_bn254::curve::G1Projective) -> ark_bn254::G1Affine {
982            use ark_ff::biginteger::BigInteger256;
983
984            let ic_affine: icicle_bn254::curve::G1Affine = p.into();
985            let x_limbs: [u64; 4] = ic_affine.x.into();
986            let y_limbs: [u64; 4] = ic_affine.y.into();
987
988            if ic_affine == icicle_bn254::curve::G1Affine::zero() {
989                ark_bn254::G1Affine::zero()
990            } else {
991                ark_bn254::G1Affine {
992                    x: BigInteger256::new(x_limbs).into(),
993                    y: BigInteger256::new(y_limbs).into(),
994                    infinity: false,
995                }
996            }
997        }
998
999        // NOTE: both bases and scalars are in montgomery form on GPU
1000        fn commit_on_gpu<'comm>(
1001            prover_param: &mut HostOrDeviceSlice<'_, icicle_bn254::curve::G1Affine>,
1002            poly: &HostOrDeviceSlice<'_, icicle_bn254::curve::ScalarField>,
1003            batch_size: usize,
1004            stream: &CudaStream,
1005        ) -> Result<HostOrDeviceSlice<'comm, icicle_bn254::curve::G1Projective>, PCSError> {
1006            let trimmed_srs_size = poly.len() / batch_size;
1007            let trimmed_prover_param = match prover_param {
1008                HostOrDeviceSlice::Device(ck, device_id) => {
1009                    HostOrDeviceSlice::Device(&mut ck[..trimmed_srs_size], *device_id)
1010                },
1011                HostOrDeviceSlice::Host(ck) => {
1012                    HostOrDeviceSlice::Host(ck[..trimmed_srs_size].to_vec())
1013                },
1014            };
1015            let mut msm_result =
1016                HostOrDeviceSlice::<'_, icicle_bn254::curve::G1Projective>::cuda_malloc(
1017                    batch_size,
1018                )?;
1019
1020            let mut cfg = MSMConfig::default();
1021            cfg.ctx.stream = stream;
1022            cfg.is_async = true;
1023            cfg.are_scalars_montgomery_form = true;
1024            cfg.are_points_montgomery_form = true;
1025
1026            #[cfg(feature = "kzg-print-trace")]
1027            let msm_time = start_timer!(|| "GPU-accelerated MSM");
1028            icicle_core::msm::msm(poly, &trimmed_prover_param, &cfg, &mut msm_result)?;
1029            #[cfg(feature = "kzg-print-trace")]
1030            end_timer!(msm_time);
1031
1032            // TODO: update after https://github.com/ingonyama-zk/icicle/pull/412
1033            // FIXME: even though `trimmed_prover_param` is an internal, temporary variable
1034            // there's still risk of double-free if `msm()` above panic. Switching to
1035            // `ManuallyDrop<HostOrDeviceSlice>` would fix this issue but aren't supported
1036            // by ICICLE's msm API.
1037            ark_std::mem::forget(trimmed_prover_param);
1038            Ok(msm_result)
1039        }
1040    }
1041}
1042
1043fn skip_leading_zeros_and_convert_to_bigints<F: PrimeField, P: DenseUVPolynomial<F>>(
1044    p: &P,
1045) -> (usize, Vec<F::BigInt>) {
1046    let mut num_leading_zeros = 0;
1047    while num_leading_zeros < p.coeffs().len() && p.coeffs()[num_leading_zeros].is_zero() {
1048        num_leading_zeros += 1;
1049    }
1050    let coeffs = convert_to_bigints(&p.coeffs()[num_leading_zeros..]);
1051    (num_leading_zeros, coeffs)
1052}
1053
1054fn convert_to_bigints<F: PrimeField>(p: &[F]) -> Vec<F::BigInt> {
1055    #[cfg(feature = "kzg-print-trace")]
1056    let to_bigint_time = start_timer!(|| "Converting polynomial coeffs to
1057    bigints");
1058
1059    let coeffs = p.iter().map(|s| s.into_bigint()).collect::<Vec<_>>();
1060
1061    #[cfg(feature = "kzg-print-trace")]
1062    end_timer!(to_bigint_time);
1063
1064    coeffs
1065}
1066
1067// Compute Lagrange poly `value * prod_{j!=i} (X-point_j)/(point_i-point_j)`
1068// We assume i < points.len()
1069fn lagrange_poly<F: PrimeField>(points: &[F], i: usize, value: F) -> DensePolynomial<F> {
1070    let mut res = DensePolynomial::from_coefficients_vec(vec![value]);
1071    let point_i = points[i];
1072    for (j, &point) in points.iter().enumerate() {
1073        if j != i {
1074            let z_inv = (point_i - point).inverse().unwrap();
1075            res = &res * &DensePolynomial::from_coefficients_vec(vec![-point * z_inv, z_inv]);
1076        }
1077    }
1078    res
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083    pub use super::*;
1084    use ark_bls12_381::Bls12_381;
1085    use ark_std::rand::Rng;
1086    pub use jf_utils::test_rng;
1087
1088    fn end_to_end_test_template<E>() -> Result<(), PCSError>
1089    where
1090        E: Pairing,
1091    {
1092        let rng = &mut test_rng();
1093        for _ in 0..100 {
1094            let mut degree = 0;
1095            while degree <= 1 {
1096                degree = usize::rand(rng) % 20;
1097            }
1098            let pp = UnivariateKzgPCS::<E>::gen_srs_for_testing(rng, degree)?;
1099            let (ck, vk) = pp.trim(degree)?;
1100            let p = <DensePolynomial<E::ScalarField> as DenseUVPolynomial<E::ScalarField>>::rand(
1101                degree, rng,
1102            );
1103            let comm = UnivariateKzgPCS::<E>::commit(&ck, &p)?;
1104            let point = E::ScalarField::rand(rng);
1105            let (proof, value) = UnivariateKzgPCS::<E>::open(&ck, &p, &point)?;
1106            assert!(
1107                UnivariateKzgPCS::<E>::verify(&vk, &comm, &point, &value, &proof)?,
1108                "proof was incorrect for max_degree = {}, polynomial_degree = {}",
1109                degree,
1110                p.degree(),
1111            );
1112        }
1113        Ok(())
1114    }
1115
1116    fn linear_polynomial_test_template<E>() -> Result<(), PCSError>
1117    where
1118        E: Pairing,
1119    {
1120        let rng = &mut test_rng();
1121        for _ in 0..100 {
1122            let degree = 50;
1123
1124            let pp = UnivariateKzgPCS::<E>::gen_srs_for_testing(rng, degree)?;
1125            let (ck, vk) = pp.trim(degree)?;
1126            let p = <DensePolynomial<E::ScalarField> as DenseUVPolynomial<E::ScalarField>>::rand(
1127                degree, rng,
1128            );
1129            let comm = UnivariateKzgPCS::<E>::commit(&ck, &p)?;
1130            let point = E::ScalarField::rand(rng);
1131            let (proof, value) = UnivariateKzgPCS::<E>::open(&ck, &p, &point)?;
1132            assert!(
1133                UnivariateKzgPCS::<E>::verify(&vk, &comm, &point, &value, &proof)?,
1134                "proof was incorrect for max_degree = {}, polynomial_degree = {}",
1135                degree,
1136                p.degree(),
1137            );
1138        }
1139        Ok(())
1140    }
1141
1142    fn batch_check_test_template<E>() -> Result<(), PCSError>
1143    where
1144        E: Pairing,
1145    {
1146        let rng = &mut test_rng();
1147        for _ in 0..10 {
1148            let mut degree = 0;
1149            while degree <= 1 {
1150                degree = usize::rand(rng) % 20;
1151            }
1152            let pp = UnivariateKzgPCS::<E>::gen_srs_for_testing(rng, degree)?;
1153            let (ck, vk) = UnivariateKzgPCS::<E>::trim(&pp, degree, None)?;
1154            let mut comms = Vec::new();
1155            let mut values = Vec::new();
1156            let mut points = Vec::new();
1157            let mut proofs = Vec::new();
1158            for _ in 0..10 {
1159                let p =
1160                    <DensePolynomial<E::ScalarField> as DenseUVPolynomial<E::ScalarField>>::rand(
1161                        degree, rng,
1162                    );
1163                let comm = UnivariateKzgPCS::<E>::commit(&ck, &p)?;
1164                let point = E::ScalarField::rand(rng);
1165                let (proof, value) = UnivariateKzgPCS::<E>::open(&ck, &p, &point)?;
1166
1167                assert!(UnivariateKzgPCS::<E>::verify(
1168                    &vk, &comm, &point, &value, &proof
1169                )?);
1170                comms.push(comm);
1171                values.push(value);
1172                points.push(point);
1173                proofs.push(proof);
1174            }
1175            assert!(UnivariateKzgPCS::<E>::batch_verify(
1176                &vk, &comms, &points, &values, &proofs, rng
1177            )?);
1178        }
1179        Ok(())
1180    }
1181
1182    fn multi_point_open_test_template<E>() -> Result<(), PCSError>
1183    where
1184        E: Pairing,
1185    {
1186        let rng = &mut test_rng();
1187        let degree = 20;
1188        let verifier_degree = 10;
1189        let pp = UnivariateKzgPCS::<E>::gen_srs_for_testing_with_verifier_degree(
1190            rng,
1191            degree,
1192            verifier_degree,
1193        )?;
1194        let (ck, vk) = pp
1195            .borrow()
1196            .trim_with_verifier_degree(degree, verifier_degree)?;
1197        for _ in 0..10 {
1198            let p = <DensePolynomial<E::ScalarField> as DenseUVPolynomial<E::ScalarField>>::rand(
1199                degree, rng,
1200            );
1201            let comm = UnivariateKzgPCS::<E>::commit(&ck, &p)?;
1202            let points: Vec<E::ScalarField> = (0..5).map(|_| E::ScalarField::rand(rng)).collect();
1203            let (proof, values) = UnivariateKzgPCS::<E>::multi_point_open(&ck, &p, &points[..])?;
1204
1205            assert!(UnivariateKzgPCS::<E>::multi_point_verify(
1206                &vk,
1207                &comm,
1208                &points[..],
1209                &values[..],
1210                &proof
1211            )?);
1212        }
1213        Ok(())
1214    }
1215
1216    #[test]
1217    fn end_to_end_test() {
1218        end_to_end_test_template::<Bls12_381>().expect("test failed for bls12-381");
1219    }
1220
1221    #[test]
1222    fn linear_polynomial_test() {
1223        linear_polynomial_test_template::<Bls12_381>().expect("test failed for bls12-381");
1224    }
1225    #[test]
1226    fn batch_check_test() {
1227        batch_check_test_template::<Bls12_381>().expect("test failed for bls12-381");
1228    }
1229
1230    #[test]
1231    fn multi_point_open_test() {
1232        multi_point_open_test_template::<Bls12_381>().expect("test failed for bls12-381");
1233    }
1234
1235    #[test]
1236    fn test_multi_open() -> Result<(), PCSError> {
1237        type E = Bls12_381;
1238        type Fr = ark_bls12_381::Fr;
1239
1240        let mut rng = test_rng();
1241        let max_degree = 33;
1242        let pp = UnivariateKzgPCS::<E>::gen_srs_for_testing(&mut rng, max_degree)?;
1243        let degrees = [14, 15, 16, 17, 18];
1244
1245        for degree in degrees {
1246            let num_points = rng.gen_range(5..30); // should allow more points than degree
1247            ark_std::println!(
1248                "Multi-opening: poly deg: {}, num of points: {}",
1249                degree,
1250                num_points
1251            );
1252
1253            // NOTE: THIS IS IMPORTANT FOR USER OF `multi_open()`!
1254            // since we will pad your polynomial degree to the next_power_of_two, you will
1255            // need to trim to the correct padded degree as follows:
1256            let (ck, _) = UnivariateKzgPCS::<E>::trim_fft_size(&pp, degree)?;
1257            let poly = <DensePolynomial<Fr> as DenseUVPolynomial<Fr>>::rand(degree, &mut rng);
1258            let points: Vec<Fr> = (0..num_points).map(|_| Fr::rand(&mut rng)).collect();
1259
1260            // First, test general points
1261            let (proofs, evals) = UnivariateKzgPCS::<E>::multi_open(&ck, &poly, &points)?;
1262            assert!(
1263                proofs.len() == evals.len() && proofs.len() == num_points,
1264                "fn multi_open() should return the correct number of proofs and evals"
1265            );
1266            points
1267                .iter()
1268                .zip(proofs.into_iter())
1269                .zip(evals.into_iter())
1270                .for_each(|((point, proof), eval)| {
1271                    assert_eq!(
1272                        UnivariateKzgPCS::<E>::open(&ck, &poly, point).unwrap(),
1273                        (proof, eval)
1274                    );
1275                });
1276            // Second, test roots-of-unity points
1277            let domain: Radix2EvaluationDomain<Fr> =
1278                UnivariateKzgPCS::<E>::multi_open_rou_eval_domain(degree, num_points)?;
1279            let (proofs, evals) =
1280                UnivariateKzgPCS::<E>::multi_open_rou(&ck, &poly, num_points, &domain)?;
1281            assert!(
1282                proofs.len() == evals.len() && proofs.len() == num_points,
1283                "fn multi_open_rou() should return the correct number of proofs and evals"
1284            );
1285
1286            domain
1287                .elements()
1288                .take(num_points)
1289                .zip(proofs.into_iter())
1290                .zip(evals.into_iter())
1291                .for_each(|((point, proof), eval)| {
1292                    assert_eq!(
1293                        UnivariateKzgPCS::<E>::open(&ck, &poly, &point).unwrap(),
1294                        (proof, eval)
1295                    );
1296                });
1297        }
1298
1299        Ok(())
1300    }
1301
1302    #[cfg(feature = "icicle")]
1303    mod icicle {
1304        use super::*;
1305        #[cfg(feature = "kzg-print-trace")]
1306        use crate::icicle_deps::warmup_new_stream;
1307        use crate::{
1308            icicle_deps::{curves::*, IcicleCurve},
1309            pcs::univariate_kzg::icicle::GPUCommittable,
1310        };
1311        use core::mem::size_of;
1312        use icicle_core::traits::{ArkConvertible, MontgomeryConvertible};
1313        use icicle_cuda_runtime::{error::CudaResultWrap, memory::HostOrDeviceSlice};
1314
1315        #[cfg(feature = "kzg-print-trace")]
1316        fn gpu_profiling<E: Pairing>() -> Result<(), PCSError>
1317        where
1318            UnivariateKzgPCS<E>: GPUCommittable<E>,
1319        {
1320            let rng = &mut test_rng();
1321            let stream = warmup_new_stream().unwrap();
1322            let degree = 2usize.pow(22);
1323
1324            let pp = UnivariateKzgPCS::<E>::gen_srs_for_testing(rng, degree)?;
1325            let (ck, _vk) = pp.trim(degree)?;
1326            let mut srs_on_gpu =
1327                <UnivariateKzgPCS<E> as GPUCommittable<E>>::load_prover_param_to_gpu(&ck, degree)?;
1328
1329            let p = <DensePolynomial<E::ScalarField> as DenseUVPolynomial<E::ScalarField>>::rand(
1330                degree, rng,
1331            );
1332
1333            let _comm =
1334                <UnivariateKzgPCS<E> as GPUCommittable<E>>::gpu_commit_with_loaded_prover_param(
1335                    &mut srs_on_gpu,
1336                    &p,
1337                    &stream,
1338                )?;
1339
1340            let polys: Vec<_> = (0..8)
1341                .map(|_| {
1342                    <DensePolynomial<E::ScalarField> as DenseUVPolynomial<E::ScalarField>>::rand(
1343                        degree / 8,
1344                        rng,
1345                    )
1346                })
1347                .collect();
1348            let _comms =
1349                <UnivariateKzgPCS<E> as GPUCommittable<E>>::gpu_batch_commit_with_loaded_prover_param(
1350                    &mut srs_on_gpu,
1351                    &polys,
1352                    &stream,
1353                )?;
1354
1355            Ok(())
1356        }
1357
1358        fn test_gpu_e2e_template<E: Pairing>() -> Result<(), PCSError>
1359        where
1360            UnivariateKzgPCS<E>: GPUCommittable<E>,
1361        {
1362            let rng = &mut test_rng();
1363            let supported_degree = 2usize.pow(12);
1364            let pp = UnivariateKzgPCS::<E>::gen_srs_for_testing(rng, supported_degree)?;
1365
1366            // testing on smaller degree for correctness
1367            for _ in 0..10 {
1368                let degree = usize::rand(rng) % 1025;
1369                let (ck, vk) = pp.trim(degree)?;
1370                let p =
1371                    <DensePolynomial<E::ScalarField> as DenseUVPolynomial<E::ScalarField>>::rand(
1372                        degree, rng,
1373                    );
1374                let comm_gpu = <UnivariateKzgPCS<E> as GPUCommittable<E>>::gpu_commit(&ck, &p)?;
1375                let comm_cpu = UnivariateKzgPCS::<E>::commit(&ck, &p)?;
1376                assert_eq!(comm_gpu, comm_cpu);
1377
1378                let point = E::ScalarField::rand(rng);
1379                let (proof, value) = UnivariateKzgPCS::<E>::open(&ck, &p, &point)?;
1380                assert!(
1381                    UnivariateKzgPCS::<E>::verify(&vk, &comm_gpu, &point, &value, &proof)?,
1382                    "proof was incorrect for max_degree = {}, polynomial_degree = {}",
1383                    degree,
1384                    p.degree(),
1385                );
1386
1387                // batch commit
1388                for i in 0..5 {
1389                    let batch_size = 10 + i;
1390                    let polys: Vec<_> = (0..batch_size)
1391                        .map(|_| {
1392                            <DensePolynomial<E::ScalarField> as DenseUVPolynomial<
1393                                    E::ScalarField,
1394                                >>::rand(degree, rng)
1395                        })
1396                        .collect();
1397                    let comms_gpu =
1398                        <UnivariateKzgPCS<E> as GPUCommittable<E>>::gpu_batch_commit(&ck, &polys)?;
1399                    let comms_cpu = UnivariateKzgPCS::<E>::batch_commit(&ck, &polys)?;
1400                    assert_eq!(comms_gpu, comms_cpu);
1401                    assert!(
1402                        <UnivariateKzgPCS<E> as GPUCommittable<E>>::gpu_batch_commit(&ck, &[])
1403                            .is_ok()
1404                    );
1405                }
1406            }
1407            Ok(())
1408        }
1409
1410        #[test]
1411        fn test_gpu_e2e() {
1412            test_gpu_e2e_template::<Bn254>().unwrap();
1413        }
1414
1415        fn test_gpu_ark_conversion_template<E: Pairing>()
1416        where
1417            UnivariateKzgPCS<E>: GPUCommittable<E>,
1418            <<UnivariateKzgPCS<E> as GPUCommittable<E>>::IC as IcicleCurve>::ScalarField:
1419                ArkConvertible<ArkEquivalent = E::ScalarField> + MontgomeryConvertible,
1420        {
1421            type ICScalarField<E> =
1422                <<UnivariateKzgPCS<E> as GPUCommittable<E>>::IC as IcicleCurve>::ScalarField;
1423            assert_eq!(size_of::<E::ScalarField>(), size_of::<ICScalarField<E>>());
1424            let size = 100usize;
1425            let mut rng = test_rng();
1426            let scalars: Vec<_> = (0..size)
1427                .map(|_| {
1428                    let mut bytes = [0u8; 32];
1429                    rng.fill_bytes(&mut bytes);
1430                    E::ScalarField::from_le_bytes_mod_order(&bytes)
1431                })
1432                .collect();
1433            let mut ic_scalars: Vec<_> = scalars
1434                .iter()
1435                .copied()
1436                .map(ICScalarField::<E>::from_ark)
1437                .collect();
1438            let mut d_scalars = HostOrDeviceSlice::cuda_malloc(size).unwrap();
1439            d_scalars.copy_from_host(&ic_scalars).unwrap();
1440            ICScalarField::<E>::to_mont(&mut d_scalars).wrap().unwrap();
1441            d_scalars.copy_to_host(&mut ic_scalars).unwrap();
1442            let transformed_scalars = unsafe { scalars.align_to::<ICScalarField<E>>().1 };
1443            assert_eq!(ic_scalars, transformed_scalars);
1444        }
1445
1446        #[test]
1447        /// This test checks whether the scalar field type in Ark has the size
1448        /// with the one in icicle. So that we could do direct reinterpret_cast
1449        /// between them.
1450        fn test_gpu_ark_conversion() {
1451            test_gpu_ark_conversion_template::<Bn254>();
1452        }
1453
1454        #[cfg(feature = "kzg-print-trace")]
1455        #[test]
1456        fn profile_gpu_commit() {
1457            // testing on large degree for profiling
1458            gpu_profiling::<Bn254>().unwrap();
1459        }
1460    }
1461}