Skip to main content

si_commitment_scheme/
pedersen.rs

1//! Pedersen commitment scheme over Curve25519.
2//!
3//! A Pedersen commitment `C = r*G + v*H` is perfectly hiding and
4//! computationally binding under the discrete logarithm assumption.
5
6use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
7use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
8use curve25519_dalek::scalar::Scalar;
9use curve25519_dalek::traits::Identity;
10use rand::rngs::OsRng;
11
12/// Public parameters for the Pedersen commitment scheme.
13#[derive(Debug, Clone)]
14pub struct PedersenParams {
15    /// Generator G.
16    pub g: RistrettoPoint,
17    /// Generator H (independent of G).
18    pub h: RistrettoPoint,
19}
20
21impl PedersenParams {
22    /// Create default parameters using standard basepoint and a derived second generator.
23    pub fn default_params() -> Self {
24        let g = RISTRETTO_BASEPOINT_POINT;
25        // Derive H deterministically from G by hashing G's encoding
26        let g_bytes = g.compress().to_bytes();
27        let h_bytes = blake3::hash(&g_bytes);
28        // Convert hash to scalar and multiply by G to get H
29        let h_scalar = Scalar::from_bytes_mod_order(*h_bytes.as_bytes());
30        let h = h_scalar * g;
31        Self { g, h }
32    }
33
34    /// Create custom parameters with given generators.
35    pub fn new(g: RistrettoPoint, h: RistrettoPoint) -> Self {
36        Self { g, h }
37    }
38}
39
40impl Default for PedersenParams {
41    fn default() -> Self {
42        Self::default_params()
43    }
44}
45
46/// A Pedersen commitment.
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct PedersenCommitment {
49    /// The commitment point C = r*G + v*H.
50    pub point: RistrettoPoint,
51    /// The compressed (serialized) form.
52    pub compressed: CompressedRistretto,
53}
54
55/// Opening information for a Pedersen commitment.
56#[derive(Debug, Clone)]
57pub struct PedersenOpening {
58    /// The committed value.
59    pub value: Scalar,
60    /// The blinding factor.
61    pub randomness: Scalar,
62}
63
64impl PedersenCommitment {
65    /// Commit to a value with a random blinding factor.
66    pub fn commit(params: &PedersenParams, value: &Scalar) -> (Self, PedersenOpening) {
67        let randomness = Scalar::random(&mut OsRng);
68        let commitment = Self::commit_with_randomness(params, value, &randomness);
69        let opening = PedersenOpening {
70            value: *value,
71            randomness,
72        };
73        (commitment, opening)
74    }
75
76    /// Commit to a value with a specific blinding factor.
77    pub fn commit_with_randomness(
78        params: &PedersenParams,
79        value: &Scalar,
80        randomness: &Scalar,
81    ) -> Self {
82        let point = randomness * params.g + value * params.h;
83        let compressed = point.compress();
84        Self { point, compressed }
85    }
86
87    /// Verify an opening against this commitment.
88    pub fn verify(
89        params: &PedersenParams,
90        opening: &PedersenOpening,
91        commitment: &PedersenCommitment,
92    ) -> bool {
93        let expected = opening.randomness * params.g + opening.value * params.h;
94        expected == commitment.point
95    }
96
97    /// Create a commitment from a u64 value.
98    pub fn commit_u64(params: &PedersenParams, value: u64) -> (Self, PedersenOpening) {
99        Self::commit(params, &Scalar::from(value))
100    }
101
102    /// The identity (zero) commitment.
103    pub fn identity() -> Self {
104        let point = RistrettoPoint::identity();
105        Self {
106            point,
107            compressed: point.compress(),
108        }
109    }
110
111    /// Add two commitments homomorphically: C1 + C2 commits to v1 + v2.
112    pub fn add(&self, other: &PedersenCommitment) -> PedersenCommitment {
113        let point = self.point + other.point;
114        PedersenCommitment {
115            point,
116            compressed: point.compress(),
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn commit_and_verify() {
127        let params = PedersenParams::default();
128        let value = Scalar::from(42u64);
129        let (commitment, opening) = PedersenCommitment::commit(&params, &value);
130        assert!(PedersenCommitment::verify(&params, &opening, &commitment));
131    }
132
133    #[test]
134    fn commit_u64_and_verify() {
135        let params = PedersenParams::default();
136        let (commitment, opening) = PedersenCommitment::commit_u64(&params, 12345);
137        assert!(PedersenCommitment::verify(&params, &opening, &commitment));
138    }
139
140    #[test]
141    fn wrong_value_fails() {
142        let params = PedersenParams::default();
143        let (commitment, mut opening) = PedersenCommitment::commit_u64(&params, 42);
144        opening.value = Scalar::from(99u64);
145        assert!(!PedersenCommitment::verify(&params, &opening, &commitment));
146    }
147
148    #[test]
149    fn wrong_randomness_fails() {
150        let params = PedersenParams::default();
151        let (commitment, mut opening) = PedersenCommitment::commit_u64(&params, 42);
152        opening.randomness = Scalar::random(&mut OsRng);
153        assert!(!PedersenCommitment::verify(&params, &opening, &commitment));
154    }
155
156    #[test]
157    fn homomorphic_addition() {
158        let params = PedersenParams::default();
159        let v1 = Scalar::from(10u64);
160        let v2 = Scalar::from(20u64);
161        let (c1, o1) = PedersenCommitment::commit(&params, &v1);
162        let (c2, o2) = PedersenCommitment::commit(&params, &v2);
163        let c_sum = c1.add(&c2);
164        let combined_value = v1 + v2;
165        let combined_randomness = o1.randomness + o2.randomness;
166        let expected =
167            PedersenCommitment::commit_with_randomness(&params, &combined_value, &combined_randomness);
168        assert_eq!(c_sum.point, expected.point);
169    }
170
171    #[test]
172    fn different_randomness_different_commitment() {
173        let params = PedersenParams::default();
174        let value = Scalar::from(42u64);
175        let (c1, _) = PedersenCommitment::commit(&params, &value);
176        let (c2, _) = PedersenCommitment::commit(&params, &value);
177        assert_ne!(c1.compressed, c2.compressed);
178    }
179
180    #[test]
181    fn deterministic_with_same_randomness() {
182        let params = PedersenParams::default();
183        let value = Scalar::from(42u64);
184        let r = Scalar::from(999u64);
185        let c1 = PedersenCommitment::commit_with_randomness(&params, &value, &r);
186        let c2 = PedersenCommitment::commit_with_randomness(&params, &value, &r);
187        assert_eq!(c1.compressed, c2.compressed);
188    }
189}