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

@@ -70,13 +70,15 @@ This reduces deployment variability and shrinks the runtime trust surface.
When started, `n_signer` immediately enters terminal input mode:
1. Prompt for mnemonic (terminal echo disabled).
2. Parse and validate mnemonic.
3. Build in-memory role/selector state.
4. Initialize transport endpoints.
5. Switch to running status display.
1. Choose mnemonic source: `[E]nter existing mnemonic` (default) or `[G]enerate new mnemonic`.
- On `E`: prompt for mnemonic with terminal echo disabled, then validate.
- On `G`: generate a fresh 12-word BIP-39 mnemonic from `getrandom(2)`, display it numbered with a "WRITE THIS DOWN — IT WILL NOT BE SHOWN AGAIN" warning, then continue. There is no confirmation step.
2. Build in-memory role/selector state from the mnemonic.
3. Pick the abstract socket name (random BIP-39 pair, or `--socket-name` override).
4. Initialize transport endpoints and bind the socket.
5. Switch to running status display, with the signer name and socket address shown in the banner.
No startup files are read or written.
No startup files are read or written. The mnemonic — typed or generated — lives only in `mlock`'d memory and is zeroized on shutdown or crash.
### 3.2 Running phase (status display + signer)
@@ -207,7 +209,9 @@ This prevents cross-protocol misuse inside one mnemonic-rooted signer process.
### 7.1 Linux desktop: abstract namespace Unix socket
Primary local transport is AF_UNIX abstract namespace (for example `@nsigner`).
Primary local transport is AF_UNIX abstract namespace.
Each running `nsigner` process binds to a unique abstract name of the form `@nsigner_<word1>_<word2>`, where the two words are picked at random from the BIP-39 English wordlist at startup (e.g. `@nsigner_hairy_dog`). This lets multiple signers coexist on one host.
Properties:
@@ -215,6 +219,17 @@ Properties:
- endpoint lifetime bound to process/kernel namespace
- no stale socket files
- caller identity via peer credentials (`SO_PEERCRED`)
- per-launch random name avoids collisions between concurrent instances and leaks no seed-derived identifier
Naming rules:
- Default: random pick at startup, displayed in the TUI banner.
- Override: `--socket-name <name>` forces a specific name (useful for scripts and tests).
- Collision: if the chosen random name is already bound by another process, `nsigner` retries with a fresh pair up to 8 times before erroring out with a hint to use `--socket-name`.
Discovery:
- `nsigner list` enumerates currently bound `nsigner_*` abstract sockets by reading `/proc/net/unix`.
### 7.2 ESP32 MCU: USB-CDC serial
@@ -258,23 +273,44 @@ Qubes deployment runs `n_signer` in a dedicated signer qube as a foreground proc
nsigner
```
Program starts in attached foreground mode and prompts for mnemonic.
Program starts in attached foreground mode and prompts for mnemonic. After mnemonic acceptance, the TUI banner shows the randomly assigned signer name and its abstract socket address.
To force a specific socket name (e.g. for scripted clients):
```bash
nsigner --socket-name my_test_signer
```
### 9.2 Send a request (client mode)
From another terminal:
From another terminal, target the signer by its socket name:
```bash
nsigner client '{"id":"1","method":"get_public_key","params":[]}'
nsigner client --socket-name nsigner_hairy_dog '{"id":"1","method":"get_public_key","params":[]}'
```
If only one signer is running you can omit the override and the client will use the default discovery rule.
Example signing request:
```bash
nsigner client '{"id":"2","method":"sign_event","params":["<event_json>",{"role":"main"}]}'
nsigner client --socket-name nsigner_hairy_dog '{"id":"2","method":"sign_event","params":["<event_json>",{"role":"main"}]}'
```
### 9.3 Example session
### 9.3 List running signers
```bash
nsigner list
```
Prints the abstract socket names of any currently running `nsigner` instances, e.g.:
```text
@nsigner_hairy_dog
@nsigner_brave_canyon
```
### 9.4 Example session
Terminal A:
@@ -282,14 +318,16 @@ Terminal A:
$ nsigner
[unlock] enter mnemonic:
[ok] session unlocked
[listen] unix-abstract:@nsigner
signer name : hairy dog
socket : @nsigner_hairy_dog
[listen] unix-abstract:@nsigner_hairy_dog
[prompt] caller=uid:1000 method=sign_event role=main -> allow? (y/n)
```
Terminal B:
```text
$ nsigner client '{"id":"2","method":"sign_event","params":["<event_json>",{"role":"main"}]}'
$ nsigner client --socket-name nsigner_hairy_dog '{"id":"2","method":"sign_event","params":["<event_json>",{"role":"main"}]}'
{"id":"2","result":"<signed_event_json>"}
```