net: Add AdvertisedVersion() for protocol version advertised to a peer
What changed, and why it matters
This commit is a privacy-focused code cleanup. Bitcoin nodes normally announce a current protocol version number when connecting to peers. For a special 'private broadcast' connection type used to hide transaction origins, the commit makes the node advertise an older fixed protocol version (70016) instead of the latest one. This prevents a small metadata leak that could help an observer identify these privacy connections by their version number. It is not a fix for an exploitable bug.
No urgent action. Treat as routine privacy improvement. Reviewers may verify that WTXID_RELAY_VERSION is the intended floor and that non-private peers still advertise PROTOCOL_VERSION.
Security signals we found
privacy hardening
fingerprinting reduction
protocol version anonymization
no memory safety or authentication fix
Evidence from the diff
Adds CNode::AdvertisedVersion(), returning WTXID_RELAY_VERSION (70016) for ConnectionType::PRIVATE_BROADCAST and PROTOCOL_VERSION otherwise. PushNodeVersion() and ProcessMessage() now use this advertised version instead of the global PROTOCOL_VERSION when building the VERSION message and computing greatest_common_version. Tests are updated to expect 70016 for private-broadcast peers. The change reduces a fingerprinting vector for private broadcast peers.
Changed components
src/net.h CNodesrc/net_processing.cpp PushNodeVersion / ProcessMessageprivate broadcast P2P connectionsp2p protocol VERSION messageInspect captured patch +13 / −6
diff --git a/src/net.h b/src/net.h
index f54a8e0c..d0cfe7c9 100644
--- a/src/net.h
+++ b/src/net.h
@@ -831,6 +831,13 @@ public:
return m_conn_type == ConnectionType::PRIVATE_BROADCAST;
}
+ /** Protocol version advertised in our VERSION message.
+ * Private broadcast connections use a fixed version to maximise anonymity. */
+ int AdvertisedVersion() const
+ {
+ return IsPrivateBroadcastConn() ? WTXID_RELAY_VERSION : PROTOCOL_VERSION;
+ }
+
bool IsInboundConn() const {
return m_conn_type == ConnectionType::INBOUND;
}
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 50ea62ff..def72704 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -1575,7 +1575,7 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode, const Peer& peer)
MakeAndPushMessage(
pnode,
NetMsgType::VERSION,
- PROTOCOL_VERSION,
+ pnode.AdvertisedVersion(),
my_services,
my_time,
// your_services + CNetAddr::V1(your_addr) is the pre-version-31402 serialization of your_addr (without nTime)
@@ -1589,7 +1589,7 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode, const Peer& peer)
LogDebug(
BCLog::NET, "send version message: version=%d, blocks=%d%s, txrelay=%d, peer=%d\n",
- PROTOCOL_VERSION, my_height,
+ pnode.AdvertisedVersion(), my_height,
fLogIPs ? strprintf(", them=%s", your_addr.ToStringAddrPort()) : "",
my_tx_relay, pnode.GetId());
}
@@ -3663,7 +3663,7 @@ void PeerManagerImpl::ProcessMessage(Peer& peer, CNode& pfrom, const std::string
}
// Change version
- const int greatest_common_version = std::min(nVersion, PROTOCOL_VERSION);
+ const int greatest_common_version = std::min(nVersion, pfrom.AdvertisedVersion());
pfrom.SetCommonVersion(greatest_common_version);
pfrom.nVersion = nVersion;
diff --git a/src/test/util/net.cpp b/src/test/util/net.cpp
index c4850c64..1415c8d4 100644
--- a/src/test/util/net.cpp
+++ b/src/test/util/net.cpp
@@ -56,7 +56,7 @@ void ConnmanTestMsg::Handshake(CNode& node,
FlushSendBuffer(node); // Drop the verack message added by SendMessages.
if (node.fDisconnect) return;
assert(node.nVersion == version);
- assert(node.GetCommonVersion() == std::min(version, PROTOCOL_VERSION));
+ assert(node.GetCommonVersion() == std::min(version, node.AdvertisedVersion()));
CNodeStateStats statestats;
assert(peerman.GetNodeStateStats(node.GetId(), statestats));
assert(statestats.m_relay_txs == (relay_txs && !node.IsBlockOnlyConn()));
diff --git a/test/functional/p2p_private_broadcast.py b/test/functional/p2p_private_broadcast.py
index e8eaee6f..eac0d610 100755
--- a/test/functional/p2p_private_broadcast.py
+++ b/test/functional/p2p_private_broadcast.py
@@ -14,7 +14,6 @@ from test_framework.p2p import (
P2PDataStore,
P2PInterface,
P2P_SERVICES,
- P2P_VERSION,
start_p2p_listener,
)
from test_framework.messages import (
@@ -46,6 +45,7 @@ from test_framework.wallet import (
MiniWallet,
)
+P2P_PRIVATE_VERSION = 70016
NUM_PRIVATE_BROADCAST_PER_TX = 3
@@ -195,7 +195,7 @@ class P2PPrivateBroadcast(BitcoinTestFramework):
})
dummy_address = CAddress()
dummy_address.nServices = 0
- assert_equal(peer.last_message["version"].nVersion, P2P_VERSION)
+ assert_equal(peer.last_message["version"].nVersion, P2P_PRIVATE_VERSION)
assert_equal(peer.last_message["version"].nServices, 0)
assert_equal(peer.last_message["version"].nTime, 0)
assert_equal(peer.last_message["version"].addrTo, dummy_address)
Why this scored 28/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.