samaharam 0.2.0

Scalable heterogeneous zero-knowledge proof aggregation for EVM chains
Documentation
//! Aggregator configuration using the Builder pattern.

use std::sync::Arc;

use crate::aggregator::Aggregator;
use crate::error::ConfigError;
use crate::traits::PairingEngine;

/// Structured Reference String (SRS) for KZG polynomial commitment.
///
/// Contains powers of tau [τ^i]G₁ for i = 0..n and [τ]G₂ for pairing checks.
/// Production SRS should be loaded from a trusted setup ceremony (e.g., Hermez/Zcash).
#[derive(Debug, Clone)]
pub struct Srs<E: PairingEngine> {
    /// Powers of tau in G1: [τ^0]G₁, [τ^1]G₁, ..., [τ^n]G₁
    pub powers_of_tau_g1: Vec<E::G1Affine>,

    /// [τ]G₂ for pairing verification
    pub tau_g2: E::G2Affine,

    /// G2 generator [1]G₂
    pub g2_generator: E::G2Affine,

    /// Log2 of max supported polynomial degree
    k: u32,
}

impl<E: PairingEngine> Srs<E> {
    /// Create SRS from ceremony data.
    ///
    /// # Arguments
    /// * `powers_g1` - Powers of tau in G1
    /// * `tau_g2` - [τ]G₂
    /// * `g2_gen` - G2 generator
    pub fn from_ceremony(
        powers_g1: Vec<E::G1Affine>,
        tau_g2: E::G2Affine,
        g2_gen: E::G2Affine,
    ) -> Self {
        let k = (powers_g1.len() as f64).log2().ceil() as u32;
        Self {
            powers_of_tau_g1: powers_g1,
            tau_g2,
            g2_generator: g2_gen,
            k,
        }
    }

    /// Create a deterministic SRS for testing (NOT cryptographically secure).
    ///
    /// Uses a fixed seed to generate predictable powers of tau.
    /// **WARNING**: Only for testing. Production must use trusted setup.
    #[cfg(any(test, test))]
    pub fn mock(k: u32) -> Self {
        use ff::Field;
        use group::{Curve, Group};

        let n = 1usize << k;

        // Deterministic "tau" from hash - NOT SECURE, testing only
        let tau = E::Fr::from(0x1234567890abcdef_u64);

        // Generate powers: [τ^0], [τ^1], ..., [τ^(n-1)]
        let mut powers_g1 = Vec::with_capacity(n);
        let mut tau_power = E::Fr::ONE;

        for _ in 0..n {
            let point = E::G1::generator() * tau_power;
            powers_g1.push(point.to_affine());
            tau_power *= tau;
        }

        // [τ]G₂
        let tau_g2 = (E::G1::generator() * tau).to_affine();
        // This is incorrect curve type - for production need actual G2 ops
        // For mock, we use the same pattern

        Self {
            powers_of_tau_g1: powers_g1,
            // Mock: reusing G2 generator as placeholder for tau_g2
            tau_g2: Self::mock_g2_point(tau),
            g2_generator: Self::mock_g2_generator(),
            k,
        }
    }

    #[cfg(any(test, test))]
    fn mock_g2_point(_scalar: E::Fr) -> E::G2Affine {
        use std::any::{Any, TypeId};

        // For Bn254, use actual G2
        if TypeId::of::<E>() == TypeId::of::<crate::backend::bn254::Bn254>() {
            use group::{Curve, Group};
            use halo2curves::bn256::G2;

            // Safe downcast via Any since we verified TypeId matches
            let point = G2::generator().to_affine();
            let point_any: &dyn Any = &point;
            
            if let Some(casted) = point_any.downcast_ref::<E::G2Affine>() {
                return casted.clone();
            }
            // Fallback (should be unreachable given TypeId check)
            panic!("Type match failed despite TypeId check");
        } else {
            panic!("SRS mock only supports Bn254")
        }
    }

