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).
8.8 KiB
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_indexselector → removed, rejected with error2006 nostr_index_deprecated(seesrc/dispatcher.c).indexonnostr_*verbs → removed, rejected with2007 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/--indexon 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 320–327). 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
index — derive 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
- Replace the
role-only +nostr_indexselector model with a combinedrole+role_pathselector. Concretely: change thenostr_signer_nsigner_*constructors (or add new ones / a selector struct) to accept both a role name and a full path. - Remove
nostr_signer_nsigner_set_nostr_index(or repurpose it to setrole+role_pathfrom an index by expanding the NIP-06 templatem/44'/1237'/N'/0/0client-side). - Update
signer_remote_params_with_selectorto always emit bothroleandrole_path. - Fix the
deriveremote path to always includeindex. - Update
NSIGNER_INTEGRATION.md,nostr_core_lib_client_updates.md, andtests/nsigner_client_test.c(which sendsnostr_get_public_keywith anostr_indexselector 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(localsign_eventhelper, not n_signer RPC)raspberry_pi_zero_nostr(localnostr_create_and_sign_event)esp32_playground(localnostr_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)
4. Recommended remediation order
- nostr_core_lib first — it is the shared dependency. Introduce a
role+role_pathselector (struct or new constructors), removenostr_indexemission, fixderive, update tests + integration doc. - sovereign_browser — update login UI to collect role + path instead of index; switch to the new nostr_core_lib API.
- nostr_terminal — rewrite its hand-rolled
nsigner_clientselector torole+role_path; update login/profile menus and the integration plan. - laantungir_website — switch the two JS scripts from
nostr_indextorole+role_path. - n_signer — fix the stale
--encodinghelp string inclient/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]