Files
n_signer/client/README.md

6.4 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:

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(..., "get_public_key", ...)
nsigner_client_sign_event nostr_signer_sign_event or nsigner_client_call(..., "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).

Use nsigner_client_call(client, "<verb>", params, &result) with these verbs:

Verb Algorithms Description
get_public_key all Returns the role's public key (see format below)
sign_event secp256k1 Sign a Nostr event (existing)
nip44_encrypt / nip44_decrypt secp256k1 NIP-44 (existing)
nip04_encrypt / nip04_decrypt secp256k1 NIP-04 (existing)
mine_event secp256k1 NIP-13 PoW mining + sign (existing)
sign_data ed25519, ml-dsa-65, slh-dsa-128s Sign arbitrary bytes (hex)
verify_signature ed25519, ml-dsa-65, slh-dsa-128s Verify a signature against the role's pubkey
ssh_sign ed25519 Sign an SSH authentication challenge
kem_encapsulate ml-kem-768 Encapsulate with a peer's ML-KEM public key
kem_decapsulate ml-kem-768 Decapsulate a ciphertext with the role's ML-KEM private key

Algorithm-based API (new)

In addition to the role-based verbs above, the signer supports algorithm-based verbs where the caller specifies algorithm and index directly, without needing a role:

Verb Algorithms Description
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
get_public_key (with algorithm) all Get public key for a derived key

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

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

Old verbs (sign_data, ssh_sign, verify_signature, kem_encapsulate, kem_decapsulate) also accept the algorithm parameter and map to the new verbs. Without algorithm, they use the role-based path (backward compatible).

get_public_key response format

  • secp256k1 (backward compatible): result is a plain hex string (cJSON_IsString(result) is true, 64 hex chars).
  • secp256k1 with {"format":"structured"} option: result is a JSON string containing {"algorithm":"secp256k1","public_key":"<hex>","key_id":"<16 hex>"}.
  • All other algorithms: result is always a JSON string containing {"algorithm":"<alg>","public_key":"<hex>","key_id":"<16 hex>"}.

Clients should parse the result string with cJSON_Parse to extract the algorithm, public_key, and key_id fields for non-secp256k1 algorithms.

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.