Hiding your name is not the same as hiding that it's you. A relay that never learns "Alice" can still learn "the same unnamed person who messaged Bob at 9, Carol at 10, and a support line at 2am," and that shape is often enough to put a name back on.
Sealed sender removed the sender's identity from the envelope and
left one thing behind: a 16-byte cert_id, an opaque ticket number the relay needs for
revocation (cancelling a bad certificate mid-life) and for abuse control. This article is
about the surprisingly sharp leak that one small field creates, and the shipped fix.
Cryptographers distinguish anonymity (the system doesn't know who you are) from unlinkability (the system can't tell that two of your actions were done by the same someone). They are different, and the second is easy to lose while thinking you have the first.
The cert_id is stable for the life of a certificate, roughly a day.
So even though the relay never learns "Alice," it can group every message carrying ticket #7 into one bucket and study that bucket: how many conversations it touches, at what hours, in what bursts, in reply to what.
That bucket is a pseudonym, and pseudonyms de-anonymize through pattern. This is the entire field of traffic analysis: working out who is who from the patterns of communication alone, without reading a single word of content.
So a stable per-user ticket, even an anonymous one, is a real leak. We want the relay to lose the thread between a person's conversations.
The tempting answer is to drop cert_id from the envelope entirely. We cannot (not
without moving the jobs it does somewhere else first) because the relay uses it for two
things that genuinely have to happen at the moment a message arrives:
cert_id against a short revocation list. No identifier, no revocation.Both are legitimate, both need some stable handle, and neither is allowed to be the user's identity. The tension is real: the same field that enables anonymous revocation is the field that enables pseudonymous clustering.
The resolution is to keep a stable ticket, but make it stable only within a single
conversation rather than across a user's whole life. Instead of one certificate per user
per day, Alice mints one certificate per conversation: a distinct cert_id for her
chat with Bob, another for her chat with Carol.
The trick that makes this cheap and private is where the certificates are filed.
Alice caches one certificate per conversation, indexed under that conversation's local channel key: a secret she and her partner already share, and which is never transmitted to anyone.
The mint request itself is byte-identical every time, so credential-svc (the separate identity service that issues these certificates) cannot tell which conversation it just issued for. The separation happens entirely on Alice's device.
Now walk it from the relay's side.
It still sees a stable ticket inside a single conversation. But it already knew those messages belonged together, because a conversation shares a routing subject anyway: the single address every message in that chat is published to.
What it has lost is the ability to tie the Bob-ticket and the Carol-ticket to each other. Alice's separate conversations are now separate unlabelled buckets.
The analogy is using a different alias at every venue. A doorman at one club can still recognize "the guy in seat 4 all night," but no one can cross-reference the clubs to discover it's the same person making the rounds, because the name on each wristband is different and unlinkable.
Not much, which is why this is the shipped choice rather than a someday-plan:
That is the entire price for erasing cross-conversation linkage at the relay.
We name what this does not fix:
Within one conversation, the relay still sees a conversation's messages grouped, but it saw that already (they share a routing subject), so per-pair certificates reveal nothing new inside a chat. This is not a regression; it is the pre-existing floor.
The number of distinct tickets a device presents ≈ its number of active conversations. That count is itself a coarse signal (roughly "how socially busy is this device") even though it links to no identity and no specific partner. It is a much fainter shape than a full cross-conversation graph, but it is not zero.
credential-svc sees the mint volume. The service that issues certificates knows how many were minted, though its ledger is keyed by a hashed user handle (a scrambled stand-in), never the raw account, and it never sees the content or recipients of anything. Mint volume is a weak, aggregate signal held by a different party than the relay.
The admission token carries a cleartext key_id. The token is the single-use anonymous
stamp that pays for a send (Anonymous Admission), and the key_id
names which of the mint's live issuing keys produced it.
This is the one field on the list that is not per-conversation, so it is the one that could re-stitch the pseudonyms above into a single identity, and it would survive every other defence here. That is why the key set is published in full and committed to the transparency log (a public, append-only record anyone can audit): a per-user issuing key would show up as a key nobody else is using.
None of these reconstruct a cross-conversation social graph, which is the leak we set out to close. But honesty is the house style, so they are on the record.
There is a cleaner design, and it is worth stating because it is the recommended next step.
True Signal-style sealed sender removes cert_id from the envelope entirely. In that
world the relay does no per-message identity check at all. Revocation moves entirely to
credential-svc: a banned account simply cannot mint fresh admission tokens, so its messages
stop being admitted rather than being recognized-and-rejected. And all relay-side
abuse control rides on the single-use admission token, which
is already anonymous and unlinkable by construction.
We did not ship that yet because it changes both the envelope format and the admission path: the riskier surgery. Per-conversation certificates deliver the property that actually matters, no cross-conversation linkage, as a localized, low-risk change that touches neither. It is the classic engineering call: ship the 90% that removes the real leak now, keep the elegant 100% as a planned migration. When the envelope format next changes, dropping the ticket is the plan.