05 Security · Metadata
The envelope does not name you.
Content encryption is table stakes. The harder fight is the envelope — who, to whom, how much, when. Here is exactly how far we take it, and where it stops.
What the envelope actually says.
Sealed-sender v2 — rolling out
This is the wire struct, field for field. The sender is a blinded commitment the relay cannot reverse; only the recipient can. The commitments rotate every hour and on every key-transparency append.
- delivery_token Per-conversation routing token. Rotates on KTS append — yesterday’s token maps to nothing today. Opaque
- sender_blinded_pid Sender Profile ID committed under (token, window). Reversible only by the recipient. Opaque
- recipient_blinded_pid Matched against commitments each device pre-registered for the current window — routing without a name. Opaque
- time_window_id The hour the commitments were minted in. Visible — this is the envelope’s honest plaintext. Plaintext
- ciphertext_hash + delivery_proof SHA-512/256 body commitment under an ML-DSA-87 signature — the relay cannot rewrite what it routes. Opaque
- ciphertext The entangled frame, unchanged — Q-Ratchet keys, AEAD, and padding all live inside it. Ciphertext
Size stops being a fingerprint.
Every payload snaps up to a fixed bucket before sealing, so “yes” and a paragraph can leave the device the same size. These are the shipped constants, not an illustration.
| Ladder | Buckets | Range | Sized for |
|---|---|---|---|
| Text | 9 | 64 B → 16 KiB | chat: median under 128 B, p99 16 KiB |
| Voice | 8 | 16 KiB → 2 MiB | Opus VBR 24–48 kbps, ~0.5 s to ~64 s |
| Media | 6 | 1 MiB → 1 GiB | QSEAL-Stream totals after 256 KiB chunking |
The fine print on traffic shape
Padding hides length within a bucket; it does not hide that a message was sent, and timing correlation by a global passive observer remains an open problem for every store-and-forward messenger, including this one. QLEAP’s three-hop routing will raise the cost of linking endpoints; it is not in this build, and it would not make the problem vanish when it lands. Today the pad ladder and the sealed envelope above carry this alone.
The storage contract, present tense.
| Data | Today | Roadmap |
|---|---|---|
| Delivered messages | erased on acknowledgment | unchanged |
| Undelivered queue | 30 days, hourly purge job | tiered ladder: 24 h / 4 h / zero-storage |
| Delivery receipts | zeroed on delivery | unchanged |
| Envelope commitments | expire with their hour window | unchanged |
The 30-day figure1 is the present-tense truth of the running relay. The tiered ladder is roadmap, and it will be written here in the present tense only when the purge code ships.
The hard questions.
Can the relay build my contact graph?
Group names, subjects, avatars, and all content are end-to-end encrypted; the relay holds only opaque routing identifiers; sender identifiers are stripped from the stored message row (sealed-sender v2), though the relay still authenticates the sender at send time. Undelivered messages queue with routing metadata under a published retention ladder — that queue window is the honest exposure, stated above.
Why is the time window visible at all?
Because routing needs a freshness anchor: the relay must know which window’s pre-registered commitments to match an envelope against. One hour is the granularity — coarse enough to blunt timing precision, fine enough to keep delivery immediate.