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:
    ACK 29cb34eed7
  apoelstra:
    ACK 29cb34eed7

Tree-SHA512: 75ba40883d9fb31026b0e94e8d3fdcf808ff38a4d10f61f99a1e14ddc48358da86bad44cd78563dc67aa5d39382a5493e73c03891317f361f590e39b6257cb84
This commit is contained in:
Andrew Poelstra 2023-04-04 17:16:26 +00:00
commit 8edfc4597b
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 64 additions and 29 deletions

View File

@ -638,6 +638,17 @@ impl NetworkValidation for NetworkUnchecked {
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.
///
/// ### 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)
/// * [BIP350 - Bech32m format for v1+ witness addresses](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki)
#[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
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>,
}
V: NetworkValidation;
#[cfg(feature = "serde")]
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
/// `Address<NetworkUnchecked>`.
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.
///
/// This method is publicly available as [`address_type`](Address<NetworkChecked>::address_type)
@ -774,7 +801,7 @@ impl<V: NetworkValidation> Address<V> {
/// # Returns
/// None if unknown, non-standard or related to the future witness version.
fn address_type_internal(&self) -> Option<AddressType> {
match self.payload {
match self.payload() {
Payload::PubkeyHash(_) => Some(AddressType::P2pkh),
Payload::ScriptHash(_) => Some(AddressType::P2sh),
Payload::WitnessProgram(ref prog) => {
@ -796,21 +823,21 @@ impl<V: NetworkValidation> Address<V> {
/// 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 {
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 {
let p2sh_prefix = match self.network() {
Network::Bitcoin => SCRIPT_ADDRESS_PREFIX_MAIN,
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::Testnet | Network::Signet => "tb",
Network::Regtest => "bcrt",
};
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;
@ -820,8 +847,8 @@ impl<V: NetworkValidation> Address<V> {
/// Create new address from given components, infering the network validation
/// marker type of the address.
#[inline]
pub fn new(network: Network, payload: Payload) -> Address<V> {
Address { network, payload, _validation: PhantomData }
pub fn new(network: Network, payload: Payload) -> Self {
Self(AddressInner { network, payload }, PhantomData)
}
}
@ -928,7 +955,7 @@ impl 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.
///
@ -958,7 +985,7 @@ impl Address {
/// # assert_eq!(writer, ADDRESS);
/// ```
pub fn to_qr_uri(&self) -> String {
let schema = match self.payload {
let schema = match self.payload() {
Payload::WitnessProgram { .. } => "BITCOIN",
_ => "bitcoin",
};
@ -972,7 +999,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.payload.inner_prog_as_bytes();
let payload = self.payload().inner_prog_as_bytes();
let xonly_pubkey = XOnlyPublicKey::from(pubkey.inner);
(*pubkey_hash.as_byte_array() == *payload)
@ -985,19 +1012,24 @@ 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.payload.inner_prog_as_bytes();
let payload = self.payload().inner_prog_as_bytes();
payload == xonly_pubkey.serialize()
}
/// 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 {
self.payload.matches_script_pubkey(script_pubkey)
self.payload().matches_script_pubkey(script_pubkey)
}
}
/// Methods that can be called only on `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,
/// 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
@ -1025,8 +1057,8 @@ impl Address<NetworkUnchecked> {
Some(AddressType::P2pkh) | Some(AddressType::P2sh)
);
match (self.network, network) {
(a, b) if a == b => true,
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,
@ -1042,7 +1074,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 { 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)
/// on [`Address`].
#[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.
impl PartialEq<Address<NetworkUnchecked>> for Address {
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,
);
assert_eq!(
Address::from_script(&addr.script_pubkey(), addr.network).as_ref(),
Address::from_script(&addr.script_pubkey(), *addr.network()).as_ref(),
Ok(addr),
"script round-trip failed for {}",
addr,

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 == Address::p2pkh(&pubkey, *address.network()))
}
Some(address_type) =>
Err(MessageSignatureError::UnsupportedAddressType(address_type)),