# 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`](../resources/nostr_core_lib): - [`nostr_core/nsigner_transport.{h,c}`](../resources/nostr_core_lib/nostr_core/nsigner_transport.h) — pluggable transport vtable (unix-abstract / tcp / usb-cdc serial / fds) + discovery. - [`nostr_core/nsigner_client.{h,c}`](../resources/nostr_core_lib/nostr_core/nsigner_client.h) — 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}`](../resources/nostr_core_lib/nostr_core/nostr_signer.h) — high-level `nostr_signer_t` abstraction (local + remote backends, 6 verbs). This is the single source of truth for the n_signer wire contract. See [`nostr_core/NSIGNER_INTEGRATION.md`](../resources/nostr_core_lib/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`](../examples/get_public_key_client.c) — uses `nsigner_transport_open_unix` + `nsigner_client_new` + `nsigner_client_call`. - [`examples/sign_event_client.c`](../examples/sign_event_client.c) — same pattern. - [`tests/test_integration.c`](../tests/test_integration.c) — same pattern; cJSON-based assertions. The `nsigner ... client ''` subcommand in [`src/main.c`](../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`](../README.md#4-api) §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":"","public_key":"","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`](../examples/pq_sign_example.c) — ML-DSA-65 sign - [`examples/pq_kem_example.c`](../examples/pq_kem_example.c) — ML-KEM-768 encaps/decaps - [`examples/ssh_sign_example.c`](../examples/ssh_sign_example.c) — ed25519 SSH sign See [`documents/CLIENT_IMPLEMENTATION.md`](../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`](../resources/nostr_core_lib/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.