The Server Helped, and Never Saw: A Field Guide to Oblivious Pseudorandom Functions
How a server applies its secret key to your secret input without ever seeing it: OPRF, VOPRF, and POPRF (RFC 9497), the engine of OPAQUE and Privacy Pass.
Permalink1. The Server Hardened Your Password Without Ever Seeing It
You want to make a stolen password database worthless. The standard defense is to key every stored password with a secret the attacker cannot exfiltrate -- a "pepper" that lives only in your server's memory -- so a dumped hash is uncrackable without also breaking the keyholder. This is the logic behind server-assisted password hardening, and it is exactly what OPAQUE was designed to formalize [3].
But look closely at the obvious way to apply that key. To hash a password under the pepper, the server has to be handed the password. That is the one thing you were trying not to do. The service that hardens your secret must first be shown your secret. It feels like a law of nature: to key a value, you have to see the value.
It is not a law of nature. Here is the whole resolution, and it fits in four lines.
The client hashes its password to a point on an elliptic curve, . It picks a fresh uniformly random scalar and sends not but , a uniformly random point. The server multiplies by its secret key: . The client divides the blind back out to get , and hashes that: .
Now state exactly what the server saw. It saw : a uniformly random point on the curve, and nothing else. It never saw the password. It never saw the output. It applied its key to a value it could not read, and handed back a value it could not read either.
A two-party protocol that computes where the server holds the key , the client holds the input , and when the protocol finishes the server has learned neither nor the output, while the client has learned only and nothing about [1].
A keyed function whose outputs are indistinguishable from those of a truly random function to anyone who does not hold . It is the "keyed lock": only the keyholder can compute it, and its outputs reveal nothing about the key [1].
The fresh, uniformly random scalar that the client multiplies into before sending it. Because is uniform and independent of the input, the transmitted point is itself uniform and carries no information about [1].
Diagram source
sequenceDiagram
participant C as Client (holds input x)
participant S as Server (holds key k)
Note over C: P holds HashToGroup(x)
Note over C: pick fresh random blind r
Note over C: blindedElement holds r times P
C->>S: send blindedElement, a uniform random point
Note over S: evaluatedElement holds k times blindedElement
S->>C: return evaluatedElement
Note over C: divide out r to recover k times P
Note over C: output holds Hash of x with that point The oblivious PRF applies a secret key to a secret input so that neither party learns the other's secret. Everything else in this guide is a variation on that one move, or a story about what happens when one half of it is done wrong.
That two-line trick is not a party piece. The same two lines are how Privacy Pass hands you a token that a server can validate but cannot link back to you [4]; how Google's Password Checkup tells you a credential appears in a breach corpus without Google learning the credential [5]; and how two organizations compute the intersection of their customer lists without either revealing its list [6]. Replace "the component that used to have to see the secret" with a blind, verifiable, rate-limited oracle that does not, and you have described every one of these systems at once.
Because the move is always the same, you can interrogate any deployment with the same three questions. Is it blind? Does the server ever see the input, or link two evaluations of it? Which key? Can the client prove the answer was produced by the one key the server committed to? How many asks? How many blind evaluations can one key answer before an attacker can reconstruct it? Hold onto those three questions. They are the spine of this article, and by the end you will see that every named failure in the primitive's history is one of them answered wrong.
The 2HashDH shape we just wrote by hand is the actual construction that ships today [7]. If it feels like Diffie-Hellman run one-sided, that is exactly right: Part 18 argued that nobody has broken the discrete logarithm, and this is its constructive twin -- the same group structure, run so that a server applies a secret rather than agreeing on one. If you are wondering why we harden passwords at all, Part 12 is the prerequisite. Here we take the hardening as given and ask how a server can perform it blind.
// STRUCTURAL MODEL, NOT REAL CRYPTO. It shows only the SHAPE of blind/evaluate/unblind.
// Model an elliptic-curve group of prime order q: a "point" is an integer mod q,
// and "scalar multiplication" scalar*point is multiplication mod q. BigInt avoids overflow.
const q = 2147483647n; // a prime group order (toy)
const mod = (a) => ((a % q) + q) % q;
const scalarMul = (s, point) => mod(s * point);
function invScalar(a) { // inverse of a scalar mod q (q prime), via Fermat
let r = 1n, b = mod(a), e = q - 2n;
while (e > 0n) { if (e & 1n) r = mod(r * b); b = mod(b * b); e >>= 1n; }
return r;
}
const k = 1337n; // SERVER secret key (client never learns it)
const Hx = 991n; // H(x): the hashed input, as a group element
const r = 42424243n; // CLIENT fresh random blind
const blinded = scalarMul(r, Hx); // client SENDS r*H(x): looks uniformly random
const evaluated = scalarMul(k, blinded); // server returns k*(r*H(x))
const unblinded = scalarMul(invScalar(r), evaluated); // client removes r: recovers k*H(x)
const direct = scalarMul(k, Hx); // what F would be if the server had seen x
console.log('server saw = ' + blinded.toString() + ' (independent of H(x)=' + Hx.toString() + ')');
console.log('client recovered = ' + unblinded.toString());
console.log('equals direct k*H(x) = ' + (unblinded === direct)); Press Run to execute.
That trick is forty years old, and for its first two decades it did not even have a name. To understand why it is secure, and why there turned out to be three versions of it, we have to start where the blinding started: in 1982, with digital cash.
2. Blinding, Before It Had a Name
The move is older than the problem it now solves. In 1982, David Chaum wanted untraceable digital cash: a way for a bank to sign a coin without learning which coin it signed, so the coin could later be spent without the bank linking it back to the withdrawal. His solution was blinding [2]. The client multiplies its message by a random factor, the bank signs the blinded message under its RSA key, and the client divides the factor out to recover a valid signature on the original. Blind, let the keyholder act, unblind. Structurally, that is the exact template an OPRF runs on. Chaum's 1982 construction used multiplicative blinding in an RSA group, and its output was a publicly verifiable signature. The OPRF performs the same blind-act-unblind dance in a Diffie-Hellman group, and its output is a pseudorandom value only the querier can read. Part 14 covers the RSA lineage in full.
So if the trick existed in 1982, why did the OPRF take until 2005 to be named and 2023 to be standardized? Because Chaum's version produced the wrong kind of output, and the right kind needed a second ingredient that did not exist yet.
Watch the shape of what Chaum built. A blind signature is public: anyone with the bank's public key can verify it. That is perfect for a coin you want a merchant to check, and exactly wrong for a pepper you want no one to be able to reproduce. To harden a password, you do not want a verifiable signature on it; you want a pseudorandom value that only the keyholder could have produced and only the querier can read.
Chaum had the blinding. He did not have a pseudorandom thing to blind. This is why "Blind RSA," standardized decades later as RFC 9474, is not an OPRF but its publicly verifiable cousin -- a distinction that will matter enormously when we reach Privacy Pass [8].
The second ingredient arrived in 1997. Moni Naor and Omer Reingold built a pseudorandom function out of pure number theory: computed as a single group exponentiation whose exponent is a subset-product of the key, , provably secure under the Decisional Diffie-Hellman assumption [9]. What mattered was not the security proof but the shape. Because the function was built from group exponentiations, it had enough algebraic structure that a blinded input could pass through it and come out blinded -- the homomorphic property that makes oblivious evaluation possible at all. A random hash function scrambles its input beyond recovery; an algebraically structured PRF lets the blind survive the trip.
Now the two ingredients were on the table. In 2005, Michael Freedman, Yuval Ishai, Benny Pinkas, and Omer Reingold fused them, gave first constructions, and coined the term: the Oblivious Pseudorandom Function [6]. They were not thinking about passwords. They wanted private keyword search and private set intersection -- letting one party query another's database, or intersect two sets, without either side revealing its contents.
The abstraction they named is the one we still use: transplant Chaum's blinding out of RSA, point it at a Diffie-Hellman-structured PRF, and you get a value that is pseudorandom rather than a signature, and blind rather than seen. RFC 9497 standardizes multiplicative blinding, the variant described here: its Blind step computes blindedElement = blind * inputElement, and that multiplicative form is what ships.
Diagram source
flowchart LR
A["1982 Chaum blinding in RSA"] --> B["1986 Fiat-Shamir transform"]
B --> C["1992 Chaum-Pedersen DLEQ proof"]
C --> D["1997 Naor-Reingold algebraic PRF"]
D --> E["2005 FIPR names the OPRF"]
E --> F["2009 Jarecki-Liu efficient DH OPRF"]
F --> G["2014 2HashDH, the workhorse"]
G --> H["2018 Privacy Pass VOPRF"]
H --> I["2022 3HashSDHI POPRF"]
I --> J["2023 RFC 9497 standard"]
J --> K["2025 RFC 9807 OPAQUE"] Two other pieces on that timeline were quietly waiting to become load-bearing. In 1986, Fiat and Shamir showed how to turn an interactive proof into a one-shot, non-interactive one by replacing the verifier's random challenge with a hash of the transcript [10]. In 1992, Chaum and Pedersen gave a zero-knowledge proof that two discrete logarithms are equal [11]. Neither was about OPRFs. Both become the machinery of verifiability when, twenty-six years later, Privacy Pass needs a client to check which key the server used. Keep them in view; they return in Section 5.
The problem was defined and it had a name. What it did not have was a construction cheap enough to ship. And the first one that looked practical turned out to have a slow poison built into it.
3. The First Construction That Got Weaker With Use
Naming a primitive is not the same as having a recipe worth shipping. The constructions that came with the 2005 definition were general and powerful and, for a deployed service, awkward. The oblivious-transfer and oblivious-polynomial-evaluation approaches were round-heavy. The Naor-Reingold-based ones inherited the PRF's per-bit structure: they consumed the input one bit at a time, so their cost scaled with the input length rather than staying flat. The linear-in-input-length cost of the early Naor-Reingold-style OPRFs is an inference from that PRF's per-bit construction rather than a benchmark quoted from the 2005 paper; treat it as a structural signpost, not a measured figure. For private set intersection in a research prototype, fine. For a server answering millions of blind queries a second, not yet.
The efficient turn came in 2009. Stanislaw Jarecki and Xiaomin Liu built the first OPRF that was both fast and committed -- verifiable -- in a prime-order group [12]. Instead of the exponentiation-per-bit structure, it used a Dodis-Yampolskiy-style PRF of the form : the server inverts in the exponent, so evaluation is a single group operation regardless of input length.
And because the construction committed to the key, the client got assurance not just that some key was applied but that it was the server's key. In the language of our three questions, this one answered Q1 and Q2 at once. It looked like the deployable OPRF.
Then look at the assumption it rested on. Security of the family reduces to a q-strong-Diffie-Hellman assumption: hardness holds as long as the attacker cannot exploit a growing collection of key-dependent samples. And three years earlier, in 2006, Jung Hee Cheon had shown precisely how to exploit them. Cheon's analysis proved that the strong-Diffie-Hellman family weakens as the attacker obtains more samples tied to the same secret -- security degrades with the number of queries [13].
Sit with what that means for an OPRF specifically. An OPRF's entire job is to be offered as a service: to answer blind query after blind query, indefinitely. But every answer this construction gave was another key-dependent sample, and Cheon's result said each sample chipped away at the secret. The primitive got weaker the more it was used. A lock that loosens every time you turn the key is a strange thing to install on a vault you intend to open a billion times.
This is worth marking as special: it is the only true assumption-level break in the whole story. Everything else that supersedes something in the lineage does so because it is cheaper, or answers one more question. Only here does a construction fall because the mathematical ground under it gave way. The strong-Diffie-Hellman assumption was the wrong foundation, and no engineering on top could fix a foundation that eroded by design.
The fix was not a cleverer attack defense. It was to stop building on an assumption that decays -- to hash the input to one group element and re-base the whole construction on an assumption that does not weaken sample by sample. That single stroke produced the workhorse that every deployment now runs, and it is where the OPRF stops being a research object and becomes infrastructure.
4. Moving the Server From Trusted to Blind
Here is the whole next fifteen years compressed into one sentence: each generation moves the server one step from "must be trusted, sees the secret" toward "blind, verifiable, query-limited," and -- the part that surprises people -- most of those steps are additive. A later construction rarely throws out its predecessor. It bolts one more guarantee onto it. To read the lineage correctly you have to separate two kinds of arrow: superseded by, a genuine replacement, and extended by, an additive answer to one more question with the old construction still running underneath.
The workhorse: 2HashDH
In 2014, Stanislaw Jarecki, Aggelos Kiayias, and Hugo Krawczyk wrote down the construction we built by hand in Section 1: [7]. Hash the input into the group with , exponentiate by the key, hash out with .
It is round-optimal -- one message each way, and you provably cannot do it in fewer. And its security rests on the One-More Gap Diffie-Hellman assumption rather than the strong-Diffie-Hellman family, which is the whole point: One-More Gap security is not a function of a growing pile of key-dependent samples, so it sidesteps the Cheon erosion that doomed the 2009 construction. "One-More" assumptions bound how much an attacker can produce relative to how many genuine evaluations it was given -- it cannot get "one more" than it was handed. This framing is what breaks the dependence on a growing q-tuple that made the strong-Diffie-Hellman family decay under Cheon's attack. Because the input is hashed to a single group element, cost is flat in input length. Jarecki, Kiayias, Krawczyk, and Jiayu Xu put it on composable footing two years later [14].
This is the OPRF that ships. But notice what it does not do. It answers Q1 -- the server never sees the input -- and nothing more. The client has no way to tell which key the server used. A malicious server can quietly assign a different key to each client and, later, use the distinct responses to tell which client was which. In an anonymity system that is fatal: the primitive designed to hide you becomes the tag that identifies you. 2HashDH left Q2 open. The next generation did not replace it; it extended it.
Verifiability you can batch: the VOPRF
In 2018, Alex Davidson, Ian Goldberg, Nick Sullivan, George Tankersley, and Filippo Valsorda shipped Privacy Pass, and with it the deployed VOPRF [4]. The addition is precisely the 1992 machinery we set aside. The server publishes a commitment to its key -- a public key -- and alongside each response attaches a Chaum-Pedersen proof that the same relates its public key and its answer, made non-interactive by Fiat-Shamir [11], [10]. The client verifies before it accepts. Answering Q2, at last.
The engineering move that made it deployable was batching: many evaluations fold into a single proof via a random linear combination, so a client redeeming a thousand tokens verifies one proof, not a thousand [4]. That is what let Privacy Pass run at content-delivery-network scale. That scale is what surfaced the primitive's real-world failure modes -- key consistency, tagging, rotation, double-spend -- that no research prototype had stress-tested.
The VOPRF still left one gap. Its verifiability binds one key to one public key, which means one key cannot cleanly serve many domains, tenants, or rotation epochs -- to separate them you would need a different key per context, and a different public key per context is, again, a tag. What was missing was a way to fold a public label into the evaluation without changing the key.
Partial obliviousness: heavy, then light
The first answer came early and expensive. In 2015, Adam Everspaugh, Rahul Chatterjee, Samuel Scott, Ari Juels, and Thomas Ristenpart built Pythia, the first partially oblivious PRF: a public per-tenant tweak let one server key serve many domains and support bulk key rotation, while the password stayed oblivious [15]. It worked, but it needed bilinear pairings -- special curves, heavier operations, a bigger dependency. Powerful, but not the thing you reach for by default.
The light answer came in 2022. Nirvan Tyagi, Sofia Celi, Thomas Ristenpart, Nick Sullivan, Stefano Tessaro, and Christopher Wood built 3HashSDHI: the first POPRF that needs no pairings [16]. It folds the public input info into the key by inversion. The server computes and returns , with a DLEQ proof against the tweaked key. The private input stays oblivious; info is public and known to both sides. And it runs, in the authors' words, as fast as the standards-track 2HashDH protocol [16].
Here is the detail worth savoring. To fold in info, 3HashSDHI uses key inversion -- where -- which is the same Dodis-Yampolskiy inversion structure Jarecki and Liu used back in 2009 [16], [12]. The mechanism that got the field burned by Cheon's attack was never actually wrong. What was wrong was the assumption it had been analyzed under. Tyagi and co-authors re-proved the same inversion under a One-More Gap assumption that reduces to plain discrete log in the algebraic group model, and the old idea walked back onto the field rehabilitated [16].
The lesson is one every protocol designer should internalize: a construction can be sound while the security assumption used to justify it is not, and better analysis can resurrect a "broken" mechanism untouched.
Diagram source
flowchart TD
G0["Gen 0: Chaum blinding plus Naor-Reingold PRF"] -->|superseded| G1["Gen 1: FIPR names the OPRF, generic"]
G1 -->|superseded on cost| G2["Gen 2: Jarecki-Liu committed DH OPRF"]
G2 -->|superseded by Cheon erosion| G3["Gen 3: 2HashDH, answers Q1"]
G3 -->|extended, adds Q2| G4["Gen 4: VOPRF with batched DLEQ"]
G4 -->|extended, adds public info| G5["Gen 5: POPRF 3HashSDHI"]
G5 --> RFC["RFC 9497: three modes, one interface"]
G3 -.->|parallel mechanism| K["KKRT16 OT-extension OPRF, for PSI"] The other lineage, in one breath
Not every OPRF is a Diffie-Hellman OPRF. In 2016, Vladimir Kolesnikov, Ranjit Kumaresan, Mike Rosulek, and Ni Trieu built a completely different one: a batched OPRF from oblivious-transfer extension, symmetric-key rather than public-key, and enormously fast in bulk [17]. It is not verifiable, not publicly reusable, and only semi-honest secure, but it intersects two sets of a million () elements each in about 3.8 seconds [17], and OT-extension OPRFs of this kind were for years the throughput workhorse of high-volume semi-honest PSI, until VOLE-based PSI overtook them in communication for very large sets [18]. The takeaway for a practitioner: "OPRF" names a goal, and at least two very different mechanisms reach it. Do not assume every system labeled OPRF is the standardized Diffie-Hellman family.
| Approach | Year | Key idea | Questions answered | Fate |
|---|---|---|---|---|
| Chaum blind signature | 1982 | Blind, sign, unblind in RSA | none (a signature, not a PRF) | Ancestor; became Blind RSA [2] |
| FIPR generic OPRF | 2005 | Obliviously evaluate an algebraic PRF; names "OPRF" | Q1 | Superseded on cost [6] |
| Jarecki-Liu | 2009 | , committed | Q1 + Q2 | Superseded: strong-DH erodes with use [12] |
| 2HashDH | 2014 | ; One-More Gap CDH | Q1 | Active core of RFC 9497 [7] |
| VOPRF | 2018 | 2HashDH + batched Chaum-Pedersen DLEQ | Q1 + Q2 | Active; extends 2HashDH [4] |
| Pythia | 2015 | Verifiable partially oblivious PRF, pairings | Q1 + Q2 + info | Superseded by 3HashSDHI [15] |
| 3HashSDHI | 2022 | ; no pairings | Q1 + Q2 + info | Active POPRF of RFC 9497 [16] |
| KKRT16 | 2016 | Batched OPRF from OT-extension (symmetric) | Q1 (relaxed, semi-honest) | Active, niche: high-volume PSI [17] |
Twenty years of constructions now answered all three questions. But each was a separate paper with its own parameters, its own group choices, and its own foot-guns. The last move was not a new construction. It was a standard that unified them and, unusually for a specification, wrote the one unavoidable cost down as a number.
5. RFC 9497: Three Modes, One Interface
In December 2023, the IRTF's Crypto Forum Research Group published RFC 9497, written by Alex Davidson, Armando Faz-Hernandez, Nick Sullivan, and Christopher Wood [1]. It did what twenty years of individual papers had not: it turned the whole family into a single misuse-resistant interface, fixed five ciphersuites, mandated the safety checks, and -- the honest move no construction paper had made -- wrote the one attack it cannot design away down as a number.
Start with the interface, because all three modes share it. Over a prime-order group with generator , and using the hash-to-group machinery of RFC 9380, the protocol is three calls [1], [19]:
Blind(x):
blind <- random nonzero scalar # fresh, uniform, per call
blindedElement <- blind * HashToGroup(x) # a uniformly random point
if blindedElement == Identity: abort # element validation
return blind, blindedElement
BlindEvaluate(k, blindedElement, [info]):
OPRF / VOPRF: evaluatedElement <- k * blindedElement
POPRF: t <- k + HashToScalar(info)
evaluatedElement <- t^(-1) * blindedElement
# VOPRF and POPRF also emit a DLEQ proof (see below)
Finalize(x, blind, evaluatedElement, [info]):
N <- blind^(-1) * evaluatedElement # OPRF/VOPRF: = k * HashToGroup(x); POPRF: = HashToGroup(x)^(1/(k+H(info)))
return Hash( len(x) || x || [len(info) || info ||] len(N) || N || "Finalize" )
The server sees only blindedElement, a uniformly random point independent of ; the client recovers exactly one PRF value and nothing about [1]. That is the same 2HashDH we have been circling, now with every byte of the output hash pinned down. HashToGroup is RFC 9380 hash-to-curve, the subject of Part 16. Its defining property is that the output point has an unknown discrete logarithm: no one, including the client, knows a scalar relating it to the generator. That unknown-discrete-log property is exactly why blinding hides the input and why the server cannot invert the map.
The three modes are three answers
This is where the second shift in understanding lands. The three modes are not three products. They are three points on one axis, distinguished by exactly which of our questions they answer.
The base OPRF answers Q1 and stops. BlindEvaluate is just , with no proof. The client trusts that some key was applied; it gets no assurance about which. Cheapest mode, smallest messages, right only when a wrong key can hurt no one but the querier [1].
The VOPRF answers Q1 and Q2. The server commits to a public key and attaches a proof; the client verifies before finalizing [1]. The POPRF answers Q1 and Q2 and binds a public info, via the key-inversion tweak. A POPRF with a fixed info collapses back to a VOPRF [1]. Base to verifiable to partially oblivious: blind, then blind-plus-which-key, then blind-plus-which-key-plus-public-tweak.
Three modes are three answers to three questions. OPRF answers "is it blind?" VOPRF adds "which key?" POPRF adds "and bind this public label." You do not choose a mode by taste; you choose it by counting how many of the three questions your threat model forces you to answer.
Diagram source
flowchart LR
Q1["Q1: is it blind?"] --> OPRF["OPRF: blind only"]
OPRF -->|add DLEQ proof against one committed key| VOPRF["VOPRF: blind plus which key"]
VOPRF -->|fold in a public info tweak| POPRF["POPRF: blind, which key, public label"] Verifiability, in depth
The proof is the 1992 machinery finally doing its job. A Chaum-Pedersen DLEQ proof establishes that two discrete logarithms are equal: that the relating to is the same relating evaluatedElement to blindedElement [1], [11]. If the server used any other key, no valid proof exists. Fiat-Shamir turns the interactive proof into a single non-interactive object the client checks offline, so verifiability costs no extra round [10]. The DLEQ proof is a sigma protocol made non-interactive by Fiat-Shamir -- the same transform behind Schnorr signatures, which Part 17 covers. The client recomputes the challenge as a hash of the transcript and checks one equation; there is no back-and-forth with the server.
A non-interactive zero-knowledge proof that two discrete logarithms are equal -- here, that the server's public key and its response were produced under the same secret key. It is what makes a VOPRF verifiable, and via a random linear combination it can prove a whole batch of responses with one object [1], [11].
Batching is what makes verifiability affordable. Many evaluations fold into one proof through a random linear combination, so a client redeeming a batch verifies a single object, not one proof per element [1], [4]. That is one proof object per batch, not -- but verifying it still forms one linear combination across the batch, so the win is skipping separate proof checks, not making verification constant-time in the batch size. That is what let anonymous-token issuance scale.
There is a subtlety that quietly carries the entire security of any anonymity system built on a VOPRF: verifiability is only as strong as key consistency. Proving the server used the key in its public key is worthless if the server hands a different public key to every client, because then the unique key is itself a deanonymizing tag. Verifiability protects you only when everyone is served under one transparently published key [1]. We will return to this in the practical guide; for now, note that "add a proof" and "pin one key" are two requirements, not one.
Diagram source
sequenceDiagram
participant C as Client (input x)
participant S as Server (key k, public pkS)
Note over C: blindedElement holds blind times HashToGroup(x)
C->>S: send blindedElement
Note over S: evaluatedElement holds k times blindedElement
Note over S: GenerateProof binds pkS and the answer to one k
S->>C: return evaluatedElement and DLEQ proof
Note over C: VerifyProof against pkS, abort on failure
Note over C: unblind, then Finalize with domain separation The machinery that makes it safe
A specification earns "misuse-resistant" in the details, and RFC 9497's details are where the accumulated scar tissue lives. Hashing and group operations are delegated, not reinvented: HashToGroup and HashToScalar come from RFC 9380, and the groups themselves from RFC 9496 -- ristretto255 and decaf448 -- or the NIST curves [1], [19], [20]. ristretto255 and decaf448 are prime-order abstractions built over Edwards curves whose raw order has a small cofactor. They remove the cofactor pitfalls that Part 15 covers -- the small-subgroup and equivalent-point traps -- which is exactly why RFC 9497 prefers them and why it mandates rejecting the identity element on deserialization.
Three requirements are worth naming because dropping any one silently breaks the guarantee:
The domain separation is the discipline Part 13 argued for, applied here: the output hash binds the input length, the input, the point, and a fixed label, so no two contexts can be coaxed into producing the same value. Inputs are bounded -- shorter than bytes, with a two-byte length prefix -- so longer inputs must be pre-hashed [1].
// STRUCTURAL MODEL. Shows why Finalize binds lengths + a "Finalize" tag, not just N.
function toyHash(str) { // a tiny non-cryptographic stand-in
let h = 5381 >>> 0;
for (const ch of str) h = (Math.imul(h, 33) ^ ch.charCodeAt(0)) >>> 0;
return (h >>> 0).toString(16).padStart(8, '0');
}
// length-prefixed encoding: len(x) || x, so boundaries are unambiguous
function lp(s) { return String(s.length).padStart(4, '0') + s; }
// RFC 9497 Finalize binds: len(x) || x || len(N) || N || "Finalize"
function finalize(x, N) { return toyHash(lp(x) + lp(N) + 'Finalize'); }
// Without length prefixes, ("ab","c") and ("a","bc") would collide. With them, they don't:
console.log('naive ab|c =', toyHash('ab' + 'c'));
console.log('naive a|bc =', toyHash('a' + 'bc'), '(collides above)');
console.log('finalize ab,c =', finalize('ab', 'c'));
console.log('finalize a,bc =', finalize('a', 'bc'), '(distinct)'); Press Run to execute.
Finally, the honesty. RFC 9497 section 7.2.3 states outright that every BlindEvaluate is a static-Diffie-Hellman oracle sample, and quantifies the erosion: the best-known attacks reduce the group's security by bits, where is the number of calls [1], [21], [13]. That is the domesticated ghost of the very failure that killed the 2009 construction -- no longer a surprise, but a budget line. We give it the full treatment it deserves in Section 8. First: who actually runs this, and what looks like it but is not.
6. Who Runs This Today
The OPRF is quietly load-bearing. You almost certainly used one this week without knowing it, because it lives one layer below the protocols you do know. Here are the four deployed consumers, each at "exactly where and why it calls the OPRF," and each paired with the look-alike an expert must not confuse it with.
OPAQUE, standardized as RFC 9807 in July 2025. This is the flagship. OPAQUE is an augmented password-authenticated key exchange: the server authenticates you by password without ever seeing the password, even at registration, and with no cleartext salt an attacker could precompute against [22], [3]. The OPRF is the tool that makes the password oblivious, and it is what defeats precomputation attacks -- there is nothing static to build a rainbow table against.
RFC 9807 derives a per-user OPRF key from a single seed, keyed by each credential_identifier, so a server compromise does not let an attacker enumerate accounts, and it discusses a threshold-OPRF defense that forces an attacker online before any dictionary attack can even begin [22]. The RFC names WhatsApp's end-to-end encrypted backups as a driving use case, and Meta's audited opaque-ke is the reference implementation [22], [23]. If Part 12 explained why offline dictionary attacks are the thing to fear, OPAQUE is the OPRF's answer to them.
Privacy Pass, RFCs 9576, 9577, and 9578, June 2024. The in-scope token type is 0x0001 = VOPRF(P-384, SHA-384); RFC 9578 says plainly that its privately verifiable issuance is based on the OPRF [24], [25], [26]. This is the VOPRF most engineers meet by name. But Privacy Pass is not one thing, and this is the boundary the whole article has been protecting.
Password breach checking. Google's Password Checkup performs a blinded, OPRF-based lookup: it checks your credential against a corpus of breached ones without Google learning the credential [5], [27]. Microsoft's Password Monitor in Edge does something related but not identical, and the difference matters.
OPRF-based private set intersection. OPRF-PSI is one approach among several -- plain Diffie-Hellman PSI, oblivious-transfer and circuit-based PSI, and homomorphic-encryption PSI all compete for different workloads [6]. Even within OPRF-PSI, the two mechanisms from Section 4 occupy different corners: the public-key 2HashDH family when you want few messages and verifiability, and the symmetric KKRT16 OPRF when you want raw throughput over enormous sets [17].
| Consumer | What the OPRF does there | The non-OPRF look-alike to not confuse it with |
|---|---|---|
| OPAQUE (RFC 9807) | Makes the password oblivious; defeats precomputation; per-user keys block enumeration [22] | A plain salted or peppered hash, where the server must see the password |
Privacy Pass token 0x0001 | VOPRF issuance of unlinkable, issuer-verifiable tokens [24] | Token 0x0002 = Blind RSA, a publicly verifiable blind signature (Apple PAT) [8] |
| Google Password Checkup | Blinded OPRF lookup against a breach corpus [5] | HIBP k-anonymity range API: leaks a SHA-1 prefix, not oblivious [29] |
| Microsoft Password Monitor | OPRF hides the query; FHE Labeled PSI does the match [28] | Assuming the OPRF alone does the set intersection |
| OPRF-based PSI | Oblivious set membership via 2HashDH or KKRT16 [17] | DH-PSI, OT/circuit-PSI, and FHE-PSI, which are different approaches |
The production substrates are a short list: Cloudflare's CIRCL in Go, the voprf crate and opaque-ke in Rust, and curve25519-dalek providing the ristretto255 group backend [30], [31], [23], [32]. Four deployments, one primitive, three look-alikes that are emphatically not it. When you actually have to choose, the decision comes down to a small matrix.
7. The Decision Matrix
Given a real problem, each decision has a crisp rule. Here they are, head to head.
Which mode?
The primary choice is the mode, and it falls straight out of the three questions.
| Dimension | OPRF (base) | VOPRF | POPRF |
|---|---|---|---|
| Questions answered | Q1 (blind) | Q1 + Q2 (which key) | Q1 + Q2 + public tweak |
| Extra output | none | DLEQ proof (batchable) | DLEQ proof (batchable) |
Public input info | no | no | yes, |
| Construction | 2HashDH | 2HashDH + DLEQ | 3HashSDHI |
| Assumption | One-More Gap CDH | One-More Gap CDH | One-More Gap SDHI (to q-DL, AGM) |
| Client trust in key | must trust | verifies | verifies |
| Best suited for | trusted-key internal oracle | anonymity systems, OPAQUE | per-tenant, rotation, buckets |
Sources: RFC 9497 sections 3.3.1 through 3.3.3, plus DGSTV18 and TCRSTW22 [1], [4], [16]. The rule: if you trust the server's key by construction and need only obliviousness, use the base OPRF. If any adversarial server could hand you a wrong or per-client key -- anonymity systems, OPAQUE -- use the VOPRF. If you need to bind a public label such as a per-tenant salt, rotation epoch, or rate-limit bucket, use the POPRF, and never put a secret in info.
Which ciphersuite?
Pick the ciphersuite by your query budget, because the group size is what buys you static-Diffie-Hellman headroom (Section 8).
| Ciphersuite | Group | Nominal security | Use when |
|---|---|---|---|
| ristretto255-SHA512 | ristretto255 | 128-bit | Default: prime-order, fast, no cofactor traps |
| decaf448-SHAKE256 | decaf448 | 224-bit | Internet-facing oracle needing static-DH headroom |
| P-256-SHA256 | NIST P-256 | 128-bit | FIPS or interop constraints |
| P-384-SHA384 | NIST P-384 | 192-bit | Higher budget; Privacy Pass 0x0001 uses this |
| P-521-SHA512 | NIST P-521 | 256-bit | Maximum static-Diffie-Hellman headroom |
Source: RFC 9497 sections 4.1 through 4.5 [1]. The rule: default to ristretto255-SHA512. If you expose an unmetered public oracle on a 128-bit group, the erosion argues for decaf448, P-384, or P-521 -- RFC 9497 recommends exactly these for applications that cannot tolerate discrete-logarithm security below 128 bits [1]. Choose P-256-SHA256 only when FIPS or interoperability forces it. And never roll your own group or hash-to-curve; use RFC 9380 and RFC 9496 [19], [20].
Which token cryptography?
If you are issuing anonymous tokens, the choice between the two Privacy Pass token types is a genuine trade-off, not a default.
| Dimension | VOPRF token 0x0001 | Blind-RSA token 0x0002 |
|---|---|---|
| Primitive | OPRF (VOPRF, P-384/SHA-384) | Blind signature (RSA-2048), not an OPRF |
| Verifiability | Issuer-verifiable only (secret key) | Publicly verifiable (RSA public key) |
| Token size | Small | Larger |
| Key rotation | Easy | Heavier (RSA keypair) |
| Flagship deployment | CDN anonymous rate-limiting | Apple Private Access Tokens |
| Spec | RFC 9578, RFC 9497 | RFC 9578, RFC 9474 |
Sources: RFC 9578 and RFC 9474 [24], [8]. The rule: if only the issuer needs to verify tokens and you want small, easily rotated tokens, use the VOPRF token. If a third party must verify a token without contacting the issuer, you need public verifiability, and that means a blind signature -- the Blind-RSA token -- which is not an OPRF at all.
The remaining two, briefly
For private set intersection, choose the public-key 2HashDH OPRF when you want few messages and malicious-secure verifiability, and the symmetric KKRT16 OPRF when you want to intersect enormous sets fast under a semi-honest model [7], [17]. For breach checking, choose an OPRF when query privacy is a hard requirement, and k-anonymity when simplicity and cache-friendliness outweigh the leak of a hash prefix [5], [29].
Every row above assumes one thing worth making explicit, because it is the property the whole family exists to deliver. Against a plain salted or peppered server-side hash, the server must see the secret to key it. That is the single compromise the OPRF removes, and each decision in this matrix is a variation on how much structure you are willing to add on top of "the server never saw it." Which is worth asking: what, precisely, can this primitive prove -- and what can it provably not?
8. What the Assumptions Buy, and What They Cost
If the discrete logarithm has never been broken, what can still go wrong with an OPRF -- and what can never be fixed? The answer separates cleanly into what the primitive provably delivers, what it rests on, and the one price it can never stop paying.
What it delivers. RFC 9497 section 7.1 enumerates the guarantees [1]. Pseudorandomness: for a random key, the output is indistinguishable from a random function. Nonmalleability: you cannot turn one evaluation into another. Verifiability, in the VOPRF and POPRF modes. Partial obliviousness, in the POPRF. And the one worth pausing on -- unconditional input secrecy. Because the blind is fresh and uniform, the point the server receives is uniform and independent of the input, so input secrecy holds even against an adversary with unbounded computation [1]. That is not "secure until someone finds a faster algorithm." It is information-theoretic. The secret was never in the message, so there is nothing for computation to extract.
What it rests on. The OPRF and VOPRF rest on the well-studied One-More Gap Computational Diffie-Hellman assumption; the POPRF on One-More Gap Strong-Diffie-Hellman-Inversion [1], [7], [16]. The POPRF's assumption comes with a load-bearing result: it is shown to be implied by the plain -discrete-logarithm assumption in the algebraic group model [16]. All three are discrete-log-family assumptions believed hard in the groups actually used, so the deployed OPRF is anchored to the same discrete-log problem Part 18 argued has stood unbroken for half a century. That is about as good a foundation as applied cryptography offers.
What it costs, forever. Now the third question comes due. Every BlindEvaluate is, by construction, a static-Diffie-Hellman oracle sample: the server computes for a fixed secret , and Brown-Gallant and Cheon showed that collecting such samples recovers at a cost that drops with the count [1], [21], [13].
The specification does not bury this. It states it:
"Best-known attacks reduce the security of the prime-order group instantiation by log_2(Q) / 2 bits, where Q is the number of BlindEvaluate calls." -- RFC 9497, section 7.2.3
Work the arithmetic and the design pressure becomes concrete. On a 128-bit group, effective key security is bits. Answer queries and you have shed 20 bits, down to 108. Answer and you are at 98. This is not an implementation flaw you can patch; it is a lower bound on the attacker's advantage that no construction in this family removes, because it follows from the primitive doing the one thing it exists to do.
| BlindEvaluate calls | Bits lost, | Effective security on a 128-bit group |
|---|---|---|
| 10 | 118 bits | |
| 20 | 108 bits | |
| 30 | 98 bits | |
| 40 | 88 bits |
Two more facts bound the theory. Round-optimality: two messages is provably the minimum, and 2HashDH achieves it, so there is no efficiency left to win there. The two-message floor is structural: the client must send at least one blinded value and receive at least one response to recover an output, so no protocol of this shape can use fewer round-trips. And two honest open edges. RFC 9497 admits that the exact multi-key, batched protocol the industry actually deploys has no security proof:
"There is currently no security analysis available for the VOPRF protocol described in this document in a setting with multiple server keys or batching." -- RFC 9497, section 7.2.1
The deployed reality -- many issuer keys, batched proofs -- outruns the single-key proof it is built on [1]. And the whole family rests on discrete log, so a scalable quantum computer running Shor's algorithm breaks obliviousness and unforgeability alike. Two of these limits are not settled facts but open frontiers -- and the sharpest one opened with a construction that was broken within a year of being proposed.
9. Where It Is Still Moving
For the single-key, non-batched, classical setting, the theory is essentially closed: round-optimal, constant communication, unconditional input secrecy, a clean assumption reducing to discrete log. The live research is almost entirely about the edges the deployments already stand on.
The post-quantum frontier, which opened with a cautionary tale. Because every deployed OPRF falls to Shor, the sharpest open problem is a post-quantum replacement -- and the first serious attempt is exactly why the field is cautious.
In 2020, Dan Boneh, Dmitry Kogan, and Katharine Woo built the first substantial post-quantum OPRF from supersingular isogenies [33]. Within a year, Andrea Basso, Peter Kutas, Simon-Philipp Merz, Christophe Petit, and Antonio Sanso cryptanalyzed it, giving a polynomial-time attack and a subexponential one that survived the obvious countermeasures -- together breaking the parameters the authors had proposed [34].
A construction proven secure under a named assumption was undone because the assumption itself did not hold. It is the cleanest possible reminder that a proof is only as good as what it reduces to. The isogeny paper actually offered two constructions. Its second one revived the 1997 Naor-Reingold PRF, reinterpreted over commutative group actions (the CSIDH setting) [33], [9]. The oldest algebraic PRF in this story turned out to be the one with a plausible post-quantum future -- a twenty-three-year-old idea made newly relevant by a change of group.
The other post-quantum directions are advancing but heavy. Martin Albrecht, Alex Davidson, Amit Deo, and Nigel Smart gave a round-optimal verifiable OPRF from ideal lattices in 2021 [35]; Albrecht and Kamil Doruk Gur made it "practical-ish and thresholdisable" in 2024, cutting its lattice bandwidth roughly fourfold and adding a threshold variant [36]. And a general framework arrived in 2025: Ward Beullens, Lucas Dodgson, Sebastian Faller, and Julia Hesse compiled quantum-safe OPRFs from secure multi-party evaluation of simple functions, reporting a working construction in about 0.57 seconds with under a megabyte of communication [37].
That is a genuine milestone -- and it is still orders of magnitude heavier than the sub-millisecond classical evaluation it aims to replace. An efficient, verifiable, partially oblivious post-quantum OPRF, matching the classical profile on all three counts at once, does not yet exist.
The proof the industry is running ahead of. Section 8's second pull quote is also an open problem: prove the multi-key, batched VOPRF that Privacy Pass actually deploys [1]. Batching is a well-used heuristic, but the formal guarantee still covers only the single-key case.
Removing the single point of failure. A threshold or distributed OPRF splits the key across several servers so no one of them holds it, which is the structural answer to server compromise. RFC 9807 names exactly this as the defense that forces an attacker online before any dictionary attack can start [22]. Round-optimal, misuse-resistant threshold OPRF specifications are still maturing.
The limits that are policy, not cryptanalysis. Two open problems are engineering rather than mathematics. The static-Diffie-Hellman budget is a permanent tax on any public oracle; the open question is operational -- what query cap and rotation cadence keep a given group above 128 bits -- not whether the bound can be improved, since Brown-Gallant and Cheon are believed essentially tight for generic groups [21], [13].
And verifiability quietly collapses if a malicious issuer hands each user a different public key, so making "one key for everyone" auditable needs key-transparency infrastructure that does not yet ship as a turnkey component [1]. Formally verified constant-time implementations sit in the same category: RFC 9497 mandates constant-time secret operations, but the guarantee is only as strong as the machine-checked code behind it [1].
None of these block you from shipping today. They tell you what to watch. What you ship today has a small, boring set of rules that separate a correct deployment from a broken one -- and unlike the open problems, those rules are entirely settled.
10. The Rules, Made Operational
Here is the whole deployment discipline in a form a reviewer can apply without rereading the RFC. It has three parts: pick two things, always do a handful, and never do another handful.
Pick the mode by the questions. Trust the key by construction: base OPRF. Must verify which key: VOPRF. Must bind a public label: POPRF, and never smuggle a secret into info, which is public by definition [1], [16]. Pick the ciphersuite by the query budget. Default to ristretto255-SHA512; for an internet-facing unmetered oracle move to decaf448, P-384, or P-521; for FIPS or interop use P-256-SHA256. Never roll your own group or hash-to-curve -- use RFC 9380 and RFC 9496 [1], [19], [20].
Diagram source
flowchart TD
Start["Choose an OPRF deployment"] --> A{"Could the server be adversarial about which key it uses?"}
A -->|no, key trusted| B["Base OPRF"]
A -->|yes| C{"Must you bind a public label such as tenant or epoch?"}
C -->|no| D["VOPRF"]
C -->|yes| E["POPRF"]
B --> F{"Unmetered public oracle?"}
D --> F
E --> F
F -->|no| G["ristretto255-SHA512 default"]
F -->|yes| H["decaf448 or P-384 or P-521"] The "never" list is just the failure catalog from the last nine sections, re-read as review rules. Each entry maps to one of the three questions answered wrong.
| Anti-pattern | Why it breaks | Question violated | The fix |
|---|---|---|---|
| Sending the un-blinded input | The server sees the input; obliviousness is gone | Q1 | Always apply the fresh blind before sending [1] |
| A MAC or HKDF "used as an OPRF" | If the server computes it, it saw the input | Q1 | Use a real two-party OPRF; the blind is the point [1] |
| Reusing the blind across calls | Two evaluations become linkable, and the input can leak | Q1 | Fresh, uniform blind every call [1] |
| Skipping element validation | Malformed points enable key-probing attacks | Q1/Q3 | Reject off-curve, out-of-range, and identity elements [1] |
| Dropping or unsoundly batching the DLEQ | A malicious server can use a per-client key and tag you | Q2 | Verify the proof; combine batches soundly [1], [4] |
| A per-user public key | The unique key is itself a deanonymizing tag | Q2 | Pin one transparently published key for all [1] |
| Non-constant-time secret ops | The key leaks through timing | Q3 | Constant-time group ops, proof, and evaluate [1] |
Treating POPRF info as secret | info is public by definition; obliviousness assumed of it fails | Q1 | Put only public labels in info [16] |
| An unmetered oracle on a 128-bit group | The key erodes by bits, unbounded | Q3 | Rate-limit, rotate, or use a bigger group [1], [21] |
| Using an OPRF where you need public verifiability | A third party cannot verify a VOPRF token | (wrong primitive) | Use a blind signature (Blind RSA), not an OPRF [8] |
// STRUCTURAL MODEL. Shows why reusing the blind across calls destroys unlinkability.
// Same additive group model: a "point" is an integer mod q, scalar*point is mod q.
const q = 2147483647;
const mod = (a) => ((a % q) + q) % q;
const scalarMul = (s, point) => mod(s * point);
const Hx = 991; // H(x): the SAME secret input, evaluated twice
const blindedWith = (r) => scalarMul(r, Hx); // what goes on the wire: r*H(x)
// BAD: reuse the same blind r for both calls
const rFixed = 55555;
const wire1_bad = blindedWith(rFixed);
const wire2_bad = blindedWith(rFixed);
console.log('reused blind -> wire equal? ', wire1_bad === wire2_bad, '(server links the two as one input)');
// GOOD: a fresh uniform blind each call
const wire1_ok = blindedWith(37337);
const wire2_ok = blindedWith(88123);
console.log('fresh blind -> wire equal? ', wire1_ok === wire2_ok, '(server cannot link them)'); Press Run to execute.
Most of those "nevers" are the same handful of mistakes phrased for different audiences: do not let the server see the input, do not let it pick a per-client key, and do not let it answer unlimited queries. It is worth answering the questions engineers actually ask about them head on.
Want to run a real one?
The voprf Rust crate and Cloudflare's CIRCL both implement all three modes against the RFC 9497 test vectors [31], [30]. A good first exercise: run the base OPRF, print the blinded element on two evaluations of the same input, and confirm the two wire values differ because the blind is fresh. Then switch to the verifiable mode, tamper with one byte of the server's response, and watch VerifyProof reject it. That single afternoon teaches more about Q1 and Q2 than any diagram.
11. Questions Engineers Actually Ask
Frequently asked questions about oblivious PRFs
Isn't an OPRF just an HMAC the server computes for me?
No, and the difference is the whole primitive. If the server computes HMAC(k, x) for you, it saw x. That is exactly the compromise the OPRF removes: it applies the key to a blinded value it cannot read, so it never sees the input or the output [1]. An HMAC that the server evaluates on your plaintext input is not oblivious by any definition.
OPRF or VOPRF -- does the difference actually matter?
In any setting with an untrusted server, yes, decisively. The base OPRF answers only "is it blind?" Without the DLEQ proof, a malicious server can hand each client a different key and later use the distinct responses to tell clients apart -- deanonymizing the very users an anonymity system is meant to protect [1], [4]. The VOPRF's proof, plus one transparently pinned public key, is what closes that gap.
Why is there a limit on how many times I can call it?
Because every BlindEvaluate is a static-Diffie-Hellman oracle sample, and collecting samples recovers the key at a cost that drops by bits after calls [1], [21], [13]. It is inherent to offering a Diffie-Hellman oracle, not an implementation defect. Rate-limit, rotate keys, or use a larger group to keep the loss inside budget.
Is Privacy Pass an OPRF?
Half of it. Privacy Pass token type 0x0001 is a VOPRF, which is an OPRF. Token type 0x0002 is Blind RSA -- a blind signature, not an OPRF -- and it is the type behind Apple's Private Access Tokens, the most visible deployment [24], [8]. So "Privacy Pass equals OPRF" is wrong; one of its two token types is a different primitive entirely.
Is Google's Password Checkup the same as Have I Been Pwned?
No. Checkup performs a blinded, OPRF-based lookup, so Google does not learn the credential being checked [5]. Have I Been Pwned's range API uses k-anonymity: you send the first five hex digits of a SHA-1 hash and receive the whole bucket to search locally [29]. That leaks a hash prefix by design. It is simpler and cache-friendly, but it is not oblivious.
Can I reuse the blind to save a random draw?
No. Reusing the blind across calls makes two evaluations of the same input produce the same on-wire value, so the server can link them, and in some settings it can leak the input [1]. The blind and the proof nonce must be freshly and uniformly drawn every single call. This is non-negotiable.
Is the deployed OPRF quantum-safe?
No. Every RFC 9497 ciphersuite rests on discrete-log hardness, which Shor's algorithm breaks, so a scalable quantum computer defeats both obliviousness and unforgeability [1]. Post-quantum OPRFs are an open frontier: the first serious isogeny construction was broken within a year [33], [34], and lattice and MPC candidates remain far heavier than classical evaluation [36], [37]. And to preempt the related question: no, a POPRF's info is not secret -- it is public by definition [16].
Read back across the whole story and the shape is unmistakable. Every named failure was one of three questions answered wrong. FIPR's early constructions answered "is it blind?" but too expensively to ship. Jarecki and Liu answered "which key?" too, and then the ground under "how many asks?" gave way beneath them. A base OPRF deployed where a VOPRF was needed answers "which key?" wrong and tags its users. A reused blind answers "is it blind?" wrong. An unmetered public oracle answers "how many asks?" wrong. The broken isogeny construction answered all three -- until its assumption did not hold. The diagnostic is not a teaching device bolted onto the history; it is the history.
And the resolution is a single move, assembled from primitives this series has already built. Take Chaum's 1982 blinding from the RSA world of Part 14, run it in the Diffie-Hellman group of Part 18 -- the discrete log that Part 18 argued nobody has broken, here run one-sided so a server applies a secret rather than agreeing on one -- hash the input into the group with the hash-to-curve of Part 16, and pin the key with the equal-discrete-log proof descended from the sigma protocols of Part 17.
Four earlier building blocks, one new primitive. Later parts of this guide return to its consumers as topics in their own right -- the augmented PAKE, anonymous credentials, private set intersection -- because each is, underneath, this same engine.
The server helped, and it still never saw.
That is the sentence to keep. A pseudorandom function is a lock only the keyholder can open, which is exactly what made it dangerous to offer as a service. The oblivious PRF removed the danger without removing the help: it lets a server apply its secret key to your secret input, prove it used the right key, and do so within a query budget it is honest about -- all without ever seeing what it operated on. Blind, verifiable, rate-limited. The server helped, and it still never saw.
Study guide
Key terms
- OPRF
- A two-party protocol computing F(k, x) where the server holds k and the client holds x, and the server learns neither x nor the output while the client learns only F(k, x).
- VOPRF
- An OPRF plus a proof that the server used the key committed in its published public key, so the client can reject any other key.
- POPRF
- A VOPRF that also binds a public input info into the evaluation, F(k, x, info), keeping x oblivious while one key serves many public contexts.
- 2HashDH
- The construction F(k, x) = H2(x, H1(x)^k): hash into the group, exponentiate by the key, hash out; the OPRF/VOPRF core of RFC 9497.
- DLEQ proof
- A Chaum-Pedersen non-interactive proof that two discrete logarithms are equal, binding the server's response to its committed key; batchable via a random linear combination.
- Blinding factor
- The fresh, uniform random scalar r that multiplies HashToGroup(x) so the transmitted point is uniform and independent of the input.
- Static Diffie-Hellman oracle
- A service answering x to x^k for fixed secret k; each query erodes k by the Brown-Gallant and Cheon bound, log2(Q)/2 bits after Q calls.
- Unlinkability
- Unconditional input secrecy: because the blind is uniform, the server cannot link or learn anything about the input, even with unbounded computation.
Comprehension questions
Which of the three questions does a base OPRF leave unanswered, and what attack does that enable?
It does not answer 'which key?' A malicious server can use a per-client key and later tag which client made a request, breaking anonymity.
Why does a POPRF's info have to be public?
The partial-obliviousness guarantee only protects the private input x; info is folded into the key as a public tweak and is known to both parties by construction.
Why can an unmetered public OPRF on a 128-bit group not keep 128-bit key security?
Every BlindEvaluate is a static-Diffie-Hellman sample, so Q queries erode the key by log2(Q)/2 bits; keeping the key forever and answering unlimited queries are provably incompatible.
References
- (2023). RFC 9497: Oblivious Pseudorandom Functions (OPRFs) Using Prime-Order Groups. https://www.rfc-editor.org/rfc/rfc9497.html ↩
- (1982). Blind Signatures for Untraceable Payments. https://doi.org/10.1007/978-1-4757-0602-4 ↩
- (2018). OPAQUE: An Asymmetric PAKE Protocol Secure Against Pre-Computation Attacks. https://eprint.iacr.org/2018/163 ↩
- (2018). Privacy Pass: Bypassing Internet Challenges Anonymously. https://doi.org/10.1515/popets-2018-0026 ↩
- (2019). Protecting accounts from credential stuffing with password breach alerting. https://www.usenix.org/system/files/sec19-thomas.pdf ↩
- (2005). Keyword Search and Oblivious Pseudorandom Functions. https://doi.org/10.1007/978-3-540-30576-7_17 ↩
- (2014). Round-Optimal Password-Protected Secret Sharing and T-PAKE in the Password-Only Model. https://eprint.iacr.org/2014/650 ↩
- (2023). RFC 9474: RSA Blind Signatures. https://www.rfc-editor.org/rfc/rfc9474.html ↩
- (1997). Number-theoretic Constructions of Efficient Pseudo-random Functions. https://doi.org/10.1109/SFCS.1997.646125 ↩
- (1986). How To Prove Yourself: Practical Solutions to Identification and Signature Problems. https://doi.org/10.1007/3-540-47721-7_12 ↩
- (1992). Wallet Databases with Observers. https://doi.org/10.1007/3-540-48071-4_7 ↩
- (2009). Efficient Oblivious Pseudorandom Function with Applications to Adaptive OT and Secure Computation of Set Intersection. https://doi.org/10.1007/978-3-642-00457-5_34 ↩
- (2006). Security Analysis of the Strong Diffie-Hellman Problem. https://doi.org/10.1007/11761679_1 ↩
- (2016). Highly-Efficient and Composable Password-Protected Secret Sharing (Or: How to Protect Your Bitcoin Wallet Online). https://doi.org/10.1109/eurosp.2016.30 ↩
- (2015). The Pythia PRF Service. https://eprint.iacr.org/2015/644 ↩
- (2022). A Fast and Simple Partially Oblivious PRF, with Applications. https://eprint.iacr.org/2021/864 ↩
- (2016). Efficient Batched Oblivious PRF with Applications to Private Set Intersection. https://eprint.iacr.org/2016/799 ↩
- (2021). VOLE-PSI: Fast OPRF and Circuit-PSI from Vector-OLE. https://eprint.iacr.org/2021/266 ↩
- (2023). RFC 9380: Hashing to Elliptic Curves. https://www.rfc-editor.org/rfc/rfc9380.html ↩
- (2023). RFC 9496: The ristretto255 and decaf448 Groups. https://www.rfc-editor.org/rfc/rfc9496.html ↩
- (2004). The Static Diffie-Hellman Problem. https://eprint.iacr.org/2004/306 ↩
- (2025). RFC 9807: The OPAQUE Augmented Password-Authenticated Key Exchange (aPAKE) Protocol. https://www.rfc-editor.org/rfc/rfc9807.html ↩
- opaque-ke: A Rust implementation of the OPAQUE aPAKE. https://github.com/facebook/opaque-ke ↩
- (2024). RFC 9578: Privacy Pass Issuance Protocols. https://www.rfc-editor.org/rfc/rfc9578.html ↩
- (2024). RFC 9576: The Privacy Pass Architecture. https://www.rfc-editor.org/rfc/rfc9576.html ↩
- (2024). RFC 9577: The Privacy Pass HTTP Authentication Scheme. https://www.rfc-editor.org/rfc/rfc9577.html ↩
- (2019). Protect your accounts from data breaches with Password Checkup. https://security.googleblog.com/2019/02/protect-your-accounts-from-data.html ↩
- (2021). Password Monitor: Safeguarding passwords in Microsoft Edge. https://www.microsoft.com/en-us/research/blog/password-monitor-safeguarding-passwords-in-microsoft-edge/ ↩
- (2018). Validating Leaked Passwords with k-Anonymity. https://blog.cloudflare.com/validating-leaked-passwords-with-k-anonymity/ ↩
- CIRCL: Cloudflare Interoperable Reusable Cryptographic Library. https://github.com/cloudflare/circl ↩
- voprf: A Rust implementation of RFC 9497. https://github.com/facebook/voprf ↩
- curve25519-dalek. https://github.com/dalek-cryptography/curve25519-dalek ↩
- (2020). Oblivious Pseudorandom Functions from Isogenies. https://eprint.iacr.org/2020/1532 ↩
- (2021). Cryptanalysis of an Oblivious PRF from Supersingular Isogenies. https://eprint.iacr.org/2021/706 ↩
- (2021). Round-Optimal Verifiable Oblivious Pseudorandom Functions from Ideal Lattices. https://eprint.iacr.org/2019/1271 ↩
- (2024). Verifiable Oblivious Pseudorandom Functions from Lattices: Practical-Ish and Thresholdisable. https://eprint.iacr.org/2024/1459 ↩
- (2025). The 2Hash OPRF Framework and Efficient Post-Quantum Instantiations. https://eprint.iacr.org/2024/450 ↩