jf_pcs/multilinear_kzg/
batching.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
7use super::{
8    open_internal,
9    srs::{MultilinearProverParam, MultilinearVerifierParam},
10    util::{build_l, compute_w_circ_l, merge_polynomials},
11    verify_internal, MultilinearKzgBatchProof, MLE,
12};
13use crate::{
14    multilinear_kzg::util::get_uni_domain,
15    prelude::{Commitment, UnivariateProverParam, UnivariateVerifierParam},
16    transcript::IOPTranscript,
17    univariate_kzg::UnivariateKzgPCS,
18    PCSError, PolynomialCommitmentScheme,
19};
20use ark_ec::pairing::Pairing;
21use ark_poly::{EvaluationDomain, MultilinearExtension, Polynomial};
22use ark_std::{end_timer, format, start_timer, string::ToString, vec, vec::Vec};
23
24/// Input
25/// - the prover parameters for univariate KZG,
26/// - the prover parameters for multilinear KZG,
27/// - a list of MLEs,
28/// - a batch commitment to all MLEs
29/// - and a same number of points,
30/// compute a batch opening for all the polynomials.
31///
32/// For simplicity, this API requires each MLE to have only one point. If
33/// the caller wish to use more than one points per MLE, it should be
34/// handled at the caller layer.
35///
36/// Returns an error if the lengths do not match.
37///
38/// Returns the proof, consists of
39/// - the multilinear KZG opening
40/// - the univariate KZG commitment to q(x)
41/// - the openings and evaluations of q(x) at omega^i and r
42///
43/// Steps:
44/// 1. build `l(points)` which is a list of univariate polynomials that goes
45/// through the points
46/// 2. build MLE `w` which is the merge of all MLEs.
47/// 3. build `q(x)` which is a univariate polynomial `W circ l`
48/// 4. commit to q(x) and sample r from transcript
49/// transcript contains: w commitment, points, q(x)'s commitment
50/// 5. build q(omega^i) and their openings
51/// 6. build q(r) and its opening
52/// 7. get a point `p := l(r)`
53/// 8. output an opening of `w` over point `p`
54/// 9. output `w(p)`
55///
56/// TODO: Migrate the batching algorithm in HyperPlonk repo
57pub(super) fn batch_open_internal<E: Pairing>(
58    uni_prover_param: &UnivariateProverParam<E>,
59    ml_prover_param: &MultilinearProverParam<E>,
60    polynomials: &[MLE<E::ScalarField>],
61    batch_commitment: &Commitment<E>,
62    points: &[Vec<E::ScalarField>],
63) -> Result<(MultilinearKzgBatchProof<E>, Vec<E::ScalarField>), PCSError> {
64    let open_timer = start_timer!(|| "batch open");
65
66    // ===================================
67    // Sanity checks on inputs
68    // ===================================
69    let points_len = points.len();
70    if points_len == 0 {
71        return Err(PCSError::InvalidParameters("points is empty".to_string()));
72    }
73
74    if points_len != polynomials.len() {
75        return Err(PCSError::InvalidParameters(
76            "polynomial length does not match point length".to_string(),
77        ));
78    }
79
80    let num_var = polynomials[0].num_vars();
81    for poly in polynomials.iter().skip(1) {
82        if poly.num_vars() != num_var {
83            return Err(PCSError::InvalidParameters(
84                "polynomials do not have same num_vars".to_string(),
85            ));
86        }
87    }
88    for point in points.iter() {
89        if point.len() != num_var {
90            return Err(PCSError::InvalidParameters(
91                "points do not have same num_vars".to_string(),
92            ));
93        }
94    }
95
96    let domain = get_uni_domain::<E::ScalarField>(points_len)?;
97
98    // 1. build `l(points)` which is a list of univariate polynomials that goes
99    // through the points
100    let uni_polys = build_l(num_var, points, &domain)?;
101
102    // 2. build MLE `w` which is the merge of all MLEs.
103    let merge_poly = merge_polynomials(polynomials)?;
104
105    // 3. build `q(x)` which is a univariate polynomial `W circ l`
106    let q_x = compute_w_circ_l(&merge_poly, &uni_polys)?;
107
108    // 4. commit to q(x) and sample r from transcript
109    // transcript contains: w commitment, points, q(x)'s commitment
110    let mut transcript = IOPTranscript::new(b"ml kzg");
111    transcript.append_serializable_element(b"w", batch_commitment)?;
112    for point in points {
113        transcript.append_serializable_element(b"w", point)?;
114    }
115
116    let q_x_commit = UnivariateKzgPCS::<E>::commit(uni_prover_param, &q_x)?;
117    transcript.append_serializable_element(b"q(x)", &q_x_commit)?;
118    let r = transcript.get_and_append_challenge(b"r")?;
119
120    // 5. build q(omega^i) and their openings
121    let mut q_x_opens = vec![];
122    let mut q_x_evals = vec![];
123    for i in 0..points_len {
124        let (q_x_open, q_x_eval) =
125            UnivariateKzgPCS::<E>::open(uni_prover_param, &q_x, &domain.element(i))?;
126        q_x_opens.push(q_x_open);
127        q_x_evals.push(q_x_eval);
128
129        // sanity check
130        let point: Vec<E::ScalarField> = uni_polys
131            .iter()
132            .rev()
133            .map(|poly| poly.evaluate(&domain.element(i)))
134            .collect();
135        let mle_eval = merge_poly.evaluate(&point).unwrap();
136        if mle_eval != q_x_eval {
137            return Err(PCSError::InvalidProver(
138                "Q(omega) does not match W(l(omega))".to_string(),
139            ));
140        }
141    }
142
143    // 6. build q(r) and its opening
144    let (q_x_open, q_r_value) = UnivariateKzgPCS::<E>::open(uni_prover_param, &q_x, &r)?;
145    q_x_opens.push(q_x_open);
146    q_x_evals.push(q_r_value);
147
148    // 7. get a point `p := l(r)`
149    let point: Vec<E::ScalarField> = uni_polys
150        .iter()
151        .rev()
152        .map(|poly| poly.evaluate(&r))
153        .collect();
154
155    // 8. output an opening of `w` over point `p`
156    let (mle_opening, mle_eval) = open_internal(ml_prover_param, &merge_poly, &point)?;
157
158    // 9. output value that is `w` evaluated at `p` (which should match `q(r)`)
159    if mle_eval != q_r_value {
160        return Err(PCSError::InvalidProver(
161            "Q(r) does not match W(l(r))".to_string(),
162        ));
163    }
164    end_timer!(open_timer);
165
166    Ok((
167        MultilinearKzgBatchProof {
168            proof: mle_opening,
169            q_x_commit,
170            q_x_opens,
171        },
172        q_x_evals,
173    ))
174}
175
176/// Verifies that the `batch_commitment` is a valid commitment
177/// to a list of MLEs for the given openings and evaluations in
178/// the batch_proof.
179///
180/// steps:
181///
182/// 1. push w, points and q_com into transcript
183/// 2. sample `r` from transcript
184/// 3. check `q(r) == batch_proof.q_x_value.last` and
185/// `q(omega^i) == batch_proof.q_x_value[i]`
186/// 4. build `l(points)` which is a list of univariate
187/// polynomials that goes through the points
188/// 5. get a point `p := l(r)`
189/// 6. verifies `p` is valid against multilinear KZG proof
190pub(super) fn batch_verify_internal<E: Pairing>(
191    uni_verifier_param: &UnivariateVerifierParam<E>,
192    ml_verifier_param: &MultilinearVerifierParam<E>,
193    batch_commitment: &Commitment<E>,
194    points: &[Vec<E::ScalarField>],
195    values: &[E::ScalarField],
196    batch_proof: &MultilinearKzgBatchProof<E>,
197) -> Result<bool, PCSError> {
198    let verify_timer = start_timer!(|| "batch verify");
199
200    // ===================================
201    // Sanity checks on inputs
202    // ===================================
203    let points_len = points.len();
204    if points_len == 0 {
205        return Err(PCSError::InvalidParameters("points is empty".to_string()));
206    }
207
208    // add one here because we also have q(r) and its opening
209    if points_len + 1 != batch_proof.q_x_opens.len() {
210        return Err(PCSError::InvalidParameters(
211            "openings length does not match point length".to_string(),
212        ));
213    }
214
215    if points_len + 1 != values.len() {
216        return Err(PCSError::InvalidParameters(
217            "values length does not match point length".to_string(),
218        ));
219    }
220
221    let num_var = points[0].len();
222    for point in points.iter().skip(1) {
223        if point.len() != num_var {
224            return Err(PCSError::InvalidParameters(format!(
225                "points do not have same num_vars ({} vs {})",
226                point.len(),
227                num_var,
228            )));
229        }
230    }
231
232    let domain = get_uni_domain::<E::ScalarField>(points_len)?;
233
234    // 1. push w, points and q_com into transcript
235    let mut transcript = IOPTranscript::new(b"ml kzg");
236    transcript.append_serializable_element(b"w", batch_commitment)?;
237    for point in points {
238        transcript.append_serializable_element(b"w", point)?;
239    }
240
241    transcript.append_serializable_element(b"q(x)", &batch_proof.q_x_commit)?;
242
243    // 2. sample `r` from transcript
244    let r = transcript.get_and_append_challenge(b"r")?;
245
246    // 3. check `q(r) == batch_proof.q_x_value.last` and `q(omega^i) =
247    // batch_proof.q_x_value[i]`
248    for (i, value) in values.iter().enumerate().take(points_len) {
249        if !UnivariateKzgPCS::verify(
250            uni_verifier_param,
251            &batch_proof.q_x_commit,
252            &domain.element(i),
253            value,
254            &batch_proof.q_x_opens[i],
255        )? {
256            #[cfg(debug_assertion)]
257            println!("q(omega^{}) verification failed", i);
258            return Ok(false);
259        }
260    }
261
262    if !UnivariateKzgPCS::verify(
263        uni_verifier_param,
264        &batch_proof.q_x_commit,
265        &r,
266        &values[points_len],
267        &batch_proof.q_x_opens[points_len],
268    )? {
269        #[cfg(debug_assertion)]
270        println!("q(r) verification failed");
271        return Ok(false);
272    }
273
274    // 4. build `l(points)` which is a list of univariate polynomials that goes
275    // through the points
276    let uni_polys = build_l(num_var, points, &domain)?;
277
278    // 5. get a point `p := l(r)`
279    let point: Vec<E::ScalarField> = uni_polys.iter().rev().map(|x| x.evaluate(&r)).collect();
280
281    // 6. verifies `p` is valid against multilinear KZG proof
282    let res = verify_internal(
283        ml_verifier_param,
284        batch_commitment,
285        &point,
286        &values[points_len],
287        &batch_proof.proof,
288    )?;
289
290    #[cfg(debug_assertion)]
291    if !res {
292        println!("multilinear KZG verification failed");
293    }
294
295    end_timer!(verify_timer);
296
297    Ok(res)
298}
299
300#[cfg(test)]
301mod tests {
302    use super::{
303        super::{util::get_batched_nv, *},
304        *,
305    };
306    use crate::multilinear_kzg::util::{compute_qx_degree, generate_evaluations};
307    use ark_bls12_381::Bls12_381 as E;
308    use ark_std::{log2, UniformRand};
309    use jf_utils::test_rng;
310    type Fr = <E as Pairing>::ScalarField;
311
312    fn test_batch_commit_helper<R: RngCore + CryptoRng>(
313        uni_params: &UnivariateUniversalParams<E>,
314        ml_params: &MultilinearUniversalParams<E>,
315        polys: &[MLE<Fr>],
316        rng: &mut R,
317    ) -> Result<(), PCSError> {
318        let merged_nv = get_batched_nv(polys[0].num_vars(), polys.len());
319        let qx_degree = compute_qx_degree(merged_nv, polys.len());
320        let padded_qx_degree = 1usize << log2(qx_degree);
321
322        let (uni_ck, uni_vk) = uni_params.trim(padded_qx_degree)?;
323        let (ml_ck, ml_vk) = ml_params.trim(merged_nv)?;
324
325        let mut points = Vec::new();
326        for poly in polys.iter() {
327            let point = (0..poly.num_vars())
328                .map(|_| Fr::rand(rng))
329                .collect::<Vec<Fr>>();
330            points.push(point);
331        }
332
333        let evals = generate_evaluations(polys, &points)?;
334
335        let com = MultilinearKzgPCS::batch_commit((ml_ck.clone(), uni_ck.clone()), polys)?;
336        let (batch_proof, evaluations) =
337            batch_open_internal(&uni_ck, &ml_ck, polys, &com, &points)?;
338
339        for (a, b) in evals.iter().zip(evaluations.iter()) {
340            assert_eq!(a, b)
341        }
342
343        // good path
344        assert!(batch_verify_internal(
345            &uni_vk,
346            &ml_vk,
347            &com,
348            &points,
349            &evaluations,
350            &batch_proof,
351        )?);
352
353        // bad commitment
354        assert!(!batch_verify_internal(
355            &uni_vk,
356            &ml_vk,
357            &Commitment(<E as Pairing>::G1Affine::default()),
358            &points,
359            &evaluations,
360            &batch_proof,
361        )?);
362
363        // bad points
364        assert!(
365            batch_verify_internal(&uni_vk, &ml_vk, &com, &points[1..], &[], &batch_proof,).is_err()
366        );
367
368        // bad proof
369        assert!(batch_verify_internal(
370            &uni_vk,
371            &ml_vk,
372            &com,
373            &points,
374            &evaluations,
375            &MultilinearKzgBatchProof {
376                proof: MultilinearKzgProof { proofs: Vec::new() },
377                q_x_commit: Commitment(<E as Pairing>::G1Affine::default()),
378                q_x_opens: vec![],
379            },
380        )
381        .is_err());
382
383        // bad value
384        let mut wrong_evals = evaluations.clone();
385        wrong_evals[0] = Fr::default();
386        assert!(!batch_verify_internal(
387            &uni_vk,
388            &ml_vk,
389            &com,
390            &points,
391            &wrong_evals,
392            &batch_proof
393        )?);
394
395        // bad q(x) commit
396        let mut wrong_proof = batch_proof;
397        wrong_proof.q_x_commit = Commitment(<E as Pairing>::G1Affine::default());
398        assert!(!batch_verify_internal(
399            &uni_vk,
400            &ml_vk,
401            &com,
402            &points,
403            &evaluations,
404            &wrong_proof,
405        )?);
406        Ok(())
407    }
408
409    #[test]
410    fn test_batch_commit_internal() -> Result<(), PCSError> {
411        let mut rng = test_rng();
412
413        let uni_params =
414            UnivariateUniversalParams::<E>::gen_srs_for_testing(&mut rng, 1usize << 15)?;
415        let ml_params = MultilinearUniversalParams::<E>::gen_srs_for_testing(&mut rng, 15)?;
416
417        // normal polynomials
418        let polys1: Vec<_> = (0..5)
419            .map(|_| MLE::from(DenseMultilinearExtension::rand(4, &mut rng)))
420            .collect();
421        test_batch_commit_helper(&uni_params, &ml_params, &polys1, &mut rng)?;
422
423        // single-variate polynomials
424        let polys1: Vec<_> = (0..5)
425            .map(|_| MLE::from(DenseMultilinearExtension::rand(1, &mut rng)))
426            .collect();
427        test_batch_commit_helper(&uni_params, &ml_params, &polys1, &mut rng)?;
428
429        Ok(())
430    }
431}