v0.0.2 - Add random mnemonic startup flow and multi-instance random socket naming

This commit is contained in:
Laan Tungir
2026-05-02 13:46:01 -04:00
parent 268b33b6d3
commit 21f1258844
25 changed files with 1175 additions and 285 deletions

View File

@@ -63,8 +63,10 @@ int main(void) {
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);
@@ -77,27 +79,34 @@ int main(void) {
mnemonic_init(&mnemonic);
mnemonic_load(&mnemonic, valid_12);
dispatcher_init(&dispatcher, &table, &mnemonic);
memset(&key_store, 0, sizeof(key_store));
dispatcher_init(&dispatcher, &table, &mnemonic, &key_store);
/* 1. Valid get_public_key no selector -> default main, not_yet_derived */
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 not_yet_derived",
response_has(resp, "\"id\":\"1\"") && response_has(resp, "\"result\":\"not_yet_derived\""));
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\":[\"{}\",{\"role\":\"main\"}]}");
check_condition("sign_event with role=main returns stub",
response_has(resp, "\"id\":\"2\"") && response_has(resp, "\"result\":\"stub:sign_event_ok\""));
"{\"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\":[\"{}\",{\"nostr_index\":0}]}");
check_condition("sign_event with nostr_index=0 returns stub",
response_has(resp, "\"id\":\"3\"") && response_has(resp, "\"result\":\"stub:sign_event_ok\""));
"{\"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 */
@@ -152,7 +161,10 @@ int main(void) {
response_has(resp, "\"id\":\"10\"") && response_has(resp, "\"code\":-32601"));
free(resp);
printf("%d/10 tests passed\n", g_passes);
crypto_wipe(&key_store);
crypto_cleanup();
return (g_passes == 10 && g_total == 10) ? 0 : 1;
printf("%d/12 tests passed\n", g_passes);
return (g_passes == 12 && g_total == 12) ? 0 : 1;
}

View File

@@ -14,7 +14,7 @@
#include <time.h>
#include <unistd.h>
#define SOCKET_NAME "nsigner"
#define SOCKET_NAME "nsigner_test_run"
#define MAX_MSG_SIZE 65536
static int g_failures = 0;
@@ -174,7 +174,7 @@ static int request_roundtrip(const char *req, char **resp) {
int main(void) {
int stdin_pipe[2];
pid_t child;
const char *mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\n\n";
const char *mnemonic = "\nabandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\n";
char *resp = NULL;
int status;
@@ -203,7 +203,7 @@ int main(void) {
close(stdin_pipe[0]);
close(stdin_pipe[1]);
execl("./build/nsigner", "./build/nsigner", (char *)NULL);
execl("./build/nsigner", "./build/nsigner", "--socket-name", SOCKET_NAME, (char *)NULL);
_exit(127);
}
@@ -226,9 +226,9 @@ int main(void) {
free(resp);
resp = NULL;
if (request_roundtrip("{\"id\":\"2\",\"method\":\"sign_event\",\"params\":[\"{}\",{\"role\":\"main\"}]}", &resp) == 0) {
if (request_roundtrip("{\"id\":\"2\",\"method\":\"sign_event\",\"params\":[\"{\\\"kind\\\":1,\\\"content\\\":\\\"hello\\\",\\\"tags\\\":[],\\\"created_at\\\":1700000000}\",{\"role\":\"main\"}]}", &resp) == 0) {
check_condition("sign_event response id", strstr(resp, "\"id\":\"2\"") != NULL);
check_condition("sign_event stub result", strstr(resp, "stub:sign_event_ok") != NULL);
check_condition("sign_event has result", strstr(resp, "\"result\":") != NULL);
} else {
check_condition("sign_event request roundtrip", 0);
}

View File

@@ -46,6 +46,8 @@ int main(void) {
const char *invalid_11 = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
char invalid_25[MNEMONIC_MAX_LEN];
const char *loaded_phrase;
char generated_12[MNEMONIC_MAX_LEN];
char generated_24[MNEMONIC_MAX_LEN];
int rc;
mnemonic_init(&state);
@@ -73,6 +75,22 @@ int main(void) {
check_condition("reject invalid 25-word mnemonic", rc == -1);
check_condition("state remains unloaded after invalid 25-word load", mnemonic_is_loaded(&state) == 0);
rc = mnemonic_generate(12, generated_12, sizeof(generated_12));
check_condition("mnemonic_generate(12) returns 0", rc == 0);
check_condition("mnemonic_generate(12) output non-empty", rc == 0 && generated_12[0] != '\0');
check_condition("load generated 12-word mnemonic", rc == 0 && mnemonic_load(&state, generated_12) == 0);
check_condition("generated 12 word_count == 12", mnemonic_is_loaded(&state) && state.word_count == 12);
mnemonic_unload(&state);
rc = mnemonic_generate(24, generated_24, sizeof(generated_24));
check_condition("mnemonic_generate(24) returns 0", rc == 0);
check_condition("mnemonic_generate(24) output non-empty", rc == 0 && generated_24[0] != '\0');
check_condition("load generated 24-word mnemonic", rc == 0 && mnemonic_load(&state, generated_24) == 0);
check_condition("generated 24 word_count == 24", mnemonic_is_loaded(&state) && state.word_count == 24);
mnemonic_unload(&state);
check_condition("two generated mnemonics differ", strcmp(generated_12, generated_24) != 0);
if (g_failures == 0) {
printf("ALL TESTS PASSED\n");
return 0;

51
tests/test_socket_name.c Normal file
View File

@@ -0,0 +1,51 @@
#include "../src/socket_name.h"
#include <stdio.h>
#include <string.h>
static int g_failures = 0;
static void check_condition(const char *name, int condition) {
if (condition) {
printf("PASS: %s\n", name);
} else {
printf("FAIL: %s\n", name);
g_failures++;
}
}
int main(void) {
char a[128];
char b[128];
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
check_condition("socket_name_random(a) succeeds", socket_name_random(a, sizeof(a)) == 0);
check_condition("socket_name_random(b) succeeds", socket_name_random(b, sizeof(b)) == 0);
check_condition("name a starts with nsigner_", strncmp(a, "nsigner_", 8) == 0);
check_condition("name b starts with nsigner_", strncmp(b, "nsigner_", 8) == 0);
{
const char *suffix = a + 8;
const char *underscore = strchr(suffix, '_');
check_condition("name a contains second underscore", underscore != NULL && underscore > suffix && underscore[1] != '\0');
}
{
const char *suffix = b + 8;
const char *underscore = strchr(suffix, '_');
check_condition("name b contains second underscore", underscore != NULL && underscore > suffix && underscore[1] != '\0');
}
check_condition("two generated names are typically different", strcmp(a, b) != 0);
if (g_failures == 0) {
printf("ALL TESTS PASSED\n");
return 0;
}
printf("TESTS FAILED: %d\n", g_failures);
return 1;
}