startKey passes bech32 nsec to NDKPrivateKeySigner — every newly-created key fails to load #8
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptom
After a successful
create_new_keyadmin RPC, the bunker logs:create_new_keyreturns successfully (the npub is computed before the failing call) and the key is persisted encrypted to disk — but the key is never loaded into the bunker's runtime keystore, so:get_keysreports[](or throws, per #5)create_new_policy/create_new_token/sign_eventflow against the new key silently times out from the client sideThis is the gating bug that blocks every signing flow against any key created via the admin RPC interface — i.e. essentially everything the daemon is for. Separate from #5 (which is the
getKeysiterator type bug) — this one is in the key loading path, not the listing path.Root cause
src/daemon/run.ts:startKey(current, unpatched):NDK 2.8.1's
NDKPrivateKeySignerconstructor (verified atnode_modules/.pnpm/@nostr-dev-kit+ndk@2.8.1_typescript@5.1.3/node_modules/@nostr-dev-kit/ndk/dist/index.js:5138):nostr-tools.getPublicKeyrequires 32-byte hex (or bytes/bigint) and throws on bech32 input. Thetry/catchinstartKeycatches the throw and returns silently — the daemon keeps running but the key is now unloaded forever (no retry path).The callers (
create_new_key.ts:26,unlockKey,startKeysat boot) all pass freshly-encoded bech32 nsecs, so EVERY load attempt hits this path.Fix
Decode the bech32 first:
(
nip19is already imported at the top of the file.) Same pattern ascreate_new_key.ts:16already uses (new NDKPrivateKeySigner(nip19.decode(_nsec).data as string)) — so the precedent for the decode-before-construct shape exists elsewhere in the codebase.Applied in our fork at
aiolabs/nsecbunkerd@<this PR>. Verified spike: post-patch,create_new_keyprovisions a target, the key loads cleanly, andget_keysreturns the newly-created key (assuming #5 is also fixed, which is needed for the iterator).Reproduction (post-#1, #2, #3 patches)
create_new_key('alice', 'passphrase')via the admin RPC interface.{npub: ...}successfully.Error loading key alice: ...in the bunker logs.get_keys— returns[](or throws per #5).Hypothesis on why this regressed
Likely worked against an older NDK version where
NDKPrivateKeySignerdecoded bech32 internally, then NDK changed to require hex without anyone updating the bunker. The fact thatcreate_new_key.ts:16already decodes (for the_nsecparameter path) supports the theory that callers got patched piecemeal butstartKeywas missed.Impact
Critical. Without this, the bunker can store keys but cannot use them. Every signing flow against an admin-created target is broken.
For the LNbits integration in
aiolabs/lnbits#18, this is unblocking — once patched,create_new_key+ NIP-46 signing flows can be tested end-to-end.Acceptance
backend/index.ts:16,admin/index.ts:56,client.ts:121) for the same bug.Cross-refs
aiolabs/lnbits#18phase 2 spike — gating the plain-WebSocket signer sidestep path from #7.getKeystype bug) — same class of error (bech32-vs-hex / object-vs-string confusion) in a different code path. Likely both regressed at the same NDK bump.~/dev/lnbits/nsec-bunker-spike-findings.mdfor the spike harness output that confirmed this.