feat(#14): bump @nostr-dev-kit/ndk 2.8.1 → 3.0.3 + nostr-tools v1 → v2.20 + acl wire-name vocabulary #15
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "issue-14-ndk-bump"
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?
Closes #14.
Why
aiolabs/lnbitsPR #38 (phase 2.4) shipped bunker-mediatedsigner.nip44_encrypt/signer.nip44_decryptand the satmachineadmin PR #30 consumer migration started using them. The first livenip44_decryptround-trip (2026-05-31T07:20Z) surfaced aNsecBunkerTimeoutError. lnbits ascribed it to a stopped bunker container; that was part of the story but masked a deeper structural problem:Finding 1 — NDK 2.8.1's
NDKNip46Backend.handlersdict has nonip44_encrypt/nip44_decryptstrategies. Wire RPCs would fall through tosendResponse(id, remotePubkey, "error", undefined, "Not authorized")(terminalNsecBunkerRpcError, not a retry-eligible timeout).Finding 2 — NDK 2.8.1 normalizes wire
nip04_encrypt→ permit methodencryptbefore callingpubkeyAllowed. lnbits's_ensure_policystores rules using wire names (nip04_encrypt,nip04_decrypt,nip44_encrypt,nip44_decrypt, kind-less). With NDK 2.8.1's normalization, our auth check looked upPolicyRule.method='encrypt'against rules stored asnip04_encrypt— never matched, fell through torequestAuthorization→ 15s timeout. The encrypt/decrypt path through nsecbunkerd has never actually worked end-to-end; phase 2.4 just made it impossible to ignore.Full diagnosis in coord log 2026-05-31T08:35Z.
What this PR does
Bumps
@nostr-dev-kit/ndkfrom2.8.1(Apr 2024) to3.0.3(Feb 2026, currentlatestdist-tag, 3 months stable). The bump comes withnostr-toolsv1 → v2.20.0 (NDK 3.x needs thenip49subpath +finalizeEvent/generateSecretKeyexports unique to nostr-tools v2).Commits
chore(deps): bump @nostr-dev-kit/ndk 2.8.1 → 3.0.3 + nostr-tools v1 → v2—package.json+ regeneratedpnpm-lock.yaml. Reviewable on its own.refactor: adapt source to NDK 3.0.3 / nostr-tools v2 surface— mechanical API-drift adjustments:NDKKindstrict numeric enum: 18 sites passing literal24134(NIP-46 admin-RPC channel) now go throughNIP46_ADMIN_RESPONSE_KINDdefined in a newsrc/daemon/admin/kinds.tswith the cast living once.NDKPrivateKeySigner(nsec)now accepts hex or nsec directly — removed the#8bech32-decode workaround.key.nsecgetter replacesnip19.nsecEncode(key.privateKey)(NDK 3 surfacesprivateKeyasstring/hex, notUint8Array, so the v2 nsecEncode args mismatched).'relay:notice'→'notice'event rename + flipped arg order.NDKUser.fromNip05(nip05)now requires anndkpositional arg.Nip46PermitCallbackParams.paramsaccess sites.req.paramsis(string | undefined)[];create_account.tsexplicitly throws on missing username/domain after authorization payload parse.src/daemon/backend/publish-event.ts(referenced removedNDKNip46Backend.signEvent; wiring was commented out atbackend/index.ts:22).refactor(acl): align IMethod with NIP-46 wire-name vocabulary— the substantive issue-14 win.IMethodis nowNIP46Method(sourced from NDK 3 directly so it can't drift). With the wire-name vocabulary on both sides, the post-#11 live-policy join (step 4 ofcheckIfPubkeyAllowed) naturally matches lnbits's existingnip04_encrypt/nip44_decryptpolicy rules. Theencrypt/decryptnormalization layer is retired — there's no longer a "name-translation" gap between the wire and the stored policy.fix(deps): cap nostr-tools at ~2.20.0— caught during regtest dogfood.nostr-tools2.21+ flipped@noble/curvesfrom1.2.0(CJS + ESM) to2.0.1(pure ESM). The regtest container's Node 20 + tsup-default-CJS combo hard-errors onrequire()of pure-ESM modules. Capping at~2.20.0keeps us inside NDK 3.0.3's>=2.17.2peer range without crossing the ESM hazard. Long-term: bump container Node to >= 22, or switch tsup to ESM output.What this unlocks beyond the immediate fix
switch_relaysNIP-46 support for client-side relay migrationdeb7f93d)#7— may close that one too)@noble/*crypto primitivesTest plan
pnpm installclean (peer-dep warning resolved post-nostr-tools ~2.20.0cap)pnpm run build(tsup) greendocker compose -f docker-compose.dev.yml build --no-cache nsecbunker) succeeds🔑 Starting keys [] / ✅ Connected to ws://lnbits:5001/nostrrelay/test/ / ✅ nsecBunker Admin Interface ready / ✅ nsecBunker ready to serve requests.nip44_encrypt/nip44_decryptstrategies present in the deployeddist/index.js(grep-verified in container)nip44_decryptround-trip from satmachineadmin's consumer — requires:bunker_name = ac35c9fc842f40f0a0e9809347cd24d1unlocked via admin RPC after the container restart (lock state didn't survive the rebuild)state_event_idis still pinned to the v1 bootstrapf46b6dcf62f6...)cassette_configs.state_event_idadvances to the new v1.1 event ID, confirming bunker-mediated decrypt succeededsign_eventflow through the bunker (any existing extension)aiolabs/nsecbunkerd#11regression: re-run the 8 live-policy auth cases — should pass identically (the rewrite is conceptually unchanged, just with widened method-name vocabulary)Pre-existing
tscerrors (NOT a regression)src/db.tsandsrc/daemon/authorize.tsshow'PrismaClient'/'Request'missing from@prisma/client. Pre-existing —prisma generatefails on this nix host (engines binary skew, parked separately as session task #14). The container's Docker build runsprisma generateagainst its own engine and resolves these at runtime. Build is green; tsc reports false-positive at host level.Coord-log thread
🤖 Generated with Claude Code
NDK 2.8.1 (Apr 2024) is 2 years old and predates NIP-46 backend-side nip44 support. With aiolabs/lnbits#38's phase-2.4 client-side migration to bunker-mediated nip44_*, the bunker's lack of a `nip44_decrypt` strategy registration causes wire RPCs to fall through to `sendResponse(id, remotePubkey, "error", undefined, "Not authorized")` at NDK 2.8.1's backend/index.ts:179. Even nip04 was silently broken: 2.8.1 normalizes the wire method to `encrypt`/`decrypt` for `pubkeyAllowed` while lnbits's policy stores wire names. The encrypt/decrypt path through nsecbunkerd has never actually worked end-to-end; it just hadn't been exercised until phase 2.4 landed. 3.0.3 (Feb 2026) is the current `latest` dist-tag and ships: - `nip44_encrypt` / `nip44_decrypt` backend handlers registered by default + wire-name `pubkeyAllowed` semantics (the immediate fix) - `switch_relays` NIP-46 support for client-side relay migration - Configurable NDKNip46Signer timeout (pairs with lnbits PR #38's matching client-side config) - NIP-44 default outgoing encryption with NIP-04 compat fallback - Async error handling fix in backend dispatch — failed strategies report errors instead of silent drop (deb7f93d) - "Not enough relays received this event" race-condition fix on publish (relevant to open #7 — may close that one too) - Signature verification moved in-house (off legacy nostr-tools v1 path) - 2 years of security/perf updates in transitive @noble/* crypto primitives `nostr-tools` bumped from ^1.17.0 to ^2.17.2 alongside because NDK 3.x's `NDKPrivateKeySigner` imports `finalizeEvent` / `generateSecretKey` + uses the `./nip49` subpath, none of which exist in nostr-tools v1.17. With v1.17 installed, `require('@nostr-dev-kit/ndk')` fails with "Package subpath './nip49' is not defined". Confirmed against the post-install module graph. Source migrations for NDK 3 / nostr-tools v2 API surface land in the follow-up commit; this commit is intentionally just the dep bump so the diff stays reviewable. Refs aiolabs/nsecbunkerd#14 + coord-log 2026-05-31T09:55Z.Mechanical adjustments to the source after the dep bump in the previous commit. No semantic changes — every site adapts to API drift between the pinned versions. Surface changes addressed: * `NDKKind` strict numeric enum (was wider in 2.8.1). 18 sites passed the literal `24134` (NIP-46 admin-RPC response kind) to `rpc.sendResponse` / `rpc.sendRequest`; NDK 3's `NDKKind` enum omits 24134. Introduced `src/daemon/admin/kinds.ts` exporting `NIP46_ADMIN_RESPONSE_KIND = 24134 as NDKKind` so the cast lives once, and routed all 18 sites through the named constant. * `NDKPrivateKeySigner` constructor now accepts nsec1 or hex directly (the `@ai-guardrail` in NDK 3 source explicitly tells callers not to `nip19.decode` ahead of construction). Simplified `Daemon.startKey` and `createNewKey` accordingly — the bech32 decode workaround for #8 was tied to NDK 2.8.1's old behavior and is no longer needed. * `NDKPrivateKeySigner.privateKey` is `string` (hex) on the public surface, not `Uint8Array`. `nostr-tools` v2's `nip19.nsecEncode` wants `Uint8Array`. Replaced `nip19.nsecEncode(key.privateKey!)` with `key.nsec` (NDK 3 exposes the getter directly), avoiding both the type mismatch and the unnecessary round-trip. For the one remaining hex-string-from-config call site, used `nostrUtils.hexToBytes` to convert before encoding. * `NDKPool` event rename: `'relay:notice'` → `'notice'`, with flipped arg order `(notice, relay)` → `(relay, notice)`. * `NDKUser.fromNip05` now requires the `ndk` instance as a 2nd positional arg (was implicit-global before). * `Nip46PermitCallbackParams.params` narrowed to `string | NostrEvent`; type guards added at the two access sites (`authorize.ts` and `acl/index.ts:requestToSigningConditionQuery`). * `req.params` is now `(string | undefined)[]` instead of `any[]`; `create_account.ts` `authorizationWithPayload` branch now explicitly throws on missing username/domain before passing to `createAccountReal` (validates what was implicit before). * Removed `src/daemon/backend/publish-event.ts` (defined a strategy that's never registered — wiring is commented out in `backend/index.ts:22`; in NDK 3 the file refs the removed `NDKNip46Backend.signEvent`). Dead since at least NDK 2.x; the bump just made the breakage visible. Pre-existing `tsc` errors at `src/db.ts` and `src/daemon/authorize.ts` on `'PrismaClient'` / `'Request'` exports are unrelated to this PR — the regtest container's nix derivation can't reach the prisma engine binary store on this host (`nsecbunkerd#14` parked separately). `pnpm run build` (tsup) is green; the Docker container runs `prisma generate` against its own engine at image-build time and resolves these at runtime. #11's wire-name policy convention adoption is the next commit — this one is purely keep-it-compiling work. Refs aiolabs/nsecbunkerd#14.Caught during regtest dogfood after the previous three commits landed. With `nostr-tools: ^2.17.2` pnpm resolved to 2.23.5, which in turn pulls `@noble/curves@2.0.1` — ESM-only. The regtest Dockerfile runs on Node 20.11.1, where CJS `require()` of pure-ESM modules is hard-blocked: Error [ERR_REQUIRE_ESM]: require() of ES Module /app/node_modules/.pnpm/@noble+curves@2.0.1/.../secp256k1.js from /app/node_modules/.pnpm/nostr-tools@2.23.5/.../index.js not supported. nostr-tools 2.21.0 was the cutover — that release flipped `@noble/curves` from `1.2.0` to `2.0.1`. 2.20.0 is the last nostr-tools 2.x release that's still CJS-friendly via @noble/curves 1.2.0. Capping our pin at `~2.20.0` keeps us within the "nostr-tools >= 2.17.2" range NDK 3.0.3 asks for in its peerDependency while sidestepping the ESM/CJS hazard. This isn't a regression we introduce — it's a CJS-output footgun unique to the regtest container's Node 20 + tsup-default-CJS combination. Long-term fix paths (out of scope here): * Bump the container's Node base image to >= 22 (where `--experimental-require-module` is on by default for `.js` files inside `package.json type: "commonjs"`) * Switch tsup output to ESM (`tsup --format esm`) — wider surface change across the daemon, the client CLI, and the Dockerfile entrypoint * Accept the cap forever (small downside: 2.21+ patch fixes won't reach us until we fix one of the above) The cap is intentionally tight (`~2.20.0` allows 2.20.x patches, nothing newer) so a future `pnpm update` doesn't silently jump us back over the 2.21 edge. Revisit when one of the long-term paths above lands. Refs aiolabs/nsecbunkerd#14, regtest dogfood 2026-05-31.