perf: run per-message verify and decrypt off the main isolate - #705
Conversation
Every NIP-44 encrypt/decrypt recomputed the conversation key - one EC scalar multiplication (5-30 ms of pure-Dart BigInt on a mid phone) plus HKDF - although it is constant per (our key, their key) pair: the node conversation of a session and each chat conversation reuse the same pair for every message, including the double decrypt per stored chat envelope during history loads. NostrUtils.conversationKeyFor caches the derived key in a bounded map and both encryptNIP44 and decryptNIP44 (the single choke point for all NIP-44 traffic: node kind-14 messages, chat and dispute chat) inject it through the nip44 fork's customConversationKey, which skips ECDH and HKDF.
Every incoming node message (kind 14) paid a Schnorr verification plus a NIP-44 decrypt on the UI isolate, and every chat envelope paid two verifications plus a decrypt - 15-90 ms of pure-Dart BigInt math per message, multiplied by history loads. - decryptNIP44DirectEvent keeps its cheap syntactic checks on the caller, resolves the cached conversation key there (so the worker skips ECDH + HKDF), and runs signature verification + decryption through Isolate.run. - chatUnwrap keeps its cheapest-first spec checks (author, kind, p tag, timestamp, size) on the caller and moves steps 6-11 (outer verify, decrypt, inner parse/verify/allowlist/kind/skew) into an isolate via the same pattern. The check order and every error message are unchanged. - The dispute reload test waits on the observable condition instead of a microtask pump count, since unwrapping is now cross-isolate async. The send path (wrapNip44) stays on the caller for now - one tap-driven sign per user action - noted as a follow-up. Depends on the conversation-key cache PR; this branch is stacked on it.
|
Warning Review limit reachedNext included review available in 47 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
The NIP-44 conversation-key cache this branch carried landed on main as #701 in a reviewed form (defensive copies, `cache:` opt-out, per-session eviction and full-reset clearing). Take main's version of that cache and of its test file wholesale, and keep only this branch's isolate work on top of it.
Summary
Item 3.4 of the performance plan (the last of Phase 3). Every incoming node message (kind 14) paid a Schnorr verification + NIP-44 decrypt on the UI isolate, and every chat envelope paid two verifications + a decrypt — 15–90 ms of pure-Dart BigInt per message, multiplied by history loads.
Changes
decryptNIP44DirectEvent: cheap syntactic checks (kind, content, canonical key, expected author) stay on the caller; signature verification + decryption run throughIsolate.runwith the pre-resolved conversation key. Error types/messages unchanged (pinned).chatUnwrap: the cheapest-first spec checks (steps 1–5: outer author, kind, p-tag, absolute timestamp, size bound) stay on the caller; steps 6–11 (outer id+sig verify, decrypt, inner parse, inner id+sig verify, signer allowlist, inner kind, relative skew) moved verbatim into_chatUnwrapHeavy, executed viaIsolate.run. Check order and every error message are identical — the chat security suite (forged signatures, allowlist, background-handoff pins) runs the isolate path with real crypto.dispute_chat_reload_testnow waits on the observable condition (bounded 5 s poll) instead of a microtask pump count — unwrapping is genuinely cross-isolate async now.Deliberately out of scope: the send path (
wrapNip44, one tap-driven sign per user action) — cheap after #701's cache; noted as follow-up.Test plan
crypto_isolate_characterization_test.dart— green before and after the surgery (venue change only): decrypt roundtrip, wrong author, forged signature, malformed key, sequential messages — all error propagation across the isolate boundaryflutter test— all greenflutter analyze— no new issues🤖 Generated with Claude Code
https://claude.ai/code/session_018fTxqxhpdL5siTgKZqwtur