arnaucube
arnaucube/hyperplonkPublic Self-hosted repository
hyperplonk/subroutines/src/pcs/multilinear_kzg/batching.rs
subroutines/src/pcs/multilinear_kzg/batching.rsRaw
  1//! Sumcheck based batch opening and verify commitment.
  2// TODO: refactoring this code to somewhere else
  3// currently IOP depends on PCS because perm check requires commitment.
  4// The sumcheck based batch opening therefore cannot stay in the PCS repo --
  5// which creates a cyclic dependency.
  6
  7use crate::{
  8    pcs::{
  9        multilinear_kzg::util::eq_eval,
 10        prelude::{Commitment, PCSError},
 11        PolynomialCommitmentScheme,
 12    },
 13    poly_iop::{prelude::SumCheck, PolyIOP},
 14    IOPProof,
 15};
 16use arithmetic::{build_eq_x_r_vec, DenseMultilinearExtension, VPAuxInfo, VirtualPolynomial};
 17use ark_ec::{msm::VariableBaseMSM, PairingEngine, ProjectiveCurve};
 18use ark_ff::PrimeField;
 19use ark_std::{end_timer, log2, start_timer, One, Zero};
 20use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
 21use std::{marker::PhantomData, sync::Arc};
 22use transcript::IOPTranscript;
 23
 24#[derive(Clone, Debug, Default, PartialEq, Eq)]
 25pub struct BatchProof<E, PCS>
 26where
 27    E: PairingEngine,
 28    PCS: PolynomialCommitmentScheme<E>,
 29{
 30    /// A sum check proof proving tilde g's sum
 31    pub(crate) sum_check_proof: IOPProof<E::Fr>,
 32    /// f_i(point_i)
 33    pub f_i_eval_at_point_i: Vec<E::Fr>,
 34    /// proof for g'(a_2)
 35    pub(crate) g_prime_proof: PCS::Proof,
 36}
 37
 38/// Steps:
 39/// 1. get challenge point t from transcript
 40/// 2. build eq(t,i) for i in [0..k]
 41/// 3. build \tilde g_i(b) = eq(t, i) * f_i(b)
 42/// 4. compute \tilde eq_i(b) = eq(b, point_i)
 43/// 5. run sumcheck on \sum_i=1..k \tilde eq_i * \tilde g_i
 44/// 6. build g'(X) = \sum_i=1..k \tilde eq_i(a2) * \tilde g_i(X) where (a2) is
 45/// the sumcheck's point 7. open g'(X) at point (a2)
 46pub(crate) fn multi_open_internal<E, PCS>(
 47    prover_param: &PCS::ProverParam,
 48    polynomials: &[PCS::Polynomial],
 49    points: &[PCS::Point],
 50    evals: &[PCS::Evaluation],
 51    transcript: &mut IOPTranscript<E::Fr>,
 52) -> Result<BatchProof<E, PCS>, PCSError>
 53where
 54    E: PairingEngine,
 55    PCS: PolynomialCommitmentScheme<
 56        E,
 57        Polynomial = Arc<DenseMultilinearExtension<E::Fr>>,
 58        Point = Vec<E::Fr>,
 59        Evaluation = E::Fr,
 60    >,
 61{
 62    let open_timer = start_timer!(|| format!("multi open {} points", points.len()));
 63
 64    // TODO: sanity checks
 65    let num_var = polynomials[0].num_vars;
 66    let k = polynomials.len();
 67    let ell = log2(k) as usize;
 68
 69    // challenge point t
 70    let t = transcript.get_and_append_challenge_vectors("t".as_ref(), ell)?;
 71
 72    // eq(t, i) for i in [0..k]
 73    let eq_t_i_list = build_eq_x_r_vec(t.as_ref())?;
 74
 75    // \tilde g_i(b) = eq(t, i) * f_i(b)
 76    let timer = start_timer!(|| format!("compute tilde g for {} points", points.len()));
 77    let mut tilde_gs = vec![];
 78    for (index, f_i) in polynomials.iter().enumerate() {
 79        let mut tilde_g_eval = vec![E::Fr::zero(); 1 << num_var];
 80        for (j, &f_i_eval) in f_i.iter().enumerate() {
 81            tilde_g_eval[j] = f_i_eval * eq_t_i_list[index];
 82        }
 83        tilde_gs.push(Arc::new(DenseMultilinearExtension::from_evaluations_vec(
 84            num_var,
 85            tilde_g_eval,
 86        )));
 87    }
 88    end_timer!(timer);
 89
 90    let timer = start_timer!(|| format!("compute tilde eq for {} points", points.len()));
 91    let tilde_eqs: Vec<Arc<DenseMultilinearExtension<<E as PairingEngine>::Fr>>> = points
 92        .par_iter()
 93        .map(|point| {
 94            let eq_b_zi = build_eq_x_r_vec(point).unwrap();
 95            Arc::new(DenseMultilinearExtension::from_evaluations_vec(
 96                num_var, eq_b_zi,
 97            ))
 98        })
 99        .collect();
100    end_timer!(timer);
101
102    // built the virtual polynomial for SumCheck
103    let timer = start_timer!(|| format!("sum check prove of {} variables", num_var));
104
105    let step = start_timer!(|| "add mle");
106    let mut sum_check_vp = VirtualPolynomial::new(num_var);
107    for (tilde_g, tilde_eq) in tilde_gs.iter().zip(tilde_eqs.into_iter()) {
108        sum_check_vp.add_mle_list([tilde_g.clone(), tilde_eq], E::Fr::one())?;
109    }
110    end_timer!(step);
111
112    let proof = match <PolyIOP<E::Fr> as SumCheck<E::Fr>>::prove(&sum_check_vp, transcript) {
113        Ok(p) => p,
114        Err(_e) => {
115            // cannot wrap IOPError with PCSError due to cyclic dependency
116            return Err(PCSError::InvalidProver(
117                "Sumcheck in batch proving Failed".to_string(),
118            ));
119        },
120    };
121
122    end_timer!(timer);
123
124    // a2 := sumcheck's point
125    let a2 = &proof.point[..num_var];
126
127    // build g'(X) = \sum_i=1..k \tilde eq_i(a2) * \tilde g_i(X) where (a2) is the
128    // sumcheck's point \tilde eq_i(a2) = eq(a2, point_i)
129    let step = start_timer!(|| "evaluate at a2");
130    let mut g_prime_evals = vec![E::Fr::zero(); 1 << num_var];
131    for (tilde_g, point) in tilde_gs.iter().zip(points.iter()) {
132        let eq_i_a2 = eq_eval(a2, point)?;
133        for (j, &tilde_g_eval) in tilde_g.iter().enumerate() {
134            g_prime_evals[j] += tilde_g_eval * eq_i_a2;
135        }
136    }
137    let g_prime = Arc::new(DenseMultilinearExtension::from_evaluations_vec(
138        num_var,
139        g_prime_evals,
140    ));
141    end_timer!(step);
142
143    let step = start_timer!(|| "pcs open");
144    let (g_prime_proof, _g_prime_eval) = PCS::open(prover_param, &g_prime, a2.to_vec().as_ref())?;
145    // assert_eq!(g_prime_eval, tilde_g_eval);
146    end_timer!(step);
147
148    let step = start_timer!(|| "evaluate fi(pi)");
149    end_timer!(step);
150    end_timer!(open_timer);
151
152    Ok(BatchProof {
153        sum_check_proof: proof,
154        f_i_eval_at_point_i: evals.to_vec(),
155        g_prime_proof,
156    })
157}
158
159/// Steps:
160/// 1. get challenge point t from transcript
161/// 2. build g' commitment
162/// 3. ensure \sum_i eq(a2, point_i) * eq(t, <i>) * f_i_evals matches the sum
163/// via SumCheck verification 4. verify commitment
164pub(crate) fn batch_verify_internal<E, PCS>(
165    verifier_param: &PCS::VerifierParam,
166    f_i_commitments: &[Commitment<E>],
167    points: &[PCS::Point],
168    proof: &BatchProof<E, PCS>,
169    transcript: &mut IOPTranscript<E::Fr>,
170) -> Result<bool, PCSError>
171where
172    E: PairingEngine,
173    PCS: PolynomialCommitmentScheme<
174        E,
175        Polynomial = Arc<DenseMultilinearExtension<E::Fr>>,
176        Point = Vec<E::Fr>,
177        Evaluation = E::Fr,
178        Commitment = Commitment<E>,
179    >,
180{
181    let open_timer = start_timer!(|| "batch verification");
182
183    // TODO: sanity checks
184
185    let k = f_i_commitments.len();
186    let ell = log2(k) as usize;
187    let num_var = proof.sum_check_proof.point.len();
188
189    // challenge point t
190    let t = transcript.get_and_append_challenge_vectors("t".as_ref(), ell)?;
191
192    // sum check point (a2)
193    let a2 = &proof.sum_check_proof.point[..num_var];
194
195    // build g' commitment
196    let step = start_timer!(|| "build homomorphic commitment");
197    let eq_t_list = build_eq_x_r_vec(t.as_ref())?;
198
199    let mut scalars = vec![];
200    let mut bases = vec![];
201
202    for (i, point) in points.iter().enumerate() {
203        let eq_i_a2 = eq_eval(a2, point)?;
204        scalars.push((eq_i_a2 * eq_t_list[i]).into_repr());
205        bases.push(f_i_commitments[i].0);
206    }
207    let g_prime_commit = VariableBaseMSM::multi_scalar_mul(&bases, &scalars);
208    end_timer!(step);
209
210    // ensure \sum_i eq(t, <i>) * f_i_evals matches the sum via SumCheck
211    let mut sum = E::Fr::zero();
212    for (i, &e) in eq_t_list.iter().enumerate().take(k) {
213        sum += e * proof.f_i_eval_at_point_i[i];
214    }
215    let aux_info = VPAuxInfo {
216        max_degree: 2,
217        num_variables: num_var,
218        phantom: PhantomData,
219    };
220    let subclaim = match <PolyIOP<E::Fr> as SumCheck<E::Fr>>::verify(
221        sum,
222        &proof.sum_check_proof,
223        &aux_info,
224        transcript,
225    ) {
226        Ok(p) => p,
227        Err(_e) => {
228            // cannot wrap IOPError with PCSError due to cyclic dependency
229            return Err(PCSError::InvalidProver(
230                "Sumcheck in batch verification failed".to_string(),
231            ));
232        },
233    };
234    let tilde_g_eval = subclaim.expected_evaluation;
235
236    // verify commitment
237    let res = PCS::verify(
238        verifier_param,
239        &Commitment(g_prime_commit.into_affine()),
240        a2.to_vec().as_ref(),
241        &tilde_g_eval,
242        &proof.g_prime_proof,
243    )?;
244
245    end_timer!(open_timer);
246    Ok(res)
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252    use crate::pcs::{
253        prelude::{MultilinearKzgPCS, MultilinearUniversalParams},
254        StructuredReferenceString,
255    };
256    use arithmetic::get_batched_nv;
257    use ark_bls12_381::Bls12_381 as E;
258    use ark_ec::PairingEngine;
259    use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
260    use ark_std::{
261        rand::{CryptoRng, RngCore},
262        test_rng,
263        vec::Vec,
264        UniformRand,
265    };
266
267    type Fr = <E as PairingEngine>::Fr;
268
269    fn test_multi_open_helper<R: RngCore + CryptoRng>(
270        ml_params: &MultilinearUniversalParams<E>,
271        polys: &[Arc<DenseMultilinearExtension<Fr>>],
272        rng: &mut R,
273    ) -> Result<(), PCSError> {
274        let merged_nv = get_batched_nv(polys[0].num_vars(), polys.len());
275        let (ml_ck, ml_vk) = ml_params.trim(merged_nv)?;
276
277        let mut points = Vec::new();
278        for poly in polys.iter() {
279            let point = (0..poly.num_vars())
280                .map(|_| Fr::rand(rng))
281                .collect::<Vec<Fr>>();
282            points.push(point);
283        }
284
285        let evals = polys
286            .iter()
287            .zip(points.iter())
288            .map(|(f, p)| f.evaluate(p).unwrap())
289            .collect::<Vec<_>>();
290
291        let commitments = polys
292            .iter()
293            .map(|poly| MultilinearKzgPCS::commit(&ml_ck.clone(), poly).unwrap())
294            .collect::<Vec<_>>();
295
296        let mut transcript = IOPTranscript::new("test transcript".as_ref());
297        transcript.append_field_element("init".as_ref(), &Fr::zero())?;
298
299        let batch_proof = multi_open_internal::<E, MultilinearKzgPCS<E>>(
300            &ml_ck,
301            polys,
302            &points,
303            &evals,
304            &mut transcript,
305        )?;
306
307        // good path
308        let mut transcript = IOPTranscript::new("test transcript".as_ref());
309        transcript.append_field_element("init".as_ref(), &Fr::zero())?;
310        assert!(batch_verify_internal::<E, MultilinearKzgPCS<E>>(
311            &ml_vk,
312            &commitments,
313            &points,
314            &batch_proof,
315            &mut transcript
316        )?);
317
318        Ok(())
319    }
320
321    #[test]
322    fn test_multi_open_internal() -> Result<(), PCSError> {
323        let mut rng = test_rng();
324
325        let ml_params = MultilinearUniversalParams::<E>::gen_srs_for_testing(&mut rng, 20)?;
326        for num_poly in 5..6 {
327            for nv in 15..16 {
328                let polys1: Vec<_> = (0..num_poly)
329                    .map(|_| Arc::new(DenseMultilinearExtension::rand(nv, &mut rng)))
330                    .collect();
331                test_multi_open_helper(&ml_params, &polys1, &mut rng)?;
332            }
333        }
334
335        Ok(())
336    }
337}