Skip to content
Draft
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
109 changes: 109 additions & 0 deletions w3f-plonk-common/src/kzg_acc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use crate::piop::VerifierPiop;
use crate::verifier::Challenges;
use crate::{ColumnsCommited, ColumnsEvaluated, Proof};
use ark_ec::pairing::Pairing;
use ark_ec::{CurveGroup, VariableBaseMSM};
use ark_ff::{PrimeField, Zero};
use ark_std::iterable::Iterable;
use ark_std::rand::Rng;
use w3f_pcs::pcs::kzg::params::KzgVerifierKey;
use w3f_pcs::pcs::kzg::{AccumulatedOpening, KZG};
use w3f_pcs::pcs::{Commitment, PCS};

// Aggregates opennings for KZG commitments.
// Somewhat similar to https://eprint.iacr.org/2020/499.pdf, section 8.
// With a difference that this accumulates opennings lazily,
// and runs `2` MSMs of size `O(n)` at the final stage,
// that gives an asymptotic saving (thanks to Pippenger)
// at the cost of linear accumulator size.
pub struct KzgAccumulator<E: Pairing> {
acc_points: Vec<E::G1Affine>,
acc_scalars: Vec<E::ScalarField>,
kzg_proofs: Vec<E::G1Affine>,
randomizers: Vec<E::ScalarField>,
kzg_vk: KzgVerifierKey<E>,
}

impl<E: Pairing> KzgAccumulator<E> {
pub fn new(kzg_vk: KzgVerifierKey<E>) -> Self {
//TODO: capacity
Self {
acc_points: vec![kzg_vk.g1],
Copy link
Member

@davxy davxy Jan 28, 2026

Choose a reason for hiding this comment

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

Requires g1 to be public

acc_scalars: vec![E::ScalarField::zero()],
kzg_proofs: vec![],
randomizers: vec![],
kzg_vk,
}
}

pub fn accumulate<F, Piop, Commitments, Evaluations, R: Rng>(
&mut self,
piop: Piop,
proof: Proof<F, KZG<E>, Commitments, Evaluations>,
challenges: Challenges<F>,
rng: &mut R,
) where
F: PrimeField,
E: Pairing<ScalarField = F>,
Piop: VerifierPiop<F, <KZG<E> as PCS<F>>::C>,
Commitments: ColumnsCommited<F, <KZG<E> as PCS<F>>::C>,
Evaluations: ColumnsEvaluated<F>,
{
let q_zeta = piop.evaluate_q_at_zeta(&challenges.alphas, proof.lin_at_zeta_omega);

let mut columns = [
piop.precommitted_columns(),
proof.column_commitments.to_vec(),
]
.concat();
columns.push(proof.quotient_commitment.clone());
let columns = columns.iter().map(|c| c.0).collect::<Vec<_>>();

let mut columns_at_zeta = proof.columns_at_zeta.to_vec();
columns_at_zeta.push(q_zeta);

let agg_at_zeta: F = columns_at_zeta
.into_iter()
.zip(challenges.nus.iter())
.map(|(y, r)| y * r)
.sum();

let lin_comm = piop.lin_poly_commitment(&challenges.alphas);

let zeta = challenges.zeta;
let zeta_omega = zeta * piop.domain_evaluated().omega();

let mut acc_points = vec![];
let mut acc_scalars = vec![];

acc_points.extend(columns);
acc_scalars.extend(challenges.nus);
acc_points.push(proof.agg_at_zeta_proof);
acc_scalars.push(zeta);
self.acc_scalars[0] -= agg_at_zeta;

let r = F::rand(rng);
// z.w openning
acc_points.extend(lin_comm.1.iter().map(|c| c.0).collect::<Vec<_>>());
acc_scalars.extend(lin_comm.0.into_iter().map(|c| c * r).collect::<Vec<_>>());
acc_points.push(proof.lin_at_zeta_omega_proof);
acc_scalars.push(zeta_omega * r);
self.acc_scalars[0] -= proof.lin_at_zeta_omega * r;

let kzg_proofs = vec![proof.agg_at_zeta_proof, proof.lin_at_zeta_omega_proof];
let randomizers = vec![F::one(), r];

self.acc_points.extend(acc_points);
self.acc_scalars.extend(acc_scalars);
self.kzg_proofs.extend(kzg_proofs);
self.randomizers.extend(randomizers);
}

pub fn verify(&self) -> bool {
let acc = (-E::G1::msm(&self.acc_points, &self.acc_scalars).unwrap()).into_affine();
let proof = E::G1::msm(&self.kzg_proofs, &self.randomizers)
.unwrap()
.into_affine();
KZG::<E>::verify_accumulated(AccumulatedOpening { acc, proof }, &self.kzg_vk)
}
}
1 change: 1 addition & 0 deletions w3f-plonk-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use w3f_pcs::pcs::{Commitment, PCS};

