Cipher — E2E encrypted messenger (Signal Protocol) on Cloudflare
Find a file
maverick 67367d5e83 Add WebRTC E2E voice/video calling (1:1)
- call.js: CallManager — RTCPeerConnection lifecycle, offer/answer/ICE/bye state
  machine, mute/camera toggles, hangup. STUN (Google + Cloudflare) for NAT.
- Signaling (SDP + ICE) rides the existing E2E message relay as {k:"call"}
  payloads — never seen by the server in clear. Media is P2P, encrypted by
  WebRTC's mandatory DTLS-SRTP, so audio/video is end-to-end and never hits R2.
- app.js: call buttons (📞/🎥) in DM headers, incoming/outgoing/connected call
  overlay with video tiles + PiP, mute/camera/hangup, ring pulse, duration timer.
- Verified signaling state machine (mocked WebRTC): both sides reach 'connected'.
  Group calls not supported yet (needs SFU). SW cache v5->v6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 13:21:52 +10:00
migrations Encrypted group chats + major UI redesign 2026-07-22 13:16:16 +10:00
public Add WebRTC E2E voice/video calling (1:1) 2026-07-22 13:21:52 +10:00
src Encrypted group chats + major UI redesign 2026-07-22 13:16:16 +10:00
.gitignore Cipher: E2E encrypted messenger scaffold (Signal Protocol + Cloudflare) 2026-07-22 01:56:59 +10:00
package-lock.json Add multi-device key sync: QR device linking + encrypted recovery code 2026-07-22 13:04:46 +10:00
package.json Add multi-device key sync: QR device linking + encrypted recovery code 2026-07-22 13:04:46 +10:00
README.md Add README with architecture overview 2026-07-22 02:06:59 +10:00
wrangler.jsonc Fix PBKDF2 iteration cap (Workers max 100k), add custom domain + error handler 2026-07-22 02:04:40 +10:00

Cipher

End-to-end encrypted messenger — a WhatsApp/Signal-style app running entirely on Cloudflare.

Live: https://cipher.theradicalparty.com

Design

The server is a dumb key directory + encrypted relay. It never sees message plaintext or private keys — only public keys and ciphertext. Real-time delivery plus offline store-and-forward, so messages arrive even when the recipient is away.

Concern How
Encryption Signal Protocol (X3DH + Double Ratchet) via libsignal, in the browser
Real-time + offline queue Per-user Mailbox Durable Object (WebSocket + SQLite queue)
Key directory D1 — users, devices, prekey bundles (public keys only)
Media Client-side AES-GCM, ciphertext stored in R2; key travels inside the E2E message
API Hono on Workers
Client Installable PWA, IndexedDB key store, Web Push wake-up
Identity Username + password; QR device-linking planned

Private keys are generated on the device and stored only in IndexedDB. They are never transmitted. See docs/ notes inline in each source file.

Architecture

 Alice (PWA)                    Cloudflare Worker                 Bob (PWA)
 ┌─────────────┐   publish pubkeys   ┌──────────────┐
 │ libsignal   │ ──────────────────► │ D1 key dir   │
 │ IndexedDB   │ ◄── prekey bundle ─ │              │
 └─────┬───────┘                     └──────────────┘
       │ encrypt (X3DH/ratchet)
       │ POST ciphertext             ┌──────────────┐   WS push    ┌──────────┐
       └───────────────────────────► │ Mailbox DO   │ ───────────► │ decrypt  │
                                      │ (queue+WS)   │   or queue   │ locally  │
       encrypt media (AES-GCM) ─► R2 └──────────────┘              └──────────┘

Develop

npm install
npm run dev                 # local Worker + assets
npm run db:migrate:local    # apply schema to local D1

Deploy

Push to main → Forgejo post-receive hook runs wrangler deploy.

git pushall main            # pushes to Forgejo (origin) + GitHub (backup)

Vendored crypto

public/js/vendor/libsignal.js is a self-contained browser bundle of @privacyresearch/libsignal-protocol-typescript (built with esbuild + a Buffer shim). Rebuild with the esbuild command noted in the commit history if bumping the library — loading crypto from a CDN at runtime is intentionally avoided.

Status

Working: registration, login, key directory, E2E text + media, real-time and offline delivery (both verified live). Planned: QR multi-device linking, group chats (sender keys / MLS), WebRTC voice/video, Web Push fan-out, delivery/read receipts, disappearing messages.