Skip to content

Commit

Permalink
feat: Implement batch operations in non_hiding_kzg module (#269)
Browse files Browse the repository at this point in the history
* feat: Implement batch operations in non_hiding_kzg module

- Added a `batch_commit` method to generate multiple commitments from a list of polynomials.
- Introduced a `batch_open` functionality for evaluating multiple polynomials at different points.
- Implemented `batch_verify` function for validation of polynomial evaluations in a multi-commitment setup.
- Verified the correctness of the batch operations with a new unit test `batch_check_test`.

* fix: convert to zip_with syntax
  • Loading branch information
huitseeker authored Jan 23, 2024
1 parent d6f4636 commit db4375f
Showing 1 changed file with 140 additions and 0 deletions.
140 changes: 140 additions & 0 deletions src/provider/non_hiding_kzg.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//! Non-hiding variant of KZG10 scheme for univariate polynomials.
use crate::zip_with_for_each;
use abomonation_derive::Abomonation;
use ff::{Field, PrimeField, PrimeFieldBits};
use group::{prime::PrimeCurveAffine, Curve, Group as _};
use itertools::Itertools as _;
use pairing::{Engine, MillerLoopResult, MultiMillerLoop};
use rand_core::{CryptoRng, RngCore};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::{Deserialize, Serialize};
use std::{borrow::Borrow, marker::PhantomData, ops::Mul};

Expand Down Expand Up @@ -278,6 +281,20 @@ where
Ok(UVKZGCommitment(C.to_affine()))
}

/// Generate a commitment for a list of polynomials
#[allow(dead_code)]
pub fn batch_commit(
prover_param: impl Borrow<KZGProverKey<E>>,
polys: &[UVKZGPoly<E::Fr>],
) -> Result<Vec<UVKZGCommitment<E>>, NovaError> {
let prover_param = prover_param.borrow();

polys
.into_par_iter()
.map(|poly| Self::commit(prover_param, poly))
.collect::<Result<Vec<UVKZGCommitment<E>>, NovaError>>()
}

/// On input a polynomial `p` and a point `point`, outputs a proof for the
/// same.
pub fn open(
Expand Down Expand Up @@ -307,6 +324,31 @@ where
))
}

/// Input a list of polynomials, and a same number of points,
/// compute a multi-opening for all the polynomials.
// This is a naive approach
// TODO: to implement a more efficient batch opening algorithm
#[allow(dead_code)]
pub fn batch_open(
prover_param: impl Borrow<KZGProverKey<E>>,
polynomials: &[UVKZGPoly<E::Fr>],
points: &[E::Fr],
) -> Result<(Vec<UVKZGProof<E>>, Vec<UVKZGEvaluation<E>>), NovaError> {
if polynomials.len() != points.len() {
// TODO: a better Error
return Err(NovaError::PCSError(PCSError::LengthError));
}
let mut batch_proof = vec![];
let mut evals = vec![];
for (poly, point) in polynomials.iter().zip_eq(points.iter()) {
let (proof, eval) = Self::open(prover_param.borrow(), poly, point)?;
batch_proof.push(proof);
evals.push(eval);
}

Ok((batch_proof, evals))
}

/// Verifies that `value` is the evaluation at `x` of the polynomial
/// committed inside `comm`.
#[allow(dead_code, clippy::unnecessary_wraps)]
Expand Down Expand Up @@ -335,6 +377,61 @@ where
let pairing_result = E::multi_miller_loop(pairing_input_refs.as_slice()).final_exponentiation();
Ok(pairing_result.is_identity().into())
}

