diff --git a/README.md b/README.md
index f12e22c7..2a79d35d 100644
--- a/README.md
+++ b/README.md
@@ -92,3 +92,5 @@ See Transaction::verify and Script::verify methods.
* Add bech32 support
+* Support segwit address types
+
diff --git a/src/blockdata/script.rs b/src/blockdata/script.rs
index 5fdba177..585521de 100644
--- a/src/blockdata/script.rs
+++ b/src/blockdata/script.rs
@@ -328,6 +328,14 @@ impl Script {
self.0[24] == opcodes::All::OP_CHECKSIG as u8
}
+ /// Checks whether a script pubkey is a p2pkh output
+ #[inline]
+ pub fn is_p2pk(&self) -> bool {
+ self.0.len() == 67 &&
+ self.0[0] == opcodes::All::OP_PUSHBYTES_65 as u8 &&
+ self.0[66] == opcodes::All::OP_CHECKSIG as u8
+ }
+
/// Checks whether a script pubkey is a p2wsh output
#[inline]
pub fn is_v0_p2wsh(&self) -> bool {
@@ -336,6 +344,14 @@ impl Script {
self.0[1] == opcodes::All::OP_PUSHBYTES_32 as u8
}
+ /// Checks whether a script pubkey is a p2wpkh output
+ #[inline]
+ pub fn is_v0_p2wpkh(&self) -> bool {
+ self.0.len() == 22 &&
+ self.0[0] == opcodes::All::OP_PUSHBYTES_0 as u8 &&
+ self.0[1] == opcodes::All::OP_PUSHBYTES_20 as u8
+ }
+
/// Whether a script can be proven to have no satisfying input
pub fn is_provably_unspendable(&self) -> bool {
!self.0.is_empty() && (opcodes::All::from(self.0[0]).classify() == opcodes::Class::ReturnOp ||
diff --git a/src/util/address.rs b/src/util/address.rs
index 18d381dd..9a8b5d99 100644
--- a/src/util/address.rs
+++ b/src/util/address.rs
@@ -19,8 +19,7 @@ use std::str::FromStr;
use std::string::ToString;
use bitcoin_bech32::{self, WitnessProgram};
-use secp256k1::Secp256k1;
-use secp256k1::key::{PublicKey, SecretKey};
+use secp256k1::key::PublicKey;
use blockdata::script;
use blockdata::opcodes;
@@ -32,6 +31,8 @@ use util::Error;
/// The method used to produce an address
#[derive(Clone, PartialEq, Debug)]
pub enum Payload {
+ /// pay-to-pubkey
+ Pubkey(PublicKey),
/// pay-to-pkhash address
PubkeyHash(Hash160),
/// P2SH address
@@ -46,38 +47,133 @@ pub struct Address {
/// The type of the address
pub payload: Payload,
/// The network on which this address is usable
- pub network: Network,
+ pub network: Network
}
impl Address {
- /// Creates an address from a public key
+ /// Creates a pay to (compressed) public key hash address from a public key
+ /// This is the preferred non-witness type address
#[inline]
- pub fn from_key(network: Network, pk: &PublicKey, compressed: bool) -> Address {
+ pub fn p2pkh(pk: &PublicKey, network: Network) -> Address {
Address {
network: network,
- payload: Payload::PubkeyHash(
- if compressed {
- Hash160::from_data(&pk.serialize()[..])
- } else {
- Hash160::from_data(&pk.serialize_uncompressed()[..])
- }
- ),
+ payload: Payload::PubkeyHash(Hash160::from_data(&pk.serialize()[..]))
}
}
- /// Creates a P2SH address from a script
+ /// Creates a pay to uncompressed public key hash address from a public key
+ /// This address type is discouraged as it uses more space but otherwise equivalent to p2pkh
+ /// therefore only adds ambiguity
#[inline]
- pub fn from_script(network: Network, script: &script::Script) -> Address {
+ pub fn p2upkh(pk: &PublicKey, network: Network) -> Address {
Address {
network: network,
- payload: Payload::ScriptHash(Hash160::from_data(&script[..])),
+ payload: Payload::PubkeyHash(Hash160::from_data(&pk.serialize_uncompressed()[..]))
+ }
+ }
+
+ /// Creates a pay to public key address from a public key
+ /// This address type was used in the early history of Bitcoin.
+ /// Satoshi's coins are still on addresses of this type.
+ #[inline]
+ pub fn p2pk(pk: &PublicKey, network: Network) -> Address {
+ Address {
+ network: network,
+ payload: Payload::Pubkey(*pk)
+ }
+ }
+
+ /// Creates a pay to script hash P2SH address from a script
+ /// This address type was introduced with BIP16 and is the popular ty implement multi-sig these days.
+ #[inline]
+ pub fn p2sh(script: &script::Script, network: Network) -> Address {
+ Address {
+ network: network,
+ payload: Payload::ScriptHash(Hash160::from_data(&script[..]))
+ }
+ }
+
+ /// Create a witness pay to public key address from a public key
+ /// This is the native segwit address type for an output redemable with a single signature
+ pub fn p2wpkh (pk: &PublicKey, network: Network) -> Address {
+ Address {
+ network: network,
+ payload: Payload::WitnessProgram(
+ // unwrap is safe as witness program is known to be correct as above
+ WitnessProgram::new(0,
+ Hash160::from_data(&pk.serialize()[..])[..].to_vec(),
+ Address::bech_network(network)).unwrap())
+ }
+ }
+
+ /// Create a pay to script address that embeds a witness pay to public key
+ /// This is a segwit address type that looks familiar (as p2sh) to legacy clients
+ pub fn p2shwpkh (pk: &PublicKey, network: Network) -> Address {
+ let builder = script::Builder::new()
+ .push_int(0)
+ .push_slice(&Hash160::from_data(&pk.serialize()[..])[..]);
+ Address {
+ network: network,
+ payload: Payload::ScriptHash(
+ Hash160::from_data(builder.into_script().into_vec().as_slice())
+ )
+ }
+ }
+
+ /// Create a witness pay to script hash address
+ pub fn p2wsh (script: &script::Script, network: Network) -> Address {
+ use crypto::sha2::Sha256;
+ use crypto::digest::Digest;
+
+ let mut digest = Sha256::new();
+ digest.input(script.clone().into_vec().as_slice());
+ let mut d = [0u8; 32];
+ digest.result(&mut d);
+
+ Address {
+ network: network,
+ payload: Payload::WitnessProgram(
+ // unwrap is safe as witness program is known to be correct as above
+ WitnessProgram::new(0, d.to_vec(), Address::bech_network(network)).unwrap()
+ )
+ }
+ }
+
+ /// Create a pay to script address that embeds a witness pay to script hash address
+ /// This is a segwit address type that looks familiar (as p2sh) to legacy clients
+ pub fn p2shwsh (script: &script::Script, network: Network) -> Address {
+ use crypto::sha2::Sha256;
+ use crypto::digest::Digest;
+
+ let mut digest = Sha256::new();
+ digest.input(script.clone().into_vec().as_slice());
+ let mut d = [0u8; 32];
+ digest.result(&mut d);
+ let ws = script::Builder::new().push_int(0).push_slice(&d).into_script();
+
+ Address {
+ network: network,
+ payload: Payload::ScriptHash(Hash160::from_data(ws.into_vec().as_slice()))
+ }
+ }
+
+ #[inline]
+ /// convert Network to bech32 network (this should go away soon)
+ fn bech_network (network: Network) -> bitcoin_bech32::constants::Network {
+ match network {
+ Network::Bitcoin => bitcoin_bech32::constants::Network::Bitcoin,
+ Network::Testnet => bitcoin_bech32::constants::Network::Testnet
}
}
/// Generates a script pubkey spending to this address
- #[inline]
pub fn script_pubkey(&self) -> script::Script {
match self.payload {
+ Payload::Pubkey(ref pk) => {
+ script::Builder::new()
+ .push_slice(&pk.serialize_uncompressed()[..])
+ .push_opcode(opcodes::All::OP_CHECKSIG)
+ },
Payload::PubkeyHash(ref hash) => {
script::Builder::new()
.push_opcode(opcodes::All::OP_DUP)
@@ -104,6 +200,17 @@ impl Address {
impl ToString for Address {
fn to_string(&self) -> String {
match self.payload {
+ // note: serialization for pay-to-pk is defined, but is irreversible
+ Payload::Pubkey(ref pk) => {
+ let hash = &Hash160::from_data(&pk.serialize_uncompressed()[..]);
+ let mut prefixed = [0; 21];
+ prefixed[0] = match self.network {
+ Network::Bitcoin => 0,
+ Network::Testnet => 111,
+ };
+ prefixed[1..].copy_from_slice(&hash[..]);
+ base58::check_encode_slice(&prefixed[..])
+ },
Payload::PubkeyHash(ref hash) => {
let mut prefixed = [0; 21];
prefixed[0] = match self.network {
@@ -112,7 +219,7 @@ impl ToString for Address {
};
prefixed[1..].copy_from_slice(&hash[..]);
base58::check_encode_slice(&prefixed[..])
- }
+ },
Payload::ScriptHash(ref hash) => {
let mut prefixed = [0; 21];
prefixed[0] = match self.network {
@@ -121,7 +228,7 @@ impl ToString for Address {
};
prefixed[1..].copy_from_slice(&hash[..]);
base58::check_encode_slice(&prefixed[..])
- }
+ },
Payload::WitnessProgram(ref witprog) => {
witprog.to_address()
},
@@ -142,9 +249,12 @@ impl FromStr for Address {
bitcoin_bech32::constants::Network::Testnet => Network::Testnet,
_ => panic!("unknown network")
};
+ if witprog.version() != 0 {
+ return Err(Error::UnsupportedWitnessVersion(witprog.version()));
+ }
return Ok(Address {
network: network,
- payload: Payload::WitnessProgram(witprog),
+ payload: Payload::WitnessProgram(witprog)
});
}
@@ -175,10 +285,7 @@ impl FromStr for Address {
x => return Err(Error::Base58(base58::Error::InvalidVersion(vec![x])))
};
- Ok(Address {
- network: network,
- payload: payload,
- })
+ Ok(Address { network, payload })
}
}
@@ -188,106 +295,6 @@ impl ::std::fmt::Debug for Address {
}
}
-#[derive(Clone, PartialEq, Eq)]
-/// A Bitcoin ECDSA private key
-pub struct Privkey {
- /// Whether this private key represents a compressed address
- pub compressed: bool,
- /// The network on which this key should be used
- pub network: Network,
- /// The actual ECDSA key
- pub key: SecretKey
-}
-
-impl Privkey {
- /// Creates an address from a public key
- #[inline]
- pub fn from_key(network: Network, sk: SecretKey, compressed: bool) -> Privkey {
- Privkey {
- compressed: compressed,
- network: network,
- key: sk
- }
- }
-
- /// Converts a private key to an address
- #[inline]
- pub fn to_address(&self, secp: &Secp256k1) -> Result
{
- let key = try!(PublicKey::from_secret_key(secp, &self.key));
- Ok(Address::from_key(self.network, &key, self.compressed))
- }
-
- /// Accessor for the underlying secp key
- #[inline]
- pub fn secret_key(&self) -> &SecretKey {
- &self.key
- }
-
- /// Accessor for the underlying secp key that consumes the privkey
- #[inline]
- pub fn into_secret_key(self) -> SecretKey {
- self.key
- }
-
- /// Accessor for the network type
- #[inline]
- pub fn network(&self) -> Network {
- self.network
- }
-
- /// Accessor for the compressed flag
- #[inline]
- pub fn is_compressed(&self) -> bool {
- self.compressed
- }
-}
-
-impl ToString for Privkey {
- fn to_string(&self) -> String {
- let mut ret = [0; 34];
- ret[0] = match self.network {
- Network::Bitcoin => 128,
- Network::Testnet => 239
- };
- ret[1..33].copy_from_slice(&self.key[..]);
- if self.compressed {
- ret[33] = 1;
- base58::check_encode_slice(&ret[..])
- } else {
- base58::check_encode_slice(&ret[..33])
- }
- }
-}
-
-impl FromStr for Privkey {
- type Err = Error;
-
- fn from_str(s: &str) -> Result {
- let data = try!(base58::from_check(s));
-
- 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]))); }
- };
-
- let secp = Secp256k1::without_caps();
- let key = try!(SecretKey::from_slice(&secp, &data[1..33])
- .map_err(|_| base58::Error::Other("Secret key out of range".to_owned())));
-
- Ok(Privkey {
- compressed: compressed,
- network: network,
- key: key
- })
- }
-}
#[cfg(test)]
mod tests {
@@ -313,7 +320,7 @@ mod tests {
network: Bitcoin,
payload: Payload::PubkeyHash(
Hash160::from(&"162c5ea71c0b23f5b9022ef047c4a86470a5b070".from_hex().unwrap()[..])
- ),
+ )
};
assert_eq!(addr.script_pubkey(), hex_script!("76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac"));
@@ -326,21 +333,30 @@ mod tests {
let secp = Secp256k1::without_caps();
let key = hex_key!(&secp, "048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183");
- let addr = Address::from_key(Bitcoin, &key, false);
+ let addr = Address::p2upkh(&key, Bitcoin);
assert_eq!(&addr.to_string(), "1QJVDzdqb1VpbDK7uDeyVXy9mR27CJiyhY");
let key = hex_key!(&secp, &"03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f");
- let addr = Address::from_key(Testnet, &key, true);
+ let addr = Address::p2pkh(&key, Testnet);
assert_eq!(&addr.to_string(), "mqkhEMH6NCeYjFybv7pvFC22MFeaNT9AQC");
}
+ #[test]
+ fn test_p2pk () {
+ // one of Satoshi's coins, from Bitcoin transaction 9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5
+ let secp = Secp256k1::without_caps();
+ let key = hex_key!(&secp, "047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77");
+ let addr = Address::p2pk(&key, Bitcoin);
+ assert_eq!(&addr.to_string(), "1HLoD9E4SDFFPDiYfNYnkBLQ85Y51J3Zb1");
+ }
+
#[test]
fn test_p2sh_address_58() {
let addr = Address {
network: Bitcoin,
payload: Payload::ScriptHash(
Hash160::from(&"162c5ea71c0b23f5b9022ef047c4a86470a5b070".from_hex().unwrap()[..])
- ),
+ )
};
assert_eq!(addr.script_pubkey(), hex_script!("a914162c5ea71c0b23f5b9022ef047c4a86470a5b07087"));
@@ -351,12 +367,31 @@ mod tests {
#[test]
fn test_p2sh_parse() {
let script = hex_script!("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae");
- let addr = Address::from_script(Testnet, &script);
+ let addr = Address::p2sh(&script, Testnet);
assert_eq!(&addr.to_string(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr");
assert_eq!(Address::from_str("2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr").unwrap(), addr);
}
+ #[test]
+ fn test_p2wpkh () {
+ // stolen from Bitcoin transaction: b3c8c2b6cfc335abbcb2c7823a8453f55d64b2b5125a9a61e8737230cdb8ce20
+ let secp = Secp256k1::without_caps();
+ let key = hex_key!(&secp, "033bc8c83c52df5712229a2f72206d90192366c36428cb0c12b6af98324d97bfbc");
+ let addr = Address::p2wpkh(&key, Bitcoin);
+ assert_eq!(&addr.to_string(), "bc1qvzvkjn4q3nszqxrv3nraga2r822xjty3ykvkuw");
+ }
+
+
+ #[test]
+ fn test_p2wsh () {
+ // stolen from Bitcoin transaction 5df912fda4becb1c29e928bec8d64d93e9ba8efa9b5b405bd683c86fd2c65667
+ let script = hex_script!("52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae");
+ let addr = Address::p2wsh(&script, Bitcoin);
+ assert_eq!(&addr.to_string(), "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej");
+ }
+
+
#[test]
fn test_bip173_vectors() {
let addrstr = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4";
@@ -371,24 +406,6 @@ mod tests {
assert_eq!(addr.script_pubkey(), hex_script!("00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"));
assert_eq!(addr.to_string(), addrstr);
- let addrstr = "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx";
- let addr = Address::from_str(addrstr).unwrap();
- assert_eq!(addr.network, Bitcoin);
- assert_eq!(addr.script_pubkey(), hex_script!("5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"));
- assert_eq!(addr.to_string(), addrstr);
-
- let addrstr = "BC1SW50QA3JX3S";
- let addr = Address::from_str(addrstr).unwrap();
- assert_eq!(addr.network, Bitcoin);
- assert_eq!(addr.script_pubkey(), hex_script!("6002751e"));
- // skip round trip cuz caps
-
- let addrstr = "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj";
- let addr = Address::from_str(addrstr).unwrap();
- assert_eq!(addr.network, Bitcoin);
- assert_eq!(addr.script_pubkey(), hex_script!("5210751e76e8199196d454941c45d1b3a323"));
- assert_eq!(addr.to_string(), addrstr);
-
let addrstr = "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy";
let addr = Address::from_str(addrstr).unwrap();
assert_eq!(addr.network, Testnet);
@@ -426,28 +443,5 @@ mod tests {
let addrstr = "bc1gmk9yu"; // empty data section
assert!(Address::from_str(addrstr).is_err());
}
-
- #[test]
- fn test_key_derivation() {
- // testnet compressed
- let sk = Privkey::from_str("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap();
- assert_eq!(sk.network(), Testnet);
- assert_eq!(sk.is_compressed(), true);
- assert_eq!(&sk.to_string(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy");
-
- let secp = Secp256k1::new();
- let pk = sk.to_address(&secp).unwrap();
- assert_eq!(&pk.to_string(), "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx");
-
- // mainnet uncompressed
- let sk = Privkey::from_str("5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3").unwrap();
- assert_eq!(sk.network(), Bitcoin);
- assert_eq!(sk.is_compressed(), false);
- assert_eq!(&sk.to_string(), "5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3");
-
- let secp = Secp256k1::new();
- let pk = sk.to_address(&secp).unwrap();
- assert_eq!(&pk.to_string(), "1GhQvF6dL8xa6wBxLnWmHcQsurx9RxiMc8");
- }
}
diff --git a/src/util/contracthash.rs b/src/util/contracthash.rs
index bd1a68b7..666aa546 100644
--- a/src/util/contracthash.rs
+++ b/src/util/contracthash.rs
@@ -215,7 +215,7 @@ pub fn create_address(secp: &Secp256k1,
network: network,
payload: address::Payload::ScriptHash(
hash::Hash160::from_data(&script[..])
- ),
+ )
})
}
diff --git a/src/util/mod.rs b/src/util/mod.rs
index 903c7e5a..77e91cce 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -16,6 +16,7 @@
//!
//! Functions needed by all parts of the Bitcoin library
+pub mod privkey;
pub mod address;
pub mod base58;
pub mod bip32;
@@ -83,7 +84,9 @@ pub enum Error {
/// The header hash is not below the target
SpvBadProofOfWork,
/// Error propagated from subsystem
- Detail(String, Box)
+ Detail(String, Box),
+ /// Unsupported witness version
+ UnsupportedWitnessVersion(u8)
}
impl fmt::Display for Error {
@@ -130,7 +133,8 @@ impl error::Error for Error {
Error::Secp256k1(ref e) => e.description(),
Error::SpvBadTarget => "target incorrect",
Error::SpvBadProofOfWork => "target correct but not attained",
- Error::Detail(_, ref e) => e.description()
+ Error::Detail(_, ref e) => e.description(),
+ Error::UnsupportedWitnessVersion(_) => "unsupported witness version"
}
}
}
diff --git a/src/util/privkey.rs b/src/util/privkey.rs
new file mode 100644
index 00000000..85710c12
--- /dev/null
+++ b/src/util/privkey.rs
@@ -0,0 +1,167 @@
+// 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 .
+//
+
+//! # private key
+//! A private key represents the secret data associated with its proposed use
+//!
+use std::str::FromStr;
+use util::Error;
+use secp256k1::Secp256k1;
+use secp256k1::key::{PublicKey, SecretKey};
+use util::address::Address;
+use network::constants::Network;
+use util::base58;
+
+#[derive(Clone, PartialEq, Eq)]
+/// A Bitcoin ECDSA private key
+pub struct Privkey {
+ /// Whether this private key represents a compressed address
+ pub compressed: bool,
+ /// The network on which this key should be used
+ pub network: Network,
+ /// The actual ECDSA key
+ pub key: SecretKey
+}
+
+impl Privkey {
+ /// Creates an address from a public key
+ #[inline]
+ pub fn from_secret_key(key: SecretKey, compressed: bool, network: Network) -> Privkey {
+ Privkey { compressed, network, key }
+ }
+
+ /// Computes the public key as supposed to be used with this secret
+ pub fn public_key(&self, secp: &Secp256k1) -> Result {
+ Ok(PublicKey::from_secret_key(secp, &self.key)?)
+ }
+
+ /// Converts a private key to a segwit address
+ #[inline]
+ pub fn to_address(&self, secp: &Secp256k1) -> Result {
+ Ok(Address::p2wpkh(&self.public_key(secp)?, self.network))
+ }
+
+ /// Converts a private key to a legacy (non-segwit) address
+ #[inline]
+ pub fn to_legacy_address(&self, secp: &Secp256k1) -> Result {
+ if self.compressed {
+ Ok(Address::p2pkh(&self.public_key(secp)?, self.network))
+ }
+ else {
+ Ok(Address::p2upkh(&self.public_key(secp)?, self.network))
+ }
+ }
+
+ /// Accessor for the underlying secp key
+ #[inline]
+ pub fn secret_key(&self) -> &SecretKey {
+ &self.key
+ }
+
+ /// Accessor for the underlying secp key that consumes the privkey
+ #[inline]
+ pub fn into_secret_key(self) -> SecretKey {
+ self.key
+ }
+
+ /// Accessor for the network type
+ #[inline]
+ pub fn network(&self) -> Network {
+ self.network
+ }
+
+ /// Accessor for the compressed flag
+ #[inline]
+ pub fn is_compressed(&self) -> bool {
+ self.compressed
+ }
+}
+
+impl ToString for Privkey {
+ fn to_string(&self) -> String {
+ let mut ret = [0; 34];
+ ret[0] = match self.network {
+ Network::Bitcoin => 128,
+ Network::Testnet => 239
+ };
+ ret[1..33].copy_from_slice(&self.key[..]);
+ if self.compressed {
+ ret[33] = 1;
+ base58::check_encode_slice(&ret[..])
+ } else {
+ base58::check_encode_slice(&ret[..33])
+ }
+ }
+}
+
+impl FromStr for Privkey {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result {
+ let data = try!(base58::from_check(s));
+
+ 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]))); }
+ };
+
+ let secp = Secp256k1::without_caps();
+ let key = try!(SecretKey::from_slice(&secp, &data[1..33])
+ .map_err(|_| base58::Error::Other("Secret key out of range".to_owned())));
+
+ Ok(Privkey {
+ compressed: compressed,
+ network: network,
+ key: key
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Privkey;
+ use secp256k1::Secp256k1;
+ use std::str::FromStr;
+ use network::constants::Network::Testnet;
+ use network::constants::Network::Bitcoin;
+
+ #[test]
+ fn test_key_derivation() {
+ // testnet compressed
+ let sk = Privkey::from_str("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap();
+ assert_eq!(sk.network(), Testnet);
+ assert_eq!(sk.is_compressed(), true);
+ assert_eq!(&sk.to_string(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy");
+
+ let secp = Secp256k1::new();
+ let pk = sk.to_legacy_address(&secp).unwrap();
+ assert_eq!(&pk.to_string(), "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx");
+
+ // mainnet uncompressed
+ let sk = Privkey::from_str("5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3").unwrap();
+ assert_eq!(sk.network(), Bitcoin);
+ assert_eq!(sk.is_compressed(), false);
+ assert_eq!(&sk.to_string(), "5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3");
+
+ let secp = Secp256k1::new();
+ let pk = sk.to_legacy_address(&secp).unwrap();
+ assert_eq!(&pk.to_string(), "1GhQvF6dL8xa6wBxLnWmHcQsurx9RxiMc8");
+ }
+}
\ No newline at end of file