use std::marker::PhantomData;
use crate::crypto::transcript::Transcript;
use crate::traits::PairingEngine;
#[derive(Debug, Clone)]
pub struct AccumulatedProof<E: PairingEngine> {
pub adjusted_commitment: E::G1Affine,
pub combined_quotient: E::G1Affine,
pub count: usize,
}
#[derive(Debug, Clone)]
pub struct AccumulatorInstance<E: PairingEngine> {
pub commitment: E::G1Affine,
pub evaluation: E::Fr,
pub point: E::Fr,
pub quotient: E::G1Affine,
}
pub struct ProofAccumulator<E: PairingEngine> {
instances: Vec<AccumulatorInstance<E>>,
transcript: Transcript,
_engine: PhantomData<E>,
}
impl<E: PairingEngine> ProofAccumulator<E> {
pub fn new(domain: &str) -> Self {
Self {
instances: Vec::new(),
transcript: Transcript::new(domain),
_engine: PhantomData,
}
}
pub fn add(&mut self, instance: AccumulatorInstance<E>) {
self.transcript.append_g1::<E>("commitment", &instance.commitment);
self.transcript.append_scalar::<E>("evaluation", &instance.evaluation);
self.transcript.append_scalar::<E>("point", &instance.point);
self.instances.push(instance);
}
pub fn len(&self) -> usize {
self.instances.len()
}
pub fn is_empty(&self) -> bool {
self.instances.is_empty()
}
pub fn fold(&mut self) -> Result<AccumulatedProof<E>, String> {
use group::{Curve, Group};
if self.instances.is_empty() {
return Err("No instances to fold".to_string());
}
let mut challenges: Vec<E::Fr> = Vec::with_capacity(self.instances.len());
for i in 0..self.instances.len() {
let challenge = self.transcript.challenge_scalar::<E>(&format!("fold_{}", i));
challenges.push(challenge);
}
let mut adjusted_commitment_acc = E::G1::identity();
let mut combined_quotient_acc = E::G1::identity();
let g1_gen = E::G1::generator();
for (instance, r) in self.instances.iter().zip(challenges.iter()) {
let comm_g1: E::G1 = instance.commitment.into();
let quot_g1: E::G1 = instance.quotient.into();
let v_g1 = g1_gen * instance.evaluation;
let adjusted = (comm_g1 - v_g1 + quot_g1 * instance.point) * *r;
adjusted_commitment_acc += adjusted;
combined_quotient_acc += quot_g1 * *r;
}
let count = self.instances.len();
self.instances.clear();
Ok(AccumulatedProof {
adjusted_commitment: adjusted_commitment_acc.to_affine(),
combined_quotient: combined_quotient_acc.to_affine(),
count,
})
}
pub fn verify_accumulated(
proof: &AccumulatedProof<E>,
srs_g2: &E::G2Affine,
tau_g2: &E::G2Affine,
) -> bool {
use group::{Curve, Group};
let combined_q: E::G1 = proof.combined_quotient.into();
let neg_quotient = (-combined_q).to_affine();
let result = E::multi_pairing([
(&proof.adjusted_commitment, srs_g2),
(&neg_quotient, tau_g2),
]);
result == E::Gt::identity()
}
#[cfg(test)]
pub fn verify_accumulated_separate_pairings(
proof: &AccumulatedProof<E>,
srs_g2: &E::G2Affine,
tau_g2: &E::G2Affine,
) -> bool {
let lhs = E::pairing(&proof.adjusted_commitment, srs_g2);
let rhs = E::pairing(&proof.combined_quotient, tau_g2);
lhs == rhs
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::bn254::Bn254;
use ff::Field;
use group::{Curve, Group};
use halo2curves::bn256::{Fr, G1};
use rand::rngs::OsRng;
fn mock_instance() -> AccumulatorInstance<Bn254> {
AccumulatorInstance {
commitment: G1::random(OsRng).to_affine(),
evaluation: Fr::random(OsRng),
point: Fr::random(OsRng),
quotient: G1::random(OsRng).to_affine(),
}
}
#[test]
fn accumulator_starts_empty() {
let acc = ProofAccumulator::<Bn254>::new("test");
assert!(acc.is_empty());
assert_eq!(acc.len(), 0);
}
#[test]
fn accumulator_tracks_instances() {
let mut acc = ProofAccumulator::<Bn254>::new("test");
acc.add(mock_instance());
acc.add(mock_instance());
assert_eq!(acc.len(), 2);
assert!(!acc.is_empty());
}
#[test]
fn fold_empty_fails() {
let mut acc = ProofAccumulator::<Bn254>::new("test");
let result = acc.fold();
assert!(result.is_err());
}
#[test]
fn fold_multiple_produces_valid_accumulation() {
let mut acc = ProofAccumulator::<Bn254>::new("test");
acc.add(mock_instance());
acc.add(mock_instance());
acc.add(mock_instance());
let folded = acc.fold().unwrap();
assert_eq!(folded.count, 3);
assert_ne!(folded.adjusted_commitment, G1::identity().to_affine());
assert_ne!(folded.combined_quotient, G1::identity().to_affine());
}
#[test]
fn fold_clears_instances() {
let mut acc = ProofAccumulator::<Bn254>::new("test");
acc.add(mock_instance());
acc.add(mock_instance());
let _ = acc.fold().unwrap();
assert!(acc.is_empty());
}
#[test]
fn fold_is_deterministic() {
let instance1 = AccumulatorInstance {
commitment: G1::generator().to_affine(),
evaluation: Fr::from(42u64),
point: Fr::from(7u64),
quotient: (G1::generator() * Fr::from(3u64)).to_affine(),
};
let instance2 = AccumulatorInstance {
commitment: (G1::generator() * Fr::from(2u64)).to_affine(),
evaluation: Fr::from(100u64),
point: Fr::from(13u64),
quotient: (G1::generator() * Fr::from(5u64)).to_affine(),
};
let mut acc1 = ProofAccumulator::<Bn254>::new("test");
acc1.add(instance1.clone());
acc1.add(instance2.clone());
let mut acc2 = ProofAccumulator::<Bn254>::new("test");
acc2.add(instance1);
acc2.add(instance2);
let folded1 = acc1.fold().unwrap();
let folded2 = acc2.fold().unwrap();
assert_eq!(folded1.adjusted_commitment, folded2.adjusted_commitment);
assert_eq!(folded1.combined_quotient, folded2.combined_quotient);
}
#[test]
fn multi_pairing_and_separate_pairing_are_equivalent() {
let instance1 = AccumulatorInstance {
commitment: G1::generator().to_affine(),
evaluation: Fr::from(42u64),
point: Fr::from(7u64),
quotient: (G1::generator() * Fr::from(3u64)).to_affine(),
};
let instance2 = AccumulatorInstance {
commitment: (G1::generator() * Fr::from(2u64)).to_affine(),
evaluation: Fr::from(100u64),
point: Fr::from(13u64),
quotient: (G1::generator() * Fr::from(5u64)).to_affine(),
};
let mut acc = ProofAccumulator::<Bn254>::new("equivalence_test");
acc.add(instance1);
acc.add(instance2);
let folded = acc.fold().unwrap();
let g2_gen = halo2curves::bn256::G2::generator().to_affine();
let tau = Fr::from(0xDEADBEEFu64);
let tau_g2 = (halo2curves::bn256::G2::generator() * tau).to_affine();
let result_multi = ProofAccumulator::<Bn254>::verify_accumulated(&folded, &g2_gen, &tau_g2);
let result_separate = ProofAccumulator::<Bn254>::verify_accumulated_separate_pairings(&folded, &g2_gen, &tau_g2);
assert_eq!(result_multi, result_separate,
"multi_pairing and separate pairing should give same result");
}
}