Files
n_signer/client/README.md

113 lines
5.7 KiB
Markdown

# 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 '<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).
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 |
| `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`](../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.