50 min read

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.

Permalink

1. 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, P=H(pw)P = H(pw). It picks a fresh uniformly random scalar rr and sends not PP but rPr \cdot P, a uniformly random point. The server multiplies by its secret key: k(rP)k \cdot (r \cdot P). The client divides the blind back out to get kPk \cdot P, and hashes that: F(k,pw)=H2(pw,kH(pw))F(k, pw) = H_2(pw,\, k \cdot H(pw)).

Now state exactly what the server saw. It saw rPr \cdot P: 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.

Oblivious Pseudorandom Function (OPRF)

A two-party protocol that computes F(k,x)F(k, x) where the server holds the key kk, the client holds the input xx, and when the protocol finishes the server has learned neither xx nor the output, while the client has learned only F(k,x)F(k, x) and nothing about kk [1].

Pseudorandom Function (PRF)

A keyed function F(k,x)F(k, x) whose outputs are indistinguishable from those of a truly random function to anyone who does not hold kk. It is the "keyed lock": only the keyholder can compute it, and its outputs reveal nothing about the key [1].

Blinding factor

The fresh, uniformly random scalar rr that the client multiplies into H(x)H(x) before sending it. Because rr is uniform and independent of the input, the transmitted point rH(x)r \cdot H(x) is itself uniform and carries no information about xx [1].

Ctrl + scroll to zoom
The OPRF round-trip: the client blinds its input to a uniform point, the server applies its key, and the client unblinds and hashes out. The server only ever sees a random 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 F(k,x)=H2(x,H1(x)k)F(k, x) = H_2(x,\, H_1(x)^k) 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.

JavaScript The blind hides the input; unblinding recovers the same value (structural model, not real crypto)
// 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: Fk(x)F_k(x) computed as a single group exponentiation whose exponent is a subset-product of the key, gaixig^{\prod a_i^{x_i}}, 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.

Ctrl + scroll to zoom
From Chaum's 1982 blinding to the standardized primitive: the ingredients (blinding, non-interactive proofs, an algebraic PRF) accumulate for two decades before the OPRF is named and then standardized.

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 g1/(k+x)g^{1/(k+x)}: the server inverts k+xk + x 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 g1/(k+x)g^{1/(k+x)} 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: F(k,x)=H2(x,H1(x)k)F(k, x) = H_2(x,\, H_1(x)^k) [7]. Hash the input into the group with H1H_1, exponentiate by the key, hash out with H2H_2.

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].

2HashDH

The construction F(k,x)=H2(x,H1(x)k)F(k, x) = H_2(x,\, H_1(x)^k): hash the input into a prime-order group, raise it to the secret key, and hash the result back out with the input bound in. It is the OPRF and VOPRF core standardized in RFC 9497 [7], [1].

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 pkS=kGpk_S = k \cdot G -- and alongside each response attaches a Chaum-Pedersen proof that the same kk 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.

VOPRF (Verifiable OPRF)

An OPRF augmented with a proof that the server applied the key committed in its published public key, so a client can reject an answer computed under any other key. In RFC 9497 the proof is a batchable Chaum-Pedersen DLEQ [1], [4].

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 t=k+H(info)t = k + H(\text{info}) and returns t1blindedElementt^{-1} \cdot \text{blindedElement}, 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].

POPRF (Partially Oblivious PRF)

A VOPRF that also binds a public input info into the evaluation, computing F(k,x,info)F(k, x, \text{info}) while keeping xx oblivious. One published key can then serve many public contexts -- tenants, rotation epochs, rate-limit buckets. A POPRF with a fixed info is functionally a VOPRF [1], [16].

Here is the detail worth savoring. To fold in info, 3HashSDHI uses key inversion -- t1t^{-1} where t=k+H(info)t = k + H(\text{info}) -- 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.

Ctrl + scroll to zoom
The OPRF lineage as a ladder: solid arrows are genuine replacements (superseded by), dashed reasoning is additive (extended by). Only one step, Cheon's erosion of strong-Diffie-Hellman, is a cryptanalytic break. KKRT16 is a separate mechanism for the same goal.

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 (2202^{20}) 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.

