Merge rust-bitcoin/rust-bitcoin#1765: unsafe address type conversions
29cb34eed7
Refactor Address struct and its methods (Harshil Jani) Pull request description: Closes #1755 In this PR the `as_unchecked` is added to the Address struct, which returns a reference to the same address but with the type Address<NetworkUnchecked>. Similarly, the `assume_checked_ref` is added to Address<NetworkUnchecked>, which returns a reference to the same address but with the type Address. ACKs for top commit: tcharding: ACK29cb34eed7
apoelstra: ACK29cb34eed7
Tree-SHA512: 75ba40883d9fb31026b0e94e8d3fdcf808ff38a4d10f61f99a1e14ddc48358da86bad44cd78563dc67aa5d39382a5493e73c03891317f361f590e39b6257cb84
This commit is contained in:
commit
8edfc4597b
|
@ -638,6 +638,17 @@ impl NetworkValidation for NetworkUnchecked {
|
||||||
const IS_CHECKED: bool = false;
|
const IS_CHECKED: bool = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The inner representation of an address, without the network validation tag.
|
||||||
|
///
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
/// A Bitcoin address.
|
/// A Bitcoin address.
|
||||||
///
|
///
|
||||||
/// ### Parsing addresses
|
/// ### Parsing addresses
|
||||||
|
@ -726,17 +737,15 @@ impl NetworkValidation for NetworkUnchecked {
|
||||||
/// * [BIP341 - Taproot: SegWit version 1 spending rules](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki)
|
/// * [BIP341 - Taproot: SegWit version 1 spending rules](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki)
|
||||||
/// * [BIP350 - Bech32m format for v1+ witness addresses](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki)
|
/// * [BIP350 - Bech32m format for v1+ witness addresses](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki)
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Address<V = NetworkChecked>
|
///
|
||||||
|
/// The `#[repr(transparent)]` attribute is used to guarantee that the layout of the
|
||||||
|
/// `Address` struct is the same as the layout of the `AddressInner` struct. This attribute is
|
||||||
|
/// an implementation detail and users should not rely on it in their code.
|
||||||
|
///
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Address<V = NetworkChecked>(AddressInner, PhantomData<V>)
|
||||||
where
|
where
|
||||||
V: NetworkValidation,
|
V: NetworkValidation;
|
||||||
{
|
|
||||||
/// The type of the address.
|
|
||||||
pub payload: Payload,
|
|
||||||
/// The network on which this address is usable.
|
|
||||||
pub network: Network,
|
|
||||||
/// Marker of the status of network validation.
|
|
||||||
_validation: PhantomData<V>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
struct DisplayUnchecked<'a>(&'a Address<NetworkUnchecked>);
|
struct DisplayUnchecked<'a>(&'a Address<NetworkUnchecked>);
|
||||||
|
@ -765,6 +774,24 @@ impl serde::Serialize for Address<NetworkUnchecked> {
|
||||||
/// 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.
|
||||||
|
pub 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`.
|
||||||
|
pub fn into_parts(self) -> (Network, Payload) {
|
||||||
|
let AddressInner { payload, network } = self.0;
|
||||||
|
(network, payload)
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the address type of the address.
|
/// Gets the address type of the address.
|
||||||
///
|
///
|
||||||
/// This method is publicly available as [`address_type`](Address<NetworkChecked>::address_type)
|
/// This method is publicly available as [`address_type`](Address<NetworkChecked>::address_type)
|
||||||
|
@ -774,7 +801,7 @@ impl<V: NetworkValidation> Address<V> {
|
||||||
/// # 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.
|
||||||
fn address_type_internal(&self) -> Option<AddressType> {
|
fn address_type_internal(&self) -> Option<AddressType> {
|
||||||
match self.payload {
|
match self.payload() {
|
||||||
Payload::PubkeyHash(_) => Some(AddressType::P2pkh),
|
Payload::PubkeyHash(_) => Some(AddressType::P2pkh),
|
||||||
Payload::ScriptHash(_) => Some(AddressType::P2sh),
|
Payload::ScriptHash(_) => Some(AddressType::P2sh),
|
||||||
Payload::WitnessProgram(ref prog) => {
|
Payload::WitnessProgram(ref prog) => {
|
||||||
|
@ -796,21 +823,21 @@ impl<V: NetworkValidation> Address<V> {
|
||||||
|
|
||||||
/// Format the address for the usage by `Debug` and `Display` implementations.
|
/// Format the address for the usage by `Debug` and `Display` implementations.
|
||||||
fn fmt_internal(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_internal(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let p2pkh_prefix = match self.network {
|
let p2pkh_prefix = match self.network() {
|
||||||
Network::Bitcoin => PUBKEY_ADDRESS_PREFIX_MAIN,
|
Network::Bitcoin => PUBKEY_ADDRESS_PREFIX_MAIN,
|
||||||
Network::Testnet | Network::Signet | Network::Regtest => PUBKEY_ADDRESS_PREFIX_TEST,
|
Network::Testnet | Network::Signet | Network::Regtest => PUBKEY_ADDRESS_PREFIX_TEST,
|
||||||
};
|
};
|
||||||
let p2sh_prefix = match self.network {
|
let p2sh_prefix = match self.network() {
|
||||||
Network::Bitcoin => SCRIPT_ADDRESS_PREFIX_MAIN,
|
Network::Bitcoin => SCRIPT_ADDRESS_PREFIX_MAIN,
|
||||||
Network::Testnet | Network::Signet | Network::Regtest => SCRIPT_ADDRESS_PREFIX_TEST,
|
Network::Testnet | Network::Signet | Network::Regtest => SCRIPT_ADDRESS_PREFIX_TEST,
|
||||||
};
|
};
|
||||||
let bech32_hrp = match self.network {
|
let bech32_hrp = match self.network() {
|
||||||
Network::Bitcoin => "bc",
|
Network::Bitcoin => "bc",
|
||||||
Network::Testnet | Network::Signet => "tb",
|
Network::Testnet | Network::Signet => "tb",
|
||||||
Network::Regtest => "bcrt",
|
Network::Regtest => "bcrt",
|
||||||
};
|
};
|
||||||
let encoding =
|
let encoding =
|
||||||
AddressEncoding { payload: &self.payload, p2pkh_prefix, p2sh_prefix, bech32_hrp };
|
AddressEncoding { payload: self.payload(), p2pkh_prefix, p2sh_prefix, bech32_hrp };
|
||||||
|
|
||||||
use fmt::Display;
|
use fmt::Display;
|
||||||
|
|
||||||
|
@ -820,8 +847,8 @@ impl<V: NetworkValidation> Address<V> {
|
||||||
/// Create new address from given components, infering the network validation
|
/// Create new address from given components, infering the network validation
|
||||||
/// marker type of the address.
|
/// marker type of the address.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(network: Network, payload: Payload) -> Address<V> {
|
pub fn new(network: Network, payload: Payload) -> Self {
|
||||||
Address { network, payload, _validation: PhantomData }
|
Self(AddressInner { network, payload }, PhantomData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -928,7 +955,7 @@ impl Address {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a script pubkey spending to this address.
|
/// Generates a script pubkey spending to this address.
|
||||||
pub fn script_pubkey(&self) -> ScriptBuf { self.payload.script_pubkey() }
|
pub fn script_pubkey(&self) -> ScriptBuf { self.payload().script_pubkey() }
|
||||||
|
|
||||||
/// Creates a URI string *bitcoin:address* optimized to be encoded in QR codes.
|
/// Creates a URI string *bitcoin:address* optimized to be encoded in QR codes.
|
||||||
///
|
///
|
||||||
|
@ -958,7 +985,7 @@ impl Address {
|
||||||
/// # assert_eq!(writer, ADDRESS);
|
/// # assert_eq!(writer, ADDRESS);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn to_qr_uri(&self) -> String {
|
pub fn to_qr_uri(&self) -> String {
|
||||||
let schema = match self.payload {
|
let schema = match self.payload() {
|
||||||
Payload::WitnessProgram { .. } => "BITCOIN",
|
Payload::WitnessProgram { .. } => "BITCOIN",
|
||||||
_ => "bitcoin",
|
_ => "bitcoin",
|
||||||
};
|
};
|
||||||
|
@ -972,7 +999,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.payload.inner_prog_as_bytes();
|
let payload = self.payload().inner_prog_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)
|
||||||
|
@ -985,19 +1012,24 @@ 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.payload.inner_prog_as_bytes();
|
let payload = self.payload().inner_prog_as_bytes();
|
||||||
payload == xonly_pubkey.serialize()
|
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_pubkey: &Script) -> bool {
|
||||||
self.payload.matches_script_pubkey(script_pubkey)
|
self.payload().matches_script_pubkey(script_pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Methods that can be called only on `Address<NetworkUnchecked>`.
|
/// Methods that can be called only on `Address<NetworkUnchecked>`.
|
||||||
impl Address<NetworkUnchecked> {
|
impl Address<NetworkUnchecked> {
|
||||||
|
/// Returns a reference to the checked address.
|
||||||
|
/// This function is dangerous in case the address is not a valid checked address.
|
||||||
|
pub fn assume_checked_ref(&self) -> &Address {
|
||||||
|
unsafe { &*(self as *const Address<NetworkUnchecked> as *const Address) }
|
||||||
|
}
|
||||||
/// Parsed addresses do not always have *one* network. The problem is that legacy testnet,
|
/// Parsed addresses do not always have *one* network. The problem is that legacy testnet,
|
||||||
/// regtest and signet addresse use the same prefix instead of multiple different ones. When
|
/// regtest and signet addresse use the same prefix instead of multiple different ones. When
|
||||||
/// parsing, such addresses are always assumed to be testnet addresses (the same is true for
|
/// parsing, such addresses are always assumed to be testnet addresses (the same is true for
|
||||||
|
@ -1025,8 +1057,8 @@ impl Address<NetworkUnchecked> {
|
||||||
Some(AddressType::P2pkh) | Some(AddressType::P2sh)
|
Some(AddressType::P2pkh) | Some(AddressType::P2sh)
|
||||||
);
|
);
|
||||||
|
|
||||||
match (self.network, network) {
|
match (self.network(), network) {
|
||||||
(a, b) if a == b => true,
|
(a, b) if *a == b => true,
|
||||||
(Network::Bitcoin, _) | (_, Network::Bitcoin) => false,
|
(Network::Bitcoin, _) | (_, Network::Bitcoin) => false,
|
||||||
(Network::Regtest, _) | (_, Network::Regtest) if !is_legacy => false,
|
(Network::Regtest, _) | (_, Network::Regtest) if !is_legacy => false,
|
||||||
(Network::Testnet, _) | (Network::Regtest, _) | (Network::Signet, _) => true,
|
(Network::Testnet, _) | (Network::Regtest, _) | (Network::Signet, _) => true,
|
||||||
|
@ -1042,7 +1074,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 { found: *self.network(), required, address: self })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1053,13 +1085,16 @@ impl Address<NetworkUnchecked> {
|
||||||
/// For details about this mechanism, see section [*Parsing addresses*](Address#parsing-addresses)
|
/// For details about this mechanism, see section [*Parsing addresses*](Address#parsing-addresses)
|
||||||
/// on [`Address`].
|
/// on [`Address`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn assume_checked(self) -> Address { Address::new(self.network, self.payload) }
|
pub fn assume_checked(self) -> Address {
|
||||||
|
let (network, payload) = self.into_parts();
|
||||||
|
Address::new(network, payload)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For NetworkUnchecked , it compare Addresses and if network and payload matches then return true.
|
// For NetworkUnchecked , it compare Addresses and if network and payload matches then return true.
|
||||||
impl PartialEq<Address<NetworkUnchecked>> for Address {
|
impl PartialEq<Address<NetworkUnchecked>> for Address {
|
||||||
fn eq(&self, other: &Address<NetworkUnchecked>) -> bool {
|
fn eq(&self, other: &Address<NetworkUnchecked>) -> bool {
|
||||||
self.network == other.network && self.payload == other.payload
|
self.network() == other.network() && self.payload() == other.payload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1201,7 +1236,7 @@ mod tests {
|
||||||
addr,
|
addr,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Address::from_script(&addr.script_pubkey(), addr.network).as_ref(),
|
Address::from_script(&addr.script_pubkey(), *addr.network()).as_ref(),
|
||||||
Ok(addr),
|
Ok(addr),
|
||||||
"script round-trip failed for {}",
|
"script round-trip failed for {}",
|
||||||
addr,
|
addr,
|
||||||
|
|
|
@ -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 == Address::p2pkh(&pubkey, *address.network()))
|
||||||
}
|
}
|
||||||
Some(address_type) =>
|
Some(address_type) =>
|
||||||
Err(MessageSignatureError::UnsupportedAddressType(address_type)),
|
Err(MessageSignatureError::UnsupportedAddressType(address_type)),
|
||||||
|
|
Loading…
Reference in New Issue