jf_pcs/multilinear_kzg/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
// Copyright (c) 2022 Espresso Systems (espressosys.com)
// This file is part of the Jellyfish library.

// You should have received a copy of the MIT License
// along with the Jellyfish library. If not, see <https://mit-license.org/>.

//! Main module for multilinear KZG commitment scheme

mod batching;
pub(crate) mod srs;
pub(crate) mod util;

use crate::{
    prelude::{Commitment, UnivariateUniversalParams},
    univariate_kzg::UnivariateKzgProof,
    PCSError, PolynomialCommitmentScheme, StructuredReferenceString,
};
#[cfg(target_has_atomic = "ptr")]
use alloc::sync::Arc;
use ark_ec::{
    pairing::Pairing,
    scalar_mul::{fixed_base::FixedBase, variable_base::VariableBaseMSM},
    AffineRepr, CurveGroup,
};
use ark_ff::PrimeField;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::{
    borrow::Borrow,
    end_timer, format,
    marker::PhantomData,
    rand::{CryptoRng, RngCore},
    start_timer,
    string::ToString,
    vec,
    vec::Vec,
    One, Zero,
};
use batching::{batch_open_internal, batch_verify_internal};
use srs::{MultilinearProverParam, MultilinearUniversalParams, MultilinearVerifierParam};
use util::merge_polynomials;

// type alias for SRS
type Srs<E> = (MultilinearUniversalParams<E>, UnivariateUniversalParams<E>);
type ProverParam<E> = <Srs<E> as StructuredReferenceString>::ProverParam;
type VerifierParam<E> = <Srs<E> as StructuredReferenceString>::VerifierParam;

/// KZG Polynomial Commitment Scheme on multilinear polynomials.
pub struct MultilinearKzgPCS<E: Pairing> {
    #[doc(hidden)]
    phantom: PhantomData<E>,
}

#[derive(Derivative, CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
#[derivative(Hash)]
/// proof of opening
pub struct MultilinearKzgProof<E: Pairing> {
    /// Evaluation of quotients
    pub proofs: Vec<E::G1Affine>,
}

#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
/// proof of batch opening
pub struct MultilinearKzgBatchProof<E: Pairing> {
    /// The actual proof
    pub proof: MultilinearKzgProof<E>,
    /// Commitment to q(x):= w(l(x)) where
    /// - `w` is the merged MLE
    /// - `l` is the list of univariate polys that goes through all points
    pub q_x_commit: Commitment<E>,
    /// openings of q(x) at 1, omega, ..., and r
    pub q_x_opens: Vec<UnivariateKzgProof<E>>,
}

/// Multi-linear Extension (MLE) polynomial, this type alias is set to owned
/// `DenseMultilinearExtension` on wasm platforms since only message-passing
/// concurrency is supported. And set to `Arc<DenseMultilinearExtension>` for
/// platforms that supports atomic operations (e.g. mostly non-wasm, MIPS, x86
/// etc.)
#[cfg(target_has_atomic = "ptr")]
pub type MLE<F> = Arc<DenseMultilinearExtension<F>>;
#[cfg(not(target_has_atomic = "ptr"))]
pub type MLE<F> = DenseMultilinearExtension<F>;

impl<E: Pairing> PolynomialCommitmentScheme for MultilinearKzgPCS<E> {
    // Config
    type SRS = Srs<E>;
    // Polynomial and its associated types
    type Polynomial = MLE<E::ScalarField>;
    type Point = Vec<E::ScalarField>;
    type Evaluation = E::ScalarField;
    // Commitments and proofs
    type Commitment = Commitment<E>;
    type BatchCommitment = Commitment<E>;
    type Proof = MultilinearKzgProof<E>;
    type BatchProof = MultilinearKzgBatchProof<E>;

    /// Trim the universal parameters to specialize the public parameters.
    /// Input both `supported_log_degree` for univariate and
    /// `supported_num_vars` for multilinear.
    fn trim(
        srs: impl Borrow<Self::SRS>,
        supported_log_degree: usize,
        supported_num_vars: Option<usize>,
    ) -> Result<(ProverParam<E>, VerifierParam<E>), PCSError> {
        let supported_num_vars = match supported_num_vars {
            Some(p) => p,
            None => {
                return Err(PCSError::InvalidParameters(
                    "multilinear should receive a num_var param".to_string(),
                ))
            },
        };
        let (uni_ck, uni_vk) = srs.borrow().1.trim(supported_log_degree)?;
        let (ml_ck, ml_vk) = srs.borrow().0.trim(supported_num_vars)?;

        Ok(((ml_ck, uni_ck), (ml_vk, uni_vk)))
    }

