Files
n_signer/client
Laan Tungir 9afbb8fcbd Refactor n_signer_client to use nostr_core_lib high-level wrappers
The CLI was hand-building cJSON params and calling the low-level
nsigner_client_call for all 16 verbs. Now it uses the high-level
nostr_signer_t typed wrappers from nostr_core_lib for 14 of 16 verbs:
get_info, get_public_key (alg + nostr), sign_event, mine_event,
nip04/44 encrypt+decrypt, sign, verify, derive, encapsulate,
decapsulate, derive_shared_secret, otp encrypt/decrypt.

The 'call' verb (raw passthrough) and 'derive --algorithm' still use
the low-level nsigner_client_call on the shared connection (created via
nostr_signer_nsigner_from_client). The 'list' verb uses
nsigner_transport_list_unix directly.

The client shrank from 945 to ~840 lines, with the per-verb cJSON
building logic now in the library. Error messages use
nostr_signer_last_error() to surface the raw n_signer RPC error text
(path_not_allowed, unknown_role, etc.).

Also adds two plan docs:
- plans/client_breaking_change_audit.md: audit of n_signer breaking
  changes vs all ~/lt/ client repos
- plans/nostr_core_lib_full_verb_coverage.md: analysis of the library
  verb coverage gap that motivated this refactor

Tests: 45/45 pass, 0 fail, 2 skip (test_n_signer_client.sh).
2026-08-06 10:17:47 -04:00
..

n_signer C Client — migrated to nostr_core_lib

The hand-rolled nsigner_client.{c,h} that previously lived in this directory has been removed. n_signer now uses the shared, transport-pluggable client stack that lives in nostr_core_lib:

This is the single source of truth for the n_signer wire contract. See nostr_core/NSIGNER_INTEGRATION.md for the full integration contract.

What moved where

Old (client/) New (nostr_core_lib)
nsigner_client_t (stack) nsigner_client_t* (heap) or nostr_signer_t*
nsigner_client_init / connect_unix / close nsigner_transport_open_unix + nsigner_client_new / nsigner_client_free
nsigner_client_get_public_key nostr_signer_get_public_key or nsigner_client_call(..., "nostr_get_public_key", ...)
nsigner_client_sign_event nostr_signer_sign_event or nsigner_client_call(..., "nostr_sign_event", ...)
nsigner_client_set_auth nsigner_client_set_auth or nostr_signer_nsigner_set_auth
nsigner_client_request / request_raw nsigner_client_call (returns parsed cJSON result)

Consumers (updated)

The nsigner ... client '<json>' subcommand in src/main.c is unaffected — it has its own raw framing pass-through and never used this directory.

Multi-Algorithm and Post-Quantum Verbs

n_signer supports six algorithms: secp256k1 (Nostr), ed25519 (SSH), x25519 (age/ECDH), ml-dsa-65 (PQ signatures, FIPS 204), slh-dsa-128s (PQ hash-based signatures, FIPS 205), and ml-kem-768 (PQ KEM, FIPS 203).

The API has two verb families (see README.md §4 for the full spec):

Algorithm-based verbs — the caller specifies algorithm and index in the options object. No role table entry is needed.

Verb Algorithms Description
get_public_key all key-deriving algorithms Returns the derived public key (structured)
sign secp256k1, ed25519, ml-dsa-65, slh-dsa-128s Sign arbitrary bytes (hex)
verify secp256k1, ed25519, ml-dsa-65, slh-dsa-128s Verify a signature
encapsulate ml-kem-768 KEM encapsulation with peer's public key
decapsulate ml-kem-768 KEM decapsulation with derived private key
derive_shared_secret x25519 ECDH key agreement
derive secp256k1 HMAC-SHA256(privkey, data) — key-derived MAC for opaque identifiers (index required)
encrypt / decrypt otp One-time pad encrypt/decrypt (algorithm:"otp")

Nostr protocol verbs — select a secp256k1 NIP-06 key via nostr_index (or role/role_path). These are role-based.

Verb Description
nostr_get_public_key Returns the role's secp256k1 public key
nostr_sign_event Sign a Nostr event
nostr_mine_event NIP-13 PoW mining + sign
nostr_nip44_encrypt / nostr_nip44_decrypt NIP-44 encrypt/decrypt
nostr_nip04_encrypt / nostr_nip04_decrypt NIP-04 encrypt/decrypt

Example: nsigner_client_call(client, "sign", "[\"68656c6c6f\",{\"algorithm\":\"ed25519\",\"index\":0}]", &result)

For secp256k1, the optional scheme parameter selects "schnorr" (default) or "ecdsa".

get_public_key response format

The algorithm-based get_public_key always returns a structured JSON string: {"algorithm":"<alg>","public_key":"<hex>","key_id":"<16 hex>"}.

The role-based nostr_get_public_key returns a plain 64-hex-char secp256k1 public key by default, or the structured form with {"format":"structured"}.

Clients should parse the result string with cJSON_Parse to extract the algorithm, public_key, and key_id fields when the result is a JSON object.

Key sizes

Algorithm Pub key Priv key Signature Ciphertext Shared secret
secp256k1 32 B 32 B 64 B
ed25519 32 B 32 B 64 B
x25519 32 B 32 B 32 B
ML-DSA-65 1952 B 4032 B 3309 B
SLH-DSA-128s 32 B 64 B 7856 B
ML-KEM-768 1184 B 2400 B 1088 B 32 B

Example clients

See documents/CLIENT_IMPLEMENTATION.md section 11 for the full multi-algorithm specification, derivation paths, and example request/response transcripts.

Why

Per plans/nsigner_integration_plan.md (Phase 7): retire per-project hand-rolled clients in favor of the shared module in nostr_core_lib, so the wire contract has one implementation and downstream projects get unix/tcp/serial/fds transports for free.