solana_zk_sdk/encryption/
pedersen.rs

1//! Pedersen commitment implementation using the Ristretto prime-order group.
2
3use {
4    crate::encryption::{PEDERSEN_COMMITMENT_LEN, PEDERSEN_OPENING_LEN},
5    core::ops::{Add, Mul, Sub},
6    curve25519_dalek::{
7        constants::{RISTRETTO_BASEPOINT_COMPRESSED, RISTRETTO_BASEPOINT_POINT},
8        ristretto::{CompressedRistretto, RistrettoPoint},
9        scalar::Scalar,
10        traits::MultiscalarMul,
11    },
12    rand::rngs::OsRng,
13    serde::{Deserialize, Serialize},
14    sha3::Sha3_512,
15    std::convert::TryInto,
16    subtle::{Choice, ConstantTimeEq},
17    zeroize::Zeroize,
18};
19
20lazy_static::lazy_static! {
21    /// Pedersen base point for encoding messages to be committed.
22    pub static ref G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT;
23    /// Pedersen base point for encoding the commitment openings.
24    pub static ref H: RistrettoPoint =
25        RistrettoPoint::hash_from_bytes::<Sha3_512>(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());
26}
27
28/// Algorithm handle for the Pedersen commitment scheme.
29pub struct Pedersen;
30impl Pedersen {
31    /// On input a message (numeric amount), the function returns a Pedersen commitment of the
32    /// message and the corresponding opening.
33    ///
34    /// This function is randomized. It internally samples a Pedersen opening using `OsRng`.
35    #[allow(clippy::new_ret_no_self)]
36    pub fn new<T: Into<Scalar>>(amount: T) -> (PedersenCommitment, PedersenOpening) {
37        let opening = PedersenOpening::new_rand();
38        let commitment = Pedersen::with(amount, &opening);
39
40        (commitment, opening)
41    }
42
43    /// On input a message (numeric amount) and a Pedersen opening, the function returns the
44    /// corresponding Pedersen commitment.
45    ///
46    /// This function is deterministic.
47    pub fn with<T: Into<Scalar>>(amount: T, opening: &PedersenOpening) -> PedersenCommitment {
48        let x: Scalar = amount.into();
49        let r = opening.get_scalar();
50
51        PedersenCommitment(RistrettoPoint::multiscalar_mul(&[x, *r], &[*G, *H]))
52    }
53
54    /// On input a message (numeric amount), the function returns a Pedersen commitment with zero
55    /// as the opening.
56    ///
57    /// This function is deterministic.
58    pub fn encode<T: Into<Scalar>>(amount: T) -> PedersenCommitment {
59        PedersenCommitment(amount.into() * &(*G))
60    }
61}
62
63/// Pedersen opening type.
64///
65/// Instances of Pedersen openings are zeroized on drop.
66#[derive(Clone, Debug, Default, Serialize, Deserialize, Zeroize)]
67#[zeroize(drop)]
68pub struct PedersenOpening(Scalar);
69impl PedersenOpening {
70    pub fn new(scalar: Scalar) -> Self {
71        Self(scalar)
72    }
73
74    pub fn get_scalar(&self) -> &Scalar {
75        &self.0
76    }
77
78    pub fn new_rand() -> Self {
79        PedersenOpening(Scalar::random(&mut OsRng))
80    }
81
82    pub fn as_bytes(&self) -> &[u8; PEDERSEN_OPENING_LEN] {
83        self.0.as_bytes()
84    }
85
86    pub fn to_bytes(&self) -> [u8; PEDERSEN_OPENING_LEN] {
87        self.0.to_bytes()
88    }
89
90    pub fn from_bytes(bytes: &[u8]) -> Option<PedersenOpening> {
91        match bytes.try_into() {
92            Ok(bytes) => Scalar::from_canonical_bytes(bytes)
93                .into_option()
94                .map(PedersenOpening),
95            _ => None,
96        }
97    }
98}
99impl Eq for PedersenOpening {}
100impl PartialEq for PedersenOpening {
101    fn eq(&self, other: &Self) -> bool {
102        self.ct_eq(other).unwrap_u8() == 1u8
103    }
104}
105impl ConstantTimeEq for PedersenOpening {
106    fn ct_eq(&self, other: &Self) -> Choice {
107        self.0.ct_eq(&other.0)
108    }
109}
110
111impl<'a, 'b> Add<&'b PedersenOpening> for &'a PedersenOpening {
112    type Output = PedersenOpening;
113
114    fn add(self, opening: &'b PedersenOpening) -> PedersenOpening {
115        PedersenOpening(&self.0 + &opening.0)
116    }
117}
118
119define_add_variants!(
120    LHS = PedersenOpening,
121    RHS = PedersenOpening,
122    Output = PedersenOpening
123);
124
125impl<'a, 'b> Sub<&'b PedersenOpening> for &'a PedersenOpening {
126    type Output = PedersenOpening;
127
128    fn sub(self, opening: &'b PedersenOpening) -> PedersenOpening {
129        PedersenOpening(&self.0 - &opening.0)
130    }
131}
132
133define_sub_variants!(
134    LHS = PedersenOpening,
135    RHS = PedersenOpening,
136    Output = PedersenOpening
137);
138
139impl<'a, 'b> Mul<&'b Scalar> for &'a PedersenOpening {
140    type Output = PedersenOpening;
141
142    fn mul(self, scalar: &'b Scalar) -> PedersenOpening {
143        PedersenOpening(&self.0 * scalar)
144    }
145}
146
147define_mul_variants!(
148    LHS = PedersenOpening,
149    RHS = Scalar,
150    Output = PedersenOpening
151);
152
153impl<'a, 'b> Mul<&'b PedersenOpening> for &'a Scalar {
154    type Output = PedersenOpening;
155
156    fn mul(self, opening: &'b PedersenOpening) -> PedersenOpening {
157        PedersenOpening(self * &opening.0)
158    }
159}
160
161define_mul_variants!(
162    LHS = Scalar,
163    RHS = PedersenOpening,
164    Output = PedersenOpening
165);
166
167/// Pedersen commitment type.
168#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
169pub struct PedersenCommitment(RistrettoPoint);
170impl PedersenCommitment {
171    pub fn new(point: RistrettoPoint) -> Self {
172        Self(point)
173    }
174
175    pub fn get_point(&self) -> &RistrettoPoint {
176        &self.0
177    }
178
179    pub fn to_bytes(&self) -> [u8; PEDERSEN_COMMITMENT_LEN] {
180        self.0.compress().to_bytes()
181    }
182
183    pub fn from_bytes(bytes: &[u8]) -> Option<PedersenCommitment> {
184        if bytes.len() != PEDERSEN_COMMITMENT_LEN {
185            return None;
186        }
187
188        let Ok(compressed_ristretto) = CompressedRistretto::from_slice(bytes) else {
189            return None;
190        };
191
192        compressed_ristretto.decompress().map(PedersenCommitment)
193    }
194}
195
196impl<'a, 'b> Add<&'b PedersenCommitment> for &'a PedersenCommitment {
197    type Output = PedersenCommitment;
198
199    fn add(self, commitment: &'b PedersenCommitment) -> PedersenCommitment {
200        PedersenCommitment(&self.0 + &commitment.0)
201    }
202}
203
204define_add_variants!(
205    LHS = PedersenCommitment,
206    RHS = PedersenCommitment,
207    Output = PedersenCommitment
208);
209
210impl<'a, 'b> Sub<&'b PedersenCommitment> for &'a PedersenCommitment {
211    type Output = PedersenCommitment;
212
213    fn sub(self, commitment: &'b PedersenCommitment) -> PedersenCommitment {
214        PedersenCommitment(&self.0 - &commitment.0)
215    }
216}
217
218define_sub_variants!(
219    LHS = PedersenCommitment,
220    RHS = PedersenCommitment,
221    Output = PedersenCommitment
222);
223
224impl<'a, 'b> Mul<&'b Scalar> for &'a PedersenCommitment {
225    type Output = PedersenCommitment;
226
227    fn mul(self, scalar: &'b Scalar) -> PedersenCommitment {
228        PedersenCommitment(scalar * &self.0)
229    }
230}
231
232define_mul_variants!(
233    LHS = PedersenCommitment,
234    RHS = Scalar,
235    Output = PedersenCommitment
236);
237
238impl<'a, 'b> Mul<&'b PedersenCommitment> for &'a Scalar {
239    type Output = PedersenCommitment;
240
241    fn mul(self, commitment: &'b PedersenCommitment) -> PedersenCommitment {
242        PedersenCommitment(self * &commitment.0)
243    }
244}
245
246define_mul_variants!(
247    LHS = Scalar,
248    RHS = PedersenCommitment,
249    Output = PedersenCommitment
250);
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn test_pedersen_homomorphic_addition() {
258        let amount_0: u64 = 77;
259        let amount_1: u64 = 57;
260
261        let rng = &mut OsRng;
262        let opening_0 = PedersenOpening(Scalar::random(rng));
263        let opening_1 = PedersenOpening(Scalar::random(rng));
264
265        let commitment_0 = Pedersen::with(amount_0, &opening_0);
266        let commitment_1 = Pedersen::with(amount_1, &opening_1);
267        let commitment_addition = Pedersen::with(amount_0 + amount_1, &(opening_0 + opening_1));
268
269        assert_eq!(commitment_addition, commitment_0 + commitment_1);
270    }
271
272    #[test]
273    fn test_pedersen_homomorphic_subtraction() {
274        let amount_0: u64 = 77;
275        let amount_1: u64 = 57;
276
277        let rng = &mut OsRng;
278        let opening_0 = PedersenOpening(Scalar::random(rng));
279        let opening_1 = PedersenOpening(Scalar::random(rng));
280
281        let commitment_0 = Pedersen::with(amount_0, &opening_0);
282        let commitment_1 = Pedersen::with(amount_1, &opening_1);
283        let commitment_addition = Pedersen::with(amount_0 - amount_1, &(opening_0 - opening_1));
284
285        assert_eq!(commitment_addition, commitment_0 - commitment_1);
286    }
287
288    #[test]
289    fn test_pedersen_homomorphic_multiplication() {
290        let amount_0: u64 = 77;
291        let amount_1: u64 = 57;
292
293        let (commitment, opening) = Pedersen::new(amount_0);
294        let scalar = Scalar::from(amount_1);
295        let commitment_addition = Pedersen::with(amount_0 * amount_1, &(opening * scalar));
296
297        assert_eq!(commitment_addition, commitment * scalar);
298        assert_eq!(commitment_addition, scalar * commitment);
299    }
300
301    #[test]
302    fn test_pedersen_commitment_bytes() {
303        let amount: u64 = 77;
304        let (commitment, _) = Pedersen::new(amount);
305
306        let encoded = commitment.to_bytes();
307        let decoded = PedersenCommitment::from_bytes(&encoded).unwrap();
308
309        assert_eq!(commitment, decoded);
310
311        // incorrect length encoding
312        assert_eq!(PedersenCommitment::from_bytes(&[0; 33]), None);
313    }
314
315    #[test]
316    fn test_pedersen_opening_bytes() {
317        let opening = PedersenOpening(Scalar::random(&mut OsRng));
318
319        let encoded = opening.to_bytes();
320        let decoded = PedersenOpening::from_bytes(&encoded).unwrap();
321
322        assert_eq!(opening, decoded);
323
324        // incorrect length encoding
325        assert_eq!(PedersenOpening::from_bytes(&[0; 33]), None);
326    }
327
328    #[test]
329    fn test_serde_pedersen_commitment() {
330        let amount: u64 = 77;
331        let (commitment, _) = Pedersen::new(amount);
332
333        let encoded = bincode::serialize(&commitment).unwrap();
334        let decoded: PedersenCommitment = bincode::deserialize(&encoded).unwrap();
335
336        assert_eq!(commitment, decoded);
337    }
338
339    #[test]
340    fn test_serde_pedersen_opening() {
341        let opening = PedersenOpening(Scalar::random(&mut OsRng));
342
343        let encoded = bincode::serialize(&opening).unwrap();
344        let decoded: PedersenOpening = bincode::deserialize(&encoded).unwrap();
345
346        assert_eq!(opening, decoded);
347    }
348}