Noise_XK
. As a pre-message, the initiator must know the identity public key of the responder. This provides a degree of identity hiding for the responder, as its static public key is never transmitted during the handshake. Instead, authentication is achieved implicitly via a series of Elliptic-Curve Diffie-Hellman (ECDH) operations followed by a MAC check.Noise_XK
) is performed in three distinct steps (acts). During each act of the handshake the following occurs: 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 set of encryption keys (ck
the chaining key and k
the encryption key); and an AEAD payload with a zero-length cipher text is sent. As this payload has no length, only a MAC is sent across. The mixing of ECDH outputs into a hash digest forms an incremental TripleDH handshake.e
and s
(both public keys) indicate possibly encrypted keying material, and es
, ee
, and se
each indicate an ECDH operation between two keys. The handshake is laid out as follows:h
. Note that the handshake state h
is never transmitted during the handshake; instead, digest is used as the Associated Data within the zero-length AEAD messages.SHA-256
is chosen as the hash function, secp256k1
as the elliptic curve, and ChaChaPoly-1305
as the AEAD construction.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 differ, then the handshake process fails immediately.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)ECDH(k, rk)
: performs an Elliptic-Curve Diffie-Hellman operation using k
, which is a valid private key, and rk
, which is a secp256k1
public key within the finite field, as defined by the curve parametersHKDF
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)
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)
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
keypairgenerateKey
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.serializeCompressed()
a || b
denotes the concatenation of two byte strings a
and b
h = SHA-256(protocolName)
protocolName = "Noise_XK_secp256k1_ChaChaPoly_SHA256"
encoded as an ASCII stringck = h
h = SHA-256(h || prologue)
prologue
is the ASCII string: lightning
h = SHA-256(h || rs.pub.serializeCompressed())
h = SHA-256(h || ls.pub.serializeCompressed())
poly1305
tag.e = generateKey()
h = SHA-256(h || e.pub.serializeCompressed())
es = ECDH(e.priv, rs)
ck, temp_k1 = HKDF(ck, es)
c = encryptWithAD(temp_k1, 0, h, zero)
zero
is a zero-length plaintexth = SHA-256(h || c)
m = 0 || e.pub.serializeCompressed() || c
to the responder over the network buffer.m
) into v
, re
, and c
:v
is the first byte of m
, re
is the next 33 bytes of m
, and c
is the last 16 bytes of m
re
) are to be deserialized into a point on the curve using affine coordinates as encoded by the key's serialized composed format.v
is an unrecognized handshake version, then the responder MUST abort the connection attempt.h = SHA-256(h || re.serializeCompressed())
es = ECDH(s.priv, re)
ck, temp_k1 = HKDF(ck, es)
p = decryptWithAD(temp_k1, 0, h, c)
h = SHA-256(h || c)
poly1305
tag.e = generateKey()
h = SHA-256(h || e.pub.serializeCompressed())
ee = ECDH(e.priv, re)
re
is the ephemeral key of the initiator, which was received during Act Oneck, temp_k2 = HKDF(ck, ee)
c = encryptWithAD(temp_k2, 0, h, zero)
zero
is a zero-length plaintexth = SHA-256(h || c)
m = 0 || e.pub.serializeCompressed() || c
to the initiator over the network buffer.m
) into v
, re
, and c
:v
is the first byte of m
, re
is the next 33 bytes of m
, and c
is the last 16 bytes of m
.v
is an unrecognized handshake version, then the responder MUST abort the connection attempt.h = SHA-256(h || re.serializeCompressed())
ee = ECDH(e.priv, re)
re
is the responder's ephemeral public keyre
) are to be deserialized into a point on the curve using affine coordinates as encoded by the key's serialized composed format.ck, temp_k2 = HKDF(ck, ee)
p = decryptWithAD(temp_k2, 0, h, c)
h = SHA-256(h || c)
HKDF
derived secret key at this point of the handshake.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.c = encryptWithAD(temp_k2, 1, h, s.pub.serializeCompressed())
s
is the static public key of the initiatorh = SHA-256(h || c)
se = ECDH(s.priv, re)
re
is the ephemeral public key of the responderck, temp_k3 = HKDF(ck, se)
t = encryptWithAD(temp_k3, 0, h, zero)
zero
is a zero-length plaintextsk, rk = HKDF(ck, zero)
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 responderrn = 0, sn = 0
m = 0 || c || t
over the network buffer.m
) into v
, c
, and t
:v
is the first byte of m
, c
is the next 49 bytes of m
, and t
is the last 16 bytes of m
v
is an unrecognized handshake version, then the responder MUST abort the connection attempt.rs = decryptWithAD(temp_k2, 1, h, c)
h = SHA-256(h || c)
se = ECDH(e.priv, rs)
e
is the responder's original ephemeral keyck, temp_k3 = HKDF(ck, se)
p = decryptWithAD(temp_k3, 0, h, t)
rk, sk = HKDF(ck, zero)
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 initiatorrn = 0, sn = 0