From 89986759e09c57c21d8e4dfed09f2f0433d0fd26 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:26:51 -0400 Subject: [PATCH] Short-circuit hex actor keys --- js/nostr.js | 39 +++++++++-- tests/watch-history.test.mjs | 123 +++++++++++++++++++++++++++++------ 2 files changed, 138 insertions(+), 24 deletions(-) diff --git a/js/nostr.js b/js/nostr.js index 7feff552..1ec94372 100644 --- a/js/nostr.js +++ b/js/nostr.js @@ -2323,6 +2323,10 @@ function normalizeActorKey(actor) { return ""; } + if (/^[0-9a-f]{64}$/i.test(trimmed)) { + return trimmed.toLowerCase(); + } + const decodedHex = decodeNpubToHex(trimmed); if (decodedHex) { return decodedHex.toLowerCase(); @@ -2587,23 +2591,48 @@ function cloneEventForCache(event) { } function decodeNpubToHex(npub) { - if (typeof npub !== "string" || !npub.trim()) { + if (typeof npub !== "string") { return ""; } - const tools = getCachedNostrTools(); + const trimmed = npub.trim(); + if (!trimmed) { + return ""; + } + + if (/^[0-9a-f]{64}$/i.test(trimmed)) { + return trimmed.toLowerCase(); + } + + const lower = trimmed.toLowerCase(); + const hasNpubPrefix = lower.startsWith("npub1"); + if (!hasNpubPrefix) { + return ""; + } + + const warnableNpub = /^npub1[023456789acdefghjklmnpqrstuvwxyz]+$/i.test( + trimmed + ); + + let tools = cachedNostrTools; + if (!tools || typeof tools?.nip19?.decode !== "function") { + const fallbackTools = readToolkitFromScope(); + if (fallbackTools) { + tools = fallbackTools; + } + } if (!tools?.nip19 || typeof tools.nip19.decode !== "function") { return ""; } try { - const decoded = tools.nip19.decode(npub.trim()); + const decoded = tools.nip19.decode(trimmed); if (decoded?.type === "npub" && typeof decoded.data === "string") { return decoded.data; } } catch (error) { - if (isDevMode) { - console.warn(`[nostr] Failed to decode npub: ${npub}`, error); + if (isDevMode && warnableNpub) { + console.warn(`[nostr] Failed to decode npub: ${trimmed}`, error); } } return ""; diff --git a/tests/watch-history.test.mjs b/tests/watch-history.test.mjs index 805f755b..f5d3705f 100644 --- a/tests/watch-history.test.mjs +++ b/tests/watch-history.test.mjs @@ -20,6 +20,7 @@ const { const { nostrClient, chunkWatchHistoryPayloadItems, + normalizeActorKey, } = await import("../js/nostr.js"); const { watchHistoryService } = await import("../js/watchHistoryService.js"); const { getApplication, setApplication } = await import( @@ -348,9 +349,86 @@ function maxCreatedAt(events = []) { }, 0); } +async function testNormalizeActorKeyShortCircuit() { + console.log("Running normalizeActorKey short-circuit test..."); + + const actorHex = "f".repeat(64); + const actorNpub = "npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"; + if (!window.NostrTools || typeof window.NostrTools !== "object") { + window.NostrTools = {}; + } + const previousNip19 = window.NostrTools.nip19; + const previousDecode = + previousNip19 && typeof previousNip19.decode === "function" + ? previousNip19.decode + : null; + const decodeCalls = []; + + window.NostrTools.nip19 = { + ...(previousNip19 || {}), + decode(value) { + decodeCalls.push(value); + if (value === actorNpub) { + return { type: "npub", data: actorHex }; + } + if (typeof previousDecode === "function") { + return previousDecode(value); + } + throw new Error("unsupported-npub"); + }, + }; + + const originalWarn = console.warn; + const warnings = []; + console.warn = (...args) => { + warnings.push(args); + }; + + try { + const upperHex = actorHex.toUpperCase(); + const normalizedHex = normalizeActorKey(upperHex); + assert.equal( + normalizedHex, + actorHex, + "hex inputs should normalize to lowercase hex", + ); + assert.equal( + decodeCalls.length, + 0, + "hex inputs should not invoke nip19.decode", + ); + assert.equal( + warnings.length, + 0, + "hex inputs should not emit decode warnings", + ); + + const normalizedNpub = normalizeActorKey(actorNpub); + assert.equal(normalizedNpub, actorHex, "npub inputs should decode to hex"); + assert.equal( + decodeCalls.length, + 1, + "npub inputs should invoke nip19.decode", + ); + assert.equal( + warnings.length, + 0, + "successful npub decode should not emit warnings", + ); + } finally { + console.warn = originalWarn; + if (previousNip19) { + window.NostrTools.nip19 = previousNip19; + } else if (window.NostrTools) { + delete window.NostrTools.nip19; + } + } +} + async function testFetchWatchHistoryExtensionDecryptsHexAndNpub() { const actorHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; - const actorNpub = "npub-test-extension-decrypt"; + const actorNpub = + "npub1testextensiondecryptqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"; const snapshotId = "extension-fetch"; const chunkIdentifier = "chunk-extension"; @@ -401,23 +479,25 @@ async function testFetchWatchHistoryExtensionDecryptsHexAndNpub() { ], }; - const previousNostrTools = window.NostrTools || {}; - window.NostrTools = { - ...previousNostrTools, - nip19: { - ...(previousNostrTools.nip19 || {}), - decode: (value) => { - if (value === actorNpub) { - return { type: "npub", data: actorHex }; - } - if ( - previousNostrTools.nip19 && - typeof previousNostrTools.nip19.decode === "function" - ) { - return previousNostrTools.nip19.decode(value); - } - throw new Error("unsupported-npub"); - }, + if (!window.NostrTools || typeof window.NostrTools !== "object") { + window.NostrTools = {}; + } + const previousNip19 = window.NostrTools.nip19; + const previousDecode = + previousNip19 && typeof previousNip19.decode === "function" + ? previousNip19.decode + : null; + + window.NostrTools.nip19 = { + ...(previousNip19 || {}), + decode: (value) => { + if (value === actorNpub) { + return { type: "npub", data: actorHex }; + } + if (typeof previousDecode === "function") { + return previousDecode(value); + } + throw new Error("unsupported-npub"); }, }; @@ -505,7 +585,11 @@ async function testFetchWatchHistoryExtensionDecryptsHexAndNpub() { await runVariant("hex", actorHex, actorHex); await runVariant("npub", actorNpub, actorNpub); } finally { - window.NostrTools = previousNostrTools; + if (previousNip19) { + window.NostrTools.nip19 = previousNip19; + } else if (window.NostrTools) { + delete window.NostrTools.nip19; + } nostrClient.ensureSessionActor = originalEnsure; nostrClient.sessionActor = originalSession; nostrClient.pubkey = originalPub; @@ -1573,6 +1657,7 @@ await testWatchHistoryStaleCacheRefresh(); await testWatchHistoryLocalFallbackWhenDisabled(); await testWatchHistorySyncEnabledForLoggedInUsers(); await testWatchHistoryAppLoginFallback(); +await testNormalizeActorKeyShortCircuit(); console.log("watch-history.test.mjs completed successfully");