diff --git a/Cargo.toml b/Cargo.toml
index cc5fc7c0..4ef3fe0b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@ path = "src/lib.rs"
bitcoinconsenus = ["bitcoinconsensus"]
[dependencies]
+bitcoin-bech32 = "0.5.1"
byteorder = "1.1"
rand = "0.3"
rust-crypto = "0.2"
diff --git a/README.md b/README.md
index 2d6ce714..f12e22c7 100644
--- a/README.md
+++ b/README.md
@@ -90,3 +90,5 @@ See Transaction::verify and Script::verify methods.
* Un-reversed the Debug output for Sha256dHash
+* Add bech32 support
+
diff --git a/src/lib.rs b/src/lib.rs
index 97c89611..af4fd673 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -41,6 +41,7 @@
#![deny(unused_mut)]
#![deny(missing_docs)]
+extern crate bitcoin_bech32;
extern crate byteorder;
extern crate crypto;
extern crate rand;
diff --git a/src/util/address.rs b/src/util/address.rs
index 0a17c6ac..18d381dd 100644
--- a/src/util/address.rs
+++ b/src/util/address.rs
@@ -18,7 +18,8 @@
use std::str::FromStr;
use std::string::ToString;
-use secp256k1::{self, Secp256k1};
+use bitcoin_bech32::{self, WitnessProgram};
+use secp256k1::Secp256k1;
use secp256k1::key::{PublicKey, SecretKey};
use blockdata::script;
@@ -26,32 +27,26 @@ use blockdata::opcodes;
use network::constants::Network;
use util::hash::Hash160;
use util::base58;
+use util::Error;
/// The method used to produce an address
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub enum Type {
- /// Standard pay-to-pkhash address
- PubkeyHash,
- /// New-fangled P2SH address
- ScriptHash
+#[derive(Clone, PartialEq, Debug)]
+pub enum Payload {
+ /// pay-to-pkhash address
+ PubkeyHash(Hash160),
+ /// P2SH address
+ ScriptHash(Hash160),
+ /// Segwit address
+ WitnessProgram(WitnessProgram),
}
-/// An address-related error
-#[derive(Clone, PartialEq, Eq, Debug)]
-pub enum Error {
- /// Private key did not represent a valid ECDSA secret key
- Secp(secp256k1::Error)
-}
-
-#[derive(Clone, PartialEq, Eq)]
+#[derive(Clone, PartialEq)]
/// A Bitcoin address
pub struct Address {
/// The type of the address
- pub ty: Type,
+ pub payload: Payload,
/// The network on which this address is usable
pub network: Network,
- /// The pubkeyhash that this address encodes
- pub hash: Hash160
}
impl Address {
@@ -59,13 +54,14 @@ impl Address {
#[inline]
pub fn from_key(network: Network, pk: &PublicKey, compressed: bool) -> Address {
Address {
- ty: Type::PubkeyHash,
network: network,
- hash: if compressed {
- Hash160::from_data(&pk.serialize()[..])
- } else {
- Hash160::from_data(&pk.serialize_uncompressed()[..])
- }
+ payload: Payload::PubkeyHash(
+ if compressed {
+ Hash160::from_data(&pk.serialize()[..])
+ } else {
+ Hash160::from_data(&pk.serialize_uncompressed()[..])
+ }
+ ),
}
}
@@ -73,29 +69,33 @@ impl Address {
#[inline]
pub fn from_script(network: Network, script: &script::Script) -> Address {
Address {
- ty: Type::ScriptHash,
network: network,
- hash: Hash160::from_data(&script[..])
+ payload: Payload::ScriptHash(Hash160::from_data(&script[..])),
}
}
/// Generates a script pubkey spending to this address
#[inline]
pub fn script_pubkey(&self) -> script::Script {
- match self.ty {
- Type::PubkeyHash => {
+ match self.payload {
+ Payload::PubkeyHash(ref hash) => {
script::Builder::new()
.push_opcode(opcodes::All::OP_DUP)
.push_opcode(opcodes::All::OP_HASH160)
- .push_slice(&self.hash[..])
+ .push_slice(&hash[..])
.push_opcode(opcodes::All::OP_EQUALVERIFY)
.push_opcode(opcodes::All::OP_CHECKSIG)
- }
- Type::ScriptHash => {
+ },
+ Payload::ScriptHash(ref hash) => {
script::Builder::new()
.push_opcode(opcodes::All::OP_HASH160)
- .push_slice(&self.hash[..])
+ .push_slice(&hash[..])
.push_opcode(opcodes::All::OP_EQUAL)
+ },
+ Payload::WitnessProgram(ref witprog) => {
+ script::Builder::new()
+ .push_int(witprog.version() as i64)
+ .push_slice(witprog.program())
}
}.into_script()
}
@@ -103,40 +103,81 @@ impl Address {
impl ToString for Address {
fn to_string(&self) -> String {
- let mut prefixed = [0; 21];
- prefixed[0] = match (self.network, self.ty) {
- (Network::Bitcoin, Type::PubkeyHash) => 0,
- (Network::Bitcoin, Type::ScriptHash) => 5,
- (Network::Testnet, Type::PubkeyHash) => 111,
- (Network::Testnet, Type::ScriptHash) => 196
- };
- prefixed[1..].copy_from_slice(&self.hash[..]);
- base58::check_encode_slice(&prefixed[..])
+ match self.payload {
+ Payload::PubkeyHash(ref hash) => {
+ 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::ScriptHash(ref hash) => {
+ let mut prefixed = [0; 21];
+ prefixed[0] = match self.network {
+ Network::Bitcoin => 5,
+ Network::Testnet => 196,
+ };
+ prefixed[1..].copy_from_slice(&hash[..]);
+ base58::check_encode_slice(&prefixed[..])
+ }
+ Payload::WitnessProgram(ref witprog) => {
+ witprog.to_address()
+ },
+ }
}
}
impl FromStr for Address {
- type Err = base58::Error;
+ type Err = Error;
- fn from_str(s: &str) -> Result
{
+ fn from_str(s: &str) -> Result {
+ // bech32 (note that upper or lowercase is allowed but NOT mixed case)
+ if &s.as_bytes()[0..3] == b"bc1" || &s.as_bytes()[0..3] == b"tb1" ||
+ &s.as_bytes()[0..3] == b"BC1" || &s.as_bytes()[0..3] == b"TB1" {
+ let witprog = try!(WitnessProgram::from_address(s));
+ let network = match witprog.network() {
+ bitcoin_bech32::constants::Network::Bitcoin => Network::Bitcoin,
+ bitcoin_bech32::constants::Network::Testnet => Network::Testnet,
+ _ => panic!("unknown network")
+ };
+ return Ok(Address {
+ network: network,
+ payload: Payload::WitnessProgram(witprog),
+ });
+ }
+
+ // Base 58
let data = try!(base58::from_check(s));
if data.len() != 21 {
- return Err(base58::Error::InvalidLength(data.len()));
+ return Err(Error::Base58(base58::Error::InvalidLength(data.len())));
}
- let (network, ty) = match data[0] {
- 0 => (Network::Bitcoin, Type::PubkeyHash),
- 5 => (Network::Bitcoin, Type::ScriptHash),
- 111 => (Network::Testnet, Type::PubkeyHash),
- 196 => (Network::Testnet, Type::ScriptHash),
- x => { return Err(base58::Error::InvalidVersion(vec![x])); }
+ let (network, payload) = match data[0] {
+ 0 => (
+ Network::Bitcoin,
+ Payload::PubkeyHash(Hash160::from(&data[1..]))
+ ),
+ 5 => (
+ Network::Bitcoin,
+ Payload::ScriptHash(Hash160::from(&data[1..]))
+ ),
+ 111 => (
+ Network::Testnet,
+ Payload::PubkeyHash(Hash160::from(&data[1..]))
+ ),
+ 196 => (
+ Network::Testnet,
+ Payload::ScriptHash(Hash160::from(&data[1..]))
+ ),
+ x => return Err(Error::Base58(base58::Error::InvalidVersion(vec![x])))
};
Ok(Address {
- ty: ty,
network: network,
- hash: Hash160::from(&data[1..])
+ payload: payload,
})
}
}
@@ -172,7 +213,7 @@ impl Privkey {
/// 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).map_err(Error::Secp));
+ let key = try!(PublicKey::from_secret_key(secp, &self.key));
Ok(Address::from_key(self.network, &key, self.compressed))
}
@@ -219,21 +260,21 @@ impl ToString for Privkey {
}
impl FromStr for Privkey {
- type Err = base58::Error;
+ type Err = Error;
- fn from_str(s: &str) -> Result {
+ fn from_str(s: &str) -> Result {
let data = try!(base58::from_check(s));
let compressed = match data.len() {
33 => false,
34 => true,
- _ => { return Err(base58::Error::InvalidLength(data.len())); }
+ _ => { return Err(Error::Base58(base58::Error::InvalidLength(data.len()))); }
};
let network = match data[0] {
128 => Network::Bitcoin,
239 => Network::Testnet,
- x => { return Err(base58::Error::InvalidVersion(vec![x])); }
+ x => { return Err(Error::Base58(base58::Error::InvalidVersion(vec![x]))); }
};
let secp = Secp256k1::without_caps();
@@ -269,14 +310,15 @@ mod tests {
#[test]
fn test_p2pkh_address_58() {
let addr = Address {
- ty: Type::PubkeyHash,
network: Bitcoin,
- hash: Hash160::from(&"162c5ea71c0b23f5b9022ef047c4a86470a5b070".from_hex().unwrap()[..])
+ payload: Payload::PubkeyHash(
+ Hash160::from(&"162c5ea71c0b23f5b9022ef047c4a86470a5b070".from_hex().unwrap()[..])
+ ),
};
assert_eq!(addr.script_pubkey(), hex_script!("76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac"));
assert_eq!(&addr.to_string(), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM");
- assert_eq!(Address::from_str("132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM"), Ok(addr));
+ assert_eq!(Address::from_str("132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM").unwrap(), addr);
}
#[test]
@@ -295,14 +337,15 @@ mod tests {
#[test]
fn test_p2sh_address_58() {
let addr = Address {
- ty: Type::ScriptHash,
network: Bitcoin,
- hash: Hash160::from(&"162c5ea71c0b23f5b9022ef047c4a86470a5b070".from_hex().unwrap()[..])
+ payload: Payload::ScriptHash(
+ Hash160::from(&"162c5ea71c0b23f5b9022ef047c4a86470a5b070".from_hex().unwrap()[..])
+ ),
};
assert_eq!(addr.script_pubkey(), hex_script!("a914162c5ea71c0b23f5b9022ef047c4a86470a5b07087"));
assert_eq!(&addr.to_string(), "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k");
- assert_eq!(Address::from_str("33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k"), Ok(addr));
+ assert_eq!(Address::from_str("33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k").unwrap(), addr);
}
#[test]
@@ -311,7 +354,77 @@ mod tests {
let addr = Address::from_script(Testnet, &script);
assert_eq!(&addr.to_string(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr");
- assert_eq!(Address::from_str("2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr"), Ok(addr));
+ assert_eq!(Address::from_str("2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr").unwrap(), addr);
+ }
+
+ #[test]
+ fn test_bip173_vectors() {
+ let addrstr = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4";
+ let addr = Address::from_str(addrstr).unwrap();
+ assert_eq!(addr.network, Bitcoin);
+ assert_eq!(addr.script_pubkey(), hex_script!("0014751e76e8199196d454941c45d1b3a323f1433bd6"));
+ // skip round-trip because we'll serialize to lowercase which won't match
+
+ let addrstr = "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7";
+ let addr = Address::from_str(addrstr).unwrap();
+ assert_eq!(addr.network, Testnet);
+ 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);
+ assert_eq!(addr.script_pubkey(), hex_script!("0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"));
+ assert_eq!(addr.to_string(), addrstr);
+
+ // bad vectors
+ let addrstr = "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty"; // invalid hrp
+ assert!(Address::from_str(addrstr).is_err());
+
+ let addrstr = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"; // invalid checksum
+ assert!(Address::from_str(addrstr).is_err());
+
+ let addrstr = "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2"; // invalid witness version
+ assert!(Address::from_str(addrstr).is_err());
+
+ let addrstr = "bc1rw5uspcuh"; // invalid program length
+ assert!(Address::from_str(addrstr).is_err());
+
+ let addrstr = "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"; // invalid program length
+ assert!(Address::from_str(addrstr).is_err());
+
+ let addrstr = "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"; // invalid program length for wit v0
+ assert!(Address::from_str(addrstr).is_err());
+
+ let addrstr = "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7"; // mixed case
+ assert!(Address::from_str(addrstr).is_err());
+
+ let addrstr = "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"; // zero padding of more than 4 bits
+ assert!(Address::from_str(addrstr).is_err());
+
+ let addrstr = "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"; // nonzero padding
+ assert!(Address::from_str(addrstr).is_err());
+
+ let addrstr = "bc1gmk9yu"; // empty data section
+ assert!(Address::from_str(addrstr).is_err());
}
#[test]
diff --git a/src/util/contracthash.rs b/src/util/contracthash.rs
index b430ed4a..bd1a68b7 100644
--- a/src/util/contracthash.rs
+++ b/src/util/contracthash.rs
@@ -213,8 +213,9 @@ pub fn create_address(secp: &Secp256k1,
let script = try!(template.to_script(&keys));
Ok(address::Address {
network: network,
- ty: address::Type::ScriptHash,
- hash: hash::Hash160::from_data(&script[..])
+ payload: address::Payload::ScriptHash(
+ hash::Hash160::from_data(&script[..])
+ ),
})
}
diff --git a/src/util/mod.rs b/src/util/mod.rs
index 1a83a6df..903c7e5a 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -29,6 +29,9 @@ pub mod uint;
use std::{error, fmt, io};
+use bitcoin_bech32;
+use secp256k1;
+
/// A trait which allows numbers to act as fixed-size bit arrays
pub trait BitArray {
/// Is bit set?
@@ -55,6 +58,10 @@ pub trait BitArray {
pub enum Error {
/// An I/O error
Io(io::Error),
+ /// Base58 encoding error
+ Base58(base58::Error),
+ /// Bech32 encoding error
+ Bech32(bitcoin_bech32::Error),
/// Error from the `byteorder` crate
ByteOrder(io::Error),
/// Network magic was not what we expected
@@ -69,6 +76,8 @@ pub enum Error {
ParseFailed,
/// An object was added but it does not link into existing history
PrevHashNotFound,
+ /// secp-related error
+ Secp256k1(secp256k1::Error),
/// The `target` field of a block header did not match the expected difficulty
SpvBadTarget,
/// The header hash is not below the target
@@ -81,10 +90,13 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Io(ref e) => fmt::Display::fmt(e, f),
+ Error::Base58(ref e) => fmt::Display::fmt(e, f),
+ Error::Bech32(ref e) => fmt::Display::fmt(e, f),
Error::ByteOrder(ref e) => fmt::Display::fmt(e, f),
Error::BadNetworkMagic(exp, got) => write!(f, "expected network magic 0x{:x}, got 0x{:x}", exp, got),
Error::BadNetworkMessage(ref got) => write!(f, "incorrect network message {}", got),
Error::Detail(ref s, ref e) => write!(f, "{}: {}", s, e),
+ Error::Secp256k1(ref e) => fmt::Display::fmt(e, f),
ref x => f.write_str(error::Error::description(x))
}
}
@@ -94,8 +106,11 @@ impl error::Error for Error {
fn cause(&self) -> Option<&error::Error> {
match *self {
Error::Io(ref e) => Some(e),
+ Error::Base58(ref e) => Some(e),
+ Error::Bech32(ref e) => Some(e),
Error::ByteOrder(ref e) => Some(e),
Error::Detail(_, ref e) => Some(e),
+ Error::Secp256k1(ref e) => Some(e),
_ => None
}
}
@@ -103,6 +118,8 @@ impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::Io(ref e) => e.description(),
+ Error::Base58(ref e) => e.description(),
+ Error::Bech32(ref e) => e.description(),
Error::ByteOrder(ref e) => e.description(),
Error::BadNetworkMagic(_, _) => "incorrect network magic",
Error::BadNetworkMessage(_) => "incorrect/unexpected network message",
@@ -110,6 +127,7 @@ impl error::Error for Error {
Error::BlockNotFound => "no such block",
Error::ParseFailed => "parsing error",
Error::PrevHashNotFound => "prevhash not found",
+ Error::Secp256k1(ref e) => e.description(),
Error::SpvBadTarget => "target incorrect",
Error::SpvBadProofOfWork => "target correct but not attained",
Error::Detail(_, ref e) => e.description()
@@ -122,4 +140,21 @@ pub fn propagate_err(s: String, res: Result) -> Result {
res.map_err(|err| Error::Detail(s, Box::new(err)))
}
+impl From for Error {
+ fn from(e: base58::Error) -> Error {
+ Error::Base58(e)
+ }
+}
+
+impl From for Error {
+ fn from(e: bitcoin_bech32::Error) -> Error {
+ Error::Bech32(e)
+ }
+}
+
+impl From for Error {
+ fn from(e: secp256k1::Error) -> Error {
+ Error::Secp256k1(e)
+ }
+}