diff --git a/src/client.ts b/src/client.ts index 0fcad60..89b1c0d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -100,7 +100,7 @@ function loadPrivateKey(): string | undefined { } else { // check if we have a @ so we try to get the npub from nip05 if (remotePubkey.includes('@')) { - const u = await NDKUser.fromNip05(remotePubkey); + const u = await NDKUser.fromNip05(remotePubkey, ndk); if (!u) { console.log(`Invalid nip05 ${remotePubkey}`); process.exit(1); diff --git a/src/daemon/admin/commands/add_policy_rule.ts b/src/daemon/admin/commands/add_policy_rule.ts index 041fd1e..7a66345 100644 --- a/src/daemon/admin/commands/add_policy_rule.ts +++ b/src/daemon/admin/commands/add_policy_rule.ts @@ -1,5 +1,6 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import prisma from "../../../db.js"; /** @@ -43,5 +44,5 @@ export default async function addPolicyRule(admin: AdminInterface, req: NDKRpcRe }); const result = JSON.stringify(["ok"]); - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } diff --git a/src/daemon/admin/commands/add_signing_condition.ts b/src/daemon/admin/commands/add_signing_condition.ts index 6cfad88..f734d24 100644 --- a/src/daemon/admin/commands/add_signing_condition.ts +++ b/src/daemon/admin/commands/add_signing_condition.ts @@ -1,5 +1,6 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import prisma from "../../../db.js"; /** @@ -42,5 +43,5 @@ export default async function addSigningCondition(admin: AdminInterface, req: ND }); const result = JSON.stringify(["ok"]); - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } diff --git a/src/daemon/admin/commands/create_account.ts b/src/daemon/admin/commands/create_account.ts index f3c026a..2919d9c 100644 --- a/src/daemon/admin/commands/create_account.ts +++ b/src/daemon/admin/commands/create_account.ts @@ -131,6 +131,9 @@ export default async function createAccount(admin: AdminInterface, req: NDKRpcRe username = payload[0]; domain = payload[1]; email = payload[2]; + if (!username || !domain) { + throw new Error('Invalid authorization payload: missing username/domain'); + } return createAccountReal(admin, req, username, domain, email); } } @@ -195,7 +198,7 @@ export async function createAccountReal( } const keyName = nip05; - const nsec = nip19.nsecEncode(key.privateKey!); + const nsec = key.nsec; currentConfig.keys[keyName] = { key: key.privateKey }; saveCurrentConfig(admin.configFile, currentConfig); diff --git a/src/daemon/admin/commands/create_new_key.ts b/src/daemon/admin/commands/create_new_key.ts index 306962c..468c3cf 100644 --- a/src/daemon/admin/commands/create_new_key.ts +++ b/src/daemon/admin/commands/create_new_key.ts @@ -1,7 +1,7 @@ import NDK, { NDKEvent, NDKPrivateKeySigner, NDKRpcRequest, type NostrEvent } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import { saveEncrypted } from "../../../commands/add.js"; -import { nip19 } from 'nostr-tools'; import { setupSkeletonProfile } from "../../lib/profile.js"; export default async function createNewKey(admin: AdminInterface, req: NDKRpcRequest) { @@ -13,7 +13,9 @@ export default async function createNewKey(admin: AdminInterface, req: NDKRpcReq let key; if (_nsec) { - key = new NDKPrivateKeySigner(nip19.decode(_nsec).data as string); + // NDK 3.x's `NDKPrivateKeySigner` accepts nsec1 or hex directly + // (see core/src/signers/private-key/index.ts `@ai-guardrail`). + key = new NDKPrivateKeySigner(_nsec); } else { key = NDKPrivateKeySigner.generate(); @@ -23,7 +25,7 @@ export default async function createNewKey(admin: AdminInterface, req: NDKRpcReq } const user = await key.user(); - const nsec = nip19.nsecEncode(key.privateKey!); + const nsec = key.nsec; await saveEncrypted( admin.configFile, @@ -38,5 +40,5 @@ export default async function createNewKey(admin: AdminInterface, req: NDKRpcReq npub: user.npub, }); - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } diff --git a/src/daemon/admin/commands/create_new_policy.ts b/src/daemon/admin/commands/create_new_policy.ts index e924c43..0a6a787 100644 --- a/src/daemon/admin/commands/create_new_policy.ts +++ b/src/daemon/admin/commands/create_new_policy.ts @@ -1,5 +1,6 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import prisma from "../../../db.js"; export default async function createNewPolicy(admin: AdminInterface, req: NDKRpcRequest) { @@ -29,5 +30,5 @@ export default async function createNewPolicy(admin: AdminInterface, req: NDKRpc } const result = JSON.stringify(["ok"]); - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } \ No newline at end of file diff --git a/src/daemon/admin/commands/create_new_token.ts b/src/daemon/admin/commands/create_new_token.ts index 04588c4..b66765f 100644 --- a/src/daemon/admin/commands/create_new_token.ts +++ b/src/daemon/admin/commands/create_new_token.ts @@ -1,5 +1,6 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import prisma from "../../../db.js"; export default async function createNewToken(admin: AdminInterface, req: NDKRpcRequest) { @@ -30,5 +31,5 @@ export default async function createNewToken(admin: AdminInterface, req: NDKRpcR if (!tokenRecord) throw new Error("Token not created"); const result = JSON.stringify(["ok"]); - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } \ No newline at end of file diff --git a/src/daemon/admin/commands/ping.ts b/src/daemon/admin/commands/ping.ts index ba63d7c..9368c44 100644 --- a/src/daemon/admin/commands/ping.ts +++ b/src/daemon/admin/commands/ping.ts @@ -1,6 +1,7 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; export default async function ping(admin: AdminInterface, req: NDKRpcRequest) { - return admin.rpc.sendResponse(req.id, req.pubkey, "ok", 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, "ok", NIP46_ADMIN_RESPONSE_KIND); } diff --git a/src/daemon/admin/commands/remove_policy_rule.ts b/src/daemon/admin/commands/remove_policy_rule.ts index b983acd..f8b7c60 100644 --- a/src/daemon/admin/commands/remove_policy_rule.ts +++ b/src/daemon/admin/commands/remove_policy_rule.ts @@ -1,5 +1,6 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import prisma from "../../../db.js"; /** @@ -29,5 +30,5 @@ export default async function removePolicyRule(admin: AdminInterface, req: NDKRp await prisma.policyRule.delete({ where: { id: ruleId } }); const result = JSON.stringify(["ok"]); - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } diff --git a/src/daemon/admin/commands/remove_signing_condition.ts b/src/daemon/admin/commands/remove_signing_condition.ts index 65eff96..0e4da78 100644 --- a/src/daemon/admin/commands/remove_signing_condition.ts +++ b/src/daemon/admin/commands/remove_signing_condition.ts @@ -1,5 +1,6 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import prisma from "../../../db.js"; /** @@ -22,5 +23,5 @@ export default async function removeSigningCondition(admin: AdminInterface, req: await prisma.signingCondition.delete({ where: { id: conditionId } }); const result = JSON.stringify(["ok"]); - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } diff --git a/src/daemon/admin/commands/rename_key_user.ts b/src/daemon/admin/commands/rename_key_user.ts index f2c4ab3..0877cf4 100644 --- a/src/daemon/admin/commands/rename_key_user.ts +++ b/src/daemon/admin/commands/rename_key_user.ts @@ -1,5 +1,6 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import prisma from "../../../db.js"; export default async function renameKeyUser(admin: AdminInterface, req: NDKRpcRequest) { @@ -25,5 +26,5 @@ export default async function renameKeyUser(admin: AdminInterface, req: NDKRpcRe }); const result = JSON.stringify(["ok"]); - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } diff --git a/src/daemon/admin/commands/revoke_token.ts b/src/daemon/admin/commands/revoke_token.ts index 1224d0a..db04c21 100644 --- a/src/daemon/admin/commands/revoke_token.ts +++ b/src/daemon/admin/commands/revoke_token.ts @@ -1,5 +1,6 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import prisma from "../../../db.js"; /** @@ -32,5 +33,5 @@ export default async function revokeToken(admin: AdminInterface, req: NDKRpcRequ }); const result = JSON.stringify(["ok"]); - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } diff --git a/src/daemon/admin/commands/revoke_user.ts b/src/daemon/admin/commands/revoke_user.ts index 409015e..9deb5b6 100644 --- a/src/daemon/admin/commands/revoke_user.ts +++ b/src/daemon/admin/commands/revoke_user.ts @@ -1,5 +1,6 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import prisma from "../../../db.js"; export default async function revokeUser(admin: AdminInterface, req: NDKRpcRequest) { @@ -20,5 +21,5 @@ export default async function revokeUser(admin: AdminInterface, req: NDKRpcReque }); const result = JSON.stringify(["ok"]); - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } diff --git a/src/daemon/admin/commands/unlock_key.ts b/src/daemon/admin/commands/unlock_key.ts index 03c8cbd..dc27f39 100644 --- a/src/daemon/admin/commands/unlock_key.ts +++ b/src/daemon/admin/commands/unlock_key.ts @@ -1,5 +1,6 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; export default async function unlockKey(admin: AdminInterface, req: NDKRpcRequest) { const [ keyName, passphrase ] = req.params as [ string, string ]; @@ -16,5 +17,5 @@ export default async function unlockKey(admin: AdminInterface, req: NDKRpcReques result = JSON.stringify({ success: false, error: e.message }); } - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } \ No newline at end of file diff --git a/src/daemon/admin/commands/update_policy.ts b/src/daemon/admin/commands/update_policy.ts index 43028e5..ebc9805 100644 --- a/src/daemon/admin/commands/update_policy.ts +++ b/src/daemon/admin/commands/update_policy.ts @@ -1,5 +1,6 @@ import { NDKRpcRequest } from "@nostr-dev-kit/ndk"; import AdminInterface from "../index.js"; +import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import prisma from "../../../db.js"; /** @@ -39,5 +40,5 @@ export default async function updatePolicy(admin: AdminInterface, req: NDKRpcReq await prisma.policy.update({ where: { id: policyId }, data }); const result = JSON.stringify(["ok"]); - return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } diff --git a/src/daemon/admin/index.ts b/src/daemon/admin/index.ts index 21e5f23..e4d8509 100644 --- a/src/daemon/admin/index.ts +++ b/src/daemon/admin/index.ts @@ -19,6 +19,7 @@ import updatePolicy from './commands/update_policy'; import addSigningCondition from './commands/add_signing_condition'; import removeSigningCondition from './commands/remove_signing_condition'; import revokeToken from './commands/revoke_token'; +import { NIP46_ADMIN_RESPONSE_KIND } from './kinds.js'; import fs from 'fs'; import { validateRequestFromAdmin } from './validations/request-from-admin'; import { dmUser } from '../../utils/dm-user'; @@ -142,7 +143,7 @@ class AdminInterface { this.ndk.connect(2500).then(() => { // connect for whitelisted admins this.rpc.subscribe({ - "kinds": [NDKKind.NostrConnect, 24134 as number], + "kinds": [NDKKind.NostrConnect, NIP46_ADMIN_RESPONSE_KIND], "#p": [this.signerUser!.pubkey] }); @@ -272,7 +273,7 @@ class AdminInterface { const key = keys.find((k) => k.name === keyName); if (!key || !key.npub) { - return this.rpc.sendResponse(req.id, req.pubkey, JSON.stringify([]), 24134); + return this.rpc.sendResponse(req.id, req.pubkey, JSON.stringify([]), NIP46_ADMIN_RESPONSE_KIND); } const npub = key.npub; @@ -294,7 +295,7 @@ class AdminInterface { }; })); - return this.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return this.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } /** @@ -326,7 +327,7 @@ class AdminInterface { }; })); - return this.rpc.sendResponse(req.id, req.pubkey, result, 24134); + return this.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } /** @@ -338,7 +339,7 @@ class AdminInterface { const result = JSON.stringify(await this.getKeys()); const pubkey = req.pubkey; - return this.rpc.sendResponse(req.id, pubkey, result, 24134); // 24134 + return this.rpc.sendResponse(req.id, pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } /** @@ -350,7 +351,7 @@ class AdminInterface { const result = JSON.stringify(await this.getKeyUsers(req)); const pubkey = req.pubkey; - return this.rpc.sendResponse(req.id, pubkey, result, 24134); // 24134 + return this.rpc.sendResponse(req.id, pubkey, result, NIP46_ADMIN_RESPONSE_KIND); } /** @@ -416,7 +417,7 @@ class AdminInterface { remoteUser.pubkey, 'acl', [params], - 24134, + NIP46_ADMIN_RESPONSE_KIND, (res: NDKRpcResponse) => { this.requestPermissionResponse( remotePubkey, diff --git a/src/daemon/admin/kinds.ts b/src/daemon/admin/kinds.ts new file mode 100644 index 0000000..85ba137 --- /dev/null +++ b/src/daemon/admin/kinds.ts @@ -0,0 +1,14 @@ +import type { NDKKind } from '@nostr-dev-kit/ndk'; + +/** + * NIP-46 admin-RPC response channel — kind-24134. Distinct from the + * standard NIP-46 client channel kind-24133 (`NDKKind.NostrConnect`) + * which carries `sign_event` / `nip04_*` / `nip44_*` / etc. + * + * nsecbunkerd's admin surface uses a dedicated kind so signer clients + * and admin clients don't subscribe to each other's events. + * + * NDK 3.x's `NDKKind` enum does not include 24134; the cast happens + * once here so callers can pass a typed value to `rpc.sendResponse`. + */ +export const NIP46_ADMIN_RESPONSE_KIND = 24134 as NDKKind; diff --git a/src/daemon/authorize.ts b/src/daemon/authorize.ts index cb9721b..2b17744 100644 --- a/src/daemon/authorize.ts +++ b/src/daemon/authorize.ts @@ -59,9 +59,8 @@ async function createRecord( ) { let params: string | undefined; - if (param?.rawEvent) { - const e = param as NDKEvent; - params = JSON.stringify(e.rawEvent()); + if (typeof param === 'object' && param !== null && 'rawEvent' in param) { + params = JSON.stringify(param.rawEvent()); } else if (param) { params = param.toString(); } diff --git a/src/daemon/backend/index.ts b/src/daemon/backend/index.ts index 861f10f..49112fc 100644 --- a/src/daemon/backend/index.ts +++ b/src/daemon/backend/index.ts @@ -18,8 +18,6 @@ export class Backend extends NDKNip46Backend { this.baseUrl = baseUrl; this.fastify = fastify; - - // this.setStrategy('publish_event', new PublishEventHandlingStrategy()); } /** diff --git a/src/daemon/backend/publish-event.ts b/src/daemon/backend/publish-event.ts deleted file mode 100644 index 3302523..0000000 --- a/src/daemon/backend/publish-event.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NDKNip46Backend } from "@nostr-dev-kit/ndk"; -import { IEventHandlingStrategy } from '@nostr-dev-kit/ndk'; - -export default class PublishEventHandlingStrategy implements IEventHandlingStrategy { - async handle(backend: NDKNip46Backend, id: string, remotePubkey: string, params: string[]): Promise { - const event = await backend.signEvent(remotePubkey, params); - if (!event) return undefined; - - console.log('Publishing event', event); - await event.publish(); - - return JSON.stringify(await event.toNostrEvent()); - } -} diff --git a/src/daemon/lib/acl/index.ts b/src/daemon/lib/acl/index.ts index b32f954..2e3559a 100644 --- a/src/daemon/lib/acl/index.ts +++ b/src/daemon/lib/acl/index.ts @@ -124,9 +124,13 @@ export function requestToSigningConditionQuery(method: IMethod, payload?: string const signingConditionQuery: any = { method }; switch (method) { - case 'sign_event': - signingConditionQuery.kind = { in: [ payload?.kind?.toString(), 'all' ] }; + case 'sign_event': { + const kindString = (typeof payload === 'object' && payload?.kind !== undefined) + ? payload.kind.toString() + : undefined; + signingConditionQuery.kind = { in: [kindString, 'all'] }; break; + } } return signingConditionQuery; diff --git a/src/daemon/run.ts b/src/daemon/run.ts index 89eda0a..f664712 100644 --- a/src/daemon/run.ts +++ b/src/daemon/run.ts @@ -1,5 +1,5 @@ import NDK, { NDKPrivateKeySigner, Nip46PermitCallback, Nip46PermitCallbackParams } from '@nostr-dev-kit/ndk'; -import { nip19 } from 'nostr-tools'; +import { nip19, utils as nostrUtils } from 'nostr-tools'; import { Backend } from './backend/index.js'; import { IMethod, @@ -38,8 +38,7 @@ function getKeys(config: DaemonConfig) { const keys: Key[] = []; for (const [name, nsec] of Object.entries(config.keys)) { - const hexpk = nip19.decode(nsec).data as string; - const user = await new NDKPrivateKeySigner(hexpk).user(); + const user = await new NDKPrivateKeySigner(nsec).user(); const key = { name, npub: user.npub, @@ -164,7 +163,7 @@ class Daemon { explicitRelayUrls: config.nostr.relays, }); this.ndk.pool.on('relay:connect', (r) => console.log(`✅ Connected to ${r.url}`) ); - this.ndk.pool.on('relay:notice', (n, r) => { console.log(`👀 Notice from ${r.url}`, n); }); + this.ndk.pool.on('notice', (r, n) => { console.log(`👀 Notice from ${r.url}`, n); }); this.ndk.pool.on('relay:disconnect', (r) => { console.log(`🚫 Disconnected from ${r.url}`); @@ -206,7 +205,10 @@ class Daemon { continue; } - const nsec = nip19.nsecEncode(settings.key); + // nostr-tools v2: `nsecEncode` takes `Uint8Array`, not hex string. + // pragma: allowlist secret + // `settings.key` is the hex-encoded private key from config. + const nsec = nip19.nsecEncode(nostrUtils.hexToBytes(settings.key)); this.loadNsec(keyName, nsec); } } @@ -226,27 +228,13 @@ class Daemon { */ async startKey(name: string, nsec: string) { const cb = signingAuthorizationCallback(name, this.adminInterface); - let hexpk: string; - - if (nsec.startsWith('nsec1')) { - try { - // NDK 2.8.1's NDKPrivateKeySigner constructor passes its - // arg straight to nostr-tools getPublicKey() which requires - // 32-byte hex / bytes / bigint, not bech32. Without this - // decode, every key created via create_new_key fails to - // load with the nostr-tools getPublicKey type error, so - // the bunker can never sign for any target it provisions. - // See aiolabs/nsecbunkerd#8. - hexpk = nip19.decode(nsec).data as string; - } catch(e) { - console.error(`Error loading key ${name}:`, e); - return - } - } else { - hexpk = nsec; - } - - const backend = new Backend(this.ndk, this.fastify, hexpk, cb, this.config.baseUrl); + // NDK 3.x's `NDKPrivateKeySigner` accepts nsec1 or hex directly + // (see `core/src/signers/private-key/index.ts` `@ai-guardrail` + // — "DO NOT use nip19.decode() to convert nsec to hex before + // passing it here"). The bech32-decode workaround for #8 was + // tied to NDK 2.8.1's old constructor behavior and is no + // longer needed post-#14 NDK bump. + const backend = new Backend(this.ndk, this.fastify, nsec, cb, this.config.baseUrl); await backend.start(); }