|
|
|
@ -33,7 +33,7 @@ use core::fmt;
|
|
|
|
|
use core::marker::PhantomData;
|
|
|
|
|
use core::str::FromStr;
|
|
|
|
|
|
|
|
|
|
use bech32::primitives::hrp::{self, Hrp};
|
|
|
|
|
use bech32::primitives::hrp::Hrp;
|
|
|
|
|
use hashes::{sha256, Hash, HashEngine};
|
|
|
|
|
use secp256k1::{Secp256k1, Verification, XOnlyPublicKey};
|
|
|
|
|
|
|
|
|
@ -44,17 +44,16 @@ use crate::blockdata::constants::{
|
|
|
|
|
};
|
|
|
|
|
use crate::blockdata::script::witness_program::WitnessProgram;
|
|
|
|
|
use crate::blockdata::script::witness_version::WitnessVersion;
|
|
|
|
|
use crate::blockdata::script::{self, Script, ScriptBuf, ScriptHash};
|
|
|
|
|
use crate::crypto::key::{PubkeyHash, PublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey};
|
|
|
|
|
use crate::blockdata::script::{self, PushBytesBuf, Script, ScriptBuf, ScriptHash};
|
|
|
|
|
use crate::crypto::key::{self, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey};
|
|
|
|
|
use crate::network::Network;
|
|
|
|
|
use crate::prelude::*;
|
|
|
|
|
use crate::script::PushBytesBuf;
|
|
|
|
|
use crate::taproot::TapNodeHash;
|
|
|
|
|
|
|
|
|
|
#[rustfmt::skip] // Keep public re-exports separate.
|
|
|
|
|
#[doc(inline)]
|
|
|
|
|
pub use self::{
|
|
|
|
|
error::{Error, ParseError, UnknownAddressTypeError},
|
|
|
|
|
error::{Error, ParseError, UnknownAddressTypeError, UnknownHrpError},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// The different types of addresses.
|
|
|
|
@ -99,61 +98,6 @@ impl FromStr for AddressType {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The method used to produce an address.
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
|
enum Payload {
|
|
|
|
|
/// P2PKH address.
|
|
|
|
|
PubkeyHash(PubkeyHash),
|
|
|
|
|
/// P2SH address.
|
|
|
|
|
ScriptHash(ScriptHash),
|
|
|
|
|
/// Segwit address.
|
|
|
|
|
WitnessProgram(WitnessProgram),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A utility struct to encode an address payload with the given parameters.
|
|
|
|
|
/// This is a low-level utility struct. Consider using `Address` instead.
|
|
|
|
|
struct AddressEncoding<'a> {
|
|
|
|
|
/// The address payload to encode.
|
|
|
|
|
payload: &'a Payload,
|
|
|
|
|
/// base58 version byte for p2pkh payloads (e.g. 0x00 for "1..." addresses).
|
|
|
|
|
p2pkh_prefix: u8,
|
|
|
|
|
/// base58 version byte for p2sh payloads (e.g. 0x05 for "3..." addresses).
|
|
|
|
|
p2sh_prefix: u8,
|
|
|
|
|
/// The bech32 human-readable part.
|
|
|
|
|
hrp: Hrp,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Formats bech32 as upper case if alternate formatting is chosen (`{:#}`).
|
|
|
|
|
impl<'a> fmt::Display for AddressEncoding<'a> {
|
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
match self.payload {
|
|
|
|
|
Payload::PubkeyHash(hash) => {
|
|
|
|
|
let mut prefixed = [0; 21];
|
|
|
|
|
prefixed[0] = self.p2pkh_prefix;
|
|
|
|
|
prefixed[1..].copy_from_slice(&hash[..]);
|
|
|
|
|
base58::encode_check_to_fmt(fmt, &prefixed[..])
|
|
|
|
|
}
|
|
|
|
|
Payload::ScriptHash(hash) => {
|
|
|
|
|
let mut prefixed = [0; 21];
|
|
|
|
|
prefixed[0] = self.p2sh_prefix;
|
|
|
|
|
prefixed[1..].copy_from_slice(&hash[..]);
|
|
|
|
|
base58::encode_check_to_fmt(fmt, &prefixed[..])
|
|
|
|
|
}
|
|
|
|
|
Payload::WitnessProgram(witness_program) => {
|
|
|
|
|
let hrp = &self.hrp;
|
|
|
|
|
let version = witness_program.version().to_fe();
|
|
|
|
|
let program = witness_program.program().as_bytes();
|
|
|
|
|
|
|
|
|
|
if fmt.alternate() {
|
|
|
|
|
bech32::segwit::encode_upper_to_fmt_unchecked(fmt, hrp, version, program)
|
|
|
|
|
} else {
|
|
|
|
|
bech32::segwit::encode_lower_to_fmt_unchecked(fmt, hrp, version, program)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mod sealed {
|
|
|
|
|
pub trait NetworkValidation {}
|
|
|
|
|
impl NetworkValidation for super::NetworkChecked {}
|
|
|
|
@ -189,10 +133,148 @@ impl NetworkValidation for NetworkUnchecked {
|
|
|
|
|
/// An `Address` is composed of a payload and a network. This struct represents the inner
|
|
|
|
|
/// representation of an address without the network validation tag, which is used to ensure that
|
|
|
|
|
/// addresses are used only on the appropriate network.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
|
struct AddressInner {
|
|
|
|
|
payload: Payload,
|
|
|
|
|
network: Network,
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
|
enum AddressInner {
|
|
|
|
|
P2pkh { hash: PubkeyHash, prefix: LegacyP2pkhPrefix },
|
|
|
|
|
P2sh { hash: ScriptHash, prefix: LegacyP2shPrefix },
|
|
|
|
|
Segwit { program: WitnessProgram, hrp: KnownHrp },
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Formats bech32 as upper case if alternate formatting is chosen (`{:#}`).
|
|
|
|
|
impl fmt::Display for AddressInner {
|
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
use AddressInner::*;
|
|
|
|
|
match self {
|
|
|
|
|
P2pkh { hash, prefix } => {
|
|
|
|
|
let mut prefixed = [0; 21];
|
|
|
|
|
prefixed[0] = prefix.to_u8();
|
|
|
|
|
prefixed[1..].copy_from_slice(&hash[..]);
|
|
|
|
|
base58::encode_check_to_fmt(fmt, &prefixed[..])
|
|
|
|
|
}
|
|
|
|
|
P2sh { hash, prefix } => {
|
|
|
|
|
let mut prefixed = [0; 21];
|
|
|
|
|
prefixed[0] = prefix.to_u8();
|
|
|
|
|
prefixed[1..].copy_from_slice(&hash[..]);
|
|
|
|
|
base58::encode_check_to_fmt(fmt, &prefixed[..])
|
|
|
|
|
}
|
|
|
|
|
Segwit { program, hrp } => {
|
|
|
|
|
let hrp = hrp.to_hrp();
|
|
|
|
|
let version = program.version().to_fe();
|
|
|
|
|
let program = program.program().as_ref();
|
|
|
|
|
|
|
|
|
|
if fmt.alternate() {
|
|
|
|
|
bech32::segwit::encode_upper_to_fmt_unchecked(fmt, &hrp, version, program)
|
|
|
|
|
} else {
|
|
|
|
|
bech32::segwit::encode_lower_to_fmt_unchecked(fmt, &hrp, version, program)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Prefix byte used for legacy P2PKH addresses.
|
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
|
enum LegacyP2pkhPrefix {
|
|
|
|
|
/// Prefix used for legacy addresses on the main Bitcoin network.
|
|
|
|
|
Mainnet,
|
|
|
|
|
/// Prefix used for legacy addresses on all other test networks (testnet, signet, regtest).
|
|
|
|
|
AllTestnets,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LegacyP2pkhPrefix {
|
|
|
|
|
/// Creates a legacy prefix from the associated `network`.
|
|
|
|
|
fn from_network(network: Network) -> Self {
|
|
|
|
|
use Network::*;
|
|
|
|
|
|
|
|
|
|
match network {
|
|
|
|
|
Bitcoin => Self::Mainnet,
|
|
|
|
|
Signet | Testnet | Regtest => Self::AllTestnets,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Converts this prefix enum to the respective byte value.
|
|
|
|
|
fn to_u8(self) -> u8 {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Mainnet => PUBKEY_ADDRESS_PREFIX_MAIN,
|
|
|
|
|
Self::AllTestnets => PUBKEY_ADDRESS_PREFIX_TEST,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Prefix byte used for legacy P2SH addresses.
|
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
|
enum LegacyP2shPrefix {
|
|
|
|
|
/// Prefix used for legacy addresses on the main Bitcoin network.
|
|
|
|
|
Mainnet,
|
|
|
|
|
/// Prefix used for legacy addresses on all other test networks (testnet, signet, regtest).
|
|
|
|
|
AllTestnets,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LegacyP2shPrefix {
|
|
|
|
|
/// Creates a legacy prefix from the associated `network`.
|
|
|
|
|
fn from_network(network: Network) -> Self {
|
|
|
|
|
use Network::*;
|
|
|
|
|
|
|
|
|
|
match network {
|
|
|
|
|
Bitcoin => Self::Mainnet,
|
|
|
|
|
Signet | Testnet | Regtest => Self::AllTestnets,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Converts this prefix enum to the respective byte value.
|
|
|
|
|
fn to_u8(self) -> u8 {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Mainnet => SCRIPT_ADDRESS_PREFIX_MAIN,
|
|
|
|
|
Self::AllTestnets => SCRIPT_ADDRESS_PREFIX_TEST,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Known bech32 human-readable parts.
|
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
|
#[non_exhaustive]
|
|
|
|
|
enum KnownHrp {
|
|
|
|
|
/// The main Bitcoin network.
|
|
|
|
|
Mainnet,
|
|
|
|
|
/// The test networks, testnet and signet.
|
|
|
|
|
Testnets,
|
|
|
|
|
/// The regtest network.
|
|
|
|
|
Regtest,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl KnownHrp {
|
|
|
|
|
/// Creates a `KnownHrp` from `network`.
|
|
|
|
|
fn from_network(network: Network) -> Self {
|
|
|
|
|
use Network::*;
|
|
|
|
|
|
|
|
|
|
match network {
|
|
|
|
|
Bitcoin => Self::Mainnet,
|
|
|
|
|
Testnet | Signet => Self::Testnets,
|
|
|
|
|
Regtest => Self::Regtest,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates a `KnownHrp` from a [`bech32::Hrp`].
|
|
|
|
|
fn from_hrp(hrp: Hrp) -> Result<Self, UnknownHrpError> {
|
|
|
|
|
if hrp == bech32::hrp::BC {
|
|
|
|
|
Ok(Self::Mainnet)
|
|
|
|
|
} else if hrp.is_valid_on_testnet() || hrp.is_valid_on_signet() {
|
|
|
|
|
Ok(Self::Testnets)
|
|
|
|
|
} else if hrp == bech32::hrp::BCRT {
|
|
|
|
|
Ok(Self::Regtest)
|
|
|
|
|
} else {
|
|
|
|
|
Err(UnknownHrpError(hrp.to_lowercase()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Converts, infallibly a known HRP to a [`bech32::Hrp`].
|
|
|
|
|
fn to_hrp(self) -> Hrp {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Mainnet => bech32::hrp::BC,
|
|
|
|
|
Self::Testnets => bech32::hrp::TB,
|
|
|
|
|
Self::Regtest => bech32::hrp::BCRT,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A Bitcoin address.
|
|
|
|
@ -317,79 +399,15 @@ impl<N: NetworkValidation> serde::Serialize for Address<N> {
|
|
|
|
|
/// Methods on [`Address`] that can be called on both `Address<NetworkChecked>` and
|
|
|
|
|
/// `Address<NetworkUnchecked>`.
|
|
|
|
|
impl<V: NetworkValidation> Address<V> {
|
|
|
|
|
/// Returns a reference to the payload of this address.
|
|
|
|
|
fn payload(&self) -> &Payload { &self.0.payload }
|
|
|
|
|
|
|
|
|
|
/// Returns a reference to the network of this address.
|
|
|
|
|
pub fn network(&self) -> &Network { &self.0.network }
|
|
|
|
|
|
|
|
|
|
/// Returns a reference to the unchecked address, which is dangerous to use if the address
|
|
|
|
|
/// is invalid in the context of `NetworkUnchecked`.
|
|
|
|
|
pub fn as_unchecked(&self) -> &Address<NetworkUnchecked> {
|
|
|
|
|
unsafe { &*(self as *const Address<V> as *const Address<NetworkUnchecked>) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Extracts and returns the network and payload components of the `Address`.
|
|
|
|
|
fn into_parts(self) -> (Network, Payload) {
|
|
|
|
|
let AddressInner { payload, network } = self.0;
|
|
|
|
|
(network, payload)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the address type of the address.
|
|
|
|
|
///
|
|
|
|
|
/// This method is publicly available as [`address_type`](Address<NetworkChecked>::address_type)
|
|
|
|
|
/// on `Address<NetworkChecked>` but internally can be called on `Address<NetworkUnchecked>` as
|
|
|
|
|
/// `address_type_internal`.
|
|
|
|
|
///
|
|
|
|
|
/// # Returns
|
|
|
|
|
/// None if unknown, non-standard or related to the future witness version.
|
|
|
|
|
fn address_type_internal(&self) -> Option<AddressType> {
|
|
|
|
|
match self.payload() {
|
|
|
|
|
Payload::PubkeyHash(_) => Some(AddressType::P2pkh),
|
|
|
|
|
Payload::ScriptHash(_) => Some(AddressType::P2sh),
|
|
|
|
|
Payload::WitnessProgram(ref prog) => {
|
|
|
|
|
// BIP-141 p2wpkh or p2wsh addresses.
|
|
|
|
|
match prog.version() {
|
|
|
|
|
WitnessVersion::V0 => match prog.program().len() {
|
|
|
|
|
20 => Some(AddressType::P2wpkh),
|
|
|
|
|
32 => Some(AddressType::P2wsh),
|
|
|
|
|
_ => unreachable!(
|
|
|
|
|
"Address creation invariant violation: invalid program length"
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
WitnessVersion::V1 if prog.program().len() == 32 => Some(AddressType::P2tr),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Format the address for the usage by `Debug` and `Display` implementations.
|
|
|
|
|
fn fmt_internal(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
let p2pkh_prefix = match self.network() {
|
|
|
|
|
Network::Bitcoin => PUBKEY_ADDRESS_PREFIX_MAIN,
|
|
|
|
|
Network::Testnet | Network::Signet | Network::Regtest => PUBKEY_ADDRESS_PREFIX_TEST,
|
|
|
|
|
};
|
|
|
|
|
let p2sh_prefix = match self.network() {
|
|
|
|
|
Network::Bitcoin => SCRIPT_ADDRESS_PREFIX_MAIN,
|
|
|
|
|
Network::Testnet | Network::Signet | Network::Regtest => SCRIPT_ADDRESS_PREFIX_TEST,
|
|
|
|
|
};
|
|
|
|
|
let hrp = match self.network() {
|
|
|
|
|
Network::Bitcoin => hrp::BC,
|
|
|
|
|
Network::Testnet | Network::Signet => hrp::TB,
|
|
|
|
|
Network::Regtest => hrp::BCRT,
|
|
|
|
|
};
|
|
|
|
|
let encoding = AddressEncoding { payload: self.payload(), p2pkh_prefix, p2sh_prefix, hrp };
|
|
|
|
|
|
|
|
|
|
use fmt::Display;
|
|
|
|
|
|
|
|
|
|
encoding.fmt(fmt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create new address from given components, infering the network validation
|
|
|
|
|
/// marker type of the address.
|
|
|
|
|
fn new(network: Network, payload: Payload) -> Self {
|
|
|
|
|
Self(AddressInner { network, payload }, PhantomData)
|
|
|
|
|
fmt::Display::fmt(&self.0, fmt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -399,9 +417,10 @@ impl Address {
|
|
|
|
|
///
|
|
|
|
|
/// This is the preferred non-witness type address.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn p2pkh(pk: &PublicKey, network: Network) -> Address {
|
|
|
|
|
let payload = Payload::PubkeyHash(pk.pubkey_hash());
|
|
|
|
|
Address(AddressInner { network, payload }, PhantomData)
|
|
|
|
|
pub fn p2pkh(pk: impl Into<PubkeyHash>, network: Network) -> Address {
|
|
|
|
|
let hash = pk.into();
|
|
|
|
|
let prefix = LegacyP2pkhPrefix::from_network(network);
|
|
|
|
|
Self(AddressInner::P2pkh { hash, prefix }, PhantomData)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates a pay to script hash P2SH address from a script.
|
|
|
|
@ -413,50 +432,52 @@ impl Address {
|
|
|
|
|
if script.len() > MAX_SCRIPT_ELEMENT_SIZE {
|
|
|
|
|
return Err(Error::ExcessiveScriptSize);
|
|
|
|
|
}
|
|
|
|
|
let payload = Payload::ScriptHash(script.script_hash());
|
|
|
|
|
Ok(Address(AddressInner { network, payload }, PhantomData))
|
|
|
|
|
let hash = script.script_hash();
|
|
|
|
|
Ok(Address::p2sh_from_hash(hash, network))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This is intentionally not public so we enforce script length checks.
|
|
|
|
|
fn p2sh_from_hash(hash: ScriptHash, network: Network) -> Address {
|
|
|
|
|
let prefix = LegacyP2shPrefix::from_network(network);
|
|
|
|
|
Self(AddressInner::P2sh { hash, prefix }, PhantomData)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates a witness pay to public key address from a public key.
|
|
|
|
|
///
|
|
|
|
|
/// This is the native segwit address type for an output redeemable with a single signature.
|
|
|
|
|
///
|
|
|
|
|
/// # Errors
|
|
|
|
|
/// Will only return an error if an uncompressed public key is provided.
|
|
|
|
|
pub fn p2wpkh(pk: &PublicKey, network: Network) -> Result<Address, Error> {
|
|
|
|
|
let prog = WitnessProgram::new(WitnessVersion::V0, pk.wpubkey_hash()?)?;
|
|
|
|
|
let payload = Payload::WitnessProgram(prog);
|
|
|
|
|
Ok(Address(AddressInner { network, payload }, PhantomData))
|
|
|
|
|
pub fn p2wpkh(
|
|
|
|
|
pk: &PublicKey,
|
|
|
|
|
network: Network,
|
|
|
|
|
) -> Result<Address, key::UncompressedPubkeyError> {
|
|
|
|
|
let program = WitnessProgram::p2wpkh(pk)?;
|
|
|
|
|
Ok(Address::from_witness_program(program, network))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates a pay to script address that embeds a witness pay to public key.
|
|
|
|
|
///
|
|
|
|
|
/// This is a segwit address type that looks familiar (as p2sh) to legacy clients.
|
|
|
|
|
///
|
|
|
|
|
/// # Errors
|
|
|
|
|
/// Will only return an Error if an uncompressed public key is provided.
|
|
|
|
|
pub fn p2shwpkh(pk: &PublicKey, network: Network) -> Result<Address, Error> {
|
|
|
|
|
pub fn p2shwpkh(
|
|
|
|
|
pk: &PublicKey,
|
|
|
|
|
network: Network,
|
|
|
|
|
) -> Result<Address, key::UncompressedPubkeyError> {
|
|
|
|
|
let builder = script::Builder::new().push_int(0).push_slice(pk.wpubkey_hash()?);
|
|
|
|
|
|
|
|
|
|
let payload = Payload::ScriptHash(builder.into_script().script_hash());
|
|
|
|
|
Ok(Address(AddressInner { network, payload }, PhantomData))
|
|
|
|
|
let script_hash = builder.as_script().script_hash();
|
|
|
|
|
Ok(Address::p2sh_from_hash(script_hash, network))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates a witness pay to script hash address.
|
|
|
|
|
pub fn p2wsh(script: &Script, network: Network) -> Address {
|
|
|
|
|
let prog = WitnessProgram::new(WitnessVersion::V0, script.wscript_hash())
|
|
|
|
|
.expect("wscript_hash has len 32 compatible with segwitv0");
|
|
|
|
|
let payload = Payload::WitnessProgram(prog);
|
|
|
|
|
Address(AddressInner { network, payload }, PhantomData)
|
|
|
|
|
let program = WitnessProgram::p2wsh(script);
|
|
|
|
|
Address::from_witness_program(program, network)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates a pay to script address that embeds a witness pay to script hash address.
|
|
|
|
|
///
|
|
|
|
|
/// This is a segwit address type that looks familiar (as p2sh) to legacy clients.
|
|
|
|
|
pub fn p2shwsh(script: &Script, network: Network) -> Address {
|
|
|
|
|
let ws = script::Builder::new().push_int(0).push_slice(script.wscript_hash()).into_script();
|
|
|
|
|
let payload = Payload::ScriptHash(ws.script_hash());
|
|
|
|
|
Address(AddressInner { network, payload }, PhantomData)
|
|
|
|
|
let builder = script::Builder::new().push_int(0).push_slice(script.wscript_hash());
|
|
|
|
|
let script_hash = builder.as_script().script_hash();
|
|
|
|
|
Address::p2sh_from_hash(script_hash, network)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates a pay to taproot address from an untweaked key.
|
|
|
|
@ -466,19 +487,14 @@ impl Address {
|
|
|
|
|
merkle_root: Option<TapNodeHash>,
|
|
|
|
|
network: Network,
|
|
|
|
|
) -> Address {
|
|
|
|
|
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
|
|
|
|
|
let prog = WitnessProgram::new(WitnessVersion::V1, output_key.to_inner().serialize())
|
|
|
|
|
.expect("taproot output key has len 32 <= 40");
|
|
|
|
|
let payload = Payload::WitnessProgram(prog);
|
|
|
|
|
Address(AddressInner { network, payload }, PhantomData)
|
|
|
|
|
let program = WitnessProgram::p2tr(secp, internal_key, merkle_root);
|
|
|
|
|
Address::from_witness_program(program, network)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates a pay to taproot address from a pre-tweaked output key.
|
|
|
|
|
pub fn p2tr_tweaked(output_key: TweakedPublicKey, network: Network) -> Address {
|
|
|
|
|
let prog = WitnessProgram::new(WitnessVersion::V1, output_key.to_inner().serialize())
|
|
|
|
|
.expect("taproot output key has len 32 <= 40");
|
|
|
|
|
let payload = Payload::WitnessProgram(prog);
|
|
|
|
|
Address(AddressInner { network, payload }, PhantomData)
|
|
|
|
|
let program = WitnessProgram::p2tr_tweaked(output_key);
|
|
|
|
|
Address::from_witness_program(program, network)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates an address from an arbitrary witness program.
|
|
|
|
@ -486,16 +502,53 @@ impl Address {
|
|
|
|
|
/// This only exists to support future witness versions. If you are doing normal mainnet things
|
|
|
|
|
/// then you likely do not need this constructor.
|
|
|
|
|
pub fn from_witness_program(program: WitnessProgram, network: Network) -> Address {
|
|
|
|
|
let inner = AddressInner { payload: Payload::WitnessProgram(program), network };
|
|
|
|
|
let hrp = KnownHrp::from_network(network);
|
|
|
|
|
let inner = AddressInner::Segwit { program, hrp };
|
|
|
|
|
Address(inner, PhantomData)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the address type of the address.
|
|
|
|
|
///
|
|
|
|
|
/// # Returns
|
|
|
|
|
///
|
|
|
|
|
/// None if unknown, non-standard or related to the future witness version.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn address_type(&self) -> Option<AddressType> { self.address_type_internal() }
|
|
|
|
|
pub fn address_type(&self) -> Option<AddressType> {
|
|
|
|
|
match self.0 {
|
|
|
|
|
AddressInner::P2pkh { .. } => Some(AddressType::P2pkh),
|
|
|
|
|
AddressInner::P2sh { .. } => Some(AddressType::P2sh),
|
|
|
|
|
AddressInner::Segwit { ref program, hrp: _ } =>
|
|
|
|
|
if program.is_p2wpkh() {
|
|
|
|
|
Some(AddressType::P2wpkh)
|
|
|
|
|
} else if program.is_p2wsh() {
|
|
|
|
|
Some(AddressType::P2wsh)
|
|
|
|
|
} else if program.is_p2tr() {
|
|
|
|
|
Some(AddressType::P2tr)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the pubkey hash for this address if this is a P2PKH address.
|
|
|
|
|
pub fn pubkey_hash(&self) -> Option<PubkeyHash> {
|
|
|
|
|
use AddressInner::*;
|
|
|
|
|
|
|
|
|
|
match self.0 {
|
|
|
|
|
P2pkh { ref hash, prefix: _ } => Some(*hash),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the script hash for this address if this is a P2SH address.
|
|
|
|
|
pub fn script_hash(&self) -> Option<ScriptHash> {
|
|
|
|
|
use AddressInner::*;
|
|
|
|
|
|
|
|
|
|
match self.0 {
|
|
|
|
|
P2sh { ref hash, prefix: _ } => Some(*hash),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Checks whether or not the address is following Bitcoin standardness rules when
|
|
|
|
|
/// *spending* from this address. *NOT* to be called by senders.
|
|
|
|
@ -514,32 +567,34 @@ impl Address {
|
|
|
|
|
|
|
|
|
|
/// Constructs an [`Address`] from an output script (`scriptPubkey`).
|
|
|
|
|
pub fn from_script(script: &Script, network: Network) -> Result<Address, Error> {
|
|
|
|
|
let payload = if script.is_p2pkh() {
|
|
|
|
|
if script.is_p2pkh() {
|
|
|
|
|
let bytes = script.as_bytes()[3..23].try_into().expect("statically 20B long");
|
|
|
|
|
Payload::PubkeyHash(PubkeyHash::from_byte_array(bytes))
|
|
|
|
|
let hash = PubkeyHash::from_byte_array(bytes);
|
|
|
|
|
Ok(Address::p2pkh(hash, network))
|
|
|
|
|
} else if script.is_p2sh() {
|
|
|
|
|
let bytes = script.as_bytes()[2..22].try_into().expect("statically 20B long");
|
|
|
|
|
Payload::ScriptHash(ScriptHash::from_byte_array(bytes))
|
|
|
|
|
let hash = ScriptHash::from_byte_array(bytes);
|
|
|
|
|
Ok(Address::p2sh_from_hash(hash, network))
|
|
|
|
|
} else if script.is_witness_program() {
|
|
|
|
|
let opcode = script.first_opcode().expect("witness_version guarantees len() > 4");
|
|
|
|
|
let opcode = script.first_opcode().expect("is_witness_program guarantees len > 4");
|
|
|
|
|
|
|
|
|
|
let witness_program = script.as_bytes()[2..].to_vec();
|
|
|
|
|
|
|
|
|
|
let witness_program =
|
|
|
|
|
WitnessProgram::new(WitnessVersion::try_from(opcode)?, witness_program)?;
|
|
|
|
|
Payload::WitnessProgram(witness_program)
|
|
|
|
|
let buf = PushBytesBuf::try_from(script.as_bytes()[2..].to_vec())
|
|
|
|
|
.expect("is_witness_program guarantees len <= 42 bytes");
|
|
|
|
|
let version = WitnessVersion::try_from(opcode)?;
|
|
|
|
|
let program = WitnessProgram::new(version, buf)?;
|
|
|
|
|
Ok(Address::from_witness_program(program, network))
|
|
|
|
|
} else {
|
|
|
|
|
return Err(Error::UnrecognizedScript);
|
|
|
|
|
};
|
|
|
|
|
Ok(Address(AddressInner { network, payload }, PhantomData))
|
|
|
|
|
Err(Error::UnrecognizedScript)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generates a script pubkey spending to this address.
|
|
|
|
|
pub fn script_pubkey(&self) -> ScriptBuf {
|
|
|
|
|
match self.payload() {
|
|
|
|
|
Payload::PubkeyHash(ref hash) => ScriptBuf::new_p2pkh(hash),
|
|
|
|
|
Payload::ScriptHash(ref hash) => ScriptBuf::new_p2sh(hash),
|
|
|
|
|
Payload::WitnessProgram(ref prog) => ScriptBuf::new_witness_program(prog),
|
|
|
|
|
use AddressInner::*;
|
|
|
|
|
match self.0 {
|
|
|
|
|
P2pkh { ref hash, prefix: _ } => ScriptBuf::new_p2pkh(hash),
|
|
|
|
|
P2sh { ref hash, prefix: _ } => ScriptBuf::new_p2sh(hash),
|
|
|
|
|
Segwit { ref program, hrp: _ } => ScriptBuf::new_witness_program(program),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -579,7 +634,7 @@ impl Address {
|
|
|
|
|
/// given key. For taproot addresses, the supplied key is assumed to be tweaked
|
|
|
|
|
pub fn is_related_to_pubkey(&self, pubkey: &PublicKey) -> bool {
|
|
|
|
|
let pubkey_hash = pubkey.pubkey_hash();
|
|
|
|
|
let payload = self.inner_prog_as_bytes();
|
|
|
|
|
let payload = self.payload_as_bytes();
|
|
|
|
|
let xonly_pubkey = XOnlyPublicKey::from(pubkey.inner);
|
|
|
|
|
|
|
|
|
|
(*pubkey_hash.as_byte_array() == *payload)
|
|
|
|
@ -592,31 +647,38 @@ impl Address {
|
|
|
|
|
/// This will only work for Taproot addresses. The Public Key is
|
|
|
|
|
/// assumed to have already been tweaked.
|
|
|
|
|
pub fn is_related_to_xonly_pubkey(&self, xonly_pubkey: &XOnlyPublicKey) -> bool {
|
|
|
|
|
let payload = self.inner_prog_as_bytes();
|
|
|
|
|
payload == xonly_pubkey.serialize()
|
|
|
|
|
xonly_pubkey.serialize() == *self.payload_as_bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns true if the address creates a particular script
|
|
|
|
|
/// This function doesn't make any allocations.
|
|
|
|
|
pub fn matches_script_pubkey(&self, script_pubkey: &Script) -> bool {
|
|
|
|
|
match &self.payload() {
|
|
|
|
|
Payload::PubkeyHash(ref hash) if script_pubkey.is_p2pkh() =>
|
|
|
|
|
&script_pubkey.as_bytes()[3..23] == <PubkeyHash as AsRef<[u8; 20]>>::as_ref(hash),
|
|
|
|
|
Payload::ScriptHash(ref hash) if script_pubkey.is_p2sh() =>
|
|
|
|
|
&script_pubkey.as_bytes()[2..22] == <ScriptHash as AsRef<[u8; 20]>>::as_ref(hash),
|
|
|
|
|
Payload::WitnessProgram(ref prog) if script_pubkey.is_witness_program() =>
|
|
|
|
|
&script_pubkey.as_bytes()[2..] == prog.program().as_bytes(),
|
|
|
|
|
Payload::PubkeyHash(_) | Payload::ScriptHash(_) | Payload::WitnessProgram(_) => false,
|
|
|
|
|
pub fn matches_script_pubkey(&self, script: &Script) -> bool {
|
|
|
|
|
use AddressInner::*;
|
|
|
|
|
match self.0 {
|
|
|
|
|
P2pkh { ref hash, prefix: _ } if script.is_p2pkh() =>
|
|
|
|
|
&script.as_bytes()[3..23] == <PubkeyHash as AsRef<[u8; 20]>>::as_ref(hash),
|
|
|
|
|
P2sh { ref hash, prefix: _ } if script.is_p2sh() =>
|
|
|
|
|
&script.as_bytes()[2..22] == <ScriptHash as AsRef<[u8; 20]>>::as_ref(hash),
|
|
|
|
|
Segwit { ref program, hrp: _ } if script.is_witness_program() =>
|
|
|
|
|
&script.as_bytes()[2..] == program.program().as_bytes(),
|
|
|
|
|
P2pkh { .. } | P2sh { .. } | Segwit { .. } => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns a byte slice of the inner program of the payload. If the payload
|
|
|
|
|
/// is a script hash or pubkey hash, a reference to the hash is returned.
|
|
|
|
|
fn inner_prog_as_bytes(&self) -> &[u8] {
|
|
|
|
|
match &self.payload() {
|
|
|
|
|
Payload::ScriptHash(hash) => hash.as_ref(),
|
|
|
|
|
Payload::PubkeyHash(hash) => hash.as_ref(),
|
|
|
|
|
Payload::WitnessProgram(prog) => prog.program().as_bytes(),
|
|
|
|
|
/// Returns the "payload" for this address.
|
|
|
|
|
///
|
|
|
|
|
/// The "payload" is the useful stuff excluding serialization prefix, the exact payload is
|
|
|
|
|
/// dependent on the inner address:
|
|
|
|
|
///
|
|
|
|
|
/// - For p2sh, the payload is the script hash.
|
|
|
|
|
/// - For p2pkh, the payload is the pubkey hash.
|
|
|
|
|
/// - For segwit addresses, the payload is the witness program.
|
|
|
|
|
fn payload_as_bytes(&self) -> &[u8] {
|
|
|
|
|
use AddressInner::*;
|
|
|
|
|
match self.0 {
|
|
|
|
|
P2sh { ref hash, prefix: _ } => hash.as_ref(),
|
|
|
|
|
P2pkh { ref hash, prefix: _ } => hash.as_ref(),
|
|
|
|
|
Segwit { ref program, hrp: _ } => program.program().as_bytes(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -650,16 +712,11 @@ impl Address<NetworkUnchecked> {
|
|
|
|
|
/// assert_eq!(address.is_valid_for_network(Network::Testnet), false);
|
|
|
|
|
/// ```
|
|
|
|
|
pub fn is_valid_for_network(&self, network: Network) -> bool {
|
|
|
|
|
let is_legacy = matches!(
|
|
|
|
|
self.address_type_internal(),
|
|
|
|
|
Some(AddressType::P2pkh) | Some(AddressType::P2sh)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
match (self.network(), network) {
|
|
|
|
|
(a, b) if *a == b => true,
|
|
|
|
|
(Network::Bitcoin, _) | (_, Network::Bitcoin) => false,
|
|
|
|
|
(Network::Regtest, _) | (_, Network::Regtest) if !is_legacy => false,
|
|
|
|
|
(Network::Testnet, _) | (Network::Regtest, _) | (Network::Signet, _) => true,
|
|
|
|
|
use AddressInner::*;
|
|
|
|
|
match self.0 {
|
|
|
|
|
P2pkh { hash: _, ref prefix } => *prefix == LegacyP2pkhPrefix::from_network(network),
|
|
|
|
|
P2sh { hash: _, ref prefix } => *prefix == LegacyP2shPrefix::from_network(network),
|
|
|
|
|
Segwit { program: _, ref hrp } => *hrp == KnownHrp::from_network(network),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -672,7 +729,7 @@ impl Address<NetworkUnchecked> {
|
|
|
|
|
if self.is_valid_for_network(required) {
|
|
|
|
|
Ok(self.assume_checked())
|
|
|
|
|
} else {
|
|
|
|
|
Err(Error::NetworkValidation { found: *self.network(), required, address: self })
|
|
|
|
|
Err(Error::NetworkValidation { required, address: self })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -684,22 +741,17 @@ impl Address<NetworkUnchecked> {
|
|
|
|
|
/// on [`Address`].
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn assume_checked(self) -> Address {
|
|
|
|
|
let (network, payload) = self.into_parts();
|
|
|
|
|
Address::new(network, payload)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
use AddressInner::*;
|
|
|
|
|
|
|
|
|
|
// For NetworkUnchecked , it compare Addresses and if network and payload matches then return true.
|
|
|
|
|
impl PartialEq<Address<NetworkUnchecked>> for Address {
|
|
|
|
|
fn eq(&self, other: &Address<NetworkUnchecked>) -> bool {
|
|
|
|
|
self.network() == other.network() && self.payload() == other.payload()
|
|
|
|
|
let inner = match self.0 {
|
|
|
|
|
P2pkh { hash, prefix } => P2pkh { hash, prefix },
|
|
|
|
|
P2sh { hash, prefix } => P2sh { hash, prefix },
|
|
|
|
|
Segwit { program, hrp } => Segwit { program, hrp },
|
|
|
|
|
};
|
|
|
|
|
Address(inner, PhantomData)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialEq<Address> for Address<NetworkUnchecked> {
|
|
|
|
|
fn eq(&self, other: &Address) -> bool { other == self }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Address> for script::ScriptBuf {
|
|
|
|
|
fn from(a: Address) -> Self { a.script_pubkey() }
|
|
|
|
|
}
|
|
|
|
@ -722,41 +774,24 @@ impl<V: NetworkValidation> fmt::Debug for Address<V> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Extracts the bech32 prefix.
|
|
|
|
|
///
|
|
|
|
|
/// # Returns
|
|
|
|
|
/// The input slice if 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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Address can be parsed only with `NetworkUnchecked`.
|
|
|
|
|
impl FromStr for Address<NetworkUnchecked> {
|
|
|
|
|
type Err = ParseError;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
// try bech32
|
|
|
|
|
let bech32_network = match find_bech32_prefix(s) {
|
|
|
|
|
// note that upper or lowercase is allowed but NOT mixed case
|
|
|
|
|
"bc" | "BC" => Some(Network::Bitcoin),
|
|
|
|
|
"tb" | "TB" => Some(Network::Testnet), // this may also be signet
|
|
|
|
|
"bcrt" | "BCRT" => Some(Network::Regtest),
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
if let Some(network) = bech32_network {
|
|
|
|
|
let (_hrp, version, data) = bech32::segwit::decode(s)?;
|
|
|
|
|
let version = WitnessVersion::try_from(version).expect("we know this is in range 0-16");
|
|
|
|
|
let program = PushBytesBuf::try_from(data).expect("decode() guarantees valid length");
|
|
|
|
|
let witness_program = WitnessProgram::new(version, program)?;
|
|
|
|
|
fn from_str(s: &str) -> Result<Address<NetworkUnchecked>, ParseError> {
|
|
|
|
|
if let Ok((hrp, witness_version, data)) = bech32::segwit::decode(s) {
|
|
|
|
|
let version = WitnessVersion::try_from(witness_version)?;
|
|
|
|
|
let buf = PushBytesBuf::try_from(data).expect("bech32 guarantees valid program length");
|
|
|
|
|
let program = WitnessProgram::new(version, buf)
|
|
|
|
|
.expect("bech32 guarantees valid program length for witness");
|
|
|
|
|
|
|
|
|
|
return Ok(Address::new(network, Payload::WitnessProgram(witness_program)));
|
|
|
|
|
let hrp = KnownHrp::from_hrp(hrp)?;
|
|
|
|
|
let inner = AddressInner::Segwit { program, hrp };
|
|
|
|
|
return Ok(Address(inner, PhantomData));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Base58
|
|
|
|
|
// If segwit decoding fails, assume its a legacy address.
|
|
|
|
|
|
|
|
|
|
if s.len() > 50 {
|
|
|
|
|
return Err(ParseError::Base58(base58::Error::InvalidLength(s.len() * 11 / 15)));
|
|
|
|
|
}
|
|
|
|
@ -765,19 +800,30 @@ impl FromStr for Address<NetworkUnchecked> {
|
|
|
|
|
return Err(ParseError::Base58(base58::Error::InvalidLength(data.len())));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let (network, payload) = match data[0] {
|
|
|
|
|
PUBKEY_ADDRESS_PREFIX_MAIN =>
|
|
|
|
|
(Network::Bitcoin, Payload::PubkeyHash(PubkeyHash::from_slice(&data[1..]).unwrap())),
|
|
|
|
|
SCRIPT_ADDRESS_PREFIX_MAIN =>
|
|
|
|
|
(Network::Bitcoin, Payload::ScriptHash(ScriptHash::from_slice(&data[1..]).unwrap())),
|
|
|
|
|
PUBKEY_ADDRESS_PREFIX_TEST =>
|
|
|
|
|
(Network::Testnet, Payload::PubkeyHash(PubkeyHash::from_slice(&data[1..]).unwrap())),
|
|
|
|
|
SCRIPT_ADDRESS_PREFIX_TEST =>
|
|
|
|
|
(Network::Testnet, Payload::ScriptHash(ScriptHash::from_slice(&data[1..]).unwrap())),
|
|
|
|
|
let (prefix, data) = data.split_first().expect("length checked above");
|
|
|
|
|
let data: [u8; 20] = data.try_into().expect("length checked above");
|
|
|
|
|
|
|
|
|
|
let inner = match *prefix {
|
|
|
|
|
PUBKEY_ADDRESS_PREFIX_MAIN => {
|
|
|
|
|
let hash = PubkeyHash::from_byte_array(data);
|
|
|
|
|
AddressInner::P2pkh { hash, prefix: LegacyP2pkhPrefix::Mainnet }
|
|
|
|
|
}
|
|
|
|
|
PUBKEY_ADDRESS_PREFIX_TEST => {
|
|
|
|
|
let hash = PubkeyHash::from_byte_array(data);
|
|
|
|
|
AddressInner::P2pkh { hash, prefix: LegacyP2pkhPrefix::AllTestnets }
|
|
|
|
|
}
|
|
|
|
|
SCRIPT_ADDRESS_PREFIX_MAIN => {
|
|
|
|
|
let hash = ScriptHash::from_byte_array(data);
|
|
|
|
|
AddressInner::P2sh { hash, prefix: LegacyP2shPrefix::Mainnet }
|
|
|
|
|
}
|
|
|
|
|
SCRIPT_ADDRESS_PREFIX_TEST => {
|
|
|
|
|
let hash = ScriptHash::from_byte_array(data);
|
|
|
|
|
AddressInner::P2sh { hash, prefix: LegacyP2shPrefix::AllTestnets }
|
|
|
|
|
}
|
|
|
|
|
x => return Err(ParseError::Base58(base58::Error::InvalidAddressVersion(x))),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(Address::new(network, payload))
|
|
|
|
|
Ok(Address(inner, PhantomData))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -800,7 +846,7 @@ mod tests {
|
|
|
|
|
use crate::crypto::key::{PublicKey, UncompressedPubkeyError};
|
|
|
|
|
use crate::network::Network::{Bitcoin, Testnet};
|
|
|
|
|
|
|
|
|
|
fn roundtrips(addr: &Address) {
|
|
|
|
|
fn roundtrips(addr: &Address, network: Network) {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
Address::from_str(&addr.to_string()).unwrap().assume_checked(),
|
|
|
|
|
*addr,
|
|
|
|
@ -808,8 +854,9 @@ mod tests {
|
|
|
|
|
addr,
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
Address::from_script(&addr.script_pubkey(), *addr.network()).as_ref(),
|
|
|
|
|
Ok(addr),
|
|
|
|
|
Address::from_script(&addr.script_pubkey(), network)
|
|
|
|
|
.expect("failed to create inner address from script_pubkey"),
|
|
|
|
|
*addr,
|
|
|
|
|
"script round-trip failed for {}",
|
|
|
|
|
addr,
|
|
|
|
|
);
|
|
|
|
@ -825,10 +872,8 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_p2pkh_address_58() {
|
|
|
|
|
let addr = Address::new(
|
|
|
|
|
Bitcoin,
|
|
|
|
|
Payload::PubkeyHash("162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse().unwrap()),
|
|
|
|
|
);
|
|
|
|
|
let hash = "162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse::<PubkeyHash>().unwrap();
|
|
|
|
|
let addr = Address::p2pkh(hash, Bitcoin);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
addr.script_pubkey(),
|
|
|
|
@ -836,30 +881,28 @@ mod tests {
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(&addr.to_string(), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM");
|
|
|
|
|
assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
|
|
|
|
|
roundtrips(&addr);
|
|
|
|
|
roundtrips(&addr, Bitcoin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_p2pkh_from_key() {
|
|
|
|
|
let key = "048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183".parse::<PublicKey>().unwrap();
|
|
|
|
|
let addr = Address::p2pkh(&key, Bitcoin);
|
|
|
|
|
let addr = Address::p2pkh(key, Bitcoin);
|
|
|
|
|
assert_eq!(&addr.to_string(), "1QJVDzdqb1VpbDK7uDeyVXy9mR27CJiyhY");
|
|
|
|
|
|
|
|
|
|
let key = "03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f"
|
|
|
|
|
.parse::<PublicKey>()
|
|
|
|
|
.unwrap();
|
|
|
|
|
let addr = Address::p2pkh(&key, Testnet);
|
|
|
|
|
let addr = Address::p2pkh(key, Testnet);
|
|
|
|
|
assert_eq!(&addr.to_string(), "mqkhEMH6NCeYjFybv7pvFC22MFeaNT9AQC");
|
|
|
|
|
assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
|
|
|
|
|
roundtrips(&addr);
|
|
|
|
|
roundtrips(&addr, Testnet);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_p2sh_address_58() {
|
|
|
|
|
let addr = Address::new(
|
|
|
|
|
Bitcoin,
|
|
|
|
|
Payload::ScriptHash("162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse().unwrap()),
|
|
|
|
|
);
|
|
|
|
|
let hash = "162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse::<ScriptHash>().unwrap();
|
|
|
|
|
let addr = Address::p2sh_from_hash(hash, Bitcoin);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
addr.script_pubkey(),
|
|
|
|
@ -867,7 +910,7 @@ mod tests {
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(&addr.to_string(), "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k");
|
|
|
|
|
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
|
|
|
|
roundtrips(&addr);
|
|
|
|
|
roundtrips(&addr, Bitcoin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
@ -876,7 +919,7 @@ mod tests {
|
|
|
|
|
let addr = Address::p2sh(&script, Testnet).unwrap();
|
|
|
|
|
assert_eq!(&addr.to_string(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr");
|
|
|
|
|
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
|
|
|
|
roundtrips(&addr);
|
|
|
|
|
roundtrips(&addr, Testnet);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
@ -894,11 +937,11 @@ mod tests {
|
|
|
|
|
let addr = Address::p2wpkh(&key, Bitcoin).unwrap();
|
|
|
|
|
assert_eq!(&addr.to_string(), "bc1qvzvkjn4q3nszqxrv3nraga2r822xjty3ykvkuw");
|
|
|
|
|
assert_eq!(addr.address_type(), Some(AddressType::P2wpkh));
|
|
|
|
|
roundtrips(&addr);
|
|
|
|
|
roundtrips(&addr, Bitcoin);
|
|
|
|
|
|
|
|
|
|
// Test uncompressed pubkey
|
|
|
|
|
key.compressed = false;
|
|
|
|
|
assert_eq!(Address::p2wpkh(&key, Bitcoin).unwrap_err(), UncompressedPubkeyError.into());
|
|
|
|
|
assert_eq!(Address::p2wpkh(&key, Bitcoin).unwrap_err(), UncompressedPubkeyError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
@ -911,7 +954,7 @@ mod tests {
|
|
|
|
|
"bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(addr.address_type(), Some(AddressType::P2wsh));
|
|
|
|
|
roundtrips(&addr);
|
|
|
|
|
roundtrips(&addr, Bitcoin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
@ -923,11 +966,11 @@ mod tests {
|
|
|
|
|
let addr = Address::p2shwpkh(&key, Bitcoin).unwrap();
|
|
|
|
|
assert_eq!(&addr.to_string(), "3QBRmWNqqBGme9er7fMkGqtZtp4gjMFxhE");
|
|
|
|
|
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
|
|
|
|
roundtrips(&addr);
|
|
|
|
|
roundtrips(&addr, Bitcoin);
|
|
|
|
|
|
|
|
|
|
// Test uncompressed pubkey
|
|
|
|
|
key.compressed = false;
|
|
|
|
|
assert_eq!(Address::p2wpkh(&key, Bitcoin).unwrap_err(), UncompressedPubkeyError.into());
|
|
|
|
|
assert_eq!(Address::p2wpkh(&key, Bitcoin).unwrap_err(), UncompressedPubkeyError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
@ -937,18 +980,19 @@ mod tests {
|
|
|
|
|
let addr = Address::p2shwsh(&script, Bitcoin);
|
|
|
|
|
assert_eq!(&addr.to_string(), "36EqgNnsWW94SreZgBWc1ANC6wpFZwirHr");
|
|
|
|
|
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
|
|
|
|
roundtrips(&addr);
|
|
|
|
|
roundtrips(&addr, Bitcoin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_non_existent_segwit_version() {
|
|
|
|
|
// 40-byte program
|
|
|
|
|
let program = hex!(
|
|
|
|
|
let program = PushBytesBuf::from(hex!(
|
|
|
|
|
"654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4"
|
|
|
|
|
);
|
|
|
|
|
let witness_prog = WitnessProgram::new(WitnessVersion::V13, program.to_vec()).unwrap();
|
|
|
|
|
let addr = Address::new(Bitcoin, Payload::WitnessProgram(witness_prog));
|
|
|
|
|
roundtrips(&addr);
|
|
|
|
|
));
|
|
|
|
|
let program = WitnessProgram::new(WitnessVersion::V13, program).expect("valid program");
|
|
|
|
|
|
|
|
|
|
let addr = Address::from_witness_program(program, Bitcoin);
|
|
|
|
|
roundtrips(&addr, Bitcoin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
@ -1106,57 +1150,6 @@ mod tests {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_valid_networks() {
|
|
|
|
|
let legacy_payload = &[
|
|
|
|
|
Payload::PubkeyHash(PubkeyHash::all_zeros()),
|
|
|
|
|
Payload::ScriptHash(ScriptHash::all_zeros()),
|
|
|
|
|
];
|
|
|
|
|
let segwit_payload = (0..=16)
|
|
|
|
|
.map(|version| {
|
|
|
|
|
Payload::WitnessProgram(
|
|
|
|
|
WitnessProgram::new(
|
|
|
|
|
WitnessVersion::try_from(version).unwrap(),
|
|
|
|
|
vec![0xab; 32], // Choose 32 to make test case valid for all witness versions(including v0)
|
|
|
|
|
)
|
|
|
|
|
.unwrap(),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
const LEGACY_EQUIVALENCE_CLASSES: &[&[Network]] =
|
|
|
|
|
&[&[Network::Bitcoin], &[Network::Testnet, Network::Regtest, Network::Signet]];
|
|
|
|
|
const SEGWIT_EQUIVALENCE_CLASSES: &[&[Network]] =
|
|
|
|
|
&[&[Network::Bitcoin], &[Network::Regtest], &[Network::Testnet, Network::Signet]];
|
|
|
|
|
|
|
|
|
|
fn test_addr_type(payloads: &[Payload], equivalence_classes: &[&[Network]]) {
|
|
|
|
|
for pl in payloads {
|
|
|
|
|
for addr_net in equivalence_classes.iter().flat_map(|ec| ec.iter()) {
|
|
|
|
|
for valid_net in equivalence_classes
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|ec| ec.contains(addr_net))
|
|
|
|
|
.flat_map(|ec| ec.iter())
|
|
|
|
|
{
|
|
|
|
|
let addr = Address::new(*addr_net, pl.clone());
|
|
|
|
|
assert!(addr.is_valid_for_network(*valid_net));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for invalid_net in equivalence_classes
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|ec| !ec.contains(addr_net))
|
|
|
|
|
.flat_map(|ec| ec.iter())
|
|
|
|
|
{
|
|
|
|
|
let addr = Address::new(*addr_net, pl.clone());
|
|
|
|
|
assert!(!addr.is_valid_for_network(*invalid_net));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test_addr_type(legacy_payload, LEGACY_EQUIVALENCE_CLASSES);
|
|
|
|
|
test_addr_type(&segwit_payload, SEGWIT_EQUIVALENCE_CLASSES);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn p2tr_from_untweaked() {
|
|
|
|
|
//Test case from BIP-086
|
|
|
|
@ -1171,7 +1164,7 @@ mod tests {
|
|
|
|
|
"bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(address.address_type(), Some(AddressType::P2tr));
|
|
|
|
|
roundtrips(&address);
|
|
|
|
|
roundtrips(&address, Bitcoin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|