/**
* test_verify_vectors.cpp — C++ verification against JS reference vectors
*
* Reads vectors.json (generated by generate_vectors.js) and verifies
* that every C++ function produces identical output to the JS reference.
*
* This is the core of the verification-first methodology:
* - JS IS the spec
* - This test compares C++ output to JS output, byte by byte
* - When a test fails, the diff tells you exactly what's wrong
*
* Build:
* cd /storage/projects/code/godot/hyperswarm-peer/test
* bash build_tests.sh (includes test_verify_vectors)
*
* Run:
* node generate_vectors.js > vectors.json
* ./test_verify_vectors vectors.json
*
* Philosophy: See memory/verification-first.md
*/
#include "compact_encoding.h"
#include "dht_messages.h"
#include "hyperdht.h"
#include "noise_ik.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sodium.h>
#include <sstream>
#include <string>
#include <vector>
// ============================================================================
// Minimal JSON parser (just enough for vectors.json)
// ============================================================================
// We parse flat key-value pairs from known paths. No dependency needed.
static std::string read_file(const char *path) {
std::ifstream f(path);
if (!f.is_open()) {
fprintf(stderr, "ERROR: Cannot open %s\n", path);
exit(1);
}
std::stringstream ss;
ss << f.rdbuf();
return ss.str();
}
// Extract a JSON string value by key path like "sections.keypair.tests[0].output.public_key"
// This is intentionally simple — vectors.json has a known structure.
static std::string json_get(const std::string &json, const std::string &path) {
// Find the key in the JSON
std::string search = "\"" + path + "\"";
// For nested paths, we search key by key
size_t pos = 0;
std::string remaining = path;
while (!remaining.empty()) {
size_t dot = remaining.find('.');
std::string key;
if (dot != std::string::npos) {
key = remaining.substr(0, dot);
remaining = remaining.substr(dot + 1);
} else {
key = remaining;
remaining = "";
}
// Handle array index: key[N]
int index = -1;
size_t bracket = key.find('[');
if (bracket != std::string::npos) {
index = std::stoi(key.substr(bracket + 1));
key = key.substr(0, bracket);
}
search = "\"" + key + "\"";
size_t found = json.find(search, pos);
if (found == std::string::npos) return "";
pos = found + search.length();
// Skip to value (past colon and whitespace)
while (pos < json.size() && (json[pos] == ' ' || json[pos] == ':' || json[pos] == '\n' || json[pos] == '\r' || json[pos] == '\t'))
pos++;
// If we need an array index, skip into the array
if (index >= 0) {
if (json[pos] == '[') pos++;
for (int i = 0; i < index; i++) {
// Skip one element (brace/bracket matching)
int depth = 0;
bool in_string = false;
while (pos < json.size()) {
char ch = json[pos++];
if (in_string) {
if (ch == '\\') pos++;
else if (ch == '"') in_string = false;
} else {
if (ch == '"') in_string = true;
else if (ch == '{' || ch == '[') depth++;
else if (ch == '}' || ch == ']') { if (--depth < 0) break; }
else if (ch == ',' && depth == 0) break;
}
}
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\n' || json[pos] == '\r' || json[pos] == '\t'))
pos++;
}
}
}
// Now pos points to the value
if (pos >= json.size()) return "";
if (json[pos] == '"') {
// String value
size_t end = json.find('"', pos + 1);
if (end == std::string::npos) return "";
return json.substr(pos + 1, end - pos - 1);
} else if (json[pos] == 't') return "true";
else if (json[pos] == 'f') return "false";
else if (json[pos] == 'n') return "null";
else {
// Number
size_t end = pos;
while (end < json.size() && json[end] != ',' && json[end] != '}' && json[end] != ']' && json[end] != '\n')
end++;
return json.substr(pos, end - pos);
}
}
// ============================================================================
// Hex utilities
// ============================================================================
static std::vector<uint8_t> from_hex(const std::string &hex) {
std::vector<uint8_t> bytes;
for (size_t i = 0; i + 1 < hex.size(); i += 2) {
uint8_t b = 0;
char c1 = hex[i], c2 = hex[i + 1];
b |= ((c1 >= 'a' ? c1 - 'a' + 10 : c1 - '0') << 4);
b |= (c2 >= 'a' ? c2 - 'a' + 10 : c2 - '0');
bytes.push_back(b);
}
return bytes;
}
static std::string to_hex(const uint8_t *data, size_t len) {
static const char hex_chars[] = "0123456789abcdef";
std::string result;
result.reserve(len * 2);
for (size_t i = 0; i < len; i++) {
result += hex_chars[(data[i] >> 4) & 0x0F];
result += hex_chars[data[i] & 0x0F];
}
return result;
}
// ============================================================================
// Test harness
// ============================================================================
static int tests_run = 0;
static int tests_passed = 0;
static int tests_failed = 0;
static void check_hex(const std::string &expected_hex, const uint8_t *actual, size_t actual_len, const char *name) {
tests_run++;
std::string actual_hex = to_hex(actual, actual_len);
if (actual_hex == expected_hex) {
tests_passed++;
printf(" PASS: %s\n", name);
} else {
tests_failed++;
printf(" FAIL: %s\n", name);
printf(" expected: %s (%zu bytes)\n", expected_hex.c_str(), expected_hex.size() / 2);
printf(" got: %s (%zu bytes)\n", actual_hex.c_str(), actual_len);
// Show first diff position
size_t min_len = std::min(expected_hex.size(), actual_hex.size());
for (size_t i = 0; i < min_len; i += 2) {
if (expected_hex.substr(i, 2) != actual_hex.substr(i, 2)) {
printf(" first diff at byte %zu: expected %s, got %s\n",
i / 2, expected_hex.substr(i, 2).c_str(), actual_hex.substr(i, 2).c_str());
break;
}
}
if (expected_hex.size() != actual_hex.size()) {
printf(" length mismatch: expected %zu bytes, got %zu bytes\n",
expected_hex.size() / 2, actual_len);
}
}
}
static void check_equal(int expected, int actual, const char *name) {
tests_run++;
if (expected == actual) {
tests_passed++;
printf(" PASS: %s\n", name);
} else {
tests_failed++;
printf(" FAIL: %s (expected %d, got %d)\n", name, expected, actual);
}
}
static void check_bool(bool expected, bool actual, const char *name) {
tests_run++;
if (expected == actual) {
tests_passed++;
printf(" PASS: %s\n", name);
} else {
tests_failed++;
printf(" FAIL: %s (expected %s, got %s)\n", name,
expected ? "true" : "false", actual ? "true" : "false");
}
}
// ============================================================================
// Section tests
// ============================================================================
static void test_keypair(const std::string &json) {
printf("\n=== Section: keypair ===\n");
// Test keypair_from_seed_A
std::string seed_hex = json_get(json, "sections.keypair.tests[0].input.seed");
std::string expected_pk = json_get(json, "sections.keypair.tests[0].output.public_key");
std::string expected_sk = json_get(json, "sections.keypair.tests[0].output.secret_key");
auto seed = from_hex(seed_hex);
uint8_t pk[32], sk[64];
crypto_sign_seed_keypair(pk, sk, seed.data());
check_hex(expected_pk, pk, 32, "keypair_from_seed_A: public_key");
check_hex(expected_sk, sk, 64, "keypair_from_seed_A: secret_key");
// Test keypair_from_seed_B
seed_hex = json_get(json, "sections.keypair.tests[1].input.seed");
expected_pk = json_get(json, "sections.keypair.tests[1].output.public_key");
expected_sk = json_get(json, "sections.keypair.tests[1].output.secret_key");
seed = from_hex(seed_hex);
crypto_sign_seed_keypair(pk, sk, seed.data());
check_hex(expected_pk, pk, 32, "keypair_from_seed_B: public_key");
check_hex(expected_sk, sk, 64, "keypair_from_seed_B: secret_key");
}
static void test_namespace(const std::string &json) {
printf("\n=== Section: namespace ===\n");
// Test hyperdht_namespaces: namespace("hyperswarm/dht", [4,5,6,0,1])
uint8_t ns_out[5 * 32];
uint8_t ids[] = {4, 5, 6, 0, 1};
NoiseIK::compute_namespace("hyperswarm/dht", ids, 5, ns_out);
std::string expected;
expected = json_get(json, "sections.namespace.tests[0].output.ns_announce");
check_hex(expected, ns_out + 0 * 32, 32, "ns_announce (id=4)");
expected = json_get(json, "sections.namespace.tests[0].output.ns_unannounce");
check_hex(expected, ns_out + 1 * 32, 32, "ns_unannounce (id=5)");
expected = json_get(json, "sections.namespace.tests[0].output.ns_mutable_put");
check_hex(expected, ns_out + 2 * 32, 32, "ns_mutable_put (id=6)");
expected = json_get(json, "sections.namespace.tests[0].output.ns_peer_handshake");
check_hex(expected, ns_out + 3 * 32, 32, "ns_peer_handshake (id=0)");
expected = json_get(json, "sections.namespace.tests[0].output.ns_peer_holepunch");
check_hex(expected, ns_out + 4 * 32, 32, "ns_peer_holepunch (id=1)");
// Also verify they match the NS.* constants from constants.js
expected = json_get(json, "sections.namespace.tests[1].output.NS_ANNOUNCE");
check_hex(expected, ns_out + 0 * 32, 32, "matches NS.ANNOUNCE constant");
}
static void test_token(const std::string &json) {
printf("\n=== Section: token ===\n");
std::string secret_hex = json_get(json, "sections.token.tests[0].input.secret");
auto secret = from_hex(secret_hex);
// Token = BLAKE2b(key=secret, data=host_string)
const char *hosts[] = {"127.0.0.1", "192.168.1.100", "10.0.0.1"};
for (int i = 0; i < 3; i++) {
char path[256];
snprintf(path, sizeof(path), "sections.token.tests[0].output[%d].token", i);
std::string expected = json_get(json, path);
uint8_t token[32];
crypto_generichash(token, 32,
reinterpret_cast<const uint8_t *>(hosts[i]), strlen(hosts[i]),
secret.data(), 32);
char name[64];
snprintf(name, sizeof(name), "token(%s)", hosts[i]);
check_hex(expected, token, 32, name);
}
}
static void test_varint(const std::string &json) {
printf("\n=== Section: varint ===\n");
int values[] = {0, 1, 127, 252, 253, 254, 255, 256, 1000, 65535, 65536, 100000, (int)0xFFFFFFFF};
int n_values = sizeof(values) / sizeof(values[0]);
for (int i = 0; i < n_values; i++) {
char path_enc[256], path_len[256];
snprintf(path_enc, sizeof(path_enc), "sections.varint.tests[%d].output.encoded", i);
snprintf(path_len, sizeof(path_len), "sections.varint.tests[%d].output.byte_length", i);
std::string expected_enc = json_get(json, path_enc);
int expected_len = std::stoi(json_get(json, path_len));
// Use EncodeState pattern (matching cenc::uint_encode)
EncodeState vs;
vs.start = 0; vs.end = 0; vs.buffer = nullptr;
uint64_t v = static_cast<uint64_t>(static_cast<uint32_t>(values[i]));
cenc::uint_preencode(vs, v);
uint8_t buf[16];
vs.buffer = buf;
vs.start = 0;
cenc::uint_encode(vs, v);
char name[64];
snprintf(name, sizeof(name), "varint(%u)", static_cast<uint32_t>(v));
check_hex(expected_enc, buf, vs.start, name);
check_equal(expected_len, (int)vs.start, (std::string(name) + " length").c_str());
}
}
static void test_peer_record(const std::string &json) {
printf("\n=== Section: peer_record ===\n");
// Test peer with no relays
std::string pk_hex = json_get(json, "sections.peer_record.tests[0].input.public_key");
std::string expected = json_get(json, "sections.peer_record.tests[0].output.encoded");
int expected_len = std::stoi(json_get(json, "sections.peer_record.tests[0].output.byte_length"));
auto pk = from_hex(pk_hex);
dht_msg::PeerRecord peer;
memcpy(peer.public_key, pk.data(), 32);
peer.relay_count = 0;
// Encode using EncodeState pattern
EncodeState s1;
s1.start = 0; s1.end = 0;
dht_msg::peer_record_preencode(s1, peer);
std::vector<uint8_t> enc1(s1.end);
s1.buffer = enc1.data();
s1.start = 0;
dht_msg::peer_record_encode(s1, peer);
check_hex(expected, enc1.data(), enc1.size(), "peer_no_relays");
check_equal(expected_len, (int)enc1.size(), "peer_no_relays length");
// Test peer with one relay
expected = json_get(json, "sections.peer_record.tests[1].output.encoded");
expected_len = std::stoi(json_get(json, "sections.peer_record.tests[1].output.byte_length"));
dht_msg::PeerRecord peer2;
memcpy(peer2.public_key, pk.data(), 32);
peer2.relay_addresses[0].ip[0] = 192; peer2.relay_addresses[0].ip[1] = 168;
peer2.relay_addresses[0].ip[2] = 1; peer2.relay_addresses[0].ip[3] = 100;
peer2.relay_addresses[0].port = 49737;
peer2.relay_count = 1;
EncodeState s2;
s2.start = 0; s2.end = 0;
dht_msg::peer_record_preencode(s2, peer2);
std::vector<uint8_t> enc2(s2.end);
s2.buffer = enc2.data();
s2.start = 0;
dht_msg::peer_record_encode(s2, peer2);
check_hex(expected, enc2.data(), enc2.size(), "peer_one_relay");
check_equal(expected_len, (int)enc2.size(), "peer_one_relay length");
}
static void test_announce_signable(const std::string &json) {
printf("\n=== Section: announce_signable (CRITICAL) ===\n");
// This is the section that caught the 8-hour bug.
// The JS annSignable function computes:
// signable = NS(32) || BLAKE2b_batch(target, node_id, token, encoded_peer, refresh)(32)
//
// We replicate this in C++ and verify byte-by-byte.
std::string target_hex = json_get(json, "sections.announce_signable.tests[0].input.target");
std::string token_hex = json_get(json, "sections.announce_signable.tests[0].input.token");
std::string node_id_hex = json_get(json, "sections.announce_signable.tests[0].input.node_id");
std::string pk_hex = json_get(json, "sections.announce_signable.tests[0].input.public_key");
auto target = from_hex(target_hex);
auto token = from_hex(token_hex);
auto node_id = from_hex(node_id_hex);
auto pk = from_hex(pk_hex);
// Get the namespace constant for ANNOUNCE
std::string ns_announce_hex = json_get(json, "sections.namespace.tests[1].output.NS_ANNOUNCE");
auto ns_announce = from_hex(ns_announce_hex);
// Encode the peer record (no relay addresses)
dht_msg::PeerRecord peer;
memcpy(peer.public_key, pk.data(), 32);
peer.relay_count = 0;
EncodeState sp;
sp.start = 0; sp.end = 0;
dht_msg::peer_record_preencode(sp, peer);
std::vector<uint8_t> encoded_peer(sp.end);
sp.buffer = encoded_peer.data();
sp.start = 0;
dht_msg::peer_record_encode(sp, peer);
// Verify encoded peer matches JS
std::string expected_peer_hex = json_get(json, "sections.announce_signable.tests[0].output.encoded_peer");
check_hex(expected_peer_hex, encoded_peer.data(), encoded_peer.size(), "encoded_peer matches JS");
// Compute signable: NS(32) || BLAKE2b_batch(target, node_id, token, encoded_peer, empty)(32)
uint8_t signable[64];
memcpy(signable, ns_announce.data(), 32);
// BLAKE2b batch hash: init, update with each input, finalize
crypto_generichash_state state;
crypto_generichash_init(&state, nullptr, 0, 32);
crypto_generichash_update(&state, target.data(), 32);
crypto_generichash_update(&state, node_id.data(), 32);
crypto_generichash_update(&state, token.data(), 32);
crypto_generichash_update(&state, encoded_peer.data(), encoded_peer.size());
// refresh = empty (0 bytes) — verified that empty doesn't change the hash
crypto_generichash_final(&state, signable + 32, 32);
std::string expected_signable = json_get(json, "sections.announce_signable.tests[0].output.signable");
check_hex(expected_signable, signable, 64, "signable_no_refresh");
// Test with refresh
std::string refresh_hex = json_get(json, "sections.announce_signable.tests[1].input.refresh");
auto refresh = from_hex(refresh_hex);
uint8_t signable2[64];
memcpy(signable2, ns_announce.data(), 32);
crypto_generichash_init(&state, nullptr, 0, 32);
crypto_generichash_update(&state, target.data(), 32);
crypto_generichash_update(&state, node_id.data(), 32);
crypto_generichash_update(&state, token.data(), 32);
crypto_generichash_update(&state, encoded_peer.data(), encoded_peer.size());
crypto_generichash_update(&state, refresh.data(), refresh.size());
crypto_generichash_final(&state, signable2 + 32, 32);
std::string expected_signable2 = json_get(json, "sections.announce_signable.tests[1].output.signable");
check_hex(expected_signable2, signable2, 64, "signable_with_refresh");
// Test unannounce (different namespace)
std::string ns_unannounce_hex = json_get(json, "sections.namespace.tests[1].output.NS_UNANNOUNCE");
auto ns_unannounce = from_hex(ns_unannounce_hex);
uint8_t signable3[64];
memcpy(signable3, ns_unannounce.data(), 32);
crypto_generichash_init(&state, nullptr, 0, 32);
crypto_generichash_update(&state, target.data(), 32);
crypto_generichash_update(&state, node_id.data(), 32);
crypto_generichash_update(&state, token.data(), 32);
crypto_generichash_update(&state, encoded_peer.data(), encoded_peer.size());
crypto_generichash_final(&state, signable3 + 32, 32);
std::string expected_signable3 = json_get(json, "sections.announce_signable.tests[2].output.signable");
check_hex(expected_signable3, signable3, 64, "signable_unannounce");
// Test different node_id (MUST produce different signable — per-node proof)
std::string node_id2_hex = json_get(json, "sections.announce_signable.tests[3].input.node_id");
auto node_id2 = from_hex(node_id2_hex);
uint8_t signable4[64];
memcpy(signable4, ns_announce.data(), 32);
crypto_generichash_init(&state, nullptr, 0, 32);
crypto_generichash_update(&state, target.data(), 32);
crypto_generichash_update(&state, node_id2.data(), 32);
crypto_generichash_update(&state, token.data(), 32);
crypto_generichash_update(&state, encoded_peer.data(), encoded_peer.size());
crypto_generichash_final(&state, signable4 + 32, 32);
std::string expected_signable4 = json_get(json, "sections.announce_signable.tests[3].output.signable");
check_hex(expected_signable4, signable4, 64, "signable_different_node_id");
// Verify it actually differs from test 1
check_bool(true, memcmp(signable, signable4, 64) != 0, "different_node_id produces different signable");
// Test sign + verify round-trip
std::string sk_hex = json_get(json, "sections.announce_signable.tests[5].input.secret_key");
auto sk = from_hex(sk_hex);
uint8_t signature[64];
std::string signable_hex = json_get(json, "sections.announce_signable.tests[5].input.signable");
auto signable_buf = from_hex(signable_hex);
crypto_sign_detached(signature, nullptr, signable_buf.data(), signable_buf.size(), sk.data());
std::string expected_sig = json_get(json, "sections.announce_signable.tests[5].output.signature");
check_hex(expected_sig, signature, 64, "sign_deterministic");
// Verify
auto pk_verify = from_hex(json_get(json, "sections.announce_signable.tests[5].input.public_key"));
int verify_ok = crypto_sign_verify_detached(signature, signable_buf.data(), signable_buf.size(), pk_verify.data());
check_bool(true, verify_ok == 0, "verify_signature");
}
static void test_noise_ik(const std::string &json) {
printf("\n=== Section: noise_ik ===\n");
// Test Ed25519 -> Curve25519 conversion
std::string ed_sk_hex = json_get(json, "sections.noise_ik.tests[0].input.ed25519_secret_key");
std::string expected_curve_sk = json_get(json, "sections.noise_ik.tests[0].output.curve25519_secret_key");
auto ed_sk = from_hex(ed_sk_hex);
uint8_t curve_sk[32];
crypto_sign_ed25519_sk_to_curve25519(curve_sk, ed_sk.data());
check_hex(expected_curve_sk, curve_sk, 32, "ed25519_to_curve25519_sk");
// Test Ed25519 pk -> Curve25519 pk
std::string ed_pk_hex = json_get(json, "sections.noise_ik.tests[1].input.ed25519_public_key");
std::string expected_curve_pk = json_get(json, "sections.noise_ik.tests[1].output.curve25519_public_key");
auto ed_pk = from_hex(ed_pk_hex);
uint8_t curve_pk[32];
crypto_sign_ed25519_pk_to_curve25519(curve_pk, ed_pk.data());
check_hex(expected_curve_pk, curve_pk, 32, "ed25519_to_curve25519_pk");
// Test DH
std::string expected_shared = json_get(json, "sections.noise_ik.tests[2].output.shared_secret");
uint8_t shared[32];
crypto_scalarmult(shared, curve_sk, curve_pk);
check_hex(expected_shared, shared, 32, "dh_shared_secret");
// Test prologue
std::string expected_prologue = json_get(json, "sections.noise_ik.tests[3].output.prologue");
uint8_t ns_out2[5 * 32];
uint8_t ids2[] = {4, 5, 6, 0, 1};
NoiseIK::compute_namespace("hyperswarm/dht", ids2, 5, ns_out2);
// ns_peer_handshake is at index 3 (id=0)
check_hex(expected_prologue, ns_out2 + 3 * 32, 32, "noise_prologue = NS.PEER_HANDSHAKE");
}
static void test_find_peer_target(const std::string &json) {
printf("\n=== Section: find_peer_target ===\n");
std::string pk_hex = json_get(json, "sections.find_peer_target.tests[0].input.public_key");
std::string expected_target = json_get(json, "sections.find_peer_target.tests[0].output.target");
auto pk = from_hex(pk_hex);
uint8_t target[32];
crypto_generichash(target, 32, pk.data(), 32, nullptr, 0);
check_hex(expected_target, target, 32, "find_peer_target = BLAKE2b(publicKey)");
}
static void test_immutable(const std::string &json) {
printf("\n=== Section: immutable ===\n");
std::string value_hex = json_get(json, "sections.immutable.tests[0].input.value");
std::string expected_target = json_get(json, "sections.immutable.tests[0].output.target");
auto value = from_hex(value_hex);
uint8_t target[32];
crypto_generichash(target, 32, value.data(), value.size(), nullptr, 0);
check_hex(expected_target, target, 32, "immutable_target = BLAKE2b(value)");
}
static void test_blake2b_batch(const std::string &json) {
printf("\n=== Section: blake2b_batch ===\n");
// Single input
std::string input1_hex = json_get(json, "sections.blake2b_batch.tests[0].input.inputs[0]");
auto input1 = from_hex(input1_hex);
uint8_t hash1[32];
crypto_generichash_state state;
crypto_generichash_init(&state, nullptr, 0, 32);
crypto_generichash_update(&state, input1.data(), input1.size());
crypto_generichash_final(&state, hash1, 32);
std::string expected1 = json_get(json, "sections.blake2b_batch.tests[0].output.hash");
check_hex(expected1, hash1, 32, "blake2b_batch_single");
// Three inputs
std::string input2_hex = json_get(json, "sections.blake2b_batch.tests[1].input.inputs[1]");
std::string input3_hex = json_get(json, "sections.blake2b_batch.tests[1].input.inputs[2]");
auto input2 = from_hex(input2_hex);
auto input3 = from_hex(input3_hex);
uint8_t hash2[32];
crypto_generichash_init(&state, nullptr, 0, 32);
crypto_generichash_update(&state, input1.data(), input1.size());
crypto_generichash_update(&state, input2.data(), input2.size());
crypto_generichash_update(&state, input3.data(), input3.size());
crypto_generichash_final(&state, hash2, 32);
std::string expected2 = json_get(json, "sections.blake2b_batch.tests[1].output.hash");
check_hex(expected2, hash2, 32, "blake2b_batch_three");
// Three inputs + empty (should equal three inputs without empty)
uint8_t hash3[32];
crypto_generichash_init(&state, nullptr, 0, 32);
crypto_generichash_update(&state, input1.data(), input1.size());
crypto_generichash_update(&state, input2.data(), input2.size());
crypto_generichash_update(&state, input3.data(), input3.size());
// Empty update
crypto_generichash_update(&state, nullptr, 0);
crypto_generichash_final(&state, hash3, 32);
std::string expected3 = json_get(json, "sections.blake2b_batch.tests[2].output.hash");
check_hex(expected3, hash3, 32, "blake2b_batch_three_plus_empty");
// Verify empty doesn't change hash
check_bool(true, memcmp(hash2, hash3, 32) == 0, "empty_trailing_input_does_not_change_hash");
}
static void test_ipv4(const std::string &json) {
printf("\n=== Section: ipv4 ===\n");
struct TestAddr {
uint8_t ip[4];
uint16_t port;
const char *name;
};
TestAddr addrs[] = {
{{127, 0, 0, 1}, 49737, "127.0.0.1:49737"},
{{192, 168, 1, 100}, 12345, "192.168.1.100:12345"},
{{10, 0, 0, 1}, 443, "10.0.0.1:443"},
{{255, 255, 255, 255}, 65535, "255.255.255.255:65535"}};
for (int i = 0; i < 4; i++) {
char path[256];
snprintf(path, sizeof(path), "sections.ipv4.tests[%d].output.encoded", i);
std::string expected = json_get(json, path);
uint8_t buf[6];
buf[0] = addrs[i].ip[0];
buf[1] = addrs[i].ip[1];
buf[2] = addrs[i].ip[2];
buf[3] = addrs[i].ip[3];
buf[4] = addrs[i].port & 0xFF;
buf[5] = (addrs[i].port >> 8) & 0xFF;
check_hex(expected, buf, 6, addrs[i].name);
}
}
// ============================================================================
// Main
// ============================================================================
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s vectors.json\n", argv[0]);
fprintf(stderr, "\nGenerate vectors first:\n");
fprintf(stderr, " node generate_vectors.js > vectors.json\n");
return 1;
}
if (sodium_init() < 0) {
fprintf(stderr, "ERROR: sodium_init() failed\n");
return 1;
}
std::string json = read_file(argv[1]);
printf("Verification-First Test Suite\n");
printf("Vectors: %s\n", argv[1]);
printf("Reference: hyperdht %s, hypercore-crypto %s\n",
json_get(json, "reference_packages.hyperdht").c_str(),
json_get(json, "reference_packages.hypercore-crypto").c_str());
printf("Generated: %s\n", json_get(json, "generated_at").c_str());
test_keypair(json);
test_namespace(json);
test_token(json);
test_varint(json);
test_peer_record(json);
test_announce_signable(json);
test_noise_ik(json);
test_find_peer_target(json);
test_immutable(json);
test_blake2b_batch(json);
test_ipv4(json);
printf("\n===========================\n");
printf("Results: %d/%d passed", tests_passed, tests_run);
if (tests_failed > 0) {
printf(" (%d FAILED)", tests_failed);
}
printf("\n===========================\n");
return tests_failed > 0 ? 1 : 0;
}