Skip to content

Instantly share code, notes, and snippets.

@dhruv
Last active July 30, 2021 00:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dhruv/808bf14d8eac0f37a0a26a1232b2c30c to your computer and use it in GitHub Desktop.
Save dhruv/808bf14d8eac0f37a0a26a1232b2c30c to your computer and use it in GitHub Desktop.
authenticated_seeds.md

Authenticated seeds

Objectives

This document proposes two paths to Bitcoin seed node authentication to facilitate conversation about the pros and cons of each.

Background and motivation

Reading:

BIP324 enables end-to-end authenticated encryption between bitcoin nodes. Optionally, if the node operators are able to compare the session ids over a secure channel, BIP324 can also make man-in-the-middle attacks observable and provide authentication.

BIP150 enables node operators (with access to secure channels) to exchange public identity-keys and introduces new p2p messages using which a node can challenge a peer to prove its identity and provide directional authentication.

As of July 2021, Bitcoin Core nodes that do not have a populated addrman use dns seeds and fixed seeds to bootstrap a database of peer addrs. DNS seeding is susceptible to DNS hijacks and both techniques are susceptible to BGP hijacks.

Encrypting connections to seeds will help increase the cost of censoring new nodes. Authenticating connections to seeds will help increase the cost of co-opting new nodes into alternate, dishonest networks.

Both approaches outlined in this document assume BIP324 e2e encryption is available for use on the Bitcoin network.

Approach 1: BIP150 authentication

In this approach we implement BIP150 authentication, and the authorized-peers file comes preloaded with the identity-public-keys(s) and domain names for all the seed nodes. Once the BIP324 encrypted session is available, the new node uses the session-id and the identity-public-key for the seed node to initiate a BIP150 AUTHCHALLENGE <-> AUTHREPLY exchange. If the seed node responds with a valid signature for the challenge hash, the node considers the seed authenticated(note, we only need unidirectional authentication for this use case) and proceeds to send the getaddr message to the seed to populate addrman.

Pros:

  • Authentication for seeds would work the same as authentication for nodes

Cons:

  • Dependency on BIP150 implementation
  • Introduction on new p2p message types is a slower and more deliberate community process (as opposed to approach #2)

Approach 2: BIP324 ECDH exchange on secure channel

In this approach, the seed node implements BIP324 with a small change. The keypair it uses in ECDH is not ephemeral. Each dns seed in chainparams.cpp, also has a secp256k1 public key attached to it and the seed node is considered the initiating node as described in the BIP324 handshake. The node that is trying to seed addrman generates an ephemeral keypair, and uses the seed's public key to non-interactively compute the ECDH secret. It then performs the second half of the ECDH handshake with the seed providing its public key which the seed node can use to generate the shared ECDH secret.

Once the handshake is complete, bi-directional encryption and unidirectional authentication is available. The node seeking seeding services can now proceed to send a getaddr message to the seed node.

This approach provides the same discrete-log security and authentication as approach 1.

Pros:

  • No BIP150 dependency
  • No new p2p message types

Cons:

  • If/when BIP150 is implemented, that is probably a cleaner and more uniform approach to authentication.
  • Doing half of the ECDH handshake over a secure, offline channel is not a commonly deployed approach. While it is cryptographically secure, we need to be more careful in choosing this option.

Considerations in both approaches

Key rotation

In both approaches, we need to allow for the seed node operators to invalidate old keys in case the private key used for identity is compromised. In general, this does not seem to be a problem though:

  • The authentication is both approaches is opportunistic as BIP324 itself is opportunistic. So unauthenticated seeding can always be made available as a fallback.
  • Seeding is done by 9 different domains and it is unlikely all keys are compromised at the same time.

Key rotation by a node operator is easier in approach #1 as it's an update to the human-readable authorized-peers. In approach #2, the node operator would need to make a change to chainparams.cpp and re-compile. This can be addressed by having the keys in user accessible files for approach #2 as well.

@LLFourn
Copy link

LLFourn commented Jul 30, 2021

Approach 1

This seems reasonable. I am not sure what the motivation was for the particulars of BIP150 were. IMO signing the session id derived from the DH key would be enough. It would be interesting to hear justifications for:

  1. why challenge string is necessary.
  2. why sending the public key with the authentication request is necessary. At least with Schnorr there is no way to recover the public key from the signature so this shouldn't be a concern. Although you could figure out which public key the server is authenticating against from a fixed set this can also be done with BIP150 just by trail and error. My preference would be to leave it out and to keep things as simple as possible.

In considering the above perhaps the protocol could be further simplified to just one new message since both parties already have the session ids. Just adding a single "auth" message should be enough. After establishing an encrypted session which you expect to be authenticated just wait for the auth message and if it doesn't come first drop the connection. This seems to achieve your goal for authenticating seeds. I may be missing wider design considerations since this is the first time I've seen/heard of BIP150.

To enable non-seed auth at a later date you could then introduce more messages to tell the peer to authenticate themselves. Whether this makes sense depends on the goals of the authentication and whether you want TOFU to be possible but I guess this can be figured out at a later date.

Approach 2

The following comes off the top of my head. When you use a static keypair for DH in this way you provide a Decisional Diffie-Hellman (DDH) oracle. Consider that I have a DDH challenge (X,Y,Z) where my task is to determine whether Z is the DH secret of X and Y. Let's say that X is the seed node's public key. I can solve the challenge by using Y as my ephemeral key and seeing if Z works as a shared secret for the session.

Now is giving a DDH oracle such a big deal? I don't think so. In the random oracle model (ROM) this kind of hashed DH key exchange is secure under the Computational Diffie-Hellman assumption so DDH doesn't matter. Theoretically without ROM I think it does need DDH to guarantee semantic security of the resulting encrypted session. This also means we couldn't securely use the node's key to do any other protocol that does need DDH (not that I can think of any reason why we would).

So the downside here is that it makes security convoluted to argue and would require more scrutiny/research. I think that it's not worth going this route it if you can just solve the problem in the protocol layer (Approach 1). Consider that the noise protocol has set the standard for the design of doing DH with static keys for authentications for all types of scenarios so if you wanted to go this way then I would copy what they do: http://www.noiseprotocol.org/noise.html. IMO it's better to keep BIP324 simple and then do approach 1 here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment