jf_pcs/multilinear_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 multilinear KZG commitment scheme
8
9mod batching;
10pub(crate) mod srs;
11pub(crate) mod util;
12
13use crate::{
14    prelude::{Commitment, UnivariateUniversalParams},
15    univariate_kzg::UnivariateKzgProof,
16    PCSError, PolynomialCommitmentScheme, StructuredReferenceString,
17};
18#[cfg(target_has_atomic = "ptr")]
19use alloc::sync::Arc;
20use ark_ec::{
21    pairing::Pairing, scalar_mul::variable_base::VariableBaseMSM, AffineRepr, CurveGroup, ScalarMul,
22};
23use ark_ff::PrimeField;
24use ark_poly::{DenseMultilinearExtension, MultilinearExtension, Polynomial};
25use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
26use ark_std::{
27    borrow::Borrow,
28    end_timer, format,
29    marker::PhantomData,
30    rand::{CryptoRng, RngCore},
31    start_timer,
32    string::ToString,
33    vec,
34    vec::Vec,
35    One, Zero,
36};
37use batching::{batch_open_internal, batch_verify_internal};
38use derive_where::derive_where;
39use srs::{MultilinearProverParam, MultilinearUniversalParams, MultilinearVerifierParam};
40use util::merge_polynomials;
41
42// type alias for SRS
43type Srs<E> = (MultilinearUniversalParams<E>, UnivariateUniversalParams<E>);
44type ProverParam<E> = <Srs<E> as StructuredReferenceString>::ProverParam;
45type VerifierParam<E> = <Srs<E> as StructuredReferenceString>::VerifierParam;
46
47/// KZG Polynomial Commitment Scheme on multilinear polynomials.
48pub struct MultilinearKzgPCS<E: Pairing> {
49    #[doc(hidden)]
50    phantom: PhantomData<E>,
51}
52
53#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
54#[derive_where(Hash)]
55/// proof of opening
56pub struct MultilinearKzgProof<E: Pairing> {
57    /// Evaluation of quotients
58    pub proofs: Vec<E::G1Affine>,
59}
60
61#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
62/// proof of batch opening
63pub struct MultilinearKzgBatchProof<E: Pairing> {
64    /// The actual proof
65    pub proof: MultilinearKzgProof<E>,
66    /// Commitment to q(x):= w(l(x)) where
67    /// - `w` is the merged MLE
68    /// - `l` is the list of univariate polys that goes through all points
69    pub q_x_commit: Commitment<E>,
70    /// openings of q(x) at 1, omega, ..., and r
71    pub q_x_opens: Vec<UnivariateKzgProof<E>>,
72}
73
74/// Multi-linear Extension (MLE) polynomial, this type alias is set to owned
75/// `DenseMultilinearExtension` on wasm platforms since only message-passing
76/// concurrency is supported. And set to `Arc<DenseMultilinearExtension>` for
77/// platforms that supports atomic operations (e.g. mostly non-wasm, MIPS, x86
78/// etc.)
79#[cfg(target_has_atomic = "ptr")]
80pub type MLE<F> = Arc<DenseMultilinearExtension<F>>;
81#[cfg(not(target_has_atomic = "ptr"))]
82pub type MLE<F> = DenseMultilinearExtension<F>;
83
84impl<E: Pairing> PolynomialCommitmentScheme for MultilinearKzgPCS<E> {
85    // Config
86    type SRS = Srs<E>;
87    // Polynomial and its associated types
88    type Polynomial = MLE<E::ScalarField>;
89    type Point = Vec<E::ScalarField>;
90    type Evaluation = E::ScalarField;
91    // Commitments and proofs
92    type Commitment = Commitment<E>;
93    type BatchCommitment = Commitment<E>;
94    type Proof = MultilinearKzgProof<E>;
95    type BatchProof = MultilinearKzgBatchProof<E>;
96
97    /// Trim the universal parameters to specialize the public parameters.
98    /// Input both `supported_log_degree` for univariate and
99    /// `supported_num_vars` for multilinear.
100    fn trim(
101        srs: impl Borrow<Self::SRS>,
102        supported_log_degree: usize,
103        supported_num_vars: Option<usize>,
104    ) -> Result<(ProverParam<E>, VerifierParam<E>), PCSError> {
105        let supported_num_vars = match supported_num_vars {
106            Some(p) => p,
107            None => {
108                return Err(PCSError::InvalidParameters(
109                    "multilinear should receive a num_var param".to_string(),
110                ))
111            },
112        };
113        let (uni_ck, uni_vk) = srs.borrow().1.trim(supported_log_degree)?;
114        let (ml_ck, ml_vk) = srs.borrow().0.trim(supported_num_vars)?;
115
116        Ok(((ml_ck, uni_ck), (ml_vk, uni_vk)))
117    }
118
119    /// Generate a commitment for a polynomial.
120    ///
121    /// This function takes `2^num_vars` number of scalar multiplications over
122    /// G1.
123    fn commit(
124        prover_param: impl Borrow<ProverParam<E>>,
125        poly: &Self::Polynomial,
126    ) -> Result<Self::Commitment, PCSError> {
127        let prover_param = prover_param.borrow();
128        let commit_timer = start_timer!(|| "commit");
129        if prover_param.0.num_vars < poly.num_vars {
130            return Err(PCSError::InvalidParameters(format!(
131                "Poly length ({}) exceeds param limit ({})",
132                poly.num_vars, prover_param.0.num_vars
133            )));
134        }
135        let ignored = prover_param.0.num_vars - poly.num_vars;
136        let scalars: Vec<_> = poly
137            .to_evaluations()
138            .into_iter()
139            .map(|x| x.into_bigint())
140            .collect();
141        let commitment = E::G1::msm_bigint(
142            &prover_param.0.powers_of_g[ignored].evals,
143            scalars.as_slice(),
144        )
145        .into_affine();
146
147        end_timer!(commit_timer);
148        Ok(Commitment(commitment))
149    }
150
151    /// Batch commit a list of polynomials.
152    ///
153    /// This function takes `2^(num_vars + log(polys.len())` number of scalar
154    /// multiplications over G1.
155    fn batch_commit(
156        prover_param: impl Borrow<ProverParam<E>>,
157        polys: &[Self::Polynomial],
158    ) -> Result<Self::Commitment, PCSError> {
159        let prover_param = prover_param.borrow();
160        let commit_timer = start_timer!(|| "multi commit");
161        let poly = merge_polynomials(polys)?;
162
163        let scalars: Vec<_> = poly
164            .to_evaluations()
165            .iter()
166            .map(|x| x.into_bigint())
167            .collect();
168
169        let commitment =
170            E::G1::msm_bigint(&prover_param.0.powers_of_g[0].evals, scalars.as_slice())
171                .into_affine();
172
173        end_timer!(commit_timer);
174        Ok(Commitment(commitment))
175    }
176
177    /// On input a polynomial `p` and a point `point`, outputs a proof for the
178    /// same. This function does not need to take the evaluation value as an
179    /// input.
180    ///
181    /// This function takes 2^{num_var +1} number of scalar multiplications over
182    /// G1:
183    /// - it proceeds with `num_var` number of rounds,
184    /// - at round i, we compute an MSM for `2^{num_var - i + 1}` number of G2
185    ///   elements.
186    fn open(
187        prover_param: impl Borrow<ProverParam<E>>,
188        polynomial: &Self::Polynomial,
189        point: &Self::Point,
190    ) -> Result<(Self::Proof, Self::Evaluation), PCSError> {
191        open_internal(&prover_param.borrow().0, polynomial, point)
192    }
193
194    /// Input
195    /// - the prover parameters for univariate KZG,
196    /// - the prover parameters for multilinear KZG,
197    /// - a list of polynomials,
198    /// - a (batch) commitment to all polynomials,
199    /// - and the same number of points, compute a batch opening for all the
200    ///   polynomials.
201    ///
202    /// For simplicity, this API requires each MLE to have only one point. If
203    /// the caller wish to use more than one point per MLE, it should be
204    /// handled at the caller layer.
205    ///
206    /// Returns an error if the lengths do not match.
207    ///
208    /// Returns the proof, consists of
209    /// - the multilinear KZG opening
210    /// - the univariate KZG commitment to q(x)
211    /// - the openings and evaluations of q(x) at omega^i and r
212    ///
213    /// Steps:
214    /// 1. build `l(points)` which is a list of univariate polynomials that goes
215    ///    through the points
216    /// 2. build MLE `w` which is the merge of all MLEs.
217    /// 3. build `q(x)` which is a univariate polynomial `W circ l`
218    /// 4. commit to q(x) and sample r from transcript transcript contains: w
219    ///    commitment, points, q(x)'s commitment
220    /// 5. build q(omega^i) and their openings
221    /// 6. build q(r) and its opening
222    /// 7. get a point `p := l(r)`
223    /// 8. output an opening of `w` over point `p`
224    /// 9. output `w(p)`
225    fn batch_open(
226        prover_param: impl Borrow<ProverParam<E>>,
227        batch_commitment: &Self::BatchCommitment,
228        polynomials: &[Self::Polynomial],
229        points: &[Self::Point],
230    ) -> Result<(Self::BatchProof, Vec<Self::Evaluation>), PCSError> {
231        batch_open_internal::<E>(
232            &prover_param.borrow().1,
233            &prover_param.borrow().0,
234            polynomials,
235            batch_commitment,
236            points,
237        )
238    }
239
240    /// Verifies that `value` is the evaluation at `x` of the polynomial
241    /// committed inside `comm`.
242    ///
243    /// This function takes
244    /// - num_var number of pairing product.
245    /// - num_var number of MSM
246    fn verify(
247        verifier_param: &VerifierParam<E>,
248        commitment: &Self::Commitment,
249        point: &Self::Point,
250        value: &E::ScalarField,
251        proof: &Self::Proof,
252    ) -> Result<bool, PCSError> {
253        verify_internal(&verifier_param.0, commitment, point, value, proof)
254    }
255
256    /// Verifies that `value` is the evaluation at `x_i` of the polynomial
257    /// `poly_i` committed inside `commitment`.
258    /// steps:
259    ///
260    /// 1. put `q(x)`'s evaluations over `(1, omega,...)` into transcript
261    /// 2. sample `r` from transcript
262    /// 3. check `q(r) == value`
263    /// 4. build `l(points)` which is a list of univariate polynomials that goes
264    ///    through the points
265    /// 5. get a point `p := l(r)`
266    /// 6. verifies `p` is verifies against proof
267    fn batch_verify<R: RngCore + CryptoRng>(
268        verifier_param: &VerifierParam<E>,
269        batch_commitment: &Self::BatchCommitment,
270        points: &[Self::Point],
271        values: &[E::ScalarField],
272        batch_proof: &Self::BatchProof,
273        _rng: &mut R,
274    ) -> Result<bool, PCSError> {
275        batch_verify_internal(
276            &verifier_param.1,
277            &verifier_param.0,
278            batch_commitment,
279            points,
280            values,
281            batch_proof,
282        )
283    }
284}
285
286/// On input a polynomial `p` and a point `point`, outputs a proof for the
287/// same. This function does not need to take the evaluation value as an
288/// input.
289///
290/// This function takes 2^{num_var} number of scalar multiplications over
291/// G1:
292/// - it proceeds with `num_var` number of rounds,
293/// - at round i, we compute an MSM for `2^{num_var - i}` number of G1 elements.
294fn open_internal<E: Pairing>(
295    prover_param: &MultilinearProverParam<E>,
296    polynomial: &DenseMultilinearExtension<E::ScalarField>,
297    point: &Vec<E::ScalarField>,
298) -> Result<(MultilinearKzgProof<E>, E::ScalarField), PCSError> {
299    let open_timer = start_timer!(|| format!("open mle with {} variable", polynomial.num_vars));
300
301    if polynomial.num_vars() > prover_param.num_vars {
302        return Err(PCSError::InvalidParameters(format!(
303            "Polynomial num_vars {} exceed the limit {}",
304            polynomial.num_vars, prover_param.num_vars
305        )));
306    }
307
308    if polynomial.num_vars() != point.len() {
309        return Err(PCSError::InvalidParameters(format!(
310            "Polynomial num_vars {} does not match point len {}",
311            polynomial.num_vars,
312            point.len()
313        )));
314    }
315
316    let nv = polynomial.num_vars();
317    // the first `ignored` SRS vectors are unused
318    let ignored = prover_param.num_vars - nv + 1;
319
320    let mut f = polynomial.to_evaluations();
321
322    let mut proofs = Vec::new();
323
324    for (i, (&point_at_k, gi)) in point
325        .iter()
326        .zip(prover_param.powers_of_g[ignored..ignored + nv].iter())
327        .enumerate()
328    {
329        let ith_round = start_timer!(|| format!("{}-th round", i));
330
331        let k = nv - 1 - i;
332        let cur_dim = 1 << k;
333        let mut q = vec![E::ScalarField::zero(); cur_dim];
334        let mut r = vec![E::ScalarField::zero(); cur_dim];
335
336        let ith_round_eval = start_timer!(|| format!("{}-th round eval", i));
337        for b in 0..(1 << k) {
338            // q[b] = f[1, b] - f[0, b]
339            q[b] = f[(b << 1) + 1] - f[b << 1];
340
341            // r[b] = f[0, b] + q[b] * p
342            r[b] = f[b << 1] + (q[b] * point_at_k);
343        }
344        f = r;
345        end_timer!(ith_round_eval);
346        let scalars: Vec<_> = q.iter().map(|x| x.into_bigint()).collect();
347
348        // this is a MSM over G1 and is likely to be the bottleneck
349        let msm_timer = start_timer!(|| format!("msm of size {} at round {}", gi.evals.len(), i));
350
351        proofs.push(E::G1::msm_bigint(&gi.evals, &scalars).into_affine());
352        end_timer!(msm_timer);
353
354        end_timer!(ith_round);
355    }
356    let eval = polynomial.evaluate(point);
357    end_timer!(open_timer);
358    Ok((MultilinearKzgProof { proofs }, eval))
359}
360
361/// Verifies that `value` is the evaluation at `x` of the polynomial
362/// committed inside `comm`.
363///
364/// This function takes
365/// - num_var number of pairing product.
366/// - num_var number of MSM
367fn verify_internal<E: Pairing>(
368    verifier_param: &MultilinearVerifierParam<E>,
369    commitment: &Commitment<E>,
370    point: &[E::ScalarField],
371    value: &E::ScalarField,
372    proof: &MultilinearKzgProof<E>,
373) -> Result<bool, PCSError> {
374    let verify_timer = start_timer!(|| "verify");
375    let num_var = point.len();
376
377    if num_var > verifier_param.num_vars {
378        return Err(PCSError::InvalidParameters(format!(
379            "point length ({}) exceeds param limit ({})",
380            num_var, verifier_param.num_vars
381        )));
382    }
383
384    let prepare_inputs_timer = start_timer!(|| "prepare pairing inputs");
385
386    let h_mul = verifier_param.h.into_group().batch_mul(point);
387
388    // the first `ignored` G2 parameters are unused
389    let ignored = verifier_param.num_vars - num_var;
390    let h_vec: Vec<_> = (0..num_var)
391        .map(|i| verifier_param.h_mask[ignored + i].into_group() - h_mul[i])
392        .collect();
393    let h_vec: Vec<E::G2Affine> = E::G2::normalize_batch(&h_vec);
394    end_timer!(prepare_inputs_timer);
395
396    let pairing_product_timer = start_timer!(|| "pairing product");
397
398    let mut pairings_l: Vec<E::G1Prepared> = proof
399        .proofs
400        .iter()
401        .map(|&x| E::G1Prepared::from(x))
402        .collect();
403
404    let mut pairings_r: Vec<E::G2Prepared> = h_vec
405        .into_iter()
406        .take(num_var)
407        .map(E::G2Prepared::from)
408        .collect();
409    pairings_l.push(E::G1Prepared::from(
410        (verifier_param.g * (*value) - commitment.0.into_group()).into_affine(),
411    ));
412    pairings_r.push(E::G2Prepared::from(verifier_param.h));
413
414    let res = E::multi_pairing(pairings_l, pairings_r).0 == E::TargetField::one();
415
416    end_timer!(pairing_product_timer);
417    end_timer!(verify_timer);
418    Ok(res)
419}
420
421#[cfg(test)]
422mod tests {
423    use super::*;
424    use ark_bls12_381::Bls12_381;
425    use ark_std::UniformRand;
426    use jf_utils::test_rng;
427    type E = Bls12_381;
428    type Fr = <E as Pairing>::ScalarField;
429
430    fn test_single_helper<R: RngCore + CryptoRng>(
431        params: &(MultilinearUniversalParams<E>, UnivariateUniversalParams<E>),
432        poly: &MLE<Fr>,
433        rng: &mut R,
434    ) -> Result<(), PCSError> {
435        let nv = poly.num_vars();
436        assert_ne!(nv, 0);
437        let uni_degree = 1;
438        let (ck, vk) = MultilinearKzgPCS::trim(params, uni_degree, Some(nv))?;
439        let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect();
440        let com = MultilinearKzgPCS::commit(&ck, poly)?;
441        let (proof, value) = MultilinearKzgPCS::open(&ck, poly, &point)?;
442
443        assert!(MultilinearKzgPCS::verify(
444            &vk, &com, &point, &value, &proof
445        )?);
446
447        let value = Fr::rand(rng);
448        assert!(!MultilinearKzgPCS::verify(
449            &vk, &com, &point, &value, &proof
450        )?);
451
452        Ok(())
453    }
454
455    #[test]
456    fn test_single_commit() -> Result<(), PCSError> {
457        let mut rng = test_rng();
458
459        let params = MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 10)?;
460
461        // normal polynomials
462        let poly1 = MLE::from(DenseMultilinearExtension::rand(8, &mut rng));
463        test_single_helper(&params, &poly1, &mut rng)?;
464
465        // single-variate polynomials
466        let poly2 = MLE::from(DenseMultilinearExtension::rand(1, &mut rng));
467        test_single_helper(&params, &poly2, &mut rng)?;
468
469        Ok(())
470    }
471
472    #[test]
473    fn setup_commit_verify_constant_polynomial() {
474        let mut rng = test_rng();
475
476        // normal polynomials
477        assert!(MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 0).is_err());
478    }
479}