From b17d7fc31c85c8b3bf6e710ddd3ebe36dc04541e Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 12 Apr 2021 13:19:42 +0200 Subject: [PATCH] Moving keys under `util::ecdsa`, re-exporting them at `util::key` This is the first step in introducing Schnorr key support as per #588 --- examples/bip32.rs | 2 +- src/blockdata/script.rs | 4 +- src/lib.rs | 4 +- src/util/address.rs | 14 +- src/util/bip143.rs | 2 +- src/util/bip32.rs | 4 +- src/util/ecdsa.rs | 511 ++++++++++++++++++++++++++++++++++++ src/util/key.rs | 495 +--------------------------------- src/util/misc.rs | 2 +- src/util/mod.rs | 3 +- src/util/psbt/map/input.rs | 2 +- src/util/psbt/map/output.rs | 2 +- src/util/psbt/mod.rs | 2 +- src/util/psbt/serialize.rs | 2 +- 14 files changed, 538 insertions(+), 511 deletions(-) create mode 100644 src/util/ecdsa.rs diff --git a/examples/bip32.rs b/examples/bip32.rs index 04816f49..3c73501d 100644 --- a/examples/bip32.rs +++ b/examples/bip32.rs @@ -4,7 +4,7 @@ use std::{env, process}; use std::str::FromStr; use bitcoin::secp256k1::Secp256k1; -use bitcoin::util::key::PrivateKey; +use bitcoin::util::ecdsa::PrivateKey; use bitcoin::util::bip32::ExtendedPrivKey; use bitcoin::util::bip32::ExtendedPubKey; use bitcoin::util::bip32::DerivationPath; diff --git a/src/blockdata/script.rs b/src/blockdata/script.rs index 729707d1..1486bc6d 100644 --- a/src/blockdata/script.rs +++ b/src/blockdata/script.rs @@ -37,7 +37,7 @@ use hashes::{Hash, hex}; #[cfg(feature="bitcoinconsensus")] use std::convert; #[cfg(feature="bitcoinconsensus")] use OutPoint; -use util::key::PublicKey; +use util::ecdsa::PublicKey; #[derive(Clone, Default, PartialOrd, Ord, PartialEq, Eq, Hash)] /// A Bitcoin script @@ -889,7 +889,7 @@ mod test { use hashes::hex::{FromHex, ToHex}; use consensus::encode::{deserialize, serialize}; use blockdata::opcodes; - use util::key::PublicKey; + use util::ecdsa::PublicKey; use util::psbt::serialize::Serialize; #[test] diff --git a/src/lib.rs b/src/lib.rs index e7387de4..9937fca4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,8 +85,8 @@ pub use util::address::AddressType; pub use util::amount::Amount; pub use util::amount::Denomination; pub use util::amount::SignedAmount; -pub use util::key::PrivateKey; -pub use util::key::PublicKey; +pub use util::ecdsa::PrivateKey; +pub use util::ecdsa::PublicKey; pub use util::merkleblock::MerkleBlock; #[cfg(all(test, feature = "unstable"))] use tests::EmptyWrite; diff --git a/src/util/address.rs b/src/util/address.rs index 2c26e7cf..ed082761 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -21,13 +21,13 @@ //! //! use bitcoin::network::constants::Network; //! use bitcoin::util::address::Address; -//! use bitcoin::util::key; +//! use bitcoin::util::ecdsa; //! use bitcoin::secp256k1::Secp256k1; //! use bitcoin::secp256k1::rand::thread_rng; //! //! // Generate random key pair //! let s = Secp256k1::new(); -//! let public_key = key::PublicKey { +//! let public_key = ecdsa::PublicKey { //! compressed: true, //! key: s.generate_keypair(&mut thread_rng()).1, //! }; @@ -46,7 +46,7 @@ use hash_types::{PubkeyHash, WPubkeyHash, ScriptHash, WScriptHash}; use blockdata::script; use network::constants::Network; use util::base58; -use util::key; +use util::ecdsa; /// Address error. #[derive(Debug, PartialEq)] @@ -220,7 +220,7 @@ impl Address { /// Creates a pay to (compressed) public key hash address from a public key /// This is the preferred non-witness type address #[inline] - pub fn p2pkh(pk: &key::PublicKey, network: Network) -> Address { + pub fn p2pkh(pk: &ecdsa::PublicKey, network: Network) -> Address { let mut hash_engine = PubkeyHash::engine(); pk.write_into(&mut hash_engine).expect("engines don't error"); @@ -244,7 +244,7 @@ impl Address { /// This is the native segwit address type for an output redeemable with a single signature /// /// Will only return an Error when an uncompressed public key is provided. - pub fn p2wpkh(pk: &key::PublicKey, network: Network) -> Result { + pub fn p2wpkh(pk: &ecdsa::PublicKey, network: Network) -> Result { if !pk.compressed { return Err(Error::UncompressedPubkey); } @@ -265,7 +265,7 @@ impl Address { /// This is a segwit address type that looks familiar (as p2sh) to legacy clients /// /// Will only return an Error when an uncompressed public key is provided. - pub fn p2shwpkh(pk: &key::PublicKey, network: Network) -> Result { + pub fn p2shwpkh(pk: &ecdsa::PublicKey, network: Network) -> Result { if !pk.compressed { return Err(Error::UncompressedPubkey); } @@ -500,7 +500,7 @@ mod tests { use blockdata::script::Script; use network::constants::Network::{Bitcoin, Testnet}; - use util::key::PublicKey; + use util::ecdsa::PublicKey; use super::*; diff --git a/src/util/bip143.rs b/src/util/bip143.rs index d12477db..78f2dd7b 100644 --- a/src/util/bip143.rs +++ b/src/util/bip143.rs @@ -274,7 +274,7 @@ mod tests { use consensus::encode::deserialize; use network::constants::Network; use util::address::Address; - use util::key::PublicKey; + use util::ecdsa::PublicKey; use hashes::hex::FromHex; use super::*; diff --git a/src/util/bip32.rs b/src/util/bip32.rs index 9e30ba6d..7e544d3a 100644 --- a/src/util/bip32.rs +++ b/src/util/bip32.rs @@ -26,8 +26,8 @@ use hashes::{sha512, Hash, HashEngine, Hmac, HmacEngine}; use secp256k1::{self, Secp256k1}; use network::constants::Network; -use util::{base58, endian}; -use util::key::{self, PublicKey, PrivateKey}; +use util::{base58, endian, key}; +use util::ecdsa::{PublicKey, PrivateKey}; /// A chain code #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/src/util/ecdsa.rs b/src/util/ecdsa.rs new file mode 100644 index 00000000..e8b2d78e --- /dev/null +++ b/src/util/ecdsa.rs @@ -0,0 +1,511 @@ +// Rust Bitcoin Library +// Written in 2014 by +// Andrew Poelstra +// 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 . +// + +//! ECDSA Bitcoin Keys +//! +//! ECDSA keys used in Bitcoin that can be roundtrip (de)serialized. +//! + +use std::fmt::{self, Write}; +use std::{io, ops}; +use std::str::FromStr; + +use secp256k1::{self, Secp256k1}; +use network::constants::Network; +use hashes::{Hash, hash160}; +use hash_types::{PubkeyHash, WPubkeyHash}; +use util::base58; +use util::key::Error; + +/// A Bitcoin ECDSA public key +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PublicKey { + /// Whether this public key should be serialized as compressed + pub compressed: bool, + /// The actual ECDSA key + pub key: secp256k1::PublicKey, +} + +impl PublicKey { + /// Returns bitcoin 160-bit hash of the public key + pub fn pubkey_hash(&self) -> PubkeyHash { + if self.compressed { + PubkeyHash::hash(&self.key.serialize()) + } else { + PubkeyHash::hash(&self.key.serialize_uncompressed()) + } + } + + /// Returns bitcoin 160-bit hash of the public key for witness program + pub fn wpubkey_hash(&self) -> Option { + if self.compressed { + Some(WPubkeyHash::from_inner( + hash160::Hash::hash(&self.key.serialize()).into_inner() + )) + } else { + // We can't create witness pubkey hashes for an uncompressed + // public keys + None + } + } + + /// Write the public key into a writer + pub fn write_into(&self, mut writer: W) -> Result<(), io::Error> { + if self.compressed { + writer.write_all(&self.key.serialize()) + } else { + writer.write_all(&self.key.serialize_uncompressed()) + } + } + + /// Read the public key from a reader + /// + /// This internally reads the first byte before reading the rest, so + /// use of a `BufReader` is recommended. + pub fn read_from(mut reader: R) -> Result { + let mut bytes = [0; 65]; + + reader.read_exact(&mut bytes[0..1])?; + let bytes = if bytes[0] < 4 { + &mut bytes[..33] + } else { + &mut bytes[..65] + }; + + reader.read_exact(&mut bytes[1..])?; + Self::from_slice(bytes).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + } + + /// Serialize the public key to bytes + pub fn to_bytes(&self) -> Vec { + let mut buf = Vec::new(); + self.write_into(&mut buf).expect("vecs don't error"); + buf + } + + /// Deserialize a public key from a slice + pub fn from_slice(data: &[u8]) -> Result { + let compressed: bool = match data.len() { + 33 => true, + 65 => false, + len => { return Err(base58::Error::InvalidLength(len).into()); }, + }; + + Ok(PublicKey { + compressed: compressed, + key: secp256k1::PublicKey::from_slice(data)?, + }) + } + + /// Computes the public key as supposed to be used with this secret + pub fn from_private_key(secp: &Secp256k1, sk: &PrivateKey) -> PublicKey { + sk.public_key(secp) + } +} + +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.compressed { + for ch in &self.key.serialize()[..] { + write!(f, "{:02x}", ch)?; + } + } else { + for ch in &self.key.serialize_uncompressed()[..] { + write!(f, "{:02x}", ch)?; + } + } + Ok(()) + } +} + +impl FromStr for PublicKey { + type Err = Error; + fn from_str(s: &str) -> Result { + let key = secp256k1::PublicKey::from_str(s)?; + Ok(PublicKey { + key: key, + compressed: s.len() == 66 + }) + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +/// A Bitcoin ECDSA private key +pub struct PrivateKey { + /// Whether this private key should be serialized as compressed + pub compressed: bool, + /// The network on which this key should be used + pub network: Network, + /// The actual ECDSA key + pub key: secp256k1::SecretKey, +} + +impl PrivateKey { + /// Creates a public key from this private key + pub fn public_key(&self, secp: &Secp256k1) -> PublicKey { + PublicKey { + compressed: self.compressed, + key: secp256k1::PublicKey::from_secret_key(secp, &self.key) + } + } + + /// Serialize the private key to bytes + pub fn to_bytes(&self) -> Vec { + self.key[..].to_vec() + } + + /// Format the private key to WIF format. + pub fn fmt_wif(&self, fmt: &mut dyn fmt::Write) -> fmt::Result { + let mut ret = [0; 34]; + ret[0] = match self.network { + Network::Bitcoin => 128, + Network::Testnet | Network::Signet | Network::Regtest => 239, + }; + ret[1..33].copy_from_slice(&self.key[..]); + let privkey = if self.compressed { + ret[33] = 1; + base58::check_encode_slice(&ret[..]) + } else { + base58::check_encode_slice(&ret[..33]) + }; + fmt.write_str(&privkey) + } + + /// Get WIF encoding of this private key. + pub fn to_wif(&self) -> String { + let mut buf = String::new(); + buf.write_fmt(format_args!("{}", self)).unwrap(); + buf.shrink_to_fit(); + buf + } + + /// Parse WIF encoded private key. + pub fn from_wif(wif: &str) -> Result { + let data = base58::from_check(wif)?; + + let compressed = match data.len() { + 33 => false, + 34 => true, + _ => { return Err(Error::Base58(base58::Error::InvalidLength(data.len()))); } + }; + + let network = match data[0] { + 128 => Network::Bitcoin, + 239 => Network::Testnet, + x => { return Err(Error::Base58(base58::Error::InvalidVersion(vec![x]))); } + }; + + Ok(PrivateKey { + compressed: compressed, + network: network, + key: secp256k1::SecretKey::from_slice(&data[1..33])?, + }) + } +} + +impl fmt::Display for PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.fmt_wif(f) + } +} + +impl fmt::Debug for PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[private key data]") + } +} + +impl FromStr for PrivateKey { + type Err = Error; + fn from_str(s: &str) -> Result { + PrivateKey::from_wif(s) + } +} + +impl ops::Index for PrivateKey { + type Output = [u8]; + fn index(&self, _: ops::RangeFull) -> &[u8] { + &self.key[..] + } +} + +#[cfg(feature = "serde")] +impl ::serde::Serialize for PrivateKey { + fn serialize(&self, s: S) -> Result { + s.collect_str(self) + } +} + +#[cfg(feature = "serde")] +impl<'de> ::serde::Deserialize<'de> for PrivateKey { + fn deserialize>(d: D) -> Result { + struct WifVisitor; + + impl<'de> ::serde::de::Visitor<'de> for WifVisitor { + type Value = PrivateKey; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("an ASCII WIF string") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + if let Ok(s) = ::std::str::from_utf8(v) { + PrivateKey::from_str(s).map_err(E::custom) + } else { + Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: ::serde::de::Error, + { + PrivateKey::from_str(v).map_err(E::custom) + } + } + + d.deserialize_str(WifVisitor) + } +} + +#[cfg(feature = "serde")] +impl ::serde::Serialize for PublicKey { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + s.collect_str(self) + } else { + if self.compressed { + s.serialize_bytes(&self.key.serialize()[..]) + } else { + s.serialize_bytes(&self.key.serialize_uncompressed()[..]) + } + } + } +} + +#[cfg(feature = "serde")] +impl<'de> ::serde::Deserialize<'de> for PublicKey { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + struct HexVisitor; + + impl<'de> ::serde::de::Visitor<'de> for HexVisitor { + type Value = PublicKey; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("an ASCII hex string") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + if let Ok(hex) = ::std::str::from_utf8(v) { + PublicKey::from_str(hex).map_err(E::custom) + } else { + Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: ::serde::de::Error, + { + PublicKey::from_str(v).map_err(E::custom) + } + } + d.deserialize_str(HexVisitor) + } else { + struct BytesVisitor; + + impl<'de> ::serde::de::Visitor<'de> for BytesVisitor { + type Value = PublicKey; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("a bytestring") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + PublicKey::from_slice(v).map_err(E::custom) + } + } + + d.deserialize_bytes(BytesVisitor) + } + } +} + +#[cfg(test)] +mod tests { + use super::{PrivateKey, PublicKey}; + use secp256k1::Secp256k1; + use std::io; + use std::str::FromStr; + use hashes::hex::ToHex; + use network::constants::Network::Testnet; + use network::constants::Network::Bitcoin; + use util::address::Address; + + #[test] + fn test_key_derivation() { + // testnet compressed + let sk = PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap(); + assert_eq!(sk.network, Testnet); + assert_eq!(sk.compressed, true); + assert_eq!(&sk.to_wif(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy"); + + let secp = Secp256k1::new(); + let pk = Address::p2pkh(&sk.public_key(&secp), sk.network); + assert_eq!(&pk.to_string(), "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"); + + // test string conversion + assert_eq!(&sk.to_string(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy"); + let sk_str = + PrivateKey::from_str("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap(); + assert_eq!(&sk.to_wif(), &sk_str.to_wif()); + + // mainnet uncompressed + let sk = PrivateKey::from_wif("5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3").unwrap(); + assert_eq!(sk.network, Bitcoin); + assert_eq!(sk.compressed, false); + assert_eq!(&sk.to_wif(), "5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3"); + + let secp = Secp256k1::new(); + let mut pk = sk.public_key(&secp); + assert_eq!(pk.compressed, false); + assert_eq!(&pk.to_string(), "042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133"); + assert_eq!(pk, PublicKey::from_str("042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133").unwrap()); + let addr = Address::p2pkh(&pk, sk.network); + assert_eq!(&addr.to_string(), "1GhQvF6dL8xa6wBxLnWmHcQsurx9RxiMc8"); + pk.compressed = true; + assert_eq!(&pk.to_string(), "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af"); + assert_eq!(pk, PublicKey::from_str("032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af").unwrap()); + } + + #[test] + fn test_pubkey_hash() { + let pk = PublicKey::from_str("032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af").unwrap(); + let upk = PublicKey::from_str("042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133").unwrap(); + assert_eq!(pk.pubkey_hash().to_hex(), "9511aa27ef39bbfa4e4f3dd15f4d66ea57f475b4"); + assert_eq!(upk.pubkey_hash().to_hex(), "ac2e7daf42d2c97418fd9f78af2de552bb9c6a7a"); + } + + #[test] + fn test_wpubkey_hash() { + let pk = PublicKey::from_str("032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af").unwrap(); + let upk = PublicKey::from_str("042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133").unwrap(); + assert_eq!(pk.wpubkey_hash().unwrap().to_hex(), "9511aa27ef39bbfa4e4f3dd15f4d66ea57f475b4"); + assert_eq!(upk.wpubkey_hash(), None); + } + + #[cfg(feature = "serde")] + #[test] + fn test_key_serde() { + use serde_test::{Configure, Token, assert_tokens}; + + static KEY_WIF: &'static str = "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy"; + static PK_STR: &'static str = "039b6347398505f5ec93826dc61c19f47c66c0283ee9be980e29ce325a0f4679ef"; + static PK_STR_U: &'static str = "\ + 04\ + 9b6347398505f5ec93826dc61c19f47c66c0283ee9be980e29ce325a0f4679ef\ + 87288ed73ce47fc4f5c79d19ebfa57da7cff3aff6e819e4ee971d86b5e61875d\ + "; + static PK_BYTES: [u8; 33] = [ + 0x03, + 0x9b, 0x63, 0x47, 0x39, 0x85, 0x05, 0xf5, 0xec, + 0x93, 0x82, 0x6d, 0xc6, 0x1c, 0x19, 0xf4, 0x7c, + 0x66, 0xc0, 0x28, 0x3e, 0xe9, 0xbe, 0x98, 0x0e, + 0x29, 0xce, 0x32, 0x5a, 0x0f, 0x46, 0x79, 0xef, + ]; + static PK_BYTES_U: [u8; 65] = [ + 0x04, + 0x9b, 0x63, 0x47, 0x39, 0x85, 0x05, 0xf5, 0xec, + 0x93, 0x82, 0x6d, 0xc6, 0x1c, 0x19, 0xf4, 0x7c, + 0x66, 0xc0, 0x28, 0x3e, 0xe9, 0xbe, 0x98, 0x0e, + 0x29, 0xce, 0x32, 0x5a, 0x0f, 0x46, 0x79, 0xef, + 0x87, 0x28, 0x8e, 0xd7, 0x3c, 0xe4, 0x7f, 0xc4, + 0xf5, 0xc7, 0x9d, 0x19, 0xeb, 0xfa, 0x57, 0xda, + 0x7c, 0xff, 0x3a, 0xff, 0x6e, 0x81, 0x9e, 0x4e, + 0xe9, 0x71, 0xd8, 0x6b, 0x5e, 0x61, 0x87, 0x5d, + ]; + + let s = Secp256k1::new(); + let sk = PrivateKey::from_str(&KEY_WIF).unwrap(); + let pk = PublicKey::from_private_key(&s, &sk); + let pk_u = PublicKey { + key: pk.key, + compressed: false, + }; + + assert_tokens(&sk, &[Token::BorrowedStr(KEY_WIF)]); + assert_tokens(&pk.compact(), &[Token::BorrowedBytes(&PK_BYTES[..])]); + assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]); + assert_tokens(&pk_u.compact(), &[Token::BorrowedBytes(&PK_BYTES_U[..])]); + assert_tokens(&pk_u.readable(), &[Token::BorrowedStr(PK_STR_U)]); + } + + fn random_key(mut seed: u8) -> PublicKey { + loop { + let mut data = [0; 65]; + for byte in &mut data[..] { + *byte = seed; + // totally a rng + seed = seed.wrapping_mul(41).wrapping_add(43); + } + if data[0] % 2 == 0 { + data[0] = 4; + if let Ok(key) = PublicKey::from_slice(&data[..]) { + return key; + } + } else { + data[0] = 2 + (data[0] >> 7); + if let Ok(key) = PublicKey::from_slice(&data[..33]) { + return key; + } + } + } + } + + #[test] + fn pubkey_read_write() { + const N_KEYS: usize = 20; + let keys: Vec<_> = (0..N_KEYS).map(|i| random_key(i as u8)).collect(); + + let mut v = vec![]; + for k in &keys { + k.write_into(&mut v).expect("writing into vec"); + } + + let mut dec_keys = vec![]; + let mut cursor = io::Cursor::new(&v); + for _ in 0..N_KEYS { + dec_keys.push(PublicKey::read_from(&mut cursor).expect("reading from vec")); + } + + assert_eq!(keys, dec_keys); + + // sanity checks + assert!(PublicKey::read_from(&mut cursor).is_err()); + assert!(PublicKey::read_from(io::Cursor::new(&[])).is_err()); + assert!(PublicKey::read_from(io::Cursor::new(&[0; 33][..])).is_err()); + assert!(PublicKey::read_from(io::Cursor::new(&[2; 32][..])).is_err()); + assert!(PublicKey::read_from(io::Cursor::new(&[0; 65][..])).is_err()); + assert!(PublicKey::read_from(io::Cursor::new(&[4; 64][..])).is_err()); + } +} diff --git a/src/util/key.rs b/src/util/key.rs index b20ff93b..d71f7065 100644 --- a/src/util/key.rs +++ b/src/util/key.rs @@ -16,14 +16,12 @@ //! Keys used in Bitcoin that can be roundtrip (de)serialized. //! -use std::fmt::{self, Write}; -use std::{io, ops, error}; -use std::str::FromStr; +pub use util::ecdsa::{PrivateKey, PublicKey}; -use secp256k1::{self, Secp256k1}; -use network::constants::Network; -use hashes::{Hash, hash160}; -use hash_types::{PubkeyHash, WPubkeyHash}; +use std::fmt; +use std::error; + +use secp256k1; use util::base58; /// A key-related error. @@ -67,486 +65,3 @@ impl From for Error { Error::Secp256k1(e) } } - -/// A Bitcoin ECDSA public key -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PublicKey { - /// Whether this public key should be serialized as compressed - pub compressed: bool, - /// The actual ECDSA key - pub key: secp256k1::PublicKey, -} - -impl PublicKey { - /// Returns bitcoin 160-bit hash of the public key - pub fn pubkey_hash(&self) -> PubkeyHash { - if self.compressed { - PubkeyHash::hash(&self.key.serialize()) - } else { - PubkeyHash::hash(&self.key.serialize_uncompressed()) - } - } - - /// Returns bitcoin 160-bit hash of the public key for witness program - pub fn wpubkey_hash(&self) -> Option { - if self.compressed { - Some(WPubkeyHash::from_inner( - hash160::Hash::hash(&self.key.serialize()).into_inner() - )) - } else { - // We can't create witness pubkey hashes for an uncompressed - // public keys - None - } - } - - /// Write the public key into a writer - pub fn write_into(&self, mut writer: W) -> Result<(), io::Error> { - if self.compressed { - writer.write_all(&self.key.serialize()) - } else { - writer.write_all(&self.key.serialize_uncompressed()) - } - } - - /// Read the public key from a reader - /// - /// This internally reads the first byte before reading the rest, so - /// use of a `BufReader` is recommended. - pub fn read_from(mut reader: R) -> Result { - let mut bytes = [0; 65]; - - reader.read_exact(&mut bytes[0..1])?; - let bytes = if bytes[0] < 4 { - &mut bytes[..33] - } else { - &mut bytes[..65] - }; - - reader.read_exact(&mut bytes[1..])?; - Self::from_slice(bytes).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) - } - - /// Serialize the public key to bytes - pub fn to_bytes(&self) -> Vec { - let mut buf = Vec::new(); - self.write_into(&mut buf).expect("vecs don't error"); - buf - } - - /// Deserialize a public key from a slice - pub fn from_slice(data: &[u8]) -> Result { - let compressed: bool = match data.len() { - 33 => true, - 65 => false, - len => { return Err(base58::Error::InvalidLength(len).into()); }, - }; - - Ok(PublicKey { - compressed: compressed, - key: secp256k1::PublicKey::from_slice(data)?, - }) - } - - /// Computes the public key as supposed to be used with this secret - pub fn from_private_key(secp: &Secp256k1, sk: &PrivateKey) -> PublicKey { - sk.public_key(secp) - } -} - -impl fmt::Display for PublicKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.compressed { - for ch in &self.key.serialize()[..] { - write!(f, "{:02x}", ch)?; - } - } else { - for ch in &self.key.serialize_uncompressed()[..] { - write!(f, "{:02x}", ch)?; - } - } - Ok(()) - } -} - -impl FromStr for PublicKey { - type Err = Error; - fn from_str(s: &str) -> Result { - let key = secp256k1::PublicKey::from_str(s)?; - Ok(PublicKey { - key: key, - compressed: s.len() == 66 - }) - } -} - -#[derive(Copy, Clone, PartialEq, Eq)] -/// A Bitcoin ECDSA private key -pub struct PrivateKey { - /// Whether this private key should be serialized as compressed - pub compressed: bool, - /// The network on which this key should be used - pub network: Network, - /// The actual ECDSA key - pub key: secp256k1::SecretKey, -} - -impl PrivateKey { - /// Creates a public key from this private key - pub fn public_key(&self, secp: &Secp256k1) -> PublicKey { - PublicKey { - compressed: self.compressed, - key: secp256k1::PublicKey::from_secret_key(secp, &self.key) - } - } - - /// Serialize the private key to bytes - pub fn to_bytes(&self) -> Vec { - self.key[..].to_vec() - } - - /// Format the private key to WIF format. - pub fn fmt_wif(&self, fmt: &mut dyn fmt::Write) -> fmt::Result { - let mut ret = [0; 34]; - ret[0] = match self.network { - Network::Bitcoin => 128, - Network::Testnet | Network::Signet | Network::Regtest => 239, - }; - ret[1..33].copy_from_slice(&self.key[..]); - let privkey = if self.compressed { - ret[33] = 1; - base58::check_encode_slice(&ret[..]) - } else { - base58::check_encode_slice(&ret[..33]) - }; - fmt.write_str(&privkey) - } - - /// Get WIF encoding of this private key. - pub fn to_wif(&self) -> String { - let mut buf = String::new(); - buf.write_fmt(format_args!("{}", self)).unwrap(); - buf.shrink_to_fit(); - buf - } - - /// Parse WIF encoded private key. - pub fn from_wif(wif: &str) -> Result { - let data = base58::from_check(wif)?; - - let compressed = match data.len() { - 33 => false, - 34 => true, - _ => { return Err(Error::Base58(base58::Error::InvalidLength(data.len()))); } - }; - - let network = match data[0] { - 128 => Network::Bitcoin, - 239 => Network::Testnet, - x => { return Err(Error::Base58(base58::Error::InvalidVersion(vec![x]))); } - }; - - Ok(PrivateKey { - compressed: compressed, - network: network, - key: secp256k1::SecretKey::from_slice(&data[1..33])?, - }) - } -} - -impl fmt::Display for PrivateKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.fmt_wif(f) - } -} - -impl fmt::Debug for PrivateKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "[private key data]") - } -} - -impl FromStr for PrivateKey { - type Err = Error; - fn from_str(s: &str) -> Result { - PrivateKey::from_wif(s) - } -} - -impl ops::Index for PrivateKey { - type Output = [u8]; - fn index(&self, _: ops::RangeFull) -> &[u8] { - &self.key[..] - } -} - -#[cfg(feature = "serde")] -impl ::serde::Serialize for PrivateKey { - fn serialize(&self, s: S) -> Result { - s.collect_str(self) - } -} - -#[cfg(feature = "serde")] -impl<'de> ::serde::Deserialize<'de> for PrivateKey { - fn deserialize>(d: D) -> Result { - struct WifVisitor; - - impl<'de> ::serde::de::Visitor<'de> for WifVisitor { - type Value = PrivateKey; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("an ASCII WIF string") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - if let Ok(s) = ::std::str::from_utf8(v) { - PrivateKey::from_str(s).map_err(E::custom) - } else { - Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) - } - } - - fn visit_str(self, v: &str) -> Result - where - E: ::serde::de::Error, - { - PrivateKey::from_str(v).map_err(E::custom) - } - } - - d.deserialize_str(WifVisitor) - } -} - -#[cfg(feature = "serde")] -impl ::serde::Serialize for PublicKey { - fn serialize(&self, s: S) -> Result { - if s.is_human_readable() { - s.collect_str(self) - } else { - if self.compressed { - s.serialize_bytes(&self.key.serialize()[..]) - } else { - s.serialize_bytes(&self.key.serialize_uncompressed()[..]) - } - } - } -} - -#[cfg(feature = "serde")] -impl<'de> ::serde::Deserialize<'de> for PublicKey { - fn deserialize>(d: D) -> Result { - if d.is_human_readable() { - struct HexVisitor; - - impl<'de> ::serde::de::Visitor<'de> for HexVisitor { - type Value = PublicKey; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("an ASCII hex string") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - if let Ok(hex) = ::std::str::from_utf8(v) { - PublicKey::from_str(hex).map_err(E::custom) - } else { - Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) - } - } - - fn visit_str(self, v: &str) -> Result - where - E: ::serde::de::Error, - { - PublicKey::from_str(v).map_err(E::custom) - } - } - d.deserialize_str(HexVisitor) - } else { - struct BytesVisitor; - - impl<'de> ::serde::de::Visitor<'de> for BytesVisitor { - type Value = PublicKey; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("a bytestring") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - PublicKey::from_slice(v).map_err(E::custom) - } - } - - d.deserialize_bytes(BytesVisitor) - } - } -} - -#[cfg(test)] -mod tests { - use super::{PrivateKey, PublicKey}; - use secp256k1::Secp256k1; - use std::io; - use std::str::FromStr; - use hashes::hex::ToHex; - use network::constants::Network::Testnet; - use network::constants::Network::Bitcoin; - use util::address::Address; - - #[test] - fn test_key_derivation() { - // testnet compressed - let sk = PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap(); - assert_eq!(sk.network, Testnet); - assert_eq!(sk.compressed, true); - assert_eq!(&sk.to_wif(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy"); - - let secp = Secp256k1::new(); - let pk = Address::p2pkh(&sk.public_key(&secp), sk.network); - assert_eq!(&pk.to_string(), "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"); - - // test string conversion - assert_eq!(&sk.to_string(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy"); - let sk_str = - PrivateKey::from_str("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap(); - assert_eq!(&sk.to_wif(), &sk_str.to_wif()); - - // mainnet uncompressed - let sk = PrivateKey::from_wif("5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3").unwrap(); - assert_eq!(sk.network, Bitcoin); - assert_eq!(sk.compressed, false); - assert_eq!(&sk.to_wif(), "5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3"); - - let secp = Secp256k1::new(); - let mut pk = sk.public_key(&secp); - assert_eq!(pk.compressed, false); - assert_eq!(&pk.to_string(), "042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133"); - assert_eq!(pk, PublicKey::from_str("042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133").unwrap()); - let addr = Address::p2pkh(&pk, sk.network); - assert_eq!(&addr.to_string(), "1GhQvF6dL8xa6wBxLnWmHcQsurx9RxiMc8"); - pk.compressed = true; - assert_eq!(&pk.to_string(), "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af"); - assert_eq!(pk, PublicKey::from_str("032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af").unwrap()); - } - - #[test] - fn test_pubkey_hash() { - let pk = PublicKey::from_str("032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af").unwrap(); - let upk = PublicKey::from_str("042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133").unwrap(); - assert_eq!(pk.pubkey_hash().to_hex(), "9511aa27ef39bbfa4e4f3dd15f4d66ea57f475b4"); - assert_eq!(upk.pubkey_hash().to_hex(), "ac2e7daf42d2c97418fd9f78af2de552bb9c6a7a"); - } - - #[test] - fn test_wpubkey_hash() { - let pk = PublicKey::from_str("032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af").unwrap(); - let upk = PublicKey::from_str("042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133").unwrap(); - assert_eq!(pk.wpubkey_hash().unwrap().to_hex(), "9511aa27ef39bbfa4e4f3dd15f4d66ea57f475b4"); - assert_eq!(upk.wpubkey_hash(), None); - } - - #[cfg(feature = "serde")] - #[test] - fn test_key_serde() { - use serde_test::{Configure, Token, assert_tokens}; - - static KEY_WIF: &'static str = "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy"; - static PK_STR: &'static str = "039b6347398505f5ec93826dc61c19f47c66c0283ee9be980e29ce325a0f4679ef"; - static PK_STR_U: &'static str = "\ - 04\ - 9b6347398505f5ec93826dc61c19f47c66c0283ee9be980e29ce325a0f4679ef\ - 87288ed73ce47fc4f5c79d19ebfa57da7cff3aff6e819e4ee971d86b5e61875d\ - "; - static PK_BYTES: [u8; 33] = [ - 0x03, - 0x9b, 0x63, 0x47, 0x39, 0x85, 0x05, 0xf5, 0xec, - 0x93, 0x82, 0x6d, 0xc6, 0x1c, 0x19, 0xf4, 0x7c, - 0x66, 0xc0, 0x28, 0x3e, 0xe9, 0xbe, 0x98, 0x0e, - 0x29, 0xce, 0x32, 0x5a, 0x0f, 0x46, 0x79, 0xef, - ]; - static PK_BYTES_U: [u8; 65] = [ - 0x04, - 0x9b, 0x63, 0x47, 0x39, 0x85, 0x05, 0xf5, 0xec, - 0x93, 0x82, 0x6d, 0xc6, 0x1c, 0x19, 0xf4, 0x7c, - 0x66, 0xc0, 0x28, 0x3e, 0xe9, 0xbe, 0x98, 0x0e, - 0x29, 0xce, 0x32, 0x5a, 0x0f, 0x46, 0x79, 0xef, - 0x87, 0x28, 0x8e, 0xd7, 0x3c, 0xe4, 0x7f, 0xc4, - 0xf5, 0xc7, 0x9d, 0x19, 0xeb, 0xfa, 0x57, 0xda, - 0x7c, 0xff, 0x3a, 0xff, 0x6e, 0x81, 0x9e, 0x4e, - 0xe9, 0x71, 0xd8, 0x6b, 0x5e, 0x61, 0x87, 0x5d, - ]; - - let s = Secp256k1::new(); - let sk = PrivateKey::from_str(&KEY_WIF).unwrap(); - let pk = PublicKey::from_private_key(&s, &sk); - let pk_u = PublicKey { - key: pk.key, - compressed: false, - }; - - assert_tokens(&sk, &[Token::BorrowedStr(KEY_WIF)]); - assert_tokens(&pk.compact(), &[Token::BorrowedBytes(&PK_BYTES[..])]); - assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]); - assert_tokens(&pk_u.compact(), &[Token::BorrowedBytes(&PK_BYTES_U[..])]); - assert_tokens(&pk_u.readable(), &[Token::BorrowedStr(PK_STR_U)]); - } - - fn random_key(mut seed: u8) -> PublicKey { - loop { - let mut data = [0; 65]; - for byte in &mut data[..] { - *byte = seed; - // totally a rng - seed = seed.wrapping_mul(41).wrapping_add(43); - } - if data[0] % 2 == 0 { - data[0] = 4; - if let Ok(key) = PublicKey::from_slice(&data[..]) { - return key; - } - } else { - data[0] = 2 + (data[0] >> 7); - if let Ok(key) = PublicKey::from_slice(&data[..33]) { - return key; - } - } - } - } - - #[test] - fn pubkey_read_write() { - const N_KEYS: usize = 20; - let keys: Vec<_> = (0..N_KEYS).map(|i| random_key(i as u8)).collect(); - - let mut v = vec![]; - for k in &keys { - k.write_into(&mut v).expect("writing into vec"); - } - - let mut dec_keys = vec![]; - let mut cursor = io::Cursor::new(&v); - for _ in 0..N_KEYS { - dec_keys.push(PublicKey::read_from(&mut cursor).expect("reading from vec")); - } - - assert_eq!(keys, dec_keys); - - // sanity checks - assert!(PublicKey::read_from(&mut cursor).is_err()); - assert!(PublicKey::read_from(io::Cursor::new(&[])).is_err()); - assert!(PublicKey::read_from(io::Cursor::new(&[0; 33][..])).is_err()); - assert!(PublicKey::read_from(io::Cursor::new(&[2; 32][..])).is_err()); - assert!(PublicKey::read_from(io::Cursor::new(&[0; 65][..])).is_err()); - assert!(PublicKey::read_from(io::Cursor::new(&[4; 64][..])).is_err()); - } -} diff --git a/src/util/misc.rs b/src/util/misc.rs index f99761a8..571889ad 100644 --- a/src/util/misc.rs +++ b/src/util/misc.rs @@ -35,7 +35,7 @@ mod message_signing { use secp256k1; use secp256k1::recovery::{RecoveryId, RecoverableSignature}; - use util::key::PublicKey; + use util::ecdsa::PublicKey; use util::address::{Address, AddressType}; /// An error used for dealing with Bitcoin Signed Messages. diff --git a/src/util/mod.rs b/src/util/mod.rs index e102c586..7f92156c 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -16,7 +16,7 @@ //! //! Functions needed by all parts of the Bitcoin library -pub mod key; +pub mod ecdsa; pub mod address; pub mod amount; pub mod base58; @@ -30,6 +30,7 @@ pub mod psbt; pub mod taproot; pub mod uint; pub mod bip158; +pub mod key; pub(crate) mod endian; diff --git a/src/util/psbt/map/input.rs b/src/util/psbt/map/input.rs index ab114e0c..ddcc1f8a 100644 --- a/src/util/psbt/map/input.rs +++ b/src/util/psbt/map/input.rs @@ -20,7 +20,7 @@ use blockdata::transaction::{SigHashType, Transaction, TxOut}; use consensus::encode; use util::bip32::KeySource; use hashes::{self, hash160, ripemd160, sha256, sha256d}; -use util::key::PublicKey; +use util::ecdsa::PublicKey; use util::psbt; use util::psbt::map::Map; use util::psbt::raw; diff --git a/src/util/psbt/map/output.rs b/src/util/psbt/map/output.rs index 1c40ec1b..6eaa9d78 100644 --- a/src/util/psbt/map/output.rs +++ b/src/util/psbt/map/output.rs @@ -19,7 +19,7 @@ use std::collections::btree_map::Entry; use blockdata::script::Script; use consensus::encode; use util::bip32::KeySource; -use util::key::PublicKey; +use util::ecdsa::PublicKey; use util::psbt; use util::psbt::map::Map; use util::psbt::raw; diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index 3cd2ef89..9e7d9110 100644 --- a/src/util/psbt/mod.rs +++ b/src/util/psbt/mod.rs @@ -222,7 +222,7 @@ mod tests { use network::constants::Network::Bitcoin; use consensus::encode::{deserialize, serialize, serialize_hex}; use util::bip32::{ChildNumber, ExtendedPrivKey, ExtendedPubKey, Fingerprint, KeySource}; - use util::key::PublicKey; + use util::ecdsa::PublicKey; use util::psbt::map::{Global, Output, Input}; use util::psbt::raw; diff --git a/src/util/psbt/serialize.rs b/src/util/psbt/serialize.rs index 5e530a45..d20a3b26 100644 --- a/src/util/psbt/serialize.rs +++ b/src/util/psbt/serialize.rs @@ -24,7 +24,7 @@ use blockdata::transaction::{SigHashType, Transaction, TxOut}; use consensus::encode::{self, serialize, Decodable}; use util::bip32::{ChildNumber, Fingerprint, KeySource}; use hashes::{hash160, ripemd160, sha256, sha256d, Hash}; -use util::key::PublicKey; +use util::ecdsa::PublicKey; use util::psbt; /// A trait for serializing a value as raw data for insertion into PSBT