296 lines
9.2 KiB
C
296 lines
9.2 KiB
C
/*
|
|
* 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 <recipient> -s <sender> [-R <relay>]... <message>
|
|
*
|
|
* Options:
|
|
* -r <recipient>: The recipient's public key (npub or hex)
|
|
* -s <sender>: The sender's private key (nsec or hex)
|
|
* -R <relay>: Relay URL to send to (can be specified multiple times)
|
|
* <message>: 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <getopt.h>
|
|
|
|
// 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 <recipient> -s <sender> [-R <relay>]... <message>\n", argv[0]);
|
|
fprintf(stderr, "Options:\n");
|
|
fprintf(stderr, " -r <recipient>: The recipient's public key (npub or hex)\n");
|
|
fprintf(stderr, " -s <sender>: The sender's private key (nsec or hex)\n");
|
|
fprintf(stderr, " -R <relay>: Relay URL to send to (can be specified multiple times)\n");
|
|
fprintf(stderr, " <message>: 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;
|
|
} |