Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
-I ../secp256k1-frost/build/include \
-I components/crypto_asm/include \
-I components/secure_element/include \
-DESP_PLATFORM=0
-std=c11

scan-build:
runs-on: ubuntu-latest
Expand Down
12 changes: 11 additions & 1 deletion main/error_codes.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const char *error_category_name(int code) {
return "psbt";
case ERR_CAT_NOSTR:
return "nostr";
case ERR_CAT_ASSERT:
return "assert";
default:
return "unknown";
}
Expand Down Expand Up @@ -185,6 +187,9 @@ const char *error_name(int code) {
case ERR_NOSTR_RELAY:
return "NOSTR_RELAY";

case ERR_ASSERT:
return "ASSERT";

default:
return "UNKNOWN";
}
Expand Down Expand Up @@ -343,6 +348,9 @@ const char *error_to_string(int code) {
case ERR_NOSTR_RELAY:
return "Nostr relay error";

case ERR_ASSERT:
return "Assertion failed";

default:
return "Unknown error";
}
Expand All @@ -359,6 +367,8 @@ int error_to_jsonrpc_code(int code) {
case ERR_PROTOCOL_INTERNAL:
return -32603;
default:
return code;
if (code >= -32768 && code <= -32000)
return code;
return -32603;
}
}
28 changes: 28 additions & 0 deletions main/error_codes.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#define ERR_CAT_SE 0x0900
#define ERR_CAT_PSBT 0x0A00
#define ERR_CAT_NOSTR 0x0B00
#define ERR_CAT_ASSERT 0x0C00

#define ERROR_CATEGORY(code) ((code) & 0xFF00)
#define ERROR_DETAIL(code) ((code) & 0x00FF)
Expand Down Expand Up @@ -102,6 +103,33 @@
#define ERR_NOSTR_SIGN (ERR_CAT_NOSTR | 0x03)
#define ERR_NOSTR_RELAY (ERR_CAT_NOSTR | 0x04)

#define ERR_ASSERT (ERR_CAT_ASSERT | 0x01)

#ifdef ESP_PLATFORM
#include "esp_log.h"
#define KEEP_ASSERT_LOG(expr_str) ESP_LOGE("ASSERT", "%s:%d: %s", __FILE__, __LINE__, expr_str)
#else
#include <stdio.h>
#define KEEP_ASSERT_LOG(expr_str) \
fprintf(stderr, "E (ASSERT): %s:%d: %s\n", __FILE__, __LINE__, expr_str)
#endif

