test(messaging): #182 deterministic cross-member group encryption E2E#186
Merged
Conversation
The group-message E2E (group-chat-multiuser.spec.ts) drove group CREATION through the UI, which runs createGroup() in-browser (Argon2 + in-browser ECDH key distribution, routinely >90s). It test.skip'd on slow runners and gave no reliable send/decrypt proof — on the #182 merge run it SKIPPED, so the actual proof came from an earlier CI log rather than a green assertion. Add a NON-skipping test that proves an encrypted group message round-trips between two DIFFERENT members (A sends, B decrypts) by distributing the group key SERVER-SIDE in the fixture, off the browser critical path: - test-user-factory.ts: `seedIsolatedGroup(n, { withKeys: true })` now also distributes the group key. `distributeGroupKeysForFixture` re-derives the creator's keypair in Node from their stored base64 salt (deterministic Argon2id), generates one AES-GCM group key, and wraps it per member via the REAL production `GroupKeyService.encryptGroupKeyForMember` (reused, not duplicated — zero wire-format drift). All rows share created_by = creator so each member unwraps with ECDH(memberPriv, creatorPub) at runtime. - group-chat-multiuser.spec.ts: new deterministic test in its own describe; the 6 existing tests (incl. the UI create-group flow) are untouched. - group-key-roundtrip.node.test.ts: real-crypto Node unit guard proving the exact wrap→unwrap→message-decrypt contract (catches a base64-salt decode drift in ~1s, before the browser). Each browser still pays one Argon2 key-unlock (same as every 1:1 iso test); the eliminated cost is the compounding in-browser group creation. Runs in chromium-msg-iso (already in MSG_ISO_GLOBS — no config change). Verified locally against served build + cloud Supabase: 2 passed, 0 skipped (~29s); unit guard 1 passed. Follows the #115 backlog "deterministic group send/decrypt E2E" nice-to-have. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a non-skipping E2E that proves an encrypted group message round-trips between two different members (A sends, B decrypts), by distributing the group key server-side in the fixture — off the slow browser critical path.
Backlog follow-up to #182 (the "deterministic group send/decrypt E2E" nice-to-have on #115).
Why
The existing group test (
group-chat-multiuser.spec.ts, "sends and reads an encrypted message in a UI-created group") drives group creation through the UI, which runscreateGroup()in-browser: generate a group AES key + ECDH-distribute it to every member (Argon2 + async crypto, routinely >90s on CI). So ittest.skips on slow runners and gives no reliable send/decrypt proof — on the #182 merge run it SKIPPED, and the actual proof came from an earlier CI log rather than a green assertion. Weak guard for a feature that had 3 live RLS bugs.How
tests/e2e/utils/test-user-factory.ts—seedIsolatedGroup(n, { withKeys: true })now also distributes the group key.distributeGroupKeysForFixturere-derives the creator's keypair in Node from their stored base64 salt (deterministic Argon2id), generates one AES-GCM group key, and wraps it per member via the real productionGroupKeyService.encryptGroupKeyForMember— reused, not duplicated, so the seededgroup_keysrows are byte-identical in shape to what the app writes (zero wire-format drift). All rows sharecreated_by = creator, so each member unwraps withECDH(memberPriv, creatorPub)at runtime — exactly thegetGroupKeyForConversationpath.tests/e2e/messaging/group-chat-multiuser.spec.ts— new deterministic test in its owndescribe. All 6 existing tests are untouched (the UI create-group flow still guards/messages/new-group).src/services/messaging/__tests__/group-key-roundtrip.node.test.ts— a real-crypto Node unit guard proving the exact wrap→unwrap→message-decrypt contract with real ECDH/AES-GCM. Catches a base64-salt decode drift in ~1s, before the browser.Each browser still pays one Argon2 key-unlock (via the ReAuth modal, same as every 1:1 iso test); the eliminated cost is the compounding in-browser group creation. Runs in
chromium-msg-iso(already inMSG_ISO_GLOBS— no config change).Verification (local, served build + cloud Supabase)
2 passed, 0 skipped(~29s). Logs show the fixture generating + ECDH-wrapping the group key per member, both members opening + unlocking, A sending, and B decrypting + rendering A's message.1 passed(real crypto).Not a design decision — CI's
chromium-msg-isowill run the same test.🤖 Generated with Claude Code