Security overview
Zentalk implements the Signal Protocol (X3DH + Double Ratchet) with a post-quantum hybrid handshake (enabled by default), layered metadata protection, and a publicly auditable key transparency log. This page maps the full stack; each layer has a dedicated page with wire-level detail.
Protocol stack
Section titled “Protocol stack”| Layer | Mechanism | Primitives | Details |
|---|---|---|---|
| Key agreement | X3DH (Extended Triple Diffie-Hellman) | X25519, Ed25519, HKDF-SHA256 | X3DH key agreement |
| Post-quantum hardening | Hybrid X3DH | X25519 + ML-KEM-768 (Kyber-768), HKDF combiner | Post-quantum hybrid encryption |
| Session encryption | Double Ratchet | X25519 DH ratchet, HMAC-SHA256 chains, AES-256-GCM | Double Ratchet |
| Groups and channels | Epoch-based symmetric key ratchet | HKDF-SHA256, AES-256-GCM | Group and channel encryption |
| Metadata protection | Sealed sender envelopes, address hashing, padding | X25519 ECDH, HKDF-SHA256, AES-256-GCM, HMAC-SHA256 | Sealed sender |
| Key transparency | Append-only Merkle log with signed roots | SHA-256 Merkle tree, inclusion proofs | Key Transparency Log |
| User verification | Safety numbers and SAS codes | Iterated SHA-512, HKDF-SHA256 | Contact verification |
The layer pages build on one another; the table lists them in recommended reading order.
Two interoperable implementations exist:
- TypeScript (web client):
zentalk/src/services/e2ee/— the reference for everything documented in this section. - Go (mesh node):
zentalk-node/pkg/protocol/x3dh.goandratchet.go— parity implementation sharing the same protocol structure and HKDF info strings.
For programmatic access to the APIs referenced throughout, see the REST API and the TypeScript SDK.
Core primitives
Section titled “Core primitives”| Purpose | Primitive | Parameters |
|---|---|---|
| Diffie-Hellman | X25519 | 32-byte keys |
| Signatures | Ed25519 | 32-byte public keys, 64-byte signatures |
| Post-quantum KEM | ML-KEM-768 (Kyber-768, NIST FIPS 203) | 1184-byte public key, 1088-byte ciphertext, 32-byte shared secret |
| Authenticated encryption | AES-256-GCM | 32-byte key, 12-byte IV, 16-byte tag |
| Key derivation | HKDF-SHA256 (RFC 5869) | Protocol-specific salts and info strings |
| Chain advancement / MACs | HMAC-SHA256 | 32-byte output |
What is end-to-end encrypted
Section titled “What is end-to-end encrypted”Encrypted with keys the server never holds:
- Direct message content (Double Ratchet, AES-256-GCM)
- Group and channel message content (epoch keys distributed over pairwise E2EE)
- Group key distribution messages
- Sender identity inside sealed-sender envelopes (X25519-sealed sender certificate)
What the server and relays can observe
Section titled “What the server and relays can observe”Zentalk’s metadata posture is summarized below; the relay-visibility details are documented in Sealed sender. In summary:
| Metadata | Current state |
|---|---|
| Sender address (sealed envelopes) | Hidden — encrypted inside the sender certificate |
| Recipient address (sealed envelopes) | Hashed (32 bytes) in v2; HMAC-bucketed into 1024 buckets in v3 |
| Mesh routing addresses | Salted SHA-256 hashes (zentalk-mesh-v1 salt) — obfuscation, not anonymity: anyone who knows an address can recompute its hash |
| Payload size | Bucketed by relay padding |
| Timing | Exact (v2); coarser timing planned |
| Presence, typing indicators, read receipts | Encrypted variants (encrypted_presence_update, encrypted_typing, encrypted_read_receipt) are implemented; mesh-only deployments enforce them by blocking the plaintext variants. Plaintext is visible to backend and relays only on non-mesh-only deployments |
| X3DH initial messages | Contain senderAddress in plaintext for new sessions only; established sessions do not leak it |
Threat model
Section titled “Threat model”The project maintains a full threat model defining nine adversary classes and six trust boundaries. In summary:
- Passive network observers (A1) — mitigated by TLS 1.3, relay padding, and E2EE. A dedicated “store now, decrypt later” sub-class (A1*) is addressed by the hybrid post-quantum handshake.
- Active MITM (A2) — mitigated by signed-prekey verification in X3DH, the Key Transparency Log, and out-of-band SAS verification. KTL is tamper-evident, not tamper-proof; a split-view attack requires out-of-band verification to detect.
- Malicious mesh validators (A3) — content is E2EE; membership tokens fail closed; replication factor of 3 or more tolerates Byzantine drop. Residual: routing tags reveal partial social-graph information until v3 bucketing is fully deployed.
- Compromised relay (A4) — sealed sender hides sender identity; sender certificates expire after 1 hour, limiting the impact of a compromise.
- Malicious clients (A5) — DPoP-bound refresh tokens, per-endpoint rate limits, and Double Ratchet replay protection (persistent IV tracking).
- Compelled access and insiders (A6–A8) — the operator can only produce ciphertext and hashed identifiers; message content keys never leave user devices.
- Device compromise (A9) — key material is wrapped with AES-256-GCM in secure browser storage; an unlocked-device adversary is outside the threat envelope, as is the case for messaging applications generally.
Where keys live
Section titled “Where keys live”All long-term and session key material is generated client-side and stored in IndexedDB via a secure key storage layer. The server stores only public key bundles (identity keys, signed prekeys, one-time prekeys) published through the keys API — see X3DH key agreement for the publish/claim flow and Authentication for how requests are authorized.