pub mod domain;
pub mod gadgets;
pub mod kzg_acc;
pub mod piop;
pub mod prover;
pub mod test_helpers;
Expand Down
2 changes: 1 addition & 1 deletion w3f-plonk-common/src/piop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub trait VerifierPiop<F: PrimeField, C: Commitment<F>> {
}

// Commitment to the aggregated linearization polynomial without the constant term.
fn lin_poly_commitment(&self, agg_coeffs: &[F]) -> C;
fn lin_poly_commitment(&self, agg_coeffs: &[F]) -> (Vec<F>, Vec<C>);

fn domain_evaluated(&self) -> &EvaluatedDomain<F>;
}
3 changes: 2 additions & 1 deletion w3f-plonk-common/src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{ColumnsCommited, ColumnsEvaluated, Proof};

pub struct PlonkVerifier<F: PrimeField, CS: PCS<F>, T: PlonkTranscript<F, CS>> {
// Polynomial commitment scheme verifier's key.
pcs_vk: CS::VK,
pub pcs_vk: CS::VK,
// Transcript,
// initialized with the public parameters and the commitments to the precommitted columns.
transcript_prelude: T,
Expand Down Expand Up @@ -64,6 +64,7 @@ impl<F: PrimeField, CS: PCS<F>, T: PlonkTranscript<F, CS>> PlonkVerifier<F, CS,
.sum();

let lin_comm = piop.lin_poly_commitment(&challenges.alphas);
let lin_comm = CS::C::combine(&lin_comm.0, &lin_comm.1);

let zeta = challenges.zeta;
let zeta_omega = zeta * piop.domain_evaluated().omega();
Expand Down
56 changes: 35 additions & 21 deletions w3f-ring-proof/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,30 +67,38 @@ mod tests {

use super::*;

fn _test_ring_proof<CS: PCS<Fq>>(domain_size: usize) {
fn _test_ring_proof<CS: PCS<Fq>>(
domain_size: usize,
batch_size: usize,
) -> (
RingVerifier<Fq, CS, BandersnatchConfig>,
Vec<(EdwardsAffine, RingProof<Fq, CS>)>,
) {
let rng = &mut test_rng();

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

let max_keyset_size = piop_params.keyset_part_size;
let keyset_size: usize = rng.gen_range(0..max_keyset_size);
let keyset_size = piop_params.keyset_part_size;
let pks = random_vec::<EdwardsAffine, _>(keyset_size, rng);
let k = rng.gen_range(0..keyset_size); // prover's secret index
let pk = pks[k].clone();

let (prover_key, verifier_key) = index::<_, CS, _>(&pcs_params, &piop_params, &pks);

// PROOF generation
let secret = Fr::rand(rng); // prover's secret scalar
let result = piop_params.h.mul(secret) + pk;
let ring_prover = RingProver::init(
prover_key,
piop_params.clone(),
k,
ArkTranscript::new(b"w3f-ring-proof-test"),
);
let t_prove = start_timer!(|| "Prove");
let proof = ring_prover.prove(secret);
let claims: Vec<(EdwardsAffine, RingProof<Fq, CS>)> = (0..batch_size)
.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 prover_pk = pks[prover_idx].clone();
let blinding_factor = Fr::rand(rng);
let blinded_pk = prover_pk + piop_params.h.mul(blinding_factor);
let blinded_pk = blinded_pk.into_affine();
let proof = prover.prove(blinding_factor);
(blinded_pk, proof)
})
.collect();
end_timer!(t_prove);

let ring_verifier = RingVerifier::init(
Expand All @@ -99,9 +107,10 @@ mod tests {
ArkTranscript::new(b"w3f-ring-proof-test"),
);
let t_verify = start_timer!(|| "Verify");
let res = ring_verifier.verify(proof, result.into_affine());
let (blinded_pks, proofs) = claims.iter().cloned().unzip();
assert!(ring_verifier.verify_batch(proofs, blinded_pks));
end_timer!(t_verify);
assert!(res);
(ring_verifier, claims)
}

#[test]
Expand Down Expand Up @@ -145,12 +154,17 @@ mod tests {
}

#[test]
// cargo test test_ring_proof_kzg --release --features="print-trace" -- --show-output
fn test_ring_proof_kzg() {
_test_ring_proof::<KZG<Bls12_381>>(2usize.pow(10));
let (verifier, claims) = _test_ring_proof::<KZG<Bls12_381>>(2usize.pow(10), 10);
let t_verify_batch = start_timer!(|| "Verify Batch KZG");
let (blinded_pks, proofs) = claims.into_iter().unzip();
assert!(verifier.verify_batch_kzg(proofs, blinded_pks));
end_timer!(t_verify_batch);
}

#[test]
fn test_ring_proof_id() {
_test_ring_proof::<w3f_pcs::pcs::IdentityCommitment>(2usize.pow(10));
_test_ring_proof::<w3f_pcs::pcs::IdentityCommitment>(2usize.pow(10), 1);
}
}
1 change: 1 addition & 0 deletions w3f-ring-proof/src/piop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ impl<F: PrimeField, G: AffineRepr<BaseField = F>> FixedColumns<F, G> {
}

// #[derive(CanonicalSerialize, CanonicalDeserialize)]
#[derive(Clone)]
pub struct ProverKey<F: PrimeField, CS: PCS<F>, G: AffineRepr<BaseField = F>> {
pub(crate) pcs_ck: CS::CK,
pub(crate) fixed_columns: FixedColumns<F, G>,
Expand Down
13 changes: 9 additions & 4 deletions w3f-ring-proof/src/piop/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl<F: PrimeField, C: Commitment<F>, Jubjub: TECurveConfig<BaseField = F>> Veri
.concat()
}

fn lin_poly_commitment(&self, agg_coeffs: &[F]) -> C {
fn lin_poly_commitment(&self, agg_coeffs: &[F]) -> (Vec<F>, Vec<C>) {
assert_eq!(agg_coeffs.len(), Self::N_CONSTRAINTS);

let inner_prod_acc = self.witness_columns_committed.inn_prod_acc.clone();
Expand All @@ -138,10 +138,15 @@ impl<F: PrimeField, C: Commitment<F>, Jubjub: TECurveConfig<BaseField = F>> Veri
cond_add_x_coeff += agg_coeffs[2] * c_acc_x;
cond_add_y_coeff += agg_coeffs[2] * c_acc_y;

C::combine(
&[inner_prod_coeff, cond_add_x_coeff, cond_add_y_coeff],
&[inner_prod_acc.clone(), cond_add_acc_x, cond_add_acc_y],
(
vec![inner_prod_coeff, cond_add_x_coeff, cond_add_y_coeff],
vec![inner_prod_acc.clone(), cond_add_acc_x, cond_add_acc_y],
)

// C::combine(
// &[inner_prod_coeff, cond_add_x_coeff, cond_add_y_coeff],
// &[inner_prod_acc.clone(), cond_add_acc_x, cond_add_acc_y],
// )
}

fn domain_evaluated(&self) -> &EvaluatedDomain<F> {
Expand Down
56 changes: 55 additions & 1 deletion w3f-ring-proof/src/ring_verifier.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use ark_ec::pairing::Pairing;
use ark_ec::twisted_edwards::{Affine, TECurveConfig};
use ark_ec::CurveGroup;
use ark_ff::PrimeField;
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::PlonkVerifier;
Expand Down Expand Up @@ -71,4 +73,56 @@ where
pub fn piop_params(&self) -> &PiopParams<F, Jubjub> {
&self.piop_params
}

pub fn verify_batch(
&self,
proofs: Vec<RingProof<F, CS>>,
results: Vec<Affine<Jubjub>>,
) -> bool {
for (proof, result) in proofs.into_iter().zip(results) {
let res = self.verify(proof, result);
if !res {
return false;
}
}
true
}
}

impl<E, Jubjub, T> RingVerifier<E::ScalarField, KZG<E>, Jubjub, T>
where
E: Pairing,
Jubjub: TECurveConfig<BaseField = E::ScalarField>,
T: PlonkTranscript<E::ScalarField, KZG<E>>,
{
// Verifies a batch of proofs against the same ring.
pub fn verify_batch_kzg(
&self,
proofs: Vec<RingProof<E::ScalarField, KZG<E>>>,
results: Vec<Affine<Jubjub>>,
) -> bool {
let mut acc = KzgAccumulator::<E>::new(self.plonk_verifier.pcs_vk.clone());
for (proof, result) in proofs.into_iter().zip(results) {
let (challenges, mut rng) = self.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<Jubjub>>::N_COLUMNS + 1,
PiopVerifier::<E::ScalarField, <KZG<E> as PCS<_>>::C, Affine<Jubjub>>::N_CONSTRAINTS,
);
let seed = self.piop_params.seed;
let seed_plus_result = (seed + result).into_affine();
let domain_at_zeta = self.piop_params.domain.evaluate(challenges.zeta);
let piop = PiopVerifier::<_, _, Affine<Jubjub>>::init(
domain_at_zeta,
self.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),
);
acc.accumulate(piop, proof, challenges, &mut rng);
}
acc.verify()
}
}
Loading