Compare commits

...
6 Commits
15 changed files with 1839 additions and 121 deletions
+4 -8
View File
@@ -152,14 +152,10 @@ chmod +x "$BUILD_DIR/$OUTPUT_NAME"
print_status "Verifying binary with ldd/file"
LDD_OK=false
if LDD_OUTPUT=$(ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1); then
echo "$LDD_OUTPUT" >&2
if echo "$LDD_OUTPUT" | grep -q "not a dynamic executable\|statically linked"; then
LDD_OK=true
fi
else
print_warning "ldd check failed (this can happen for foreign-arch binaries)"
echo "$LDD_OUTPUT" >&2
LDD_OUTPUT=$(ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1 || true)
echo "$LDD_OUTPUT" >&2
if echo "$LDD_OUTPUT" | grep -q "not a dynamic executable\|statically linked"; then
LDD_OK=true
fi
FILE_OUTPUT=$(file "$BUILD_DIR/$OUTPUT_NAME" 2>&1 || true)
+26
View File
@@ -0,0 +1,26 @@
#ifndef NSIGNER_AUTH_H
#define NSIGNER_AUTH_H
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include <time.h>
typedef struct {
int enabled;
unsigned char privkey[32];
char label[64];
} nt_nsigner_auth_ctx_t;
/*
* Build a TCP auth envelope event (kind 27235) for an n_signer JSON-RPC request.
* Caller owns *out_auth and must cJSON_Delete() it.
* Returns 0 on success.
*/
int nt_nsigner_auth_build(const char *request_id,
const char *method,
const cJSON *params,
const nt_nsigner_auth_ctx_t *auth_ctx,
time_t created_at,
cJSON **out_auth);
#endif /* NSIGNER_AUTH_H */
+146 -6
View File
@@ -2,6 +2,22 @@
#define NSIGNER_CLIENT_H
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include "nsigner_auth.h"
/* Selector for key derivation/role in n_signer requests. */
typedef struct {
int has_nostr_index;
int nostr_index;
char role[64];
} nt_nsigner_selector_t;
static inline nt_nsigner_selector_t nsigner_selector_default(void) {
nt_nsigner_selector_t s;
s.has_nostr_index = 1;
s.nostr_index = 0;
s.role[0] = '\0';
return s;
}
/* Error codes */
#define NT_NSIGNER_OK 0
@@ -10,6 +26,7 @@
#define NT_NSIGNER_E_UNAUTH -3
#define NT_NSIGNER_E_PARSE -4
#define NT_NSIGNER_E_ERROR -5
#define NT_NSIGNER_E_AUTH -6
/*
* Enumerate running nsigner instances by reading /proc/net/unix.
@@ -39,21 +56,144 @@ int nsigner_rpc(int fd, const cJSON *request, cJSON **response_out, int read_tim
* socket_name: without '@' prefix.
* role: the role selector string (e.g. "main").
*/
int nsigner_get_public_key(const char *socket_name, const char *role, char *out_hex_65);
int nsigner_get_public_key(const char *socket_name, const nt_nsigner_selector_t *selector, char *out_hex_65);
int nsigner_sign_event(const char *socket_name, const char *role,
int nsigner_sign_event(const char *socket_name, const nt_nsigner_selector_t *selector,
const char *unsigned_event_json, char **signed_event_json_out);
int nsigner_nip04_encrypt(const char *socket_name, const char *role,
int nsigner_nip04_encrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
int nsigner_nip04_decrypt(const char *socket_name, const char *role,
int nsigner_nip04_decrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
int nsigner_nip44_encrypt(const char *socket_name, const char *role,
int nsigner_nip44_encrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
int nsigner_nip44_decrypt(const char *socket_name, const char *role,
int nsigner_nip44_decrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
/*
* URL helpers for FIPS/TCP transport.
* URL format: http://host:port (path/query ignored if present).
*/
int nsigner_get_public_key_url(const char *url, const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth, char *out_hex_65);
int nsigner_sign_event_url(const char *url, const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth,
const char *unsigned_event_json, char **signed_event_json_out);
int nsigner_nip04_encrypt_url(const char *url, const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth,
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
int nsigner_nip04_decrypt_url(const char *url, const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth,
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
int nsigner_nip44_encrypt_url(const char *url, const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth,
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
int nsigner_nip44_decrypt_url(const char *url, const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth,
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
/*
* Persistent URL session helpers.
* Open once and reuse the fd for all RPC calls to keep a stable TCP source port.
*/
int nsigner_session_open_url(const char *url);
void nsigner_session_close(int fd);
int nsigner_session_get_public_key(int *fd_inout,
const char *url,
const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth,
char *out_hex_65);
int nsigner_session_sign_event(int *fd_inout,
const char *url,
const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth,
const char *unsigned_event_json,
char **signed_event_json_out);
int nsigner_session_nip04_encrypt(int *fd_inout,
const char *url,
const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth,
const char *peer_pub_hex,
const char *plaintext,
char **cipher_out);
int nsigner_session_nip04_decrypt(int *fd_inout,
const char *url,
const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth,
const char *peer_pub_hex,
const char *ciphertext,
char **plain_out);
int nsigner_session_nip44_encrypt(int *fd_inout,
const char *url,
const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth,
const char *peer_pub_hex,
const char *plaintext,
char **cipher_out);
int nsigner_session_nip44_decrypt(int *fd_inout,
const char *url,
const nt_nsigner_selector_t *selector,
const nt_nsigner_auth_ctx_t *auth,
const char *peer_pub_hex,
const char *ciphertext,
char **plain_out);
/*
* qrexec helpers for cross-qube n_signer transport.
* target_qube: destination signer qube (e.g. "nsigner-vault").
* service_name: qrexec service name (e.g. "qubes.NsignerRpc").
*/
int nsigner_qrexec_get_public_key(const char *target_qube,
const char *service_name,
const nt_nsigner_selector_t *selector,
char *out_hex_65);
int nsigner_qrexec_sign_event(const char *target_qube,
const char *service_name,
const nt_nsigner_selector_t *selector,
const char *unsigned_event_json,
char **signed_event_json_out);
int nsigner_qrexec_nip04_encrypt(const char *target_qube,
const char *service_name,
const nt_nsigner_selector_t *selector,
const char *peer_pub_hex,
const char *plaintext,
char **cipher_out);
int nsigner_qrexec_nip04_decrypt(const char *target_qube,
const char *service_name,
const nt_nsigner_selector_t *selector,
const char *peer_pub_hex,
const char *ciphertext,
char **plain_out);
int nsigner_qrexec_nip44_encrypt(const char *target_qube,
const char *service_name,
const nt_nsigner_selector_t *selector,
const char *peer_pub_hex,
const char *plaintext,
char **cipher_out);
int nsigner_qrexec_nip44_decrypt(const char *target_qube,
const char *service_name,
const nt_nsigner_selector_t *selector,
const char *peer_pub_hex,
const char *ciphertext,
char **plain_out);
#endif /* NSIGNER_CLIENT_H */
+23 -3
View File
@@ -2,25 +2,45 @@
#define SIGNER_H
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include "nsigner_client.h"
#include <time.h>
#include <stdint.h>
typedef enum {
NT_SIGNER_LOCAL = 0,
NT_SIGNER_NSIGNER = 1
NT_SIGNER_NSIGNER = 1,
NT_SIGNER_NSIGNER_QREXEC = 2,
NT_SIGNER_NSIGNER_URL = 3
} nt_signer_kind_t;
typedef struct {
nt_signer_kind_t kind;
char socket_name[128];
char role[64];
char qrexec_target[128];
char qrexec_service[128];
char endpoint_url[256];
int url_session_fd;
nt_nsigner_selector_t selector;
nt_nsigner_auth_ctx_t url_auth;
} nt_signer_t;
extern nt_signer_t g_signer;
void signer_init(void);
void signer_shutdown(void);
int signer_init_local(void);
int signer_init_nsigner(const char *socket_name);
void signer_set_selector(int has_index, int index, const char *role);
int signer_init_nsigner(const char *socket_name, const nt_nsigner_selector_t *selector);
int signer_init_nsigner_qrexec(const char *target_qube, const char *service_name, const nt_nsigner_selector_t *selector);
int signer_init_nsigner_url(const char *endpoint_url, const nt_nsigner_selector_t *selector);
int signer_init_nsigner_url_with_session(const char *endpoint_url,
const nt_nsigner_selector_t *selector,
int session_fd);
int signer_init_nsigner_url_with_session_auth(const char *endpoint_url,
const nt_nsigner_selector_t *selector,
int session_fd,
const nt_nsigner_auth_ctx_t *auth_or_null);
int signer_create_and_sign(int kind,
const char *content,
+4 -1
View File
@@ -179,7 +179,10 @@ int db_open(void) {
return -1;
}
snprintf(db_path, sizeof(db_path), "%s/nostr.db", dir_path);
if (snprintf(db_path, sizeof(db_path), "%s/nostr.db", dir_path) >= (int)sizeof(db_path)) {
fprintf(stderr, "db_open: database path too long\n");
return -1;
}
rc = sqlite3_open(db_path, &g_db);
if (rc != SQLITE_OK) {
fprintf(stderr, "db_open: sqlite open failed: %s\n", sqlite3_errmsg(g_db));
+4 -2
View File
@@ -3,6 +3,7 @@
#include "tui.h"
#include "../resources/nostr_core_lib/nostr_core/nostr_common.h"
#include "signer.h"
/*
* Canonical version is the latest git tag.
@@ -10,8 +11,8 @@
*/
#define NT_VERSION_MAJOR 0
#define NT_VERSION_MINOR 0
#define NT_VERSION_PATCH 6
#define NT_VERSION "v0.0.6"
#define NT_VERSION_PATCH 12
#define NT_VERSION "v0.0.12"
#include <stdio.h>
#include <stdlib.h>
@@ -208,6 +209,7 @@ int main(int argc, char *argv[]) {
menu_main();
nostr_cleanup();
signer_shutdown();
state_cleanup();
tui_cleanup();
db_close();
+7 -7
View File
@@ -500,7 +500,7 @@ static void ai_chat(const ai_prefs_t *prefs) {
char auth[4096];
char *resp_json;
char *answer;
char hold[16];
char hold[512];
if (!prefs) {
return;
@@ -513,7 +513,7 @@ static void ai_chat(const ai_prefs_t *prefs) {
if (!prefs->api_key || prefs->api_key[0] == '\0') {
tui_print("AI API key is empty. Configure settings first.");
tui_get_line(">", hold, (int)sizeof(hold));
tui_get_line("Press Enter to continue >", hold, (int)sizeof(hold));
return;
}
@@ -548,7 +548,7 @@ static void ai_chat(const ai_prefs_t *prefs) {
if (!resp_json) {
tui_print("AI request failed.");
tui_get_line(">", hold, (int)sizeof(hold));
tui_get_line("Press Enter to continue >", hold, (int)sizeof(hold));
return;
}
@@ -563,7 +563,7 @@ static void ai_chat(const ai_prefs_t *prefs) {
tui_print("Failed to parse AI response.");
}
free(answer);
tui_get_line(">", hold, (int)sizeof(hold));
tui_get_line("Press Enter to continue >", hold, (int)sizeof(hold));
}
static void ai_choose_model(ai_prefs_t *prefs) {
@@ -585,7 +585,7 @@ static void ai_settings(ai_prefs_t *prefs) {
char endpoint[512];
char key[1024];
char model[256];
char hold[16];
char hold[512];
if (!prefs) {
return;
@@ -617,12 +617,12 @@ static void ai_settings(ai_prefs_t *prefs) {
tui_print("Prefs saved.");
}
tui_get_line(">", hold, (int)sizeof(hold));
tui_get_line("Press Enter to continue >", hold, (int)sizeof(hold));
}
void menu_ai(void) {
ai_prefs_t prefs;
char input[16];
char input[512];
ai_prefs_init(&prefs);
(void)ai_load_prefs(&prefs);
+2 -1
View File
@@ -127,7 +127,8 @@ void menu_follows(void) {
tui_get_line(">", input, (int)sizeof(input));
continue;
}
snprintf(follow_hex, sizeof(follow_hex), "%s", who);
strncpy(follow_hex, who, sizeof(follow_hex) - 1U);
follow_hex[sizeof(follow_hex) - 1U] = '\0';
} else {
tui_print("Enter npub or 64-char hex pubkey.");
tui_get_line(">", input, (int)sizeof(input));
+1 -1
View File
@@ -60,7 +60,7 @@ static void live_preview_text(const char *text, int max_len, char *buf, size_t b
buf[0] = '\0';
if (!text || text[0] == '\0' || max_len <= 0) {
snprintf(buf, buf_size, "");
buf[0] = '\0';
return;
}
+217 -7
View File
@@ -1,6 +1,7 @@
#include "tui.h"
#include "state.h"
#include "net.h"
#include "db.h"
#include "publish.h"
#include "signer.h"
@@ -16,6 +17,7 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/random.h>
#define NT_DEV_SEED_PHRASE "cube dirt movie learn depth axis ball view aunt electric finish release"
@@ -160,20 +162,45 @@ void menu_login(void) {
unsigned char pub[32];
tui_clear_screen();
tui_print("LOGIN");
tui_print("LOGIN\n");
tui_print("^_E^:nter test/dev seed fallback");
tui_print("^_P^:rivate key (nsec or 64-hex)");
tui_print("^_M^:nemonic (12 words)");
tui_print("^_S^:igner local (same qube)");
tui_print("^_U^:RL signer (FIPS/web address)");
tui_print("^_N^:ew account");
tui_print("^_Q^:uit");
tui_get_line(">", input, (int)sizeof(input));
if (strcmp(input, "q") == 0 || strcmp(input, "x") == 0) {
tui_print("\nHasta luego.\n");
signer_shutdown();
exit(0);
}
if (input[0] == 'e' || input[0] == 'E') {
input[0] = '\0';
} else if (input[0] == 'p' || input[0] == 'P') {
tui_get_line("Private key (nsec or 64-hex) >", input, (int)sizeof(input));
if (input[0] == '\0') {
continue;
}
} else if (input[0] == 'm' || input[0] == 'M') {
tui_get_line("Seed phrase (12 words) >", input, (int)sizeof(input));
if (input[0] == '\0') {
continue;
}
}
if (strcmp(input, "s") == 0 || strcmp(input, "S") == 0) {
char **names = NULL;
int name_count = 0;
char pubkey_hex[65] = {0};
int selected = 0;
nt_nsigner_selector_t selector = nsigner_selector_default();
char idxbuf[32];
int nostr_index = 0;
tui_print("Searching for running n_signer instances...");
@@ -188,20 +215,31 @@ void menu_login(void) {
selected = 0;
} else {
int idx;
char idxbuf[16];
char pickbuf[16];
tui_print("Found %d n_signer instances:", name_count);
for (idx = 0; idx < name_count; idx++) {
tui_print(" [%d] @%s", idx, names[idx]);
}
tui_get_line("Select (0) >", idxbuf, (int)sizeof(idxbuf));
selected = (idxbuf[0] != '\0') ? atoi(idxbuf) : 0;
tui_get_line("Select (0) >", pickbuf, (int)sizeof(pickbuf));
selected = (pickbuf[0] != '\0') ? atoi(pickbuf) : 0;
if (selected < 0 || selected >= name_count) {
selected = 0;
}
}
tui_get_line("Seed phrase index (0) >", idxbuf, (int)sizeof(idxbuf));
if (idxbuf[0] != '\0') {
nostr_index = atoi(idxbuf);
if (nostr_index < 0) {
nostr_index = 0;
}
}
selector.has_nostr_index = 1;
selector.nostr_index = nostr_index;
selector.role[0] = '\0';
tui_print("Waiting for n_signer approval...");
if (nsigner_get_public_key(names[selected], "main", pubkey_hex) != 0) {
if (nsigner_get_public_key(names[selected], &selector, pubkey_hex) != 0) {
int i;
tui_print("Failed to get public key from n_signer (denied or error).");
for (i = 0; i < name_count; i++) {
@@ -212,7 +250,7 @@ void menu_login(void) {
continue;
}
signer_init_nsigner(names[selected]);
signer_init_nsigner(names[selected], &selector);
snprintf(g_state.npub_hex, sizeof(g_state.npub_hex), "%s", pubkey_hex);
{
@@ -247,6 +285,173 @@ void menu_login(void) {
return;
}
if (strcmp(input, "u") == 0 || strcmp(input, "U") == 0) {
char pubkey_hex[65] = {0};
char endpoint_url[256];
const char *saved_endpoint = db_get_local("nsigner_url_endpoint");
nt_nsigner_selector_t selector = nsigner_selector_default();
char idxbuf[32];
int nostr_index = 0;
int session_fd = -1;
nt_nsigner_auth_ctx_t auth = {0};
snprintf(endpoint_url, sizeof(endpoint_url), "%s", (saved_endpoint && saved_endpoint[0] != '\0') ? saved_endpoint : "");
tui_get_line("Signer URL (http://<npub>.fips:8080) >", endpoint_url, (int)sizeof(endpoint_url));
if (endpoint_url[0] == '\0') {
if (saved_endpoint && saved_endpoint[0] != '\0') {
snprintf(endpoint_url, sizeof(endpoint_url), "%s", saved_endpoint);
} else {
tui_print("No URL provided.");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
}
tui_get_line("Seed phrase index (0) >", idxbuf, (int)sizeof(idxbuf));
if (idxbuf[0] != '\0') {
nostr_index = atoi(idxbuf);
if (nostr_index < 0) {
nostr_index = 0;
}
}
selector.has_nostr_index = 1;
selector.nostr_index = nostr_index;
selector.role[0] = '\0';
tui_print("Connecting to n_signer URL: %s", endpoint_url);
tui_print("Waiting for n_signer approval...");
auth.enabled = 1;
if (getrandom(auth.privkey, sizeof(auth.privkey), 0) != (ssize_t)sizeof(auth.privkey)) {
tui_print("Failed to generate URL auth key.");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
snprintf(auth.label, sizeof(auth.label), "%s", "nostr_terminal");
session_fd = nsigner_session_open_url(endpoint_url);
if (session_fd < 0) {
tui_print("Failed to open persistent connection to n_signer URL.");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
if (nsigner_session_get_public_key(&session_fd, endpoint_url, &selector, &auth, pubkey_hex) != 0) {
nsigner_session_close(session_fd);
tui_print("Failed to get public key from n_signer URL (denied or error).");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
if (signer_init_nsigner_url_with_session_auth(endpoint_url, &selector, session_fd, &auth) != 0) {
nsigner_session_close(session_fd);
tui_print("Failed to initialize URL signer mode.");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
(void)db_set_local("nsigner_url_endpoint", endpoint_url);
snprintf(g_state.npub_hex, sizeof(g_state.npub_hex), "%s", pubkey_hex);
{
unsigned char pub_bytes[32];
nostr_hex_to_bytes(pubkey_hex, pub_bytes, 32);
nostr_key_to_bech32(pub_bytes, "npub", g_state.npub_bech32);
}
g_state.nsec_hex[0] = '\0';
g_state.nsec_bech32[0] = '\0';
g_state.seed_phrase[0] = '\0';
g_state.logged_in = 1;
tui_print("Signed in via n_signer URL (%s)", endpoint_url);
tui_print("npub: %s", g_state.npub_bech32);
(void)state_load_user_info();
if (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") == 0) {
tui_print("No profile found on relays. Setting up new user...");
menu_add_new_user();
(void)state_load_user_info();
}
return;
}
if (strcmp(input, "S") == 0) {
char pubkey_hex[65] = {0};
char target_qube[128];
char service_name[128];
const char *saved_target = db_get_local("nsigner_qrexec_target");
nt_nsigner_selector_t selector = nsigner_selector_default();
char idxbuf[32];
int nostr_index = 0;
snprintf(target_qube, sizeof(target_qube), "%s", (saved_target && saved_target[0] != '\0') ? saved_target : "nsigner-vault");
snprintf(service_name, sizeof(service_name), "%s", "qubes.NsignerRpc");
tui_get_line("Target signer qube (nsigner-vault) >", target_qube, (int)sizeof(target_qube));
if (target_qube[0] == '\0') {
snprintf(target_qube, sizeof(target_qube), "%s", (saved_target && saved_target[0] != '\0') ? saved_target : "nsigner-vault");
}
tui_get_line("Qrexec service (qubes.NsignerRpc) >", service_name, (int)sizeof(service_name));
if (service_name[0] == '\0') {
snprintf(service_name, sizeof(service_name), "%s", "qubes.NsignerRpc");
}
tui_get_line("Seed phrase index (0) >", idxbuf, (int)sizeof(idxbuf));
if (idxbuf[0] != '\0') {
nostr_index = atoi(idxbuf);
if (nostr_index < 0) {
nostr_index = 0;
}
}
selector.has_nostr_index = 1;
selector.nostr_index = nostr_index;
selector.role[0] = '\0';
tui_print("Calling qrexec service %s in %s...", service_name, target_qube);
tui_print("Waiting for n_signer approval...");
if (nsigner_qrexec_get_public_key(target_qube, service_name, &selector, pubkey_hex) != 0) {
tui_print("Failed to get public key via qrexec (denied or error).");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
if (signer_init_nsigner_qrexec(target_qube, service_name, &selector) != 0) {
tui_print("Failed to initialize qrexec signer mode.");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
(void)db_set_local("nsigner_qrexec_target", target_qube);
snprintf(g_state.npub_hex, sizeof(g_state.npub_hex), "%s", pubkey_hex);
{
unsigned char pub_bytes[32];
nostr_hex_to_bytes(pubkey_hex, pub_bytes, 32);
nostr_key_to_bech32(pub_bytes, "npub", g_state.npub_bech32);
}
g_state.nsec_hex[0] = '\0';
g_state.nsec_bech32[0] = '\0';
g_state.seed_phrase[0] = '\0';
g_state.logged_in = 1;
tui_print("Signed in via qrexec n_signer (%s:%s)", target_qube, service_name);
tui_print("npub: %s", g_state.npub_bech32);
(void)state_load_user_info();
if (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") == 0) {
tui_print("No profile found on relays. Setting up new user...");
menu_add_new_user();
(void)state_load_user_info();
}
return;
}
if (strcmp(input, "n") == 0) {
char mnemonic[256];
char idxbuf[32];
@@ -377,12 +582,17 @@ void menu_login(void) {
continue;
}
tui_print("");
tui_print("Using seed phrase: %s", g_state.seed_phrase);
tui_print("");
tui_get_line("Press Enter to continue >", input, (int)sizeof(input));
(void)state_load_user_info();
return;
}
}
tui_print("Invalid login input. Enter 12 words, nsec, 64-hex, [s], [n], or [q].");
tui_print("Invalid login input. Choose a menu command or enter valid key/mnemonic data.");
tui_get_line(">", input, (int)sizeof(input));
}
}
+22
View File
@@ -29,6 +29,28 @@ void menu_profile(void) {
if (g_signer.kind == NT_SIGNER_NSIGNER) {
tui_print("Signer: n_signer (@%s)", g_signer.socket_name);
if (g_signer.selector.has_nostr_index) {
tui_print("Signer selector: nostr_index=%d", g_signer.selector.nostr_index);
} else {
tui_print("Signer selector: role=%s", g_signer.selector.role[0] ? g_signer.selector.role : "main");
}
} else if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
tui_print("Signer: n_signer qrexec");
tui_print("Signer target: %s", g_signer.qrexec_target);
tui_print("Signer service: %s", g_signer.qrexec_service);
if (g_signer.selector.has_nostr_index) {
tui_print("Signer selector: nostr_index=%d", g_signer.selector.nostr_index);
} else {
tui_print("Signer selector: role=%s", g_signer.selector.role[0] ? g_signer.selector.role : "main");
}
} else if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
tui_print("Signer: n_signer URL");
tui_print("Signer endpoint: %s", g_signer.endpoint_url);
if (g_signer.selector.has_nostr_index) {
tui_print("Signer selector: nostr_index=%d", g_signer.selector.nostr_index);
} else {
tui_print("Signer selector: role=%s", g_signer.selector.role[0] ? g_signer.selector.role : "main");
}
} else {
tui_print("nsecHex: %s", g_state.nsec_hex);
tui_print("nsec: %s", g_state.nsec_bech32);
+59 -56
View File
@@ -8,6 +8,7 @@
#include "../resources/nostr_core_lib/nostr_core/nip042.h"
#include "../resources/nostr_core_lib/nostr_core/utils.h"
#include "../resources/nostr_core_lib/nostr_websocket/nostr_websocket_tls.h"
#include "../resources/nostr_core_lib/nostr_core/nostr_core.h"
#include <curl/curl.h>
#include <errno.h>
@@ -19,7 +20,7 @@
#include <unistd.h>
#define NT_SUB_ID "sub1"
#define NT_WS_BUFFER_SIZE 65536
#define NT_WS_BUFFER_SIZE 262144
static long long nt_now_ms(void) {
struct timeval tv;
@@ -529,7 +530,7 @@ void nt_publish_results_free(nt_publish_result_t *results, int result_count) {
}
/* Query one relay and append deduplicated EVENT payloads until EOSE/timeout. */
static int nt_query_one_relay(const char *relay_url,
static __attribute__((unused)) int nt_query_one_relay(const char *relay_url,
cJSON *filter,
int timeout_ms,
int verbose,
@@ -685,13 +686,12 @@ int nt_query_sync_verbose(const char *filter_json,
char ***events_out,
int *event_count) {
cJSON *filter = NULL;
nostr_relay_pool_t *pool = NULL;
cJSON **pool_events = NULL;
int pool_event_count = 0;
char **events = NULL;
char **seen_ids = NULL;
int events_n = 0;
int events_cap = 0;
int seen_n = 0;
int seen_cap = 0;
int success_relays = 0;
int i;
if (!events_out || !event_count || !filter_json || !relay_urls || relay_count <= 0) {
@@ -708,59 +708,62 @@ int nt_query_sync_verbose(const char *filter_json,
return -1;
}
for (i = 0; i < relay_count; i++) {
const char *relay = relay_urls[i];
int rc;
const char *reason = "unknown";
if (!relay || relay[0] == '\0') {
continue;
}
if (verbose && phase_label) {
fprintf(stderr, "%s: attempting relay %d/%d %s\n", phase_label, i + 1, relay_count, relay);
}
rc = nt_query_one_relay(relay,
filter,
timeout_ms,
verbose,
phase_label,
&events,
&events_n,
&events_cap,
&seen_ids,
&seen_n,
&seen_cap,
&reason);
if (rc < 0) {
if (verbose && phase_label) {
fprintf(stderr, "%s: failed %s (%s)\n", phase_label, relay, reason ? reason : "error");
}
nt_free_string_array(events, events_n);
nt_free_string_array(seen_ids, seen_n);
cJSON_Delete(filter);
return -1;
}
if (rc > 0) {
success_relays++;
if (verbose && phase_label) {
fprintf(stderr, "%s: connected %s (%s)\n", phase_label, relay, reason ? reason : "ok");
}
} else if (verbose && phase_label) {
fprintf(stderr, "%s: failed %s (%s)\n", phase_label, relay, reason ? reason : "failed");
}
}
nt_free_string_array(seen_ids, seen_n);
cJSON_Delete(filter);
if (success_relays == 0) {
nt_free_string_array(events, events_n);
pool = nostr_relay_pool_create(NULL);
if (!pool) {
cJSON_Delete(filter);
fprintf(stderr, "query: failed to create relay pool\n");
return -1;
}
if (verbose && phase_label) {
fprintf(stderr,
"%s: querying %d relays via nostr_core relay pool\n",
phase_label,
relay_count);
}
pool_events = nostr_relay_pool_query_sync(pool,
relay_urls,
relay_count,
filter,
&pool_event_count,
timeout_ms);
cJSON_Delete(filter);
filter = NULL;
if (!pool_events || pool_event_count <= 0) {
nostr_relay_pool_destroy(pool);
return -1;
}
for (i = 0; i < pool_event_count; i++) {
char *event_str = cJSON_PrintUnformatted(pool_events[i]);
if (!event_str || nt_push_string(&events, &events_n, &events_cap, event_str) != 0) {
free(event_str);
nt_free_string_array(events, events_n);
for (i = 0; i < pool_event_count; i++) {
cJSON_Delete(pool_events[i]);
}
free(pool_events);
nostr_relay_pool_destroy(pool);
return -1;
}
}
for (i = 0; i < pool_event_count; i++) {
cJSON_Delete(pool_events[i]);
}
free(pool_events);
if (verbose && phase_label) {
fprintf(stderr,
"%s: relay pool collected %d unique events\n",
phase_label,
events_n);
}
nostr_relay_pool_destroy(pool);
*events_out = events;
*event_count = events_n;
return 0;
+109
View File
@@ -0,0 +1,109 @@
#include "nsigner_auth.h"
#include "../resources/nostr_core_lib/nostr_core/nip001.h"
#include "../resources/nostr_core_lib/nostr_core/utils.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define NT_NSIGNER_AUTH_EVENT_KIND 27235
static cJSON *nt_nsigner_auth_build_tag_pair(const char *name, const char *value) {
cJSON *pair;
if (!name || !value) {
return NULL;
}
pair = cJSON_CreateArray();
if (!pair) {
return NULL;
}
cJSON_AddItemToArray(pair, cJSON_CreateString(name));
cJSON_AddItemToArray(pair, cJSON_CreateString(value));
return pair;
}
static int nt_nsigner_auth_compute_params_hash_hex(const cJSON *params, char out_hex_65[65]) {
char *params_json = NULL;
unsigned char hash[32];
int rc = -1;
if (!out_hex_65) {
return -1;
}
if (params) {
params_json = cJSON_PrintUnformatted((cJSON *)params);
} else {
params_json = strdup("null");
}
if (!params_json) {
return -1;
}
if (nostr_sha256((const unsigned char *)params_json, strlen(params_json), hash) != 0) {
goto cleanup;
}
nostr_bytes_to_hex(hash, sizeof(hash), out_hex_65);
out_hex_65[64] = '\0';
rc = 0;
cleanup:
free(params_json);
return rc;
}
int nt_nsigner_auth_build(const char *request_id,
const char *method,
const cJSON *params,
const nt_nsigner_auth_ctx_t *auth_ctx,
time_t created_at,
cJSON **out_auth) {
char body_hash_hex[65];
cJSON *tags = NULL;
cJSON *auth_event = NULL;
if (!request_id || request_id[0] == '\0' ||
!method || method[0] == '\0' ||
!auth_ctx || !auth_ctx->enabled ||
!out_auth) {
return -1;
}
*out_auth = NULL;
if (nt_nsigner_auth_compute_params_hash_hex(params, body_hash_hex) != 0) {
return -1;
}
tags = cJSON_CreateArray();
if (!tags) {
return -1;
}
cJSON_AddItemToArray(tags, nt_nsigner_auth_build_tag_pair("nsigner_rpc", request_id));
cJSON_AddItemToArray(tags, nt_nsigner_auth_build_tag_pair("nsigner_method", method));
cJSON_AddItemToArray(tags, nt_nsigner_auth_build_tag_pair("nsigner_body_hash", body_hash_hex));
if (created_at <= 0) {
created_at = time(NULL);
}
auth_event = nostr_create_and_sign_event(NT_NSIGNER_AUTH_EVENT_KIND,
auth_ctx->label,
tags,
auth_ctx->privkey,
created_at);
if (!auth_event) {
cJSON_Delete(tags);
return -1;
}
*out_auth = auth_event;
return 0;
}
+889 -19
View File
File diff suppressed because it is too large Load Diff
+326 -10
View File
@@ -15,7 +15,18 @@
nt_signer_t g_signer = {
.kind = NT_SIGNER_LOCAL,
.socket_name = {0},
.role = "main",
.endpoint_url = {0},
.url_session_fd = -1,
.selector = {
.has_nostr_index = 1,
.nostr_index = 0,
.role = {0}
},
.url_auth = {
.enabled = 0,
.privkey = {0},
.label = {0}
}
};
static int signer_local_private_key(unsigned char out_priv[32]) {
@@ -28,10 +39,38 @@ static int signer_local_private_key(unsigned char out_priv[32]) {
return 0;
}
void signer_set_selector(int has_index, int index, const char *role) {
g_signer.selector.has_nostr_index = has_index ? 1 : 0;
g_signer.selector.nostr_index = (index < 0) ? 0 : index;
if (role && role[0] != '\0') {
snprintf(g_signer.selector.role, sizeof(g_signer.selector.role), "%s", role);
} else {
g_signer.selector.role[0] = '\0';
}
}
void signer_init(void) {
nt_nsigner_selector_t sel = nsigner_selector_default();
if (g_signer.url_session_fd >= 0) {
nsigner_session_close(g_signer.url_session_fd);
g_signer.url_session_fd = -1;
}
g_signer.kind = NT_SIGNER_LOCAL;
g_signer.socket_name[0] = '\0';
snprintf(g_signer.role, sizeof(g_signer.role), "%s", "main");
g_signer.qrexec_target[0] = '\0';
g_signer.qrexec_service[0] = '\0';
g_signer.endpoint_url[0] = '\0';
signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role);
g_signer.url_auth.enabled = 0;
memset(g_signer.url_auth.privkey, 0, sizeof(g_signer.url_auth.privkey));
g_signer.url_auth.label[0] = '\0';
}
void signer_shutdown(void) {
memset(g_signer.url_auth.privkey, 0, sizeof(g_signer.url_auth.privkey));
signer_init();
}
int signer_init_local(void) {
@@ -39,16 +78,97 @@ int signer_init_local(void) {
return 0;
}
int signer_init_nsigner(const char *socket_name) {
int signer_init_nsigner(const char *socket_name, const nt_nsigner_selector_t *selector) {
nt_nsigner_selector_t sel = selector ? *selector : nsigner_selector_default();
if (!socket_name || socket_name[0] == '\0') {
return -1;
}
g_signer.kind = NT_SIGNER_NSIGNER;
snprintf(g_signer.socket_name, sizeof(g_signer.socket_name), "%s", socket_name);
snprintf(g_signer.role, sizeof(g_signer.role), "%s", "main");
g_signer.qrexec_target[0] = '\0';
g_signer.qrexec_service[0] = '\0';
g_signer.endpoint_url[0] = '\0';
if (g_signer.url_session_fd >= 0) {
nsigner_session_close(g_signer.url_session_fd);
g_signer.url_session_fd = -1;
}
signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role);
return 0;
}
int signer_init_nsigner_qrexec(const char *target_qube, const char *service_name, const nt_nsigner_selector_t *selector) {
nt_nsigner_selector_t sel = selector ? *selector : nsigner_selector_default();
if (!target_qube || target_qube[0] == '\0' || !service_name || service_name[0] == '\0') {
return -1;
}
g_signer.kind = NT_SIGNER_NSIGNER_QREXEC;
g_signer.socket_name[0] = '\0';
snprintf(g_signer.qrexec_target, sizeof(g_signer.qrexec_target), "%s", target_qube);
snprintf(g_signer.qrexec_service, sizeof(g_signer.qrexec_service), "%s", service_name);
g_signer.endpoint_url[0] = '\0';
if (g_signer.url_session_fd >= 0) {
nsigner_session_close(g_signer.url_session_fd);
g_signer.url_session_fd = -1;
}
signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role);
return 0;
}
int signer_init_nsigner_url_with_session_auth(const char *endpoint_url,
const nt_nsigner_selector_t *selector,
int session_fd,
const nt_nsigner_auth_ctx_t *auth_or_null) {
nt_nsigner_selector_t sel = selector ? *selector : nsigner_selector_default();
if (!endpoint_url || endpoint_url[0] == '\0') {
if (session_fd >= 0) {
nsigner_session_close(session_fd);
}
return -1;
}
g_signer.kind = NT_SIGNER_NSIGNER_URL;
g_signer.socket_name[0] = '\0';
g_signer.qrexec_target[0] = '\0';
g_signer.qrexec_service[0] = '\0';
snprintf(g_signer.endpoint_url, sizeof(g_signer.endpoint_url), "%s", endpoint_url);
if (g_signer.url_session_fd >= 0 && g_signer.url_session_fd != session_fd) {
nsigner_session_close(g_signer.url_session_fd);
}
g_signer.url_session_fd = session_fd;
if (g_signer.url_session_fd < 0) {
g_signer.url_session_fd = nsigner_session_open_url(g_signer.endpoint_url);
}
signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role);
g_signer.url_auth.enabled = 0;
memset(g_signer.url_auth.privkey, 0, sizeof(g_signer.url_auth.privkey));
g_signer.url_auth.label[0] = '\0';
if (auth_or_null && auth_or_null->enabled) {
g_signer.url_auth.enabled = 1;
memcpy(g_signer.url_auth.privkey, auth_or_null->privkey, sizeof(g_signer.url_auth.privkey));
snprintf(g_signer.url_auth.label, sizeof(g_signer.url_auth.label), "%s", auth_or_null->label);
}
return 0;
}
int signer_init_nsigner_url_with_session(const char *endpoint_url,
const nt_nsigner_selector_t *selector,
int session_fd) {
return signer_init_nsigner_url_with_session_auth(endpoint_url, selector, session_fd, NULL);
}
int signer_init_nsigner_url(const char *endpoint_url, const nt_nsigner_selector_t *selector) {
return signer_init_nsigner_url_with_session_auth(endpoint_url, selector, -1, NULL);
}
int signer_get_local_private_key(unsigned char out_priv[32]) {
if (!out_priv) {
return -1;
@@ -114,7 +234,7 @@ int signer_create_and_sign(int kind,
}
rc = nsigner_sign_event(g_signer.socket_name,
g_signer.role,
&g_signer.selector,
unsigned_json,
signed_event_json_out);
@@ -123,6 +243,109 @@ int signer_create_and_sign(int kind,
return rc;
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
cJSON *unsigned_event = NULL;
cJSON *tags_copy = NULL;
char *unsigned_json = NULL;
int rc = -1;
unsigned_event = cJSON_CreateObject();
if (!unsigned_event) {
return -1;
}
if (!cJSON_AddNumberToObject(unsigned_event, "kind", kind) ||
!cJSON_AddStringToObject(unsigned_event, "content", content ? content : "")) {
cJSON_Delete(unsigned_event);
return -1;
}
tags_copy = tags ? cJSON_Duplicate(tags, 1) : cJSON_CreateArray();
if (!tags_copy) {
cJSON_Delete(unsigned_event);
return -1;
}
if (!cJSON_AddItemToObject(unsigned_event, "tags", tags_copy)) {
cJSON_Delete(tags_copy);
cJSON_Delete(unsigned_event);
return -1;
}
if (!cJSON_AddNumberToObject(unsigned_event, "created_at", (double)timestamp) ||
!cJSON_AddStringToObject(unsigned_event, "pubkey", g_state.npub_hex)) {
cJSON_Delete(unsigned_event);
return -1;
}
unsigned_json = cJSON_PrintUnformatted(unsigned_event);
if (!unsigned_json) {
cJSON_Delete(unsigned_event);
return -1;
}
rc = nsigner_qrexec_sign_event(g_signer.qrexec_target,
g_signer.qrexec_service,
&g_signer.selector,
unsigned_json,
signed_event_json_out);
free(unsigned_json);
cJSON_Delete(unsigned_event);
return rc;
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
cJSON *unsigned_event = NULL;
cJSON *tags_copy = NULL;
char *unsigned_json = NULL;
int rc = -1;
unsigned_event = cJSON_CreateObject();
if (!unsigned_event) {
return -1;
}
if (!cJSON_AddNumberToObject(unsigned_event, "kind", kind) ||
!cJSON_AddStringToObject(unsigned_event, "content", content ? content : "")) {
cJSON_Delete(unsigned_event);
return -1;
}
tags_copy = tags ? cJSON_Duplicate(tags, 1) : cJSON_CreateArray();
if (!tags_copy) {
cJSON_Delete(unsigned_event);
return -1;
}
if (!cJSON_AddItemToObject(unsigned_event, "tags", tags_copy)) {
cJSON_Delete(tags_copy);
cJSON_Delete(unsigned_event);
return -1;
}
if (!cJSON_AddNumberToObject(unsigned_event, "created_at", (double)timestamp) ||
!cJSON_AddStringToObject(unsigned_event, "pubkey", g_state.npub_hex)) {
cJSON_Delete(unsigned_event);
return -1;
}
unsigned_json = cJSON_PrintUnformatted(unsigned_event);
if (!unsigned_json) {
cJSON_Delete(unsigned_event);
return -1;
}
rc = nsigner_session_sign_event(&g_signer.url_session_fd,
g_signer.endpoint_url,
&g_signer.selector,
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
unsigned_json,
signed_event_json_out);
free(unsigned_json);
cJSON_Delete(unsigned_event);
return rc;
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
@@ -172,11 +395,28 @@ int signer_sign_event_json(const char *unsigned_event_json, char **signed_event_
if (g_signer.kind == NT_SIGNER_NSIGNER) {
return nsigner_sign_event(g_signer.socket_name,
g_signer.role,
&g_signer.selector,
unsigned_event_json,
signed_event_json_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
return nsigner_qrexec_sign_event(g_signer.qrexec_target,
g_signer.qrexec_service,
&g_signer.selector,
unsigned_event_json,
signed_event_json_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
return nsigner_session_sign_event(&g_signer.url_session_fd,
g_signer.endpoint_url,
&g_signer.selector,
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
unsigned_event_json,
signed_event_json_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
@@ -235,12 +475,31 @@ int signer_nip04_encrypt(const char *peer_pub_hex, const char *plaintext, char *
if (g_signer.kind == NT_SIGNER_NSIGNER) {
return nsigner_nip04_encrypt(g_signer.socket_name,
g_signer.role,
&g_signer.selector,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
return nsigner_qrexec_nip04_encrypt(g_signer.qrexec_target,
g_signer.qrexec_service,
&g_signer.selector,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
return nsigner_session_nip04_encrypt(&g_signer.url_session_fd,
g_signer.endpoint_url,
&g_signer.selector,
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
@@ -277,12 +536,31 @@ int signer_nip04_decrypt(const char *peer_pub_hex, const char *ciphertext, char
if (g_signer.kind == NT_SIGNER_NSIGNER) {
return nsigner_nip04_decrypt(g_signer.socket_name,
g_signer.role,
&g_signer.selector,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
return nsigner_qrexec_nip04_decrypt(g_signer.qrexec_target,
g_signer.qrexec_service,
&g_signer.selector,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
return nsigner_session_nip04_decrypt(&g_signer.url_session_fd,
g_signer.endpoint_url,
&g_signer.selector,
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
@@ -319,12 +597,31 @@ int signer_nip44_encrypt(const char *peer_pub_hex, const char *plaintext, char *
if (g_signer.kind == NT_SIGNER_NSIGNER) {
return nsigner_nip44_encrypt(g_signer.socket_name,
g_signer.role,
&g_signer.selector,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
return nsigner_qrexec_nip44_encrypt(g_signer.qrexec_target,
g_signer.qrexec_service,
&g_signer.selector,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
return nsigner_session_nip44_encrypt(&g_signer.url_session_fd,
g_signer.endpoint_url,
&g_signer.selector,
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
@@ -361,12 +658,31 @@ int signer_nip44_decrypt(const char *peer_pub_hex, const char *ciphertext, char
if (g_signer.kind == NT_SIGNER_NSIGNER) {
return nsigner_nip44_decrypt(g_signer.socket_name,
g_signer.role,
&g_signer.selector,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
return nsigner_qrexec_nip44_decrypt(g_signer.qrexec_target,
g_signer.qrexec_service,
&g_signer.selector,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
return nsigner_session_nip44_decrypt(&g_signer.url_session_fd,
g_signer.endpoint_url,
&g_signer.selector,
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}