/* * 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:: use remote nsigner over AF_UNIX abstract socket * - serial:: use remote nsigner over USB CDC serial (e.g. /dev/ttyACM0) * - tcp::: use remote nsigner over TCP (auth envelope required by n_signer) * - fds::: 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 #include #include #include #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::\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::\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::\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:, --signer serial:, --signer tcp::, or --signer fds::\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; }