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