The cryptography in the rest of this series defends against adversaries who attack the math. This article is about the adversary who attacks the interface: by using the system exactly as designed, only too much, or dishonestly, or with numbers they do not own. None of the fancy lattice machinery (the post-quantum math the rest of the series is built on) helps here, and pretending otherwise is how privacy systems get quietly defeated at their most boring seam.
Prerequisites: Contact Discovery. Familiarity with the Verifiable OPRF and PIR helps but isn't required.
Look at what the OPRF is, from the outside. An OPRF (an
oblivious pseudorandom function) is a keyed hash the server computes for you without ever
seeing your input. Viewed as a service, it is a public endpoint that takes a phone number and
returns its presence_id, the opaque identifier that stands in for that number everywhere
else in the system.
That is not an accident. It is the whole point. Alice must be able to compute presence_ids
for numbers she does not own (her contacts' numbers), or discovery could not work at
all. The service is, by design, a machine that turns numbers into identifiers for anyone who
asks.
An endpoint that computes a function on inputs you do not control is what cryptographers call an oracle, and oracles get abused in three characteristic ways.
All three are protocol-compliant. The attacker follows every rule, and the cryptography sees nothing wrong.
presence_ids for numbers you don't control so you can watch who looks them up.presence_id to your
inbox, so that people trying to reach them reach you instead: a discovery-layer
person-in-the-middle.Notice these are not cryptographic breaks. They are misuse. The defenses are correspondingly more classical: rate limits, proof-of-control, and authentication. They are just as essential as the lattices. A system is only as private as its least-defended interface.
You cannot forbid a caller from asking about a number they don't own. That ability is the feature. What you can do is make asking about all ten billion numbers prohibitively slow.
The defense is unglamorous and effective: rate limiting. Every call to the oblivious-eval endpoint (the API route that actually runs the OPRF) is metered in fixed time windows. The meter counts per network origin, with a global backstop on top, so no single actor can pump the oracle fast enough to sweep the number space in any reasonable time.
There is deliberately no per-account meter. The endpoint takes no session at all, because a client has to be able to derive an identifier before it has an account to be metered against.
A user syncing an 800-entry address book fits comfortably under the cap. An enumerator trying to grind ten billion numbers hits a wall.
One decision here runs against a common instinct and is worth calling out: the limiter fails closed. If the rate-limiting subsystem is itself unavailable, the endpoint denies rather than allows.
The tempting alternative is to fail open, so the feature keeps working during an outage. That is a classic self-own. An attacker who can knock the limiter offline would get an unmetered enumeration oracle at exactly the moment they want one.
We chose to let discovery degrade rather than let the oracle run wild. Early on it was the other way round; the enumeration threat model is what flipped it.
We could have leaned harder on making the OPRF itself slow: a deliberately expensive PRF (pseudorandom function, the keyed hash at the OPRF's core) raises the per-query cost for everyone, attacker and honest user alike. But punishing every honest lookup to slow an attacker is a bad trade, and it interacts badly with the "must feel instant" requirement. Rate limiting targets the abuse pattern (too many queries) rather than the operation (one query), which is the right place to put the cost.
Every defense so far assumes the attacker is outside, poking the public endpoint. Now flip to the worst realistic case: an adversary who breaches the server itself and becomes a silent observer, reading process memory, the database, the logs, the wire.
What do they learn? Almost nothing, and that is the whole payoff of the cryptography in this series.
The query traffic is Ring-LWE ciphertext (encrypted under
the lattice scheme this series builds on), sealed under each
client's ephemeral key, a single-use key the server never held. Not a single lookup
decrypts. The PIR queries (PIR is private information retrieval, a way to fetch a database
record without the server learning which record you asked for) are the same: encrypted
selectors the intruder cannot invert. And the directory at rest
is a table of opaque presence_ids, keyed-hash outputs that mean nothing without the key.
A hacked-in observer sees a pile of noise and a pile of pseudonyms, and can attach neither to a person.
There is exactly one thing on that server worth stealing: the everlasting key s from The Verifiable OPRF.
It is the master secret that turns a presence_id back into a phone number by brute force.
With s in hand, an attacker can compute Hash(F_s(phone)) offline for every one of the
ten billion possible numbers. An intruder holding both s and a directory dump could
therefore reverse the membership set:
work out which numbers are registered.
So "does a server breach leak anything useful?" collapses to one question: can the intruder get s?
Two facts bound the damage even if they do. A stolen s is a forward enumeration capability, not a time machine. It does not retroactively decrypt any recorded query. And it does not reveal who looked up whom, because PIR never exposed the lookups in the first place.
The worst case is that the directory becomes phone-queryable, which reverts discovery to a keyed hash whose key has leaked. Bad, bounded, not a collapse.
Baseline custody is the obvious hygiene. s lives in a KMS/HSM, loaded fail-closed at boot, kept off the database, its evaluation rate-limited per §2. A KMS/HSM is a key-management service backed by dedicated key hardware: you hand it work to do with a key, and it never hands the key back.
That still leaves a real gap. To evaluate the OPRF, the honest server must hold s in process memory, and a memory-reading intruder can scrape it from there.
We could file "don't let your RAM get read" under someone else's problem. But the entire ambition of the design is to make a breach boring, so we close this the same way the system closes the compelled-operator threat elsewhere: run the evaluator inside an attested enclave, or TEE (trusted execution environment).
A TEE is a CPU-isolated compartment whose memory not even the host operating system can read, and which can prove remotely which code is running inside it. Sealed in one, s becomes non-exportable (provisioned to enclave-sealed storage, never in plaintext process memory), so a total server compromise yields the same nothing as a partial one.
We treat attested-enclave custody as the destination rather than a prerequisite. The lattice OPRF's guarantees hold wherever s sits. The enclave is simply what makes the claim literal: hack in, observe everything, learn nothing useful.
The second abuse is fake identities. The defense is a Sybil gate: before a number can be listed in the directory as reachable, whoever is registering it must prove they actually control that phone number.
Proving control of a phone is done the mundane way: an SMS one-time code, verified through a gateway. The interesting part is the wrapping, which stops the server issuing the credential from building a phone-to-account link out of it.
The verification service checks the code and issues a verified-phone token. The client can later present that token, blindly, to prove "some real, SMS-verified human stands behind this listing", without the token being traceable back to the verification event.
The unlinkability comes from a blind-signature dance, the same shape as an oblivious pseudorandom function. The client hands over a disguised value (this disguising step is called blinding). The issuer signs it without ever seeing what it signed. And the credential the client unwraps is one the issuer cannot recognize when it comes back.
Here is a place to be scrupulously honest about the post-quantum boundary, because it is exactly the kind of detail that gets glossed.
For a while the Sybil-gate token was the one classical holdout: a verified-phone credential built on an elliptic-curve blind signature while the rest of discovery had already moved to lattices.
That was a deliberate and clearly bounded exception. The token is used once, at registration, upstream of everything else. What its secret protects is anti-abuse strength, not anything about who knows whom. And it is not part of the discovery lookup at all. A future quantum forgery would have let someone register a fake listing, never decrypt anyone's contacts.
We have since closed it. The verified-phone token, and its companion rate-limit and admission token, moved onto the same lattice oblivious OPRF the lookup uses, with its own issuer key. The classical blind-signature code was deleted outright.
So there is now no classical cryptography left in the discovery lookup or in the Sybil-gate credential. Both are post-quantum end to end, and a 2035 adversary can no more forge a listing credential than it can decrypt discovery traffic.
That migration moved two properties, not one, and both are worth naming.
Unforgeability improved. It went from resting on discrete log (the hard problem elliptic-curve cryptography is built on, and one a quantum computer breaks) to resting on a lattice problem that it does not. That was the win we were after.
Unlinkability got slightly weaker. The old blinding was a one-time pad (a cipher that stays unbreakable even against unlimited computing power), which made unlinkability unconditional. The lattice version hides the client's input under Ring-LWE instead, which makes it computational: it now rests on a hard math problem rather than on pure information theory. A quantum computer still cannot link a token to its issuance, but an adversary with truly unbounded computation could. Anonymous Admission works through that trade in full.
One internal detail reinforces all of this at rest. The directory and the issuance ledger are
keyed by the lattice presence_id, never by any phone-derived value the server could
invert. Even the listing ledger holds only the post-quantum identifier, so a full dump reveals
nothing about which numbers it corresponds to.
The third abuse is claiming someone else's slot. Defense: a directory entry can only be
published by someone holding a phone-ownership certificate for that presence_id, and the
publish endpoint refuses entries without a valid one. The certificate is minted by the
verification service (§4) when you prove control of your number, and it cryptographically binds
this presence_id to your right to publish under it. No cert, no entry; someone else's
cert, wrong presence_id, rejected.
The certificate's signature is a hybrid. It carries a classical Ed25519 signature (the elliptic-curve scheme signing most of the internet today) and a post-quantum ML-DSA signature, the lattice scheme standardized by NIST, the US standards body. Both are required.
Requiring both is the honest middle path. The ML-DSA half gives post-quantum forgery resistance immediately, and keeping the battle-tested classical half means a flaw in the younger lattice-signature implementation cannot silently open the door on its own.
Note what layer this is. Publishing rights are authorization, not discovery confidentiality. Putting a post-quantum signature here extends the post-quantum posture past the lookup and into the surrounding integrity layer, wherever we reasonably can.
The certificate authorizes, but it does not freshen. Two mechanisms exist because of that gap.
The first closes a replay: an attacker re-sending a request they captured earlier. A certificate carries no session and lives about a year, so a captured publish request stays replayable for as long as the certificate does.
Replay an old publish under a new version number and it re-pins contacts to a key its owner has since rotated away from. Clients do keep a rollback floor: they refuse any entry with a version number lower than one they have already accepted. That floor does not catch this attack, because it only compares version numbers, and the replay arrives with a higher one.
So the directory records every binding a number has ever retired and refuses to accept one back. Re-publishing the current value is still allowed and changes nothing: clients routinely republish when they register, and that has to stay harmless.
The second closes a window. A binding is not served the moment it is written. Contact-facing lookups read the folded view: the set of bindings the transparency log (a public, append-only record of bindings that can prove exactly what it contains) is already able to prove. That makes "discoverable" and "provable" name the same instant. A client that cannot find a proof for a served key is therefore seeing something genuinely anomalous, not a publish that has not caught up yet (What a Lookup Proof Binds).
We could have skipped certificates entirely and let the directory be write-open, on the theory that the OPRF's unlinkability makes poisoning pointless.
That reasoning is a trap. Unlinkability hides who a presence_id belongs to from the
server. But a targeted attacker who already knows their victim's number can compute the
victim's presence_id themselves (that is the feature) and then poison that specific slot.
Unlinkability is not authorization. Publication has to be authenticated, full stop.
It is worth stepping back to see how this article fits the whole. Contact-discovery privacy rests on three independent legs, and this article is the third:
The recurring point of the series is sharpest here: the first two legs are where the beautiful mathematics lives, and it would be tempting to think they are the security. But an enumeration oracle, a Sybil flood, or a poisoned entry defeats the system without touching a single lattice. The boring interfaces are load-bearing. Cryptography earns its keep only inside a system whose plain, protocol-compliant surfaces are also defended.
presence_id, so nothing about
your number sits at rest in a form the server could invert.Next, descend into the engine room: Algorithm: Oblivious Linear Evaluation from Ring-LWE.