Files
didactyl/src/tools/tool_nostr_identity.c

258 lines
8.9 KiB
C

#define _POSIX_C_SOURCE 200809L
#include "tools_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cjson/cJSON.h"
#include "../../nostr_core_lib/nostr_core/nostr_core.h"
static char* json_error_local(const char* msg) {
cJSON* root = cJSON_CreateObject();
if (!root) return NULL;
cJSON_AddBoolToObject(root, "success", 0);
cJSON_AddStringToObject(root, "error", msg ? msg : "unknown error");
char* out = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return out;
}
static cJSON* parse_args_local(const char* args_json) {
const char* raw = args_json ? args_json : "{}";
cJSON* args = cJSON_Parse(raw);
if (!args || !cJSON_IsObject(args)) {
cJSON_Delete(args);
return NULL;
}
return args;
}
static int is_hex_string_len_local(const char* s, size_t expected_len) {
if (!s) return 0;
if (strlen(s) != expected_len) return 0;
for (size_t i = 0; i < expected_len; i++) {
if (!((s[i] >= '0' && s[i] <= '9') ||
(s[i] >= 'a' && s[i] <= 'f') ||
(s[i] >= 'A' && s[i] <= 'F'))) {
return 0;
}
}
return 1;
}
char* execute_nostr_encode(const char* args_json) {
cJSON* args = parse_args_local(args_json);
if (!args) return json_error_local("invalid arguments JSON");
cJSON* type = cJSON_GetObjectItemCaseSensitive(args, "type");
cJSON* hex = cJSON_GetObjectItemCaseSensitive(args, "hex");
cJSON* relays = cJSON_GetObjectItemCaseSensitive(args, "relays");
cJSON* kind = cJSON_GetObjectItemCaseSensitive(args, "kind");
cJSON* identifier = cJSON_GetObjectItemCaseSensitive(args, "identifier");
if (!type || !cJSON_IsString(type) || !type->valuestring ||
!hex || !cJSON_IsString(hex) || !hex->valuestring || !is_hex_string_len_local(hex->valuestring, 64U)) {
cJSON_Delete(args);
return json_error_local("nostr_encode requires type string and hex 64-char string");
}
char type_name[32];
snprintf(type_name, sizeof(type_name), "%s", type->valuestring);
unsigned char key_bytes[32];
if (nostr_hex_to_bytes(hex->valuestring, key_bytes, sizeof(key_bytes)) != 0) {
cJSON_Delete(args);
return json_error_local("nostr_encode invalid hex");
}
const char** relay_ptrs = NULL;
int relay_count = 0;
if (relays) {
if (!cJSON_IsArray(relays)) {
cJSON_Delete(args);
return json_error_local("nostr_encode relays must be an array of relay URLs");
}
relay_count = cJSON_GetArraySize(relays);
if (relay_count > 0) {
relay_ptrs = (const char**)malloc((size_t)relay_count * sizeof(char*));
if (!relay_ptrs) {
cJSON_Delete(args);
return json_error_local("memory allocation failed");
}
for (int i = 0; i < relay_count; i++) {
cJSON* relay = cJSON_GetArrayItem(relays, i);
if (!relay || !cJSON_IsString(relay) || !relay->valuestring || relay->valuestring[0] == '\0') {
free(relay_ptrs);
cJSON_Delete(args);
return json_error_local("nostr_encode relays must contain non-empty strings");
}
relay_ptrs[i] = relay->valuestring;
}
}
}
char bech32[256] = {0};
char uri[1200] = {0};
int rc = NOSTR_ERROR_INVALID_INPUT;
if (strcmp(type_name, "npub") == 0) {
rc = nostr_key_to_bech32(key_bytes, "npub", bech32);
if (rc == NOSTR_SUCCESS) {
snprintf(uri, sizeof(uri), "nostr:%s", bech32);
}
} else if (strcmp(type_name, "nsec") == 0) {
rc = nostr_key_to_bech32(key_bytes, "nsec", bech32);
if (rc == NOSTR_SUCCESS) {
snprintf(uri, sizeof(uri), "nostr:%s", bech32);
}
} else if (strcmp(type_name, "note") == 0) {
rc = nostr_key_to_bech32(key_bytes, "note", bech32);
if (rc == NOSTR_SUCCESS) {
snprintf(uri, sizeof(uri), "nostr:%s", bech32);
}
} else if (strcmp(type_name, "nprofile") == 0) {
rc = nostr_build_uri_nprofile(key_bytes, relay_ptrs, relay_count, uri, sizeof(uri));
} else if (strcmp(type_name, "nevent") == 0) {
int kind_value = -1;
if (kind) {
if (!cJSON_IsNumber(kind)) {
free(relay_ptrs);
cJSON_Delete(args);
return json_error_local("nostr_encode nevent kind must be an integer");
}
kind_value = (int)kind->valuedouble;
}
rc = nostr_build_uri_nevent(key_bytes, relay_ptrs, relay_count, NULL, kind_value, 0, uri, sizeof(uri));
} else if (strcmp(type_name, "naddr") == 0) {
if (!kind || !cJSON_IsNumber(kind)) {
free(relay_ptrs);
cJSON_Delete(args);
return json_error_local("nostr_encode naddr requires kind integer");
}
if (!identifier || !cJSON_IsString(identifier) || !identifier->valuestring || identifier->valuestring[0] == '\0') {
free(relay_ptrs);
cJSON_Delete(args);
return json_error_local("nostr_encode naddr requires non-empty identifier string");
}
rc = nostr_build_uri_naddr(identifier->valuestring, key_bytes, (int)kind->valuedouble,
relay_ptrs, relay_count, uri, sizeof(uri));
} else {
free(relay_ptrs);
cJSON_Delete(args);
return json_error_local("nostr_encode currently supports npub, nsec, note, nprofile, nevent, naddr");
}
free(relay_ptrs);
cJSON_Delete(args);
if (rc != NOSTR_SUCCESS) {
return json_error_local("nostr_encode failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "type", type_name);
cJSON_AddStringToObject(out, "uri", uri);
cJSON_AddBoolToObject(out, "limited_support", 1);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
char* execute_nostr_decode(const char* args_json) {
cJSON* args = parse_args_local(args_json);
if (!args) return json_error_local("invalid arguments JSON");
cJSON* uri_in = cJSON_GetObjectItemCaseSensitive(args, "uri");
if (!uri_in || !cJSON_IsString(uri_in) || !uri_in->valuestring || uri_in->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error_local("nostr_decode requires uri string");
}
const char* in = uri_in->valuestring;
if (strncmp(in, "nostr:", 6) == 0) {
in += 6;
}
unsigned char key[32];
char key_hex[65] = {0};
const char* type = NULL;
int rc = NOSTR_ERROR_INVALID_INPUT;
if (strncmp(in, "npub1", 5) == 0) {
type = "npub";
rc = nostr_decode_npub(in, key);
} else if (strncmp(in, "nsec1", 5) == 0) {
type = "nsec";
rc = nostr_decode_nsec(in, key);
} else {
cJSON_Delete(args);
return json_error_local("nostr_decode currently supports npub and nsec");
}
cJSON_Delete(args);
if (rc != NOSTR_SUCCESS) {
return json_error_local("nostr_decode failed");
}
nostr_bytes_to_hex(key, 32, key_hex);
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "type", type);
if (strcmp(type, "npub") == 0) {
cJSON_AddStringToObject(out, "pubkey", key_hex);
} else {
cJSON_AddStringToObject(out, "private_key", key_hex);
}
cJSON_AddBoolToObject(out, "limited_support", 1);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
char* execute_nostr_pubkey(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
cJSON* args = parse_args_local(args_json);
if (!args) return json_error_local("invalid arguments JSON");
cJSON_Delete(args);
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "pubkey", ctx->cfg->keys.public_key_hex);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
char* execute_nostr_npub(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
cJSON* args = parse_args_local(args_json);
if (!args) return json_error_local("invalid arguments JSON");
cJSON_Delete(args);
char npub[256] = {0};
if (nostr_key_to_bech32(ctx->cfg->keys.public_key, "npub", npub) != NOSTR_SUCCESS) {
return json_error_local("failed to encode npub");
}
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "npub", npub);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}