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::{AffineCurve, PairingEngine, ProjectiveCurve};
 18use ark_std::{end_timer, log2, start_timer, One, Zero};
 19use std::{marker::PhantomData, rc::Rc};
 20use transcript::IOPTranscript;
 21
 22#[derive(Clone, Debug, Default, PartialEq, Eq)]
 23pub struct BatchProof<E, PCS>
 24where
 25    E: PairingEngine,
 26    PCS: PolynomialCommitmentScheme<E>,
 27{
 28    /// A sum check proof proving tilde g's sum
 29    pub(crate) sum_check_proof: IOPProof<E::Fr>,
 30    /// f_i(point_i)
 31    pub f_i_eval_at_point_i: Vec<E::Fr>,
 32    /// proof for g'(a_2)
 33    pub(crate) g_prime_proof: PCS::Proof,
 34}
 35
 36/// Steps:
 37/// 1. get challenge point t from transcript
 38/// 2. build eq(t,i) for i in [0..k]
 39/// 3. build \tilde g_i(b) = eq(t, i) * f_i(b)
 40/// 4. compute \tilde eq_i(b) = eq(b, point_i)
 41/// 5. run sumcheck on \sum_i=1..k \tilde eq_i * \tilde g_i
 42/// 6. build g'(X) = \sum_i=1..k \tilde eq_i(a2) * \tilde g_i(X) where (a2) is
 43/// the sumcheck's point 7. open g'(X) at point (a2)
 44pub(crate) fn multi_open_internal<E, PCS>(
 45    prover_param: &PCS::ProverParam,
 46    polynomials: &[PCS::Polynomial],
 47    points: &[PCS::Point],
 48    evals: &[PCS::Evaluation],
 49    transcript: &mut IOPTranscript<E::Fr>,
 50) -> Result<BatchProof<E, PCS>, PCSError>
 51where
 52    E: PairingEngine,
 53    PCS: PolynomialCommitmentScheme<
 54        E,
 55        Polynomial = Rc<DenseMultilinearExtension<E::Fr>>,
 56        Point = Vec<E::Fr>,
 57        Evaluation = E::Fr,
 58    >,
 59{
 60    let open_timer = start_timer!(|| format!("multi open {} points", points.len()));
 61
 62    // TODO: sanity checks
 63    let num_var = polynomials[0].num_vars;
 64    let k = polynomials.len();
 65    let ell = log2(k) as usize;
 66
 67    // challenge point t
 68    let t = transcript.get_and_append_challenge_vectors("t".as_ref(), ell)?;
 69
 70    // eq(t, i) for i in [0..k]
 71    let eq_t_i_list = build_eq_x_r_vec(t.as_ref())?;
 72
 73    // \tilde g_i(b) = eq(t, i) * f_i(b)
 74    let timer = start_timer!(|| format!("compute tilde g for {} points", points.len()));
 75    let mut tilde_gs = vec![];
 76    for (index, f_i) in polynomials.iter().enumerate() {
 77        let mut tilde_g_eval = vec![E::Fr::zero(); 1 << num_var];
 78        for (j, &f_i_eval) in f_i.iter().enumerate() {
 79            tilde_g_eval[j] = f_i_eval * eq_t_i_list[index];
 80        }
 81        tilde_gs.push(Rc::new(DenseMultilinearExtension::from_evaluations_vec(
 82            num_var,
 83            tilde_g_eval,
 84        )));
 85    }
 86    end_timer!(timer);
 87
 88    let timer = start_timer!(|| format!("compute tilde eq for {} points", points.len()));
 89    let mut tilde_eqs = vec![];
 90    for point in points.iter() {
 91        let eq_b_zi = build_eq_x_r_vec(point)?;
 92        tilde_eqs.push(Rc::new(DenseMultilinearExtension::from_evaluations_vec(
 93            num_var, eq_b_zi,
 94        )));
 95    }
 96    end_timer!(timer);
 97
 98    // built the virtual polynomial for SumCheck
 99    let timer = start_timer!(|| format!("sum check prove of {} variables", num_var));
100
101    let step = start_timer!(|| "add mle");
102    let mut sum_check_vp = VirtualPolynomial::new(num_var);
103    for (tilde_g, tilde_eq) in tilde_gs.iter().zip(tilde_eqs.into_iter()) {
104        sum_check_vp.add_mle_list([tilde_g.clone(), tilde_eq], E::Fr::one())?;
105    }
106    end_timer!(step);
107
108    let proof = match <PolyIOP<E::Fr> as SumCheck<E::Fr>>::prove(&sum_check_vp, transcript) {
109        Ok(p) => p,
110        Err(_e) => {
111            // cannot wrap IOPError with PCSError due to cyclic dependency
112            return Err(PCSError::InvalidProver(
113                "Sumcheck in batch proving Failed".to_string(),
114            ));
115        },
116    };
117
118    end_timer!(timer);
119
120    // a2 := sumcheck's point
121    let a2 = &proof.point[..num_var];
122
123    // build g'(X) = \sum_i=1..k \tilde eq_i(a2) * \tilde g_i(X) where (a2) is the
124    // sumcheck's point \tilde eq_i(a2) = eq(a2, point_i)
125    let step = start_timer!(|| "evaluate at a2");
126    let mut g_prime_evals = vec![E::Fr::zero(); 1 << num_var];
127    for (tilde_g, point) in tilde_gs.iter().zip(points.iter()) {
128        let eq_i_a2 = eq_eval(a2, point)?;
129        for (j, &tilde_g_eval) in tilde_g.iter().enumerate() {
130            g_prime_evals[j] += tilde_g_eval * eq_i_a2;
131        }
132    }
133    let g_prime = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
134        num_var,
135        g_prime_evals,
136    ));
137    end_timer!(step);
138
139    let step = start_timer!(|| "pcs open");
140    let (g_prime_proof, _g_prime_eval) = PCS::open(prover_param, &g_prime, a2.to_vec().as_ref())?;
141    // assert_eq!(g_prime_eval, tilde_g_eval);
142    end_timer!(step);
143
144    let step = start_timer!(|| "evaluate fi(pi)");
145    end_timer!(step);
146    end_timer!(open_timer);
147
148    Ok(BatchProof {
149        sum_check_proof: proof,
150        f_i_eval_at_point_i: evals.to_vec(),
151        g_prime_proof,
152    })
153}
154
155/// Steps:
156/// 1. get challenge point t from transcript
157/// 2. build g' commitment
158/// 3. ensure \sum_i eq(a2, point_i) * eq(t, <i>) * f_i_evals matches the sum
159/// via SumCheck verification 4. verify commitment
160pub(crate) fn batch_verify_internal<E, PCS>(
161    verifier_param: &PCS::VerifierParam,
162    f_i_commitments: &[Commitment<E>],
163    points: &[PCS::Point],
164    proof: &BatchProof<E, PCS>,
165    transcript: &mut IOPTranscript<E::Fr>,
166) -> Result<bool, PCSError>
167where
168    E: PairingEngine,
169    PCS: PolynomialCommitmentScheme<
170        E,
171        Polynomial = Rc<DenseMultilinearExtension<E::Fr>>,
172        Point = Vec<E::Fr>,
173        Evaluation = E::Fr,
174        Commitment = Commitment<E>,
175    >,
176{
177    let open_timer = start_timer!(|| "batch verification");
178
179    // TODO: sanity checks
180
181    let k = f_i_commitments.len();
182    let ell = log2(k) as usize;
183    let num_var = proof.sum_check_proof.point.len();
184
185    // challenge point t
186    let t = transcript.get_and_append_challenge_vectors("t".as_ref(), ell)?;
187
188    // sum check point (a2)
189    let a2 = &proof.sum_check_proof.point[..num_var];
190
191    // build g' commitment
192    let eq_t_list = build_eq_x_r_vec(t.as_ref())?;
193
194    let mut g_prime_commit = E::G1Affine::zero().into_projective();
195
196    for (i, point) in points.iter().enumerate() {
197        let eq_i_a2 = eq_eval(a2, point)?;
198        let tmp = eq_i_a2 * eq_t_list[i];
199        g_prime_commit += &f_i_commitments[i].0.mul(tmp);
200    }
201
202    // ensure \sum_i eq(t, <i>) * f_i_evals matches the sum via SumCheck
203    let mut sum = E::Fr::zero();
204    for (i, &e) in eq_t_list.iter().enumerate().take(k) {
205        sum += e * proof.f_i_eval_at_point_i[i];
206    }
207    let aux_info = VPAuxInfo {
208        max_degree: 2,
209        num_variables: num_var,
210        phantom: PhantomData,
211    };
212    let subclaim = match <PolyIOP<E::Fr> as SumCheck<E::Fr>>::verify(
213        sum,
214        &proof.sum_check_proof,
215        &aux_info,
216        transcript,
217    ) {
218        Ok(p) => p,
219        Err(_e) => {
220            // cannot wrap IOPError with PCSError due to cyclic dependency
221            return Err(PCSError::InvalidProver(
222                "Sumcheck in batch verification failed".to_string(),
223            ));
224        },
225    };
226    let tilde_g_eval = subclaim.expected_evaluation;
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}