OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / src / noise_ik.h

125 lines · c ref:
#ifndef HYPERSWARM_NOISE_IK_H
#define HYPERSWARM_NOISE_IK_H

// Noise IK handshake over Ed25519 — wire-compatible with hyperdht/noise-wrap.
// Protocol: Noise_IK_Ed25519_ChaChaPoly_BLAKE2b
//
// IK pattern (2 messages, initiator knows responder's static key):
//   Preshare:     rs
//   Msg 1 (I→R):  e, es, s, ss
//   Msg 2 (R→I):  e, ee, se
//
// Ed25519 DH uses crypto_scalarmult_ed25519_noclamp with SHA512-derived scalar.
// This is SEPARATE from NoiseHandshake (Noise XX, Curve25519) which is for
// post-connection stream encryption. NoiseIK handles DHT relay handshakes.

#include <cstdint>
#include <cstring>
#include <vector>

struct Ed25519KeyPair {
	uint8_t public_key[32]; // Ed25519 public key.
	uint8_t secret_key[64]; // Ed25519 secret key (seed + public key).
};

class NoiseIK {
public:
	static constexpr int HASHLEN = 64;   // BLAKE2b output.
	static constexpr int DHLEN = 32;     // Ed25519 DH output.
	static constexpr int PKLEN = 32;     // Ed25519 public key.
	static constexpr int SKLEN = 64;     // Ed25519 secret key.
	static constexpr int MACBYTES = 16;  // ChaCha20-Poly1305 tag.
	static constexpr int KEYBYTES = 32;  // Symmetric cipher key.
	static constexpr int BLOCKLEN = 128; // BLAKE2b block size for HMAC.

	// prologue: 32-byte namespace hash (NS.PEER_HANDSHAKE).
	// remote_pk: responder's Ed25519 public key (32 bytes).
	//   - initiator: must provide remote_pk.
	//   - responder: remote_pk is nullptr.
	NoiseIK(bool initiator, const Ed25519KeyPair &static_key,
			const uint8_t *prologue, const uint8_t *remote_pk);
	~NoiseIK();

	// Create the next outgoing handshake message.
	// payload: optional plaintext to encrypt inside the handshake.
	// Returns the raw bytes to send.
	std::vector<uint8_t> send(const uint8_t *payload = nullptr, size_t payload_len = 0);

	// Process an incoming handshake message.
	// Returns the decrypted payload.
	std::vector<uint8_t> recv(const uint8_t *data, size_t len);

	bool is_complete() const { return _complete; }

	// After completion: derived keys and state.
	const uint8_t *remote_public_key() const { return _rs; }
	const uint8_t *tx_key() const { return _tx; }
	const uint8_t *rx_key() const { return _rx; }
	const uint8_t *handshake_hash() const { return _hash; }

	// Derive holepunch secret: BLAKE2b(NS_PEER_HOLEPUNCH, handshake_hash).
	void derive_holepunch_secret(const uint8_t *ns_peer_holepunch, uint8_t *out_32) const;

	// Generate an Ed25519 keypair.
	static Ed25519KeyPair generate_keypair();

	// Compute namespace hashes for HyperDHT.
	// ns_name: "hyperswarm/dht"
	// ids: command IDs to hash [ANNOUNCE=4, UNANNOUNCE=5, MUTABLE_PUT=6, PEER_HANDSHAKE=0, PEER_HOLEPUNCH=1]
	// out: array of 32-byte hashes, one per ID.
	static void compute_namespace(const char *ns_name, const uint8_t *ids,
								  int count, uint8_t *out);

private:
	bool _initiator;
	int _message_index;
	bool _complete;

	Ed25519KeyPair _s;     // Static keypair.
	Ed25519KeyPair _e;     // Ephemeral keypair.
	bool _e_generated;

	uint8_t _rs[PKLEN];    // Remote static.
	uint8_t _re[PKLEN];    // Remote ephemeral.

	// Symmetric state.
	uint8_t _digest[HASHLEN];
	uint8_t _chaining_key[HASHLEN];
	uint8_t _cipher_key[KEYBYTES];
	bool _has_key;
	uint64_t _nonce;

	// Output keys.
	uint8_t _tx[KEYBYTES];
	uint8_t _rx[KEYBYTES];
	uint8_t _hash[HASHLEN];

	// Ed25519 DH: crypto_scalarmult_ed25519_noclamp with SHA512-derived scalar.
	static void _ed25519_dh(uint8_t *output, const uint8_t *secret_key_64,
							const uint8_t *remote_public_key);

	// Symmetric state operations (identical logic to NoiseHandshake, different DH).
	void _mix_hash(const uint8_t *data, size_t len);
	void _mix_key_from_dh(const uint8_t *remote_pk, const uint8_t *local_sk_64);

	void _encrypt_and_hash(std::vector<uint8_t> &out,
						   const uint8_t *pt, size_t pt_len);
	std::vector<uint8_t> _decrypt_and_hash(const uint8_t *ct, size_t ct_len);

	void _encrypt(uint8_t *ct, const uint8_t *pt, size_t pt_len,
				  const uint8_t *ad, size_t ad_len);
	bool _decrypt(uint8_t *pt, const uint8_t *ct, size_t ct_len,
				  const uint8_t *ad, size_t ad_len);

	static void _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 = nullptr);
	static void _hmac(uint8_t *out, const uint8_t *key, size_t key_len,
					  const uint8_t *data, size_t data_len);

	void _initialise_key(const uint8_t *key);
	void _split();
};

#endif // HYPERSWARM_NOISE_IK_H