Skip to main content

si_commitment_scheme/
batch.rs

1//! Batch opening and verification of commitments.
2//!
3//! Efficiently open and verify multiple Pedersen commitments at once.
4
5use crate::pedersen::{PedersenCommitment, PedersenOpening, PedersenParams};
6use curve25519_dalek::scalar::Scalar;
7use rand::rngs::OsRng;
8
9/// A batch of commitment openings.
10#[derive(Debug, Clone)]
11pub struct BatchCommitment {
12    /// Commitments in this batch.
13    pub commitments: Vec<PedersenCommitment>,
14    /// Openings for each commitment.
15    pub openings: Vec<PedersenOpening>,
16}
17
18impl BatchCommitment {
19    /// Create a batch of commitments to random values.
20    pub fn commit_batch(params: &PedersenParams, values: &[Scalar]) -> Self {
21        let mut commitments = Vec::with_capacity(values.len());
22        let mut openings = Vec::with_capacity(values.len());
23        for value in values {
24            let (c, o) = PedersenCommitment::commit(params, value);
25            commitments.push(c);
26            openings.push(o);
27        }
28        BatchCommitment { commitments, openings }
29    }
30
31    /// Create a batch of commitments to u64 values.
32    pub fn commit_batch_u64(params: &PedersenParams, values: &[u64]) -> Self {
33        let scalars: Vec<Scalar> = values.iter().map(|&v| Scalar::from(v)).collect();
34        Self::commit_batch(params, &scalars)
35    }
36
37    /// Verify all openings in the batch.
38    pub fn verify_all(&self, params: &PedersenParams) -> bool {
39        if self.commitments.len() != self.openings.len() {
40            return false;
41        }
42        self.commitments
43            .iter()
44            .zip(&self.openings)
45            .all(|(c, o)| PedersenCommitment::verify(params, o, c))
46    }
47
48    /// Number of commitments in the batch.
49    pub fn len(&self) -> usize {
50        self.commitments.len()
51    }
52
53    /// Whether the batch is empty.
54    pub fn is_empty(&self) -> bool {
55        self.commitments.is_empty()
56    }
57
58    /// Aggregate all commitments using random linear combination.
59    /// Returns (aggregated_commitment, aggregated_opening).
60    pub fn aggregate(&self) -> (PedersenCommitment, PedersenOpening) {
61        assert!(!self.commitments.is_empty());
62        let mut agg_point = self.commitments[0].point;
63        let mut agg_value = self.openings[0].value;
64        let mut agg_randomness = self.openings[0].randomness;
65
66        for i in 1..self.commitments.len() {
67            let challenge = Scalar::random(&mut OsRng);
68            agg_point += challenge * self.commitments[i].point;
69            agg_value += challenge * self.openings[i].value;
70            agg_randomness += challenge * self.openings[i].randomness;
71        }
72
73        (
74            PedersenCommitment {
75                point: agg_point,
76                compressed: agg_point.compress(),
77            },
78            PedersenOpening {
79                value: agg_value,
80                randomness: agg_randomness,
81            },
82        )
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn batch_commit_and_verify() {
92        let params = PedersenParams::default();
93        let batch = BatchCommitment::commit_batch_u64(&params, &[10, 20, 30, 40]);
94        assert!(batch.verify_all(&params));
95        assert_eq!(batch.len(), 4);
96    }
97
98    #[test]
99    fn batch_single_commitment() {
100        let params = PedersenParams::default();
101        let batch = BatchCommitment::commit_batch_u64(&params, &[42]);
102        assert!(batch.verify_all(&params));
103        assert_eq!(batch.len(), 1);
104    }
105
106    #[test]
107    fn batch_tampered_opening_fails() {
108        let params = PedersenParams::default();
109        let mut batch = BatchCommitment::commit_batch_u64(&params, &[10, 20, 30]);
110        batch.openings[1].value = Scalar::from(999u64);
111        assert!(!batch.verify_all(&params));
112    }
113
114    #[test]
115    fn batch_aggregate_verifies() {
116        let params = PedersenParams::default();
117        let batch = BatchCommitment::commit_batch_u64(&params, &[10, 20, 30]);
118        let (agg_comm, agg_open) = batch.aggregate();
119        assert!(PedersenCommitment::verify(&params, &agg_open, &agg_comm));
120    }
121}