    /// Generate a commitment for a polynomial.
    ///
    /// This function takes `2^num_vars` number of scalar multiplications over
    /// G1.
    fn commit(
        prover_param: impl Borrow<ProverParam<E>>,
        poly: &Self::Polynomial,
    ) -> Result<Self::Commitment, PCSError> {
        let prover_param = prover_param.borrow();
        let commit_timer = start_timer!(|| "commit");
        if prover_param.0.num_vars < poly.num_vars {
            return Err(PCSError::InvalidParameters(format!(
                "Poly length ({}) exceeds param limit ({})",
                poly.num_vars, prover_param.0.num_vars
            )));
        }
        let ignored = prover_param.0.num_vars - poly.num_vars;
        let scalars: Vec<_> = poly
            .to_evaluations()
            .into_iter()
            .map(|x| x.into_bigint())
            .collect();
        let commitment = E::G1::msm_bigint(
            &prover_param.0.powers_of_g[ignored].evals,
            scalars.as_slice(),
        )
        .into_affine();

        end_timer!(commit_timer);
        Ok(Commitment(commitment))
    }

    /// Batch commit a list of polynomials.
    ///
    /// This function takes `2^(num_vars + log(polys.len())` number of scalar
    /// multiplications over G1.
    fn batch_commit(
        prover_param: impl Borrow<ProverParam<E>>,
        polys: &[Self::Polynomial],
    ) -> Result<Self::Commitment, PCSError> {
        let prover_param = prover_param.borrow();
        let commit_timer = start_timer!(|| "multi commit");
        let poly = merge_polynomials(polys)?;

        let scalars: Vec<_> = poly
            .to_evaluations()
            .iter()
            .map(|x| x.into_bigint())
            .collect();

        let commitment =
            E::G1::msm_bigint(&prover_param.0.powers_of_g[0].evals, scalars.as_slice())
                .into_affine();

        end_timer!(commit_timer);
        Ok(Commitment(commitment))
    }

    /// On input a polynomial `p` and a point `point`, outputs a proof for the
    /// same. This function does not need to take the evaluation value as an
    /// input.
    ///
    /// This function takes 2^{num_var +1} number of scalar multiplications over
    /// G1:
    /// - it proceeds with `num_var` number of rounds,
    /// - at round i, we compute an MSM for `2^{num_var - i + 1}` number of G2
    ///   elements.
    fn open(
        prover_param: impl Borrow<ProverParam<E>>,
        polynomial: &Self::Polynomial,
        point: &Self::Point,
    ) -> Result<(Self::Proof, Self::Evaluation), PCSError> {
        open_internal(&prover_param.borrow().0, polynomial, point)
    }

    /// Input
    /// - the prover parameters for univariate KZG,
    /// - the prover parameters for multilinear KZG,
    /// - a list of polynomials,
    /// - a (batch) commitment to all polynomials,
    /// - and the same number of points,
    /// compute a batch opening for all the polynomials.
    ///
    /// For simplicity, this API requires each MLE to have only one point. If
    /// the caller wish to use more than one point per MLE, it should be
    /// handled at the caller layer.
    ///
    /// Returns an error if the lengths do not match.
    ///
    /// Returns the proof, consists of
    /// - the multilinear KZG opening
    /// - the univariate KZG commitment to q(x)
    /// - the openings and evaluations of q(x) at omega^i and r
    ///
    /// Steps:
    /// 1. build `l(points)` which is a list of univariate polynomials that goes
    /// through the points
    /// 2. build MLE `w` which is the merge of all MLEs.
    /// 3. build `q(x)` which is a univariate polynomial `W circ l`
    /// 4. commit to q(x) and sample r from transcript
    /// transcript contains: w commitment, points, q(x)'s commitment
    /// 5. build q(omega^i) and their openings
    /// 6. build q(r) and its opening
    /// 7. get a point `p := l(r)`
    /// 8. output an opening of `w` over point `p`
    /// 9. output `w(p)`
    fn batch_open(
        prover_param: impl Borrow<ProverParam<E>>,
        batch_commitment: &Self::BatchCommitment,
        polynomials: &[Self::Polynomial],
        points: &[Self::Point],
    ) -> Result<(Self::BatchProof, Vec<Self::Evaluation>), PCSError> {
        batch_open_internal::<E>(
            &prover_param.borrow().1,
            &prover_param.borrow().0,
            polynomials,
            batch_commitment,
            points,
        )
    }

