This section uses the following notation for keys:
- Q is the pre-shared key
- Eipub is the initiator ephemeral public key
- Srpriv is the responder static private key
- Tsend, Trecv are the session keys derived
The Noise handshake starts with initiator (I) and responder (R) computing the hash function of the protocol name:
h0 = hash("Noise_IXpsk0_25519_ChaChaPoly_SHA256")
ck0 = h0
Then the tokens of the handshake pattern are processed sequentially:
-
-> psk, e, s
The initiator computes:
(ck1, t, k0) = HKDF3(ck0, Q)
h1 = hash(h0 || t)
(Eipub, Eipriv) = keygen()
write Eipub
h2 = hash(h1 || Eipub)
(ck2, k1) = HKDF2(ck1, Eipub)
encrypted-static = enc(k1, 0, Sipub, h2)
write encrypted-static
h3 = hash(h2 || encrypted-static)
encrypted-data = enc(k1, 1, Sipub, h2)
h4 = hash(h3 || encrypted-data) -
<- e, ee, se, s, es
The responder computes:
(Erpub, Erpriv) = keygen()
write Erpub
h5 = hash(h4 || Erpub)
(ck3, k2) = HKDF2(ck2, Erpub)
(ck4, k3) = HKDF2(ck3, DH(Eipub, Erpriv))
(ck5, k4) = HKDF2(ck4, DH(Sipub, Erpriv))
encrypted-static = enc(k4, 2, Sipub, h5)
write encrypted-static
h6 = hash(h5 || encrypted-static)
(ck6, k5) = HKDF2(ck5, DH(Eipub, Srpriv))
encrypted-data = enc(k5, 4, data, h6)
write encrypted-data
h7 = hash(h6 || encrypted-data)
A the end both initiator and responder will compute the session keys:
(Tsend, Trecv) = HKDF2(ck7, empty)