Switching from a NIP-46 account to another account (e.g. an nsec account) failed to
switch BACK with "No remote signer session is stored on this device." Root cause:
disconnectRemoteSigner() defaults to keepStored:false and calls the no-argument
clearStoredNip46Session(), which removed the entire per-account v2 map
(bitvid:nip46:sessions:v2) — deleting EVERY saved account's session, not just a
teardown of the live connection. Four nip46Connector connect/handshake-error paths
call the same no-arg clear, so any connect hiccup also nuked all sessions.
Fix: the no-argument clearStoredNip46Session() now clears ONLY the legacy v1
"last-connected" default slot and leaves the per-account v2 map intact. A specific
account is still forgotten via clearStoredNip46Session(pubkey) (unchanged), so
removing one account is unaffected while switching between saved NIP-46 accounts
survives connection teardown.
Spec correction: the prior test asserted the buggy "no-arg clear wipes everything";
replaced with the corrected behavior + a switch-away/switch-back regression.
Test: tests/nip46-multi-account.test.mjs (7). Lint + build green.
NOTE: this prevents future wipes; a session already deleted by the bug must be
reconnected once (Login → remote signer) — after which switching persists.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Mirror the per-account nsec key store for remote-signer sessions: move from a
single slot (bitvid:nip46:session:v1) to a per-user-pubkey map
(bitvid:nip46:sessions:v2) so several saved NIP-46 accounts each keep their own
reconnectable session and the profile switcher can move between them.
Previously only the last-connected session survived, so switching among saved
NIP-46 accounts reconnected the wrong one (or failed).
- nip46Client.js: new per-pubkey session map keyed by userPubkey.
readStoredNip46Session takes an optional target pubkey; writeStoredNip46-
SessionSync writes both the map and the v1 last-connected default;
clearStoredNip46Session(pubkey) forgets one account (no-arg wipes all). Legacy
v1 slot migrated into the map on first read. Adds listStoredNip46SessionPubkeys().
- SignerManager: useStoredRemoteSigner({ pubkey }) selects the target account's
session and, on access-denial, forgets only that account. getStoredNip46-
Metadata(pubkey) is per-account.
- nip46 provider threads expectPubkey into the reuse call so a switch reconnects
the requested account, with the expectPubkey guard as a backstop.
nip46Connector.js is dead code; the live path is entirely in SignerManager.
Tests: tests/nip46-multi-account.test.mjs (7).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
- Refactored duplicate `5000` ms timeouts in `js/nostr/nip07Permissions.js`, `js/nostr/managers/SignerManager.js`, and `js/nostr/nip46Client.js`.
- Introduced `NIP07_EXTENSION_WAIT_TIMEOUT_MS` and `NIP46_PING_TIMEOUT_MS`.
- Created execution artifacts and scheduler completion log.
Co-authored-by: PR0M3TH3AN <[email protected]>
Moved logging helpers (`summarizeHexForLog`, `summarizeSecretForLog`, etc.) from `js/nostr/nip46Client.js` to `js/nostr/nip46LoggingUtils.js` to reduce clutter and improve reuse. Updated `js/nostr/client.js` and `js/services/authProviders/nip46.js` to import from the new module, removing duplicate definitions in the latter. Verified with existing tests.
Co-authored-by: PR0M3TH3AN <[email protected]>
Removes the custom `bech32Polymod` and related Bech32 decoding functions from `js/nostr/nip46Client.js` in favor of using `nostr-tools` library implementation via `nip19.decode`. This reduces code duplication and relies on the standard library for decoding npubs.
Breaking Changes:
- None. `decodeNpubToHex` now strictly requires `nostr-tools` to be available, which is consistent with the rest of the file's dependencies.
Tests:
- Verified with targeted unit tests (`tests/nostr/nip46Client.test.js`).
- Verified with a reproduction script (`tests/repro_issue.mjs`) ensuring `decodeNpubToHex` correctly decodes valid npubs and handles invalid inputs.
Co-authored-by: PR0M3TH3AN <[email protected]>
- Implement AES-GCM encryption for NIP-46 `clientPrivateKey` and `secret` using `js/nostr/sessionActor.js` primitives.
- Update `writeStoredNip46Session` to accept an optional `passphrase` and encrypt sensitive fields before storage.
- Update `readStoredNip46Session` to support encrypted session retrieval.
- Add `decryptNip46Session` helper to unlock session keys with a user-provided passphrase.
- Update `NostrClient` methods `connectRemoteSigner` and `useStoredRemoteSigner` to support the new encryption flow.
- Ensure plain text keys are never persisted to `localStorage` (default safe behavior maintained).
Signed-off-by: Jules <[email protected]>
Co-authored-by: PR0M3TH3AN <[email protected]>
Moves the repeatedly defined HEX64_REGEX constant to a shared utility
module to improve maintainability and consistency. Updates all consumers
to import from the new location.
Signed-off-by: Jules <[email protected]>
Co-authored-by: PR0M3TH3AN <[email protected]>
Removed `export` from `NIP46_SESSION_STORAGE_KEY`, `NIP46_PUBLISH_TIMEOUT_MS`, `NIP46_RESPONSE_TIMEOUT_MS`, `NIP46_SIGN_EVENT_TIMEOUT_MS`, and `NIP46_MAX_RETRIES` in `js/nostr/nip46Client.js` as they are only used internally.
Updated `js/nostr/client.js` to remove unused imports of these constants.
Verified that `NIP46_HANDSHAKE_TIMEOUT_MS` and `NIP46_AUTH_CHALLENGE_MAX_ATTEMPTS` are used in `js/nostr/client.js` and thus remain exported.
Co-authored-by: PR0M3TH3AN <[email protected]>
Limits the O(N^2) pairwise check in `normalizeNip46CiphertextPayload` to small arrays (<= 16 items) to prevent combinatorial explosion. This heuristic is only intended for small split-payload cases, and processing large arrays this way was unnecessary and caused performance issues.
Benchmark results (200 items, 100 iterations):
- Before: ~54.84ms per iteration
- After: ~0.12ms per iteration
- Improvement: ~457x speedup
Co-authored-by: PR0M3TH3AN <[email protected]>
- 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 <[email protected]>
- Implements a priority queue for NIP-07 (browser extension) requests to allow critical tasks (blocklist/prefs decryption) to jump the line.
- Updates NIP-46 client and adapters to support priority overrides for encryption/decryption.
- Updates UserBlockListManager, SubscriptionsManager, and HashtagPreferencesService to request HIGH priority when decrypting data.
- Ensures strict serialization for NIP-07 to prevent extension errors while optimizing latency for critical data.
Co-authored-by: PR0M3TH3AN <[email protected]>
- Added `js/nostr/nip46Queue.js` to manage concurrent RPC requests.
- Updated `Nip46RpcClient` to serialize outgoing requests, preventing "rate-limited" errors.
- Implemented exponential backoff for retries when relays reject events due to rate limits.
- Added priority levels to `sendRpc` to prioritize user actions (signing, connecting) over background tasks (decrypting).
- Verified with new unit tests in `tests/unit/nip46Queue.test.mjs`.
- Updated `Nip46RpcClient` in `js/nostr/nip46Client.js` to implement `nip04Encrypt`, `nip04Decrypt`, `nip44Encrypt`, and `nip44Decrypt` methods by calling the respective RPC methods on the remote signer.
- Exposed these encryption methods in the `activeSignerCache` object returned by `getActiveSigner()`.
- This ensures that functions relying on encryption (like `StorageService` unlocking and DM decryption) work correctly when logged in via NIP-46.
- Simplified `components/login-modal.html` to center the QR code and use a "Copy Link" button instead of a large textarea.
- Updated `js/ui/loginModalController.js` to support the new DOM structure and error handling.
- Added `docs/nip46-roadmap.md` to document current state and future features.
- Fixed `NostrClient.destroy()` usage (synchronous void method) in `js/nostr/client.js`.
- Relaxed NIP-46 `connect` response validation in `js/nostr/client.js` and `js/nostr/nip46Client.js` to accept "ack" (case-insensitive) as a valid success signal, preventing timeouts with signers that do not echo the secret.