Skip to content

perf: run per-message verify and decrypt off the main isolate - #705

Merged
grunch merged 3 commits into
mainfrom
perf/crypto-worker-isolate
Aug 31, 2026
Merged

perf: run per-message verify and decrypt off the main isolate#705
grunch merged 3 commits into
mainfrom
perf/crypto-worker-isolate

Conversation

@grunch

@grunch grunch commented Aug 31, 2026

Copy link
Copy Markdown
Member

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.

Stacked PR — depends on #701 (perf/nip44-conversation-key-cache): this branch resolves the cached conversation key on the caller isolate and hands it to the worker, so the isolate never recomputes ECDH/HKDF. Retarget to main after #701 merges.

Changes

  • decryptNIP44DirectEvent: cheap syntactic checks (kind, content, canonical key, expected author) stay on the caller; signature verification + decryption run through Isolate.run with 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 via Isolate.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_test now 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

  • New 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 boundary
  • Chat security + redundant-decrypt suites (real crypto through the isolate path) — green
  • Full flutter test — all green
  • flutter analyze — no new issues
  • Manual: burst of incoming messages while scrolling — no dropped frames attributable to decrypt; chat history load keeps UI responsive

🤖 Generated with Claude Code

https://claude.ai/code/session_018fTxqxhpdL5siTgKZqwtur

grunch added 2 commits August 30, 2026 21:05
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.
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 47 minutes.

View limit details

Limit 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.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: f1c18aa5-7144-4596-b6f4-8f6973417c3d

📥 Commits

Reviewing files that changed from the base of the PR and between 65a5801 and 9013d49.

📒 Files selected for processing (4)
  • lib/data/models/nostr_event.dart
  • lib/shared/utils/nostr_utils.dart
  • test/features/disputes/dispute_chat_reload_test.dart
  • test/shared/utils/crypto_isolate_characterization_test.dart

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T01:14:11.369639Z acf658f PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Base automatically changed from perf/nip44-conversation-key-cache to main August 31, 2026 20:30
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant