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::{
 17    build_eq_x_r_vec, fix_last_variables, DenseMultilinearExtension, VPAuxInfo, VirtualPolynomial,
 18};
 19use ark_ec::{AffineCurve, PairingEngine, ProjectiveCurve};
 20use ark_std::{end_timer, log2, start_timer, One, Zero};
 21use std::{marker::PhantomData, rc::Rc};
 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
 43/// 5. run sumcheck on \tilde eq * \tilde g(i, b)
 44/// 6. build g'(a2) where (a1, a2) is the sumcheck's point
 45pub(crate) fn multi_open_internal<E, PCS>(
 46    prover_param: &PCS::ProverParam,
 47    polynomials: &[PCS::Polynomial],
 48    points: &[PCS::Point],
 49    evals: &[PCS::Evaluation],
 50    transcript: &mut IOPTranscript<E::Fr>,
 51) -> Result<BatchProof<E, PCS>, PCSError>
 52where
 53    E: PairingEngine,
 54    PCS: PolynomialCommitmentScheme<
 55        E,
 56        Polynomial = Rc<DenseMultilinearExtension<E::Fr>>,
 57        Point = Vec<E::Fr>,
 58        Evaluation = E::Fr,
 59    >,
 60{
 61    let open_timer = start_timer!(|| format!("multi open {} points", points.len()));
 62
 63    // TODO: sanity checks
 64    let num_var = polynomials[0].num_vars;
 65    let k = polynomials.len();
 66    let ell = log2(k) as usize;
 67    let merged_num_var = num_var + ell;
 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_g_eval = vec![E::Fr::zero(); 1 << (ell + num_var)];
 78    let block_size = 1 << num_var;
 79    for (index, f_i) in polynomials.iter().enumerate() {
 80        for (j, &f_i_eval) in f_i.iter().enumerate() {
 81            tilde_g_eval[index * block_size + j] = f_i_eval * eq_t_i_list[index];
 82        }
 83    }
 84    let tilde_g = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
 85        merged_num_var,
 86        tilde_g_eval,
 87    ));
 88    end_timer!(timer);
 89
 90    let timer = start_timer!(|| format!("compute tilde eq for {} points", points.len()));
 91    let mut tilde_eq_eval = vec![E::Fr::zero(); 1 << (ell + num_var)];
 92    for (index, point) in points.iter().enumerate() {
 93        let eq_b_zi = build_eq_x_r_vec(point)?;
 94        let start = index * block_size;
 95        tilde_eq_eval[start..start + block_size].copy_from_slice(eq_b_zi.as_slice());
 96    }
 97    let tilde_eq = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
 98        merged_num_var,
 99        tilde_eq_eval,
