1 Commits

16 changed files with 1332 additions and 422 deletions

59
include/nsigner_client.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef NSIGNER_CLIENT_H
#define NSIGNER_CLIENT_H
#include "../resources/nostr_core_lib/cjson/cJSON.h"
/* Error codes */
#define NT_NSIGNER_OK 0
#define NT_NSIGNER_E_IO -1
#define NT_NSIGNER_E_DENIED -2
#define NT_NSIGNER_E_UNAUTH -3
#define NT_NSIGNER_E_PARSE -4
#define NT_NSIGNER_E_ERROR -5
/*
* Enumerate running nsigner instances by reading /proc/net/unix.
* Returns 0 on success. Caller must free each name and the array.
* Names are returned WITHOUT the leading '@'.
*/
int nsigner_list(char ***names_out, int *count_out);
/*
* Connect to an nsigner abstract namespace socket.
* Returns file descriptor >= 0 on success, -1 on failure.
* The name should NOT include the leading '@'.
*/
int nsigner_connect(const char *name);
/*
* Send a JSON-RPC request and receive the response.
* Uses 4-byte big-endian length-prefixed framing.
* read_timeout_ms: timeout for reading response (use 60000 for prompt-requiring methods).
* Returns NT_NSIGNER_OK on success with response in *response_out (caller must cJSON_Delete).
* On error returns one of the NT_NSIGNER_E_* codes.
*/
int nsigner_rpc(int fd, const cJSON *request, cJSON **response_out, int read_timeout_ms);
/*
* High-level helpers (each opens a connection, sends RPC, closes).
* 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_sign_event(const char *socket_name, const char *role,
const char *unsigned_event_json, char **signed_event_json_out);
int nsigner_nip04_encrypt(const char *socket_name, const char *role,
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
int nsigner_nip04_decrypt(const char *socket_name, const char *role,
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
int nsigner_nip44_encrypt(const char *socket_name, const char *role,
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
int nsigner_nip44_decrypt(const char *socket_name, const char *role,
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
#endif /* NSIGNER_CLIENT_H */

45
include/signer.h Normal file
View File

@@ -0,0 +1,45 @@
#ifndef SIGNER_H
#define SIGNER_H
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include <time.h>
typedef enum {
NT_SIGNER_LOCAL = 0,
NT_SIGNER_NSIGNER = 1
} nt_signer_kind_t;
typedef struct {
nt_signer_kind_t kind;
char socket_name[128];
char role[64];
} nt_signer_t;
extern nt_signer_t g_signer;
void signer_init(void);
int signer_init_local(void);
int signer_init_nsigner(const char *socket_name);
int signer_create_and_sign(int kind,
const char *content,
cJSON *tags,
time_t timestamp,
char **signed_event_json_out);
int signer_nip04_encrypt(const char *peer_pub_hex, const char *plaintext, char **cipher_out);
int signer_nip04_decrypt(const char *peer_pub_hex, const char *ciphertext, char **plain_out);
int signer_nip44_encrypt(const char *peer_pub_hex, const char *plaintext, char **cipher_out);
int signer_nip44_decrypt(const char *peer_pub_hex, const char *ciphertext, char **plain_out);
int signer_nip44_self_encrypt(const char *plaintext, char **cipher_out);
int signer_nip44_self_decrypt(const char *sender_pub_hex, const char *ciphertext, char **plain_out);
int signer_sign_event_json(const char *unsigned_event_json, char **signed_event_json_out);
/* Returns 0 on success. Only works in NT_SIGNER_LOCAL mode; returns -1 for NT_SIGNER_NSIGNER. */
int signer_get_local_private_key(unsigned char out_priv[32]);
#endif /* SIGNER_H */

View File

