From 43cbf24f0e9d8e28a997b08443a8b94dcd3dc5ca Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Wed, 3 Jun 2026 11:43:01 -0700 Subject: [PATCH] refactor(dashcore): removed Address::network() and replace Network with AddressPrefix --- dash/src/address.rs | 108 +++++++++++++++----------- dash/src/sign_message.rs | 4 +- key-wallet-ffi/src/transaction.rs | 2 +- key-wallet/src/account/bls_account.rs | 2 +- key-wallet/tests/address_tests.rs | 10 +-- 5 files changed, 71 insertions(+), 55 deletions(-) diff --git a/dash/src/address.rs b/dash/src/address.rs index 7a3a0783a..7955abe5d 100644 --- a/dash/src/address.rs +++ b/dash/src/address.rs @@ -108,8 +108,6 @@ 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, }, @@ -131,10 +129,10 @@ impl fmt::Display for Error { Error::ExcessiveScriptSize => write!(f, "script size exceed 520 bytes"), Error::UnrecognizedScript => write!(f, "script is not a p2pkh, p2sh or witness program"), Error::UnknownAddressType(ref s) => write!(f, "unknown address type: '{}' is either invalid or not supported in rust-dash", s), - Error::NetworkValidation { required, found, ref address } => { + Error::NetworkValidation { required, ref address } => { write!(f, "address ")?; address.fmt_internal(f)?; // Using fmt_internal in order to remove the "Address(..)" wrapper - write!(f, " belongs to network {} which is different from required {}", found, required) + write!(f, " is not valid for the required network {}", required) } } } @@ -607,6 +605,10 @@ impl Payload { Payload::WitnessProgram(prog) } + fn is_legacy(&self) -> bool { + matches!(self, Payload::PubkeyHash(_) | Payload::ScriptHash(_)) + } + /// 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] { @@ -695,6 +697,39 @@ impl NetworkValidation for NetworkUnchecked { const IS_CHECKED: bool = false; } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +enum AddressPrefix { + /// Mainnet — base58 `X`/`7`, or bech32 `ds`. + Mainnet, + /// Testnet, devnet, and legacy (base58) regtest — base58 `y`/`8`, or bech32 `tb + Testnet, + /// Regtest witness programs — bech32 `dsrt` only. + RegtestBech32, +} + +impl AddressPrefix { + /// Determines the prefix an address built for `network` with `payload` is encoded with. + fn for_address(network: Network, payload: &Payload) -> Self { + match network { + Network::Mainnet => AddressPrefix::Mainnet, + Network::Testnet | Network::Devnet => AddressPrefix::Testnet, + Network::Regtest if payload.is_legacy() => AddressPrefix::Testnet, + Network::Regtest => AddressPrefix::RegtestBech32, + } + } + + fn is_valid_for_network(self, network: Network, payload: &Payload) -> bool { + match self { + AddressPrefix::Mainnet => network == Network::Mainnet, + AddressPrefix::Testnet => { + matches!(network, Network::Testnet | Network::Devnet) + || (payload.is_legacy() && network == Network::Regtest) + } + AddressPrefix::RegtestBech32 => network == Network::Regtest, + } + } +} + /// 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 @@ -703,7 +738,7 @@ impl NetworkValidation for NetworkUnchecked { #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct AddressInner { payload: Payload, - network: Network, + prefix: AddressPrefix, } /// A Dash address. @@ -946,26 +981,12 @@ impl Address { &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 { unsafe { &*(self as *const Address as *const Address) } } - /// 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::address_type) @@ -997,18 +1018,18 @@ impl Address { /// 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::Mainnet => PUBKEY_ADDRESS_PREFIX_MAIN, - Network::Testnet | Network::Devnet | Network::Regtest => PUBKEY_ADDRESS_PREFIX_TEST, + let p2pkh_prefix = match self.0.prefix { + AddressPrefix::Mainnet => PUBKEY_ADDRESS_PREFIX_MAIN, + _ => PUBKEY_ADDRESS_PREFIX_TEST, }; - let p2sh_prefix = match self.network() { - Network::Mainnet => SCRIPT_ADDRESS_PREFIX_MAIN, - Network::Testnet | Network::Devnet | Network::Regtest => SCRIPT_ADDRESS_PREFIX_TEST, + let p2sh_prefix = match self.0.prefix { + AddressPrefix::Mainnet => SCRIPT_ADDRESS_PREFIX_MAIN, + _ => SCRIPT_ADDRESS_PREFIX_TEST, }; - let bech32_hrp = match self.network() { - Network::Mainnet => "ds", - Network::Testnet | Network::Devnet => "tb", - Network::Regtest => "dsrt", + let bech32_hrp = match self.0.prefix { + AddressPrefix::Mainnet => "ds", + AddressPrefix::Testnet => "tb", + AddressPrefix::RegtestBech32 => "dsrt", }; let encoding = AddressEncoding { payload: self.payload(), @@ -1026,9 +1047,11 @@ impl Address { /// marker type of the address. #[inline] pub fn new(network: Network, payload: Payload) -> Self { + let prefix = AddressPrefix::for_address(network, &payload); + Self( AddressInner { - network, + prefix, payload, }, PhantomData, @@ -1246,17 +1269,7 @@ impl Address { /// 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::Mainnet, _) | (_, Network::Mainnet) => false, - (Network::Regtest, _) | (_, Network::Regtest) if !is_legacy => false, - (Network::Testnet, _) | (Network::Regtest, _) | (Network::Devnet, _) => true, - } + self.0.prefix.is_valid_for_network(network, &self.0.payload) } /// Checks whether network of this address is as required. @@ -1269,7 +1282,6 @@ impl Address { Ok(self.assume_checked()) } else { Err(Error::NetworkValidation { - found: *self.network(), required, address: self, }) @@ -1284,8 +1296,7 @@ impl Address { /// on [`Address`]. #[inline] pub fn assume_checked(self) -> Address { - let (network, payload) = self.into_parts(); - Address::new(network, payload) + Address(self.0, PhantomData) } /// Returns the payload as a vector. @@ -1297,7 +1308,7 @@ impl Address { // For NetworkUnchecked , it compare Addresses and if network and payload matches then return true. impl PartialEq> for Address { fn eq(&self, other: &Address) -> bool { - self.network() == other.network() && self.payload() == other.payload() + self.0.prefix == other.0.prefix && self.payload() == other.payload() } } @@ -1478,8 +1489,13 @@ mod tests { "string round-trip failed for {}", addr, ); + let network = match addr.0.prefix { + AddressPrefix::Mainnet => Network::Mainnet, + AddressPrefix::Testnet => Network::Testnet, + AddressPrefix::RegtestBech32 => Network::Regtest, + }; assert_eq!( - Address::from_script(&addr.script_pubkey(), *addr.network()).as_ref(), + Address::from_script(&addr.script_pubkey(), network).as_ref(), Ok(addr), "script round-trip failed for {}", addr, diff --git a/dash/src/sign_message.rs b/dash/src/sign_message.rs index eabc156e9..6180eb616 100644 --- a/dash/src/sign_message.rs +++ b/dash/src/sign_message.rs @@ -39,7 +39,7 @@ mod message_signing { use secp256k1; use secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; - use crate::address::{Address, AddressType}; + use crate::address::{Address, AddressType, Payload}; use crate::crypto::key::PublicKey; #[cfg(feature = "base64")] use crate::prelude::*; @@ -172,7 +172,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.payload() == Payload::p2pkh(&pubkey)) } Some(address_type) => { Err(MessageSignatureError::UnsupportedAddressType(address_type)) diff --git a/key-wallet-ffi/src/transaction.rs b/key-wallet-ffi/src/transaction.rs index 2f38b742e..03b0f3bc4 100644 --- a/key-wallet-ffi/src/transaction.rs +++ b/key-wallet-ffi/src/transaction.rs @@ -688,7 +688,7 @@ pub unsafe extern "C" fn address_to_pubkey_hash( match address_str.parse::>() { Ok(addr) => { - if *addr.network() != expected_network { + if !addr.is_valid_for_network(expected_network) { return -1; } diff --git a/key-wallet/src/account/bls_account.rs b/key-wallet/src/account/bls_account.rs index 12a1c031d..59324c0be 100644 --- a/key-wallet/src/account/bls_account.rs +++ b/key-wallet/src/account/bls_account.rs @@ -506,6 +506,6 @@ mod tests { let address = result.expect("Failed to derive BLS address"); // Verify it's a valid testnet address - assert_eq!(address.network(), &Network::Testnet); + assert!(address.as_unchecked().is_valid_for_network(Network::Testnet)); } } diff --git a/key-wallet/tests/address_tests.rs b/key-wallet/tests/address_tests.rs index 41d46ff31..4ff8cd38c 100644 --- a/key-wallet/tests/address_tests.rs +++ b/key-wallet/tests/address_tests.rs @@ -16,7 +16,7 @@ fn test_p2pkh_address_creation() { // Create P2PKH address let address = Address::p2pkh(&dash_pubkey, DashNetwork::Mainnet); - assert_eq!(*address.network(), DashNetwork::Mainnet); + assert!(address.as_unchecked().is_valid_for_network(DashNetwork::Mainnet)); assert_eq!(address.address_type(), Some(AddressType::P2pkh)); // Check that it generates a valid Dash address (starts with 'X') @@ -33,7 +33,7 @@ fn test_p2sh_address_creation() { // Create P2SH address let address = Address::p2sh(&script, DashNetwork::Mainnet).unwrap(); - assert_eq!(*address.network(), DashNetwork::Mainnet); + assert!(address.as_unchecked().is_valid_for_network(DashNetwork::Mainnet)); assert_eq!(address.address_type(), Some(AddressType::P2sh)); // Check that it generates a valid Dash P2SH address (starts with '7') @@ -53,7 +53,7 @@ fn test_testnet_address() { // Create testnet P2PKH address let address = Address::p2pkh(&dash_pubkey, DashNetwork::Testnet); - assert_eq!(*address.network(), DashNetwork::Testnet); + assert!(address.as_unchecked().is_valid_for_network(DashNetwork::Testnet)); assert_eq!(address.address_type(), Some(AddressType::P2pkh)); // Check that it generates a valid testnet address (starts with 'y') @@ -85,7 +85,7 @@ fn test_address_parsing() { let parsed_mainnet = Address::::from_str(&mainnet_str).unwrap(); let checked_mainnet = parsed_mainnet.require_network(DashNetwork::Mainnet).unwrap(); - assert_eq!(*checked_mainnet.network(), DashNetwork::Mainnet); + assert!(checked_mainnet.as_unchecked().is_valid_for_network(DashNetwork::Mainnet)); assert_eq!(checked_mainnet.address_type(), Some(AddressType::P2pkh)); // Create a testnet address @@ -104,7 +104,7 @@ fn test_address_parsing() { let parsed_testnet = Address::::from_str(&testnet_str).unwrap(); let checked_testnet = parsed_testnet.require_network(DashNetwork::Testnet).unwrap(); - assert_eq!(*checked_testnet.network(), DashNetwork::Testnet); + assert!(checked_testnet.as_unchecked().is_valid_for_network(DashNetwork::Testnet)); assert_eq!(checked_testnet.address_type(), Some(AddressType::P2pkh)); }