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
76 changes: 76 additions & 0 deletions w3f-ring-proof/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use w3f_plonk_common::Proof;
pub use crate::piop::{params::PiopParams, FixedColumnsCommitted, ProverKey, VerifierKey};
use crate::piop::{RingCommitments, RingEvaluations};

pub mod multi_ring_batch_verifier;
mod piop;
pub mod ring;
pub mod ring_prover;
Expand Down Expand Up @@ -185,4 +186,79 @@ mod tests {
fn test_ring_proof_id() {
_test_ring_proof::<w3f_pcs::pcs::IdentityCommitment>(2usize.pow(10), 1);
}

#[test]
fn test_multi_ring_batch_verify_kzg() {
let rng = &mut test_rng();
let domain_size = 2usize.pow(9);
let proofs_per_ring = 4;

let (pcs_params, piop_params) = setup::<_, KZG<Bls12_381>>(rng, domain_size);

// Ring A
let keyset_size_a = piop_params.keyset_part_size;
let pks_a = random_vec::<EdwardsAffine, _>(keyset_size_a, rng);
let (prover_key_a, verifier_key_a) =
index::<_, KZG<Bls12_381>, _>(&pcs_params, &piop_params, &pks_a);

// Ring B (smaller keyset)
let keyset_size_b = piop_params.keyset_part_size / 2;
let pks_b = random_vec::<EdwardsAffine, _>(keyset_size_b, rng);
let (prover_key_b, verifier_key_b) =
index::<_, KZG<Bls12_381>, _>(&pcs_params, &piop_params, &pks_b);

let mut generate_claims = |prover_key: &ProverKey<Fq, KZG<Bls12_381>, EdwardsAffine>,
pks: &[EdwardsAffine],
keyset_size: usize| {
(0..proofs_per_ring)
.map(|_| {
let prover_idx = rng.gen_range(0..keyset_size);
let prover = RingProver::init(
prover_key.clone(),
piop_params.clone(),
prover_idx,
ArkTranscript::new(b"w3f-ring-proof-test"),
);
let blinding_factor = Fr::rand(rng);
let blinded_pk =
(pks[prover_idx] + piop_params.h.mul(blinding_factor)).into_affine();
let proof = prover.prove(blinding_factor);
(blinded_pk, proof)
})
.collect::<Vec<_>>()
};

let claims_a = generate_claims(&prover_key_a, &pks_a, keyset_size_a);
let claims_b = generate_claims(&prover_key_b, &pks_b, keyset_size_b);

let verifier_a = RingVerifier::init(
verifier_key_a,
piop_params.clone(),
ArkTranscript::new(b"w3f-ring-proof-test"),
);
let verifier_b = RingVerifier::init(
verifier_key_b,
piop_params,
ArkTranscript::new(b"w3f-ring-proof-test"),
);

// Sanity: individual verification
for (result, proof) in &claims_a {
assert!(verifier_a.verify(proof.clone(), *result));
}
for (result, proof) in &claims_b {
assert!(verifier_b.verify(proof.clone(), *result));
}

// Multi-ring batch verification
use crate::multi_ring_batch_verifier::MultiRingBatchVerifier;
let mut batch = MultiRingBatchVerifier::new(verifier_a.pcs_vk().clone());
for (result, proof) in claims_a {
batch.push(&verifier_a, proof, result);
}
for (result, proof) in claims_b {
batch.push(&verifier_b, proof, result);
}
assert!(batch.verify());
}
}
131 changes: 131 additions & 0 deletions w3f-ring-proof/src/multi_ring_batch_verifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use ark_ec::pairing::Pairing;
use ark_ec::twisted_edwards::{Affine, TECurveConfig};
use ark_ec::CurveGroup;
use ark_std::rand::RngCore;
use w3f_pcs::pcs::kzg::params::KzgVerifierKey;
use w3f_pcs::pcs::kzg::KZG;
use w3f_pcs::pcs::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;

use crate::piop::PiopVerifier;
use crate::ring_verifier::RingVerifier;
use crate::RingProof;

/// A ring proof preprocessed for multi-ring batch verification.
///
/// 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>
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],
}

/// 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),
}
}

/// 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>,
proof: RingProof<E::ScalarField, KZG<E>>,
result: Affine<J>,
) -> PreparedMultiRingItem<'a, E, J, T>
where
J: TECurveConfig<BaseField = E::ScalarField>,
T: PlonkTranscript<E::ScalarField, KZG<E>>,
{
let (challenges, mut rng) = verifier.plonk_verifier.restore_challenges(
&result,
&proof,
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 = verifier.piop_params.seed;
let seed_plus_result = (seed + result).into_affine();
let domain_at_zeta = verifier.piop_params.domain.evaluate(challenges.zeta);
let piop = PiopVerifier::<_, _, Affine<J>>::init(
domain_at_zeta,
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),
);

let mut entropy = [0_u8; 32];
rng.fill_bytes(&mut entropy);

PreparedMultiRingItem {
verifier,
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());
}

/// Adds a ring proof to the batch, preparing and accumulating it immediately.
pub fn push<J, T>(
&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);
}

/// Verifies all accumulated proofs in a single batched pairing check.
pub fn verify(&self) -> bool {
self.acc.verify()
}
}
10 changes: 7 additions & 3 deletions w3f-ring-proof/src/ring_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ where
Jubjub: TECurveConfig<BaseField = F>,
T: PlonkTranscript<F, CS>,
{
piop_params: PiopParams<F, Jubjub>,
fixed_columns_committed: FixedColumnsCommitted<F, CS::C>,
plonk_verifier: PlonkVerifier<F, CS, T>,
pub(crate) piop_params: PiopParams<F, Jubjub>,
pub(crate) fixed_columns_committed: FixedColumnsCommitted<F, CS::C>,
pub(crate) plonk_verifier: PlonkVerifier<F, CS, T>,
}

impl<F, CS, Jubjub, T> RingVerifier<F, CS, Jubjub, T>
Expand Down Expand Up @@ -76,6 +76,10 @@ where
&self.piop_params
}

pub fn pcs_vk(&self) -> &CS::VK {
&self.plonk_verifier.pcs_vk
}

pub fn verify_batch(
&self,
proofs: Vec<RingProof<F, CS>>,
Expand Down
Loading