OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / src / noise_handshake.h

127 lines · c ref:
#ifndef HYPERSWARM_NOISE_HANDSHAKE_H
#define HYPERSWARM_NOISE_HANDSHAKE_H

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

// Noise XX handshake state machine.
// Protocol: Noise_XX_25519_ChaChaPoly_BLAKE2b
// Compatible with @hyperswarm/secret-stream (via noise-handshake).
//
// XX pattern (3 messages):
//   Msg 1 (initiator → responder): e
//   Msg 2 (responder → initiator): e, ee, s, es
//   Msg 3 (initiator → responder): s, se

struct NoiseKeypair {
	uint8_t public_key[32];  // Curve25519 public key
	uint8_t secret_key[32];  // Curve25519 secret key
};

class NoiseHandshake {
public:
	static constexpr int HASHLEN = 64;   // BLAKE2b output
	static constexpr int DHLEN = 32;     // Curve25519
	static constexpr int PKLEN = 32;
	static constexpr int SKLEN = 32;
	static constexpr int MACBYTES = 16;  // ChaCha20-Poly1305 tag
	static constexpr int NONCEBYTES = 12;
	static constexpr int KEYBYTES = 32;
	static constexpr int BLOCKLEN = 128; // BLAKE2b block size

	NoiseHandshake(bool is_initiator, const NoiseKeypair &static_key);
	~NoiseHandshake();

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

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

	// True when the 3-message handshake is complete.
	bool is_complete() const { return _complete; }

	// After completion: remote peer's static public key (32 bytes).
	const uint8_t *remote_public_key() const { return _rs; }

	// After completion: derived symmetric keys (32 bytes each).
	const uint8_t *tx_key() const { return _tx; }
	const uint8_t *rx_key() const { return _rx; }

	// After completion: handshake hash (64 bytes).
	const uint8_t *handshake_hash() const { return _hash; }

	// Generate a Curve25519 keypair (static, for identity).
	static NoiseKeypair generate_keypair();

private:
	bool _initiator;
	int _message_index;
	bool _complete;

	// Static keypair (Curve25519, long-lived identity).
	NoiseKeypair _s;

	// Ephemeral keypair (Curve25519, generated per-handshake).
	NoiseKeypair _e;
	bool _e_generated;

	// Remote keys
	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 (after completion)
	uint8_t _tx[KEYBYTES];
	uint8_t _rx[KEYBYTES];
	uint8_t _hash[HASHLEN];

	// -- Symmetric state operations (matching noise-handshake JS) --

	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);
	void _mix_key_normal(const uint8_t *key, size_t key_len);

	// Encrypt plaintext, append to out, update hash.
	void _encrypt_and_hash(std::vector<uint8_t> &out,
						   const uint8_t *pt, size_t pt_len);

	// Decrypt ciphertext, return plaintext, update hash.
	std::vector<uint8_t> _decrypt_and_hash(const uint8_t *ct, size_t ct_len);

	// Raw AEAD encrypt/decrypt with current cipher key + nonce.
	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);

	// ECDH: output = scalarmult(local_sk, remote_pk).
	static void _dh(uint8_t *output, const uint8_t *local_sk, const uint8_t *remote_pk);

	// HKDF: extract-and-expand using HMAC-BLAKE2b.
	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);

	// HMAC-BLAKE2b
	static void _hmac(uint8_t *out, const uint8_t *key, size_t key_len,
					  const uint8_t *data, size_t data_len);

	// Split: derive tx/rx keys from chaining key.
	void _split();

	// Initialise cipher key.
	void _initialise_key(const uint8_t *key);
};

#endif // HYPERSWARM_NOISE_HANDSHAKE_H