INVITE ONLY
OBSERVATORY

Research

protocol-spec

FSOR: Cryptographic Erasure of Server-Held Ciphertext

Version
v1.0
Category
protocol-spec
Published
Last updated
Authors
QERYX Research
Primitives HKDF-SHA-512 ChaCha20-Poly1305 ML-DSA-87 SHA-512
What changed v1.0
  1. v1.0 First publication of the FSOR envelope, destruction flow, auditor verification, and what FSOR does not promise.
Contents
  1. §1 Why FSOR exists
  2. §2 The server-blindness contract
  3. §3 The at-rest envelope
  4. §4 The destruction flow
  5. §5 Auditor verification
  6. §6 What FSOR does not promise
  7. §7 References

FSOR — Forward-Secrecy Output Reset — is the server-side at-rest layer beneath the end-to-end Q-Ratchet. It wraps every stored ciphertext in a per-conversation envelope whose key is derived at runtime and lives only in process memory. On destroy, the envelope epoch advances and the old key is zeroized: bytes remaining in any backup, WAL, autovacuum page, cache, or replica become envelope-locked under a key that no longer exists anywhere.

Why FSOR exists

A hard DELETE removes the live row and nothing else. Residue survives in the write-ahead log, autovacuum heap pages, the cache layer, file-store blobs, backup snapshots, and replica WALs. FSOR closes that gap cryptographically instead of chasing every copy: the bytes that survive are useless without a key that was never persisted.

The server-blindness contract

The wrap key cannot decrypt the user's plaintext. It can only peel the outer at-rest envelope, exposing the same end-to-end ciphertext that would have been on disk before this layer existed.

The wrap key derives at runtime from a master held only in an environment variable — never persisted, never logged — via HKDF-SHA-512 per (conversation, epoch). The database stores only the epoch number and an opaque SHA-384 commitment that lets an auditor prove a row binds the right epoch without exposing the key. Even a leaked master reveals only ciphertext still protected by ML-KEM-1024 + X25519 + ChaCha20-Poly1305. FSOR is strictly defensive: it can never make confidentiality worse than the inner layer, only better.

The at-rest envelope

wrap_key(conv, epoch) = HKDF-SHA-512(master,
    info = "QERYX-FSOR-WRAP-v1" || epoch_be8 || conv_id, 32)

wrapped = nonce(12) || ChaCha20-Poly1305(
    key = wrap_key, aad = "QERYX-FSOR-ENVELOPE-v1",
    plaintext = E2E_ciphertext)

On first successful delivery the stored ciphertext is nulled in place and the row becomes a tombstone: a re-fetch answers 410 Gone with the erasure timestamp. Undelivered messages ride a soft-delete grace, then a bounded offline horizon (default 7 days), then irrevocable purge.

The destruction flow

  1. The conversation's active epoch retires; its wrap key is zeroized in process memory.
  2. A fresh epoch row with a new commitment is appended — the ledger is append-only.
  3. A Merkle tree is built over the destroyed messages and its root signed with ML-DSA-87 under the context QERYX-FSOR-ROOT-v1, committing the operator to exactly what was destroyed.
  4. Row deletion fires a database trigger that fans the purge out to the cache layer and the file store — a catch-net that holds even for future code paths that forget to purge directly.

After step 1, WAL and backup residue for the retired epoch is permanently un-unwrappable. Rotating the master itself (an operator runbook item, recommended monthly) additionally locks every previously retired epoch against a later master leak.

Auditor verification

A user who receives a destruction notice holds everything needed to check it:

ML-DSA-87.VerifyWithCtx(
    pk  = pinned operator key,
    ctx = "QERYX-FSOR-ROOT-v1",
    msg = proof_root || epoch_before || epoch_after || leaf_count,
    sig = proof_root_signature) == OK

The Merkle root commits to which messages were destroyed; a user still holding ciphertext can re-derive leaves and prove inclusion or exclusion. If verification passes, the operator is cryptographically committed to the destruction.

What FSOR does not promise

FSOR destroys a key, and destroying a key destroys what that key wrapped. Every surface it does not reach is listed here.

  • The soft-delete grace window. Until ten minutes after delivery, the wrapped envelope is recoverable with operator cooperation. Past the window, recovery requires breaking the inner end-to-end layer and the wrap key both.
  • Live operator coercion. An operator under live legal compulsion could be forced to shadow-copy inbound ciphertext before FSOR wraps it. The WebSocket fanout path erases server-side as soon as push delivery succeeds, but this remains a policy-and-audit control, not a cryptographic one — and we say so.
  • A leaked master. Rows written under a leaked master are unwrappable by the leaker — down to the pre-FSOR baseline of end-to-end ciphertext at rest — until the next rotation restores the guarantee for new rows.

References

  1. QERYX Protocol Specification §8 — docs/SPEC/06-fsor-server-erasure.md; implementation backend/src/services/fsor.rs and the destruction-ledger migrations.
  2. RFC 5869 — HKDF; RFC 8439 — ChaCha20-Poly1305.
  3. NIST FIPS 204 — ML-DSA (destruction-root signatures).
  4. What a Subpoena Yields — the plain-language consequence of this construction.