/* * NOSTR Event Generation Test Suite * Tests complete workflow from mnemonic to signed event using specific test vectors */ #include #include #include #include #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; }