Voluntary Mind

(WIP) BIP324 Notes

My primary 2021 Bitcoin Core project is to try and enable authenticated connections to seeds for nodes first connecting to the network. You can read more about the goal of the project here. I explored the following approaches to get to authenticated seeds:

This page is a work-in-progress - it contains my notes on BIP324 and open questions.

Why is AEAD useful for the Bitcoin Core p2p network?

The current Bitcoin p2p protocol (v1) is unencrypted, and unauthenticated. This makes it susceptible to BGP(border gateway protocol) and MITM(man-in-the-middle) attacks. The v1 protocol does use a double-SHA256 checksum truncated to 4 bytes. This checksum is (1) more computationally expensive than it needs to be and (2) only protects against random, non-malicious networking errors and not against MITM attacks (an MITM attacker can replace the content of the p2p message as well as the checksum). As we’ll see, #1 is what creates the opportunity for v2 to be faster and more secure than v1.

What is BIP324?

BIP324 AEAD encryption and the v2 protocol is compatible with the v1 protocol and is designed to avoid segmenting the network. A v2 node signals its ability to accept v2 connections using the service flag NODE_P2P_V2 = (1 << 11). This flag is available to peers via the seed filtering mechanism and is also made available in the p2p ADDR message. Per BIP324, a v2 node must accept encrypted connections via the handshake mechanism explained below.

Cipher suite

BIP324 combines the Chacha20 symmetric cipher for encryption and the Poly1305 MAC(message authentication code). This cipher suite is also used in TLS 1.3 AEAD. However, because https uses SSL Certificate Authorities, and Bitcoin can’t, despite using Chacha20-Poly1305, the v2 protocol will remain vulnerable to MITM attacks. The attacks on v2 however become observable as we’ll see below.

The v2 protocol uses a Diffie-Hellman handshake to establish a shared secret between the peers. It then uses HKDF extract-then-expand to deterministically (1) make sure that the bits in the shared secret are from a distribution close to uniform and (2) derive four keys and an encryption session id.

Handshake

The initiator generates an ephemeral secp256k1 (no new dependencies!) key pair (pub_A, priv_a) where pub_A = priv_a * G and sends just the 32 bytes of the public key pub_A to the peer. This key must NOT:

Using a standard 33byte pubkey with 0x02 or 0x03 at the beginning would make a handshake trivially identifiable by someone listening on the wire. If we only use ODD pubkeys, the handshake looks pseudo-random (it is still easy to identify a bitcoin handshake by checking if the first 32 bytes is a valid secp256k1 curve point). It is a low hanging fruit that improves the identifiability-robustness slightly with almost no cost.

There is nothing else sent in the initiation. No headers, no checksum, no payload. The entire TCP message is just the 32 bytes of the public key. The other peer does exactly the same, and responds with its own 32-byte secp256k1 public key pub_B. Once both peers know the other public key, they can each use their private key with that to generate a shared secret that we will call ECDH_KEY. So long as an attacker knows neither private key, and the cannot solve the discrete log problem over elliptic curves, the shared secret remains a secret.

ECDH_KEY = pub_A * priv_b = pub_B * priv_a = priv_a * priv_b * G

Key extraction from shared secret

Once the shared secret is established, both peers run a deterministic process to generate 4 keys and 1 session id: Keys K1A and K2A are used by peer A to send messages to peer B. Keys K1B and K2B are used by peer B. At first I thought the two keys per peer were one for the encryption and one for the MAC. But that is not the case. From BIP324:

The instance keyed by K_1 is a stream cipher that is used for the per-message metadata, specifically for the poly1305 authentication key as well as for the length encryption. The second instance, keyed by K_2, is used to encrypt the entire payload.

Two separate cipher instances are used here so as to keep the packet lengths confidential (best effort; for passive observing) but not create an oracle for the packet payload cipher by decrypting and using the packet length prior to checking the MAC. By using an independently-keyed cipher instance to encrypt the length, an active attacker seeking to exploit the packet input handling as a decryption oracle can learn nothing about the payload contents or its MAC (assuming key derivation, ChaCha20 and Poly1305 are secure). Active observers can still obtain the message length (ex. active ciphertext bit flipping or traffic shemantics analysis)

