Skip to content

Commit

Permalink
Merge pull request #60 from rust-bitcoin/2018-08-fromstr
Browse files Browse the repository at this point in the history
add `FromStr` implementation for key types
  • Loading branch information
apoelstra authored Nov 4, 2018
2 parents 0b1640f + 68c838f commit 724192e
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 45 deletions.
95 changes: 72 additions & 23 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -40,6 +40,17 @@ impl fmt::Display for SecretKey {
}
}

impl str::FromStr for SecretKey {
type Err = Error;
fn from_str(s: &str) -> Result<SecretKey, Error> {
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,
Expand Down Expand Up @@ -73,6 +84,26 @@ impl fmt::Display for PublicKey {
}
}

impl str::FromStr for PublicKey {
type Err = Error;
fn from_str(s: &str) -> Result<PublicKey, Error> {
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<R: Rng>(rng: &mut R) -> [u8; 32] {
let mut ret = [0u8; 32];
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
131 changes: 109 additions & 22 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -161,9 +161,44 @@ 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];
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<Signature, Error> {
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);
Expand Down Expand Up @@ -708,36 +743,51 @@ impl<C: Verification> Secp256k1<C> {
}
}

/// 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<usize, ()> {
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 std::str::FromStr;

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]
Expand Down Expand Up @@ -836,6 +886,43 @@ 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_eq!(&format!("{:?}", sig), 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(
Expand Down

0 comments on commit 724192e

Please sign in to comment.