nip 17, and 59
This commit is contained in:
341
tests/nip17_test.c
Normal file
341
tests/nip17_test.c
Normal file
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user