This two cipher instance solution seems to be derived from [email protected] into [email protected] The reasoning for using two instances for openssh can be read here. Essentially there are three ways to do AEAD:

The last two are insecure because they require decryption before checking the MAC and allows for an active attacker to build timing or padding oracles. Encrypt-and-MAC is also insecure because a MAC has no confidentiality requirement. However, the only acceptable Encrypt-then-MAC requires that the payload length be in the clear which still allows for padding oracles and traffic analysis. We can avoid that by using one stream cipher instance to encrypt the length of the payload and to generate a MAC, and then a second instance to encrypt the payload.

To understand the choice of two stream cipher instances in more details, read the [email protected] 1.5 spec. There you’ll see:

Two separate cipher instances are used here so as to keep the packet lengths confidential but not create an oracle for the packet payload cipher by decrypting and using the packet length prior to checking the MAC. By using an independently-keyed cipher instance to encrypt the length, an active attacker seeking to exploit the packet input handling as a decryption oracle can learn nothing about the payload contents or its MAC (assuming key derivation, ChaCha20 and Poly1305 are secure).

TODO: I don’t quite understand how encrypting payload length helps if the v2 message structure implies payload length = tcp_packet_size - 16(mac) - 3 bytes encrypted length

The keys are extracted from ECDH_KEY using HKDF extract-then-expand steps as shown below.

Extract the pseudo-random key(PRK) from potentially weak input key material(IKM). This is especially important as a node has no way to make sure that the peer is using sufficient entropy to generate its key:

PRK = HKDF_EXTRACT(hash=SHA256, salt="BitcoinSharedSecret||INITIATOR_32BYTES_PUBKEY||RESPONDER_32BYTES_PUBKEY||NETWORK_MAGIC", ikm=ECDH_KEY).

This makes PRK deterministically derived from ECDH_KEY but from a closer to uniform distribution. KDF extraction helps make sure exploits don’t occur in case the initial key pairs were derived with insufficient entropy.

Expand into deterministic output key material (OKM):

K1A = HKDF_EXPAND(prk=PRK, hash=SHA256, info="BitcoinK_1_A", L=32)
K1B = HKDF_EXPAND(prk=PRK, hash=SHA256, info="BitcoinK_1_B", L=32)
K2A = HKDF_EXPAND(prk=PRK, hash=SHA256, info="BitcoinK_2_A", L=32)
K2B = HKDF_EXPAND(prk=PRK, hash=SHA256, info="BitcoinK_2_B", L=32)
SID = HKDF_EXPAND(prk=PRK, hash=SHA256, info="BitcoinSessionID", L=32).

Session ID

SID, the last derived value above is deterministically derived from the shared secret. So if both peers can communicate over a secret channel and compare the session id, they can be assured there is no man-in-the-middle attack being executed. However, in the existing Bitcoin network, these is no such secure channel. The claim is that the possibility of checking the session IDs and observability of MITM attacks dissuades such attacks.

TODO: MITM not an issue with authenticated seeds because one public key would be in the codebase and cannot be then MITMed.

[email protected]

FAQs

Can’t I just use tor or a VPN?

Yes, you could encrypt p2p traffic with an OSI level technique but that requires awareness, expertise and some degree of adversarial thinking that shouldn’t be a requirement to be a full node operator.

Will encryption mean more CPU to run a full node?

Will the v2 protocol require more bandwidth for a full node?

Isn’t Diffie-Hellman key exchange susceptible to man-in-the-middle attacks?

Why is the network magic not present in the p2p v2 message structure?

The network bytes are present in the v1 message structure to ensure that two nodes that intend to be on different networks do not end up talking to each other. Since the derivation of the PRK in the HKDF_EXTRACT step (using the ECDH_KEY as weak input key material) uses the network magiv bytes in the salt, two nodes that intend to be on different networks will end up generating different PRKs and will not be able to talk to each other.

BIP324 timeline and PRs

Critical path Pull Requests:

Bitcoin Core

libsecp256k1:

Refactors to existing code pre-requisite for BIP324 v2 protocol introduction:

Supporting Pull Requests:

Things I am looking to understand