/* * test_ed25519.cpp — Unit tests for Ed25519Utils. * * Copyright (c) 2026 Are Bjørby * SPDX-License-Identifier: MIT */ #include "Ed25519Utils.hpp" #include #include #include #include #include #include #include using namespace antpeer::crypto; static int pass = 0; static int fail = 0; #define CHECK(cond, msg) do { \ if (cond) { ++pass; } \ else { ++fail; fprintf(stderr, "FAIL: %s (line %d)\n", msg, __LINE__); } \ } while(0) /* ── Hex utilities ────────────────────────────────────────────────── */ static void test_hex_roundtrip() { uint8_t data[] = {0x00, 0x01, 0xab, 0xcd, 0xef, 0xff}; std::string hex = to_hex(data, sizeof(data)); CHECK(hex == "0001abcdef" "ff", "hex encoding"); uint8_t out[6]; CHECK(from_hex(hex, out, sizeof(out)), "hex decoding"); CHECK(memcmp(data, out, sizeof(data)) == 0, "hex round-trip"); } static void test_hex_invalid() { uint8_t out[4]; CHECK(!from_hex("0g01", out, sizeof(out)), "invalid hex char"); CHECK(!from_hex("001", out, sizeof(out)), "odd length hex"); CHECK(!from_hex("0011223344", out, 2), "hex too long for buffer"); } static void test_hex_case() { uint8_t out[2]; CHECK(from_hex("AbCd", out, sizeof(out)), "mixed case hex"); CHECK(out[0] == 0xab && out[1] == 0xcd, "mixed case values"); } /* ── Keygen ───────────────────────────────────────────────────────── */ static void test_keygen() { KeyPair kp = keygen(); CHECK(kp.private_key.size() == 64, "private key length"); CHECK(kp.public_key.size() == 64, "public key length"); CHECK(kp.key_id.size() == 32, "key_id length"); } static void test_keygen_unique() { KeyPair a = keygen(); KeyPair b = keygen(); CHECK(a.private_key != b.private_key, "unique private keys"); CHECK(a.public_key != b.public_key, "unique public keys"); CHECK(a.key_id != b.key_id, "unique key_ids"); } /* ── Sign / Verify ────────────────────────────────────────────────── */ static void test_sign_verify() { KeyPair kp = keygen(); std::string data = "hello world"; std::string sig = sign(data, kp.private_key); CHECK(sig.size() == 128, "signature length (128 hex = 64 bytes)"); CHECK(verify(data, sig, kp.public_key), "valid signature"); } static void test_sign_verify_binary() { KeyPair kp = keygen(); uint8_t data[] = {0x00, 0x01, 0x02, 0xff}; std::string sig = sign(data, sizeof(data), kp.private_key); CHECK(verify(data, sizeof(data), sig, kp.public_key), "binary data sign/verify"); } static void test_sign_verify_empty() { KeyPair kp = keygen(); std::string sig = sign("", kp.private_key); CHECK(sig.size() == 128, "signature of empty data"); CHECK(verify("", sig, kp.public_key), "verify empty data"); } static void test_verify_wrong_data() { KeyPair kp = keygen(); std::string sig = sign("hello", kp.private_key); CHECK(!verify("world", sig, kp.public_key), "reject wrong data"); } static void test_verify_wrong_key() { KeyPair kp1 = keygen(); KeyPair kp2 = keygen(); std::string sig = sign("hello", kp1.private_key); CHECK(!verify("hello", sig, kp2.public_key), "reject wrong key"); } static void test_verify_bad_signature() { KeyPair kp = keygen(); std::string sig = sign("hello", kp.private_key); /* Flip a byte in the signature */ sig[0] = (sig[0] == '0') ? '1' : '0'; CHECK(!verify("hello", sig, kp.public_key), "reject tampered signature"); } static void test_verify_invalid_hex() { KeyPair kp = keygen(); CHECK(!verify("hello", "not_hex", kp.public_key), "reject non-hex sig"); CHECK(!verify("hello", "00", kp.public_key), "reject short sig"); } static void test_sign_deterministic() { KeyPair kp = keygen(); std::string sig1 = sign("same data", kp.private_key); std::string sig2 = sign("same data", kp.private_key); CHECK(sig1 == sig2, "Ed25519 sign is deterministic"); } /* ── Nonce ────────────────────────────────────────────────────────── */ static void test_nonce() { std::string n = nonce(32); CHECK(n.size() == 64, "nonce length (32 bytes = 64 hex)"); } static void test_nonce_unique() { std::string a = nonce(32); std::string b = nonce(32); CHECK(a != b, "nonces are unique"); } static void test_nonce_custom_size() { std::string n = nonce(16); CHECK(n.size() == 32, "16-byte nonce = 32 hex"); } static void test_nonce_invalid() { bool threw = false; try { nonce(0); } catch (const std::invalid_argument&) { threw = true; } CHECK(threw, "nonce(0) throws"); threw = false; try { nonce(257); } catch (const std::invalid_argument&) { threw = true; } CHECK(threw, "nonce(257) throws"); } /* ── Key I/O ──────────────────────────────────────────────────────── */ static void test_save_load_key() { char tmppath[] = "/tmp/test_ed25519_XXXXXX"; int fd = mkstemp(tmppath); CHECK(fd >= 0, "mkstemp"); close(fd); KeyPair kp = keygen(); save_key(kp, tmppath); KeyPair loaded = load_key(tmppath); CHECK(loaded.private_key == kp.private_key, "loaded private key"); CHECK(loaded.public_key == kp.public_key, "loaded public key"); CHECK(loaded.key_id == kp.key_id, "loaded key_id"); /* Loaded key produces valid signatures */ std::string sig = sign("test", loaded.private_key); CHECK(verify("test", sig, loaded.public_key), "loaded key signs correctly"); unlink(tmppath); } static void test_load_key_missing() { bool threw = false; try { load_key("/tmp/nonexistent_key_file_12345"); } catch (const std::runtime_error&) { threw = true; } CHECK(threw, "load missing key throws"); } /* ── Trusted key store ────────────────────────────────────────────── */ static void test_trusted_keys() { char tmppath[] = "/tmp/test_trusted_XXXXXX"; int fd = mkstemp(tmppath); CHECK(fd >= 0, "mkstemp trusted"); close(fd); KeyPair kp1 = keygen(); KeyPair kp2 = keygen(); FILE* f = fopen(tmppath, "w"); fprintf(f, "# Trusted keys\n"); fprintf(f, "ALPHA %s %s\n", kp1.key_id.c_str(), kp1.public_key.c_str()); fprintf(f, "BRAVO %s %s\n", kp2.key_id.c_str(), kp2.public_key.c_str()); fprintf(f, "\n"); fprintf(f, "# Another key for ALPHA (rotation)\n"); KeyPair kp3 = keygen(); fprintf(f, "ALPHA %s %s\n", kp3.key_id.c_str(), kp3.public_key.c_str()); fclose(f); auto keys = load_trusted_keys(tmppath); CHECK(keys.size() == 3, "loaded 3 trusted keys"); const TrustedKey* tk = find_trusted_key(keys, "ALPHA"); CHECK(tk != nullptr, "find ALPHA"); CHECK(tk->public_key == kp1.public_key, "ALPHA first key"); tk = find_trusted_key(keys, "ALPHA", kp3.key_id); CHECK(tk != nullptr, "find ALPHA by key_id"); CHECK(tk->public_key == kp3.public_key, "ALPHA rotated key"); tk = find_trusted_key(keys, "BRAVO"); CHECK(tk != nullptr, "find BRAVO"); CHECK(tk->public_key == kp2.public_key, "BRAVO key"); tk = find_trusted_key(keys, "UNKNOWN"); CHECK(tk == nullptr, "unknown OID not found"); unlink(tmppath); } static void test_trusted_keys_missing() { bool threw = false; try { load_trusted_keys("/tmp/nonexistent_trusted_12345"); } catch (const std::runtime_error&) { threw = true; } CHECK(threw, "load missing trusted keys throws"); } /* ── Cross-key verification (integration) ─────────────────────────── */ static void test_challenge_response_flow() { /* Simulate: server challenges client, client signs, server verifies */ KeyPair server_key = keygen(); KeyPair client_key = keygen(); /* Server generates nonce */ std::string challenge = nonce(32); /* Client signs nonce with their private key */ std::string proof = sign(challenge, client_key.private_key); /* Server verifies using client's known public key */ CHECK(verify(challenge, proof, client_key.public_key), "challenge-response flow"); /* Wrong public key rejects */ CHECK(!verify(challenge, proof, server_key.public_key), "challenge-response wrong key"); } /* ── Main ─────────────────────────────────────────────────────────── */ int main() { /* Hex */ test_hex_roundtrip(); test_hex_invalid(); test_hex_case(); /* Keygen */ test_keygen(); test_keygen_unique(); /* Sign/Verify */ test_sign_verify(); test_sign_verify_binary(); test_sign_verify_empty(); test_verify_wrong_data(); test_verify_wrong_key(); test_verify_bad_signature(); test_verify_invalid_hex(); test_sign_deterministic(); /* Nonce */ test_nonce(); test_nonce_unique(); test_nonce_custom_size(); test_nonce_invalid(); /* Key I/O */ test_save_load_key(); test_load_key_missing(); /* Trusted keys */ test_trusted_keys(); test_trusted_keys_missing(); /* Integration */ test_challenge_response_flow(); printf("%d/%d tests passed\n", pass, pass + fail); return fail > 0 ? 1 : 0; }