use std::marker::PhantomData;
use coins_core::{
bases::{decode_base58, encode_base58},
enc::{AddressEncoder, EncodingError, EncodingResult},
hashes::MarkedDigestOutput,
};
use crate::{
enc::bases::{decode_bech32, encode_bech32},
types::script::{ScriptPubkey, ScriptType},
};
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum Address {
Pkh(String),
Sh(String),
Wpkh(String),
Wsh(String),
}
impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let addr = match &self {
Address::Pkh(s) => s,
Address::Sh(s) => s,
Address::Wpkh(s) => s,
Address::Wsh(s) => s,
};
write!(f, "{}", addr)
}
}
impl AsRef<str> for Address {
fn as_ref(&self) -> &str {
match &self {
Address::Pkh(s) => s,
Address::Sh(s) => s,
Address::Wpkh(s) => s,
Address::Wsh(s) => s,
}
}
}
impl Address {
pub fn as_string(&self) -> String {
match &self {
Address::Pkh(s) => s.clone(),
Address::Sh(s) => s.clone(),
Address::Wpkh(s) => s.clone(),
Address::Wsh(s) => s.clone(),
}
}
pub fn to_descriptor(&self) -> String {
format!("addr({})", self.as_string())
}
}
pub trait NetworkParams {
const HRP: &'static str;
const PKH_VERSION: u8;
const SH_VERSION: u8;
}
pub trait BitcoinEncoderMarker:
AddressEncoder<Address = Address, Error = EncodingError, RecipientIdentifier = ScriptPubkey>
{
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BitcoinEncoder<P: NetworkParams>(PhantomData<fn(P) -> P>);
impl<P: NetworkParams> AddressEncoder for BitcoinEncoder<P> {
type Address = Address;
type Error = EncodingError;
type RecipientIdentifier = ScriptPubkey;
fn encode_address(s: &ScriptPubkey) -> EncodingResult<Address> {
match s.standard_type() {
ScriptType::Pkh(payload) => {
Ok(Address::Pkh(encode_base58(
P::PKH_VERSION,
payload.as_slice(),
)))
}
ScriptType::Sh(payload) => {
Ok(Address::Sh(encode_base58(
P::SH_VERSION,
payload.as_slice(),
)))
}
ScriptType::Wsh(_) => Ok(Address::Wsh(encode_bech32(P::HRP, s.items())?)),
ScriptType::Wpkh(_) => Ok(Address::Wpkh(encode_bech32(P::HRP, s.items())?)),
ScriptType::OpReturn(_) => Err(EncodingError::NullDataScript),
ScriptType::NonStandard => Err(EncodingError::UnknownScriptType),
}
}
fn decode_address(addr: &Address) -> ScriptPubkey {
match &addr {
Address::Pkh(s) => decode_base58(P::PKH_VERSION, s).unwrap().into(),
Address::Sh(s) => decode_base58(P::SH_VERSION, s).unwrap().into(),
Address::Wpkh(s) | Address::Wsh(s) => decode_bech32(P::HRP, s).unwrap().into(),
}
}
fn string_to_address(string: &str) -> EncodingResult<Address> {
let s = string.to_owned();
if s.starts_with(P::HRP) {
let result = decode_bech32(P::HRP, &s)?;
match result.len() {
22 => Ok(Address::Wpkh(s)),
34 => Ok(Address::Wsh(s)),
_ => Err(EncodingError::UnknownScriptType),
}
} else if decode_base58(P::PKH_VERSION, &s).is_ok() {
Ok(Address::Pkh(s))
} else if decode_base58(P::SH_VERSION, &s).is_ok() {
Ok(Address::Sh(s))
} else {
Err(EncodingError::UnknownScriptType)
}
}
}
impl<P: NetworkParams> BitcoinEncoderMarker for BitcoinEncoder<P> {}
#[derive(Debug, Clone)]
pub struct Main;
impl NetworkParams for Main {
const HRP: &'static str = "bc";
const PKH_VERSION: u8 = 0x00;
const SH_VERSION: u8 = 0x05;
}
#[derive(Debug, Clone)]
pub struct Test;
impl NetworkParams for Test {
const HRP: &'static str = "tb";
const PKH_VERSION: u8 = 0x6f;
const SH_VERSION: u8 = 0xc4;
}
#[derive(Debug, Clone)]
pub struct Sig;
impl NetworkParams for Sig {
const HRP: &'static str = "sb";
const PKH_VERSION: u8 = 0x7d;
const SH_VERSION: u8 = 0x57;
}
pub type MainnetEncoder = BitcoinEncoder<Main>;
pub type TestnetEncoder = BitcoinEncoder<Test>;
pub type SignetEncoder = BitcoinEncoder<Sig>;
#[cfg(test)]
mod test {
use super::*;
#[test]
fn it_wraps_address_strings() {
let cases = [
(
"bc1qza7dfgl2q83cf68fqkkdd754qx546h4u9vd9tg".to_owned(),
Address::Wpkh("bc1qza7dfgl2q83cf68fqkkdd754qx546h4u9vd9tg".to_owned()),
),
(
"bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej".to_owned(),
Address::Wsh(
"bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej".to_owned(),
),
),
(
"1AqE7oGF1EUoJviX1uuYrwpRBdEBTuGhES".to_owned(),
Address::Pkh("1AqE7oGF1EUoJviX1uuYrwpRBdEBTuGhES".to_owned()),
),
(
"3HXNFmJpxjgTVFN35Y9f6Waje5YFsLEQZ2".to_owned(),
Address::Sh("3HXNFmJpxjgTVFN35Y9f6Waje5YFsLEQZ2".to_owned()),
),
];
for case in cases.iter() {
assert_eq!(MainnetEncoder::string_to_address(&case.0).unwrap(), case.1);
}
let errors = [
"hello",
"this isn't a real address",
"bc10pu8s7rc0pu8s7rc0putt44am", ];
for case in errors.iter() {
match MainnetEncoder::string_to_address(case) {
Err(EncodingError::UnknownScriptType) => {}
_ => panic!("expected err UnknownScriptType"),
}
}
}
#[test]
fn it_encodes_addresses() {
let cases = [
(
ScriptPubkey::new(
hex::decode("a914e88869b88866281ab166541ad8aafba8f8aba47a87").unwrap(),
),
Address::Sh("3NtY7BrF3xrcb31JXXaYCKVcz1cH3Azo5y".to_owned()),
),
(
ScriptPubkey::new(
hex::decode("76a9140e5c3c8d420c7f11e88d76f7b860d471e6517a4488ac").unwrap(),
),
Address::Pkh("12JvxPk4mT4PKMVHuHc1aQGBZpotQWQwF6".to_owned()),
),
(
ScriptPubkey::new(
hex::decode(
"00201bf8a1831db5443b42a44f30a121d1b616d011ab15df62b588722a845864cc99",
)
.unwrap(),
),
Address::Wsh(
"bc1qr0u2rqcak4zrks4yfuc2zgw3kctdqydtzh0k9dvgwg4ggkryejvsy49jvz".to_owned(),
),
),
(
ScriptPubkey::new(
hex::decode("00141bf8a1831db5443b42a44f30a121d1b616d011ab").unwrap(),
),
Address::Wpkh("bc1qr0u2rqcak4zrks4yfuc2zgw3kctdqydt3wy5yh".to_owned()),
),
];
for case in cases.iter() {
assert_eq!(MainnetEncoder::encode_address(&case.0).unwrap(), case.1);
}
let errors = [
(ScriptPubkey::new(hex::decode("01201bf8a1831db5443b42a44f30a121d1b616d011ab15df62b588722a845864cc99").unwrap())), (ScriptPubkey::new(hex::decode("a914e88869b88866281ab166541ad8aafba8f8aba47a89").unwrap())), (ScriptPubkey::new(hex::decode("aa14e88869b88866281ab166541ad8aafba8f8aba47a87").unwrap())), (ScriptPubkey::new(hex::decode("76a9140e5c3c8d420c7f11e88d76f7b860d471e6517a4488ad").unwrap())), (ScriptPubkey::new(hex::decode("77a9140e5c3c8d420c7f11e88d76f7b860d471e6517a4488ac").unwrap())), (ScriptPubkey::new(hex::decode("01141bf8a1831db5443b42a44f30a121d1b616d011ab").unwrap())), (ScriptPubkey::new(hex::decode("0011223344").unwrap())), (ScriptPubkey::new(hex::decode("deadbeefdeadbeefdeadbeefdeadbeef").unwrap())), (ScriptPubkey::new(hex::decode("02031bf8a1831db5443b42a44f30a121d1b616d011ab15df62b588722a845864cc99041bf8a1831db5443b42a44f30a121d1b616d011ab15df62b588722a845864cc9902af").unwrap())), ];
for case in errors.iter() {
match MainnetEncoder::encode_address(case) {
Err(EncodingError::UnknownScriptType) => {}
_ => panic!("expected err UnknownScriptType"),
}
}
}
#[test]
fn it_allows_you_to_unwrap_strings_from_addresses() {
let cases = [
(
"3NtY7BrF3xrcb31JXXaYCKVcz1cH3Azo5y".to_owned(),
Address::Sh("3NtY7BrF3xrcb31JXXaYCKVcz1cH3Azo5y".to_owned()),
),
(
"12JvxPk4mT4PKMVHuHc1aQGBZpotQWQwF6".to_owned(),
Address::Pkh("12JvxPk4mT4PKMVHuHc1aQGBZpotQWQwF6".to_owned()),
),
(
"bc1qr0u2rqcak4zrks4yfuc2zgw3kctdqydtzh0k9dvgwg4ggkryejvsy49jvz".to_owned(),
Address::Wsh(
"bc1qr0u2rqcak4zrks4yfuc2zgw3kctdqydtzh0k9dvgwg4ggkryejvsy49jvz".to_owned(),
),
),
(
"bc1qr0u2rqcak4zrks4yfuc2zgw3kctdqydt3wy5yh".to_owned(),
Address::Wpkh("bc1qr0u2rqcak4zrks4yfuc2zgw3kctdqydt3wy5yh".to_owned()),
),
];
for case in cases.iter() {
assert_eq!(case.1.as_string(), case.0);
}
}
}