diff --git a/ci/checks/classify_merge_profile.py b/ci/checks/classify_merge_profile.py index f00e503cbc..af6d234960 100755 --- a/ci/checks/classify_merge_profile.py +++ b/ci/checks/classify_merge_profile.py @@ -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. diff --git a/ci/checks/test_classify_merge_profile.py b/ci/checks/test_classify_merge_profile.py index 917059be4f..4fdb90d35c 100755 --- a/ci/checks/test_classify_merge_profile.py +++ b/ci/checks/test_classify_merge_profile.py @@ -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", diff --git a/doc/rpc/annotations/v30.2-delta.yml b/doc/rpc/annotations/v30.2-delta.yml index 1bc02b5f77..2a93e0963b 100644 --- a/doc/rpc/annotations/v30.2-delta.yml +++ b/doc/rpc/annotations/v30.2-delta.yml @@ -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.", diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index a03bd5663c..d7020a75a1 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -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; @@ -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); @@ -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) : "", @@ -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" diff --git a/src/common/args.cpp b/src/common/args.cpp index ce0b414cca..dbd5604ec2 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -745,6 +745,8 @@ const std::vector 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) diff --git a/src/init.cpp b/src/init.cpp index 297a4f2824..4dba9c2790 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -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=", "Make automatic outbound connections only to network (" + 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); @@ -649,6 +651,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-limitdescendantsize=", strprintf("Do not accept transactions if any ancestor would have more than kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT_KVB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-test=