/* * NIP-42 Authentication of Clients to Relays Test Suite * Tests auth challenge generation, event creation, validation, and message parsing * Following TESTS POLICY: Shows expected vs actual values, prints entire JSON events */ #define _GNU_SOURCE // For strdup on Linux #include #include #include #include #include #include "../nostr_core/nip042.h" #include "../nostr_core/nip001.h" #include "../nostr_core/nostr_common.h" #include "../nostr_core/utils.h" #include "../cjson/cJSON.h" // Ensure strdup is declared #ifndef strdup extern char *strdup(const char *s); #endif // Test counter for tracking progress static int test_count = 0; static int passed_tests = 0; void print_test_header(const char* test_name) { test_count++; printf("\n=== TEST %d: %s ===\n", test_count, test_name); } void print_test_result(int passed, const char* test_name) { if (passed) { passed_tests++; printf("✅ PASS: %s\n", test_name); } else { printf("❌ FAIL: %s\n", test_name); } } void print_json_comparison(const char* label, cJSON* expected, cJSON* actual) { char* expected_str = cJSON_Print(expected); char* actual_str; if (actual) { actual_str = cJSON_Print(actual); } else { actual_str = strdup("NULL"); } printf("%s Expected JSON:\n%s\n", label, expected_str ? expected_str : "NULL"); printf("%s Actual JSON:\n%s\n", label, actual_str ? actual_str : "NULL"); if (expected_str) free(expected_str); if (actual_str) free(actual_str); } // Test 1: Challenge generation int test_challenge_generation(void) { print_test_header("Challenge Generation"); nostr_auth_challenge_t challenge1, challenge2; printf("Generating first challenge...\n"); int result1 = nostr_nip42_generate_challenge(challenge1.challenge, NOSTR_NIP42_DEFAULT_CHALLENGE_LENGTH); printf("Result: %d (%s)\n", result1, nostr_strerror(result1)); if (result1 != NOSTR_SUCCESS) { printf("❌ Failed to generate first challenge\n"); return 0; } printf("First challenge: %s\n", challenge1.challenge); printf("Challenge length: %lu\n", strlen(challenge1.challenge)); printf("Expected length: %d\n", NOSTR_NIP42_DEFAULT_CHALLENGE_LENGTH * 2); if (strlen(challenge1.challenge) != NOSTR_NIP42_DEFAULT_CHALLENGE_LENGTH * 2) { printf("❌ Challenge length incorrect\n"); return 0; } printf("Generating second challenge...\n"); int result2 = nostr_nip42_generate_challenge(challenge2.challenge, NOSTR_NIP42_DEFAULT_CHALLENGE_LENGTH); printf("Result: %d (%s)\n", result2, nostr_strerror(result2)); if (result2 != NOSTR_SUCCESS) { printf("❌ Failed to generate second challenge\n"); return 0; } printf("Second challenge: %s\n", challenge2.challenge); // Challenges should be different (extremely high probability) if (strcmp(challenge1.challenge, challenge2.challenge) == 0) { printf("❌ Two challenges are identical (highly unlikely)\n"); return 0; } printf("✅ Challenges are different (good entropy)\n"); return 1; } // Test 2: AUTH message creation (server-side challenge) int test_auth_message_creation(void) { print_test_header("AUTH Message Creation - Server Challenge"); nostr_auth_challenge_t challenge; int gen_result = nostr_nip42_generate_challenge(challenge.challenge, NOSTR_NIP42_DEFAULT_CHALLENGE_LENGTH); if (gen_result != NOSTR_SUCCESS) { printf("❌ Failed to generate challenge for message test\n"); return 0; } printf("Generated challenge: %s\n", challenge.challenge); // Create AUTH challenge message: ["AUTH", "challenge_string"] cJSON* message_array = cJSON_CreateArray(); if (!message_array) { printf("❌ Failed to create message array\n"); return 0; } cJSON_AddItemToArray(message_array, cJSON_CreateString("AUTH")); cJSON_AddItemToArray(message_array, cJSON_CreateString(challenge.challenge)); char* auth_message = cJSON_PrintUnformatted(message_array); cJSON_Delete(message_array); if (!auth_message) { printf("❌ Failed to create AUTH message\n"); return 0; } int result = NOSTR_SUCCESS; printf("Message creation result: %d (%s)\n", result, nostr_strerror(result)); printf("AUTH message: %s\n", auth_message); // Parse the message to verify format cJSON* parsed = cJSON_Parse(auth_message); if (!parsed) { printf("❌ AUTH message is not valid JSON\n"); return 0; } // Check if it's an array with 2 elements if (!cJSON_IsArray(parsed) || cJSON_GetArraySize(parsed) != 2) { printf("❌ AUTH message is not a 2-element array\n"); cJSON_Delete(parsed); return 0; } // Check first element is "AUTH" cJSON* first = cJSON_GetArrayItem(parsed, 0); if (!cJSON_IsString(first) || strcmp(cJSON_GetStringValue(first), "AUTH") != 0) { printf("❌ First element is not 'AUTH'\n"); cJSON_Delete(parsed); return 0; } // Check second element is our challenge string cJSON* second = cJSON_GetArrayItem(parsed, 1); if (!cJSON_IsString(second) || strcmp(cJSON_GetStringValue(second), challenge.challenge) != 0) { printf("❌ Second element is not our challenge string\n"); printf("Expected: %s\n", challenge.challenge); printf("Actual: %s\n", cJSON_GetStringValue(second)); cJSON_Delete(parsed); free(auth_message); return 0; } printf("✅ AUTH challenge message format is correct\n"); cJSON_Delete(parsed); free(auth_message); return 1; } // Test 3: Authentication event creation (client-side) int test_auth_event_creation(void) { print_test_header("Authentication Event Creation - Client Side"); const char* private_key_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe"; const char* relay_url = "wss://relay.example.com"; const char* challenge_string = "test_challenge_12345678901234567890123456789012"; unsigned char private_key[32]; nostr_hex_to_bytes(private_key_hex, private_key, 32); printf("Private key (hex): %s\n", private_key_hex); printf("Relay URL: %s\n", relay_url); printf("Challenge: %s\n", challenge_string); cJSON* auth_event = nostr_nip42_create_auth_event(challenge_string, relay_url, private_key, 0); if (!auth_event) { printf("❌ Failed to create authentication event\n"); return 0; } char* event_str = cJSON_Print(auth_event); printf("Created Auth Event JSON:\n%s\n", event_str); free(event_str); // Validate the event structure int structure_result = nostr_validate_event_structure(auth_event); printf("Structure validation result: %d (%s)\n", structure_result, nostr_strerror(structure_result)); if (structure_result != NOSTR_SUCCESS) { printf("❌ Auth event failed structure validation\n"); cJSON_Delete(auth_event); return 0; } // Validate the event signature int crypto_result = nostr_verify_event_signature(auth_event); printf("Signature validation result: %d (%s)\n", crypto_result, nostr_strerror(crypto_result)); if (crypto_result != NOSTR_SUCCESS) { printf("❌ Auth event failed signature validation\n"); cJSON_Delete(auth_event); return 0; } // Check kind is 22242 cJSON* kind = cJSON_GetObjectItem(auth_event, "kind"); if (!cJSON_IsNumber(kind) || cJSON_GetNumberValue(kind) != 22242) { printf("❌ Auth event kind is not 22242\n"); cJSON_Delete(auth_event); return 0; } // Check for relay tag cJSON* tags = cJSON_GetObjectItem(auth_event, "tags"); int found_relay = 0, found_challenge = 0; if (cJSON_IsArray(tags)) { cJSON* tag = NULL; cJSON_ArrayForEach(tag, tags) { if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) { cJSON* tag_name = cJSON_GetArrayItem(tag, 0); cJSON* tag_value = cJSON_GetArrayItem(tag, 1); if (cJSON_IsString(tag_name) && cJSON_IsString(tag_value)) { if (strcmp(cJSON_GetStringValue(tag_name), "relay") == 0) { found_relay = 1; if (strcmp(cJSON_GetStringValue(tag_value), relay_url) != 0) { printf("❌ Relay tag value incorrect\n"); printf("Expected: %s\n", relay_url); printf("Actual: %s\n", cJSON_GetStringValue(tag_value)); cJSON_Delete(auth_event); return 0; } } else if (strcmp(cJSON_GetStringValue(tag_name), "challenge") == 0) { found_challenge = 1; if (strcmp(cJSON_GetStringValue(tag_value), challenge_string) != 0) { printf("❌ Challenge tag value incorrect\n"); printf("Expected: %s\n", challenge_string); printf("Actual: %s\n", cJSON_GetStringValue(tag_value)); cJSON_Delete(auth_event); return 0; } } } } } } if (!found_relay) { printf("❌ Missing relay tag\n"); cJSON_Delete(auth_event); return 0; } if (!found_challenge) { printf("❌ Missing challenge tag\n"); cJSON_Delete(auth_event); return 0; } printf("✅ Authentication event created successfully with correct tags\n"); cJSON_Delete(auth_event); return 1; } // Test 4: Authentication event validation (server-side) int test_auth_event_validation(void) { print_test_header("Authentication Event Validation - Server Side"); // Create a valid auth event first const char* private_key_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe"; const char* relay_url = "wss://relay.example.com"; const char* challenge_string = "validation_challenge_1234567890123456789012"; unsigned char private_key[32]; nostr_hex_to_bytes(private_key_hex, private_key, 32); cJSON* auth_event = nostr_nip42_create_auth_event(challenge_string, relay_url, private_key, 0); if (!auth_event) { printf("❌ Failed to create auth event for validation test\n"); return 0; } char* event_str = cJSON_Print(auth_event); printf("Auth Event to validate:\n%s\n", event_str); free(event_str); // Test successful validation printf("Testing successful validation...\n"); int result = nostr_nip42_verify_auth_event(auth_event, challenge_string, relay_url, 0); printf("Validation result: %d (%s)\n", result, nostr_strerror(result)); if (result != NOSTR_SUCCESS) { printf("❌ Valid auth event failed validation\n"); cJSON_Delete(auth_event); return 0; } printf("✅ Valid auth event passed validation\n"); // Test wrong relay URL printf("\nTesting wrong relay URL...\n"); result = nostr_nip42_verify_auth_event(auth_event, challenge_string, "wss://wrong.relay.com", 0); printf("Expected: NOSTR_ERROR_NIP42_URL_MISMATCH (-206)\n"); printf("Actual: %d (%s)\n", result, nostr_strerror(result)); if (result != NOSTR_ERROR_NIP42_URL_MISMATCH) { printf("❌ Wrong relay validation didn't fail correctly\n"); cJSON_Delete(auth_event); return 0; } printf("✅ Wrong relay URL correctly rejected\n"); // Test wrong challenge printf("\nTesting wrong challenge...\n"); result = nostr_nip42_verify_auth_event(auth_event, "wrong_challenge_string_here", relay_url, 0); printf("Expected: NOSTR_ERROR_NIP42_INVALID_CHALLENGE (-203)\n"); printf("Actual: %d (%s)\n", result, nostr_strerror(result)); if (result != NOSTR_ERROR_NIP42_INVALID_CHALLENGE) { printf("❌ Wrong challenge validation didn't fail correctly\n"); cJSON_Delete(auth_event); return 0; } printf("✅ Wrong challenge correctly rejected\n"); cJSON_Delete(auth_event); return 1; } // Test 5: AUTH message parsing (client-side) int test_auth_message_parsing(void) { print_test_header("AUTH Message Parsing - Client Side"); // Test parsing challenge message const char* challenge_msg = "[\"AUTH\", \"test_challenge_from_server_123456789012\"]"; printf("Parsing AUTH challenge message: %s\n", challenge_msg); char extracted_challenge[NOSTR_NIP42_MAX_CHALLENGE_LENGTH]; int result = nostr_nip42_parse_auth_challenge(challenge_msg, extracted_challenge, sizeof(extracted_challenge)); printf("Parse result: %d (%s)\n", result, nostr_strerror(result)); printf("Expected challenge: test_challenge_from_server_123456789012\n"); printf("Extracted challenge: %s\n", extracted_challenge); if (result != NOSTR_SUCCESS) { printf("❌ Failed to parse valid AUTH message\n"); return 0; } if (strcmp(extracted_challenge, "test_challenge_from_server_123456789012") != 0) { printf("❌ Extracted challenge doesn't match expected\n"); return 0; } printf("✅ AUTH challenge message parsed correctly\n"); // Test invalid message format printf("\nTesting invalid message format...\n"); const char* invalid_msg = "[\"WRONG\", \"challenge\"]"; result = nostr_nip42_parse_auth_challenge(invalid_msg, extracted_challenge, sizeof(extracted_challenge)); printf("Parse result: %d (%s)\n", result, nostr_strerror(result)); printf("Expected: NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT (-205)\n"); if (result != NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT) { printf("❌ Invalid message format should have failed\n"); return 0; } printf("✅ Invalid message format correctly rejected\n"); return 1; } // Test 6: AUTH response message creation (client-side) int test_auth_response_creation(void) { print_test_header("AUTH Response Message Creation - Client Side"); // Create an auth event first const char* private_key_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe"; const char* relay_url = "wss://relay.example.com"; const char* challenge_string = "response_test_challenge_1234567890123456"; unsigned char private_key[32]; nostr_hex_to_bytes(private_key_hex, private_key, 32); cJSON* auth_event = nostr_nip42_create_auth_event(challenge_string, relay_url, private_key, 0); if (!auth_event) { printf("❌ Failed to create auth event for response test\n"); return 0; } char* auth_response = nostr_nip42_create_auth_message(auth_event); int result = auth_response ? NOSTR_SUCCESS : NOSTR_ERROR_MEMORY_FAILED; printf("Response creation result: %d (%s)\n", result, nostr_strerror(result)); printf("AUTH response message: %s\n", auth_response ? auth_response : "NULL"); if (result != NOSTR_SUCCESS) { printf("❌ Failed to create AUTH response message\n"); cJSON_Delete(auth_event); return 0; } // Parse and validate the response format cJSON* parsed = cJSON_Parse(auth_response); if (!parsed) { printf("❌ AUTH response is not valid JSON\n"); cJSON_Delete(auth_event); return 0; } // Should be ["AUTH", ] if (!cJSON_IsArray(parsed) || cJSON_GetArraySize(parsed) != 2) { printf("❌ AUTH response is not a 2-element array\n"); cJSON_Delete(parsed); cJSON_Delete(auth_event); return 0; } cJSON* first = cJSON_GetArrayItem(parsed, 0); if (!cJSON_IsString(first) || strcmp(cJSON_GetStringValue(first), "AUTH") != 0) { printf("❌ First element is not 'AUTH'\n"); cJSON_Delete(parsed); cJSON_Delete(auth_event); return 0; } cJSON* second = cJSON_GetArrayItem(parsed, 1); if (!cJSON_IsObject(second)) { printf("❌ Second element is not an object (event)\n"); cJSON_Delete(parsed); cJSON_Delete(auth_event); return 0; } // Check if the event in the response matches our created event cJSON* response_kind = cJSON_GetObjectItem(second, "kind"); if (!cJSON_IsNumber(response_kind) || cJSON_GetNumberValue(response_kind) != 22242) { printf("❌ Response event kind is not 22242\n"); cJSON_Delete(parsed); cJSON_Delete(auth_event); return 0; } printf("✅ AUTH response message format is correct\n"); cJSON_Delete(parsed); cJSON_Delete(auth_event); free(auth_response); return 1; } // Test 7: Error conditions and edge cases int test_error_conditions(void) { print_test_header("Error Conditions and Edge Cases"); int all_passed = 1; // Test 1: NULL parameters printf("\nSubtest 1: NULL parameters\n"); int result = nostr_nip42_generate_challenge(NULL, 32); printf("Expected: NOSTR_ERROR_INVALID_INPUT (-1)\n"); printf("Actual: %d (%s)\n", result, nostr_strerror(result)); if (result != NOSTR_ERROR_INVALID_INPUT) all_passed = 0; // Test 2: Invalid challenge length printf("\nSubtest 2: Invalid challenge in validation\n"); const char* private_key_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe"; const char* relay_url = "wss://relay.example.com"; const char* valid_challenge = "valid_challenge_1234567890123456789012345"; unsigned char private_key[32]; nostr_hex_to_bytes(private_key_hex, private_key, 32); cJSON* auth_event = nostr_nip42_create_auth_event(valid_challenge, relay_url, private_key, 0); if (auth_event) { result = nostr_nip42_verify_auth_event(auth_event, "short", relay_url, 0); // Too short printf("Expected: NOSTR_ERROR_NIP42_INVALID_CHALLENGE (-203)\n"); printf("Actual: %d (%s)\n", result, nostr_strerror(result)); if (result != NOSTR_ERROR_NIP42_INVALID_CHALLENGE) all_passed = 0; cJSON_Delete(auth_event); } else { printf("❌ Failed to create auth event for validation test\n"); all_passed = 0; } // Test 3: Invalid JSON parsing printf("\nSubtest 3: Invalid JSON in message parsing\n"); char challenge_buffer[NOSTR_NIP42_MAX_CHALLENGE_LENGTH]; result = nostr_nip42_parse_auth_challenge("invalid json", challenge_buffer, sizeof(challenge_buffer)); printf("Expected: NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT (-205)\n"); printf("Actual: %d (%s)\n", result, nostr_strerror(result)); if (result != NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT) all_passed = 0; // Test 4: Wrong array size printf("\nSubtest 4: Wrong array size in message parsing\n"); result = nostr_nip42_parse_auth_challenge("[\"AUTH\"]", challenge_buffer, sizeof(challenge_buffer)); // Only 1 element printf("Expected: NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT (-205)\n"); printf("Actual: %d (%s)\n", result, nostr_strerror(result)); if (result != NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT) all_passed = 0; return all_passed; } // Test 8: Full authentication flow simulation int test_full_auth_flow(void) { print_test_header("Full Authentication Flow Simulation"); const char* private_key_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe"; const char* relay_url = "wss://test-relay.nostr.com"; unsigned char private_key[32]; nostr_hex_to_bytes(private_key_hex, private_key, 32); printf("=== STEP 1: Server generates challenge ===\n"); nostr_auth_challenge_t challenge; int result = nostr_nip42_generate_challenge(challenge.challenge, NOSTR_NIP42_DEFAULT_CHALLENGE_LENGTH); if (result != NOSTR_SUCCESS) { printf("❌ Failed to generate challenge\n"); return 0; } printf("Generated challenge: %s\n", challenge.challenge); printf("\n=== STEP 2: Server sends AUTH message ===\n"); cJSON* message_array = cJSON_CreateArray(); cJSON_AddItemToArray(message_array, cJSON_CreateString("AUTH")); cJSON_AddItemToArray(message_array, cJSON_CreateString(challenge.challenge)); char* auth_message = cJSON_PrintUnformatted(message_array); cJSON_Delete(message_array); if (!auth_message) { printf("❌ Failed to create AUTH message\n"); return 0; } printf("Server sends: %s\n", auth_message); printf("\n=== STEP 3: Client parses AUTH message ===\n"); char parsed_challenge[NOSTR_NIP42_MAX_CHALLENGE_LENGTH]; result = nostr_nip42_parse_auth_challenge(auth_message, parsed_challenge, sizeof(parsed_challenge)); if (result != NOSTR_SUCCESS) { printf("❌ Failed to parse AUTH message\n"); free(auth_message); return 0; } printf("Client extracted challenge: %s\n", parsed_challenge); if (strcmp(challenge.challenge, parsed_challenge) != 0) { printf("❌ Parsed challenge doesn't match original\n"); free(auth_message); return 0; } printf("\n=== STEP 4: Client creates auth event ===\n"); cJSON* auth_event = nostr_nip42_create_auth_event(parsed_challenge, relay_url, private_key, 0); if (!auth_event) { printf("❌ Failed to create auth event\n"); return 0; } char* event_str = cJSON_Print(auth_event); printf("Client created auth event:\n%s\n", event_str); free(event_str); printf("\n=== STEP 5: Client sends AUTH response ===\n"); char* auth_response = nostr_nip42_create_auth_message(auth_event); if (!auth_response) { printf("❌ Failed to create AUTH response\n"); cJSON_Delete(auth_event); free(auth_message); return 0; } printf("Client sends: %s\n", auth_response); printf("\n=== STEP 6: Server validates auth event ===\n"); result = nostr_nip42_verify_auth_event(auth_event, challenge.challenge, relay_url, 0); printf("Server validation result: %d (%s)\n", result, nostr_strerror(result)); if (result != NOSTR_SUCCESS) { printf("❌ Server validation failed\n"); cJSON_Delete(auth_event); free(auth_message); free(auth_response); return 0; } printf("✅ FULL AUTHENTICATION FLOW COMPLETED SUCCESSFULLY!\n"); printf("✅ Challenge generated -> Message sent -> Challenge parsed -> Event created -> Response sent -> Event validated\n"); cJSON_Delete(auth_event); free(auth_message); free(auth_response); return 1; } int main(void) { printf("=== NIP-42 Authentication of Clients to Relays Test Suite ===\n"); printf("Following TESTS POLICY: Shows expected vs actual values, prints entire JSON events\n"); printf("Tests both client-side and server-side authentication functionality\n"); // Initialize crypto library if (nostr_init() != NOSTR_SUCCESS) { printf("❌ Failed to initialize nostr library\n"); return 1; } int all_passed = 1; int test_result; // Test 1: Challenge generation test_result = test_challenge_generation(); print_test_result(test_result, "Challenge Generation"); if (!test_result) all_passed = 0; // Test 2: AUTH message creation test_result = test_auth_message_creation(); print_test_result(test_result, "AUTH Message Creation - Server Challenge"); if (!test_result) all_passed = 0; // Test 3: Auth event creation test_result = test_auth_event_creation(); print_test_result(test_result, "Authentication Event Creation - Client Side"); if (!test_result) all_passed = 0; // Test 4: Auth event validation test_result = test_auth_event_validation(); print_test_result(test_result, "Authentication Event Validation - Server Side"); if (!test_result) all_passed = 0; // Test 5: AUTH message parsing test_result = test_auth_message_parsing(); print_test_result(test_result, "AUTH Message Parsing - Client Side"); if (!test_result) all_passed = 0; // Test 6: AUTH response creation test_result = test_auth_response_creation(); print_test_result(test_result, "AUTH Response Message Creation - Client Side"); if (!test_result) all_passed = 0; // Test 7: Error conditions test_result = test_error_conditions(); print_test_result(test_result, "Error Conditions and Edge Cases"); if (!test_result) all_passed = 0; // Test 8: Full authentication flow test_result = test_full_auth_flow(); print_test_result(test_result, "Full Authentication Flow Simulation"); if (!test_result) all_passed = 0; // Summary printf("\n=== TEST SUMMARY ===\n"); printf("Total tests: %d\n", test_count); printf("Passed: %d\n", passed_tests); printf("Failed: %d\n", test_count - passed_tests); if (all_passed) { printf("🎉 ALL TESTS PASSED! NIP-42 Authentication implementation is working correctly.\n"); printf("✅ Challenge generation works\n"); printf("✅ AUTH message creation/parsing works\n"); printf("✅ Authentication event creation works\n"); printf("✅ Authentication event validation works\n"); printf("✅ Full authentication flow works\n"); printf("✅ Error handling works\n"); } else { printf("❌ SOME TESTS FAILED. Please review the output above.\n"); } nostr_cleanup(); return all_passed ? 0 : 1; }