From cef64aa5d3e5f9f33963134cf3f38e8aaae0274c Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 18 Jan 2026 08:56:53 -0500 Subject: [PATCH] refactor: extract signer cipher helpers --- js/nostr/adapters/nsecAdapter.js | 136 +------------------------ js/nostr/client.js | 160 +----------------------------- js/nostr/signerHelpers.js | 165 +++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 294 deletions(-) create mode 100644 js/nostr/signerHelpers.js diff --git a/js/nostr/adapters/nsecAdapter.js b/js/nostr/adapters/nsecAdapter.js index c8f5f6e3..5ce8ef6a 100644 --- a/js/nostr/adapters/nsecAdapter.js +++ b/js/nostr/adapters/nsecAdapter.js @@ -1,6 +1,7 @@ // js/nostr/adapters/nsecAdapter.js import { signEventWithPrivateKey } from "../publishHelpers.js"; +import { createPrivateKeyCipherClosures } from "../signerHelpers.js"; import { ensureNostrTools, getCachedNostrTools } from "../toolkit.js"; import { normalizeActorKey } from "../watchHistory.js"; @@ -39,141 +40,6 @@ async function resolvePublicKey(privateKey, pubkey) { return ""; } -async function createPrivateKeyCipherClosures(privateKey) { - const normalizedPrivateKey = normalizePrivateKey(privateKey); - if (!normalizedPrivateKey) { - return {}; - } - - const tools = (await ensureNostrTools()) || getCachedNostrTools(); - if (!tools) { - return {}; - } - - const closures = {}; - - const normalizeTargetPubkey = (candidate) => { - const normalized = normalizeActorKey(candidate); - if (!normalized || !HEX64_REGEX.test(normalized)) { - throw new Error("A hex-encoded pubkey is required for encryption."); - } - return normalized; - }; - - const resolveHexToBytes = () => { - if (typeof tools?.utils?.hexToBytes === "function") { - return (value) => tools.utils.hexToBytes(value); - } - - return (value) => { - if (typeof value !== "string") { - throw new Error("Invalid hex input."); - } - const trimmed = value.trim(); - if (!trimmed || trimmed.length % 2 !== 0) { - throw new Error("Invalid hex input."); - } - - const bytes = new Uint8Array(trimmed.length / 2); - for (let index = 0; index < trimmed.length; index += 2) { - const byte = Number.parseInt(trimmed.slice(index, index + 2), 16); - if (Number.isNaN(byte)) { - throw new Error("Invalid hex input."); - } - bytes[index / 2] = byte; - } - return bytes; - }; - }; - - if ( - tools?.nip04?.encrypt && - typeof tools.nip04.encrypt === "function" && - tools?.nip04?.decrypt && - typeof tools.nip04.decrypt === "function" - ) { - closures.nip04Encrypt = async (targetPubkey, plaintext) => - tools.nip04.encrypt( - normalizedPrivateKey, - normalizeTargetPubkey(targetPubkey), - plaintext, - ); - - closures.nip04Decrypt = async (targetPubkey, ciphertext) => - tools.nip04.decrypt( - normalizedPrivateKey, - normalizeTargetPubkey(targetPubkey), - ciphertext, - ); - } - - const nip44 = tools?.nip44 || null; - let nip44Encrypt = null; - let nip44Decrypt = null; - let nip44GetConversationKey = null; - - if (nip44?.v2 && typeof nip44.v2 === "object") { - if (typeof nip44.v2.encrypt === "function") { - nip44Encrypt = nip44.v2.encrypt; - } - if (typeof nip44.v2.decrypt === "function") { - nip44Decrypt = nip44.v2.decrypt; - } - if (typeof nip44.v2?.utils?.getConversationKey === "function") { - nip44GetConversationKey = nip44.v2.utils.getConversationKey; - } - } - - if ((!nip44Encrypt || !nip44Decrypt) && nip44 && typeof nip44 === "object") { - if (typeof nip44.encrypt === "function") { - nip44Encrypt = nip44.encrypt; - } - if (typeof nip44.decrypt === "function") { - nip44Decrypt = nip44.decrypt; - } - if (!nip44GetConversationKey) { - if (typeof nip44.getConversationKey === "function") { - nip44GetConversationKey = nip44.getConversationKey; - } else if (typeof nip44.utils?.getConversationKey === "function") { - nip44GetConversationKey = nip44.utils.getConversationKey; - } - } - } - - if (nip44Encrypt && nip44Decrypt && nip44GetConversationKey) { - const hexToBytes = resolveHexToBytes(); - let cachedPrivateKeyBytes = null; - const getPrivateKeyBytes = () => { - if (!cachedPrivateKeyBytes) { - cachedPrivateKeyBytes = hexToBytes(normalizedPrivateKey); - } - return cachedPrivateKeyBytes; - }; - - const conversationKeyCache = new Map(); - const ensureConversationKey = (targetPubkey) => { - const normalizedTarget = normalizeTargetPubkey(targetPubkey); - const cached = conversationKeyCache.get(normalizedTarget); - if (cached) { - return cached; - } - - const privateKeyBytes = getPrivateKeyBytes(); - const derived = nip44GetConversationKey(privateKeyBytes, normalizedTarget); - conversationKeyCache.set(normalizedTarget, derived); - return derived; - }; - - closures.nip44Encrypt = async (targetPubkey, plaintext) => - nip44Encrypt(plaintext, ensureConversationKey(targetPubkey)); - - closures.nip44Decrypt = async (targetPubkey, ciphertext) => - nip44Decrypt(ciphertext, ensureConversationKey(targetPubkey)); - } - - return closures; -} - export async function createNsecAdapter({ privateKey, pubkey } = {}) { const normalizedPrivateKey = normalizePrivateKey(privateKey); const resolvedPubkey = await resolvePublicKey(normalizedPrivateKey, pubkey); diff --git a/js/nostr/client.js b/js/nostr/client.js index 3ddcfd10..41dc2d0e 100644 --- a/js/nostr/client.js +++ b/js/nostr/client.js @@ -163,6 +163,7 @@ import { summarizeRelayPublishResultsForLog, } from "./nip46Client.js"; import { profileCache } from "../state/profileCache.js"; +import { createPrivateKeyCipherClosures } from "./signerHelpers.js"; function normalizeProfileFromEvent(event) { if (!event || !event.content) return null; @@ -809,165 +810,6 @@ function eventToAddressPointer(event) { return ""; } -let loggedMissingCipherToolkit = false; -let loggedMissingNip04Cipher = false; -let loggedMissingNip44Cipher = false; - -async function createPrivateKeyCipherClosures(privateKey) { - const normalizedPrivateKey = - typeof privateKey === "string" && HEX64_REGEX.test(privateKey) - ? privateKey.toLowerCase() - : ""; - - if (!normalizedPrivateKey) { - return {}; - } - - const tools = (await ensureNostrTools()) || getCachedNostrTools(); - if (!tools) { - if (!loggedMissingCipherToolkit) { - loggedMissingCipherToolkit = true; - devLogger.warn( - "[nostr] nostr-tools bundle missing for private key cipher helpers.", - ); - } - return {}; - } - - const closures = {}; - - const normalizeTargetPubkey = (candidate) => { - const normalized = normalizeActorKey(candidate); - if (!normalized || !HEX64_REGEX.test(normalized)) { - throw new Error("A hex-encoded pubkey is required for encryption."); - } - return normalized; - }; - - const resolveHexToBytes = () => { - if (typeof tools?.utils?.hexToBytes === "function") { - return (value) => tools.utils.hexToBytes(value); - } - - return (value) => { - if (typeof value !== "string") { - throw new Error("Invalid hex input."); - } - const trimmed = value.trim(); - if (!trimmed || trimmed.length % 2 !== 0) { - throw new Error("Invalid hex input."); - } - - const bytes = new Uint8Array(trimmed.length / 2); - for (let index = 0; index < trimmed.length; index += 2) { - const byte = Number.parseInt(trimmed.slice(index, index + 2), 16); - if (Number.isNaN(byte)) { - throw new Error("Invalid hex input."); - } - bytes[index / 2] = byte; - } - return bytes; - }; - }; - - if ( - tools?.nip04?.encrypt && - typeof tools.nip04.encrypt === "function" && - tools?.nip04?.decrypt && - typeof tools.nip04.decrypt === "function" - ) { - closures.nip04Encrypt = async (targetPubkey, plaintext) => - tools.nip04.encrypt( - normalizedPrivateKey, - normalizeTargetPubkey(targetPubkey), - plaintext, - ); - - closures.nip04Decrypt = async (targetPubkey, ciphertext) => - tools.nip04.decrypt( - normalizedPrivateKey, - normalizeTargetPubkey(targetPubkey), - ciphertext, - ); - } else if (!loggedMissingNip04Cipher) { - loggedMissingNip04Cipher = true; - devLogger.warn( - "[nostr] nip04 helpers unavailable in nostr-tools bundle.", - ); - } - - const nip44 = tools?.nip44 || null; - let nip44Encrypt = null; - let nip44Decrypt = null; - let nip44GetConversationKey = null; - - if (nip44?.v2 && typeof nip44.v2 === "object") { - if (typeof nip44.v2.encrypt === "function") { - nip44Encrypt = nip44.v2.encrypt; - } - if (typeof nip44.v2.decrypt === "function") { - nip44Decrypt = nip44.v2.decrypt; - } - if (typeof nip44.v2?.utils?.getConversationKey === "function") { - nip44GetConversationKey = nip44.v2.utils.getConversationKey; - } - } - - if ((!nip44Encrypt || !nip44Decrypt) && nip44 && typeof nip44 === "object") { - if (typeof nip44.encrypt === "function") { - nip44Encrypt = nip44.encrypt; - } - if (typeof nip44.decrypt === "function") { - nip44Decrypt = nip44.decrypt; - } - if (!nip44GetConversationKey) { - if (typeof nip44.getConversationKey === "function") { - nip44GetConversationKey = nip44.getConversationKey; - } else if (typeof nip44.utils?.getConversationKey === "function") { - nip44GetConversationKey = nip44.utils.getConversationKey; - } - } - } - - if (nip44Encrypt && nip44Decrypt && nip44GetConversationKey) { - const hexToBytes = resolveHexToBytes(); - let cachedPrivateKeyBytes = null; - const getPrivateKeyBytes = () => { - if (!cachedPrivateKeyBytes) { - cachedPrivateKeyBytes = hexToBytes(normalizedPrivateKey); - } - return cachedPrivateKeyBytes; - }; - - const conversationKeyCache = new Map(); - const ensureConversationKey = (targetPubkey) => { - const normalizedTarget = normalizeTargetPubkey(targetPubkey); - const cached = conversationKeyCache.get(normalizedTarget); - if (cached) { - return cached; - } - - const privateKeyBytes = getPrivateKeyBytes(); - const derived = nip44GetConversationKey(privateKeyBytes, normalizedTarget); - conversationKeyCache.set(normalizedTarget, derived); - return derived; - }; - - closures.nip44Encrypt = async (targetPubkey, plaintext) => - nip44Encrypt(plaintext, ensureConversationKey(targetPubkey)); - - closures.nip44Decrypt = async (targetPubkey, ciphertext) => - nip44Decrypt(ciphertext, ensureConversationKey(targetPubkey)); - } else if (!loggedMissingNip44Cipher) { - loggedMissingNip44Cipher = true; - devLogger.warn( - "[nostr] nip44 helpers unavailable in nostr-tools bundle.", - ); - } - - return closures; -} - function cloneEventForCache(event) { if (!event || typeof event !== "object") { return null; diff --git a/js/nostr/signerHelpers.js b/js/nostr/signerHelpers.js new file mode 100644 index 00000000..51857eff --- /dev/null +++ b/js/nostr/signerHelpers.js @@ -0,0 +1,165 @@ +// js/nostr/signerHelpers.js + +import { devLogger } from "../utils/logger.js"; +import { HEX64_REGEX } from "./nip46Client.js"; +import { ensureNostrTools, getCachedNostrTools } from "./toolkit.js"; +import { normalizeActorKey } from "./watchHistory.js"; + +let loggedMissingCipherToolkit = false; +let loggedMissingNip04Cipher = false; +let loggedMissingNip44Cipher = false; + +export async function createPrivateKeyCipherClosures(privateKey) { + const normalizedPrivateKey = + typeof privateKey === "string" && HEX64_REGEX.test(privateKey) + ? privateKey.toLowerCase() + : ""; + + if (!normalizedPrivateKey) { + return {}; + } + + const tools = (await ensureNostrTools()) || getCachedNostrTools(); + if (!tools) { + if (!loggedMissingCipherToolkit) { + loggedMissingCipherToolkit = true; + devLogger.warn( + "[nostr] nostr-tools bundle missing for private key cipher helpers.", + ); + } + return {}; + } + + const closures = {}; + + const normalizeTargetPubkey = (candidate) => { + const normalized = normalizeActorKey(candidate); + if (!normalized || !HEX64_REGEX.test(normalized)) { + throw new Error("A hex-encoded pubkey is required for encryption."); + } + return normalized; + }; + + const resolveHexToBytes = () => { + if (typeof tools?.utils?.hexToBytes === "function") { + return (value) => tools.utils.hexToBytes(value); + } + + return (value) => { + if (typeof value !== "string") { + throw new Error("Invalid hex input."); + } + const trimmed = value.trim(); + if (!trimmed || trimmed.length % 2 !== 0) { + throw new Error("Invalid hex input."); + } + + const bytes = new Uint8Array(trimmed.length / 2); + for (let index = 0; index < trimmed.length; index += 2) { + const byte = Number.parseInt(trimmed.slice(index, index + 2), 16); + if (Number.isNaN(byte)) { + throw new Error("Invalid hex input."); + } + bytes[index / 2] = byte; + } + return bytes; + }; + }; + + if ( + tools?.nip04?.encrypt && + typeof tools.nip04.encrypt === "function" && + tools?.nip04?.decrypt && + typeof tools.nip04.decrypt === "function" + ) { + closures.nip04Encrypt = async (targetPubkey, plaintext) => + tools.nip04.encrypt( + normalizedPrivateKey, + normalizeTargetPubkey(targetPubkey), + plaintext, + ); + + closures.nip04Decrypt = async (targetPubkey, ciphertext) => + tools.nip04.decrypt( + normalizedPrivateKey, + normalizeTargetPubkey(targetPubkey), + ciphertext, + ); + } else if (!loggedMissingNip04Cipher) { + loggedMissingNip04Cipher = true; + devLogger.warn( + "[nostr] nip04 helpers unavailable in nostr-tools bundle.", + ); + } + + const nip44 = tools?.nip44 || null; + let nip44Encrypt = null; + let nip44Decrypt = null; + let nip44GetConversationKey = null; + + if (nip44?.v2 && typeof nip44.v2 === "object") { + if (typeof nip44.v2.encrypt === "function") { + nip44Encrypt = nip44.v2.encrypt; + } + if (typeof nip44.v2.decrypt === "function") { + nip44Decrypt = nip44.v2.decrypt; + } + if (typeof nip44.v2?.utils?.getConversationKey === "function") { + nip44GetConversationKey = nip44.v2.utils.getConversationKey; + } + } + + if ((!nip44Encrypt || !nip44Decrypt) && nip44 && typeof nip44 === "object") { + if (typeof nip44.encrypt === "function") { + nip44Encrypt = nip44.encrypt; + } + if (typeof nip44.decrypt === "function") { + nip44Decrypt = nip44.decrypt; + } + if (!nip44GetConversationKey) { + if (typeof nip44.getConversationKey === "function") { + nip44GetConversationKey = nip44.getConversationKey; + } else if (typeof nip44.utils?.getConversationKey === "function") { + nip44GetConversationKey = nip44.utils.getConversationKey; + } + } + } + + if (nip44Encrypt && nip44Decrypt && nip44GetConversationKey) { + const hexToBytes = resolveHexToBytes(); + let cachedPrivateKeyBytes = null; + const getPrivateKeyBytes = () => { + if (!cachedPrivateKeyBytes) { + cachedPrivateKeyBytes = hexToBytes(normalizedPrivateKey); + } + return cachedPrivateKeyBytes; + }; + + const conversationKeyCache = new Map(); + const ensureConversationKey = (targetPubkey) => { + const normalizedTarget = normalizeTargetPubkey(targetPubkey); + const cached = conversationKeyCache.get(normalizedTarget); + if (cached) { + return cached; + } + + const privateKeyBytes = getPrivateKeyBytes(); + const derived = nip44GetConversationKey(privateKeyBytes, normalizedTarget); + conversationKeyCache.set(normalizedTarget, derived); + return derived; + }; + + closures.nip44Encrypt = async (targetPubkey, plaintext) => + nip44Encrypt(plaintext, ensureConversationKey(targetPubkey)); + + closures.nip44Decrypt = async (targetPubkey, ciphertext) => + nip44Decrypt(ciphertext, ensureConversationKey(targetPubkey)); + } else if (!loggedMissingNip44Cipher) { + loggedMissingNip44Cipher = true; + devLogger.warn( + "[nostr] nip44 helpers unavailable in nostr-tools bundle.", + ); + } + + return closures; +}