solana_zk_sdk/encryption/
pedersen.rs

1//! Pedersen commitment implementation using the Ristretto prime-order group.
2
3#[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    /// Pedersen base point for encoding messages to be committed.
24    pub static ref G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT;
25    /// Pedersen base point for encoding the commitment openings.
26    pub static ref H: RistrettoPoint =
27        RistrettoPoint::hash_from_bytes::<Sha3_512>(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());
28}
29
30/// Algorithm handle for the Pedersen commitment scheme.
31#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
32pub struct Pedersen;
33impl Pedersen {
34    /// On input a message (numeric amount), the function returns a Pedersen commitment of the
35    /// message and the corresponding opening.
36    ///
37    /// This function is randomized. It internally samples a Pedersen opening using `OsRng`.
38    #[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    /// On input a message (numeric amount) and a Pedersen opening, the function returns the
47    /// corresponding Pedersen commitment.
48    ///
49    /// This function is deterministic.
50    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    /// On input a message (numeric amount), the function returns a Pedersen commitment with zero
58    /// as the opening.
59    ///
60    /// This function is deterministic.
61    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/// Pedersen opening type.
75///
76/// Instances of Pedersen openings are zeroized on drop.
77#[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/// Pedersen commitment type.
185#[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        // incorrect length encoding
330        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        // incorrect length encoding
343        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}