#define KEEP_ASSERT(expr) \
do { \
if (!(expr)) { \
KEEP_ASSERT_LOG(#expr); \
return ERR_ASSERT; \
} \
} while (0)

#define KEEP_ASSERT_VOID(expr) \
do { \
if (!(expr)) { \
KEEP_ASSERT_LOG(#expr); \
return; \
} \
} while (0)

const char *error_to_string(int code);
const char *error_name(int code);
const char *error_category_name(int code);
Expand Down
204 changes: 119 additions & 85 deletions main/frost_coordinator.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,34 @@
#include <string.h>
#include <stdlib.h>

#include "log_compat.h"

#ifdef ESP_PLATFORM
#include "esp_log.h"
#include "esp_websocket_client.h"
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#else
#include <stdio.h>
#define ESP_LOGI(tag, fmt, ...) printf("[%s] " fmt "\n", tag, ##__VA_ARGS__)
#define ESP_LOGE(tag, fmt, ...) printf("[%s] ERROR: " fmt "\n", tag, ##__VA_ARGS__)
#define ESP_LOGW(tag, fmt, ...) printf("[%s] WARN: " fmt "\n", tag, ##__VA_ARGS__)
#endif

#define TAG "frost_coord"
#define TAG "frost_coord"
#define MAX_WS_MESSAGE_SIZE 4096
#define MAX_PUBLISH_MSG_SIZE 4108
#define WS_SEND_TIMEOUT_MS 10000

static bool is_safe_subscription_id(const char *id) {
if (!id || *id == '\0')
return false;
size_t len = 0;
for (const char *p = id; *p; p++) {
char c = *p;
bool valid = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
c == '-' || c == '_';
if (!valid)
return false;
if (++len > 64)
return false;
}
return true;
}

typedef struct {
char url[RELAY_URL_LEN];
Expand Down Expand Up @@ -71,83 +86,89 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i
break;

case WEBSOCKET_EVENT_DATA:
if (data->op_code == 0x01 && data->data_len > 0) {
if (data->op_code == 0x01 && data->data_len > 0 &&
(size_t)data->data_len < MAX_WS_MESSAGE_SIZE) {
char *msg = malloc(data->data_len + 1);
if (msg) {
memcpy(msg, data->data_ptr, data->data_len);
msg[data->data_len] = '\0';

cJSON *arr = cJSON_Parse(msg);
if (arr && cJSON_IsArray(arr) && cJSON_GetArraySize(arr) >= 1) {
cJSON *type = cJSON_GetArrayItem(arr, 0);
if (type && cJSON_IsString(type)) {
if (strcmp(type->valuestring, "EVENT") == 0 &&
cJSON_GetArraySize(arr) >= 3) {
cJSON *event = cJSON_GetArrayItem(arr, 2);
if (event && cJSON_IsObject(event)) {
cJSON *kind = cJSON_GetObjectItem(event, "kind");
if (kind && cJSON_IsNumber(kind)) {
char *event_str = cJSON_PrintUnformatted(event);
if (event_str) {
int k = kind->valueint;
if (k == FROST_KIND_SIGN_REQUEST &&
g_ctx.callbacks.on_sign_request) {
frost_sign_request_t req;
if (frost_parse_sign_request(
event_str, &g_ctx.current_group, g_ctx.privkey,
&req) == 0) {
g_ctx.callbacks.on_sign_request(
&req, g_ctx.callbacks.user_ctx);
frost_sign_request_free(&req);
}
} else if (k == FROST_KIND_SIGN_RESPONSE &&
g_ctx.callbacks.on_sign_response) {
frost_sign_response_t resp;
if (frost_parse_sign_response(
event_str, &g_ctx.current_group, g_ctx.privkey,
&resp) == 0) {
g_ctx.callbacks.on_sign_response(
&resp, g_ctx.callbacks.user_ctx);
}
} else if (k == FROST_KIND_DKG_ROUND1 &&
g_ctx.callbacks.on_dkg_round1) {
frost_dkg_round1_t r1;
if (frost_parse_dkg_round1_event(
event_str, &g_ctx.current_group, g_ctx.privkey,
&r1) == 0) {
g_ctx.callbacks.on_dkg_round1(
&r1, g_ctx.callbacks.user_ctx);
}
} else if (k == FROST_KIND_DKG_ROUND2 &&
g_ctx.callbacks.on_dkg_round2) {
frost_dkg_round2_t r2;
if (frost_parse_dkg_round2_event(
event_str, &g_ctx.current_group, g_ctx.privkey,
&r2) == 0) {
g_ctx.callbacks.on_dkg_round2(
&r2, g_ctx.callbacks.user_ctx);
}
} else if (k == NIP46_KIND_NOSTR_CONNECT &&
g_ctx.callbacks.on_nip46_request) {
nip46_request_t nip46_req;
if (frost_parse_nip46_event(event_str, g_ctx.privkey,
&nip46_req) == 0) {
g_ctx.callbacks.on_nip46_request(
&nip46_req, g_ctx.callbacks.user_ctx);
frost_nip46_request_free(&nip46_req);
}
if (!msg)
break;
memcpy(msg, data->data_ptr, data->data_len);
msg[data->data_len] = '\0';

cJSON *arr = cJSON_Parse(msg);
free(msg);
if (arr && cJSON_IsArray(arr) && cJSON_GetArraySize(arr) >= 1) {
cJSON *type = cJSON_GetArrayItem(arr, 0);
if (type && cJSON_IsString(type)) {
if (strcmp(type->valuestring, "EVENT") == 0 && cJSON_GetArraySize(arr) >= 3) {
cJSON *event = cJSON_GetArrayItem(arr, 2);
if (event && cJSON_IsObject(event)) {
cJSON *kind = cJSON_GetObjectItem(event, "kind");
if (kind && cJSON_IsNumber(kind)) {
char *event_str = cJSON_PrintUnformatted(event);
if (event_str) {
int k = kind->valueint;
if (k == FROST_KIND_SIGN_REQUEST &&
g_ctx.callbacks.on_sign_request) {
frost_sign_request_t *req = malloc(sizeof(*req));
if (req && frost_parse_sign_request(
event_str, &g_ctx.current_group,
g_ctx.privkey, req) == 0) {
g_ctx.callbacks.on_sign_request(
req, g_ctx.callbacks.user_ctx);
frost_sign_request_free(req);
}
free(req);
} else if (k == FROST_KIND_SIGN_RESPONSE &&
g_ctx.callbacks.on_sign_response) {
frost_sign_response_t *resp = malloc(sizeof(*resp));
if (resp && frost_parse_sign_response(
event_str, &g_ctx.current_group,
g_ctx.privkey, resp) == 0) {
g_ctx.callbacks.on_sign_response(
resp, g_ctx.callbacks.user_ctx);
}
free(resp);
} else if (k == FROST_KIND_DKG_ROUND1 &&
g_ctx.callbacks.on_dkg_round1) {
frost_dkg_round1_t *r1 = malloc(sizeof(*r1));
if (r1 && frost_parse_dkg_round1_event(
event_str, &g_ctx.current_group,
g_ctx.privkey, r1) == 0) {
g_ctx.callbacks.on_dkg_round1(r1,
g_ctx.callbacks.user_ctx);
}
free(event_str);
free(r1);
} else if (k == FROST_KIND_DKG_ROUND2 &&
g_ctx.callbacks.on_dkg_round2) {
frost_dkg_round2_t *r2 = malloc(sizeof(*r2));
if (r2 && frost_parse_dkg_round2_event(
event_str, &g_ctx.current_group,
g_ctx.privkey, r2) == 0) {
g_ctx.callbacks.on_dkg_round2(r2,
g_ctx.callbacks.user_ctx);
}
free(r2);
} else if (k == NIP46_KIND_NOSTR_CONNECT &&
g_ctx.callbacks.on_nip46_request) {
nip46_request_t *nip46_req = malloc(sizeof(*nip46_req));
if (nip46_req &&
frost_parse_nip46_event(event_str, g_ctx.privkey,
nip46_req) == 0) {
g_ctx.callbacks.on_nip46_request(
nip46_req, g_ctx.callbacks.user_ctx);
frost_nip46_request_free(nip46_req);
}
free(nip46_req);
}
free(event_str);
}
}
}
}
cJSON_Delete(arr);
} else if (arr) {
cJSON_Delete(arr);
}
free(msg);
cJSON_Delete(arr);
} else if (arr) {
cJSON_Delete(arr);
}
}
break;
Expand Down Expand Up @@ -353,6 +374,8 @@ int frost_coordinator_set_group(const frost_group_t *group) {
int frost_coordinator_subscribe(const char *subscription_id) {
if (!g_initialized || !g_ctx.has_group)
return -1;
if (!is_safe_subscription_id(subscription_id))
return -2;

char pubkey_hex[65];
bytes_to_hex(g_ctx.pubkey, 32, pubkey_hex, sizeof(pubkey_hex));
Expand All @@ -367,8 +390,12 @@ int frost_coordinator_subscribe(const char *subscription_id) {
for (int i = 0; i < g_ctx.relay_count; i++) {
relay_connection_t *relay = &g_ctx.relays[i];
if (relay->state == COORDINATOR_STATE_CONNECTED && relay->ws_handle) {
esp_websocket_client_send_text(relay->ws_handle, filter, strlen(filter), portMAX_DELAY);
ESP_LOGI(TAG, "Subscribed on %s", relay->url);
int ret = esp_websocket_client_send_text(relay->ws_handle, filter, strlen(filter),
pdMS_TO_TICKS(WS_SEND_TIMEOUT_MS));
if (ret < 0)
ESP_LOGW(TAG, "Subscribe send failed on %s: %d", relay->url, ret);
else
ESP_LOGI(TAG, "Subscribed on %s", relay->url);
}
}
#endif
Expand All @@ -380,6 +407,8 @@ int frost_coordinator_subscribe(const char *subscription_id) {
int frost_coordinator_unsubscribe(const char *subscription_id) {
if (!g_initialized)
return -1;
if (!is_safe_subscription_id(subscription_id))
return -2;

char close_msg[128];
snprintf(close_msg, sizeof(close_msg), "[\"CLOSE\",\"%s\"]", subscription_id);
Expand All @@ -388,8 +417,10 @@ int frost_coordinator_unsubscribe(const char *subscription_id) {
for (int i = 0; i < g_ctx.relay_count; i++) {
relay_connection_t *relay = &g_ctx.relays[i];
if (relay->state == COORDINATOR_STATE_CONNECTED && relay->ws_handle) {
esp_websocket_client_send_text(relay->ws_handle, close_msg, strlen(close_msg),
portMAX_DELAY);
int ret = esp_websocket_client_send_text(relay->ws_handle, close_msg, strlen(close_msg),
pdMS_TO_TICKS(WS_SEND_TIMEOUT_MS));
if (ret < 0)
ESP_LOGW(TAG, "Unsubscribe send failed on %s: %d", relay->url, ret);
}
}
#endif
Expand All @@ -402,23 +433,26 @@ static int publish_event(const char *event_json) {
return -1;

size_t msg_len = strlen(event_json) + 12;
char *msg = malloc(msg_len);
if (msg_len > MAX_PUBLISH_MSG_SIZE)
return -1;
char *msg = malloc(msg_len + 1);
if (!msg)
return -1;

snprintf(msg, msg_len, "[\"EVENT\",%s]", event_json);
snprintf(msg, msg_len + 1, "[\"EVENT\",%s]", event_json);

int published = 0;
#ifdef ESP_PLATFORM
for (int i = 0; i < g_ctx.relay_count; i++) {
relay_connection_t *relay = &g_ctx.relays[i];
if (relay->state == COORDINATOR_STATE_CONNECTED && relay->ws_handle) {
esp_websocket_client_send_text(relay->ws_handle, msg, strlen(msg), portMAX_DELAY);
published++;
int ret = esp_websocket_client_send_text(relay->ws_handle, msg, strlen(msg),
pdMS_TO_TICKS(WS_SEND_TIMEOUT_MS));
if (ret >= 0)
published++;
}
}
#endif

free(msg);
ESP_LOGI(TAG, "Published to %d relays", published);
return published;
Expand Down
Loading