Skip to content

End-to-End Encryption

All communication between the mobile/web app and your desktop backend is end-to-end encrypted. The Cloudflare relay only forwards opaque encrypted blobs — it cannot read, log, or tamper with your messages.

Mobile App Relay (Cloudflare) Desktop Backend
───────── ────────────────── ───────────────
Generate X25519 keypair Forwards encrypted blobs Generate X25519 keypair
│ │
├──── e2e_get_bundle ──────────────────────────────────────────►│
│◄─── server_public_key ───────────────────────────────────────┤
│ │
│ Compute shared secret (ECDH + HSalsa20) │
│ │
├──── e2e_register(device_id, public_key) ────────────────────►│
│ │ Compute same shared secret │
│ │ │
├════ Encrypted messages ══════╪═══════════════════════════════►│
│◄═══ Encrypted responses ═════╪═══════════════════════════════┤
Only sees: {c, n, deviceId}
Cannot decrypt anything
ComponentAlgorithmImplementation
Key ExchangeX25519 (Curve25519 ECDH)x25519_dalek (Rust), tweetnacl (JS)
Key DerivationHSalsa20Matches NaCl box.before()
EncryptionXSalsa20-Poly1305Authenticated encryption with 24-byte nonce
NonceRandom 24 bytes per messageOsRng (Rust), nacl.randomBytes (JS)
Key StorageSecure enclave / SecureStoreexpo-secure-store on mobile

Every message over the relay is a JSON envelope:

{
"c": "base64-encoded-ciphertext",
"n": "base64-encoded-nonce",
"deviceId": "device-uuid"
}

The plaintext (your actual message) is JSON-serialized, encrypted with XSalsa20-Poly1305, and base64-encoded. The relay forwards this blob without any ability to inspect it.

  • Keypair generated on first launch and stored in SecureStore (iOS Keychain / Android Keystore)
  • Device ID persisted across sessions
  • Keys survive app updates but are regenerated on reinstall
  • Keypair generated at startup
  • Server public key shared during handshake via e2e_get_bundle
  • Per-device sessions tracked for multi-device support
  1. Mobile app calls e2e_get_bundle to get the server’s X25519 public key
  2. App generates (or loads) its own X25519 keypair
  3. Both sides compute the shared secret: ECDH(my_secret, their_public) → HSalsa20 key derivation
  4. App registers its public key and device ID via e2e_register
  5. All subsequent messages are encrypted with the shared XSalsa20-Poly1305 key
  • Confidentiality — XSalsa20-Poly1305 with unique random nonces per message
  • Integrity — Poly1305 MAC detects any tampering
  • Forward secrecy — Server generates fresh keys on restart; compromising old keys doesn’t expose future sessions
  • Zero-knowledge relay — The Cloudflare Worker relay is cryptographically excluded from reading any message content