Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/checks/classify_merge_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"src/test/CMakeLists.txt",
"src/test/fuzz/CMakeLists.txt",
"src/util/CMakeLists.txt",
"test/CMakeLists.txt",
# The jobs and the gate: this classifier, the workflows that run it and
# the PQ jobs, the scripts and evidence check of those jobs, and the
# tests of that wiring.
Expand Down
1 change: 1 addition & 0 deletions ci/checks/test_classify_merge_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ def test_pq_unit_jobs_required_for_mlkem_and_transport_paths(self) -> None:
"src/test/CMakeLists.txt",
"src/test/fuzz/CMakeLists.txt",
"src/bench/CMakeLists.txt",
"test/CMakeLists.txt",
"cmake/bitcoin-build-config.h.in",
# Gate and CI wiring, and its tests.
".github/workflows/ci.yml",
Expand Down
25 changes: 25 additions & 0 deletions doc/rpc/annotations/v30.2-delta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,31 @@
"Lane-specific estimates filter which blocks contribute work while using the same active-chain elapsed-time window as the all-lane estimate; they do not use a lane-local timestamp window."
]
},
"getpqtransportinfo": {
"status_override": "new_since_v30_2",
"delta_summary": "Adds the hybrid post-quantum v2 transport (ML-KEM-1024 and ECDH) diagnostics RPC.",
"delta_notes": [
"This method does not exist in Bitcoin Core v30.2.",
"Every field is always present, and empty collections are [].",
"Counters count events since startup, not exclusive outcomes; they start over when instance_id changes."
]
},
"getpeerinfo": {
"status_override": "semantic_changed_since_v30_2",
"delta_summary": "Adds transport_pq and transport_pq_status, and session_id is the hybrid session id for a hybrid post-quantum peer.",
"delta_notes": [
"transport_pq is true once the peer's key confirmation verified.",
"transport_pq_status is pending, hybrid, legacy_peer, fallback, off or v1. A connection that closes on a hybrid failure reports pending until it is removed.",
"transport_protocol_type keeps its values: detecting until the handshake completes, then v2."
]
},
"getnetworkinfo": {
"status_override": "surface_changed_since_v30_2",
"delta_summary": "Adds connections_pq, the number of hybrid post-quantum connections.",
"delta_notes": [
"connections_pq counts transport_pq over the same peers getpeerinfo lists."
]
},
"getmininginfo": {
"status_override": "semantic_changed_since_v30_2",
"delta_summary": "networkhashps is the default total qbit chainwork hashrate estimate, and next is the permissionless/native candidate.",
Expand Down
8 changes: 6 additions & 2 deletions src/bitcoin-cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ class NetinfoRequestHandler : public BaseRequestHandler
std::string age;
std::string services;
std::string transport_protocol_type;
bool is_transport_pq;
double min_ping;
double ping;
int64_t addr_processed;
Expand Down Expand Up @@ -555,10 +556,12 @@ class NetinfoRequestHandler : public BaseRequestHandler
const std::string services{FormatServices(services_names)};
const std::string sub_version{peer["subver"].isNull() ? "" : peer["subver"].get_str()};
const std::string transport{peer["transport_protocol_type"].isNull() ? "v1" : peer["transport_protocol_type"].get_str()};
// A server without hybrid post-quantum transport, such as v1.0.0, has no transport_pq.
const bool is_transport_pq{peer["transport_pq"].isNull() ? false : peer["transport_pq"].get_bool()};
const bool is_addr_relay_enabled{peer["addr_relay_enabled"].isNull() ? false : peer["addr_relay_enabled"].get_bool()};
const bool is_bip152_hb_from{peer["bip152_hb_from"].isNull() ? false : peer["bip152_hb_from"].get_bool()};
const bool is_bip152_hb_to{peer["bip152_hb_to"].isNull() ? false : peer["bip152_hb_to"].get_bool()};
m_peers.push_back({addr, sub_version, conn_type, NETWORK_SHORT_NAMES[network_id], age, services, transport, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_outbound, is_tx_relay});
m_peers.push_back({addr, sub_version, conn_type, NETWORK_SHORT_NAMES[network_id], age, services, transport, is_transport_pq, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_outbound, is_tx_relay});
m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length);
m_max_addr_processed_length = std::max(ToString(addr_processed).length(), m_max_addr_processed_length);
m_max_addr_rate_limited_length = std::max(ToString(addr_rate_limited).length(), m_max_addr_rate_limited_length);
Expand Down Expand Up @@ -593,7 +596,7 @@ class NetinfoRequestHandler : public BaseRequestHandler
peer.network,
m_max_services_length, // variable spacing
peer.services,
(peer.transport_protocol_type.size() == 2 && peer.transport_protocol_type[0] == 'v') ? peer.transport_protocol_type[1] : ' ',
peer.is_transport_pq ? "2p" : (peer.transport_protocol_type.size() == 2 && peer.transport_protocol_type[0] == 'v') ? peer.transport_protocol_type.substr(1) : "",
PingTimeToString(peer.min_ping),
PingTimeToString(peer.ping),
peer.last_send ? ToString(time_now - peer.last_send) : "",
Expand Down Expand Up @@ -720,6 +723,7 @@ class NetinfoRequestHandler : public BaseRequestHandler
" \"2\" - P2P_V2: peer supports version 2 P2P transport protocol, as defined in BIP 324\n"
" \"u\" - UNKNOWN: unrecognized bit flag\n"
" v Version of transport protocol used for the connection\n"
" \"2p\" - v2 with hybrid post-quantum session keys (getpeerinfo \"transport_pq\" is true)\n"
" mping Minimum observed ping time, in milliseconds (ms)\n"
" ping Last observed ping time, in milliseconds (ms)\n"
" send Time since last message sent to the peer, in seconds\n"
Expand Down
2 changes: 2 additions & 0 deletions src/common/args.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,8 @@ const std::vector<std::string> TEST_OPTIONS_DOC{
"reindex_after_failure_noninteractive_yes (When asked for a reindex after failure interactively, simulate as-if answered with 'yes')",
"bip94 (enforce BIP94 consensus rules)",
"disable_witness_pruning (disable witness pruning in regtest)",
"pq_fail_first_packet (corrupt this node's ML-KEM shared secret on every hybrid post-quantum v2 handshake, so the peer's key confirmation fails)",
"mlkem_portable (use the portable ML-KEM-1024 code instead of native code)",
};

