//! Sumcheck based batch opening and verify commitment. // TODO: refactoring this code to somewhere else // currently IOP depends on PCS because perm check requires commitment. // The sumcheck based batch opening therefore cannot stay in the PCS repo -- // which creates a cyclic dependency. use crate::{ pcs::{ multilinear_kzg::util::eq_eval, prelude::{Commitment, PCSError}, PolynomialCommitmentScheme, }, poly_iop::{prelude::SumCheck, PolyIOP}, IOPProof, }; use arithmetic::{build_eq_x_r_vec, DenseMultilinearExtension, VPAuxInfo, VirtualPolynomial}; use ark_ec::{msm::VariableBaseMSM, PairingEngine, ProjectiveCurve}; use ark_ff::PrimeField; use ark_std::{end_timer, log2, start_timer, One, Zero}; use std::{marker::PhantomData, sync::Arc}; use transcript::IOPTranscript; #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct BatchProof where E: PairingEngine, PCS: PolynomialCommitmentScheme, { /// A sum check proof proving tilde g's sum pub(crate) sum_check_proof: IOPProof, /// f_i(point_i) pub f_i_eval_at_point_i: Vec, /// proof for g'(a_2) pub(crate) g_prime_proof: PCS::Proof, } /// Steps: /// 1. get challenge point t from transcript /// 2. build eq(t,i) for i in [0..k] /// 3. build \tilde g_i(b) = eq(t, i) * f_i(b) /// 4. compute \tilde eq_i(b) = eq(b, point_i) /// 5. run sumcheck on \sum_i=1..k \tilde eq_i * \tilde g_i /// 6. build g'(X) = \sum_i=1..k \tilde eq_i(a2) * \tilde g_i(X) where (a2) is /// the sumcheck's point 7. open g'(X) at point (a2) pub(crate) fn multi_open_internal( prover_param: &PCS::ProverParam, polynomials: &[PCS::Polynomial], points: &[PCS::Point], evals: &[PCS::Evaluation], transcript: &mut IOPTranscript, ) -> Result, PCSError> where E: PairingEngine, PCS: PolynomialCommitmentScheme< E, Polynomial = Arc>, Point = Vec, Evaluation = E::Fr, >, { let open_timer = start_timer!(|| format!("multi open {} points", points.len())); // TODO: sanity checks let num_var = polynomials[0].num_vars; let k = polynomials.len(); let ell = log2(k) as usize; // challenge point t let t = transcript.get_and_append_challenge_vectors("t".as_ref(), ell)?; // eq(t, i) for i in [0..k] let eq_t_i_list = build_eq_x_r_vec(t.as_ref())?; // \tilde g_i(b) = eq(t, i) * f_i(b) let timer = start_timer!(|| format!("compute tilde g for {} points", points.len())); let mut tilde_gs = vec![]; for (index, f_i) in polynomials.iter().enumerate() { let mut tilde_g_eval = vec![E::Fr::zero(); 1 << num_var]; for (j, &f_i_eval) in f_i.iter().enumerate() { tilde_g_eval[j] = f_i_eval * eq_t_i_list[index]; } tilde_gs.push(Arc::new(DenseMultilinearExtension::from_evaluations_vec( num_var, tilde_g_eval, ))); } end_timer!(timer); let timer = start_timer!(|| format!("compute tilde eq for {} points", points.len())); let mut tilde_eqs = vec![]; for point in points.iter() { let eq_b_zi = build_eq_x_r_vec(point)?; tilde_eqs.push(Arc::new(DenseMultilinearExtension::from_evaluations_vec( num_var, eq_b_zi, ))); } end_timer!(timer); // built the virtual polynomial for SumCheck let timer = start_timer!(|| format!("sum check prove of {} variables", num_var)); let step = start_timer!(|| "add mle"); let mut sum_check_vp = VirtualPolynomial::new(num_var); for (tilde_g, tilde_eq) in tilde_gs.iter().zip(tilde_eqs.into_iter()) { sum_check_vp.add_mle_list([tilde_g.clone(), tilde_eq], E::Fr::one())?; } end_timer!(step); let proof = match as SumCheck>::prove(&sum_check_vp, transcript) { Ok(p) => p, Err(_e) => { // cannot wrap IOPError with PCSError due to cyclic dependency return Err(PCSError::InvalidProver( "Sumcheck in batch proving Failed".to_string(), )); }, }; end_timer!(timer); // a2 := sumcheck's point let a2 = &proof.point[..num_var]; // build g'(X) = \sum_i=1..k \tilde eq_i(a2) * \tilde g_i(X) where (a2) is the // sumcheck's point \tilde eq_i(a2) = eq(a2, point_i) let step = start_timer!(|| "evaluate at a2"); let mut g_prime_evals = vec![E::Fr::zero(); 1 << num_var]; for (tilde_g, point) in tilde_gs.iter().zip(points.iter()) { let eq_i_a2 = eq_eval(a2, point)?; for (j, &tilde_g_eval) in tilde_g.iter().enumerate() { g_prime_evals[j] += tilde_g_eval * eq_i_a2; } } let g_prime = Arc::new(DenseMultilinearExtension::from_evaluations_vec( num_var, g_prime_evals, )); end_timer!(step); let step = start_timer!(|| "pcs open"); let (g_prime_proof, _g_prime_eval) = PCS::open(prover_param, &g_prime, a2.to_vec().as_ref())?; // assert_eq!(g_prime_eval, tilde_g_eval); end_timer!(step); let step = start_timer!(|| "evaluate fi(pi)"); end_timer!(step); end_timer!(open_timer); Ok(BatchProof { sum_check_proof: proof, f_i_eval_at_point_i: evals.to_vec(), g_prime_proof, }) } /// Steps: /// 1. get challenge point t from transcript /// 2. build g' commitment /// 3. ensure \sum_i eq(a2, point_i) * eq(t, ) * f_i_evals matches the sum /// via SumCheck verification 4. verify commitment pub(crate) fn batch_verify_internal( verifier_param: &PCS::VerifierParam, f_i_commitments: &[Commitment], points: &[PCS::Point], proof: &BatchProof, transcript: &mut IOPTranscript, ) -> Result where E: PairingEngine, PCS: PolynomialCommitmentScheme< E, Polynomial = Arc>, Point = Vec, Evaluation = E::Fr, Commitment = Commitment, >, { let open_timer = start_timer!(|| "batch verification"); // TODO: sanity checks let k = f_i_commitments.len(); let ell = log2(k) as usize; let num_var = proof.sum_check_proof.point.len(); // challenge point t let t = transcript.get_and_append_challenge_vectors("t".as_ref(), ell)?; // sum check point (a2) let a2 = &proof.sum_check_proof.point[..num_var]; // build g' commitment let step = start_timer!(|| "build homomorphic commitment"); let eq_t_list = build_eq_x_r_vec(t.as_ref())?; let mut scalars = vec![]; let mut bases = vec![]; for (i, point) in points.iter().enumerate() { let eq_i_a2 = eq_eval(a2, point)?; scalars.push((eq_i_a2 * eq_t_list[i]).into_repr()); bases.push(f_i_commitments[i].0); } let g_prime_commit = VariableBaseMSM::multi_scalar_mul(&bases, &scalars); end_timer!(step); // ensure \sum_i eq(t, ) * f_i_evals matches the sum via SumCheck let mut sum = E::Fr::zero(); for (i, &e) in eq_t_list.iter().enumerate().take(k) { sum += e * proof.f_i_eval_at_point_i[i]; } let aux_info = VPAuxInfo { max_degree: 2, num_variables: num_var, phantom: PhantomData, }; let subclaim = match as SumCheck>::verify( sum, &proof.sum_check_proof, &aux_info, transcript, ) { Ok(p) => p, Err(_e) => { // cannot wrap IOPError with PCSError due to cyclic dependency return Err(PCSError::InvalidProver( "Sumcheck in batch verification failed".to_string(), )); }, }; let tilde_g_eval = subclaim.expected_evaluation; // verify commitment let res = PCS::verify( verifier_param, &Commitment(g_prime_commit.into_affine()), a2.to_vec().as_ref(), &tilde_g_eval, &proof.g_prime_proof, )?; end_timer!(open_timer); Ok(res) } #[cfg(test)] mod tests { use super::*; use crate::pcs::{ prelude::{MultilinearKzgPCS, MultilinearUniversalParams}, StructuredReferenceString, }; use arithmetic::get_batched_nv; use ark_bls12_381::Bls12_381 as E; use ark_ec::PairingEngine; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; use ark_std::{ rand::{CryptoRng, RngCore}, test_rng, vec::Vec, UniformRand, }; type Fr = ::Fr; fn test_multi_open_helper( ml_params: &MultilinearUniversalParams, polys: &[Arc>], rng: &mut R, ) -> Result<(), PCSError> { let merged_nv = get_batched_nv(polys[0].num_vars(), polys.len()); let (ml_ck, ml_vk) = ml_params.trim(merged_nv)?; let mut points = Vec::new(); for poly in polys.iter() { let point = (0..poly.num_vars()) .map(|_| Fr::rand(rng)) .collect::>(); points.push(point); } let evals = polys .iter() .zip(points.iter()) .map(|(f, p)| f.evaluate(p).unwrap()) .collect::>(); let commitments = polys .iter() .map(|poly| MultilinearKzgPCS::commit(&ml_ck.clone(), poly).unwrap()) .collect::>(); let mut transcript = IOPTranscript::new("test transcript".as_ref()); transcript.append_field_element("init".as_ref(), &Fr::zero())?; let batch_proof = multi_open_internal::>( &ml_ck, polys, &points, &evals, &mut transcript, )?; // good path let mut transcript = IOPTranscript::new("test transcript".as_ref()); transcript.append_field_element("init".as_ref(), &Fr::zero())?; assert!(batch_verify_internal::>( &ml_vk, &commitments, &points, &batch_proof, &mut transcript )?); Ok(()) } #[test] fn test_multi_open_internal() -> Result<(), PCSError> { let mut rng = test_rng(); let ml_params = MultilinearUniversalParams::::gen_srs_for_testing(&mut rng, 20)?; for num_poly in 5..6 { for nv in 15..16 { let polys1: Vec<_> = (0..num_poly) .map(|_| Arc::new(DenseMultilinearExtension::rand(nv, &mut rng))) .collect(); test_multi_open_helper(&ml_params, &polys1, &mut rng)?; } } Ok(()) } }