diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs index ae48a9b3f..9fa8dd4d3 100644 --- a/bitcoin/src/address/mod.rs +++ b/bitcoin/src/address/mod.rs @@ -194,7 +194,7 @@ impl fmt::Display for AddressInner { pub enum KnownHrp { /// The main Bitcoin network. Mainnet, - /// The test networks, testnet and signet. + /// The test networks, testnet (testnet3), and signet. Testnets, /// The regtest network. Regtest, @@ -207,7 +207,7 @@ impl KnownHrp { match network { Bitcoin => Self::Mainnet, - Testnet | Signet => Self::Testnets, + Testnet(_) | Signet => Self::Testnets, Regtest => Self::Regtest, } } @@ -708,11 +708,11 @@ impl Address { /// network a simple comparison is not enough anymore. Instead this function can be used. /// /// ```rust - /// use bitcoin::{Address, Network}; + /// use bitcoin::{Address, Network, TestnetVersion}; /// use bitcoin::address::NetworkUnchecked; /// /// let address: Address = "2N83imGV3gPwBzKJQvWJ7cRUY2SpUyU6A5e".parse().unwrap(); - /// assert!(address.is_valid_for_network(Network::Testnet)); + /// assert!(address.is_valid_for_network(Network::Testnet(TestnetVersion::V3))); /// assert!(address.is_valid_for_network(Network::Regtest)); /// assert!(address.is_valid_for_network(Network::Signet)); /// @@ -720,7 +720,6 @@ impl Address { /// /// let address: Address = "32iVBEu4dxkUQk9dJbZUiBiQdmypcEyJRf".parse().unwrap(); /// 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, n: Network) -> bool { use AddressInner::*; @@ -942,7 +941,7 @@ mod tests { let addr = Address::p2pkh(key, NetworkKind::Test); assert_eq!(&addr.to_string(), "mqkhEMH6NCeYjFybv7pvFC22MFeaNT9AQC"); assert_eq!(addr.address_type(), Some(AddressType::P2pkh)); - roundtrips(&addr, Testnet); + roundtrips(&addr, Testnet(crate::TestnetVersion::V3)); } #[test] @@ -965,7 +964,7 @@ mod tests { 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); + roundtrips(&addr, Testnet(crate::TestnetVersion::V3)); } #[test] @@ -1282,7 +1281,7 @@ mod tests { let address = address_string .parse::>() .expect("address") - .require_network(Network::Testnet) + .require_network(Network::Testnet(crate::TestnetVersion::V3)) .expect("testnet"); let pubkey_string = "04e96e22004e3db93530de27ccddfdf1463975d2138ac018fc3e7ba1a2e5e0aad8e424d0b55e2436eb1d0dcd5cb2b8bcc6d53412c22f358de57803a6a655fbbd04"; diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs index c5ddf11fa..a093b266c 100644 --- a/bitcoin/src/blockdata/block.rs +++ b/bitcoin/src/blockdata/block.rs @@ -546,7 +546,7 @@ mod tests { // Check testnet block 000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b #[test] fn segwit_block_test() { - let params = Params::new(Network::Testnet); + let params = Params::new(Network::Testnet(crate::TestnetVersion::V3)); let segwit_block = include_bytes!("../../tests/data/testnet_block_000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b.raw").to_vec(); let decode: Result = deserialize(&segwit_block); diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs index 8e828304b..8948f96ef 100644 --- a/bitcoin/src/blockdata/constants.rs +++ b/bitcoin/src/blockdata/constants.rs @@ -17,7 +17,7 @@ use crate::opcodes::all::*; use crate::pow::CompactTarget; use crate::transaction::{self, OutPoint, Transaction, TxIn, TxOut}; use crate::witness::Witness; -use crate::{script, Amount, BlockHash, Sequence}; +use crate::{script, Amount, BlockHash, Sequence, TestnetVersion}; /// How many seconds between blocks we expect on average. pub const TARGET_BLOCK_SPACING: u32 = 600; @@ -94,7 +94,6 @@ fn bitcoin_genesis_tx() -> Transaction { sequence: Sequence::MAX, witness: Witness::default(), }); - // Outputs let out_script = script::Builder::new().push_slice(GENESIS_OUTPUT_PK).push_opcode(OP_CHECKSIG).into_script(); @@ -106,10 +105,12 @@ fn bitcoin_genesis_tx() -> Transaction { /// Constructs and returns the genesis block. pub fn genesis_block(params: impl AsRef) -> Block { + let params_ref = params.as_ref(); let txdata = vec![bitcoin_genesis_tx()]; let hash: sha256d::Hash = txdata[0].compute_txid().into(); - let merkle_root = hash.into(); - match params.as_ref().network { + let merkle_root: crate::TxMerkleNode = hash.into(); + + match params_ref.network { Network::Bitcoin => Block { header: block::Header { version: block::Version::ONE, @@ -121,7 +122,7 @@ pub fn genesis_block(params: impl AsRef) -> Block { }, txdata, }, - Network::Testnet => Block { + Network::Testnet(TestnetVersion::V3) => Block { header: block::Header { version: block::Version::ONE, prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH, @@ -170,7 +171,7 @@ impl ChainHash { 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247, 79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, ]); - /// `ChainHash` for testnet bitcoin. + /// `ChainHash` for testnet3 bitcoin. pub const TESTNET: Self = Self([ 67, 73, 127, 215, 248, 38, 149, 113, 8, 244, 163, 15, 217, 206, 195, 174, 186, 121, 151, 32, 132, 233, 14, 173, 1, 234, 51, 9, 0, 0, 0, 0, @@ -191,9 +192,12 @@ impl ChainHash { /// See [BOLT 0](https://github.com/lightning/bolts/blob/ffeece3dab1c52efdb9b53ae476539320fa44938/00-introduction.md#chain_hash) /// for specification. pub fn using_genesis_block(params: impl AsRef) -> Self { - let network = params.as_ref().network; - let hashes = [Self::BITCOIN, Self::TESTNET, Self::SIGNET, Self::REGTEST]; - hashes[network as usize] + match params.as_ref().network { + Network::Bitcoin => Self::BITCOIN, + Network::Testnet(TestnetVersion::V3) => Self::TESTNET, + Network::Signet => Self::SIGNET, + Network::Regtest => Self::REGTEST, + } } /// Returns the hash of the `network` genesis block for use as a chain hash. @@ -201,8 +205,12 @@ impl ChainHash { /// See [BOLT 0](https://github.com/lightning/bolts/blob/ffeece3dab1c52efdb9b53ae476539320fa44938/00-introduction.md#chain_hash) /// for specification. pub const fn using_genesis_block_const(network: Network) -> Self { - let hashes = [Self::BITCOIN, Self::TESTNET, Self::SIGNET, Self::REGTEST]; - hashes[network as usize] + match network { + Network::Bitcoin => Self::BITCOIN, + Network::Testnet(TestnetVersion::V3) => Self::TESTNET, + Network::Signet => Self::SIGNET, + Network::Regtest => Self::REGTEST, + } } /// Converts genesis block hash into `ChainHash`. @@ -332,7 +340,7 @@ mod test { #[allow(unreachable_patterns)] // This is specifically trying to catch later added variants. match network { Network::Bitcoin => {}, - Network::Testnet => {}, + Network::Testnet(TestnetVersion::V3) => {}, Network::Signet => {}, Network::Regtest => {}, _ => panic!("update ChainHash::using_genesis_block and chain_hash_genesis_block with new variants"), @@ -352,7 +360,7 @@ mod test { chain_hash_genesis_block! { mainnet_chain_hash_genesis_block, Network::Bitcoin; - testnet_chain_hash_genesis_block, Network::Testnet; + testnet_chain_hash_genesis_block, Network::Testnet(TestnetVersion::V3); signet_chain_hash_genesis_block, Network::Signet; regtest_chain_hash_genesis_block, Network::Regtest; } diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index 46f352a95..a030bf3ea 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -135,7 +135,7 @@ pub use crate::{ crypto::key::{self, PrivateKey, PubkeyHash, PublicKey, CompressedPublicKey, WPubkeyHash, XOnlyPublicKey}, crypto::sighash::{self, LegacySighash, SegwitV0Sighash, TapSighash, TapSighashTag}, merkle_tree::{MerkleBlock, TxMerkleNode, WitnessMerkleNode}, - network::{Network, NetworkKind}, + network::{Network, NetworkKind, TestnetVersion}, network::params::{self, Params}, pow::{CompactTarget, Target, Work}, psbt::Psbt, diff --git a/bitcoin/src/network/mod.rs b/bitcoin/src/network/mod.rs index 26c517ab3..6db563003 100644 --- a/bitcoin/src/network/mod.rs +++ b/bitcoin/src/network/mod.rs @@ -25,7 +25,7 @@ use core::str::FromStr; use internals::write_err; #[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use crate::constants::ChainHash; use crate::p2p::Magic; @@ -57,27 +57,70 @@ impl From for NetworkKind { match n { Bitcoin => NetworkKind::Main, - Testnet | Signet | Regtest => NetworkKind::Test, + Testnet(_) | Signet | Regtest => NetworkKind::Test, } } } +/// The testnet version to act on. +#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)] +#[non_exhaustive] +pub enum TestnetVersion { + /// Testnet version 3. + V3, +} + /// The cryptocurrency network to act on. #[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] #[non_exhaustive] pub enum Network { /// Mainnet Bitcoin. Bitcoin, /// Bitcoin's testnet network. - Testnet, + Testnet(TestnetVersion), /// Bitcoin's signet network. Signet, /// Bitcoin's regtest network. Regtest, } +#[cfg(feature = "serde")] +impl Serialize for Network { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.as_display_str()) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Network { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct NetworkVisitor; + + impl<'de> Visitor<'de> for NetworkVisitor { + type Value = Network; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a valid network identifier") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + Network::from_str(value).map_err(E::custom) + } + } + + deserializer.deserialize_str(NetworkVisitor) + } +} + impl Network { /// Creates a `Network` from the magic bytes. /// @@ -118,7 +161,8 @@ impl Network { pub fn to_core_arg(self) -> &'static str { match self { Network::Bitcoin => "main", - Network::Testnet => "test", + // For user-side compatibility, testnet3 is retained as test + Network::Testnet(TestnetVersion::V3) => "test", Network::Signet => "signet", Network::Regtest => "regtest", } @@ -138,7 +182,7 @@ impl Network { let network = match core_arg { "main" => Bitcoin, - "test" => Testnet, + "test" => Testnet(TestnetVersion::V3), "signet" => Signet, "regtest" => Regtest, _ => return Err(ParseNetworkError(core_arg.to_owned())), @@ -175,13 +219,23 @@ impl Network { /// Returns the associated network parameters. pub const fn params(self) -> &'static Params { - const PARAMS: [Params; 4] = [ - Params::new(Network::Bitcoin), - Params::new(Network::Testnet), - Params::new(Network::Signet), - Params::new(Network::Regtest), - ]; - &PARAMS[self as usize] + match self { + Network::Bitcoin => &Params::BITCOIN, + Network::Testnet(TestnetVersion::V3) => &Params::TESTNET, + Network::Signet => &Params::SIGNET, + Network::Regtest => &Params::REGTEST, + } + } + + /// Returns a string representation of the `Network` enum variant. + /// This is useful for displaying the network type as a string. + const fn as_display_str(self) -> &'static str { + match self { + Network::Bitcoin => "bitcoin", + Network::Testnet(TestnetVersion::V3) => "testnet", + Network::Signet => "signet", + Network::Regtest => "regtest", + } } } @@ -249,31 +303,26 @@ impl FromStr for Network { type Err = ParseNetworkError; #[inline] + /// Parses a string slice into a `Network` enum variant. + /// This function is used internally to convert a string representation + /// of a network type into its corresponding `Network` enum variant. + /// Returns `Ok(Network)` if the string matches a known network type, + /// otherwise returns `Err(ParseNetworkError)`. fn from_str(s: &str) -> Result { - use Network::*; - - let network = match s { - "bitcoin" => Bitcoin, - "testnet" => Testnet, - "signet" => Signet, - "regtest" => Regtest, - _ => return Err(ParseNetworkError(s.to_owned())), - }; - Ok(network) + match s { + "bitcoin" => Ok(Network::Bitcoin), + // For user-side compatibility, testnet3 is retained as testnet + "testnet" => Ok(Network::Testnet(TestnetVersion::V3)), + "signet" => Ok(Network::Signet), + "regtest" => Ok(Network::Regtest), + _ => Err(ParseNetworkError(s.to_owned())), + } } } impl fmt::Display for Network { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - use Network::*; - - let s = match *self { - Bitcoin => "bitcoin", - Testnet => "testnet", - Signet => "signet", - Regtest => "regtest", - }; - write!(f, "{}", s) + write!(f, "{}", self.as_display_str()) } } @@ -300,7 +349,7 @@ impl TryFrom for Network { match chain_hash { // Note: any new network entries must be matched against here. ChainHash::BITCOIN => Ok(Network::Bitcoin), - ChainHash::TESTNET => Ok(Network::Testnet), + ChainHash::TESTNET => Ok(Network::Testnet(TestnetVersion::V3)), ChainHash::SIGNET => Ok(Network::Signet), ChainHash::REGTEST => Ok(Network::Regtest), _ => Err(UnknownChainHashError(chain_hash)), @@ -317,12 +366,18 @@ mod tests { #[test] fn serialize_test() { assert_eq!(serialize(&Network::Bitcoin.magic()), &[0xf9, 0xbe, 0xb4, 0xd9]); - assert_eq!(serialize(&Network::Testnet.magic()), &[0x0b, 0x11, 0x09, 0x07]); + assert_eq!( + serialize(&Network::Testnet(crate::TestnetVersion::V3).magic()), + &[0x0b, 0x11, 0x09, 0x07] + ); assert_eq!(serialize(&Network::Signet.magic()), &[0x0a, 0x03, 0xcf, 0x40]); assert_eq!(serialize(&Network::Regtest.magic()), &[0xfa, 0xbf, 0xb5, 0xda]); assert_eq!(deserialize(&[0xf9, 0xbe, 0xb4, 0xd9]).ok(), Some(Network::Bitcoin.magic())); - assert_eq!(deserialize(&[0x0b, 0x11, 0x09, 0x07]).ok(), Some(Network::Testnet.magic())); + assert_eq!( + deserialize(&[0x0b, 0x11, 0x09, 0x07]).ok(), + Some(Network::Testnet(crate::TestnetVersion::V3).magic()) + ); assert_eq!(deserialize(&[0x0a, 0x03, 0xcf, 0x40]).ok(), Some(Network::Signet.magic())); assert_eq!(deserialize(&[0xfa, 0xbf, 0xb5, 0xda]).ok(), Some(Network::Regtest.magic())); } @@ -330,12 +385,15 @@ mod tests { #[test] fn string_test() { assert_eq!(Network::Bitcoin.to_string(), "bitcoin"); - assert_eq!(Network::Testnet.to_string(), "testnet"); + assert_eq!(Network::Testnet(crate::TestnetVersion::V3).to_string(), "testnet"); assert_eq!(Network::Regtest.to_string(), "regtest"); assert_eq!(Network::Signet.to_string(), "signet"); assert_eq!("bitcoin".parse::().unwrap(), Network::Bitcoin); - assert_eq!("testnet".parse::().unwrap(), Network::Testnet); + assert_eq!( + "testnet".parse::().unwrap(), + Network::Testnet(crate::TestnetVersion::V3) + ); assert_eq!("regtest".parse::().unwrap(), Network::Regtest); assert_eq!("signet".parse::().unwrap(), Network::Signet); assert!("fakenet".parse::().is_err()); @@ -386,8 +444,12 @@ mod tests { #[cfg(feature = "serde")] fn serde_roundtrip() { use Network::*; - let tests = - [(Bitcoin, "bitcoin"), (Testnet, "testnet"), (Signet, "signet"), (Regtest, "regtest")]; + let tests = vec![ + (Bitcoin, "bitcoin"), + (Testnet(crate::TestnetVersion::V3), "testnet"), + (Signet, "signet"), + (Regtest, "regtest"), + ]; for tc in tests { let network = tc.0; @@ -405,7 +467,7 @@ mod tests { fn from_to_core_arg() { let expected_pairs = [ (Network::Bitcoin, "main"), - (Network::Testnet, "test"), + (Network::Testnet(crate::TestnetVersion::V3), "test"), (Network::Regtest, "regtest"), (Network::Signet, "signet"), ]; diff --git a/bitcoin/src/network/params.rs b/bitcoin/src/network/params.rs index ca93169c6..8414c955c 100644 --- a/bitcoin/src/network/params.rs +++ b/bitcoin/src/network/params.rs @@ -73,6 +73,7 @@ use crate::network::Network; #[cfg(doc)] use crate::pow::CompactTarget; use crate::pow::Target; +use crate::TestnetVersion; /// Parameters that influence chain consensus. #[non_exhaustive] @@ -158,7 +159,7 @@ impl Params { /// The testnet parameters. pub const TESTNET: Params = Params { - network: Network::Testnet, + network: Network::Testnet(TestnetVersion::V3), bip16_time: 1333238400, // Apr 1 2012 bip34_height: BlockHeight::from_u32(21111), // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8 bip65_height: BlockHeight::from_u32(581885), // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6 @@ -211,7 +212,7 @@ impl Params { pub const fn new(network: Network) -> Self { match network { Network::Bitcoin => Params::MAINNET, - Network::Testnet => Params::TESTNET, + Network::Testnet(TestnetVersion::V3) => Params::TESTNET, Network::Signet => Params::SIGNET, Network::Regtest => Params::REGTEST, } @@ -247,7 +248,7 @@ impl AsRef for Network { fn as_ref(&self) -> &Params { match *self { Network::Bitcoin => &MAINNET, - Network::Testnet => &TESTNET, + Network::Testnet(TestnetVersion::V3) => &TESTNET, Network::Signet => &SIGNET, Network::Regtest => ®TEST, } diff --git a/bitcoin/src/p2p/mod.rs b/bitcoin/src/p2p/mod.rs index a682ca398..eea926043 100644 --- a/bitcoin/src/p2p/mod.rs +++ b/bitcoin/src/p2p/mod.rs @@ -28,7 +28,7 @@ use internals::{debug_from_display, impl_to_hex_from_lower_hex, write_err}; use io::{BufRead, Write}; use crate::consensus::encode::{self, Decodable, Encodable}; -use crate::network::{Network, Params}; +use crate::network::{Network, Params, TestnetVersion}; use crate::prelude::{Borrow, BorrowMut, String, ToOwned}; #[rustfmt::skip] @@ -216,7 +216,7 @@ pub struct Magic([u8; 4]); impl Magic { /// Bitcoin mainnet network magic bytes. pub const BITCOIN: Self = Self([0xF9, 0xBE, 0xB4, 0xD9]); - /// Bitcoin testnet network magic bytes. + /// Bitcoin testnet3 network magic bytes. pub const TESTNET: Self = Self([0x0B, 0x11, 0x09, 0x07]); /// Bitcoin signet network magic bytes. pub const SIGNET: Self = Self([0x0A, 0x03, 0xCF, 0x40]); @@ -245,12 +245,12 @@ impl FromStr for Magic { } macro_rules! generate_network_magic_conversion { - ($(Network::$network:ident => Magic::$magic:ident,)*) => { + ($(Network::$network:ident$((TestnetVersion::$testnet_version:ident))? => Magic::$magic:ident,)*) => { impl From for Magic { fn from(network: Network) -> Magic { match network { $( - Network::$network => Magic::$magic, + Network::$network$((TestnetVersion::$testnet_version))? => Magic::$magic, )* } } @@ -262,7 +262,7 @@ macro_rules! generate_network_magic_conversion { fn try_from(magic: Magic) -> Result { match magic { $( - Magic::$magic => Ok(Network::$network), + Magic::$magic => Ok(Network::$network$((TestnetVersion::$testnet_version))?), )* _ => Err(UnknownMagicError(magic)), } @@ -270,10 +270,11 @@ macro_rules! generate_network_magic_conversion { } }; } - +// Generate conversion functions for all known networks. +// `Network -> Magic` and `Magic -> Network` generate_network_magic_conversion! { Network::Bitcoin => Magic::BITCOIN, - Network::Testnet => Magic::TESTNET, + Network::Testnet(TestnetVersion::V3) => Magic::TESTNET, Network::Signet => Magic::SIGNET, Network::Regtest => Magic::REGTEST, } @@ -430,7 +431,7 @@ mod tests { fn magic_from_str() { let known_network_magic_strs = [ ("f9beb4d9", Network::Bitcoin), - ("0b110907", Network::Testnet), + ("0b110907", Network::Testnet(TestnetVersion::V3)), ("fabfb5da", Network::Regtest), ("0a03cf40", Network::Signet), ]; diff --git a/bitcoin/tests/psbt-sign-taproot.rs b/bitcoin/tests/psbt-sign-taproot.rs index 6f4563988..3d5a71d77 100644 --- a/bitcoin/tests/psbt-sign-taproot.rs +++ b/bitcoin/tests/psbt-sign-taproot.rs @@ -94,7 +94,7 @@ fn psbt_sign_taproot() { // let keystore = Keystore { mfp: mfp.parse::().unwrap(), - sk: PrivateKey::new(kp.secret_key(), Network::Testnet), + sk: PrivateKey::new(kp.secret_key(), Network::Testnet(bitcoin::TestnetVersion::V3)), }; let _ = psbt_key_path_spend.sign(&keystore, secp); @@ -124,7 +124,7 @@ fn psbt_sign_taproot() { let keystore = Keystore { mfp: mfp.parse::().unwrap(), - sk: PrivateKey::new(kp.secret_key(), Network::Testnet), + sk: PrivateKey::new(kp.secret_key(), Network::Testnet(bitcoin::TestnetVersion::V3)), }; // @@ -195,7 +195,7 @@ fn create_taproot_tree( fn create_p2tr_address(tree: TaprootSpendInfo) -> Address { let output_key = tree.output_key(); - Address::p2tr_tweaked(output_key, Network::Testnet) + Address::p2tr_tweaked(output_key, Network::Testnet(bitcoin::TestnetVersion::V3)) } fn create_psbt_for_taproot_key_path_spend(