ApproachYearKey ideaQuestions answeredFate
Chaum blind signature1982Blind, sign, unblind in RSAnone (a signature, not a PRF)Ancestor; became Blind RSA [2]
FIPR generic OPRF2005Obliviously evaluate an algebraic PRF; names "OPRF"Q1Superseded on cost [6]
Jarecki-Liu2009g1/(k+x)g^{1/(k+x)}, committedQ1 + Q2Superseded: strong-DH erodes with use [12]
2HashDH2014H2(x,H1(x)k)H_2(x, H_1(x)^k); One-More Gap CDHQ1Active core of RFC 9497 [7]
VOPRF20182HashDH + batched Chaum-Pedersen DLEQQ1 + Q2Active; extends 2HashDH [4]
Pythia2015Verifiable partially oblivious PRF, pairingsQ1 + Q2 + infoSuperseded by 3HashSDHI [15]
3HashSDHI2022(k+H(info))1(k + H(\text{info}))^{-1}; no pairingsQ1 + Q2 + infoActive POPRF of RFC 9497 [16]
KKRT162016Batched 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 GG, 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 xx; the client recovers exactly one PRF value and nothing about kk [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 kblindedElementk \cdot \text{blindedElement}, 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 pkS=kGpk_S = k \cdot G 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.

Ctrl + scroll to zoom
The three modes as an additive ladder: each mode answers one more question than the last, and the earlier construction keeps running underneath.

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 kk relating pkSpk_S to GG is the same kk 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.

DLEQ proof (Chaum-Pedersen)

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 nn -- but verifying it still forms one linear combination across the batch, so the win is skipping nn 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.

Ctrl + scroll to zoom
The VOPRF round-trip: the server attaches a DLEQ proof that its committed public key and its response share the same secret key, and the client verifies before finalizing.

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 21612^{16}-1 bytes, with a two-byte length prefix -- so longer inputs must be pre-hashed [1].

Unlinkability (unconditional input secrecy)

The guarantee that the server cannot link two evaluations of the same input, nor learn anything about the input. Because the blind is fresh and uniform, this holds even against an adversary with unbounded computation -- it is information-theoretic, not merely computational [1], [4].

JavaScript Domain separation: the Finalize tag and length prefixes stop cross-context collisions (structural model)
// 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 log2(Q)/2\log_2(Q)/2 bits, where QQ 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].

ConsumerWhat the OPRF does thereThe 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 0x0001VOPRF issuance of unlinkable, issuer-verifiable tokens [24]Token 0x0002 = Blind RSA, a publicly verifiable blind signature (Apple PAT) [8]
Google Password CheckupBlinded OPRF lookup against a breach corpus [5]HIBP k-anonymity range API: leaks a SHA-1 prefix, not oblivious [29]
Microsoft Password MonitorOPRF hides the query; FHE Labeled PSI does the match [28]Assuming the OPRF alone does the set intersection
OPRF-based PSIOblivious 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.

DimensionOPRF (base)VOPRFPOPRF
Questions answeredQ1 (blind)Q1 + Q2 (which key)Q1 + Q2 + public tweak
Extra outputnoneDLEQ proof (batchable)DLEQ proof (batchable)
Public input infononoyes, F(k,x,info)F(k, x, \text{info})
Construction2HashDH2HashDH + DLEQ3HashSDHI
AssumptionOne-More Gap CDHOne-More Gap CDHOne-More Gap SDHI (to q-DL, AGM)
Client trust in keymust trustverifiesverifies
Best suited fortrusted-key internal oracleanonymity systems, OPAQUEper-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).

CiphersuiteGroupNominal securityUse when
ristretto255-SHA512ristretto255128-bitDefault: prime-order, fast, no cofactor traps
decaf448-SHAKE256decaf448224-bitInternet-facing oracle needing static-DH headroom
P-256-SHA256NIST P-256128-bitFIPS or interop constraints
P-384-SHA384NIST P-384192-bitHigher budget; Privacy Pass 0x0001 uses this
P-521-SHA512NIST P-521256-bitMaximum 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 log2(Q)/2\log_2(Q)/2 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.

DimensionVOPRF token 0x0001Blind-RSA token 0x0002
PrimitiveOPRF (VOPRF, P-384/SHA-384)Blind signature (RSA-2048), not an OPRF
VerifiabilityIssuer-verifiable only (secret key)Publicly verifiable (RSA public key)
Token sizeSmallLarger
Key rotationEasyHeavier (RSA keypair)
Flagship deploymentCDN anonymous rate-limitingApple Private Access Tokens
SpecRFC 9578, RFC 9497RFC 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 qq-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 xxkx \mapsto x^k for a fixed secret kk, and Brown-Gallant and Cheon showed that collecting such samples recovers kk at a cost that drops with the count [1], [21], [13].

Static Diffie-Hellman oracle

A service that, for a fixed secret key kk, answers queries of the form xxkx \mapsto x^k. Each answer is a sample that erodes the key: after QQ queries, the best-known attacks recover kk at a cost reduced by log2(Q)/2\log_2(Q)/2 bits (the Brown-Gallant and Cheon bound). Offering an OPRF is offering this oracle [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 128log2(Q)/2128 - \log_2(Q)/2 bits. Answer 2402^{40} queries and you have shed 20 bits, down to 108. Answer 2602^{60} 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 QQBits lost, log2(Q)/2\log_2(Q)/2Effective security on a 128-bit group
2202^{20}10118 bits
2402^{40}20108 bits
2602^{60}3098 bits
2802^{80}4088 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].

