From ffd8f0e23cce5305e2442213ecaec6abf0352cfc Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:56:44 +0000 Subject: [PATCH] fix: Sync package-lock.json and fix tests for nostr-tools v2 - Synced `package-lock.json` with `package.json` to resolve `npm ci` failures. - Updated `nostr-tools` dependencies to resolve conflicts. - Fixed `SyntaxError` in `tests/nostr-login-permissions.test.mjs` caused by duplicate code. - Updated `js/nostr/client.js`, `js/nostr/publishHelpers.js`, `js/nostr/adapters/nsecAdapter.js`, `js/nostr/nip46Client.js` to use `hexToBytes` helper when calling `nostr-tools` functions, as v2+ expects `Uint8Array` instead of hex strings. - Added `hexToBytes` helper in `js/utils/hex.js`. - Updated tests (`tests/nostr/dm-direct-message-flow.test.mjs`, `tests/nostr-send-direct-message.test.mjs`, `tests/nostr-private-key-signer.test.mjs`, `tests/nostr/nip46Client.test.js`) to use `Uint8Array` for keys where required. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- js/nostr/adapters/nsecAdapter.js | 3 +- js/nostr/client.js | 13 +- js/nostr/nip46Client.js | 13 +- js/nostr/publishHelpers.js | 3 +- js/utils/hex.js | 18 ++ package-lock.json | 297 +++++++------------- package.json | 5 +- tests/nostr-login-permissions.test.mjs | 59 +--- tests/nostr-private-key-signer.test.mjs | 4 +- tests/nostr-send-direct-message.test.mjs | 11 +- tests/nostr/dm-direct-message-flow.test.mjs | 16 +- tests/nostr/nip46Client.test.js | 48 ++-- 12 files changed, 186 insertions(+), 304 deletions(-) diff --git a/js/nostr/adapters/nsecAdapter.js b/js/nostr/adapters/nsecAdapter.js index 5bb94cc2..bcc944ca 100644 --- a/js/nostr/adapters/nsecAdapter.js +++ b/js/nostr/adapters/nsecAdapter.js @@ -3,6 +3,7 @@ import { signEventWithPrivateKey } from "../publishHelpers.js"; import { createPrivateKeyCipherClosures } from "../signerHelpers.js"; import { ensureNostrTools, getCachedNostrTools } from "../toolkit.js"; +import { hexToBytes } from "../../utils/hex.js"; import { normalizeActorKey } from "../watchHistory.js"; const HEX64_REGEX = /^[0-9a-f]{64}$/i; @@ -30,7 +31,7 @@ async function resolvePublicKey(privateKey, pubkey) { const tools = (await ensureNostrTools()) || getCachedNostrTools(); if (tools?.getPublicKey && typeof tools.getPublicKey === "function") { - const derived = tools.getPublicKey(privateKey); + const derived = tools.getPublicKey(hexToBytes(privateKey)); const normalized = normalizeActorKey(derived); if (normalized && HEX64_REGEX.test(normalized)) { return normalized; diff --git a/js/nostr/client.js b/js/nostr/client.js index 67cf4861..d08f6b34 100644 --- a/js/nostr/client.js +++ b/js/nostr/client.js @@ -130,6 +130,7 @@ import { summarizeDmEventForLog, } from "./dmDecryptDiagnostics.js"; import { devLogger, userLogger } from "../utils/logger.js"; +import { hexToBytes } from "../utils/hex.js"; import { LRUCache } from "../utils/lruCache.js"; import { updateConversationFromMessage, writeMessages } from "../storage/dmDb.js"; import { @@ -1629,7 +1630,7 @@ export class NostrClient { if (!tools || typeof tools.getPublicKey !== "function") { throw new Error("Public key derivation is unavailable for remote signing."); } - publicKey = tools.getPublicKey(privateKey); + publicKey = tools.getPublicKey(hexToBytes(privateKey)); } if (!publicKey || !HEX64_REGEX.test(publicKey)) { @@ -2275,7 +2276,7 @@ export class NostrClient { if (!tools || typeof tools.getPublicKey !== "function") { throw new Error("Public key derivation is unavailable for the remote signer handshake."); } - clientPublicKey = normalizeNostrPubkey(tools.getPublicKey(clientPrivateKey)); + clientPublicKey = normalizeNostrPubkey(tools.getPublicKey(hexToBytes(clientPrivateKey))); } if (!clientPublicKey || !HEX64_REGEX.test(clientPublicKey)) { @@ -3440,7 +3441,7 @@ export class NostrClient { let pubkey = ""; try { - pubkey = getPublicKey(normalizedPrivateKey); + pubkey = getPublicKey(hexToBytes(normalizedPrivateKey)); } catch (error) { let retrySuccess = false; try { @@ -3551,7 +3552,7 @@ export class NostrClient { let pubkey = ""; try { - pubkey = getPublicKey(privateKey); + pubkey = getPublicKey(hexToBytes(privateKey)); } catch (error) { const failure = new Error("Failed to derive the public key."); failure.cause = error; @@ -3595,7 +3596,7 @@ export class NostrClient { throw new Error("Public key derivation is unavailable."); } try { - normalizedPubkey = getPublicKey(normalizedPrivateKey); + normalizedPubkey = getPublicKey(hexToBytes(normalizedPrivateKey)); } catch (error) { const failure = new Error("Failed to derive the public key."); failure.cause = error; @@ -3733,7 +3734,7 @@ export class NostrClient { throw new Error("Public key derivation is unavailable."); } try { - normalizedPubkey = getPublicKey(normalizedPrivateKey); + normalizedPubkey = getPublicKey(hexToBytes(normalizedPrivateKey)); } catch (error) { const failure = new Error("Failed to derive the public key."); failure.cause = error; diff --git a/js/nostr/nip46Client.js b/js/nostr/nip46Client.js index 9f31f957..ad262fd6 100644 --- a/js/nostr/nip46Client.js +++ b/js/nostr/nip46Client.js @@ -25,6 +25,7 @@ import { assertAnyRelayAccepted as defaultAssertAnyRelayAccepted, } from "../nostrPublish.js"; import { devLogger, userLogger } from "../utils/logger.js"; +import { hexToBytes } from "../utils/hex.js"; import { Nip46RequestQueue, NIP46_PRIORITY } from "./nip46Queue.js"; export const HEX64_REGEX = /^[0-9a-f]{64}$/i; @@ -1071,7 +1072,10 @@ function resolveAvailableNip46Ciphers( typeof nip44v2GetConversationKey === "function" ) { registerCipher("nip44.v2", () => { - const conversationKey = nip44v2GetConversationKey(privateKey, remotePubkey); + const conversationKey = nip44v2GetConversationKey( + hexToBytes(privateKey), + remotePubkey, + ); if (!conversationKey) { throw new Error("Failed to derive a nip44 conversation key for remote signing."); @@ -1095,7 +1099,10 @@ function resolveAvailableNip46Ciphers( typeof nip44GetConversationKey === "function" ) { registerCipher("nip44", () => { - const conversationKey = nip44GetConversationKey(privateKey, remotePubkey); + const conversationKey = nip44GetConversationKey( + hexToBytes(privateKey), + remotePubkey, + ); if (!conversationKey) { throw new Error("Failed to derive a nip44 conversation key for remote signing."); @@ -1494,7 +1501,7 @@ export class Nip46RpcClient { if (!tools || typeof tools.getPublicKey !== "function") { throw new Error("Public key derivation is unavailable."); } - this.clientPublicKey = tools.getPublicKey(this.clientPrivateKey); + this.clientPublicKey = tools.getPublicKey(hexToBytes(this.clientPrivateKey)); if (!this.clientPublicKey || !HEX64_REGEX.test(this.clientPublicKey)) { throw new Error("Failed to derive a valid public key for the remote signer session."); } diff --git a/js/nostr/publishHelpers.js b/js/nostr/publishHelpers.js index 429fcd10..ad0c149d 100644 --- a/js/nostr/publishHelpers.js +++ b/js/nostr/publishHelpers.js @@ -16,6 +16,7 @@ import { getCachedNostrTools, readToolkitFromScope, } from "./toolkit.js"; +import { hexToBytes } from "../utils/hex.js"; import { DEFAULT_NIP07_PERMISSION_METHODS } from "./nip07Permissions.js"; import { devLogger, userLogger } from "../utils/logger.js"; import { logRebroadcastCountFailure } from "./countDiagnostics.js"; @@ -134,7 +135,7 @@ export function signEventWithPrivateKey(event, privateKey) { throw new Error("Missing signing primitives"); } - const finalized = finalizeFn(prepared, normalizedKey); + const finalized = finalizeFn(prepared, hexToBytes(normalizedKey)); if ( !finalized || typeof finalized.id !== "string" || diff --git a/js/utils/hex.js b/js/utils/hex.js index 367f78bd..38839bb7 100644 --- a/js/utils/hex.js +++ b/js/utils/hex.js @@ -9,3 +9,21 @@ export function normalizeHexString(value) { export const normalizeHexId = normalizeHexString; export const normalizeHexPubkey = normalizeHexString; + +export function hexToBytes(hex) { + if (typeof hex !== "string") { + throw new TypeError("hexToBytes: expected string, got " + typeof hex); + } + const normalized = hex.trim(); + if (normalized.length % 2) throw new Error("hex string must have even length"); + const array = new Uint8Array(normalized.length / 2); + for (let i = 0; i < array.length; i++) { + const j = i * 2; + const byte = parseInt(normalized.substring(j, j + 2), 16); + if (Number.isNaN(byte) || byte < 0) { + throw new Error("Invalid hex string"); + } + array[i] = byte; + } + return array; +} diff --git a/package-lock.json b/package-lock.json index 0d6176dd..09d2204f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@tailwindcss/typography": "^0.5.19", "autoprefixer": "^10.4.24", "cross-env": "^10.1.0", - "esbuild": "^0.25.12", + "esbuild": "^0.27.2", "jsdom": "^27.4.0", "nostr-tools": "^2.19.4", "postcss": "^8.5.6", @@ -64,16 +64,16 @@ } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.7.6", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz", - "integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==", + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.7.tgz", + "integrity": "sha512-8CO/UQ4tzDd7ula+/CVimJIVWez99UJlbMyIgk8xOnhAVPKLnBZmUFYVgugS441v2ZqUq5EnSh6B0Ua0liSFAA==", "license": "MIT", "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.4" + "lru-cache": "^11.2.5" } }, "node_modules/@asamuzakjp/nwsapi": { @@ -83,9 +83,9 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { @@ -816,9 +816,9 @@ "license": "MIT" }, "node_modules/@noble/ciphers": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", - "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz", + "integrity": "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -828,22 +828,13 @@ } }, "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", + "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", "license": "MIT", "dependencies": { "@noble/hashes": "2.0.1" }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "license": "MIT", "engines": { "node": ">= 20.19.0" }, @@ -915,82 +906,36 @@ } }, "node_modules/@scure/base": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", - "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz", + "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@scure/bip32": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", - "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32/node_modules/@noble/curves": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", - "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-2.0.1.tgz", + "integrity": "sha512-4Md1NI5BzoVP+bhyJaY3K6yMesEFzNS1sE/cP+9nuvE7p/b0kx9XbpDHHFl8dHtufcbdHRUUQdRqLIPHN/s7yA==", "license": "MIT", "dependencies": { - "@noble/hashes": "1.3.1" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32/node_modules/@noble/curves/node_modules/@noble/hashes": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", - "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32/node_modules/@noble/hashes": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", - "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", - "license": "MIT", - "engines": { - "node": ">= 16" + "@noble/curves": "2.0.1", + "@noble/hashes": "2.0.1", + "@scure/base": "2.0.0" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip39": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", - "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-2.0.1.tgz", + "integrity": "sha512-PsxdFj/d2AcJcZDX1FXN3dDgitDDTmwf78rKZq1a6c1P1Nan1X/Sxc7667zU3U+AN60g7SxxP0YCVw2H/hBycg==", "license": "MIT", "dependencies": { - "@noble/hashes": "~1.3.0", - "@scure/base": "~1.1.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39/node_modules/@noble/hashes": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", - "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", - "license": "MIT", - "engines": { - "node": ">= 16" + "@noble/hashes": "2.0.1", + "@scure/base": "2.0.0" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -1294,19 +1239,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1440,17 +1372,13 @@ "license": "CC-BY-4.0" }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" @@ -1472,6 +1400,23 @@ "url": "https://github.com/chalk/chalk-template?sponsor=1" } }, + "node_modules/chalk-template/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -1612,27 +1557,6 @@ "dev": true, "license": "MIT" }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1891,9 +1815,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -1964,13 +1888,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/fake-indexeddb": { "version": "6.2.5", "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.5.tgz", @@ -2246,6 +2163,18 @@ "dev": true, "license": "MIT" }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -2568,18 +2497,6 @@ } } }, - "node_modules/jsdom/node_modules/html-encoding-sniffer": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", - "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.6.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -2841,9 +2758,9 @@ } }, "node_modules/nostr-tools": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.20.0.tgz", - "integrity": "sha512-Kq/2lMyeOdGvpDsYH2an8HP4H0aFCqwKythhTzxfgZTVv4L3NOgrJw2SxH8jkWlH8xPhWxGfN6lFtC+EAa2qYQ==", + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.22.1.tgz", + "integrity": "sha512-LJKy4lU6thO6Z6CVWkfqHGDt9m/M5IfRlmEI2hBXYLw4xa3jpfIHKJxXQhx/8C3TcN0YPkMRJlhGmu/g0VH80g==", "license": "Unlicense", "dependencies": { "@noble/ciphers": "2.1.1", @@ -2863,18 +2780,6 @@ } } }, - "node_modules/nostr-tools/node_modules/@noble/hashes": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", - "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/nostr-wasm": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz", @@ -3614,10 +3519,24 @@ } }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT" }, "node_modules/saxes": { @@ -3684,19 +3603,6 @@ "node": ">= 0.8" } }, - "node_modules/serve/node_modules/chalk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", - "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3719,17 +3625,11 @@ } }, "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "ISC" }, "node_modules/slash": { "version": "3.0.0", @@ -4500,6 +4400,19 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ws": { "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", diff --git a/package.json b/package.json index b385c9c3..3a1aded8 100644 --- a/package.json +++ b/package.json @@ -49,12 +49,11 @@ "@tailwindcss/typography": "^0.5.19", "autoprefixer": "^10.4.24", "cross-env": "^10.1.0", - "esbuild": "^0.25.12", + "esbuild": "^0.27.2", "jsdom": "^27.4.0", "nostr-tools": "^2.19.4", "postcss": "^8.5.6", "tailwindcss": "^3.4.19", - "ws": "^8.19.0", - "cross-env": "^10.1.0" + "ws": "^8.19.0" } } diff --git a/tests/nostr-login-permissions.test.mjs b/tests/nostr-login-permissions.test.mjs index 97831732..42218683 100644 --- a/tests/nostr-login-permissions.test.mjs +++ b/tests/nostr-login-permissions.test.mjs @@ -153,68 +153,11 @@ function setupLoginEnvironment({ enableImpl, getPublicKey = HEX_PUBKEY } = {}) { }; } -test("NIP-07 login requests decrypt permissions upfront", async () => { - const env = setupLoginEnvironment(); - try { - const result = await nip07Provider.login({ nostrClient }); - const pubkey = result.pubkey; - assert.equal(pubkey, HEX_PUBKEY); - assert.ok(env.enableCalls.length >= 1, "extension.enable should be invoked"); - - // Explicit checks for permissions we know should be there - assert.ok( - cache.has("nip04.encrypt"), - "nip04.encrypt permission should be tracked as granted", - ); - assert.ok( - cache.has("nip04.decrypt"), - "nip04.decrypt permission should be tracked as granted", - ); - assert.ok( - cache.has("nip44.encrypt"), - "nip44.encrypt permission should be tracked as granted", - ); - assert.ok( - cache.has("nip44.decrypt"), - "nip44.decrypt permission should be tracked as granted", - ); - - // Dynamic check for all expected methods - for (const method of EXPECTED_ENCRYPTION_PERMISSIONS) { - assert.ok( - cache.has(method), - `${method} permission should be tracked as granted`, - ); - } - - assert.ok( - cache.has("sign_event"), - "sign_event permission should be tracked as granted", - ); - } finally { - env.restore(); - nostrClient.logout(); - clearStoredPermissions(); - }); - +describe("NIP-07 Login Permissions", () => { afterEach(() => { clearStoredPermissions(); }); - const result = await nip07Provider.login({ nostrClient }); - const pubkey = result.pubkey; - assert.equal(pubkey, HEX_PUBKEY); - - // Refresh cache reference - const cache = nostrClient.extensionPermissionCache; - - for (const method of EXPECTED_ENCRYPTION_PERMISSIONS) { - assert.ok( - cache.has(method), - `nostrClient should track ${method} after login`, - ); - } - it("NIP-07 login requests decrypt permissions upfront", async () => { const env = setupLoginEnvironment(); try { diff --git a/tests/nostr-private-key-signer.test.mjs b/tests/nostr-private-key-signer.test.mjs index 610c66a9..5645ecb7 100644 --- a/tests/nostr-private-key-signer.test.mjs +++ b/tests/nostr-private-key-signer.test.mjs @@ -45,14 +45,14 @@ describe("Nostr Private Key Signer", () => { const outboundCipher = await signer.nip04Encrypt(recipientPubkey, message); assert.equal(typeof outboundCipher, "string"); const decryptedByRecipient = await nostrTools.nip04.decrypt( - recipientPrivateHex, + recipientKeyBytes, signer.pubkey, outboundCipher, ); assert.equal(decryptedByRecipient, message); const inboundCipher = await nostrTools.nip04.encrypt( - recipientPrivateHex, + recipientKeyBytes, signer.pubkey, message, ); diff --git a/tests/nostr-send-direct-message.test.mjs b/tests/nostr-send-direct-message.test.mjs index 2b23e1a7..4ca31a46 100644 --- a/tests/nostr-send-direct-message.test.mjs +++ b/tests/nostr-send-direct-message.test.mjs @@ -92,10 +92,9 @@ test("sendDirectMessage succeeds with private key signer and no extension", asyn nostrClient.readRelays = [RELAY_URL]; nostrClient.writeRelays = [RELAY_URL]; - const senderPrivateKey = canonicalTools.utils.bytesToHex( - canonicalTools.generateSecretKey(), - ); - const senderPubkey = canonicalTools.getPublicKey(senderPrivateKey); + const senderSecret = canonicalTools.generateSecretKey(); + const senderPrivateKey = canonicalTools.utils.bytesToHex(senderSecret); + const senderPubkey = canonicalTools.getPublicKey(senderSecret); const recipientSecret = canonicalTools.generateSecretKey(); const recipientPrivateKey = canonicalTools.utils.bytesToHex(recipientSecret); const recipientPubkey = canonicalTools.getPublicKey(recipientSecret); @@ -112,7 +111,7 @@ test("sendDirectMessage succeeds with private key signer and no extension", asyn const originalNip44 = signer?.nip44Encrypt; if (signer) { signer.signEvent = (event) => { - const finalized = canonicalTools.finalizeEvent(event, senderPrivateKey); + const finalized = canonicalTools.finalizeEvent(event, senderSecret); return { ...event, id: finalized.id, sig: finalized.sig }; }; delete signer.nip04Encrypt; @@ -133,7 +132,7 @@ test("sendDirectMessage succeeds with private key signer and no extension", asyn assert.notEqual(event.content, "", "ciphertext should not be empty"); const decrypted = await canonicalTools.nip04.decrypt( - recipientPrivateKey, + recipientSecret, senderPubkey, event.content, ); diff --git a/tests/nostr/dm-direct-message-flow.test.mjs b/tests/nostr/dm-direct-message-flow.test.mjs index 25759eeb..ccf8fdfe 100644 --- a/tests/nostr/dm-direct-message-flow.test.mjs +++ b/tests/nostr/dm-direct-message-flow.test.mjs @@ -151,24 +151,24 @@ async function setupDmScenario() { const senderPrivateKey = nostrTools.utils.bytesToHex(senderSecret); const receiverPrivateKey = nostrTools.utils.bytesToHex(receiverSecret); - const senderPubkey = nostrTools.getPublicKey(senderPrivateKey); - const receiverPubkey = nostrTools.getPublicKey(receiverPrivateKey); + const senderPubkey = nostrTools.getPublicKey(senderSecret); + const receiverPubkey = nostrTools.getPublicKey(receiverSecret); - const createSigner = (privateKey, pubkey) => ({ + const createSigner = (privateKeyBytes, pubkey) => ({ type: "test", pubkey, signEvent: (event) => { - const finalized = nostrTools.finalizeEvent(event, privateKey); + const finalized = nostrTools.finalizeEvent(event, privateKeyBytes); return { ...event, id: finalized.id, sig: finalized.sig }; }, nip04Encrypt: (target, plaintext) => - nostrTools.nip04.encrypt(privateKey, target, plaintext), + nostrTools.nip04.encrypt(privateKeyBytes, target, plaintext), nip04Decrypt: (target, ciphertext) => - nostrTools.nip04.decrypt(privateKey, target, ciphertext), + nostrTools.nip04.decrypt(privateKeyBytes, target, ciphertext), }); - const senderSigner = createSigner(senderPrivateKey, senderPubkey); - const receiverSigner = createSigner(receiverPrivateKey, receiverPubkey); + const senderSigner = createSigner(senderSecret, senderPubkey); + const receiverSigner = createSigner(receiverSecret, receiverPubkey); const relayPool = new FakeRelayPool(); diff --git a/tests/nostr/nip46Client.test.js b/tests/nostr/nip46Client.test.js index bb47a053..450f4e2e 100644 --- a/tests/nostr/nip46Client.test.js +++ b/tests/nostr/nip46Client.test.js @@ -25,8 +25,8 @@ test("Nip46RpcClient encrypts payloads with nip44 conversation keys", async () = const clientSecret = utils.bytesToHex(generateSecretKey()); const remoteSecret = utils.bytesToHex(generateSecretKey()); - const clientPublicKey = getPublicKey(clientSecret); - const remotePubkey = getPublicKey(remoteSecret); + const clientPublicKey = getPublicKey(utils.hexToBytes(clientSecret)); + const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret)); const rpcClient = new Nip46RpcClient({ nostrClient: { relays: [] }, @@ -43,8 +43,8 @@ test("Nip46RpcClient encrypts payloads with nip44 conversation keys", async () = const conversationKey = typeof nip44.v2.getConversationKey === "function" - ? nip44.v2.getConversationKey(clientSecret, remotePubkey) - : nip44.v2.utils.getConversationKey(clientSecret, remotePubkey); + ? nip44.v2.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey) + : nip44.v2.utils.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey); const decryptedWithTools = nip44.v2.decrypt(ciphertext, conversationKey); assert.equal( decryptedWithTools, @@ -71,13 +71,13 @@ test("decryptNip46PayloadWithKeys handles nip44.v2 ciphertext", async () => { const clientSecret = utils.bytesToHex(generateSecretKey()); const remoteSecret = utils.bytesToHex(generateSecretKey()); - const remotePubkey = getPublicKey(remoteSecret); + const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret)); const serialized = JSON.stringify({ message: "hello", count: 42 }); const conversationKey = typeof nip44.v2.getConversationKey === "function" - ? nip44.v2.getConversationKey(clientSecret, remotePubkey) - : nip44.v2.utils.getConversationKey(clientSecret, remotePubkey); + ? nip44.v2.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey) + : nip44.v2.utils.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey); const ciphertext = nip44.v2.encrypt(serialized, conversationKey); const { plaintext, algorithm } = await decryptNip46PayloadWithKeys( @@ -104,13 +104,13 @@ test("decryptNip46PayloadWithKeys coerces structured handshake payloads", async const clientSecret = utils.bytesToHex(generateSecretKey()); const remoteSecret = utils.bytesToHex(generateSecretKey()); - const remotePubkey = getPublicKey(remoteSecret); + const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret)); const serialized = JSON.stringify({ message: "hello", count: 42 }); const conversationKey = typeof nip44.v2.getConversationKey === "function" - ? nip44.v2.getConversationKey(clientSecret, remotePubkey) - : nip44.v2.utils.getConversationKey(clientSecret, remotePubkey); + ? nip44.v2.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey) + : nip44.v2.utils.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey); const ciphertext = nip44.v2.encrypt(serialized, conversationKey); const [ciphertextPart, nonce] = ciphertext.split("\n"); @@ -154,13 +154,13 @@ test("decryptNip46PayloadWithKeys decodes buffer-based handshake payloads", asyn const clientSecret = utils.bytesToHex(generateSecretKey()); const remoteSecret = utils.bytesToHex(generateSecretKey()); - const remotePubkey = getPublicKey(remoteSecret); + const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret)); const serialized = JSON.stringify({ message: "hello", count: 42 }); const conversationKey = typeof nip44.v2.getConversationKey === "function" - ? nip44.v2.getConversationKey(clientSecret, remotePubkey) - : nip44.v2.utils.getConversationKey(clientSecret, remotePubkey); + ? nip44.v2.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey) + : nip44.v2.utils.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey); const ciphertext = nip44.v2.encrypt(serialized, conversationKey); const [ciphertextPart, nonce] = ciphertext.split("\n"); const ciphertextBuffer = Buffer.from(ciphertext, "utf8"); @@ -197,10 +197,10 @@ test("decryptNip46PayloadWithKeys supports nip04-style ciphertext wrappers", asy const clientSecret = utils.bytesToHex(generateSecretKey()); const remoteSecret = utils.bytesToHex(generateSecretKey()); - const remotePubkey = getPublicKey(remoteSecret); + const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret)); const serialized = JSON.stringify({ message: "hello", count: 42 }); - const ciphertext = await nip04.encrypt(clientSecret, remotePubkey, serialized); + const ciphertext = await nip04.encrypt(utils.hexToBytes(clientSecret), remotePubkey, serialized); const [ciphertextPart, iv] = ciphertext.split("?iv="); const structuredPayload = { @@ -254,8 +254,8 @@ test("parseNip46ConnectionString handles remote signer key hints", async () => { const signerSecret = utils.bytesToHex(generateSecretKey()); const userSecret = utils.bytesToHex(generateSecretKey()); - const signerPubkey = getPublicKey(signerSecret).toLowerCase(); - const userPubkey = getPublicKey(userSecret).toLowerCase(); + const signerPubkey = getPublicKey(utils.hexToBytes(signerSecret)).toLowerCase(); + const userPubkey = getPublicKey(utils.hexToBytes(userSecret)).toLowerCase(); const signerNpub = nip19.npubEncode(signerPubkey); const userNpub = nip19.npubEncode(userPubkey); @@ -295,13 +295,13 @@ test( const remoteSignerSecret = utils.bytesToHex(generateSecretKey()); const userSecret = utils.bytesToHex(generateSecretKey()); - const remoteSignerPubkey = getPublicKey(remoteSignerSecret).toLowerCase(); - const userPubkey = getPublicKey(userSecret).toLowerCase(); + const remoteSignerPubkey = getPublicKey(utils.hexToBytes(remoteSignerSecret)).toLowerCase(); + const userPubkey = getPublicKey(utils.hexToBytes(userSecret)).toLowerCase(); const conversationKey = typeof nip44.v2.getConversationKey === "function" - ? nip44.v2.getConversationKey(clientSecret, remoteSignerPubkey) - : nip44.v2.utils.getConversationKey(clientSecret, remoteSignerPubkey); + ? nip44.v2.getConversationKey(utils.hexToBytes(clientSecret), remoteSignerPubkey) + : nip44.v2.utils.getConversationKey(utils.hexToBytes(clientSecret), remoteSignerPubkey); const payload = JSON.stringify({ id: "ack", result: "ok" }); const ciphertext = nip44.v2.encrypt(payload, conversationKey); @@ -333,12 +333,12 @@ test( const { attemptDecryptNip46HandshakePayload } = await loadNip46Module(); const clientSecret = utils.bytesToHex(generateSecretKey()); - const clientPubkey = getPublicKey(clientSecret).toLowerCase(); + const clientPubkey = getPublicKey(utils.hexToBytes(clientSecret)).toLowerCase(); const remoteSecret = utils.bytesToHex(generateSecretKey()); - const remotePubkey = getPublicKey(remoteSecret).toLowerCase(); + const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret)).toLowerCase(); const payload = JSON.stringify({ id: "ack", result: "ack" }); - const ciphertext = nip04.encrypt(remoteSecret, clientPubkey, payload); + const ciphertext = nip04.encrypt(utils.hexToBytes(remoteSecret), clientPubkey, payload); const [ciphertextPart, ivPart] = ciphertext.split("?iv="); assert.ok(ivPart, "nip04 encryption should emit an iv segment");