/// Verifies that `value_i` is the evaluation at `x_i` of the polynomial
/// `poly_i` committed inside `comm`.
// This is a naive approach
// TODO: to implement the more efficient batch verification algorithm
#[allow(dead_code, clippy::unnecessary_wraps)]
pub fn batch_verify<R: RngCore + CryptoRng>(
verifier_params: impl Borrow<KZGVerifierKey<E>>,
multi_commitment: &[UVKZGCommitment<E>],
points: &[E::Fr],
values: &[UVKZGEvaluation<E>],
batch_proof: &[UVKZGProof<E>],
rng: &mut R,
) -> Result<bool, NovaError> {
let verifier_params = verifier_params.borrow();

let mut total_c = <E::G1>::identity();
let mut total_w = <E::G1>::identity();

let mut randomizer = E::Fr::ONE;
// Instead of multiplying g and gamma_g in each turn, we simply accumulate
// their coefficients and perform a final multiplication at the end.
let mut g_multiplier = E::Fr::ZERO;
zip_with_for_each!(
into_iter,
(multi_commitment, points, values, batch_proof),
|c, z, v, proof| {
let w = proof.proof;
let mut temp = w.mul(*z);
temp += &c.0;
let c = temp;
g_multiplier += &(randomizer * v.0);
total_c += &c.mul(randomizer);
total_w += &w.mul(randomizer);
// We don't need to sample randomizers from the full field,
// only from 128-bit strings.
randomizer = E::Fr::from_u128(rand::Rng::gen::<u128>(rng));
}
);
total_c -= &verifier_params.g.mul(g_multiplier);

let mut affine_points = vec![E::G1Affine::identity(); 2];
E::G1::batch_normalize(&[-total_w, total_c], &mut affine_points);
let (total_w, total_c) = (affine_points[0], affine_points[1]);

let result = E::multi_miller_loop(&[
(&total_w, &verifier_params.beta_h.into()),
(&total_c, &verifier_params.h.into()),
])
.final_exponentiation()
.is_identity()
.into();

Ok(result)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -379,4 +476,47 @@ mod tests {
fn end_to_end_test() {
end_to_end_test_template::<halo2curves::bn256::Bn256>().expect("test failed for Bn256");
}

fn batch_check_test_template<E>() -> Result<(), NovaError>
where
E: MultiMillerLoop,
E::Fr: PrimeFieldBits,
E::G1: DlogGroup<ScalarExt = E::Fr, AffineExt = E::G1Affine>,
{
for _ in 0..10 {
let mut rng = &mut thread_rng();

let degree = rng.gen_range(2..20);

let pp = UniversalKZGParam::<E>::gen_srs_for_testing(&mut rng, degree);
let (ck, vk) = pp.trim(degree);

let mut comms = Vec::new();
let mut values = Vec::new();
let mut points = Vec::new();
let mut proofs = Vec::new();
for _ in 0..10 {
let mut rng = rng.clone();
let p = random(degree, &mut rng);
let comm = UVKZGPCS::<E>::commit(&ck, &p)?;
let point = E::Fr::random(rng);
let (proof, value) = UVKZGPCS::<E>::open(&ck, &p, &point)?;

assert!(UVKZGPCS::<E>::verify(&vk, &comm, &point, &proof, &value)?);
comms.push(comm);
values.push(value);
points.push(point);
proofs.push(proof);
}
assert!(UVKZGPCS::<E>::batch_verify(
&vk, &comms, &points, &values, &proofs, &mut rng
)?);
}
Ok(())
}

#[test]
fn batch_check_test() {
batch_check_test_template::<halo2curves::bn256::Bn256>().expect("test failed for Bn256");
}
}

1 comment on commit db4375f

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarks

Table of Contents

Overview

This benchmark report shows the Arecibo GPU benchmarks.
NVIDIA L4
Intel(R) Xeon(R) CPU @ 2.20GHz
32 vCPUs
125 GB RAM
Workflow run: https://github.com/lurk-lab/arecibo/actions/runs/7628113135

Benchmark Results

RecursiveSNARK-NIVC-2

ref=d6f4636 ref=db4375f
Prove-NumCons-6540 53.08 ms (✅ 1.00x) 52.89 ms (✅ 1.00x faster)
Verify-NumCons-6540 32.88 ms (✅ 1.00x) 32.67 ms (✅ 1.01x faster)
Prove-NumCons-1028888 344.08 ms (✅ 1.00x) 345.11 ms (✅ 1.00x slower)
Verify-NumCons-1028888 255.03 ms (✅ 1.00x) 259.37 ms (✅ 1.02x slower)

CompressedSNARK-NIVC-Commitments-2

ref=d6f4636 ref=db4375f
Prove-NumCons-6540 14.12 s (✅ 1.00x) 14.07 s (✅ 1.00x faster)
Verify-NumCons-6540 78.49 ms (✅ 1.00x) 79.59 ms (✅ 1.01x slower)
Prove-NumCons-1028888 112.11 s (✅ 1.00x) 111.98 s (✅ 1.00x faster)
Verify-NumCons-1028888 772.24 ms (✅ 1.00x) 780.08 ms (✅ 1.01x slower)

Made with criterion-table

Please sign in to comment.