#include "../src/dispatcher.h" #include "../src/enforcement.h" #include "../src/role_table.h" #include #include #include static int g_passes = 0; static int g_total = 0; static void check_condition(const char *name, int condition) { g_total++; if (condition) { printf("PASS: %s\n", name); g_passes++; } else { printf("FAIL: %s\n", name); } } static int response_has(const char *response, const char *needle) { return (response != NULL && needle != NULL && strstr(response, needle) != NULL); } static role_entry_t make_nostr_entry(const char *name, int idx) { role_entry_t e; memset(&e, 0, sizeof(e)); strncpy(e.name, name, sizeof(e.name) - 1); strncpy(e.purpose_str, "nostr", sizeof(e.purpose_str) - 1); strncpy(e.curve_str, "secp256k1", sizeof(e.curve_str) - 1); e.purpose = role_purpose_from_str(e.purpose_str); e.curve = role_curve_from_str(e.curve_str); e.selector_type = SELECTOR_NOSTR_INDEX; e.nostr_index = idx; e.derived = 0; return e; } static role_entry_t make_path_entry(const char *name, const char *purpose, const char *curve, const char *path) { role_entry_t e; memset(&e, 0, sizeof(e)); strncpy(e.name, name, sizeof(e.name) - 1); strncpy(e.purpose_str, purpose, sizeof(e.purpose_str) - 1); strncpy(e.curve_str, curve, sizeof(e.curve_str) - 1); strncpy(e.role_path, path, sizeof(e.role_path) - 1); e.purpose = role_purpose_from_str(e.purpose_str); e.curve = role_curve_from_str(e.curve_str); e.selector_type = SELECTOR_ROLE_PATH; e.nostr_index = -1; e.derived = 0; return e; } int main(void) { role_table_t table; role_entry_t main_role; role_entry_t ssh_role; mnemonic_state_t mnemonic; dispatcher_ctx_t dispatcher; key_store_t key_store; const char *valid_12 = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; char *resp; int derived; role_table_init(&table); main_role = make_nostr_entry("main", 0); ssh_role = make_path_entry("ssh_key", "ssh", "ed25519", "m/44'/822'/0'/0/0"); role_table_add(&table, &main_role); role_table_add(&table, &ssh_role); mnemonic_init(&mnemonic); mnemonic_load(&mnemonic, valid_12); memset(&key_store, 0, sizeof(key_store)); dispatcher_init(&dispatcher, &table, &mnemonic, &key_store); derived = crypto_init(); check_condition("crypto_init succeeds", derived == 0); derived = crypto_derive_all(&key_store, &table, &mnemonic); check_condition("crypto_derive_all derives at least one key", derived >= 1); /* 1. Valid get_public_key no selector -> default main, real pubkey */ resp = dispatcher_handle_request(&dispatcher, "{\"id\":\"1\",\"method\":\"get_public_key\",\"params\":[\"\"]}"); check_condition("get_public_key default role returns derived hex", response_has(resp, "\"id\":\"1\"") && !response_has(resp, "not_yet_derived") && response_has(resp, "\"result\":\"")); free(resp); /* 2. sign_event with role main */ resp = dispatcher_handle_request(&dispatcher, "{\"id\":\"2\",\"method\":\"sign_event\",\"params\":[\"{\\\"kind\\\":1,\\\"content\\\":\\\"hello\\\",\\\"tags\\\":[]}\",{\"role\":\"main\"}]}"); check_condition("sign_event with role=main returns signed event", response_has(resp, "\"id\":\"2\"") && response_has(resp, "pubkey") && response_has(resp, "sig") && response_has(resp, "created_at")); free(resp); /* 3. sign_event with nostr_index 0 */ resp = dispatcher_handle_request(&dispatcher, "{\"id\":\"3\",\"method\":\"sign_event\",\"params\":[\"{\\\"kind\\\":1,\\\"content\\\":\\\"hello2\\\",\\\"tags\\\":[]}\",{\"nostr_index\":0}]}"); check_condition("sign_event with nostr_index=0 returns signed event", response_has(resp, "\"id\":\"3\"") && response_has(resp, "pubkey") && response_has(resp, "sig") && response_has(resp, "created_at")); free(resp); /* 4. ambiguous selector role + nostr_index */ resp = dispatcher_handle_request(&dispatcher, "{\"id\":\"4\",\"method\":\"sign_event\",\"params\":[\"{}\",{\"role\":\"main\",\"nostr_index\":0}]}"); check_condition("ambiguous selector returns 1001", response_has(resp, "\"id\":\"4\"") && response_has(resp, "\"code\":1001")); free(resp); /* 5. role not found */ resp = dispatcher_handle_request(&dispatcher, "{\"id\":\"5\",\"method\":\"sign_event\",\"params\":[\"{}\",{\"role\":\"nonexistent\"}]}"); check_condition("role not found returns 1002", response_has(resp, "\"id\":\"5\"") && response_has(resp, "\"code\":1002")); free(resp); /* 6. purpose mismatch: sign_event against ssh role */ resp = dispatcher_handle_request(&dispatcher, "{\"id\":\"6\",\"method\":\"sign_event\",\"params\":[\"{}\",{\"role\":\"ssh_key\"}]}"); check_condition("purpose mismatch returns 1004", response_has(resp, "\"id\":\"6\"") && response_has(resp, "\"code\":1004")); free(resp); /* 7. invalid JSON */ resp = dispatcher_handle_request(&dispatcher, "{\"id\":\"7\",\"method\":\"sign_event\",\"params\":[\"{}\"]"); check_condition("invalid JSON returns -32700", response_has(resp, "\"code\":-32700")); free(resp); /* 8. missing method */ resp = dispatcher_handle_request(&dispatcher, "{\"id\":\"8\",\"params\":[\"{}\"]}"); check_condition("missing method returns -32600", response_has(resp, "\"id\":\"8\"") && response_has(resp, "\"code\":-32600")); free(resp); /* 9. mnemonic not loaded */ mnemonic_unload(&mnemonic); resp = dispatcher_handle_request(&dispatcher, "{\"id\":\"9\",\"method\":\"get_public_key\",\"params\":[\"\"]}"); check_condition("mnemonic not loaded returns 1006", response_has(resp, "\"id\":\"9\"") && response_has(resp, "\"code\":1006")); free(resp); mnemonic_load(&mnemonic, valid_12); /* 10. unknown verb via enforcement */ resp = dispatcher_handle_request(&dispatcher, "{\"id\":\"10\",\"method\":\"foo_bar\",\"params\":[\"\"]}"); check_condition("unknown verb returns -32601", response_has(resp, "\"id\":\"10\"") && response_has(resp, "\"code\":-32601")); free(resp); crypto_wipe(&key_store); crypto_cleanup(); printf("%d/12 tests passed\n", g_passes); return (g_passes == 12 && g_total == 12) ? 0 : 1; }