BIP: 324 Layer: Peer Services Title: Version 2 P2P Encrypted Transport Protocol Author: Dhruv Mehta <email@example.com> Tim Ruffing <firstname.lastname@example.org> Jonas Schnelli <email@example.com> Pieter Wuille <firstname.lastname@example.org> Status: Draft Type: Standards Track Created: 2019-03-08 License: BSD-3-Clause
This document proposes a new Bitcoin P2P protocol, which features transport encryption and slightly lower bandwidth usage.
This document is licensed under the 3-clause BSD license.
Bitcoin is a permissionless network whose purpose is to reach consensus over public data. Since all data relayed in the Bitcoin P2P network is inherently public, and the protocol lacks a notion of cryptographic identities, peers talk to each other over unencrypted and unauthenticated connections. Nevertheless, this plaintext nature of the current P2P protocol (referred to as v1 in this document) has severe drawbacks in the presence of attackers:
- While the relayed data itself is public in nature, the associated metadata may reveal private information and hamper privacy of users. For example, a global passive attacker eavesdropping on all Bitcoin P2P connections can trivially identify the source and timing of a transaction.
- Since connections are unauthenticated, they can be tampered with at a low cost and often even with a low risk of detection. For example, an attacker can alter specific bytes of a connection (such as node flags) on-the-fly without the need to keep any state.
- The protocol is self-revealing. For example, deep packet inspection can identify a P2P connection trivially because connections start with a fixed sequence of magic bytes. The ability to detect connections enables censorship and facilitates the aforementioned attacks as well as other attacks which require the attacker to control the connections of victims, e.g., eclipse attacks targeted at miners.
This countermeasure increases the costs of the aforementioned attacks substantially. Encryption, even when it is unauthenticated and used only when both endpoints support v2, impedes eavesdropping or tampering by forcing the attacker to either perform a persistent man-in-the-middle attack or downgrade connections to v1. Since both of these are active attacks, they are in principle detectable by the endpoints and thus hard to perform covertly at scale: Even very basic checks, e.g., operators manually comparing protocol versions and session IDs (as supported by the proposed protocol), will expose the attacker. Moreover, a pseudorandom bytestream excludes identification techniques based on pattern matching, and makes it easier to shape the bytestream in order to mimic other protocols used on the Internet.
As we have argued above, unauthenticated encryption provides strictly better security than no encryption. Thus all connections should use encryption, even if they are unauthenticated.
When it comes to authentication, the situation is not as clear as for encryption. Due to Bitcoin's permissionless nature, authentication will always be restricted to specific scenarios (e.g., connections between peers belonging to the same operator), and whether some form of (possibly partially anonymous) authentication is desired depends on the specific requirements of the involved peers. As a consequence, we believe that authentication should be addressed separately (if desired), and this proposal aims to provide a solid technical basis for future protocol upgrades, including the addition of optional authentication.
Traffic analysis, e.g., observing message lengths and timing, as well as active attacks can still reveal that the Bitcoin v2 P2P protocol is in use. Nevertheless, a pseudorandom bytestream raises the cost of fingerprinting the protocol substantially, and may force some intermediaries to attack any protocol they cannot identify, causing collateral cost.
A pseudorandom bytestream is unopinionated, canonical and does not self-identify. If other commonly used protocols choose similarly, their bytestreams will become indistinguishable from Bitcoin P2P traffic. Moreover, traffic shapers and protocol wrappers (for example, making the traffic look like HTTPS or SSH) can further mitigate traffic analysis and active attacks but are out of scope for this proposal.
Our goal includes making opportunistic encryption ubiquitously available, as that provides the best defense against large-scale attacks. That implies protecting both the manual, deliberate connections node operators instruct their software to make, as well as the the automatic connections Bitcoin nodes make with each other based on IP addresses obtained via gossip. While encryption per se is already possible with proxy networks or VPN networks, these are not desirable or applicable for automatic connections at scale:
- Proxy networks like Tor or I2P introduce a separate address space, independent from network topology, with a very low cost per address. Thus, it is not desirable to have a substantial portion of nodes be exclusively connected this way, as it would significantly reduce Eclipse attack costs. Additionally, Tor connections come with significant bandwidth and latency costs that may not be desirable for all network users.
- VPN networks like WireGuard or OpenVPN inherently define a private network, which requires manual configuration, which isn't a realistic avenue for automatic connections.
While it would be possible to rely on an existing general-purpose transport encryption protocol such as TLS or Noise, the specific requirements of the Bitcoin P2P network laid out above make these protocols an unsuitable choice.
The primary requirement which existing protocols fail to meet is a sufficiently modular treatment of encryption and authentication. As we argue above, whether and which form of authentication is desired in the Bitcoin P2P network will depend on the specific requirements of the involved peers (resulting in a mix of authenticated and unauthenticated connections), and thus the question of authentication should be decoupled from encryption. However, native support for a handful of standard authentication scenarios (e.g., using digital signatures and certificates) is at core of the design of existing general-purpose transport encryption protocols. This focus on authentication would not provide clear benefits for the Bitcoin P2P network but would come with a large amount of additional complexity.
In contrast, our proposal instead aims for simple modular design that makes it possible to address authentication separately. Our proposal provides a foundation for authentication by exporting a session ID that uniquely identifies the encrypted channel. After an encrypted channel has been established, the two endpoints are able to use any authentication protocol to confirm that they have the same session ID. (This is sometimes called channel binding because the session ID binds the encrypted channel to the authentication protocol.) Since in our proposal, any authentication needs to run after an encrypted connection has been established, the price we pay for this modularity is a possibly higher number of roundtrips as opposed to other protocols that perform authentication alongside with the Diffie-Hellman key exchange. However, the resulting increase in connection establishment latency is a not a concern for Bitcoin's long-lived connections, which typically live for hours or even weeks.
Besides this fundamentally different treatment of authentication, further technical issues with off-the-shelf transport layer encryption protocols arise when applied to our desired use case:
- Noise and TLS do not offer a pseudorandom bytestream.
- Noise and TLS do not offer native support for elliptic curve cryptography on the curve secp256k1 as otherwise used in Bitcoin. While using secp256k1 is not strictly necessary, it is the obvious choice is for any new asymmetric cryptography in Bitcoin because it minimizes the cryptographic hardness assumptions as well as the dependencies that Bitcoin software will need.
- Both TLS and Noise provide a stream-based interface to the application-layer whereas Bitcoin requires a packet-based interface, resulting in the need for an additional thin layer to perform packet serialization and deserialization.
This proposal aims to achieve the following properties:
- Confidentiality against passive attacks: A passive attacker having recorded a v2 P2P bytestream (without timing information and packet metadata) must not be able to determine the plaintext being exchanged by the nodes.
- Observability of active attacks: A session ID identifiying the encrypted channel uniquely is derived deterministically from a Diffie-Hellman negotiation. An active man-in-the-middle attacker is forced to incur a risk of being detected as peer operators can compare session IDs manually, or using optional authentication methods possibly introduced in future protocol versions.
- Pseudorandom bytestream: A passive attacker having recorded a v2 P2P bytestream (without timing information and packet metadata) must not be able to distinguish it from a uniformly random bytestream.
- Shapable bytestream: It should be possible to shape the bytestream using decoy traffic to increase resistance to traffic analysis (for example, to conceal block propagation).
- Forward secrecy: An eavesdropping attacker who compromises a peer's sessions secrets should not be able to decrypt past session traffic, except for the latest few messages.
- Upgradability: The proposal provides an upgrade path using transport versioning which can be used to add features like authentication, PQC handshake upgrade, etc. in the future.
- Compatibility: v2 clients will allow inbound v1 connections to minimize risk of network partitions.
- Low overhead: the introduction of a new P2P transport protocol should not substantially increase computational cost or bandwidth for nodes that implement it, compared to the current protocol.
- Both peers generate ephemeral keypairs and exchange public keys.
- The public keys exchanged on wire use an encoding which makes the handshake (and therefore, the entire bytestream, as what follows is encrypted) indistinguishable from random.
- Both peers then deterministically derive the ECDH secret and further derive encryption keys, a session ID and rekeying salt from the shared secret.
- We use a two layer cipher suite. The inner layer uses the ChaCha20/Poly1305 AEAD defined in RFC8439 to provide confidentiality and authenticity of individual messages, using a battle-tested authenticated encryption suite. The outer layer prefixes every message with lengths encrypted using ChaCha20 directly to provide confidentiality against passive attackers only and a pseudorandom bytestream.
- The keys for both layers are rotated after a fixed number of messages for forward secrecy.
Peers supporting the v2 transport protocol signal support by advertising the
NODE_P2P_V2 = (1 << 11) service flag in addr relay. If met with immediate disconnection when establishing a v2 connection, clients implementing this proposal are encouraged to retry connecting using the v1 protocol.
The structure of the v2 encrypted messages is as follows:
|Field||Size in bytes||Comments|
Encrypted payload. |
---------------------------------------------------------------------------------------------- | Initiator Responder | | | | x, ellsq_X = v2_keygen(initiating=True) | | | | --- ellsq_X ---> | | | | y, ellsq_Y = v2_keygen(initiating=False) | | ECDH_SECRET = secp256k1_ellsq_ecdh_secret( | | y, ellsq_X, ellsq_Y, initiating=False) | | | | <-- ellsq_Y + v2_enc_msg(initiator, RESPONDER_TRANSPORT_VERSION) --- | | | | ECDH_SECRET = secp256k1_ellsq_ecdh_secret(x, ellsq_X, ellsq_Y, initiating=True) | | | | --- v2_enc_msg(responder, INITIATOR_TRANSPORT_VERSION) ---> | | | ----------------------------------------------------------------------------------------------
def initiate_v2_handshake(responder): x, ellsq_X = v2_keygen(initiating=True) send(responder, ellsq_X)
The responder generates an ephemeral keypair for itself and derives the shared ECDH secret which enables it to instantiate the encrypted transport. It then sends 64 bytes of the unencrypted Elligator-squared encoding of its own public key appended with a v2 protocol encrypted message where the payload is set to a transport version number. v2.0 clients implementing this proposal will send an empty payload which should be interpreted as version 0. This design choice allows deferral of the transport version number encoding until version 1. If the received version number is malformed, it will be interpreted as empty.
def respond_v2_handshake(initiator, ellsq_X): y, ellsq_Y = v2_keygen(initiating=False) ecdh_secret = secp256k1_ellsq_ecdh_secret(y, ellsq_X, ellsq_Y, initiating=False) initialize_v2_transport(initiator, ecdh_secret, initiating=False) send_bytes = ellsq_Y + v2_enc_msg(initiator, TRANSPORT_VERSION) send(initiator, send_bytes)
Upon receiving the encoded responder public key, the initiator derives the shared ECDH secret, instantiates the encrypted transport and sends its own transport version number as well. The transport session version is set to the minimum of the supported versions.
def initiator_complete_handshake(responder, response): ellsq_Y = response[:64] ecdh_secret = secp256k1_ellsq_ecdh_secret(x, ellsq_X, ellsq_Y, initiating=True) initialize_v2_transport(responder, ecdh_secret, initiating=True) responder_transport_version = v2_dec_msg(responder, response[64:]) send(responder, v2_enc_msg(responder, TRANSPORT_VERSION)) set_transport_version(responder, min(responder_transport_version, TRANSPORT_VERSION))
The responder also similarly sets the session version:
def responder_complete_handshake(initiator, msg): initiator_transport_version = v2_dec_msg(initiator, msg) set_transport_version(initiator, min(initiator_transport_version, TRANSPORT_VERSION))
To aid disambiguation of v1 and v2 handshakes, public keys with Elligator-squared encodings starting with the 12-bytes of
NETWORK_MAGIC || "version\x00" are forbidden for use by the initiator. This restriction does not apply to the responder.
def v2_keygen(initiating): while True: priv, ellsq_pub = secp256k1_ellsq_ecdh_keygen() if not (initiating and ellsq_pub[:12] == NETWORK_MAGIC + "version\x00"): break return priv, ellsq_pub def secp256k1_ellsq_ecdh_keygen(): priv, pub = secp256k1_keygen() ellsq_pub = secp256k1_ellsq_encode(pub) return priv, ellsq_pub
The peers derive their shared ECDH secret only from the x-coordinate of the secret curve point (ignoring the y-coordinate).
def secp256k1_ellsq_ecdh_secret(priv, ellsq_X, ellsq_Y, initiating): ellsq_theirs = ellsq_Y if initiating else ellsq_X pub_theirs = secp256k1_ellsq_decode(ellsq_theirs) xonly_ecdh = secp256k1_xonly_ecdh_raw(pub_theirs, priv) return sha256_tagged("secp256k1_ellsq_xonly_ecdh", ellsq_X + ellsq_Y + xonly_ecdh)
sha256_tagged(tag, x) returns a tagged hash value
SHA256(SHA256(tag) || SHA256(tag) || x) as in BIP340, and
secp256k1_xonly_ecdh_raw(X, y) returns the 32-byte big-endian encoding of the x-coordinate of the point
pt = y*X on the secp256k1 curve.
Elligator-squared prescribes the properties of the mapping function from field elements to curve points and provides one such choice in section 4.3. The mapping function used in this proposal is described in another paper by Fouque and Tibouchi. Let
f be the function from field elements to curve points, defined as follows:
def f(t): c = 0xa2d2ba93507f1df233770c2a797962cc61f6d15da14ecd47d8d27ae1cd5f852 x1 = (c - 1)/2 - c*t^2 / (t^2 + 8) (mod p) x2 = (-c - 1)/2 + c*t^2 / (t^2 + 8) (mod p) x3 = 1 - (t^2 + 8)^2 / (3*t^2) (mod p) # At least one of (x1, x2, x3) is guaranteed to be a valid x-coordinate on the curve for any t for x_candidate in (x1, x2, x3): if secp256k1_is_valid_x_coord(x_candidate): x = x_candidate break # Pick the curve point where the Y co-ordinate is the same parity(even/odd) as t if t % 2: y = secp256k1_odd_y(x) else: y = secp256k1_even_y(x) return secp256k1_point(x, y)
The Elligator-squared encoding of a curve point P(public key) consists of the 32-byte big-endian encodings of field elements
u2 concatenated, where
f(u1)+f(u2) = P. The encoding algorithm is described in the paper, and effectively picks a uniformly random pair
(u1,u2) among those which encode
P. For completeness, to make the encoding able to deal with all inputs, if
f(u1)+f(u2) is the point at infinity, the decoding is defined to be
f(u1) instead. A detailed writeup on the encoding algorithm we use can be found here.
The authenticated encryption construction proposed here requires two 32-byte keys per communication direction and a rekey salt. These (in addition to a session ID) are computed using HKDF per RFC 5869 as shown below.
def initialize_v2_transport(peer, ecdh_secret, initiating): # Include NETWORK_MAGIC to ensure a connection between nodes on different networks will immediately fail prk = HKDF_Extract(Hash=sha256, salt="bitcoin_v2_shared_secret" + NETWORK_MAGIC, ikm=ecdh_secret) # We no longer need the ECDH secret memory_cleanse(ecdh_secret) initiator_L = HKDF_Expand(Hash=sha256, PRK=prk, info="initiator_L", L=32) initiator_P = HKDF_Expand(Hash=sha256, PRK=prk, info="initiator_P", L=32) responder_L = HKDF_Expand(Hash=sha256, PRK=prk, info="responder_L", L=32) responder_P = HKDF_Expand(Hash=sha256, PRK=prk, info="responder_P", L=32) if initiating: peer.send_L = initiator_L peer.send_P = initiator_P peer.recv_L = responder_L peer.recv_P = responder_P else: peer.recv_L = initiator_L peer.recv_P = initiator_P peer.send_L = responder_L peer.send_P = responder_P peer.session_id = HKDF_Expand(Hash=sha256, PRK=prk, info="session_id", L=32) peer.rekey_salt = HKDF_Expand(Hash=sha256, PRK=prk, info="rekey_salt", L=32)
The session ID uniquely identifies the encrypted channel. v2 clients supporting this proposal may present the entire session ID (encoded as a hex string) to the node operator to allow for manual, out of band comparison with the peer node operator. Future transport versions may introduce optional authentication methods that compare the session ID as seen by the two endpoints in order to bind the encrypted channel to the authentication.
The BIP324 cipher suite uses a two layer cipher suite. We use RFC8439 for the inner-layer which gives us the ciphertext (for a 1-byte header and the application layer payload) and a MAC tag authenticating the ciphertext. Since we use a single bytestream to send multiple messages, we still need to provide the peer with the length of the ciphertext to locate the message boundary. We use
FSChaCha20, a new cryptogtraphic primitive introduced below (keyed separately) to encrypt the ciphertext length. This results in an entirely pseudorandom bytestream.
rfc8439_encrypt_and_auth(key, nonce, plaintext) is defined as
chacha20_aead_encrypt(aad, key, nonce, constant, plaintext) according to RFC8439 with
aad set to be empty. It takes a 256-bit key, 96-bit nonce and returns a ciphertext that is the same length as the plaintext along with a 128-bit message authentication code tag.
ChaCha20PRF(key, nonce, ctr) is the pseudorandom function (PRF) defined in this paper. We use it with a 256-bit key, a 96-bit nonce (to stay similar to how RFC8439 uses ChaCha20) and a 32-bit counter as inputs, and obtain 512 pseudorandom bits.
The PRF can be composed into a deterministic random bit generator (DRBG) producing a pseudorandom keystream as shown below.
def ChaCha20DRBG(key, nonce): ctr = 0 while ctr < 2**32: yield ChaCha20PRF(key, nonce, ctr) ctr += 1
The stream cipher derived from
ChaCha20DRBG(key, nonce) (by XORing the keystream with the plaintext) does not provide forward secrecy in case of a compromised key. This proposal outlines a new
FSChaCha20(key) stream cipher construction to permit implementations that offer forward secrecy.
FSChaCha20(key) stream cipher is composed by re-setting the key on the underlying
ChaCha20DRBG(key) instance using
key = SHA256(rekey_salt + key) every 256 messages.   The nonce is incremented upon re-key.
CHACHA20_BLOCKSIZE = 64 # bytes REKEY_INTERVAL = 256 # messages def get_nonce(rekey_ctr, msg_ctr): return msg_ctr.to_bytes(4, 'little') + rekey_ctr.to_bytes(8, 'little') class FSChaCha20: def __init__(self, key, rekey_salt): self.key = key self.rekey_ctr = 0 # internal 64-bit re-key counter self.msg_ctr = 0 self.keystream = b"" self.rekey_salt = rekey_salt def encipher(input): # We do not use the message counter in the nonce to reduce wasted pseudorandom bytes in the outer layer nonce = get_nonce(self.rekey_ctr, 0) if len(self.keystream) < len(input): for _ in range(((len(input) - 1) // CHACHA20_BLOCKSIZE) + 1): self.keystream += ChaCha20DRBG(self.key, nonce) output = input ^ self.keystream[:len(input)] self.keystream = self.keystream[len(input):] self.msg_ctr += 1 if self.msg_ctr == REKEY_INTERVAL: self.rekey_ctr += 1 self.msg_ctr = 0 self.key = hashlib.sha256(self.rekey_salt + self.key).digest() return output
As mentioned above, our approach uses two layers. We will call the outer layer,
FSChaCha20 used for encrypting the 3-byte length per message) and the inner layer,
P(RFC8439 instance used for encrypting and authenticating the 1-byte header and application layer payload).
P is re-keyed every 256 messages similar to
Before encrypting or decrypting the first message:
rekey_ctr = 0 # 64-bits msg_ctr = 0 # 32-bits L = FSChaCha20(k_L, rekey_salt)
When encrypting a message
M of length
- Set a 96-bit nonce
nonce = rekey_ctr + msg_ctr
len(M) < 2^24 - 1. If not, the message cannot be encrypted
1if you want the receiver to ignore this message, else set it to
header = ignore << 7
(C, mac_tag) = rfc8439_encrypt_and_auth(k_P, nonce, header + M)where
Cis the cipertext and
mac_tagis the authentication tag
lengthto the 3-byte little endian encoding for
len(C)(which is the same as
1 + len(M)since the header is 1-byte)
C = L.encipher(length) + C
C || mac_tagis now the content that can be transmitted to maintain confidentiality and integrity.
msg_ctr. If it is 256, re-key
k_P = SHA256(rekey_salt + k_P), increment the
rekey_ctrand reset the message counter
msg_ctr = 0
- Set a 96-bit nonce
nonce = rekey_ctr + msg_ctr
- The encrypted length can be decrypted as soon as 3 bytes of ciphertext,
Chave been received. Set
length = L.encipher(C[:3])and interpret
lengthas the little-endian encoded length of the ciphertext payload, i.e.
len(C)which is also
1 + len(M).
- Compute the mac tag for
C[3:3+len(C)]according to RFC8439 using
constantset to empty and compare it in constant time to the 16 bytes starting at
C[3+len(C)]. The byte comparison must be done in a constant time manner. If the expected MAC tag differs from the provided MAC tag, the connection MUST be immediately terminated.
C[3:3+len(C)]per RFC8439. Interpret the first byte of the obtained plaintext as
ignore = header >> 7.
ignoreis non-zero, ignore the message, else, pass the remaining
len(M)bytes of the plaintext to the application layer.
msg_ctr. If it is 256, re-key
k_P = SHA256(rekey_salt + k_P), increment the
rekey_ctrand reset the message counter
msg_ctr = 0
LENGTH_FIELD_LEN = 3 HEADER_LEN = 1 RFC8439_TAG_LEN = 32 IGNORE_BIT_POS = 7 REKEY_INTERVAL = 256 # Yields (disconnect, ignore_message, bytes) def BIP324CipherSuite(key_L, key_P, rekey_salt, is_encrypt, crypt_bytes, set_ignore=False): L = FSChaCha20(key_L, rekey_salt) rekey_ctr = 0 msg_ctr = 0 while True: nonce = get_nonce(rekey_ctr, msg_ctr) ret = b"" ignore = False disconnect = False if is_encrypt: if len(crypt_bytes) >= 2**24: raise MessageTooLongErr payload_len = HEADER_LEN + len(crypt_bytes) header = 1 << IGNORE_BIT_POS if set_ignore else 0 ciphertext, mac_tag = rfc8439_encrypt_and_auth(key_P, nonce, header.to_bytes(HEADER_LEN, 'big') + crypt_bytes) ret += L.encipher(payload_len.to_bytes(LENGTH_FIELD_LEN, 'little')) + ciphertext + mac_tag else: payload_len = int.from_bytes(crypt_bytes[:LENGTH_FIELD_LEN], 'little') mac_tag = crypt_bytes[LENGTH_FIELD_LEN + payload_len: LENGTH_FIELD_LEN + payload_len + RFC8439_TAG_LEN] authenticated, plaintext = rfc8439_auth_and_decrypt(key_P, nonce, crypt_bytes[LENGTH_FIELD_LEN:LENGTH_FIELD_LEN + payload_len], mac_tag) # Terminate connection if authentication fails if not authenticated: disconnect = True else: assert len(crypt_bytes) == LENGTH_FIELD_LEN + payload_len + RFC8439_TAG_LEN header = plaintext[:HEADER_LEN] ignore = header >> IGNORE_BIT_POS if not ignore: ret += plaintext[HEADER_LEN:] msg_ctr += 1 if msg_ctr == REKEY_INTERVAL: key_P = hashlib.sha256(rekey_salt + key_P).digest() msg_ctr = 0 rekey_ctr += 1 yield (disconnect, ignore, ret)
def v2_enc_msg(peer, msg_bytes, ignore=False): _, _, ret = BIP324CipherSuite(peer.send_L, peer.send_P, peer.rekey_salt, True, msg_bytes, ignore) return ret
def v2_dec_msg(peer, encrypted_bytes): disconnect, ignore, ret = BIP324CipherSuite(peer.recv_L, peer.recv_P, peer.rekey_salt, False, encrypted_bytes) if disconnect: peer.disconnect = True return None if ignore: return b"" return ret
TODO: Update the numbers here after changing the code.
Each v1 P2P message uses a double-SHA256 checksum truncated to 4 bytes. Roughly the same amount of computation power is required for encrypting and authenticating a v2 P2P message as proposed.
v2 Bitcoin P2P messages use the v2 encrypted message structure shown above. The ciphertext payload is composed of:
|Field||Size in bytes||Comments|
|message_type||1-13||Encrypted one byte message type ID or ASCII message type|
If the first byte is in the range
[1, 12], it is interpreted as the number of ASCII bytes that follow for the message type. If it is in the range
[13, 255], it is interpreted as a message type ID. This structure results in smaller messages than the v1 protocol as most messages sent/received will have a message type ID. 
The message type IDs for existing P2P message types are:
ADDR:13 BLOCK:14 BLOCKTXN:15 CMPCTBLOCK:16 FEEFILTER:17 FILTERADD:18 FILTERCLEAR:19 FILTERLOAD:20 GETADDR:21 GETBLOCKS:22 GETBLOCKTXN:23 GETDATA:24 GETHEADERS:25 HEADERS:26 INV:27 MEMPOOL:28 MERKLEBLOCK:29 NOTFOUND:30 PING:31 PONG:32 SENDCMPCT:33 SENDHEADERS:34 TX:35 VERACK:36 VERSION:37 GETCFILTERS:38 CFILTER:39 GETCFHEADERS:40 CFHEADERS:41 GETCFCHECKPT:42 CFCHECKPT:43 WTXIDRELAY:44 ADDRV2:45 SENDADDRV2:46
The message types may be updated separately after BIP finalization.
TODO: Update test vectors after updating the code.
The test vectors below illustrate the lifecycle of the authenticated encryption scheme proposed in this document. While most fields are self explanatory,
ciphertext_mac_0 is the ciphertext and MAC obtained by encrypting the
plaintext one time and
ciphertext_mac_999 is the ciphertext and MAC obtained on the 1000th iteration of encrypting the same payload.
initiator_privkey: 9cdfc7df74056ddebee98e3310026ecb11578cad9c5d09457194cc2162a1973b responder_privkey: 2030aaaf44a1437c07c938aa33c58751a6aee0c0e48e285f8031b137f498921d initiator_ellsq_r32: c1efb3a6738a6d612f5f27dc35959c7e5c7d3ec15ffae3ca3159abd1582e8db7 responder_ellsq_r32: cb9dfe3802ae4caf320971d52f36f284ad88ddb976976cc2deb6bb39d0a79fde initiator_ellsq: c6e4580be2a41dd5bfe632c46bc77c184908feb169b5d54afa79413ee48b56c6630d17f20004fc8c3de11013e979079f76066ed14a7cd774a642f0aaa5297691 responder_ellsq: 9e3fae2f60318dfa19a05120be6e44923052999d873f49f44a2bd87454d2dcada92a7871e7bfb52ea3f5f6a6c2f77c8e9d72afbb52d6d43db16a4aae9e8e2ddd shared_ecdh_secret: bfd8c535f857d7497d29e48b157b9e3d7a88686824ee8f840971be70a7be5920 initiator_F: 9700dfcbf5184af88ef064cff42eedb18b1afcc2ca50789350f5a4ede06cff86 initiator_V: 3a94fe3415bc019459a7f63a9d289f4216bbfddba15b0fcdf4c23ca4ea9a3b37 responder_F: 9112e0dc3d5b8eb2e541174fde11d206010f5476e1ce1cfa88cf47d9ba36f3c8 responder_V: aec27acf4b74660d1e2c84c12ff3abfb7fd37b663862fcc3cb774742f5f30ad8 session_id: c601328e586f36ab09d9645d5585b1666bf08c4586355c043bf5f987e214f638 initiator_plaintext: 9bc0f24442a76af47b9daa9f0c99d41381c0c06698ffc4ad069acf3d20928277433818565904cdd66ea93b1b755a3293d1d154110faa8add3dcafed2328fffea initiator_ciphertext_mac_0: 721aa1b6d699142d723b0e0c80279e7609d97204b06b45de55a3619488c76e3674a73beb702de27e71fc08c5120c6efe07d737704f5aa49b751d32b9022d86074af4e38985438193ed633f9932221834 initiator_ciphertext_mac_999: 74278bacb4201e2937839039ca292aa4dc9585ccc7cfe4ff22dbdacca871fa8cd0fc6f5a7ad9be439abc4ee0a6be0ef7bee7cbb6091f6c4628f9620849def1ab866760313d94d7a032599f0f14388117 responder_plaintext: 2822acbe87f9e1a284f2eaa9a56f948fc0de0c91f342ae541722b758d7956c9a8fe25b082789a2fb5b23da639d05e438461e7fcf92262dbeaeebbacbf01dcfb2 responder_ciphertext_mac_0: e731c8ec08bc3d5b65bab054a8e9e8ab8c23b65b85f09eef8d72eab5325d65b321ccbda552a1035c85f6f5e8e17778a1332b16f637335db39e04dbc4bdfa3cc8a131d10d66ad6ed49bc91b204fa7a41a responder_ciphertext_mac_999: 523e22e00ef15bae2066dc4b9ac343b8e25261027d19f95b9b4c4917616365984093ea3d9d4505797d5b348bc50a1038418413b2164f472ba4a7062ee9cd7fb4d2934a8350a42af41e695b574d510494
initiator_privkey: 44e5fe764c43d1f7a60ead0acd0e74a4b14f7b7fc056984a993dede99e04c743 responder_privkey: fe6065b12cdfd53b9cd9b55c491063d60abdccc3090d2cdba17bf093fe363f09 initiator_ellsq_r32: f54a836324dcb9c5701c3f73edf96ebfea053a2af1be4e7bb178bf721bad5e4d responder_ellsq_r32: ef7cd5de28f2b6b77f59ef3b4d00939841e0ab9ab5fdd351e83ce0626c90e866 initiator_ellsq: 673dbb95454e936c66e87ad6febc84386218e71cb677b6a5dee7301fdc7838148928194c153c17b0d4a279034d3f470932fc3fbed996e4fc026963a5124a2e9f responder_ellsq: fe50e78b30f2ffee97740c6b5a0788677b70d57876a0c54a7e447413406e79e91c8a756b83592d9297deb884687302ecef77e32c02d74ca52cf0548542cde1e0 shared_ecdh_secret: f3652ea42e8bf49137128c1a19cbfecb246a2dd70e76e7ab63acca54cdc2d8f0 initiator_F: e3b4c4944ddc3ac5155957b7a0cf2950fcc4e3de100868af82ff33873c96230f initiator_V: a437bb9995bcaeb53bd9cbdb18ea754af2442150d05aa9b5224f492794b60978 responder_F: 77593b41779de3f9e16fa8e708a7d33710b7a5c8c37a60a7456403133061513c responder_V: 5254941ce174f447d639aac2e6984130a82580317e0761d0fe85e5600177e4a1 session_id: 9522bb78e9e23c7e4d8dc3e54cbe90aaf01123abafd796764e83fc345029eb75 initiator_plaintext: a4874343279f3ca57427a8649833a7d276023e1035f85a7bfe19597055657192b9d2c102c69f0c8b2fdaeb064cc7432e549614e5aef603f9cf41e44a2f0b41b0 initiator_ciphertext_mac_0: 6ff5d19e58165c774b2f698712b55f06bafe393457cbec42c94b07f13e50f0f7482ffc5650c08a4133ceb8bc4bb1140ae3f0f33c42b30675ed3f5f47faf2c40a173ff87b62fec3c543d7f96745658f5d initiator_ciphertext_mac_999: 75c5d09f8db9d969815d21c217a9308e5d5a89f208f6b9cb53566def79d43f2e170b6a41674abf443bdf3d0c1eaa9a0375378450d702c1f17e7fdfa65bac4144de87cd9118098e655598e4493ccb939d responder_plaintext: dc77639158727bd733b8accc7c4bd27d329653bceace8be353b02fa56dda8598ff52c833e6aa826c9b7458d978490b24e6cd267afe6f4f1f47edf732e6d08beb responder_ciphertext_mac_0: 2089ce8cd6fec8975d4cbd82fcc36a5368e5bea528a90068fc822c850dccae127a36b1b38aba6bd72e60fb98bc696d349ad3b2eed8cf10b95b31baf9c28eafbbd6f1412f5bc3179e0c8f797ba4777353 responder_ciphertext_mac_999: 614ab91b337bee5973e8e95847736f3e2c82c198b14cc6254b0a367466b2328504e69b994c7553270057097146133ccc5b5f1ae87583520f2ec468e946c01dd6e08fd9d5f961c9b0aa5ec38e3e73bf54
initiator_privkey: 2e26264ea126f08b8baa90f394defc2af8e5e1a3392c1cf6456ba7879494cc29 responder_privkey: 9fc639ee5a340b6d646d3c0ab35e634c565d25d8dde5fa2ca2e79fad07c9a6d5 initiator_ellsq_r32: 794d7f24d16def675f37c41277349fc7186bfa38943f3e349fb98a9c28cd92a8 responder_ellsq_r32: 2b8e3b215a08098a43604a9f0305a2ec5f5dc0cc624293fc285c6e5f1ad412f9 initiator_ellsq: 96bb686b49a706bed07dd1e124ab0cd7257e29d3f8b4174e804e944aff8ad5bc8460784d65e716b2a6669ffc35afcc7ab9d6f4d4ce4aaa948cae292c0ccfccfa responder_ellsq: 6381f713bab8b2fc1f1221670a8c02e6abe280aac6e54098366aba04c37f3680eed2cd477d93fbc8ef888e0bdca4e1fb077bd82586a391d7683f5c7cbdc58ea4 shared_ecdh_secret: 9d87e83bb4282e32e03c3c3dfafdd0a99c908f34563b8023f4e317036851ded1 initiator_F: 325fdbc587a13b7294845feb4d9ec66741f133d7d87f159c949394ad9165da35 initiator_V: a3f77893d7c3874babb0d3389900a4fcc74668b2416ebdce6804ac9198f0c751 responder_F: e31d3f38d4ed8fbb4ddee0d930a74a2a15523de59086e48ca1b795dd73edac92 responder_V: 4884f94ba80d6ec5a48530aad1060f6e54f10dd8bba5960995507e9816135370 session_id: 2d401c456cc77c97c2adb3a0d2bc280c4c629850675cf682528ef3a4fc8b9c3f initiator_plaintext: 868d3648fbd816c72c54c12f403127503ba305eeb1a65a144009fae2b6e3bea4c76034a88ccee3a3396c69a970b4fff2f381097d33943a0d455f6f3303a4d3dd initiator_ciphertext_mac_0: c2dd81a1b58d227e593e222299d4365c15af30e22ef3d7ededc5ceb3f5c7ef9d19f432c1801799101ff4f8d7eeb50e02fb3968eff2b1dd1468309dfb91d172961c70c78826242ba6086f1aee0447baef initiator_ciphertext_mac_999: d02ae3450d2c627ef7ce1f6d9c79033c0a120ae3bc3a81b40a32265a56a941d275a9e69d93e39d566176d606bf6985af2d2604b7c5c713ded491b4687243c780e86954ba123de33ed59d53a9579dae4c responder_plaintext: ba67e844fa8d7aa6b77dbb1737c3080da7b65c36b219d91da499b2fb58b6e6e711e7d2960ce744d1e15351badf205a829f7b55b74e971e0a9547d88ec3c30686 responder_ciphertext_mac_0: cdbd75b66a6e328bb6caa980e7fdbb4a6c29280d1c183b0aa3115d364091878f35389dcef92a3beabc54afe30802b0344df32b6f9a12673dab8ae373cd56bed6ca9f19825f75b2b562b079826655e40a responder_ciphertext_mac_999: 385c2140033bf344028b282baec542346ce8116b1820f79c07d7e3e2a496bd6dac54bba7237a659803d8c94e3f803829dec71729d9f6e9e8063bcef9776d5992e3003c3ff3f4f5ebaa310dbd49a07c8c
initiator_privkey: a371e20223e60e967233fe079f052aeabd30f6c6781314f3e7c44e049c648b7d responder_privkey: 8063aec031db643874c6629942c402e48f7d74abaf97a8faf8d4628010e46ba4 initiator_ellsq_r32: ec23b3eab32028a9981ff20851abdd10846951b88989950cc31565bd9a3cda79 responder_ellsq_r32: 546bfd88292d90a9bbf697380c68f017fdf911d20acad6c3c7e900eff0205a83 initiator_ellsq: 838423b90876431a97d31c881bf168d3e64dc5b9ce2feb3d344910a3394aec57c0c701c5d4b99febb62c637319369e02fcb5a0af3f5879e65ea0892e08ed3704 responder_ellsq: 8ac47b65a6f9670216a4ce1b25a04caab0db3383d2ba940cd335c5a5953ffc4de1dec70c8606e0ee009f49dff607b5c72240f67e1574893a7b4997b99a39d29c shared_ecdh_secret: 0275605a44d65f47d81ec714315e8b92731b1b063daec2b3125dd455d3099b7f initiator_F: 162698894238972d7c851908961235f530890ef251e6da6c20f8e64fcb122ff2 initiator_V: ed0650d151c90c106d8d1578aa9c27f7ebc13adc08e2b4f4ef642859546fc8cb responder_F: 70d28c04c85fdc62d20cb982e329a976f18e3c98e8f4c769b0d5504bb8a975e2 responder_V: 1b5b7b526af54a45c1aebf2e43cf30742798e638a0e3ca5ce4e4373fcf472b7c session_id: dde1588198203a7d2309df7bc3b67941004f97a63cadd83ec1b8859cc3e0ff9e initiator_plaintext: 3e7443578c300b7210860c17168c9e11414781f6168710968777b567f1c27165bc8118ef402150549c18de9b567b85d4046fbef91f502f8cf4c298888ddd434b initiator_ciphertext_mac_0: 5ab6cb5ec037e6eb84a6c411f68e0d7bda6b17da949f03cb8521bebb825f7d9e72699989961ea29b2c4d253d3636dbc96c45f261ef883592282922160949772fb43e63a8084afb808063c7b7a186feb7 initiator_ciphertext_mac_999: 4804238eef14c76a1bd602c0755ea53bc0790f252dff4ca9996d0ed2497db7226ec631e412e07d715922cb03921d907f02e5f1daa82d0e97a730edb8a84f628b9f7c9f4c4889867a6c95c16586e592ee responder_plaintext: 7f6c9fbae0c003bb38ee2e73c31b248d639cc63b0d5d57b05f57c8b82122d61e401af33d481304a7d956b9ca730500890908682b14933cde958bf497cbcbbd45 responder_ciphertext_mac_0: c9fa4d4c1a404d1e707b8412d5e668129fd7a0fd2ae85be93c1b5f81bed223d9ce54d4369ada34d94bb1b2650a4e175ae0d3fc30a0fde3fd8b43ba37d84382cff5e5d1f0f0a956bd678e96c99041d5f4 responder_ciphertext_mac_999: e89ca85ef9edbc1fce19b1af7972201070359c94cbe7c2f26fcbc76c97d7187730ab5e6470483b8b49f22680cbf8d6973fafab181aa5bd0bfeff0e57b56e33cfc505ec09b94647c4eedcde989104bfae
initiator_privkey: 928861cf12421b8174bce71bdbdf4397213e17977e40116d79fd42372dfce856 responder_privkey: 1b06ce10bfdeb76e002d370df40120eb0472b432c5f6535d6a47cff44e126255 initiator_ellsq_r32: 1f909dc3ba59acbc6d24f589712cba5ac3926d7c8bc79f02316f4d1adb4f1b26 responder_ellsq_r32: 8bc6a59833a8e94810665ac0360b8c976d3f6dfec9573ae8333759e7d5fa8af8 initiator_ellsq: 4b681cf9aced2d5dbc515a1b82229e8fd9109d25827a8788064957f1578203aab31a5f52bfdcd625b0deead1d6172bbfead0be81365ab986584dfbff6620d403 responder_ellsq: ae43ccfeb45f7acc8a6e40d6c202d3845e8d19c8e1233030770b394c0ef5e239da0bb9ef921d5d7fcc6835f5d88ce538728919b5a465b25da786a19018c2891f shared_ecdh_secret: 1832945b258ae71af6fd14266047629ae4363587ee0e7ec8bc5d94a4aee2cfb7 initiator_F: 9663c2dedeb0cac8d16696944b5da3b726f900dc8dd8fcb59704d1c0c22968ca initiator_V: dbd218da57787a363f2fad7b797ab124e09dace125fb03930c750e651cd348b7 responder_F: 16d67432eb7866746ffdcfa660bdcedfe3b964aa99cb50b9fa426cfe8b09c276 responder_V: 002f859dc28fce49f9bdaada974688ae872deb089c200b5d00101abb2b74389b session_id: 0c77bd7b101a9f2da27da6ea0aba3bffb0679c7bdeff3ddeb5e00a375570ec93 initiator_plaintext: 7ab5826761ecf971d0aeff4c14eed091a206d29ddd84681c206b333bf0e121fcc5f8d45a266ce9ded4f7476edd0ab941c59cf4bca47f9327cf26a78ab4c9e7d6 initiator_ciphertext_mac_0: f85759a7cea0ea0f1ee5296866aadb04bfa84bf1c4ac99136e0af930a60057569a6f58b4ea6baac2d02ff876d783c8941af563b3b172e81d381698ba624e053029365d1ae71e2b2938e20c55900ef547 initiator_ciphertext_mac_999: afb98425ca1fad833bcc5f7a0f82d35e7e695957b220b24ae7c58c3b29e89cfa030b01e58015340c1476d39f53e9a6594b7f230f91e9ccb16513d91d2d840920ad76623430825083b4739ca09fa4268f responder_plaintext: dee314b076651a31f0e7451f4e0c3cebddeb6ce82d937b14e036cfa8ae8a91d3afd2760351c0c146fe8740874a3e281fb298cb00a1d9e58a1081f173466ceed6 responder_ciphertext_mac_0: 174770b958408c64838759f470480cacaed1f9034c80254763a0aa0648a5cd1c09df983da2f848c9c7f05e6c69df3a8547fa88b545d6b8b497d34ac4885f3fd4656e5baee10c99f1325f651723a1e0c9 responder_ciphertext_mac_999: 458304181686c4deaf25b223a59951edeaf5ca95202875c50f4fbc4549dde8c098bb6021a543a324a88f0d832079fb7cdf10c606472385e946aa5d8c7d16aa19b440416542848b12b215b759079ac987
- ^ For more information, see the Bitcoin over Tor isn't a Good Idea paper
- ^ While Noise and TLS (as a draft) offer similar protocol extensions for exporting session IDs, using channel binding for authentication is not at the focus of their design and would not avoid the bulk of additional complexity due to the native support of authentication methods.
- ^ Elligator-squared is an encoding for elliptic curve points that allows us to make the serialization indistinguishable from a uniformly distributed bitstream. While a random 256-bit string has about 50% chance of being a valid x-coordinate on the secp256k1 curve, every 512-bit string is a valid Elligator-squared encoding of a curve point, making the encoded point indistinguishable from random when using an encoder that can sample uniformly.
- ^ In writing this proposal, the running time of Elligator-squared encoding was benchmarked. Encoded ECDH is about ~2.72x as expensive than the unencoded ECDH. Given the fast performance of ECDH and the low frequency of new connections, we found the performance trade-off acceptable for the pseudorandom bytestream and future censorship resistance it can enable.
- ^ Once an encrypted transport and its session version is established, further properties of the transport can be negotiated. Examples include post-quantum cryptography upgrades to the handshake and optional authentication. While this proposal does not include any such features, it allows for an upgrade path using transport version numbers. As a hypothetical example, v2.1 clients would exchange the transport version number 1. If both peers are v2.1, the transport session version would be 1. This could imply that before any P2P messages are exchanged, a post-quantum key exchange mechanism must be used (as defined in the hypothetical v2.1 protocol) to upgrade the security of the derived encryption keys against quantum computers.
- ^ Disallowing elligator-squared encodings that begin with the specified prefix does result in a less than perfectly pseudorandom bytestream. While the 2^-96 bias this introduces implies a security level that is below our 128-bit security level, the low frequency of connections makes this practically unexploitable, while significantly simplifying compatibility.
- ^ Using only the x-coordinate provides the same security as using a full encoding of the secret curve point but might allow for more efficient ECDH implementations in the future, e.g., based on x-only multiplication ladders. Nevertheless, the function
secp256k1_ellsq_ecdh_secretcan be implemented by calling the existing
secp256k1_ecdhfunction in libsecp256k1 with an appropriate
secp256k1_ecdh_hash_functionthat simply ignores the y-coordinate, and thus implementing this proposal does not require an entirely new ECDH implementation in libsecp256k1.
- ^ An MITM attacker should be assumed to have knowledge of the plaintext corresponding to the encrypted 3-byte length. This can lead to malleability in the encrypted length ciphertext since it is used prior to checking the MAC. We use two cipher instances with separate keys to avoid any consequences of such malleability leaking into the payload portion of the ciphertext. This is loosely inspired by a [similar](http://bxr.su/OpenBSD/usr.bin/ssh/PROTOCOL.chacha20poly1305). The two layered approach makes it easier to reason about the security of the suite. The cost in creating an additional ChaCha20 instance is very low.
- ^ The associated data is empty in our proposal as the length of the ciphertext (separately encrypted and transmit in the first 3 bytes) is implicitly authenticated in RFC8439. Even though the outer layer does not authenticate the encrypted message length, the authentication is provided by the inner layer.
- ^ If an attacker passively collects ciphertext and later learns the key, they can decrypt all the historical ciphertext.
- ^ Assuming a node sends only ping messages every 20 minutes (the timeout interval for post-BIP31 connections) on a connection, the node will transmit 256 messages in about 3.56 days. This means soft rekeying after a fixed number of messages automatically translates to an upper-bound of time interval for rekeying.
- ^ Re-keying ensures forward secrecy within the DRBG , i.e., an attacker compromising the current DRBG key cannot derive past DRBG keys.
- ^ Even if future evolutions of the protocol need messages longer than
2^24-2bytes, they should be split into multiple application-layer messages anyway, to avoid needing very large receive buffers per peer.
- ^ As of the writing of this document,
BIP324CipherSuitefor 1MB payloads (encrypt + decrypt) was about 1.5x faster than
2 * (SHA256(1MB) + SHA256(32 bytes)).
- ^ Here are some length comparisons between v1 and v2 messages:
v1 inv: 4(Magic)+12(Message-Type)+4(MessageSize)+4(Checksum)+37(Payload) == 61 v2 inv: 3(Length)+1(Header)+1(Message-Type)+37(Payload)+16(MAC) == 58 v1 ping: 4(Magic)+12(Message-Type)+4(MessageSize)+4(Checksum)+8(Payload) == 32 v2 ping: 3(Length)+1(Header)+1(Message-Type)+8(Payload)+16(MAC) == 29 v1 block: 4(Magic)+12(Message-Type)+4(MessageSize)+4(Checksum)+1’048’576(Payload) = 1’048’600 v2 block: 3(Length)+1(Header)+1(Message-Type)+1’048’576(Payload)+16(MAC) = 1’048’597
Thanks to everyone (alphabetical order) that helped invent and develop the ideas in this proposal:
- Lloyd Fournier
- Gregory Maxwell