242 lines
7.5 KiB
C
242 lines
7.5 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 <recipient_pubkey> <message> [sender_nsec]
|
|
*
|
|
* Arguments:
|
|
* recipient_pubkey: The npub or hex public key of the recipient
|
|
* message: The message to send
|
|
* sender_nsec: (optional) The nsec private key to use for sending.
|
|
* If not provided, uses a default test key.
|
|
*
|
|
* Example:
|
|
* ./send_nip17_dm npub1example... "Hello from NIP-17!" nsec1test...
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "../nostr_core/nostr_core.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.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 to hex if needed
|
|
*/
|
|
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[]) {
|
|
if (argc < 3 || argc > 4) {
|
|
fprintf(stderr, "Usage: %s <recipient_pubkey> <message> [sender_nsec]\n\n", argv[0]);
|
|
fprintf(stderr, "Arguments:\n");
|
|
fprintf(stderr, " recipient_pubkey: npub or hex public key of recipient\n");
|
|
fprintf(stderr, " message: The message to send\n");
|
|
fprintf(stderr, " sender_nsec: (optional) nsec private key. Uses test key if not provided.\n\n");
|
|
fprintf(stderr, "Example:\n");
|
|
fprintf(stderr, " %s npub1example... \"Hello!\" nsec1test...\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
const char* recipient_pubkey_input = argv[1];
|
|
const char* message = argv[2];
|
|
const char* sender_nsec_input = (argc >= 4) ? argv[3] : DEFAULT_SENDER_NSEC;
|
|
|
|
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");
|
|
return 1;
|
|
}
|
|
|
|
// Convert recipient pubkey
|
|
char recipient_pubkey_hex[65];
|
|
if (convert_pubkey_to_hex(recipient_pubkey_input, recipient_pubkey_hex) != 0) {
|
|
return 1;
|
|
}
|
|
|
|
// Convert sender private key
|
|
unsigned char sender_privkey[32];
|
|
if (convert_nsec_to_private_key(sender_nsec_input, sender_privkey) != 0) {
|
|
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("🌐 Relay: %s\n\n", DEFAULT_RELAY);
|
|
|
|
// 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
|
|
DEFAULT_RELAY, // relay hint
|
|
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 relay
|
|
printf("\n📤 Publishing gift wrap to relay...\n");
|
|
|
|
const char* relay_urls[] = {DEFAULT_RELAY};
|
|
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, "\n❌ 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("\n✅ Successfully published NIP-17 DM!\n");
|
|
|
|
// Clean up
|
|
free(publish_results);
|
|
for (int i = 0; i < gift_wrap_count; i++) {
|
|
cJSON_Delete(gift_wraps[i]);
|
|
}
|
|
|
|
nostr_cleanup();
|
|
|
|
printf("\n🎉 DM sent successfully! The recipient can now decrypt it using their private key.\n");
|
|
|
|
return 0;
|
|
} |