Files
n_signer/plans/client_breaking_change_audit.md
Laan Tungir 9afbb8fcbd Refactor n_signer_client to use nostr_core_lib high-level wrappers
The CLI was hand-building cJSON params and calling the low-level
nsigner_client_call for all 16 verbs. Now it uses the high-level
nostr_signer_t typed wrappers from nostr_core_lib for 14 of 16 verbs:
get_info, get_public_key (alg + nostr), sign_event, mine_event,
nip04/44 encrypt+decrypt, sign, verify, derive, encapsulate,
decapsulate, derive_shared_secret, otp encrypt/decrypt.

The 'call' verb (raw passthrough) and 'derive --algorithm' still use
the low-level nsigner_client_call on the shared connection (created via
nostr_signer_nsigner_from_client). The 'list' verb uses
nsigner_transport_list_unix directly.

The client shrank from 945 to ~840 lines, with the per-verb cJSON
building logic now in the library. Error messages use
nostr_signer_last_error() to surface the raw n_signer RPC error text
(path_not_allowed, unknown_role, etc.).

Also adds two plan docs:
- plans/client_breaking_change_audit.md: audit of n_signer breaking
  changes vs all ~/lt/ client repos
- plans/nostr_core_lib_full_verb_coverage.md: analysis of the library
  verb coverage gap that motivated this refactor

Tests: 45/45 pass, 0 fail, 2 skip (test_n_signer_client.sh).
2026-08-06 10:17:47 -04:00

8.8 KiB
Raw Permalink Blame History

Audit: n_signer Breaking Changes vs Client Repos

1. The breaking changes made to n_signer

Three changes on the n_signer wire protocol are breaking for every existing client. All three are landed in src/ and documented in README.md §4.

1.1 Verb renames (legacy names removed)

Source: plans/legacy_verb_aliases.md — COMPLETED.

Old wire verb New wire verb
sign_event nostr_sign_event
mine_event nostr_mine_event
nip04_encrypt nostr_nip04_encrypt
nip04_decrypt nostr_nip04_decrypt
nip44_encrypt nostr_nip44_encrypt
nip44_decrypt nostr_nip44_decrypt
get_public_key (role branch) nostr_get_public_key

The role-based get_public_key was split: algorithm-based stays get_public_key; Nostr-protocol key selection is now nostr_get_public_key. The old alias names are gone — no shim, no fallthrough.

1.2 Selector model rewrite (nostr_index / index removed for nostr verbs)

Source: plans/role_path_authorization.md.

  • nostr_index selector → removed, rejected with error 2006 nostr_index_deprecated (see src/dispatcher.c).
  • index on nostr_* verbs → removed, rejected with 2007 index_deprecated.
  • The only accepted selector for nostr_* verbs is now {"role":"<name>", "role_path":"<full-path>"} sent together. Either field alone is rejected: 2008 role_required / 2009 path_required (README.md §4.6).
  • No backward compatibility. --nostr-index / --index on the client CLI are removed; replaced by --role + --path.

1.3 OTP encoding values changed

encrypt / decrypt (algorithm otp) now take encoding = "ascii" (ASCII-armored, default) or "binary" (base64 raw .otp blob) (src/dispatcher.c, src/otp_pad.c).

Note: client/n_signer_client.c help text still advertises --encoding <base64|hex> — that is a stale doc string inside n_signer's own client and should be fixed to ascii|binary.


2. Are these reflected in the nostr_core_lib repo? — NO

nostr_core_lib is the shared client library that every C-based n_signer client links against. It is out of date and will fail against current n_signer. Specific gaps:

2.1 Still emits the removed nostr_index selector

nostr_core_lib/nostr_core/nostr_signer.c signer_remote_params_with_selector() emits {"nostr_index":N} when set (lines 320327). n_signer now rejects this with 2006 nostr_index_deprecated.

The public API nostr_signer_nsigner_set_nostr_index() still exists and is the documented way to select a key — it is now a dead end.

2.2 Sends role without role_path

When nostr_index is not set, the same helper emits only {"role":"..."} (line 341) with no role_path. n_signer now requires both and rejects role-only with 2009 path_required.

The nostr_signer_nsigner_* factory constructors (nostr_signer.h) take a single const char* role parameter — there is no way to pass a role_path through the high-level API at all.

2.3 derive (HMAC) path is half-broken

nostr_signer.c builds {"algorithm":"secp256k1","index":N} for the derive verb. The algorithm-based derive verb still accepts index, so the nostr_index branch works. But the role-only branch (line 563) sends {"role":"..."} with no indexderive requires index and will reject it.

