Pixieby Sociofabric

The Envelope: Minimizing What the Relay Must See

The relay (the server that carries sealed messages between phones) cannot be blinded completely. To decide whether to make a phone ring like a call or nudge it silently in the background, it has to read something about the message. The whole game is making that "something" as coarse and uninformative as physics allows, and then proving the relay can't cheat to learn more.

Sealed Sender and Sender Unlinkability hid the sender. This one is about the header: the small set of fields on the outside of each message that tell the relay where to send it and how to handle it. The discipline is keeping that small set from becoming a metadata channel: a way to learn facts about your messages (who, when, what kind) without ever reading their content.


1. Why the relay needs to read anything at all

An incoming voice call must make the phone ring (full-screen, override silent mode, wake the CPU hard) within a second, or the feature is broken.

A "someone reacted to your post" should at most nudge quietly, and often should not wake the phone at all. A background sync between your own devices should be invisible.

Those are wildly different handling decisions, and the relay is the thing that has to make them.

So the relay must read how to handle each message.

The danger is that "how to handle" usually correlates tightly with "what kind of message this is," and the kind of message is exactly the metadata we are trying to hide.

If the header said type = reaction, or type = typing_indicator, or type = call_offer, the relay would learn the texture of every conversation: who reacts to whom, who is typing, when calls happen.


2. Two headers: a public coarse one, a sealed fine one

The design splits the header in two.

Inside the seal (the encrypted part of the envelope, which only the recipient's phone can open) rides the true type, one of a couple of dozen fine-grained kinds: message, reaction, post, call_offer, sync_request, sync_delta, device_list, huddle turns (one speaker's turn in a live group session), receipts, and so on. Only Bob's phone, after decrypting, ever sees this. It is what the app actually acts on.

On the outside, where the relay reads it, rides a single coarse field: the WakeClass, with just six values:

WakeClass Handling Example inner types (hidden)
voip ring hard, never suppressed a call offer
alert user-visible notification a direct message
sync visible banner; must be able to relaunch a force-quit sibling a sync request
ack tiny, non-waking receipt a delivery/read receipt
call_tail never wakes (call teardown) hang-up housekeeping
silent never wakes on its own almost everything else

sync is the one that looks out of place, and it is worth a sentence. It is loud on purpose. A silent push notification cannot relaunch a sibling (another device on your own account) once the user has force-quit the app there. And relaunching one is exactly what a newly added device needs, so that it can be served its first snapshot of your data. This class fires during onboarding and not in steady state, so making it visible costs no ongoing notification churn.

The last row is where the privacy lives.

The mapping from the fine types down to six coarse classes is a fixed, many-to-one projection (a lookup table that sends every fine type to exactly one coarse class, with no randomness and no exceptions), built so that most types collapse into silent. Posts, reactions, huddle turns, sync deltas, device-list changes: all of it lands on the same coarse value, indistinguishable from one another to the relay.

The relay learns "this is a background thing, do not wake for it" and nothing finer.

INSIDE THE SEAL TRUE TYPE · MANY KINDS OUTSIDE · RELAY READS WAKECLASS · 6 VALUES CALL_OFFER MESSAGE SYNC_REQUEST RECEIPT HANG-UP POST REACTION SYNC_DELTA + MANY MORE FIXED MANY-TO-ONE PROJECTION VOIP ALERT SYNC ACK CALL_TAIL SILENT ALL LOOK IDENTICAL TO THE RELAY EVERY KIND GOES IN · THE RELAY SEES ONE OF SIX

This is exactly the pattern modern protocols converged on. In MLS (Messaging Layer Security, the group-messaging standard; RFC 9420), the encrypted PrivateMessage hides the content type from the server. In TLS (the encryption protocol behind the padlock in your browser), Encrypted Client Hello hides which site you're connecting to behind a shared front. The relay's WakeClass is the same idea: reveal the minimum the transport needs to function, seal the rest.

The timestamp gets the same treatment. The created_at the relay sees is rounded to a 5-minute bucket, so the relay can enforce message expiry without learning the precise instant you hit send. That blurs the fine timing that traffic analysis feeds on: the craft of inferring who is talking to whom, and about what, from nothing but the sizes and timings of sealed messages, without ever opening one.


3. The attack you have to think about: a lying relay

Splitting the header raises an immediate question. The WakeClass is outside the seal, so the relay (or anyone who tampers with the envelope in flight) can change it. What stops a malicious relay from rewriting silent to voip (the ring-hard class for calls) to make your phone ring at 3am? Or, more insidiously, from using the WakeClass as a covert channel: a field that is supposed to be routing plumbing, quietly repurposed to signal something to whoever is watching?

Here is the elegant part: the recipient does not trust the outer WakeClass; it re-derives it. After decrypting, Bob's phone reads the true inner type, computes what the WakeClass should be (the same fixed projection), and checks it against the outer value the relay used. If they disagree, the message is rejected outright.

RELAY EDITS THE HINT BOB RE-DERIVES AUTHORITY OUTER WAKECLASS SILENT VOIP SEALED TRUE TYPE REACTION DELIVER OPEN SEAL REACTION FIXED MAP → SILENT = OUTER HINT VOIP MISMATCH · REJECT A FALSE HINT CAN DEGRADE A WAKE · IT CANNOT CHANGE AN ACCEPTED TYPE THE OUTER CLASS IS A HINT · THE SEALED TYPE IS AUTHORITY

Follow the consequences.

The outer WakeClass can only ever affect how the phone was woken. It is a physical, best-effort hint. It can never affect what the message is, or whether it is accepted, because that is governed by the sealed inner type Bob verifies.

So the worst a malicious relay can do by lying about the class is degrade the notification: fail to ring for a real call, or ring for something that should not have rung.

That is a denial-of-service (an attack that breaks a feature rather than stealing a secret), and a visible one. It is not a privacy break.

This "the untrusted field is only a hint, and the trusted field is re-derived and checked" shape is worth internalizing: it recurs whenever you must expose a routing hint to an adversary without letting the hint become authority.


4. A machine-checked guarantee

Claims like "two different message types are indistinguishable on the wire" are exactly the kind of thing that is easy to assert and hard to actually have.

So this one is not just asserted. It is proved by machine.

The property is stated and checked in the formal corpus, the project's library of machine-checked proofs: two messages sharing a WakeClass are indistinguishable to the relay, meaning the envelope leaks nothing beyond the coarse class. It is checked in two different styles of model. A symbolic model treats encryption as a perfect black box and checks the protocol's logic for flaws. A computational model drops that idealization and bounds what any efficient attacker could actually compute. The claim is checked in both.


5. The honest residual

The relay is not blind, and we don't pretend it is. From the envelope it still sees:

So the residual is: the relay knows a message of some coarse urgency class landed on this address in this five-minute window. That is the irreducible minimum for a transport that must route and must wake, and every finer fact about the message is sealed, verified end to end, and provably absent from what the relay reads.

OUTSIDE · RELAY READS INSIDE · ONLY BOB OPENS TO · ROTATING ADDRESS WAKECLASS · 6 VALUES CREATED_AT · 5-MIN WINDOW TICKET + TOKEN SEAL BOUNDARY SENDER IDENTITY · LOCKED EXACT TYPE · LOCKED EXACT TIME · LOCKED CONTENT · LOCKED BOB'S KEY FOUR COARSE FACTS OUTSIDE · EVERY FINER FACT SEALED

Next we take away the last thing you might assume a relay has: a place to keep the message. A Relay That Holds Nothing.


References & further reading

← Sender Unlinkability: The Pseudonym ProblemA Relay That Holds Nothing: Ephemeral Delivery →