The verified state is reached only when both local and remote confirmation flags are true, and it is set in a single place so the transition cannot be reached by another route.
Verification is enforced, not merely displayed. Before it completes, the session declines to act on control messages from the peer — reconnection signalling, call setup, message deletion and delivery receipts. Only the verification exchange itself and liveness probes run earlier, because they have to.
## Message protection layers
```text
ECDH (P-384)
↓
HKDF key schedule
┌─────────┬───────┬──────────┬─────────────┐
↓ ↓ ↓ ↓ ↓
message MAC metadata fingerprint ratchet root
key key key (→ SAS) ↓
Double Ratchet
per-message keys
```
Chat content is encrypted under a ratchet-derived key when both peers support it, and under the session message key otherwise. Either way it reaches the interface through one authenticated path; frames that fail authentication are dropped rather than displayed.
## Forward secrecy
Per-message keys are derived from a chain key by a one-way function and destroyed after use, and each change of direction introduces a fresh ECDH key pair that re-keys the session root. Out-of-order delivery is supported within fixed bounds (512 skipped keys per chain, 1024 retained in total, five-minute expiry), which limits how much state a peer can cause to be held. Incoming frames are authenticated before any ratchet state is committed, so a bad frame cannot desynchronise a live session.
See [`CRYPTOGRAPHY.md`](CRYPTOGRAPHY.md) for the key schedule and framing details.