100    ));
101    end_timer!(timer);
102
103    // built the virtual polynomial for SumCheck
104    let timer = start_timer!(|| format!("sum check prove of {} variables", num_var + ell));
105
106    let step = start_timer!(|| "add mle");
107    let mut sum_check_vp = VirtualPolynomial::new(num_var + ell);
108    sum_check_vp.add_mle_list([tilde_g.clone(), tilde_eq], E::Fr::one())?;
109    end_timer!(step);
110
111    let proof = match <PolyIOP<E::Fr> as SumCheck<E::Fr>>::prove(&sum_check_vp, transcript) {
112        Ok(p) => p,
113        Err(_e) => {
114            // cannot wrap IOPError with PCSError due to cyclic dependency
115            return Err(PCSError::InvalidProver(
116                "Sumcheck in batch proving Failed".to_string(),
117            ));
118        },
119    };
120
121    end_timer!(timer);
122
123    // (a1, a2) := sumcheck's point
124    let step = start_timer!(|| "open at a2");
125    let a1 = &proof.point[num_var..];
126    let a2 = &proof.point[..num_var];
127    end_timer!(step);
128
129    // build g'(a2)
130    let step = start_timer!(|| "evaluate at a2");
131    let g_prime = Rc::new(fix_last_variables(&tilde_g, a1));
132    end_timer!(step);
133
134    let step = start_timer!(|| "pcs open");
135    let (g_prime_proof, _g_prime_eval) = PCS::open(prover_param, &g_prime, a2.to_vec().as_ref())?;
136    // assert_eq!(g_prime_eval, tilde_g_eval);
137    end_timer!(step);
138
139    let step = start_timer!(|| "evaluate fi(pi)");
140    end_timer!(step);
141    end_timer!(open_timer);
142
143    Ok(BatchProof {
144        sum_check_proof: proof,
145        f_i_eval_at_point_i: evals.to_vec(),
146        g_prime_proof,
147    })
148}
149
150/// Steps:
151/// 1. get challenge point t from transcript
152/// 2. build g' commitment
153/// 3. ensure \sum_i eq(t, <i>) * f_i_evals matches the sum via SumCheck
154/// verification 4. verify commitment
155pub(crate) fn batch_verify_internal<E, PCS>(
156    verifier_param: &PCS::VerifierParam,
157    f_i_commitments: &[Commitment<E>],
158    points: &[PCS::Point],
159    proof: &BatchProof<E, PCS>,
160    transcript: &mut IOPTranscript<E::Fr>,
161) -> Result<bool, PCSError>
162where
163    E: PairingEngine,
164    PCS: PolynomialCommitmentScheme<
165        E,
166        Polynomial = Rc<DenseMultilinearExtension<E::Fr>>,
167        Point = Vec<E::Fr>,
168        Evaluation = E::Fr,
169        Commitment = Commitment<E>,
170    >,
171{
172    let open_timer = start_timer!(|| "batch verification");
173
174    // TODO: sanity checks
175
176    let k = f_i_commitments.len();
177    let ell = log2(k) as usize;
178    let num_var = proof.sum_check_proof.point.len() - ell;
179
180    // challenge point t
181    let t = transcript.get_and_append_challenge_vectors("t".as_ref(), ell)?;
182
183    // sum check point (a1, a2)
184    let a1 = &proof.sum_check_proof.point[num_var..];
185    let a2 = &proof.sum_check_proof.point[..num_var];
186
187    // build g' commitment
188    let eq_a1_list = build_eq_x_r_vec(a1)?;
189    let eq_t_list = build_eq_x_r_vec(t.as_ref())?;
190
191    let mut g_prime_commit = E::G1Affine::zero().into_projective();
192    for i in 0..k {
193        let tmp = eq_a1_list[i] * eq_t_list[i];
194        g_prime_commit += &f_i_commitments[i].0.mul(tmp);
195    }
196
197    // ensure \sum_i eq(t, <i>) * f_i_evals matches the sum via SumCheck
198    // verification
199    let mut sum = E::Fr::zero();
200    for (i, &e) in eq_t_list.iter().enumerate().take(k) {
201        sum += e * proof.f_i_eval_at_point_i[i];
202    }
203    let aux_info = VPAuxInfo {
204        max_degree: 2,
205        num_variables: num_var + ell,
206        phantom: PhantomData,
207    };
208    let subclaim = match <PolyIOP<E::Fr> as SumCheck<E::Fr>>::verify(
209        sum,
210        &proof.sum_check_proof,
211        &aux_info,
212        transcript,
213    ) {
214        Ok(p) => p,
215        Err(_e) => {
216            // cannot wrap IOPError with PCSError due to cyclic dependency
217            return Err(PCSError::InvalidProver(
218                "Sumcheck in batch verification failed".to_string(),
219            ));
220        },
221    };
222    let mut eq_tilde_eval = E::Fr::zero();
223    for (point, &coef) in points.iter().zip(eq_a1_list.iter()) {
224        eq_tilde_eval += coef * eq_eval(a2, point)?;
225    }
226    let tilde_g_eval = subclaim.expected_evaluation / eq_tilde_eval;
227
228    // verify commitment
229    let res = PCS::verify(
230        verifier_param,
231        &Commitment(g_prime_commit.into_affine()),
232        a2.to_vec().as_ref(),
233        &tilde_g_eval,
234        &proof.g_prime_proof,
235    )?;
236
237    end_timer!(open_timer);
238    Ok(res)
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244    use crate::pcs::{
245        prelude::{MultilinearKzgPCS, MultilinearUniversalParams},
246        StructuredReferenceString,
247    };
248    use arithmetic::get_batched_nv;
249    use ark_bls12_381::Bls12_381 as E;
250    use ark_ec::PairingEngine;
251    use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
252    use ark_std::{
253        rand::{CryptoRng, RngCore},
254        test_rng,
255        vec::Vec,
256        UniformRand,
257    };
258
259    type Fr = <E as PairingEngine>::Fr;
260
261    fn test_multi_open_helper<R: RngCore + CryptoRng>(
262        ml_params: &MultilinearUniversalParams<E>,
263        polys: &[Rc<DenseMultilinearExtension<Fr>>],
264        rng: &mut R,
265    ) -> Result<(), PCSError> {
266        let merged_nv = get_batched_nv(polys[0].num_vars(), polys.len());
267        let (ml_ck, ml_vk) = ml_params.trim(merged_nv)?;
268
269        let mut points = Vec::new();
270        for poly in polys.iter() {
271            let point = (0..poly.num_vars())
272                .map(|_| Fr::rand(rng))
273                .collect::<Vec<Fr>>();
274            points.push(point);
275        }
276
277        let evals = polys
278            .iter()
279            .zip(points.iter())
280            .map(|(f, p)| f.evaluate(p).unwrap())
281            .collect::<Vec<_>>();
282
283        let commitments = polys
284            .iter()
285            .map(|poly| MultilinearKzgPCS::commit(&ml_ck.clone(), poly).unwrap())
286            .collect::<Vec<_>>();
287
288        let mut transcript = IOPTranscript::new("test transcript".as_ref());
289        transcript.append_field_element("init".as_ref(), &Fr::zero())?;
290
291        let batch_proof = multi_open_internal::<E, MultilinearKzgPCS<E>>(
292            &ml_ck,
293            polys,
294            &points,
295            &evals,
296            &mut transcript,
297        )?;
298
299        // good path
300        let mut transcript = IOPTranscript::new("test transcript".as_ref());
301        transcript.append_field_element("init".as_ref(), &Fr::zero())?;
302        assert!(batch_verify_internal::<E, MultilinearKzgPCS<E>>(
303            &ml_vk,
304            &commitments,
305            &points,
306            &batch_proof,
307            &mut transcript
308        )?);
309
310        Ok(())
311    }
312
313    #[test]
314    fn test_multi_open_internal() -> Result<(), PCSError> {
315        let mut rng = test_rng();
316
317        let ml_params = MultilinearUniversalParams::<E>::gen_srs_for_testing(&mut rng, 20)?;
318        for num_poly in 5..6 {
319            for nv in 15..16 {
320                let polys1: Vec<_> = (0..num_poly)
321                    .map(|_| Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)))
322                    .collect();
323                test_multi_open_helper(&ml_params, &polys1, &mut rng)?;
324            }
325        }
326
327        Ok(())
328    }
329}