mirror of
https://github.com/lightning/bolts.git
synced 2024-11-19 10:00:04 +01:00
791 lines
25 KiB
Markdown
791 lines
25 KiB
Markdown
|
# BOLT #1: Message Format, Encryption, Authentication and Initialization
|
||
|
|
||
|
All communications between Lightning nodes should be encrypted in order to
|
||
|
provide confidentiality for all transcripts between nodes, and authenticated to
|
||
|
avoid malicious interference. Each node has a known long-term identifier which
|
||
|
is a public key on Bitcoin's `secp256k1` curve. This long-term public key is
|
||
|
used within the protocol to establish an encrypted+authenticated connection
|
||
|
with peers, and also to authenticate any information advertised on the behalf
|
||
|
of a node.
|
||
|
|
||
|
## Communication Protocols
|
||
|
|
||
|
This protocol is written with TCP in mind, but could use any ordered,
|
||
|
reliable transport.
|
||
|
|
||
|
The default TCP port is `9735`. This corresponds to hexadecimal `2607`,
|
||
|
the unicode code point for LIGHTNING.<sup>[2](#reference-2)</sup>
|
||
|
|
||
|
## Message Format and Handling
|
||
|
|
||
|
All messages are of form:
|
||
|
|
||
|
1. `4-byte` big-endian data length.
|
||
|
2. `4-byte` big-endian type.
|
||
|
3. Data bytes as specified by the length.
|
||
|
|
||
|
All data fields are big-endian unless otherwise specified.
|
||
|
|
||
|
### Requirements
|
||
|
|
||
|
A node MUST NOT send a message with data length greater than `8388608`
|
||
|
bytes. A node MUST NOT send an evenly-typed message not listed here
|
||
|
without prior negotiation.
|
||
|
|
||
|
A node MUST disconnect if it receives a message with data length
|
||
|
greater than `8388608` bytes; it MUST NOT fail the channels in that case.
|
||
|
|
||
|
A node MUST ignore a received message of unknown type, if that type is
|
||
|
odd. A node MUST fail the channels if it receives a message of unknown
|
||
|
type, if that type is even.
|
||
|
|
||
|
A node MUST ignore any additional data within a message, beyond the
|
||
|
length it expects for that type.
|
||
|
|
||
|
### Rationale
|
||
|
|
||
|
The standard endian of `SHA2` and the encoding of bitcoin public keys
|
||
|
are big endian, thus it would be unusual to use a different endian for
|
||
|
other fields.
|
||
|
|
||
|
Length is limited to avoid memory exhaustion attacks, yet still allow
|
||
|
(for example) an entire bitcoin block to be comfortable forwarded as a
|
||
|
reasonable upper limit.
|
||
|
|
||
|
The "it's OK to be odd" rule allows for future optional extensions
|
||
|
without negotiation or special coding in clients. The "ignore
|
||
|
additional data" rule similarly allows for future expansion.
|
||
|
|
||
|
## Cryptographic Messaging Overview
|
||
|
|
||
|
Prior to sending any protocol related messages, nodes must first initiate the
|
||
|
cryptographic session state which is used to encrypt and authenticate all
|
||
|
messages sent between nodes. The initialization of this cryptographic session
|
||
|
state is completely distinct from any inner protocol message header or
|
||
|
conventions.
|
||
|
|
||
|
The transcript between two nodes is separated into two distinct segments:
|
||
|
|
||
|
1. First, before any actual data transfer, both nodes participate in an
|
||
|
authenticated key agreement protocol which is based off of the Noise
|
||
|
Protocol Framework<sup>[4](#reference-4)</sup>.
|
||
|
2. If the initial handshake is successful, then nodes enter the transport
|
||
|
message exchange phase. In the transport message exchange phase, all
|
||
|
messages are `AEAD` ciphertexts.
|
||
|
|
||
|
### Authenticated Key Agreement Handshake
|
||
|
|
||
|
The handshake chosen for the authenticated key exchange is `Noise_XK`. As a
|
||
|
"pre-message", we assume that the initiator knows the identity public key of
|
||
|
the responder. This handshake provides a degree of identity hiding for the
|
||
|
responder, its public key is _never_ transmitted during the handshake. Instead,
|
||
|
authentication is achieved implicitly via a series of `ECDH` operations followed
|
||
|
by a `MAC` check.
|
||
|
|
||
|
The authenticated key agreement (`Noise_XK`) is performed in three distinct
|
||
|
steps. During each "act" of the handshake, some (possibly encrypted) keying
|
||
|
material is sent to the other party, an `ECDH` is performed based on exactly
|
||
|
which act is being executed with the result mixed into the current sent of
|
||
|
encryption keys (`ck` and `k`), and finally an `AEAD` payload with a zero
|
||
|
length cipher text is sent. As this payload is of length zero, only a `MAC` is
|
||
|
sent across. The mixing of `ECDH` outputs into a hash digest forms an
|
||
|
incremental TripleDH handshake.
|
||
|
|
||
|
Using the language of the Noise Protocol, `e` and `s` indicate possibly
|
||
|
encrypted keying material, and `es, ee, se` indicates `ECDH` operations. The
|
||
|
handshake is laid out as follows:
|
||
|
|
||
|
Noise_XK(s, rs):
|
||
|
<- s
|
||
|
...
|
||
|
-> e, es
|
||
|
<- e, ee
|
||
|
-> s, se
|
||
|
|
||
|
All of the handshake data sent across the wire including the keying material is
|
||
|
incrementally hashed into a session-wide "handshake digest", `h`. Note that the
|
||
|
handshake state `h`, is never transmitted during the handshake, instead digest
|
||
|
is used as the Authenticated Data within the zero-length AEAD messages.
|
||
|
|
||
|
By authenticating each message sent, we can ensure that a MiTM hasn't modified
|
||
|
or replaced any of the data sent across as part of a handshake, as the MAC
|
||
|
check would fail on the other side if so.
|
||
|
|
||
|
A successful check of the `MAC` by the receiver indicates implicitly that all
|
||
|
authentication has been successful up to that point. If `MAC` check ever fails
|
||
|
during the handshake process, then the connection is to be immediately
|
||
|
terminated.
|
||
|
|
||
|
## Handshake Versioning
|
||
|
|
||
|
Each message sent during the initial handshake starts with a single leading
|
||
|
byte which indicates the version used for the current handshake. A version of 0
|
||
|
indicates that no change is necessary, while a non-zero version indicate the
|
||
|
client has deviated from the protocol originally specified within this
|
||
|
document. Clients MUST reject handshake attempts initiated with an unknown
|
||
|
version.
|
||
|
|
||
|
### Transport Message Exchange
|
||
|
|
||
|
The actual protocol messages sent during the transport message exchange phase
|
||
|
are encapsulated within `AEAD` ciphertexts. Each message is prefixed with
|
||
|
another `AEAD` ciphertext which encodes the total length of the next transport
|
||
|
message. The length prefix itself is protected with a MAC in order to avoid
|
||
|
the creation of an oracle and to also prevent a MiTM from modifying the length
|
||
|
prefix thereby causing a node to erroneously read an incorrect number of bytes.
|
||
|
|
||
|
|
||
|
## Protocol Message Encapsulation
|
||
|
|
||
|
Once both sides have entered the transport message exchange phase (after a
|
||
|
successful completion of the handshake), Lightning Network protocol messages
|
||
|
will be encapsulated within the exchanged `AEAD` ciphertexts. The maximum size
|
||
|
of transport messages is `65535-bytes`. Node MUST NOT send a transport message
|
||
|
which exceeds this size. Note that this is only a cryptographic messaging limit
|
||
|
within the protocol, and not a limit on the message size of Lightning Network
|
||
|
protocol messages. A Lightning Network message which exceeds this size can be
|
||
|
chunked into several messages before being sent.
|
||
|
|
||
|
### Noise Protocol Instantiation
|
||
|
|
||
|
Concrete instantiations of the Noise Protocol are require the definition of
|
||
|
three abstract cryptographic objects: the hash function, the elliptic curve,
|
||
|
and finally the `AEAD` cipher scheme. Within our instantiation `SHA-256` is
|
||
|
chosen as the hash function, `secp256k1` as the elliptic curve, and finally
|
||
|
`ChaChaPoly-1305` as the `AEAD` construction. The composition of `ChaChaPoly`
|
||
|
and `Poly1305` used MUST conform to `RFC 7539`<sup>[3](#reference-3)</sup>. With this laid out, the
|
||
|
official Noise protocol name for our variant is:
|
||
|
`Noise_XK_secp256k1_ChaChaPoly_SHA256`. The ascii string representation of
|
||
|
this value is hashed into a digest used to initialize the starting handshake
|
||
|
state. If the protocol names of two endpoints differs, then the handshake
|
||
|
process fails immediately.
|
||
|
|
||
|
|
||
|
## Authenticated Key Exchange Handshake Specification
|
||
|
|
||
|
The handshake proceeds in three acts, taking 1.5 round trips. Each handshake is
|
||
|
a _fixed_ sized payload without any header or additional meta-data attached.
|
||
|
The exact size of each Act is as follows:
|
||
|
|
||
|
* **Act One**: `50 bytes`
|
||
|
* **Act Two**: `50 bytes`
|
||
|
* **Act Three**: `66 bytes`
|
||
|
|
||
|
### Handshake State
|
||
|
|
||
|
Throughout the handshake process, each side maintains these three variables:
|
||
|
|
||
|
* `ck`: The **chaining key**. This value is the accumulated hash of all
|
||
|
previous ECDH outputs. At the end of the handshake, `ck` is used to derive
|
||
|
the encryption keys for transport messages.
|
||
|
|
||
|
* `h`: The **handshake hash**. This value is the accumulated hash of _all_
|
||
|
handshake data that has been sent and received so far during the handshake
|
||
|
process.
|
||
|
|
||
|
* `temp_k`: An **intermediate key** key used to encrypt/decrypt the
|
||
|
zero-length AEAD payloads at the end of each handshake message.
|
||
|
|
||
|
* `n`: A **counter-based nonce** which is to be used with `temp_k` to encrypt
|
||
|
each message with a new nonce.
|
||
|
|
||
|
* `e`: A party's **ephemeral public key**. For each session a node MUST generate a
|
||
|
new ephemeral key with strong cryptographic randomness.
|
||
|
|
||
|
* `s`: A party's **static public key**.
|
||
|
|
||
|
The following functions will also be referenced:
|
||
|
|
||
|
* `HKDF`: a function is defined in [3](#reference-3), evaluated with a zero-length `info`
|
||
|
field.
|
||
|
|
||
|
* `encryptWithAD(ad, plaintext)`: outputs `encrypt(k, n++, ad, plaintext)`
|
||
|
* where `encrypt` is an evaluation of `ChaChaPoly-Poly1305` with the
|
||
|
passed arguments.
|
||
|
|
||
|
* `decryptWithAD(ad, ciphertext)`: outputs `decrypt(k, n++, ad, ciphertext)`
|
||
|
* where `decrypt` is an evaluation of `ChaChaPoly-Poly1305` with the
|
||
|
passed arguments.
|
||
|
|
||
|
* `e = generateKey()`
|
||
|
* where generateKey generates a fresh secp256k1 keypair
|
||
|
|
||
|
* `a || b` denotes the concatenation of two byte strings `a` and `b`
|
||
|
|
||
|
|
||
|
### Handshake State Initialization
|
||
|
|
||
|
Before the start of the first act, both sides initialize their per-sessions
|
||
|
state as follows:
|
||
|
|
||
|
* `h = SHA-256(protocolName)`
|
||
|
* where `protocolName = "Noise_XK_secp256k1_ChaChaPoly_SHA256"` encoded as
|
||
|
an ascii string.
|
||
|
|
||
|
* `ck = h`
|
||
|
|
||
|
|
||
|
* `temp_k = empty`
|
||
|
* where `empty` is a byte string of length 32 fully zeroed out.
|
||
|
|
||
|
|
||
|
* `n = 0`
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || prologue)`
|
||
|
* where `prologue` is the ascii string: `lightning`.
|
||
|
|
||
|
|
||
|
As a concluding step, both sides mix the responder's public key into the
|
||
|
handshake digest:
|
||
|
|
||
|
|
||
|
* The initiating node mixes in the responding node's static public key
|
||
|
serialized in Bitcoin's DER compressed format:
|
||
|
* `h = SHA-256(h || rs.serializeCompressed())`
|
||
|
|
||
|
|
||
|
* The responding node mixes in their local static public key serialized in
|
||
|
Bitcoin's DER compressed format:
|
||
|
* `h = SHA-256(h || ls.serializeCompressed())`
|
||
|
|
||
|
|
||
|
### Handshake Exchange
|
||
|
|
||
|
|
||
|
#### Act One
|
||
|
|
||
|
|
||
|
```
|
||
|
-> e, es
|
||
|
```
|
||
|
|
||
|
|
||
|
Act One is sent from initiator to responder. During `Act One`, the initiator
|
||
|
attempts to satisfy an implicit challenge by the responder. To complete this
|
||
|
challenge, the initiator _must_ know the static public key of the responder.
|
||
|
|
||
|
|
||
|
The handshake message is _exactly_ `50 bytes`: `1 byte` for the handshake
|
||
|
version, `33 bytes` for the compressed ephemeral public key of the initiator,
|
||
|
and `16 bytes` for the `poly1305` tag.
|
||
|
|
||
|
|
||
|
**Sender Actions:**
|
||
|
|
||
|
|
||
|
* `e = generateKey()`
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || e.serializeCompressed())`
|
||
|
* The newly generated ephemeral key is accumulated into our running
|
||
|
handshake digest.
|
||
|
|
||
|
|
||
|
* `s = ECDH(e, rs)`
|
||
|
* The initiator performs a ECDH between its newly generated ephemeral key
|
||
|
with the remote node's static public key.
|
||
|
|
||
|
|
||
|
* `ck, temp_k = HKDF(ck, s)`
|
||
|
* This phase generates a new temporary encryption key (`temp_k`) which is
|
||
|
used to generate the authenticating MAC.
|
||
|
|
||
|
|
||
|
* `c = encryptWithAD(h, zero)`
|
||
|
* where `zero` is a zero-length plaintext
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || c)`
|
||
|
* Finally, the generated ciphertext is accumulated into the authenticating
|
||
|
handshake digest.
|
||
|
|
||
|
|
||
|
* Send `m = 0 || e || c` to the responder over the network buffer.
|
||
|
|
||
|
|
||
|
**Receiver Actions:**
|
||
|
|
||
|
|
||
|
* Read _exactly_ `50-bytes` from the network buffer.
|
||
|
|
||
|
|
||
|
* Parse out the read message (`m`) into `v = m[0]`, `e = m[1:34]` and `c = m[43:]`
|
||
|
* where `m[0]` is the _first_ byte of `m`, `m[1:33]` are the next `33`
|
||
|
bytes of `m` and `m[34:]` is the last 16 bytes of `m`
|
||
|
|
||
|
|
||
|
* If `v` is an unrecognized handshake version, then the the responder MUST
|
||
|
abort the connection attempt.
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || e.serializeCompressed())`
|
||
|
* Accumulate the initiator's ephemeral key into the authenticating
|
||
|
handshake digest.
|
||
|
|
||
|
* `s = ECDH(s, e)`
|
||
|
* The responder performs an ECDH between its static public key and the
|
||
|
initiator's ephemeral public key.
|
||
|
|
||
|
|
||
|
* `ck, temp_k = HKDF(ck, s)`
|
||
|
* This phase generates a new temporary encryption key (`temp_k`) which will
|
||
|
be used to shortly check the authenticating MAC.
|
||
|
|
||
|
|
||
|
* `p = decryptWithAD(h, c)`
|
||
|
* If the MAC check in this operation fails, then the initiator does _not_
|
||
|
know our static public key. If so, then the responder MUST terminate the
|
||
|
connection without any further messages.
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || c)`
|
||
|
* Mix the received ciphertext into the handshake digest. This step serves
|
||
|
to ensure the payload wasn't modified by a MiTM.
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
#### Act Two
|
||
|
```
|
||
|
<- e, ee
|
||
|
```
|
||
|
|
||
|
`Act Two` is sent from the responder to the initiator. `Act Two` will _only_
|
||
|
take place if `Act One` was successful. `Act One` was successful if the
|
||
|
responder was able to properly decrypt and check the `MAC` of the tag sent at
|
||
|
the end of `Act One`.
|
||
|
|
||
|
The handshake is _exactly_ `50 bytes:` `1 byte` for the handshake version, `33
|
||
|
bytes` for the compressed ephemeral public key of the initiator, and `16 bytes`
|
||
|
for the `poly1305` tag.
|
||
|
|
||
|
**Sender Actions:**
|
||
|
|
||
|
|
||
|
* `e = generateKey()`
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || e.serializeCompressed())`
|
||
|
* The newly generated ephemeral key is accumulated into our running
|
||
|
handshake digest.
|
||
|
|
||
|
|
||
|
* `s = ECDH(e, re)`
|
||
|
* where `re` is the ephemeral key of the initiator which was received
|
||
|
during `ActOne`.
|
||
|
|
||
|
|
||
|
* `ck, temp_k = HKDF(ck, s)`
|
||
|
* This phase generates a new temporary encryption key (`temp_k`) which is
|
||
|
used to generate the authenticating MAC.
|
||
|
|
||
|
|
||
|
* `c = encryptWithAD(h, zero)`
|
||
|
* where `zero` is a zero-length plaintext
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || c)`
|
||
|
* Finally, the generated ciphertext is accumulated into the authenticating
|
||
|
handshake digest.
|
||
|
|
||
|
* Send `m = 0 || e || c` to the initiator over the network buffer.
|
||
|
|
||
|
|
||
|
**Receiver Actions:**
|
||
|
|
||
|
|
||
|
* Read _exactly_ `50-bytes` from the network buffer.
|
||
|
|
||
|
|
||
|
* Parse out the read message (`m`) into `v = m[0]`, e = m[1:34]` and `c = m[43:]`
|
||
|
* where `m[0]` is the _first_ byte of `m`, `m[1:33]` are the next `33`
|
||
|
bytes of `m` and `m[34:]` is the last 16 bytes of `m`
|
||
|
|
||
|
|
||
|
* If `v` is an unrecognized handshake version, then the the responder MUST
|
||
|
abort the connection attempt.
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || e.serializeCompressed())`
|
||
|
|
||
|
|
||
|
* `s = ECDH(re, e)`
|
||
|
* where `re` is the responder's ephemeral public key.
|
||
|
|
||
|
|
||
|
* `ck, temp_k = HKDF(ck, s)`
|
||
|
* This phase generates a new temporary encryption key (`temp_k`) which is
|
||
|
used to generate the authenticating MAC.
|
||
|
|
||
|
|
||
|
* `p = decryptWithAD(h, c)`
|
||
|
* If the MAC check in this operation fails, then the initiator MUST
|
||
|
terminate the connection without any further messages.
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || c)`
|
||
|
* Mix the received ciphertext into the handshake digest. This step serves
|
||
|
to ensure the payload wasn't modified by a MiTM.
|
||
|
|
||
|
|
||
|
#### Act Three
|
||
|
```
|
||
|
-> s, se
|
||
|
```
|
||
|
|
||
|
|
||
|
`Act Three` is the final phase in the authenticated key agreement described in
|
||
|
this section. This act is sent from the initiator to the responder as a final
|
||
|
concluding step. `Act Three` is only executed `iff` `Act Two` was successful.
|
||
|
During `Act Three`, the initiator transports its static public key to the
|
||
|
responder encrypted with _strong_ forward secrecy using the accumulated `HKDF`
|
||
|
derived secret key at this point of the handshake.
|
||
|
|
||
|
|
||
|
The handshake is _exactly_ `66 bytes`: `1 byte` for the handshake version, `33
|
||
|
bytes` for the ephemeral public key encrypted with the `ChaCha20` stream
|
||
|
cipher, `16 bytes` for the encrypted public key's tag generated via the `AEAD`
|
||
|
construction, and `16 bytes` for a final authenticating tag.
|
||
|
|
||
|
|
||
|
**Sender Actions:**
|
||
|
|
||
|
|
||
|
* `c = encryptWithAD(h, s.serializeCompressed())`
|
||
|
* where `s` is the static public key of the initiator.
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || c)`
|
||
|
|
||
|
|
||
|
* `s = ECDH(s, re)`
|
||
|
* where `re` is the ephemeral public key of the responder.
|
||
|
|
||
|
|
||
|
* `ck, temp_k = HKDF(ck, s)`
|
||
|
* Mix the finaly intermediate shared secret into the running chaining key.
|
||
|
|
||
|
|
||
|
* `t = encryptWithAD(h, zero)`
|
||
|
* where `zero` is a zero-length plaintext
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || t)`
|
||
|
|
||
|
|
||
|
* `sk, rk = HKDF(ck, zero)`
|
||
|
* where `zero` is a zero-length plaintext,
|
||
|
|
||
|
|
||
|
`sk` is the key to be used by the initiator to encrypt messages to the
|
||
|
responder,
|
||
|
|
||
|
|
||
|
and `rk` is the key to be used by the initiator to decrypt messages sent by
|
||
|
the responder.
|
||
|
|
||
|
* This step generates the final encryption keys to be used for sending and
|
||
|
receiving messages for the duration of the session.
|
||
|
|
||
|
|
||
|
* Send `m = 0 || c || t` over the network buffer.
|
||
|
|
||
|
|
||
|
**Receiver Actions:**
|
||
|
|
||
|
|
||
|
* Read _exactly_ `66-bytes` from the network buffer.
|
||
|
|
||
|
|
||
|
* Parse out the read message (`m`) into `v = m[0]`, `c = m[1:50]` and `t = m[50:]`
|
||
|
|
||
|
|
||
|
* If `v` is an unrecognized handshake version, then the the responder MUST
|
||
|
abort the connection attempt.
|
||
|
|
||
|
|
||
|
* `rs = decryptWithAD(h, c)
|
||
|
* At this point, the responder has recovered the static public key of the
|
||
|
initiator.
|
||
|
|
||
|
|
||
|
* `h = SHA-256(h || rs.serializeCompressed())`
|
||
|
|
||
|
|
||
|
* `s = ECDH(e, rs)`
|
||
|
* where `e` is the responder's original ephemeral key
|
||
|
|
||
|
|
||
|
* `p = decryptWithAD(h, t)`
|
||
|
* If the MAC check in this operation fails, then the responder MUST
|
||
|
terminate the connection without any further messages.
|
||
|
|
||
|
|
||
|
* `rk, sk = HKDF(ck, zero)`
|
||
|
* where `zero` is a zero-length plaintext,
|
||
|
|
||
|
|
||
|
`rk` is the key to be used by the responder to decrypt the messages sent
|
||
|
by the responder,
|
||
|
|
||
|
|
||
|
and `sk` is the key to be used by the initiator to encrypt messages to
|
||
|
the responder,
|
||
|
|
||
|
* This step generates the final encryption keys to be used for sending and
|
||
|
receiving messages for the duration of the session.
|
||
|
|
||
|
|
||
|
## Transport Message Specification
|
||
|
|
||
|
At the conclusion of `Act Three` both sides have derived the encryption keys
|
||
|
which will be used to encrypt/decrypt messages for the remainder of the
|
||
|
session.
|
||
|
|
||
|
The *maximum* size of _any_ transport message MUST NOT exceed 65535 bytes. A
|
||
|
maximum payload size of 65535 simplifies testing and also makes memory
|
||
|
management and avoid exhaustion attacks easy. Note that the protocol messages
|
||
|
encapsulated in within the encrypted transport messages can be larger that the
|
||
|
maximum transport messages. If a party wishes to send a message larger then
|
||
|
65535 bytes, then they can simply partition the message into chunks less than
|
||
|
the maximum size, sending each of them sequentially. Messages which exceed the
|
||
|
max message size MUST be partitioned into chunks of size `65519 bytes`, in
|
||
|
order to leave room for the `16-byte` `MAC`.
|
||
|
|
||
|
|
||
|
In order to make make traffic analysis more difficult, then length prefix for
|
||
|
all encrypted transport messages is also encrypted. We additionally add a
|
||
|
`16-byte` `Poly-1305` tag to the encrypted length prefix in order to ensure
|
||
|
that the packet length hasn't been modified with in-flight, and also to avoid
|
||
|
creating a decryption oracle.
|
||
|
|
||
|
|
||
|
The structure of transport messages resembles the following:
|
||
|
```
|
||
|
+------------------------------
|
||
|
|2-byte encrypted packet length|
|
||
|
+------------------------------
|
||
|
| 16-byte MAC of the encrypted |
|
||
|
| packet length |
|
||
|
+------------------------------
|
||
|
| |
|
||
|
| |
|
||
|
| ciphertext |
|
||
|
| |
|
||
|
| |
|
||
|
+------------------------------
|
||
|
```
|
||
|
|
||
|
The prefixed packet lengths are encoded as a `16-byte` big-endian integer.
|
||
|
|
||
|
|
||
|
### Encrypting Messages
|
||
|
|
||
|
|
||
|
In order to encrypt a message (`m`), given a sending key (`sk`), and a nonce
|
||
|
(`n`), the following is done:
|
||
|
|
||
|
|
||
|
* let `l = len(m)`,
|
||
|
where `len` obtains the length in bytes of the message.
|
||
|
|
||
|
|
||
|
* Serialize `l` into `2-bytes` encoded as a big-endian integer.
|
||
|
|
||
|
|
||
|
* Encrypt `l` using `ChaChaPoly-1305`, `n`, and `sk` to obtain `lc`
|
||
|
(`18-bytes`)
|
||
|
* The nonce for `sk MUST be incremented after this step.
|
||
|
|
||
|
|
||
|
* Finally encrypt the message itself (`m`) using the same procedure used to
|
||
|
encrypt the length prefix. Let encrypted ciphertext be known as `c`.
|
||
|
* The nonce for `sk` MUST be incremented after this step.
|
||
|
|
||
|
* Send `lc || c` over the network buffer.
|
||
|
|
||
|
|
||
|
### Decrypting Messages
|
||
|
|
||
|
|
||
|
In order to decrypt the _next_ message in the network stream, the following is
|
||
|
done:
|
||
|
|
||
|
|
||
|
* Read _exactly_ `18-bytes` from the network buffer.
|
||
|
|
||
|
|
||
|
* Let the encrypted length prefix be known as `lc`
|
||
|
|
||
|
|
||
|
* Decrypt `lc` using `ChaChaPoly-1305`, `n`, and `rk` to obtain size of the
|
||
|
encrypted packet `l`.
|
||
|
* The nonce for `rk` MUST be incremented after this step.
|
||
|
|
||
|
|
||
|
* Read _exactly_ `l` bytes from the network buffer, let the bytes be known as
|
||
|
`c`.
|
||
|
|
||
|
|
||
|
* Decrypt `c` using `ChaChaPoly-1305`, `n`, and `rk` to obtain decrypted
|
||
|
plaintext packet `p`.
|
||
|
|
||
|
|
||
|
* The nonce for `rk` MUST be incremented after this step.
|
||
|
|
||
|
|
||
|
## Transport Message Key Rotation
|
||
|
|
||
|
|
||
|
Changing keys regularly and forgetting the previous key is useful for
|
||
|
preventing decryption of old messages in the case of later key leakage (ie.
|
||
|
backwards secrecy).
|
||
|
|
||
|
|
||
|
Key rotation is performed for _each_ key (`sk` and `rk`) _individually _. A key
|
||
|
is to be rotated after a party sends of decrypts `1000` messages with it.
|
||
|
This can be properly accounted for by rotating the key once the nonce dedicated
|
||
|
to it exceeds `1000`.
|
||
|
|
||
|
|
||
|
Key rotation for a key `k` is performed according to the following:
|
||
|
|
||
|
|
||
|
* Let `ck` be the chaining key obtained at the end of `Act Three`.
|
||
|
* `ck, k' = HKDF(ck, k)`
|
||
|
* The underscore indicates that only `32-bytes` are extracted from the
|
||
|
`HKDF`.
|
||
|
* Reset the nonce for the key to `n = 0`.
|
||
|
* `k = k'`
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
## Future Directions
|
||
|
|
||
|
|
||
|
Protocol messages may be padded out to the full maximum message length in order
|
||
|
to max traffic analysis even more difficult.
|
||
|
|
||
|
The initial handshake message may also be padded out to a fixed size in order
|
||
|
to obscure exactly which of the Noise handshakes is being executed.
|
||
|
|
||
|
In order to allow zero-RTT encrypted+authenticated communication, a Noise Pipes
|
||
|
protocol can be adopted which composes two handshakes, potentially falling back
|
||
|
to a full handshake if static public keys have changed.
|
||
|
|
||
|
## Initialization Message
|
||
|
|
||
|
Once authentication is complete, the first message reveals the
|
||
|
features supported or required by this node. Odd features are
|
||
|
optional, even features are compulsory ("it's OK to be odd!"). The
|
||
|
meaning of these bits will be defined in future.
|
||
|
|
||
|
1. type: 16 (MSG_INIT)
|
||
|
2. data: [4:len] [len:globalfeatures] [4:len] [len:localfeatures]
|
||
|
|
||
|
The 4-byte len fields indicate the number of bytes in the immediately
|
||
|
following field.
|
||
|
|
||
|
|
||
|
### Requirements
|
||
|
|
||
|
|
||
|
The sending node SHOULD use the minimum lengths required to represent
|
||
|
the feature fields. The sending node MUST set feature bits
|
||
|
corresponding to features is requires the peer to support, and SHOULD
|
||
|
set feature bits corresponding to features it optionally supports.
|
||
|
|
||
|
|
||
|
The receiving node MUST fail the channels if it receives a
|
||
|
`globalfeatures` or `localfeatures` with an even bit set which it does
|
||
|
not understand.
|
||
|
|
||
|
|
||
|
Each node MUST wait to receive MSG_INIT before sending any other
|
||
|
messages.
|
||
|
|
||
|
|
||
|
### Rationale
|
||
|
|
||
|
|
||
|
The even/odd semantic allows future incompatible changes, or backward
|
||
|
compatible changes. Bits should generally be assigned in pairs, so
|
||
|
that optional features can later become compulsory.
|
||
|
|
||
|
|
||
|
Nodes wait for receipt of the other's features to simplify error
|
||
|
diagnosis where features are incompatible.
|
||
|
|
||
|
|
||
|
## Error Message
|
||
|
|
||
|
|
||
|
For simplicity of diagnosis, it is often useful to tell the peer that
|
||
|
something is incorrect.
|
||
|
|
||
|
|
||
|
1. type: 17 (`MSG_ERROR`)
|
||
|
2. data: [8:channel-id] [4:len] [len:data]
|
||
|
|
||
|
|
||
|
The 4-byte len field indicates the number of bytes in the immediately
|
||
|
following field.
|
||
|
|
||
|
|
||
|
### Requirements
|
||
|
|
||
|
|
||
|
A node SHOULD send `MSG_ERROR` for protocol violations or internal
|
||
|
errors which make channels unusable or further communication unusable.
|
||
|
A node MAY send an empty [data] field. A node sending `MSG_ERROR` MUST
|
||
|
fail the channel referred to by the `channel-id`, or if `channel-id`
|
||
|
is 0xFFFFFFFFFFFFFFFF it MUST fail all channels and MUST close the
|
||
|
connection. A node MUST NOT set `len` to greater than the data length.
|
||
|
|
||
|
|
||
|
A node receiving `MSG_ERROR` MUST fail the channel referred to by
|
||
|
`channel-id`, or if `channel-id` is 0xFFFFFFFFFFFFFFFF it MUST fail
|
||
|
all channels and MUST close the connection. A receiving node MUST truncate `len` to the remainder of the packet if it is larger.
|
||
|
|
||
|
|
||
|
A receiving node SHOULD only print out `data` verbatim if it is a
|
||
|
valid string.
|
||
|
|
||
|
|
||
|
### Rationale
|
||
|
|
||
|
|
||
|
There are unrecoverable errors which require abort of conversations;
|
||
|
if the connection is simply dropped then the peer may retry
|
||
|
connection. It's also useful to describe protocol violations for
|
||
|
diagnosis, as it indicates that one peer has a bug.
|
||
|
|
||
|
|
||
|
It may be wise not to distinguish errors in production settings, lest
|
||
|
it leak information, thus the optional data field.
|
||
|
|
||
|
|
||
|
# Security Considerations #
|
||
|
|
||
|
|
||
|
It is strongly recommended that existing, commonly-used, validated
|
||
|
libraries be used for encryption and decryption, to avoid the many
|
||
|
implementation pitfalls possible.
|
||
|
|
||
|
## Acknowledgements
|
||
|
|
||
|
TODO(roasbeef); fin
|
||
|
|
||
|
|
||
|
# References
|
||
|
1. <a id="reference-1">https://en.bitcoin.it/wiki/Secp256k1</a>
|
||
|
2. <a id="reference-2">http://www.unicode.org/charts/PDF/U2600.pdf</a>
|
||
|
3. <a id="reference-3">https://tools.ietf.org/html/rfc7539</a>
|
||
|
4. <a id="reference-4">http://noiseprotocol.org/noise.html</a>
|
||
|
|
||
|
# Authors
|
||
|
|
||
|
FIXME
|