From: Liam Gilligan <liamdgilligan@gmail.com>
To: Bitcoin Development Mailing List <bitcoindev@googlegroups.com>
Subject: [bitcoindev] [BIP Proposal] Transport Feature Negotiation
Date: Thu, 17 Sep 2026 16:23:13 -0700 (PDT) [thread overview]
Message-ID: <29174459-1523-4cf8-a20f-5ce1abc302c3n@googlegroups.com> (raw)
[-- Attachment #1.1: Type: text/plain, Size: 13126 bytes --]
Hi everyone,
TL;DR (using Terms and Defs below): In order to support encryption upgrades
and the addition of authentication to P2P, I propose we specify a Transport
Feature Negotiation protocol, which gives a framework for modifying the
Transport Layer. This makes use of the version packet BIP-324 left
unspecified for exactly this purpose, redefining it as a **negotiation
packet** which carries the features a node supports.
## Terms and Defs
There are some unfortunately similar names, so I'll lay out some
terminology/definitions in addition to those I made in roasbeef's thread[1].
- **Transport Layer**: The details of how Protocol messages are encoded on
wire and what security services[2] are provided during transmission.
- The objects the Transport Layer concerns itself with are **packets**,
which can contain the Protocol Layer's **messages**.
- A **Transport** is a particular version of the Transport Layer; V1 and V2
are the two that exist today.
- **Protocol Layer**: The layer that defines which Bitcoin P2P messages and
commands are used.
- The objects the Protocol Layer concerns itself with are **messages**,
which are contained in Transport Layer's **packets**.
- A **Protocol** is a particular version of the Protocol Layer; these are
the protocol versions carried in the version message, currently up to 70017.
- **P2P Handshake**: The exchange between two nodes to ensure they are
using compatible Transports and Protocols. This can simply be called "the
handshake". It consists of two phases:
- **Transport Phase**: The process in which the Transport is established.
- V1 Transport exchanges no bytes during its Transport Phase.
- V2 Transport's Transport Phase is defined by BIP-324[3].
- The V2 Transport Phase is terminated by a **version packet**, which is a
Transport Layer packet which contains no Protocol Layer message. The
version packet will never contain the version message defined below.
BIP-324 leaves its contents unspecified and sends it empty, making it
exactly 20 bytes on the wire.
- A **negotiation packet** is that same packet with its contents specified.
This proposal defines those contents as a list of features; a negotiation
packet advertising no features is empty, and is therefore a version packet.
- **Protocol Phase**: The process in which the Protocol is established.
- This is done through an exchange of messages contained between a
**version message** and a **verack message** (or simply verack). The verack
terminates both the Protocol Phase and the handshake itself.
- As the name suggests, a version message is a Protocol message contained
within a Transport Layer packet. It will never be contained in the V2
Transport's version packet.
- Note that nodes not complying with BIP-434[4] may attempt modifying the
Protocol Layer after the verack.
- **Feature**: a modification to the Transport Layer which can be
negotiated -- an encryption upgrade or authentication, for example. A
feature is negotiated when both peers agree to use it, by rules given
below. A feature exists only within the negotiation packet, whose contents
are a list of features and nothing else, and is identified by a one-byte
header. BIP-434 uses "feature" for the Protocol Layer equivalent; the two
never appear in the same packet.
## Motivation and Rationale
As discussed earlier in roasbeef's thread[1], a PQ upgrade to the Transport
is needed. The general consensus from that thread was that a CTU solution
would be best for this, which means we need a mechanism for performing such
an upgrade. BIP-324 was designed with a future PQ upgrade in mind, and left
an unspecified version packet specifically to perform it, alongside
authentication and other Transport upgrades.
The version packet seems like a good fit, but is it the only option? There
are two main alternatives that come to mind: BIP-434 and defining a V3
Transport.
BIP-434 is a complete P2P negotiation protocol, but is only used for
negotiating which messages are sent and how they encode data. In other
words, BIP-434 is sort of "Protocol Feature Negotiation". But encryption
and authentication are Transport features, not Protocol features. The
timing is also an issue -- the Protocol Phase of the handshake will take
place over two separate Transports.
Defining a V3 Transport by fully replacing the ElligatorSwift ECDH is also
an option, but there are some issues with this. First, it wouldn't be a CTU
path that was (loosely) agreed upon in roasbeef's thread, and instead it
would be an OSH solution. More concretely, it would require committing to
one specific PQ KEM right off the bat. In the event that this KEM was
broken or otherwise rejected, it would require a V4 Transport to replace
it. Negotiating the PQ KEM to use in band allows for modularity and
backwards-compatibility, which is not offered by a V3.
That leaves in-band negotiation inside V2. Given that a negotiation packet
is needed in any case, defining an entirely new one instead of redefining
the version packet adds overhead and potentially round trips. The version
packet is existent, unused, and positioned ideally and intentionally for
this purpose.
## Proposed Design
Transport Feature Negotiation specifies what the negotiation packet
contains:
```
Negotiation Packet Contents:
-------------------------------------
| Feature | Feature | ... | Feature |
-------------------------------------
Feature structure:
------------------------------------------
| CompactSize | Header | Feature Payload |
------------------------------------------
```
The contents of the negotiation packet are a list of features and they are
not packaged like a message[5], so no message type is prefixed. The
negotiation packet can carry as many features as can fit in the maximum
packet size[6]. A node should disconnect if it can't parse a negotiation
packet.
The CompactSize indicates the size of the Header + Payload in bytes.
The Header is one byte that indicates what feature is being negotiated, and
nodes should ignore unsupported features. Header values should be recorded
in a table maintained alongside this specification, as BIP-324 does for its
one-byte message type IDs[7]. The value zero is reserved so that a later
revision can introduce a different structure. Because the CompactSize
covers the Header as well as the Payload, a new Header can take on any
shape. Until an alternative header format is specified, a zero header
should be ignored. A node must not send more than one feature with the same
header, and a node receiving a negotiation packet in which a header repeats
should act on the first occurrence and ignore[8] the rest.
The Feature Payload contains information relevant to a feature being
negotiated, and can be empty. How the payload is structured and interpreted
is defined by that specific feature. A feature may specify that **feature
packets** can be sent -- additional packets whose contents can be used for
upgrade. These packets may only be sent if that feature was negotiated, and
they extend the Transport Phase past the negotiation packet. Their contents
must begin with the header of their respective feature, and feature packets
sent with headers that haven't been negotiated should be ignored.
A node supporting no features sends an empty negotiation packet -- which is
identical to a version packet -- and therefore current V2 nodes are already
compliant[9], advertising and selecting features correctly and ignoring
unsupported ones.
One negotiation packet from each side completes negotiation, but each acts
slightly differently. The first negotiation packet acts as advertisement:
it should contain every feature a node supports, including those that are
mutually incompatible. The second negotiation packet acts as selection: it
responds with a subset of the first packet's features, thus deciding which
upgrades are performed. A feature is considered to be negotiated if and
only if it appears in the selecting packet. The two peers are therefore the
advertiser and the selector.
Features that require payloads should allow the advertiser to omit payloads
from their advertised feature (for example, to advertise ML-KEM support but
not send the pubkey). This lets the advertiser avoid computing potentially
costly setup for a feature which their peer may not support, at the cost of
a half round-trip. Similarly, these features should require the selector to
always send payloads, and allow the advertiser to send their payload after
the fact in a feature packet.
If no negotiated features require feature packets, the Transport Phase ends
with the second negotiation packet in the same way BIP-324 terminates it.
Otherwise, it terminates when the last feature packet is sent.
Nodes should accept inbound connections regardless of what features their
peer supports[10], though a node may disconnect peers based on theirs.
A MitM *can* strip features from the negotiation packet, and a node can't
tell if its peer doesn't support a feature or had its
advertisement/selection stripped. An authentication feature can make this
detectable[11].
## Scope and Open Questions
I can't imagine that there will be any Transport features implemented in
practice aside from encryption and authentication upgrades. Should we
restrict the features to just those? This would make ordering feature
packets and coordinating the encryption upgrades easier. Is there some
other subset of possible features we want to restrict to? Let me know your
thoughts.
Should we allow for "staggered negotiation"? Staggered negotiation is what
I'm calling the process of splitting feature advertisements across separate
negotiation packets. This could allow nodes to withhold from advertising
authentication features until after an encryption upgrade is negotiated and
performed, for example. I haven't described a mechanism for it (because it
is sort of a headache), but if you all think there is a need, a mechanism
could be readily designed, and it must be included from the start. There is
no way to future-proof the protocol to include staggering.
Must the responder be the advertiser? As specified by BIP-324, the
responder (the node accepting the inbound connection) sends the version
packet first and thus will always be the advertising peer, meaning the
initiator gets to choose between mutually incompatible features. Should we
allow the initiator this privilege? We could choose not to by allowing the
responder to pass off the obligation of advertisement to the initiator.
What are your thoughts?
Best,
Liam
---
[1] https://groups.google.com/g/bitcoindev/c/n_5WuKVYqwI/
[2] https://csrc.nist.gov/glossary/term/security_service
[3] https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki
[4] https://github.com/bitcoin/bips/blob/master/bip-0434.md
[5]
https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki#v2-bitcoin-p2p-message-structure
[6] A BIP-324 packet carries a three-byte contents length, so any packet's
contents are bounded at 2^24 - 1 bytes:
https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki#packet-encryption
[7] https://github.com/bitcoin/bips/blob/master/bip-0324/message_type_ids.md
[8] Note that this is only relevant in the event that the two features
carry distinct payloads.
[9] BIP-324 specifies this receive behaviour: "Receives a packet, ignores
its contents. The idea is that features added by future versions get
negotiated based on what is supported by both parties... we defer
specifying the encoding for such version content until there is a need for
it." Core implements it literally -- see VERSION_CONTENTS in src/net.h --
and any BIP-324 compliant implementation should do the same.
[10] This is to avoid partitioning. If a peer doesn't support the level of
desired encryption or authentication, then the connection can be treated
like a V1. That is, sensitive data can be withheld from unsecure or
unauthenticated connections.
[11] If every node challenges every peer, there is no signal telling a MitM
which connections are safe to tamper with, and a node expecting an
authenticated connection can treat a missing authentication advertisement
as evidence of interference. The connection need not end and should
continue unauthenticated, as with any peer lacking some feature -- but the
interference becomes visible. If CRQCs become a serious threat, an
authentication feature might go further and treat a connection as authentic
only where a PQ KEM was also performed: authentication binds to the
session, and a session established by classical key exchange alone gives a
quantum attacker everything that binding rests on.
--
You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/29174459-1523-4cf8-a20f-5ce1abc302c3n%40googlegroups.com.
[-- Attachment #1.2: Type: text/html, Size: 14264 bytes --]
reply other threads:[~2026-09-17 23:39 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=29174459-1523-4cf8-a20f-5ce1abc302c3n@googlegroups.com \
--to=liamdgilligan@gmail.com \
--cc=bitcoindev@googlegroups.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox