# 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(..., "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)

- [`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 '<json>'` 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).

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

- [`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.
