433 lines
18 KiB
C
433 lines
18 KiB
C
/*
|
|
* NOSTR Event Generation Test Suite
|
|
* Tests complete workflow from mnemonic to signed event using specific test vectors
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include "../nostr_core/nostr_common.h"
|
|
#include "../nostr_core/nip001.h"
|
|
#include "../nostr_core/nip006.h"
|
|
#include "../nostr_core/nip019.h"
|
|
#include "../cjson/cJSON.h"
|
|
|
|
// Test vector structure
|
|
typedef struct {
|
|
const char* mnemonic;
|
|
const char* expected_nsec_hex;
|
|
const char* expected_nsec;
|
|
const char* expected_npub_hex;
|
|
const char* expected_npub;
|
|
const char* name;
|
|
} test_vector_t;
|
|
|
|
// Test vectors to validate against
|
|
static const test_vector_t TEST_VECTORS[] = {
|
|
{
|
|
.name = "Vector 1",
|
|
.mnemonic = "fetch risk mention yellow cluster hunt voyage acquire leader caution romance solid",
|
|
.expected_nsec_hex = "b46173ac0cc222f73246d6be63f5c0bd90d92b118f99f582cd11d077490d0794",
|
|
.expected_nsec = "nsec1k3sh8tqvcg30wvjx66lx8awqhkgdj2c337vltqkdz8g8wjgdq72q3mrze9",
|
|
.expected_npub_hex = "a11258677dd416ca4c9e352e0e02ad2d8784a18c3a963604d0c63dc7b74eec66",
|
|
.expected_npub = "npub15yf9sema6stv5ny7x5hquq4d9krcfgvv82trvpxscc7u0d6wa3nqmvcv3a"
|
|
},
|
|
{
|
|
.name = "Vector 2",
|
|
.mnemonic = "leader monkey parrot ring guide accident before fence cannon height naive bean",
|
|
.expected_nsec_hex = "7f7ff03d123792d6ac594bfa67bf6d0c0ab55b6b1fdb6249303fe861f1ccba9a",
|
|
.expected_nsec = "nsec10allq0gjx7fddtzef0ax00mdps9t2kmtrldkyjfs8l5xruwvh2dq0lhhkp",
|
|
.expected_npub_hex = "17162c921dc4d2518f9a101db33695df1afb56ab82f5ff3e5da6eec3ca5cd917",
|
|
.expected_npub = "npub1zutzeysacnf9rru6zqwmxd54mud0k44tst6l70ja5mhv8jjumytsd2x7nu"
|
|
},
|
|
{
|
|
.name = "Vector 3",
|
|
.mnemonic = "what bleak badge arrange retreat wolf trade produce cricket blur garlic valid proud rude strong choose busy staff weather area salt hollow arm fade",
|
|
.expected_nsec_hex = "c15d739894c81a2fcfd3a2df85a0d2c0dbc47a280d092799f144d73d7ae78add",
|
|
.expected_nsec = "nsec1c9wh8xy5eqdzln7n5t0ctgxjcrdug73gp5yj0x03gntn67h83twssdfhel",
|
|
.expected_npub_hex = "d41b22899549e1f3d335a31002cfd382174006e166d3e658e3a5eecdb6463573",
|
|
.expected_npub = "npub16sdj9zv4f8sl85e45vgq9n7nsgt5qphpvmf7vk8r5hhvmdjxx4es8rq74h"
|
|
}
|
|
};
|
|
|
|
static const size_t NUM_TEST_VECTORS = sizeof(TEST_VECTORS) / sizeof(TEST_VECTORS[0]);
|
|
|
|
// Constants for event generation test
|
|
static const uint32_t TEST_CREATED_AT = 1698623783;
|
|
static const char* TEST_CONTENT = "Hello";
|
|
|
|
// Expected events for each test vector
|
|
typedef struct {
|
|
const char* expected_event_id;
|
|
const char* expected_signature;
|
|
const char* expected_json;
|
|
} expected_event_t;
|
|
|
|
static const expected_event_t EXPECTED_EVENTS[] = {
|
|
{
|
|
// Vector 1 expected event
|
|
.expected_event_id = "c790e29519cc43ad87a4e061c36b4740cf1085e2c9eabb6971ea97f3859eb008",
|
|
.expected_signature = "9acb3e409a8b329316bd4184ad74a50db7764a4370ad863f97fb37858d87c380c9299a7adef19dfd29481f51eb81e28ebba2a6d2bbcc4085a1b07ca8339e8d0c",
|
|
.expected_json = "{\n"
|
|
"\t\"pubkey\":\t\"a11258677dd416ca4c9e352e0e02ad2d8784a18c3a963604d0c63dc7b74eec66\",\n"
|
|
"\t\"created_at\":\t1698623783,\n"
|
|
"\t\"kind\":\t1,\n"
|
|
"\t\"tags\":\t[],\n"
|
|
"\t\"content\":\t\"Hello\",\n"
|
|
"\t\"id\":\t\"c790e29519cc43ad87a4e061c36b4740cf1085e2c9eabb6971ea97f3859eb008\",\n"
|
|
"\t\"sig\":\t\"9acb3e409a8b329316bd4184ad74a50db7764a4370ad863f97fb37858d87c380c9299a7adef19dfd29481f51eb81e28ebba2a6d2bbcc4085a1b07ca8339e8d0c\"\n"
|
|
"}"
|
|
},
|
|
{
|
|
// Vector 2 expected event
|
|
.expected_event_id = "e28fda46caa56eb6f62c7871409e6c76cd43a47fca14878b91e49d8ee8e52c27",
|
|
.expected_signature = "7a7ce178e18b1065a9642985a3fb815ed52772c34fc6e67515de012558968f6428509b9cf93cf6faf17db387b833196a5be48ed1154c1c2dffb1c30293318e3d",
|
|
.expected_json = "{\n"
|
|
"\t\"pubkey\":\t\"17162c921dc4d2518f9a101db33695df1afb56ab82f5ff3e5da6eec3ca5cd917\",\n"
|
|
"\t\"created_at\":\t1698623783,\n"
|
|
"\t\"kind\":\t1,\n"
|
|
"\t\"tags\":\t[],\n"
|
|
"\t\"content\":\t\"Hello\",\n"
|
|
"\t\"id\":\t\"e28fda46caa56eb6f62c7871409e6c76cd43a47fca14878b91e49d8ee8e52c27\",\n"
|
|
"\t\"sig\":\t\"7a7ce178e18b1065a9642985a3fb815ed52772c34fc6e67515de012558968f6428509b9cf93cf6faf17db387b833196a5be48ed1154c1c2dffb1c30293318e3d\"\n"
|
|
"}"
|
|
},
|
|
{
|
|
// Vector 3 expected event
|
|
.expected_event_id = "ad349fdb162ea874d8b685e682b9dcc84b5bd72c4efac51e295db39b7623cde0",
|
|
.expected_signature = "11e2280cca6f2e0f638fbf60f8aa744a4c228ba19f4d787a51298ec23be4a226e5046477cf6444a804c81aa08dd287e9647f0b45f8a02700da4a387187d4b3dc",
|
|
.expected_json = "{\n"
|
|
"\t\"pubkey\":\t\"d41b22899549e1f3d335a31002cfd382174006e166d3e658e3a5eecdb6463573\",\n"
|
|
"\t\"created_at\":\t1698623783,\n"
|
|
"\t\"kind\":\t1,\n"
|
|
"\t\"tags\":\t[],\n"
|
|
"\t\"content\":\t\"Hello\",\n"
|
|
"\t\"id\":\t\"ad349fdb162ea874d8b685e682b9dcc84b5bd72c4efac51e295db39b7623cde0\",\n"
|
|
"\t\"sig\":\t\"11e2280cca6f2e0f638fbf60f8aa744a4c228ba19f4d787a51298ec23be4a226e5046477cf6444a804c81aa08dd287e9647f0b45f8a02700da4a387187d4b3dc\"\n"
|
|
"}"
|
|
}
|
|
};
|
|
|
|
// Helper functions
|
|
static void print_test_result(const char* test_name, int passed, const char* expected, const char* actual) {
|
|
if (passed) {
|
|
printf("✓ %s: PASSED\n", test_name);
|
|
} else {
|
|
printf("❌ %s: FAILED\n", test_name);
|
|
printf(" Expected: %s\n", expected);
|
|
printf(" Actual: %s\n", actual);
|
|
}
|
|
}
|
|
|
|
static void bytes_to_hex_lowercase(const unsigned char* bytes, size_t len, char* hex_out) {
|
|
for (size_t i = 0; i < len; i++) {
|
|
sprintf(hex_out + i * 2, "%02x", bytes[i]);
|
|
}
|
|
hex_out[len * 2] = '\0';
|
|
}
|
|
|
|
// Test single vector for mnemonic to keys
|
|
static int test_single_vector_mnemonic_to_keys(const test_vector_t* vector, unsigned char* private_key_out, unsigned char* public_key_out) {
|
|
printf("\n=== Testing %s: Mnemonic to Keys ===\n", vector->name);
|
|
printf("Input mnemonic: %s\n", vector->mnemonic);
|
|
printf("Input account: 0\n");
|
|
|
|
unsigned char private_key[32];
|
|
unsigned char public_key[32];
|
|
|
|
// Derive keys from mnemonic using account 0
|
|
printf("Calling nostr_derive_keys_from_mnemonic()...\n");
|
|
int ret = nostr_derive_keys_from_mnemonic(vector->mnemonic, 0, private_key, public_key);
|
|
printf("Function returned: %d\n", ret);
|
|
|
|
if (ret != NOSTR_SUCCESS) {
|
|
printf("❌ Key derivation failed with code: %d\n", ret);
|
|
printf("Expected: Success (0)\n");
|
|
printf("Actual: Error (%d)\n", ret);
|
|
return 0;
|
|
}
|
|
|
|
// Convert private key to hex (lowercase)
|
|
char nsec_hex[65];
|
|
bytes_to_hex_lowercase(private_key, 32, nsec_hex);
|
|
|
|
// Convert public key to hex (lowercase)
|
|
char npub_hex[65];
|
|
bytes_to_hex_lowercase(public_key, 32, npub_hex);
|
|
|
|
// Test nsecHex
|
|
printf("\nPrivate Key (nsecHex):\n");
|
|
printf("Expected: %s\n", vector->expected_nsec_hex);
|
|
printf("Actual: %s\n", nsec_hex);
|
|
int nsec_hex_match = (strcmp(nsec_hex, vector->expected_nsec_hex) == 0);
|
|
printf("Result: %s\n", nsec_hex_match ? "✓ PASS" : "❌ FAIL");
|
|
|
|
// Test npubHex
|
|
printf("\nPublic Key (npubHex):\n");
|
|
printf("Expected: %s\n", vector->expected_npub_hex);
|
|
printf("Actual: %s\n", npub_hex);
|
|
int npub_hex_match = (strcmp(npub_hex, vector->expected_npub_hex) == 0);
|
|
printf("Result: %s\n", npub_hex_match ? "✓ PASS" : "❌ FAIL");
|
|
|
|
// Copy keys for use in other tests
|
|
if (private_key_out) memcpy(private_key_out, private_key, 32);
|
|
if (public_key_out) memcpy(public_key_out, public_key, 32);
|
|
|
|
return nsec_hex_match && npub_hex_match;
|
|
}
|
|
|
|
// Test single vector for nsec encoding
|
|
static int test_single_vector_nsec_encoding(const test_vector_t* vector, const unsigned char* private_key) {
|
|
printf("\n=== Testing %s: nsec Encoding ===\n", vector->name);
|
|
|
|
// Show input private key in hex
|
|
char private_key_hex[65];
|
|
bytes_to_hex_lowercase(private_key, 32, private_key_hex);
|
|
printf("Input private key (hex): %s\n", private_key_hex);
|
|
|
|
char nsec[100];
|
|
printf("Calling nostr_key_to_bech32() with hrp='nsec'...\n");
|
|
int ret = nostr_key_to_bech32(private_key, "nsec", nsec);
|
|
printf("Function returned: %d\n", ret);
|
|
|
|
if (ret != NOSTR_SUCCESS) {
|
|
printf("❌ nsec encoding failed with code: %d\n", ret);
|
|
printf("Expected: Success (0)\n");
|
|
printf("Actual: Error (%d)\n", ret);
|
|
return 0;
|
|
}
|
|
|
|
printf("\nnsec Encoding:\n");
|
|
printf("Expected: %s\n", vector->expected_nsec);
|
|
printf("Actual: %s\n", nsec);
|
|
int nsec_match = (strcmp(nsec, vector->expected_nsec) == 0);
|
|
printf("Result: %s\n", nsec_match ? "✓ PASS" : "❌ FAIL");
|
|
|
|
return nsec_match;
|
|
}
|
|
|
|
// Test single vector for npub encoding
|
|
static int test_single_vector_npub_encoding(const test_vector_t* vector, const unsigned char* public_key) {
|
|
printf("\n=== Testing %s: npub Encoding ===\n", vector->name);
|
|
|
|
char npub[100];
|
|
int ret = nostr_key_to_bech32(public_key, "npub", npub);
|
|
|
|
if (ret != NOSTR_SUCCESS) {
|
|
printf("❌ npub encoding failed with code: %d\n", ret);
|
|
return 0;
|
|
}
|
|
|
|
int npub_match = (strcmp(npub, vector->expected_npub) == 0);
|
|
print_test_result("npub encoding", npub_match, vector->expected_npub, npub);
|
|
|
|
return npub_match;
|
|
}
|
|
|
|
// Test single vector for event generation
|
|
static int test_single_vector_event_generation(const test_vector_t* vector, const unsigned char* private_key, size_t vector_index) {
|
|
printf("\n=== Testing %s: Signed Event Generation ===\n", vector->name);
|
|
|
|
// Create and sign event with fixed timestamp
|
|
cJSON* event = nostr_create_and_sign_event(1, TEST_CONTENT, NULL, private_key, TEST_CREATED_AT);
|
|
|
|
if (!event) {
|
|
printf("❌ Event creation failed\n");
|
|
return 0;
|
|
}
|
|
|
|
// Extract event fields
|
|
cJSON* id_item = cJSON_GetObjectItem(event, "id");
|
|
cJSON* pubkey_item = cJSON_GetObjectItem(event, "pubkey");
|
|
cJSON* created_at_item = cJSON_GetObjectItem(event, "created_at");
|
|
cJSON* kind_item = cJSON_GetObjectItem(event, "kind");
|
|
cJSON* content_item = cJSON_GetObjectItem(event, "content");
|
|
cJSON* sig_item = cJSON_GetObjectItem(event, "sig");
|
|
cJSON* tags_item = cJSON_GetObjectItem(event, "tags");
|
|
|
|
if (!id_item || !pubkey_item || !created_at_item || !kind_item ||
|
|
!content_item || !sig_item || !tags_item) {
|
|
printf("❌ Event missing required fields\n");
|
|
cJSON_Delete(event);
|
|
return 0;
|
|
}
|
|
|
|
// Validate field types
|
|
if (!cJSON_IsString(id_item) || !cJSON_IsString(pubkey_item) ||
|
|
!cJSON_IsNumber(created_at_item) || !cJSON_IsNumber(kind_item) ||
|
|
!cJSON_IsString(content_item) || !cJSON_IsString(sig_item) ||
|
|
!cJSON_IsArray(tags_item)) {
|
|
printf("❌ Event fields have wrong types\n");
|
|
cJSON_Delete(event);
|
|
return 0;
|
|
}
|
|
|
|
// Extract values
|
|
const char* event_id = cJSON_GetStringValue(id_item);
|
|
const char* pubkey = cJSON_GetStringValue(pubkey_item);
|
|
uint32_t created_at = (uint32_t)cJSON_GetNumberValue(created_at_item);
|
|
int kind = (int)cJSON_GetNumberValue(kind_item);
|
|
const char* content = cJSON_GetStringValue(content_item);
|
|
|
|
// Test each field
|
|
int tests_passed = 0;
|
|
int total_tests = 6;
|
|
|
|
// Test kind
|
|
if (kind == 1) {
|
|
printf("✓ Event kind: PASSED (1)\n");
|
|
tests_passed++;
|
|
} else {
|
|
printf("❌ Event kind: FAILED (expected 1, got %d)\n", kind);
|
|
}
|
|
|
|
// Test pubkey - only check for first vector since we have expected values for that one
|
|
if (strcmp(vector->name, "Vector 1") == 0) {
|
|
int pubkey_match = (strcmp(pubkey, vector->expected_npub_hex) == 0);
|
|
print_test_result("Event pubkey", pubkey_match, vector->expected_npub_hex, pubkey);
|
|
if (pubkey_match) tests_passed++;
|
|
} else {
|
|
// For other vectors, just check that pubkey matches the expected public key
|
|
int pubkey_match = (strcmp(pubkey, vector->expected_npub_hex) == 0);
|
|
print_test_result("Event pubkey", pubkey_match, vector->expected_npub_hex, pubkey);
|
|
if (pubkey_match) tests_passed++;
|
|
}
|
|
|
|
// Test created_at
|
|
if (created_at == TEST_CREATED_AT) {
|
|
printf("✓ Event created_at: PASSED (%u)\n", created_at);
|
|
tests_passed++;
|
|
} else {
|
|
printf("❌ Event created_at: FAILED (expected %u, got %u)\n", TEST_CREATED_AT, created_at);
|
|
}
|
|
|
|
// Test content
|
|
int content_match = (strcmp(content, TEST_CONTENT) == 0);
|
|
print_test_result("Event content", content_match, TEST_CONTENT, content);
|
|
if (content_match) tests_passed++;
|
|
|
|
// Test tags (should be empty array)
|
|
int tags_empty = (cJSON_GetArraySize(tags_item) == 0);
|
|
if (tags_empty) {
|
|
printf("✓ Event tags: PASSED (empty array)\n");
|
|
tests_passed++;
|
|
} else {
|
|
printf("❌ Event tags: FAILED (expected empty array, got %d items)\n",
|
|
cJSON_GetArraySize(tags_item));
|
|
}
|
|
|
|
// Get expected event for this vector
|
|
const expected_event_t* expected = &EXPECTED_EVENTS[vector_index];
|
|
|
|
// Test event ID
|
|
int id_match = (strcmp(event_id, expected->expected_event_id) == 0);
|
|
print_test_result("Event ID", id_match, expected->expected_event_id, event_id);
|
|
if (id_match) tests_passed++;
|
|
|
|
// Print expected vs generated event JSONs side by side
|
|
printf("\n=== EXPECTED EVENT JSON ===\n");
|
|
printf("%s\n", expected->expected_json);
|
|
|
|
printf("\n=== GENERATED EVENT JSON ===\n");
|
|
char* event_json = cJSON_Print(event);
|
|
if (event_json) {
|
|
printf("%s\n", event_json);
|
|
free(event_json);
|
|
}
|
|
|
|
cJSON_Delete(event);
|
|
|
|
printf("\nEvent generation: %d/%d tests passed\n", tests_passed, total_tests);
|
|
return (tests_passed == total_tests);
|
|
}
|
|
|
|
// Test all vectors
|
|
static int test_all_vectors() {
|
|
printf("\n=== Testing All Vectors ===\n");
|
|
|
|
int total_vectors_passed = 0;
|
|
|
|
for (size_t i = 0; i < NUM_TEST_VECTORS; i++) {
|
|
const test_vector_t* vector = &TEST_VECTORS[i];
|
|
printf("\n" "==========================================\n");
|
|
printf("Testing %s\n", vector->name);
|
|
printf("==========================================\n");
|
|
|
|
unsigned char private_key[32];
|
|
unsigned char public_key[32];
|
|
|
|
// Step 1: Test mnemonic to keys
|
|
int keys_passed = test_single_vector_mnemonic_to_keys(vector, private_key, public_key);
|
|
|
|
// Step 2: Test nsec encoding
|
|
int nsec_passed = test_single_vector_nsec_encoding(vector, private_key);
|
|
|
|
// Step 3: Test npub encoding
|
|
int npub_passed = test_single_vector_npub_encoding(vector, public_key);
|
|
|
|
// Step 4: Test event generation (only if keys work)
|
|
int event_passed = 0;
|
|
if (keys_passed) {
|
|
event_passed = test_single_vector_event_generation(vector, private_key, i);
|
|
}
|
|
|
|
// Summary for this vector
|
|
printf("\n%s Summary:\n", vector->name);
|
|
printf(" Keys: %s\n", keys_passed ? "✓ PASS" : "❌ FAIL");
|
|
printf(" nsec: %s\n", nsec_passed ? "✓ PASS" : "❌ FAIL");
|
|
printf(" npub: %s\n", npub_passed ? "✓ PASS" : "❌ FAIL");
|
|
printf(" Event: %s\n", event_passed ? "✓ PASS" : "❌ FAIL");
|
|
|
|
if (keys_passed && nsec_passed && npub_passed && event_passed) {
|
|
printf(" Overall: ✓ PASS\n");
|
|
total_vectors_passed++;
|
|
} else {
|
|
printf(" Overall: ❌ FAIL\n");
|
|
}
|
|
}
|
|
|
|
printf("\n" "==========================================\n");
|
|
printf("FINAL RESULTS: %d/%zu vectors passed\n", total_vectors_passed, NUM_TEST_VECTORS);
|
|
printf("==========================================\n");
|
|
|
|
return (total_vectors_passed == (int)NUM_TEST_VECTORS);
|
|
}
|
|
|
|
int main() {
|
|
printf("NOSTR Event Generation Test Suite\n");
|
|
printf("=================================\n");
|
|
printf("Testing against multiple test vectors for ecosystem compatibility\n");
|
|
|
|
// Initialize NOSTR library
|
|
if (nostr_init() != NOSTR_SUCCESS) {
|
|
printf("❌ Failed to initialize NOSTR library\n");
|
|
return 1;
|
|
}
|
|
|
|
// Run all vector tests
|
|
int success = test_all_vectors();
|
|
|
|
// Print final results
|
|
printf("\n=================================\n");
|
|
if (success) {
|
|
printf("🎉 ALL TEST VECTORS PASSED!\n");
|
|
printf("✅ Your NOSTR implementation produces the exact same results as all test vectors\n");
|
|
printf("✅ This confirms compatibility with other NOSTR tools\n");
|
|
} else {
|
|
printf("❌ SOME TEST VECTORS FAILED\n");
|
|
printf("❌ Your implementation produces different results than expected\n");
|
|
printf("❌ This indicates compatibility issues with other NOSTR tools\n");
|
|
|
|
printf("\nFor debugging purposes, review the detailed output above to see:\n");
|
|
printf(" - Which vectors passed/failed\n");
|
|
printf(" - Expected vs actual values for each test\n");
|
|
printf(" - Whether the issue is in key derivation, encoding, or event generation\n");
|
|
}
|
|
|
|
// Cleanup
|
|
nostr_cleanup();
|
|
|
|
return success ? 0 : 1;
|
|
}
|