si-commitment-scheme 0.1.0

Pedersen commitments, Blake3-based polynomial commitments, batch opening/verification with binding and hiding properties
Documentation
//! Batch opening and verification of commitments.
//!
//! Efficiently open and verify multiple Pedersen commitments at once.

use crate::pedersen::{PedersenCommitment, PedersenOpening, PedersenParams};
use curve25519_dalek::scalar::Scalar;
use rand::rngs::OsRng;

/// A batch of commitment openings.
#[derive(Debug, Clone)]
pub struct BatchCommitment {
    /// Commitments in this batch.
    pub commitments: Vec<PedersenCommitment>,
    /// Openings for each commitment.
    pub openings: Vec<PedersenOpening>,
}

impl BatchCommitment {
    /// Create a batch of commitments to random values.
    pub fn commit_batch(params: &PedersenParams, values: &[Scalar]) -> Self {
        let mut commitments = Vec::with_capacity(values.len());
        let mut openings = Vec::with_capacity(values.len());
        for value in values {
            let (c, o) = PedersenCommitment::commit(params, value);
            commitments.push(c);
            openings.push(o);
        }
        BatchCommitment { commitments, openings }
    }

    /// Create a batch of commitments to u64 values.
    pub fn commit_batch_u64(params: &PedersenParams, values: &[u64]) -> Self {
        let scalars: Vec<Scalar> = values.iter().map(|&v| Scalar::from(v)).collect();
        Self::commit_batch(params, &scalars)
    }

    /// Verify all openings in the batch.
    pub fn verify_all(&self, params: &PedersenParams) -> bool {
        if self.commitments.len() != self.openings.len() {
            return false;
        }
        self.commitments
            .iter()
            .zip(&self.openings)
            .all(|(c, o)| PedersenCommitment::verify(params, o, c))
    }

    /// Number of commitments in the batch.
    pub fn len(&self) -> usize {
        self.commitments.len()
    }

    /// Whether the batch is empty.
    pub fn is_empty(&self) -> bool {
        self.commitments.is_empty()
    }

    /// Aggregate all commitments using random linear combination.
    /// Returns (aggregated_commitment, aggregated_opening).
    pub fn aggregate(&self) -> (PedersenCommitment, PedersenOpening) {
        assert!(!self.commitments.is_empty());
        let mut agg_point = self.commitments[0].point;
        let mut agg_value = self.openings[0].value;
        let mut agg_randomness = self.openings[0].randomness;

        for i in 1..self.commitments.len() {
            let challenge = Scalar::random(&mut OsRng);
            agg_point += challenge * self.commitments[i].point;
            agg_value += challenge * self.openings[i].value;
            agg_randomness += challenge * self.openings[i].randomness;
        }

        (
            PedersenCommitment {
                point: agg_point,
                compressed: agg_point.compress(),
            },
            PedersenOpening {
                value: agg_value,
                randomness: agg_randomness,
            },
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn batch_commit_and_verify() {
        let params = PedersenParams::default();
        let batch = BatchCommitment::commit_batch_u64(&params, &[10, 20, 30, 40]);
        assert!(batch.verify_all(&params));
        assert_eq!(batch.len(), 4);
    }

    #[test]
    fn batch_single_commitment() {
        let params = PedersenParams::default();
        let batch = BatchCommitment::commit_batch_u64(&params, &[42]);
        assert!(batch.verify_all(&params));
        assert_eq!(batch.len(), 1);
    }

    #[test]
    fn batch_tampered_opening_fails() {
        let params = PedersenParams::default();
        let mut batch = BatchCommitment::commit_batch_u64(&params, &[10, 20, 30]);
        batch.openings[1].value = Scalar::from(999u64);
        assert!(!batch.verify_all(&params));
    }

    #[test]
    fn batch_aggregate_verifies() {
        let params = PedersenParams::default();
        let batch = BatchCommitment::commit_batch_u64(&params, &[10, 20, 30]);
        let (agg_comm, agg_open) = batch.aggregate();
        assert!(PedersenCommitment::verify(&params, &agg_open, &agg_comm));
    }
}