Ctrl + scroll to zoom
Operational mode selection: answer two or three yes/no questions to reach a mode, then one more to size the group.

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-patternWhy it breaksQuestion violatedThe fix
Sending the un-blinded inputThe server sees the input; obliviousness is goneQ1Always apply the fresh blind before sending [1]
A MAC or HKDF "used as an OPRF"If the server computes it, it saw the inputQ1Use a real two-party OPRF; the blind is the point [1]
Reusing the blind across callsTwo evaluations become linkable, and the input can leakQ1Fresh, uniform blind every call [1]
Skipping element validationMalformed points enable key-probing attacksQ1/Q3Reject off-curve, out-of-range, and identity elements [1]
Dropping or unsoundly batching the DLEQA malicious server can use a per-client key and tag youQ2Verify the proof; combine batches soundly [1], [4]
A per-user public keyThe unique key is itself a deanonymizing tagQ2Pin one transparently published key for all [1]
Non-constant-time secret opsThe key leaks through timingQ3Constant-time group ops, proof, and evaluate [1]
Treating POPRF info as secretinfo is public by definition; obliviousness assumed of it failsQ1Put only public labels in info [16]
An unmetered oracle on a 128-bit groupThe key erodes by log2(Q)/2\log_2(Q)/2 bits, unboundedQ3Rate-limit, rotate, or use a bigger group [1], [21]
Using an OPRF where you need public verifiabilityA third party cannot verify a VOPRF token(wrong primitive)Use a blind signature (Blind RSA), not an OPRF [8]
JavaScript Why a fresh blind is non-negotiable: reuse makes two evaluations linkable (structural model)
// 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 log2(Q)/2\log_2(Q)/2 bits after QQ 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

  1. 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.

  2. 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.

  3. 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

  1. Alex Davidson, Armando Faz-Hernández, Nick Sullivan, & Christopher A. Wood (2023). RFC 9497: Oblivious Pseudorandom Functions (OPRFs) Using Prime-Order Groups. https://www.rfc-editor.org/rfc/rfc9497.html
  2. David Chaum (1982). Blind Signatures for Untraceable Payments. https://doi.org/10.1007/978-1-4757-0602-4
  3. Stanisław Jarecki, Hugo Krawczyk, & Jiayu Xu (2018). OPAQUE: An Asymmetric PAKE Protocol Secure Against Pre-Computation Attacks. https://eprint.iacr.org/2018/163
  4. Alex Davidson, Ian Goldberg, Nick Sullivan, George Tankersley, & Filippo Valsorda (2018). Privacy Pass: Bypassing Internet Challenges Anonymously. https://doi.org/10.1515/popets-2018-0026
  5. Kurt Thomas, Jennifer Pullman, Kevin Yeo, Ananth Raghunathan, Patrick Gage Kelley, Luca Invernizzi, Borbala Benko, Tadek Pietraszek, Sarvar Patel, Dan Boneh, & Elie Bursztein (2019). Protecting accounts from credential stuffing with password breach alerting. https://www.usenix.org/system/files/sec19-thomas.pdf
  6. Michael J. Freedman, Yuval Ishai, Benny Pinkas, & Omer Reingold (2005). Keyword Search and Oblivious Pseudorandom Functions. https://doi.org/10.1007/978-3-540-30576-7_17
  7. Stanisław Jarecki, Aggelos Kiayias, & Hugo Krawczyk (2014). Round-Optimal Password-Protected Secret Sharing and T-PAKE in the Password-Only Model. https://eprint.iacr.org/2014/650
  8. Frank Denis, Frederic Jacobs, & Christopher A. Wood (2023). RFC 9474: RSA Blind Signatures. https://www.rfc-editor.org/rfc/rfc9474.html
  9. Moni Naor & Omer Reingold (1997). Number-theoretic Constructions of Efficient Pseudo-random Functions. https://doi.org/10.1109/SFCS.1997.646125
  10. Amos Fiat & Adi Shamir (1986). How To Prove Yourself: Practical Solutions to Identification and Signature Problems. https://doi.org/10.1007/3-540-47721-7_12
  11. David Chaum & Torben Pryds Pedersen (1992). Wallet Databases with Observers. https://doi.org/10.1007/3-540-48071-4_7
  12. Stanisław Jarecki & Xiaomin Liu (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
  13. Jung Hee Cheon (2006). Security Analysis of the Strong Diffie-Hellman Problem. https://doi.org/10.1007/11761679_1
  14. Stanisław Jarecki, Aggelos Kiayias, Hugo Krawczyk, & Jiayu Xu (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
  15. Adam Everspaugh, Rahul Chatterjee, Samuel Scott, Ari Juels, & Thomas Ristenpart (2015). The Pythia PRF Service. https://eprint.iacr.org/2015/644
  16. Nirvan Tyagi, Sofía Celi, Thomas Ristenpart, Nick Sullivan, Stefano Tessaro, & Christopher A. Wood (2022). A Fast and Simple Partially Oblivious PRF, with Applications. https://eprint.iacr.org/2021/864
  17. Vladimir Kolesnikov, Ranjit Kumaresan, Mike Rosulek, & Ni Trieu (2016). Efficient Batched Oblivious PRF with Applications to Private Set Intersection. https://eprint.iacr.org/2016/799
  18. Peter Rindal & Phillipp Schoppmann (2021). VOLE-PSI: Fast OPRF and Circuit-PSI from Vector-OLE. https://eprint.iacr.org/2021/266
  19. Armando Faz-Hernández, Sam Scott, Nick Sullivan, Riad S. Wahby, & Christopher A. Wood (2023). RFC 9380: Hashing to Elliptic Curves. https://www.rfc-editor.org/rfc/rfc9380.html
  20. Henry de Valence, Jack Grigg, Mike Hamburg, Isis Lovecruft, George Tankersley, & Filippo Valsorda (2023). RFC 9496: The ristretto255 and decaf448 Groups. https://www.rfc-editor.org/rfc/rfc9496.html
  21. Daniel R. L. Brown & Robert P. Gallant (2004). The Static Diffie-Hellman Problem. https://eprint.iacr.org/2004/306
  22. Daniel Bourdrez, Hugo Krawczyk, Kevin Lewi, & Christopher A. Wood (2025). RFC 9807: The OPAQUE Augmented Password-Authenticated Key Exchange (aPAKE) Protocol. https://www.rfc-editor.org/rfc/rfc9807.html
  23. Meta opaque-ke: A Rust implementation of the OPAQUE aPAKE. https://github.com/facebook/opaque-ke
  24. Sofía Celi, Alex Davidson, Steven Valdez, & Christopher A. Wood (2024). RFC 9578: Privacy Pass Issuance Protocols. https://www.rfc-editor.org/rfc/rfc9578.html
  25. (2024). RFC 9576: The Privacy Pass Architecture. https://www.rfc-editor.org/rfc/rfc9576.html
  26. (2024). RFC 9577: The Privacy Pass HTTP Authentication Scheme. https://www.rfc-editor.org/rfc/rfc9577.html
  27. Google (2019). Protect your accounts from data breaches with Password Checkup. https://security.googleblog.com/2019/02/protect-your-accounts-from-data.html
  28. Microsoft Research (2021). Password Monitor: Safeguarding passwords in Microsoft Edge. https://www.microsoft.com/en-us/research/blog/password-monitor-safeguarding-passwords-in-microsoft-edge/
  29. Junade Ali (2018). Validating Leaked Passwords with k-Anonymity. https://blog.cloudflare.com/validating-leaked-passwords-with-k-anonymity/
  30. Cloudflare CIRCL: Cloudflare Interoperable Reusable Cryptographic Library. https://github.com/cloudflare/circl
  31. Meta voprf: A Rust implementation of RFC 9497. https://github.com/facebook/voprf
  32. dalek-cryptography curve25519-dalek. https://github.com/dalek-cryptography/curve25519-dalek
  33. Dan Boneh, Dmitry Kogan, & Katharine Woo (2020). Oblivious Pseudorandom Functions from Isogenies. https://eprint.iacr.org/2020/1532
  34. Andrea Basso, Péter Kutas, Simon-Philipp Merz, Christophe Petit, & Antonio Sanso (2021). Cryptanalysis of an Oblivious PRF from Supersingular Isogenies. https://eprint.iacr.org/2021/706
  35. Martin R. Albrecht, Alex Davidson, Amit Deo, & Nigel P. Smart (2021). Round-Optimal Verifiable Oblivious Pseudorandom Functions from Ideal Lattices. https://eprint.iacr.org/2019/1271
  36. Martin R. Albrecht & Kamil Doruk Gür (2024). Verifiable Oblivious Pseudorandom Functions from Lattices: Practical-Ish and Thresholdisable. https://eprint.iacr.org/2024/1459
  37. Ward Beullens, Lucas Dodgson, Sebastian H. Faller, & Julia Hesse (2025). The 2Hash OPRF Framework and Efficient Post-Quantum Instantiations. https://eprint.iacr.org/2024/450