5.7 KiB
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:
nostr_core/nsigner_transport.{h,c}— pluggable transport vtable (unix-abstract / tcp / usb-cdc serial / fds) + discovery.nostr_core/nsigner_client.{h,c}— heap-allocated client: 4-byte length-prefixed framing, kind-27235 auth envelope (self-contained), JSON-RPC verbs, cJSON result parsing, RPC error mapping.nostr_core/nostr_signer.{h,c}— high-levelnostr_signer_tabstraction (local + remote backends, 6 verbs).
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)
examples/get_public_key_client.c— usesnsigner_transport_open_unix+nsigner_client_new+nsigner_client_call.examples/sign_event_client.c— same pattern.tests/test_integration.c— same pattern; cJSON-based assertions.
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 |
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
examples/pq_sign_example.c— ML-DSA-65 signexamples/pq_kem_example.c— ML-KEM-768 encaps/decapsexamples/ssh_sign_example.c— ed25519 SSH sign
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.