/* * NIP-17 Private Direct Messages Test Program * * Tests the complete NIP-17 DM flow using synchronous relay operations: * 1. Generate sender and recipient keypairs * 2. Create a DM from sender to recipient * 3. Publish the gift-wrapped DM to relay * 4. Subscribe to receive the DM back * 5. Decrypt and verify the received DM */ #define _GNU_SOURCE #define _POSIX_C_SOURCE 200809L // Enable debug output to see all relay messages #define NOSTR_DEBUG_ENABLED #include "../nostr_core/nostr_core.h" #include #include #include #include #include // Forward declarations for crypto functions int nostr_secp256k1_get_random_bytes(unsigned char* buf, size_t len); // Test configuration #define RELAY_URL "wss://relay.laantungir.net" #define TEST_TIMEOUT_MS 5000 // Progress callback for publishing void publish_progress_callback(const char* relay_url, const char* status, const char* message, int success_count, int total_relays, int completed_relays, void* user_data) { (void)user_data; if (relay_url) { printf("๐Ÿ“ก PUBLISH [%s]: %s", relay_url, status); if (message) { printf(" - %s", message); } printf(" (%d/%d completed, %d successful)\n", completed_relays, total_relays, success_count); } else { printf("๐Ÿ“ก PUBLISH COMPLETE: %d/%d successful\n", success_count, total_relays); } } // Progress callback for querying/subscribing void query_progress_callback(const char* relay_url, const char* status, const char* event_id, int event_count, int total_relays, int completed_relays, void* user_data) { (void)user_data; if (relay_url) { printf("๐Ÿ” QUERY [%s]: %s", relay_url, status); if (event_id) { printf(" - Event: %.12s...", event_id); } if (event_count > 0) { printf(" (%d events)", event_count); } printf(" (%d/%d completed)\n", completed_relays, total_relays); } else { printf("๐Ÿ” QUERY COMPLETE: %d events found\n", event_count); } } /** * Generate a random keypair for testing */ void generate_test_keypair(unsigned char* private_key, char* pubkey_hex) { // Generate random private key if (nostr_secp256k1_get_random_bytes(private_key, 32) != 1) { fprintf(stderr, "Failed to generate random private key\n"); exit(1); } // Derive public key unsigned char public_key[32]; if (nostr_ec_public_key_from_private_key(private_key, public_key) != 0) { fprintf(stderr, "Failed to derive public key\n"); exit(1); } // Convert to hex nostr_bytes_to_hex(public_key, 32, pubkey_hex); } /** * Main test function */ int main(int argc, char* argv[]) { (void)argc; // Suppress unused parameter warning (void)argv; // Suppress unused parameter warning printf("๐Ÿงช NIP-17 Private Direct Messages Test (Synchronous)\n"); printf("=================================================\n\n"); // Initialize crypto if (nostr_init() != NOSTR_SUCCESS) { fprintf(stderr, "Failed to initialize crypto\n"); return 1; } // Generate keypairs unsigned char sender_privkey[32]; unsigned char recipient_privkey[32]; char sender_pubkey_hex[65]; char recipient_pubkey_hex[65]; printf("๐Ÿ”‘ Generating keypairs...\n"); generate_test_keypair(sender_privkey, sender_pubkey_hex); generate_test_keypair(recipient_privkey, recipient_pubkey_hex); printf("๐Ÿ“ค Sender pubkey: %s\n", sender_pubkey_hex); printf("๐Ÿ“ฅ Recipient pubkey: %s\n", recipient_pubkey_hex); printf("\n"); // Create DM event with timestamp printf("๐Ÿ’ฌ Creating DM event...\n"); time_t now = time(NULL); char test_message[256]; snprintf(test_message, sizeof(test_message), "Hello from NIP-17! This is a private direct message sent at %ld", now); const char* recipient_pubkeys[] = {recipient_pubkey_hex}; cJSON* dm_event = nostr_nip17_create_chat_event( test_message, recipient_pubkeys, 1, "NIP-17 Test", NULL, // no reply RELAY_URL, sender_pubkey_hex ); if (!dm_event) { fprintf(stderr, "Failed to create DM event\n"); return 1; } printf("๐Ÿ“ Created DM event (kind 14)\n"); // Send DM (create gift wraps) printf("๐ŸŽ Creating gift wraps...\n"); cJSON* gift_wraps[10]; // Max 10 gift wraps int gift_wrap_count = nostr_nip17_send_dm( dm_event, recipient_pubkeys, 1, sender_privkey, gift_wraps, 10 ); cJSON_Delete(dm_event); // Original DM event no longer needed if (gift_wrap_count <= 0) { fprintf(stderr, "Failed to create gift wraps\n"); return 1; } printf("โœ… Created %d gift wrap(s)\n", gift_wrap_count); // Print the gift wrap JSON printf("\n๐Ÿ“„ Gift wrap event JSON:\n"); printf("========================\n"); char* gift_wrap_json = cJSON_Print(gift_wraps[0]); printf("%s\n", gift_wrap_json); free(gift_wrap_json); // PHASE 1: Publish the gift wrap to relay printf("\n๐Ÿ“ค PHASE 1: Publishing gift wrap to relay\n"); printf("==========================================\n"); const char* relay_urls[] = {RELAY_URL}; int success_count = 0; publish_result_t* publish_results = synchronous_publish_event_with_progress( relay_urls, 1, // single relay gift_wraps[0], // Send the first gift wrap &success_count, 10, // 10 second timeout publish_progress_callback, NULL, // no user data 0, // NIP-42 disabled NULL // no private key for auth ); if (!publish_results || success_count != 1) { fprintf(stderr, "โŒ Failed to publish gift wrap (success_count: %d)\n", success_count); // Clean up gift wraps for (int i = 0; i < gift_wrap_count; i++) { cJSON_Delete(gift_wraps[i]); } if (publish_results) free(publish_results); return 1; } printf("โœ… Successfully published gift wrap!\n"); // Clean up publish results and gift wraps free(publish_results); for (int i = 0; i < gift_wrap_count; i++) { cJSON_Delete(gift_wraps[i]); } // Small delay to let the relay process the event printf("โณ Waiting 2 seconds for relay to process...\n"); sleep(2); // PHASE 2: Subscribe to receive the DM back printf("\n๐Ÿ“ฅ PHASE 2: Subscribing to receive DM back\n"); printf("===========================================\n"); // Create filter for gift wraps addressed to recipient cJSON* filter = cJSON_CreateObject(); cJSON* kinds = cJSON_CreateArray(); cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1059)); // Gift wrap kind cJSON_AddItemToObject(filter, "kinds", kinds); // Filter for gift wraps with p tag matching recipient cJSON* p_tags = cJSON_CreateArray(); cJSON_AddItemToArray(p_tags, cJSON_CreateString(recipient_pubkey_hex)); cJSON_AddItemToObject(filter, "#p", p_tags); // Print the subscription filter JSON printf("๐Ÿ“„ Subscription filter JSON:\n"); printf("============================\n"); char* filter_json = cJSON_Print(filter); printf("%s\n", filter_json); free(filter_json); int query_result_count = 0; cJSON** query_results = synchronous_query_relays_with_progress( relay_urls, 1, // single relay filter, RELAY_QUERY_ALL_RESULTS, // Get all matching events &query_result_count, 10, // 10 second timeout per relay query_progress_callback, NULL, // no user data 0, // NIP-42 disabled NULL // no private key for auth ); cJSON_Delete(filter); // Clean up filter if (!query_results || query_result_count == 0) { fprintf(stderr, "โŒ No DM events received back from relay\n"); if (query_results) free(query_results); return 1; } printf("โœ… Received %d event(s) from relay!\n", query_result_count); // Process the received events printf("\n๐Ÿ” PHASE 3: Processing received events\n"); printf("=====================================\n"); int dm_found = 0; cJSON* received_dm = NULL; for (int i = 0; i < query_result_count; i++) { cJSON* event = query_results[i]; cJSON* kind_item = cJSON_GetObjectItem(event, "kind"); cJSON* id_item = cJSON_GetObjectItem(event, "id"); if (kind_item && cJSON_IsNumber(kind_item) && cJSON_GetNumberValue(kind_item) == 1059) { printf("๐ŸŽ Found gift wrap event: %.12s...\n", id_item && cJSON_IsString(id_item) ? cJSON_GetStringValue(id_item) : "unknown"); // Try to decrypt this gift wrap cJSON* decrypted_dm = nostr_nip17_receive_dm(event, recipient_privkey); if (decrypted_dm) { printf("โœ… Successfully decrypted gift wrap!\n"); // Verify the decrypted DM cJSON* content_item = cJSON_GetObjectItem(decrypted_dm, "content"); cJSON* dm_kind_item = cJSON_GetObjectItem(decrypted_dm, "kind"); cJSON* pubkey_item = cJSON_GetObjectItem(decrypted_dm, "pubkey"); if (content_item && dm_kind_item && pubkey_item) { const char* decrypted_content = cJSON_GetStringValue(content_item); int decrypted_kind = (int)cJSON_GetNumberValue(dm_kind_item); const char* decrypted_pubkey = cJSON_GetStringValue(pubkey_item); printf("๐Ÿ“ง Decrypted DM:\n"); printf(" Kind: %d\n", decrypted_kind); printf(" From: %s\n", decrypted_pubkey); printf(" Content: %s\n", decrypted_content); // Verify the DM content (check that it contains our message with timestamp) int content_ok = strstr(decrypted_content, "Hello from NIP-17! This is a private direct message sent at ") != NULL; if (decrypted_kind == 14 && content_ok && strlen(decrypted_content) >= 64 && // Should be at least as long as the prefix strcmp(decrypted_pubkey, sender_pubkey_hex) == 0) { printf("โœ… DM verification successful!\n"); dm_found = 1; received_dm = decrypted_dm; // Keep reference for cleanup break; // Found our DM, no need to check more } else { printf("โŒ DM verification failed\n"); cJSON_Delete(decrypted_dm); } } else { printf("โŒ Invalid decrypted DM structure\n"); cJSON_Delete(decrypted_dm); } } else { printf("โŒ Failed to decrypt gift wrap\n"); } } } // Clean up query results for (int i = 0; i < query_result_count; i++) { cJSON_Delete(query_results[i]); } free(query_results); if (!dm_found) { fprintf(stderr, "โŒ Could not find or decrypt the expected DM\n"); return 1; } printf("\n๐ŸŽ‰ NIP-17 synchronous test completed successfully!\n"); // Cleanup if (received_dm) { cJSON_Delete(received_dm); } nostr_cleanup(); return 0; }