Relay
Sealed Sender: Hiding Who Is Talking
6 min read
The relay has a job that sounds self-contradictory: it must confirm that a message comes from a real, non-banned user — and it must do that without ever learning which user.
Content encryption (the previous article) hides what is said. This article hides who said it, from the one party in the best position to build a social graph out of that fact: the server in the middle.
1. The leak that survives encryption
Go back to the strawman inbox. Even with a perfectly encrypted body, the row the relay
handles looks like (from: Alice, to: Bob, …). That from: Alice is the whole ballgame.
It lets the relay log "Alice talks to Bob at 9pm, to Carol at midnight, to a
crisis-hotline number on Tuesday" — a portrait assembled entirely from envelopes it was
never supposed to open, because it never had to: the sender wrote their own name on the
outside.
The obvious fix is to not write the name on the outside. But then two problems appear that the naive design was quietly solving for free:
- Routing. The relay still needs to know where the message goes (
to: Bob) — that one is unavoidable and we accept it. - Authorization. The relay should not burn delivery resources on a message from a banned account or a spammer. Normally you enforce that by looking at who sent it. If the sender is anonymous, how do you check they are allowed to send at all?
Signal named and solved the first half of this in 2018 with sealed sender, and our design follows that shape, with the authorization half handled by a separate anonymous token we will get to in Anonymous Admission.
2. Put the identity inside the seal
The core move is almost too simple: the sender's identity goes inside the encrypted
payload, not on the envelope. Alice's public key and account handle are sealed in the
blob that only Bob can open. The relay routes by the outside (to: Bob) and never sees a
from at all.
But that immediately raises Bob's problem. If anyone can seal a blob to Bob claiming to be Alice, how does Bob know it is really Alice and not an impostor? In the old design the relay vouched for the sender; now the relay is blindfolded. The authenticity has to travel inside the seal too.
So Alice includes, inside the payload, a sender certificate: a short document that says "the holder of this public key is a registered, non-banned user," signed by a service Bob trusts. Bob opens the seal, reads the certificate, checks the signature, and only then believes the message. The relay never participates in that check — it happens end to end, between Alice's claim and Bob's verification, through a server that saw only ciphertext.
3. The sender certificate, concretely
The certificate is minted by credential-svc (a registration/identity service, separate from the relay) when Alice proves she is a logged-in, in-good-standing user. It binds her sending public key to that blessing and carries an expiry. A few deliberate choices:
- It is hybrid-signed: Ed25519 and ML-DSA-65. Following the post-quantum rule from article 00, the certificate carries two signatures — the classical Ed25519 and the lattice ML-DSA-65 — and Bob checks both. A future quantum computer that forges Ed25519 still cannot forge ML-DSA, so a certificate cannot be counterfeited even by an adversary who has broken the classical curve.
- It is short-lived (about a day) and auto-renews at half its life. A stolen certificate is only useful for hours, and revocation (below) never has to fight a long-lived credential.
- It carries a 16-byte identifier, the
cert_id. This is the only piece of the certificate the relay is allowed to see — it rides on the outer envelope — and it exists so the relay can do the two anonymous jobs it still needs: check the certificate hasn't been revoked, and feed an anonymous abuse meter. The certificate body (the keys, the identity, the signatures) stays sealed.
Think of the cert_id as a numbered coat-check ticket. It proves you checked in with the
front desk, and the bouncer can cross it against a short list of tickets that have been
cancelled — but the number, by itself, is not your name.
4. What the relay sees, precisely
When Alice sends, the outer envelope the relay handles carries only:
to— the recipient's routing key (unavoidable),- a coarse WakeClass — how urgently to wake Bob (see The Envelope),
- a fuzzy, 5-minute-bucketed timestamp,
- the
cert_id— the anonymous coat-check ticket, - one single-use admission token — the anonymous "postage" that authorizes the send (see Anonymous Admission),
- and the sealed payload — opaque bytes.
From that, the relay learns almost exactly one sentence: "some real, non-banned user (ticket
cert_id) paid one token to send some class of message to Bob at roughly this time." No
account, no public key, no name. The identity is inside the seal, verified only by Bob.
"Almost", because the token carries one more field the relay reads in the clear: a key_id
naming which live issuing key minted it. Under honest operation that is about two bits — there
are only a couple of live keys at a time — but the issuer chooses it, which makes it exactly
the sort of field that could be turned into a per-user tag. It is not left to good intentions;
The Admission Token §5 is entirely about the checks that keep
it meaningless.
5. The signature that became a MAC
There is one subtlety that connects this article to the very last one in the series, and it is worth flagging now because it changes what "the certificate proves."
You might expect Alice to also sign each individual message with her key, so Bob knows this specific message — not just the certificate — came from her. Early designs did exactly that. We removed it, on purpose. A per-message signature is transferable proof: it lets Bob (or anyone who later sees the transcript) prove to a third party that Alice wrote those exact words. That is a courtroom exhibit, and it is the opposite of the deniability a private conversation should have.
So the inner per-message authenticator is not a signature but a MAC — a check computed under a key that both Alice and Bob share for their conversation. It convinces Bob the message is authentic (only someone with the shared key could have made it, and Bob didn't), but it proves nothing to anyone else, because Bob could have produced the very same tag himself. Why that is safe, and how the shared key gets established deniably, is Deniability; the exact bytes are in Sealing the Envelope.
6. The honest residual
Sealed sender hides who. It does not, by itself, hide sameness.
The cert_id on the envelope is stable for the life of a certificate — about a day. That
is deliberately load-bearing (it is how revocation and abuse-metering work without an
identity), but it means a relay watching the traffic can cluster: every message bearing
ticket #7 is "the same pseudonymous user," even if it never learns the user's name. Over a
day, that pseudonym's whole social pattern — who they message, when, how much — is visible
as one shape, just an unlabeled one.
A pseudonym is a weaker leak than a name, but it is still a leak, and closing it is a real piece of engineering on its own. That is the next article: Sender Unlinkability.
References & further reading
- Signal, "Technology Preview: Sealed Sender for Signal" — the original design; ours adds hybrid post-quantum certificates and moves per-message authenticity to a deniable MAC.
- NIST FIPS 204 (ML-DSA) — the lattice signature in the hybrid certificate.
- The End-to-End Layer — the seal the identity hides inside.
- Deniability — why the per-message signature became a MAC.