From aa4e86758ad306b30fe41b1708bda70301f5c7ca Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 5 Oct 2025 20:49:56 -0400 Subject: [PATCH] Fix NWC encryption fallback handling --- js/payments/nwcClient.js | 48 ++++++++++++++++----------- tests/nwc-client.test.mjs | 70 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 19 deletions(-) diff --git a/js/payments/nwcClient.js b/js/payments/nwcClient.js index 3bc69e30..8ec495bb 100644 --- a/js/payments/nwcClient.js +++ b/js/payments/nwcClient.js @@ -341,16 +341,21 @@ function createNip44Encryption(context) { function getWalletSupportedEncryption(infoEvent) { if (!infoEvent || !Array.isArray(infoEvent.tags)) { - return ["nip04"]; + return { schemes: [], explicit: false }; } const tag = infoEvent.tags.find((entry) => Array.isArray(entry) && entry[0] === "encryption"); if (!tag || typeof tag[1] !== "string") { - return ["nip04"]; + return { schemes: [], explicit: false }; } const values = normalizeSpaceSeparatedValues(tag[1]); - return values.length ? values : ["nip04"]; + if (!values.length) { + return { schemes: [], explicit: false }; + } + + const schemes = Array.from(new Set(values)); + return { schemes, explicit: true }; } function getEncryptionCandidates(context) { @@ -487,31 +492,35 @@ async function ensureEncryption(context) { } } - const walletSchemes = getWalletSupportedEncryption(infoEvent); + const { schemes: walletSchemes } = getWalletSupportedEncryption(infoEvent); const candidates = getEncryptionCandidates(context).filter((candidate) => { return !state.unsupported.has(candidate.scheme); }); - for (const candidate of candidates) { - if (walletSchemes.includes(candidate.scheme)) { - context.encryption = candidate; - return candidate; - } + const compatibleCandidates = walletSchemes.length + ? candidates.filter((candidate) => walletSchemes.includes(candidate.scheme)) + : candidates; + + if (compatibleCandidates.length) { + const selected = compatibleCandidates[0]; + context.encryption = selected; + return selected; } - const fallback = candidates.find((candidate) => candidate.scheme === "nip04"); - if (fallback) { - console.warn( - "[nwcClient] Falling back to nip04 encryption despite wallet not advertising support." - ); - context.encryption = fallback; - return fallback; - } - - if (walletSchemes.includes("nip04")) { + if ( + walletSchemes.includes("nip04") && + !candidates.some((candidate) => candidate.scheme === "nip04") + ) { throw new Error("NostrTools.nip04.encrypt is not available."); } + if (walletSchemes.length) { + const message = `Wallet advertises unsupported encryption schemes: ${walletSchemes.join(", ")}.`; + const error = new Error(message); + error.code = "UNSUPPORTED_ENCRYPTION"; + throw error; + } + throw new Error("No compatible wallet encryption scheme available."); } @@ -983,4 +992,5 @@ export const __TESTING__ = Object.freeze({ ensureActiveState, buildPayInvoiceParams, getActiveState: () => activeState, + ensureEncryptionForContext: ensureEncryption, }); diff --git a/tests/nwc-client.test.mjs b/tests/nwc-client.test.mjs index e0056f9e..3e7cec80 100644 --- a/tests/nwc-client.test.mjs +++ b/tests/nwc-client.test.mjs @@ -48,6 +48,21 @@ await (async () => { getPublicKey() { return "c".repeat(64); }, + nip44: { + v2: { + async encrypt(plaintext, key) { + return `nip44:${key}:${plaintext}`; + }, + async decrypt(ciphertext, key) { + return `nip44:${key}:${ciphertext}`; + }, + utils: { + getConversationKey(secretBytes, pubkey) { + return `key-${pubkey}-${secretBytes.length}`; + }, + }, + }, + }, }; const walletPubkey = "b".repeat(64); @@ -75,6 +90,61 @@ await (async () => { assert.equal(reparsed.secretKey, secretKey); assert.equal(reparsed.queryParams.lud16, lud16); + context.infoEvent = { + kind: 13194, + tags: [["encryption", "nip44_v2"]], + }; + + const encryption = await __TESTING__.ensureEncryptionForContext(context); + assert.equal(encryption.scheme, "nip44_v2"); + const encrypted = await encryption.encrypt("payload"); + assert.match(encrypted, /^nip44:key-b{64}-/); + + resetWalletClient(); +})(); + +await (async () => { + resetWalletClient(); + + window.NostrTools = { + getPublicKey() { + return "c".repeat(64); + }, + nip04: { + async encrypt() { + return "nip04"; + }, + async decrypt() { + return "nip04"; + }, + }, + }; + + const walletPubkey = "b".repeat(64); + const secretKey = "a".repeat(64); + const relays = ["wss://relay.one.example"]; + const uri = + `nostr+walletconnect://${walletPubkey}` + + `?relay=${encodeURIComponent(relays[0])}` + + `&secret=${secretKey}`; + + const context = ensureActiveState({ nwcUri: uri }); + context.infoEvent = { + kind: 13194, + tags: [["encryption", "nip44_v2"]], + }; + + await assert.rejects( + () => __TESTING__.ensureEncryptionForContext(context), + (error) => { + assert.match( + error?.message || "", + /Wallet advertises unsupported encryption schemes: nip44_v2\./ + ); + return true; + } + ); + resetWalletClient(); })();