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