Remove Network from AddressInner

An `AddressInner` struct 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 a `KnownHrp` for bech32
addresses.

Also enables removing the `AddressEncoding` struct as we can display the
`AddressInner` struct directly. (The `Display` impl is on `AddressInner`
and not directly on address to ignore the `NetworkValidation` wrapper,
may be able to be simplified still further.)
This commit is contained in:
Tobin C. Harding 2023-05-04 12:03:25 +10:00
parent 3490433618
commit 923ce7402d
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
7 changed files with 434 additions and 367 deletions

View File

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

View File

@ -140,7 +140,7 @@ impl ScriptBuf {
/// Does not do any checks on version or program length.
///
/// 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,
program: T,
) -> Self {

View File

@ -9,8 +9,13 @@
use core::fmt;
use hashes::Hash as _;
use secp256k1::{Secp256k1, Verification};
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.
///
@ -46,11 +51,69 @@ impl WitnessProgram {
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.
pub fn version(&self) -> WitnessVersion { self.version }
/// Returns the witness 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.

View File

@ -781,7 +781,7 @@ mod tests {
assert_eq!(&sk.to_wif(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy");
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");
// test string conversion
@ -802,7 +802,7 @@ mod tests {
assert!(!pk.compressed);
assert_eq!(&pk.to_string(), "042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133");
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");
pk.compressed = true;
assert_eq!(

View File

@ -151,7 +151,7 @@ mod message_signing {
match address.address_type() {
Some(AddressType::P2pkh) => {
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) =>
Err(MessageSignatureError::UnsupportedAddressType(address_type)),
@ -242,7 +242,7 @@ mod tests {
assert!(pubkey.compressed);
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));
let p2wpkh = Address::p2wpkh(&pubkey, Network::Bitcoin).unwrap();
assert_eq!(
@ -280,7 +280,7 @@ mod tests {
PublicKey::from_slice(&BASE64_STANDARD.decode(pubkey_base64).expect("base64 string"))
.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));
}
}

View File

@ -145,7 +145,7 @@ fn serde_regression_witness() {
fn serde_regression_address() {
let s = include_str!("data/serde/public_key_hex");
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 want = include_bytes!("data/serde/address_bincode") as &[_];