safe_zk_token_sdk/encryption/
pedersen.rs

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