Skip to main content

mpc_ristretto/
commitment.rs

1//! Pedersen commitment implementation, borrowed from
2//! https://github.com/dalek-cryptography/bulletproofs/blob/main/src/generators.rs#L29
3
4use curve25519_dalek::{
5    constants::{RISTRETTO_BASEPOINT_COMPRESSED, RISTRETTO_BASEPOINT_POINT},
6    ristretto::RistrettoPoint,
7    scalar::Scalar,
8    traits::MultiscalarMul,
9};
10use rand_core::{OsRng, RngCore};
11use sha3::{Digest, Sha3_512};
12
13/// Implementation of a Pedersen commitment scheme, modified from:
14/// https://github.com/dalek-cryptography/bulletproofs/blob/main/src/generators.rs#L29
15#[allow(non_snake_case)]
16#[derive(Copy, Clone)]
17pub(crate) struct PedersenGens {
18    /// Base for the committed value
19    pub B: RistrettoPoint,
20    /// Base for the blinding factor
21    pub B_blinding: RistrettoPoint,
22}
23
24impl PedersenGens {
25    /// Creates a Pedersen commitment using the value scalar and a blinding factor.
26    pub fn commit(&self, value: Scalar, blinding: Scalar) -> RistrettoPoint {
27        RistrettoPoint::multiscalar_mul(&[value, blinding], &[self.B, self.B_blinding])
28    }
29}
30
31impl Default for PedersenGens {
32    fn default() -> Self {
33        PedersenGens {
34            B: RISTRETTO_BASEPOINT_POINT,
35            B_blinding: RistrettoPoint::hash_from_bytes::<Sha3_512>(
36                RISTRETTO_BASEPOINT_COMPRESSED.as_bytes(),
37            ),
38        }
39    }
40}
41
42/// Represents a Pedersen commitment to and underlying value
43#[derive(Clone, Debug)]
44pub(crate) struct PedersenCommitment {
45    /// The commitment to x generated by xG + rH for randomness r
46    commitment: RistrettoPoint,
47    /// The blinding factor `r` used in commitment generation
48    blinding_factor: Scalar,
49    /// The underlying value `x` that has been comitted to
50    value: Scalar,
51}
52
53impl PedersenCommitment {
54    /// Fetch the commitment
55    #[inline]
56    pub fn get_commitment(&self) -> RistrettoPoint {
57        self.commitment
58    }
59
60    #[inline]
61    pub fn get_blinding(&self) -> Scalar {
62        self.blinding_factor
63    }
64
65    #[inline]
66    pub fn get_value(&self) -> Scalar {
67        self.value
68    }
69
70    /// Create a Pedersen commitment to a value
71    pub fn commit(value: Scalar) -> PedersenCommitment {
72        // Sample a secure random blinding scalar
73        let mut rng = OsRng {};
74        let blinding_factor = Scalar::random(&mut rng);
75
76        Self {
77            commitment: PedersenGens::default().commit(value, blinding_factor),
78            blinding_factor,
79            value,
80        }
81    }
82
83    /// Verify a commitment
84    #[allow(dead_code)]
85    pub fn verify(&self) -> bool {
86        PedersenCommitment::verify_from_values(self.commitment, self.blinding_factor, self.value)
87    }
88
89    /// A convenience method for verifying a commitment that does not require the user to
90    /// build a PedersenCommitment instance
91    pub fn verify_from_values(
92        commitment: RistrettoPoint,
93        blinding_factor: Scalar,
94        value: Scalar,
95    ) -> bool {
96        PedersenGens::default()
97            .commit(value, blinding_factor)
98            .eq(&commitment)
99    }
100}
101
102/// A hash commitment to a RistrettoPoint
103#[derive(Clone, Debug)]
104pub struct RistrettoCommitment {
105    /// The commitment to the underlying value
106    commitment: Scalar,
107    /// The randomness used to blind the input to the hash
108    blinding_factor: Scalar,
109    /// The underlying RistrettoPoint that has been salted and hashed into `commitment`
110    value: RistrettoPoint,
111}
112
113impl RistrettoCommitment {
114    #[inline]
115    pub fn get_commitment(&self) -> Scalar {
116        self.commitment
117    }
118
119    #[inline]
120    pub fn get_blinding(&self) -> Scalar {
121        self.blinding_factor
122    }
123
124    #[inline]
125    pub fn get_value(&self) -> RistrettoPoint {
126        self.value
127    }
128
129    /// Create a hash commitment from the given RistrettoPoint
130    pub fn commit(point: RistrettoPoint) -> RistrettoCommitment {
131        // Allocate an 8 byte buffer for the blinding factor and fill with random bytes
132        let mut rng = OsRng {};
133        let blinding_factor = rng.next_u64();
134
135        // Compute SHA3_512(point||blinding)
136        let mut hasher = Sha3_512::new();
137        hasher.input(point.compress().as_bytes());
138        hasher.input(blinding_factor.to_le_bytes());
139
140        Self {
141            commitment: Scalar::from_hash(hasher),
142            blinding_factor: Scalar::from(blinding_factor),
143            value: point,
144        }
145    }
146
147    /// Verify a commitment
148    pub fn verify(&self) -> bool {
149        RistrettoCommitment::verify_from_values(self.commitment, self.blinding_factor, self.value)
150    }
151
152    /// Verify a commitment from the values, avoid constructing a RistrettoCommitment instance
153    pub fn verify_from_values(
154        commitment: Scalar,
155        blinding_factor: Scalar,
156        value: RistrettoPoint,
157    ) -> bool {
158        // Hash the value and the blinding factor
159        let mut hasher = Sha3_512::new();
160        hasher.input(value.compress().as_bytes());
161
162        let blinding_factor_bytes: [u8; 8] = blinding_factor.to_bytes()[..8]
163            .try_into()
164            .expect("Not enough bytes in hash");
165        hasher.input(blinding_factor_bytes);
166
167        Scalar::from_hash(hasher).eq(&commitment)
168    }
169}
170
171#[cfg(test)]
172mod pedersen_tests {
173    use curve25519_dalek::scalar::Scalar;
174    use rand_core::OsRng;
175
176    use super::PedersenCommitment;
177
178    #[test]
179    fn test_commit_and_open() {
180        // Commit to a random value, open it correctly, then attempt to open it incorrectly
181        let mut rng = OsRng {};
182        let value = Scalar::random(&mut rng);
183        let bad_value = Scalar::random(&mut rng);
184
185        let commitment = PedersenCommitment::commit(value);
186        assert!(commitment.verify());
187        assert!(!PedersenCommitment::verify_from_values(
188            commitment.commitment,
189            commitment.blinding_factor,
190            bad_value
191        ))
192    }
193}
194
195#[cfg(test)]
196mod hash_commit_tests {
197    use curve25519_dalek::ristretto::RistrettoPoint;
198    use rand_core::OsRng;
199
200    use super::RistrettoCommitment;
201
202    #[test]
203    fn test_commit_and_open() {
204        // Commit to a random value, open it correctly, then attempt to open it incorrectly
205        let mut rng = OsRng {};
206        let value = RistrettoPoint::random(&mut rng);
207        let bad_value = RistrettoPoint::random(&mut rng);
208
209        let commitment = RistrettoCommitment::commit(value);
210        assert!(commitment.verify());
211        assert!(!RistrettoCommitment::verify_from_values(
212            commitment.get_commitment(),
213            commitment.get_blinding(),
214            bad_value
215        ))
216    }
217}