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; 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,

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