Two phases, one key idea.
The protocol splits into a batch matching phase that runs hours or days ahead of time, and a real-time serving phase that answers in nanoseconds. Splitting them is what lets the cryptography be strong: the expensive work happens off the hot path, so nothing has to compromise to fit inside a 100 ms auction window.
The organizing principle: raw identities never move. A buyer turns its targets into opaque tokens; each seller derives the same function over its own database, on its own hardware, and ships back only an unforgeable proof for the records it actually has. Match computation is decentralized to the data, not the data centralized to the computation.
None of this is advertising-specific. We use a buyer/seller framing below because it's concrete, but the two parties can be any data holders — a fraud co-op and a merchant, two lenders, a hospital and a registry. Anything you can reduce to a canonical key, you can match on.
What you can match on.
The protocol doesn't care what an identifier means — only that both sides can normalize it to the same canonical byte string. That's the whole flexibility: one field or a composite of many, the token is computed identically.
| Record type | Fields you key on | What it unlocks |
|---|---|---|
| Traffic | ip (+ user-agent) | Dedup a visitor stream, or enrich it against a partner's IP risk/intent signal. |
| Emails / device | email · phone · maid | Audience overlap, suppression lists, identity resolution. |
| Leads | name · address · phone · email · location | Match and dedup lead lists across partners without exposing either list. |
| Transactions | name · address · phone · location | Tie purchases to an audience, or check a transaction against a fraud set. |
A single field is the simple case. For a multi-field record — a lead or a transaction — you normalize each field and concatenate them into one canonical key, so the whole record collapses to a single token exactly like an email would. (Partial matching, e.g. name+phone or name+address, is just several derived keys checked in parallel.)
Roles and notation.
- Buyer — holds the target list, mints campaign keys and target tokens, verifies proofs, activates bidding.
- Token distribution service — routes opaque token sets buyer → sellers. Cannot decrypt, reverse, or interpret them. Never touches a user database, never sees a match.
- Seller / publisher — holds a user database, runs matching locally, emits proofs of possession for matched users only.
Pre-auction matching.
Runs decoupled from real-time bidding, so there are no latency constraints on the crypto. This phase produces the token set every seller will match against.
Campaign key generation
For each campaign the buyer draws a fresh key from a CSPRNG. It is unique per campaign and destroyed at campaign end — that scoping is what kills cross-campaign linkage later.
Target token generation
Each target identity becomes a token T. The output looks like a random 256-bit value and reveals nothing about the identity without k.
Token distribution
The buyer ships the token set plus a campaign_id through the distribution service to participating sellers. The service forwards opaque bytes; no target identities are ever transmitted.
Proof of possession.
Everything here happens inside the seller's infrastructure boundary. No user data leaves the seller's environment at any point in this phase.
Key receipt
The campaign key k reaches each participating seller over a secure channel. (Alternative embodiments derive it via Diffie–Hellman, removing explicit transmission.)
Token set indexing
The seller builds a hash table over the token set for O(1) lookup. A 50M-target campaign is ~1.6 GB at 32 bytes/token — O(N) memory, O(N) build.
Parallel database scan
For each of its M users the seller derives the same function and probes the table. Every HMAC is independent — embarrassingly parallel across cores, servers, or GPUs.
Witness generation
A match isn't an assertion — it's a proof. The witness W binds the identity to the campaign, so it can't be replayed elsewhere, and can't be produced without actually holding the user.
Proof transmission
Sellers with matches return the witness set; sellers without send nothing. Forging a valid witness would require inverting HMAC — infeasible.
Scale & throughput.
Cost is linear: a seller computes M HMACs per campaign, C × M for C concurrent campaigns. Reference workload below is 80M users × 2,000 campaigns = 160B HMAC operations per full cycle.
| Configuration | Throughput | Full cycle |
|---|---|---|
| 64-core CPU server | ~64M ops/s | — |
| Cluster of 10 CPU servers | ~640M ops/s | ~4 min |
| Single NVIDIA H100 | ~7B ops/s | ~23 s |
| 8-GPU node | ~56B ops/s | ~3 s |
Incremental updates. You rarely rerun the full cycle. A new user is C HMACs (one per active campaign) — negligible. A new campaign is M HMACs against the full DB — ~11 ms for 80M users on a single GPU. Between incremental updates and periodic recompute, the match cache is never more than minutes stale.
Real-time serving via Bloom filters.
The batch phase yields a match table: per user, the campaigns it matched. To serve that at impression time, the publisher compiles each campaign's matched IDs into a Bloom filter for the edge.
- 5M matched IDs at a 0.1% false-positive rate ≈ 9 MB; 2,000 campaigns ≈ 18 GB — fits on one edge server or partitions across nodes.
- One probe ≈ 200 ns; 2,000 campaigns ≈ 0.4 ms total — invisible inside a 50–100 ms header-bidding window.
- The ~0.1% false positives fall back to a full cache verification at negligible cost.
Verification & activation.
Proof verification
The buyer recomputes the expected witness and compares. Equality is cryptographic certainty that the seller holds the target.
Qualified seller set
Sellers with valid proofs form a campaign-and-target-specific qualified set.
Bidding activation
In subsequent auctions the buyer restricts bids to the qualified set — spend lands only on inventory that can demonstrably reach the target.
Key destruction
At campaign end k is destroyed. Past witnesses become useless for any cross-campaign tracking.
What actually crosses the wire.
The publisher's user database never leaves its infrastructure. Concretely:
- The raw user database & email addresses
- Every non-matching user record
- The HMAC derivation & match checking
- Bloom filter construction
- Inbound: opaque token set +
campaign_id+ keyk - Outbound: witnesses for matched users only
- Each witness proves possession, reveals no identity
No external party — buyer, distribution service, or network operator — touches the user database at any step. This is architectural, not a policy promise: there is no step in the protocol where user data is transmitted externally.
Metering & settlement.
If raw data never crosses the wire, how does anyone get paid for it — and how does the operator take a cut without becoming the honeypot it just designed away? By splitting the system into two pipes that never touch:
- Raw records, keys, blinding factors
- Stays inside each party's trust boundary
- Operator is never present here
- Usage counts, signed receipts, settlement
- Operator is present here
- Sees that a query happened, never what it was
It's the Stripe arrangement: present in the billing flow, absent from the product. The trick is making the count un-gameable without putting the operator on the live path — which is what pre-minted metering tokens do.
Mint metering tokens
The operator mints signed, single-use metering tokens to the client — prepaid or against a credit line. Minting requires the operator's private key, which never ships in the open-source client, so reading or forking the code doesn't let you print your own.
Spend one per query
Each query carries one token. The provider verifies the operator's signature offline — no live call to the operator, no added latency, no availability bottleneck — and only then serves the response. An honest provider refuses any query without a valid token.
Tri-party receipt
The receipt is now bound three ways: the client signed the query, the provider signed the response, and the operator's signature rides in via the token it minted. Neither party can deny or misreport the query to the other or to the operator.
Redeem & settle
The provider batches spent tokens and redeems them with the operator. That redemption is when usage is counted and the take-rate is applied — asynchronously, off the live path. Actual money movement rides on Stripe Connect or similar; the novel piece is the verifiable count, not moving dollars.
Two business models, sequenced. License the rails for a flat or tiered fee first (simple, no payments build, no disintermediation risk); layer the marketplace take on usage once there's liquidity. Throughout, the operator stays in the metering pipe and out of the data pipe — three signatures on every receipt, zero visibility into the bytes.
Security properties.
T is a one-way function of the identity. Without k, the token reveals nothing recoverable.
Campaign-scoped keys make precomputed tables useless — an attacker would need a fresh table per campaign across a 256-bit keyspace.
Producing a valid witness without the identity means inverting HMAC or finding a collision — both infeasible.
Unique, destroyed-after-use keys mean witnesses from different campaigns can't be correlated.
Binding campaign_id into the witness stops reuse across campaigns.
The distribution service routes opaque bytes; no intermediary performs or can influence the match.
Scope & primitives.
This is tuned for the operations where cryptography is cheap: matching, membership checks, dedup, enrichment. General joint analytics over arbitrary SQL needs heavy MPC — that's where centralized clean rooms still win, and we cede it. The line: we do the checks and the matches; the clean rooms do the spreadsheets.
The hash function is swappable for any equivalent primitive — BLAKE3, SHA-3, KMAC, truncated HKDF-Expand. Acceleration can use GPUs, FPGAs, or ASICs given the embarrassingly parallel derivation. And the matching node can run inside a confidential-computing enclave (SGX, SEV, TrustZone) on the publisher's own hardware where attestation is required. The architectural invariant never changes: computation on the data owner's hardware, with their data, under their control.
FAQ.
The objections an engineer actually raises, answered directly.
If both sides compute HMAC(k, …), doesn't the key holder learn the other side's data? +
Couldn't a party that holds k just brute-force the token space? +
What exactly is the difference between the HMAC mode and the OPRF mode? +
How do you match a multi-field record like a lead or a transaction? +
normalize(name) || normalize(phone) || normalize(zip) || normalize(addr). The record collapses to a single token. If you want fuzzy or partial matching (name+phone or name+address), you derive several keys per record and check each in parallel.What does “normalize” actually have to do? +
What about typos and formatting drift — is this only exact match? +
Can a seller learn which of my targets it didn't match? +
Doesn't sending the whole token set leak the size of my audience? +
What stops a seller from just claiming it has a user it doesn't? +
W = HMAC(k, normalize(ID) || campaign_id), which the buyer recomputes and checks. Producing a valid witness without actually holding the identity means inverting HMAC — infeasible. “Yes” is no longer an assertion; it's a proof.Can a witness from one campaign be replayed against another? +
campaign_id is bound into the witness, and each campaign uses a fresh key that's destroyed afterward. A witness is only valid for the exact campaign it was generated under.How is this different from a clean room? +
Why not just exchange SHA-256 hashes of emails like everyone else? +
H(email) is a privacy fig leaf; keyed derivation isn't.