What changed, and why it matters
This commit is a routine internal code reorganization. It removes the definitions of Bitcoin network types (like Mainnet, Testnet, Signet) from the main `bitcoin` crate and instead re-exports them from a new dedicated `bitcoin-network-kind` crate. It also adds fallback handling for any future testnet versions. There is no indication this fixes a security vulnerability.
No security action required. Treat as normal maintenance/refactoring commit. Reviewers may want to verify that the new `bitcoin-network-kind` crate preserves identical serialization and parsing behavior, and that the `Network::Testnet(_)` fallback is intentional and documented.
Security signals we found
No security-relevant signals present
Refactoring / crate split only
Adds wildcard fallback match arm for future TestnetVersion variants
Evidence from the diff
The patch refactors bitcoin::network by deleting the local Network, NetworkKind, TestnetVersion, and ParseNetworkError definitions and replacing them with pub use network::{...} re-exports from the new bitcoin-network-kind crate. Cargo manifests and feature flags are updated accordingly. Additionally, match arms for Network::Testnet(_) are added in constants.rs, params.rs, and network/mod.rs to treat unspecified future testnet versions as TESTNET3. No security bug is described or evident in the diff.
Changed components
bitcoin/src/network/mod.rsbitcoin/src/network/params.rsbitcoin/src/blockdata/constants.rsbitcoin/Cargo.tomlCargo-minimal.lockCargo-recent.lockInspect captured patch +27 / −323
diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index 950ab0a6..d4954011 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -57,6 +57,7 @@ dependencies = [
"bitcoin-consensus-encoding",
"bitcoin-internals",
"bitcoin-io",
+ "bitcoin-network-kind",
"bitcoin-primitives",
"bitcoin-units",
"bitcoin_hashes",
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index 6749a667..e90d5e33 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -56,6 +56,7 @@ dependencies = [
"bitcoin-consensus-encoding",
"bitcoin-internals",
"bitcoin-io",
+ "bitcoin-network-kind",
"bitcoin-primitives",
"bitcoin-units",
"bitcoin_hashes",
diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml
index 54f8a4ae..477e2b8a 100644
--- a/bitcoin/Cargo.toml
+++ b/bitcoin/Cargo.toml
@@ -16,9 +16,9 @@ exclude = ["tests", "contrib"]
# If you change features or optional dependencies in any way please update the "# Cargo features" section in lib.rs as well.
[features]
default = [ "std", "secp-recovery" ]
-std = ["base58/std", "bech32/std", "encoding/std", "hashes/std", "hex/std", "internals/std", "io/std", "primitives/std", "secp256k1/std", "units/std", "base64?/std", "bitcoinconsensus?/std"]
+std = ["base58/std", "bech32/std", "encoding/std", "hashes/std", "hex/std", "internals/std", "io/std", "network/std", "primitives/std", "secp256k1/std", "units/std", "base64?/std", "bitcoinconsensus?/std"]
rand = ["secp256k1/rand"]
-serde = ["base64", "dep:serde", "hashes/serde", "internals/serde", "primitives/serde", "secp256k1/serde", "units/serde"]
+serde = ["base64", "dep:serde", "hashes/serde", "internals/serde", "network/serde", "primitives/serde", "secp256k1/serde", "units/serde"]
secp-global-context = ["secp256k1/global-context"]
secp-lowmemory = ["secp256k1/lowmemory"]
secp-recovery = ["secp256k1/recovery"]
@@ -32,6 +32,7 @@ encoding = { package = "bitcoin-consensus-encoding", path = "../consensus_encodi
hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = ["alloc"] }
internals = { package = "bitcoin-internals", path = "../internals", version = "0.5.0", features = ["alloc", "hex"] }
io = { package = "bitcoin-io", path = "../io", version = "=0.4.0-rc.0", default-features = false, features = ["alloc", "hashes"] }
+network = { package = "bitcoin-network-kind", path = "../network", version = "0.1.0", default-features = false, features = ["alloc"]}
primitives = { package = "bitcoin-primitives", path = "../primitives", version = "=1.0.0-rc.2", default-features = false, features = ["alloc", "hex"] }
secp256k1 = { version = "0.32.0-beta.2", default-features = false, features = ["alloc"] }
units = { package = "bitcoin-units", path = "../units", version = "=1.0.0-rc.4", default-features = false, features = ["alloc"] }
diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs
index d44d35ae..92cc1784 100644
--- a/bitcoin/src/blockdata/constants.rs
+++ b/bitcoin/src/blockdata/constants.rs
@@ -162,6 +162,18 @@ pub fn genesis_block(params: impl AsRef<Params>) -> Block<Checked> {
transactions,
)
.assume_checked(witness_root),
+ Network::Testnet(_) => Block::new_unchecked(
+ block::Header {
+ version: block::Version::ONE,
+ prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
+ merkle_root,
+ time: BlockTime::from_u32(1296688602),
+ bits: CompactTarget::from_consensus(0x1d00ffff),
+ nonce: 414098458,
+ },
+ transactions,
+ )
+ .assume_checked(witness_root),
Network::Signet => Block::new_unchecked(
block::Header {
version: block::Version::ONE,
@@ -238,6 +250,7 @@ impl ChainHash {
Network::Bitcoin => Self::BITCOIN,
Network::Testnet(TestnetVersion::V3) => Self::TESTNET3,
Network::Testnet(TestnetVersion::V4) => Self::TESTNET4,
+ Network::Testnet(_) => Self::TESTNET3,
Network::Signet => Self::SIGNET,
Network::Regtest => Self::REGTEST,
}
@@ -252,6 +265,7 @@ impl ChainHash {
Network::Bitcoin => Self::BITCOIN,
Network::Testnet(TestnetVersion::V3) => Self::TESTNET3,
Network::Testnet(TestnetVersion::V4) => Self::TESTNET4,
+ Network::Testnet(_) => Self::TESTNET3,
Network::Signet => Self::SIGNET,
Network::Regtest => Self::REGTEST,
}
diff --git a/bitcoin/src/network/mod.rs b/bitcoin/src/network/mod.rs
index 53dbcfe3..165ecb78 100644
--- a/bitcoin/src/network/mod.rs
+++ b/bitcoin/src/network/mod.rs
@@ -9,171 +9,16 @@
pub mod params;
use core::fmt;
-use core::str::FromStr;
-
-use internals::write_err;
-#[cfg(feature = "serde")]
-use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use crate::constants::ChainHash;
-use crate::prelude::{String, ToOwned};
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
pub use self::params::Params;
-
-/// 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 == Self::Main }
-}
-
-impl From<Network> for NetworkKind {
- fn from(n: Network) -> Self {
- use Network::*;
-
- match n {
- Bitcoin => Self::Main,
- Testnet(_) | Signet | Regtest => Self::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,
- /// Testnet version 4.
- V4,
-}
-
-/// The cryptocurrency network to act on.
-///
-/// This is an exhaustive enum, meaning that we cannot add any future networks without defining a
-/// new, incompatible version of this type. If you are using this type directly and wish to support the
-/// new network, this will be a breaking change to your APIs and likely require changes in your code.
-///
-/// If you are concerned about forward compatibility, consider using `T: Into<Params>` instead of
-/// this type as a parameter to functions in your public API, or directly using the `Params` type.
-// For extensive discussion on the usage of `non_exhaustive` please see:
-// https://github.com/rust-bitcoin/rust-bitcoin/issues/2225
-#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)]
-pub enum Network {
- /// Mainnet Bitcoin.
- Bitcoin,
- /// Bitcoin's testnet network.
- Testnet(TestnetVersion),
- /// Bitcoin's signet network.
- Signet,
- /// Bitcoin's regtest network.
- Regtest,
-}
-
-#[cfg(feature = "serde")]
-impl Serialize for Network {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- serializer.serialize_str(self.as_display_str())
- }
-}
-
-#[cfg(feature = "serde")]
-impl<'de> Deserialize<'de> for Network {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- struct NetworkVisitor;
-
- impl Visitor<'_> for NetworkVisitor {
- type Value = Network;
-
- fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
- formatter.write_str("a valid network identifier")
- }
-
- fn visit_str<E>(self, value: &str) -> Result<Network, E>
- where
- E: serde::de::Error,
- {
- Network::from_str(value).map_err(E::custom)
- }
- }
-
- deserializer.deserialize_str(NetworkVisitor)
- }
-}
-
-impl Network {
- /// Converts a `Network` to its equivalent `bitcoind -chain` argument name.
- ///
- /// ```bash
- /// $ bitcoin-23.0/bin/bitcoind --help | grep -C 3 '\-chain=<chain>'
- /// Chain selection options:
- ///
- /// -chain=<chain>
- /// Use the chain <chain> (default: main). Allowed values: main, test, signet, regtest
- /// ```
- pub fn to_core_arg(self) -> &'static str {
- match self {
- Self::Bitcoin => "main",
- // For user-side compatibility, testnet3 is retained as test
- Self::Testnet(TestnetVersion::V3) => "test",
- Self::Testnet(TestnetVersion::V4) => "testnet4",
- Self::Signet => "signet",
- Self::Regtest => "regtest",
- }
- }
-
- /// Converts a `bitcoind -chain` argument name to its equivalent `Network`.
- ///
- /// ```bash
- /// $ bitcoin-23.0/bin/bitcoind --help | grep -C 3 '\-chain=<chain>'
- /// Chain selection options:
- ///
- /// -chain=<chain>
- /// Use the chain <chain> (default: main). Allowed values: main, test, signet, regtest
- /// ```
- pub fn from_core_arg(core_arg: &str) -> Result<Self, ParseNetworkError> {
- use Network::*;
-
- let network = match core_arg {
- "main" => Bitcoin,
- "test" => Testnet(TestnetVersion::V3),
- "testnet4" => Testnet(TestnetVersion::V4),
- "signet" => Signet,
- "regtest" => Regtest,
- _ => return Err(ParseNetworkError(core_arg.to_owned())),
- };
- Ok(network)
- }
-
- /// 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 {
- Self::Bitcoin => "bitcoin",
- Self::Testnet(TestnetVersion::V3) => "testnet",
- Self::Testnet(TestnetVersion::V4) => "testnet4",
- Self::Signet => "signet",
- Self::Regtest => "regtest",
- }
- }
-}
+#[doc(inline)]
+pub use network::{Network, NetworkKind, TestnetVersion};
+#[doc(no_inline)]
+pub use network::ParseNetworkError;
/// Extension functionality for the [`Network`] type.
// `define_extension_trait` chokes on the rustdoc example code.
@@ -208,6 +53,7 @@ pub trait NetworkExt: sealed::Sealed {
/// Returns the associated network parameters.
fn params(self) -> &'static Params;
}
+
impl NetworkExt for Network {
fn chain_hash(self) -> ChainHash { ChainHash::using_genesis_block_const(self) }
@@ -221,6 +67,7 @@ impl NetworkExt for Network {
Self::Bitcoin => &Params::BITCOIN,
Self::Testnet(TestnetVersion::V3) => &Params::TESTNET3,
Self::Testnet(TestnetVersion::V4) => &Params::TESTNET4,
+ Self::Testnet(_) => &Params::TESTNET3,
Self::Signet => &Params::SIGNET,
Self::Regtest => &Params::REGTEST,
}
@@ -232,89 +79,6 @@ mod sealed {
impl Sealed for super::Network {}
}
-#[cfg(feature = "serde")]
-pub mod as_core_arg {
- //! Module for serialization/deserialization of network variants into/from Bitcoin Core values
- #![allow(missing_docs)]
-
- use crate::Network;
-
- pub fn serialize<S>(network: &Network, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: serde::Serializer,
- {
- serializer.serialize_str(network.to_core_arg())
- }
-
- pub fn deserialize<'de, D>(deserializer: D) -> Result<Network, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- struct NetworkVisitor;
-
- impl serde::de::Visitor<'_> for NetworkVisitor {
- type Value = Network;
-
- fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
- Network::from_core_arg(s).map_err(|_| {
- E::invalid_value(
- serde::de::Unexpected::Str(s),
- &"bitcoin network encoded as a string (either main, test, testnet4, signet or regtest)",
- )
- })
- }
-
- fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
- write!(
- formatter,
- "bitcoin network encoded as a string (either main, test, testnet4, signet or regtest)"
- )
- }
- }
-
- deserializer.deserialize_str(NetworkVisitor)
- }
-}
-
-/// An error in parsing network string.
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[non_exhaustive]
-pub struct ParseNetworkError(String);
-
-impl fmt::Display for ParseNetworkError {
- fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
- write_err!(f, "failed to parse {} as network", self.0; self)
- }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for ParseNetworkError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
-}
-
-impl FromStr for Network {
- type Err = ParseNetworkError;
-
- #[inline]
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s {
- "bitcoin" => Ok(Self::Bitcoin),
- // For user-side compatibility, testnet3 is retained as testnet
- "testnet" => Ok(Self::Testnet(TestnetVersion::V3)),
- "testnet4" => Ok(Self::Testnet(TestnetVersion::V4)),
- "signet" => Ok(Self::Signet),
- "regtest" => Ok(Self::Regtest),
- _ => Err(ParseNetworkError(s.to_owned())),
- }
- }
-}
-
-impl fmt::Display for Network {
- fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
- write!(f, "{}", self.as_display_str())
- }
-}
-
/// Error in parsing network from chain hash.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
@@ -348,82 +112,4 @@ impl TryFrom<ChainHash> for Network {
}
#[cfg(test)]
-mod tests {
- use super::{Network, TestnetVersion};
-
- #[test]
- fn string() {
- assert_eq!(Network::Bitcoin.to_string(), "bitcoin");
- assert_eq!(Network::Testnet(TestnetVersion::V3).to_string(), "testnet");
- assert_eq!(Network::Testnet(TestnetVersion::V4).to_string(), "testnet4");
- assert_eq!(Network::Regtest.to_string(), "regtest");
- assert_eq!(Network::Signet.to_string(), "signet");
-
- assert_eq!("bitcoin".parse::<Network>().unwrap(), Network::Bitcoin);
- assert_eq!("testnet".parse::<Network>().unwrap(), Network::Testnet(TestnetVersion::V3));
- assert_eq!("testnet4".parse::<Network>().unwrap(), Network::Testnet(TestnetVersion::V4));
- assert_eq!("regtest".parse::<Network>().unwrap(), Network::Regtest);
- assert_eq!("signet".parse::<Network>().unwrap(), Network::Signet);
- assert!("fakenet".parse::<Network>().is_err());
- }
-
- #[test]
- #[cfg(feature = "serde")]
- fn serde_roundtrip() {
- use Network::*;
- let tests = vec![
- (Bitcoin, "bitcoin"),
- (Testnet(TestnetVersion::V3), "testnet"),
- (Testnet(TestnetVersion::V4), "testnet4"),
- (Signet, "signet"),
- (Regtest, "regtest"),
- ];
-
- for tc in tests {
- let network = tc.0;
-
- let want = format!("\"{}\"", tc.1);
- let got = serde_json::to_string(&tc.0).expect("failed to serialize network");
- assert_eq!(got, want);
-
- let back: Network = serde_json::from_str(&got).expect("failed to deserialize network");
- assert_eq!(back, network);
- }
- }
-
- #[test]
- fn from_to_core_arg() {
- let expected_pairs = [
- (Network::Bitcoin, "main"),
- (Network::Testnet(TestnetVersion::V3), "test"),
- (Network::Testnet(TestnetVersion::V4), "testnet4"),
- (Network::Regtest, "regtest"),
- (Network::Signet, "signet"),
- ];
-
- for (net, core_arg) in &expected_pairs {
- assert_eq!(Network::from_core_arg(core_arg), Ok(*net));
- assert_eq!(net.to_core_arg(), *core_arg);
- }
- }
-
- #[test]
- #[cfg(feature = "serde")]
- fn serde_as_core_arg() {
- #[derive(Serialize, Deserialize, PartialEq, Debug)]
- struct T {
- #[serde(with = "crate::network::as_core_arg")]
- pub network: Network,
- }
-
- serde_test::assert_tokens(
- &T { network: Network::Bitcoin },
- &[
- serde_test::Token::Struct { name: "T", len: 1 },
- serde_test::Token::Str("network"),
- serde_test::Token::Str("main"),
- serde_test::Token::StructEnd,
- ],
- );
- }
-}
+mod tests {}
diff --git a/bitcoin/src/network/params.rs b/bitcoin/src/network/params.rs
index 32eca90f..a754ac04 100644
--- a/bitcoin/src/network/params.rs
+++ b/bitcoin/src/network/params.rs
@@ -245,6 +245,7 @@ impl Params {
Network::Bitcoin => Self::MAINNET,
Network::Testnet(TestnetVersion::V3) => Self::TESTNET3,
Network::Testnet(TestnetVersion::V4) => Self::TESTNET4,
+ Network::Testnet(_) => Self::TESTNET3,
Network::Signet => Self::SIGNET,
Network::Regtest => Self::REGTEST,
}
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.