bool HasTestOption(const ArgsManager& args, const std::string& test_option)
Expand Down
96 changes: 95 additions & 1 deletion src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <common/system.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
#include <crypto/mlkem.h>
#include <deploymentstatus.h>
#include <hash.h>
#include <httprpc.h>
Expand Down Expand Up @@ -565,6 +566,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddArg("-i2pacceptincoming", strprintf("Whether to accept inbound I2P connections (default: %i). Ignored if -i2psam is not set. Listening for inbound I2P connections is done through the SAM proxy, not by binding to a local address and port.", DEFAULT_I2P_ACCEPT_INCOMING), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-onlynet=<net>", "Make automatic outbound connections only to network <net> (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-v2transport", strprintf("Support v2 transport (default: %u)", DEFAULT_V2_TRANSPORT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-v2pqtransport", strprintf("Support hybrid post-quantum key exchange (ML-KEM-1024) in v2 transport. Has no effect with -v2transport=0. A non-numeric value, or a double negative such as -nov2pqtransport=0, warns at startup and states how it was read (default: %u)", DEFAULT_V2_PQ_TRANSPORT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
Expand Down Expand Up @@ -649,6 +651,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddArg("-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT_KVB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-test=<option>", "Pass a test-only option. Options include : " + Join(TEST_OPTIONS_DOC, ", ") + ".", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-capturemessages", "Capture all P2P messages to disk", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-mlkemportable", "Use the portable ML-KEM-1024 code instead of native code, in case native code misbehaves on this CPU. A non-numeric value, or a double negative such as -nomlkemportable=0, warns at startup and states how it was read (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-mocktime=<n>", "Replace actual time with " + UNIX_EPOCH_TIME + " (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_VALIDATION_CACHE_BYTES >> 20), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-maxtipage=<n>",
Expand Down Expand Up @@ -847,6 +850,69 @@ int available_fds;
ServiceFlags g_local_services = ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS);
int64_t peer_connect_timeout;
std::set<BlockFilterType> g_enabled_filter_types;
//! Set once by AppInitParameterInteraction(), for the startup line and the connection manager.
PQTransportConfig g_pq_transport_config;
//! Set once by AppInitParameterInteraction(): -mlkemportable or -test=mlkem_portable.
bool g_mlkem_force_portable{false};

/**
* How a boolean setting's text relates to what it is read as. Booleans are read as integers
* (InterpretBool()), so a string that isn't one completely, under the same whitespace and sign
* rules, is NON_NUMERIC: -X=true is read as 0, and -X=1abc or an overflowing number as 1. An empty
* value means 1. ArgsManager keeps a negated setting only as a bool, false for -noX and -noX=1, and
* true for a DOUBLE_NEGATIVE, -noX given 0 or any non-numeric value, which switches X on. Its text
* is gone, so -noX=1abc, read as 0 like -noX, is PLAIN.
*/
enum class BoolSettingText { PLAIN, NON_NUMERIC, DOUBLE_NEGATIVE };

BoolSettingText GetBoolSettingText(const common::SettingsValue& value)
{
if (value.isBool()) return value.get_bool() ? BoolSettingText::DOUBLE_NEGATIVE : BoolSettingText::PLAIN;
if (!value.isStr() || value.get_str().empty()) return BoolSettingText::PLAIN;
std::string_view number{util::TrimStringView(value.get_str())};
if (number.starts_with('+') && !number.starts_with("+-")) number.remove_prefix(1);
return ToIntegral<int>(number) ? BoolSettingText::PLAIN : BoolSettingText::NON_NUMERIC;
}

/** The warning for the boolean option -name when its setting's text doesn't say what it is read
* as: the value given, if ArgsManager kept it, what it was read as, and the effect. */
std::optional<bilingual_str> BoolSettingWarning(const std::string& name, const common::SettingsValue& value, bool read_as,
const bilingual_str& effect)
{
switch (GetBoolSettingText(value)) {
case BoolSettingText::PLAIN:
return std::nullopt;
case BoolSettingText::NON_NUMERIC:
return strprintf(_("-%s=%s is not a number and was read as -%s=%d: %s. Use -%s=1 or -%s=0."),
name, value.get_str(), name, read_as, effect, name, name);
case BoolSettingText::DOUBLE_NEGATIVE:
return strprintf(_("-no%s was given 0 or a non-numeric value, so it was read as -%s=1: %s. Use -%s=0 to switch it off."),
name, name, effect, name);
} // no default case, so the compiler can warn about missing cases
assert(false);
}

/** The hybrid post-quantum v2 transport configuration of the effective settings, and the warnings
* they need. */
PQTransportConfig GetPQTransportConfig(const ArgsManager& args, std::vector<bilingual_str>& warnings)
{
const common::SettingsValue pq_setting{args.GetSetting("-v2pqtransport")};
const std::optional<bool> pq_explicit{SettingToBool(pq_setting)};
const PQTransportConfig config{
.v2_enabled = args.GetBoolArg("-v2transport", DEFAULT_V2_TRANSPORT),
.pq_requested = pq_explicit.value_or(DEFAULT_V2_PQ_TRANSPORT),
.fail_first_packet = HasTestOption(args, "pq_fail_first_packet"),
};
if (auto warning{BoolSettingWarning("v2pqtransport", pq_setting, config.pq_requested,
config.Enabled() ? _("hybrid transport is ON") : _("hybrid transport is OFF"))}) {
warnings.push_back(std::move(*warning));
}
// Only an explicit request warns, never the default.
if (pq_explicit == true && !config.v2_enabled) {
warnings.push_back(_("-v2pqtransport=1 has no effect because -v2transport=0. Set -v2transport=1 to use hybrid post-quantum v2 transport."));
}
return config;
}

} // namespace

Expand Down Expand Up @@ -967,8 +1033,24 @@ bool AppInitParameterInteraction(const ArgsManager& args)
}
}

