Edited 14_encrypted_transport.asciidoc with Atlas code editor

pull/910/head
kristen@oreilly.com 3 years ago
parent d2d58e4320
commit e2674e6293

@ -37,7 +37,7 @@ that requires encrypted communication between two parties.
=== The Channel Graph as Decentralized Public Key Infrastructure
((("channel graph","decentralized public key infrastructure")))((("Lightning encrypted transport protocol","channel graph as decentralized public key infrastructure")))((("PKI (public key infrastructure)")))((("public key infrastructure (PKI)")))As we learned in <<routing>> when discussing multihop forwarding, every node has a long-term
((("channel graph","decentralized public key infrastructure")))((("Lightning encrypted transport protocol","channel graph as decentralized public key infrastructure")))((("PKI (public key infrastructure)")))((("public key infrastructure (PKI)")))As we learned in <<routing>>, every node has a long-term
identity that is used as the identifier for a vertex during pathfinding 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
@ -248,37 +248,38 @@ handshake as:
`ECDH(k, rk)`:: Performs an Elliptic Curve DiffieHellman operation using
`k`, which is a valid `secp256k1` private key, and `rk`, which is a valid public key.
+
** The returned value is the SHA-256 of the compressed format of the
The returned value is the SHA-256 of the compressed format of the
generated point.
`HKDF(salt,ikm)`:: A function defined in `RFC 5869`,
evaluated with a zero-length `info` field.
+
** All invocations of `HKDF` implicitly return 64 bytes of
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` (Internet Engineering Task Force variant)
Where `encrypt` is an evaluation of `ChaCha20-Poly1305` (Internet Engineering Task Force 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)
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
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()`
+
Where the object also has a single method:
** `.serializeCompressed()`
`a || b`:: This denotes the concatenation of two byte strings `a` and `b`.
@ -289,13 +290,15 @@ starting state that they'll use to advance the handshake process. To start,
both sides need to construct the initial handshake digest `h`.
1. ++h = SHA-256(__protocolName__)++
* Where ++__protocolName__ = "Noise_XK_secp256k1_ChaChaPoly_SHA256"++ encoded as
+
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`.
+
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.
@ -306,12 +309,10 @@ 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())`
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())`
Bitcoin's compressed format: `h = SHA-256(h || ls.pub.serializeCompressed())`
===== Handshake acts
@ -348,18 +349,23 @@ Sender actions:
1. `e = generateKey()`
2. `h = SHA-256(h || e.pub.serializeCompressed())`
* The newly generated ephemeral key is accumulated into the running
+
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
+
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
+
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 plain text.
+
Where `zero` is a zero-length plain text.
6. `h = SHA-256(h || c)`
* Finally, the generated ciphertext is accumulated into the authenticating
+
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.
@ -410,18 +416,23 @@ Sender actions:
1. `e = generateKey()`
2. `h = SHA-256(h || e.pub.serializeCompressed())`
* The newly generated ephemeral key is accumulated into the running
+
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
+
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
+
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 plain text.
+
Where `zero` is a zero-length plain text.
6. `h = SHA-256(h || c)`
* Finally, the generated ciphertext is accumulated into the authenticating
+
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.
@ -429,24 +440,30 @@ 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
+
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
+
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
+
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
+
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
+
The received ciphertext is mixed into the handshake digest. This step serves
to ensure the payload wasn't modified by a MITM.
====== Act Three
@ -470,54 +487,68 @@ 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.
+
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.
+
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.
+
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 plain text.
+
Where `zero` is a zero-length plain text.
6. `sk, rk = HKDF(ck, zero)`
* Where `zero` is a zero-length plain text,
+
Where `zero` is a zero-length plain text,
`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
+
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.
+
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
+
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
+
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.
+
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
+
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 plain text,
+
Where `zero` is a zero-length plain text,
`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
+
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.(((range="endofrange", startref="ix_14_encrypted_transport-asciidoc6")))(((range="endofrange", startref="ix_14_encrypted_transport-asciidoc5")))(((range="endofrange", startref="ix_14_encrypted_transport-asciidoc4")))
+
The sending and receiving nonces are initialized to 0.(((range="endofrange", startref="ix_14_encrypted_transport-asciidoc6")))(((range="endofrange", startref="ix_14_encrypted_transport-asciidoc5")))(((range="endofrange", startref="ix_14_encrypted_transport-asciidoc4")))
===== Transport message encryption
@ -555,7 +586,8 @@ 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.
+
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).
@ -566,7 +598,8 @@ completed:
* 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 this encrypted ciphertext be known as `c`.
* The nonce `sn` must be incremented after this step.
+
The nonce `sn` must be incremented after this step.
5. Send `lc || c` over the network buffer.
====== Receiving and decrypting messages
@ -584,7 +617,8 @@ steps are completed:
known as `c`.
5. Decrypt `c` (using `ChaCha20-Poly1305`, `rn`, and `rk`) to obtain decrypted
plain-text packet `p`.
* The nonce `rn` must be incremented after this step.
+
The nonce `rn` must be incremented after this step.
===== Lightning message key rotation

Loading…
Cancel
Save