feat: e2e encryption for direct messages#134
Closed
encryptedDegen wants to merge 1 commit into
Closed
Conversation
Encrypts DM bodies client-side with X25519 + XChaCha20-Poly1305. Each
user derives a deterministic messaging keypair from a wallet signature
(seed never leaves the browser) and publishes the public key with a
binding signature so peers can verify it against the wallet address
before encrypting. Old plaintext messages remain readable; new messages
ride in the existing `body` field as `enc:v1:<base64>`.
Backend must add `public_encryption_key` + `public_encryption_key_signature`
columns and the corresponding GET/PUT endpoints — see JSDoc in
src/api/user/{publishEncryptionKey,getEncryptionKey}.ts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Collaborator
Author
|
PR#135 gets that done |
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.
Summary
bodyfield asenc:v1:<base64>and are detected by prefix.What's in the PR
src/lib/crypto/— X25519 derivation, XChaCha20-Poly1305 cipher, IndexedDB keystore,enc:v1wire format, peer-verify helpers.src/hooks/chat/useMessagingKeys.ts+messagingKeysSingleton.ts+MessagingKeysMount— owns keypair lifecycle, auto-derives on auth, publishes pubkey + binding sig once per account.useSendMessage): refuses to fall back to plaintext if peer key is missing — explicit error instead.useChatSocket,useChatMessages,useChatsInbox): decrypts incoming WS messages, history pages, and inbox previews.messageRowandchatRowrenderdecrypted_body ?? bodywith[Unable to decrypt]fallback.threadViewshows banners + disables the composer when own keys aren't ready or the peer hasn't enrolled.publicEncryptionKey+publicEncryptionKeySignaturefrom/auth/me.@noble/hashesto v2 forced one import path fix inimageUploadModal.tsx(sha256moved fromsha256.jstosha2.js).Crypto choices
@noble/curves,@noble/ciphers,@noble/hashes(audited, dependency-free, ~30kb).Backend changes required (api.grails.app)
Documented inline in
src/api/user/publishEncryptionKey.tsandgetEncryptionKey.ts. Quick version:public_encryption_key(string, ~44 chars base64) andpublic_encryption_key_signature(string, 132 chars0x...).PUT /users/me/encryption-key— body{ publicKey, signature }. Server must verifyrecoverAddress(bindingMessage(addr, publicKey), signature) === addrbefore persisting. Exact message format:src/lib/crypto/derive.ts(bindingMessage).GET /users/:address/encryption-key— returns{ address, publicKey, signature }or 404.GET /auth/meand any chat participant payloads (theChatParticipanttype now expectspublic_encryption_keyandpublic_encryption_key_signature).bodyfield as before.Threat model
UX
Test plan
POST /chats/:id/messagescontainsenc:v1:...enc:v1:visible🤖 Generated with Claude Code