// The v2 transport configuration: the hybrid post-quantum key exchange's warnings, startup line,
// connections and RPC all use this one.
std::vector<bilingual_str> pq_warnings;
g_pq_transport_config = GetPQTransportConfig(args, pq_warnings);
for (const bilingual_str& warning : pq_warnings) InitWarning(warning);

// The ML-KEM-1024 code, selected before any networking object exists (AppInitMain()).
const common::SettingsValue portable_setting{args.GetSetting("-mlkemportable")};
const bool portable{SettingToBool(portable_setting).value_or(false)};
g_mlkem_force_portable = portable || HasTestOption(args, "mlkem_portable");
if (auto warning{BoolSettingWarning("mlkemportable", portable_setting, portable,
g_mlkem_force_portable ? _("ML-KEM-1024 uses portable code") :
_("ML-KEM-1024 uses native code where the CPU supports it"))}) {
InitWarning(*warning);
}

// Signal NODE_P2P_V2 if BIP324 v2 transport is enabled.
if (args.GetBoolArg("-v2transport", DEFAULT_V2_TRANSPORT)) {
if (g_pq_transport_config.v2_enabled) {
g_local_services = ServiceFlags(g_local_services | NODE_P2P_V2);
}

Expand Down Expand Up @@ -1620,6 +1702,17 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
node.addrman = std::move(*addrman);
}

// Select the ML-KEM-1024 code before any networking object exists, and name it.
mlkem::InitializeRuntime(/*force_portable=*/g_mlkem_force_portable);
{
const mlkem::BackendNames backends{mlkem::GetBackendNames()};
LogInfo("v2 pq: %s arith=%s keccak=%s",
!g_pq_transport_config.v2_enabled ? "disabled (-v2transport=0)" :
!g_pq_transport_config.pq_requested ? "disabled (-v2pqtransport=0)" :
"enabled",
backends.arith, backends.keccak);
}

FastRandomContext rng;
assert(!node.banman);
node.banman = std::make_unique<BanMan>(args.GetDataDirNet() / "banlist", &uiInterface, args.GetIntArg("-bantime", DEFAULT_MISBEHAVING_BANTIME));
Expand Down Expand Up @@ -2113,6 +2206,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
connOptions.m_peer_connect_timeout = peer_connect_timeout;
connOptions.whitelist_forcerelay = args.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY);
connOptions.whitelist_relay = args.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY);
connOptions.m_pq = g_pq_transport_config;

// Port to bind to if `-bind=addr` is provided without a `:port` suffix.
const uint16_t default_bind_port =
Expand Down
Loading
Loading