    #[cfg(any(test, test))]
    fn mock_g2_generator() -> E::G2Affine {
        use std::any::TypeId;

        if TypeId::of::<E>() == TypeId::of::<crate::backend::bn254::Bn254>() {
            use group::{Curve, Group};
            use halo2curves::bn256::G2;

            let point = G2::generator().to_affine();
            unsafe { std::mem::transmute_copy(&point) }
        } else {
            panic!("SRS mock only supports Bn254")
        }
    }

    /// Get the log size of the SRS.
    pub fn k(&self) -> u32 {
        self.k
    }

    /// Get the maximum polynomial degree this SRS can commit to.
    pub fn max_degree(&self) -> usize {
        self.powers_of_tau_g1.len() - 1
    }
}

/// Builder for configuring an Aggregator.
///
/// # Example
///
/// ```rust,ignore
/// let aggregator = AggregatorBuilder::<Bn254>::new()
///     .with_srs(srs)
///     .max_batch_size(64)
///     .enable_parallelism()
///     .build()?;
/// ```
#[derive(Debug)]
pub struct AggregatorBuilder<E: PairingEngine> {
    srs: Option<Arc<Srs<E>>>,
    max_batch_size: Option<usize>,
    parallel: bool,
}

impl<E: PairingEngine> Default for AggregatorBuilder<E> {
    fn default() -> Self {
        Self::new()
    }
}

impl<E: PairingEngine> AggregatorBuilder<E> {
    /// Create a new builder with default settings.
    pub fn new() -> Self {
        Self {
            srs: None,
            max_batch_size: None,
            parallel: false,
        }
    }

    /// Set the Structured Reference String.
    pub fn with_srs(mut self, srs: Arc<Srs<E>>) -> Self {
        self.srs = Some(srs);
        self
    }

    /// Set the maximum batch size for aggregation.
    ///
    /// Default is 32 if not specified.
    pub fn max_batch_size(mut self, size: usize) -> Self {
        self.max_batch_size = Some(size);
        self
    }

    /// Enable parallel proof processing.
    ///
    /// Requires the `parallel` feature.
    pub fn enable_parallelism(mut self) -> Self {
        self.parallel = true;
        self
    }

    /// Build the Aggregator.
    ///
    /// # Errors
    ///
    /// Returns `ConfigError::MissingSrs` if SRS was not provided.
    /// Returns `ConfigError::InvalidBatchSize` if batch size is 0.
    pub fn build(self) -> Result<Aggregator<E>, ConfigError> {
        let srs = self.srs.ok_or(ConfigError::MissingSrs)?;
        let max_batch_size = self.max_batch_size.unwrap_or(32);

        if max_batch_size == 0 {
            return Err(ConfigError::InvalidBatchSize(0));
        }

        Ok(Aggregator::new(srs, max_batch_size, self.parallel))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::backend::bn254::Bn254;

    #[test]
    fn builder_requires_srs() {
        let result = AggregatorBuilder::<Bn254>::new().build();
        assert!(matches!(result, Err(ConfigError::MissingSrs)));
    }

    #[test]
    fn builder_rejects_zero_batch_size() {
        let srs = Arc::new(Srs::<Bn254>::mock(10));
        let result = AggregatorBuilder::<Bn254>::new().with_srs(srs).max_batch_size(0).build();

        assert!(matches!(result, Err(ConfigError::InvalidBatchSize(0))));
    }

    #[test]
    fn builder_defaults_batch_size() {
        let srs = Arc::new(Srs::<Bn254>::mock(10));
        let aggregator = AggregatorBuilder::<Bn254>::new().with_srs(srs).build().unwrap();

        assert_eq!(aggregator.max_batch_size(), 32);
    }

    #[test]
    fn builder_fluent_api() {
        let srs = Arc::new(Srs::<Bn254>::mock(10));
        let aggregator = AggregatorBuilder::<Bn254>::new()
            .with_srs(srs)
            .max_batch_size(64)
            .enable_parallelism()
            .build()
            .unwrap();

        assert_eq!(aggregator.max_batch_size(), 64);
    }
}