v0.2.54 - Full n_signer transport support in setup wizard: added nsigner_serial, nsigner_fds, and nsigner_qrexec (Qubes cross-qube) modes. Updated nostr_core_lib to v0.6.10 (qrexec transport). Extended signer_config_t with serial_device, fds_read_fd/fds_write_fd, target_qube, service_name fields. Added --signer-serial, --signer-fds, --signer-qrexec, --signer-service CLI flags. Wizard transport picker now offers all 5 remote transports with auto-discovery for unix/serial. Existing-agent flow supports signer-based identity (no nsec required) with NIP-44 decrypt/encrypt routed through signer verbs. Systemd install supports serial/qrexec; fds rejected with clear message. Connectivity-check errors now pause for Enter before re-rendering. Deleted nostr_core_lib.old. Added docs/SIGNER.md.
This commit is contained in:
+138
@@ -0,0 +1,138 @@
|
||||
# Signer Providers
|
||||
|
||||
Didactyl can sign Nostr events and perform NIP-04/NIP-44 encryption either with
|
||||
an in-process private key (`local`) or by delegating to a running
|
||||
[`n_signer`](../n_signer) process over one of five transports. In any
|
||||
`nsigner_*` mode the agent process holds **no nsec** — the private key lives
|
||||
only in `n_signer`'s locked memory, and the agent pubkey is discovered at
|
||||
startup via `nostr_signer_get_public_key()`.
|
||||
|
||||
## Modes
|
||||
|
||||
| Mode | Transport | Holds nsec in agent? | Persistable to genesis? | Installable as systemd? |
|
||||
|------|-----------|----------------------|-------------------------|-------------------------|
|
||||
| `local` | in-process | yes | yes | yes |
|
||||
| `nsigner_unix` | AF_UNIX abstract socket | no | yes | yes |
|
||||
| `nsigner_tcp` | TCP `host:port` | no | yes | yes |
|
||||
| `nsigner_serial` | USB CDC-ACM serial | no | yes | yes |
|
||||
| `nsigner_fds` | pre-connected fd pair | no | **no** (runtime-only) | **no** |
|
||||
| `nsigner_qrexec` | Qubes qrexec (cross-qube) | no | yes | yes |
|
||||
|
||||
### `local`
|
||||
Default, backward-compatible. The 32-byte private key is derived from
|
||||
`keys.nsec` and held in `cfg.keys.private_key`. Use this when you accept the
|
||||
nsec living in the agent process.
|
||||
|
||||
### `nsigner_unix`
|
||||
Delegates to `n_signer` over an abstract namespace Unix socket
|
||||
(`@<socket_name>`). If `socket_name` is empty, the agent auto-discovers running
|
||||
`n_signer` sockets via `nsigner_transport_list_unix()`. Best for same-host
|
||||
deployments where `n_signer` runs in an attended terminal.
|
||||
|
||||
### `nsigner_tcp`
|
||||
Delegates to `n_signer` over TCP. Requires `auth_privkey_hex` (64-char hex) for
|
||||
the kind-27235 auth envelope that `n_signer` enforces on TCP listeners. Use for
|
||||
cross-host or FIPS-mode deployments.
|
||||
|
||||
### `nsigner_serial`
|
||||
Delegates to `n_signer` over a USB CDC-ACM serial device (e.g. `/dev/ttyACM0`).
|
||||
The wizard auto-discovers `/dev/ttyACM*` devices via
|
||||
`nsigner_transport_list_serial()`. Use for air-gapped/hardware-token signing.
|
||||
|
||||
### `nsigner_fds`
|
||||
Delegates to `n_signer` over a pre-connected file-descriptor pair
|
||||
(`read_fd`, `write_fd`). Intended for qrexec-style fd-passing deployments.
|
||||
**Cannot be persisted to genesis** (fds are runtime-only) and **cannot be
|
||||
installed as a systemd service** (fds cannot be embedded in `ExecStart`). Supply
|
||||
via `--signer-fds <read:write>` at boot.
|
||||
|
||||
### `nsigner_qrexec`
|
||||
Delegates to `n_signer` running in **another Qubes OS qube** via qrexec. The
|
||||
agent spawns `qrexec-client-vm <target_qube> <service_name>` as a subprocess
|
||||
and pipes framed JSON-RPC through its stdin/stdout. No network; caller identity
|
||||
is hypervisor-vouched (`QREXEC_REMOTE_DOMAIN` → `qubes:<source-vm>` on the
|
||||
signer side). This is the recommended Qubes cross-qube signing path.
|
||||
|
||||
Prerequisites (in the signer qube, e.g. `nostr_signer`):
|
||||
- `n_signer` running persistently: `nsigner --listen unix --socket-name nsigner --bridge-source-trusted`
|
||||
- The `qubes.NsignerRpc` qrexec service installed (see `n_signer/packaging/qubes/`)
|
||||
- dom0 qrexec policy allowing the agent qube to call the service
|
||||
|
||||
The service name defaults to `qubes.NsignerRpc`; override with `--signer-service`
|
||||
or the `signer.service_name` config field.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Genesis (`signer` block)
|
||||
|
||||
```jsonc
|
||||
"signer": {
|
||||
"mode": "nsigner_unix",
|
||||
"socket_name": "", // nsigner_unix: "" = auto-discover
|
||||
"role": "main",
|
||||
"timeout_ms": 15000,
|
||||
"auth_privkey_hex": "", // nsigner_tcp only
|
||||
"tcp_host": "127.0.0.1", // nsigner_tcp
|
||||
"tcp_port": 11111, // nsigner_tcp
|
||||
"serial_device": "/dev/ttyACM0", // nsigner_serial
|
||||
"target_qube": "nostr_signer", // nsigner_qrexec: target Qubes qube
|
||||
"service_name": "qubes.NsignerRpc" // nsigner_qrexec: qrexec service (default qubes.NsignerRpc)
|
||||
// nsigner_fds: fds_read_fd/fds_write_fd are CLI-only, NOT persisted here
|
||||
}
|
||||
```
|
||||
|
||||
### CLI flags
|
||||
|
||||
| Flag | Purpose |
|
||||
|------|---------|
|
||||
| `--signer <mode>` | Select signer mode (overrides config/env). |
|
||||
| `--signer-socket <name>` | Abstract socket name for `nsigner_unix`. |
|
||||
| `--signer-role <role>` | `n_signer` role selector (default `main`). |
|
||||
| `--signer-timeout <ms>` | Per-call timeout (default `15000`). |
|
||||
| `--signer-tcp <host:port>` | Shorthand for `--signer nsigner_tcp` + host/port. |
|
||||
| `--signer-serial <device>` | Shorthand for `--signer nsigner_serial` + device. |
|
||||
| `--signer-fds <read:write>` | Shorthand for `--signer nsigner_fds` + fd pair. |
|
||||
| `--signer-qrexec <target_qube>` | Shorthand for `--signer nsigner_qrexec` + target qube. |
|
||||
| `--signer-service <name>` | qrexec service name (default `qubes.NsignerRpc`). |
|
||||
|
||||
Precedence: CLI flag > `DIDACTYL_SIGNER` env var > genesis `signer.mode` >
|
||||
default (`local`).
|
||||
|
||||
In any `nsigner_*` mode, `--nsec` / `DIDACTYL_NSEC` are not required.
|
||||
|
||||
## Interactive setup wizard
|
||||
|
||||
The wizard's **New agent → sign with a running n_signer** path and the
|
||||
**Existing agent** flow both present a transport picker:
|
||||
|
||||
- `u` nix socket (auto-discovers `@nsigner*` abstract sockets)
|
||||
- `t` cp (prompts for `host:port` + optional auth privkey)
|
||||
- `s` erial (auto-discovers `/dev/ttyACM*`)
|
||||
- `f` d pair (prompts for `read_fd:write_fd` — advanced)
|
||||
- `e` xec qrexec (prompts for target qube + service name — Qubes cross-qube)
|
||||
|
||||
After a transport is chosen, the wizard runs a connectivity check
|
||||
(`nostr_signer_get_public_key`) and populates the agent pubkey before
|
||||
continuing.
|
||||
|
||||
The **Existing agent** flow additionally lets you recover an agent **without
|
||||
entering an nsec** — kind-30078 config recall/publish is routed through the
|
||||
signer verbs (`nostr_signer_nip44_decrypt` / `nostr_signer_nip44_encrypt`), so
|
||||
the nsec never enters the agent process during recovery.
|
||||
|
||||
## Systemd install
|
||||
|
||||
`install_system_service_with_dedicated_user()` embeds the signer flags in
|
||||
`ExecStart` for `nsigner_unix`, `nsigner_tcp`, `nsigner_serial`, and
|
||||
`nsigner_qrexec`.
|
||||
`nsigner_fds` is rejected with a clear message (fds are runtime-only and cannot
|
||||
be embedded in `ExecStart`); choose **boot now** or wire fd-passing yourself
|
||||
via a wrapper unit.
|
||||
|
||||
## Security tradeoff
|
||||
|
||||
`local` keeps the nsec in the agent process. If the agent is compromised, the
|
||||
nsec is exposed. Remote (`nsigner_*`) modes keep the nsec in `n_signer`'s
|
||||
mlock'd memory; a compromised agent can request signatures but cannot extract
|
||||
the raw key. The tradeoff is operational: `n_signer` must be running and
|
||||
reachable when the agent needs to sign.
|
||||
Reference in New Issue
Block a user