diff --git a/src/key.rs b/src/key.rs index 01bf790..718ee51 100644 --- a/src/key.rs +++ b/src/key.rs @@ -21,7 +21,7 @@ use core::{fmt, str}; use super::{from_hex, Secp256k1}; use super::Error::{self, InvalidPublicKey, InvalidPublicKeySum, InvalidSecretKey}; -use Signing; +use ::{Signing}; use Verification; use constants; use ffi::{self, CPtr}; @@ -29,22 +29,7 @@ use ffi::{self, CPtr}; /// Secret 256-bit key used as `x` in an ECDSA signature pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]); impl_array_newtype!(SecretKey, u8, constants::SECRET_KEY_SIZE); -impl_pretty_debug!(SecretKey); - -impl fmt::LowerHex for SecretKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for ch in &self.0[..] { - write!(f, "{:02x}", *ch)?; - } - Ok(()) - } -} - -impl fmt::Display for SecretKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::LowerHex::fmt(self, f) - } -} +impl_display_secret!(SecretKey); impl str::FromStr for SecretKey { type Err = Error; @@ -164,6 +149,12 @@ impl SecretKey { SecretKey(sk) } + /// Serialize the secret key as byte value + #[inline] + pub fn serialize_secret(&self) -> [u8; constants::SECRET_KEY_SIZE] { + self.0 + } + #[inline] /// Negates one secret key. pub fn negate_assign( @@ -233,7 +224,8 @@ impl SecretKey { impl ::serde::Serialize for SecretKey { fn serialize(&self, s: S) -> Result { if s.is_human_readable() { - s.collect_str(self) + let mut buf = [0u8; 64]; + s.serialize_str(::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization")) } else { s.serialize_bytes(&self[..]) } @@ -516,7 +508,7 @@ impl Ord for PublicKey { #[cfg(test)] mod test { use Secp256k1; - use from_hex; + use {from_hex, to_hex}; use super::super::Error::{InvalidPublicKey, InvalidSecretKey}; use super::{PublicKey, SecretKey}; use super::super::constants; @@ -710,7 +702,11 @@ mod test { let (sk, _) = s.generate_keypair(&mut DumbRng(0)); assert_eq!(&format!("{:?}", sk), - "SecretKey(0100000000000000020000000000000003000000000000000400000000000000)"); + "SecretKey(#d3e0c51a23169bb5)"); + + let mut buf = [0u8; constants::SECRET_KEY_SIZE * 2]; + assert_eq!(to_hex(&sk[..], &mut buf).unwrap(), + "0100000000000000020000000000000003000000000000000400000000000000"); } #[test] @@ -733,7 +729,7 @@ mod test { let pk = PublicKey::from_slice(&[0x02, 0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f, 0x1c, 0x97, 0x09, 0xe2, 0x30, 0x92, 0x06, 0x7d, 0x06, 0x83, 0x7f, 0x30, 0xaa, 0x0c, 0xd0, 0x54, 0x4a, 0xc8, 0x87, 0xfe, 0x91, 0xdd, 0xd1, 0x66]).expect("pk"); assert_eq!( - sk.to_string(), + sk.display_secret().to_string(), "01010101010101010001020304050607ffff0000ffff00006363636363636363" ); assert_eq!( diff --git a/src/lib.rs b/src/lib.rs index 9c1c5ac..6586898 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,10 +137,11 @@ pub use secp256k1_sys as ffi; #[cfg(all(test, target_arch = "wasm32"))] extern crate wasm_bindgen_test; #[cfg(feature = "alloc")] extern crate alloc; -use core::{fmt, ptr, str}; #[macro_use] mod macros; +#[macro_use] +mod secret; mod context; pub mod constants; pub mod ecdh; @@ -156,7 +157,7 @@ pub use key::PublicKey; pub use context::*; use core::marker::PhantomData; use core::ops::Deref; -use core::mem; +use core::{mem, fmt, ptr, str}; use ffi::{CPtr, types::AlignedType}; #[cfg(feature = "global-context-less-secure")] @@ -851,6 +852,28 @@ fn from_hex(hex: &str, target: &mut [u8]) -> Result { Ok(idx / 2) } +/// Utility function used to encode hex into a target u8 buffer. Returns +/// a reference to the target buffer as an str. Returns an error if the target +/// buffer isn't big enough. +#[inline] +fn to_hex<'a>(src: &[u8], target: &'a mut [u8]) -> Result<&'a str, ()> { + let hex_len = src.len() * 2; + if target.len() < hex_len { + return Err(()); + } + const HEX_TABLE: [u8; 16] = *b"0123456789abcdef"; + + let mut i = 0; + for &b in src { + target[i] = HEX_TABLE[usize::from(b >> 4)]; + target[i+1] = HEX_TABLE[usize::from(b & 0b00001111)]; + i +=2 ; + } + let result = &target[..hex_len]; + debug_assert!(str::from_utf8(result).is_ok()); + return unsafe { Ok(str::from_utf8_unchecked(result)) }; +} + #[cfg(test)] mod tests { @@ -859,7 +882,7 @@ mod tests { use std::marker::PhantomData; use key::{SecretKey, PublicKey}; - use super::from_hex; + use super::{from_hex, to_hex}; use super::constants; use super::{Secp256k1, Signature, Message}; use super::Error::{InvalidMessage, IncorrectSignature, InvalidSignature}; @@ -1186,6 +1209,32 @@ mod tests { assert!(Message::from_slice(&[1; constants::MESSAGE_SIZE]).is_ok()); } + #[test] + fn test_hex() { + let mut rng = thread_rng(); + const AMOUNT: usize = 1024; + for i in 0..AMOUNT { + // 255 isn't a valid utf8 character. + let mut hex_buf = [255u8; AMOUNT*2]; + let mut src_buf = [0u8; AMOUNT]; + let mut result_buf = [0u8; AMOUNT]; + let src = &mut src_buf[0..i]; + rng.fill_bytes(src); + + let hex = to_hex(src, &mut hex_buf).unwrap(); + assert_eq!(from_hex(hex, &mut result_buf).unwrap(), i); + assert_eq!(src, &result_buf[..i]); + } + + + assert!(to_hex(&[1;2], &mut [0u8; 3]).is_err()); + assert!(to_hex(&[1;2], &mut [0u8; 4]).is_ok()); + assert!(from_hex("deadbeaf", &mut [0u8; 3]).is_err()); + assert!(from_hex("deadbeaf", &mut [0u8; 4]).is_ok()); + assert!(from_hex("a", &mut [0u8; 4]).is_err()); + assert!(from_hex("ag", &mut [0u8; 4]).is_err()); + } + #[test] #[cfg(not(fuzzing))] // fixed sig vectors can't work with fuzz-sigs fn test_low_s() { diff --git a/src/macros.rs b/src/macros.rs index bfd41b7..f8d2b5e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -18,10 +18,10 @@ macro_rules! impl_pretty_debug { impl ::core::fmt::Debug for $thing { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { write!(f, "{}(", stringify!($thing))?; - for i in self[..].iter().cloned() { + for i in &self[..] { write!(f, "{:02x}", i)?; } - write!(f, ")") + f.write_str(")") } } } diff --git a/src/schnorrsig.rs b/src/schnorrsig.rs index c369e23..f56e2b1 100644 --- a/src/schnorrsig.rs +++ b/src/schnorrsig.rs @@ -76,8 +76,9 @@ impl str::FromStr for Signature { } /// Opaque data structure that holds a keypair consisting of a secret and a public key. -#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)] +#[derive(Clone)] pub struct KeyPair(ffi::KeyPair); +impl_display_secret!(KeyPair); /// A Schnorr public key, used for verification of Schnorr signatures #[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)] diff --git a/src/secret.rs b/src/secret.rs new file mode 100644 index 0000000..34153c2 --- /dev/null +++ b/src/secret.rs @@ -0,0 +1,152 @@ +// Bitcoin secp256k1 bindings +// Written in 2021 by +// Maxim Orlovsky +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! Helpers for displaying secret values + +use ::core::fmt; +use ::{SecretKey, schnorrsig::KeyPair, to_hex}; +use constants::SECRET_KEY_SIZE; + +macro_rules! impl_display_secret { + // Default hasher exists only in standard library and not alloc + ($thing:ident) => { + #[cfg(feature = "std")] + impl ::core::fmt::Debug for $thing { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use ::core::hash::Hasher; + const DEBUG_HASH_TAG: &[u8] = &[ + 0x66, 0xa6, 0x77, 0x1b, 0x9b, 0x6d, 0xae, 0xa1, 0xb2, 0xee, 0x4e, 0x07, 0x49, + 0x4a, 0xac, 0x87, 0xa9, 0xb8, 0x5b, 0x4b, 0x35, 0x02, 0xaa, 0x6d, 0x0f, 0x79, + 0xcb, 0x63, 0xe6, 0xf8, 0x66, 0x22 + ]; // =SHA256(b"rust-secp256k1DEBUG"); + + let mut hasher = ::std::collections::hash_map::DefaultHasher::new(); + + hasher.write(DEBUG_HASH_TAG); + hasher.write(DEBUG_HASH_TAG); + hasher.write(&self.serialize_secret()); + let hash = hasher.finish(); + + f.debug_tuple(stringify!($thing)) + .field(&format_args!("#{:016x}", hash)) + .finish() + } + } + } +} + +/// Helper struct for safely printing secrets (like [`SecretKey`] value). +/// Formats the explicit byte value of the secret kept inside the type as a +/// little-endian hexadecimal string using the provided formatter. +/// +/// Secrets should not implement neither [`Debug`] and [`Display`] traits directly, +/// and instead provide `fn display_secret<'a>(&'a self) -> DisplaySecret<'a>` +/// function to be used in different display contexts (see "examples" below). +/// +/// [`Display`]: fmt::Display +/// [`Debug`]: fmt::Debug +pub struct DisplaySecret { + secret: [u8; SECRET_KEY_SIZE] +} + +impl fmt::Debug for DisplaySecret { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut slice = [0u8; 64]; + let hex = to_hex(&self.secret, &mut slice).expect("fixed-size hex serializer failed"); + f.debug_tuple("DisplaySecret") + .field(&hex) + .finish() + } +} + +impl fmt::Display for DisplaySecret { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for i in &self.secret { + write!(f, "{:02x}", i)?; + } + Ok(()) + } +} + +impl SecretKey { + /// Formats the explicit byte value of the secret key kept inside the type as a + /// little-endian hexadecimal string using the provided formatter. + /// + /// This is the only method that outputs the actual secret key value, and, thus, + /// should be used with extreme precaution. + /// + /// # Example + /// + /// ``` + /// use secp256k1::key::ONE_KEY; + /// let key = ONE_KEY; + /// // Normal display hides value + /// assert_eq!( + /// "SecretKey(#2518682f7819fb2d)", + /// format!("{:?}", key) + /// ); + /// // Here we explicitly display the secret value: + /// assert_eq!( + /// "0000000000000000000000000000000000000000000000000000000000000001", + /// format!("{}", key.display_secret()) + /// ); + /// assert_eq!( + /// "DisplaySecret(\"0000000000000000000000000000000000000000000000000000000000000001\")", + /// format!("{:?}", key.display_secret()) + /// ); + /// ``` + #[inline] + pub fn display_secret(&self) -> DisplaySecret { + DisplaySecret { secret: self.serialize_secret() } + } +} + +impl KeyPair { + /// Formats the explicit byte value of the secret key kept inside the type as a + /// little-endian hexadecimal string using the provided formatter. + /// + /// This is the only method that outputs the actual secret key value, and, thus, + /// should be used with extreme precaution. + /// + /// # Example + /// + /// ``` + /// use secp256k1::key::ONE_KEY; + /// use secp256k1::schnorrsig::KeyPair; + /// use secp256k1::Secp256k1; + /// + /// let secp = Secp256k1::new(); + /// let key = ONE_KEY; + /// let key = KeyPair::from_secret_key(&secp, key); + /// + /// // Normal display hides value + /// assert_eq!( + /// "KeyPair(#2518682f7819fb2d)", + /// format!("{:?}", key) + /// ); + /// // Here we explicitly display the secret value: + /// assert_eq!( + /// "0000000000000000000000000000000000000000000000000000000000000001", + /// format!("{}", key.display_secret()) + /// ); + /// assert_eq!( + /// "DisplaySecret(\"0000000000000000000000000000000000000000000000000000000000000001\")", + /// format!("{:?}", key.display_secret()) + /// ); + #[inline] + pub fn display_secret(&self) -> DisplaySecret { + DisplaySecret { secret: self.serialize_secret() } + } +}