10 KiB
n_signer / nostr_core_lib Adoption Plan
Overview
Adopt the major changes from n_signer (v0.0.57 → v0.1.21) and nostr_core_lib (v0.6.10 → v0.6.13) into sovereign_browser. The key change is migrating from the deprecated nostr_index selector to the new role + role_path selector model.
Background
n_signer API Changes
The n_signer project redesigned its API:
-
Unified verb naming: All Nostr-specific verbs now use
nostr_prefixget_public_key→nostr_get_public_key(for Nostr keys)sign_event→nostr_sign_eventnip04_encrypt→nostr_nip04_encrypt- etc.
-
Algorithm-based API: New generic verbs for non-Nostr crypto
get_public_key,sign,verify,encapsulate,decapsulate,derive_shared_secret,derive,encrypt,decrypt- Support for secp256k1, ed25519, x25519, ml-dsa-65, slh-dsa-128s, ml-kem-768, otp
-
Role-based selectors:
nostr_indexis deprecated- Old:
{"nostr_index": N}→ automatically expanded tom/44'/1237'/N'/0/0 - New:
{"role": "<name>", "role_path": "<full-bip44-path>"}— both required together - The role must be pre-registered in the signer's wizard
- The role_path must match the role's registered template
- Old:
-
New error codes:
2006 nostr_index_deprecated—nostr_indexis removed2007 index_deprecated—indexremoved for nostr verbs2008 role_required—rolerequired when usingrole_path2009 path_required—role_pathrequired for roles with variable templates
nostr_core_lib Changes
The upstream library added:
-
New public API in
nostr_signer.h:nostr_signer_last_error()— human-readable error from last failed callnostr_signer_get_info()— signer metadata (name, version, verbs, algorithms)nostr_signer_get_public_key_alg()— algorithm-based public keynostr_signer_sign()/nostr_signer_verify()— generic sign/verifynostr_signer_encapsulate()/nostr_signer_decapsulate()— ML-KEM-768nostr_signer_derive_shared_secret()— X25519nostr_signer_otp_encrypt()/nostr_signer_otp_decrypt()— OTPnostr_signer_mine_event()— NIP-13 proof-of-worknostr_signer_nsigner_from_transport()/from_client()— flexible constructorsnostr_signer_nsigner_set_role_path()— set full BIP-44 path
-
Changed behavior:
nostr_signer_nsigner_set_nostr_index()is now a compatibility shim that expands torole="main"+role_path="m/44'/1237'/N'/0/0"- Remote backend sends
role+role_pathinstead ofnostr_index - New internal fields:
role_path[128],has_role_path,derive_index,has_derive_index
-
New files:
nip034.c/h(NIP-34 git stuff)
Current sovereign_browser Usage
The browser currently uses these deprecated APIs:
| File | Line | Current Usage |
|---|---|---|
src/key_store.c |
84-86 | nostr_signer_nsigner_set_nostr_index(signer, identity->nsigner_index) |
src/key_store.h |
53 | int nsigner_index; in key_store_identity_t |
src/login_dialog.c |
524 | nostr_signer_nsigner_set_nostr_index(signer, nostr_index) |
src/login_dialog.c |
1043-1089 | Key index spinner UI with m/44'/1237'/N'/0/0 label |
src/agent_login.c |
394 | nostr_signer_nsigner_set_nostr_index(signer, index) |
src/cli.c |
63-66 | --nsigner-index CLI flag |
src/cli.h |
64 | int nsigner_index; in cli_args_t |
Adoption Plan
Phase 1: Update Vendored nostr_core_lib
Update the vendored library from v0.6.10 to v0.6.13.
Tasks:
- Sync
nostr_core_lib/directory with upstream v0.6.13 - Verify build succeeds with
make - Test basic login flows still work (local, seed, readonly)
New APIs available after update:
nostr_signer_last_error()— better error messagesnostr_signer_get_info()— signer capability detectionnostr_signer_nsigner_set_role_path()— new selector API- Algorithm-based verbs (for future use)
Phase 2: Migrate to role + role_path Selector
Replace the deprecated nostr_index with explicit role + role_path.
2.1 Update Data Structures
src/key_store.h — Change key_store_identity_t:
/* BEFORE */
int nsigner_index; /* nostr_index (NIP-06 m/44'/1237'/N'/0/0) */
/* AFTER */
int nsigner_index; /* NIP-06 index (0, 1, 2, ...) */
char nsigner_role[64]; /* role name (default: "nostr_range") */
src/cli.h — Change cli_args_t:
/* BEFORE */
int nsigner_index; /* --nsigner-index (-1 = unset) */
/* AFTER */
int nsigner_index; /* --nsigner-index (-1 = unset) */
char *nsigner_role; /* --nsigner-role (default: "nostr_range") */
2.2 Update CLI Flags
src/cli.c:
- Keep
--nsigner-index <n>(unchanged) - Add
--nsigner-role <name>(default: "nostr_range") - The role + index are combined client-side to form the full role_path
- Update help text
2.3 Update Login Dialog UI
src/login_dialog.c — Add a Role entry field after the Service entry. The Key Index spinner stays the same.
BEFORE:
Connect to n_signer hardware signer:
Transport: [USB Serial / UNIX Socket / TCP / Other Qube ▼]
Target Qube: [nostr_signer________________________]
Service: [qubes.NsignerRpc________]
Key Index (m/44'/1237'/0'/0/0): [0]
[n_signer is a foreground, RAM-only hardware signer...]
AFTER:
Connect to n_signer hardware signer:
Transport: [USB Serial / UNIX Socket / TCP / Other Qube ▼]
Target Qube: [nostr_signer________________________]
Service: [qubes.NsignerRpc________]
Role: [nostr_range_________________]
Key Index (m/44'/1237'/0'/0/0): [0]
[n_signer is a foreground, RAM-only hardware signer...]
The Role field is a simple text entry with default value "nostr_range". The Key Index spinner remains unchanged — it still selects the NIP-06 index. The role + index are combined client-side to form the full role_path (e.g. role="nostr_range" + index=0 → role_path="m/44'/1237'/0'/0/0").
2.4 Update Signer Creation
src/key_store.c — key_store_create_signer():
/* BEFORE */
if (signer && identity->nsigner_index >= 0) {
nostr_signer_nsigner_set_nostr_index(signer, identity->nsigner_index);
}
/* AFTER */
if (signer) {
/* Build role_path from role + index: m/44'/1237'/N'/0/0 */
char role_path[128];
snprintf(role_path, sizeof(role_path), "m/44'/1237'/%d'/0/0", identity->nsigner_index);
nostr_signer_nsigner_set_role_path(signer, role_path);
/* Note: role is passed to the constructor */
}
src/login_dialog.c — on_sign_in_clicked():
/* BEFORE */
int rc_idx = nostr_signer_nsigner_set_nostr_index(signer, nostr_index);
/* AFTER */
/* Build role_path from role + index */
char role_path[128];
snprintf(role_path, sizeof(role_path), "m/44'/1237'/%d'/0/0", nostr_index);
int rc_idx = nostr_signer_nsigner_set_role_path(signer, role_path);
src/agent_login.c — login_nsigner():
/* BEFORE */
int rc = nostr_signer_nsigner_set_nostr_index(signer, index);
/* AFTER */
/* Get role from params (default: "nostr_range") */
const char *role = cJSON_GetStringValue(cJSON_GetObjectItem(params, "role"));
if (!role || !role[0]) role = "nostr_range";
/* Build role_path from index */
char role_path[128];
snprintf(role_path, sizeof(role_path), "m/44'/1237'/%d'/0/0", index);
int rc = nostr_signer_nsigner_set_role_path(signer, role_path);
2.5 Update MCP/Agent Tools
src/agent_mcp.c — Update login tool schema:
{
"method": "nsigner",
"transport": "qrexec",
"device": "nostr_signer",
"service": "qubes.NsignerRpc",
"role": "nostr_range",
"index": 0
}
Phase 3: Error Handling Improvements
Use the new nostr_signer_last_error() for better error messages.
src/login_dialog.c and src/agent_login.c:
/* BEFORE */
const char *err_str = nostr_strerror(rc);
/* AFTER */
const char *err_str = nostr_signer_last_error(signer);
if (!err_str || err_str[0] == '\0') {
err_str = nostr_strerror(rc);
}
Phase 4: Testing
- Unit tests: Update
tests/test_bookmarks_tree.cif needed - Integration tests:
- Test n_signer login with each transport (serial, unix, tcp, qrexec)
- Test with different roles (main, nostr_range, custom)
- Test error cases (unknown role, path mismatch, etc.)
- MCP tests: Test agent login with new role/path parameters
Migration Notes
Backward Compatibility
- Saved identities: The browser does not persist n_signer identities to disk (in-memory only), so no migration of saved data is needed.
- CLI scripts:
--nsigner-indexstill works;--nsigner-roleis optional (defaults to "nostr_range"). - MCP/API clients: The
logintool'sindexparameter still works;roleis optional.
Default Role
The default role is "nostr_range":
- This matches the n_signer wizard preset #2
- The client combines role + index to form the full path:
m/44'/1237'/N'/0/0 - The n_signer server verifies the path matches the role's registered template
How It Works
- User selects a role (default: "nostr_range") and key index (default: 0)
- Client builds the full derivation path:
m/44'/1237'/<index>'/0/0 - Client sends
{"role": "<role>", "role_path": "<path>"}to the signer - Signer verifies the path matches the role's template and derives the key
Files to Modify
| File | Changes |
|---|---|
nostr_core_lib/ |
Sync to v0.6.13 |
src/key_store.h |
Replace nsigner_index with nsigner_role + nsigner_role_path |
src/key_store.c |
Update key_store_create_signer() to use new API |
src/login_dialog.c |
Replace index spinner with role/path UI, update sign-in logic |
src/agent_login.c |
Update login_nsigner() to accept role/path params |
src/cli.c |
Replace --nsigner-index with --nsigner-role + --nsigner-role-path |
src/cli.h |
Update cli_args_t structure |
src/agent_mcp.c |
Update login tool schema and handler |
README.md |
Update CLI flags documentation |
.roo/agents.md |
Update login tool documentation |
Success Criteria
- Browser builds successfully with updated nostr_core_lib
- n_signer login works with all transports (serial, unix, tcp, qrexec)
- Role + role_path selector works correctly
- No use of deprecated
nostr_indexAPI - Error messages use
nostr_signer_last_error()for clarity - MCP login tool accepts role/path parameters