📅 Original date posted:2016-03-07
📝 Original message:
Hi all,
I'm back, and I've even had some sleep! I've decided I need to
do more documentation in parallel with actual implementation, so I've
put this temporarily in a fork of Mats' document repo:
https://github.com/rustyrussell/lightning/blob/master/communications/low/01-encryption.md
But here it is inline (I think I prefer plaintext, but MD isn't too
painful to read). Feedback welcome, this is what I'll be moving my
implementation towards...
Thanks!
Rusty.
# Basis of Lightning Technology RFC 1 #
# Status #
Initial draft
# Author #
Rusty Russell, Blockstream <mailto:rusty at rustcorp.com.au>
# Abstract #
All communications between lightning nodes should be encrypted to make
analysis more difficult, and authenticated to avoid malicious
interference. Each node has a known identifier which is a unique
bitcoin-style public key[1].
## Initial Handshake ##
The first packet sent by a node is of form:
1. `length`: 4 byte little-endian
2. `sessionpubkey`: 33 byte DER-encoded compressed public EC-key
The `length` field is the length after the field itself, and MUST be
33 or greater. `length` MUST NOT exceed 1MB (1048576 bytes).
The `sessionpubkey` field is a compressed public key corresponding to
a `sessionsecretkey`. The receiver MUST check that `sessionpubkey` is
a valid point.
The `sessionsecretkey` MUST be is unguessable, MUST BE unique for this
session, MUST NOT be zero and MUST BE a valid EC key.
Additional fields MAY be added, and MUST be included in the `length` field. These MUST be ignored by implementations which do not understand them.
### Derivation of the Shared Secret and Encryption Keys ###
Once a node has received the initial handshake, it can derive the
shared secret using the received `sessionpubkey` point and its own
`sessionsecretkey` scalar using EC Diffie-Hellman.
Now both nodes have obtained the shared secret, all packets are
encrypted using keys derived from the shared secret. Keys are derived
as follows:
* sending-key: SHA256(shared-secret || sending-node-id)
* receiving-key: SHA256(shared-secret || receiving-node-id)
ie. each node combines the secret with its node id to produce the key
to encrypt data it sends.
## Encryption of Packets ##
The protocol uses Authenticated Encryption with Additional Data using
ChaCha20-Poly1305[2].
Each packet contains a header and a body. The header consists of a
4-byte length indicating the size of the unencrypted body, and an
8-byte packet counter. The 4-byte length and 8-byte counter MUST be
encoded in little-endian. The 8-byte counter MUST begin at 0 and be
incremented before each transmission after the initial authentication
packet; it MAY be non-zero for the authentication packet for
re-establishing an existing session.
The 12-byte header for each packet is encrypted separately (resulting
in a 28 byte header, when the authentication tag is appended), to
offer additional protection from traffic analysis.
The body also has a 16-byte authentication tag appended.
Nonces are 64-bit little-endian numbers, which MUST begin at 0 and
MUST be incremented after each encryption (ie. twice per packet), such
that headers are encrypted with even nonces and the packet bodies
encrypted with odd nonces.
## Authentication Packet ##
Once the shared secret has been exchanged, the identity of the peer
has still not been authenticated. The first packet sent MUST be an
authentication packet:
message authenticate {
// Which node this is.
required bitcoin_pubkey node_id = 1;
// Signature of your session key.
required signature session_sig = 2;
};
The receiving node MUST check that:
1. `node_id` is the expected value for the sending node.
2. `session_sig` is a valid secp256k1 ECDSA signature encoded as a
32-byte big endian R value, followed by a 32-byte big endian S value.
3. `session_sig` is the signature of the SHA256 of SHA256 of the receivers
`node_id`, using the secret key corresponding to the sender's `node_id`.
Additional fields MAY be included, and MUST BE ignored if not
understood (to allow for future extensions).
## Rationale ##
Multiple choices are possible for symmetric encryption; AES256-GSM is
the other obvious choice but it is slower if there is no hardware
acceleration, and the well-supported libsodium[3] doesn't support it
on non-accelerated platforms.
The header encryption could use a different key for encryption and
eschew 16-bytes for the authentication tag, but modern APIs tend to
shy away from offering unauthenticated encryption.
While multiple choices are possible for public-key cryptography, the
bitcoin protocol already relies on the secp256k1 elliptic curve, so
reusing it here avoids additional dependencies.
The authentication signature ensures that the node owning the
`node_id` has specifically initiated this session; using double-sha256
is done because bitcoin transaction signatures use that scheme.
# 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.
# References #
1. https://en.bitcoin.it/wiki/Secp256k1
2. https://tools.ietf.org/html/rfc7539
3. https://download.libsodium.org/doc/index.html
# Acknowledgements #
Thanks to Olaoluwa Osuntokun for pointing out the idea of encrypting
the length, and noting that it needed a separate key if it didn't
include the authentication tag.
Thanks to Mats Jerratsch and Anthony Towns for feedback on initial
handshake design.
# Feedback #
Feedback is welcome on the [lightning-dev list](https://lists.linuxfoundation.org/mailman/listinfo/lightning-dev).