secp256k1_zkp/zkp/
whitelist.rs

1//! Bindings for the "whitelist" ring signature implementation in secp256k1-zkp.
2//!
3//! This implementation is used for Liquid PAK list inclusion proofs.
4
5#[cfg(feature = "std")]
6use std::{fmt, str};
7
8use crate::ffi::CPtr;
9#[cfg(feature = "std")]
10use crate::from_hex;
11use crate::{ffi, Error, PublicKey, Secp256k1, SecretKey, Signing, Verification};
12
13/// A whitelist ring signature.
14#[derive(Clone, PartialEq, Eq, Hash)]
15#[repr(transparent)]
16pub struct WhitelistSignature(ffi::WhitelistSignature);
17
18impl WhitelistSignature {
19    /// Number of keys in the whitelist.
20    pub fn n_keys(&self) -> usize {
21        self.0.n_keys
22    }
23
24    /// Serialize to bytes.
25    #[cfg(feature = "std")]
26    pub fn serialize(&self) -> Vec<u8> {
27        let mut buf = vec![0; 33 + 32 * self.n_keys()];
28
29        let mut out_len = buf.len();
30        let ret = unsafe {
31            ffi::secp256k1_whitelist_signature_serialize(
32                ffi::secp256k1_context_no_precomp,
33                buf.as_mut_ptr(),
34                &mut out_len,
35                &self.0,
36            )
37        };
38        assert_eq!(ret, 1, "failed to serialize whitelist signature");
39        assert_eq!(
40            out_len,
41            buf.len(),
42            "whitelist serialized to unexpected length"
43        );
44
45        buf
46    }
47
48    /// Parse a whitelist ring signature from a byte slice.
49    pub fn from_slice(bytes: &[u8]) -> Result<Self, Error> {
50        let mut sig = ffi::WhitelistSignature::default();
51
52        let ret = unsafe {
53            ffi::secp256k1_whitelist_signature_parse(
54                ffi::secp256k1_context_no_precomp,
55                &mut sig,
56                bytes.as_ptr(),
57                bytes.len(),
58            )
59        };
60        if ret != 1 {
61            return Err(Error::InvalidWhitelistSignature);
62        }
63
64        Ok(WhitelistSignature(sig))
65    }
66
67    /// Create a new whitelist ring signature for the given PAK list and whitelist key.
68    pub fn new<C: Signing>(
69        secp: &Secp256k1<C>,
70        online_keys: &[PublicKey],
71        offline_keys: &[PublicKey],
72        whitelist_key: &PublicKey,
73        online_secret_key: &SecretKey,
74        summed_secret_key: &SecretKey,
75        key_index: usize,
76    ) -> Result<WhitelistSignature, Error> {
77        if online_keys.len() != offline_keys.len() {
78            return Err(Error::InvalidPakList);
79        }
80        let n_keys = online_keys.len();
81
82        let mut sig = ffi::WhitelistSignature::default();
83        let ret = unsafe {
84            ffi::secp256k1_whitelist_sign(
85                secp.ctx().as_ptr(),
86                &mut sig,
87                // These two casts are legit because PublicKey has repr(transparent).
88                online_keys.as_c_ptr() as *const ffi::PublicKey,
89                offline_keys.as_c_ptr() as *const ffi::PublicKey,
90                n_keys,
91                whitelist_key.as_c_ptr(),
92                online_secret_key.as_c_ptr(),
93                summed_secret_key.as_c_ptr(),
94                key_index,
95            )
96        };
97        if ret != 1 {
98            return Err(Error::CannotCreateWhitelistSignature);
99        }
100
101        Ok(WhitelistSignature(sig))
102    }
103
104    /// Verify the given whitelist signature against the PAK list and whitelist key.
105    pub fn verify<C: Verification>(
106        &self,
107        secp: &Secp256k1<C>,
108        online_keys: &[PublicKey],
109        offline_keys: &[PublicKey],
110        whitelist_key: &PublicKey,
111    ) -> Result<(), Error> {
112        if online_keys.len() != offline_keys.len() {
113            return Err(Error::InvalidPakList);
114        }
115        let n_keys = online_keys.len();
116
117        let ret = unsafe {
118            ffi::secp256k1_whitelist_verify(
119                secp.ctx().as_ptr(),
120                &self.0,
121                // These two casts are legit because PublicKey has repr(transparent).
122                online_keys.as_c_ptr() as *const ffi::PublicKey,
123                offline_keys.as_c_ptr() as *const ffi::PublicKey,
124                n_keys,
125                whitelist_key.as_c_ptr(),
126            )
127        };
128        if ret != 1 {
129            return Err(Error::InvalidWhitelistProof);
130        }
131
132        Ok(())
133    }
134
135    /// Obtains a raw const pointer suitable for use with FFI functions
136    #[inline]
137    pub fn as_ptr(&self) -> *const ffi::WhitelistSignature {
138        &self.0
139    }
140
141    /// Obtains a raw mutable pointer suitable for use with FFI functions
142    #[inline]
143    pub fn as_mut_ptr(&mut self) -> *mut ffi::WhitelistSignature {
144        &mut self.0
145    }
146}
147
148#[cfg(feature = "std")]
149impl fmt::LowerHex for WhitelistSignature {
150    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151        for ch in self.serialize().iter() {
152            write!(f, "{:02x}", ch)?;
153        }
154        Ok(())
155    }
156}
157
158#[cfg(feature = "std")]
159impl fmt::Display for WhitelistSignature {
160    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
161        fmt::LowerHex::fmt(self, f)
162    }
163}
164
165#[cfg(feature = "std")]
166impl fmt::Debug for WhitelistSignature {
167    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
168        fmt::Display::fmt(self, f)
169    }
170}
171
172#[cfg(feature = "std")]
173impl str::FromStr for WhitelistSignature {
174    type Err = Error;
175    fn from_str(s: &str) -> Result<WhitelistSignature, Error> {
176        let mut buf = vec![0; s.len() / 2];
177        from_hex(s, &mut buf).map_err(|_| Error::InvalidWhitelistSignature)?;
178        WhitelistSignature::from_slice(&buf)
179    }
180}
181
182#[cfg(all(feature = "serde", feature = "std"))]
183impl ::serde::Serialize for WhitelistSignature {
184    fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
185        if s.is_human_readable() {
186            s.collect_str(self)
187        } else {
188            s.serialize_bytes(&self.serialize())
189        }
190    }
191}
192
193#[cfg(all(feature = "serde", feature = "std"))]
194impl<'de> ::serde::Deserialize<'de> for WhitelistSignature {
195    fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
196        use crate::serde_util;
197
198        if d.is_human_readable() {
199            d.deserialize_str(serde_util::FromStrVisitor::new("an ASCII hex string"))
200        } else {
201            d.deserialize_bytes(serde_util::BytesVisitor::new(
202                "a bytestring",
203                WhitelistSignature::from_slice,
204            ))
205        }
206    }
207}
208
209impl CPtr for WhitelistSignature {
210    type Target = ffi::WhitelistSignature;
211    fn as_c_ptr(&self) -> *const Self::Target {
212        self.as_ptr()
213    }
214
215    fn as_mut_c_ptr(&mut self) -> *mut Self::Target {
216        self.as_mut_ptr()
217    }
218}
219
220#[cfg(all(test, feature = "global-context"))]
221mod tests {
222    use super::*;
223    use crate::SECP256K1;
224    use rand::thread_rng;
225
226    fn test_whitelist_proof_roundtrip(n_keys: usize) {
227        let mut rng = thread_rng();
228        let (keys_online, pak_online) = (0..n_keys)
229            .map(|_| SECP256K1.generate_keypair(&mut rng))
230            .unzip::<_, _, Vec<_>, Vec<_>>();
231        let (keys_offline, pak_offline) = (0..n_keys)
232            .map(|_| SECP256K1.generate_keypair(&mut rng))
233            .unzip::<_, _, Vec<_>, Vec<_>>();
234
235        let (whitelist_sk, whitelist_pk) = SECP256K1.generate_keypair(&mut rng);
236
237        for our_idx in vec![0, n_keys / 2, n_keys - 1].into_iter() {
238            // sign
239
240            let summed_key = keys_offline[our_idx]
241                .add_tweak(&whitelist_sk.into())
242                .unwrap();
243
244            let signature = WhitelistSignature::new(
245                SECP256K1,
246                &pak_online,
247                &pak_offline,
248                &whitelist_pk,
249                &keys_online[our_idx],
250                &summed_key,
251                our_idx,
252            )
253            .unwrap();
254            assert_eq!(n_keys, signature.n_keys());
255
256            // verify
257
258            signature
259                .verify(SECP256K1, &pak_online, &pak_offline, &whitelist_pk)
260                .unwrap();
261
262            // round trip
263
264            let encoded = signature.serialize();
265            let decoded = WhitelistSignature::from_slice(&encoded).unwrap();
266            assert_eq!(n_keys, decoded.n_keys());
267            assert_eq!(signature, decoded);
268            decoded
269                .verify(SECP256K1, &pak_online, &pak_offline, &whitelist_pk)
270                .unwrap();
271        }
272    }
273
274    #[test]
275    fn test_whitelist_proof_roundtrip_n1() {
276        test_whitelist_proof_roundtrip(1);
277    }
278
279    #[test]
280    fn test_whitelist_proof_roundtrip_n50() {
281        test_whitelist_proof_roundtrip(50);
282    }
283
284    #[test]
285    fn test_whitelist_proof_roundtrip_n255() {
286        test_whitelist_proof_roundtrip(255);
287    }
288
289    #[test]
290    fn test_whitelist_proof_invalid() {
291        let n_keys = 255;
292
293        let mut rng = thread_rng();
294        let (keys_online, pak_online) = (0..n_keys)
295            .map(|_| SECP256K1.generate_keypair(&mut rng))
296            .unzip::<_, _, Vec<_>, Vec<_>>();
297        let (keys_offline, pak_offline) = (0..n_keys)
298            .map(|_| SECP256K1.generate_keypair(&mut rng))
299            .unzip::<_, _, Vec<_>, Vec<_>>();
300
301        let (whitelist_sk, whitelist_pk) = SECP256K1.generate_keypair(&mut rng);
302
303        let our_idx = 100;
304        let summed_key = keys_offline[our_idx]
305            .add_tweak(&whitelist_sk.into())
306            .unwrap();
307
308        {
309            // wrong pak
310            let offline = pak_offline[1..].to_vec();
311            assert_eq!(
312                Err(Error::InvalidPakList),
313                WhitelistSignature::new(
314                    SECP256K1,
315                    &pak_online,
316                    &offline, // wrong pak
317                    &whitelist_pk,
318                    &keys_online[our_idx],
319                    &summed_key,
320                    our_idx,
321                )
322            );
323        }
324
325        let correct_signature = WhitelistSignature::new(
326            SECP256K1,
327            &pak_online,
328            &pak_offline,
329            &whitelist_pk,
330            &keys_online[our_idx],
331            &summed_key,
332            our_idx,
333        )
334        .unwrap();
335
336        {
337            // wrong n_keys
338            let sig = unsafe {
339                let sig = correct_signature.clone();
340                let ptr = sig.as_c_ptr() as *mut ffi::WhitelistSignature;
341                (*ptr).n_keys -= 1;
342                sig
343            };
344            assert_eq!(
345                Err(Error::InvalidWhitelistProof),
346                sig.verify(SECP256K1, &pak_online, &pak_offline, &whitelist_pk,)
347            );
348        }
349
350        {
351            // wrong pak
352            let offline = pak_offline[1..].to_vec();
353            assert_eq!(
354                Err(Error::InvalidPakList),
355                correct_signature.verify(SECP256K1, &pak_online, &offline, &whitelist_pk,)
356            );
357        }
358
359        {
360            // verify for online pubkey
361            assert_eq!(
362                Err(Error::InvalidWhitelistProof),
363                correct_signature.verify(
364                    SECP256K1,
365                    &pak_online,
366                    &pak_offline,
367                    &pak_online[our_idx],
368                )
369            );
370        }
371
372        {
373            // verify for offline pubkey
374            assert_eq!(
375                Err(Error::InvalidWhitelistProof),
376                correct_signature.verify(
377                    SECP256K1,
378                    &pak_online,
379                    &pak_offline,
380                    &pak_offline[our_idx],
381                )
382            );
383        }
384
385        {
386            // incorrectly serialized with byte added
387            let mut encoded = correct_signature.serialize();
388            encoded.push(42);
389            assert_eq!(
390                Err(Error::InvalidWhitelistSignature),
391                WhitelistSignature::from_slice(&encoded),
392            );
393        }
394
395        {
396            // incorrectly serialized with byte changed
397            let mut encoded = correct_signature.serialize();
398            let len = encoded.len();
399            encoded[len - 1] ^= 0x01;
400            let decoded = WhitelistSignature::from_slice(&encoded).unwrap();
401            assert_eq!(
402                Err(Error::InvalidWhitelistProof),
403                decoded.verify(SECP256K1, &pak_online, &pak_offline, &whitelist_pk,)
404            );
405        }
406
407        {
408            // offline key instead of summed
409            let sig = WhitelistSignature::new(
410                SECP256K1,
411                &pak_online,
412                &pak_offline,
413                &whitelist_pk,
414                &keys_online[our_idx],
415                &keys_offline[our_idx], // actual offline key, not summed
416                our_idx,
417            )
418            .unwrap();
419
420            assert_eq!(
421                Err(Error::InvalidWhitelistProof),
422                sig.verify(SECP256K1, &pak_online, &pak_offline, &whitelist_pk,)
423            );
424            assert_eq!(
425                Err(Error::InvalidWhitelistProof),
426                sig.verify(SECP256K1, &pak_online, &pak_offline, &pak_offline[our_idx],)
427            );
428        }
429
430        {
431            // whitelist key instead of summed
432            let sig = WhitelistSignature::new(
433                SECP256K1,
434                &pak_online,
435                &pak_offline,
436                &whitelist_pk,
437                &keys_online[our_idx],
438                &whitelist_sk, // whitelist key, not summed
439                our_idx,
440            )
441            .unwrap();
442
443            assert_eq!(
444                Err(Error::InvalidWhitelistProof),
445                sig.verify(SECP256K1, &pak_online, &pak_offline, &whitelist_pk,)
446            );
447        }
448
449        assert_eq!(
450            Ok(()),
451            correct_signature.verify(SECP256K1, &pak_online, &pak_offline, &whitelist_pk,)
452        );
453    }
454}