# `nsigner_client` — Linux CLI for n_signer A standalone Linux command-line client that connects to a running [`n_signer`](https://github.com/your-org/n_signer) process and calls its JSON-RPC verbs over stdin/stdout. Designed for pipe-to-`nak` workflows. ## Build ```bash make clients ``` Produces `build/nsigner_client`. Links `nostr_core_lib` exactly like the existing examples. ## Usage ``` nsigner_client [global options] [verb args...] ``` ### Global options | Flag | Default | Meaning | |------|---------|---------| | `--socket-name`, `-n ` | auto-discover | Abstract socket name without `@` | | `--timeout ` | `5000` | Transport timeout | | `--tcp ` | none | TCP transport (requires `--auth-privkey`) | | `--serial ` | none | USB CDC-ACM serial transport | | `--qrexec ` | none | Qubes qrexec transport | | `--auth-privkey <32-byte hex>` | none | Auth envelope privkey for TCP | | `--auth-label ` | none | Auth envelope label | ### Selector options (for `nostr_*` verbs) | Flag | Meaning | JSON emitted | |------|---------|-------------| | `--role ` | Named path-role registered in the signer | `{"role":""}` | | `--path ` | Full BIP-44 derivation path | `{"role_path":""}` | ### Algorithm options (for algorithm-based verbs) | Flag | Default | Meaning | |------|---------|---------| | `--algorithm ` | none | `secp256k1`/`ed25519`/`x25519`/`ml-dsa-65`/`slh-dsa-128s`/`ml-kem-768`/`otp` | | `--index ` | `0` | Algorithm derivation index | | `--scheme ` | `schnorr` | secp256k1 `sign`/`verify` only | | `--encoding ` | `base64` | OTP `encrypt`/`decrypt` only | | `--format ` | `plain` | `nostr_get_public_key` output shape | ### Mine-event options | Flag | Meaning | |------|---------| | `--difficulty ` | Target leading zero bits | | `--threads ` | Mining threads (default 1) | | `--timeout-sec ` | Mining timeout in seconds | ## Verb reference ### Utility | Verb | stdout | |------|--------| | `list` | Lists running n_signer abstract sockets (one `@name` per line) | ### Metadata | Verb | RPC method | stdout | |------|------------|--------| | `get-info` | `get_info` | raw result JSON (name, version, verbs, algorithms) | ### Nostr verbs (role-based) | Verb | RPC method | stdin/argv | stdout | |------|------------|------------|--------| | `get-public-key` | `nostr_get_public_key` | none | pubkey hex (or structured JSON with `--format structured`) | | `sign-event` | `nostr_sign_event` | event JSON from argv or stdin | signed event JSON | | `mine-event` | `nostr_mine_event` | event JSON from argv or stdin | signed mined event JSON | | `nip04-encrypt ` | `nostr_nip04_encrypt` | plaintext from argv or stdin | ciphertext | | `nip04-decrypt ` | `nostr_nip04_decrypt` | ciphertext from argv or stdin | plaintext | | `nip44-encrypt ` | `nostr_nip44_encrypt` | plaintext from argv or stdin | ciphertext | | `nip44-decrypt ` | `nostr_nip44_decrypt` | ciphertext from argv or stdin | plaintext | ### Algorithm-based verbs | Verb | RPC method | argv | stdout | |------|------------|------|--------| | `get-public-key` | `get_public_key` | none | structured JSON `{"algorithm":...,"public_key":...,"key_id":...}` | | `sign ` | `sign` | hex bytes | structured JSON `{"signature":...,"algorithm":...,"key_id":...}` | | `verify ` | `verify` | hex bytes | `valid` / `invalid` (exit 0/1) | | `derive ` | `derive` | UTF-8 data (argv or stdin) | structured JSON | | `encapsulate ` | `encapsulate` | hex | structured JSON | | `decapsulate ` | `decapsulate` | hex | structured JSON | | `derive-shared-secret ` | `derive_shared_secret` | hex | shared secret hex | | `encrypt ` | `encrypt` | plaintext (base64 by default) | ciphertext | | `decrypt <ciphertext>` | `decrypt` | ciphertext | plaintext | ### Generic escape hatch | Verb | RPC method | input | stdout | |------|------------|-------|--------| | `call <method>` | `<method>` | JSON params array from stdin or argv | raw result JSON | ## Selector explanation The `nostr_*` verbs select a key via the options object using both `--role` and `--path`: - **`--role <name> --path <path>`** — Both are required for all `nostr_*` verbs. The role authorizes the request and determines the encryption scheme. The path selects the specific key to derive. Sends `{"role":"<name>","role_path":"<path>"}` to the server. - **`--role` without `--path`** — Client-side error: `--path is required for nostr verbs`. - **`--path` without `--role`** — Client-side error: `--role is required for nostr verbs`. For algorithm verbs, `--algorithm` and `--index` populate the options object instead. ## Pipe-to-nak recipes ```bash # Get public key nsigner_client --role main --path "m/44'/1237'/0'/0/0" get-public-key # Sign an event and publish via nak echo '{"kind":1,"content":"hello nostr","tags":[],"created_at":1700000000}' \ | nsigner_client --role main --path "m/44'/1237'/0'/0/0" sign-event \ | nak publish # Mine a proof-of-work event echo '{"kind":1,"content":"pow","tags":[],"created_at":1700000000}' \ | nsigner_client --role main --path "m/44'/1237'/0'/0/0" mine-event --difficulty 20 --threads 4 # NIP-44 encrypt nsigner_client --role main --path "m/44'/1237'/0'/0/0" nip44-encrypt <peer-pubkey> "secret message" # Algorithm-based signing nsigner_client --algorithm ed25519 --index 0 sign 68656c6c6f # Verify a signature nsigner_client --algorithm secp256k1 verify <msg-hex> <sig-hex> && echo "valid" # Get signer info nsigner_client get-info ``` ## Transport options | Transport | Flag | Notes | |-----------|------|-------| | UNIX abstract socket | `--socket-name <name>` or auto-discover | Default. Auto-discovers if exactly one `@nsigner*` socket exists. | | TCP | `--tcp <host:port>` | Requires `--auth-privkey` for auth envelope. | | Serial (USB CDC-ACM) | `--serial <device>` | e.g. `--serial /dev/ttyACM0` | | Qubes qrexec | `--qrexec <qube:service>` | e.g. `--qrexec sys-signer:qubes.NsignerRpc` | ## Exit codes | Code | Meaning | |------|---------| | 0 | Success | | 1 | Invalid (verify verb only — signature is invalid) | | 2 | Error (transport, RPC, or usage error) | For `verify`: exit 0 = valid signature, exit 1 = invalid signature, exit 2 = error. ## stdin/stdout contract - All payload output goes to stdout as a single line, newline-terminated. - All diagnostics (errors, warnings) go to stderr. - `sign-event`, `nip04-*`, `nip44-*`, `derive`, `encrypt`, `decrypt` read their payload from argv if present, otherwise from stdin (one line). - `sign`, `verify`, `encapsulate`, `decapsulate`, `derive-shared-secret` take hex from argv only (binary payloads). - `call` reads a JSON params array from stdin (one line) or argv. ## See also - [`n_signer_client_PLAN.md`](n_signer_client_PLAN.md) — the full implementation plan - [`README.md`](../README.md) — n_signer main documentation (API §4) - [`examples/sign_event_client.c`](../examples/sign_event_client.c) — reference example