Refactor Address
- use AddressError instead of encode::Error - replace using bech32-bitcoin with Payload::WitnessProgram variant
This commit is contained in:
parent
cc0f1143dc
commit
385a657974
|
@ -20,7 +20,7 @@ unstable = []
|
||||||
use-serde = ["serde", "bitcoin_hashes/serde"]
|
use-serde = ["serde", "bitcoin_hashes/serde"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitcoin-bech32 = "0.9.0"
|
bech32 = "0.7.1"
|
||||||
byteorder = "1.2"
|
byteorder = "1.2"
|
||||||
bitcoin_hashes = "0.7"
|
bitcoin_hashes = "0.7"
|
||||||
bitcoinconsensus = { version = "0.16", optional = true }
|
bitcoinconsensus = { version = "0.16", optional = true }
|
||||||
|
|
|
@ -38,7 +38,6 @@ use std::io::{Cursor, Read, Write};
|
||||||
use byteorder::{LittleEndian, WriteBytesExt, ReadBytesExt};
|
use byteorder::{LittleEndian, WriteBytesExt, ReadBytesExt};
|
||||||
use hex::encode as hex_encode;
|
use hex::encode as hex_encode;
|
||||||
|
|
||||||
use bitcoin_bech32;
|
|
||||||
use bitcoin_hashes::{sha256d, Hash as HashTrait};
|
use bitcoin_hashes::{sha256d, Hash as HashTrait};
|
||||||
use secp256k1;
|
use secp256k1;
|
||||||
|
|
||||||
|
@ -56,8 +55,6 @@ pub enum Error {
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
/// Base58 encoding error
|
/// Base58 encoding error
|
||||||
Base58(base58::Error),
|
Base58(base58::Error),
|
||||||
/// Bech32 encoding error
|
|
||||||
Bech32(bitcoin_bech32::Error),
|
|
||||||
/// Error from the `byteorder` crate
|
/// Error from the `byteorder` crate
|
||||||
ByteOrder(io::Error),
|
ByteOrder(io::Error),
|
||||||
/// secp-related error
|
/// secp-related error
|
||||||
|
@ -104,7 +101,6 @@ impl fmt::Display for Error {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Io(ref e) => fmt::Display::fmt(e, f),
|
Error::Io(ref e) => fmt::Display::fmt(e, f),
|
||||||
Error::Base58(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::ByteOrder(ref e) => fmt::Display::fmt(e, f),
|
||||||
Error::Secp256k1(ref e) => fmt::Display::fmt(e, f),
|
Error::Secp256k1(ref e) => fmt::Display::fmt(e, f),
|
||||||
Error::Psbt(ref e) => fmt::Display::fmt(e, f),
|
Error::Psbt(ref e) => fmt::Display::fmt(e, f),
|
||||||
|
@ -126,7 +122,6 @@ impl error::Error for Error {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Io(ref e) => Some(e),
|
Error::Io(ref e) => Some(e),
|
||||||
Error::Base58(ref e) => Some(e),
|
Error::Base58(ref e) => Some(e),
|
||||||
Error::Bech32(ref e) => Some(e),
|
|
||||||
Error::ByteOrder(ref e) => Some(e),
|
Error::ByteOrder(ref e) => Some(e),
|
||||||
Error::Secp256k1(ref e) => Some(e),
|
Error::Secp256k1(ref e) => Some(e),
|
||||||
Error::Psbt(ref e) => Some(e),
|
Error::Psbt(ref e) => Some(e),
|
||||||
|
@ -146,7 +141,6 @@ impl error::Error for Error {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Io(ref e) => e.description(),
|
Error::Io(ref e) => e.description(),
|
||||||
Error::Base58(ref e) => e.description(),
|
Error::Base58(ref e) => e.description(),
|
||||||
Error::Bech32(ref e) => e.description(),
|
|
||||||
Error::ByteOrder(ref e) => e.description(),
|
Error::ByteOrder(ref e) => e.description(),
|
||||||
Error::Secp256k1(ref e) => e.description(),
|
Error::Secp256k1(ref e) => e.description(),
|
||||||
Error::Psbt(ref e) => e.description(),
|
Error::Psbt(ref e) => e.description(),
|
||||||
|
@ -170,13 +164,6 @@ impl From<base58::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
impl From<bitcoin_bech32::Error> for Error {
|
|
||||||
fn from(e: bitcoin_bech32::Error) -> Error {
|
|
||||||
Error::Bech32(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
impl From<secp256k1::Error> for Error {
|
impl From<secp256k1::Error> for Error {
|
||||||
fn from(e: secp256k1::Error) -> Error {
|
fn from(e: secp256k1::Error) -> Error {
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
|
||||||
extern crate bitcoin_bech32;
|
extern crate bech32;
|
||||||
extern crate bitcoin_hashes;
|
extern crate bitcoin_hashes;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate hex;
|
extern crate hex;
|
||||||
|
|
|
@ -43,16 +43,85 @@
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use bitcoin_bech32::{self, WitnessProgram, u5};
|
use bech32::{self, u5, FromBase32, ToBase32};
|
||||||
use bitcoin_hashes::{hash160, Hash};
|
use bitcoin_hashes::{hash160, Hash};
|
||||||
|
|
||||||
use blockdata::opcodes;
|
use blockdata::opcodes;
|
||||||
use blockdata::script;
|
use blockdata::script;
|
||||||
use network::constants::Network;
|
use network::constants::Network;
|
||||||
use consensus::encode;
|
|
||||||
use util::base58;
|
use util::base58;
|
||||||
use util::key;
|
use util::key;
|
||||||
|
|
||||||
|
/// Address error.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Base58 encoding error
|
||||||
|
Base58(base58::Error),
|
||||||
|
/// Bech32 encoding error
|
||||||
|
Bech32(bech32::Error),
|
||||||
|
/// The bech32 payload was empty
|
||||||
|
EmptyBech32Payload,
|
||||||
|
/// Script version must be 0 to 16 inclusive
|
||||||
|
InvalidWitnessVersion(u8),
|
||||||
|
/// The witness program must be between 2 and 40 bytes in length.
|
||||||
|
InvalidWitnessProgramLength(usize),
|
||||||
|
/// A v0 witness program must be either of length 20 or 32.
|
||||||
|
InvalidSegwitV0ProgramLength(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let desc = ::std::error::Error::description(self);
|
||||||
|
match *self {
|
||||||
|
Error::Base58(ref e) => write!(f, "{}: {}", desc, e),
|
||||||
|
Error::Bech32(ref e) => write!(f, "{}: {}", desc, e),
|
||||||
|
Error::InvalidWitnessVersion(v) => write!(f, "{}: {}", desc, v),
|
||||||
|
Error::InvalidWitnessProgramLength(l) => write!(f, "{}: {}", desc, l),
|
||||||
|
Error::InvalidSegwitV0ProgramLength(l) => write!(f, "{}: {}", desc, l),
|
||||||
|
_ => f.write_str(desc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::error::Error for Error {
|
||||||
|
fn cause(&self) -> Option<&::std::error::Error> {
|
||||||
|
match *self {
|
||||||
|
Error::Base58(ref e) => Some(e),
|
||||||
|
Error::Bech32(ref e) => Some(e),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
Error::Base58(..) => "base58 error",
|
||||||
|
Error::Bech32(..) => "bech32 error",
|
||||||
|
Error::EmptyBech32Payload => "the bech32 payload was empty",
|
||||||
|
Error::InvalidWitnessVersion(..) => "invalid witness script version",
|
||||||
|
Error::InvalidWitnessProgramLength(..) => {
|
||||||
|
"the witness program must be between 2 and 40 bytes in length"
|
||||||
|
},
|
||||||
|
Error::InvalidSegwitV0ProgramLength(..) => {
|
||||||
|
"a v0 witness program must be either of length 20 or 32"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl From<base58::Error> for Error {
|
||||||
|
fn from(e: base58::Error) -> Error {
|
||||||
|
Error::Base58(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl From<bech32::Error> for Error {
|
||||||
|
fn from(e: bech32::Error) -> Error {
|
||||||
|
Error::Bech32(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The method used to produce an address
|
/// The method used to produce an address
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub enum Payload {
|
pub enum Payload {
|
||||||
|
@ -61,7 +130,12 @@ pub enum Payload {
|
||||||
/// P2SH address
|
/// P2SH address
|
||||||
ScriptHash(hash160::Hash),
|
ScriptHash(hash160::Hash),
|
||||||
/// Segwit address
|
/// Segwit address
|
||||||
WitnessProgram(WitnessProgram),
|
WitnessProgram {
|
||||||
|
/// The witness program version
|
||||||
|
version: u5,
|
||||||
|
/// The witness program
|
||||||
|
program: Vec<u8>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
@ -106,11 +180,10 @@ impl Address {
|
||||||
|
|
||||||
Address {
|
Address {
|
||||||
network: network,
|
network: network,
|
||||||
payload: Payload::WitnessProgram(
|
payload: Payload::WitnessProgram {
|
||||||
// unwrap is safe as witness program is known to be correct as above
|
version: u5::try_from_u8(0).expect("0<32"),
|
||||||
WitnessProgram::new(u5::try_from_u8(0).expect("0<32"),
|
program: hash160::Hash::from_engine(hash_engine)[..].to_vec(),
|
||||||
hash160::Hash::from_engine(hash_engine)[..].to_vec(),
|
},
|
||||||
Address::bech_network(network)).unwrap())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,14 +211,10 @@ impl Address {
|
||||||
|
|
||||||
Address {
|
Address {
|
||||||
network: network,
|
network: network,
|
||||||
payload: Payload::WitnessProgram(
|
payload: Payload::WitnessProgram {
|
||||||
// unwrap is safe as witness program is known to be correct as above
|
version: u5::try_from_u8(0).expect("0<32"),
|
||||||
WitnessProgram::new(
|
program: sha256::Hash::hash(&script[..])[..].to_vec(),
|
||||||
u5::try_from_u8(0).expect("0<32"),
|
},
|
||||||
sha256::Hash::hash(&script[..])[..].to_vec(),
|
|
||||||
Address::bech_network(network)
|
|
||||||
).unwrap()
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,39 +233,44 @@ impl Address {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Check whether or not the address is following Bitcoin
|
||||||
/// convert Network to bech32 network (this should go away soon)
|
/// standardness rules.
|
||||||
fn bech_network (network: Network) -> bitcoin_bech32::constants::Network {
|
///
|
||||||
match network {
|
/// Segwit addresses with unassigned witness versions or non-standard
|
||||||
Network::Bitcoin => bitcoin_bech32::constants::Network::Bitcoin,
|
/// program sizes are considered non-standard.
|
||||||
Network::Testnet => bitcoin_bech32::constants::Network::Testnet,
|
pub fn is_standard(&self) -> bool {
|
||||||
Network::Regtest => bitcoin_bech32::constants::Network::Regtest,
|
match self.payload {
|
||||||
|
Payload::WitnessProgram {
|
||||||
|
version: ver,
|
||||||
|
program: ref prog,
|
||||||
|
} => {
|
||||||
|
// BIP-141 p2wpkh or p2wsh addresses.
|
||||||
|
ver.to_u8() == 0 && (prog.len() == 20 || prog.len() == 32)
|
||||||
|
}
|
||||||
|
Payload::PubkeyHash(_) => true,
|
||||||
|
Payload::ScriptHash(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a script pubkey spending to this address
|
/// Generates a script pubkey spending to this address
|
||||||
pub fn script_pubkey(&self) -> script::Script {
|
pub fn script_pubkey(&self) -> script::Script {
|
||||||
match self.payload {
|
match self.payload {
|
||||||
Payload::PubkeyHash(ref hash) => {
|
Payload::PubkeyHash(ref hash) => script::Builder::new()
|
||||||
script::Builder::new()
|
.push_opcode(opcodes::all::OP_DUP)
|
||||||
.push_opcode(opcodes::all::OP_DUP)
|
.push_opcode(opcodes::all::OP_HASH160)
|
||||||
.push_opcode(opcodes::all::OP_HASH160)
|
.push_slice(&hash[..])
|
||||||
.push_slice(&hash[..])
|
.push_opcode(opcodes::all::OP_EQUALVERIFY)
|
||||||
.push_opcode(opcodes::all::OP_EQUALVERIFY)
|
.push_opcode(opcodes::all::OP_CHECKSIG),
|
||||||
.push_opcode(opcodes::all::OP_CHECKSIG)
|
Payload::ScriptHash(ref hash) => script::Builder::new()
|
||||||
},
|
.push_opcode(opcodes::all::OP_HASH160)
|
||||||
Payload::ScriptHash(ref hash) => {
|
.push_slice(&hash[..])
|
||||||
script::Builder::new()
|
.push_opcode(opcodes::all::OP_EQUAL),
|
||||||
.push_opcode(opcodes::all::OP_HASH160)
|
Payload::WitnessProgram {
|
||||||
.push_slice(&hash[..])
|
version: ver,
|
||||||
.push_opcode(opcodes::all::OP_EQUAL)
|
program: ref prog,
|
||||||
},
|
} => script::Builder::new().push_int(ver.to_u8() as i64).push_slice(&prog),
|
||||||
Payload::WitnessProgram(ref witprog) => {
|
}
|
||||||
script::Builder::new()
|
.into_script()
|
||||||
.push_int(witprog.version().to_u8() as i64)
|
|
||||||
.push_slice(witprog.program())
|
|
||||||
}
|
|
||||||
}.into_script()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,48 +294,88 @@ impl Display for Address {
|
||||||
};
|
};
|
||||||
prefixed[1..].copy_from_slice(&hash[..]);
|
prefixed[1..].copy_from_slice(&hash[..]);
|
||||||
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
|
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
|
||||||
},
|
}
|
||||||
Payload::WitnessProgram(ref witprog) => {
|
Payload::WitnessProgram {
|
||||||
fmt.write_str(&witprog.to_address())
|
version: ver,
|
||||||
},
|
program: ref prog,
|
||||||
|
} => {
|
||||||
|
let mut b32_data = vec![ver];
|
||||||
|
b32_data.extend_from_slice(&prog.to_base32());
|
||||||
|
let hrp = match self.network {
|
||||||
|
Network::Bitcoin => "bc",
|
||||||
|
Network::Testnet => "tb",
|
||||||
|
Network::Regtest => "bcrt",
|
||||||
|
};
|
||||||
|
bech32::encode_to_fmt(fmt, &hrp, &b32_data).expect("only errors on invalid HRP")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Address {
|
/// Extract the bech32 prefix.
|
||||||
type Err = encode::Error;
|
/// Returns the same slice when no prefix is found.
|
||||||
|
fn find_bech32_prefix(bech32: &str) -> &str {
|
||||||
|
// Split at the last occurrence of the separator character '1'.
|
||||||
|
match bech32.rfind("1") {
|
||||||
|
None => bech32,
|
||||||
|
Some(sep) => bech32.split_at(sep).0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Address, encode::Error> {
|
impl FromStr for Address {
|
||||||
// bech32 (note that upper or lowercase is allowed but NOT mixed case)
|
type Err = Error;
|
||||||
if s.starts_with("bc1") || s.starts_with("BC1") ||
|
|
||||||
s.starts_with("tb1") || s.starts_with("TB1") ||
|
fn from_str(s: &str) -> Result<Address, Error> {
|
||||||
s.starts_with("bcrt1") || s.starts_with("BCRT1")
|
// try bech32
|
||||||
{
|
let bech32_network = match find_bech32_prefix(s) {
|
||||||
let witprog = WitnessProgram::from_address(s)?;
|
// note that upper or lowercase is allowed but NOT mixed case
|
||||||
let network = match witprog.network() {
|
"bc" | "BC" => Some(Network::Bitcoin),
|
||||||
bitcoin_bech32::constants::Network::Bitcoin => Network::Bitcoin,
|
"tb" | "TB" => Some(Network::Testnet),
|
||||||
bitcoin_bech32::constants::Network::Testnet => Network::Testnet,
|
"bcrt" | "BCRT" => Some(Network::Regtest),
|
||||||
bitcoin_bech32::constants::Network::Regtest => Network::Regtest,
|
_ => None,
|
||||||
_ => panic!("unknown network")
|
};
|
||||||
};
|
if let Some(network) = bech32_network {
|
||||||
if witprog.version().to_u8() != 0 {
|
// decode as bech32
|
||||||
return Err(encode::Error::UnsupportedWitnessVersion(witprog.version().to_u8()));
|
let (_, payload) = bech32::decode(s)?;
|
||||||
|
if payload.len() == 0 {
|
||||||
|
return Err(Error::EmptyBech32Payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the script version and program (converted from 5-bit to 8-bit)
|
||||||
|
let (version, program) = {
|
||||||
|
let (v, p5) = payload.split_at(1);
|
||||||
|
(v[0], Vec::from_base32(p5)?)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generic segwit checks.
|
||||||
|
if version.to_u8() > 16 {
|
||||||
|
return Err(Error::InvalidWitnessVersion(version.to_u8()));
|
||||||
|
}
|
||||||
|
if program.len() < 2 || program.len() > 40 {
|
||||||
|
return Err(Error::InvalidWitnessProgramLength(program.len()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific segwit v0 check.
|
||||||
|
if version.to_u8() == 0 && (program.len() != 20 && program.len() != 32) {
|
||||||
|
return Err(Error::InvalidSegwitV0ProgramLength(program.len()));
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(Address {
|
return Ok(Address {
|
||||||
|
payload: Payload::WitnessProgram {
|
||||||
|
version: version,
|
||||||
|
program: program,
|
||||||
|
},
|
||||||
network: network,
|
network: network,
|
||||||
payload: Payload::WitnessProgram(witprog),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Base58
|
||||||
if s.len() > 50 {
|
if s.len() > 50 {
|
||||||
return Err(encode::Error::Base58(base58::Error::InvalidLength(s.len() * 11 / 15)));
|
return Err(Error::Base58(base58::Error::InvalidLength(s.len() * 11 / 15)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base 58
|
|
||||||
let data = base58::from_check(s)?;
|
let data = base58::from_check(s)?;
|
||||||
|
|
||||||
if data.len() != 21 {
|
if data.len() != 21 {
|
||||||
return Err(encode::Error::Base58(base58::Error::InvalidLength(data.len())));
|
return Err(Error::Base58(base58::Error::InvalidLength(data.len())));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (network, payload) = match data[0] {
|
let (network, payload) = match data[0] {
|
||||||
|
@ -281,7 +395,7 @@ impl FromStr for Address {
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
Payload::ScriptHash(hash160::Hash::from_slice(&data[1..]).unwrap())
|
Payload::ScriptHash(hash160::Hash::from_slice(&data[1..]).unwrap())
|
||||||
),
|
),
|
||||||
x => return Err(encode::Error::Base58(base58::Error::InvalidVersion(vec![x])))
|
x => return Err(Error::Base58(base58::Error::InvalidVersion(vec![x]))),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Address {
|
Ok(Address {
|
||||||
|
|
Loading…
Reference in New Issue