/* * NIP-17 Private Direct Messages - Command Line Application * * This example demonstrates how to send NIP-17 private direct messages * using the Nostr Core Library. * * Usage: * ./send_nip17_dm -r -s [-R ]... * * Options: * -r : The recipient's public key (npub or hex) * -s : The sender's private key (nsec or hex) * -R : Relay URL to send to (can be specified multiple times) * : The message to send (must be the last argument) * * If no relays are specified, uses default relay. * If no sender key is provided, uses a default test key. * * Examples: * ./send_nip17_dm -r npub1example... -s nsec1test... -R wss://relay1.com "Hello from NIP-17!" * ./send_nip17_dm -r 4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa -s aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -R ws://localhost:8888 "config" */ #define _GNU_SOURCE #define _POSIX_C_SOURCE 200809L #include "../nostr_core_lib/nostr_core/nostr_core.h" #include #include #include #include #include // Default test private key (for demonstration - DO NOT USE IN PRODUCTION) #define DEFAULT_SENDER_NSEC "nsec12kgt0dv2k2safv6s32w8f89z9uw27e68hjaa0d66c5xvk70ezpwqncd045" // Default relay for sending DMs #define DEFAULT_RELAY "wss://relay.laantungir.net" // 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("๐Ÿ“ก [%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); } } /** * Convert npub or hex pubkey to hex format */ int convert_pubkey_to_hex(const char* input_pubkey, char* output_hex) { // Check if it's already hex (64 characters) if (strlen(input_pubkey) == 64) { // Assume it's already hex strcpy(output_hex, input_pubkey); return 0; } // Check if it's an npub (starts with "npub1") if (strncmp(input_pubkey, "npub1", 5) == 0) { // Convert npub to hex unsigned char pubkey_bytes[32]; if (nostr_decode_npub(input_pubkey, pubkey_bytes) != 0) { fprintf(stderr, "Error: Invalid npub format\n"); return -1; } nostr_bytes_to_hex(pubkey_bytes, 32, output_hex); return 0; } fprintf(stderr, "Error: Public key must be 64-character hex or valid npub\n"); return -1; } /** * Convert nsec to private key bytes if needed */ int convert_nsec_to_private_key(const char* input_nsec, unsigned char* private_key) { // Check if it's already hex (64 characters) if (strlen(input_nsec) == 64) { // Convert hex to bytes if (nostr_hex_to_bytes(input_nsec, private_key, 32) != 0) { fprintf(stderr, "Error: Invalid hex private key\n"); return -1; } return 0; } // Check if it's an nsec (starts with "nsec1") if (strncmp(input_nsec, "nsec1", 5) == 0) { // Convert nsec directly to private key bytes if (nostr_decode_nsec(input_nsec, private_key) != 0) { fprintf(stderr, "Error: Invalid nsec format\n"); return -1; } return 0; } fprintf(stderr, "Error: Private key must be 64-character hex or valid nsec\n"); return -1; } /** * Main function */ int main(int argc, char* argv[]) { char* recipient_key = NULL; char* sender_key = NULL; char** relays = NULL; int relay_count = 0; char* message = NULL; // Parse command line options int opt; while ((opt = getopt(argc, argv, "r:s:R:")) != -1) { switch (opt) { case 'r': recipient_key = optarg; break; case 's': sender_key = optarg; break; case 'R': relays = realloc(relays, (relay_count + 1) * sizeof(char*)); relays[relay_count] = optarg; relay_count++; break; default: fprintf(stderr, "Usage: %s -r -s [-R ]... \n", argv[0]); fprintf(stderr, "Options:\n"); fprintf(stderr, " -r : The recipient's public key (npub or hex)\n"); fprintf(stderr, " -s : The sender's private key (nsec or hex)\n"); fprintf(stderr, " -R : Relay URL to send to (can be specified multiple times)\n"); fprintf(stderr, " : The message to send (must be the last argument)\n"); return 1; } } // Check for required arguments if (!recipient_key) { fprintf(stderr, "Error: Recipient key (-r) is required\n"); return 1; } // Get message from remaining arguments if (optind >= argc) { fprintf(stderr, "Error: Message is required\n"); return 1; } message = argv[optind]; // Use default values if not provided if (!sender_key) { sender_key = DEFAULT_SENDER_NSEC; } if (relay_count == 0) { relays = malloc(sizeof(char*)); relays[0] = DEFAULT_RELAY; relay_count = 1; } printf("๐Ÿงช NIP-17 Private Direct Message Sender\n"); printf("======================================\n\n"); // Initialize crypto if (nostr_init() != NOSTR_SUCCESS) { fprintf(stderr, "Failed to initialize crypto\n"); free(relays); return 1; } // Convert recipient pubkey char recipient_pubkey_hex[65]; if (convert_pubkey_to_hex(recipient_key, recipient_pubkey_hex) != 0) { free(relays); return 1; } // Convert sender private key unsigned char sender_privkey[32]; if (convert_nsec_to_private_key(sender_key, sender_privkey) != 0) { free(relays); return 1; } // Derive sender public key for display unsigned char sender_pubkey_bytes[32]; char sender_pubkey_hex[65]; if (nostr_ec_public_key_from_private_key(sender_privkey, sender_pubkey_bytes) != 0) { fprintf(stderr, "Failed to derive sender public key\n"); return 1; } nostr_bytes_to_hex(sender_pubkey_bytes, 32, sender_pubkey_hex); printf("๐Ÿ“ค Sender: %s\n", sender_pubkey_hex); printf("๐Ÿ“ฅ Recipient: %s\n", recipient_pubkey_hex); printf("๐Ÿ’ฌ Message: %s\n", message); printf("๐ŸŒ Relays: "); for (int i = 0; i < relay_count; i++) { printf("%s", relays[i]); if (i < relay_count - 1) printf(", "); } printf("\n\n"); // Create DM event printf("๐Ÿ’ฌ Creating DM event...\n"); const char* recipient_pubkeys[] = {recipient_pubkey_hex}; cJSON* dm_event = nostr_nip17_create_chat_event( message, recipient_pubkeys, 1, "NIP-17 CLI", // subject NULL, // no reply relays[0], // relay hint (use first relay) 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); // Publish the gift wrap to relays printf("\n๐Ÿ“ค Publishing gift wrap to %d relay(s)...\n", relay_count); int success_count = 0; publish_result_t* publish_results = synchronous_publish_event_with_progress( (const char**)relays, relay_count, 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 == 0) { fprintf(stderr, "\nโŒ Failed to publish gift wrap to any relay (success_count: %d/%d)\n", success_count, relay_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); free(relays); return 1; } printf("\nโœ… Successfully published NIP-17 DM to %d/%d relay(s)!\n", success_count, relay_count); // Clean up free(publish_results); for (int i = 0; i < gift_wrap_count; i++) { cJSON_Delete(gift_wraps[i]); } free(relays); nostr_cleanup(); printf("\n๐ŸŽ‰ DM sent successfully! The recipient can now decrypt it using their private key.\n"); return 0; }