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