use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
use amplify::Wrapper;
use bitcoin::hashes::{hex, Hash};
use bitcoin::schnorr::TweakedPublicKey;
use bitcoin::secp256k1::XOnlyPublicKey;
use bitcoin::util::address::{self, Payload, WitnessVersion};
use bitcoin::{secp256k1, Address, PubkeyHash, Script, ScriptHash, WPubkeyHash, WScriptHash};
use crate::PubkeyScript;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SegWitInfo {
PreSegWit,
Ambiguous,
SegWit(WitnessVersion),
}
impl SegWitInfo {
#[inline]
pub fn witness_version(self) -> Option<WitnessVersion> {
match self {
SegWitInfo::PreSegWit => None,
SegWitInfo::Ambiguous => None,
SegWitInfo::SegWit(version) => Some(version),
}
}
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
#[derive(StrictEncode, StrictDecode)]
pub struct AddressCompat {
pub payload: AddressPayload,
pub network: AddressNetwork,
}
impl AddressCompat {
pub fn from_script(script: &PubkeyScript, network: AddressNetwork) -> Option<Self> {
Address::from_script(script.as_inner(), network.bitcoin_network())
.map_err(|_| address::Error::UncompressedPubkey)
.and_then(Self::try_from)
.ok()
}
pub fn script_pubkey(self) -> PubkeyScript { self.payload.script_pubkey() }
pub fn is_testnet(self) -> bool { self.network != AddressNetwork::Mainnet }
}
impl From<AddressCompat> for Address {
fn from(compact: AddressCompat) -> Self {
compact
.payload
.into_address(compact.network.bitcoin_network())
}
}
impl TryFrom<Address> for AddressCompat {
type Error = address::Error;
fn try_from(address: Address) -> Result<Self, Self::Error> {
Ok(AddressCompat {
payload: address.payload.try_into()?,
network: address.network.into(),
})
}
}
impl From<AddressCompat> for PubkeyScript {
fn from(compact: AddressCompat) -> Self { Address::from(compact).script_pubkey().into() }
}
impl Display for AddressCompat {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&Address::from(*self), f) }
}
impl FromStr for AddressCompat {
type Err = address::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Address::from_str(s).and_then(AddressCompat::try_from)
}
}
#[derive(
Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From
)]
#[derive(StrictEncode, StrictDecode)]
pub enum AddressPayload {
#[from]
#[display("raw_pkh({0})")]
PubkeyHash(PubkeyHash),
#[from]
#[display("raw_sh({0})")]
ScriptHash(ScriptHash),
#[from]
#[display("raw_wpkh({0})")]
WPubkeyHash(WPubkeyHash),
#[from]
#[display("raw_wsh({0})")]
WScriptHash(WScriptHash),
#[from]
#[display("raw_tr({output_key})")]
Taproot {
output_key: TweakedPublicKey,
},
}
impl AddressPayload {
pub fn into_address(self, network: bitcoin::Network) -> Address {
Address {
payload: self.into(),
network,
}
}
pub fn from_address(address: Address) -> Option<Self> { Self::from_payload(address.payload) }
pub fn from_payload(payload: Payload) -> Option<Self> {
Some(match payload {
Payload::PubkeyHash(pkh) => AddressPayload::PubkeyHash(pkh),
Payload::ScriptHash(sh) => AddressPayload::ScriptHash(sh),
Payload::WitnessProgram { version, program }
if version.to_num() == 0 && program.len() == 20 =>
{
AddressPayload::WPubkeyHash(
WPubkeyHash::from_slice(&program)
.expect("WPubkeyHash vec length estimation is broken"),
)
}
Payload::WitnessProgram { version, program }
if version.to_num() == 0 && program.len() == 32 =>
{
AddressPayload::WScriptHash(
WScriptHash::from_slice(&program)
.expect("WScriptHash vec length estimation is broken"),
)
}
Payload::WitnessProgram { version, program }
if version.to_num() == 1 && program.len() == 32 =>
{
AddressPayload::Taproot {
output_key: TweakedPublicKey::dangerous_assume_tweaked(
XOnlyPublicKey::from_slice(&program)
.expect("Taproot public key vec length estimation is broken"),
),
}
}
_ => return None,
})
}
pub fn from_script(script: &PubkeyScript) -> Option<Self> {
Address::from_script(script.as_inner(), bitcoin::Network::Bitcoin)
.ok()
.and_then(Self::from_address)
}
pub fn script_pubkey(self) -> PubkeyScript {
match self {
AddressPayload::PubkeyHash(hash) => Script::new_p2pkh(&hash),
AddressPayload::ScriptHash(hash) => Script::new_p2sh(&hash),
AddressPayload::WPubkeyHash(hash) => Script::new_v0_p2wpkh(&hash),
AddressPayload::WScriptHash(hash) => Script::new_v0_p2wsh(&hash),
AddressPayload::Taproot { output_key } => Script::new_v1_p2tr_tweaked(output_key),
}
.into()
}
}
impl From<AddressPayload> for Payload {
fn from(ap: AddressPayload) -> Self {
match ap {
AddressPayload::PubkeyHash(pkh) => Payload::PubkeyHash(pkh),
AddressPayload::ScriptHash(sh) => Payload::ScriptHash(sh),
AddressPayload::WPubkeyHash(wpkh) => Payload::WitnessProgram {
version: WitnessVersion::V0,
program: wpkh.to_vec(),
},
AddressPayload::WScriptHash(wsh) => Payload::WitnessProgram {
version: WitnessVersion::V0,
program: wsh.to_vec(),
},
AddressPayload::Taproot { output_key } => Payload::WitnessProgram {
version: WitnessVersion::V1,
program: output_key.serialize().to_vec(),
},
}
}
}
impl TryFrom<Payload> for AddressPayload {
type Error = address::Error;
fn try_from(payload: Payload) -> Result<Self, Self::Error> {
Ok(match payload {
Payload::PubkeyHash(hash) => AddressPayload::PubkeyHash(hash),
Payload::ScriptHash(hash) => AddressPayload::ScriptHash(hash),
Payload::WitnessProgram { version, program } if version.to_num() == 0u8 => {
if program.len() == 32 {
AddressPayload::WScriptHash(
WScriptHash::from_slice(&program)
.expect("WScriptHash is broken: it must be 32 byte len"),
)
} else if program.len() == 20 {
AddressPayload::WPubkeyHash(
WPubkeyHash::from_slice(&program)
.expect("WScriptHash is broken: it must be 20 byte len"),
)
} else {
panic!(
"bitcoin::Address is broken: v0 witness program must be either 32 or 20 \
bytes len"
)
}
}
Payload::WitnessProgram { version, program } if version.to_num() == 1u8 => {
if program.len() == 32 {
AddressPayload::Taproot {
output_key: TweakedPublicKey::dangerous_assume_tweaked(
XOnlyPublicKey::from_slice(&program)
.expect("bip340::PublicKey is broken: it must be 32 byte len"),
),
}
} else {
panic!(
"bitcoin::Address is broken: v1 witness program must be either 32 bytes \
len"
)
}
}
Payload::WitnessProgram { version, .. } => {
return Err(address::Error::InvalidWitnessVersion(version.to_num()))
}
})
}
}
impl From<AddressPayload> for PubkeyScript {
fn from(ap: AddressPayload) -> Self {
ap.into_address(bitcoin::Network::Bitcoin)
.script_pubkey()
.into()
}
}
#[derive(
Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error, From
)]
#[display(doc_comments)]
pub enum AddressParseError {
UnknownPrefix(String),
UnrecognizedStringFormat,
PrefixAbsent,
#[from(hex::Error)]
WrongPayloadHashData,
#[from(secp256k1::Error)]
WrongPublicKeyData,
UnrecognizedAddressNetwork,
UnrecognizedAddressFormat,
WrongWitnessVersion,
}
impl FromStr for AddressPayload {
type Err = AddressParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
let mut split = s.trim_end_matches(')').split('(');
Ok(match (split.next(), split.next(), split.next()) {
(_, _, Some(_)) => return Err(AddressParseError::UnrecognizedStringFormat),
(Some("pkh"), Some(hash), None) => {
AddressPayload::PubkeyHash(PubkeyHash::from_str(hash)?)
}
(Some("sh"), Some(hash), None) => {
AddressPayload::ScriptHash(ScriptHash::from_str(hash)?)
}
(Some("wpkh"), Some(hash), None) => {
AddressPayload::WPubkeyHash(WPubkeyHash::from_str(hash)?)
}
(Some("wsh"), Some(hash), None) => {
AddressPayload::WScriptHash(WScriptHash::from_str(hash)?)
}
(Some("pkxo"), Some(hash), None) => AddressPayload::Taproot {
output_key: TweakedPublicKey::dangerous_assume_tweaked(XOnlyPublicKey::from_str(
hash,
)?),
},
(Some(prefix), ..) => return Err(AddressParseError::UnknownPrefix(prefix.to_owned())),
(None, ..) => return Err(AddressParseError::PrefixAbsent),
})
}
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
pub enum AddressFormat {
#[display("P2PKH")]
P2pkh,
#[display("P2SH")]
P2sh,
#[display("P2WPKH")]
P2wpkh,
#[display("P2WSH")]
P2wsh,
#[display("P2TR")]
P2tr,
#[display("P2W{0}")]
Future(WitnessVersion),
}
impl AddressFormat {
pub fn witness_version(self) -> Option<WitnessVersion> {
match self {
AddressFormat::P2pkh => None,
AddressFormat::P2sh => None,
AddressFormat::P2wpkh | AddressFormat::P2wsh => Some(WitnessVersion::V0),
AddressFormat::P2tr => Some(WitnessVersion::V1),
AddressFormat::Future(ver) => Some(ver),
}
}
}
impl From<Address> for AddressFormat {
fn from(address: Address) -> Self { address.payload.into() }
}
impl From<Payload> for AddressFormat {
fn from(payload: Payload) -> Self {
match payload {
Payload::PubkeyHash(_) => AddressFormat::P2pkh,
Payload::ScriptHash(_) => AddressFormat::P2sh,
Payload::WitnessProgram { version, program }
if version.to_num() == 0 && program.len() == 32 =>
{
AddressFormat::P2wsh
}
Payload::WitnessProgram { version, program }
if version.to_num() == 0 && program.len() == 20 =>
{
AddressFormat::P2wpkh
}
Payload::WitnessProgram { version, .. } if version.to_num() == 1 => AddressFormat::P2tr,
Payload::WitnessProgram { version, .. } => AddressFormat::Future(version),
}
}
}
impl FromStr for AddressFormat {
type Err = AddressParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[allow(clippy::match_str_case_mismatch)]
Ok(match s.to_uppercase().as_str() {
"P2PKH" => AddressFormat::P2pkh,
"P2SH" => AddressFormat::P2sh,
"P2WPKH" => AddressFormat::P2wpkh,
"P2WSH" => AddressFormat::P2wsh,
"P2TR" => AddressFormat::P2tr,
s if s.starts_with("P2W") => AddressFormat::Future(
WitnessVersion::from_str(&s[3..])
.map_err(|_| AddressParseError::WrongWitnessVersion)?,
),
_ => return Err(AddressParseError::UnrecognizedAddressFormat),
})
}
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[derive(StrictEncode, StrictDecode)]
pub enum AddressNetwork {
#[display("mainnet")]
Mainnet,
#[display("testnet")]
Testnet,
#[display("regtest")]
Regtest,
}
impl FromStr for AddressNetwork {
type Err = AddressParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.to_lowercase().as_str() {
"mainnet" => AddressNetwork::Mainnet,
"testnet" => AddressNetwork::Testnet,
"regtest" => AddressNetwork::Regtest,
_ => return Err(AddressParseError::UnrecognizedAddressNetwork),
})
}
}
impl From<Address> for AddressNetwork {
fn from(address: Address) -> Self { address.network.into() }
}
impl From<bitcoin::Network> for AddressNetwork {
fn from(network: bitcoin::Network) -> Self {
match network {
bitcoin::Network::Bitcoin => AddressNetwork::Mainnet,
bitcoin::Network::Testnet => AddressNetwork::Testnet,
bitcoin::Network::Signet => AddressNetwork::Testnet,
bitcoin::Network::Regtest => AddressNetwork::Regtest,
}
}
}
impl AddressNetwork {
fn bitcoin_network(self) -> bitcoin::Network {
match self {
AddressNetwork::Mainnet => bitcoin::Network::Bitcoin,
AddressNetwork::Testnet => bitcoin::Network::Testnet,
AddressNetwork::Regtest => bitcoin::Network::Regtest,
}
}
pub fn is_testnet(self) -> bool { self != Self::Mainnet }
}