Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion w3f-ring-proof/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "w3f-ring-proof"
version = "0.0.6"
version = "0.0.7"
edition = "2021"
authors = ["Sergey Vasilyev <swasilyev@gmail.com>"]
license = "MIT/Apache-2.0"
Expand Down
13 changes: 5 additions & 8 deletions w3f-ring-proof/benches/ring_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,22 +195,19 @@ fn bench_verify_batch_kzg(c: &mut Criterion) {
.map(|_| generate_proof(&piop_params, &pcs_params, &pks, rng))
.collect();

let (_, verifier_key) = index::<_, CS, _>(&pcs_params, &piop_params, &pks);
let verifier = RingVerifier::init(verifier_key, piop_params, make_transcript());

for batch_size in [1, 4, 16, 32] {
let (results, proofs): (Vec<_>, Vec<_>) = claims[..batch_size].iter().cloned().unzip();

group.bench_with_input(
BenchmarkId::new("kzg_accumulator", batch_size),
&batch_size,
|b, _| {
// Recreate verifier each iteration since verify_batch_kzg consumes self.
b.iter_batched(
|| {
let (_, vk) = index::<_, CS, _>(&pcs_params, &piop_params, &pks);
let verifier =
RingVerifier::init(vk, piop_params.clone(), make_transcript());
(verifier, proofs.clone(), results.clone())
},
|(verifier, proofs, results)| verifier.verify_batch_kzg(proofs, results),
|| (proofs.clone(), results.clone()),
|(proofs, results)| verifier.verify_batch_kzg(proofs, results),
BatchSize::LargeInput,
);
},
Expand Down
7 changes: 5 additions & 2 deletions w3f-ring-proof/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,11 @@ mod tests {
}

// Multi-ring batch verification
use crate::multi_ring_batch_verifier::MultiRingBatchVerifier;
let mut batch = MultiRingBatchVerifier::new(verifier_a.pcs_vk().clone());
use crate::multi_ring_batch_verifier::BatchVerifier;
let mut batch = BatchVerifier::new(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So technically, BatchVerifier is RingVerifier that forgot his piop (almost).

verifier_a.pcs_vk().clone(),
verifier_a.plonk_verifier.transcript_prelude.clone(),
);
for (result, proof) in claims_a {
batch.push(&verifier_a, proof, result);
}
Expand Down
115 changes: 62 additions & 53 deletions w3f-ring-proof/src/multi_ring_batch_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,53 +15,33 @@ use crate::ring_verifier::RingVerifier;
use crate::RingProof;

/// A ring proof preprocessed for multi-ring batch verification.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the comment is nonsense

///
/// Holds a reference to the `RingVerifier` that was used during preparation,
/// so that `push_prepared` can access the correct ring's transcript prelude.
pub struct PreparedMultiRingItem<'a, E, J, T>
pub struct BatchItem<E, J>
where
E: Pairing,
J: TECurveConfig<BaseField = E::ScalarField>,
T: PlonkTranscript<E::ScalarField, KZG<E>>,
{
verifier: &'a RingVerifier<E::ScalarField, KZG<E>, J, T>,
piop: PiopVerifier<E::ScalarField, <KZG<E> as PCS<E::ScalarField>>::C, Affine<J>>,
proof: RingProof<E::ScalarField, KZG<E>>,
challenges: Challenges<E::ScalarField>,
entropy: [u8; 32],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so this thing is an "almost evaluated verification clam". the challenges i guess don't depend on the batch, prover otherwise couldn't. what is the entropy then? Can we may be just hash twice to get the batch randomness so that we have simple security?

}

/// Accumulating batch verifier for ring proofs across multiple rings.
///
/// Unlike `KzgBatchVerifier` which is tied to a single ring,
/// this verifier can accumulate proofs from different rings (keysets)
/// into a single batched pairing check.
///
/// All rings must share the same KZG SRS (same `KzgVerifierKey`).
pub struct MultiRingBatchVerifier<E: Pairing> {
acc: KzgAccumulator<E>,
}

impl<E: Pairing> MultiRingBatchVerifier<E> {
/// Creates a new multi-ring batch verifier.
pub fn new(kzg_vk: KzgVerifierKey<E>) -> Self {
Self {
acc: KzgAccumulator::<E>::new(kzg_vk),
}
}

impl<E, J> BatchItem<E, J>
where
E: Pairing,
J: TECurveConfig<BaseField = E::ScalarField>,
{
/// Prepares a ring proof for batch verification without accumulating it.
///
/// The returned item holds a reference to the `verifier` and is independent
/// of the accumulator state, so multiple proofs (even from different rings)
/// can be prepared in parallel.
pub fn prepare<'a, J, T>(
verifier: &'a RingVerifier<E::ScalarField, KZG<E>, J, T>,
/// The returned item is independent of both any accumulator state and
/// the originating `RingVerifier`, so multiple proofs (even from
/// different rings) can be prepared in parallel.
pub fn new<T>(
verifier: &RingVerifier<E::ScalarField, KZG<E>, J, T>,
proof: RingProof<E::ScalarField, KZG<E>>,
result: Affine<J>,
) -> PreparedMultiRingItem<'a, E, J, T>
) -> Self
where
J: TECurveConfig<BaseField = E::ScalarField>,
T: PlonkTranscript<E::ScalarField, KZG<E>>,
{
let (challenges, mut rng) = verifier.plonk_verifier.restore_challenges(
Expand All @@ -85,43 +65,72 @@ impl<E: Pairing> MultiRingBatchVerifier<E> {
let mut entropy = [0_u8; 32];
rng.fill_bytes(&mut entropy);

PreparedMultiRingItem {
verifier,
Self {
piop,
proof,
challenges,
entropy,
}
}
}

/// Accumulates a previously prepared proof into the batch.
///
/// This is the second step of the two-phase batch verification workflow:
/// 1. `prepare` - can be parallelized across multiple proofs
/// 2. `push_prepared` - must be called sequentially (mutates the accumulator)
pub fn push_prepared<J, T>(&mut self, item: PreparedMultiRingItem<'_, E, J, T>)
where
J: TECurveConfig<BaseField = E::ScalarField>,
T: PlonkTranscript<E::ScalarField, KZG<E>>,
{
let mut ts = item.verifier.plonk_verifier.transcript_prelude.clone();
ts._add_serializable(b"batch-entropy", &item.entropy);
self.acc
.accumulate(item.piop, item.proof, item.challenges, &mut ts.to_rng());
/// Accumulating batch verifier for ring proofs across one or more rings.
///
/// Accumulates proofs from one or more rings (keysets) into a single batched
/// pairing check. All rings must share the same KZG SRS.
///
/// Holds its own transcript instance, cloned on each `push_prepared` call so
/// the per-proof entropy can be folded in without touching the originating
/// `RingVerifier`. The transcript's initial state is not load-bearing; any
/// valid `T` works (e.g. the prelude of any ring verifier being batched).
pub struct BatchVerifier<E: Pairing, T>
where
T: PlonkTranscript<E::ScalarField, KZG<E>>,
{
acc: KzgAccumulator<E>,
transcript: T,
}

impl<E: Pairing, T> BatchVerifier<E, T>
where
T: PlonkTranscript<E::ScalarField, KZG<E>>,
{
/// Creates a new multi-ring batch verifier.
pub fn new(kzg_vk: KzgVerifierKey<E>, transcript: T) -> Self {
Self {
acc: KzgAccumulator::<E>::new(kzg_vk),
transcript,
}
}

/// Adds a ring proof to the batch, preparing and accumulating it immediately.
pub fn push<J, T>(
/// Adds a ring proof to the batch.
pub fn push<J>(
&mut self,
verifier: &RingVerifier<E::ScalarField, KZG<E>, J, T>,
proof: RingProof<E::ScalarField, KZG<E>>,
result: Affine<J>,
) where
J: TECurveConfig<BaseField = E::ScalarField>,
T: PlonkTranscript<E::ScalarField, KZG<E>>,
{
let item = Self::prepare(verifier, proof, result);
self.push_prepared(item);
self.push_prepared(BatchItem::new(verifier, proof, result));
}

/// Accumulates a prepared [`BatchItem`] into the batch.
///
/// Equivalent to [`push`](Self::push), but splits the work: the caller
/// builds the [`BatchItem`] (transcript replay, challenge derivation,
/// PIOP setup) separately from accumulation. Useful when preparation
/// should be parallelized. `BatchItem::new` is independent of the
/// accumulator state, so multiple items can be built in parallel and
/// then pushed sequentially here.
pub fn push_prepared<J>(&mut self, item: BatchItem<E, J>)
where
J: TECurveConfig<BaseField = E::ScalarField>,
{
let mut ts = self.transcript.clone();
ts._add_serializable(b"batch-entropy", &item.entropy);
self.acc
.accumulate(item.piop, item.proof, item.challenges, &mut ts.to_rng());
Comment on lines +130 to +133
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I mainly want your attention here: we extend a clone of a shared vanilla transcript (stored in the main BatchVerifier object) with per-proof entropy before deriving the corresponding RNG.

Since entropy is obtained by squeezing the per-proof verifier transcript, it should be sufficient to use it directly rather than requiring access to the per-proof verifier transcript itself

}

/// Verifies all accumulated proofs in a single batched pairing check.
Expand Down
127 changes: 10 additions & 117 deletions w3f-ring-proof/src/ring_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ use ark_ec::pairing::Pairing;
use ark_ec::twisted_edwards::{Affine, TECurveConfig};
use ark_ec::CurveGroup;
use ark_ff::PrimeField;
use ark_std::rand::RngCore;
use w3f_pcs::pcs::kzg::KZG;
use w3f_pcs::pcs::{RawVerifierKey, PCS};
use w3f_plonk_common::kzg_acc::KzgAccumulator;
use w3f_plonk_common::piop::VerifierPiop;
use w3f_plonk_common::transcript::PlonkTranscript;
use w3f_plonk_common::verifier::{Challenges, PlonkVerifier};
use w3f_plonk_common::verifier::PlonkVerifier;

use crate::multi_ring_batch_verifier::BatchVerifier;
use crate::piop::params::PiopParams;
use crate::piop::{FixedColumnsCommitted, PiopVerifier, VerifierKey};
use crate::{ArkTranscript, RingProof};
Expand Down Expand Up @@ -95,131 +94,25 @@ where
}
}

/// Accumulating batch verifier for ring proofs using KZG polynomial commitment scheme.
pub struct KzgBatchVerifier<E, J, T = ArkTranscript>
where
E: Pairing,
J: TECurveConfig<BaseField = E::ScalarField>,
T: PlonkTranscript<E::ScalarField, KZG<E>>,
{
pub acc: KzgAccumulator<E>,
pub verifier: RingVerifier<E::ScalarField, KZG<E>, J, T>,
}

/// A ring proof that has been preprocessed for batch verification.
pub struct PreparedBatchItem<E, J>
where
E: Pairing,
J: TECurveConfig<BaseField = E::ScalarField>,
{
piop: PiopVerifier<E::ScalarField, <KZG<E> as PCS<E::ScalarField>>::C, Affine<J>>,
proof: RingProof<E::ScalarField, KZG<E>>,
challenges: Challenges<E::ScalarField>,
entropy: [u8; 32],
}

impl<E, J, T> KzgBatchVerifier<E, J, T>
where
E: Pairing,
J: TECurveConfig<BaseField = E::ScalarField>,
T: PlonkTranscript<E::ScalarField, KZG<E>>,
{
/// Prepares a ring proof for batch verification without accumulating it.
///
/// Returns a `PreparedBatchItem` that can later be passed to `push_prepared`.
///
/// This method is independent of the accumulator state, so multiple proofs can be
/// prepared in parallel (e.g., using `rayon`). Each prepared item is in the order
/// of a few KB, so for large batches you may want to prepare and push incrementally
/// rather than holding all prepared items in memory at once.
pub fn prepare(
&self,
proof: RingProof<E::ScalarField, KZG<E>>,
result: Affine<J>,
) -> PreparedBatchItem<E, J> {
let (challenges, mut rng) = self.verifier.plonk_verifier.restore_challenges(
&result,
&proof,
// '1' accounts for the quotient polynomial that is aggregated together with the columns
PiopVerifier::<E::ScalarField, <KZG<E> as PCS<_>>::C, Affine<J>>::N_COLUMNS + 1,
PiopVerifier::<E::ScalarField, <KZG<E> as PCS<_>>::C, Affine<J>>::N_CONSTRAINTS,
);
let seed = self.verifier.piop_params.seed;
let seed_plus_result = (seed + result).into_affine();
let domain_at_zeta = self.verifier.piop_params.domain.evaluate(challenges.zeta);
let piop = PiopVerifier::<_, _, Affine<J>>::init(
domain_at_zeta,
self.verifier.fixed_columns_committed.clone(),
proof.column_commitments.clone(),
proof.columns_at_zeta.clone(),
(seed.x, seed.y),
(seed_plus_result.x, seed_plus_result.y),
);

// Pick some entropy from plonk verifier for later usage
let mut entropy = [0_u8; 32];
rng.fill_bytes(&mut entropy);

PreparedBatchItem {
piop,
proof,
challenges,
entropy,
}
}

/// Accumulates a previously prepared proof into the batch.
///
/// This is the second step of the two-phase batch verification workflow:
/// 1. `prepare` - can be parallelized across multiple proofs
/// 2. `push_prepared` - must be called sequentially (mutates the accumulator)
///
/// For simpler usage where parallelism isn't needed, use `push` instead.
pub fn push_prepared(&mut self, item: PreparedBatchItem<E, J>) {
let mut ts = self.verifier.plonk_verifier.transcript_prelude.clone();
ts._add_serializable(b"batch-entropy", &item.entropy);
self.acc
.accumulate(item.piop, item.proof, item.challenges, &mut ts.to_rng());
}

/// Adds a ring proof to the batch, preparing and accumulating it immediately.
///
/// The proof's pairing equation is aggregated into the internal accumulator.
/// Call `verify` after pushing all proofs to perform the batched verification.
pub fn push(&mut self, proof: RingProof<E::ScalarField, KZG<E>>, result: Affine<J>) {
let item = self.prepare(proof, result);
self.push_prepared(item);
}

/// Verifies all accumulated proofs in a single batched pairing check.
pub fn verify(&self) -> bool {
self.acc.verify()
}
}

impl<E, J, T> RingVerifier<E::ScalarField, KZG<E>, J, T>
where
E: Pairing,
J: TECurveConfig<BaseField = E::ScalarField>,
T: PlonkTranscript<E::ScalarField, KZG<E>>,
{
/// Build a new batch verifier.
pub fn kzg_batch_verifier(self) -> KzgBatchVerifier<E, J, T> {
KzgBatchVerifier {
acc: KzgAccumulator::<E>::new(self.plonk_verifier.pcs_vk.clone()),
verifier: self,
}
}

/// Verifies a batch of proofs against the same ring.
/// Verifies a batch of proofs against this ring in a single batched
/// pairing check, using a `MultiRingBatchVerifier` under the hood.
pub fn verify_batch_kzg(
self,
&self,
proofs: Vec<RingProof<E::ScalarField, KZG<E>>>,
results: Vec<Affine<J>>,
) -> bool {
let mut batch = self.kzg_batch_verifier();
let mut batch = BatchVerifier::new(
self.plonk_verifier.pcs_vk.clone(),
self.plonk_verifier.transcript_prelude.clone(),
);
for (proof, result) in proofs.into_iter().zip(results) {
batch.push(proof, result);
batch.push(self, proof, result);
}
batch.verify()
}
Expand Down
Loading