#include "noise_ik.h"
#include <sodium.h>
#include "debug_log.h"
// Protocol name for Noise_IK_Ed25519_ChaChaPoly_BLAKE2b
static const char *PROTOCOL_NAME = "Noise_IK_Ed25519_ChaChaPoly_BLAKE2b";
// ---- Construction / Destruction ----
NoiseIK::NoiseIK(bool initiator, const Ed25519KeyPair &static_key,
const uint8_t *prologue, const uint8_t *remote_pk)
: _initiator(initiator), _message_index(0), _complete(false),
_e_generated(false), _has_key(false), _nonce(0) {
if (sodium_init() < 0) {
DHT_LOG("NoiseIK: sodium_init failed\n");
}
memcpy(&_s, &static_key, sizeof(Ed25519KeyPair));
memset(_rs, 0, PKLEN);
memset(_re, 0, PKLEN);
memset(&_e, 0, sizeof(Ed25519KeyPair));
memset(_cipher_key, 0, KEYBYTES);
memset(_tx, 0, KEYBYTES);
memset(_rx, 0, KEYBYTES);
memset(_hash, 0, HASHLEN);
// Initialise symmetric state with protocol name.
size_t proto_len = strlen(PROTOCOL_NAME);
memset(_digest, 0, HASHLEN);
if (proto_len <= HASHLEN) {
memcpy(_digest, PROTOCOL_NAME, proto_len);
} else {
crypto_generichash(_digest, HASHLEN,
(const uint8_t *)PROTOCOL_NAME, proto_len,
nullptr, 0);
}
// chainingKey = digest.
memcpy(_chaining_key, _digest, HASHLEN);
// mixHash(prologue).
if (prologue) {
_mix_hash(prologue, 32);
} else {
uint8_t empty = 0;
_mix_hash(&empty, 0);
}
// IK preshare: mix the responder's static key into hash.
if (_initiator) {
// Initiator knows the remote (responder) public key.
if (remote_pk) {
memcpy(_rs, remote_pk, PKLEN);
_mix_hash(_rs, PKLEN);
}
} else {
// Responder preshares its own static key.
_mix_hash(_s.public_key, PKLEN);
}
}
NoiseIK::~NoiseIK() {
sodium_memzero(&_s, sizeof(Ed25519KeyPair));
sodium_memzero(&_e, sizeof(Ed25519KeyPair));
sodium_memzero(_cipher_key, KEYBYTES);
sodium_memzero(_tx, KEYBYTES);
sodium_memzero(_rx, KEYBYTES);
sodium_memzero(_digest, HASHLEN);
sodium_memzero(_chaining_key, HASHLEN);
}
// ---- Key generation ----
Ed25519KeyPair NoiseIK::generate_keypair() {
if (sodium_init() < 0) {
DHT_LOG("NoiseIK: sodium_init failed\n");
}
Ed25519KeyPair kp;
crypto_sign_keypair(kp.public_key, kp.secret_key);
return kp;
}
void NoiseIK::compute_namespace(const char *ns_name, const uint8_t *ids,
int count, uint8_t *out) {
// ns = BLAKE2b(name_string), 32 bytes.
uint8_t ns[33];
crypto_generichash(ns, 32,
(const uint8_t *)ns_name, strlen(ns_name),
nullptr, 0);
// For each ID: out[i] = BLAKE2b(ns || id_byte), 32 bytes.
for (int i = 0; i < count; i++) {
ns[32] = ids[i];
crypto_generichash(out + i * 32, 32, ns, 33, nullptr, 0);
}
}
// ---- Ed25519 DH ----
void NoiseIK::_ed25519_dh(uint8_t *output, const uint8_t *secret_key_64,
const uint8_t *remote_public_key) {
// Derive scalar from Ed25519 secret key:
// SHA512(seed) → clamp → first 32 bytes = scalar.
uint8_t sk_hash[64];
crypto_hash_sha512(sk_hash, secret_key_64, 32); // Hash only the seed (first 32 bytes).
// Clamp.
sk_hash[0] &= 248;
sk_hash[31] &= 127;
sk_hash[31] |= 64;
// DH: output = scalarmult_ed25519_noclamp(scalar, publicKey).
int rc = crypto_scalarmult_ed25519_noclamp(output, sk_hash, remote_public_key);
(void)rc; // Can fail if point is identity — not expected in handshake context.
sodium_memzero(sk_hash, 64);
}
// ---- Handshake: Send ----
std::vector<uint8_t> NoiseIK::send(const uint8_t *payload, size_t payload_len) {
std::vector<uint8_t> out;
int msg = _message_index++;
if (msg == 0 && _initiator) {
// IK Message 1 (Initiator → Responder): e, es, s, ss
// e: generate and send ephemeral.
crypto_sign_keypair(_e.public_key, _e.secret_key);
_e_generated = true;
_mix_hash(_e.public_key, PKLEN);
out.insert(out.end(), _e.public_key, _e.public_key + PKLEN);
// es: DH(ephemeral, remote_static).
_mix_key_from_dh(_rs, _e.secret_key);
// s: encrypt and send static public key.
_encrypt_and_hash(out, _s.public_key, PKLEN);
// ss: DH(static, remote_static).
_mix_key_from_dh(_rs, _s.secret_key);
// Encrypt payload.
_encrypt_and_hash(out, payload, payload_len);
} else if (msg == 1 && !_initiator) {
// IK Message 2 (Responder → Initiator): e, ee, se
// e: generate and send ephemeral.
crypto_sign_keypair(_e.public_key, _e.secret_key);
_e_generated = true;
_mix_hash(_e.public_key, PKLEN);
out.insert(out.end(), _e.public_key, _e.public_key + PKLEN);
// ee: DH(responder_ephemeral, initiator_ephemeral).
_mix_key_from_dh(_re, _e.secret_key);
// se: DH(initiator_static_pk, responder_ephemeral_sk).
// IK msg 2 tokens: e, ee, se. We're responder.
// se = DH(remote_static, our_ephemeral) = DH(_rs, _e.secret_key).
_mix_key_from_dh(_rs, _e.secret_key);
// Encrypt payload.
_encrypt_and_hash(out, payload, payload_len);
// Handshake complete.
_split();
}
return out;
}
// ---- Handshake: Receive ----
std::vector<uint8_t> NoiseIK::recv(const uint8_t *data, size_t len) {
size_t offset = 0;
int msg = _message_index++;
if (msg == 0 && !_initiator) {
// Receive IK Message 1 (Initiator → Responder): e, es, s, ss
if (len < PKLEN) return {};
// e: read remote ephemeral.
memcpy(_re, data + offset, PKLEN);
offset += PKLEN;
_mix_hash(_re, PKLEN);
// es: DH(initiator_ephemeral, responder_static).
// We're responder: DH(_re, _s.secret_key).
_mix_key_from_dh(_re, _s.secret_key);
// s: decrypt remote static key.
size_t s_len = _has_key ? (PKLEN + MACBYTES) : PKLEN;
if (offset + s_len > len) return {};
auto s_dec = _decrypt_and_hash(data + offset, s_len);
offset += s_len;
if (s_dec.size() != PKLEN) return {};
memcpy(_rs, s_dec.data(), PKLEN);
// ss: DH(responder_static, initiator_static).
// We're responder: DH(_rs, _s.secret_key).
_mix_key_from_dh(_rs, _s.secret_key);
// Decrypt payload.
return _decrypt_and_hash(data + offset, len - offset);
} else if (msg == 1 && _initiator) {
// Receive IK Message 2 (Responder → Initiator): e, ee, se
if (len < PKLEN) return {};
// e: read remote ephemeral.
memcpy(_re, data + offset, PKLEN);
offset += PKLEN;
_mix_hash(_re, PKLEN);
// ee: DH(initiator_ephemeral, responder_ephemeral).
_mix_key_from_dh(_re, _e.secret_key);
// se: DH(initiator_static, responder_ephemeral).
// We're initiator: DH(_re, _s.secret_key).
_mix_key_from_dh(_re, _s.secret_key);
// Decrypt payload.
auto payload = _decrypt_and_hash(data + offset, len - offset);
// Handshake complete.
_split();
return payload;
}
return {};
}
// ---- Holepunch secret derivation ----
void NoiseIK::derive_holepunch_secret(const uint8_t *ns_peer_holepunch,
uint8_t *out_32) const {
// holepunchSecret = BLAKE2b(key=handshake_hash, data=NS_PEER_HOLEPUNCH).
// Matches JS: sodium.crypto_generichash(out, NS.PEER_HOLEPUNCH, this.handshake.hash)
// Note: JS generichash(output, input, key) → BLAKE2b with key.
crypto_generichash(out_32, 32, ns_peer_holepunch, 32, _hash, HASHLEN);
}
// ---- Symmetric state operations ----
void NoiseIK::_mix_hash(const uint8_t *data, size_t len) {
uint8_t tmp[HASHLEN];
size_t total = HASHLEN + len;
std::vector<uint8_t> buf(total);
memcpy(buf.data(), _digest, HASHLEN);
if (len > 0) {
memcpy(buf.data() + HASHLEN, data, len);
}
crypto_generichash(tmp, HASHLEN, buf.data(), total, nullptr, 0);
memcpy(_digest, tmp, HASHLEN);
}
void NoiseIK::_mix_key_from_dh(const uint8_t *remote_pk, const uint8_t *local_sk_64) {
uint8_t dh_output[DHLEN];
_ed25519_dh(dh_output, local_sk_64, remote_pk);
uint8_t new_ck[HASHLEN];
uint8_t temp_key[HASHLEN];
_hkdf(_chaining_key, HASHLEN, dh_output, DHLEN, new_ck, temp_key);
memcpy(_chaining_key, new_ck, HASHLEN);
_initialise_key(temp_key);
sodium_memzero(dh_output, DHLEN);
sodium_memzero(new_ck, HASHLEN);
sodium_memzero(temp_key, HASHLEN);
}
void NoiseIK::_initialise_key(const uint8_t *key) {
memcpy(_cipher_key, key, KEYBYTES);
_has_key = true;
_nonce = 0;
}
void NoiseIK::_encrypt_and_hash(std::vector<uint8_t> &out,
const uint8_t *pt, size_t pt_len) {
if (!_has_key) {
out.insert(out.end(), pt, pt + pt_len);
_mix_hash(pt, pt_len);
} else {
size_t ct_len = pt_len + MACBYTES;
size_t old_size = out.size();
out.resize(old_size + ct_len);
_encrypt(out.data() + old_size, pt, pt_len, _digest, HASHLEN);
_mix_hash(out.data() + old_size, ct_len);
}
}
std::vector<uint8_t> NoiseIK::_decrypt_and_hash(const uint8_t *ct, size_t ct_len) {
if (!_has_key) {
std::vector<uint8_t> pt(ct, ct + ct_len);
_mix_hash(ct, ct_len);
return pt;
} else {
if (ct_len < MACBYTES) return {};
size_t pt_len = ct_len - MACBYTES;
std::vector<uint8_t> pt(pt_len);
if (!_decrypt(pt.data(), ct, ct_len, _digest, HASHLEN)) {
return {};
}
_mix_hash(ct, ct_len);
return pt;
}
}
// ---- AEAD (ChaCha20-Poly1305 IETF) ----
void NoiseIK::_encrypt(uint8_t *ct, const uint8_t *pt, size_t pt_len,
const uint8_t *ad, size_t ad_len) {
uint8_t nonce[12];
memset(nonce, 0, 12);
uint32_t counter = static_cast<uint32_t>(_nonce);
memcpy(nonce + 4, &counter, 4);
unsigned long long ct_len_out;
crypto_aead_chacha20poly1305_ietf_encrypt(
ct, &ct_len_out,
pt, pt_len,
ad, ad_len,
nullptr, nonce, _cipher_key);
_nonce++;
}
bool NoiseIK::_decrypt(uint8_t *pt, const uint8_t *ct, size_t ct_len,
const uint8_t *ad, size_t ad_len) {
uint8_t nonce[12];
memset(nonce, 0, 12);
uint32_t counter = static_cast<uint32_t>(_nonce);
memcpy(nonce + 4, &counter, 4);
unsigned long long pt_len_out;
int ret = crypto_aead_chacha20poly1305_ietf_decrypt(
pt, &pt_len_out,
nullptr,
ct, ct_len,
ad, ad_len,
nonce, _cipher_key);
_nonce++;
return ret == 0;
}
// ---- HKDF (HMAC-BLAKE2b) ----
void NoiseIK::_hmac(uint8_t *out, const uint8_t *key, size_t key_len,
const uint8_t *data, size_t data_len) {
uint8_t hmac_key[BLOCKLEN];
memset(hmac_key, 0, BLOCKLEN);
if (key_len > BLOCKLEN) {
crypto_generichash(hmac_key, HASHLEN, key, key_len, nullptr, 0);
} else {
memcpy(hmac_key, key, key_len);
}
uint8_t outer_pad[BLOCKLEN];
uint8_t inner_pad[BLOCKLEN];
for (int i = 0; i < BLOCKLEN; i++) {
outer_pad[i] = 0x5c ^ hmac_key[i];
inner_pad[i] = 0x36 ^ hmac_key[i];
}
sodium_memzero(hmac_key, BLOCKLEN);
uint8_t inner_hash[HASHLEN];
{
size_t inner_len = BLOCKLEN + data_len;
std::vector<uint8_t> inner_buf(inner_len);
memcpy(inner_buf.data(), inner_pad, BLOCKLEN);
if (data_len > 0) {
memcpy(inner_buf.data() + BLOCKLEN, data, data_len);
}
crypto_generichash(inner_hash, HASHLEN, inner_buf.data(), inner_len, nullptr, 0);
}
sodium_memzero(inner_pad, BLOCKLEN);
{
uint8_t outer_buf[BLOCKLEN + HASHLEN];
memcpy(outer_buf, outer_pad, BLOCKLEN);
memcpy(outer_buf + BLOCKLEN, inner_hash, HASHLEN);
crypto_generichash(out, HASHLEN, outer_buf, BLOCKLEN + HASHLEN, nullptr, 0);
sodium_memzero(outer_buf, BLOCKLEN + HASHLEN);
}
sodium_memzero(outer_pad, BLOCKLEN);
sodium_memzero(inner_hash, HASHLEN);
}
void NoiseIK::_hkdf(const uint8_t *salt, size_t salt_len,
const uint8_t *ikm, size_t ikm_len,
uint8_t *out1, uint8_t *out2, uint8_t *out3) {
uint8_t prk[HASHLEN];
_hmac(prk, salt, salt_len, ikm, ikm_len);
uint8_t counter;
counter = 1;
_hmac(out1, prk, HASHLEN, &counter, 1);
{
uint8_t t2_input[HASHLEN + 1];
memcpy(t2_input, out1, HASHLEN);
t2_input[HASHLEN] = 2;
_hmac(out2, prk, HASHLEN, t2_input, HASHLEN + 1);
}
if (out3) {
uint8_t t3_input[HASHLEN + 1];
memcpy(t3_input, out2, HASHLEN);
t3_input[HASHLEN] = 3;
_hmac(out3, prk, HASHLEN, t3_input, HASHLEN + 1);
}
sodium_memzero(prk, HASHLEN);
}
void NoiseIK::_split() {
uint8_t k1[HASHLEN];
uint8_t k2[HASHLEN];
uint8_t empty = 0;
_hkdf(_chaining_key, HASHLEN, &empty, 0, k1, k2);
if (_initiator) {
memcpy(_tx, k1, KEYBYTES);
memcpy(_rx, k2, KEYBYTES);
} else {
memcpy(_tx, k2, KEYBYTES);
memcpy(_rx, k1, KEYBYTES);
}
memcpy(_hash, _digest, HASHLEN);
_complete = true;
sodium_memzero(&_e, sizeof(Ed25519KeyPair));
sodium_memzero(_re, PKLEN);
sodium_memzero(_cipher_key, KEYBYTES);
sodium_memzero(_chaining_key, HASHLEN);
sodium_memzero(k1, HASHLEN);
sodium_memzero(k2, HASHLEN);
}