[Lightning-dev] [BOLT RFC#1] Encryption spec

Rusty Russell rusty at rustcorp.com.au
Mon Mar 7 03:51:02 UTC 2016


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).


More information about the Lightning-dev mailing list