v0.0.29 - Feather firmware: auto-approve sign_event and always return explicit JSON-RPC errors for unhandled/oversized requests
This commit is contained in:
@@ -7,8 +7,6 @@
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "cJSON.h"
|
||||
#include "secp256k1.h"
|
||||
@@ -16,10 +14,16 @@
|
||||
#include "secp256k1_schnorrsig.h"
|
||||
|
||||
#include "bech32.h"
|
||||
#include "buttons.h"
|
||||
#include "display.h"
|
||||
#include "key_derivation.h"
|
||||
#include "mnemonic.h"
|
||||
#include "usb_transport.h"
|
||||
#include "ui.h"
|
||||
#include "secure_mem.h"
|
||||
#include "nip004.h"
|
||||
#include "nip044.h"
|
||||
#include "nostr_common.h"
|
||||
|
||||
#define FIRMWARE_VERSION "0.0.1"
|
||||
#ifndef GIT_HASH
|
||||
@@ -28,12 +32,7 @@
|
||||
|
||||
static const char *TAG = "nsigner";
|
||||
|
||||
#define BTN_DENY_GPIO 0 /* D0/BOOT, active LOW */
|
||||
#define BTN_APPROVE_GPIO 1 /* D1, active HIGH */
|
||||
#define BTN_ALWAYS_GPIO 2 /* D2, active HIGH */
|
||||
|
||||
#define APPROVAL_TIMEOUT_MS 30000
|
||||
#define APPROVAL_DEBOUNCE_MS 30
|
||||
|
||||
typedef enum {
|
||||
APPROVAL_DENY = 0,
|
||||
@@ -42,7 +41,6 @@ typedef enum {
|
||||
APPROVAL_TIMEOUT = 3,
|
||||
} approval_decision_t;
|
||||
|
||||
static bool s_buttons_inited = false;
|
||||
static bool s_always_allow_sign = false;
|
||||
|
||||
#define AUTH_REQUIRED 1
|
||||
@@ -72,89 +70,29 @@ static char s_pubkey_hex[65];
|
||||
static char s_response_buf[2048];
|
||||
static char s_signed_event_buf[1600];
|
||||
|
||||
static char s_encrypt_buf[1600];
|
||||
|
||||
static int transport_init(void)
|
||||
{
|
||||
return usb_transport_init();
|
||||
}
|
||||
|
||||
static void buttons_init(void)
|
||||
{
|
||||
if (s_buttons_inited) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_config_t deny_cfg = {
|
||||
.pin_bit_mask = 1ULL << BTN_DENY_GPIO,
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&deny_cfg);
|
||||
|
||||
gpio_config_t approve_cfg = {
|
||||
.pin_bit_mask = (1ULL << BTN_APPROVE_GPIO) | (1ULL << BTN_ALWAYS_GPIO),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_ENABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&approve_cfg);
|
||||
|
||||
s_buttons_inited = true;
|
||||
}
|
||||
|
||||
static bool button_pressed_deny(void)
|
||||
{
|
||||
return gpio_get_level(BTN_DENY_GPIO) == 0;
|
||||
}
|
||||
|
||||
static bool button_pressed_approve(void)
|
||||
{
|
||||
return gpio_get_level(BTN_APPROVE_GPIO) == 1;
|
||||
}
|
||||
|
||||
static bool button_pressed_always(void)
|
||||
{
|
||||
return gpio_get_level(BTN_ALWAYS_GPIO) == 1;
|
||||
}
|
||||
|
||||
static approval_decision_t wait_for_approval(uint32_t timeout_ms)
|
||||
{
|
||||
uint32_t elapsed = 0;
|
||||
const uint32_t step = 20;
|
||||
btn_event_t evt = buttons_wait(timeout_ms);
|
||||
|
||||
/* Drain any currently-held button before sampling fresh presses. */
|
||||
while (button_pressed_deny() || button_pressed_approve() || button_pressed_always()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
elapsed += 10;
|
||||
if (elapsed >= timeout_ms) {
|
||||
return APPROVAL_TIMEOUT;
|
||||
}
|
||||
if (evt.kind == BTN_EVT_NONE) {
|
||||
return APPROVAL_TIMEOUT;
|
||||
}
|
||||
|
||||
while (elapsed < timeout_ms) {
|
||||
if (button_pressed_deny()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(APPROVAL_DEBOUNCE_MS));
|
||||
if (button_pressed_deny()) {
|
||||
return APPROVAL_DENY;
|
||||
}
|
||||
}
|
||||
if (button_pressed_always()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(APPROVAL_DEBOUNCE_MS));
|
||||
if (button_pressed_always()) {
|
||||
return APPROVAL_ALWAYS;
|
||||
}
|
||||
}
|
||||
if (button_pressed_approve()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(APPROVAL_DEBOUNCE_MS));
|
||||
if (button_pressed_approve()) {
|
||||
return APPROVAL_ONCE;
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(step));
|
||||
elapsed += step;
|
||||
if (evt.id == BTN_D0) {
|
||||
return APPROVAL_DENY;
|
||||
}
|
||||
if (evt.id == BTN_D1) {
|
||||
return APPROVAL_ONCE;
|
||||
}
|
||||
if (evt.id == BTN_D2) {
|
||||
return APPROVAL_ALWAYS;
|
||||
}
|
||||
|
||||
return APPROVAL_TIMEOUT;
|
||||
@@ -189,24 +127,24 @@ static void show_approval_prompt_with_caller(const cJSON *event_in, const char *
|
||||
snprintf(line_content, sizeof(line_content), "%.50s", content);
|
||||
|
||||
display_clear(RGB565_BLACK);
|
||||
display_draw_text(10, 10, "APPROVE sign_event?", RGB565_YELLOW, RGB565_BLACK);
|
||||
display_draw_text(10, 24, line_caller, RGB565_MAGENTA, RGB565_BLACK);
|
||||
display_draw_text(10, 10, "APPROVE sign_event?", RGB565_RED, RGB565_BLACK);
|
||||
display_draw_text(10, 24, line_caller, RGB565_WHITE, RGB565_BLACK);
|
||||
display_draw_text(10, 40, line_kind, RGB565_WHITE, RGB565_BLACK);
|
||||
display_draw_text(10, 56, line_content, RGB565_CYAN, RGB565_BLACK);
|
||||
display_draw_text(10, 80, "D1=allow once", RGB565_GREEN, RGB565_BLACK);
|
||||
display_draw_text(10, 96, "D2=always allow", RGB565_GREEN, RGB565_BLACK);
|
||||
display_draw_text(10, 112, "D0=deny", RGB565_RED, RGB565_BLACK);
|
||||
display_draw_text(10, 56, line_content, RGB565_WHITE, RGB565_BLACK);
|
||||
display_draw_text(10, 80, "D1=allow once", RGB565_DIM_GRAY, RGB565_BLACK);
|
||||
display_draw_text(10, 96, "D2=always allow", RGB565_DIM_GRAY, RGB565_BLACK);
|
||||
display_draw_text(10, 112, "D0=deny", RGB565_DIM_GRAY, RGB565_BLACK);
|
||||
}
|
||||
|
||||
static void show_idle_screen(void)
|
||||
{
|
||||
display_clear(RGB565_BLACK);
|
||||
display_draw_text(10, 10, "nostr npub", RGB565_GREEN, RGB565_BLACK);
|
||||
display_draw_text(10, 30, s_npub_short, RGB565_GREEN, RGB565_BLACK);
|
||||
display_draw_text(10, 10, "nostr npub", RGB565_WHITE, RGB565_BLACK);
|
||||
display_draw_text(10, 30, s_npub_short, RGB565_WHITE, RGB565_BLACK);
|
||||
display_draw_text(10, 50, "mn:", RGB565_WHITE, RGB565_BLACK);
|
||||
display_draw_text(46, 50, s_mnemonic_short, RGB565_YELLOW, RGB565_BLACK);
|
||||
display_draw_text(10, 70, "v" FIRMWARE_VERSION, RGB565_WHITE, RGB565_BLACK);
|
||||
display_draw_text(10, 90, "ready", RGB565_CYAN, RGB565_BLACK);
|
||||
display_draw_text(46, 50, s_mnemonic_short, RGB565_WHITE, RGB565_BLACK);
|
||||
display_draw_text(10, 70, "v" FIRMWARE_VERSION, RGB565_DIM_GRAY, RGB565_BLACK);
|
||||
display_draw_text(10, 90, "ready", RGB565_DIM_GRAY, RGB565_BLACK);
|
||||
}
|
||||
|
||||
static int recv_frame_stdin(uint8_t *payload, size_t payload_max, size_t *out_len)
|
||||
@@ -761,93 +699,143 @@ done:
|
||||
return rc;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
static int parse_nostr_index_from_params(cJSON *params, uint32_t *out_index)
|
||||
{
|
||||
uint32_t rx_frames = 0;
|
||||
uint32_t tx_frames = 0;
|
||||
size_t last_len = 0;
|
||||
int params_count;
|
||||
cJSON *last_item;
|
||||
cJSON *idx_item;
|
||||
int idx;
|
||||
|
||||
ESP_LOGI(TAG, "n_signer v%s booting...", FIRMWARE_VERSION);
|
||||
printf("n_signer booting\n");
|
||||
|
||||
ESP_ERROR_CHECK(display_init());
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 10, "n_signer", RGB565_YELLOW, RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 30, "deriving keys...", RGB565_YELLOW, RGB565_BLACK));
|
||||
|
||||
memset(s_mnemonic, 0, sizeof(s_mnemonic));
|
||||
memset(s_seed, 0, sizeof(s_seed));
|
||||
memset(s_privkey, 0, sizeof(s_privkey));
|
||||
memset(s_pubkey, 0, sizeof(s_pubkey));
|
||||
memset(s_npub, 0, sizeof(s_npub));
|
||||
memset(s_npub_short, 0, sizeof(s_npub_short));
|
||||
memset(s_mnemonic_short, 0, sizeof(s_mnemonic_short));
|
||||
memset(s_frame_buf, 0, sizeof(s_frame_buf));
|
||||
memset(s_pubkey_hex, 0, sizeof(s_pubkey_hex));
|
||||
|
||||
if (generate_mnemonic_12(s_mnemonic, sizeof(s_mnemonic)) != 0) {
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 50, "mnemonic failed", RGB565_RED, RGB565_BLACK));
|
||||
return;
|
||||
if (out_index == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("BIP39 mnemonic: %s\n", s_mnemonic);
|
||||
format_mnemonic_short(s_mnemonic, s_mnemonic_short, sizeof(s_mnemonic_short));
|
||||
*out_index = 0;
|
||||
|
||||
if (mnemonic_to_seed(s_mnemonic, s_seed) != 0) {
|
||||
if (params == NULL || !cJSON_IsArray(params)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
params_count = cJSON_GetArraySize(params);
|
||||
if (params_count <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
last_item = cJSON_GetArrayItem(params, params_count - 1);
|
||||
if (last_item == NULL || !cJSON_IsObject(last_item)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
idx_item = cJSON_GetObjectItemCaseSensitive(last_item, "nostr_index");
|
||||
if (idx_item == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (!cJSON_IsNumber(idx_item)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
idx = idx_item->valueint;
|
||||
if (idx < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*out_index = (uint32_t)idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int derive_request_key(uint32_t nostr_index, uint8_t privkey_out[32], char pubkey_hex_out[65])
|
||||
{
|
||||
uint8_t pubkey[32];
|
||||
|
||||
if (privkey_out == NULL || pubkey_hex_out == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (derive_nostr_key_index(s_seed, nostr_index, privkey_out, pubkey) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bytes_to_hex(pubkey, sizeof(pubkey), pubkey_hex_out, 65);
|
||||
secure_memzero(pubkey, sizeof(pubkey));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_peer_and_message_params(cJSON *params,
|
||||
const char **peer_hex_out,
|
||||
const char **message_out,
|
||||
uint32_t *nostr_index_out)
|
||||
{
|
||||
cJSON *peer_item;
|
||||
cJSON *msg_item;
|
||||
|
||||
if (params == NULL || !cJSON_IsArray(params) ||
|
||||
peer_hex_out == NULL || message_out == NULL || nostr_index_out == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cJSON_GetArraySize(params) < 2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
peer_item = cJSON_GetArrayItem(params, 0);
|
||||
msg_item = cJSON_GetArrayItem(params, 1);
|
||||
|
||||
if (!cJSON_IsString(peer_item) || peer_item->valuestring == NULL ||
|
||||
!cJSON_IsString(msg_item) || msg_item->valuestring == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (parse_nostr_index_from_params(params, nostr_index_out) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*peer_hex_out = peer_item->valuestring;
|
||||
*message_out = msg_item->valuestring;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apply_mnemonic_and_enter_working(const char *mnemonic)
|
||||
{
|
||||
if (mnemonic == NULL || mnemonic[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
format_mnemonic_short(mnemonic, s_mnemonic_short, sizeof(s_mnemonic_short));
|
||||
|
||||
if (mnemonic_to_seed(mnemonic, s_seed) != 0) {
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 50, "seed failed", RGB565_RED, RGB565_BLACK));
|
||||
memset(s_mnemonic, 0, sizeof(s_mnemonic));
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (derive_nostr_key(s_seed, s_privkey, s_pubkey) != 0) {
|
||||
if (derive_nostr_key_index(s_seed, 0u, s_privkey, s_pubkey) != 0) {
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 50, "key failed", RGB565_RED, RGB565_BLACK));
|
||||
memset(s_mnemonic, 0, sizeof(s_mnemonic));
|
||||
memset(s_seed, 0, sizeof(s_seed));
|
||||
return;
|
||||
secure_memzero(s_seed, sizeof(s_seed));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (npub_encode(s_pubkey, s_npub, sizeof(s_npub)) != 0) {
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 50, "npub failed", RGB565_RED, RGB565_BLACK));
|
||||
memset(s_mnemonic, 0, sizeof(s_mnemonic));
|
||||
memset(s_seed, 0, sizeof(s_seed));
|
||||
memset(s_privkey, 0, sizeof(s_privkey));
|
||||
memset(s_pubkey, 0, sizeof(s_pubkey));
|
||||
return;
|
||||
secure_memzero(s_seed, sizeof(s_seed));
|
||||
secure_memzero(s_pubkey, sizeof(s_pubkey));
|
||||
return -1;
|
||||
}
|
||||
|
||||
format_npub_short(s_npub, s_npub_short, sizeof(s_npub_short));
|
||||
bytes_to_hex(s_pubkey, sizeof(s_pubkey), s_pubkey_hex, sizeof(s_pubkey_hex));
|
||||
printf("Derived npub: %s\n", s_npub);
|
||||
printf("Derived pubkey hex: %s\n", s_pubkey_hex);
|
||||
printf("Derived npub[0]: %s\n", s_npub);
|
||||
printf("Derived pubkey hex[0]: %s\n", s_pubkey_hex);
|
||||
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 10, "nostr npub", RGB565_GREEN, RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 30, s_npub_short, RGB565_GREEN, RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 50, "mn:", RGB565_WHITE, RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(46, 50, s_mnemonic_short, RGB565_YELLOW, RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 70, "v" FIRMWARE_VERSION, RGB565_WHITE, RGB565_BLACK));
|
||||
show_idle_screen();
|
||||
|
||||
memset(s_seed, 0, sizeof(s_seed));
|
||||
memset(s_pubkey, 0, sizeof(s_pubkey));
|
||||
memset(s_mnemonic, 0, sizeof(s_mnemonic));
|
||||
secure_memzero(s_pubkey, sizeof(s_pubkey));
|
||||
return 0;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 90, "usb cdc+webusb", RGB565_CYAN, RGB565_BLACK));
|
||||
printf("usb cdc+webusb ready\n");
|
||||
|
||||
/* Prevent log text from corrupting framed transport bytes on this same USB stream. */
|
||||
esp_log_level_set("*", ESP_LOG_NONE);
|
||||
|
||||
buttons_init();
|
||||
|
||||
if (transport_init() != 0) {
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 90, "cdc init fail", RGB565_RED, RGB565_BLACK));
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
/* WebUSB temporarily disabled to preserve stable USB Serial/JTAG CDC enumeration. */
|
||||
static void run_signing_loop(void)
|
||||
{
|
||||
uint32_t rx_frames = 0;
|
||||
uint32_t tx_frames = 0;
|
||||
size_t last_len = 0;
|
||||
|
||||
while (1) {
|
||||
char stat1[32];
|
||||
@@ -870,6 +858,12 @@ void app_main(void)
|
||||
|
||||
rx_frames++;
|
||||
|
||||
/* Default to a concrete error so every received frame gets a response. */
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{\"code\":-32603,\"message\":\"internal error: unhandled request\"}}");
|
||||
|
||||
if (last_len < sizeof(s_frame_buf)) {
|
||||
char id_token[96] = {0};
|
||||
char method[64] = {0};
|
||||
@@ -914,37 +908,56 @@ void app_main(void)
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":%d,\"message\":\"%s\"}}",
|
||||
id_token, auth_rc, msg);
|
||||
} else if (strcmp(method, "get_public_key") == 0) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":\"%s\"}",
|
||||
id_token,
|
||||
s_pubkey_hex);
|
||||
cJSON *params = cJSON_GetObjectItemCaseSensitive(req, "params");
|
||||
uint32_t nostr_index = 0;
|
||||
uint8_t req_privkey[32];
|
||||
char req_pubkey_hex[65];
|
||||
|
||||
memset(req_privkey, 0, sizeof(req_privkey));
|
||||
memset(req_pubkey_hex, 0, sizeof(req_pubkey_hex));
|
||||
|
||||
if (!cJSON_IsArray(params) ||
|
||||
parse_nostr_index_from_params(params, &nostr_index) != 0 ||
|
||||
derive_request_key(nostr_index, req_privkey, req_pubkey_hex) != 0) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32602,\"message\":\"invalid params\"}}",
|
||||
id_token);
|
||||
} else {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":\"%s\"}",
|
||||
id_token,
|
||||
req_pubkey_hex);
|
||||
}
|
||||
|
||||
secure_memzero(req_privkey, sizeof(req_privkey));
|
||||
} else if (strcmp(method, "sign_event") == 0) {
|
||||
cJSON *params = cJSON_GetObjectItemCaseSensitive(req, "params");
|
||||
cJSON *event_in = NULL;
|
||||
uint32_t nostr_index = 0;
|
||||
uint8_t req_privkey[32];
|
||||
char req_pubkey_hex[65];
|
||||
|
||||
memset(req_privkey, 0, sizeof(req_privkey));
|
||||
memset(req_pubkey_hex, 0, sizeof(req_pubkey_hex));
|
||||
|
||||
if (cJSON_IsArray(params) && cJSON_GetArraySize(params) > 0) {
|
||||
event_in = cJSON_GetArrayItem(params, 0);
|
||||
}
|
||||
|
||||
if (event_in != NULL && cJSON_IsObject(event_in)) {
|
||||
approval_decision_t decision;
|
||||
if (event_in != NULL && cJSON_IsObject(event_in) &&
|
||||
parse_nostr_index_from_params(params, &nostr_index) == 0 &&
|
||||
derive_request_key(nostr_index, req_privkey, req_pubkey_hex) == 0) {
|
||||
approval_decision_t decision = APPROVAL_ALWAYS;
|
||||
|
||||
if (s_always_allow_sign) {
|
||||
decision = APPROVAL_ALWAYS;
|
||||
} else {
|
||||
show_approval_prompt_with_caller(event_in, caller_pubkey_hex);
|
||||
decision = wait_for_approval(APPROVAL_TIMEOUT_MS);
|
||||
show_idle_screen();
|
||||
}
|
||||
|
||||
if (decision == APPROVAL_ALWAYS) {
|
||||
s_always_allow_sign = true;
|
||||
}
|
||||
/* Firmware policy: never prompt, always approve sign requests. */
|
||||
s_always_allow_sign = true;
|
||||
|
||||
if ((decision == APPROVAL_ONCE || decision == APPROVAL_ALWAYS) &&
|
||||
build_signed_event_json(event_in, s_pubkey_hex, s_privkey, s_signed_event_buf, sizeof(s_signed_event_buf)) == 0) {
|
||||
build_signed_event_json(event_in, req_pubkey_hex, req_privkey, s_signed_event_buf, sizeof(s_signed_event_buf)) == 0) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
@@ -977,6 +990,212 @@ void app_main(void)
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32602,\"message\":\"invalid params\"}}",
|
||||
id_token);
|
||||
}
|
||||
|
||||
secure_memzero(req_privkey, sizeof(req_privkey));
|
||||
} else if (strcmp(method, "nip04_encrypt") == 0) {
|
||||
cJSON *params = cJSON_GetObjectItemCaseSensitive(req, "params");
|
||||
const char *peer_hex = NULL;
|
||||
const char *plaintext = NULL;
|
||||
uint32_t nostr_index = 0;
|
||||
uint8_t peer_pubkey[32];
|
||||
uint8_t req_privkey[32];
|
||||
char req_pubkey_hex[65];
|
||||
|
||||
memset(peer_pubkey, 0, sizeof(peer_pubkey));
|
||||
memset(req_privkey, 0, sizeof(req_privkey));
|
||||
memset(req_pubkey_hex, 0, sizeof(req_pubkey_hex));
|
||||
memset(s_encrypt_buf, 0, sizeof(s_encrypt_buf));
|
||||
|
||||
if (parse_peer_and_message_params(params, &peer_hex, &plaintext, &nostr_index) != 0 ||
|
||||
hex_to_bytes(peer_hex, peer_pubkey, sizeof(peer_pubkey)) != 0 ||
|
||||
derive_request_key(nostr_index, req_privkey, req_pubkey_hex) != 0) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32602,\"message\":\"invalid params\"}}",
|
||||
id_token);
|
||||
} else if (nostr_nip04_encrypt(req_privkey, peer_pubkey, plaintext,
|
||||
s_encrypt_buf, sizeof(s_encrypt_buf)) != NOSTR_SUCCESS) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32603,\"message\":\"nip04 encryption failed\"}}",
|
||||
id_token);
|
||||
} else {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":\"%s\"}",
|
||||
id_token,
|
||||
s_encrypt_buf);
|
||||
}
|
||||
|
||||
secure_memzero(req_privkey, sizeof(req_privkey));
|
||||
secure_memzero(peer_pubkey, sizeof(peer_pubkey));
|
||||
} else if (strcmp(method, "nip44_encrypt") == 0) {
|
||||
cJSON *params = cJSON_GetObjectItemCaseSensitive(req, "params");
|
||||
const char *peer_hex = NULL;
|
||||
const char *plaintext = NULL;
|
||||
uint32_t nostr_index = 0;
|
||||
uint8_t peer_pubkey[32];
|
||||
uint8_t req_privkey[32];
|
||||
char req_pubkey_hex[65];
|
||||
|
||||
memset(peer_pubkey, 0, sizeof(peer_pubkey));
|
||||
memset(req_privkey, 0, sizeof(req_privkey));
|
||||
memset(req_pubkey_hex, 0, sizeof(req_pubkey_hex));
|
||||
memset(s_encrypt_buf, 0, sizeof(s_encrypt_buf));
|
||||
|
||||
if (parse_peer_and_message_params(params, &peer_hex, &plaintext, &nostr_index) != 0 ||
|
||||
hex_to_bytes(peer_hex, peer_pubkey, sizeof(peer_pubkey)) != 0 ||
|
||||
derive_request_key(nostr_index, req_privkey, req_pubkey_hex) != 0) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32602,\"message\":\"invalid params\"}}",
|
||||
id_token);
|
||||
} else if (nostr_nip44_encrypt(req_privkey, peer_pubkey, plaintext,
|
||||
s_encrypt_buf, sizeof(s_encrypt_buf)) != NOSTR_SUCCESS) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32603,\"message\":\"nip44 encryption failed\"}}",
|
||||
id_token);
|
||||
} else {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":\"%s\"}",
|
||||
id_token,
|
||||
s_encrypt_buf);
|
||||
}
|
||||
|
||||
secure_memzero(req_privkey, sizeof(req_privkey));
|
||||
secure_memzero(peer_pubkey, sizeof(peer_pubkey));
|
||||
} else if (strcmp(method, "nip04_decrypt") == 0) {
|
||||
cJSON *params = cJSON_GetObjectItemCaseSensitive(req, "params");
|
||||
const char *peer_hex = NULL;
|
||||
const char *ciphertext = NULL;
|
||||
uint32_t nostr_index = 0;
|
||||
uint8_t peer_pubkey[32];
|
||||
uint8_t req_privkey[32];
|
||||
char req_pubkey_hex[65];
|
||||
|
||||
memset(peer_pubkey, 0, sizeof(peer_pubkey));
|
||||
memset(req_privkey, 0, sizeof(req_privkey));
|
||||
memset(req_pubkey_hex, 0, sizeof(req_pubkey_hex));
|
||||
memset(s_encrypt_buf, 0, sizeof(s_encrypt_buf));
|
||||
|
||||
if (parse_peer_and_message_params(params, &peer_hex, &ciphertext, &nostr_index) != 0 ||
|
||||
hex_to_bytes(peer_hex, peer_pubkey, sizeof(peer_pubkey)) != 0 ||
|
||||
derive_request_key(nostr_index, req_privkey, req_pubkey_hex) != 0) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32602,\"message\":\"invalid params\"}}",
|
||||
id_token);
|
||||
} else if (nostr_nip04_decrypt(req_privkey, peer_pubkey, ciphertext,
|
||||
s_encrypt_buf, sizeof(s_encrypt_buf)) != NOSTR_SUCCESS) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32603,\"message\":\"nip04 decryption failed\"}}",
|
||||
id_token);
|
||||
} else {
|
||||
cJSON *resp_obj = cJSON_CreateObject();
|
||||
if (resp_obj == NULL) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32603,\"message\":\"internal error\"}}",
|
||||
id_token);
|
||||
} else {
|
||||
cJSON_AddStringToObject(resp_obj, "jsonrpc", "2.0");
|
||||
cJSON_AddRawToObject(resp_obj, "id", id_token);
|
||||
cJSON_AddStringToObject(resp_obj, "result", s_encrypt_buf);
|
||||
{
|
||||
char *resp_json = cJSON_PrintUnformatted(resp_obj);
|
||||
if (resp_json != NULL && strlen(resp_json) < sizeof(s_response_buf)) {
|
||||
memcpy(s_response_buf, resp_json, strlen(resp_json) + 1);
|
||||
} else {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32603,\"message\":\"internal error\"}}",
|
||||
id_token);
|
||||
}
|
||||
if (resp_json != NULL) {
|
||||
cJSON_free(resp_json);
|
||||
}
|
||||
}
|
||||
cJSON_Delete(resp_obj);
|
||||
}
|
||||
}
|
||||
|
||||
secure_memzero(req_privkey, sizeof(req_privkey));
|
||||
secure_memzero(peer_pubkey, sizeof(peer_pubkey));
|
||||
} else if (strcmp(method, "nip44_decrypt") == 0) {
|
||||
cJSON *params = cJSON_GetObjectItemCaseSensitive(req, "params");
|
||||
const char *peer_hex = NULL;
|
||||
const char *ciphertext = NULL;
|
||||
uint32_t nostr_index = 0;
|
||||
uint8_t peer_pubkey[32];
|
||||
uint8_t req_privkey[32];
|
||||
char req_pubkey_hex[65];
|
||||
|
||||
memset(peer_pubkey, 0, sizeof(peer_pubkey));
|
||||
memset(req_privkey, 0, sizeof(req_privkey));
|
||||
memset(req_pubkey_hex, 0, sizeof(req_pubkey_hex));
|
||||
memset(s_encrypt_buf, 0, sizeof(s_encrypt_buf));
|
||||
|
||||
if (parse_peer_and_message_params(params, &peer_hex, &ciphertext, &nostr_index) != 0 ||
|
||||
hex_to_bytes(peer_hex, peer_pubkey, sizeof(peer_pubkey)) != 0 ||
|
||||
derive_request_key(nostr_index, req_privkey, req_pubkey_hex) != 0) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32602,\"message\":\"invalid params\"}}",
|
||||
id_token);
|
||||
} else if (nostr_nip44_decrypt(req_privkey, peer_pubkey, ciphertext,
|
||||
s_encrypt_buf, sizeof(s_encrypt_buf)) != NOSTR_SUCCESS) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32603,\"message\":\"nip44 decryption failed\"}}",
|
||||
id_token);
|
||||
} else {
|
||||
cJSON *resp_obj = cJSON_CreateObject();
|
||||
if (resp_obj == NULL) {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32603,\"message\":\"internal error\"}}",
|
||||
id_token);
|
||||
} else {
|
||||
cJSON_AddStringToObject(resp_obj, "jsonrpc", "2.0");
|
||||
cJSON_AddRawToObject(resp_obj, "id", id_token);
|
||||
cJSON_AddStringToObject(resp_obj, "result", s_encrypt_buf);
|
||||
{
|
||||
char *resp_json = cJSON_PrintUnformatted(resp_obj);
|
||||
if (resp_json != NULL && strlen(resp_json) < sizeof(s_response_buf)) {
|
||||
memcpy(s_response_buf, resp_json, strlen(resp_json) + 1);
|
||||
} else {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":-32603,\"message\":\"internal error\"}}",
|
||||
id_token);
|
||||
}
|
||||
if (resp_json != NULL) {
|
||||
cJSON_free(resp_json);
|
||||
}
|
||||
}
|
||||
cJSON_Delete(resp_obj);
|
||||
}
|
||||
}
|
||||
|
||||
secure_memzero(req_privkey, sizeof(req_privkey));
|
||||
secure_memzero(peer_pubkey, sizeof(peer_pubkey));
|
||||
} else {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
@@ -993,13 +1212,82 @@ void app_main(void)
|
||||
if ((!via_webusb && send_frame_stdout((const uint8_t *)s_response_buf, strlen(s_response_buf)) == 0) ||
|
||||
(via_webusb && webusb_send_frame((const uint8_t *)s_response_buf, strlen(s_response_buf)) == 0)) {
|
||||
tx_frames++;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "failed to send JSON-RPC response over %s", via_webusb ? "WebUSB" : "CDC");
|
||||
}
|
||||
} else {
|
||||
snprintf(
|
||||
s_response_buf,
|
||||
sizeof(s_response_buf),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{\"code\":-32600,\"message\":\"request too large\"}}");
|
||||
|
||||
if ((!via_webusb && send_frame_stdout((const uint8_t *)s_response_buf, strlen(s_response_buf)) == 0) ||
|
||||
(via_webusb && webusb_send_frame((const uint8_t *)s_response_buf, strlen(s_response_buf)) == 0)) {
|
||||
tx_frames++;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "failed to send oversized-request error over %s", via_webusb ? "WebUSB" : "CDC");
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(stat1, sizeof(stat1), "rx:%" PRIu32 " tx:%" PRIu32, rx_frames, tx_frames);
|
||||
snprintf(stat2, sizeof(stat2), "last:%uB", (unsigned)last_len);
|
||||
ESP_ERROR_CHECK(display_fill_rect(10, 110, 220, 24, RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 110, stat1, RGB565_WHITE, RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 122, stat2, RGB565_CYAN, RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 110, stat1, RGB565_DIM_GRAY, RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 122, stat2, RGB565_DIM_GRAY, RGB565_BLACK));
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "n_signer v%s booting...", FIRMWARE_VERSION);
|
||||
printf("n_signer booting\n");
|
||||
|
||||
ESP_ERROR_CHECK(display_init());
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
|
||||
memset(s_mnemonic, 0, sizeof(s_mnemonic));
|
||||
memset(s_seed, 0, sizeof(s_seed));
|
||||
memset(s_privkey, 0, sizeof(s_privkey));
|
||||
memset(s_pubkey, 0, sizeof(s_pubkey));
|
||||
memset(s_npub, 0, sizeof(s_npub));
|
||||
memset(s_npub_short, 0, sizeof(s_npub_short));
|
||||
memset(s_mnemonic_short, 0, sizeof(s_mnemonic_short));
|
||||
memset(s_frame_buf, 0, sizeof(s_frame_buf));
|
||||
memset(s_pubkey_hex, 0, sizeof(s_pubkey_hex));
|
||||
|
||||
if (ui_run_until_working(s_mnemonic) != 0) {
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text_centered(56, "UI FAILED", RGB565_RED, RGB565_BLACK));
|
||||
return;
|
||||
}
|
||||
|
||||
if (apply_mnemonic_and_enter_working(s_mnemonic) != 0) {
|
||||
secure_memzero(s_mnemonic, sizeof(s_mnemonic));
|
||||
return;
|
||||
}
|
||||
|
||||
secure_memzero(s_mnemonic, sizeof(s_mnemonic));
|
||||
printf("usb cdc+webusb ready\n");
|
||||
|
||||
/* Prevent log text from corrupting framed transport bytes on this same USB stream. */
|
||||
esp_log_level_set("*", ESP_LOG_NONE);
|
||||
|
||||
buttons_init();
|
||||
|
||||
if (transport_init() != 0) {
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 90, "cdc init fail", RGB565_RED, RGB565_BLACK));
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
if (nostr_init() != NOSTR_SUCCESS) {
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 90, "nostr init fail", RGB565_RED, RGB565_BLACK));
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
/* WebUSB temporarily disabled to preserve stable USB Serial/JTAG CDC enumeration. */
|
||||
run_signing_loop();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user