301 lines
9.9 KiB
C
301 lines
9.9 KiB
C
/*
|
|
* note_poster - minimal sign-in + kind-1 post test app
|
|
*
|
|
* Supports signer modes:
|
|
* - local (default): sign in with a local key (nsec or hex private key)
|
|
* - unix:<name>: use remote nsigner over AF_UNIX abstract socket
|
|
* - serial:<path>: use remote nsigner over USB CDC serial (e.g. /dev/ttyACM0)
|
|
* - tcp:<host>:<port>: use remote nsigner over TCP (auth envelope required by n_signer)
|
|
* - fds:<read_fd>:<write_fd>: use remote nsigner over existing framed fd pair (stdio/qrexec helper)
|
|
*
|
|
* Creates and signs a kind-1 note through nostr_signer_t,
|
|
* then publishes to wss://relay.laantungir.net
|
|
*/
|
|
|
|
#include "../nostr_core/nostr_core.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#define DEFAULT_RELAY "wss://relay.laantungir.net"
|
|
|
|
static 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 ? status : "(unknown)");
|
|
if (message) {
|
|
printf(" - %s", message);
|
|
}
|
|
printf(" (%d/%d complete, %d success)\n", completed_relays, total_relays, success_count);
|
|
}
|
|
}
|
|
|
|
static int read_line(const char* prompt, char* out, size_t out_sz) {
|
|
size_t len;
|
|
if (!prompt || !out || out_sz == 0) {
|
|
return -1;
|
|
}
|
|
|
|
printf("%s", prompt);
|
|
if (!fgets(out, (int)out_sz, stdin)) {
|
|
return -1;
|
|
}
|
|
|
|
len = strlen(out);
|
|
if (len > 0 && out[len - 1] == '\n') {
|
|
out[len - 1] = '\0';
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_private_key_input(const char* input, unsigned char out_privkey[32]) {
|
|
if (!input || !out_privkey) {
|
|
return -1;
|
|
}
|
|
|
|
if (strncmp(input, "nsec1", 5) == 0) {
|
|
return nostr_decode_nsec(input, out_privkey);
|
|
}
|
|
|
|
if (strlen(input) == 64) {
|
|
return nostr_hex_to_bytes(input, out_privkey, 32);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
unsigned char private_key[32] = {0};
|
|
char key_input[256] = {0};
|
|
char note_content[2048] = {0};
|
|
nostr_signer_t* signer = NULL;
|
|
char pubkey_hex[65] = {0};
|
|
cJSON* note_event = NULL;
|
|
cJSON* id_item;
|
|
const char* relay_urls[] = {DEFAULT_RELAY};
|
|
int success_count = 0;
|
|
publish_result_t* results = NULL;
|
|
enum {
|
|
SIGNER_MODE_LOCAL = 0,
|
|
SIGNER_MODE_UNIX = 1,
|
|
SIGNER_MODE_SERIAL = 2,
|
|
SIGNER_MODE_TCP = 3,
|
|
SIGNER_MODE_FDS = 4
|
|
} signer_mode = SIGNER_MODE_LOCAL;
|
|
const char* unix_name = NULL;
|
|
const char* serial_path = NULL;
|
|
char tcp_host[256] = {0};
|
|
int tcp_port = 0;
|
|
int fds_read_fd = -1;
|
|
int fds_write_fd = -1;
|
|
int argi = 1;
|
|
|
|
if (argc >= 3 && strcmp(argv[1], "--signer") == 0) {
|
|
if (strncmp(argv[2], "unix:", 5) == 0 && argv[2][5] != '\0') {
|
|
signer_mode = SIGNER_MODE_UNIX;
|
|
unix_name = argv[2] + 5;
|
|
} else if (strncmp(argv[2], "serial:", 7) == 0 && argv[2][7] != '\0') {
|
|
signer_mode = SIGNER_MODE_SERIAL;
|
|
serial_path = argv[2] + 7;
|
|
} else if (strncmp(argv[2], "tcp:", 4) == 0 && argv[2][4] != '\0') {
|
|
const char* spec = argv[2] + 4;
|
|
const char* sep = strrchr(spec, ':');
|
|
char* endptr = NULL;
|
|
long parsed_port;
|
|
size_t host_len;
|
|
|
|
if (sep == NULL || sep == spec || sep[1] == '\0') {
|
|
fprintf(stderr, "Invalid tcp signer format. Use tcp:<host>:<port>\n");
|
|
return 1;
|
|
}
|
|
|
|
host_len = (size_t)(sep - spec);
|
|
if (host_len >= sizeof(tcp_host)) {
|
|
fprintf(stderr, "TCP host too long\n");
|
|
return 1;
|
|
}
|
|
|
|
memcpy(tcp_host, spec, host_len);
|
|
tcp_host[host_len] = '\0';
|
|
|
|
parsed_port = strtol(sep + 1, &endptr, 10);
|
|
if (endptr == NULL || *endptr != '\0' || parsed_port <= 0 || parsed_port > 65535) {
|
|
fprintf(stderr, "Invalid TCP port in --signer tcp:<host>:<port>\n");
|
|
return 1;
|
|
}
|
|
|
|
signer_mode = SIGNER_MODE_TCP;
|
|
tcp_port = (int)parsed_port;
|
|
} else if (strncmp(argv[2], "fds:", 4) == 0 && argv[2][4] != '\0') {
|
|
int parsed = -1;
|
|
parsed = sscanf(argv[2] + 4, "%d:%d", &fds_read_fd, &fds_write_fd);
|
|
if (parsed != 2 || fds_read_fd < 0 || fds_write_fd < 0) {
|
|
fprintf(stderr, "Invalid fds signer format. Use fds:<read_fd>:<write_fd>\n");
|
|
return 1;
|
|
}
|
|
signer_mode = SIGNER_MODE_FDS;
|
|
} else if (strcmp(argv[2], "local") == 0) {
|
|
signer_mode = SIGNER_MODE_LOCAL;
|
|
} else {
|
|
fprintf(stderr,
|
|
"Invalid --signer value. Use --signer local, --signer unix:<name>, --signer serial:<path>, --signer tcp:<host>:<port>, or --signer fds:<read_fd>:<write_fd>\n");
|
|
return 1;
|
|
}
|
|
argi = 3;
|
|
}
|
|
|
|
if (signer_mode == SIGNER_MODE_LOCAL) {
|
|
if (argc > argi) {
|
|
strncpy(key_input, argv[argi], sizeof(key_input) - 1);
|
|
argi++;
|
|
} else if (read_line("Enter nsec or 64-char hex private key: ", key_input, sizeof(key_input)) != 0) {
|
|
fprintf(stderr, "Failed to read key input\n");
|
|
return 1;
|
|
}
|
|
|
|
fprintf(stderr, "WARNING: This is a TEST-KEY-ONLY tool. Do not use a real/production nsec. Keys are read into process memory in plaintext.\n");
|
|
if (parse_private_key_input(key_input, private_key) != 0) {
|
|
fprintf(stderr, "Invalid private key input (must be nsec1... or 64-char hex)\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (argc > argi) {
|
|
strncpy(note_content, argv[argi], sizeof(note_content) - 1);
|
|
} else if (read_line("Enter note content: ", note_content, sizeof(note_content)) != 0) {
|
|
fprintf(stderr, "Failed to read note content\n");
|
|
return 1;
|
|
}
|
|
|
|
if (nostr_init() != NOSTR_SUCCESS) {
|
|
fprintf(stderr, "Failed to initialize nostr library\n");
|
|
return 1;
|
|
}
|
|
|
|
if (signer_mode == SIGNER_MODE_UNIX) {
|
|
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
|
signer = nostr_signer_nsigner_unix(unix_name, NULL, 15000);
|
|
if (!signer) {
|
|
fprintf(stderr, "Failed to initialize unix nsigner backend (%s)\n", unix_name);
|
|
nostr_cleanup();
|
|
return 1;
|
|
}
|
|
#else
|
|
fprintf(stderr, "This build does not include nsigner client support\n");
|
|
nostr_cleanup();
|
|
return 1;
|
|
#endif
|
|
} else if (signer_mode == SIGNER_MODE_SERIAL) {
|
|
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
|
signer = nostr_signer_nsigner_serial(serial_path, NULL, 15000);
|
|
if (!signer) {
|
|
fprintf(stderr, "Failed to initialize serial nsigner backend (%s)\n", serial_path);
|
|
nostr_cleanup();
|
|
return 1;
|
|
}
|
|
#else
|
|
fprintf(stderr, "This build does not include nsigner client support\n");
|
|
nostr_cleanup();
|
|
return 1;
|
|
#endif
|
|
} else if (signer_mode == SIGNER_MODE_TCP) {
|
|
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
|
signer = nostr_signer_nsigner_tcp(tcp_host, tcp_port, NULL, 15000);
|
|
if (!signer) {
|
|
fprintf(stderr, "Failed to initialize tcp nsigner backend (%s:%d)\n", tcp_host, tcp_port);
|
|
nostr_cleanup();
|
|
return 1;
|
|
}
|
|
#else
|
|
fprintf(stderr, "This build does not include nsigner client support\n");
|
|
nostr_cleanup();
|
|
return 1;
|
|
#endif
|
|
} else if (signer_mode == SIGNER_MODE_FDS) {
|
|
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
|
signer = nostr_signer_nsigner_fds(fds_read_fd, fds_write_fd, NULL, 15000);
|
|
if (!signer) {
|
|
fprintf(stderr, "Failed to initialize fds nsigner backend (%d:%d)\n", fds_read_fd, fds_write_fd);
|
|
nostr_cleanup();
|
|
return 1;
|
|
}
|
|
#else
|
|
fprintf(stderr, "This build does not include nsigner client support\n");
|
|
nostr_cleanup();
|
|
return 1;
|
|
#endif
|
|
} else {
|
|
signer = nostr_signer_local(private_key);
|
|
if (!signer) {
|
|
fprintf(stderr, "Failed to initialize local signer\n");
|
|
nostr_cleanup();
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (nostr_signer_get_public_key(signer, pubkey_hex) != NOSTR_SUCCESS) {
|
|
fprintf(stderr, "Failed to get signer pubkey\n");
|
|
nostr_signer_free(signer);
|
|
nostr_cleanup();
|
|
return 1;
|
|
}
|
|
|
|
printf("Signed in as pubkey: %s\n", pubkey_hex);
|
|
|
|
note_event = nostr_create_and_sign_event_with_signer(1, note_content, NULL, signer, time(NULL));
|
|
if (!note_event) {
|
|
fprintf(stderr, "Failed to create/sign note event\n");
|
|
nostr_signer_free(signer);
|
|
nostr_cleanup();
|
|
return 1;
|
|
}
|
|
|
|
id_item = cJSON_GetObjectItem(note_event, "id");
|
|
if (id_item && cJSON_IsString(id_item)) {
|
|
printf("Created event id: %s\n", cJSON_GetStringValue(id_item));
|
|
}
|
|
|
|
printf("Publishing to %s ...\n", DEFAULT_RELAY);
|
|
results = synchronous_publish_event_with_progress(
|
|
relay_urls,
|
|
1,
|
|
note_event,
|
|
&success_count,
|
|
12,
|
|
publish_progress_callback,
|
|
NULL,
|
|
0,
|
|
NULL
|
|
);
|
|
|
|
if (!results || success_count < 1 || results[0] != PUBLISH_SUCCESS) {
|
|
fprintf(stderr, "Publish failed (success_count=%d, result=%d)\n",
|
|
success_count,
|
|
results ? (int)results[0] : -999);
|
|
if (results) {
|
|
free(results);
|
|
}
|
|
cJSON_Delete(note_event);
|
|
nostr_signer_free(signer);
|
|
nostr_cleanup();
|
|
return 1;
|
|
}
|
|
|
|
printf("✅ Published successfully to %s\n", DEFAULT_RELAY);
|
|
|
|
free(results);
|
|
cJSON_Delete(note_event);
|
|
nostr_signer_free(signer);
|
|
nostr_cleanup();
|
|
return 0;
|
|
}
|