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
22lazy_static::lazy_static! {
23 pub static ref G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT;
25 pub static ref H: RistrettoPoint =
27 RistrettoPoint::hash_from_bytes::<Sha3_512>(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());
28}
29
30#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
32pub struct Pedersen;
33impl Pedersen {
34 #[allow(clippy::new_ret_no_self)]
39 pub fn new<T: Into<Scalar>>(amount: T) -> (PedersenCommitment, PedersenOpening) {
40 let opening = PedersenOpening::new_rand();
41 let commitment = Pedersen::with(amount, &opening);
42
43 (commitment, opening)
44 }
45
46 pub fn with<T: Into<Scalar>>(amount: T, opening: &PedersenOpening) -> PedersenCommitment {
51 let x: Scalar = amount.into();
52 let r = opening.get_scalar();
53
54 PedersenCommitment(RistrettoPoint::multiscalar_mul(&[x, *r], &[*G, *H]))
55 }
56
57 pub fn encode<T: Into<Scalar>>(amount: T) -> PedersenCommitment {
62 PedersenCommitment(amount.into() * &(*G))
63 }
64}
65
66#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
67impl Pedersen {
68 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = withU64))]
69 pub fn with_u64(amount: u64, opening: &PedersenOpening) -> PedersenCommitment {
70 Pedersen::with(amount, opening)
71 }
72}
73
74#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
78#[derive(Clone, Debug, Default, Serialize, Deserialize, Zeroize)]
79#[zeroize(drop)]
80pub struct PedersenOpening(Scalar);
81
82#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
83impl PedersenOpening {
84 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = newRand))]
85 pub fn new_rand() -> Self {
86 PedersenOpening(Scalar::random(&mut OsRng))
87 }
88}
89
90impl PedersenOpening {
91 pub fn new(scalar: Scalar) -> Self {
92 Self(scalar)
93 }
94
95 pub fn get_scalar(&self) -> &Scalar {
96 &self.0
97 }
98
99 pub fn as_bytes(&self) -> &[u8; PEDERSEN_OPENING_LEN] {
100 self.0.as_bytes()
101 }
102
103 pub fn to_bytes(&self) -> [u8; PEDERSEN_OPENING_LEN] {
104 self.0.to_bytes()
105 }
106
107 pub fn from_bytes(bytes: &[u8]) -> Option<PedersenOpening> {
108 match bytes.try_into() {
109 Ok(bytes) => Scalar::from_canonical_bytes(bytes)
110 .into_option()
111 .map(PedersenOpening),
112 _ => None,
113 }
114 }
115}
116impl Eq for PedersenOpening {}
117impl PartialEq for PedersenOpening {
118 fn eq(&self, other: &Self) -> bool {
119 self.ct_eq(other).unwrap_u8() == 1u8
120 }
121}
122impl ConstantTimeEq for PedersenOpening {
123 fn ct_eq(&self, other: &Self) -> Choice {
124 self.0.ct_eq(&other.0)
125 }
126}
127
128impl<'b> Add<&'b PedersenOpening> for &PedersenOpening {
129 type Output = PedersenOpening;
130
131 fn add(self, opening: &'b PedersenOpening) -> PedersenOpening {
132 PedersenOpening(&self.0 + &opening.0)
133 }
134}
135
136define_add_variants!(
137 LHS = PedersenOpening,
138 RHS = PedersenOpening,
139 Output = PedersenOpening
140);
141
142impl<'b> Sub<&'b PedersenOpening> for &PedersenOpening {
143 type Output = PedersenOpening;
144
145 fn sub(self, opening: &'b PedersenOpening) -> PedersenOpening {
146 PedersenOpening(&self.0 - &opening.0)
147 }
148}
149
150define_sub_variants!(
151 LHS = PedersenOpening,
152 RHS = PedersenOpening,
153 Output = PedersenOpening
154);
155
156impl<'b> Mul<&'b Scalar> for &PedersenOpening {
157 type Output = PedersenOpening;
158
159 fn mul(self, scalar: &'b Scalar) -> PedersenOpening {
160 PedersenOpening(&self.0 * scalar)
161 }
162}
163
164define_mul_variants!(
165 LHS = PedersenOpening,
166 RHS = Scalar,
167 Output = PedersenOpening
168);
169
170impl<'b> Mul<&'b PedersenOpening> for &Scalar {
171 type Output = PedersenOpening;
172
173 fn mul(self, opening: &'b PedersenOpening) -> PedersenOpening {
174 PedersenOpening(self * &opening.0)
175 }
176}
177
178define_mul_variants!(
179 LHS = Scalar,
180 RHS = PedersenOpening,
181 Output = PedersenOpening
182);
183
184#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
186#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
187pub struct PedersenCommitment(RistrettoPoint);
188impl PedersenCommitment {
189 pub fn new(point: RistrettoPoint) -> Self {
190 Self(point)
191 }
192
193 pub fn get_point(&self) -> &RistrettoPoint {
194 &self.0
195 }
196
197 pub fn to_bytes(&self) -> [u8; PEDERSEN_COMMITMENT_LEN] {
198 self.0.compress().to_bytes()
199 }
200
201 pub fn from_bytes(bytes: &[u8]) -> Option<PedersenCommitment> {
202 if bytes.len() != PEDERSEN_COMMITMENT_LEN {
203 return None;
204 }
205
206 let Ok(compressed_ristretto) = CompressedRistretto::from_slice(bytes) else {
207 return None;
208 };
209
210 compressed_ristretto.decompress().map(PedersenCommitment)
211 }
212}
213
214impl<'b> Add<&'b PedersenCommitment> for &PedersenCommitment {
215 type Output = PedersenCommitment;
216
217 fn add(self, commitment: &'b PedersenCommitment) -> PedersenCommitment {
218 PedersenCommitment(&self.0 + &commitment.0)
219 }
220}
221
222define_add_variants!(
223 LHS = PedersenCommitment,
224 RHS = PedersenCommitment,
225 Output = PedersenCommitment
226);
227
228impl<'b> Sub<&'b PedersenCommitment> for &PedersenCommitment {
229 type Output = PedersenCommitment;
230
231 fn sub(self, commitment: &'b PedersenCommitment) -> PedersenCommitment {
232 PedersenCommitment(&self.0 - &commitment.0)
233 }
234}
235
236define_sub_variants!(
237 LHS = PedersenCommitment,
238 RHS = PedersenCommitment,
239 Output = PedersenCommitment
240);
241
242impl<'b> Mul<&'b Scalar> for &PedersenCommitment {
243 type Output = PedersenCommitment;
244
245 fn mul(self, scalar: &'b Scalar) -> PedersenCommitment {
246 PedersenCommitment(scalar * &self.0)
247 }
248}
249
250define_mul_variants!(
251 LHS = PedersenCommitment,
252 RHS = Scalar,
253 Output = PedersenCommitment
254);
255
256impl<'b> Mul<&'b PedersenCommitment> for &Scalar {
257 type Output = PedersenCommitment;
258
259 fn mul(self, commitment: &'b PedersenCommitment) -> PedersenCommitment {
260 PedersenCommitment(self * &commitment.0)
261 }
262}
263
264define_mul_variants!(
265 LHS = Scalar,
266 RHS = PedersenCommitment,
267 Output = PedersenCommitment
268);
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn test_pedersen_homomorphic_addition() {
276 let amount_0: u64 = 77;
277 let amount_1: u64 = 57;
278
279 let rng = &mut OsRng;
280 let opening_0 = PedersenOpening(Scalar::random(rng));
281 let opening_1 = PedersenOpening(Scalar::random(rng));
282
283 let commitment_0 = Pedersen::with(amount_0, &opening_0);
284 let commitment_1 = Pedersen::with(amount_1, &opening_1);
285 let commitment_addition = Pedersen::with(amount_0 + amount_1, &(opening_0 + opening_1));
286
287 assert_eq!(commitment_addition, commitment_0 + commitment_1);
288 }
289
290 #[test]
291 fn test_pedersen_homomorphic_subtraction() {
292 let amount_0: u64 = 77;
293 let amount_1: u64 = 57;
294
295 let rng = &mut OsRng;
296 let opening_0 = PedersenOpening(Scalar::random(rng));
297 let opening_1 = PedersenOpening(Scalar::random(rng));
298
299 let commitment_0 = Pedersen::with(amount_0, &opening_0);
300 let commitment_1 = Pedersen::with(amount_1, &opening_1);
301 let commitment_addition = Pedersen::with(amount_0 - amount_1, &(opening_0 - opening_1));
302
303 assert_eq!(commitment_addition, commitment_0 - commitment_1);
304 }
305
306 #[test]
307 fn test_pedersen_homomorphic_multiplication() {
308 let amount_0: u64 = 77;
309 let amount_1: u64 = 57;
310
311 let (commitment, opening) = Pedersen::new(amount_0);
312 let scalar = Scalar::from(amount_1);
313 let commitment_addition = Pedersen::with(amount_0 * amount_1, &(opening * scalar));
314
315 assert_eq!(commitment_addition, commitment * scalar);
316 assert_eq!(commitment_addition, scalar * commitment);
317 }
318
319 #[test]
320 fn test_pedersen_commitment_bytes() {
321 let amount: u64 = 77;
322 let (commitment, _) = Pedersen::new(amount);
323
324 let encoded = commitment.to_bytes();
325 let decoded = PedersenCommitment::from_bytes(&encoded).unwrap();
326
327 assert_eq!(commitment, decoded);
328
329 assert_eq!(PedersenCommitment::from_bytes(&[0; 33]), None);
331 }
332
333 #[test]
334 fn test_pedersen_opening_bytes() {
335 let opening = PedersenOpening(Scalar::random(&mut OsRng));
336
337 let encoded = opening.to_bytes();
338 let decoded = PedersenOpening::from_bytes(&encoded).unwrap();
339
340 assert_eq!(opening, decoded);
341
342 assert_eq!(PedersenOpening::from_bytes(&[0; 33]), None);
344 }
345
346 #[test]
347 fn test_serde_pedersen_commitment() {
348 let amount: u64 = 77;
349 let (commitment, _) = Pedersen::new(amount);
350
351 let encoded = bincode::serialize(&commitment).unwrap();
352 let decoded: PedersenCommitment = bincode::deserialize(&encoded).unwrap();
353
354 assert_eq!(commitment, decoded);
355 }
356
357 #[test]
358 fn test_serde_pedersen_opening() {
359 let opening = PedersenOpening(Scalar::random(&mut OsRng));
360
361 let encoded = bincode::serialize(&opening).unwrap();
362 let decoded: PedersenOpening = bincode::deserialize(&encoded).unwrap();
363
364 assert_eq!(opening, decoded);
365 }
366}