solana_zk_sdk/encryption/
pedersen.rs1#[cfg(target_arch = "wasm32")]
4use wasm_bindgen::prelude::*;
5use {
6 crate::encryption::{PEDERSEN_COMMITMENT_LEN, PEDERSEN_OPENING_LEN},
7 core::ops::{Add, Mul, Sub},
8 curve25519_dalek::{
9 constants::{RISTRETTO_BASEPOINT_COMPRESSED, RISTRETTO_BASEPOINT_POINT},
10 ristretto::{CompressedRistretto, RistrettoPoint},
11 scalar::Scalar,
12 traits::MultiscalarMul,
13 },
14 rand::rngs::OsRng,
15 serde::{Deserialize, Serialize},
16 sha3::Sha3_512,
17 std::convert::TryInto,
18 subtle::{Choice, ConstantTimeEq},
19 zeroize::Zeroize,
20};
21
22pub const G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT;
24pub static H: std::sync::LazyLock<RistrettoPoint> = std::sync::LazyLock::new(|| {
26 RistrettoPoint::hash_from_bytes::<Sha3_512>(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes())
27});
28
29#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
31pub struct Pedersen;
32impl Pedersen {
33 #[allow(clippy::new_ret_no_self)]
38 pub fn new<T: Into<Scalar>>(amount: T) -> (PedersenCommitment, PedersenOpening) {
39 let opening = PedersenOpening::new_rand();
40 let commitment = Pedersen::with(amount, &opening);
41
42 (commitment, opening)
43 }
44
45 pub fn with<T: Into<Scalar>>(amount: T, opening: &PedersenOpening) -> PedersenCommitment {
50 let x: Scalar = amount.into();
51 let r = opening.get_scalar();
52
53 PedersenCommitment(RistrettoPoint::multiscalar_mul(&[x, *r], &[G, *H]))
54 }
55
56 pub fn encode<T: Into<Scalar>>(amount: T) -> PedersenCommitment {
61 PedersenCommitment(amount.into() * &G)
62 }
63}
64
65#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
66impl Pedersen {
67 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = withU64))]
68 pub fn with_u64(amount: u64, opening: &PedersenOpening) -> PedersenCommitment {
69 Pedersen::with(amount, opening)
70 }
71}
72
73#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
77#[derive(Clone, Debug, Default, Serialize, Deserialize, Zeroize)]
78#[zeroize(drop)]
79pub struct PedersenOpening(Scalar);
80
81#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
82impl PedersenOpening {
83 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = newRand))]
84 pub fn new_rand() -> Self {
85 PedersenOpening(Scalar::random(&mut OsRng))
86 }
87}
88
89impl PedersenOpening {
90 pub fn new(scalar: Scalar) -> Self {
91 Self(scalar)
92 }
93
94 pub fn get_scalar(&self) -> &Scalar {
95 &self.0
96 }
97
98 pub fn as_bytes(&self) -> &[u8; PEDERSEN_OPENING_LEN] {
99 self.0.as_bytes()
100 }
101
102 pub fn to_bytes(&self) -> [u8; PEDERSEN_OPENING_LEN] {
103 self.0.to_bytes()
104 }
105
106 pub fn from_bytes(bytes: &[u8]) -> Option<PedersenOpening> {
107 match bytes.try_into() {
108 Ok(bytes) => Scalar::from_canonical_bytes(bytes)
109 .into_option()
110 .map(PedersenOpening),
111 _ => None,
112 }
113 }
114}
115impl Eq for PedersenOpening {}
116impl PartialEq for PedersenOpening {
117 fn eq(&self, other: &Self) -> bool {
118 self.ct_eq(other).unwrap_u8() == 1u8
119 }
120}
121impl ConstantTimeEq for PedersenOpening {
122 fn ct_eq(&self, other: &Self) -> Choice {
123 self.0.ct_eq(&other.0)
124 }
125}
126
127impl<'b> Add<&'b PedersenOpening> for &PedersenOpening {
128 type Output = PedersenOpening;
129
130 fn add(self, opening: &'b PedersenOpening) -> PedersenOpening {
131 PedersenOpening(&self.0 + &opening.0)
132 }
133}
134
135define_add_variants!(
136 LHS = PedersenOpening,
137 RHS = PedersenOpening,
138 Output = PedersenOpening
139);
140
141impl<'b> Sub<&'b PedersenOpening> for &PedersenOpening {
142 type Output = PedersenOpening;
143
144 fn sub(self, opening: &'b PedersenOpening) -> PedersenOpening {
145 PedersenOpening(&self.0 - &opening.0)
146 }
147}
148
149define_sub_variants!(
150 LHS = PedersenOpening,
151 RHS = PedersenOpening,
152 Output = PedersenOpening
153);
154
155impl<'b> Mul<&'b Scalar> for &PedersenOpening {
156 type Output = PedersenOpening;
157
158 fn mul(self, scalar: &'b Scalar) -> PedersenOpening {
159 PedersenOpening(&self.0 * scalar)
160 }
161}
162
163define_mul_variants!(
164 LHS = PedersenOpening,
165 RHS = Scalar,
166 Output = PedersenOpening
167);
168
169impl<'b> Mul<&'b PedersenOpening> for &Scalar {
170 type Output = PedersenOpening;
171
172 fn mul(self, opening: &'b PedersenOpening) -> PedersenOpening {
173 PedersenOpening(self * &opening.0)
174 }
175}
176
177define_mul_variants!(
178 LHS = Scalar,
179 RHS = PedersenOpening,
180 Output = PedersenOpening
181);
182
183#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
185#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
186pub struct PedersenCommitment(RistrettoPoint);
187impl PedersenCommitment {
188 pub fn new(point: RistrettoPoint) -> Self {
189 Self(point)
190 }
191
192 pub fn get_point(&self) -> &RistrettoPoint {
193 &self.0
194 }
195
196 pub fn to_bytes(&self) -> [u8; PEDERSEN_COMMITMENT_LEN] {
197 self.0.compress().to_bytes()
198 }
199
200 pub fn from_bytes(bytes: &[u8]) -> Option<PedersenCommitment> {
201 if bytes.len() != PEDERSEN_COMMITMENT_LEN {
202 return None;
203 }
204
205 let Ok(compressed_ristretto) = CompressedRistretto::from_slice(bytes) else {
206 return None;
207 };
208
209 compressed_ristretto.decompress().map(PedersenCommitment)
210 }
211}
212
213impl<'b> Add<&'b PedersenCommitment> for &PedersenCommitment {
214 type Output = PedersenCommitment;
215
216 fn add(self, commitment: &'b PedersenCommitment) -> PedersenCommitment {
217 PedersenCommitment(&self.0 + &commitment.0)
218 }
219}
220
221define_add_variants!(
222 LHS = PedersenCommitment,
223 RHS = PedersenCommitment,
224 Output = PedersenCommitment
225);
226
227impl<'b> Sub<&'b PedersenCommitment> for &PedersenCommitment {
228 type Output = PedersenCommitment;
229
230 fn sub(self, commitment: &'b PedersenCommitment) -> PedersenCommitment {
231 PedersenCommitment(&self.0 - &commitment.0)
232 }
233}
234
235define_sub_variants!(
236 LHS = PedersenCommitment,
237 RHS = PedersenCommitment,
238 Output = PedersenCommitment
239);
240
241impl<'b> Mul<&'b Scalar> for &PedersenCommitment {
242 type Output = PedersenCommitment;
243
244 fn mul(self, scalar: &'b Scalar) -> PedersenCommitment {
245 PedersenCommitment(scalar * &self.0)
246 }
247}
248
249define_mul_variants!(
250 LHS = PedersenCommitment,
251 RHS = Scalar,
252 Output = PedersenCommitment
253);
254
255impl<'b> Mul<&'b PedersenCommitment> for &Scalar {
256 type Output = PedersenCommitment;
257
258 fn mul(self, commitment: &'b PedersenCommitment) -> PedersenCommitment {
259 PedersenCommitment(self * &commitment.0)
260 }
261}
262
263define_mul_variants!(
264 LHS = Scalar,
265 RHS = PedersenCommitment,
266 Output = PedersenCommitment
267);
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn test_pedersen_homomorphic_addition() {
275 let amount_0: u64 = 77;
276 let amount_1: u64 = 57;
277
278 let rng = &mut OsRng;
279 let opening_0 = PedersenOpening(Scalar::random(rng));
280 let opening_1 = PedersenOpening(Scalar::random(rng));
281
282 let commitment_0 = Pedersen::with(amount_0, &opening_0);
283 let commitment_1 = Pedersen::with(amount_1, &opening_1);
284 let commitment_addition = Pedersen::with(amount_0 + amount_1, &(opening_0 + opening_1));
285
286 assert_eq!(commitment_addition, commitment_0 + commitment_1);
287 }
288
289 #[test]
290 fn test_pedersen_homomorphic_subtraction() {
291 let amount_0: u64 = 77;
292 let amount_1: u64 = 57;
293
294 let rng = &mut OsRng;
295 let opening_0 = PedersenOpening(Scalar::random(rng));
296 let opening_1 = PedersenOpening(Scalar::random(rng));
297
298 let commitment_0 = Pedersen::with(amount_0, &opening_0);
299 let commitment_1 = Pedersen::with(amount_1, &opening_1);
300 let commitment_addition = Pedersen::with(amount_0 - amount_1, &(opening_0 - opening_1));
301
302 assert_eq!(commitment_addition, commitment_0 - commitment_1);
303 }
304
305 #[test]
306 fn test_pedersen_homomorphic_multiplication() {
307 let amount_0: u64 = 77;
308 let amount_1: u64 = 57;
309
310 let (commitment, opening) = Pedersen::new(amount_0);
311 let scalar = Scalar::from(amount_1);
312 let commitment_multiplication = Pedersen::with(amount_0 * amount_1, &(opening * scalar));
313
314 assert_eq!(commitment_multiplication, commitment * scalar);
315 assert_eq!(commitment_multiplication, scalar * commitment);
316 }
317
318 #[test]
319 fn test_pedersen_commitment_bytes() {
320 let amount: u64 = 77;
321 let (commitment, _) = Pedersen::new(amount);
322
323 let encoded = commitment.to_bytes();
324 let decoded = PedersenCommitment::from_bytes(&encoded).unwrap();
325
326 assert_eq!(commitment, decoded);
327
328 assert_eq!(PedersenCommitment::from_bytes(&[0; 33]), None);
330 }
331
332 #[test]
333 fn test_pedersen_opening_bytes() {
334 let opening = PedersenOpening(Scalar::random(&mut OsRng));
335
336 let encoded = opening.to_bytes();
337 let decoded = PedersenOpening::from_bytes(&encoded).unwrap();
338
339 assert_eq!(opening, decoded);
340
341 assert_eq!(PedersenOpening::from_bytes(&[0; 33]), None);
343 }
344
345 #[test]
346 fn test_serde_pedersen_commitment() {
347 let amount: u64 = 77;
348 let (commitment, _) = Pedersen::new(amount);
349
350 let encoded = bincode::serialize(&commitment).unwrap();
351 let decoded: PedersenCommitment = bincode::deserialize(&encoded).unwrap();
352
353 assert_eq!(commitment, decoded);
354 }
355
356 #[test]
357 fn test_serde_pedersen_opening() {
358 let opening = PedersenOpening(Scalar::random(&mut OsRng));
359
360 let encoded = bincode::serialize(&opening).unwrap();
361 let decoded: PedersenOpening = bincode::deserialize(&encoded).unwrap();
362
363 assert_eq!(opening, decoded);
364 }
365
366 #[test]
367 fn test_homomorphic_addition_with_zero() {
368 let amount: u64 = 77;
369 let (commitment_0, opening_0) = Pedersen::new(amount);
370 let (commitment_1, opening_1) = Pedersen::new(0_u64);
371
372 let expected_commitment = Pedersen::with(amount, &(opening_0.clone() + opening_1.clone()));
374 assert_eq!(expected_commitment, commitment_0 + commitment_1);
375 }
376
377 #[test]
378 fn test_pedersen_encode() {
379 let amount: u64 = 123;
380
381 let zero_opening = PedersenOpening::default(); let commitment_with_zero = Pedersen::with(amount, &zero_opening);
384
385 let encoded_commitment = Pedersen::encode(amount);
387
388 assert_eq!(encoded_commitment, commitment_with_zero);
389
390 let (random_commitment, _) = Pedersen::new(amount);
392 assert_ne!(encoded_commitment, random_commitment);
393 }
394
395 #[test]
396 fn test_invalid_commitment_verification() {
397 let amount: u64 = 50;
398 let wrong_amount: u64 = 51;
399
400 let (commitment, opening) = Pedersen::new(amount);
401
402 let forged_commitment = Pedersen::with(wrong_amount, &opening);
404
405 assert_ne!(commitment, forged_commitment);
406 }
407
408 #[test]
409 fn test_pedersen_commitment_from_invalid_bytes() {
410 let invalid_bytes = [
414 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
415 0, 0, 0,
416 ];
417
418 assert_eq!(PedersenCommitment::from_bytes(&invalid_bytes), None);
419 }
420}