secp256k1_zkp/zkp/
pedersen.rs

1use ffi::CPtr;
2
3use crate::ffi;
4use crate::{from_hex, Error, Generator, Secp256k1, Signing, Tweak, ZERO_TWEAK};
5use core::{fmt, slice, str};
6
7/// Represents a commitment to a single u64 value.
8#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash, PartialOrd, Ord)]
9pub struct PedersenCommitment(ffi::PedersenCommitment);
10
11impl PedersenCommitment {
12    /// Serialize a pedersen commitment.
13    ///
14    /// The format of this serialization is stable and platform-independent.
15    pub fn serialize(&self) -> [u8; 33] {
16        let mut bytes = [0u8; 33];
17
18        let ret = unsafe {
19            ffi::secp256k1_pedersen_commitment_serialize(
20                ffi::secp256k1_context_no_precomp,
21                bytes.as_mut_ptr(),
22                &self.0,
23            )
24        };
25        assert_eq!(ret, 1, "failed to serialize pedersen commitment");
26
27        bytes
28    }
29
30    /// Parse a pedersen commitment from a byte slice.
31    pub fn from_slice(bytes: &[u8]) -> Result<Self, Error> {
32        let mut commitment = ffi::PedersenCommitment::default();
33
34        let ret = unsafe {
35            ffi::secp256k1_pedersen_commitment_parse(
36                ffi::secp256k1_context_no_precomp,
37                &mut commitment,
38                bytes.as_ptr(),
39            )
40        };
41
42        if ret != 1 {
43            return Err(Error::InvalidPedersenCommitment);
44        }
45
46        Ok(PedersenCommitment(commitment))
47    }
48
49    /// Create a new [`PedersenCommitment`] that commits to the given value with
50    /// a certain blinding factor and generator.
51    /// Use the [PedersenCommitment::new_unblinded] for creating a commitment
52    /// using zero blinding factor.
53    pub fn new<C: Signing>(
54        secp: &Secp256k1<C>,
55        value: u64,
56        blinding_factor: Tweak,
57        generator: Generator,
58    ) -> Self {
59        let mut commitment = ffi::PedersenCommitment::default();
60
61        let ret = unsafe {
62            ffi::secp256k1_pedersen_commit(
63                secp.ctx().as_ptr(),
64                &mut commitment,
65                blinding_factor.as_c_ptr(),
66                value,
67                generator.as_inner(),
68            )
69        };
70        assert_eq!(
71            ret, 1,
72            "failed to create pedersen commitment, likely a bad blinding factor"
73        );
74
75        PedersenCommitment(commitment)
76    }
77
78    /// Create a new [`PedersenCommitment`] that commits to the given value
79    /// with a zero blinding factor and the [`Generator`].
80    pub fn new_unblinded<C: Signing>(
81        secp: &Secp256k1<C>,
82        value: u64,
83        generator: Generator,
84    ) -> Self {
85        PedersenCommitment::new(secp, value, ZERO_TWEAK, generator)
86    }
87
88    pub(crate) fn as_inner(&self) -> &ffi::PedersenCommitment {
89        &self.0
90    }
91}
92
93/// Represents all secret data involved in making a [`PedersenCommitment`] where one of the generators is blinded.
94///
95/// A Pedersen commitment of the form `P = vT' + r'G` can be expressed as `vT + (vr + r')G` if `T' = T + rG` with:
96/// - `v` = `value`
97/// - `T` being a public key generated from a [`crate::Tag`]
98/// - `r` = `generator_blinding_factor`
99/// - `r'` = `value_blinding_factor`
100#[derive(Debug)]
101pub struct CommitmentSecrets {
102    /// The value that is committed to.
103    pub value: u64,
104    /// The blinding factor used when committing to the value.
105    pub value_blinding_factor: Tweak,
106    /// The blinding factor used when producing the [`Generator`] that is necessary to commit to the value.
107    pub generator_blinding_factor: Tweak,
108}
109
110impl CommitmentSecrets {
111    /// Constructor.
112    pub fn new(value: u64, value_blinding_factor: Tweak, generator_blinding_factor: Tweak) -> Self {
113        CommitmentSecrets {
114            value,
115            value_blinding_factor,
116            generator_blinding_factor,
117        }
118    }
119}
120
121/// Compute a blinding factor such that the sum of all blinding factors in both sets is equal.
122pub fn compute_adaptive_blinding_factor<C: Signing>(
123    secp: &Secp256k1<C>,
124    value: u64,
125    generator_blinding_factor: Tweak,
126    set_a: &[CommitmentSecrets],
127    set_b: &[CommitmentSecrets],
128) -> Tweak {
129    let value_blinding_factor_placeholder = ZERO_TWEAK; // this placeholder will be filled with the generated blinding factor
130
131    let (mut values, mut secrets) = set_a
132        .iter()
133        .chain(set_b.iter())
134        .map(|c| {
135            (
136                c.value,
137                (c.value_blinding_factor, c.generator_blinding_factor),
138            )
139        })
140        .unzip::<_, _, Vec<_>, Vec<_>>();
141    values.push(value);
142    secrets.push((value_blinding_factor_placeholder, generator_blinding_factor));
143
144    let (vbf, gbf) = secrets
145        .iter_mut()
146        .map(|(s_v, s_g)| (s_v.as_mut_c_ptr(), s_g.as_c_ptr()))
147        .unzip::<_, _, Vec<_>, Vec<_>>();
148
149    let ret = unsafe {
150        ffi::secp256k1_pedersen_blind_generator_blind_sum(
151            secp.ctx().as_ptr(),
152            values.as_ptr(),
153            gbf.as_ptr(),
154            vbf.as_ptr(),
155            set_a.len() + set_b.len() + 1,
156            set_a.len(),
157        )
158    };
159    assert_eq!(1, ret, "failed to compute blinding factor");
160
161    let last = vbf.last().expect("this vector is never empty");
162    let slice = unsafe { slice::from_raw_parts(*last, 32) };
163    Tweak::from_slice(slice).expect("data is always 32 bytes")
164}
165
166/// Verifies that the sum of the committed values within the commitments of both sets is equal.
167#[must_use]
168pub fn verify_commitments_sum_to_equal<C: Signing>(
169    secp: &Secp256k1<C>,
170    a: &[PedersenCommitment],
171    b: &[PedersenCommitment],
172) -> bool {
173    let a = a.iter().map(|c| &c.0).collect::<Vec<_>>();
174    let b = b.iter().map(|c| &c.0).collect::<Vec<_>>();
175
176    let ret = unsafe {
177        ffi::secp256k1_pedersen_verify_tally(
178            secp.ctx().as_ptr(),
179            a.as_ptr(),
180            a.len(),
181            b.as_ptr(),
182            b.len(),
183        )
184    };
185
186    ret == 1
187}
188
189impl fmt::LowerHex for PedersenCommitment {
190    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
191        let ser = self.serialize();
192        for ch in &ser[..] {
193            write!(f, "{:02x}", *ch)?;
194        }
195        Ok(())
196    }
197}
198
199impl fmt::Display for PedersenCommitment {
200    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201        fmt::LowerHex::fmt(self, f)
202    }
203}
204
205impl str::FromStr for PedersenCommitment {
206    type Err = Error;
207    fn from_str(s: &str) -> Result<Self, Error> {
208        let mut res = [0; 33];
209        match from_hex(s, &mut res) {
210            Ok(33) => Self::from_slice(&res[0..33]),
211            _ => Err(Error::InvalidPedersenCommitment),
212        }
213    }
214}
215
216#[cfg(feature = "serde")]
217impl ::serde::Serialize for PedersenCommitment {
218    fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
219        if s.is_human_readable() {
220            s.collect_str(self)
221        } else {
222            s.serialize_bytes(&self.serialize())
223        }
224    }
225}
226
227#[cfg(feature = "serde")]
228impl<'de> ::serde::Deserialize<'de> for PedersenCommitment {
229    fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
230        use crate::serde_util;
231
232        if d.is_human_readable() {
233            d.deserialize_str(serde_util::FromStrVisitor::new("an ASCII hex string"))
234        } else {
235            d.deserialize_bytes(serde_util::BytesVisitor::new(
236                "a bytestring",
237                PedersenCommitment::from_slice,
238            ))
239        }
240    }
241}
242
243#[cfg(all(test, feature = "global-context"))]
244mod tests {
245    use std::str::FromStr;
246
247    use super::*;
248    use crate::{Tag, SECP256K1};
249    use rand::thread_rng;
250
251    #[cfg(target_arch = "wasm32")]
252    use wasm_bindgen_test::wasm_bindgen_test as test;
253
254    impl CommitmentSecrets {
255        pub fn random(value: u64) -> Self {
256            Self {
257                value,
258                value_blinding_factor: Tweak::new(&mut thread_rng()),
259                generator_blinding_factor: Tweak::new(&mut thread_rng()),
260            }
261        }
262
263        pub fn commit(&self, tag: Tag) -> PedersenCommitment {
264            let generator = Generator::new_blinded(SECP256K1, tag, self.generator_blinding_factor);
265
266            PedersenCommitment::new(SECP256K1, self.value, self.value_blinding_factor, generator)
267        }
268    }
269
270    #[test]
271    fn test_unblinded_pedersen_commitments() {
272        let tag = Tag::random();
273        let unblinded_gen = Generator::new_unblinded(SECP256K1, tag);
274        let one_comm = PedersenCommitment::new_unblinded(SECP256K1, 1, unblinded_gen); // 1*G
275        let two_comm = PedersenCommitment::new_unblinded(SECP256K1, 2, unblinded_gen); // 2*G
276        let three_comm = PedersenCommitment::new_unblinded(SECP256K1, 3, unblinded_gen); // 3*G
277        let six_comm = PedersenCommitment::new_unblinded(SECP256K1, 6, unblinded_gen); // 6*G
278
279        let commitment_sums_are_equal = verify_commitments_sum_to_equal(
280            SECP256K1,
281            &[one_comm, two_comm, three_comm],
282            &[six_comm],
283        );
284
285        assert!(commitment_sums_are_equal);
286    }
287
288    #[test]
289    fn test_serialize_and_parse_pedersen_commitment() {
290        let commitment = CommitmentSecrets::random(1000).commit(Tag::random());
291
292        let bytes = commitment.serialize();
293        let got = PedersenCommitment::from_slice(&bytes).unwrap();
294
295        assert_eq!(got, commitment);
296    }
297
298    #[test]
299    fn test_equal_sum_of_commitments() {
300        let tag_1 = Tag::random();
301        let tag_2 = Tag::random();
302
303        let secrets_1 = CommitmentSecrets::random(1000);
304        let commitment_1 = secrets_1.commit(tag_1);
305        let secrets_2 = CommitmentSecrets::random(1000);
306        let commitment_2 = secrets_2.commit(tag_2);
307
308        let secrets_3 = CommitmentSecrets::random(1000);
309        let commitment_3 = secrets_3.commit(tag_1);
310
311        let tbf_4 = Tweak::new(&mut thread_rng());
312        let blinded_tag_4 = Generator::new_blinded(SECP256K1, tag_2, tbf_4);
313        let vbf_4 = compute_adaptive_blinding_factor(
314            SECP256K1,
315            1000,
316            tbf_4,
317            &[secrets_1, secrets_2],
318            &[secrets_3],
319        );
320        let commitment_4 = PedersenCommitment::new(SECP256K1, 1000, vbf_4, blinded_tag_4);
321
322        let commitment_sums_are_equal = verify_commitments_sum_to_equal(
323            SECP256K1,
324            &[commitment_1, commitment_2],
325            &[commitment_3, commitment_4],
326        );
327
328        assert!(commitment_sums_are_equal);
329    }
330
331    #[test]
332    fn test_pedersen_from_str() {
333        let commitment = CommitmentSecrets::random(1000).commit(Tag::random());
334
335        let string = commitment.to_string();
336        let from_str = PedersenCommitment::from_str(&string);
337
338        assert_eq!(Ok(commitment), from_str)
339    }
340
341    #[cfg(feature = "serde")]
342    #[test]
343    fn test_pedersen_de_serialization() {
344        use serde_test::Configure;
345        use serde_test::{assert_tokens, Token};
346
347        let commitment = PedersenCommitment::from_slice(&[
348            9, 7, 166, 63, 171, 227, 228, 157, 87, 19, 233, 218, 252, 171, 254, 202, 228, 138, 19,
349            124, 26, 29, 131, 42, 33, 212, 151, 151, 89, 0, 135, 201, 254,
350        ])
351        .unwrap();
352
353        assert_tokens(
354            &commitment.readable(),
355            &[Token::Str(
356                "0907a63fabe3e49d5713e9dafcabfecae48a137c1a1d832a21d49797590087c9fe",
357            )],
358        );
359
360        assert_tokens(
361            &commitment.compact(),
362            &[Token::Bytes(&[
363                9, 7, 166, 63, 171, 227, 228, 157, 87, 19, 233, 218, 252, 171, 254, 202, 228, 138,
364                19, 124, 26, 29, 131, 42, 33, 212, 151, 151, 89, 0, 135, 201, 254,
365            ])],
366        );
367    }
368
369    // TODO: Test prefix of serialization
370}