Repository: rust-bitcoin/rust-bitcoin
Pull request #2232 (2023-11-27 23:58:54)

Add `NetworkKind`
Add a new type `NetworkKind` the describes the kind of network we are on, ether mainnet or one of the test nets (testnet, regtest, signet).

Use it in `Address`, `PrivateKey`, and `bip32`.

apoelstra: This looks great to me. Am a little worried about the bip32 case where theoretically there could be more prefixes added (and no doubt we'll get the ypub/zpub people whining that we're making it even harder for them to implement their weird layer violations). But I'm doubtful. The model we've moved to, in my view, is that keys are "just keys" and networks should be specified elsewhere, e.g. at the descriptor layer.


Patch:
diff --git a/bitcoin/examples/bip32.rs b/bitcoin/examples/bip32.rs
index 98f19673..de027003 100644
--- a/bitcoin/examples/bip32.rs
+++ b/bitcoin/examples/bip32.rs
@@ -8,7 +8,7 @@
 use bitcoin::hex::FromHex;
 use bitcoin::secp256k1::ffi::types::AlignedType;
 use bitcoin::secp256k1::Secp256k1;
-use bitcoin::CompressedPublicKey;
+use bitcoin::{Network, NetworkKind, CompressedPublicKey};
 
 fn main() {
     // This example derives root xprv from a 32-byte seed,
@@ -26,10 +26,7 @@ fn main() {
 
     let seed_hex = &args[1];
     println!("Seed: {}", seed_hex);
-
-    // default network as mainnet
-    let network = bitcoin::Network::Bitcoin;
-    println!("Network: {:?}", network);
+    println!("Using mainnet network");
 
     let seed = Vec::from_hex(seed_hex).unwrap();
 
@@ -39,7 +36,7 @@ fn main() {
     let secp = Secp256k1::preallocated_new(buf.as_mut_slice()).unwrap();
 
     // calculate root key from seed
-    let root = Xpriv::new_master(network, &seed).unwrap();
+    let root = Xpriv::new_master(NetworkKind::Main, &seed).unwrap();
     println!("Root key: {}", root);
 
     // derive child xpub
@@ -53,6 +50,6 @@ fn main() {
     // manually creating indexes this time
     let zero = ChildNumber::from_normal_idx(0).unwrap();
     let public_key = xpub.derive_pub(&secp, &[zero, zero]).unwrap().public_key;
-    let address = Address::p2wpkh(&CompressedPublicKey(public_key), network);
+    let address = Address::p2wpkh(&CompressedPublicKey(public_key), Network::Bitcoin);
     println!("First receiving address: {}", address);
 }
diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs
index 336f63b3..b1843824 100644
--- a/bitcoin/src/address/mod.rs
+++ b/bitcoin/src/address/mod.rs
@@ -46,7 +46,7 @@
 use crate::blockdata::script::witness_version::WitnessVersion;
 use crate::blockdata::script::{self, PushBytesBuf, Script, ScriptBuf, ScriptHash};
 use crate::crypto::key::{PubkeyHash, PublicKey, CompressedPublicKey, TweakedPublicKey, UntweakedPublicKey};
-use crate::network::Network;
+use crate::network::{Network, NetworkKind};
 use crate::prelude::*;
 use crate::taproot::TapNodeHash;
 
@@ -135,8 +135,8 @@ impl NetworkValidation for NetworkUnchecked {
 /// addresses are used only on the appropriate network.
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 enum AddressInner {
-    P2pkh { hash: PubkeyHash, prefix: LegacyP2pkhPrefix },
-    P2sh { hash: ScriptHash, prefix: LegacyP2shPrefix },
+    P2pkh { hash: PubkeyHash, network: NetworkKind },
+    P2sh { hash: ScriptHash, network: NetworkKind },
     Segwit { program: WitnessProgram, hrp: KnownHrp },
 }
 
@@ -145,15 +145,21 @@ impl fmt::Display for AddressInner {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
         use AddressInner::*;
         match self {
-            P2pkh { hash, prefix } => {
+            P2pkh { hash, network } => {
                 let mut prefixed = [0; 21];
-                prefixed[0] = prefix.to_u8();
+                prefixed[0] = match network {
+                    NetworkKind::Main => PUBKEY_ADDRESS_PREFIX_MAIN,
+                    NetworkKind::Test => PUBKEY_ADDRESS_PREFIX_TEST,
+                };
                 prefixed[1..].copy_from_slice(&hash[..]);
                 base58::encode_check_to_fmt(fmt, &prefixed[..])
             }
-            P2sh { hash, prefix } => {
+            P2sh { hash, network } => {
                 let mut prefixed = [0; 21];
-                prefixed[0] = prefix.to_u8();
+                prefixed[0] = match network {
+                    NetworkKind::Main => SCRIPT_ADDRESS_PREFIX_MAIN,
+                    NetworkKind::Test => SCRIPT_ADDRESS_PREFIX_TEST,
+                };
                 prefixed[1..].copy_from_slice(&hash[..]);
                 base58::encode_check_to_fmt(fmt, &prefixed[..])
             }
@@ -172,64 +178,6 @@ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
     }
 }
 
-/// 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]
@@ -277,6 +225,10 @@ fn to_hrp(self) -> Hrp {
     }
 }
 
+impl From<Network> for KnownHrp {
+    fn from(n: Network) -> Self { Self::from_network(n) }
+}
+
 /// A Bitcoin address.
 ///
 /// ### Parsing addresses
@@ -412,10 +364,9 @@ impl Address {
     ///
     /// This is the preferred non-witness type address.
     #[inline]
-    pub fn p2pkh(pk: impl Into<PubkeyHash>, network: Network) -> Address {
+    pub fn p2pkh(pk: impl Into<PubkeyHash>, network: impl Into<NetworkKind>) -> Address {
         let hash = pk.into();
-        let prefix = LegacyP2pkhPrefix::from_network(network);
-        Self(AddressInner::P2pkh { hash, prefix }, PhantomData)
+        Self(AddressInner::P2pkh { hash, network: network.into() }, PhantomData)
     }
 
     /// Creates a pay to script hash P2SH address from a script.
@@ -423,7 +374,7 @@ pub fn p2pkh(pk: impl Into<PubkeyHash>, network: Network) -> Address {
     /// This address type was introduced with BIP16 and is the popular type to implement multi-sig
     /// these days.
     #[inline]
-    pub fn p2sh(script: &Script, network: Network) -> Result<Address, Error> {
+    pub fn p2sh(script: &Script, network: impl Into<NetworkKind>) -> Result<Address, Error> {
         if script.len() > MAX_SCRIPT_ELEMENT_SIZE {
             return Err(Error::ExcessiveScriptSize);
         }
@@ -432,9 +383,8 @@ pub fn p2sh(script: &Script, network: Network) -> Result<Address, Error> {
     }
 
     // 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)
+    fn p2sh_from_hash(hash: ScriptHash, network: impl Into<NetworkKind>) -> Address {
+        Self(AddressInner::P2sh { hash, network: network.into() }, PhantomData)
     }
 
     /// Creates a witness pay to public key address from a public key.
@@ -453,8 +403,8 @@ pub fn p2wpkh(
     /// This is a segwit address type that looks familiar (as p2sh) to legacy clients.
     pub fn p2shwpkh(
         pk: &CompressedPublicKey,
-        network: Network,
-    ) -> Self {
+        network: impl Into<NetworkKind>,
+    ) -> Address {
         let builder = script::Builder::new().push_int(0).push_slice(pk.wpubkey_hash());
         let script_hash = builder.as_script().script_hash();
         Address::p2sh_from_hash(script_hash, network)
@@ -469,7 +419,7 @@ pub fn p2wsh(script: &Script, network: Network) -> Address {
     /// 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 {
+    pub fn p2shwsh(script: &Script, network: impl Into<NetworkKind>) -> Address {
         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)
@@ -496,6 +446,7 @@ pub fn p2tr_tweaked(output_key: TweakedPublicKey, network: Network) -> Address {
     ///
     /// This only exists to support future witness versions. If you are doing normal mainnet things
     /// then you likely do not need this constructor.
+    // TODO: This is still arguably wrong, could take a KnownHrp/Hrp instead of Network.
     pub fn from_witness_program(program: WitnessProgram, network: Network) -> Address {
         let hrp = KnownHrp::from_network(network);
         let inner = AddressInner::Segwit { program, hrp };
@@ -530,7 +481,7 @@ pub fn pubkey_hash(&self) -> Option<PubkeyHash> {
         use AddressInner::*;
 
         match self.0 {
-            P2pkh { ref hash, prefix: _ } => Some(*hash),
+            P2pkh { ref hash, network: _ } => Some(*hash),
             _ => None,
         }
     }
@@ -540,7 +491,7 @@ pub fn script_hash(&self) -> Option<ScriptHash> {
         use AddressInner::*;
 
         match self.0 {
-            P2sh { ref hash, prefix: _ } => Some(*hash),
+            P2sh { ref hash, network: _ } => Some(*hash),
             _ => None,
         }
     }
@@ -587,9 +538,13 @@ pub fn from_script(script: &Script, network: Network) -> Result<Address, Error>
     pub fn script_pubkey(&self) -> ScriptBuf {
         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),
+            P2pkh { ref hash, network: _ } => ScriptBuf::new_p2pkh(hash),
+            P2sh { ref hash, network: _ } => ScriptBuf::new_p2sh(hash),
+            Segwit { ref program, hrp: _ } => {
+                let prog = program.program();
+                let version = program.version();
+                ScriptBuf::new_witness_program_unchecked(version, prog)
+            }
         }
     }
 
@@ -650,9 +605,9 @@ pub fn is_related_to_xonly_pubkey(&self, xonly_pubkey: &XOnlyPublicKey) -> bool
     pub fn matches_script_pubkey(&self, script: &Script) -> bool {
         use AddressInner::*;
         match self.0 {
-            P2pkh { ref hash, prefix: _ } if script.is_p2pkh() =>
+            P2pkh { ref hash, network: _ } 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() =>
+            P2sh { ref hash, network: _ } 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(),
@@ -671,8 +626,8 @@ pub fn matches_script_pubkey(&self, script: &Script) -> bool {
     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(),
+            P2sh { ref hash, network: _ } => hash.as_ref(),
+            P2pkh { ref hash, network: _ } => hash.as_ref(),
             Segwit { ref program, hrp: _ } => program.program().as_bytes(),
         }
     }
@@ -706,12 +661,12 @@ pub fn assume_checked_ref(&self) -> &Address {
     /// assert!(address.is_valid_for_network(Network::Bitcoin));
     /// assert_eq!(address.is_valid_for_network(Network::Testnet), false);
     /// ```
-    pub fn is_valid_for_network(&self, network: Network) -> bool {
+    pub fn is_valid_for_network(&self, n: Network) -> bool {
         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),
+            P2pkh { hash: _, ref network } => *network == NetworkKind::from(n),
+            P2sh { hash: _, ref network } => *network == NetworkKind::from(n),
+            Segwit { program: _, ref hrp } => *hrp == KnownHrp::from_network(n),
         }
     }
 
@@ -739,8 +694,8 @@ pub fn assume_checked(self) -> Address {
         use AddressInner::*;
 
         let inner = match self.0 {
-            P2pkh { hash, prefix } => P2pkh { hash, prefix },
-            P2sh { hash, prefix } => P2sh { hash, prefix },
+            P2pkh { hash, network } => P2pkh { hash, network },
+            P2sh { hash, network } => P2sh { hash, network },
             Segwit { program, hrp } => Segwit { program, hrp },
         };
         Address(inner, PhantomData)
@@ -801,19 +756,19 @@ fn from_str(s: &str) -> Result<Address<NetworkUnchecked>, ParseError> {
         let inner = match *prefix {
             PUBKEY_ADDRESS_PREFIX_MAIN => {
                 let hash = PubkeyHash::from_byte_array(data);
-                AddressInner::P2pkh { hash, prefix: LegacyP2pkhPrefix::Mainnet }
+                AddressInner::P2pkh { hash, network: NetworkKind::Main }
             }
             PUBKEY_ADDRESS_PREFIX_TEST => {
                 let hash = PubkeyHash::from_byte_array(data);
-                AddressInner::P2pkh { hash, prefix: LegacyP2pkhPrefix::AllTestnets }
+                AddressInner::P2pkh { hash, network: NetworkKind::Test }
             }
             SCRIPT_ADDRESS_PREFIX_MAIN => {
                 let hash = ScriptHash::from_byte_array(data);
-                AddressInner::P2sh { hash, prefix: LegacyP2shPrefix::Mainnet }
+                AddressInner::P2sh { hash, network: NetworkKind::Main }
             }
             SCRIPT_ADDRESS_PREFIX_TEST => {
                 let hash = ScriptHash::from_byte_array(data);
-                AddressInner::P2sh { hash, prefix: LegacyP2shPrefix::AllTestnets }
+                AddressInner::P2sh { hash, network: NetworkKind::Test }
             }
             x => return Err(ParseError::Base58(base58::Error::InvalidAddressVersion(x))),
         };
@@ -868,7 +823,7 @@ fn roundtrips(addr: &Address, network: Network) {
     #[test]
     fn test_p2pkh_address_58() {
         let hash = "162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse::<PubkeyHash>().unwrap();
-        let addr = Address::p2pkh(hash, Bitcoin);
+        let addr = Address::p2pkh(hash, NetworkKind::Main);
 
         assert_eq!(
             addr.script_pubkey(),
@@ -882,13 +837,13 @@ fn test_p2pkh_address_58() {
     #[test]
     fn test_p2pkh_from_key() {
         let key = "048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183".parse::<PublicKey>().unwrap();
-        let addr = Address::p2pkh(key, Bitcoin);
+        let addr = Address::p2pkh(key, NetworkKind::Main);
         assert_eq!(&addr.to_string(), "1QJVDzdqb1VpbDK7uDeyVXy9mR27CJiyhY");
 
         let key = "03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f"
             .parse::<PublicKey>()
             .unwrap();
-        let addr = Address::p2pkh(key, Testnet);
+        let addr = Address::p2pkh(key, NetworkKind::Test);
         assert_eq!(&addr.to_string(), "mqkhEMH6NCeYjFybv7pvFC22MFeaNT9AQC");
         assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
         roundtrips(&addr, Testnet);
@@ -897,7 +852,7 @@ fn test_p2pkh_from_key() {
     #[test]
     fn test_p2sh_address_58() {
         let hash = "162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse::<ScriptHash>().unwrap();
-        let addr = Address::p2sh_from_hash(hash, Bitcoin);
+        let addr = Address::p2sh_from_hash(hash, NetworkKind::Main);
 
         assert_eq!(
             addr.script_pubkey(),
@@ -911,7 +866,7 @@ fn test_p2sh_address_58() {
     #[test]
     fn test_p2sh_parse() {
         let script = ScriptBuf::from_hex("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae").unwrap();
-        let addr = Address::p2sh(&script, Testnet).unwrap();
+        let addr = Address::p2sh(&script, NetworkKind::Test).unwrap();
         assert_eq!(&addr.to_string(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr");
         assert_eq!(addr.address_type(), Some(AddressType::P2sh));
         roundtrips(&addr, Testnet);
@@ -920,7 +875,7 @@ fn test_p2sh_parse() {
     #[test]
     fn test_p2sh_parse_for_large_script() {
         let script = ScriptBuf::from_hex("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123").unwrap();
-        assert_eq!(Address::p2sh(&script, Testnet), Err(Error::ExcessiveScriptSize));
+        assert_eq!(Address::p2sh(&script, NetworkKind::Test), Err(Error::ExcessiveScriptSize));
     }
 
     #[test]
@@ -954,7 +909,7 @@ fn test_p2shwpkh() {
         let key = "026c468be64d22761c30cd2f12cbc7de255d592d7904b1bab07236897cc4c2e766"
             .parse::<CompressedPublicKey>()
             .unwrap();
-        let addr = Address::p2shwpkh(&key, Bitcoin);
+        let addr = Address::p2shwpkh(&key, NetworkKind::Main);
         assert_eq!(&addr.to_string(), "3QBRmWNqqBGme9er7fMkGqtZtp4gjMFxhE");
         assert_eq!(addr.address_type(), Some(AddressType::P2sh));
         roundtrips(&addr, Bitcoin);
@@ -964,7 +919,7 @@ fn test_p2shwpkh() {
     fn test_p2shwsh() {
         // stolen from Bitcoin transaction f9ee2be4df05041d0e0a35d7caa3157495ca4f93b233234c9967b6901dacf7a9
         let script = ScriptBuf::from_hex("522103e5529d8eaa3d559903adb2e881eb06c86ac2574ffa503c45f4e942e2a693b33e2102e5f10fcdcdbab211e0af6a481f5532536ec61a5fdbf7183770cf8680fe729d8152ae").unwrap();
-        let addr = Address::p2shwsh(&script, Bitcoin);
+        let addr = Address::p2shwsh(&script, NetworkKind::Main);
         assert_eq!(&addr.to_string(), "36EqgNnsWW94SreZgBWc1ANC6wpFZwirHr");
         assert_eq!(addr.address_type(), Some(AddressType::P2sh));
         roundtrips(&addr, Bitcoin);
diff --git a/bitcoin/src/bip32.rs b/bitcoin/src/bip32.rs
index ad4dfcf9..5bda3575 100644
--- a/bitcoin/src/bip32.rs
+++ b/bitcoin/src/bip32.rs
@@ -21,7 +21,7 @@
 use crate::base58;
 use crate::crypto::key::{self, Keypair, PrivateKey, CompressedPublicKey};
 use crate::internal_macros::impl_bytes_newtype;
-use crate::network::Network;
+use crate::network::NetworkKind;
 use crate::prelude::*;
 
 /// Version bytes for extended public keys on the Bitcoin network.
@@ -69,7 +69,7 @@ fn from_hmac(hmac: Hmac<sha512::Hash>) -> Self {
 #[cfg_attr(feature = "std", derive(Debug))]
 pub struct Xpriv {
     /// The network this key is to be used on
-    pub network: Network,
+    pub network: NetworkKind,
     /// How many derivations this key is from the master (which is 0)
     pub depth: u8,
     /// Fingerprint of the parent key (0 for master)
@@ -101,8 +101,8 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 /// Extended public key
 #[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)]
 pub struct Xpub {
-    /// The network this key is to be used on
-    pub network: Network,
+    /// The network kind this key is to be used on
+    pub network: NetworkKind,
     /// How many derivations this key is from the master (which is 0)
     pub depth: u8,
     /// Fingerprint of the parent key
@@ -558,13 +558,13 @@ fn from(err: base58::Error) -> Self { Error::Base58(err) }
 
 impl Xpriv {
     /// Construct a new master key from a seed value
-    pub fn new_master(network: Network, seed: &[u8]) -> Result<Xpriv, Error> {
+    pub fn new_master(network: impl Into<NetworkKind>, seed: &[u8]) -> Result<Xpriv, Error> {
         let mut hmac_engine: HmacEngine<sha512::Hash> = HmacEngine::new(b"Bitcoin seed");
         hmac_engine.input(seed);
         let hmac_result: Hmac<sha512::Hash> = Hmac::from_engine(hmac_engine);
 
         Ok(Xpriv {
-            network,
+            network: network.into(),
             depth: 0,
             parent_fingerprint: Default::default(),
             child_number: ChildNumber::from_normal_idx(0)?,
@@ -645,9 +645,9 @@ pub fn decode(data: &[u8]) -> Result<Xpriv, Error> {
         }
 
         let network = if data.starts_with(&VERSION_BYTES_MAINNET_PRIVATE) {
-            Network::Bitcoin
+            NetworkKind::Main
         } else if data.starts_with(&VERSION_BYTES_TESTNETS_PRIVATE) {
-            Network::Testnet
+            NetworkKind::Test
         } else {
             let (b0, b1, b2, b3) = (data[0], data[1], data[2], data[3]);
             return Err(Error::UnknownVersion([b0, b1, b2, b3]));
@@ -671,8 +671,8 @@ pub fn decode(data: &[u8]) -> Result<Xpriv, Error> {
     pub fn encode(&self) -> [u8; 78] {
         let mut ret = [0; 78];
         ret[0..4].copy_from_slice(&match self.network {
-            Network::Bitcoin => VERSION_BYTES_MAINNET_PRIVATE,
-            Network::Testnet | Network::Signet | Network::Regtest => VERSION_BYTES_TESTNETS_PRIVATE,
+            NetworkKind::Main => VERSION_BYTES_MAINNET_PRIVATE,
+            NetworkKind::Test => VERSION_BYTES_TESTNETS_PRIVATE,
         });
         ret[4] = self.depth;
         ret[5..9].copy_from_slice(&self.parent_fingerprint[..]);
@@ -777,9 +777,9 @@ pub fn decode(data: &[u8]) -> Result<Xpub, Error> {
         }
 
         let network = if data.starts_with(&VERSION_BYTES_MAINNET_PUBLIC) {
-            Network::Bitcoin
+            NetworkKind::Main
         } else if data.starts_with(&VERSION_BYTES_TESTNETS_PUBLIC) {
-            Network::Testnet
+            NetworkKind::Test
         } else {
             let (b0, b1, b2, b3) = (data[0], data[1], data[2], data[3]);
             return Err(Error::UnknownVersion([b0, b1, b2, b3]));
@@ -803,8 +803,8 @@ pub fn decode(data: &[u8]) -> Result<Xpub, Error> {
     pub fn encode(&self) -> [u8; 78] {
         let mut ret = [0; 78];
         ret[0..4].copy_from_slice(&match self.network {
-            Network::Bitcoin => VERSION_BYTES_MAINNET_PUBLIC,
-            Network::Testnet | Network::Signet | Network::Regtest => VERSION_BYTES_TESTNETS_PUBLIC,
+            NetworkKind::Main => VERSION_BYTES_MAINNET_PUBLIC,
+            NetworkKind::Test => VERSION_BYTES_TESTNETS_PUBLIC,
         });
         ret[4] = self.depth;
         ret[5..9].copy_from_slice(&self.parent_fingerprint[..]);
@@ -884,7 +884,6 @@ mod tests {
 
     use super::ChildNumber::{Hardened, Normal};
     use super::*;
-    use crate::network::Network::{self, Bitcoin};
 
     #[test]
     fn test_parse_derivation_path() {
@@ -965,7 +964,7 @@ fn test_derivation_path_conversion_index() {
 
     fn test_path<C: secp256k1::Signing + secp256k1::Verification>(
         secp: &Secp256k1<C>,
-        network: Network,
+        network: NetworkKind,
         seed: &[u8],
         path: DerivationPath,
         expected_sk: &str,
@@ -1060,32 +1059,32 @@ fn test_vector_1() {
         let seed = hex!("000102030405060708090a0b0c0d0e0f");
 
         // m
-        test_path(&secp, Bitcoin, &seed, "m".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m".parse().unwrap(),
                   "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
                   "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8");
 
         // m/0h
-        test_path(&secp, Bitcoin, &seed, "m/0h".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m/0h".parse().unwrap(),
                   "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
                   "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw");
 
         // m/0h/1
-        test_path(&secp, Bitcoin, &seed, "m/0h/1".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m/0h/1".parse().unwrap(),
                    "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
                    "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ");
 
         // m/0h/1/2h
-        test_path(&secp, Bitcoin, &seed, "m/0h/1/2h".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m/0h/1/2h".parse().unwrap(),
                   "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
                   "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5");
 
         // m/0h/1/2h/2
-        test_path(&secp, Bitcoin, &seed, "m/0h/1/2h/2".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m/0h/1/2h/2".parse().unwrap(),
                   "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
                   "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV");
 
         // m/0h/1/2h/2/1000000000
-        test_path(&secp, Bitcoin, &seed, "m/0h/1/2h/2/1000000000".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m/0h/1/2h/2/1000000000".parse().unwrap(),
                   "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
                   "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy");
     }
@@ -1096,32 +1095,32 @@ fn test_vector_2() {
         let seed = hex!("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542");
 
         // m
-        test_path(&secp, Bitcoin, &seed, "m".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m".parse().unwrap(),
                   "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
                   "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB");
 
         // m/0
-        test_path(&secp, Bitcoin, &seed, "m/0".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m/0".parse().unwrap(),
                   "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
                   "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH");
 
         // m/0/2147483647h
-        test_path(&secp, Bitcoin, &seed, "m/0/2147483647h".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m/0/2147483647h".parse().unwrap(),
                   "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
                   "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a");
 
         // m/0/2147483647h/1
-        test_path(&secp, Bitcoin, &seed, "m/0/2147483647h/1".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m/0/2147483647h/1".parse().unwrap(),
                   "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
                   "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon");
 
         // m/0/2147483647h/1/2147483646h
-        test_path(&secp, Bitcoin, &seed, "m/0/2147483647h/1/2147483646h".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m/0/2147483647h/1/2147483646h".parse().unwrap(),
                   "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
                   "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL");
 
         // m/0/2147483647h/1/2147483646h/2
-        test_path(&secp, Bitcoin, &seed, "m/0/2147483647h/1/2147483646h/2".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m/0/2147483647h/1/2147483646h/2".parse().unwrap(),
                   "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
                   "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt");
     }
@@ -1132,12 +1131,12 @@ fn test_vector_3() {
         let seed = hex!("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be");
 
         // m
-        test_path(&secp, Bitcoin, &seed, "m".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m".parse().unwrap(),
                   "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6",
                   "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13");
 
         // m/0h
-        test_path(&secp, Bitcoin, &seed, "m/0h".parse().unwrap(),
+        test_path(&secp, NetworkKind::Main, &seed, "m/0h".parse().unwrap(),
                   "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L",
                   "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y");
     }
@@ -1200,7 +1199,7 @@ fn schnorr_broken_privkey_zeros() {
         }
 
         let xpriv = Xpriv {
-            network: Network::Bitcoin,
+            network: NetworkKind::Main,
             depth: 0,
             parent_fingerprint: Default::default(),
             child_number: ChildNumber::Normal { index: 0 },
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index ba7cf541..97e0014e 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -16,7 +16,7 @@
 
 use crate::crypto::ecdsa;
 use crate::internal_macros::impl_asref_push_bytes;
-use crate::network::Network;
+use crate::network::NetworkKind;
 use crate::prelude::*;
 use crate::taproot::{TapNodeHash, TapTweakHash};
 use crate::{base58, io};
@@ -390,8 +390,8 @@ fn from(key: &CompressedPublicKey) -> Self { key.wpubkey_hash() }
 pub struct PrivateKey {
     /// Whether this private key should be serialized as compressed
     pub compressed: bool,
-    /// The network on which this key should be used
-    pub network: Network,
+    /// The network kind on which this key should be used
+    pub network: NetworkKind,
     /// The actual ECDSA key
     pub inner: secp256k1::SecretKey,
 }
@@ -400,20 +400,23 @@ impl PrivateKey {
     /// Constructs new compressed ECDSA private key using the secp256k1 algorithm and
     /// a secure random number generator.
     #[cfg(feature = "rand-std")]
-    pub fn generate(network: Network) -> PrivateKey {
+    pub fn generate(network: impl Into<NetworkKind>) -> PrivateKey {
         let secret_key = secp256k1::SecretKey::new(&mut rand::thread_rng());
-        PrivateKey::new(secret_key, network)
+        PrivateKey::new(secret_key, network.into())
     }
     /// Constructs compressed ECDSA private key from the provided generic Secp256k1 private key
     /// and the specified network
-    pub fn new(key: secp256k1::SecretKey, network: Network) -> PrivateKey {
-        PrivateKey { compressed: true, network, inner: key }
+    pub fn new(key: secp256k1::SecretKey, network: impl Into<NetworkKind>) -> PrivateKey {
+        PrivateKey { compressed: true, network: network.into(), inner: key }
     }
 
     /// Constructs uncompressed (legacy) ECDSA private key from the provided generic Secp256k1
     /// private key and the specified network
-    pub fn new_uncompressed(key: secp256k1::SecretKey, network: Network) -> PrivateKey {
-        PrivateKey { compressed: false, network, inner: key }
+    pub fn new_uncompressed(
+        key: secp256k1::SecretKey,
+        network: impl Into<NetworkKind>,
+    ) -> PrivateKey {
+        PrivateKey { compressed: false, network: network.into(), inner: key }
     }
 
     /// Creates a public key from this private key
@@ -428,17 +431,16 @@ pub fn public_key<C: secp256k1::Signing>(&self, secp: &Secp256k1<C>) -> PublicKe
     pub fn to_bytes(self) -> Vec<u8> { self.inner[..].to_vec() }
 
     /// Deserialize a private key from a slice
-    pub fn from_slice(data: &[u8], network: Network) -> Result<PrivateKey, Error> {
+    pub fn from_slice(data: &[u8], network: impl Into<NetworkKind>) -> Result<PrivateKey, Error> {
         Ok(PrivateKey::new(secp256k1::SecretKey::from_slice(data)?, network))
     }
 
     /// Format the private key to WIF format.
+    #[rustfmt::skip]
     pub fn fmt_wif(&self, fmt: &mut dyn fmt::Write) -> fmt::Result {
         let mut ret = [0; 34];
-        ret[0] = match self.network {
-            Network::Bitcoin => 128,
-            Network::Testnet | Network::Signet | Network::Regtest => 239,
-        };
+        ret[0] = if self.network.is_mainnet() { 128 } else { 239 };
+
         ret[1..33].copy_from_slice(&self.inner[..]);
         let privkey = if self.compressed {
             ret[33] = 1;
@@ -470,8 +472,8 @@ pub fn from_wif(wif: &str) -> Result<PrivateKey, Error> {
         };
 
         let network = match data[0] {
-            128 => Network::Bitcoin,
-            239 => Network::Testnet,
+            128 => NetworkKind::Main,
+            239 => NetworkKind::Test,
             x => {
                 return Err(Error::Base58(base58::Error::InvalidAddressVersion(x)));
             }
@@ -953,14 +955,14 @@ mod tests {
 
     use super::*;
     use crate::address::Address;
-    use crate::network::Network::{Bitcoin, Testnet};
+    use crate::network::NetworkKind;
 
     #[test]
     fn test_key_derivation() {
         // testnet compressed
         let sk =
             PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap();
-        assert_eq!(sk.network, Testnet);
+        assert_eq!(sk.network, NetworkKind::Test);
         assert!(sk.compressed);
         assert_eq!(&sk.to_wif(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy");
 
@@ -977,7 +979,7 @@ fn test_key_derivation() {
         // mainnet uncompressed
         let sk =
             PrivateKey::from_wif("5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3").unwrap();
-        assert_eq!(sk.network, Bitcoin);
+        assert_eq!(sk.network, NetworkKind::Main);
         assert!(!sk.compressed);
         assert_eq!(&sk.to_wif(), "5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3");
 
@@ -1295,7 +1297,7 @@ fn public_key_from_str_wrong_length() {
     fn private_key_debug_is_obfuscated() {
         let sk =
             PrivateKey::from_str("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap();
-        let want = "PrivateKey { compressed: true, network: Testnet, inner: SecretKey(#32014e414fdce702) }";
+        let want = "PrivateKey { compressed: true, network: Test, inner: SecretKey(#32014e414fdce702) }";
         let got = format!("{:?}", sk);
         assert_eq!(got, want)
     }
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index 53b94258..0ebbe5a2 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -129,7 +129,7 @@
     crypto::key::{self, PrivateKey, PubkeyHash, PublicKey, CompressedPublicKey, WPubkeyHash, XOnlyPublicKey},
     crypto::sighash::{self, LegacySighash, SegwitV0Sighash, TapSighash, TapSighashTag},
     merkle_tree::MerkleBlock,
-    network::Network,
+    network::{Network, NetworkKind},
     pow::{CompactTarget, Target, Work},
     psbt::Psbt,
     sighash::{EcdsaSighashType, TapSighashType},
diff --git a/bitcoin/src/network.rs b/bitcoin/src/network.rs
index 32102715..9c76d70f 100644
--- a/bitcoin/src/network.rs
+++ b/bitcoin/src/network.rs
@@ -31,6 +31,33 @@
 use crate::p2p::Magic;
 use crate::prelude::{String, ToOwned};
 
+/// What kind of network we are on.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum NetworkKind {
+    /// The Bitcoin mainnet network.
+    Main,
+    /// Some kind of testnet network.
+    Test,
+}
+
+// We explicitly do not provide `is_testnet`, using `!network.is_mainnet()` is less
+// ambiguous due to confusion caused by signet/testnet/regtest.
+impl NetworkKind {
+    /// Returns true if this is real mainnet bitcoin.
+    pub fn is_mainnet(&self) -> bool { *self == NetworkKind::Main }
+}
+
+impl From<Network> for NetworkKind {
+    fn from(n: Network) -> Self {
+        use Network::*;
+
+        match n {
+            Bitcoin => NetworkKind::Main,
+            Testnet | Signet | Regtest => NetworkKind::Test,
+        }
+    }
+}
+
 /// The cryptocurrency network to act on.
 #[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 925ea413..4581e770 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -1014,7 +1014,7 @@ mod tests {
     use crate::blockdata::script::ScriptBuf;
     use crate::blockdata::transaction::{self, OutPoint, Sequence, Transaction, TxIn, TxOut};
     use crate::blockdata::witness::Witness;
-    use crate::network::Network::Bitcoin;
+    use crate::network::NetworkKind;
     use crate::psbt::map::{Input, Output};
     use crate::psbt::raw;
     use crate::psbt::serialize::{Deserialize, Serialize};
@@ -1151,7 +1151,7 @@ fn serialize_then_deserialize_output() {
 
         let mut hd_keypaths: BTreeMap<secp256k1::PublicKey, KeySource> = Default::default();
 
-        let mut sk: Xpriv = Xpriv::new_master(Bitcoin, &seed).unwrap();
+        let mut sk: Xpriv = Xpriv::new_master(NetworkKind::Main, &seed).unwrap();
 
         let fprint = sk.fingerprint(secp);
 
@@ -1926,7 +1926,7 @@ fn gen_keys() -> (PrivateKey, PublicKey, Secp256k1<All>) {
         let secp = Secp256k1::new();
 
         let sk = SecretKey::new(&mut thread_rng());
-        let priv_key = PrivateKey::new(sk, crate::Network::Regtest);
+        let priv_key = PrivateKey::new(sk, NetworkKind::Test);
         let pk = PublicKey::from_private_key(&secp, &priv_key);
 
         (priv_key, pk, secp)
diff --git a/bitcoin/src/sign_message.rs b/bitcoin/src/sign_message.rs
index c543c4ff..c16bc810 100644
--- a/bitcoin/src/sign_message.rs
+++ b/bitcoin/src/sign_message.rs
@@ -226,7 +226,7 @@ fn test_message_signature() {
 
         use secp256k1;
 
-        use crate::{Address, AddressType, Network};
+        use crate::{Address, AddressType, Network, NetworkKind};
 
         let secp = secp256k1::Secp256k1::new();
         let message = "rust-bitcoin MessageSignature test";
@@ -243,12 +243,14 @@ fn test_message_signature() {
             .try_into()
             .expect("compressed was set to true");
 
+        let p2pkh = Address::p2pkh(pubkey, NetworkKind::Main);
+        assert_eq!(signature2.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(true));
         let p2wpkh = Address::p2wpkh(&pubkey, Network::Bitcoin);
         assert_eq!(
             signature2.is_signed_by_address(&secp, &p2wpkh, msg_hash),
             Err(MessageSignatureError::UnsupportedAddressType(AddressType::P2wpkh))
         );
-        let p2shwpkh = Address::p2shwpkh(&pubkey, Network::Bitcoin);
+        let p2shwpkh = Address::p2shwpkh(&pubkey, NetworkKind::Main);
         assert_eq!(
             signature2.is_signed_by_address(&secp, &p2shwpkh, msg_hash),
             Err(MessageSignatureError::UnsupportedAddressType(AddressType::P2sh))
@@ -266,7 +268,7 @@ fn test_incorrect_message_signature() {
         use secp256k1;
 
         use crate::crypto::key::PublicKey;
-        use crate::{Address, Network};
+        use crate::{Address, NetworkKind};
 
         let secp = secp256k1::Secp256k1::new();
         let message = "a different message from what was signed";
@@ -283,7 +285,7 @@ fn test_incorrect_message_signature() {
             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, NetworkKind::Main);
         assert_eq!(signature.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(false));
     }
 }


Test patch:
diff --git a/bitcoin/tests/psbt.rs b/bitcoin/tests/psbt.rs
index 7ad4bda5..f8d51b13 100644
--- a/bitcoin/tests/psbt.rs
+++ b/bitcoin/tests/psbt.rs
@@ -13,12 +13,10 @@
 use bitcoin::script::PushBytes;
 use bitcoin::secp256k1::{self, Secp256k1};
 use bitcoin::{
-    absolute, Amount, Denomination, Network, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence,
-    Transaction, TxIn, TxOut, Witness,
+    absolute, Amount, Denomination, NetworkKind, OutPoint, PrivateKey, PublicKey, ScriptBuf,
+    Sequence, Transaction, TxIn, TxOut, Witness,
 };
 
-const NETWORK: Network = Network::Testnet;
-
 #[track_caller]
 fn hex_psbt(s: &str) -> Psbt {
     let v: Vec<u8> = Vec::from_hex(s).expect("valid hex digits");
@@ -124,7 +122,7 @@ fn build_extended_private_key() -> Xpriv {
     let xpriv = Xpriv::from_str(extended_private_key).unwrap();
 
     let sk = PrivateKey::from_wif(seed).unwrap();
-    let seeded = Xpriv::new_master(NETWORK, &sk.inner.secret_bytes()).unwrap();
+    let seeded = Xpriv::new_master(NetworkKind::Test, &sk.inner.secret_bytes()).unwrap();
     assert_eq!(xpriv, seeded);
 
     xpriv
diff --git a/bitcoin/tests/serde.rs b/bitcoin/tests/serde.rs
index 430e9b6c..04686a7c 100644
--- a/bitcoin/tests/serde.rs
+++ b/bitcoin/tests/serde.rs
@@ -38,7 +38,7 @@
 use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
 use bitcoin::taproot::{self, ControlBlock, LeafVersion, TapTree, TaprootBuilder};
 use bitcoin::{
-    ecdsa, transaction, Address, Amount, Block, Network, OutPoint, PrivateKey, PublicKey,
+    ecdsa, transaction, Address, Amount, Block, NetworkKind, OutPoint, PrivateKey, PublicKey,
     ScriptBuf, Sequence, Target, Transaction, TxIn, TxOut, Txid, Work,
 };
 
@@ -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, NetworkKind::Main);
 
     let got = serialize(&addr).unwrap();
     let want = include_bytes!("data/serde/address_bincode") as &[_];