Merge rust-bitcoin/rust-bitcoin#1832: Remove Network from AddressInner
1ee989a3af
Remove private fmt_internal function (Tobin C. Harding)923ce7402d
Remove Network from AddressInner (Tobin C. Harding)3490433618
Return error from wpubkey_hash (Tobin C. Harding)f7ab253ce4
Remove stale comment (Tobin C. Harding) Pull request description: An `AddressInner` struct (contains `Network` field) is created when parsing address strings however address strings do not map 1:1 to `Network` because signet and testnet use the same bech32 prefix "tb". We can fix this by inlining the `Payload` variants into `AddressInner` and adding prefix enums for legacy addresses and an `Hrp` for bech32 addresses. Fix: #1819 ACKs for top commit: Kixunil: ACK1ee989a3af
apoelstra: ACK1ee989a3af
Tree-SHA512: 1c2749dc929a1e9ad9b9feb01bec5c96b5aec07c6d646d88652deca7abe485907403116e9e29a0ab7dc06223254c4b49a384043284ec0a68fd76f9ab551e9e8a
This commit is contained in:
commit
199c482b26
|
@ -17,8 +17,6 @@ pub enum Error {
|
||||||
WitnessVersion(witness_version::TryFromError),
|
WitnessVersion(witness_version::TryFromError),
|
||||||
/// A witness program error.
|
/// A witness program error.
|
||||||
WitnessProgram(witness_program::Error),
|
WitnessProgram(witness_program::Error),
|
||||||
/// An uncompressed pubkey was used where it is not allowed.
|
|
||||||
UncompressedPubkey,
|
|
||||||
/// Address size more than 520 bytes is not allowed.
|
/// Address size more than 520 bytes is not allowed.
|
||||||
ExcessiveScriptSize,
|
ExcessiveScriptSize,
|
||||||
/// Script is not a p2pkh, p2sh or witness program.
|
/// Script is not a p2pkh, p2sh or witness program.
|
||||||
|
@ -27,11 +25,11 @@ pub enum Error {
|
||||||
NetworkValidation {
|
NetworkValidation {
|
||||||
/// Network that was required.
|
/// Network that was required.
|
||||||
required: Network,
|
required: Network,
|
||||||
/// Network on which the address was found to be valid.
|
|
||||||
found: Network,
|
|
||||||
/// The address itself
|
/// The address itself
|
||||||
address: Address<NetworkUnchecked>,
|
address: Address<NetworkUnchecked>,
|
||||||
},
|
},
|
||||||
|
/// Unknown hrp for current bitcoin networks (in bech32 address).
|
||||||
|
UnknownHrp(UnknownHrpError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
|
@ -41,19 +39,14 @@ impl fmt::Display for Error {
|
||||||
match *self {
|
match *self {
|
||||||
WitnessVersion(ref e) => write_err!(f, "witness version construction error"; e),
|
WitnessVersion(ref e) => write_err!(f, "witness version construction error"; e),
|
||||||
WitnessProgram(ref e) => write_err!(f, "witness program error"; e),
|
WitnessProgram(ref e) => write_err!(f, "witness program error"; e),
|
||||||
UncompressedPubkey =>
|
|
||||||
write!(f, "an uncompressed pubkey was used where it is not allowed"),
|
|
||||||
ExcessiveScriptSize => write!(f, "script size exceed 520 bytes"),
|
ExcessiveScriptSize => write!(f, "script size exceed 520 bytes"),
|
||||||
UnrecognizedScript => write!(f, "script is not a p2pkh, p2sh or witness program"),
|
UnrecognizedScript => write!(f, "script is not a p2pkh, p2sh or witness program"),
|
||||||
NetworkValidation { required, found, ref address } => {
|
NetworkValidation { required, ref address } => {
|
||||||
write!(f, "address ")?;
|
write!(f, "address ")?;
|
||||||
address.fmt_internal(f)?; // Using fmt_internal in order to remove the "Address<NetworkUnchecked>(..)" wrapper
|
fmt::Display::fmt(&address.0, f)?;
|
||||||
write!(
|
write!(f, " is not valid on {}", required)
|
||||||
f,
|
|
||||||
" belongs to network {} which is different from required {}",
|
|
||||||
found, required
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
Error::UnknownHrp(ref e) => write_err!(f, "unknown hrp"; e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,10 +59,8 @@ impl std::error::Error for Error {
|
||||||
match self {
|
match self {
|
||||||
WitnessVersion(e) => Some(e),
|
WitnessVersion(e) => Some(e),
|
||||||
WitnessProgram(e) => Some(e),
|
WitnessProgram(e) => Some(e),
|
||||||
UncompressedPubkey
|
UnknownHrp(e) => Some(e),
|
||||||
| ExcessiveScriptSize
|
ExcessiveScriptSize | UnrecognizedScript | NetworkValidation { .. } => None,
|
||||||
| UnrecognizedScript
|
|
||||||
| NetworkValidation { .. } => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,6 +101,8 @@ pub enum ParseError {
|
||||||
WitnessVersion(witness_version::TryFromError),
|
WitnessVersion(witness_version::TryFromError),
|
||||||
/// A witness program error.
|
/// A witness program error.
|
||||||
WitnessProgram(witness_program::Error),
|
WitnessProgram(witness_program::Error),
|
||||||
|
/// Tried to parse an unknown HRP.
|
||||||
|
UnknownHrp(UnknownHrpError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ParseError {
|
impl fmt::Display for ParseError {
|
||||||
|
@ -121,6 +114,7 @@ impl fmt::Display for ParseError {
|
||||||
Bech32(ref e) => write_err!(f, "bech32 segwit decoding error"; e),
|
Bech32(ref e) => write_err!(f, "bech32 segwit decoding error"; e),
|
||||||
WitnessVersion(ref e) => write_err!(f, "witness version conversion/parsing error"; e),
|
WitnessVersion(ref e) => write_err!(f, "witness version conversion/parsing error"; e),
|
||||||
WitnessProgram(ref e) => write_err!(f, "witness program error"; e),
|
WitnessProgram(ref e) => write_err!(f, "witness program error"; e),
|
||||||
|
UnknownHrp(ref e) => write_err!(f, "tried to parse an unknown hrp"; e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,6 +129,7 @@ impl std::error::Error for ParseError {
|
||||||
Bech32(ref e) => Some(e),
|
Bech32(ref e) => Some(e),
|
||||||
WitnessVersion(ref e) => Some(e),
|
WitnessVersion(ref e) => Some(e),
|
||||||
WitnessProgram(ref e) => Some(e),
|
WitnessProgram(ref e) => Some(e),
|
||||||
|
UnknownHrp(ref e) => Some(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,3 +149,21 @@ impl From<witness_version::TryFromError> for ParseError {
|
||||||
impl From<witness_program::Error> for ParseError {
|
impl From<witness_program::Error> for ParseError {
|
||||||
fn from(e: witness_program::Error) -> Self { Self::WitnessProgram(e) }
|
fn from(e: witness_program::Error) -> Self { Self::WitnessProgram(e) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<UnknownHrpError> for ParseError {
|
||||||
|
fn from(e: UnknownHrpError) -> Self { Self::UnknownHrp(e) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unknown HRP error.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct UnknownHrpError(pub String);
|
||||||
|
|
||||||
|
impl fmt::Display for UnknownHrpError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "unknown hrp: {}", self.0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for UnknownHrpError {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
|
||||||
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ use core::fmt;
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
|
|
||||||
use bech32::primitives::hrp::{self, Hrp};
|
use bech32::primitives::hrp::Hrp;
|
||||||
use hashes::{sha256, Hash, HashEngine};
|
use hashes::{sha256, Hash, HashEngine};
|
||||||
use secp256k1::{Secp256k1, Verification, XOnlyPublicKey};
|
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_program::WitnessProgram;
|
||||||
use crate::blockdata::script::witness_version::WitnessVersion;
|
use crate::blockdata::script::witness_version::WitnessVersion;
|
||||||
use crate::blockdata::script::{self, Script, ScriptBuf, ScriptHash};
|
use crate::blockdata::script::{self, PushBytesBuf, Script, ScriptBuf, ScriptHash};
|
||||||
use crate::crypto::key::{PubkeyHash, PublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey};
|
use crate::crypto::key::{self, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey};
|
||||||
use crate::network::Network;
|
use crate::network::Network;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::script::PushBytesBuf;
|
|
||||||
use crate::taproot::TapNodeHash;
|
use crate::taproot::TapNodeHash;
|
||||||
|
|
||||||
#[rustfmt::skip] // Keep public re-exports separate.
|
#[rustfmt::skip] // Keep public re-exports separate.
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use self::{
|
pub use self::{
|
||||||
error::{Error, ParseError, UnknownAddressTypeError},
|
error::{Error, ParseError, UnknownAddressTypeError, UnknownHrpError},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The different types of addresses.
|
/// 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 {
|
mod sealed {
|
||||||
pub trait NetworkValidation {}
|
pub trait NetworkValidation {}
|
||||||
impl NetworkValidation for super::NetworkChecked {}
|
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
|
/// 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
|
/// representation of an address without the network validation tag, which is used to ensure that
|
||||||
/// addresses are used only on the appropriate network.
|
/// addresses are used only on the appropriate network.
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
struct AddressInner {
|
enum AddressInner {
|
||||||
payload: Payload,
|
P2pkh { hash: PubkeyHash, prefix: LegacyP2pkhPrefix },
|
||||||
network: Network,
|
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.
|
/// A Bitcoin address.
|
||||||
|
@ -298,7 +380,7 @@ struct DisplayUnchecked<'a, N: NetworkValidation>(&'a Address<N>);
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
impl<N: NetworkValidation> fmt::Display for DisplayUnchecked<'_, N> {
|
impl<N: NetworkValidation> fmt::Display for DisplayUnchecked<'_, N> {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { self.0.fmt_internal(fmt) }
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0 .0, fmt) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
@ -317,80 +399,11 @@ impl<N: NetworkValidation> serde::Serialize for Address<N> {
|
||||||
/// Methods on [`Address`] that can be called on both `Address<NetworkChecked>` and
|
/// Methods on [`Address`] that can be called on both `Address<NetworkChecked>` and
|
||||||
/// `Address<NetworkUnchecked>`.
|
/// `Address<NetworkUnchecked>`.
|
||||||
impl<V: NetworkValidation> Address<V> {
|
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
|
/// Returns a reference to the unchecked address, which is dangerous to use if the address
|
||||||
/// is invalid in the context of `NetworkUnchecked`.
|
/// is invalid in the context of `NetworkUnchecked`.
|
||||||
pub fn as_unchecked(&self) -> &Address<NetworkUnchecked> {
|
pub fn as_unchecked(&self) -> &Address<NetworkUnchecked> {
|
||||||
unsafe { &*(self as *const Address<V> as *const 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Methods and functions that can be called only on `Address<NetworkChecked>`.
|
/// Methods and functions that can be called only on `Address<NetworkChecked>`.
|
||||||
|
@ -399,9 +412,10 @@ impl Address {
|
||||||
///
|
///
|
||||||
/// This is the preferred non-witness type address.
|
/// This is the preferred non-witness type address.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn p2pkh(pk: &PublicKey, network: Network) -> Address {
|
pub fn p2pkh(pk: impl Into<PubkeyHash>, network: Network) -> Address {
|
||||||
let payload = Payload::PubkeyHash(pk.pubkey_hash());
|
let hash = pk.into();
|
||||||
Address(AddressInner { network, payload }, PhantomData)
|
let prefix = LegacyP2pkhPrefix::from_network(network);
|
||||||
|
Self(AddressInner::P2pkh { hash, prefix }, PhantomData)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a pay to script hash P2SH address from a script.
|
/// Creates a pay to script hash P2SH address from a script.
|
||||||
|
@ -413,55 +427,52 @@ impl Address {
|
||||||
if script.len() > MAX_SCRIPT_ELEMENT_SIZE {
|
if script.len() > MAX_SCRIPT_ELEMENT_SIZE {
|
||||||
return Err(Error::ExcessiveScriptSize);
|
return Err(Error::ExcessiveScriptSize);
|
||||||
}
|
}
|
||||||
let payload = Payload::ScriptHash(script.script_hash());
|
let hash = script.script_hash();
|
||||||
Ok(Address(AddressInner { network, payload }, PhantomData))
|
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.
|
/// 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.
|
/// This is the native segwit address type for an output redeemable with a single signature.
|
||||||
///
|
pub fn p2wpkh(
|
||||||
/// # Errors
|
pk: &PublicKey,
|
||||||
/// Will only return an error if an uncompressed public key is provided.
|
network: Network,
|
||||||
pub fn p2wpkh(pk: &PublicKey, network: Network) -> Result<Address, Error> {
|
) -> Result<Address, key::UncompressedPubkeyError> {
|
||||||
let prog = WitnessProgram::new(
|
let program = WitnessProgram::p2wpkh(pk)?;
|
||||||
WitnessVersion::V0,
|
Ok(Address::from_witness_program(program, network))
|
||||||
pk.wpubkey_hash().ok_or(Error::UncompressedPubkey)?,
|
|
||||||
)?;
|
|
||||||
let payload = Payload::WitnessProgram(prog);
|
|
||||||
Ok(Address(AddressInner { network, payload }, PhantomData))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a pay to script address that embeds a witness pay to public key.
|
/// 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.
|
/// This is a segwit address type that looks familiar (as p2sh) to legacy clients.
|
||||||
///
|
pub fn p2shwpkh(
|
||||||
/// # Errors
|
pk: &PublicKey,
|
||||||
/// Will only return an Error if an uncompressed public key is provided.
|
network: Network,
|
||||||
pub fn p2shwpkh(pk: &PublicKey, network: Network) -> Result<Address, Error> {
|
) -> Result<Address, key::UncompressedPubkeyError> {
|
||||||
let builder = script::Builder::new()
|
let builder = script::Builder::new().push_int(0).push_slice(pk.wpubkey_hash()?);
|
||||||
.push_int(0)
|
let script_hash = builder.as_script().script_hash();
|
||||||
.push_slice(pk.wpubkey_hash().ok_or(Error::UncompressedPubkey)?);
|
Ok(Address::p2sh_from_hash(script_hash, network))
|
||||||
|
|
||||||
let payload = Payload::ScriptHash(builder.into_script().script_hash());
|
|
||||||
Ok(Address(AddressInner { network, payload }, PhantomData))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a witness pay to script hash address.
|
/// Creates a witness pay to script hash address.
|
||||||
pub fn p2wsh(script: &Script, network: Network) -> Address {
|
pub fn p2wsh(script: &Script, network: Network) -> Address {
|
||||||
let prog = WitnessProgram::new(WitnessVersion::V0, script.wscript_hash())
|
let program = WitnessProgram::p2wsh(script);
|
||||||
.expect("wscript_hash has len 32 compatible with segwitv0");
|
Address::from_witness_program(program, network)
|
||||||
let payload = Payload::WitnessProgram(prog);
|
|
||||||
Address(AddressInner { network, payload }, PhantomData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a pay to script address that embeds a witness pay to script hash address.
|
/// 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.
|
/// This is a segwit address type that looks familiar (as p2sh) to legacy clients.
|
||||||
pub fn p2shwsh(script: &Script, network: Network) -> Address {
|
pub fn p2shwsh(script: &Script, network: Network) -> Address {
|
||||||
let ws = script::Builder::new().push_int(0).push_slice(script.wscript_hash()).into_script();
|
let builder = script::Builder::new().push_int(0).push_slice(script.wscript_hash());
|
||||||
let payload = Payload::ScriptHash(ws.script_hash());
|
let script_hash = builder.as_script().script_hash();
|
||||||
Address(AddressInner { network, payload }, PhantomData)
|
Address::p2sh_from_hash(script_hash, network)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a pay to taproot address from an untweaked key.
|
/// Creates a pay to taproot address from an untweaked key.
|
||||||
|
@ -471,21 +482,14 @@ impl Address {
|
||||||
merkle_root: Option<TapNodeHash>,
|
merkle_root: Option<TapNodeHash>,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Address {
|
) -> Address {
|
||||||
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
|
let program = WitnessProgram::p2tr(secp, internal_key, merkle_root);
|
||||||
let prog = WitnessProgram::new(WitnessVersion::V1, output_key.to_inner().serialize())
|
Address::from_witness_program(program, network)
|
||||||
.expect("taproot output key has len 32 <= 40");
|
|
||||||
let payload = Payload::WitnessProgram(prog);
|
|
||||||
Address(AddressInner { network, payload }, PhantomData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a pay to taproot address from a pre-tweaked output key.
|
/// Creates a pay to taproot address from a pre-tweaked output key.
|
||||||
///
|
|
||||||
/// This method is not recommended for use, [`Address::p2tr()`] should be used where possible.
|
|
||||||
pub fn p2tr_tweaked(output_key: TweakedPublicKey, network: Network) -> Address {
|
pub fn p2tr_tweaked(output_key: TweakedPublicKey, network: Network) -> Address {
|
||||||
let prog = WitnessProgram::new(WitnessVersion::V1, output_key.to_inner().serialize())
|
let program = WitnessProgram::p2tr_tweaked(output_key);
|
||||||
.expect("taproot output key has len 32 <= 40");
|
Address::from_witness_program(program, network)
|
||||||
let payload = Payload::WitnessProgram(prog);
|
|
||||||
Address(AddressInner { network, payload }, PhantomData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an address from an arbitrary witness program.
|
/// Creates an address from an arbitrary witness program.
|
||||||
|
@ -493,16 +497,53 @@ impl Address {
|
||||||
/// This only exists to support future witness versions. If you are doing normal mainnet things
|
/// This only exists to support future witness versions. If you are doing normal mainnet things
|
||||||
/// then you likely do not need this constructor.
|
/// then you likely do not need this constructor.
|
||||||
pub fn from_witness_program(program: WitnessProgram, network: Network) -> Address {
|
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)
|
Address(inner, PhantomData)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the address type of the address.
|
/// Gets the address type of the address.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
|
///
|
||||||
/// None if unknown, non-standard or related to the future witness version.
|
/// None if unknown, non-standard or related to the future witness version.
|
||||||
#[inline]
|
#[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
|
/// Checks whether or not the address is following Bitcoin standardness rules when
|
||||||
/// *spending* from this address. *NOT* to be called by senders.
|
/// *spending* from this address. *NOT* to be called by senders.
|
||||||
|
@ -521,32 +562,34 @@ impl Address {
|
||||||
|
|
||||||
/// Constructs an [`Address`] from an output script (`scriptPubkey`).
|
/// Constructs an [`Address`] from an output script (`scriptPubkey`).
|
||||||
pub fn from_script(script: &Script, network: Network) -> Result<Address, Error> {
|
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");
|
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() {
|
} else if script.is_p2sh() {
|
||||||
let bytes = script.as_bytes()[2..22].try_into().expect("statically 20B long");
|
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() {
|
} 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 buf = PushBytesBuf::try_from(script.as_bytes()[2..].to_vec())
|
||||||
|
.expect("is_witness_program guarantees len <= 42 bytes");
|
||||||
let witness_program =
|
let version = WitnessVersion::try_from(opcode)?;
|
||||||
WitnessProgram::new(WitnessVersion::try_from(opcode)?, witness_program)?;
|
let program = WitnessProgram::new(version, buf)?;
|
||||||
Payload::WitnessProgram(witness_program)
|
Ok(Address::from_witness_program(program, network))
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::UnrecognizedScript);
|
Err(Error::UnrecognizedScript)
|
||||||
};
|
}
|
||||||
Ok(Address(AddressInner { network, payload }, PhantomData))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a script pubkey spending to this address.
|
/// Generates a script pubkey spending to this address.
|
||||||
pub fn script_pubkey(&self) -> ScriptBuf {
|
pub fn script_pubkey(&self) -> ScriptBuf {
|
||||||
match self.payload() {
|
use AddressInner::*;
|
||||||
Payload::PubkeyHash(ref hash) => ScriptBuf::new_p2pkh(hash),
|
match self.0 {
|
||||||
Payload::ScriptHash(ref hash) => ScriptBuf::new_p2sh(hash),
|
P2pkh { ref hash, prefix: _ } => ScriptBuf::new_p2pkh(hash),
|
||||||
Payload::WitnessProgram(ref prog) => ScriptBuf::new_witness_program(prog),
|
P2sh { ref hash, prefix: _ } => ScriptBuf::new_p2sh(hash),
|
||||||
|
Segwit { ref program, hrp: _ } => ScriptBuf::new_witness_program(program),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,7 +629,7 @@ impl Address {
|
||||||
/// given key. For taproot addresses, the supplied key is assumed to be tweaked
|
/// given key. For taproot addresses, the supplied key is assumed to be tweaked
|
||||||
pub fn is_related_to_pubkey(&self, pubkey: &PublicKey) -> bool {
|
pub fn is_related_to_pubkey(&self, pubkey: &PublicKey) -> bool {
|
||||||
let pubkey_hash = pubkey.pubkey_hash();
|
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);
|
let xonly_pubkey = XOnlyPublicKey::from(pubkey.inner);
|
||||||
|
|
||||||
(*pubkey_hash.as_byte_array() == *payload)
|
(*pubkey_hash.as_byte_array() == *payload)
|
||||||
|
@ -599,31 +642,38 @@ impl Address {
|
||||||
/// This will only work for Taproot addresses. The Public Key is
|
/// This will only work for Taproot addresses. The Public Key is
|
||||||
/// assumed to have already been tweaked.
|
/// assumed to have already been tweaked.
|
||||||
pub fn is_related_to_xonly_pubkey(&self, xonly_pubkey: &XOnlyPublicKey) -> bool {
|
pub fn is_related_to_xonly_pubkey(&self, xonly_pubkey: &XOnlyPublicKey) -> bool {
|
||||||
let payload = self.inner_prog_as_bytes();
|
xonly_pubkey.serialize() == *self.payload_as_bytes()
|
||||||
payload == xonly_pubkey.serialize()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the address creates a particular script
|
/// Returns true if the address creates a particular script
|
||||||
/// This function doesn't make any allocations.
|
/// This function doesn't make any allocations.
|
||||||
pub fn matches_script_pubkey(&self, script_pubkey: &Script) -> bool {
|
pub fn matches_script_pubkey(&self, script: &Script) -> bool {
|
||||||
match &self.payload() {
|
use AddressInner::*;
|
||||||
Payload::PubkeyHash(ref hash) if script_pubkey.is_p2pkh() =>
|
match self.0 {
|
||||||
&script_pubkey.as_bytes()[3..23] == <PubkeyHash as AsRef<[u8; 20]>>::as_ref(hash),
|
P2pkh { ref hash, prefix: _ } if script.is_p2pkh() =>
|
||||||
Payload::ScriptHash(ref hash) if script_pubkey.is_p2sh() =>
|
&script.as_bytes()[3..23] == <PubkeyHash as AsRef<[u8; 20]>>::as_ref(hash),
|
||||||
&script_pubkey.as_bytes()[2..22] == <ScriptHash as AsRef<[u8; 20]>>::as_ref(hash),
|
P2sh { ref hash, prefix: _ } if script.is_p2sh() =>
|
||||||
Payload::WitnessProgram(ref prog) if script_pubkey.is_witness_program() =>
|
&script.as_bytes()[2..22] == <ScriptHash as AsRef<[u8; 20]>>::as_ref(hash),
|
||||||
&script_pubkey.as_bytes()[2..] == prog.program().as_bytes(),
|
Segwit { ref program, hrp: _ } if script.is_witness_program() =>
|
||||||
Payload::PubkeyHash(_) | Payload::ScriptHash(_) | Payload::WitnessProgram(_) => false,
|
&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
|
/// Returns the "payload" for this address.
|
||||||
/// is a script hash or pubkey hash, a reference to the hash is returned.
|
///
|
||||||
fn inner_prog_as_bytes(&self) -> &[u8] {
|
/// The "payload" is the useful stuff excluding serialization prefix, the exact payload is
|
||||||
match &self.payload() {
|
/// dependent on the inner address:
|
||||||
Payload::ScriptHash(hash) => hash.as_ref(),
|
///
|
||||||
Payload::PubkeyHash(hash) => hash.as_ref(),
|
/// - For p2sh, the payload is the script hash.
|
||||||
Payload::WitnessProgram(prog) => prog.program().as_bytes(),
|
/// - 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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -657,16 +707,11 @@ impl Address<NetworkUnchecked> {
|
||||||
/// assert_eq!(address.is_valid_for_network(Network::Testnet), false);
|
/// assert_eq!(address.is_valid_for_network(Network::Testnet), false);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn is_valid_for_network(&self, network: Network) -> bool {
|
pub fn is_valid_for_network(&self, network: Network) -> bool {
|
||||||
let is_legacy = matches!(
|
use AddressInner::*;
|
||||||
self.address_type_internal(),
|
match self.0 {
|
||||||
Some(AddressType::P2pkh) | Some(AddressType::P2sh)
|
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),
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -679,7 +724,7 @@ impl Address<NetworkUnchecked> {
|
||||||
if self.is_valid_for_network(required) {
|
if self.is_valid_for_network(required) {
|
||||||
Ok(self.assume_checked())
|
Ok(self.assume_checked())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::NetworkValidation { found: *self.network(), required, address: self })
|
Err(Error::NetworkValidation { required, address: self })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,22 +736,17 @@ impl Address<NetworkUnchecked> {
|
||||||
/// on [`Address`].
|
/// on [`Address`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn assume_checked(self) -> Address {
|
pub fn assume_checked(self) -> Address {
|
||||||
let (network, payload) = self.into_parts();
|
use AddressInner::*;
|
||||||
Address::new(network, payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For NetworkUnchecked , it compare Addresses and if network and payload matches then return true.
|
let inner = match self.0 {
|
||||||
impl PartialEq<Address<NetworkUnchecked>> for Address {
|
P2pkh { hash, prefix } => P2pkh { hash, prefix },
|
||||||
fn eq(&self, other: &Address<NetworkUnchecked>) -> bool {
|
P2sh { hash, prefix } => P2sh { hash, prefix },
|
||||||
self.network() == other.network() && self.payload() == other.payload()
|
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 {
|
impl From<Address> for script::ScriptBuf {
|
||||||
fn from(a: Address) -> Self { a.script_pubkey() }
|
fn from(a: Address) -> Self { a.script_pubkey() }
|
||||||
}
|
}
|
||||||
|
@ -714,56 +754,39 @@ impl From<Address> for script::ScriptBuf {
|
||||||
// Alternate formatting `{:#}` is used to return uppercase version of bech32 addresses which should
|
// Alternate formatting `{:#}` is used to return uppercase version of bech32 addresses which should
|
||||||
// be used in QR codes, see [`Address::to_qr_uri`].
|
// be used in QR codes, see [`Address::to_qr_uri`].
|
||||||
impl fmt::Display for Address {
|
impl fmt::Display for Address {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { self.fmt_internal(fmt) }
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, fmt) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: NetworkValidation> fmt::Debug for Address<V> {
|
impl<V: NetworkValidation> fmt::Debug for Address<V> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
if V::IS_CHECKED {
|
if V::IS_CHECKED {
|
||||||
self.fmt_internal(f)
|
fmt::Display::fmt(&self.0, f)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "Address<NetworkUnchecked>(")?;
|
write!(f, "Address<NetworkUnchecked>(")?;
|
||||||
self.fmt_internal(f)?;
|
fmt::Display::fmt(&self.0, f)?;
|
||||||
write!(f, ")")
|
write!(f, ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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`.
|
/// Address can be parsed only with `NetworkUnchecked`.
|
||||||
impl FromStr for Address<NetworkUnchecked> {
|
impl FromStr for Address<NetworkUnchecked> {
|
||||||
type Err = ParseError;
|
type Err = ParseError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Address<NetworkUnchecked>, ParseError> {
|
||||||
// try bech32
|
if let Ok((hrp, witness_version, data)) = bech32::segwit::decode(s) {
|
||||||
let bech32_network = match find_bech32_prefix(s) {
|
let version = WitnessVersion::try_from(witness_version)?;
|
||||||
// note that upper or lowercase is allowed but NOT mixed case
|
let buf = PushBytesBuf::try_from(data).expect("bech32 guarantees valid program length");
|
||||||
"bc" | "BC" => Some(Network::Bitcoin),
|
let program = WitnessProgram::new(version, buf)
|
||||||
"tb" | "TB" => Some(Network::Testnet), // this may also be signet
|
.expect("bech32 guarantees valid program length for witness");
|
||||||
"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)?;
|
|
||||||
|
|
||||||
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 {
|
if s.len() > 50 {
|
||||||
return Err(ParseError::Base58(base58::Error::InvalidLength(s.len() * 11 / 15)));
|
return Err(ParseError::Base58(base58::Error::InvalidLength(s.len() * 11 / 15)));
|
||||||
}
|
}
|
||||||
|
@ -772,19 +795,30 @@ impl FromStr for Address<NetworkUnchecked> {
|
||||||
return Err(ParseError::Base58(base58::Error::InvalidLength(data.len())));
|
return Err(ParseError::Base58(base58::Error::InvalidLength(data.len())));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (network, payload) = match data[0] {
|
let (prefix, data) = data.split_first().expect("length checked above");
|
||||||
PUBKEY_ADDRESS_PREFIX_MAIN =>
|
let data: [u8; 20] = data.try_into().expect("length checked above");
|
||||||
(Network::Bitcoin, Payload::PubkeyHash(PubkeyHash::from_slice(&data[1..]).unwrap())),
|
|
||||||
SCRIPT_ADDRESS_PREFIX_MAIN =>
|
let inner = match *prefix {
|
||||||
(Network::Bitcoin, Payload::ScriptHash(ScriptHash::from_slice(&data[1..]).unwrap())),
|
PUBKEY_ADDRESS_PREFIX_MAIN => {
|
||||||
PUBKEY_ADDRESS_PREFIX_TEST =>
|
let hash = PubkeyHash::from_byte_array(data);
|
||||||
(Network::Testnet, Payload::PubkeyHash(PubkeyHash::from_slice(&data[1..]).unwrap())),
|
AddressInner::P2pkh { hash, prefix: LegacyP2pkhPrefix::Mainnet }
|
||||||
SCRIPT_ADDRESS_PREFIX_TEST =>
|
}
|
||||||
(Network::Testnet, Payload::ScriptHash(ScriptHash::from_slice(&data[1..]).unwrap())),
|
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))),
|
x => return Err(ParseError::Base58(base58::Error::InvalidAddressVersion(x))),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Address::new(network, payload))
|
Ok(Address(inner, PhantomData))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -804,10 +838,10 @@ mod tests {
|
||||||
use secp256k1::XOnlyPublicKey;
|
use secp256k1::XOnlyPublicKey;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::crypto::key::PublicKey;
|
use crate::crypto::key::{PublicKey, UncompressedPubkeyError};
|
||||||
use crate::network::Network::{Bitcoin, Testnet};
|
use crate::network::Network::{Bitcoin, Testnet};
|
||||||
|
|
||||||
fn roundtrips(addr: &Address) {
|
fn roundtrips(addr: &Address, network: Network) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Address::from_str(&addr.to_string()).unwrap().assume_checked(),
|
Address::from_str(&addr.to_string()).unwrap().assume_checked(),
|
||||||
*addr,
|
*addr,
|
||||||
|
@ -815,8 +849,9 @@ mod tests {
|
||||||
addr,
|
addr,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Address::from_script(&addr.script_pubkey(), *addr.network()).as_ref(),
|
Address::from_script(&addr.script_pubkey(), network)
|
||||||
Ok(addr),
|
.expect("failed to create inner address from script_pubkey"),
|
||||||
|
*addr,
|
||||||
"script round-trip failed for {}",
|
"script round-trip failed for {}",
|
||||||
addr,
|
addr,
|
||||||
);
|
);
|
||||||
|
@ -832,10 +867,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_p2pkh_address_58() {
|
fn test_p2pkh_address_58() {
|
||||||
let addr = Address::new(
|
let hash = "162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse::<PubkeyHash>().unwrap();
|
||||||
Bitcoin,
|
let addr = Address::p2pkh(hash, Bitcoin);
|
||||||
Payload::PubkeyHash("162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse().unwrap()),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
addr.script_pubkey(),
|
addr.script_pubkey(),
|
||||||
|
@ -843,30 +876,28 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(&addr.to_string(), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM");
|
assert_eq!(&addr.to_string(), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
|
||||||
roundtrips(&addr);
|
roundtrips(&addr, Bitcoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_p2pkh_from_key() {
|
fn test_p2pkh_from_key() {
|
||||||
let key = "048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183".parse::<PublicKey>().unwrap();
|
let key = "048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183".parse::<PublicKey>().unwrap();
|
||||||
let addr = Address::p2pkh(&key, Bitcoin);
|
let addr = Address::p2pkh(key, Bitcoin);
|
||||||
assert_eq!(&addr.to_string(), "1QJVDzdqb1VpbDK7uDeyVXy9mR27CJiyhY");
|
assert_eq!(&addr.to_string(), "1QJVDzdqb1VpbDK7uDeyVXy9mR27CJiyhY");
|
||||||
|
|
||||||
let key = "03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f"
|
let key = "03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f"
|
||||||
.parse::<PublicKey>()
|
.parse::<PublicKey>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let addr = Address::p2pkh(&key, Testnet);
|
let addr = Address::p2pkh(key, Testnet);
|
||||||
assert_eq!(&addr.to_string(), "mqkhEMH6NCeYjFybv7pvFC22MFeaNT9AQC");
|
assert_eq!(&addr.to_string(), "mqkhEMH6NCeYjFybv7pvFC22MFeaNT9AQC");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
|
||||||
roundtrips(&addr);
|
roundtrips(&addr, Testnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_p2sh_address_58() {
|
fn test_p2sh_address_58() {
|
||||||
let addr = Address::new(
|
let hash = "162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse::<ScriptHash>().unwrap();
|
||||||
Bitcoin,
|
let addr = Address::p2sh_from_hash(hash, Bitcoin);
|
||||||
Payload::ScriptHash("162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse().unwrap()),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
addr.script_pubkey(),
|
addr.script_pubkey(),
|
||||||
|
@ -874,7 +905,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(&addr.to_string(), "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k");
|
assert_eq!(&addr.to_string(), "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
||||||
roundtrips(&addr);
|
roundtrips(&addr, Bitcoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -883,7 +914,7 @@ mod tests {
|
||||||
let addr = Address::p2sh(&script, Testnet).unwrap();
|
let addr = Address::p2sh(&script, Testnet).unwrap();
|
||||||
assert_eq!(&addr.to_string(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr");
|
assert_eq!(&addr.to_string(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
||||||
roundtrips(&addr);
|
roundtrips(&addr, Testnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -901,11 +932,11 @@ mod tests {
|
||||||
let addr = Address::p2wpkh(&key, Bitcoin).unwrap();
|
let addr = Address::p2wpkh(&key, Bitcoin).unwrap();
|
||||||
assert_eq!(&addr.to_string(), "bc1qvzvkjn4q3nszqxrv3nraga2r822xjty3ykvkuw");
|
assert_eq!(&addr.to_string(), "bc1qvzvkjn4q3nszqxrv3nraga2r822xjty3ykvkuw");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2wpkh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2wpkh));
|
||||||
roundtrips(&addr);
|
roundtrips(&addr, Bitcoin);
|
||||||
|
|
||||||
// Test uncompressed pubkey
|
// Test uncompressed pubkey
|
||||||
key.compressed = false;
|
key.compressed = false;
|
||||||
assert_eq!(Address::p2wpkh(&key, Bitcoin), Err(Error::UncompressedPubkey));
|
assert_eq!(Address::p2wpkh(&key, Bitcoin).unwrap_err(), UncompressedPubkeyError);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -918,7 +949,7 @@ mod tests {
|
||||||
"bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej"
|
"bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej"
|
||||||
);
|
);
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2wsh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2wsh));
|
||||||
roundtrips(&addr);
|
roundtrips(&addr, Bitcoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -930,11 +961,11 @@ mod tests {
|
||||||
let addr = Address::p2shwpkh(&key, Bitcoin).unwrap();
|
let addr = Address::p2shwpkh(&key, Bitcoin).unwrap();
|
||||||
assert_eq!(&addr.to_string(), "3QBRmWNqqBGme9er7fMkGqtZtp4gjMFxhE");
|
assert_eq!(&addr.to_string(), "3QBRmWNqqBGme9er7fMkGqtZtp4gjMFxhE");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
||||||
roundtrips(&addr);
|
roundtrips(&addr, Bitcoin);
|
||||||
|
|
||||||
// Test uncompressed pubkey
|
// Test uncompressed pubkey
|
||||||
key.compressed = false;
|
key.compressed = false;
|
||||||
assert_eq!(Address::p2wpkh(&key, Bitcoin), Err(Error::UncompressedPubkey));
|
assert_eq!(Address::p2wpkh(&key, Bitcoin).unwrap_err(), UncompressedPubkeyError);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -944,18 +975,19 @@ mod tests {
|
||||||
let addr = Address::p2shwsh(&script, Bitcoin);
|
let addr = Address::p2shwsh(&script, Bitcoin);
|
||||||
assert_eq!(&addr.to_string(), "36EqgNnsWW94SreZgBWc1ANC6wpFZwirHr");
|
assert_eq!(&addr.to_string(), "36EqgNnsWW94SreZgBWc1ANC6wpFZwirHr");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
||||||
roundtrips(&addr);
|
roundtrips(&addr, Bitcoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_non_existent_segwit_version() {
|
fn test_non_existent_segwit_version() {
|
||||||
// 40-byte program
|
// 40-byte program
|
||||||
let program = hex!(
|
let program = PushBytesBuf::from(hex!(
|
||||||
"654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4"
|
"654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4"
|
||||||
);
|
));
|
||||||
let witness_prog = WitnessProgram::new(WitnessVersion::V13, program.to_vec()).unwrap();
|
let program = WitnessProgram::new(WitnessVersion::V13, program).expect("valid program");
|
||||||
let addr = Address::new(Bitcoin, Payload::WitnessProgram(witness_prog));
|
|
||||||
roundtrips(&addr);
|
let addr = Address::from_witness_program(program, Bitcoin);
|
||||||
|
roundtrips(&addr, Bitcoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1113,57 +1145,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]
|
#[test]
|
||||||
fn p2tr_from_untweaked() {
|
fn p2tr_from_untweaked() {
|
||||||
//Test case from BIP-086
|
//Test case from BIP-086
|
||||||
|
@ -1178,7 +1159,7 @@ mod tests {
|
||||||
"bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr"
|
"bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr"
|
||||||
);
|
);
|
||||||
assert_eq!(address.address_type(), Some(AddressType::P2tr));
|
assert_eq!(address.address_type(), Some(AddressType::P2tr));
|
||||||
roundtrips(&address);
|
roundtrips(&address, Bitcoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -140,7 +140,7 @@ impl ScriptBuf {
|
||||||
/// Does not do any checks on version or program length.
|
/// Does not do any checks on version or program length.
|
||||||
///
|
///
|
||||||
/// Convenience method used by `new_p2wpkh`, `new_p2wsh`, `new_p2tr`, and `new_p2tr_tweaked`.
|
/// Convenience method used by `new_p2wpkh`, `new_p2wsh`, `new_p2tr`, and `new_p2tr_tweaked`.
|
||||||
fn new_witness_program_unchecked<T: AsRef<PushBytes>>(
|
pub(crate) fn new_witness_program_unchecked<T: AsRef<PushBytes>>(
|
||||||
version: WitnessVersion,
|
version: WitnessVersion,
|
||||||
program: T,
|
program: T,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
|
@ -9,8 +9,13 @@
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
|
use hashes::Hash as _;
|
||||||
|
use secp256k1::{Secp256k1, Verification};
|
||||||
|
|
||||||
use crate::blockdata::script::witness_version::WitnessVersion;
|
use crate::blockdata::script::witness_version::WitnessVersion;
|
||||||
use crate::blockdata::script::{PushBytes, PushBytesBuf, PushBytesErrorReport};
|
use crate::blockdata::script::{PushBytes, PushBytesBuf, PushBytesErrorReport, Script};
|
||||||
|
use crate::crypto::key::{self, PublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey};
|
||||||
|
use crate::taproot::TapNodeHash;
|
||||||
|
|
||||||
/// The segregated witness program.
|
/// The segregated witness program.
|
||||||
///
|
///
|
||||||
|
@ -46,11 +51,69 @@ impl WitnessProgram {
|
||||||
Ok(WitnessProgram { version, program })
|
Ok(WitnessProgram { version, program })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a [`WitnessProgram`] from a 20 byte pubkey hash.
|
||||||
|
fn new_p2wpkh(program: [u8; 20]) -> Self {
|
||||||
|
WitnessProgram { version: WitnessVersion::V0, program: program.into() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`WitnessProgram`] from a 32 byte script hash.
|
||||||
|
fn new_p2wsh(program: [u8; 32]) -> Self {
|
||||||
|
WitnessProgram { version: WitnessVersion::V0, program: program.into() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`WitnessProgram`] from a 32 byte serialize taproot xonly pubkey.
|
||||||
|
fn new_p2tr(program: [u8; 32]) -> Self {
|
||||||
|
WitnessProgram { version: WitnessVersion::V1, program: program.into() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`WitnessProgram`] from `pk` for a P2WPKH output.
|
||||||
|
pub fn p2wpkh(pk: &PublicKey) -> Result<Self, key::UncompressedPubkeyError> {
|
||||||
|
let hash = pk.wpubkey_hash()?;
|
||||||
|
let program = WitnessProgram::new_p2wpkh(hash.to_byte_array());
|
||||||
|
Ok(program)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`WitnessProgram`] from `script` for a P2WSH output.
|
||||||
|
pub fn p2wsh(script: &Script) -> Self {
|
||||||
|
let hash = script.wscript_hash();
|
||||||
|
WitnessProgram::new_p2wsh(hash.to_byte_array())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a pay to taproot address from an untweaked key.
|
||||||
|
pub fn p2tr<C: Verification>(
|
||||||
|
secp: &Secp256k1<C>,
|
||||||
|
internal_key: UntweakedPublicKey,
|
||||||
|
merkle_root: Option<TapNodeHash>,
|
||||||
|
) -> Self {
|
||||||
|
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
|
||||||
|
let pubkey = output_key.to_inner().serialize();
|
||||||
|
WitnessProgram::new_p2tr(pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a pay to taproot address from a pre-tweaked output key.
|
||||||
|
pub fn p2tr_tweaked(output_key: TweakedPublicKey) -> Self {
|
||||||
|
let pubkey = output_key.to_inner().serialize();
|
||||||
|
WitnessProgram::new_p2tr(pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the witness program version.
|
/// Returns the witness program version.
|
||||||
pub fn version(&self) -> WitnessVersion { self.version }
|
pub fn version(&self) -> WitnessVersion { self.version }
|
||||||
|
|
||||||
/// Returns the witness program.
|
/// Returns the witness program.
|
||||||
pub fn program(&self) -> &PushBytes { &self.program }
|
pub fn program(&self) -> &PushBytes { &self.program }
|
||||||
|
|
||||||
|
/// Returns true if this witness program is for a P2WPKH output.
|
||||||
|
pub fn is_p2wpkh(&self) -> bool {
|
||||||
|
self.version == WitnessVersion::V0 && self.program.len() == 20
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this witness program is for a P2WPSH output.
|
||||||
|
pub fn is_p2wsh(&self) -> bool {
|
||||||
|
self.version == WitnessVersion::V0 && self.program.len() == 32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this witness program is for a P2TR output.
|
||||||
|
pub fn is_p2tr(&self) -> bool { self.version == WitnessVersion::V1 && self.program.len() == 32 }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Witness program error.
|
/// Witness program error.
|
||||||
|
|
|
@ -59,15 +59,13 @@ impl PublicKey {
|
||||||
pub fn pubkey_hash(&self) -> PubkeyHash { self.with_serialized(PubkeyHash::hash) }
|
pub fn pubkey_hash(&self) -> PubkeyHash { self.with_serialized(PubkeyHash::hash) }
|
||||||
|
|
||||||
/// Returns bitcoin 160-bit hash of the public key for witness program
|
/// Returns bitcoin 160-bit hash of the public key for witness program
|
||||||
pub fn wpubkey_hash(&self) -> Option<WPubkeyHash> {
|
pub fn wpubkey_hash(&self) -> Result<WPubkeyHash, UncompressedPubkeyError> {
|
||||||
if self.compressed {
|
if self.compressed {
|
||||||
Some(WPubkeyHash::from_byte_array(
|
Ok(WPubkeyHash::from_byte_array(
|
||||||
hash160::Hash::hash(&self.inner.serialize()).to_byte_array(),
|
hash160::Hash::hash(&self.inner.serialize()).to_byte_array(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
// We can't create witness pubkey hashes for an uncompressed
|
Err(UncompressedPubkeyError)
|
||||||
// public keys
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -741,6 +739,22 @@ impl From<hex::HexToArrayError> for Error {
|
||||||
fn from(e: hex::HexToArrayError) -> Self { Error::Hex(e) }
|
fn from(e: hex::HexToArrayError) -> Self { Error::Hex(e) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Segwit public keys must always be compressed.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct UncompressedPubkeyError;
|
||||||
|
|
||||||
|
impl fmt::Display for UncompressedPubkeyError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_str("segwit public keys must always be compressed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for UncompressedPubkeyError {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -762,7 +776,7 @@ mod tests {
|
||||||
assert_eq!(&sk.to_wif(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy");
|
assert_eq!(&sk.to_wif(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy");
|
||||||
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
let pk = Address::p2pkh(&sk.public_key(&secp), sk.network);
|
let pk = Address::p2pkh(sk.public_key(&secp), sk.network);
|
||||||
assert_eq!(&pk.to_string(), "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx");
|
assert_eq!(&pk.to_string(), "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx");
|
||||||
|
|
||||||
// test string conversion
|
// test string conversion
|
||||||
|
@ -783,7 +797,7 @@ mod tests {
|
||||||
assert!(!pk.compressed);
|
assert!(!pk.compressed);
|
||||||
assert_eq!(&pk.to_string(), "042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133");
|
assert_eq!(&pk.to_string(), "042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133");
|
||||||
assert_eq!(pk, PublicKey::from_str("042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133").unwrap());
|
assert_eq!(pk, PublicKey::from_str("042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133").unwrap());
|
||||||
let addr = Address::p2pkh(&pk, sk.network);
|
let addr = Address::p2pkh(pk, sk.network);
|
||||||
assert_eq!(&addr.to_string(), "1GhQvF6dL8xa6wBxLnWmHcQsurx9RxiMc8");
|
assert_eq!(&addr.to_string(), "1GhQvF6dL8xa6wBxLnWmHcQsurx9RxiMc8");
|
||||||
pk.compressed = true;
|
pk.compressed = true;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -821,7 +835,7 @@ mod tests {
|
||||||
pk.wpubkey_hash().unwrap().to_string(),
|
pk.wpubkey_hash().unwrap().to_string(),
|
||||||
"9511aa27ef39bbfa4e4f3dd15f4d66ea57f475b4"
|
"9511aa27ef39bbfa4e4f3dd15f4d66ea57f475b4"
|
||||||
);
|
);
|
||||||
assert_eq!(upk.wpubkey_hash(), None);
|
assert!(upk.wpubkey_hash().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
|
@ -151,7 +151,7 @@ mod message_signing {
|
||||||
match address.address_type() {
|
match address.address_type() {
|
||||||
Some(AddressType::P2pkh) => {
|
Some(AddressType::P2pkh) => {
|
||||||
let pubkey = self.recover_pubkey(secp_ctx, msg_hash)?;
|
let pubkey = self.recover_pubkey(secp_ctx, msg_hash)?;
|
||||||
Ok(*address == Address::p2pkh(&pubkey, *address.network()))
|
Ok(address.pubkey_hash() == Some(pubkey.pubkey_hash()))
|
||||||
}
|
}
|
||||||
Some(address_type) =>
|
Some(address_type) =>
|
||||||
Err(MessageSignatureError::UnsupportedAddressType(address_type)),
|
Err(MessageSignatureError::UnsupportedAddressType(address_type)),
|
||||||
|
@ -242,7 +242,7 @@ mod tests {
|
||||||
assert!(pubkey.compressed);
|
assert!(pubkey.compressed);
|
||||||
assert_eq!(pubkey.inner, secp256k1::PublicKey::from_secret_key(&secp, &privkey));
|
assert_eq!(pubkey.inner, secp256k1::PublicKey::from_secret_key(&secp, &privkey));
|
||||||
|
|
||||||
let p2pkh = Address::p2pkh(&pubkey, Network::Bitcoin);
|
let p2pkh = Address::p2pkh(pubkey, Network::Bitcoin);
|
||||||
assert_eq!(signature2.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(true));
|
assert_eq!(signature2.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(true));
|
||||||
let p2wpkh = Address::p2wpkh(&pubkey, Network::Bitcoin).unwrap();
|
let p2wpkh = Address::p2wpkh(&pubkey, Network::Bitcoin).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -280,7 +280,7 @@ mod tests {
|
||||||
PublicKey::from_slice(&BASE64_STANDARD.decode(pubkey_base64).expect("base64 string"))
|
PublicKey::from_slice(&BASE64_STANDARD.decode(pubkey_base64).expect("base64 string"))
|
||||||
.expect("pubkey slice");
|
.expect("pubkey slice");
|
||||||
|
|
||||||
let p2pkh = Address::p2pkh(&pubkey, Network::Bitcoin);
|
let p2pkh = Address::p2pkh(pubkey, Network::Bitcoin);
|
||||||
assert_eq!(signature.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(false));
|
assert_eq!(signature.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,7 @@ fn serde_regression_witness() {
|
||||||
fn serde_regression_address() {
|
fn serde_regression_address() {
|
||||||
let s = include_str!("data/serde/public_key_hex");
|
let s = include_str!("data/serde/public_key_hex");
|
||||||
let pk = PublicKey::from_str(s.trim()).unwrap();
|
let pk = PublicKey::from_str(s.trim()).unwrap();
|
||||||
let addr = Address::p2pkh(&pk, Network::Bitcoin);
|
let addr = Address::p2pkh(pk, Network::Bitcoin);
|
||||||
|
|
||||||
let got = serialize(&addr).unwrap();
|
let got = serialize(&addr).unwrap();
|
||||||
let want = include_bytes!("data/serde/address_bincode") as &[_];
|
let want = include_bytes!("data/serde/address_bincode") as &[_];
|
||||||
|
|
Loading…
Reference in New Issue