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:
    ACK 1ee989a3af
  apoelstra:
    ACK 1ee989a3af

Tree-SHA512: 1c2749dc929a1e9ad9b9feb01bec5c96b5aec07c6d646d88652deca7abe485907403116e9e29a0ab7dc06223254c4b49a384043284ec0a68fd76f9ab551e9e8a
This commit is contained in:
Andrew Poelstra 2023-12-11 17:54:46 +00:00
commit 199c482b26
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
7 changed files with 461 additions and 390 deletions

View File

@ -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 }
}

View File

@ -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]

View File

@ -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 {

View File

@ -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.

View File

@ -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")]

View File

@ -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));
} }
} }

View File

@ -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 &[_];