aboutsummaryrefslogtreecommitdiffstats
path: root/src/nostr.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-04 08:23:50 -0700
committerAdam Soltys <[email protected]>2026-09-04 08:24:04 -0700
commit7bdc0fb00f8f77075a449732971f4689d892e921 (patch)
tree39441dd778c5fc56dd1874559a529c0bb48eb16d /src/nostr.js
parent2d17fd652c3c2c56d6027c9271bf9b9854412677 (diff)
downloadcoinosv3-7bdc0fb00f8f77075a449732971f4689d892e921.tar.gz
coinosv3-7bdc0fb00f8f77075a449732971f4689d892e921.zip
Move nip44 decryption off the main thread — the remaining boot secp256k1 after verification moved off (chunk-60dfb4nt in profiles). The verify worker grows two ops: 'openWraps' opens community (Concord) wraps given the symmetric stream conversation key — nip44 decrypt + the seal's schnorr verify + hash checks per wrap, no secret crossing the thread boundary since stream keys are group material; 'unwrapDMs' runs the full NIP-17 unwrap (two ECDH ops + seal verify) when the wallet holds the raw identity key. Remote signers can't export their key, so their DM path stays on the main thread, exactly as before. The time-sliced main-thread queues survive as fallbacks — a dead or stalled worker only changes WHERE the crypto runs (workerCrypto resolves null and callers re-enqueue), never whether chat catches up. Bonus: subChannel derives its stream key once per channel instead of once per wrap (groupKey does hkdf + ECDH per call — real curve work on a 200-wrap backfill)
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
Diffstat (limited to 'src/nostr.js')
-rw-r--r--src/nostr.js28
1 files changed, 27 insertions, 1 deletions
diff --git a/src/nostr.js b/src/nostr.js
index 8b6bc7e..9fa892c 100644
--- a/src/nostr.js
+++ b/src/nostr.js
@@ -16,7 +16,7 @@ import { getPublicKey, finalizeEvent, generateSecretKey, verifyEvent } from 'nos
import { decode as nip19decode, npubEncode, nsecEncode } from 'nostr-tools/nip19';
import { wrapEvent as nip17WrapEvent } from 'nostr-tools/nip17';
import { SimplePool } from 'nostr-tools/pool';
-import { randomBytes } from '@noble/hashes/utils';
+import { randomBytes, bytesToHex } from '@noble/hashes/utils';
import { base64urlnopad } from '@scure/base';
// One shared pool for all relay I/O — it manages connection lifecycles (and the
@@ -71,6 +71,32 @@ function verifyEventsAsync(events) {
// A single event → Promise<boolean>. Used to gate live subscription events.
function verifyOneAsync(event) { return verifyEventsAsync([event]).then((r) => !!r[0]); }
+// Heavier nip44 jobs ride the same worker: opening community wraps (decrypt
+// under the symmetric stream key + seal verify per wrap) and unwrapping
+// NIP-17 DMs when the raw identity key is in hand (two ECDH ops + a seal
+// verify each) — the remaining boot secp256k1 after verification moved off.
+// Resolves to the worker's results array, or null when the worker can't take
+// the job (never created, died, stalled) — callers keep their time-sliced
+// main-thread fallback, so a dead worker only changes WHERE the crypto runs.
+function workerCrypto(msg, timeoutMs = 10_000) {
+ const w = verifyWorker();
+ if (!w) return null;
+ return new Promise((resolve) => {
+ const reqId = ++_vwSeq;
+ const t = setTimeout(() => { _vwPending.delete(reqId); resolve(null); }, timeoutMs);
+ _vwPending.set(reqId, (r) => { clearTimeout(t); resolve(Array.isArray(r) ? r : null); });
+ try { w.postMessage({ reqId, ...msg }); }
+ catch { clearTimeout(t); _vwPending.delete(reqId); resolve(null); }
+ });
+}
+// convKey / sk are Uint8Array; hex crosses the thread boundary.
+export function openWrapsOffthread(wraps, convKey) {
+ return workerCrypto({ op: 'openWraps', wraps, convKey: bytesToHex(convKey) });
+}
+export function unwrapDMsOffthread(wraps, sk) {
+ return workerCrypto({ op: 'unwrapDMs', wraps, sk: bytesToHex(sk) });
+}
+
// Subscribe / publish on an explicit relay set, independent of the sync
// feature's configuration. NWC needs this: the relays it advertises in a
// connection URI must be exactly the ones it listens on, or a client's