secp256k1_zkp/zkp/
rangeproof.rs

1use ffi::CPtr;
2
3use crate::ffi::RANGEPROOF_MAX_LENGTH;
4use crate::from_hex;
5use crate::Error;
6use crate::Generator;
7use crate::PedersenCommitment;
8use crate::Verification;
9use crate::{ffi, Secp256k1, SecretKey, Signing, Tweak};
10use std::ops::Range;
11use std::str;
12
13/// Represents a range proof.
14///
15/// TODO: Store rangeproof info
16#[derive(Debug, PartialEq, Clone, Eq, Hash, PartialOrd, Ord)]
17pub struct RangeProof {
18    inner: ffi::RangeProof,
19}
20
21impl RangeProof {
22    /// Serialize to bytes.
23    pub fn serialize(&self) -> Vec<u8> {
24        self.inner.to_bytes()
25    }
26
27    /// Parse from byte slice.
28    ///
29    /// TODO: Rename to parse (and other similar functions)
30    pub fn from_slice(bytes: &[u8]) -> Result<Self, Error> {
31        let mut exp = 0;
32        let mut mantissa = 0;
33        let mut min_value = 0;
34        let mut max_value = 0;
35
36        let ret = unsafe {
37            ffi::secp256k1_rangeproof_info(
38                ffi::secp256k1_context_no_precomp,
39                &mut exp,
40                &mut mantissa,
41                &mut min_value,
42                &mut max_value,
43                bytes.as_ptr(),
44                bytes.len(),
45            )
46        };
47
48        if ret == 0 {
49            return Err(Error::InvalidRangeProof);
50        }
51
52        Ok(RangeProof {
53            inner: ffi::RangeProof::new(bytes),
54        })
55    }
56
57    /// Get length.
58    pub fn len(&self) -> usize {
59        self.inner.len()
60    }
61
62    /// Check if it's empty.
63    pub fn is_empty(&self) -> bool {
64        self.inner.is_empty()
65    }
66
67    /// Prove that `commitment` hides a value within a range, with the lower bound set to `min_value`.
68    #[allow(clippy::too_many_arguments)]
69    pub fn new<C: Signing>(
70        secp: &Secp256k1<C>,
71        min_value: u64,
72        commitment: PedersenCommitment,
73        value: u64,
74        commitment_blinding: Tweak,
75        message: &[u8],
76        additional_commitment: &[u8],
77        sk: SecretKey,
78        exp: i32,
79        min_bits: u8,
80        additional_generator: Generator,
81    ) -> Result<RangeProof, Error> {
82        let mut proof = [0u8; RANGEPROOF_MAX_LENGTH];
83        let mut proof_length = RANGEPROOF_MAX_LENGTH;
84
85        let ret = unsafe {
86            ffi::secp256k1_rangeproof_sign(
87                secp.ctx().as_ptr(),
88                proof.as_mut_ptr(),
89                &mut proof_length,
90                min_value,
91                commitment.as_inner(),
92                commitment_blinding.as_c_ptr(),
93                sk.as_c_ptr(),
94                exp,
95                min_bits as i32,
96                value,
97                message.as_ptr(),
98                message.len(),
99                additional_commitment.as_ptr(),
100                additional_commitment.len(),
101                additional_generator.as_inner(),
102            )
103        };
104
105        if ret == 0 {
106            return Err(Error::CannotMakeRangeProof);
107        }
108
109        Ok(RangeProof {
110            inner: ffi::RangeProof::new(&proof[..proof_length]),
111        })
112    }
113
114    /// Verify that the committed value is within a range.
115    ///
116    /// If the verification is successful, return the actual range of possible values.
117    pub fn verify<C: Verification>(
118        &self,
119        secp: &Secp256k1<C>,
120        commitment: PedersenCommitment,
121        additional_commitment: &[u8],
122        additional_generator: Generator,
123    ) -> Result<Range<u64>, Error> {
124        let mut min_value = 0u64;
125        let mut max_value = 0u64;
126
127        let ret = unsafe {
128            ffi::secp256k1_rangeproof_verify(
129                secp.ctx().as_ptr(),
130                &mut min_value,
131                &mut max_value,
132                commitment.as_inner(),
133                self.inner.as_ptr(),
134                self.inner.len(),
135                additional_commitment.as_ptr(),
136                additional_commitment.len(),
137                additional_generator.as_inner(),
138            )
139        };
140
141        if ret == 0 {
142            return Err(Error::InvalidRangeProof);
143        }
144
145        Ok(Range {
146            start: min_value,
147            end: max_value + 1,
148        })
149    }
150
151    /// Verify a range proof proof and rewind the proof to recover information sent by its author.
152    pub fn rewind<C: Verification>(
153        &self,
154        secp: &Secp256k1<C>,
155        commitment: PedersenCommitment,
156        sk: SecretKey,
157        additional_commitment: &[u8],
158        additional_generator: Generator,
159    ) -> Result<(Opening, Range<u64>), Error> {
160        let mut min_value = 0u64;
161        let mut max_value = 0u64;
162
163        let mut blinding_factor = [0u8; 32];
164        let mut value = 0u64;
165        let mut message = [0u8; 4096];
166        let mut message_length = 4096usize;
167
168        let ret = unsafe {
169            ffi::secp256k1_rangeproof_rewind(
170                secp.ctx().as_ptr(),
171                blinding_factor.as_mut_ptr(),
172                &mut value,
173                message.as_mut_ptr(),
174                &mut message_length,
175                sk.as_c_ptr(),
176                &mut min_value,
177                &mut max_value,
178                commitment.as_inner(),
179                self.inner.as_ptr(),
180                self.inner.len(),
181                additional_commitment.as_ptr(),
182                additional_commitment.len(),
183                additional_generator.as_inner(),
184            )
185        };
186
187        if ret == 0 {
188            return Err(Error::InvalidRangeProof);
189        }
190
191        let opening = Opening {
192            value,
193            blinding_factor: Tweak::from_slice(&blinding_factor)?,
194            message: message[..message_length].into(),
195        };
196
197        let range = Range {
198            start: min_value,
199            end: max_value + 1,
200        };
201
202        Ok((opening, range))
203    }
204}
205
206#[cfg(feature = "hashes")]
207impl ::core::fmt::Display for RangeProof {
208    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
209        use internals::hex::display::DisplayHex;
210
211        write!(f, "{:x}", &self.serialize().as_slice().as_hex())
212    }
213}
214
215impl str::FromStr for RangeProof {
216    type Err = Error;
217    fn from_str(s: &str) -> Result<RangeProof, Error> {
218        let mut res = vec![0u8; s.len() / 2];
219        match from_hex(s, &mut res) {
220            Ok(_) => RangeProof::from_slice(&res),
221            _ => Err(Error::InvalidRangeProof),
222        }
223    }
224}
225
226#[cfg(all(feature = "serde", feature = "hashes"))]
227impl ::serde::Serialize for RangeProof {
228    fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
229        if s.is_human_readable() {
230            s.collect_str(&self)
231        } else {
232            s.serialize_bytes(&self.serialize())
233        }
234    }
235}
236
237#[cfg(all(feature = "serde", feature = "hashes"))]
238impl<'de> ::serde::Deserialize<'de> for RangeProof {
239    fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<RangeProof, D::Error> {
240        use crate::serde_util;
241
242        if d.is_human_readable() {
243            d.deserialize_str(serde_util::FromStrVisitor::new("an ASCII hex string"))
244        } else {
245            d.deserialize_bytes(serde_util::BytesVisitor::new(
246                "a bytestring",
247                RangeProof::from_slice,
248            ))
249        }
250    }
251}
252
253/// The result of rewinding a range proof.
254///
255/// Rewinding a range proof reveals ("opens") the stored information and allows us to access information the prover embedded in the proof.
256pub struct Opening {
257    /// The value that the prover originally committed to in the Pedersen commitment.
258    pub value: u64,
259    /// The blinding factor that was used to create the Pedersen commitment of above value.
260    pub blinding_factor: Tweak,
261    /// The message that was embedded by the prover.
262    pub message: Box<[u8]>,
263}
264
265#[cfg(all(test, feature = "global-context"))] // use global context for convenience
266mod tests {
267    use super::*;
268    use crate::{CommitmentSecrets, Tag, SECP256K1};
269    use rand::thread_rng;
270
271    #[cfg(target_arch = "wasm32")]
272    use wasm_bindgen_test::wasm_bindgen_test as test;
273
274    #[test]
275    fn create_and_verify_range_proof() {
276        let value = 1_000;
277        let commitment_secrets = CommitmentSecrets::random(value);
278        let tag = Tag::random();
279        let commitment = commitment_secrets.commit(tag);
280
281        let message = b"foo";
282        let additional_commitment = b"bar";
283
284        let sk = SecretKey::new(&mut thread_rng());
285        let additional_generator =
286            Generator::new_blinded(SECP256K1, tag, commitment_secrets.generator_blinding_factor);
287
288        let proof = RangeProof::new(
289            SECP256K1,
290            1,
291            commitment,
292            value,
293            commitment_secrets.value_blinding_factor,
294            message,
295            additional_commitment,
296            sk,
297            0,
298            52,
299            additional_generator,
300        )
301        .unwrap();
302
303        proof
304            .verify(
305                SECP256K1,
306                commitment,
307                additional_commitment,
308                additional_generator,
309            )
310            .unwrap();
311
312        #[cfg(feature = "hashes")]
313        {
314            use std::str::FromStr;
315            use std::string::ToString;
316            let proof_str = proof.to_string();
317            assert_eq!(proof, RangeProof::from_str(&proof_str).unwrap());
318        }
319    }
320
321    #[test]
322    fn rewind_range_proof() {
323        let value = 1_000;
324        let commitment_secrets = CommitmentSecrets::random(value);
325        let tag = Tag::random();
326        let commitment = commitment_secrets.commit(tag);
327
328        let message = b"foo";
329        let additional_commitment = b"bar";
330
331        let sk = SecretKey::new(&mut thread_rng());
332        let additional_generator =
333            Generator::new_blinded(SECP256K1, tag, commitment_secrets.generator_blinding_factor);
334
335        let proof = RangeProof::new(
336            SECP256K1,
337            1,
338            commitment,
339            value,
340            commitment_secrets.value_blinding_factor,
341            message,
342            additional_commitment,
343            sk,
344            0,
345            52,
346            additional_generator,
347        )
348        .unwrap();
349
350        let (opening, _range) = proof
351            .rewind(
352                SECP256K1,
353                commitment,
354                sk,
355                additional_commitment,
356                additional_generator,
357            )
358            .unwrap();
359
360        assert_eq!(opening.value, commitment_secrets.value);
361        assert_eq!(
362            opening.blinding_factor,
363            commitment_secrets.value_blinding_factor
364        );
365
366        assert!(opening.message.starts_with(message));
367        assert!(opening
368            .message
369            .ends_with(&vec![0; opening.message.len() - message.len()]));
370    }
371}