diff --git a/src/constants.rs b/src/constants.rs index cb185e4fc..668683ffc 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -34,6 +34,15 @@ pub const MAX_SIGNATURE_SIZE: usize = 72; /// The maximum size of a compact signature pub const COMPACT_SIGNATURE_SIZE: usize = 64; +/// Size of a Schnorr signature +pub const SCHNORRSIG_SIGNATURE_SIZE: usize = 64; + +/// Size of a Schnorr public key +pub const SCHNORRSIG_PUBLIC_KEY_SIZE: usize = 32; + +/// Size of a key pair +pub const KEY_PAIR_SIZE: usize = 96; + /// The Prime for the secp256k1 field element. pub const FIELD_SIZE: [u8; 32] = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, diff --git a/src/lib.rs b/src/lib.rs index 10a4ed310..79fa82c48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,6 +142,7 @@ mod context; pub mod constants; pub mod ecdh; pub mod key; +pub mod schnorrsig; #[cfg(feature = "recovery")] pub mod recovery; diff --git a/src/macros.rs b/src/macros.rs index cafe960da..a8258be5c 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -67,7 +67,7 @@ macro_rules! serde_impl( if d.is_human_readable() { let sl: &str = ::serde::Deserialize::deserialize(d)?; - SecretKey::from_str(sl).map_err(D::Error::custom) + $t::from_str(sl).map_err(D::Error::custom) } else { let sl: &[u8] = ::serde::Deserialize::deserialize(d)?; if sl.len() != $len { diff --git a/src/schnorrsig.rs b/src/schnorrsig.rs new file mode 100644 index 000000000..132b74947 --- /dev/null +++ b/src/schnorrsig.rs @@ -0,0 +1,654 @@ +//! # schnorrsig +//! Support for Schnorr signatures. +//! + +#[cfg(any(test, feature = "rand-std"))] +use rand::thread_rng; +#[cfg(any(test, feature = "rand"))] +use rand::{CryptoRng, Rng}; + +use super::Error::{InvalidPublicKey, InvalidSecretKey, InvalidSignature}; +use super::{from_hex, Error}; +use core::{fmt, ptr, str}; +use ffi::{self, CPtr}; +use {constants, Secp256k1}; +use {Message, Signing, Verification}; + +/// Represents a Schnorr signature. +pub struct Signature([u8; constants::SCHNORRSIG_SIGNATURE_SIZE]); +impl_array_newtype!(Signature, u8, constants::SCHNORRSIG_SIGNATURE_SIZE); +impl_pretty_debug!(Signature); +serde_impl!(Signature, constants::SCHNORRSIG_SIGNATURE_SIZE); + +impl fmt::LowerHex for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for ch in &self.0[..] { + write!(f, "{:02x}", ch)?; + } + Ok(()) + } +} + +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::LowerHex::fmt(self, f) + } +} + +impl str::FromStr for Signature { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut res = [0; constants::SCHNORRSIG_SIGNATURE_SIZE]; + match from_hex(s, &mut res) { + Ok(constants::SCHNORRSIG_SIGNATURE_SIZE) => { + Signature::from_slice(&res[0..constants::SCHNORRSIG_SIGNATURE_SIZE]) + } + _ => Err(Error::InvalidSignature), + } + } +} + +/// Opaque data structure that holds a keypair consisting of a secret and a public key. +#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)] +pub struct KeyPair(ffi::KeyPair); + +/// A Schnorr public key, used for verification of Schnorr signatures +#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)] +pub struct PublicKey(ffi::XOnlyPublicKey); + +impl fmt::LowerHex for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let ser = self.serialize(); + for ch in &ser[..] { + write!(f, "{:02x}", *ch)?; + } + Ok(()) + } +} + +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::LowerHex::fmt(self, f) + } +} + +impl str::FromStr for PublicKey { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut res = [0; constants::SCHNORRSIG_PUBLIC_KEY_SIZE]; + match from_hex(s, &mut res) { + Ok(constants::SCHNORRSIG_PUBLIC_KEY_SIZE) => { + PublicKey::from_slice(&res[0..constants::SCHNORRSIG_PUBLIC_KEY_SIZE]) + } + _ => Err(InvalidPublicKey), + } + } +} + +impl Signature { + /// Creates a Signature directly from a slice + #[inline] + pub fn from_slice(data: &[u8]) -> Result { + match data.len() { + constants::SCHNORRSIG_SIGNATURE_SIZE => { + let mut ret = [0; constants::SCHNORRSIG_SIGNATURE_SIZE]; + ret[..].copy_from_slice(data); + Ok(Signature(ret)) + } + _ => Err(InvalidSignature), + } + } +} + +impl KeyPair { + /// Obtains a raw const pointer suitable for use with FFI functions + #[inline] + pub fn as_ptr(&self) -> *const ffi::KeyPair { + &self.0 as *const _ + } + + /// Obtains a raw mutable pointer suitable for use with FFI functions + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut ffi::KeyPair { + &mut self.0 as *mut _ + } + + /// Creates a Schnorr KeyPair directly from a secret key slice + #[inline] + pub fn from_seckey_slice( + secp: &Secp256k1, + data: &[u8], + ) -> Result { + if data.is_empty() || data.len() != constants::SECRET_KEY_SIZE { + return Err(InvalidPublicKey); + } + + let mut kp = ffi::KeyPair::new(); + unsafe { + if ffi::secp256k1_keypair_create(secp.ctx, &mut kp, data.as_c_ptr()) == 1 { + Ok(KeyPair(kp)) + } else { + Err(InvalidSecretKey) + } + } + } + + /// Creates a Schnorr KeyPair directly from a secret key string + #[inline] + pub fn from_seckey_str(secp: &Secp256k1, s: &str) -> Result { + let mut res = [0; constants::SECRET_KEY_SIZE]; + match from_hex(s, &mut res) { + Ok(constants::SECRET_KEY_SIZE) => { + KeyPair::from_seckey_slice(secp, &res[0..constants::SECRET_KEY_SIZE]) + } + _ => Err(InvalidPublicKey), + } + } + + /// Creates a new random secret key. Requires compilation with the "rand" feature. + #[inline] + #[cfg(any(test, feature = "rand"))] + pub fn new(secp: &Secp256k1, rng: &mut R) -> KeyPair { + let mut random_32_bytes = || { + let mut ret = [0u8; 32]; + rng.fill_bytes(&mut ret); + ret + }; + let mut data = random_32_bytes(); + let mut keypair = ffi::KeyPair::new(); + unsafe { + while ffi::secp256k1_keypair_create(secp.ctx, &mut keypair, data.as_c_ptr()) == 0 { + data = random_32_bytes(); + } + } + KeyPair(keypair) + } + + + /// Tweak a keypair by adding tweak32 to the secret key and updating the + /// public key accordingly. + #[inline] + pub fn add_assign( + &mut self, + secp: &Secp256k1, + tweak: &[u8], + ) -> Result<(), Error> { + if tweak.len() != 32 { + return Err(Error::InvalidTweak); + } + + unsafe { + let err = ffi::secp256k1_keypair_xonly_tweak_add( + secp.ctx, + &mut self.0 as *mut _, + tweak.as_c_ptr(), + ); + + return if err == 1 { + Ok(()) + } else { + Err(Error::InvalidTweak) + }; + } + } +} + +impl PublicKey { + /// Obtains a raw const pointer suitable for use with FFI functions + #[inline] + pub fn as_ptr(&self) -> *const ffi::XOnlyPublicKey { + &self.0 as *const _ + } + + /// Obtains a raw mutable pointer suitable for use with FFI functions + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut ffi::XOnlyPublicKey { + &mut self.0 as *mut _ + } + + /// Creates a new Schnorr public key from a Schnorr key pair + #[inline] + pub fn from_keypair(secp: &Secp256k1, keypair: &KeyPair) -> PublicKey { + let mut xonly_pk = ffi::XOnlyPublicKey::new(); + let mut pk_parity = 0; + unsafe { + let ret = ffi::secp256k1_keypair_xonly_pub( + secp.ctx, + &mut xonly_pk, + &mut pk_parity, + keypair.as_ptr(), + ); + debug_assert_eq!(ret, 1); + } + PublicKey(xonly_pk) + } + + /// Creates a Schnorr public key directly from a slice + #[inline] + pub fn from_slice(data: &[u8]) -> Result { + if data.is_empty() || data.len() != constants::SCHNORRSIG_PUBLIC_KEY_SIZE { + return Err(InvalidPublicKey); + } + + let mut pk = ffi::XOnlyPublicKey::new(); + unsafe { + if ffi::secp256k1_xonly_pubkey_parse( + ffi::secp256k1_context_no_precomp, + &mut pk, + data.as_c_ptr(), + ) == 1 + { + Ok(PublicKey(pk)) + } else { + Err(InvalidPublicKey) + } + } + } + + #[inline] + /// Serialize the key as a byte-encoded pair of values. In compressed form + /// the y-coordinate is represented by only a single bit, as x determines + /// it up to one bit. + pub fn serialize(&self) -> [u8; constants::SCHNORRSIG_PUBLIC_KEY_SIZE] { + let mut ret = [0; constants::SCHNORRSIG_PUBLIC_KEY_SIZE]; + + unsafe { + let err = ffi::secp256k1_xonly_pubkey_serialize( + ffi::secp256k1_context_no_precomp, + ret.as_mut_c_ptr(), + self.as_c_ptr(), + ); + debug_assert_eq!(err, 1); + } + ret + } +} + +impl CPtr for PublicKey { + type Target = ffi::XOnlyPublicKey; + fn as_c_ptr(&self) -> *const Self::Target { + self.as_ptr() + } + + fn as_mut_c_ptr(&mut self) -> *mut Self::Target { + self.as_mut_ptr() + } +} + +/// Creates a new Schnorr public key from a FFI x-only public key +impl From for PublicKey { + #[inline] + fn from(pk: ffi::XOnlyPublicKey) -> PublicKey { + PublicKey(pk) + } +} + +serde_impl_from_slice!(PublicKey); + +impl Secp256k1 { + fn schnorrsig_sign_helper( + &self, + msg: &Message, + keypair: &KeyPair, + nonce_data: *const ffi::types::c_void, + ) -> Result { + unsafe { + let mut sig = [0u8; constants::SCHNORRSIG_SIGNATURE_SIZE]; + assert_eq!( + 1, + ffi::secp256k1_schnorrsig_sign( + self.ctx, + sig.as_mut_c_ptr(), + msg.as_c_ptr(), + keypair.as_ptr(), + ffi::secp256k1_nonce_function_bip340, + nonce_data + ) + ); + + Ok(Signature(sig)) + } + } + + /// Create a schnorr signature internally using the ThreadRng random number + /// generator to generate the auxiliary random data. + /// Requires compilation with "rand-std" feature. + #[cfg(any(test, feature = "rand-std"))] + pub fn schnorrsig_sign(&self, msg: &Message, keypair: &KeyPair) -> Result { + let mut rng = thread_rng(); + self.schnorrsig_sign_with_rng(msg, keypair, &mut rng) + } + + /// Create a schnorr signature without using any auxiliary random data. + pub fn schnorrsig_sign_no_aux_rand( + &self, + msg: &Message, + keypair: &KeyPair, + ) -> Result { + self.schnorrsig_sign_helper(msg, keypair, ptr::null()) + } + + /// Create a Schnorr signature using the given auxiliary random data. + pub fn schnorrsig_sign_with_aux_rand( + &self, + msg: &Message, + keypair: &KeyPair, + aux_rand: &[u8; 32], + ) -> Result { + self.schnorrsig_sign_helper( + msg, + keypair, + aux_rand.as_c_ptr() as *const ffi::types::c_void, + ) + } + + /// Create a schnorr signature using the given random number generator to + /// generate the auxiliary random data. Requires compilation with "rand" + /// feature. + #[cfg(any(test, feature = "rand"))] + pub fn schnorrsig_sign_with_rng( + &self, + msg: &Message, + keypair: &KeyPair, + rng: &mut R, + ) -> Result { + let mut aux = [0u8; 32]; + rng.fill_bytes(&mut aux); + self.schnorrsig_sign_helper(msg, keypair, aux.as_c_ptr() as *const ffi::types::c_void) + } + + /// Verify a Schnorr signature. + pub fn schnorrsig_verify( + &self, + sig: &Signature, + msg: &Message, + pubkey: &PublicKey, + ) -> Result<(), Error> { + unsafe { + let ret = ffi::secp256k1_schnorrsig_verify( + self.ctx, + sig.as_c_ptr(), + msg.as_c_ptr(), + pubkey.as_c_ptr(), + ); + + return if ret == 1 { + Ok(()) + } else { + Err(Error::InvalidSignature) + }; + } + } + + /// Generates a random Schnorr KeyPair and its associated Schnorr PublicKey. + /// Convenience function for `schnorrsig::KeyPair::new` and + /// `schnorrsig::PublicKey::from_keypair`; call those functions directly for + /// batch key generation. Requires a signing-capable context. Requires compilation + /// with the "rand" feature. + #[inline] + #[cfg(any(test, feature = "rand"))] + pub fn generate_schnorrsig_keypair( + &self, + rng: &mut R, + ) -> (KeyPair, PublicKey) { + let sk = KeyPair::new(self, rng); + let pubkey = PublicKey::from_keypair(self, &sk); + (sk, pubkey) + } +} + +#[cfg(test)] +mod tests { + use super::super::Error::InvalidPublicKey; + use super::super::{constants, from_hex, All, Message, Secp256k1}; + use super::{KeyPair, PublicKey, Signature}; + use rand::{rngs::ThreadRng, thread_rng, Error, ErrorKind, RngCore}; + use rand_core::impls; + use std::iter; + use std::str::FromStr; + + macro_rules! hex_32 { + ($hex:expr) => {{ + let mut result = [0; 32]; + from_hex($hex, &mut result).expect("valid hex string"); + result + }}; + } + + fn test_schnorrsig_sign_helper( + sign: fn(&Secp256k1, &Message, &KeyPair, &mut ThreadRng) -> Signature, + ) { + let secp = Secp256k1::new(); + + let mut rng = thread_rng(); + let (seckey, pubkey) = secp.generate_schnorrsig_keypair(&mut rng); + let mut msg = [0; 32]; + + for _ in 0..100 { + rng.fill_bytes(&mut msg); + let msg = Message::from_slice(&msg).unwrap(); + + let sig = sign(&secp, &msg, &seckey, &mut rng); + + assert!(secp.schnorrsig_verify(&sig, &msg, &pubkey).is_ok()); + } + } + + #[test] + fn test_schnorrsig_sign_with_aux_rand_verify() { + test_schnorrsig_sign_helper(|secp, msg, seckey, rng| { + let mut aux_rand = [0; 32]; + rng.fill_bytes(&mut aux_rand); + secp.schnorrsig_sign_with_aux_rand(msg, seckey, &aux_rand) + .unwrap() + }) + } + + #[test] + fn test_schnorrsig_sign_with_rng_verify() { + test_schnorrsig_sign_helper(|secp, msg, seckey, mut rng| { + secp.schnorrsig_sign_with_rng(msg, seckey, &mut rng) + .unwrap() + }) + } + + #[test] + fn test_schnorrsig_sign_verify() { + test_schnorrsig_sign_helper(|secp, msg, seckey, _| { + secp.schnorrsig_sign(msg, seckey).unwrap() + }) + } + + #[test] + fn test_schnorrsig_sign_no_aux_rand_verify() { + test_schnorrsig_sign_helper(|secp, msg, seckey, _| { + secp.schnorrsig_sign_no_aux_rand(msg, seckey).unwrap() + }) + } + + #[test] + fn test_schnorrsig_sign() { + let secp = Secp256k1::new(); + + let hex_msg = hex_32!("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614"); + let msg = Message::from_slice(&hex_msg).unwrap(); + let sk = KeyPair::from_seckey_str( + &secp, + "688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF", + ) + .unwrap(); + let aux_rand: [u8; 32] = + hex_32!("02CCE08E913F22A36C5648D6405A2C7C50106E7AA2F1649E381C7F09D16B80AB"); + let expected_sig = Signature::from_str("6470FD1303DDA4FDA717B9837153C24A6EAB377183FC438F939E0ED2B620E9EE5077C4A8B8DCA28963D772A94F5F0DDF598E1C47C137F91933274C7C3EDADCE8").unwrap(); + + let sig = secp + .schnorrsig_sign_with_aux_rand(&msg, &sk, &aux_rand) + .unwrap(); + + assert_eq!(expected_sig, sig); + } + + #[test] + fn test_schnorrsig_verify() { + let secp = Secp256k1::new(); + + let hex_msg = hex_32!("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614"); + let msg = Message::from_slice(&hex_msg).unwrap(); + let sig = Signature::from_str("6470FD1303DDA4FDA717B9837153C24A6EAB377183FC438F939E0ED2B620E9EE5077C4A8B8DCA28963D772A94F5F0DDF598E1C47C137F91933274C7C3EDADCE8").unwrap(); + let pubkey = + PublicKey::from_str("B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390") + .unwrap(); + + assert!(secp.schnorrsig_verify(&sig, &msg, &pubkey).is_ok()); + } + + #[test] + fn pubkey_from_slice() { + assert_eq!(PublicKey::from_slice(&[]), Err(InvalidPublicKey)); + assert_eq!(PublicKey::from_slice(&[1, 2, 3]), Err(InvalidPublicKey)); + let pk = PublicKey::from_slice(&[ + 0xB3, 0x3C, 0xC9, 0xED, 0xC0, 0x96, 0xD0, 0xA8, 0x34, 0x16, 0x96, 0x4B, 0xD3, 0xC6, + 0x24, 0x7B, 0x8F, 0xEC, 0xD2, 0x56, 0xE4, 0xEF, 0xA7, 0x87, 0x0D, 0x2C, 0x85, 0x4B, + 0xDE, 0xB3, 0x33, 0x90, + ]); + assert!(pk.is_ok()); + } + + #[test] + fn pubkey_serialize_roundtrip() { + let secp = Secp256k1::new(); + let (_, pubkey) = secp.generate_schnorrsig_keypair(&mut thread_rng()); + let ser = pubkey.serialize(); + let pubkey2 = PublicKey::from_slice(&ser).unwrap(); + assert_eq!(pubkey, pubkey2); + } + + #[test] + fn test_pubkey_from_bad_slice() { + // Bad sizes + assert_eq!( + PublicKey::from_slice(&[0; constants::SCHNORRSIG_PUBLIC_KEY_SIZE - 1]), + Err(InvalidPublicKey) + ); + assert_eq!( + PublicKey::from_slice(&[0; constants::SCHNORRSIG_PUBLIC_KEY_SIZE + 1]), + Err(InvalidPublicKey) + ); + + // Bad parse + assert_eq!( + PublicKey::from_slice(&[0xff; constants::SCHNORRSIG_PUBLIC_KEY_SIZE]), + Err(InvalidPublicKey) + ); + assert_eq!( + PublicKey::from_slice(&[0x55; constants::SCHNORRSIG_PUBLIC_KEY_SIZE]), + Err(InvalidPublicKey) + ); + assert_eq!(PublicKey::from_slice(&[]), Err(InvalidPublicKey)); + } + + #[test] + fn test_pubkey_display_output() { + let secp = Secp256k1::new(); + static SK_BYTES: [u8; 32] = [ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, + ]; + + let s = Secp256k1::signing_only(); + let sk = KeyPair::from_seckey_slice(&secp, &SK_BYTES).expect("sk"); + let pk = PublicKey::from_keypair(&s, &sk); + + assert_eq!( + pk.to_string(), + "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166" + ); + assert_eq!( + PublicKey::from_str("18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166") + .unwrap(), + pk + ); + + assert!(PublicKey::from_str( + "00000000000000000000000000000000000000000000000000000000000000000" + ) + .is_err()); + assert!(PublicKey::from_str( + "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd16601" + ) + .is_err()); + assert!(PublicKey::from_str( + "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd16" + ) + .is_err()); + assert!(PublicKey::from_str( + "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd1" + ) + .is_err()); + assert!(PublicKey::from_str( + "xx18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd1" + ) + .is_err()); + + let long_str: String = iter::repeat('a').take(1024 * 1024).collect(); + assert!(PublicKey::from_str(&long_str).is_err()); + } + + #[test] + fn test_pubkey_serialize() { + struct DumbRng(u32); + impl RngCore for DumbRng { + fn next_u32(&mut self) -> u32 { + self.0 = self.0.wrapping_add(1); + self.0 + } + fn next_u64(&mut self) -> u64 { + self.next_u32() as u64 + } + fn try_fill_bytes(&mut self, _dest: &mut [u8]) -> Result<(), Error> { + Err(Error::new(ErrorKind::Unavailable, "not implemented")) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + impls::fill_bytes_via_next(self, dest); + } + } + + let s = Secp256k1::new(); + let (_, pubkey) = s.generate_schnorrsig_keypair(&mut DumbRng(0)); + assert_eq!( + &pubkey.serialize()[..], + &[ + 124, 121, 49, 14, 253, 63, 197, 50, 39, 194, 107, 17, 193, 219, 108, 154, 126, 9, + 181, 248, 2, 12, 149, 233, 198, 71, 149, 134, 250, 184, 154, 229 + ][..] + ); + } + + #[cfg(feature = "serde")] + #[test] + fn test_signature_serde() { + use serde_test::{assert_tokens, Configure, Token}; + + let s = Secp256k1::new(); + + let msg = Message::from_slice(&[1; 32]).unwrap(); + let keypair = KeyPair::from_seckey_slice(&s, &[2; 32]).unwrap(); + let aux = [3; 32]; + let sig = s.schnorrsig_sign_with_aux_rand(&msg, &keypair, &aux).unwrap(); + static SIG_BYTES: [u8; constants::SCHNORRSIG_SIGNATURE_SIZE] = [ + 0x14, 0xd0, 0xbf, 0x1a, 0x89, 0x53, 0x50, 0x6f, 0xb4, 0x60, 0xf5, 0x8b, 0xe1, 0x41, + 0xaf, 0x76, 0x7f, 0xd1, 0x12, 0x53, 0x5f, 0xb3, 0x92, 0x2e, 0xf2, 0x17, 0x30, 0x8e, + 0x2c, 0x26, 0x70, 0x6f, 0x1e, 0xeb, 0x43, 0x2b, 0x3d, 0xba, 0x9a, 0x01, 0x08, 0x2f, + 0x9e, 0x4d, 0x4e, 0xf5, 0x67, 0x8a, 0xd0, 0xd9, 0xd5, 0x32, 0xc0, 0xdf, 0xa9, 0x07, + 0xb5, 0x68, 0x72, 0x2d, 0x0b, 0x01, 0x19, 0xba, + ]; + static SIG_STR: &'static str = "\ + 14d0bf1a8953506fb460f58be141af767fd112535fb3922ef217308e2c26706f1eeb432b3dba9a01082f9e4d4ef5678ad0d9d532c0dfa907b568722d0b0119ba\ + "; + + assert_tokens(&sig.compact(), &[Token::BorrowedBytes(&SIG_BYTES[..])]); + assert_tokens(&sig.readable(), &[Token::BorrowedStr(SIG_STR)]); + } +}