Standardise Noise handshake for libp2p #195
There have been isolated initiatives to implement a Noise handshake for libp2p, and it's time to standardise on a common approach for interoperability
Some questions to answer
Editor note. I found the 25519 nomenclature a tad confusing. This clarifies the difference between X25519, Ed25519, Curve25519; excerpt:
To sum things up, there are two possibilities:
In other words, it's a choice to make: do we stop being generic over the format of the node's public key, or do we go against the design of the Noise protocol? I'm personally in favour of option 1.
In rust-libp2p, the suite is in the protocol name.
rust-libp2p does not do that and it does not currently support KK, but XX. If and when a new static DH key is generated, as well as its lifetime, is left to the application. It just supports either to reuse an ed25519 signing keypair or to authenticate a separate static DH keypair via any libp2p identity keypair. I think in substrate, the static DH key is generated and signed with the node's (persistent) identity keypair when the node starts and persists in-memory for the lifetime of the process.
The implementation in rust-libp2p should indeed currently support that, since the public identity keys are sent along as noise handshake payloads in addition to the static DH keys and the type of identity keys used is not part of the (static) handshake pattern name.
You make it sound like using Noise with an Ed25519 keypair is a triviality, but I don't think that is true. Ed25519 keypairs and Curve25519 keypairs for use with X25519 are not in one-to-one correspondence. An ed25519 keypair can be converted to a Curve25519 keypair but that cannot be reversed, hence the need to transmit the public identity key (in the handshake payloads) even when reusing an ed25519 keypair for x25519 in rust-libp2p.
I think this is misleading and to my knowledge is neither what rust-libp2p does nor what substrate does. The static key is not replaced by an ephemeral key. Static DH keys in Noise are by definition the keys that are reused across multiple Noise handshake executions whereas ephemeral keys are by definition used in a single handshake. rust-libp2p-noise indeed only supports handshake patterns also involving static DH keys. The fact that a static DH keypair only persists for e.g. the lifetime of a process does not make it an ephemeral keypair as far as the Noise protocol is concerned. As I mentioned in libp2p/rust-libp2p#1027 I don't agree that signing the static public DH keys with separate long-lived signature keys is "against the design of Noise", but a legitimate extension making use of the handshake payloads.
Personally, I think it very desirable to have a Noise integration that works with any libp2p identity keypair. As I mentioned in libp2p/rust-libp2p#1027, using handshakes from the Noise Signatures Extension Spec may be preferable to the current implementation in rust-libp2p in order to remove the indirection over static DH keys, though I think that it would preclude handshakes between nodes with different types of signature keypairs, as these types seem to be fixed for a particular such handshake pattern.
It does look like embedding a certificate in the handshake payload is suggested by the spec as a means of authenticating the static public key. It’s also very similar to what we’re doing for TLS 1.3 - we embed the identity key and a signature over the TLS session key into a certificate and send it in the handshake.
Also, it may not be desireable to use the identity key as the Noise static key, even if it is of a compatible key type. The security considerations section of the spec (linked above) says
Which suggests that if the identity key is used as the Noise static key, it would be difficult to prove that it’s safe to also use for e.g secio.
I agree that we shouldn't try to use libp2p identity keys as Noise static keys. It seems simplest to just always use the libp2p identity key to sign the Noise static key, regardless of key type.
Regarding the Noise Signatures spec, it does look like basically what we want, but as @romanb mentions, the signature algorithm is fixed in the handshake type. So two peers with different identity key types would not be able to perform the handshake with each other.
Below is a rough outline that assumes that we're essentially codifying the rust-libp2p behavior, except that there's no special treatment of ed25519 keys. I can start writing it up next week, if there's no major objections, or retool it if there are.
Intro & Context
What is Noise, why do we want it, etc.
Supported ciphers & hashes
rust-libp2p always uses ChaChaPoly and SHA2-265.
Is there a good reason to also support AESGCM (hardware support, maybe)?
Supported Handshake Patterns
rust-libp2p currently supports IK, IX, and XX.
Noise Pipes describes a "compound protocol" using XX, IK, and XX+fallback to
I propose we spec out support for XX, IK, and XX+fallback to enable the Noise Pipes 0-RTT
Should we also support IX?
Authenticating Noise static keys using libp2p identities
Describe and specify the transmission of the libp2p identity key and signature
Note that the lifetime of the Noise static keys is application-specific.
Protocol / Handshake negotiation
How do we negotiate which supported handshake to use? To play nice with the rest
Noise messages have a maximum size of 65535 bytes, which makes it simple to
We should think about this some more :) nQUIC seems to only support the IK