    /// Verifies that `value` is the evaluation at `x` of the polynomial
    /// committed inside `comm`.
    ///
    /// This function takes
    /// - num_var number of pairing product.
    /// - num_var number of MSM
    fn verify(
        verifier_param: &VerifierParam<E>,
        commitment: &Self::Commitment,
        point: &Self::Point,
        value: &E::ScalarField,
        proof: &Self::Proof,
    ) -> Result<bool, PCSError> {
        verify_internal(&verifier_param.0, commitment, point, value, proof)
    }

    /// Verifies that `value` is the evaluation at `x_i` of the polynomial
    /// `poly_i` committed inside `commitment`.
    /// steps:
    ///
    /// 1. put `q(x)`'s evaluations over `(1, omega,...)` into transcript
    /// 2. sample `r` from transcript
    /// 3. check `q(r) == value`
    /// 4. build `l(points)` which is a list of univariate polynomials that goes
    /// through the points
    /// 5. get a point `p := l(r)`
    /// 6. verifies `p` is verifies against proof
    fn batch_verify<R: RngCore + CryptoRng>(
        verifier_param: &VerifierParam<E>,
        batch_commitment: &Self::BatchCommitment,
        points: &[Self::Point],
        values: &[E::ScalarField],
        batch_proof: &Self::BatchProof,
        _rng: &mut R,
    ) -> Result<bool, PCSError> {
        batch_verify_internal(
            &verifier_param.1,
            &verifier_param.0,
            batch_commitment,
            points,
            values,
            batch_proof,
        )
    }
}

/// On input a polynomial `p` and a point `point`, outputs a proof for the
/// same. This function does not need to take the evaluation value as an
/// input.
///
/// This function takes 2^{num_var} number of scalar multiplications over
/// G1:
/// - it proceeds with `num_var` number of rounds,
/// - at round i, we compute an MSM for `2^{num_var - i}` number of G1 elements.
fn open_internal<E: Pairing>(
    prover_param: &MultilinearProverParam<E>,
    polynomial: &DenseMultilinearExtension<E::ScalarField>,
    point: &[E::ScalarField],
) -> Result<(MultilinearKzgProof<E>, E::ScalarField), PCSError> {
    let open_timer = start_timer!(|| format!("open mle with {} variable", polynomial.num_vars));

    if polynomial.num_vars() > prover_param.num_vars {
        return Err(PCSError::InvalidParameters(format!(
            "Polynomial num_vars {} exceed the limit {}",
            polynomial.num_vars, prover_param.num_vars
        )));
    }

    if polynomial.num_vars() != point.len() {
        return Err(PCSError::InvalidParameters(format!(
            "Polynomial num_vars {} does not match point len {}",
            polynomial.num_vars,
            point.len()
        )));
    }

    let nv = polynomial.num_vars();
    // the first `ignored` SRS vectors are unused
    let ignored = prover_param.num_vars - nv + 1;

    let mut f = polynomial.to_evaluations();

    let mut proofs = Vec::new();

    for (i, (&point_at_k, gi)) in point
        .iter()
        .zip(prover_param.powers_of_g[ignored..ignored + nv].iter())
        .enumerate()
    {
        let ith_round = start_timer!(|| format!("{}-th round", i));

        let k = nv - 1 - i;
        let cur_dim = 1 << k;
        let mut q = vec![E::ScalarField::zero(); cur_dim];
        let mut r = vec![E::ScalarField::zero(); cur_dim];

        let ith_round_eval = start_timer!(|| format!("{}-th round eval", i));
        for b in 0..(1 << k) {
            // q[b] = f[1, b] - f[0, b]
            q[b] = f[(b << 1) + 1] - f[b << 1];

            // r[b] = f[0, b] + q[b] * p
            r[b] = f[b << 1] + (q[b] * point_at_k);
        }
        f = r;
        end_timer!(ith_round_eval);
        let scalars: Vec<_> = q.iter().map(|x| x.into_bigint()).collect();

        // this is a MSM over G1 and is likely to be the bottleneck
        let msm_timer = start_timer!(|| format!("msm of size {} at round {}", gi.evals.len(), i));

        proofs.push(E::G1::msm_bigint(&gi.evals, &scalars).into_affine());
        end_timer!(msm_timer);

        end_timer!(ith_round);
    }
    let eval = polynomial
        .evaluate(point)
        .ok_or_else(|| PCSError::InvalidParameters("fail to eval poly at the point".to_string()))?;
    end_timer!(open_timer);
    Ok((MultilinearKzgProof { proofs }, eval))
}

/// Verifies that `value` is the evaluation at `x` of the polynomial
/// committed inside `comm`.
///
/// This function takes
/// - num_var number of pairing product.
/// - num_var number of MSM
fn verify_internal<E: Pairing>(
    verifier_param: &MultilinearVerifierParam<E>,
    commitment: &Commitment<E>,
    point: &[E::ScalarField],
    value: &E::ScalarField,
    proof: &MultilinearKzgProof<E>,
) -> Result<bool, PCSError> {
    let verify_timer = start_timer!(|| "verify");
    let num_var = point.len();

    if num_var > verifier_param.num_vars {
        return Err(PCSError::InvalidParameters(format!(
            "point length ({}) exceeds param limit ({})",
            num_var, verifier_param.num_vars
        )));
    }

    let prepare_inputs_timer = start_timer!(|| "prepare pairing inputs");

    let scalar_size = E::ScalarField::MODULUS_BIT_SIZE as usize;
    let window_size = FixedBase::get_mul_window_size(num_var);

    let h_table =
        FixedBase::get_window_table(scalar_size, window_size, verifier_param.h.into_group());
    let h_mul: Vec<E::G2> = FixedBase::msm(scalar_size, window_size, &h_table, point);

    // the first `ignored` G2 parameters are unused
    let ignored = verifier_param.num_vars - num_var;
    let h_vec: Vec<_> = (0..num_var)
        .map(|i| verifier_param.h_mask[ignored + i].into_group() - h_mul[i])
        .collect();
    let h_vec: Vec<E::G2Affine> = E::G2::normalize_batch(&h_vec);
    end_timer!(prepare_inputs_timer);

    let pairing_product_timer = start_timer!(|| "pairing product");

    let mut pairings_l: Vec<E::G1Prepared> = proof
        .proofs
        .iter()
        .map(|&x| E::G1Prepared::from(x))
        .collect();

    let mut pairings_r: Vec<E::G2Prepared> = h_vec
        .into_iter()
        .take(num_var)
        .map(E::G2Prepared::from)
        .collect();
    pairings_l.push(E::G1Prepared::from(
        (verifier_param.g * (*value) - commitment.0.into_group()).into_affine(),
    ));
    pairings_r.push(E::G2Prepared::from(verifier_param.h));

    let res = E::multi_pairing(pairings_l, pairings_r).0 == E::TargetField::one();

    end_timer!(pairing_product_timer);
    end_timer!(verify_timer);
    Ok(res)
}

#[cfg(test)]
mod tests {
    use super::*;
    use ark_bls12_381::Bls12_381;
    use ark_std::UniformRand;
    use jf_utils::test_rng;
    type E = Bls12_381;
    type Fr = <E as Pairing>::ScalarField;

    fn test_single_helper<R: RngCore + CryptoRng>(
        params: &(MultilinearUniversalParams<E>, UnivariateUniversalParams<E>),
        poly: &MLE<Fr>,
        rng: &mut R,
    ) -> Result<(), PCSError> {
        let nv = poly.num_vars();
        assert_ne!(nv, 0);
        let uni_degree = 1;
        let (ck, vk) = MultilinearKzgPCS::trim(params, uni_degree, Some(nv))?;
        let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect();
        let com = MultilinearKzgPCS::commit(&ck, poly)?;
        let (proof, value) = MultilinearKzgPCS::open(&ck, poly, &point)?;

        assert!(MultilinearKzgPCS::verify(
            &vk, &com, &point, &value, &proof
        )?);

        let value = Fr::rand(rng);
        assert!(!MultilinearKzgPCS::verify(
            &vk, &com, &point, &value, &proof
        )?);

        Ok(())
    }

    #[test]
    fn test_single_commit() -> Result<(), PCSError> {
        let mut rng = test_rng();

        let params = MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 10)?;

        // normal polynomials
        let poly1 = MLE::from(DenseMultilinearExtension::rand(8, &mut rng));
        test_single_helper(&params, &poly1, &mut rng)?;

        // single-variate polynomials
        let poly2 = MLE::from(DenseMultilinearExtension::rand(1, &mut rng));
        test_single_helper(&params, &poly2, &mut rng)?;

        Ok(())
    }

    #[test]
    fn setup_commit_verify_constant_polynomial() {
        let mut rng = test_rng();

        // normal polynomials
        assert!(MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 0).is_err());
    }
}