diff options
| author | Adam Soltys <[email protected]> | 2026-09-04 08:23:50 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-04 08:24:04 -0700 |
| commit | 7bdc0fb00f8f77075a449732971f4689d892e921 (patch) | |
| tree | 39441dd778c5fc56dd1874559a529c0bb48eb16d /src/nostr.js | |
| parent | 2d17fd652c3c2c56d6027c9271bf9b9854412677 (diff) | |
| download | coinosv3-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.js | 28 |
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 |