2.4 Documentation is stale

NSIGNER_INTEGRATION.md still tells integrators to use nostr_index and role-only selectors, and plans/nostr_core_lib_client_updates.md proposes nostr_index support as the chosen design — both predate the selector rewrite.

2.5 What needs to change in nostr_core_lib

  1. Replace the role-only + nostr_index selector model with a combined role + role_path selector. Concretely: change the nostr_signer_nsigner_* constructors (or add new ones / a selector struct) to accept both a role name and a full path.
  2. Remove nostr_signer_nsigner_set_nostr_index (or repurpose it to set role + role_path from an index by expanding the NIP-06 template m/44'/1237'/N'/0/0 client-side).
  3. Update signer_remote_params_with_selector to always emit both role and role_path.
  4. Fix the derive remote path to always include index.
  5. Update NSIGNER_INTEGRATION.md, nostr_core_lib_client_updates.md, and tests/nsigner_client_test.c (which sends nostr_get_public_key with a nostr_index selector at line 297).

3. Repos in ~/lt/ that need client edits

Tier 1 — Direct n_signer wire clients (BROKEN now)

These talk the n_signer JSON-RPC protocol directly and will fail against current n_signer:

Repo Files Problem
nostr_core_lib nostr_core/nostr_signer.c, nostr_signer.h, nsigner_client.c, NSIGNER_INTEGRATION.md, tests/nsigner_client_test.c, examples/note_poster.c Emits removed nostr_index; sends role without role_path. Shared lib — fixing this fixes all C clients that link it.
nostr_terminal src/nsigner_client.c, include/nsigner_client.h, src/signer.c, src/menu_login.c, src/menu_profile.c, plans/n_signer_integration.md Has its own hand-rolled nsigner_client that sends {"nostr_index":N} (nsigner_client.c). Selector struct is has_nostr_index/nostr_index/role with no role_path. Login menu prompts for "index" only.
sovereign_browser src/login_dialog.c, src/agent_login.c, src/key_store.c, src/key_store.h Uses nostr_signer_nsigner_* from nostr_core_lib + nostr_signer_nsigner_set_nostr_index. UI has a nostr_index spin button. Breaks via the lib, and the UI needs a role+path input.
laantungir_website scripts/publish_nostr.js, scripts/get_nsigner_pubkey.js Raw JSON-RPC over qrexec sending {"nostr_index": N} (publish_nostr.js). Will get 2006.

Tier 2 — Indirect (breaks once Tier 1 lib is fixed, or uses nostr_core_lib local signing only)

Repo Status Action
n_signer (this repo) client/n_signer_client.c help text says --encoding <base64|hex> but server wants ascii|binary; the client itself already uses --role+--path correctly per role_path_authorization.md. Fix the stale --encoding help string.

Not affected (use local nostr_core_lib signing, not n_signer remote)

These call nostr_create_and_sign_event / nostr_signer_local with a local private key — they do not speak the n_signer wire protocol and are unaffected:

  • open_wire (local sign_event helper, not n_signer RPC)
  • raspberry_pi_zero_nostr (local nostr_create_and_sign_event)
  • esp32_playground (local nostr_create_and_sign_event)

Not affected (NIP-46 to arbitrary remote signers, not n_signer)

These use NIP-46 method names (sign_event, nip04_encrypt, …) per the NIP-46 spec, targeting generic remote signers / browser extensions — not n_signer's renamed verbs. No change needed unless they specifically add an n_signer backend:

  • primal-web-app (src/lib/nip46/nip46.ts)
  • super_ball (web/nostr.bundle.js)
  • nips (spec docs)

  1. nostr_core_lib first — it is the shared dependency. Introduce a role + role_path selector (struct or new constructors), remove nostr_index emission, fix derive, update tests + integration doc.
  2. sovereign_browser — update login UI to collect role + path instead of index; switch to the new nostr_core_lib API.
  3. nostr_terminal — rewrite its hand-rolled nsigner_client selector to role + role_path; update login/profile menus and the integration plan.
  4. laantungir_website — switch the two JS scripts from nostr_index to role + role_path.
  5. n_signer — fix the stale --encoding help string in client/n_signer_client.c.

A Mermaid overview of the dependency order:

flowchart LR
    NS[n_signer wire changes] --> NCL[nostr_core_lib]
    NCL --> SB[sovereign_browser]
    NCL --> NT[nostr_terminal]
    NS --> LW[laantungir_website]
    NS --> NSC[n_signer client help text]