@@ -16,8 +16,7 @@ typedef struct {
char *kind10002_json;
char *kind10096_json;
char *kind17375_json;
char **kind30078_events;
int kind30078_count;
char *user_settings_json;
const char *bootstrap_relays[8];
int bootstrap_relay_count;
@@ -39,4 +38,7 @@ void state_parse_relay_list(void);
const char **state_get_read_relays(int *count);
const char **state_get_write_relays(int *count);
int state_nip44_self_encrypt(const char *plaintext, char **cipher_out);
char *state_nip44_self_decrypt(const char *sender_pub_hex, const char *ciphertext);
#endif /* STATE_H */

View File

@@ -10,8 +10,8 @@
*/
#define NT_VERSION_MAJOR 0
#define NT_VERSION_MINOR 0
#define NT_VERSION_PATCH 5
#define NT_VERSION "v0.0.5"
#define NT_VERSION_PATCH 6
#define NT_VERSION "v0.0.6"
#include <stdio.h>
#include <stdlib.h>
@@ -33,8 +33,6 @@ void menu_ai(void);
void menu_ecash(void);
static void menu_show_loaded_events(void) {
int i;
tui_clear_screen();
tui_print("LOADED EVENTS\n");
@@ -58,10 +56,9 @@ static void menu_show_loaded_events(void) {
tui_print("%s", g_state.kind17375_json ? g_state.kind17375_json : "(not loaded)");
tui_print("");
tui_print("KIND 30078 events loaded: %d", g_state.kind30078_count);
for (i = 0; i < g_state.kind30078_count; i++) {
tui_print("[%d] %s", i + 1, g_state.kind30078_events[i] ? g_state.kind30078_events[i] : "(null)");
}
tui_print("USER-SETTINGS (kind 30078, d=user-settings):");
tui_print("%s", g_state.user_settings_json ? g_state.user_settings_json : "(not loaded)");
tui_print("");
tui_print("\nPress Enter to return.");
{
@@ -91,7 +88,7 @@ void menu_main(void) {
tui_print("^_B^:logs/posts");
tui_print("^_L^:ive feeds");
tui_print("^_M^:essage");
tui_print("^_D^:odo");
tui_print("To^_d^:o");
tui_print("^_J^:ournal");
tui_print("^_A^:i");
tui_print("^_E^:cash");

View File

@@ -1,16 +1,14 @@
#include "tui.h"
#include "state.h"
#include "net.h"
#include "publish.h"
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include "../resources/nostr_core_lib/nostr_core/nip004.h"
#include "../resources/nostr_core_lib/nostr_core/utils.h"
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
typedef struct {
@@ -80,6 +78,99 @@ static void ai_prefs_free(ai_prefs_t *prefs) {
memset(prefs, 0, sizeof(*prefs));
}
static int ai_ends_with_chat_completions(const char *s) {
static const char *suffix = "/chat/completions";
size_t s_len;
size_t suffix_len;
if (!s) {
return 0;
}
s_len = strlen(s);
suffix_len = strlen(suffix);
if (s_len < suffix_len) {
return 0;
}
return strcmp(s + (s_len - suffix_len), suffix) == 0;
}
static char *ai_join_chat_completions(const char *base_url) {
size_t base_len;
int needs_slash;
char *out;
if (!base_url || base_url[0] == '\0') {
return ai_strdup("https://api.venice.ai/api/v1/chat/completions");
}
if (ai_ends_with_chat_completions(base_url)) {
return ai_strdup(base_url);
}
base_len = strlen(base_url);
needs_slash = (base_len > 0U && base_url[base_len - 1U] != '/');
out = (char *)malloc(base_len + (needs_slash ? 1U : 0U) + strlen("chat/completions") + 1U);
if (!out) {
return NULL;
}
snprintf(out,
base_len + (needs_slash ? 1U : 0U) + strlen("chat/completions") + 1U,
"%s%schat/completions",
base_url,
needs_slash ? "/" : "");
return out;
}
static char *ai_base_url_from_endpoint(const char *endpoint) {
static const char *suffix = "/chat/completions";
size_t endpoint_len;
size_t suffix_len;
char *out;
if (!endpoint || endpoint[0] == '\0') {
return ai_strdup("");
}
endpoint_len = strlen(endpoint);
suffix_len = strlen(suffix);
if (endpoint_len >= suffix_len &&
strcmp(endpoint + (endpoint_len - suffix_len), suffix) == 0) {
out = (char *)malloc(endpoint_len - suffix_len + 1U);
if (!out) {
return NULL;
}
memcpy(out, endpoint, endpoint_len - suffix_len);
out[endpoint_len - suffix_len] = '\0';
return out;
}
return ai_strdup(endpoint);
}
static cJSON *ai_make_d_tag(const char *dval) {
cJSON *tags = cJSON_CreateArray();
cJSON *d_tag;
if (!tags) {
return NULL;
}
d_tag = cJSON_CreateArray();
if (!d_tag) {
cJSON_Delete(tags);
return NULL;
}
cJSON_AddItemToArray(d_tag, cJSON_CreateString("d"));
cJSON_AddItemToArray(d_tag, cJSON_CreateString(dval ? dval : ""));
cJSON_AddItemToArray(tags, d_tag);
return tags;
}
static size_t nt_http_write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
nt_http_buffer_t *buf = (nt_http_buffer_t *)userdata;
size_t total = size * nmemb;
@@ -142,156 +233,37 @@ static void nt_configure_curl_tls(CURL *curl) {
}
}
static int ai_encrypt_for_self(const char *plaintext, char **cipher_out) {
unsigned char priv[32];
unsigned char pub[32];
char *out;
size_t out_sz;
if (!plaintext || !cipher_out) {
return -1;
}
*cipher_out = NULL;
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
return -1;
}
if (nostr_hex_to_bytes(g_state.npub_hex, pub, 32) != 0) {
return -1;
}
out_sz = (strlen(plaintext) * 4U) + 256U;
out = (char *)malloc(out_sz);
if (!out) {
return -1;
}
if (nostr_nip04_encrypt(priv, pub, plaintext, out, out_sz) != 0) {
free(out);
return -1;
}
*cipher_out = out;
return 0;
}
static char *ai_decrypt_from_pub(const char *sender_pub_hex, const char *ciphertext) {
unsigned char priv[32];
unsigned char sender_pub[32];
size_t out_sz;
char *out;
if (!sender_pub_hex || !ciphertext) {
return NULL;
}
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
return NULL;
}
if (nostr_hex_to_bytes(sender_pub_hex, sender_pub, 32) != 0) {
return NULL;
}
out_sz = strlen(ciphertext) + 4096U;
out = (char *)malloc(out_sz);
if (!out) {
return NULL;
}
if (nostr_nip04_decrypt(priv, sender_pub, ciphertext, out, out_sz) != 0) {
free(out);
return NULL;
}
return out;
}
static cJSON *ai_make_d_tag(const char *dval) {
cJSON *tags = cJSON_CreateArray();
cJSON *d_tag;
if (!tags) {
return NULL;
}
d_tag = cJSON_CreateArray();
if (!d_tag) {
cJSON_Delete(tags);
return NULL;
}
cJSON_AddItemToArray(d_tag, cJSON_CreateString("d"));
cJSON_AddItemToArray(d_tag, cJSON_CreateString(dval ? dval : ""));
cJSON_AddItemToArray(tags, d_tag);
return tags;
}
static int ai_load_prefs(ai_prefs_t *prefs) {
const char **read_relays;
int relay_count = 0;
char filter[256];
char *event_json = NULL;
cJSON *event;
cJSON *content_item;
cJSON *pubkey_item;
char *plain;
cJSON *root;
cJSON *endpoint;
cJSON *llm;
cJSON *api_key;
cJSON *model;
cJSON *base_url;
cJSON *legacy_endpoint;
char *endpoint;
if (!prefs) {
if (!prefs || !g_state.user_settings_json) {
return -1;
}
read_relays = state_get_read_relays(&relay_count);
snprintf(filter,
sizeof(filter),
"{\"authors\":[\"%s\"],\"kinds\":[30078],\"#d\":[\"prefs\"],\"limit\":1}",
g_state.npub_hex);
if (nt_get_first(filter, read_relays, relay_count, 7000, &event_json) != 0) {
return -1;
}
if (!event_json) {
return 0;
}
event = cJSON_Parse(event_json);
free(event_json);
if (!event) {
return -1;
}
content_item = cJSON_GetObjectItemCaseSensitive(event, "content");
pubkey_item = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
if (!cJSON_IsString(content_item) || !content_item->valuestring ||
!cJSON_IsString(pubkey_item) || !pubkey_item->valuestring) {
cJSON_Delete(event);
return -1;
}
plain = ai_decrypt_from_pub(pubkey_item->valuestring, content_item->valuestring);
cJSON_Delete(event);
if (!plain) {
return -1;
}
root = cJSON_Parse(plain);
free(plain);
root = cJSON_Parse(g_state.user_settings_json);
if (!root || !cJSON_IsObject(root)) {
cJSON_Delete(root);
return -1;
}
endpoint = cJSON_GetObjectItemCaseSensitive(root, "ai_endpoint");
api_key = cJSON_GetObjectItemCaseSensitive(root, "ai_api_key");
model = cJSON_GetObjectItemCaseSensitive(root, "ai_model");
if (cJSON_IsString(endpoint) && endpoint->valuestring) {
ai_set_string(&prefs->endpoint, endpoint->valuestring);
llm = cJSON_GetObjectItemCaseSensitive(root, "global_llm");
if (!cJSON_IsObject(llm)) {
cJSON_Delete(root);
return -1;
}
api_key = cJSON_GetObjectItemCaseSensitive(llm, "api_key");
model = cJSON_GetObjectItemCaseSensitive(llm, "model");
base_url = cJSON_GetObjectItemCaseSensitive(llm, "base_url");
legacy_endpoint = cJSON_GetObjectItemCaseSensitive(llm, "endpoint");
if (cJSON_IsString(api_key) && api_key->valuestring) {
ai_set_string(&prefs->api_key, api_key->valuestring);
}
@@ -299,51 +271,106 @@ static int ai_load_prefs(ai_prefs_t *prefs) {
ai_set_string(&prefs->model, model->valuestring);
}
endpoint = NULL;
if (cJSON_IsString(base_url) && base_url->valuestring) {
endpoint = ai_join_chat_completions(base_url->valuestring);
} else if (cJSON_IsString(legacy_endpoint) && legacy_endpoint->valuestring) {
endpoint = ai_strdup(legacy_endpoint->valuestring);
}
if (endpoint) {
ai_set_string(&prefs->endpoint, endpoint);
free(endpoint);
}
cJSON_Delete(root);
return 0;
}
static int ai_save_prefs(const ai_prefs_t *prefs) {
cJSON *root;
char *json;
char *cipher = NULL;
cJSON *llm;
cJSON *tags;
char *new_json;
char *cipher = NULL;
char *base_url = NULL;
int posted;
if (!prefs) {
return -1;
}
root = cJSON_CreateObject();
if (!root) {
root = g_state.user_settings_json
? cJSON_Parse(g_state.user_settings_json)
: cJSON_CreateObject();
if (!root || !cJSON_IsObject(root)) {
cJSON_Delete(root);
return -1;
}
cJSON_AddStringToObject(root, "ai_endpoint", prefs->endpoint ? prefs->endpoint : "");
cJSON_AddStringToObject(root, "ai_api_key", prefs->api_key ? prefs->api_key : "");
cJSON_AddStringToObject(root, "ai_model", prefs->model ? prefs->model : "");
if (!cJSON_GetObjectItemCaseSensitive(root, "v")) {
cJSON_AddNumberToObject(root, "v", 2);
}
cJSON_DeleteItemFromObjectCaseSensitive(root, "updatedAt");
cJSON_AddNumberToObject(root, "updatedAt", (double)time(NULL));
json = cJSON_PrintUnformatted(root);
if (!cJSON_GetObjectItemCaseSensitive(root, "global_zaps")) {
cJSON_AddObjectToObject(root, "global_zaps");
}
if (!cJSON_GetObjectItemCaseSensitive(root, "global_ui")) {
cJSON_AddObjectToObject(root, "global_ui");
}
if (!cJSON_GetObjectItemCaseSensitive(root, "global_relays")) {
cJSON_AddObjectToObject(root, "global_relays");
}
if (!cJSON_GetObjectItemCaseSensitive(root, "global_experimental")) {
cJSON_AddObjectToObject(root, "global_experimental");
}
llm = cJSON_GetObjectItemCaseSensitive(root, "global_llm");
if (!cJSON_IsObject(llm)) {
cJSON_DeleteItemFromObjectCaseSensitive(root, "global_llm");
llm = cJSON_AddObjectToObject(root, "global_llm");
}
if (!llm) {
cJSON_Delete(root);
return -1;
}
base_url = ai_base_url_from_endpoint(prefs->endpoint ? prefs->endpoint : "");
if (!base_url) {
cJSON_Delete(root);
return -1;
}
cJSON_DeleteItemFromObjectCaseSensitive(llm, "api_key");
cJSON_AddStringToObject(llm, "api_key", prefs->api_key ? prefs->api_key : "");
cJSON_DeleteItemFromObjectCaseSensitive(llm, "model");
cJSON_AddStringToObject(llm, "model", prefs->model ? prefs->model : "");
cJSON_DeleteItemFromObjectCaseSensitive(llm, "base_url");
cJSON_AddStringToObject(llm, "base_url", base_url);
new_json = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
if (!json) {
free(base_url);
if (!new_json) {
return -1;
}
if (ai_encrypt_for_self(json, &cipher) != 0) {
free(json);
free(g_state.user_settings_json);
g_state.user_settings_json = new_json;
if (state_nip44_self_encrypt(new_json, &cipher) != 0 || !cipher) {
return -1;
}
tags = ai_make_d_tag("prefs");
tags = ai_make_d_tag("user-settings");
if (!tags) {
free(json);
free(cipher);
return -1;
}
posted = publish_signed_event_with_report("Prefs", 30078, cipher, tags, 8000);
posted = publish_signed_event_with_report("UserSettings", 30078, cipher, tags, 8000);
cJSON_Delete(tags);
free(json);
free(cipher);
return (posted < 0) ? -1 : 0;

View File

@@ -3,6 +3,7 @@
#include "net.h"
#include "publish.h"
#include "editor.h"
#include "signer.h"
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include "../resources/nostr_core_lib/nostr_core/nip004.h"
@@ -49,63 +50,13 @@ static void diary_free_entries(diary_entry_t *entries, int count) {
}
static int diary_encrypt_for_self(const char *plaintext, char **cipher_out) {
unsigned char priv[32];
unsigned char pub[32];
char *out;
size_t out_sz;
if (!plaintext || !cipher_out) {
return -1;
}
*cipher_out = NULL;
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
return -1;
}
if (nostr_hex_to_bytes(g_state.npub_hex, pub, 32) != 0) {
return -1;
}
out_sz = (strlen(plaintext) * 4U) + 256U;
out = (char *)malloc(out_sz);
if (!out) {
return -1;
}
if (nostr_nip04_encrypt(priv, pub, plaintext, out, out_sz) != 0) {
free(out);
return -1;
}
*cipher_out = out;
return 0;
return signer_nip44_self_encrypt(plaintext, cipher_out);
}
static char *diary_decrypt_from_pub(const char *sender_pub_hex, const char *ciphertext) {
unsigned char priv[32];
unsigned char sender_pub[32];
size_t out_sz;
char *out;
char *out = NULL;
if (!sender_pub_hex || !ciphertext) {
return NULL;
}
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
return NULL;
}
if (nostr_hex_to_bytes(sender_pub_hex, sender_pub, 32) != 0) {
return NULL;
}
out_sz = strlen(ciphertext) + 4096U;
out = (char *)malloc(out_sz);
if (!out) {
return NULL;
}
if (nostr_nip04_decrypt(priv, sender_pub, ciphertext, out, out_sz) != 0) {
free(out);
if (signer_nip44_decrypt(sender_pub_hex, ciphertext, &out) != 0) {
return NULL;
}

View File

@@ -1,6 +1,7 @@
#include "tui.h"
#include "state.h"
#include "net.h"
#include "signer.h"
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include "../resources/nostr_core_lib/nostr_core/nip001.h"
@@ -165,8 +166,8 @@ static void menu_dm_send(void) {
return;
}
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
tui_print("Invalid private key in state.");
if (signer_get_local_private_key(priv) != 0) {
tui_print("Private key unavailable for DM send.");
tui_get_line(">", hold, (int)sizeof(hold));
return;
}
@@ -228,33 +229,9 @@ static void menu_dm_send(void) {
}
static char *dm_try_decrypt_kind4(const char *sender_pub_hex, const char *ciphertext) {
unsigned char priv[32];
unsigned char sender_pub[32];
size_t out_sz;
char *out;
if (!sender_pub_hex || !ciphertext) {
return NULL;
}
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
return NULL;
}
if (nostr_hex_to_bytes(sender_pub_hex, sender_pub, 32) != 0) {
return NULL;
}
out_sz = strlen(ciphertext) + 1024U;
out = (char *)malloc(out_sz);
if (!out) {
return NULL;
}
if (nostr_nip04_decrypt(priv, sender_pub, ciphertext, out, out_sz) != 0) {
free(out);
return NULL;
}
char *out = NULL;
if (!sender_pub_hex || !ciphertext) return NULL;
if (signer_nip04_decrypt(sender_pub_hex, ciphertext, &out) != 0) return NULL;
return out;
}
@@ -344,7 +321,7 @@ static void dm_collect_nip17_kind1059(dm_item_t **items, int *items_count) {
return;
}
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
if (signer_get_local_private_key(priv) != 0) {
return;
}

View File

@@ -3,6 +3,8 @@
#include "net.h"
#include "publish.h"
#include "signer.h"
#include "nsigner_client.h"
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include "../resources/nostr_core_lib/nostr_core/nip006.h"
@@ -167,6 +169,84 @@ void menu_login(void) {
exit(0);
}
if (strcmp(input, "s") == 0 || strcmp(input, "S") == 0) {
char **names = NULL;
int name_count = 0;
char pubkey_hex[65] = {0};
int selected = 0;
tui_print("Searching for running n_signer instances...");
if (nsigner_list(&names, &name_count) != 0 || name_count == 0) {
tui_print("No running n_signer found. Start `nsigner` and try again.");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
if (name_count == 1) {
tui_print("Found: @%s", names[0]);
selected = 0;
} else {
int idx;
char idxbuf[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;
if (selected < 0 || selected >= name_count) {
selected = 0;
}
}
tui_print("Waiting for n_signer approval...");
if (nsigner_get_public_key(names[selected], "main", 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++) {
free(names[i]);
}
free(names);
tui_get_line(">", input, (int)sizeof(input));
continue;
}
signer_init_nsigner(names[selected]);
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 (@%s)", names[selected]);
tui_print("npub: %s", g_state.npub_bech32);
{
int i;
for (i = 0; i < name_count; i++) {
free(names[i]);
}
free(names);
}
(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];
@@ -302,7 +382,7 @@ void menu_login(void) {
}
}
tui_print("Invalid login input. Enter 12 words, nsec, 64-hex, [n], or [q].");
tui_print("Invalid login input. Enter 12 words, nsec, 64-hex, [s], [n], or [q].");
tui_get_line(">", input, (int)sizeof(input));
}
}

View File

@@ -3,6 +3,7 @@
#include "net.h"
#include "publish.h"
#include "editor.h"
#include "signer.h"
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include "../resources/nostr_core_lib/nostr_core/nip004.h"
@@ -105,30 +106,9 @@ static char *posts_find_tag_value(cJSON *tags, const char *tag_name) {
}
static char *posts_try_decrypt_nip04(const char *sender_pub_hex, const char *ciphertext) {
unsigned char priv[32];
unsigned char sender_pub[32];
size_t out_sz;
char *out;
char *out = NULL;
if (!sender_pub_hex || !ciphertext) {
return NULL;
}
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
return NULL;
}
if (nostr_hex_to_bytes(sender_pub_hex, sender_pub, 32) != 0) {
return NULL;
}
out_sz = strlen(ciphertext) + 1024U;
out = (char *)malloc(out_sz);
if (!out) {
return NULL;
}
if (nostr_nip04_decrypt(priv, sender_pub, ciphertext, out, out_sz) != 0) {
free(out);
if (signer_nip04_decrypt(sender_pub_hex, ciphertext, &out) != 0) {
return NULL;
}
@@ -136,37 +116,7 @@ static char *posts_try_decrypt_nip04(const char *sender_pub_hex, const char *cip
}
static int posts_encrypt_nip04_for_self(const char *plaintext, char **cipher_out) {
unsigned char priv[32];
unsigned char pub[32];
size_t out_sz;
char *out;
if (!plaintext || !cipher_out) {
return -1;
}
*cipher_out = NULL;
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
return -1;
}
if (nostr_hex_to_bytes(g_state.npub_hex, pub, 32) != 0) {
return -1;
}
out_sz = (strlen(plaintext) * 4U) + 256U;
out = (char *)malloc(out_sz);
if (!out) {
return -1;
}
if (nostr_nip04_encrypt(priv, pub, plaintext, out, out_sz) != 0) {
free(out);
return -1;
}
*cipher_out = out;
return 0;
return signer_nip04_encrypt(g_state.npub_hex, plaintext, cipher_out);
}
static int posts_push_item(post_item_t **items, int *count, post_item_t *src) {

View File

@@ -1,6 +1,7 @@
#include "tui.h"
#include "state.h"
#include "net.h"
#include "signer.h"
#include "publish.h"
@@ -26,8 +27,12 @@ void menu_profile(void) {
tui_clear_screen();
tui_print("PROFILE\n");
tui_print("nsecHex: %s", g_state.nsec_hex);
tui_print("nsec: %s", g_state.nsec_bech32);
if (g_signer.kind == NT_SIGNER_NSIGNER) {
tui_print("Signer: n_signer (@%s)", g_signer.socket_name);
} else {
tui_print("nsecHex: %s", g_state.nsec_hex);
tui_print("nsec: %s", g_state.nsec_bech32);
}
tui_print("npubHex: %s", g_state.npub_hex);
tui_print("npub: %s", g_state.npub_bech32);
tui_print("");

View File

@@ -2,6 +2,7 @@
#include "state.h"
#include "net.h"
#include "publish.h"
#include "signer.h"
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include "../resources/nostr_core_lib/nostr_core/nip004.h"
@@ -86,64 +87,13 @@ static int todo_parse_index_1based(const char *input, int count) {
}
static int todo_encrypt_for_self(const char *plaintext, char **cipher_out) {
unsigned char priv[32];
unsigned char pub[32];
size_t out_sz;
char *out;
if (!plaintext || !cipher_out) {
return -1;
}
*cipher_out = NULL;
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
return -1;
}
if (nostr_hex_to_bytes(g_state.npub_hex, pub, 32) != 0) {
return -1;
}
out_sz = (strlen(plaintext) * 4U) + 256U;
out = (char *)malloc(out_sz);
if (!out) {
return -1;
}
if (nostr_nip04_encrypt(priv, pub, plaintext, out, out_sz) != 0) {
free(out);
return -1;
}
*cipher_out = out;
return 0;
return signer_nip44_self_encrypt(plaintext, cipher_out);
}
static char *todo_decrypt_from_pub(const char *sender_pub_hex, const char *ciphertext) {
unsigned char priv[32];
unsigned char sender_pub[32];
size_t out_sz;
char *out;
char *out = NULL;
if (!sender_pub_hex || !ciphertext) {
return NULL;
}
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
return NULL;
}
if (nostr_hex_to_bytes(sender_pub_hex, sender_pub, 32) != 0) {
return NULL;
}
out_sz = strlen(ciphertext) + 2048U;
out = (char *)malloc(out_sz);
if (!out) {
return NULL;
}
if (nostr_nip04_decrypt(priv, sender_pub, ciphertext, out, out_sz) != 0) {
free(out);
if (signer_nip44_decrypt(sender_pub_hex, ciphertext, &out) != 0) {
return NULL;
}

View File

@@ -2,6 +2,7 @@
#include "db.h"
#include "state.h"
#include "signer.h"
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include "../resources/nostr_core_lib/nostr_core/nip042.h"
@@ -244,7 +245,7 @@ static int nt_send_nip42_auth(nostr_ws_client_t *client,
*auth_event_id_out = NULL;
if (nostr_hex_to_bytes(g_state.nsec_hex, private_key, sizeof(private_key)) != 0) {
if (signer_get_local_private_key(private_key) != 0) {
return -1;
}

467
src/nsigner_client.c Normal file
View File

@@ -0,0 +1,467 @@
#include "nsigner_client.h"
#include <arpa/inet.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <unistd.h>
static int write_all(int fd, const void *buf, size_t len) {
const unsigned char *p = (const unsigned char *)buf;
while (len > 0) {
ssize_t n = write(fd, p, len);
if (n < 0) {
if (errno == EINTR) {
continue;
}
return -1;
}
if (n == 0) {
return -1;
}
p += (size_t)n;
len -= (size_t)n;
}
return 0;
}
static int read_all(int fd, void *buf, size_t len) {
unsigned char *p = (unsigned char *)buf;
while (len > 0) {
ssize_t n = read(fd, p, len);
if (n < 0) {
if (errno == EINTR) {
continue;
}
return -1;
}
if (n == 0) {
return -1;
}
p += (size_t)n;
len -= (size_t)n;
}
return 0;
}
static int set_recv_timeout_ms(int fd, int timeout_ms) {
struct timeval tv;
if (timeout_ms <= 0) {
return 0;
}
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
return setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}
static int nsigner_error_code_from_response(const cJSON *response) {
const cJSON *error_item;
const char *name = NULL;
error_item = cJSON_GetObjectItemCaseSensitive((cJSON *)response, "error");
if (!error_item || cJSON_IsNull(error_item)) {
return NT_NSIGNER_OK;
}
if (cJSON_IsString(error_item) && error_item->valuestring) {
name = error_item->valuestring;
} else if (cJSON_IsObject(error_item)) {
const cJSON *message_item = cJSON_GetObjectItemCaseSensitive((cJSON *)error_item, "message");
if (cJSON_IsString(message_item) && message_item->valuestring) {
name = message_item->valuestring;
}
}
if (name && strcmp(name, "approval_denied") == 0) {
return NT_NSIGNER_E_DENIED;
}
if (name && strcmp(name, "unauthorized") == 0) {
return NT_NSIGNER_E_UNAUTH;
}
return NT_NSIGNER_E_ERROR;
}
int nsigner_list(char ***names_out, int *count_out) {
FILE *fp;
char line[512];
char **names = NULL;
int count = 0;
int cap = 0;
if (!names_out || !count_out) {
return NT_NSIGNER_E_IO;
}
*names_out = NULL;
*count_out = 0;
fp = fopen("/proc/net/unix", "r");
if (!fp) {
return NT_NSIGNER_E_IO;
}
while (fgets(line, sizeof(line), fp)) {
char *at = strchr(line, '@');
if (!at) {
continue;
}
if (strncmp(at + 1, "nsigner", 7) != 0) {
continue;
}
char *end = at + 1;
size_t nlen;
char *name_copy;
int exists = 0;
while (*end && *end != '\n' && *end != ' ' && *end != '\t') {
end++;
}
nlen = (size_t)(end - (at + 1));
if (nlen == 0) {
continue;
}
for (int i = 0; i < count; i++) {
if (strncmp(names[i], at + 1, nlen) == 0 && names[i][nlen] == '\0') {
exists = 1;
break;
}
}
if (exists) {
continue;
}
name_copy = (char *)malloc(nlen + 1);
if (!name_copy) {
for (int i = 0; i < count; i++) {
free(names[i]);
}
free(names);
fclose(fp);
return NT_NSIGNER_E_IO;
}
memcpy(name_copy, at + 1, nlen);
name_copy[nlen] = '\0';
if (count == cap) {
int new_cap = (cap == 0) ? 4 : (cap * 2);
char **new_names = (char **)realloc(names, (size_t)new_cap * sizeof(char *));
if (!new_names) {
free(name_copy);
for (int i = 0; i < count; i++) {
free(names[i]);
}
free(names);
fclose(fp);
return NT_NSIGNER_E_IO;
}
names = new_names;
cap = new_cap;
}
names[count++] = name_copy;
}
fclose(fp);
*names_out = names;
*count_out = count;
return NT_NSIGNER_OK;
}
int nsigner_connect(const char *name) {
int fd;
struct sockaddr_un addr;
struct timeval tv;
size_t name_len;
socklen_t addr_len;
if (!name || !*name) {
return -1;
}
name_len = strlen(name);
if (name_len >= sizeof(addr.sun_path)) {
return -1;
}
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
return -1;
}
tv.tv_sec = 5;
tv.tv_usec = 0;
(void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
addr.sun_path[0] = '\0';
memcpy(&addr.sun_path[1], name, name_len);
addr_len = (socklen_t)(offsetof(struct sockaddr_un, sun_path) + 1 + name_len);
if (connect(fd, (struct sockaddr *)&addr, addr_len) != 0) {
close(fd);
return -1;
}
return fd;
}
int nsigner_rpc(int fd, const cJSON *request, cJSON **response_out, int read_timeout_ms) {
char *req_json = NULL;
uint32_t req_len;
uint32_t req_be;
uint32_t resp_be;
uint32_t resp_len;
char *resp_buf = NULL;
cJSON *response = NULL;
int err_code;
if (fd < 0 || !request || !response_out) {
return NT_NSIGNER_E_IO;
}
*response_out = NULL;
req_json = cJSON_PrintUnformatted((cJSON *)request);
if (!req_json) {
return NT_NSIGNER_E_IO;
}
req_len = (uint32_t)strlen(req_json);
req_be = htonl(req_len);
if (write_all(fd, &req_be, sizeof(req_be)) != 0 ||
write_all(fd, req_json, req_len) != 0) {
free(req_json);
return NT_NSIGNER_E_IO;
}
free(req_json);
if (set_recv_timeout_ms(fd, read_timeout_ms) != 0) {
return NT_NSIGNER_E_IO;
}
if (read_all(fd, &resp_be, sizeof(resp_be)) != 0) {
return NT_NSIGNER_E_IO;
}
resp_len = ntohl(resp_be);
if (resp_len == 0 || resp_len > (16U * 1024U * 1024U)) {
return NT_NSIGNER_E_PARSE;
}
resp_buf = (char *)malloc((size_t)resp_len + 1U);
if (!resp_buf) {
return NT_NSIGNER_E_IO;
}
if (read_all(fd, resp_buf, resp_len) != 0) {
free(resp_buf);
return NT_NSIGNER_E_IO;
}
resp_buf[resp_len] = '\0';
response = cJSON_Parse(resp_buf);
free(resp_buf);
if (!response || !cJSON_IsObject(response)) {
cJSON_Delete(response);
return NT_NSIGNER_E_PARSE;
}
err_code = nsigner_error_code_from_response(response);
if (err_code != NT_NSIGNER_OK) {
cJSON_Delete(response);
return err_code;
}
if (!cJSON_GetObjectItemCaseSensitive(response, "result")) {
cJSON_Delete(response);
return NT_NSIGNER_E_ERROR;
}
*response_out = response;
return NT_NSIGNER_OK;
}
static int nsigner_call_string_method(const char *socket_name,
const char *role,
const char *method,
const char **args,
int arg_count,
char **result_out) {
int fd = -1;
cJSON *request = NULL;
cJSON *params = NULL;
cJSON *role_obj = NULL;
cJSON *response = NULL;
cJSON *result_item;
int rc;
if (!socket_name || !method || !result_out || arg_count < 0) {
return NT_NSIGNER_E_IO;
}
*result_out = NULL;
fd = nsigner_connect(socket_name);
if (fd < 0) {
return NT_NSIGNER_E_IO;
}
request = cJSON_CreateObject();
params = cJSON_CreateArray();
if (!request || !params) {
cJSON_Delete(request);
cJSON_Delete(params);
close(fd);
return NT_NSIGNER_E_IO;
}
cJSON_AddStringToObject(request, "id", "1");
cJSON_AddStringToObject(request, "method", method);
for (int i = 0; i < arg_count; i++) {
if (!args || !args[i]) {
cJSON_Delete(request);
cJSON_Delete(params);
close(fd);
return NT_NSIGNER_E_IO;
}
cJSON_AddItemToArray(params, cJSON_CreateString(args[i]));
}
role_obj = cJSON_CreateObject();
if (!role_obj) {
cJSON_Delete(request);
cJSON_Delete(params);
close(fd);
return NT_NSIGNER_E_IO;
}
cJSON_AddStringToObject(role_obj, "role", (role && *role) ? role : "main");
cJSON_AddItemToArray(params, role_obj);
cJSON_AddItemToObject(request, "params", params);
rc = nsigner_rpc(fd, request, &response, 60000);
cJSON_Delete(request);
close(fd);
if (rc != NT_NSIGNER_OK) {
return rc;
}
result_item = cJSON_GetObjectItemCaseSensitive(response, "result");
if (!cJSON_IsString(result_item) || !result_item->valuestring) {
cJSON_Delete(response);
return NT_NSIGNER_E_PARSE;
}
*result_out = strdup(result_item->valuestring);
cJSON_Delete(response);
return *result_out ? NT_NSIGNER_OK : NT_NSIGNER_E_IO;
}
int nsigner_get_public_key(const char *socket_name, const char *role, char *out_hex_65) {
char *result = NULL;
int rc;
size_t len;
if (!out_hex_65) {
return NT_NSIGNER_E_IO;
}
rc = nsigner_call_string_method(socket_name, role, "get_public_key", NULL, 0, &result);
if (rc != NT_NSIGNER_OK) {
return rc;
}
len = strlen(result);
if (len != 64) {
free(result);
return NT_NSIGNER_E_PARSE;
}
memcpy(out_hex_65, result, len + 1);
free(result);
return NT_NSIGNER_OK;
}
int nsigner_sign_event(const char *socket_name, const char *role,
const char *unsigned_event_json, char **signed_event_json_out) {
const char *args[1];
if (!unsigned_event_json || !signed_event_json_out) {
return NT_NSIGNER_E_IO;
}
args[0] = unsigned_event_json;
return nsigner_call_string_method(socket_name, role, "sign_event", args, 1, signed_event_json_out);
}
int nsigner_nip04_encrypt(const char *socket_name, const char *role,
const char *peer_pub_hex, const char *plaintext, char **cipher_out) {
const char *args[2];
if (!peer_pub_hex || !plaintext || !cipher_out) {
return NT_NSIGNER_E_IO;
}
args[0] = peer_pub_hex;
args[1] = plaintext;
return nsigner_call_string_method(socket_name, role, "nip04_encrypt", args, 2, cipher_out);
}
int nsigner_nip04_decrypt(const char *socket_name, const char *role,
const char *peer_pub_hex, const char *ciphertext, char **plain_out) {
const char *args[2];
if (!peer_pub_hex || !ciphertext || !plain_out) {
return NT_NSIGNER_E_IO;
}
args[0] = peer_pub_hex;
args[1] = ciphertext;
return nsigner_call_string_method(socket_name, role, "nip04_decrypt", args, 2, plain_out);
}
int nsigner_nip44_encrypt(const char *socket_name, const char *role,
const char *peer_pub_hex, const char *plaintext, char **cipher_out) {
const char *args[2];
if (!peer_pub_hex || !plaintext || !cipher_out) {
return NT_NSIGNER_E_IO;
}
args[0] = peer_pub_hex;
args[1] = plaintext;
return nsigner_call_string_method(socket_name, role, "nip44_encrypt", args, 2, cipher_out);
}
int nsigner_nip44_decrypt(const char *socket_name, const char *role,
const char *peer_pub_hex, const char *ciphertext, char **plain_out) {
const char *args[2];
if (!peer_pub_hex || !ciphertext || !plain_out) {
return NT_NSIGNER_E_IO;
}
args[0] = peer_pub_hex;
args[1] = ciphertext;
return nsigner_call_string_method(socket_name, role, "nip44_decrypt", args, 2, plain_out);
}

View File

@@ -3,9 +3,7 @@
#include "net.h"
#include "state.h"
#include "tui.h"
#include "../resources/nostr_core_lib/nostr_core/nip001.h"
#include "../resources/nostr_core_lib/nostr_core/utils.h"
#include "signer.h"
#include <stdio.h>
#include <stdlib.h>
@@ -16,9 +14,6 @@ int publish_signed_event_with_report(const char *label,
const char *content,
cJSON *tags,
int timeout_ms) {
unsigned char priv[32];
cJSON *tags_copy = NULL;
cJSON *event = NULL;
char *event_json = NULL;
const char **write_relays;
int relay_count = 0;
@@ -31,31 +26,8 @@ int publish_signed_event_with_report(const char *label,
label = "Event";
}
if (nostr_hex_to_bytes(g_state.nsec_hex, priv, 32) != 0) {
tui_print("%s: invalid private key in state.", label);
return -1;
}
tags_copy = tags ? cJSON_Duplicate(tags, 1) : cJSON_CreateArray();
if (!tags_copy) {
tui_print("%s: failed to prepare tags.", label);
return -1;
}
event = nostr_create_and_sign_event(kind,
content ? content : "",
tags_copy,
priv,
time(NULL));
cJSON_Delete(tags_copy);
if (!event) {
tui_print("%s: failed to create signed event.", label);
return -1;
}
event_json = cJSON_PrintUnformatted(event);
cJSON_Delete(event);
if (!event_json) {
if (signer_create_and_sign(kind, content ? content : "", tags, time(NULL), &event_json) != 0 ||
!event_json) {
tui_print("%s: failed to serialize event.", label);
return -1;
}

406
src/signer.c Normal file
View File

@@ -0,0 +1,406 @@
#include "signer.h"
#include "state.h"
#include "nsigner_client.h"
#include "../resources/nostr_core_lib/nostr_core/nip001.h"
#include "../resources/nostr_core_lib/nostr_core/nip004.h"
#include "../resources/nostr_core_lib/nostr_core/nip044.h"
#include "../resources/nostr_core_lib/nostr_core/utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
nt_signer_t g_signer = {
.kind = NT_SIGNER_LOCAL,
.socket_name = {0},
.role = "main",
};
static int signer_local_private_key(unsigned char out_priv[32]) {
if (!out_priv) {
return -1;
}
if (nostr_hex_to_bytes(g_state.nsec_hex, out_priv, 32) != 0) {
return -1;
}
return 0;
}
void signer_init(void) {
g_signer.kind = NT_SIGNER_LOCAL;
g_signer.socket_name[0] = '\0';
snprintf(g_signer.role, sizeof(g_signer.role), "%s", "main");
}
int signer_init_local(void) {
signer_init();
return 0;
}
int signer_init_nsigner(const char *socket_name) {
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");
return 0;
}
int signer_get_local_private_key(unsigned char out_priv[32]) {
if (!out_priv) {
return -1;
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
return signer_local_private_key(out_priv);
}
int signer_create_and_sign(int kind,
const char *content,
cJSON *tags,
time_t timestamp,
char **signed_event_json_out) {
unsigned char priv[32];
cJSON *tags_copy;
cJSON *event;
if (!signed_event_json_out) {
return -1;
}
*signed_event_json_out = NULL;
if (g_signer.kind == NT_SIGNER_NSIGNER) {
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_sign_event(g_signer.socket_name,
g_signer.role,
unsigned_json,
signed_event_json_out);
free(unsigned_json);
cJSON_Delete(unsigned_event);
return rc;
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
if (signer_local_private_key(priv) != 0) {
return -1;
}
tags_copy = tags ? cJSON_Duplicate(tags, 1) : cJSON_CreateArray();
if (!tags_copy) {
return -1;
}
event = nostr_create_and_sign_event(kind,
content ? content : "",
tags_copy,
priv,
timestamp);
cJSON_Delete(tags_copy);
if (!event) {
return -1;
}
*signed_event_json_out = cJSON_PrintUnformatted(event);
cJSON_Delete(event);
return *signed_event_json_out ? 0 : -1;
}
int signer_sign_event_json(const char *unsigned_event_json, char **signed_event_json_out) {
unsigned char priv[32];
cJSON *unsigned_event;
cJSON *kind_item;
cJSON *content_item;
cJSON *tags_item;
cJSON *created_at_item;
cJSON *tags_copy;
cJSON *signed_event;
int kind;
const char *content;
time_t created_at;
if (!unsigned_event_json || !signed_event_json_out) {
return -1;
}
*signed_event_json_out = NULL;
if (g_signer.kind == NT_SIGNER_NSIGNER) {
return nsigner_sign_event(g_signer.socket_name,
g_signer.role,
unsigned_event_json,
signed_event_json_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
if (signer_local_private_key(priv) != 0) {
return -1;
}
unsigned_event = cJSON_Parse(unsigned_event_json);
if (!unsigned_event || !cJSON_IsObject(unsigned_event)) {
cJSON_Delete(unsigned_event);
return -1;
}
kind_item = cJSON_GetObjectItemCaseSensitive(unsigned_event, "kind");
content_item = cJSON_GetObjectItemCaseSensitive(unsigned_event, "content");
tags_item = cJSON_GetObjectItemCaseSensitive(unsigned_event, "tags");
created_at_item = cJSON_GetObjectItemCaseSensitive(unsigned_event, "created_at");
if (!cJSON_IsNumber(kind_item) || !cJSON_IsArray(tags_item) || !cJSON_IsNumber(created_at_item)) {
cJSON_Delete(unsigned_event);
return -1;
}
kind = (int)kind_item->valuedouble;
content = (cJSON_IsString(content_item) && content_item->valuestring) ? content_item->valuestring : "";
created_at = (time_t)created_at_item->valuedouble;
tags_copy = cJSON_Duplicate(tags_item, 1);
cJSON_Delete(unsigned_event);
if (!tags_copy) {
return -1;
}
signed_event = nostr_create_and_sign_event(kind, content, tags_copy, priv, created_at);
cJSON_Delete(tags_copy);
if (!signed_event) {
return -1;
}
*signed_event_json_out = cJSON_PrintUnformatted(signed_event);
cJSON_Delete(signed_event);
return *signed_event_json_out ? 0 : -1;
}
int signer_nip04_encrypt(const char *peer_pub_hex, const char *plaintext, char **cipher_out) {
unsigned char priv[32];
unsigned char peer_pub[32];
size_t out_sz;
if (!peer_pub_hex || !plaintext || !cipher_out) {
return -1;
}
*cipher_out = NULL;
if (g_signer.kind == NT_SIGNER_NSIGNER) {
return nsigner_nip04_encrypt(g_signer.socket_name,
g_signer.role,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
if (signer_local_private_key(priv) != 0 ||
nostr_hex_to_bytes(peer_pub_hex, peer_pub, 32) != 0) {
return -1;
}
out_sz = (strlen(plaintext) * 2U) + 1024U;
*cipher_out = (char *)malloc(out_sz);
if (!*cipher_out) {
return -1;
}
if (nostr_nip04_encrypt(priv, peer_pub, plaintext, *cipher_out, out_sz) != 0) {
free(*cipher_out);
*cipher_out = NULL;
return -1;
}
return 0;
}
int signer_nip04_decrypt(const char *peer_pub_hex, const char *ciphertext, char **plain_out) {
unsigned char priv[32];
unsigned char peer_pub[32];
size_t out_sz;
if (!peer_pub_hex || !ciphertext || !plain_out) {
return -1;
}
*plain_out = NULL;
if (g_signer.kind == NT_SIGNER_NSIGNER) {
return nsigner_nip04_decrypt(g_signer.socket_name,
g_signer.role,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
if (signer_local_private_key(priv) != 0 ||
nostr_hex_to_bytes(peer_pub_hex, peer_pub, 32) != 0) {
return -1;
}
out_sz = strlen(ciphertext) + 1024U;
*plain_out = (char *)malloc(out_sz);
if (!*plain_out) {
return -1;
}
if (nostr_nip04_decrypt(priv, peer_pub, ciphertext, *plain_out, out_sz) != 0) {
free(*plain_out);
*plain_out = NULL;
return -1;
}
return 0;
}
int signer_nip44_encrypt(const char *peer_pub_hex, const char *plaintext, char **cipher_out) {
unsigned char priv[32];
unsigned char peer_pub[32];
size_t out_sz;
if (!peer_pub_hex || !plaintext || !cipher_out) {
return -1;
}
*cipher_out = NULL;
if (g_signer.kind == NT_SIGNER_NSIGNER) {
return nsigner_nip44_encrypt(g_signer.socket_name,
g_signer.role,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
if (signer_local_private_key(priv) != 0 ||
nostr_hex_to_bytes(peer_pub_hex, peer_pub, 32) != 0) {
return -1;
}
out_sz = (strlen(plaintext) * 2U) + 2048U;
*cipher_out = (char *)malloc(out_sz);
if (!*cipher_out) {
return -1;
}
if (nostr_nip44_encrypt(priv, peer_pub, plaintext, *cipher_out, out_sz) != 0) {
free(*cipher_out);
*cipher_out = NULL;
return -1;
}
return 0;
}
int signer_nip44_decrypt(const char *peer_pub_hex, const char *ciphertext, char **plain_out) {
unsigned char priv[32];
unsigned char peer_pub[32];
size_t out_sz;
if (!peer_pub_hex || !ciphertext || !plain_out) {
return -1;
}
*plain_out = NULL;
if (g_signer.kind == NT_SIGNER_NSIGNER) {
return nsigner_nip44_decrypt(g_signer.socket_name,
g_signer.role,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
if (signer_local_private_key(priv) != 0 ||
nostr_hex_to_bytes(peer_pub_hex, peer_pub, 32) != 0) {
return -1;
}
out_sz = strlen(ciphertext) + 4096U;
*plain_out = (char *)malloc(out_sz);
if (!*plain_out) {
return -1;
}
if (nostr_nip44_decrypt(priv, peer_pub, ciphertext, *plain_out, out_sz) != 0) {
free(*plain_out);
*plain_out = NULL;
return -1;
}
return 0;
}
int signer_nip44_self_encrypt(const char *plaintext, char **cipher_out) {
if (!plaintext || !cipher_out) {
return -1;
}
return signer_nip44_encrypt(g_state.npub_hex, plaintext, cipher_out);
}
int signer_nip44_self_decrypt(const char *sender_pub_hex, const char *ciphertext, char **plain_out) {
if (!sender_pub_hex || !ciphertext || !plain_out) {
return -1;
}
return signer_nip44_decrypt(sender_pub_hex, ciphertext, plain_out);
}

View File

@@ -3,11 +3,17 @@
#include "db.h"
#include "net.h"
#include "publish.h"
#include "signer.h"
#include "../resources/nostr_core_lib/cjson/cJSON.h"
#include "../resources/nostr_core_lib/nostr_core/nip044.h"
#include "../resources/nostr_core_lib/nostr_core/utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
nt_state_t g_state;
@@ -134,9 +140,8 @@ void state_cleanup(void) {
free(g_state.kind17375_json);
g_state.kind17375_json = NULL;
state_free_string_array(g_state.kind30078_events, g_state.kind30078_count);
g_state.kind30078_events = NULL;
g_state.kind30078_count = 0;
free(g_state.user_settings_json);
g_state.user_settings_json = NULL;
state_free_string_array(g_state.read_relays, g_state.read_relay_count);
g_state.read_relays = NULL;
@@ -279,29 +284,18 @@ static int state_pick_latest_json(char **slot, long long *slot_created, const ch
return 0;
}
static int state_push_event_copy(char ***arr, int *count, const char *event_json) {
char **next;
char *copy;
int state_nip44_self_encrypt(const char *plaintext, char **cipher_out) {
return signer_nip44_self_encrypt(plaintext, cipher_out);
}
if (!arr || !count || !event_json) {
return -1;
char *state_nip44_self_decrypt(const char *sender_pub_hex, const char *ciphertext) {
char *out = NULL;
if (signer_nip44_self_decrypt(sender_pub_hex, ciphertext, &out) != 0) {
return NULL;
}
copy = state_strdup_local(event_json);
if (!copy) {
return -1;
}
next = (char **)realloc(*arr, (size_t)(*count + 1) * sizeof(char *));
if (!next) {
free(copy);
return -1;
}
*arr = next;
(*arr)[*count] = copy;
(*count)++;
return 0;
return out;
}
int state_load_user_info(void) {
@@ -314,6 +308,7 @@ int state_load_user_info(void) {
char **events = NULL;
int event_count = 0;
int i;
char *settings_event_json = NULL;
char *kind0_event = NULL;
char *kind3_event = NULL;
@@ -339,9 +334,8 @@ int state_load_user_info(void) {
g_state.kind10096_json = NULL;
free(g_state.kind17375_json);
g_state.kind17375_json = NULL;
state_free_string_array(g_state.kind30078_events, g_state.kind30078_count);
g_state.kind30078_events = NULL;
g_state.kind30078_count = 0;
free(g_state.user_settings_json);
g_state.user_settings_json = NULL;
fprintf(stderr, "Attempting to fetch kind 10002 relays from bootstrap relays\n");
snprintf(filter,
@@ -363,10 +357,10 @@ int state_load_user_info(void) {
state_parse_relay_list();
relays = state_get_read_relays(&relay_count);
fprintf(stderr, "Attempting to fetch kinds [0,3,10096,17375,30078] from user relays\n");
fprintf(stderr, "Attempting to fetch kinds [0,3,10096,17375] from user relays\n");
snprintf(filter,
sizeof(filter),
"{\"authors\":[\"%s\"],\"kinds\":[0,3,10096,17375,30078],\"limit\":200}",
"{\"authors\":[\"%s\"],\"kinds\":[0,3,10096,17375],\"limit\":200}",
g_state.npub_hex);
if (nt_query_sync_verbose(filter,
@@ -401,8 +395,6 @@ int state_load_user_info(void) {
(void)state_pick_latest_json(&kind10096_event, &kind10096_created, events[i]);
} else if (kind == 17375) {
(void)state_pick_latest_json(&kind17375_event, &kind17375_created, events[i]);
} else if (kind == 30078) {
(void)state_push_event_copy(&g_state.kind30078_events, &g_state.kind30078_count, events[i]);
}
cJSON_Delete(ev);
@@ -432,14 +424,43 @@ int state_load_user_info(void) {
free(kind10096_event);
free(kind17375_event);
fprintf(stderr, "Attempting to fetch user-settings (kind 30078, d=user-settings)\n");
snprintf(filter,
sizeof(filter),
"{\"authors\":[\"%s\"],\"kinds\":[30078],\"#d\":[\"user-settings\"],\"limit\":1}",
g_state.npub_hex);
if (nt_get_first_verbose(filter,
relays,
relay_count,
6000,
"Phase 3 (user-settings)",
1,
&settings_event_json) == 0 && settings_event_json) {
cJSON *ev = cJSON_Parse(settings_event_json);
if (ev) {
cJSON *content_item = cJSON_GetObjectItemCaseSensitive(ev, "content");
cJSON *pubkey_item = cJSON_GetObjectItemCaseSensitive(ev, "pubkey");
if (cJSON_IsString(content_item) && cJSON_IsString(pubkey_item)) {
char *decrypted = state_nip44_self_decrypt(pubkey_item->valuestring,
content_item->valuestring);
if (decrypted) {
g_state.user_settings_json = decrypted;
}
}
cJSON_Delete(ev);
}
free(settings_event_json);
}
fprintf(stderr,
"Load summary: 10002=%s 0=%s 3=%s 10096=%s 17375=%s 30078=%d\n",
"Load summary: 10002=%s 0=%s 3=%s 10096=%s 17375=%s user_settings=%s\n",
g_state.kind10002_json ? "ok" : "missing",
(g_state.kind0_json && strcmp(g_state.kind0_json, "{}") != 0) ? "ok" : "missing",
(g_state.kind3_json && strstr(g_state.kind3_json, "\"tags\"") != NULL) ? "ok" : "missing",
(g_state.kind10096_json && strcmp(g_state.kind10096_json, "{}") != 0) ? "ok" : "missing",
(g_state.kind17375_json && strcmp(g_state.kind17375_json, "{}") != 0) ? "ok" : "missing",
g_state.kind30078_count);
g_state.user_settings_json ? "ok" : "missing");
snprintf(g_state.user_name, sizeof(g_state.user_name), "%s", "not logged in");
if (g_state.kind0_json) {