From 572adb2873132383eb971558e874fe2caede1905 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 26 Aug 2018 18:39:41 +0000 Subject: [PATCH 1/3] add `FromStr` implementation for key types --- src/key.rs | 95 +++++++++++++++++++++++++++++++++++++++++------------- src/lib.rs | 54 +++++++++++++++++++------------ 2 files changed, 106 insertions(+), 43 deletions(-) diff --git a/src/key.rs b/src/key.rs index 2a5ea72..36af2c3 100644 --- a/src/key.rs +++ b/src/key.rs @@ -17,9 +17,9 @@ #[cfg(any(test, feature = "rand"))] use rand::Rng; -use std::{fmt, mem}; +use std::{fmt, mem, str}; -use super::{Secp256k1}; +use super::{from_hex, Secp256k1}; use super::Error::{self, InvalidPublicKey, InvalidSecretKey}; use Signing; use Verification; @@ -40,6 +40,17 @@ impl fmt::Display for SecretKey { } } +impl str::FromStr for SecretKey { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut res = [0; constants::SECRET_KEY_SIZE]; + match from_hex(s, &mut res) { + Ok(constants::SECRET_KEY_SIZE) => Ok(SecretKey(res)), + _ => Err(Error::InvalidSecretKey) + } + } +} + /// The number 1 encoded as a secret key /// Deprecated; `static` is not what I want; use `ONE_KEY` instead pub static ONE: SecretKey = SecretKey([0, 0, 0, 0, 0, 0, 0, 0, @@ -73,6 +84,26 @@ impl fmt::Display for PublicKey { } } +impl str::FromStr for PublicKey { + type Err = Error; + fn from_str(s: &str) -> Result { + let secp = Secp256k1::without_caps(); + let mut res = [0; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE]; + match from_hex(s, &mut res) { + Ok(constants::PUBLIC_KEY_SIZE) => { + PublicKey::from_slice( + &secp, + &res[0..constants::PUBLIC_KEY_SIZE] + ) + } + Ok(constants::UNCOMPRESSED_PUBLIC_KEY_SIZE) => { + PublicKey::from_slice(&secp, &res) + } + _ => Err(Error::InvalidPublicKey) + } + } +} + #[cfg(any(test, feature = "rand"))] fn random_32_bytes(rng: &mut R) -> [u8; 32] { let mut ret = [0u8; 32]; @@ -318,34 +349,22 @@ impl<'de> ::serde::Deserialize<'de> for PublicKey { #[cfg(test)] mod test { - use super::super::{Secp256k1}; + use Secp256k1; + use from_hex; use super::super::Error::{InvalidPublicKey, InvalidSecretKey}; use super::{PublicKey, SecretKey}; use super::super::constants; use rand::{Rng, thread_rng}; + use std::iter; + use std::str::FromStr; macro_rules! hex { - ($hex:expr) => { - { - let mut vec = Vec::new(); - let mut b = 0; - for (idx, c) in $hex.as_bytes().iter().enumerate() { - b <<= 4; - match *c { - b'A'...b'F' => b |= c - b'A' + 10, - b'a'...b'f' => b |= c - b'a' + 10, - b'0'...b'9' => b |= c - b'0', - _ => panic!("Bad hex"), - } - if (idx & 1) == 1 { - vec.push(b); - b = 0; - } - } - vec - } - } + ($hex:expr) => ({ + let mut result = vec![0; $hex.len() / 2]; + from_hex($hex, &mut result).expect("valid hex string"); + result + }); } #[test] @@ -482,10 +501,40 @@ mod test { sk.to_string(), "01010101010101010001020304050607ffff0000ffff00006363636363636363" ); + assert_eq!( + SecretKey::from_str("01010101010101010001020304050607ffff0000ffff00006363636363636363").unwrap(), + sk + ); assert_eq!( pk.to_string(), "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166" ); + assert_eq!( + PublicKey::from_str("0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166").unwrap(), + pk + ); + assert_eq!( + PublicKey::from_str("04\ + 18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166\ + 84B84DB303A340CD7D6823EE88174747D12A67D2F8F2F9BA40846EE5EE7A44F6" + ).unwrap(), + pk + ); + + assert!(SecretKey::from_str("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").is_err()); + assert!(SecretKey::from_str("01010101010101010001020304050607ffff0000ffff0000636363636363636363").is_err()); + assert!(SecretKey::from_str("01010101010101010001020304050607ffff0000ffff0000636363636363636").is_err()); + assert!(SecretKey::from_str("01010101010101010001020304050607ffff0000ffff000063636363636363").is_err()); + assert!(SecretKey::from_str("01010101010101010001020304050607ffff0000ffff000063636363636363xx").is_err()); + assert!(PublicKey::from_str("0300000000000000000000000000000000000000000000000000000000000000000").is_err()); + assert!(PublicKey::from_str("0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd16601").is_err()); + assert!(PublicKey::from_str("0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd16").is_err()); + assert!(PublicKey::from_str("0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd1").is_err()); + assert!(PublicKey::from_str("xx0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd1").is_err()); + + let long_str: String = iter::repeat('a').take(1024 * 1024).collect(); + assert!(SecretKey::from_str(&long_str).is_err()); + assert!(PublicKey::from_str(&long_str).is_err()); } #[test] diff --git a/src/lib.rs b/src/lib.rs index f437448..f7f9440 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -708,36 +708,50 @@ impl Secp256k1 { } } +/// Utility function used to parse hex into a target u8 buffer. Returns +/// the number of bytes converted or an error if it encounters an invalid +/// character or unexpected end of string. +fn from_hex(hex: &str, target: &mut [u8]) -> Result { + if hex.len() % 2 == 1 || hex.len() > target.len() * 2 { + return Err(()); + } + + let mut b = 0; + let mut idx = 0; + for c in hex.bytes() { + b <<= 4; + match c { + b'A'...b'F' => b |= c - b'A' + 10, + b'a'...b'f' => b |= c - b'a' + 10, + b'0'...b'9' => b |= c - b'0', + _ => return Err(()), + } + if (idx & 1) == 1 { + target[idx / 2] = b; + b = 0; + } + idx += 1; + } + Ok(idx / 2) +} + + #[cfg(test)] mod tests { use rand::{Rng, thread_rng}; use key::{SecretKey, PublicKey}; + use super::from_hex; use super::constants; use super::{Secp256k1, Signature, RecoverableSignature, Message, RecoveryId}; use super::Error::{InvalidMessage, IncorrectSignature, InvalidSignature}; macro_rules! hex { - ($hex:expr) => { - { - let mut vec = Vec::new(); - let mut b = 0; - for (idx, c) in $hex.as_bytes().iter().enumerate() { - b <<= 4; - match *c { - b'A'...b'F' => b |= c - b'A' + 10, - b'a'...b'f' => b |= c - b'a' + 10, - b'0'...b'9' => b |= c - b'0', - _ => panic!("Bad hex"), - } - if (idx & 1) == 1 { - vec.push(b); - b = 0; - } - } - vec - } - } + ($hex:expr) => ({ + let mut result = vec![0; $hex.len() / 2]; + from_hex($hex, &mut result).expect("valid hex string"); + result + }); } #[test] From 30aa3a0c2856051a264c087b13fd78033e1db187 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 26 Aug 2018 18:52:17 +0000 Subject: [PATCH 2/3] add `fmt::Display` and `str::FromStr` impls for `Signature` --- src/lib.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f7f9440..299e981 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,7 +142,7 @@ extern crate libc; use libc::size_t; -use std::{error, fmt, ops, ptr}; +use std::{error, fmt, ops, ptr, str}; #[cfg(any(test, feature = "rand"))] use rand::Rng; #[macro_use] @@ -164,6 +164,35 @@ pub struct RecoveryId(i32); #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Signature(ffi::Signature); +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut v = [0; 72]; + let mut len = v.len() as size_t; + let secp = Secp256k1::without_caps(); + unsafe { + let err = ffi::secp256k1_ecdsa_signature_serialize_der(secp.ctx, v.as_mut_ptr(), + &mut len, self.as_ptr()); + debug_assert!(err == 1); + } + for ch in &v[..] { + write!(f, "{:02x}", *ch)?; + } + Ok(()) + } +} + +impl str::FromStr for Signature { + type Err = Error; + fn from_str(s: &str) -> Result { + let secp = Secp256k1::without_caps(); + let mut res = [0; 72]; + match from_hex(s, &mut res) { + Ok(x) => Signature::from_der(&secp, &res[0..x]), + _ => Err(Error::InvalidSignature), + } + } +} + /// An ECDSA signature with a recovery ID for pubkey recovery #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct RecoverableSignature(ffi::RecoverableSignature); @@ -739,6 +768,7 @@ fn from_hex(hex: &str, target: &mut [u8]) -> Result { #[cfg(test)] mod tests { use rand::{Rng, thread_rng}; + use std::str::FromStr; use key::{SecretKey, PublicKey}; use super::from_hex; @@ -850,6 +880,42 @@ mod tests { } } + #[test] + fn signature_display() { + let secp = Secp256k1::without_caps(); + let hex_str = "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab45"; + let byte_str = hex!(hex_str); + + assert_eq!( + Signature::from_der(&secp, &byte_str).expect("byte str decode"), + Signature::from_str(&hex_str).expect("byte str decode") + ); + + let sig = Signature::from_str(&hex_str).expect("byte str decode"); + assert_eq!(&sig.to_string(), hex_str); + + assert!(Signature::from_str( + "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a0f436a\ + 72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab4" + ).is_err()); + assert!(Signature::from_str( + "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a0f436a\ + 72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab" + ).is_err()); + assert!(Signature::from_str( + "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a0f436a\ + 72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eabxx" + ).is_err()); + assert!(Signature::from_str( + "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a0f436a\ + 72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab45\ + 72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab45\ + 72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab45\ + 72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab45\ + 72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab45" + ).is_err()); + } + #[test] fn signature_lax_der() { macro_rules! check_lax_sig( From 68c838f357b0b42784298379006bbbf2431de3dd Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 28 Oct 2018 15:11:04 +0000 Subject: [PATCH 3/3] change `Debug` impl for `Signature` to use `Display` --- src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 299e981..2e62425 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,9 +161,15 @@ use std::marker::PhantomData; pub struct RecoveryId(i32); /// An ECDSA signature -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq)] pub struct Signature(ffi::Signature); +impl fmt::Debug for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + impl fmt::Display for Signature { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut v = [0; 72]; @@ -893,6 +899,7 @@ mod tests { let sig = Signature::from_str(&hex_str).expect("byte str decode"); assert_eq!(&sig.to_string(), hex_str); + assert_eq!(&format!("{:?}", sig), hex_str); assert!(Signature::from_str( "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a0f436a\