From cd9b724ca6b793579614e50c4a4d1b86232cea85 Mon Sep 17 00:00:00 2001 From: redshift <213178690+1ftredsh@users.noreply.github.com> Date: Tue, 19 May 2026 02:00:18 +0800 Subject: [PATCH] fix(nwc): double hex-encoding of client pubkey and unused connection secret Two bugs caused NWC responses to never arrive: 1. getPublicKey() in nostr-tools v2 already returns a hex string, but the code wrapped it with Buffer.from().toString('hex'), producing a double-encoded 128-char string. The relay rejected the REQ filter with 'filter item too large' because the #p tag value was invalid. 2. The 'secret' from the NWC connection string (the pre-shared client private key per NIP-47) was parsed but never used. Instead a new random keypair was generated on every restart, so the wallet service would see requests from an unexpected pubkey. Also removed 'since' from the subscription filter (simpler, some relays choke on it) and added debug logging for the REQ filter. --- src/daemon/wallet/nwc-client.ts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/daemon/wallet/nwc-client.ts b/src/daemon/wallet/nwc-client.ts index 559b343..22ae7dc 100644 --- a/src/daemon/wallet/nwc-client.ts +++ b/src/daemon/wallet/nwc-client.ts @@ -3,7 +3,6 @@ // Bun's native WebSocket for relay communication. import { - generateSecretKey, getPublicKey, nip04, finalizeEvent, @@ -130,12 +129,12 @@ export function createNwcClient(options: NwcClientOptions): NwcClient { const publishTimeoutMs = options.publishTimeoutMs ?? 10000; const maxReconnectAttempts = options.maxReconnectAttempts ?? 5; - // Generate or use provided client keypair + // Client keypair — use secret from the connection string (per NIP-47) const clientSecretKey = options.clientSecretKey ? Buffer.from(options.clientSecretKey, "hex") - : generateSecretKey(); - const clientPubkey = getPublicKey(clientSecretKey); - const clientPubkeyHex = Buffer.from(clientPubkey).toString("hex"); + : Buffer.from(parsed.secret, "hex"); + // getPublicKey returns a hex string (not Uint8Array) so use it directly + const clientPubkeyHex = getPublicKey(clientSecretKey); // ── State ────────────────────────────────────────────────────── let ws: WebSocket | null = null; @@ -371,15 +370,12 @@ export function createNwcClient(options: NwcClientOptions): NwcClient { // Subscribe to response events (kind 23195) tagged for us subscriptionId = `routstrd-nwc-${clientPubkeyHex.slice(0, 8)}`; - sendRaw([ - "REQ", - subscriptionId, - { - kinds: [NWC_RESPONSE_KIND], - "#p": [clientPubkeyHex], - since: Math.floor(Date.now() / 1000), - }, - ]); + const subFilter = { + kinds: [NWC_RESPONSE_KIND], + "#p": [clientPubkeyHex], + }; + debugLog(`REQ filter: ${JSON.stringify(subFilter)}`); + sendRaw(["REQ", subscriptionId, subFilter]); debugLog(`Subscribed to responses: ${subscriptionId}`); resolve(); };