Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9911dd4a6 | ||
|
|
9c51e261dd | ||
|
|
e7d8b49ecc | ||
|
|
48ecf5f934 | ||
|
|
d6221f019b | ||
|
|
1630a872b1 | ||
|
|
e8de95a949 | ||
|
|
cdf90295a2 | ||
|
|
ca1c95c41c |
163
build.sh
163
build.sh
@@ -1,49 +1,170 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# nt MUSL static build script (emulates c-relay style)
|
||||
# Produces build/nt_static_x86_64 on x86_64 hosts.
|
||||
# nt MUSL static build script
|
||||
# Produces build/nt_static_x86_64 or build/nt_static_arm64
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
print_status() { echo -e "${BLUE}[INFO]${NC} $1" >&2; }
|
||||
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" >&2; }
|
||||
print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" >&2; }
|
||||
print_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BUILD_DIR="$SCRIPT_DIR/build"
|
||||
DOCKERFILE="$SCRIPT_DIR/Dockerfile.alpine-musl"
|
||||
OUTPUT_NAME="nt_static_x86_64"
|
||||
TARGET_ARCH=""
|
||||
|
||||
show_usage() {
|
||||
echo "nt static builder"
|
||||
echo ""
|
||||
echo "USAGE:"
|
||||
echo " $0 [--arch <x86_64|arm64>]"
|
||||
echo ""
|
||||
echo "OPTIONS:"
|
||||
echo " --arch <arch> Target architecture: x86_64 or arm64"
|
||||
echo " -h, --help Show this help"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--arch)
|
||||
if [[ -z "$2" ]]; then
|
||||
print_error "--arch requires a value: x86_64 or arm64"
|
||||
exit 1
|
||||
fi
|
||||
case "$2" in
|
||||
x86_64|arm64)
|
||||
TARGET_ARCH="$2"
|
||||
;;
|
||||
*)
|
||||
print_error "Unsupported architecture '$2'"
|
||||
print_error "Supported values: x86_64, arm64"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
show_usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown argument '$1'"
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "[ERROR] docker is required"
|
||||
print_error "docker is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "[ERROR] docker daemon is not running"
|
||||
print_error "docker daemon is not running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HOST_UNAME="$(uname -m)"
|
||||
case "$HOST_UNAME" in
|
||||
x86_64)
|
||||
HOST_ARCH="x86_64"
|
||||
;;
|
||||
aarch64|arm64)
|
||||
HOST_ARCH="arm64"
|
||||
;;
|
||||
*)
|
||||
HOST_ARCH="unknown"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -z "$TARGET_ARCH" ]]; then
|
||||
if [[ "$HOST_ARCH" == "unknown" ]]; then
|
||||
TARGET_ARCH="x86_64"
|
||||
print_warning "Unknown host architecture '$HOST_UNAME'; defaulting target arch to x86_64"
|
||||
else
|
||||
TARGET_ARCH="$HOST_ARCH"
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$TARGET_ARCH" in
|
||||
x86_64)
|
||||
PLATFORM="linux/amd64"
|
||||
OUTPUT_NAME="nt_static_x86_64"
|
||||
;;
|
||||
arm64)
|
||||
PLATFORM="linux/arm64"
|
||||
OUTPUT_NAME="nt_static_arm64"
|
||||
;;
|
||||
*)
|
||||
print_error "Unsupported target architecture '$TARGET_ARCH'"
|
||||
print_error "Supported values: x86_64, arm64"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$HOST_ARCH" != "unknown" && "$HOST_ARCH" != "$TARGET_ARCH" ]]; then
|
||||
print_status "Cross-building from host '$HOST_ARCH' to target '$TARGET_ARCH'"
|
||||
if ! docker buildx version >/dev/null 2>&1; then
|
||||
print_error "docker buildx is required for cross-architecture builds"
|
||||
print_error "Install/enable buildx + binfmt and retry."
|
||||
echo "docker run --privileged --rm tonistiigi/binfmt --install arm64" >&2
|
||||
exit 1
|
||||
fi
|
||||
print_success "docker buildx is available"
|
||||
|
||||
if ! docker run --rm --platform "$PLATFORM" alpine:3.19 /bin/true >/dev/null 2>&1; then
|
||||
print_error "Cross-architecture execution is not available (binfmt/QEMU not configured)"
|
||||
print_error "Enable binfmt support and retry:"
|
||||
echo "docker run --privileged --rm tonistiigi/binfmt --install arm64" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
echo "[INFO] Building MUSL static binary via Docker (output stage)..."
|
||||
docker build \
|
||||
--platform linux/amd64 \
|
||||
-f "$DOCKERFILE" \
|
||||
-t nt-musl-output:latest \
|
||||
.
|
||||
print_status "Building MUSL static binary for $TARGET_ARCH ($PLATFORM)..."
|
||||
(
|
||||
cd "$SCRIPT_DIR"
|
||||
docker build \
|
||||
--platform "$PLATFORM" \
|
||||
-f Dockerfile.alpine-musl \
|
||||
-t nt-musl-output:latest \
|
||||
.
|
||||
)
|
||||
|
||||
echo "[INFO] Extracting binary..."
|
||||
print_status "Extracting /nt_static from image..."
|
||||
CONTAINER_ID=$(docker create nt-musl-output:latest /nt_static)
|
||||
if ! docker cp "$CONTAINER_ID:/nt_static" "$BUILD_DIR/$OUTPUT_NAME"; then
|
||||
echo "[ERROR] Could not copy /nt_static from output image"
|
||||
print_error "Could not copy /nt_static from output image"
|
||||
docker rm "$CONTAINER_ID" >/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
docker rm "$CONTAINER_ID" >/dev/null
|
||||
docker rm "$CONTAINER_ID" >/dev/null || true
|
||||
chmod +x "$BUILD_DIR/$OUTPUT_NAME"
|
||||
|
||||
echo "[INFO] Verifying static link..."
|
||||
if ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1 | grep -q "not a dynamic executable\|statically linked"; then
|
||||
echo "[SUCCESS] Binary is static: $BUILD_DIR/$OUTPUT_NAME"
|
||||
else
|
||||
echo "[WARNING] ldd output unexpected:"
|
||||
ldd "$BUILD_DIR/$OUTPUT_NAME" || true
|
||||
print_status "Verifying binary with ldd/file"
|
||||
LDD_OK=false
|
||||
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
|
||||
|
||||
echo "[DONE] $BUILD_DIR/$OUTPUT_NAME"
|
||||
FILE_OUTPUT=$(file "$BUILD_DIR/$OUTPUT_NAME" 2>&1 || true)
|
||||
echo "$FILE_OUTPUT" >&2
|
||||
|
||||
if [[ "$LDD_OK" == true ]] || echo "$FILE_OUTPUT" | grep -q "statically linked"; then
|
||||
print_success "Binary verified as static: $BUILD_DIR/$OUTPUT_NAME"
|
||||
else
|
||||
print_warning "Could not conclusively verify static linking"
|
||||
fi
|
||||
|
||||
print_success "Done: $BUILD_DIR/$OUTPUT_NAME"
|
||||
|
||||
26
include/nsigner_auth.h
Normal file
26
include/nsigner_auth.h
Normal 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 */
|
||||
199
include/nsigner_client.h
Normal file
199
include/nsigner_client.h
Normal file
@@ -0,0 +1,199 @@
|
||||
#ifndef NSIGNER_CLIENT_H
|
||||
#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
|
||||
#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
|
||||
#define NT_NSIGNER_E_AUTH -6
|
||||
|
||||
/*
|
||||
* 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 nt_nsigner_selector_t *selector, char *out_hex_65);
|
||||
|
||||
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 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 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 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 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 */
|
||||
65
include/signer.h
Normal file
65
include/signer.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#ifndef SIGNER_H
|
||||
#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_QREXEC = 2,
|
||||
NT_SIGNER_NSIGNER_URL = 3
|
||||
} nt_signer_kind_t;
|
||||
|
||||
typedef struct {
|
||||
nt_signer_kind_t kind;
|
||||
char socket_name[128];
|
||||
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);
|
||||
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,
|
||||
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 */
|
||||
@@ -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 */
|
||||
|
||||
@@ -18,6 +18,7 @@ print_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
||||
|
||||
COMMIT_MESSAGE=""
|
||||
VERSION_INCREMENT_TYPE="patch" # patch|minor|major
|
||||
RELEASE_MODE=false
|
||||
|
||||
show_usage() {
|
||||
echo "nt increment and push"
|
||||
@@ -29,7 +30,21 @@ show_usage() {
|
||||
echo " -p, --patch Increment patch version (default)"
|
||||
echo " -m, --minor Increment minor version"
|
||||
echo " -M, --major Increment major version"
|
||||
echo " -r, --release After bump+push, build/upload release assets to Gitea"
|
||||
echo " -h, --help Show help"
|
||||
echo ""
|
||||
echo "EXAMPLES:"
|
||||
echo " $0 \"Fix relay reconnect bug\""
|
||||
echo " $0 -m \"Add profile editing\""
|
||||
echo " $0 -r -p \"Release patch build\""
|
||||
echo " $0 -r -m \"Release minor build\""
|
||||
echo ""
|
||||
echo "RELEASE MODE (-r):"
|
||||
echo " 1) Normal bump + commit + tag + push runs first"
|
||||
echo " 2) Build x86_64 static binary (required)"
|
||||
echo " 3) Build arm64 static binary (warning only on failure)"
|
||||
echo " 4) Create source tarball"
|
||||
echo " 5) Create/upload Gitea release assets if ~/.gitea_token exists"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
@@ -46,6 +61,10 @@ while [[ $# -gt 0 ]]; do
|
||||
VERSION_INCREMENT_TYPE="major"
|
||||
shift
|
||||
;;
|
||||
-r|--release)
|
||||
RELEASE_MODE=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_usage
|
||||
exit 0
|
||||
@@ -71,77 +90,289 @@ if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LATEST_TAG=$(git tag -l 'v*.*.*' | sort -V | tail -n 1 || true)
|
||||
if [[ -z "$LATEST_TAG" ]]; then
|
||||
LATEST_TAG="v0.0.0"
|
||||
print_warning "No version tags found, starting from $LATEST_TAG"
|
||||
fi
|
||||
get_and_increment_version() {
|
||||
LATEST_TAG=$(git tag -l 'v*.*.*' | sort -V | tail -n 1 || true)
|
||||
if [[ -z "$LATEST_TAG" ]]; then
|
||||
LATEST_TAG="v0.0.0"
|
||||
print_warning "No version tags found, starting from $LATEST_TAG"
|
||||
fi
|
||||
|
||||
VERSION=${LATEST_TAG#v}
|
||||
if [[ $VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
MAJOR=${BASH_REMATCH[1]}
|
||||
MINOR=${BASH_REMATCH[2]}
|
||||
PATCH=${BASH_REMATCH[3]}
|
||||
else
|
||||
print_error "Invalid version format in tag: $LATEST_TAG"
|
||||
exit 1
|
||||
fi
|
||||
VERSION=${LATEST_TAG#v}
|
||||
if [[ $VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
MAJOR=${BASH_REMATCH[1]}
|
||||
MINOR=${BASH_REMATCH[2]}
|
||||
PATCH=${BASH_REMATCH[3]}
|
||||
else
|
||||
print_error "Invalid version format in tag: $LATEST_TAG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$VERSION_INCREMENT_TYPE" in
|
||||
major)
|
||||
NEW_MAJOR=$((MAJOR + 1))
|
||||
NEW_MINOR=0
|
||||
NEW_PATCH=0
|
||||
;;
|
||||
minor)
|
||||
NEW_MAJOR=$MAJOR
|
||||
NEW_MINOR=$((MINOR + 1))
|
||||
NEW_PATCH=0
|
||||
;;
|
||||
*)
|
||||
NEW_MAJOR=$MAJOR
|
||||
NEW_MINOR=$MINOR
|
||||
NEW_PATCH=$((PATCH + 1))
|
||||
;;
|
||||
esac
|
||||
case "$VERSION_INCREMENT_TYPE" in
|
||||
major)
|
||||
NEW_MAJOR=$((MAJOR + 1))
|
||||
NEW_MINOR=0
|
||||
NEW_PATCH=0
|
||||
;;
|
||||
minor)
|
||||
NEW_MAJOR=$MAJOR
|
||||
NEW_MINOR=$((MINOR + 1))
|
||||
NEW_PATCH=0
|
||||
;;
|
||||
*)
|
||||
NEW_MAJOR=$MAJOR
|
||||
NEW_MINOR=$MINOR
|
||||
NEW_PATCH=$((PATCH + 1))
|
||||
;;
|
||||
esac
|
||||
|
||||
NEW_VERSION="v${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}"
|
||||
print_status "Current version: $LATEST_TAG"
|
||||
print_status "New version: $NEW_VERSION"
|
||||
NEW_VERSION="v${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}"
|
||||
print_status "Current version: $LATEST_TAG"
|
||||
print_status "New version: $NEW_VERSION"
|
||||
}
|
||||
|
||||
if [[ ! -f "src/main.c" ]]; then
|
||||
print_error "src/main.c not found"
|
||||
exit 1
|
||||
fi
|
||||
update_version_macros() {
|
||||
if [[ ! -f "src/main.c" ]]; then
|
||||
print_error "src/main.c not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_status "Updating version macros in src/main.c"
|
||||
sed -i "s/^#define NT_VERSION_MAJOR .*/#define NT_VERSION_MAJOR ${NEW_MAJOR}/" src/main.c
|
||||
sed -i "s/^#define NT_VERSION_MINOR .*/#define NT_VERSION_MINOR ${NEW_MINOR}/" src/main.c
|
||||
sed -i "s/^#define NT_VERSION_PATCH .*/#define NT_VERSION_PATCH ${NEW_PATCH}/" src/main.c
|
||||
sed -i "s/^#define NT_VERSION \".*\"/#define NT_VERSION \"${NEW_VERSION}\"/" src/main.c
|
||||
print_status "Updating version macros in src/main.c"
|
||||
sed -i "s/^#define NT_VERSION_MAJOR .*/#define NT_VERSION_MAJOR ${NEW_MAJOR}/" src/main.c
|
||||
sed -i "s/^#define NT_VERSION_MINOR .*/#define NT_VERSION_MINOR ${NEW_MINOR}/" src/main.c
|
||||
sed -i "s/^#define NT_VERSION_PATCH .*/#define NT_VERSION_PATCH ${NEW_PATCH}/" src/main.c
|
||||
sed -i "s/^#define NT_VERSION \".*\"/#define NT_VERSION \"${NEW_VERSION}\"/" src/main.c
|
||||
|
||||
print_success "Updated version in src/main.c to $NEW_VERSION"
|
||||
print_success "Updated version in src/main.c to $NEW_VERSION"
|
||||
}
|
||||
|
||||
print_status "Staging changes"
|
||||
git add src/main.c CMakeLists.txt build.sh increment_and_push.sh README.md plans/planning.md include/*.h src/*.c .test_mnemonic 2>/dev/null || git add .
|
||||
git_commit_tag_push() {
|
||||
print_status "Staging changes"
|
||||
git add src/main.c CMakeLists.txt build.sh increment_and_push.sh README.md plans/planning.md include/*.h src/*.c .test_mnemonic 2>/dev/null || git add .
|
||||
|
||||
if git diff --staged --quiet; then
|
||||
print_warning "No changes to commit"
|
||||
else
|
||||
git commit -m "$NEW_VERSION - $COMMIT_MESSAGE"
|
||||
print_success "Committed changes"
|
||||
fi
|
||||
if git diff --staged --quiet; then
|
||||
print_warning "No changes to commit"
|
||||
else
|
||||
git commit -m "$NEW_VERSION - $COMMIT_MESSAGE"
|
||||
print_success "Committed changes"
|
||||
fi
|
||||
|
||||
if git rev-parse "$NEW_VERSION" >/dev/null 2>&1; then
|
||||
print_warning "Tag $NEW_VERSION already exists locally; recreating"
|
||||
git tag -d "$NEW_VERSION" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if git rev-parse "$NEW_VERSION" >/dev/null 2>&1; then
|
||||
print_warning "Tag $NEW_VERSION already exists locally; recreating"
|
||||
git tag -d "$NEW_VERSION" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
git tag "$NEW_VERSION"
|
||||
print_success "Created tag: $NEW_VERSION"
|
||||
git tag "$NEW_VERSION"
|
||||
print_success "Created tag: $NEW_VERSION"
|
||||
|
||||
print_status "Pushing commit and tag"
|
||||
git push
|
||||
git push origin "$NEW_VERSION"
|
||||
print_status "Pushing commit and tag"
|
||||
git push
|
||||
git push origin "$NEW_VERSION"
|
||||
|
||||
print_success "Done. Pushed $NEW_VERSION"
|
||||
print_success "Pushed $NEW_VERSION"
|
||||
}
|
||||
|
||||
build_release_binary() {
|
||||
print_status "Building release binaries"
|
||||
|
||||
if [[ ! -x "./build.sh" ]]; then
|
||||
print_error "./build.sh not found or not executable"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ./build.sh; then
|
||||
print_success "Built x86_64 static binary"
|
||||
else
|
||||
print_error "x86_64 build failed; aborting release mode"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ./build.sh --arch arm64; then
|
||||
print_success "Built arm64 static binary"
|
||||
else
|
||||
print_warning "arm64 build failed (continuing release with available assets)"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
create_source_tarball() {
|
||||
local tarball_name="nostr_terminal-${NEW_VERSION#v}.tar.gz"
|
||||
|
||||
print_status "Creating source tarball: $tarball_name"
|
||||
if tar -czf "$tarball_name" \
|
||||
--exclude='build/*' \
|
||||
--exclude='.git*' \
|
||||
--exclude='*.db' \
|
||||
--exclude='*.db-*' \
|
||||
--exclude='*.log' \
|
||||
--exclude='*.tar.gz' \
|
||||
.; then
|
||||
print_success "Created source tarball: $tarball_name"
|
||||
echo "$tarball_name"
|
||||
return 0
|
||||
else
|
||||
print_error "Failed to create source tarball"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
create_gitea_release() {
|
||||
if [[ ! -f "$HOME/.gitea_token" ]]; then
|
||||
print_warning "No $HOME/.gitea_token found. Skipping release creation/upload."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local token
|
||||
token=$(tr -d '\n\r' < "$HOME/.gitea_token")
|
||||
local api_url="https://git.laantungir.net/api/v1/repos/laantungir/nostr_terminal"
|
||||
|
||||
print_status "Creating Gitea release: $NEW_VERSION"
|
||||
local response
|
||||
response=$(curl -s -X POST "$api_url/releases" \
|
||||
-H "Authorization: token $token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\": \"$NEW_VERSION\", \"name\": \"$NEW_VERSION\", \"body\": \"$COMMIT_MESSAGE\"}")
|
||||
|
||||
if echo "$response" | grep -q '"id"'; then
|
||||
local release_id
|
||||
release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
|
||||
print_success "Created release $NEW_VERSION (id=$release_id)"
|
||||
echo "$release_id"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if echo "$response" | grep -qi "already exists"; then
|
||||
print_warning "Release $NEW_VERSION already exists; resolving release id"
|
||||
local existing
|
||||
existing=$(curl -s -H "Authorization: token $token" "$api_url/releases/tags/$NEW_VERSION")
|
||||
if echo "$existing" | grep -q '"id"'; then
|
||||
local release_id
|
||||
release_id=$(echo "$existing" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
|
||||
print_status "Using existing release id=$release_id"
|
||||
echo "$release_id"
|
||||
return 0
|
||||
fi
|
||||
|
||||
print_error "Could not resolve existing release ID for $NEW_VERSION"
|
||||
return 1
|
||||
fi
|
||||
|
||||
print_error "Failed to create release"
|
||||
print_error "Response: $response"
|
||||
return 1
|
||||
}
|
||||
|
||||
upload_one_asset_with_retry() {
|
||||
local assets_url="$1"
|
||||
local token="$2"
|
||||
local file_path="$3"
|
||||
local max_attempts=3
|
||||
local attempt=1
|
||||
|
||||
while [[ $attempt -le $max_attempts ]]; do
|
||||
print_status "Uploading $(basename "$file_path") (attempt $attempt/$max_attempts)"
|
||||
local response
|
||||
response=$(curl -s -X POST "$assets_url" \
|
||||
-H "Authorization: token $token" \
|
||||
-F "attachment=@$file_path;filename=$(basename "$file_path")")
|
||||
|
||||
if echo "$response" | grep -q '"id"'; then
|
||||
print_success "Uploaded $(basename "$file_path")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ $attempt -lt $max_attempts ]]; then
|
||||
print_warning "Upload attempt failed for $(basename "$file_path"), retrying..."
|
||||
sleep 2
|
||||
else
|
||||
print_error "Failed to upload $(basename "$file_path") after $max_attempts attempts"
|
||||
print_error "Response: $response"
|
||||
fi
|
||||
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
upload_release_assets() {
|
||||
local release_id="$1"
|
||||
local tarball_path="$2"
|
||||
|
||||
if [[ ! -f "$HOME/.gitea_token" ]]; then
|
||||
print_warning "No $HOME/.gitea_token found. Skipping asset uploads."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local token
|
||||
token=$(tr -d '\n\r' < "$HOME/.gitea_token")
|
||||
local api_url="https://git.laantungir.net/api/v1/repos/laantungir/nostr_terminal"
|
||||
local assets_url="$api_url/releases/$release_id/assets"
|
||||
|
||||
if [[ -f "build/nt_static_x86_64" ]]; then
|
||||
upload_one_asset_with_retry "$assets_url" "$token" "build/nt_static_x86_64" || true
|
||||
else
|
||||
print_warning "Asset missing: build/nt_static_x86_64"
|
||||
fi
|
||||
|
||||
if [[ -f "build/nt_static_arm64" ]]; then
|
||||
upload_one_asset_with_retry "$assets_url" "$token" "build/nt_static_arm64" || true
|
||||
else
|
||||
print_warning "Asset missing: build/nt_static_arm64 (skipping)"
|
||||
fi
|
||||
|
||||
if [[ -n "$tarball_path" && -f "$tarball_path" ]]; then
|
||||
print_status "Uploading $(basename "$tarball_path")"
|
||||
local response
|
||||
response=$(curl -s -X POST "$assets_url" \
|
||||
-H "Authorization: token $token" \
|
||||
-F "attachment=@$tarball_path;filename=$(basename "$tarball_path")")
|
||||
|
||||
if echo "$response" | grep -q '"id"'; then
|
||||
print_success "Uploaded $(basename "$tarball_path")"
|
||||
else
|
||||
print_warning "Failed to upload $(basename "$tarball_path"): $response"
|
||||
fi
|
||||
else
|
||||
print_warning "Tarball missing, skipping upload"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
get_and_increment_version
|
||||
update_version_macros
|
||||
git_commit_tag_push
|
||||
|
||||
if [[ "$RELEASE_MODE" == true ]]; then
|
||||
print_status "Release mode enabled"
|
||||
|
||||
if ! build_release_binary; then
|
||||
print_error "Release mode aborted due to required x86_64 build failure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local tarball_path=""
|
||||
if tarball_path=$(create_source_tarball); then
|
||||
:
|
||||
else
|
||||
print_warning "Proceeding without source tarball"
|
||||
fi
|
||||
|
||||
local release_id=""
|
||||
if release_id=$(create_gitea_release); then
|
||||
if [[ "$release_id" =~ ^[0-9]+$ ]]; then
|
||||
upload_release_assets "$release_id" "$tarball_path"
|
||||
print_success "Release workflow finished for $NEW_VERSION"
|
||||
else
|
||||
print_warning "Release creation/upload skipped (no release id)"
|
||||
fi
|
||||
else
|
||||
print_error "Release creation failed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
print_success "Done. Pushed $NEW_VERSION"
|
||||
}
|
||||
|
||||
main
|
||||
|
||||
5
src/db.c
5
src/db.c
@@ -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));
|
||||
|
||||
17
src/main.c
17
src/main.c
@@ -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 3
|
||||
#define NT_VERSION "v0.0.3"
|
||||
#define NT_VERSION_PATCH 12
|
||||
#define NT_VERSION "v0.0.12"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -33,8 +34,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 +57,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 +89,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");
|
||||
@@ -211,6 +209,7 @@ int main(int argc, char *argv[]) {
|
||||
menu_main();
|
||||
|
||||
nostr_cleanup();
|
||||
signer_shutdown();
|
||||
state_cleanup();
|
||||
tui_cleanup();
|
||||
db_close();
|
||||
|
||||
347
src/menu_ai.c
347
src/menu_ai.c
@@ -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;
|
||||
@@ -473,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;
|
||||
@@ -486,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;
|
||||
}
|
||||
|
||||
@@ -521,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;
|
||||
}
|
||||
|
||||
@@ -536,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) {
|
||||
@@ -558,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;
|
||||
@@ -590,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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -778,7 +778,7 @@ void menu_ecash(void) {
|
||||
tui_print("^_R^:emove mint");
|
||||
tui_print("^_I^:mport token");
|
||||
tui_print("^_S^:end token");
|
||||
tui_print("^_X^:xit");
|
||||
tui_print("E^_x^:it");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ void menu_follows(void) {
|
||||
tui_print("^_A^:dd follow");
|
||||
tui_print("^_D^:elete follow");
|
||||
tui_print("^_P^:ost changes and exit.");
|
||||
tui_print("^_X^:xit without saving");
|
||||
tui_print("E^_x^:it without saving");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
294
src/menu_login.c
294
src/menu_login.c
@@ -1,8 +1,11 @@
|
||||
#include "tui.h"
|
||||
#include "state.h"
|
||||
#include "net.h"
|
||||
#include "db.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"
|
||||
@@ -14,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"
|
||||
|
||||
@@ -158,15 +162,296 @@ 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...");
|
||||
|
||||
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 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) >", 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], &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++) {
|
||||
free(names[i]);
|
||||
}
|
||||
free(names);
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
signer_init_nsigner(names[selected], &selector);
|
||||
|
||||
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, "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];
|
||||
@@ -297,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, [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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "tui.h"
|
||||
#include "state.h"
|
||||
#include "net.h"
|
||||
#include "signer.h"
|
||||
|
||||
#include "publish.h"
|
||||
|
||||
@@ -26,8 +27,34 @@ 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);
|
||||
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);
|
||||
}
|
||||
tui_print("npubHex: %s", g_state.npub_hex);
|
||||
tui_print("npub: %s", g_state.npub_bech32);
|
||||
tui_print("");
|
||||
@@ -45,7 +72,7 @@ void menu_profile(void) {
|
||||
tui_print("\n");
|
||||
tui_print("^_M^:odify account");
|
||||
tui_print("^_P^:ost changes and exit.");
|
||||
tui_print("^_X^:xit without saving");
|
||||
tui_print("E^_x^:it without saving");
|
||||
|
||||
tui_get_line(">", menu_sel, (int)sizeof(menu_sel));
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ void menu_relays(void) {
|
||||
tui_print("^_D^:elete relay");
|
||||
tui_print("^_M^:odify relay");
|
||||
tui_print("^_P^:ost changes and exit.");
|
||||
tui_print("^_X^:xit without saving");
|
||||
tui_print("E^_x^:it without saving");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -457,7 +407,7 @@ void menu_todo(void) {
|
||||
tui_print("^_D^:elete item");
|
||||
tui_print("^_R^:eorder (swap two)");
|
||||
tui_print("^_P^:ost/save changes");
|
||||
tui_print("^_X^:xit without saving");
|
||||
tui_print("E^_x^:it without saving");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
|
||||
|
||||
118
src/net.c
118
src/net.c
@@ -2,11 +2,13 @@
|
||||
|
||||
#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"
|
||||
#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>
|
||||
@@ -18,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;
|
||||
@@ -244,7 +246,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;
|
||||
}
|
||||
|
||||
@@ -528,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,
|
||||
@@ -684,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) {
|
||||
@@ -707,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
src/nsigner_auth.c
Normal file
109
src/nsigner_auth.c
Normal 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;
|
||||
}
|
||||
1337
src/nsigner_client.c
Normal file
1337
src/nsigner_client.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
722
src/signer.c
Normal file
722
src/signer.c
Normal file
@@ -0,0 +1,722 @@
|
||||
#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},
|
||||
.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]) {
|
||||
if (!out_priv) {
|
||||
return -1;
|
||||
}
|
||||
if (nostr_hex_to_bytes(g_state.nsec_hex, out_priv, 32) != 0) {
|
||||
return -1;
|
||||
}
|
||||
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';
|
||||
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) {
|
||||
signer_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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.selector,
|
||||
unsigned_json,
|
||||
signed_event_json_out);
|
||||
|
||||
free(unsigned_json);
|
||||
cJSON_Delete(unsigned_event);
|
||||
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;
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
85
src/state.c
85
src/state.c
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user