solana_zk_sdk/encryption/
pedersen.rs1use {
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 pub static ref G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT;
23 pub static ref H: RistrettoPoint =
25 RistrettoPoint::hash_from_bytes::<Sha3_512>(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());
26}
27
28pub struct Pedersen;
30impl Pedersen {
31 #[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 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 pub fn encode<T: Into<Scalar>>(amount: T) -> PedersenCommitment {
59 PedersenCommitment(amount.into() * &(*G))
60 }
61}
62
63#[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#[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 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 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}