#![deny(missing_docs)]
#![deny(non_upper_case_globals)]
#![deny(non_camel_case_types)]
#![deny(non_snake_case)]
#![deny(unused_mut)]
extern crate bech32;
use bech32::{Bech32, ToBase32, FromBase32};
pub use bech32::u5;
use std::{error, fmt};
use std::str::FromStr;
use std::string::ToString;
pub mod constants;
use constants::Network;
#[derive(PartialEq, Eq, Debug, Clone, PartialOrd, Ord, Hash)]
pub struct WitnessProgram {
version: u5,
program: Vec<u8>,
network: Network,
bech32: Bech32,
}
impl WitnessProgram {
pub fn new(version: u5, program: Vec<u8>, network: Network) -> Result<WitnessProgram, Error> {
let hrp = constants::hrp(&network);
let mut b32_data: Vec<u5> = vec![version];
let p5 = program.to_base32();
b32_data.extend_from_slice(&p5);
let bech32 = Bech32::new(hrp, b32_data)?;
let ret = WitnessProgram {
version: version,
program: program,
network: network,
bech32: bech32,
};
ret.validate()?;
Ok(ret)
}
pub fn to_address(&self) -> String {
self.to_string()
}
pub fn from_address(address: &str) -> Result<WitnessProgram, Error> {
WitnessProgram::from_str(address)
}
pub fn to_scriptpubkey(&self) -> Vec<u8> {
let mut pubkey: Vec<u8> = Vec::new();
let mut v: u8 = self.version.into();
if v > 0 {
v += 0x50;
}
pubkey.push(v);
pubkey.push(self.program.len() as u8);
pubkey.extend_from_slice(&self.program);
pubkey
}
pub fn from_scriptpubkey(pubkey: &[u8], network: Network) -> Result<WitnessProgram, Error> {
if pubkey.len() < 4 {
return Err(Error::ScriptPubkeyTooShort)
}
let proglen: usize = pubkey[1] as usize;
if pubkey.len() != 2 + proglen {
return Err(Error::ScriptPubkeyInvalidLength)
}
let mut v: u8 = pubkey[0];
if v > 0x50 {
v -= 0x50;
}
let v = u5::try_from_u8(v).expect("range is already guaranteed by code above");
let program = &pubkey[2..];
WitnessProgram::new(v, program.to_vec(), network)
}
pub fn validate(&self) -> Result<(), Error> {
if self.version.to_u8() > 16 {
return Err(Error::InvalidScriptVersion)
}
if self.program.len() < 2 || self.program.len() > 40 {
return Err(Error::InvalidLength)
}
if self.version.to_u8() == 0 &&
self.program.len() != 20 && self.program.len() != 32 {
return Err(Error::InvalidVersionLength)
}
Ok(())
}
pub fn version(&self) -> u5 {
self.version
}
pub fn program(&self) -> &[u8] {
&self.program
}
pub fn network(&self) -> Network {
self.network
}
}
impl ToString for WitnessProgram {
fn to_string(&self) -> String {
self.bech32.to_string()
}
}
impl FromStr for WitnessProgram {
type Err = Error;
fn from_str(s: &str) -> Result<WitnessProgram, Error> {
let b32 = s.parse::<Bech32>()?;
let network_classified = match constants::classify(b32.hrp()) {
Some(nc) => nc,
None => return Err(Error::InvalidHumanReadablePart)
};
if b32.data().is_empty() || b32.data().len() > 65 {
return Err(Error::Bech32(bech32::Error::InvalidLength))
}
let (version, program) = {
let (v, p5) = b32.data().split_at(1);
let program = Vec::from_base32(p5)?;
(v[0], program)
};
let wp = WitnessProgram {
version: version,
program: program,
network: network_classified,
bech32: b32,
};
wp.validate()?;
Ok(wp)
}
}
#[derive(PartialEq, Debug)]
pub enum Error {
Bech32(bech32::Error),
InvalidHumanReadablePart,
ScriptPubkeyTooShort,
ScriptPubkeyInvalidLength,
InvalidLength,
InvalidVersionLength,
InvalidScriptVersion,
}
impl From<bech32::Error> for Error {
fn from(e: bech32::Error) -> Error {
Error::Bech32(e)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Bech32(ref e) => write!(f, "{}", e),
Error::InvalidHumanReadablePart => write!(f, "invalid human-readable part"),
Error::ScriptPubkeyTooShort => write!(f, "scriptpubkey too short"),
Error::ScriptPubkeyInvalidLength => write!(f, "scriptpubkey length mismatch"),
Error::InvalidLength => write!(f, "invalid length"),
Error::InvalidVersionLength => write!(f, "program length incompatible with version"),
Error::InvalidScriptVersion => write!(f, "invalid script versio"),
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::Bech32(_) => "Bech32 error",
Error::InvalidHumanReadablePart => "invalid human-readable part",
Error::ScriptPubkeyTooShort => "scriptpubkey too short",
Error::ScriptPubkeyInvalidLength => "scriptpubkey length mismatch",
Error::InvalidLength => "invalid length",
Error::InvalidVersionLength => "program length incompatible with version",
Error::InvalidScriptVersion => "invalid script version"
}
}
fn cause(&self) -> Option<&std::error::Error> {
match *self {
Error::Bech32(ref e) => Some(e),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use ::*;
use ::constants::Network;
use bech32;
#[test]
fn valid_address() {
let pairs: Vec<(&str, Vec<u8>, Network)> = vec![
(
"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
vec![
0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6
],
Network::Bitcoin,
),
(
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
vec![
0x00, 0x20, 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, 0x04,
0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, 0x6c, 0x98, 0x56, 0x78,
0xcd, 0x4d, 0x27, 0xa1, 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32,
0x62
],
Network::Testnet
),
(
"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
vec![
0x51, 0x28, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6,
0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c,
0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6
],
Network::Bitcoin,
),
(
"BC1SW50QA3JX3S",
vec![
0x60, 0x02, 0x75, 0x1e
],
Network::Bitcoin,
),
(
"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
vec![
0x52, 0x10, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23
],
Network::Bitcoin,
),
(
"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
vec![
0x00, 0x20, 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21,
0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, 0x36, 0x2b, 0x99, 0xd5,
0xe9, 0x1c, 0x6c, 0xe2, 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64,
0x33
],
Network::Testnet,
),
(
"bcrt1qn3h68k2u0rr49skx05qw7veynpf4lfppd2demt",
vec![
0x00, 0x14, 0x9c, 0x6f, 0xa3, 0xd9, 0x5c, 0x78, 0xc7, 0x52, 0xc2,
0xc6, 0x7d, 0x00, 0xef, 0x33, 0x24, 0x98, 0x53, 0x5f, 0xa4, 0x21,
],
Network::Regtest,
),
];
for p in pairs {
let (address, scriptpubkey, network) = p;
let version = if scriptpubkey[0] == 0 { 0 } else { scriptpubkey[0] - 0x50 };
let dec_result = WitnessProgram::from_address(&address);
assert!(dec_result.is_ok());
let prog = dec_result.unwrap();
let pubkey = prog.to_scriptpubkey();
assert_eq!(pubkey, scriptpubkey);
assert_eq!(prog.network(), network);
assert_eq!(prog.version().to_u8(), version);
assert_eq!(prog.program(), &scriptpubkey[2..]);
let spk_result = WitnessProgram::from_scriptpubkey(&scriptpubkey, prog.network);
assert!(spk_result.is_ok());
assert_eq!(prog, spk_result.unwrap());
let enc_address = prog.to_address();
assert_eq!(address.to_lowercase(), enc_address.to_lowercase());
}
}
#[test]
fn invalid_address() {
let pairs: Vec<(&str, Error)> = vec!(
("tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
Error::InvalidHumanReadablePart),
("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
Error::Bech32(bech32::Error::InvalidChecksum)),
("BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
Error::InvalidScriptVersion),
("bc1rw5uspcuh",
Error::InvalidLength),
("bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
Error::Bech32(bech32::Error::InvalidLength)),
("BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
Error::InvalidVersionLength),
("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
Error::Bech32(bech32::Error::MixedCase)),
("tb1pw508d6qejxtdg4y5r3zarqfsj6c3",
Error::Bech32(bech32::Error::InvalidPadding)),
("bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
Error::Bech32(bech32::Error::InvalidPadding)),
("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
Error::Bech32(bech32::Error::InvalidPadding)),
("bc1gmk9yu",
Error::Bech32(bech32::Error::InvalidLength)),
);
for p in pairs {
let (address, desired_error) = p;
let dec_result = WitnessProgram::from_address(&address);
if dec_result.is_ok() {
panic!("Should be invalid: {:?}", address);
}
assert_eq!(dec_result.unwrap_err(), desired_error);
}
}
}