Merge rust-bitcoin/rust-secp256k1#312: Display and Debug for secret keys prints a hash

6810c2b547 Dedicated display_secret fn for secret-containing types (Dr Maxim Orlovsky)
635a6ae441 Add to_hex converter and add tests for hex conversion (Elichai Turkel)

Pull request description:

  Extract of concept ACK part of #311 related to changing the way secret keys are displayed/printed out

ACKs for top commit:
  apoelstra:
    ACK 6810c2b547
  thomaseizinger:
    ACK 6810c2b547

Tree-SHA512: 22ad7b22f47b177e299ec133129d607f8c3ced1970c4c9bea6e81e49506534c7e15b4fb1d745ba1d3f85f27715f7793c6fef0b93f258037665b7f740b967afe5
This commit is contained in:
Andrew Poelstra 2021-11-02 17:57:43 +00:00
commit 88196bdb3d
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
5 changed files with 225 additions and 27 deletions

View File

@ -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<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
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!(

View File

@ -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<usize, ()> {
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() {

View File

@ -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(")")
}
}
}

View File

@ -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)]

152
src/secret.rs Normal file
View File

@ -0,0 +1,152 @@
// Bitcoin secp256k1 bindings
// Written in 2021 by
// Maxim Orlovsky <orlovsky@pandoracore.com>
//
// 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 <http://creativecommons.org/publicdomain/zero/1.0/>.
//
//! 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() }
}
}