mirror of https://github.com/lnbook/lnbook
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
604 lines
29 KiB
Plaintext
604 lines
29 KiB
Plaintext
3 years ago
|
= Brontide: Lightning's Encrypted Message Transport
|
||
3 years ago
|
|
||
3 years ago
|
== Intro
|
||
3 years ago
|
|
||
|
Unlike the vanilla Bitcoin P2P network, every node in the Lightning Network is
|
||
|
identified by a unique public key which serves as it identity. By default, this
|
||
|
public key is used to end-to-end encrypt _all_ communication within the
|
||
|
network. Encryption by default at the lowest level of the protocol ensures that
|
||
|
all messages are authenticated, are immune to man-in-the-middle attacks,
|
||
|
snooping by 3rd parties, and ensures privacy at the fundamental transport
|
||
|
level. In this chapter, we'll learn about the encryption protocol used by the
|
||
|
Lightning network in detail. Upon completion of this chapter, the reader will
|
||
|
be familiar with the state of the art in encrypted messaging protocols, as well
|
||
|
as the various properties such a protocol provides to the network. It's worth
|
||
|
mentioning that the core of the encrypted message transport is _agonstic_ to
|
||
|
its usage within the context of the Lightning Network. As a result, the
|
||
|
custom encrypted message transport Lightning uses, commonly referred to as
|
||
|
"Brontide" (more on that later) can be dropped into any context that requires
|
||
|
encrypted communication between two parties.
|
||
|
|
||
3 years ago
|
== The Channel Graph as Decentralized Public Key Infrastructure
|
||
3 years ago
|
|
||
|
As we learned in the chapter on multi-hop forwarding, very node has a long-term
|
||
|
identity that is used as the identifier for a vertex during path finding and
|
||
|
also used in the asymmetric cryptographic operations related to the creation of
|
||
|
onion encrypted routing packets. This public key, which serves as a node's
|
||
|
long-term identity is included in the DNS bootstrapping response, as well as
|
||
|
embedded within the Channel Graph. As a result, before a node attempts to
|
||
|
connect out to another node on the P2P network, it already knows the public key
|
||
|
of the node it wishes to connect to.
|
||
|
|
||
|
Additionally, if the node being connected to already h a series of public
|
||
|
channels within the graph, then the connecting node is able to further verify
|
||
|
the sanctity of the identity of the node. As the entire channel graph is fully
|
||
|
authenticated, one can view it as a sort of decentralized public key
|
||
|
infrastructure: in order to register a key, a public channel in the Bitcoin
|
||
|
blockchain must be opened, once a node no longer has any public channels, then
|
||
|
they've effectively been removed from the PKI.
|
||
|
|
||
|
As Lightning is a decentralized network, it's imperative that no one central
|
||
|
party is designated the power to provision a public key identity within the
|
||
|
network. In place of a central party, the Lightning Network uses the Bitcoin
|
||
|
blockchain as a sybil mitigation mechanism, as gaining an identity on the
|
||
|
network has a tangible cost: the fee needed to create a channel in the
|
||
|
blockchain, as well as the opportunity cost of the capital allocated to their
|
||
|
channels. In the process of essentially rolling a domain specific PKI, the
|
||
|
Lightning network is able to significantly simply its encrypted transport
|
||
|
protocol as it doesn't need to deal with all the complexities that come along
|
||
|
with TLS, the Transport Layer Security protocol.
|
||
|
|
||
3 years ago
|
== Why Not TLS?
|
||
3 years ago
|
|
||
|
Readers familiar with the TLS system may be wondering at this point: why wasn't
|
||
|
TLS used in spite of the drawbacks of the existing PKI system? It is indeed a
|
||
|
fact that "self signed certificates" can be used to effectively sidestep the
|
||
|
existing global PKI system by simply asserting to the identity of a given
|
||
|
public key amongst a set of peers. However, even with the existing PKI system
|
||
|
out of the way, TLS has several drawbacks that prompted the creators of the LN
|
||
|
to instead opt for a more compact custom encryption protocol.
|
||
|
|
||
|
To start with, TLS is a protocol that has been around for several decades and
|
||
|
as a result has evolved over time as new advances have been made in the space
|
||
|
of transport encryption. However, overtime this evolution has caused the
|
||
|
protocol to balloon in size and complexity. Over the past few decades several
|
||
|
vulnerabilities in TLS has been discovered, and patched with each evolution
|
||
|
further increasing the complexity of the protocol. As a result of the age of
|
||
|
the protocol several versions and iterations exist, meaning a client needs to
|
||
|
understand many of the prior iterations of the protocol in order to communicate
|
||
|
with a large portion of the public internet further increasing implementation
|
||
|
complexity.
|
||
|
|
||
|
In the past several memory safety vulnerabilities have been discovered in
|
||
|
widely used implementations of SSL/TLS. Packaging such a protocol within every
|
||
|
Lightning node would serve to increase the attack surface of nodes exposed to
|
||
|
to the public peer to peer network. In order to increase the security of the
|
||
|
network as a whole, and minimize exploitable attack surface, the creators of
|
||
|
the LN instead opted to adopt the Noise Protocol Framework. Noise as a protocol
|
||
|
internalizes several of the security and privacy lessons learned over time due
|
||
|
to continual scrutiny of the TLS protocol over decades. In a way, the existence
|
||
|
of Noise allows the community to effective "start over", with a more compact,
|
||
|
simplified protocol that retains all the added benefits of TLS.
|
||
|
|
||
3 years ago
|
== The Noise Protocol Framework
|
||
3 years ago
|
|
||
|
The Noise Protocol Framework is a modern, extensible, and flexible message
|
||
|
encryption protocol designed by the creators of the Signal protocol. The Signal
|
||
|
protocol is one of the most widely used message encryption protocols in the
|
||
|
world. It's used by both Signal and Whatsapp, which cumulatively are used by
|
||
|
over a billion people around the world. The Noise framework is the result of
|
||
|
decades of evolution both within academia as well as industry of message
|
||
|
encryption protocols. Lightning uses the Noise protocol framework to implement
|
||
|
a _message oriented_ encryption protocol used by all nodes to communicate with
|
||
|
each other.
|
||
|
|
||
|
A communication session using Noise has two distinct phases: the handshake
|
||
|
phase, and the messaging phase. Before two parties can communicate with each
|
||
|
other, they first need to arrive at a shared secret known only to them which
|
||
|
will be used to encrypt and authenticate messages sent to each other. A flavor
|
||
|
of an authenticated key agreement is used to arrive at a final shared key
|
||
|
between the tow parties. In the context of the Noise protocol, this
|
||
|
authenticated key agreement is referred to as a "handshake". Once that
|
||
|
handshake has been completed, both nodes can now being to send each other
|
||
|
encrypted messages. Each time peers need to connect, or reconnect to each
|
||
|
other, a fresh iteration of the handshake protocol is executed ensuring that
|
||
|
forward secrecy (leaking the key of a prior transcript doesn't compromise any
|
||
|
future transcripts) is achieved.
|
||
|
|
||
|
As the Noise protocol allows a protocol designer to drop choose from several
|
||
|
cryptographic primitives such as symmetric encryption and public key
|
||
|
cryptography, its customary that each flavor of the Noise protocol is referred
|
||
|
to by a unique name. In the spirit of "Noise", each flavor of the protocol
|
||
|
selects a name derived from some sort of "noise". In the context of the
|
||
|
Lightning Network, the flavor of Noise use will be referred to from here on as
|
||
|
"Brontide". A brontide is a low billowing noise, similar to what one would hear
|
||
|
during a thunderstorm when very far away.
|
||
|
|
||
3 years ago
|
=== Noise Protocol Handshakes
|
||
3 years ago
|
|
||
|
The Noise protocol is extremely flexible in that it advertises several
|
||
|
handshakes, each with different security and privacy properties for a would be
|
||
|
protocol implementer to select from. A deep exploration of each of the
|
||
|
handshakes, and their various trade-offs is out of the scope of this chapter.
|
||
|
With that said, the Lighting Network uses a specific handshake referred to as
|
||
|
`Noise_XK`. The unique property provided by this handshake is "identity
|
||
|
hiding": in order for a node to initiate a connection with another node, it
|
||
|
must first know it's public key. Mechanically, this means that the public key
|
||
|
of the responder is actually never transmitted during the context of the
|
||
|
handshake. Instead, a clever series of Elliptic-Curve Diffie-Hellman (ECDH) and
|
||
|
Message Authentication Code (MAC) checks are used to authenticate the
|
||
|
responder.
|
||
|
|
||
3 years ago
|
=== Handshake Notation & Protocol Flow
|
||
3 years ago
|
|
||
|
Each handshakes typically consist of several steps. At each step some
|
||
|
(possibly) encrypted material is sent to the opposite party, an ECDH (or
|
||
|
several) are performed, with the result of the handshake being "mixed" into a
|
||
|
protocol "transcript". This transcript serves to authenticate each step of the
|
||
|
protocol and helps thwart a flavor of main-man-in-the-middle attacks. At the
|
||
|
end of the handshake, two keys `ck` and `k` are produced which are used to
|
||
|
encrypt messages (`k`) as well as rotate keys (`ck`) throughout the lifetime of
|
||
|
the session.
|
||
|
|
||
|
In the context of a handshake, `s` is usually a long-term static public key.
|
||
|
Within Brontide, the public key crypto system used is an elliptic curve one,
|
||
|
instantiated with the `secp256k1` curve which is used elsewhere in Bitcoin.
|
||
|
Several ephemeral keys are generated throughout the handshake. We use `e` to
|
||
|
refer to a new ephemeral key. ECDH operations between two keys are notated as
|
||
|
the concatenation of two keys. As an example, `ee` represents an ECDH operation
|
||
|
between two ephemeral keys.
|
||
|
|
||
3 years ago
|
== Brontide: Lightning's P2P Encryption
|
||
3 years ago
|
|
||
3 years ago
|
=== High-Level Overview
|
||
3 years ago
|
|
||
|
Using the notation laid out earlier, we can succinctly describe the `Noise_XK`
|
||
|
as follows:
|
||
|
```
|
||
|
Noise_XK(s, rs):
|
||
|
<- rs
|
||
|
...
|
||
|
-> e, e(rs)
|
||
|
<- e, ee
|
||
|
-> s, se
|
||
|
```
|
||
|
|
||
|
The protocol begins with the "pre-transmission" of the responder's static key
|
||
|
(`rs)` to the initiator. Before executing the handshake, the initiator is to
|
||
|
generate its own static key (`s`). During each step of the handshake, all
|
||
|
material sent across the wire, as well as the keys sent/used are incrementally
|
||
|
hashed into a "handshake digest", `h`. This digest is never sent across the
|
||
|
wire during the handshake, and is instead used as the "Associated Data" when an
|
||
|
AEAD (authenticated encryption w/ associated data) is sent across the wire.
|
||
|
Associated data allows an encryption protocol to authenticate additional
|
||
|
information along side a cipher text packet. In other domains, the AD may be a
|
||
|
domain name, or plaintext portion of the packet.
|
||
|
|
||
|
The existence of `h` ensures that if a portion of a transmitted handshake
|
||
|
message is replaced, then the other side will notice. At each step, a MAC
|
||
|
digest is checked. If the MAC check succeeds, then the receiving party knows
|
||
|
that the handshake has been successful up until that point. Otherwise if a MAC
|
||
|
check ever fails, then the handshake process has failed, and the connection
|
||
|
should be terminated.
|
||
|
|
||
|
Brontide also adds a new piece of data to each handshake message: a protocol
|
||
|
version. The initial protocol version is `0`. At the time of writing, no new
|
||
|
protocol versions has been created. As a result, if a peer receives a version
|
||
|
other than `0`, then they should reject the handshake initiation attempt.
|
||
|
|
||
|
As far as cryptographic primitives, `SHA-256` is used as the hash function of
|
||
|
choice, `secp256k1` as the elliptic curve, and `ChaChaPoly-130` as the AEAD
|
||
|
(symmetric encryption) construction.
|
||
|
|
||
|
Each variant of the Noise protocol has a unique ASCII string used to uniquely
|
||
|
refer to it. In order to ensure that two parties are using the same protocol
|
||
|
variant, the ASCII string is hashed into a digest, which is used to initialize
|
||
|
the starting handshake state. In the context of Brontide, the ASCII string
|
||
|
describing the protocol is: `Noise_XK_secp256k1_ChaChaPoly_SHA256`.
|
||
|
|
||
3 years ago
|
=== Brontide: A Handshake in Three Acts
|
||
3 years ago
|
|
||
|
The handshake portion of Brontide can be see prated into three distinct "acts".
|
||
|
The entire handshake takes 1.5 round trips between the initiator and responder.
|
||
|
At each act, a single message is sent between both parties. The handshake
|
||
|
message is a _fixed_ sized payload prefixed by the protocol version.
|
||
|
|
||
|
The Noise protocol uses an object oriented inspired notation to describe the
|
||
|
protocol at each step. During set up of the handshake state, each side will
|
||
|
initialize the following "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 Lightning 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_k1`, `temp_k2`, `temp_k3`: the **intermediate keys**. These are used to
|
||
|
encrypt and decrypt the zero-length AEAD payloads at the end of each handshake
|
||
|
message.
|
||
|
|
||
|
* `e`: a party's **ephemeral keypair**. For each session, a node MUST generate a
|
||
|
new ephemeral key with strong cryptographic randomness.
|
||
|
|
||
|
* `s`: a party's **static keypair** (`ls` for local, `rs` for remote)
|
||
|
|
||
|
Given this handshake+messaging session state, we'll then define a series of
|
||
|
functions that will operate on the handshake and messaging state. When
|
||
|
describing the handshake protocol, we'll use these variables in a manner
|
||
|
similar to pseudo-code in order to reduce the verbosity of the explanation of
|
||
|
each step in the protocol. We'll define the _functional_ primitives of the
|
||
|
handshake as:
|
||
|
|
||
|
* `ECDH(k, rk)`: performs an Elliptic-Curve Diffie-Hellman operation using
|
||
|
`k`, which is a valid `secp256k1` private key, and `rk`, which is a valid public key
|
||
|
* The returned value is the SHA256 of the compressed format of the
|
||
|
generated point.
|
||
|
|
||
|
* `HKDF(salt,ikm)`: a function defined in `RFC 5869`<sup>[3](#reference-3)</sup>,
|
||
|
evaluated with a zero-length `info` field
|
||
|
* All invocations of `HKDF` implicitly return 64 bytes of
|
||
|
cryptographic randomness using the extract-and-expand component of the
|
||
|
`HKDF`.
|
||
|
|
||
|
* `encryptWithAD(k, n, ad, plaintext)`: outputs `encrypt(k, n, ad, plaintext)`
|
||
|
* Where `encrypt` is an evaluation of `ChaCha20-Poly1305` (IETF variant)
|
||
|
with the passed arguments, with nonce `n` encoded as 32 zero bits,
|
||
|
followed by a *little-endian* 64-bit value. Note: this follows the Noise
|
||
|
Protocol convention, rather than our normal endian.
|
||
|
|
||
|
* `decryptWithAD(k, n, ad, ciphertext)`: outputs `decrypt(k, n, ad, ciphertext)`
|
||
|
* Where `decrypt` is an evaluation of `ChaCha20-Poly1305` (IETF variant)
|
||
|
with the passed arguments, with nonce `n` encoded as 32 zero bits,
|
||
|
followed by a *little-endian* 64-bit value.
|
||
|
|
||
|
* `generateKey()`: generates and returns a fresh `secp256k1` keypair
|
||
|
* Where the object returned by `generateKey` has two attributes:
|
||
|
* `.pub`, which returns an abstract object representing the public key
|
||
|
* `.priv`, which represents the private key used to generate the
|
||
|
public key
|
||
|
* Where the object also has a single method:
|
||
|
* `.serializeCompressed()`
|
||
|
|
||
|
* `a || b` denotes the concatenation of two byte strings `a` and `b`
|
||
|
|
||
3 years ago
|
==== Handshake Session State Initialization
|
||
3 years ago
|
|
||
|
Before starting the handshake process, both sides need to initialize the
|
||
|
starting state that they'll use to advance the handshake process. To start,
|
||
|
both sides need to construct the initial handshake digest `h` which will be
|
||
|
used as the initial handshake digest.
|
||
|
|
||
|
1. `h = SHA-256(protocolName)`
|
||
|
* where `protocolName = "Noise_XK_secp256k1_ChaChaPoly_SHA256"` encoded as
|
||
|
an ASCII string
|
||
|
|
||
|
2. `ck = h`
|
||
|
|
||
|
3. `h = SHA-256(h || prologue)`
|
||
|
* where `prologue` is the ASCII string: `lightning`
|
||
|
|
||
|
In addition to the protocol name, we also add in an extra "prologue" that is
|
||
|
used to further bind the protocol context to the Lightning network.
|
||
|
|
||
|
To conclude the initialization step, both sides mix the responder's public key
|
||
|
into the handshake digest. As this digest is used as the associated data with a
|
||
|
zero-length ciphertext (only the MAC) is sent, this ensures that the initiator
|
||
|
does indeed know the public key of the responder.
|
||
|
|
||
|
* The initiating node mixes in the responding node's static public key
|
||
|
serialized in Bitcoin's compressed format:
|
||
|
* `h = SHA-256(h || rs.pub.serializeCompressed())`
|
||
|
|
||
|
* The responding node mixes in their local static public key serialized in
|
||
|
Bitcoin's compressed format:
|
||
|
* `h = SHA-256(h || ls.pub.serializeCompressed())`
|
||
|
|
||
3 years ago
|
==== Handshake Acts
|
||
3 years ago
|
|
||
|
After the initial handshake initialization, we can begin the actual execution
|
||
|
of the handshake process. The Brontide handshake is compromised of a series of
|
||
|
three messages sent between the initiator and responder, hence referred to as
|
||
|
"acts". As each act is a single message sent between the parties, a handshake
|
||
|
is completed in a total of 1.5 round trips (0.5 for each act).
|
||
|
|
||
|
The first act completes the initial portion of the incremental Triple Diffie
|
||
|
Hellman key exchange (using a new ephemeral key generated by the initiator),
|
||
|
and also ensures that the initiator actually knows the long-term public key of
|
||
|
the responder. During the second act, the responder transmits the thermal key
|
||
|
they wish to use for the session to the initiator, and one again incrementally
|
||
|
mixes this new key into the Triple DH handshake. During the third and final
|
||
|
act, the initiator transmits their long-term static public key to the
|
||
|
responder, and executes the final DH operation to mix that into the final
|
||
|
resulting shared secret.
|
||
|
|
||
3 years ago
|
===== Act One
|
||
3 years ago
|
|
||
|
```
|
||
|
-> 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:**
|
||
|
|
||
|
1. `e = generateKey()`
|
||
|
2. `h = SHA-256(h || e.pub.serializeCompressed())`
|
||
|
* The newly generated ephemeral key is accumulated into the running
|
||
|
handshake digest.
|
||
|
3. `es = ECDH(e.priv, rs)`
|
||
|
* The initiator performs an ECDH between its newly generated ephemeral
|
||
|
key and the remote node's static public key.
|
||
|
4. `ck, temp_k1 = HKDF(ck, es)`
|
||
|
* A new temporary encryption key is generated, which is
|
||
|
used to generate the authenticating MAC.
|
||
|
5. `c = encryptWithAD(temp_k1, 0, h, zero)`
|
||
|
* where `zero` is a zero-length plaintext
|
||
|
6. `h = SHA-256(h || c)`
|
||
|
* Finally, the generated ciphertext is accumulated into the authenticating
|
||
|
handshake digest.
|
||
|
7. Send `m = 0 || e.pub.serializeCompressed() || c` to the responder over the network buffer.
|
||
|
|
||
|
**Receiver Actions:**
|
||
|
|
||
|
1. Read _exactly_ 50 bytes from the network buffer.
|
||
|
2. Parse the read message (`m`) into `v`, `re`, and `c`:
|
||
|
* where `v` is the _first_ byte of `m`, `re` is the next 33
|
||
|
bytes of `m`, and `c` is the last 16 bytes of `m`
|
||
|
* The raw bytes of the remote party's ephemeral public key (`re`) are to be
|
||
|
deserialized into a point on the curve using affine coordinates as encoded
|
||
|
by the key's serialized composed format.
|
||
|
3. If `v` is an unrecognized handshake version, then the responder MUST
|
||
|
abort the connection attempt.
|
||
|
4. `h = SHA-256(h || re.serializeCompressed())`
|
||
|
* The responder accumulates the initiator's ephemeral key into the authenticating
|
||
|
handshake digest.
|
||
|
5. `es = ECDH(s.priv, re)`
|
||
|
* The responder performs an ECDH between its static private key and the
|
||
|
initiator's ephemeral public key.
|
||
|
6. `ck, temp_k1 = HKDF(ck, es)`
|
||
|
* A new temporary encryption key is generated, which will
|
||
|
shortly be used to check the authenticating MAC.
|
||
|
7. `p = decryptWithAD(temp_k1, 0, h, c)`
|
||
|
* If the MAC check in this operation fails, then the initiator does _not_
|
||
|
know the responder's static public key. If this is the case, then the
|
||
|
responder MUST terminate the connection without any further messages.
|
||
|
8. `h = SHA-256(h || c)`
|
||
|
* The received ciphertext is mixed into the handshake digest. This step serves
|
||
|
to ensure the payload wasn't modified by a MITM.
|
||
|
|
||
3 years ago
|
===== Act Two
|
||
3 years ago
|
|
||
|
```
|
||
|
<- 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 responder, and 16 bytes
|
||
|
for the `poly1305` tag.
|
||
|
|
||
|
**Sender Actions:**
|
||
|
|
||
|
1. `e = generateKey()`
|
||
|
2. `h = SHA-256(h || e.pub.serializeCompressed())`
|
||
|
* The newly generated ephemeral key is accumulated into the running
|
||
|
handshake digest.
|
||
|
3. `ee = ECDH(e.priv, re)`
|
||
|
* where `re` is the ephemeral key of the initiator, which was received
|
||
|
during Act One
|
||
|
4. `ck, temp_k2 = HKDF(ck, ee)`
|
||
|
* A new temporary encryption key is generated, which is
|
||
|
used to generate the authenticating MAC.
|
||
|
5. `c = encryptWithAD(temp_k2, 0, h, zero)`
|
||
|
* where `zero` is a zero-length plaintext
|
||
|
6. `h = SHA-256(h || c)`
|
||
|
* Finally, the generated ciphertext is accumulated into the authenticating
|
||
|
handshake digest.
|
||
|
7. Send `m = 0 || e.pub.serializeCompressed() || c` to the initiator over the network buffer.
|
||
|
|
||
|
**Receiver Actions:**
|
||
|
|
||
|
1. Read _exactly_ 50 bytes from the network buffer.
|
||
|
2. Parse the read message (`m`) into `v`, `re`, and `c`:
|
||
|
* where `v` is the _first_ byte of `m`, `re` is the next 33
|
||
|
bytes of `m`, and `c` is the last 16 bytes of `m`.
|
||
|
3. If `v` is an unrecognized handshake version, then the responder MUST
|
||
|
abort the connection attempt.
|
||
|
4. `h = SHA-256(h || re.serializeCompressed())`
|
||
|
5. `ee = ECDH(e.priv, re)`
|
||
|
* where `re` is the responder's ephemeral public key
|
||
|
* The raw bytes of the remote party's ephemeral public key (`re`) are to be
|
||
|
deserialized into a point on the curve using affine coordinates as encoded
|
||
|
by the key's serialized composed format.
|
||
|
6. `ck, temp_k2 = HKDF(ck, ee)`
|
||
|
* A new temporary encryption key is generated, which is
|
||
|
used to generate the authenticating MAC.
|
||
|
7. `p = decryptWithAD(temp_k2, 0, h, c)`
|
||
|
* If the MAC check in this operation fails, then the initiator MUST
|
||
|
terminate the connection without any further messages.
|
||
|
8. `h = SHA-256(h || c)`
|
||
|
* The received ciphertext is mixed into the handshake digest. This step serves
|
||
|
to ensure the payload wasn't modified by a MITM.
|
||
|
|
||
3 years ago
|
===== Act Three
|
||
3 years ago
|
|
||
|
```
|
||
|
-> 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
|
||
|
concluding step. Act Three is executed _if and only if_ 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 static 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:**
|
||
|
|
||
|
1. `c = encryptWithAD(temp_k2, 1, h, s.pub.serializeCompressed())`
|
||
|
* where `s` is the static public key of the initiator
|
||
|
2. `h = SHA-256(h || c)`
|
||
|
3. `se = ECDH(s.priv, re)`
|
||
|
* where `re` is the ephemeral public key of the responder
|
||
|
4. `ck, temp_k3 = HKDF(ck, se)`
|
||
|
* The final intermediate shared secret is mixed into the running chaining key.
|
||
|
5. `t = encryptWithAD(temp_k3, 0, h, zero)`
|
||
|
* where `zero` is a zero-length plaintext
|
||
|
6. `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
|
||
|
* The final encryption keys, to be used for sending and
|
||
|
receiving messages for the duration of the session, are generated.
|
||
|
7. `rn = 0, sn = 0`
|
||
|
* The sending and receiving nonces are initialized to 0.
|
||
|
8. Send `m = 0 || c || t` over the network buffer.
|
||
|
|
||
|
**Receiver Actions:**
|
||
|
|
||
|
1. Read _exactly_ 66 bytes from the network buffer.
|
||
|
2. Parse the read message (`m`) into `v`, `c`, and `t`:
|
||
|
* where `v` is the _first_ byte of `m`, `c` is the next 49
|
||
|
bytes of `m`, and `t` is the last 16 bytes of `m`
|
||
|
3. If `v` is an unrecognized handshake version, then the responder MUST
|
||
|
abort the connection attempt.
|
||
|
4. `rs = decryptWithAD(temp_k2, 1, h, c)`
|
||
|
* At this point, the responder has recovered the static public key of the
|
||
|
initiator.
|
||
|
5. `h = SHA-256(h || c)`
|
||
|
6. `se = ECDH(e.priv, rs)`
|
||
|
* where `e` is the responder's original ephemeral key
|
||
|
7. `ck, temp_k3 = HKDF(ck, se)`
|
||
|
8. `p = decryptWithAD(temp_k3, 0, h, t)`
|
||
|
* If the MAC check in this operation fails, then the responder MUST
|
||
|
terminate the connection without any further messages.
|
||
|
9. `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 initiator,
|
||
|
and `sk` is the key to be used by the responder to encrypt messages to
|
||
|
the initiator
|
||
|
* The final encryption keys, to be used for sending and
|
||
|
receiving messages for the duration of the session, are generated.
|
||
|
10. `rn = 0, sn = 0`
|
||
|
* The sending and receiving nonces are initialized to 0.
|
||
|
|
||
3 years ago
|
==== Transport Message Encryption
|
||
3 years ago
|
|
||
|
At the conclusion of Act Three, both sides have derived the encryption keys, which
|
||
|
will be used to encrypt and decrypt messages for the remainder of the
|
||
|
session.
|
||
|
|
||
|
The actual Lightning protocol messages are encapsulated within AEAD ciphertexts.
|
||
|
Each message is prefixed with another AEAD ciphertext, which encodes the total
|
||
|
length of the following Lightning message (not including its MAC).
|
||
|
|
||
|
The *maximum* size of _any_ Lightning message MUST NOT exceed `65535` bytes. A
|
||
|
maximum size of `65535` simplifies testing, makes memory management easier, and
|
||
|
helps mitigate memory-exhaustion attacks.
|
||
|
|
||
|
In order to make traffic analysis more difficult, the length prefix for all
|
||
|
encrypted Lightning messages is also encrypted. Additionally a 16-byte
|
||
|
`Poly-1305` tag is added to the encrypted length prefix in order to ensure that
|
||
|
the packet length hasn't been modified when in-flight and also to avoid
|
||
|
creating a decryption oracle.
|
||
|
|
||
|
The structure of packets on the wire resembles the following:
|
||
|
|
||
|
```
|
||
|
+-------------------------------
|
||
|
|2-byte encrypted message length|
|
||
|
+-------------------------------
|
||
|
| 16-byte MAC of the encrypted |
|
||
|
| message length |
|
||
|
+-------------------------------
|
||
|
| |
|
||
|
| |
|
||
|
| encrypted Lightning |
|
||
|
| message |
|
||
|
| |
|
||
|
+-------------------------------
|
||
|
| 16-byte MAC of the |
|
||
|
| Lightning message |
|
||
|
+-------------------------------
|
||
|
```
|
||
|
|
||
|
The prefixed message length is encoded as a 2-byte big-endian integer, for a
|
||
|
total maximum packet length of `2 + 16 + 65535 + 16` = `65569` bytes.
|
||
|
|
||
3 years ago
|
===== Encrypting and Sending Messages
|
||
3 years ago
|
|
||
|
In order to encrypt and send a Lightning message (`m`) to the network stream,
|
||
|
given a sending key (`sk`) and a nonce (`sn`), the following steps are
|
||
|
completed:
|
||
|
|
||
|
1. Let `l = len(m)`.
|
||
|
* where `len` obtains the length in bytes of the Lightning message
|
||
|
2. Serialize `l` into 2 bytes encoded as a big-endian integer.
|
||
|
3. Encrypt `l` (using `ChaChaPoly-1305`, `sn`, and `sk`), to obtain `lc`
|
||
|
(18 bytes)
|
||
|
* The nonce `sn` is encoded as a 96-bit little-endian number. As the
|
||
|
decoded nonce is 64 bits, the 96-bit nonce is encoded as: 32 bits
|
||
|
of leading 0s followed by a 64-bit value.
|
||
|
* The nonce `sn` MUST be incremented after this step.
|
||
|
* A zero-length byte slice is to be passed as the AD (associated data).
|
||
|
4. 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 `sn` MUST be incremented after this step.
|
||
|
5. Send `lc || c` over the network buffer.
|
||
|
|
||
3 years ago
|
===== Receiving and Decrypting Messages
|
||
3 years ago
|
|
||
|
In order to decrypt the _next_ message in the network stream, the following
|
||
|
steps are completed:
|
||
|
|
||
|
1. Read _exactly_ 18 bytes from the network buffer.
|
||
|
2. Let the encrypted length prefix be known as `lc`.
|
||
|
3. Decrypt `lc` (using `ChaCha20-Poly1305`, `rn`, and `rk`), to obtain the size of
|
||
|
the encrypted packet `l`.
|
||
|
* A zero-length byte slice is to be passed as the AD (associated data).
|
||
|
* The nonce `rn` MUST be incremented after this step.
|
||
|
4. Read _exactly_ `l+16` bytes from the network buffer, and let the bytes be
|
||
|
known as `c`.
|
||
|
5. Decrypt `c` (using `ChaCha20-Poly1305`, `rn`, and `rk`), to obtain decrypted
|
||
|
plaintext packet `p`.
|
||
|
* The nonce `rn` MUST be incremented after this step.
|
||
|
|
||
3 years ago
|
==== Lightning Message Key Rotation
|
||
3 years ago
|
|
||
|
Changing keys regularly and forgetting previous keys is useful to prevent the
|
||
|
decryption of old messages, in the case of later key leakage (i.e. backwards
|
||
|
secrecy).
|
||
|
|
||
|
Key rotation is performed for _each_ key (`sk` and `rk`) _individually_. A key
|
||
|
is to be rotated after a party encrypts or decrypts 1000 times with it (i.e.
|
||
|
every 500 messages). 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 steps:
|
||
|
|
||
|
1. Let `ck` be the chaining key obtained at the end of Act Three.
|
||
|
2. `ck', k' = HKDF(ck, k)`
|
||
|
3. Reset the nonce for the key to `n = 0`.
|
||
|
4. `k = k'`
|
||
|
5. `ck = ck'`
|