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