<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Parag Mali - tag: oprf</title><description>Posts tagged oprf.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sat, 25 Jul 2026 08:57:57 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/oprf/rss.xml" rel="self" type="application/rss+xml"/><item><title>The Server Helped, and Never Saw: A Field Guide to Oblivious Pseudorandom Functions</title><link>https://paragmali.com/blog/the-server-helped-and-never-saw-a-field-guide-to-oblivious-p/</link><guid isPermaLink="true">https://paragmali.com/blog/the-server-helped-and-never-saw-a-field-guide-to-oblivious-p/</guid><description>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.</description><pubDate>Mon, 13 Jul 2026 10:23:11 GMT</pubDate><content:encoded>
**An oblivious pseudorandom function (OPRF) lets a server apply its secret key to your secret input so that it learns neither the input nor the output.** That single move is the engine under OPAQUE logins, Privacy Pass tokens, and private password-breach checks [@rfc9497]. The trick is David Chaum&apos;s 1982 blinding transplanted from RSA into a Diffie-Hellman group [@chaum82]: hash your input to a curve point, multiply by a fresh random blind, let the server multiply by its key, then divide the blind back out. RFC 9497 (2023) pins this down as three modes -- OPRF, VOPRF, and POPRF -- that answer three questions: is it blind, which key produced the answer, and how many times can one key be asked before it bleeds out [@rfc9497]. This is the field guide to that engine: every generation that built it, every real break that shaped it, and every way it still gets misused in production code.
&lt;h2&gt;1. The Server Hardened Your Password Without Ever Seeing It&lt;/h2&gt;
&lt;p&gt;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 &quot;pepper&quot; that lives only in your server&apos;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 [@opaque-paper].&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;It is not a law of nature. Here is the whole resolution, and it fits in four lines.&lt;/p&gt;
&lt;p&gt;The client hashes its password to a point on an elliptic curve, $P = H(pw)$. It picks a fresh uniformly random scalar $r$ and sends not $P$ but $r \cdot P$, a uniformly random point. The server multiplies by its secret key: $k \cdot (r \cdot P)$. The client divides the blind back out to get $k \cdot P$, and hashes that: $F(k, pw) = H_2(pw,, k \cdot H(pw))$.&lt;/p&gt;
&lt;p&gt;Now state exactly what the server saw. It saw $r \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.&lt;/p&gt;

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

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

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

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-&amp;gt;&amp;gt;S: send blindedElement, a uniform random point
    Note over S: evaluatedElement holds k times blindedElement
    S-&amp;gt;&amp;gt;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
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The oblivious PRF applies a secret key to a secret input so that neither party learns the other&apos;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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That two-line trick is not a party piece. The &lt;em&gt;same&lt;/em&gt; two lines are how Privacy Pass hands you a token that a server can validate but cannot link back to you [@dgstv18]; how Google&apos;s Password Checkup tells you a credential appears in a breach corpus without Google learning the credential [@thomas19]; and how two organizations compute the intersection of their customer lists without either revealing its list [@fipr05]. Replace &quot;the component that used to have to see the secret&quot; with a blind, verifiable, rate-limited oracle that does not, and you have described every one of these systems at once.&lt;/p&gt;
&lt;p&gt;Because the move is always the same, you can interrogate any deployment with the same three questions. &lt;strong&gt;Is it blind?&lt;/strong&gt; Does the server ever see the input, or link two evaluations of it? &lt;strong&gt;Which key?&lt;/strong&gt; Can the client prove the answer was produced by the one key the server committed to? &lt;strong&gt;How many asks?&lt;/strong&gt; 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&apos;s history is one of them answered wrong.&lt;/p&gt;
&lt;p&gt;The 2HashDH shape $F(k, x) = H_2(x,, H_1(x)^k)$ we just wrote by hand is the actual construction that ships today [@jkk14]. If it feels like Diffie-Hellman run one-sided, that is exactly right: Part 18 argued that nobody has broken &lt;a href=&quot;https://paragmali.com/blog/nobody-broke-the-discrete-log-a-field-guide-to-diffie-hellma/&quot; rel=&quot;noopener&quot;&gt;the discrete logarithm&lt;/a&gt;, and this is its constructive twin -- the same group structure, run so that a server &lt;em&gt;applies&lt;/em&gt; a secret rather than agreeing on one. If you are wondering why we &lt;a href=&quot;https://paragmali.com/blog/the-one-job-of-a-password-hash-why-fast-is-the-enemy-and-mem/&quot; rel=&quot;noopener&quot;&gt;harden passwords&lt;/a&gt; at all, Part 12 is the prerequisite. Here we take the hardening as given and ask how a server can perform it blind.&lt;/p&gt;
&lt;p&gt;{`
// STRUCTURAL MODEL, NOT REAL CRYPTO. It shows only the SHAPE of blind/evaluate/unblind.
// Model an elliptic-curve group of prime order q: a &quot;point&quot; is an integer mod q,
// and &quot;scalar multiplication&quot; scalar*point is multiplication mod q. BigInt avoids overflow.
const q = 2147483647n;                       // a prime group order (toy)
const mod = (a) =&amp;gt; ((a % q) + q) % q;
const scalarMul = (s, point) =&amp;gt; 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 &amp;gt; 0n) { if (e &amp;amp; 1n) r = mod(r * b); b = mod(b * b); e &amp;gt;&amp;gt;= 1n; }
  return r;
}&lt;/p&gt;
&lt;p&gt;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&lt;/p&gt;
&lt;p&gt;const blinded   = scalarMul(r, Hx);           // client SENDS r&lt;em&gt;H(x): looks uniformly random
const evaluated = scalarMul(k, blinded);      // server returns k&lt;/em&gt;(r&lt;em&gt;H(x))
const unblinded = scalarMul(invScalar(r), evaluated); // client removes r: recovers k&lt;/em&gt;H(x)
const direct    = scalarMul(k, Hx);           // what F would be if the server had seen x&lt;/p&gt;
&lt;p&gt;console.log(&apos;server saw            = &apos; + blinded.toString() + &apos; (independent of H(x)=&apos; + Hx.toString() + &apos;)&apos;);
console.log(&apos;client recovered      = &apos; + unblinded.toString());
console.log(&apos;equals direct k*H(x)  = &apos; + (unblinded === direct));
`}&lt;/p&gt;
&lt;p&gt;That trick is forty years old, and for its first two decades it did not even have a name. To understand why it is &lt;em&gt;secure&lt;/em&gt;, and why there turned out to be &lt;em&gt;three&lt;/em&gt; versions of it, we have to start where the blinding started: in 1982, with digital cash.&lt;/p&gt;
&lt;h2&gt;2. Blinding, Before It Had a Name&lt;/h2&gt;
&lt;p&gt;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 [@chaum82]. The client multiplies its message by a random factor, the bank signs the blinded message under its &lt;a href=&quot;https://paragmali.com/blog/rsa-is-a-trapdoor-not-a-cryptosystem-oaep-pss-and-the-25-yea/&quot; rel=&quot;noopener&quot;&gt;RSA key&lt;/a&gt;, 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&apos;s 1982 construction used multiplicative blinding in an RSA group, and its output was a publicly verifiable &lt;em&gt;signature&lt;/em&gt;. 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.&lt;/p&gt;
&lt;p&gt;So if the trick existed in 1982, why did the OPRF take until 2005 to be named and 2023 to be standardized? Because Chaum&apos;s version produced the wrong kind of output, and the right kind needed a second ingredient that did not exist yet.&lt;/p&gt;
&lt;p&gt;Watch the shape of what Chaum built. A blind &lt;em&gt;signature&lt;/em&gt; is public: anyone with the bank&apos;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.&lt;/p&gt;
&lt;p&gt;Chaum had the blinding. He did not have a &lt;em&gt;pseudorandom&lt;/em&gt; thing to blind. This is why &quot;Blind RSA,&quot; 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 [@rfc9474].&lt;/p&gt;
&lt;p&gt;The second ingredient arrived in 1997. Moni Naor and Omer Reingold built a pseudorandom function out of pure number theory: $F_k(x)$ computed as a single group exponentiation whose exponent is a subset-product of the key, $g^{\prod a_i^{x_i}}$, provably secure under the Decisional Diffie-Hellman assumption [@naorreingold97]. What mattered was not the security proof but the &lt;em&gt;shape&lt;/em&gt;. 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.&lt;/p&gt;
&lt;p&gt;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 &lt;strong&gt;Oblivious Pseudorandom Function&lt;/strong&gt; [@fipr05]. They were not thinking about passwords. They wanted private keyword search and private set intersection -- letting one party query another&apos;s database, or intersect two sets, without either side revealing its contents.&lt;/p&gt;
&lt;p&gt;The abstraction they named is the one we still use: transplant Chaum&apos;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 &lt;em&gt;multiplicative&lt;/em&gt; blinding, the variant described here: its &lt;code&gt;Blind&lt;/code&gt; step computes &lt;code&gt;blindedElement = blind * inputElement&lt;/code&gt;, and that multiplicative form is what ships.&lt;/p&gt;

flowchart LR
    A[&quot;1982 Chaum blinding in RSA&quot;] --&amp;gt; B[&quot;1986 Fiat-Shamir transform&quot;]
    B --&amp;gt; C[&quot;1992 Chaum-Pedersen DLEQ proof&quot;]
    C --&amp;gt; D[&quot;1997 Naor-Reingold algebraic PRF&quot;]
    D --&amp;gt; E[&quot;2005 FIPR names the OPRF&quot;]
    E --&amp;gt; F[&quot;2009 Jarecki-Liu efficient DH OPRF&quot;]
    F --&amp;gt; G[&quot;2014 2HashDH, the workhorse&quot;]
    G --&amp;gt; H[&quot;2018 Privacy Pass VOPRF&quot;]
    H --&amp;gt; I[&quot;2022 3HashSDHI POPRF&quot;]
    I --&amp;gt; J[&quot;2023 RFC 9497 standard&quot;]
    J --&amp;gt; K[&quot;2025 RFC 9807 OPAQUE&quot;]
&lt;p&gt;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&apos;s random challenge with a hash of the transcript [@fiatshamir86]. In 1992, Chaum and Pedersen gave a zero-knowledge proof that two discrete logarithms are equal [@chaumped92]. Neither was about OPRFs. Both become the machinery of &lt;em&gt;verifiability&lt;/em&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;3. The First Construction That Got Weaker With Use&lt;/h2&gt;
&lt;p&gt;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&apos;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&apos;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.&lt;/p&gt;
&lt;p&gt;The efficient turn came in 2009. Stanislaw Jarecki and Xiaomin Liu built the first OPRF that was both fast and &lt;em&gt;committed&lt;/em&gt; -- verifiable -- in a prime-order group [@jareckiliu09]. Instead of the exponentiation-per-bit structure, it used a Dodis-Yampolskiy-style PRF of the form $g^{1/(k+x)}$: the server inverts $k + x$ in the exponent, so evaluation is a single group operation regardless of input length.&lt;/p&gt;
&lt;p&gt;And because the construction committed to the key, the client got assurance not just that &lt;em&gt;some&lt;/em&gt; key was applied but that it was the server&apos;s key. In the language of our three questions, this one answered Q1 and Q2 at once. It looked like the deployable OPRF.&lt;/p&gt;
&lt;p&gt;Then look at the assumption it rested on. Security of the $g^{1/(k+x)}$ family reduces to a &lt;em&gt;q-strong-Diffie-Hellman&lt;/em&gt; 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&apos;s analysis proved that the strong-Diffie-Hellman family weakens as the attacker obtains more samples tied to the same secret -- security &lt;em&gt;degrades with the number of queries&lt;/em&gt; [@cheon06].&lt;/p&gt;
&lt;p&gt;Sit with what that means for an OPRF specifically. An OPRF&apos;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&apos;s result said each sample chipped away at the secret. The primitive got &lt;em&gt;weaker the more it was used&lt;/em&gt;. 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.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Most cryptographic primitives are static: a hash function is exactly as strong after a trillion evaluations as after one. The 2009 Jarecki-Liu OPRF was not. Its security was a budget that the act of answering queries spent down, because each blind evaluation handed the attacker another sample against the strong-Diffie-Hellman assumption it relied on [@cheon06]. For a primitive whose whole purpose is to be queried without limit, that is a design-level contradiction, not a tuning problem. This is our third question -- &lt;em&gt;how many asks?&lt;/em&gt; -- appearing for the first time, and appearing as a wound rather than a footnote.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The fix was not a cleverer attack defense. It was to stop building on an assumption that decays -- to hash the input to &lt;em&gt;one&lt;/em&gt; 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.&lt;/p&gt;
&lt;h2&gt;4. Moving the Server From Trusted to Blind&lt;/h2&gt;
&lt;p&gt;Here is the whole next fifteen years compressed into one sentence: each generation moves the server one step from &quot;must be trusted, sees the secret&quot; toward &quot;blind, verifiable, query-limited,&quot; and -- the part that surprises people -- most of those steps are &lt;em&gt;additive&lt;/em&gt;. 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: &lt;strong&gt;superseded by&lt;/strong&gt;, a genuine replacement, and &lt;strong&gt;extended by&lt;/strong&gt;, an additive answer to one more question with the old construction still running underneath.&lt;/p&gt;
&lt;h3&gt;The workhorse: 2HashDH&lt;/h3&gt;
&lt;p&gt;In 2014, Stanislaw Jarecki, Aggelos Kiayias, and Hugo Krawczyk wrote down the construction we built by hand in Section 1: $F(k, x) = H_2(x,, H_1(x)^k)$ [@jkk14]. Hash the input into the group with $H_1$, exponentiate by the key, hash out with $H_2$.&lt;/p&gt;
&lt;p&gt;It is round-optimal -- one message each way, and you provably cannot do it in fewer. And its security rests on the &lt;strong&gt;One-More Gap Diffie-Hellman&lt;/strong&gt; 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.&quot;One-More&quot; assumptions bound how much an attacker can produce relative to how many genuine evaluations it was given -- it cannot get &quot;one more&quot; 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&apos;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 [@jkkx16].&lt;/p&gt;

The construction $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 [@jkk14], [@rfc9497].
&lt;p&gt;This is the OPRF that ships. But notice what it does &lt;em&gt;not&lt;/em&gt; do. It answers Q1 -- the server never sees the input -- and nothing more. The client has no way to tell &lt;em&gt;which&lt;/em&gt; 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.&lt;/p&gt;
&lt;h3&gt;Verifiability you can batch: the VOPRF&lt;/h3&gt;
&lt;p&gt;In 2018, Alex Davidson, Ian Goldberg, Nick Sullivan, George Tankersley, and Filippo Valsorda shipped Privacy Pass, and with it the deployed &lt;strong&gt;VOPRF&lt;/strong&gt; [@dgstv18]. The addition is precisely the 1992 machinery we set aside. The server publishes a commitment to its key -- a public key $pk_S = k \cdot G$ -- and alongside each response attaches a Chaum-Pedersen proof that the &lt;em&gt;same&lt;/em&gt; $k$ relates its public key and its answer, made non-interactive by Fiat-Shamir [@chaumped92], [@fiatshamir86]. The client verifies before it accepts. Answering Q2, at last.&lt;/p&gt;
&lt;p&gt;The engineering move that made it deployable was batching: many evaluations fold into a &lt;em&gt;single&lt;/em&gt; proof via a random linear combination, so a client redeeming a thousand tokens verifies one proof, not a thousand [@dgstv18]. That is what let Privacy Pass run at content-delivery-network scale. That scale is what surfaced the primitive&apos;s real-world failure modes -- key consistency, tagging, rotation, double-spend -- that no research prototype had stress-tested.&lt;/p&gt;

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 [@rfc9497], [@dgstv18].
&lt;p&gt;The VOPRF still left one gap. Its verifiability binds &lt;em&gt;one&lt;/em&gt; key to &lt;em&gt;one&lt;/em&gt; 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 &lt;em&gt;public&lt;/em&gt; label into the evaluation without changing the key.&lt;/p&gt;
&lt;h3&gt;Partial obliviousness: heavy, then light&lt;/h3&gt;
&lt;p&gt;The first answer came early and expensive. In 2015, Adam Everspaugh, Rahul Chatterjee, Samuel Scott, Ari Juels, and Thomas Ristenpart built Pythia, the first &lt;strong&gt;partially oblivious&lt;/strong&gt; PRF: a public per-tenant tweak let one server key serve many domains and support bulk key rotation, while the password stayed oblivious [@pythia]. It worked, but it needed bilinear pairings -- special curves, heavier operations, a bigger dependency. Powerful, but not the thing you reach for by default.&lt;/p&gt;
&lt;p&gt;The light answer came in 2022. Nirvan Tyagi, Sofia Celi, Thomas Ristenpart, Nick Sullivan, Stefano Tessaro, and Christopher Wood built &lt;strong&gt;3HashSDHI&lt;/strong&gt;: the first POPRF that needs no pairings [@tcrstw22]. It folds the public input &lt;code&gt;info&lt;/code&gt; into the key by inversion. The server computes $t = k + H(\text{info})$ and returns $t^{-1} \cdot \text{blindedElement}$, with a DLEQ proof against the tweaked key. The private input stays oblivious; &lt;code&gt;info&lt;/code&gt; is public and known to both sides. And it runs, in the authors&apos; words, as fast as the standards-track 2HashDH protocol [@tcrstw22].&lt;/p&gt;

A VOPRF that also binds a *public* input `info` into the evaluation, computing $F(k, x, \text{info})$ while keeping $x$ 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 [@rfc9497], [@tcrstw22].
&lt;p&gt;Here is the detail worth savoring. To fold in &lt;code&gt;info&lt;/code&gt;, 3HashSDHI uses key inversion -- $t^{-1}$ where $t = k + H(\text{info})$ -- which is the &lt;em&gt;same&lt;/em&gt; Dodis-Yampolskiy inversion structure Jarecki and Liu used back in 2009 [@tcrstw22], [@jareckiliu09]. The mechanism that got the field burned by Cheon&apos;s attack was never actually wrong. What was wrong was the &lt;em&gt;assumption it had been analyzed under&lt;/em&gt;. 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 [@tcrstw22].&lt;/p&gt;
&lt;p&gt;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 &quot;broken&quot; mechanism untouched.&lt;/p&gt;

flowchart TD
    G0[&quot;Gen 0: Chaum blinding plus Naor-Reingold PRF&quot;] --&amp;gt;|superseded| G1[&quot;Gen 1: FIPR names the OPRF, generic&quot;]
    G1 --&amp;gt;|superseded on cost| G2[&quot;Gen 2: Jarecki-Liu committed DH OPRF&quot;]
    G2 --&amp;gt;|superseded by Cheon erosion| G3[&quot;Gen 3: 2HashDH, answers Q1&quot;]
    G3 --&amp;gt;|extended, adds Q2| G4[&quot;Gen 4: VOPRF with batched DLEQ&quot;]
    G4 --&amp;gt;|extended, adds public info| G5[&quot;Gen 5: POPRF 3HashSDHI&quot;]
    G5 --&amp;gt; RFC[&quot;RFC 9497: three modes, one interface&quot;]
    G3 -.-&amp;gt;|parallel mechanism| K[&quot;KKRT16 OT-extension OPRF, for PSI&quot;]
&lt;h3&gt;The other lineage, in one breath&lt;/h3&gt;
&lt;p&gt;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 [@kkrt16]. It is not verifiable, not publicly reusable, and only semi-honest secure, but it intersects two sets of a million ($2^{20}$) elements each in about 3.8 seconds [@kkrt16], 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 [@rindalvolepsi]. The takeaway for a practitioner: &quot;OPRF&quot; names a &lt;em&gt;goal&lt;/em&gt;, and at least two very different mechanisms reach it. Do not assume every system labeled OPRF is the standardized Diffie-Hellman family.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Key idea&lt;/th&gt;
&lt;th&gt;Questions answered&lt;/th&gt;
&lt;th&gt;Fate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Chaum blind signature&lt;/td&gt;
&lt;td&gt;1982&lt;/td&gt;
&lt;td&gt;Blind, sign, unblind in RSA&lt;/td&gt;
&lt;td&gt;none (a signature, not a PRF)&lt;/td&gt;
&lt;td&gt;Ancestor; became Blind RSA [@chaum82]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FIPR generic OPRF&lt;/td&gt;
&lt;td&gt;2005&lt;/td&gt;
&lt;td&gt;Obliviously evaluate an algebraic PRF; names &quot;OPRF&quot;&lt;/td&gt;
&lt;td&gt;Q1&lt;/td&gt;
&lt;td&gt;Superseded on cost [@fipr05]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jarecki-Liu&lt;/td&gt;
&lt;td&gt;2009&lt;/td&gt;
&lt;td&gt;$g^{1/(k+x)}$, committed&lt;/td&gt;
&lt;td&gt;Q1 + Q2&lt;/td&gt;
&lt;td&gt;Superseded: strong-DH erodes with use [@jareckiliu09]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2HashDH&lt;/td&gt;
&lt;td&gt;2014&lt;/td&gt;
&lt;td&gt;$H_2(x, H_1(x)^k)$; One-More Gap CDH&lt;/td&gt;
&lt;td&gt;Q1&lt;/td&gt;
&lt;td&gt;Active core of RFC 9497 [@jkk14]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VOPRF&lt;/td&gt;
&lt;td&gt;2018&lt;/td&gt;
&lt;td&gt;2HashDH + batched Chaum-Pedersen DLEQ&lt;/td&gt;
&lt;td&gt;Q1 + Q2&lt;/td&gt;
&lt;td&gt;Active; extends 2HashDH [@dgstv18]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pythia&lt;/td&gt;
&lt;td&gt;2015&lt;/td&gt;
&lt;td&gt;Verifiable partially oblivious PRF, pairings&lt;/td&gt;
&lt;td&gt;Q1 + Q2 + info&lt;/td&gt;
&lt;td&gt;Superseded by 3HashSDHI [@pythia]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3HashSDHI&lt;/td&gt;
&lt;td&gt;2022&lt;/td&gt;
&lt;td&gt;$(k + H(\text{info}))^{-1}$; no pairings&lt;/td&gt;
&lt;td&gt;Q1 + Q2 + info&lt;/td&gt;
&lt;td&gt;Active POPRF of RFC 9497 [@tcrstw22]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KKRT16&lt;/td&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;Batched OPRF from OT-extension (symmetric)&lt;/td&gt;
&lt;td&gt;Q1 (relaxed, semi-honest)&lt;/td&gt;
&lt;td&gt;Active, niche: high-volume PSI [@kkrt16]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;5. RFC 9497: Three Modes, One Interface&lt;/h2&gt;
&lt;p&gt;In December 2023, the IRTF&apos;s Crypto Forum Research Group published RFC 9497, written by Alex Davidson, Armando Faz-Hernandez, Nick Sullivan, and Christopher Wood [@rfc9497]. 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.&lt;/p&gt;
&lt;p&gt;Start with the interface, because all three modes share it. Over a prime-order group with generator $G$, and using the &lt;a href=&quot;https://paragmali.com/blog/the-doorway-into-the-group-a-field-guide-to-hashing-a-string/&quot; rel=&quot;noopener&quot;&gt;hash-to-group machinery of RFC 9380&lt;/a&gt;, the protocol is three calls [@rfc9497], [@rfc9380]:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Blind(x):
    blind           &amp;lt;- random nonzero scalar               # fresh, uniform, per call
    blindedElement  &amp;lt;- blind * HashToGroup(x)              # a uniformly random point
    if blindedElement == Identity: abort                   # element validation
    return blind, blindedElement

BlindEvaluate(k, blindedElement, [info]):
    OPRF / VOPRF:  evaluatedElement &amp;lt;- k * blindedElement
    POPRF:         t &amp;lt;- k + HashToScalar(info)
                   evaluatedElement &amp;lt;- t^(-1) * blindedElement
    # VOPRF and POPRF also emit a DLEQ proof (see below)

Finalize(x, blind, evaluatedElement, [info]):
    N &amp;lt;- 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 || &quot;Finalize&quot; )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server sees only &lt;code&gt;blindedElement&lt;/code&gt;, a uniformly random point independent of $x$; the client recovers exactly one PRF value and nothing about $k$ [@rfc9497]. That is the same 2HashDH we have been circling, now with every byte of the output hash pinned down.&lt;code&gt;HashToGroup&lt;/code&gt; is RFC 9380 hash-to-curve, the subject of Part 16. Its defining property is that the output point has an &lt;em&gt;unknown&lt;/em&gt; 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.&lt;/p&gt;
&lt;h3&gt;The three modes are three answers&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;base OPRF&lt;/strong&gt; answers Q1 and stops. &lt;code&gt;BlindEvaluate&lt;/code&gt; is just $k \cdot \text{blindedElement}$, with no proof. The client trusts that &lt;em&gt;some&lt;/em&gt; 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 [@rfc9497].&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;VOPRF&lt;/strong&gt; answers Q1 and Q2. The server commits to a public key $pk_S = k \cdot G$ and attaches a proof; the client verifies before finalizing [@rfc9497]. The &lt;strong&gt;POPRF&lt;/strong&gt; answers Q1 and Q2 and binds a public &lt;code&gt;info&lt;/code&gt;, via the key-inversion tweak. A POPRF with a fixed &lt;code&gt;info&lt;/code&gt; collapses back to a VOPRF [@rfc9497]. Base to verifiable to partially oblivious: blind, then blind-plus-which-key, then blind-plus-which-key-plus-public-tweak.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Three modes are three answers to three questions. OPRF answers &quot;is it blind?&quot; VOPRF adds &quot;which key?&quot; POPRF adds &quot;and bind this public label.&quot; 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.&lt;/p&gt;
&lt;/blockquote&gt;

flowchart LR
    Q1[&quot;Q1: is it blind?&quot;] --&amp;gt; OPRF[&quot;OPRF: blind only&quot;]
    OPRF --&amp;gt;|add DLEQ proof against one committed key| VOPRF[&quot;VOPRF: blind plus which key&quot;]
    VOPRF --&amp;gt;|fold in a public info tweak| POPRF[&quot;POPRF: blind, which key, public label&quot;]
&lt;h3&gt;Verifiability, in depth&lt;/h3&gt;
&lt;p&gt;The proof is the 1992 machinery finally doing its job. A Chaum-Pedersen DLEQ proof establishes that two discrete logarithms are equal: that the $k$ relating $pk_S$ to $G$ is the &lt;em&gt;same&lt;/em&gt; $k$ relating &lt;code&gt;evaluatedElement&lt;/code&gt; to &lt;code&gt;blindedElement&lt;/code&gt; [@rfc9497], [@chaumped92]. 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 [@fiatshamir86].The DLEQ proof is a sigma protocol made non-interactive by Fiat-Shamir -- the same transform behind &lt;a href=&quot;https://paragmali.com/blog/the-math-held-the-interface-leaked-a-field-guide-to-digital-/&quot; rel=&quot;noopener&quot;&gt;Schnorr signatures&lt;/a&gt;, 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.&lt;/p&gt;

A non-interactive zero-knowledge proof that two discrete logarithms are equal -- here, that the server&apos;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 [@rfc9497], [@chaumped92].
&lt;p&gt;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 [@rfc9497], [@dgstv18]. That is one proof object per batch, not $n$ -- but verifying it still forms one linear combination across the batch, so the win is skipping $n$ separate proof checks, not making verification constant-time in the batch size. That is what let anonymous-token issuance scale.&lt;/p&gt;
&lt;p&gt;There is a subtlety that quietly carries the entire security of any anonymity system built on a VOPRF: verifiability is only as strong as &lt;em&gt;key consistency&lt;/em&gt;. Proving the server used the key in its public key is worthless if the server hands a &lt;em&gt;different&lt;/em&gt; public key to every client, because then the unique key is itself a deanonymizing tag. Verifiability protects you only when everyone is served under &lt;em&gt;one&lt;/em&gt; transparently published key [@rfc9497]. We will return to this in the practical guide; for now, note that &quot;add a proof&quot; and &quot;pin one key&quot; are two requirements, not one.&lt;/p&gt;

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-&amp;gt;&amp;gt;S: send blindedElement
    Note over S: evaluatedElement holds k times blindedElement
    Note over S: GenerateProof binds pkS and the answer to one k
    S-&amp;gt;&amp;gt;C: return evaluatedElement and DLEQ proof
    Note over C: VerifyProof against pkS, abort on failure
    Note over C: unblind, then Finalize with domain separation
&lt;h3&gt;The machinery that makes it safe&lt;/h3&gt;
&lt;p&gt;A specification earns &quot;misuse-resistant&quot; in the details, and RFC 9497&apos;s details are where the accumulated scar tissue lives. Hashing and group operations are delegated, not reinvented: &lt;code&gt;HashToGroup&lt;/code&gt; and &lt;code&gt;HashToScalar&lt;/code&gt; come from RFC 9380, and the groups themselves from RFC 9496 -- &lt;a href=&quot;https://paragmali.com/blog/the-curve-was-hard-the-gap-was-soft-a-field-guide-to-using-e/&quot; rel=&quot;noopener&quot;&gt;ristretto255 and decaf448&lt;/a&gt; -- or the NIST curves [@rfc9497], [@rfc9380], [@rfc9496].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.&lt;/p&gt;
&lt;p&gt;Three requirements are worth naming because dropping any one silently breaks the guarantee:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; RFC 9497 requires that every deserialized element be &lt;em&gt;validated&lt;/em&gt; -- on-curve, in the right range, and not the identity -- or an attacker can probe the key with malformed inputs [@rfc9497]. It requires that operations on secret data, &lt;em&gt;including&lt;/em&gt; &lt;code&gt;GenerateProof&lt;/code&gt; and &lt;code&gt;BlindEvaluate&lt;/code&gt;, run in &lt;em&gt;constant time&lt;/em&gt;, or the server leaks its key through timing [@rfc9497]. And it requires &lt;em&gt;domain separation&lt;/em&gt;: the literal &lt;code&gt;&quot;Finalize&quot;&lt;/code&gt; tag and the mode context string keep one application&apos;s evaluations from colliding with another&apos;s [@rfc9497]. None of these is optional hardening; each is load-bearing for a property the primitive claims.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href=&quot;https://paragmali.com/blog/one-secret-is-not-one-key-the-discipline-of-key-derivation-w/&quot; rel=&quot;noopener&quot;&gt;domain separation&lt;/a&gt; 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 $2^{16}-1$ bytes, with a two-byte length prefix -- so longer inputs must be pre-hashed [@rfc9497].&lt;/p&gt;

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 [@rfc9497], [@dgstv18].
&lt;p&gt;{`
// STRUCTURAL MODEL. Shows why Finalize binds lengths + a &quot;Finalize&quot; tag, not just N.
function toyHash(str) {                       // a tiny non-cryptographic stand-in
  let h = 5381 &amp;gt;&amp;gt;&amp;gt; 0;
  for (const ch of str) h = (Math.imul(h, 33) ^ ch.charCodeAt(0)) &amp;gt;&amp;gt;&amp;gt; 0;
  return (h &amp;gt;&amp;gt;&amp;gt; 0).toString(16).padStart(8, &apos;0&apos;);
}
// length-prefixed encoding: len(x) || x, so boundaries are unambiguous
function lp(s) { return String(s.length).padStart(4, &apos;0&apos;) + s; }&lt;/p&gt;
&lt;p&gt;// RFC 9497 Finalize binds: len(x) || x || len(N) || N || &quot;Finalize&quot;
function finalize(x, N) { return toyHash(lp(x) + lp(N) + &apos;Finalize&apos;); }&lt;/p&gt;
&lt;p&gt;// Without length prefixes, (&quot;ab&quot;,&quot;c&quot;) and (&quot;a&quot;,&quot;bc&quot;) would collide. With them, they don&apos;t:
console.log(&apos;naive  ab|c   =&apos;, toyHash(&apos;ab&apos; + &apos;c&apos;));
console.log(&apos;naive  a|bc   =&apos;, toyHash(&apos;a&apos; + &apos;bc&apos;), &apos;(collides above)&apos;);
console.log(&apos;finalize ab,c =&apos;, finalize(&apos;ab&apos;, &apos;c&apos;));
console.log(&apos;finalize a,bc =&apos;, finalize(&apos;a&apos;, &apos;bc&apos;), &apos;(distinct)&apos;);
`}&lt;/p&gt;
&lt;p&gt;Finally, the honesty. RFC 9497 section 7.2.3 states outright that every &lt;code&gt;BlindEvaluate&lt;/code&gt; is a static-Diffie-Hellman oracle sample, and quantifies the erosion: the best-known attacks reduce the group&apos;s security by $\log_2(Q)/2$ bits, where $Q$ is the number of calls [@rfc9497], [@browngallant04], [@cheon06]. 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.&lt;/p&gt;
&lt;h2&gt;6. Who Runs This Today&lt;/h2&gt;
&lt;p&gt;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 &lt;em&gt;do&lt;/em&gt; know. Here are the four deployed consumers, each at &quot;exactly where and why it calls the OPRF,&quot; and each paired with the look-alike an expert must not confuse it with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OPAQUE, standardized as RFC 9807 in July 2025.&lt;/strong&gt; 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 [@rfc9807], [@opaque-paper]. 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.&lt;/p&gt;
&lt;p&gt;RFC 9807 derives a &lt;em&gt;per-user&lt;/em&gt; OPRF key from a single seed, keyed by each &lt;code&gt;credential_identifier&lt;/code&gt;, 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 [@rfc9807]. The RFC names WhatsApp&apos;s end-to-end encrypted backups as a driving use case, and Meta&apos;s audited &lt;code&gt;opaque-ke&lt;/code&gt; is the reference implementation [@rfc9807], [@opaque-ke]. If Part 12 explained why offline dictionary attacks are the thing to fear, OPAQUE is the OPRF&apos;s answer to them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Privacy Pass, RFCs 9576, 9577, and 9578, June 2024.&lt;/strong&gt; The in-scope token type is &lt;code&gt;0x0001 = VOPRF(P-384, SHA-384)&lt;/code&gt;; RFC 9578 says plainly that its privately verifiable issuance is based on the OPRF [@rfc9578], [@rfc9576], [@rfc9577]. This is the VOPRF most engineers meet by name. But Privacy Pass is &lt;em&gt;not one thing&lt;/em&gt;, and this is the boundary the whole article has been protecting.&lt;/p&gt;

RFC 9578 defines two interchangeable token types. Token `0x0001` is a VOPRF, and it is an OPRF. Token `0x0002` is Blind RSA (2048-bit), standardized in RFC 9474, and it is a blind *signature* -- Chaum&apos;s 1982 construction, publicly verifiable, and emphatically *not* an OPRF [@rfc9578], [@rfc9474]. The most visible Privacy Pass deployment in the world, Apple&apos;s Private Access Tokens, uses the Blind-RSA token, not the VOPRF one. The two make a real, standardized trade-off: VOPRF tokens are issuer-verifiable only, small, and easy to rotate; Blind-RSA tokens are publicly verifiable with the RSA public key but larger and heavier to rotate [@rfc9578]. So never write &quot;Privacy Pass equals OPRF.&quot; One of its two token types is not an OPRF at all -- a point the earlier field-guide entry on anonymous credentials shipping develops further.
&lt;p&gt;&lt;strong&gt;Password breach checking.&lt;/strong&gt; Google&apos;s Password Checkup performs a blinded, OPRF-based lookup: it checks your credential against a corpus of breached ones without Google learning the credential [@thomas19], [@google-blog]. Microsoft&apos;s Password Monitor in Edge does something related but not identical, and the difference matters.&lt;/p&gt;

It is tempting to describe Microsoft Password Monitor as &quot;an OPRF breach check,&quot; but that undersells the machinery. The OPRF is one layer; the actual set-matching runs on *homomorphic encryption* -- Labeled Private Set Intersection built on the Microsoft SEAL library, evaluating the match under encryption [@msft-monitor]. The OPRF hides the query; the fully homomorphic layer performs the private lookup against the breach set. Do not attribute the set intersection to the OPRF alone. And note the well-known *non-OPRF* alternative for the same job: Have I Been Pwned&apos;s k-anonymity range API, where the client sends the first five hex digits of a SHA-1 hash and receives the whole bucket to search locally [@cloudflare-kanon]. That leaks a hash prefix by design; it is prefix-bucketed hashing, simpler and cache-friendly, but not oblivious. The earlier entry on [Edge&apos;s two password cryptographies](/blog/edge-two-password-cryptographies/) traces this contrast in detail.
&lt;p&gt;&lt;strong&gt;OPRF-based private set intersection.&lt;/strong&gt; OPRF-PSI is &lt;em&gt;one approach among several&lt;/em&gt; -- plain Diffie-Hellman PSI, oblivious-transfer and circuit-based PSI, and homomorphic-encryption PSI all compete for different workloads [@fipr05]. 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 [@kkrt16].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Consumer&lt;/th&gt;
&lt;th&gt;What the OPRF does there&lt;/th&gt;
&lt;th&gt;The non-OPRF look-alike to not confuse it with&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;OPAQUE (RFC 9807)&lt;/td&gt;
&lt;td&gt;Makes the password oblivious; defeats precomputation; per-user keys block enumeration [@rfc9807]&lt;/td&gt;
&lt;td&gt;A plain salted or peppered hash, where the server &lt;em&gt;must&lt;/em&gt; see the password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privacy Pass token &lt;code&gt;0x0001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;VOPRF issuance of unlinkable, issuer-verifiable tokens [@rfc9578]&lt;/td&gt;
&lt;td&gt;Token &lt;code&gt;0x0002&lt;/code&gt; = Blind RSA, a publicly verifiable blind &lt;em&gt;signature&lt;/em&gt; (Apple PAT) [@rfc9474]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Password Checkup&lt;/td&gt;
&lt;td&gt;Blinded OPRF lookup against a breach corpus [@thomas19]&lt;/td&gt;
&lt;td&gt;HIBP k-anonymity range API: leaks a SHA-1 prefix, not oblivious [@cloudflare-kanon]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft Password Monitor&lt;/td&gt;
&lt;td&gt;OPRF hides the query; FHE Labeled PSI does the match [@msft-monitor]&lt;/td&gt;
&lt;td&gt;Assuming the OPRF alone does the set intersection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPRF-based PSI&lt;/td&gt;
&lt;td&gt;Oblivious set membership via 2HashDH or KKRT16 [@kkrt16]&lt;/td&gt;
&lt;td&gt;DH-PSI, OT/circuit-PSI, and FHE-PSI, which are different approaches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The production substrates are a short list: Cloudflare&apos;s CIRCL in Go, the &lt;code&gt;voprf&lt;/code&gt; crate and &lt;code&gt;opaque-ke&lt;/code&gt; in Rust, and curve25519-dalek providing the ristretto255 group backend [@circl], [@voprf-crate], [@opaque-ke], [@dalek]. 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.&lt;/p&gt;
&lt;h2&gt;7. The Decision Matrix&lt;/h2&gt;
&lt;p&gt;Given a real problem, each decision has a crisp rule. Here they are, head to head.&lt;/p&gt;
&lt;h3&gt;Which mode?&lt;/h3&gt;
&lt;p&gt;The primary choice is the mode, and it falls straight out of the three questions.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;OPRF (base)&lt;/th&gt;
&lt;th&gt;VOPRF&lt;/th&gt;
&lt;th&gt;POPRF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Questions answered&lt;/td&gt;
&lt;td&gt;Q1 (blind)&lt;/td&gt;
&lt;td&gt;Q1 + Q2 (which key)&lt;/td&gt;
&lt;td&gt;Q1 + Q2 + public tweak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra output&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;DLEQ proof (batchable)&lt;/td&gt;
&lt;td&gt;DLEQ proof (batchable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public input &lt;code&gt;info&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes, $F(k, x, \text{info})$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Construction&lt;/td&gt;
&lt;td&gt;2HashDH&lt;/td&gt;
&lt;td&gt;2HashDH + DLEQ&lt;/td&gt;
&lt;td&gt;3HashSDHI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assumption&lt;/td&gt;
&lt;td&gt;One-More Gap CDH&lt;/td&gt;
&lt;td&gt;One-More Gap CDH&lt;/td&gt;
&lt;td&gt;One-More Gap SDHI (to q-DL, AGM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client trust in key&lt;/td&gt;
&lt;td&gt;must trust&lt;/td&gt;
&lt;td&gt;verifies&lt;/td&gt;
&lt;td&gt;verifies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best suited for&lt;/td&gt;
&lt;td&gt;trusted-key internal oracle&lt;/td&gt;
&lt;td&gt;anonymity systems, OPAQUE&lt;/td&gt;
&lt;td&gt;per-tenant, rotation, buckets&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Sources: RFC 9497 sections 3.3.1 through 3.3.3, plus DGSTV18 and TCRSTW22 [@rfc9497], [@dgstv18], [@tcrstw22]. &lt;strong&gt;The rule:&lt;/strong&gt; if you trust the server&apos;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 &lt;em&gt;public&lt;/em&gt; label such as a per-tenant salt, rotation epoch, or rate-limit bucket, use the POPRF, and never put a secret in &lt;code&gt;info&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Which ciphersuite?&lt;/h3&gt;
&lt;p&gt;Pick the ciphersuite by your query budget, because the group size is what buys you static-Diffie-Hellman headroom (Section 8).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ciphersuite&lt;/th&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Nominal security&lt;/th&gt;
&lt;th&gt;Use when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;ristretto255-SHA512&lt;/td&gt;
&lt;td&gt;ristretto255&lt;/td&gt;
&lt;td&gt;128-bit&lt;/td&gt;
&lt;td&gt;Default: prime-order, fast, no cofactor traps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;decaf448-SHAKE256&lt;/td&gt;
&lt;td&gt;decaf448&lt;/td&gt;
&lt;td&gt;224-bit&lt;/td&gt;
&lt;td&gt;Internet-facing oracle needing static-DH headroom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P-256-SHA256&lt;/td&gt;
&lt;td&gt;NIST P-256&lt;/td&gt;
&lt;td&gt;128-bit&lt;/td&gt;
&lt;td&gt;FIPS or interop constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P-384-SHA384&lt;/td&gt;
&lt;td&gt;NIST P-384&lt;/td&gt;
&lt;td&gt;192-bit&lt;/td&gt;
&lt;td&gt;Higher budget; Privacy Pass &lt;code&gt;0x0001&lt;/code&gt; uses this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P-521-SHA512&lt;/td&gt;
&lt;td&gt;NIST P-521&lt;/td&gt;
&lt;td&gt;256-bit&lt;/td&gt;
&lt;td&gt;Maximum static-Diffie-Hellman headroom&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Source: RFC 9497 sections 4.1 through 4.5 [@rfc9497]. &lt;strong&gt;The rule:&lt;/strong&gt; default to ristretto255-SHA512. If you expose an &lt;em&gt;unmetered public&lt;/em&gt; oracle on a 128-bit group, the $\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 [@rfc9497]. 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 [@rfc9380], [@rfc9496].&lt;/p&gt;
&lt;h3&gt;Which token cryptography?&lt;/h3&gt;
&lt;p&gt;If you are issuing anonymous tokens, the choice between the two Privacy Pass token types is a genuine trade-off, not a default.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;VOPRF token &lt;code&gt;0x0001&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Blind-RSA token &lt;code&gt;0x0002&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Primitive&lt;/td&gt;
&lt;td&gt;OPRF (VOPRF, P-384/SHA-384)&lt;/td&gt;
&lt;td&gt;Blind &lt;em&gt;signature&lt;/em&gt; (RSA-2048), not an OPRF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verifiability&lt;/td&gt;
&lt;td&gt;Issuer-verifiable only (secret key)&lt;/td&gt;
&lt;td&gt;Publicly verifiable (RSA public key)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token size&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;Larger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key rotation&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Heavier (RSA keypair)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flagship deployment&lt;/td&gt;
&lt;td&gt;CDN anonymous rate-limiting&lt;/td&gt;
&lt;td&gt;Apple Private Access Tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spec&lt;/td&gt;
&lt;td&gt;RFC 9578, RFC 9497&lt;/td&gt;
&lt;td&gt;RFC 9578, RFC 9474&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Sources: RFC 9578 and RFC 9474 [@rfc9578], [@rfc9474]. &lt;strong&gt;The rule:&lt;/strong&gt; if only the issuer needs to verify tokens and you want small, easily rotated tokens, use the VOPRF token. If a &lt;em&gt;third party&lt;/em&gt; 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.&lt;/p&gt;
&lt;h3&gt;The remaining two, briefly&lt;/h3&gt;
&lt;p&gt;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 [@jkk14], [@kkrt16]. 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 [@thomas19], [@cloudflare-kanon].&lt;/p&gt;
&lt;p&gt;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 &lt;em&gt;must see the secret&lt;/em&gt; 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 &quot;the server never saw it.&quot; Which is worth asking: what, precisely, can this primitive prove -- and what can it provably not?&lt;/p&gt;
&lt;h2&gt;8. What the Assumptions Buy, and What They Cost&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it delivers.&lt;/strong&gt; RFC 9497 section 7.1 enumerates the guarantees [@rfc9497]. 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 -- &lt;em&gt;unconditional input secrecy&lt;/em&gt;. 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 [@rfc9497]. That is not &quot;secure until someone finds a faster algorithm.&quot; It is information-theoretic. The secret was never in the message, so there is nothing for computation to extract.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it rests on.&lt;/strong&gt; 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 [@rfc9497], [@jkk14], [@tcrstw22]. The POPRF&apos;s assumption comes with a load-bearing result: it is shown to be implied by the plain $q$-discrete-logarithm assumption in the algebraic group model [@tcrstw22]. 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it costs, forever.&lt;/strong&gt; Now the third question comes due. Every &lt;code&gt;BlindEvaluate&lt;/code&gt; is, by construction, a static-Diffie-Hellman oracle sample: the server computes $x \mapsto x^k$ for a fixed secret $k$, and Brown-Gallant and Cheon showed that collecting such samples recovers $k$ at a cost that drops with the count [@rfc9497], [@browngallant04], [@cheon06].&lt;/p&gt;

A service that, for a fixed secret key $k$, answers queries of the form $x \mapsto x^k$. Each answer is a sample that erodes the key: after $Q$ queries, the best-known attacks recover $k$ at a cost reduced by $\log_2(Q)/2$ bits (the Brown-Gallant and Cheon bound). Offering an OPRF *is* offering this oracle [@browngallant04], [@cheon06].
&lt;p&gt;The specification does not bury this. It states it:&lt;/p&gt;

&quot;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.&quot; -- RFC 9497, section 7.2.3
&lt;p&gt;Work the arithmetic and the design pressure becomes concrete. On a 128-bit group, effective key security is $128 - \log_2(Q)/2$ bits. Answer $2^{40}$ queries and you have shed 20 bits, down to 108. Answer $2^{60}$ and you are at 98. This is not an implementation flaw you can patch; it is a lower bound on the attacker&apos;s advantage that &lt;em&gt;no construction in this family removes&lt;/em&gt;, because it follows from the primitive doing the one thing it exists to do.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;BlindEvaluate calls $Q$&lt;/th&gt;
&lt;th&gt;Bits lost, $\log_2(Q)/2$&lt;/th&gt;
&lt;th&gt;Effective security on a 128-bit group&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;$2^{20}$&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;118 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$2^{40}$&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;108 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$2^{60}$&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;98 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$2^{80}$&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;88 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &quot;Keep this key secret forever&quot; and &quot;answer unlimited public blind queries on a 128-bit group&quot; cannot both hold. The static-Diffie-Hellman erosion is inherent to exposing the oracle, so an unmetered public OPRF on a 128-bit group provably cannot retain 128-bit key security [@rfc9497], [@browngallant04]. The only resolutions are operational: rate-limit &lt;code&gt;BlindEvaluate&lt;/code&gt;, rotate the key on a schedule, or move to a larger group (decaf448, P-384, P-521). You are choosing a budget, not eliminating a cost.&lt;/p&gt;
&lt;/blockquote&gt;

Look at what just happened across five sections. The strong-Diffie-Hellman decay that doomed the 2009 Jarecki-Liu construction was treated then as a fatal flaw -- the reason to abandon a whole generation [@jareckiliu09], [@cheon06]. The *same physics* reappears in RFC 9497, but now it is a written-down budget with a formula and a mitigation list [@rfc9497]. Nothing about the mathematics changed. What changed is the engineering posture: a phenomenon that was a silent, disqualifying weakness became an explicit, managed policy knob. This is often how a field matures -- not by defeating a limit, but by naming it precisely enough to live with it.
&lt;p&gt;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:&lt;/p&gt;

&quot;There is currently no security analysis available for the VOPRF protocol described in this document in a setting with multiple server keys or batching.&quot; -- RFC 9497, section 7.2.1
&lt;p&gt;The deployed reality -- many issuer keys, batched proofs -- outruns the single-key proof it is built on [@rfc9497]. And the whole family rests on discrete log, so a scalable quantum computer running Shor&apos;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.&lt;/p&gt;
&lt;h2&gt;9. Where It Is Still Moving&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The post-quantum frontier, which opened with a cautionary tale.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;p&gt;In 2020, Dan Boneh, Dmitry Kogan, and Katharine Woo built the first substantial post-quantum OPRF from supersingular isogenies [@bkw20]. 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 [@basso21].&lt;/p&gt;
&lt;p&gt;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 &lt;em&gt;two&lt;/em&gt; constructions. Its second one revived the 1997 Naor-Reingold PRF, reinterpreted over commutative group actions (the CSIDH setting) [@bkw20], [@naorreingold97]. 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.&lt;/p&gt;
&lt;p&gt;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 [@adds21]; Albrecht and Kamil Doruk Gur made it &quot;practical-ish and thresholdisable&quot; in 2024, cutting its lattice bandwidth roughly fourfold and adding a threshold variant [@albrechtgur24]. 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 [@beullens25].&lt;/p&gt;
&lt;p&gt;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, &lt;em&gt;verifiable&lt;/em&gt;, &lt;em&gt;partially oblivious&lt;/em&gt; post-quantum OPRF, matching the classical profile on all three counts at once, does not yet exist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The proof the industry is running ahead of.&lt;/strong&gt; Section 8&apos;s second pull quote is also an open problem: prove the multi-key, batched VOPRF that Privacy Pass actually deploys [@rfc9497]. Batching is a well-used heuristic, but the formal guarantee still covers only the single-key case.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Removing the single point of failure.&lt;/strong&gt; 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 [@rfc9807]. Round-optimal, misuse-resistant threshold OPRF specifications are still maturing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The limits that are policy, not cryptanalysis.&lt;/strong&gt; 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 [@browngallant04], [@cheon06].&lt;/p&gt;
&lt;p&gt;And verifiability quietly collapses if a malicious issuer hands each user a different public key, so making &quot;one key for everyone&quot; &lt;em&gt;auditable&lt;/em&gt; needs key-transparency infrastructure that does not yet ship as a turnkey component [@rfc9497]. 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 [@rfc9497].&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;10. The Rules, Made Operational&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pick the mode by the questions.&lt;/strong&gt; Trust the key by construction: base OPRF. Must verify which key: VOPRF. Must bind a public label: POPRF, and never smuggle a secret into &lt;code&gt;info&lt;/code&gt;, which is public by definition [@rfc9497], [@tcrstw22]. &lt;strong&gt;Pick the ciphersuite by the query budget.&lt;/strong&gt; 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 [@rfc9497], [@rfc9380], [@rfc9496].&lt;/p&gt;

flowchart TD
    Start[&quot;Choose an OPRF deployment&quot;] --&amp;gt; A{&quot;Could the server be adversarial about which key it uses?&quot;}
    A --&amp;gt;|no, key trusted| B[&quot;Base OPRF&quot;]
    A --&amp;gt;|yes| C{&quot;Must you bind a public label such as tenant or epoch?&quot;}
    C --&amp;gt;|no| D[&quot;VOPRF&quot;]
    C --&amp;gt;|yes| E[&quot;POPRF&quot;]
    B --&amp;gt; F{&quot;Unmetered public oracle?&quot;}
    D --&amp;gt; F
    E --&amp;gt; F
    F --&amp;gt;|no| G[&quot;ristretto255-SHA512 default&quot;]
    F --&amp;gt;|yes| H[&quot;decaf448 or P-384 or P-521&quot;]
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every call: draw a &lt;em&gt;fresh, uniform blind&lt;/em&gt;, and a fresh nonce for the DLEQ proof. Every received element: &lt;em&gt;validate&lt;/em&gt; it -- on the curve, in range, and not the identity -- before you touch it [@rfc9497]. Every secret operation, including &lt;code&gt;BlindEvaluate&lt;/code&gt; and &lt;code&gt;GenerateProof&lt;/code&gt;: run it in &lt;em&gt;constant time&lt;/em&gt; [@rfc9497]. Every output: &lt;em&gt;domain-separate&lt;/em&gt; with the &lt;code&gt;&quot;Finalize&quot;&lt;/code&gt; tag and mode context, and keep inputs within the length limit [@rfc9497]. For a VOPRF or POPRF: publish and pin &lt;em&gt;one&lt;/em&gt; public key, transparently, for everyone. For a POPRF: treat an &lt;code&gt;InverseError&lt;/code&gt; as a signal that the tweak collided and it is time to rotate [@tcrstw22]. And bound $Q$: rate-limit &lt;code&gt;BlindEvaluate&lt;/code&gt; or rotate keys so the static-Diffie-Hellman loss stays inside budget [@rfc9497], [@browngallant04].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &quot;never&quot; 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.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Anti-pattern&lt;/th&gt;
&lt;th&gt;Why it breaks&lt;/th&gt;
&lt;th&gt;Question violated&lt;/th&gt;
&lt;th&gt;The fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Sending the un-blinded input&lt;/td&gt;
&lt;td&gt;The server sees the input; obliviousness is gone&lt;/td&gt;
&lt;td&gt;Q1&lt;/td&gt;
&lt;td&gt;Always apply the fresh blind before sending [@rfc9497]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A MAC or HKDF &quot;used as an OPRF&quot;&lt;/td&gt;
&lt;td&gt;If the server computes it, it &lt;em&gt;saw&lt;/em&gt; the input&lt;/td&gt;
&lt;td&gt;Q1&lt;/td&gt;
&lt;td&gt;Use a real two-party OPRF; the blind is the point [@rfc9497]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reusing the blind across calls&lt;/td&gt;
&lt;td&gt;Two evaluations become linkable, and the input can leak&lt;/td&gt;
&lt;td&gt;Q1&lt;/td&gt;
&lt;td&gt;Fresh, uniform blind every call [@rfc9497]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skipping element validation&lt;/td&gt;
&lt;td&gt;Malformed points enable key-probing attacks&lt;/td&gt;
&lt;td&gt;Q1/Q3&lt;/td&gt;
&lt;td&gt;Reject off-curve, out-of-range, and identity elements [@rfc9497]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dropping or unsoundly batching the DLEQ&lt;/td&gt;
&lt;td&gt;A malicious server can use a per-client key and tag you&lt;/td&gt;
&lt;td&gt;Q2&lt;/td&gt;
&lt;td&gt;Verify the proof; combine batches soundly [@rfc9497], [@dgstv18]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A per-user public key&lt;/td&gt;
&lt;td&gt;The unique key is itself a deanonymizing tag&lt;/td&gt;
&lt;td&gt;Q2&lt;/td&gt;
&lt;td&gt;Pin one transparently published key for all [@rfc9497]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-constant-time secret ops&lt;/td&gt;
&lt;td&gt;The key leaks through timing&lt;/td&gt;
&lt;td&gt;Q3&lt;/td&gt;
&lt;td&gt;Constant-time group ops, proof, and evaluate [@rfc9497]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Treating POPRF &lt;code&gt;info&lt;/code&gt; as secret&lt;/td&gt;
&lt;td&gt;&lt;code&gt;info&lt;/code&gt; is public by definition; obliviousness assumed of it fails&lt;/td&gt;
&lt;td&gt;Q1&lt;/td&gt;
&lt;td&gt;Put only public labels in &lt;code&gt;info&lt;/code&gt; [@tcrstw22]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An unmetered oracle on a 128-bit group&lt;/td&gt;
&lt;td&gt;The key erodes by $\log_2(Q)/2$ bits, unbounded&lt;/td&gt;
&lt;td&gt;Q3&lt;/td&gt;
&lt;td&gt;Rate-limit, rotate, or use a bigger group [@rfc9497], [@browngallant04]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Using an OPRF where you need public verifiability&lt;/td&gt;
&lt;td&gt;A third party cannot verify a VOPRF token&lt;/td&gt;
&lt;td&gt;(wrong primitive)&lt;/td&gt;
&lt;td&gt;Use a blind signature (Blind RSA), not an OPRF [@rfc9474]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;{`
// STRUCTURAL MODEL. Shows why reusing the blind across calls destroys unlinkability.
// Same additive group model: a &quot;point&quot; is an integer mod q, scalar*point is mod q.
const q = 2147483647;
const mod = (a) =&amp;gt; ((a % q) + q) % q;
const scalarMul = (s, point) =&amp;gt; mod(s * point);&lt;/p&gt;
&lt;p&gt;const Hx = 991;                              // H(x): the SAME secret input, evaluated twice
const blindedWith = (r) =&amp;gt; scalarMul(r, Hx); // what goes on the wire: r*H(x)&lt;/p&gt;
&lt;p&gt;// BAD: reuse the same blind r for both calls
const rFixed = 55555;
const wire1_bad = blindedWith(rFixed);
const wire2_bad = blindedWith(rFixed);
console.log(&apos;reused blind -&amp;gt; wire equal? &apos;, wire1_bad === wire2_bad, &apos;(server links the two as one input)&apos;);&lt;/p&gt;
&lt;p&gt;// GOOD: a fresh uniform blind each call
const wire1_ok = blindedWith(37337);
const wire2_ok = blindedWith(88123);
console.log(&apos;fresh blind  -&amp;gt; wire equal? &apos;, wire1_ok === wire2_ok, &apos;(server cannot link them)&apos;);
`}&lt;/p&gt;
&lt;p&gt;Most of those &quot;nevers&quot; 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.&lt;/p&gt;

The `voprf` Rust crate and Cloudflare&apos;s CIRCL both implement all three modes against the RFC 9497 test vectors [@voprf-crate], [@circl]. 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&apos;s response, and watch `VerifyProof` reject it. That single afternoon teaches more about Q1 and Q2 than any diagram.
&lt;h2&gt;11. Questions Engineers Actually Ask&lt;/h2&gt;


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 [@rfc9497]. An HMAC that the server evaluates on your plaintext input is not oblivious by any definition.


In any setting with an untrusted server, yes, decisively. The base OPRF answers only &quot;is it blind?&quot; 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 [@rfc9497], [@dgstv18]. The VOPRF&apos;s proof, plus one transparently pinned public key, is what closes that gap.


Because every `BlindEvaluate` is a static-Diffie-Hellman oracle sample, and collecting samples recovers the key at a cost that drops by $\log_2(Q)/2$ bits after $Q$ calls [@rfc9497], [@browngallant04], [@cheon06]. 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.


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&apos;s Private Access Tokens, the most visible deployment [@rfc9578], [@rfc9474]. So &quot;Privacy Pass equals OPRF&quot; is wrong; one of its two token types is a different primitive entirely.


No. Checkup performs a blinded, OPRF-based lookup, so Google does not learn the credential being checked [@thomas19]. Have I Been Pwned&apos;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 [@cloudflare-kanon]. That leaks a hash prefix by design. It is simpler and cache-friendly, but it is not oblivious.


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 [@rfc9497]. The blind and the proof nonce must be freshly and uniformly drawn every single call. This is non-negotiable.


No. Every RFC 9497 ciphersuite rests on discrete-log hardness, which Shor&apos;s algorithm breaks, so a scalable quantum computer defeats both obliviousness and unforgeability [@rfc9497]. Post-quantum OPRFs are an open frontier: the first serious isogeny construction was broken within a year [@bkw20], [@basso21], and lattice and MPC candidates remain far heavier than classical evaluation [@albrechtgur24], [@beullens25]. And to preempt the related question: no, a POPRF&apos;s `info` is not secret -- it is public by definition [@tcrstw22].

&lt;p&gt;Read back across the whole story and the shape is unmistakable. Every named failure was one of three questions answered wrong. FIPR&apos;s early constructions answered &quot;is it blind?&quot; but too expensively to ship. Jarecki and Liu answered &quot;which key?&quot; too, and then the ground under &quot;how many asks?&quot; gave way beneath them. A base OPRF deployed where a VOPRF was needed answers &quot;which key?&quot; wrong and tags its users. A reused blind answers &quot;is it blind?&quot; wrong. An unmetered public oracle answers &quot;how many asks?&quot; 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 &lt;em&gt;is&lt;/em&gt; the history.&lt;/p&gt;
&lt;p&gt;And the resolution is a single move, assembled from primitives this series has already built. Take Chaum&apos;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 &lt;em&gt;applies&lt;/em&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;

The server helped, and it still never saw.
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;oblivious-pseudorandom-functions-oprf-voprf-poprf&quot; keyTerms={[
  { term: &quot;OPRF&quot;, definition: &quot;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).&quot; },
  { term: &quot;VOPRF&quot;, definition: &quot;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.&quot; },
  { term: &quot;POPRF&quot;, definition: &quot;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.&quot; },
  { term: &quot;2HashDH&quot;, definition: &quot;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.&quot; },
  { term: &quot;DLEQ proof&quot;, definition: &quot;A Chaum-Pedersen non-interactive proof that two discrete logarithms are equal, binding the server&apos;s response to its committed key; batchable via a random linear combination.&quot; },
  { term: &quot;Blinding factor&quot;, definition: &quot;The fresh, uniform random scalar r that multiplies HashToGroup(x) so the transmitted point is uniform and independent of the input.&quot; },
  { term: &quot;Static Diffie-Hellman oracle&quot;, definition: &quot;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.&quot; },
  { term: &quot;Unlinkability&quot;, definition: &quot;Unconditional input secrecy: because the blind is uniform, the server cannot link or learn anything about the input, even with unbounded computation.&quot; }
]} questions={[
  { q: &quot;Which of the three questions does a base OPRF leave unanswered, and what attack does that enable?&quot;, a: &quot;It does not answer &apos;which key?&apos; A malicious server can use a per-client key and later tag which client made a request, breaking anonymity.&quot; },
  { q: &quot;Why does a POPRF&apos;s info have to be public?&quot;, a: &quot;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.&quot; },
  { q: &quot;Why can an unmetered public OPRF on a 128-bit group not keep 128-bit key security?&quot;, a: &quot;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.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>oblivious-pseudorandom-function</category><category>oprf</category><category>voprf</category><category>cryptography</category><category>privacy-pass</category><category>opaque</category><category>rfc-9497</category><category>applied-cryptography</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>The Doorway Into the Group: A Field Guide to Hashing a String Onto an Elliptic Curve (RFC 9380)</title><link>https://paragmali.com/blog/the-doorway-into-the-group-a-field-guide-to-hashing-a-string/</link><guid isPermaLink="true">https://paragmali.com/blog/the-doorway-into-the-group-a-field-guide-to-hashing-a-string/</guid><description>Turning a string into an elliptic-curve point is a two-decade problem behind a one-line API. Why H(m)*G and hunt-and-peck both fail, and how RFC 9380 fixes it.</description><pubDate>Sun, 12 Jul 2026 15:35:47 GMT</pubDate><content:encoded>
**Turning a string into an elliptic-curve point is a two-decade research problem hiding behind a one-line API.** A safe map must produce a point whose discrete log is unknown, in time that does not depend on the input, with a distribution a proof can treat as a random oracle. The two obvious shortcuts each fail one of those tests: $H(m)\cdot G$ publishes the discrete log (forge BLS, break OPRFs), and try-and-increment leaks the input through timing (the Dragonblood attack read WPA3 passwords) [@rfc9380] [@vr20]. RFC 9380 (2023) finally delivers all three at once by hashing to two field elements, mapping each with a per-curve function, and adding the points [@rfc9380]. The standard is now quietly load-bearing under BLS on Ethereum, every elliptic-curve OPRF, OPAQUE, Privacy Pass, and WPA3&apos;s constant-time fix. This guide shows you why the naive maps fail, how the standard works, and exactly which suite to use for your curve and protocol.
&lt;h2&gt;1. They Timed the Handshake and Read the Wi-Fi Password&lt;/h2&gt;
&lt;p&gt;In April 2019, two researchers announced they could recover your Wi-Fi password without guessing it, phishing it, or breaking any cipher. They simply &lt;em&gt;timed&lt;/em&gt; how long WPA3 took to turn the password into a point on an elliptic curve, and the clock spelled out the secret [@vr20] [@dragonblood-site]. The flaw was not in the encryption or the key exchange. It lived in a single, overlooked step that nearly every discrete-log protocol quietly leans on: the map that carries an arbitrary string into the group.&lt;/p&gt;
&lt;p&gt;Get that one step wrong in the &lt;em&gt;other&lt;/em&gt; obvious way, computing the point as $H(m)\cdot G$, and you leak a different secret entirely. Now &lt;em&gt;anyone&lt;/em&gt; can forge the signatures that map was supposed to protect [@rfc9380] [@bls01]. Two shortcuts, two spilled secrets, one primitive. This is the field guide to getting it right.&lt;/p&gt;

A function that maps an arbitrary byte string to a point on an elliptic curve, such that the point&apos;s discrete logarithm (its relationship to the group generator) is unknown even to the party computing it. RFC 9380 standardizes the interface as `hash_to_curve` and `encode_to_curve` [@rfc9380].
&lt;p&gt;The primitive looks trivial. Hash the input, get a point, move on. But &quot;get a point&quot; hides a genuine algorithm, because on an elliptic curve only about half of the field elements are valid $x$-coordinates. The two things every engineer tries first are the two things you must never ship, and each betrays a different secret. So before you write a single line, ask three questions.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; When you turn a string into a curve point, ask: (1) do you know the point&apos;s discrete log; (2) does your running time depend on the input; and (3) is the output distribution one your protocol&apos;s proof can treat as a random oracle? Every safe construction answers &lt;strong&gt;no, no, yes&lt;/strong&gt;. Every named failure in this article is a wrong answer to exactly one of these.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;WPA3-Personal answered the second question wrong. Its handshake, Simultaneous Authentication of Equals (SAE), derives a &quot;password element&quot; by hunt-and-peck: hash the password with a counter, test whether the result is a valid coordinate, and increment until it is [@rfc7664]. The number of iterations depends on the password, so the time the handshake takes is a function of the secret. Mathy Vanhoef and Eyal Ronen showed that an attacker within range can measure that timing and mount an offline dictionary attack, recovering the password without ever seeing a valid login [@vr20] [@dragonblood-site].&lt;/p&gt;

sequenceDiagram
    participant STA as Client (victim)
    participant AP as Access point
    participant Atk as Attacker (passive)
    Note over STA,AP: WPA3-SAE, password shared by both sides
    STA-&amp;gt;&amp;gt;STA: Derive password element by hunt-and-peck
    Note over STA: Iteration count depends on the password
    STA-&amp;gt;&amp;gt;AP: Send SAE Commit after the loop finishes
    Atk--&amp;gt;&amp;gt;STA: Measure time from trigger to Commit
    Note over Atk: Timing reveals the iteration count
    Atk-&amp;gt;&amp;gt;Atk: Offline dictionary attack narrows candidates
&lt;p&gt;RFC 9380, the standard that finally settled this primitive, is blunt about why try-and-increment is unsafe.&lt;/p&gt;

&quot;The running time of this method, which is generally referred to as a probabilistic try-and-increment algorithm, depends on the input string. As such, it is not safe to use in protocols sensitive to timing side channels, as was exemplified by the Dragonblood attack.&quot; -- RFC 9380, Appendix A [@rfc9380]
&lt;p&gt;The story got worse before it got better. The Wi-Fi Alliance&apos;s first attempt at a fix switched SAE to Brainpool curves, which &lt;em&gt;reintroduced&lt;/em&gt; a leak: the encoder now had to loop to find a hash smaller than the prime, and the number of iterations depended on the password &lt;em&gt;and&lt;/em&gt; the client&apos;s MAC address [@dragonblood-site].This second act is the whole lesson in miniature: constant time cannot be patched in curve by curve. The disclosure site concludes that &quot;implementing Dragonfly and WPA3 without side-channel leaks is surprisingly hard&quot; [@dragonblood-site]. Two rounds of the same class of bug, in the handshake now standard across WPA3 Wi-Fi.&lt;/p&gt;
&lt;p&gt;Now hold that failure next to its mirror image. Suppose BLS signatures had mapped a message the naive way, $H(m) = H_n(m)\cdot G$ for a public hash $H_n$. A BLS signature is $\sigma = [sk]\cdot H(m)$, so it would equal $[sk]\cdot H_n(m)\cdot G = H_n(m)\cdot([sk]\cdot G) = H_n(m)\cdot PK$, where $PK$ is the public key [@rfc9380] [@bls01]. Every term on the right is public. Anyone forges any signature with no secret key at all. One shortcut leaked a low-entropy password through the clock; the other would hand out the signing key&apos;s power to the entire world. The primitive that sits between them is where the security of BLS, every elliptic-curve OPRF, and every password PAKE is actually won or lost.&lt;/p&gt;
&lt;p&gt;This is the sibling of a claim Part 15 made about the curve itself: &lt;a href=&quot;https://paragmali.com/blog/the-curve-was-hard-the-gap-was-soft-a-field-guide-to-using-e/&quot; rel=&quot;noopener&quot;&gt;the elliptic-curve discrete-log problem&lt;/a&gt; was hard; the danger was the soft gap around it. Here the gap has a name and a shape. It is the doorway, the map that carries untrusted strings into the group. Get the doorway wrong and you publish the discrete log, leak the password through the clock, or hand your proof a distribution it never agreed to. To see why &quot;hash to a point&quot; is an algorithm and not a one-liner, we have to go back to the moment it stopped being free.&lt;/p&gt;
&lt;h2&gt;2. When Hashing to a Group Stopped Being Free&lt;/h2&gt;
&lt;p&gt;For a generation of cryptographers, &quot;hash into the group&quot; was not a problem worth naming. In a prime field $\mathbb{F}_p^&lt;em&gt;$, the discrete-log setting Diffie and Hellman worked in, you hash your input to an integer and reduce it modulo $p$, and you are already inside the group. Every nonzero residue is a group element, so the map is a single modular reduction with nothing to prove.This is the free lunch elliptic curves took away. In $\mathbb{F}_p^&lt;/em&gt;$ the group &lt;em&gt;is&lt;/em&gt; the set of nonzero field elements, so any nonzero hash output is already a valid element. On a curve, the group is a sparse subset of the plane, and most field elements are not on it.&lt;/p&gt;
&lt;p&gt;Elliptic curves broke the deal. When Victor Miller and Neal Koblitz independently proposed elliptic-curve cryptography in 1985 and 1987, they replaced $\mathbb{F}_p^*$ with the points of a curve $y^2 = x^3 + ax + b$ [@miller85] [@koblitz87]. That change bought smaller keys and harder discrete logs, but it quietly created a new problem: for a random field element $x$, the quantity $x^3 + ax + b$ is a perfect square only about half the time, and only then is $x$ the coordinate of an actual point. &quot;Hash to a group element&quot; stopped being a reduction and became a search.&lt;/p&gt;
&lt;p&gt;Nobody worried, because for a decade nobody needed to hash &lt;em&gt;to a point&lt;/em&gt;. Ordinary ECDSA and ECDH hash to a &lt;em&gt;scalar&lt;/em&gt; and multiply a fixed generator, which never leaves the safe world of exponents.Hashing to a scalar is trivial and always safe: any integer modulo the group order is a valid exponent. Hashing to a &lt;em&gt;point&lt;/em&gt; is the hard problem, because most field elements are not on the curve.&lt;/p&gt;
&lt;p&gt;Then, around the turn of the millennium, the demand arrived from two directions at once, and both directions needed a point.&lt;/p&gt;

A signature scheme by Boneh, Lynn, and Shacham built on a bilinear pairing. The signature on a message $m$ is $\sigma = [sk]\cdot H(m)$, where $H(m)$ is the message hashed *to a curve point* and $sk$ is the secret key. Verification checks a pairing equation. Security requires that $H(m)$ have unknown discrete log, or signatures become forgeable [@bls01].
&lt;p&gt;The first direction was pairing-based cryptography. In 2001 Boneh, Lynn, and Shacham introduced BLS short signatures, whose entire construction rests on hashing the message to a curve point of unknown discrete log, because the signature &lt;em&gt;is&lt;/em&gt; that point scaled by the secret key [@bls01]. The same year, Boneh and Franklin built identity-based encryption on the identical primitive, hashing an identity string to a point [@bf01]. BLS shipped with the first answer anyone reaches for: &lt;code&gt;MapToGroup&lt;/code&gt;, a try-and-increment loop [@bls01]. Boneh and Franklin instead gave a &lt;em&gt;deterministic&lt;/em&gt; map, but only for certain supersingular curves over fields of characteristic $p \equiv 2 \pmod 3$, too narrow a class to serve as general infrastructure [@bf01].&lt;/p&gt;
&lt;p&gt;The second direction was password-authenticated key exchange. In 2008 Dan Harkins proposed Simultaneous Authentication of Equals, a balanced PAKE that maps a &lt;em&gt;password&lt;/em&gt; to a group element and then runs a zero-knowledge proof of it [@sae08]. To do the mapping it introduced hunt-and-peck, the same try-and-increment idea aimed at a secret input. SAE was standardized as RFC 7664 in 2015, including its &quot;Hunting and Pecking with ECC Groups&quot; derivation, and then shipped to consumers in 2018 as the core of WPA3-Personal [@rfc7664] [@vr20].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Pairing signatures and password PAKEs look unrelated, but both need the same thing: a hash whose output is a curve point of unknown discrete log. Pairing crypto needs it so a signature can &lt;em&gt;be&lt;/em&gt; that point; a PAKE needs it so the password element hides the password. The primitive was born load-bearing under two families of protocols in the same decade [@bls01] [@sae08].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A scoping note worth keeping honest: not every PAKE maps a password to a curve point. SPAKE2 uses fixed, independently generated group elements $M$ and $N$ rather than hashing the password onto the curve, and SRP works in an integer group, not on an elliptic curve at all. The &quot;everything depends on hash-to-curve&quot; claim is precise, not universal, and it applies to the named constructions in this article: SAE, CPace, OPAQUE&apos;s OPRF, the RFC 9497 OPRFs, and BLS.The lesson for a field guide: SPAKE2 and SRP are the counterexamples that keep &quot;all PAKEs need hash-to-curve&quot; from being true, and they stop you hunting for the primitive where it is not.&lt;/p&gt;
&lt;p&gt;So by 2008 the problem was named and both naive answers were on the table, shipped in real systems: $H(m)\cdot G$ carried the finite-field intuition onto curves, and try-and-increment searched for a valid coordinate. Each was correct in the sense that it produced a usable point. Each was also a disaster, and for opposite reasons. To see exactly what each one leaks, we have to look at the two shortcuts side by side.&lt;/p&gt;
&lt;h2&gt;3. Two Shortcuts, Two Spilled Secrets&lt;/h2&gt;
&lt;p&gt;Both obvious maps are correct in the ordinary sense: they produce a point on the curve, quickly, with a few lines of code. That is exactly what makes them dangerous. Each satisfies most of the three properties and fails precisely one, and the property each fails is different, which is why they leak different secrets.&lt;/p&gt;
&lt;h3&gt;Shortcut A: compute the point as a known multiple of the generator&lt;/h3&gt;
&lt;p&gt;Hash the message to an integer $H_n(m)$ and scalar-multiply the generator: $P = H_n(m)\cdot G$. The output is uniform on the group, costs one scalar multiplication, and is trivially constant-time. It assumes nothing about the curve. It is the finite-field intuition carried straight onto the curve, and it violates Property 1: whoever computed $P$ knows its discrete log, because they chose it.&lt;/p&gt;
&lt;p&gt;RFC 9380 does not hedge about the consequence.&lt;/p&gt;

&quot;The resulting point has a known discrete log relationship to $P$. Thus, except in cases where this method is specified by the protocol, it must not be used; doing so risks catastrophic security failures.&quot; -- RFC 9380, Appendix A [@rfc9380]
&lt;p&gt;&quot;Catastrophic&quot; is exact, not rhetorical. Take BLS. The public key is $PK = [sk]\cdot G$, and a signature is $\sigma = [sk]\cdot H(m)$. If the map were $H(m) = [H_n(m)]\cdot G$ for a public hash $H_n$, then&lt;/p&gt;
&lt;p&gt;$$\sigma = [sk]\cdot H(m) = [sk]\cdot[H_n(m)]\cdot G = [H_n(m)]\cdot([sk]\cdot G) = [H_n(m)]\cdot PK.$$&lt;/p&gt;
&lt;p&gt;Every quantity on the right -- $H_n(m)$ from the public message, $PK$ from the wire -- is public. Anyone computes the valid signature on any message directly, with no access to $sk$ [@rfc9380] [@bls01]. This is not an edge case or a timing quirk. It is total, structural forgeability on every input. The same knife cuts elsewhere: for an OPRF, knowing the discrete log of the mapped input breaks obliviousness; for a PAKE, a password element with known discrete log no longer hides the password.&lt;/p&gt;
&lt;p&gt;{`
// ILLUSTRATION, NOT AN IMPLEMENTATION.
// We model the group as integers mod a small prime order n, with
// &quot;scalar mult&quot; [k]*P written as (k * P) mod n. Discrete log is trivial
// here on purpose -- the point is the STRUCTURE of the forgery, not hardness.
function scalarMul(k, P, n) { return ((k % n) * (P % n)) % n; }&lt;/p&gt;
&lt;p&gt;const n = 97;          // toy prime group order
const G = 1;           // generator
const sk = 42;         // signer&apos;s SECRET key
const PK = scalarMul(sk, G, n);   // public key = [sk]*G&lt;/p&gt;
&lt;p&gt;// The UNSAFE map: H(m) = [Hn(m)]*G, so its discrete log Hn(m) is public.
const Hn_m = 7;                    // public hash of the message
const Hm = scalarMul(Hn_m, G, n);  // the mapped point (known DL!)&lt;/p&gt;
&lt;p&gt;// Honest signer computes sigma = [sk]*H(m)
const legitSig = scalarMul(sk, Hm, n);&lt;/p&gt;
&lt;p&gt;// Attacker never sees sk. Uses only Hn(m) and PK:
const forgedSig = scalarMul(Hn_m, PK, n);   // [Hn(m)]*PK&lt;/p&gt;
&lt;p&gt;console.log(&quot;legit signature :&quot;, legitSig);
console.log(&quot;forged signature:&quot;, forgedSig, &quot;(computed with NO secret key)&quot;);
console.log(&quot;forgery succeeds:&quot;, legitSig === forgedSig);
`}&lt;/p&gt;
&lt;h3&gt;Shortcut B: search for a valid coordinate&lt;/h3&gt;
&lt;p&gt;The other reflex fixes Property 1 and breaks Property 2. Hash the input together with a counter, test whether the candidate is a valid $x$-coordinate, and loop until it is.&lt;/p&gt;

A map that hashes the input with a counter to a candidate $x$, tests whether $x^3 + ax + b$ is a quadratic residue (a valid coordinate), and increments the counter until it is. A random $x$ works about half the time, so the expected number of tries is two [@rfc9380]. The output has *unknown* discrete log, but the number of iterations depends on the input.
&lt;p&gt;Because nobody chose the output as a multiple of $G$, its discrete log is genuinely unknown, so Property 1 holds. That is exactly why BLS and SAE could ship with it and be functionally correct. The flaw is Property 2: the loop count is a function of the input, so the running time is too. When the input is a secret -- a password, a pre-shared key -- the clock becomes an oracle. That is Dragonblood [@vr20] [@rfc9380].&lt;/p&gt;
&lt;p&gt;{`
// ILLUSTRATION, NOT AN IMPLEMENTATION.
// Count hunt-and-peck iterations for several secret inputs over a toy field.
// A constant-time map would take the SAME work for every input; this does not.
const p = 103;                 // toy prime, p = 3 mod 4
const a = 2, b = 3;            // toy curve y^2 = x^3 + a x + b&lt;/p&gt;
&lt;p&gt;function modpow(base, exp, mod) {
  let r = 1; base = base % mod;
  while (exp &amp;gt; 0) {
    if (exp &amp;amp; 1) r = (r * base) % mod;
    base = (base * base) % mod;
    exp = exp &amp;gt;&amp;gt; 1;
  }
  return r;
}
// Euler&apos;s criterion: x is a nonzero square mod p iff x^((p-1)/2) == 1
function isSquare(x, p) { return x === 0 ? true : modpow(x, (p - 1) / 2, p) === 1; }&lt;/p&gt;
&lt;p&gt;// A stand-in for H(input || ctr): deterministic, input-dependent pseudo-x.
function candidateX(seed, ctr, p) { return ((seed * 131 + ctr * 419 + 17) % p + p) % p; }&lt;/p&gt;
&lt;p&gt;function iterations(seed) {
  for (let ctr = 1; ctr &amp;lt; 200; ctr++) {
    const x = candidateX(seed, ctr, p);
    const g = (((x * x % p) * x % p) + a * x + b) % p;
    if (isSquare(g, p)) return ctr;
  }
  return -1;
}&lt;/p&gt;
&lt;p&gt;for (const pw of [11, 23, 37, 58, 71, 90]) {
  console.log(&quot;secret input &quot; + pw + &quot; -&amp;gt; &quot; + iterations(pw) + &quot; iterations&quot;);
}
console.log(&quot;Different inputs, different work: that difference is the leak.&quot;);
`}&lt;/p&gt;
&lt;p&gt;Here is the table that makes the whole article click. Two maps, three properties, and each map fails a different one.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Map&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Unknown discrete log (Prop 1)&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Constant time (Prop 2)&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Right distribution (Prop 3)&lt;/th&gt;
&lt;th&gt;The secret it spills&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;$H(m)\cdot G$&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;Violated&lt;/strong&gt; (DL is $H_n(m)$)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;Satisfied&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;Satisfied (uniform)&lt;/td&gt;
&lt;td&gt;The signing key&apos;s power -- universal forgery [@rfc9380] [@bls01]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Try-and-increment&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;Satisfied&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;Violated&lt;/strong&gt; (loop depends on input)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;Roughly satisfied&lt;/td&gt;
&lt;td&gt;The secret input -- via handshake timing [@rfc9380] [@vr20]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

Try-and-increment is only a *vulnerability* when the mapped input is secret. A BLS signature over a *public* message runs the same variable-time loop, but the message is public, so the timing reveals nothing an attacker did not already have. The Dragonblood break works because a Wi-Fi password is a low-entropy secret. This is why the accuracy-critical phrasing is &quot;variable time is dangerous when the input is secret,&quot; never &quot;variable time is always dangerous&quot; [@rfc9380] [@vr20].
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If the input to your map is a password, a PSK, or any low-entropy secret, a data-dependent loop over it is a timing oracle. Use a constant-time map. RFC 9380 declines to even specify try-and-increment &quot;because the goal is to specify algorithms that can plausibly be computed in constant time&quot; [@rfc9380].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So the requirement is now sharp, and it looks almost contradictory. You need a map whose output has unknown discrete log, like try-and-increment, but whose running time is fixed, unlike try-and-increment. You need the safety of the search without the search. For a few years it was not obvious such a thing existed. The story of how the field built it, one property at a time, is the evolution of the primitive itself.&lt;/p&gt;
&lt;h2&gt;4. Moving Each Property Into the Curve&lt;/h2&gt;
&lt;p&gt;The next thirteen years read like a chain of defenses in which the defenders keep advancing. Each generation takes one property that used to depend on the implementer&apos;s care and locks it into the construction, where it holds by mathematics rather than by discipline. Three of those steps are true property wins. The rest are the engineering that made the winning map cheap enough and general enough to run on every curve real protocols use. Being honest about which is which is the difference between a clean story and a true one.&lt;/p&gt;

flowchart TD
    Naive[&quot;Naive maps: known DL or variable time&quot;] --&amp;gt;|&quot;one leaks the DL, one leaks timing&quot;| G1[&quot;SvdW 2006: constant time, unknown DL&quot;]
    G1 --&amp;gt;|&quot;cheaper per-curve maps&quot;| G2[&quot;Icart and Simplified SWU, 2007 to 2010&quot;]
    G1 --&amp;gt;|&quot;a single map is not a random oracle&quot;| G3[&quot;BCIMRT 2010: sum of two maps&quot;]
    G2 --&amp;gt;|&quot;still a single map&quot;| G3
    G3 --&amp;gt;|&quot;map per curve family&quot;| G4[&quot;Elligator 2, 2013: Montgomery and Edwards&quot;]
    G3 --&amp;gt;|&quot;pairing curves with A zero&quot;| G5[&quot;Isogeny SSWU, 2019: BLS12-381&quot;]
    G4 --&amp;gt; RFC[&quot;RFC 9380, 2023: one interface&quot;]
    G5 --&amp;gt; RFC
&lt;h3&gt;The first constant-time map: Shallue-van de Woestijne (2006)&lt;/h3&gt;
&lt;p&gt;The contradiction from the last section -- unknown discrete log without a search -- was resolved in 2006 by Andrew Shallue and Christiaan van de Woestijne. Their map is the &quot;SW&quot; in &quot;SSWU.&quot; Given a field element $u$, algebraic identities guarantee that among a small fixed set of candidate $x$-values derived from $u$, at least one is a valid coordinate [@sw06]. So you compute the candidates, test them in a fixed order with a constant number of operations, and select a valid one. There is no rejection loop, which means no data-dependent branch, which means constant time by construction [@sw06] [@rfc9380]. The discrete log stays unknown. For the first time, a single map was simultaneously unknown-DL and constant-time on almost any curve with characteristic other than 2 or 3.&lt;/p&gt;
&lt;p&gt;The idea had roots. Schinzel and Skalba had constructed curve points deterministically for a restricted class of curves in 2004, and Skalba generalized it in 2005; Shallue and van de Woestijne further generalized and simplified that line [@ss04] [@s05] [@rfc9380]. Boneh and Franklin&apos;s 2001 supersingular map was a deterministic co-origin, but too narrow to be general infrastructure [@bf01]. SvdW survives today in RFC 9380 as the universal fallback for any curve the specialized maps do not cover [@rfc9380].&lt;/p&gt;
&lt;p&gt;But SvdW carried a flaw that every deterministic map after it would share, and it is the one no amount of cleverness in a single map can dodge.&lt;/p&gt;
&lt;h3&gt;The wall every single map hits&lt;/h3&gt;
&lt;p&gt;RFC 9380 states the obstacle plainly: the deterministic maps &quot;map to some fraction of the points&quot; on the curve, &quot;not the entire curve,&quot; which &quot;means that they cannot be used directly to construct a random oracle that outputs points on the curve&quot; [@rfc9380]. A single SvdW map hits only a constant fraction of the points and is statistically distinguishable from uniform. Any proof that models the map as a random oracle -- BLS&apos;s unforgeability, an OPRF&apos;s pseudorandomness, a password mapping -- does not go through. This is Property 3, and it is missing.&lt;/p&gt;

A formal criterion, defined by Maurer, Renner, and Holenstein in 2004, for when a constructed function can safely stand in for an ideal object such as a random oracle inside a security proof. A hash-to-curve construction is &quot;indifferentiable from a random oracle&quot; when no efficient distinguisher can separate it from a true random oracle, even given access to the underlying building blocks [@mrh04].
&lt;p&gt;Before that wall was scaled, the field spent a few years making the map cheaper.&lt;/p&gt;
&lt;h3&gt;Cheaper maps: Ulas, Icart, Simplified SWU (2007-2010)&lt;/h3&gt;
&lt;p&gt;Ulas simplified SvdW in 2007 (the &quot;U&quot; in SWU) [@u07]. Icart gave an independent closed-form map in 2009 for fields with $p \equiv 2 \pmod 3$, built on a single cube root [@icart09]. Brier and coauthors published the &quot;Simplified SWU&quot; map in 2010 for fields with $p \equiv 3 \pmod 4$ [@bcimrt10]. Each is a fixed rational formula costing roughly one field exponentiation plus a few multiplications and one inversion -- cheaper and easier to implement in constant time than SvdW&apos;s heavier parameterization.&lt;/p&gt;

A closed-form rational map from a field element to a short-Weierstrass curve point $y^2 = x^3 + Ax + B$, descended from the Shallue-van de Woestijne map via Ulas and Brier et al. In its base form it applies when $p \equiv 3 \pmod 4$ and $A\cdot B \neq 0$, and costs about one square root plus constant-time conditional selects [@u07] [@bcimrt10] [@rfc9380].
&lt;p&gt;This was progress, but not on Property 3. Cheaper did not mean uniform: each remained a single map hitting a fraction of the curve, still unusable as a random oracle [@rfc9380]. And the modular conditions left gaps. Icart needs $p \equiv 2 \pmod 3$; Simplified SWU needs $p \equiv 3 \pmod 4$; neither covers curves with $A = 0$ such as secp256k1 and BLS12-381. The deeper question was sharper and universal: could you build something provably indifferentiable out of these non-uniform pieces? The answer arrived, remarkably, in the very same 2010 paper that gave Simplified SWU.&lt;/p&gt;
&lt;h3&gt;The breakthrough: add two maps (2010)&lt;/h3&gt;
&lt;p&gt;Here is the shift the whole standard turns on. A single deterministic map cannot be a random oracle. But the &lt;em&gt;sum of two independent maps&lt;/em&gt; can be. Hash the message to two field elements, map each to a point, and add the points:&lt;/p&gt;
&lt;p&gt;$$H(m) = f\big(H_0(m)\big) + f\big(H_1(m)\big).$$&lt;/p&gt;
&lt;p&gt;Brier, Coron, Icart, Madore, Randriam, and Tibouchi proved this construction indifferentiable from a random oracle, and it recovers Property 3 while keeping Properties 1 and 2 [@bcimrt10] [@mrh04]. All three properties, at last, guaranteed at once.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; A single deterministic map can &lt;em&gt;never&lt;/em&gt; be a random oracle: it reaches only a fraction of the curve and is distinguishable from uniform. The fix is structural, not a cleverer single map. Hash to two field elements, map each, and add the two points. The sum is indifferentiable from a random oracle. This is exactly why &lt;code&gt;hash_to_curve&lt;/code&gt; (the &lt;code&gt;_RO_&lt;/code&gt; suites) costs twice what &lt;code&gt;encode_to_curve&lt;/code&gt; (the &lt;code&gt;_NU_&lt;/code&gt; suites) costs: the second map is not waste, it is what buys the distribution [@bcimrt10] [@ffstv13] [@rfc9380].&lt;/p&gt;
&lt;/blockquote&gt;

flowchart TD
    M[&quot;msg and DST&quot;] --&amp;gt; HF[&quot;hash_to_field to two elements&quot;]
    HF --&amp;gt; Q0[&quot;map_to_curve of u0 gives Q0&quot;]
    HF --&amp;gt; Q1[&quot;map_to_curve of u1 gives Q1&quot;]
    Q0 --&amp;gt; ADD[&quot;add points: Q0 plus Q1&quot;]
    Q1 --&amp;gt; ADD
    ADD --&amp;gt; RO[&quot;sum is indifferentiable from a random oracle&quot;]
    RO --&amp;gt; CC[&quot;clear_cofactor&quot;]
    CC --&amp;gt; P[&quot;point in the prime-order subgroup&quot;]
&lt;p&gt;Precision matters here, because it is easy to over-credit. Brier et al. proved the sum-of-two construction indifferentiable &lt;em&gt;specifically for Icart&apos;s map&lt;/em&gt;. The general result -- that summing two copies of essentially &lt;em&gt;any&lt;/em&gt; deterministic map yields a random oracle -- is due to Farashahi, Fouque, Shparlinski, Tibouchi, and Voloch in 2013.Attribute the general theorem to FFSTV13, not to BCIMRT. RFC 9380&apos;s Appendix A is careful about this: BCIMRT proved it for Icart&apos;s method; FFSTV13 &quot;improve the analysis, showing that it applies to essentially all deterministic mappings&quot; [@rfc9380] [@ffstv13]. After 2010, nothing about Property 3 was left to invent. What remained was coverage: the sum-of-two construction needs a concrete constant-time inner map for &lt;em&gt;your&lt;/em&gt; curve family, and two families were still uncovered.&lt;/p&gt;
&lt;h3&gt;Covering the remaining curves: Elligator 2 (2013) and isogeny SSWU (2019)&lt;/h3&gt;
&lt;p&gt;The Montgomery and twisted-Edwards curves -- Curve25519, Curve448, and the Edwards forms behind modern signature and OPRF suites -- needed their own map, because SSWU targets short-Weierstrass curves. Elligator 2 supplied it.&lt;/p&gt;

An efficient, invertible map from a field element to a point on any curve that has a point of order 2 (Montgomery or twisted-Edwards form), introduced by Bernstein, Hamburg, Krasnova, and Lange in 2013. RFC 9380 uses it for the Curve25519/448 and edwards25519/448 families and the ristretto255/decaf448 groups [@bhkl13] [@rfc9380].
&lt;p&gt;Elligator&apos;s origin is a small marvel. It was built for censorship resistance, a way to make a public key look like uniform random bytes so a network censor cannot even tell a key exchange is happening.The elligator.org site records the pivot in its own words: a number of protocols &quot;need a way to map an arbitrary input to an elliptic curve point whose factorisation is unknown (&apos;hashing to an elliptic curve&apos;), such as verifiable random functions (VRFs) and oblivious pseudorandom functions (OPRFs).&quot; A steganography tool became hash-to-curve infrastructure [@elligator-site]. The tool built to &lt;em&gt;hide&lt;/em&gt; a key exchange became the constant-time inner map for an entire curve family&apos;s hashing.&lt;/p&gt;
&lt;p&gt;The last gap was the pairing-friendly curve BLS needs. BLS12-381 has $A = 0$, exactly where Simplified SWU does not apply. In 2019 Riad Wahby and Dan Boneh closed it: map with SSWU onto an &lt;em&gt;isogenous&lt;/em&gt; curve where the parameters are admissible, then push the point back to the target curve through a fixed isogeny of small degree [@wb19] [@rfc9380]. The result is constant-time and works in any characteristic, and it is what made constant-time hashing to BLS12-381 practical at blockchain scale. Their headline measurement is the number that ends an old excuse: the constant-time functions run &quot;within 9% of the fastest, non-constant-time alternatives&quot; [@wb19].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Generation&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Key idea&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;What it secured&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;$H(m)\cdot G$&lt;/td&gt;
&lt;td&gt;~2001&lt;/td&gt;
&lt;td&gt;scalar-mult the generator&lt;/td&gt;
&lt;td&gt;constant&lt;/td&gt;
&lt;td&gt;nothing (DL is known)&lt;/td&gt;
&lt;td&gt;never safe as a map [@rfc9380]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Try-and-increment&lt;/td&gt;
&lt;td&gt;2001-2008&lt;/td&gt;
&lt;td&gt;search for a valid coordinate&lt;/td&gt;
&lt;td&gt;variable&lt;/td&gt;
&lt;td&gt;unknown discrete log&lt;/td&gt;
&lt;td&gt;deprecated for secret inputs [@bls01] [@rfc9380]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SvdW&lt;/td&gt;
&lt;td&gt;2006&lt;/td&gt;
&lt;td&gt;rational parameterization, no loop&lt;/td&gt;
&lt;td&gt;constant&lt;/td&gt;
&lt;td&gt;constant time by construction&lt;/td&gt;
&lt;td&gt;general fallback [@sw06]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ulas / Icart / Simplified SWU&lt;/td&gt;
&lt;td&gt;2007-2010&lt;/td&gt;
&lt;td&gt;cheaper closed-form maps&lt;/td&gt;
&lt;td&gt;constant&lt;/td&gt;
&lt;td&gt;efficiency, per-curve forms&lt;/td&gt;
&lt;td&gt;inner maps [@u07] [@icart09] [@bcimrt10]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BCIMRT sum-of-two&lt;/td&gt;
&lt;td&gt;2010&lt;/td&gt;
&lt;td&gt;add two mapped points&lt;/td&gt;
&lt;td&gt;constant&lt;/td&gt;
&lt;td&gt;random-oracle distribution&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;_RO_&lt;/code&gt; core [@bcimrt10] [@ffstv13]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elligator 2&lt;/td&gt;
&lt;td&gt;2013&lt;/td&gt;
&lt;td&gt;invertible map for order-2 curves&lt;/td&gt;
&lt;td&gt;constant&lt;/td&gt;
&lt;td&gt;Montgomery/Edwards coverage&lt;/td&gt;
&lt;td&gt;active [@bhkl13]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isogeny SSWU&lt;/td&gt;
&lt;td&gt;2019&lt;/td&gt;
&lt;td&gt;map via an isogenous curve&lt;/td&gt;
&lt;td&gt;constant&lt;/td&gt;
&lt;td&gt;$A=0$ pairing-curve coverage&lt;/td&gt;
&lt;td&gt;active [@wb19]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Every curve family that deployed protocols use now has a constant-time, unknown-DL inner map, and the sum-of-two construction wraps any of them into a random oracle. All three properties are achievable everywhere. But an implementer still has to assemble the hashing, the map, the cofactor clearing, the domain separation, and the choice of variant, by hand, and any one of those is a place to slip. What packages the whole lineage so you cannot get it wrong?&lt;/p&gt;
&lt;h2&gt;5. RFC 9380: All Three Properties at Once&lt;/h2&gt;
&lt;p&gt;In August 2023 the CFRG published RFC 9380, &quot;Hashing to Elliptic Curves,&quot; by Armando Faz-Hernandez, Sam Scott, Nick Sullivan, Riad Wahby, and Christopher Wood [@rfc9380]. It invents no new map. It does something more useful: it folds two decades of maps into a single interface an ordinary engineer cannot easily misuse, and it makes every security-relevant choice legible in the name.&lt;/p&gt;
&lt;p&gt;The pipeline has three stages [@rfc9380]:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hash_to_field&lt;/code&gt;&lt;/strong&gt; turns the input and a domain tag into one or more field elements, using &lt;code&gt;expand_message_xmd&lt;/code&gt; for fixed-output hashes like SHA-256 and &lt;code&gt;expand_message_xof&lt;/code&gt; for extendable-output functions like SHAKE.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;map_to_curve&lt;/code&gt;&lt;/strong&gt; sends each field element to a curve point using the map that fits the curve: Simplified SWU for short-Weierstrass curves, isogeny-SSWU for the $A = 0$ pairing curves, Elligator 2 for Montgomery and Edwards curves, and SvdW as the general fallback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;clear_cofactor&lt;/code&gt;&lt;/strong&gt; forces the result into the prime-order subgroup.&lt;/li&gt;
&lt;/ol&gt;

flowchart TD
    IN[&quot;input msg and DST&quot;] --&amp;gt; HF[&quot;hash_to_field&quot;]
    HF --&amp;gt; BR{&quot;which variant&quot;}
    BR --&amp;gt;|&quot;_RO_ hash_to_curve&quot;| TWO[&quot;two field elements, two maps, add&quot;]
    BR --&amp;gt;|&quot;_NU_ encode_to_curve&quot;| ONE[&quot;one field element, one map&quot;]
    TWO --&amp;gt; CC[&quot;clear_cofactor&quot;]
    ONE --&amp;gt; CC
    CC --&amp;gt; OUT[&quot;point in the prime-order subgroup&quot;]
&lt;h3&gt;The two-element margin is a proven bound&lt;/h3&gt;
&lt;p&gt;The first stage hides a subtlety worth pausing on. Reducing a uniformly random integer modulo $p$ is not exactly uniform, because the top of the range wraps around. RFC 9380 fixes this by oversampling.&lt;/p&gt;

The first pipeline stage: expand the input and the domain tag into field elements. It draws $L = \lceil(\lceil\log_2 p\rceil + k)/8\rceil$ bytes per element and reduces modulo $p$, so each element is within statistical distance $2^{-k}$ of uniform. The standard suites set $k = 128$, giving a $2^{-128}$ margin [@rfc9380].
&lt;p&gt;That $2^{-128}$ is a proven statistical bound, not a hopeful heuristic, and it is exactly why the byte-length formula is what it is [@rfc9380].&lt;/p&gt;
&lt;h3&gt;The variant is in the name&lt;/h3&gt;
&lt;p&gt;The choice between a uniform, proof-usable point and a cheaper nonuniform one is encoded directly in the suite name. &lt;code&gt;hash_to_curve&lt;/code&gt;, the &lt;code&gt;_RO_&lt;/code&gt; family, runs the two-element sum-of-two construction and is indifferentiable from a random oracle. &lt;code&gt;encode_to_curve&lt;/code&gt;, the &lt;code&gt;_NU_&lt;/code&gt; family, runs a single map at about half the cost and produces a nonuniform distribution [@rfc9380]. The trailing tag is not decoration; it tells you whether the output is safe to feed a proof that assumes a random oracle.&lt;/p&gt;
&lt;p&gt;RFC 9380 suite identifiers follow the pattern &lt;code&gt;CURVE_XMD:HASH_MAP_VARIANT_&lt;/code&gt;. Once you can read one, you can read them all.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;In &lt;code&gt;BLS12381G2_XMD:SHA-256_SSWU_RO_&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;What it fixes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Curve / group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BLS12381G2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the target group: BLS12-381, subgroup G2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expander&lt;/td&gt;
&lt;td&gt;&lt;code&gt;XMD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;expand_message_xmd&lt;/code&gt; (a fixed-output hash)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SHA-256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the underlying hash function&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inner map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SSWU&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Simplified SWU (isogeny variant here, since $A=0$)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variant&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_RO_&lt;/code&gt;: uniform, indifferentiable from a random oracle&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Read &lt;code&gt;edwards25519_XMD:SHA-512_ELL2_RO_&lt;/code&gt; the same way and it decodes itself: the edwards25519 group, XMD expansion over SHA-512, the Elligator 2 map, uniform variant [@rfc9380]. The name is the spec.&lt;/p&gt;
&lt;p&gt;{`
// Decompose an RFC 9380 suite ID: CURVE_EXPANDER:HASH_MAP_VARIANT_
function parseSuite(id) {
  const [left, right] = id.split(&quot;:&quot;);
  const leftParts = left.split(&quot;&lt;em&gt;&quot;);
  const expander = leftParts.pop();          // XMD or XOF
  const curve = leftParts.join(&quot;&lt;/em&gt;&quot;);         // e.g. BLS12381G2, edwards25519
  const rp = right.split(&quot;&lt;em&gt;&quot;).filter(function (s) { return s.length &amp;gt; 0; });
  const hash = rp[0];                         // SHA-256, SHA-512, SHAKE256
  const variant = rp[rp.length - 1];          // RO or NU
  const map = rp.slice(1, rp.length - 1).join(&quot;&lt;/em&gt;&quot;); // SSWU, ELL2, SVDW
  return { curve: curve, expander: expander, hash: hash, map: map,
           variant: variant,
           uniform: variant === &quot;RO&quot; ? &quot;uniform (random oracle)&quot; : &quot;nonuniform&quot; };
}&lt;/p&gt;
&lt;p&gt;[&quot;BLS12381G2_XMD:SHA-256_SSWU_RO_&quot;,
 &quot;edwards25519_XMD:SHA-512_ELL2_RO_&quot;,
 &quot;P256_XMD:SHA-256_SSWU_NU_&quot;].forEach(function (id) {
  console.log(id);
  console.log(&quot;   &quot;, JSON.stringify(parseSuite(id)));
});
`}&lt;/p&gt;
&lt;h3&gt;The two steps you must not skip&lt;/h3&gt;
&lt;p&gt;Two pipeline stages are easy to forget and dangerous to omit. The first is the domain tag.&lt;/p&gt;

A byte string, unique to a protocol and its version, mixed into `hash_to_field` so the same input maps to different points in different protocols. RFC 9380 makes it mandatory; DSTs longer than 255 bytes are themselves hashed before use. Without a unique DST, two protocols that share a curve can be driven to collide [@rfc9380].
&lt;p&gt;The second is cofactor clearing, the final stage that lands the point where the group arithmetic is safe.&lt;/p&gt;

The final pipeline stage: multiply the mapped point (or apply a faster endomorphism-based equivalent) by the curve&apos;s cofactor to force it into the prime-order subgroup, closing off small-subgroup attacks. edwards25519 has cofactor 8; BLS12-381&apos;s G1 and G2 have large cofactors with fast clearing methods [@rfc9380].
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The everyday act is now tiny: name a suite. The suite ID fixes the curve, the expander, the hash, the inner map, and the distribution variant, and a vetted library does the rest. You are not choosing a construction. You are declaring your curve and your distribution need, and RFC 9380 has already made the safe choice for each [@rfc9380].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Step back and the three properties resolve into one sentence. The mapped point had to be a &lt;em&gt;stranger&lt;/em&gt; -- its discrete log unknown, or forgery follows. It had to &lt;em&gt;arrive on time&lt;/em&gt; -- in work independent of the input, or the clock leaks the secret. And it had to &lt;em&gt;look the part&lt;/em&gt; -- drawn from a distribution the proof can treat as a random oracle, or the proof does not hold.&lt;/p&gt;

The point had to be a stranger, arrive on time, and look the part. RFC 9380 is the standard that guarantees all three, and encodes which ones you asked for in a single suite name.
&lt;p&gt;That framing also closes the loop this article opened. WPA3&apos;s answer to Dragonblood is Hash-to-Element, or H2E.&lt;/p&gt;

After Dragonblood, WPA3-Personal replaced hunt-and-peck with H2E, a constant-time, SSWU-based derivation of the SAE password element, and the Wi-Fi standard was updated to compute the password element &quot;in constant time&quot; [@dragonblood-site]. RFC 9380 supplies the governing principle: a constant-time map is required when the input is secret [@rfc9380]. The hostap and w1.fi advisories track the corresponding constant-time SAE patches shipped to real deployments [@w1fi-security]. The precise IEEE 802.11 clause is not reproduced here; the point is that the fix is exactly the constant-time approach this standard consolidates.
&lt;p&gt;The doorway is finally built to spec. The natural next question is who is actually walking through it, because the answer is: far more of your daily software than you would guess.&lt;/p&gt;
&lt;h2&gt;6. Who Quietly Depends on This Today&lt;/h2&gt;
&lt;p&gt;You have almost certainly relied on hash-to-curve this week without noticing. It sits under signatures that finalize blockchains, under the private authentication that hides your browsing from a token issuer, under the password login protocol that never sees your password, and under WPA3. None of these advertise the primitive. All of them break if it is wrong.&lt;/p&gt;

flowchart TD
    BLS[&quot;BLS signatures on Ethereum&quot;] --&amp;gt; H2C[&quot;RFC 9380 hash-to-curve&quot;]
    OPRF[&quot;OPRF and VOPRF (RFC 9497)&quot;] --&amp;gt;|&quot;one HashToGroup call&quot;| H2C
    OPAQUE[&quot;OPAQUE aPAKE (RFC 9807)&quot;] --&amp;gt;|&quot;indirect&quot;| OPRF
    PP[&quot;Privacy Pass (RFC 9578)&quot;] --&amp;gt; OPRF
    CPace[&quot;CPace PAKE (draft)&quot;] --&amp;gt; H2C
    SAE[&quot;WPA3-Personal SAE&quot;] --&amp;gt;|&quot;H2E&quot;| H2C
&lt;h3&gt;BLS signatures and Ethereum consensus&lt;/h3&gt;
&lt;p&gt;The highest-volume deployment, by signature-verification throughput, is BLS. Every BLS signature hashes its message to a point in G2 and scales it by the secret key, so &lt;code&gt;hash_to_point&lt;/code&gt; is not an accessory but the heart of the scheme [@draft-bls-signature-07]. Ethereum&apos;s consensus specification fixes this for validator signatures: it adopts the suite &lt;code&gt;BLS12381G2_XMD:SHA-256_SSWU_RO_&lt;/code&gt; -- the isogeny-SSWU map wrapped in the &lt;code&gt;_RO_&lt;/code&gt; construction -- so every consensus client hashes exactly this way [@eth-consensus-specs] [@draft-bls-signature-07] [@rfc9380]. The &lt;code&gt;_RO_&lt;/code&gt; variant is not optional here: the unforgeability proof models the map as a random oracle, so a nonuniform &lt;code&gt;_NU_&lt;/code&gt; map would void the guarantee. Notably, the scheme the entire Ethereum validator set relies on is still specified only by an Internet-Draft, which the consensus spec references directly [@eth-consensus-specs] [@draft-bls-signature-07].Unlike ECDSA, BLS is deterministic and uses no per-signature nonce, so it sidesteps the &lt;a href=&quot;https://paragmali.com/blog/one-number-used-twice-how-a-repeated-nonce-hands-over-your-p/&quot; rel=&quot;noopener&quot;&gt;nonce-reuse failures that have repeatedly broken ECDSA deployments&lt;/a&gt; (Part 8). Its one hash-to-curve call becomes the security-critical primitive in place of the nonce [@draft-bls-signature-07].&lt;/p&gt;
&lt;h3&gt;OPRFs, and everything built on them&lt;/h3&gt;
&lt;p&gt;The second large consumer is the oblivious pseudorandom function and its verifiable and partially-oblivious variants, standardized in RFC 9497 [@rfc9497].&lt;/p&gt;

A two-party protocol in which a client learns $F(k, x)$ for its input $x$ and the server&apos;s key $k$, while the server learns neither $x$ nor the result. The client&apos;s first step hashes $x$ to a curve point and blinds it; that single `HashToGroup` call is the protocol&apos;s entire hash-to-curve surface [@rfc9497].
&lt;p&gt;The dependency is remarkably concentrated. The client&apos;s &lt;code&gt;Blind(input)&lt;/code&gt; computes &lt;code&gt;HashToGroup(input)&lt;/code&gt; and then blinds the point; every other message is scalar arithmetic and proofs. So the whole hash-to-curve exposure of an OPRF is one call, instantiated with Elligator 2 for ristretto255 and SSWU for the P-256, P-384, and P-521 suites [@rfc9497].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For an OPRF, the entire hash-to-curve attack surface is the single &lt;code&gt;HashToGroup&lt;/code&gt; call in the client&apos;s &lt;code&gt;Blind&lt;/code&gt; step. Get that one call right -- correct suite, correct DST, identity element rejected on the wire -- and the primitive is handled. Get it wrong and obliviousness or correctness collapses [@rfc9497].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Because OPRFs are a building block, their hash-to-curve dependency propagates. OPAQUE, the augmented PAKE standardized as RFC 9807 in July 2025, lets a server authenticate a user by password without ever learning the password, and it does so by running an OPRF; its hash-to-curve dependency is indirect but real, flowing through RFC 9497 [@rfc9807] [@rfc9497]. &lt;a href=&quot;https://paragmali.com/blog/the-age-gate-that-doesnt-know-your-age-how-anonymous-credent/&quot; rel=&quot;noopener&quot;&gt;Privacy Pass&lt;/a&gt;, standardized across RFC 9576, RFC 9577, and RFC 9578 in June 2024, issues anonymous authentication tokens; its privately verifiable issuance is &quot;based on the Oblivious Pseudorandom Function,&quot; with token type 0x0001 being a VOPRF over P-384, which is the SSWU path [@rfc9578] [@rfc9576] [@rfc9577].&lt;/p&gt;
&lt;h3&gt;PAKEs and Wi-Fi&lt;/h3&gt;
&lt;p&gt;Two more consumers map a secret rather than a public message. CPace, a balanced PAKE still in CFRG draft with datatracker status &quot;Waiting for IRTF Chair,&quot; maps the password to a session generator using SSWU or Elligator 2 depending on the group [@draft-cpace]. And WPA3-Personal&apos;s SAE now derives its password element with the constant-time H2E map, the fix that closed Dragonblood [@dragonblood-site].&lt;/p&gt;
&lt;p&gt;The whole deployed picture fits in one adoption map.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Consumer&lt;/th&gt;
&lt;th&gt;Where hash-to-curve is called&lt;/th&gt;
&lt;th&gt;Suite / map&lt;/th&gt;
&lt;th&gt;Standard status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;BLS / Ethereum consensus&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hash_to_point(msg)&lt;/code&gt; into G2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BLS12381G2_XMD:SHA-256_SSWU_RO_&lt;/code&gt; [@draft-bls-signature-07]&lt;/td&gt;
&lt;td&gt;Internet-Draft [@draft-bls-signature-07]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPRF / VOPRF / POPRF&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Blind&lt;/code&gt; calls &lt;code&gt;HashToGroup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ristretto255 to Elligator 2; P-curves to SSWU [@rfc9497]&lt;/td&gt;
&lt;td&gt;RFC 9497 (2023) [@rfc9497]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPAQUE (aPAKE)&lt;/td&gt;
&lt;td&gt;indirect, via its OPRF&lt;/td&gt;
&lt;td&gt;inherits the OPRF suite [@rfc9807]&lt;/td&gt;
&lt;td&gt;RFC 9807 (2025) [@rfc9807]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privacy Pass&lt;/td&gt;
&lt;td&gt;VOPRF token issuance&lt;/td&gt;
&lt;td&gt;P-384 VOPRF to SSWU [@rfc9578]&lt;/td&gt;
&lt;td&gt;RFC 9576/9577/9578 (2024) [@rfc9578]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPace (balanced PAKE)&lt;/td&gt;
&lt;td&gt;password to session generator&lt;/td&gt;
&lt;td&gt;SSWU or Elligator 2 by group [@draft-cpace]&lt;/td&gt;
&lt;td&gt;draft, &quot;Waiting for IRTF Chair&quot; [@draft-cpace]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WPA3-Personal (SAE)&lt;/td&gt;
&lt;td&gt;password element (PWE)&lt;/td&gt;
&lt;td&gt;H2E, constant-time SSWU [@dragonblood-site]&lt;/td&gt;
&lt;td&gt;IEEE 802.11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;What actually ships&lt;/h3&gt;
&lt;p&gt;Under all of this sit a few load-bearing libraries. The blst library implements BLS12-381 hash-to-curve in C and assembly, is compliant with RFC 9380, was audited by NCC Group in January 2021, and is the subject of an ongoing Galois formal-verification effort [@blst] [@blst-verification]. Cloudflare&apos;s CIRCL provides RFC 9380 hash-to-curve and RFC 9497 OPRFs in Go, though it labels itself experimental and cautions against production use of parts of it [@circl]. In Rust, curve25519-dalek exposes an Elligator-based one-way map through &lt;code&gt;RistrettoPoint::from_uniform_bytes&lt;/code&gt; [@curve25519-dalek].&lt;/p&gt;
&lt;p&gt;The headline is quiet by design: the primitive is standardized and load-bearing under a stack of shipping protocols, and almost nobody who depends on it has to think about it. That is what a well-built doorway looks like. Which raises a fair question: if every consumer just names a suite, is there ever a real choice to make, or has the standard decided everything for you?&lt;/p&gt;
&lt;h2&gt;7. The Decision Matrix&lt;/h2&gt;
&lt;p&gt;There is no &quot;best&quot; hash-to-curve, and that is a feature. Almost every knob is set for you by something structural: the curve you already committed to, or the distribution your proof already assumes. The value of a field guide is naming which axis governs which choice, and where a wrong turn is a vulnerability rather than a mere preference.&lt;/p&gt;
&lt;h3&gt;Distribution need decides the variant&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;_RO_&lt;/code&gt; versus &lt;code&gt;_NU_&lt;/code&gt; choice is governed by one question: does your proof need a random oracle? For BLS, OPRFs, and password mapping, it does, so &lt;code&gt;_RO_&lt;/code&gt; is the default. The &lt;code&gt;_NU_&lt;/code&gt; variant is an optimization you reach for only with a specific proof that non-uniformity is acceptable, because its output is measurably not uniform.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;&lt;code&gt;hash_to_curve&lt;/code&gt; (&lt;code&gt;_RO_&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;&lt;code&gt;encode_to_curve&lt;/code&gt; (&lt;code&gt;_NU_&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Field elements and maps&lt;/td&gt;
&lt;td&gt;2 elements, 2 maps, 1 addition&lt;/td&gt;
&lt;td&gt;1 element, 1 map&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relative cost&lt;/td&gt;
&lt;td&gt;about twice &lt;code&gt;_NU_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;baseline, about half of &lt;code&gt;_RO_&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output distribution&lt;/td&gt;
&lt;td&gt;indifferentiable from a random oracle [@rfc9380]&lt;/td&gt;
&lt;td&gt;nonuniform: at least one eighth of G, weights vary by up to a factor of four [@rfc9380]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proof compatibility&lt;/td&gt;
&lt;td&gt;usable where a proof assumes a random oracle&lt;/td&gt;
&lt;td&gt;only where non-uniformity is provably sufficient&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision rule&lt;/td&gt;
&lt;td&gt;default for BLS, OPRF, PAKE mapping&lt;/td&gt;
&lt;td&gt;optimization only, with a proof&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Getting this backward is a shippable misuse: a &lt;code&gt;_NU_&lt;/code&gt; map where the proof assumed &lt;code&gt;_RO_&lt;/code&gt; produces a point from a distribution the proof never agreed to, which is the third of our three failures made concrete [@rfc9380].&lt;/p&gt;
&lt;h3&gt;Curve family decides the map&lt;/h3&gt;
&lt;p&gt;The choice among the inner maps is not a matter of taste at all. It is fixed by the shape of your curve.&lt;/p&gt;

flowchart TD
    Q{&quot;what curve do you have&quot;}
    Q --&amp;gt;|&quot;short Weierstrass, p is 3 mod 4&quot;| SSWU[&quot;Simplified SWU&quot;]
    Q --&amp;gt;|&quot;A is zero: secp256k1, BLS12-381&quot;| ISO[&quot;Isogeny SSWU&quot;]
    Q --&amp;gt;|&quot;Montgomery or Edwards&quot;| ELL[&quot;Elligator 2&quot;]
    Q --&amp;gt;|&quot;none of the above&quot;| SVDW[&quot;SvdW fallback&quot;]
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Map&lt;/th&gt;
&lt;th&gt;Curve condition&lt;/th&gt;
&lt;th&gt;Curves in RFC 9380&lt;/th&gt;
&lt;th&gt;Dominant cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Simplified SWU&lt;/td&gt;
&lt;td&gt;short-Weierstrass, $p \equiv 3 \pmod 4$, $AB \neq 0$&lt;/td&gt;
&lt;td&gt;P-256, P-384, P-521&lt;/td&gt;
&lt;td&gt;about one square root plus selects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isogeny SSWU&lt;/td&gt;
&lt;td&gt;$A = 0$ Weierstrass&lt;/td&gt;
&lt;td&gt;secp256k1, BLS12-381 G1/G2&lt;/td&gt;
&lt;td&gt;SSWU on $E&apos;$ plus one isogeny evaluation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elligator 2&lt;/td&gt;
&lt;td&gt;curve with a point of order 2&lt;/td&gt;
&lt;td&gt;curve25519/448, edwards25519/448, ristretto255/decaf448&lt;/td&gt;
&lt;td&gt;about one exponentiation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SvdW&lt;/td&gt;
&lt;td&gt;any curve, characteristic other than 2 or 3&lt;/td&gt;
&lt;td&gt;fallback when none of the above fit&lt;/td&gt;
&lt;td&gt;a few exponentiations and inversions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you find yourself deliberating between SSWU and Elligator 2, stop: your curve has already decided. SSWU serves short-Weierstrass curves, isogeny-SSWU serves the $A=0$ pairing curves, and Elligator 2 serves Montgomery and Edwards curves. The only choice genuinely left to you is the variant, &lt;code&gt;_RO_&lt;/code&gt; or &lt;code&gt;_NU_&lt;/code&gt;, and the default is &lt;code&gt;_RO_&lt;/code&gt; [@rfc9380].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The two knobs that are real, but small&lt;/h3&gt;
&lt;p&gt;Two remaining choices are genuine but low-stakes. The expander, &lt;code&gt;expand_message_xmd&lt;/code&gt; versus &lt;code&gt;expand_message_xof&lt;/code&gt;, follows your hash family: XMD for SHA-2, XOF for SHAKE, and neither dominates on security, so you pick the hash you already have [@rfc9380]. And within BLS, you can hash to either pairing group.&lt;/p&gt;

BLS can hash to either pairing group. Hashing to G2 is more expensive, because G2 lives over the larger field $\mathbb{F}_{p^2}$ and has a larger cofactor, but the minimal-pubkey-size setting Ethereum uses signs in G2. Minimal-signature-size deployments hash to the cheaper G1 instead. This is a real cost-versus-size trade-off, not a security decision, and it is why fast G2 hashing has its own literature [@rfc9380] [@sbcdk09].
&lt;h3&gt;The number that ends an old argument&lt;/h3&gt;
&lt;p&gt;For years, the excuse for variable-time hunt-and-peck was that constant-time maps were too slow. Wahby and Boneh&apos;s nine-percent measurement retired it: constant time is essentially free here, and simpler to implement correctly, which is why RFC 9380 can require it without asking anyone to pay for the safety [@wb19].&lt;/p&gt;
&lt;p&gt;It is worth remembering how strange this matrix would look to a cryptographer of 1995. In a finite field, hashing into the group is a single modular reduction with no decision matrix at all [@miller85]. Elliptic curves bought hard discrete logs and small keys, and the bill came due as this exact table: a real algorithm, with real parameters, that you must get right. Two maps, an oversampling margin, a distribution that is deliberately &quot;almost uniform&quot;: none of it is arbitrary. The next section proves they are the right choices, not just good ones.&lt;/p&gt;
&lt;h2&gt;8. Why Two Maps, and How Uniform Is &quot;Uniform&quot;&lt;/h2&gt;
&lt;p&gt;The two-map construction can feel like a clever hack. It is not. It is the provable optimum, and the theory that says so also explains the oversampling margin and the one caveat you must respect.&lt;/p&gt;
&lt;h3&gt;The lower bound that forces two maps&lt;/h3&gt;
&lt;p&gt;Start with what a single map cannot do. No efficient deterministic map to a general elliptic curve is both surjective and uniform. Its image is only a constant fraction of the curve, with some points weighted more heavily than others. RFC 9380 quantifies the residual for the single-map case exactly: the output distribution &quot;includes at least one eighth of the points in G,&quot; and the probability of points &quot;varies by at most a factor of four&quot; [@rfc9380]. Those bounds descend from the analyses of the individual maps [@sw06] [@bhkl13] [@ft12]. A distribution that misses seven-eighths of the group in the worst case and re-weights the rest simply cannot be indifferentiable from a uniform random oracle. One map is not enough, and this is why.&lt;/p&gt;
&lt;h3&gt;The matching upper bound&lt;/h3&gt;
&lt;p&gt;Now the other side. Hashing to two field elements, mapping each, and adding the points &lt;em&gt;is&lt;/em&gt; indifferentiable from a random oracle, proven for Icart&apos;s map by Brier et al. and generalized to essentially all maps by Farashahi et al. [@bcimrt10] [@ffstv13] [@mrh04]. The lower bound says one map cannot suffice; the upper bound says two do. The gap is closed. The &lt;code&gt;_RO_&lt;/code&gt; construction is the tight, achievable answer to &quot;produce a uniform, proof-usable point,&quot; and RFC 9380 standardizes precisely that optimum, no more and no less [@rfc9380].&lt;/p&gt;
&lt;h3&gt;Why the oversampling margin is what it is&lt;/h3&gt;
&lt;p&gt;The same &quot;prove it, do not hope it&quot; discipline governs &lt;code&gt;hash_to_field&lt;/code&gt;. Reducing a uniform $L$-byte integer modulo $p$ leaves a small bias from the wrap-around at the top of the range. Oversampling by $k$ bits, with&lt;/p&gt;
&lt;p&gt;$$L = \left\lceil \frac{\lceil \log_2 p \rceil + k}{8} \right\rceil,$$&lt;/p&gt;
&lt;p&gt;drives the statistical distance from uniform below $2^{-k}$. RFC 9380 fixes $k = 128$, so every field element is within $2^{-128}$ of uniform [@rfc9380]. That is a proven bound, which is why truncating the byte length is not a harmless optimization but a real weakening of the margin.&lt;/p&gt;
&lt;h3&gt;The one caveat that survives all of this&lt;/h3&gt;
&lt;p&gt;Here the humility arrives. &quot;Indifferentiable from a random oracle&quot; does not mean &quot;safe to drop into any random-oracle proof.&quot;&lt;/p&gt;

RFC 9380 cites Ristenpart, Shacham, and Shrimpton for the sharpest limit on the whole approach: &quot;not all security proofs that rely on random oracles continue to hold when those oracles are replaced by indifferentiable functionalities&quot; [@rfc9380]. So `_RO_` is the right default, but a protocol&apos;s proof still has to be the composable kind that survives the substitution. Indifferentiability is a strong guarantee, not an unconditional one.
&lt;p&gt;There is a smaller but instructive caveat in the same spirit. Either encoding can, with probability roughly $1/r$ for a random input, output the identity element.That probability is negligible and it is infeasible to find such an input deliberately, so RFC 9380 says protocols &quot;SHOULD NOT add a special case to detect and &apos;fix&apos; the identity element.&quot; The special case is itself a bug surface, more likely to introduce a flaw than to prevent one [@rfc9380]. The right move is to do nothing special, which is counterintuitive enough that people get it wrong by trying too hard.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For classical elliptic curves the core problem is solved in the strongest sense available: a single map provably cannot be a uniform random oracle, the two-map sum provably is, and RFC 9380 standardizes exactly that optimum. What is left is not the classical mathematics. It is implementation assurance and the post-quantum question [@rfc9380] [@ffstv13].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One boundary is worth stating precisely, because it sets up everything still open. The &quot;unknown discrete log&quot; property adds no new hardness assumption: it inherits the elliptic-curve discrete-log hardness of the target group, the same assumption Part 15 examined [@rfc9380]. And so, like all elliptic-curve cryptography, it falls to Shor&apos;s algorithm on a fault-tolerant quantum computer. Post-quantum &lt;em&gt;signatures&lt;/em&gt; sidestep the primitive entirely, because they use no curve. But the &lt;em&gt;consumers that specifically need a point of unknown discrete log&lt;/em&gt; -- OPRFs and VRFs -- have no such escape. That is where the frontier actually is.&lt;/p&gt;
&lt;h2&gt;9. Where It Is Still Moving&lt;/h2&gt;
&lt;p&gt;A solved primitive with live frontiers sounds like a contradiction, but hash-to-curve is exactly that. The classical mathematics is settled; the engineering, the assurance, and the post-quantum future are not.&lt;/p&gt;
&lt;h3&gt;Constant time, all the way down&lt;/h3&gt;
&lt;p&gt;RFC 9380 guarantees constant time at the interface: no data-dependent loop. The guarantee has to hold much further down, though, through the field primitives the map calls -- the square root, the sign function &lt;code&gt;sgn0&lt;/code&gt;, the zero-guarded inversion &lt;code&gt;inv0&lt;/code&gt;, general inversion, and the conditional selects -- each of which can leak through timing, cache, or micro-architecture if written naively [@rfc9380]. And the map runs on secrets: passwords in SAE and CPace, blinded inputs in OPRFs. A leak in one field routine re-opens the exact class of attack Dragonblood exploited.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Constant time must be structural in the map and then verified down to the field primitives, not patched in after the fact. Dragonblood&apos;s second act (Section 1) is the standing proof: fixing timing curve by curve is how you get burned twice [@rfc9380] [@dragonblood-site].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The expensive curves&lt;/h3&gt;
&lt;p&gt;Hashing to G2 of BLS12-381, over the field $\mathbb{F}_{p^2}$ and with a large cofactor, is markedly costlier than hashing to G1. Faster maps and faster cofactor clearing are open engineering targets, and they matter because Ethereum verifies huge volumes of signatures, so every millisecond of G2 hashing multiplies across the network. Wahby and Boneh brought constant-time hashing within nine percent of variable-time, and the fast-G2 cofactor-clearing line descends from work like Scott and coauthors&apos; &quot;Fast Hashing to G2,&quot; but G2 remains the expensive path, and each new pairing-friendly curve keeps the problem alive [@wb19] [@sbcdk09].&lt;/p&gt;
&lt;h3&gt;Machine-checked implementations&lt;/h3&gt;
&lt;p&gt;Because constant time is hard to get right, the field increasingly wants implementations proven correct by machine, not just audited by eye. The leading edge is visible in blst, the highest-volume hash-to-G2 implementation: it was audited by NCC Group in January 2021, and its formal verification by Galois is, in the project&apos;s own words, ongoing [@blst] [@blst-verification]. That is the honest status: verification of a deployed hash-to-curve path is in progress, not finished. Full end-to-end machine-checked hash-to-curve -- map, cofactor clearing, and constant time, across curve families -- remains open.&lt;/p&gt;
&lt;h3&gt;The genuinely open frontier: post-quantum&lt;/h3&gt;
&lt;p&gt;The deepest gap is post-quantum. Signatures have a post-quantum future without curves, so they abandon hash-to-curve. The unknown-DL consumers do not have that luxury. OPRFs and VRFs -- and therefore OPAQUE, Privacy Pass, and anonymous-credential systems built on them -- rest on the hardness of the elliptic-curve discrete log, and a quantum adversary breaks that foundation.Lattice- and isogeny-based OPRF and VRF constructions exist in the research literature, but none is competitive with the elliptic-curve versions in bandwidth or round complexity, and none is standardized. This guide states the frontier without pinning a specific paper, holding to the same citation standard as the rest of the series. There is no drop-in &quot;post-quantum hash-to-curve,&quot; because the whole unknown-DL abstraction has to be rebuilt on different assumptions, and the competitive replacement does not yet exist.&lt;/p&gt;
&lt;h3&gt;Steady-state engineering&lt;/h3&gt;
&lt;p&gt;Finally, the everyday work: as deployments adopt new pairing-friendly curves (BLS12-377 and BW6 are recent examples) and new prime-order abstractions, each needs its admissible map and cofactor-clearing constants worked out and folded into the suite registry [@rfc9380]. This keeps the &quot;which map for which curve&quot; table a living document rather than a finished one. Given all of it, the practical question remains: what should you actually do on Monday?&lt;/p&gt;
&lt;h2&gt;10. The Decision Rules, Made Operational&lt;/h2&gt;
&lt;p&gt;Here is the spine turned into something you can apply to a code review this afternoon. The rules are short because the standard did the hard part; your job is to name the right suite and not undo its work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use a vetted suite, never a hand-rolled map.&lt;/strong&gt; Always call a RFC 9380 suite from a constant-time library for your curve. Never compute $H(m)\cdot G$, and never write your own map. The two most catastrophic errors in this whole area are the two shortcuts from Section 3 [@rfc9380].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Default to &lt;code&gt;_RO_&lt;/code&gt;.&lt;/strong&gt; Use &lt;code&gt;hash_to_curve&lt;/code&gt; (&lt;code&gt;_RO_&lt;/code&gt;) unless you hold a specific proof that non-uniformity is acceptable, in which case &lt;code&gt;encode_to_curve&lt;/code&gt; (&lt;code&gt;_NU_&lt;/code&gt;) halves the cost [@rfc9380].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Always set a unique DST.&lt;/strong&gt; Make the domain-separation tag specific to your protocol and version, hash it if it exceeds 255 bytes, and never share one across protocols. This is &lt;a href=&quot;https://paragmali.com/blog/one-secret-is-not-one-key-the-discipline-of-key-derivation-w/&quot; rel=&quot;noopener&quot;&gt;domain separation&lt;/a&gt;, the same discipline Part 13 develops for keys [@rfc9380].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Map by curve, not by taste.&lt;/strong&gt; Short-Weierstrass with $p \equiv 3 \pmod 4$ uses SSWU; $A = 0$ uses isogeny-SSWU; Montgomery and Edwards use Elligator 2; anything else uses SvdW [@rfc9380].&lt;/p&gt;
&lt;p&gt;Then the per-protocol specifics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BLS:&lt;/strong&gt; use &lt;code&gt;BLS12381G2_XMD:SHA-256_SSWU_RO_&lt;/code&gt; for minimal-pubkey-size, or the G1 suite for minimal-signature-size; &lt;code&gt;_RO_&lt;/code&gt; is required for the unforgeability proof; clear the cofactor [@draft-bls-signature-07].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OPRF, VOPRF, OPAQUE:&lt;/strong&gt; call RFC 9497&apos;s &lt;code&gt;HashToGroup&lt;/code&gt; and &lt;code&gt;HashToScalar&lt;/code&gt; at the ciphersuite&apos;s suite; do not substitute your own map; reject the identity element on the wire [@rfc9497].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PAKE mapping a password (SAE, CPace):&lt;/strong&gt; use a constant-time map only, never hunt-and-peck a secret, and pre-hash the password with &lt;a href=&quot;https://paragmali.com/blog/the-one-job-of-a-password-hash-why-fast-is-the-enemy-and-mem/&quot; rel=&quot;noopener&quot;&gt;a slow KDF such as PBKDF2, scrypt, or Argon2&lt;/a&gt; before mapping, so an adversary who sees an intermediate value still faces a slow dictionary attack [@rfc9380]. Part 12&apos;s password-entropy argument is exactly why this matters.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Pick the suite for your curve, default to &lt;code&gt;_RO_&lt;/code&gt;, set a unique DST, and clear the cofactor. Four decisions, each with a safe default, and RFC 9380 has already made three of them for you the moment you name the suite [@rfc9380].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The mirror image of the rules is the list of things that never belong in a diff. Each is a real, shippable bug that maps to exactly one of the three properties, or to the two guardrails around them.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Misuse&lt;/th&gt;
&lt;th&gt;What it violates&lt;/th&gt;
&lt;th&gt;The fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;$P = H(m)\cdot G$ instead of a map&lt;/td&gt;
&lt;td&gt;Unknown discrete log&lt;/td&gt;
&lt;td&gt;use a real RFC 9380 map [@rfc9380]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hunt-and-peck over a secret&lt;/td&gt;
&lt;td&gt;Constant time&lt;/td&gt;
&lt;td&gt;constant-time suite; H2E for SAE [@rfc9380] [@dragonblood-site]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_NU_&lt;/code&gt; where the proof needs &lt;code&gt;_RO_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Random-oracle distribution&lt;/td&gt;
&lt;td&gt;default to &lt;code&gt;_RO_&lt;/code&gt; [@rfc9380]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing or shared DST&lt;/td&gt;
&lt;td&gt;Domain separation&lt;/td&gt;
&lt;td&gt;unique per-protocol, per-version DST [@rfc9380]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skipped cofactor clearing&lt;/td&gt;
&lt;td&gt;Subgroup safety&lt;/td&gt;
&lt;td&gt;clear the cofactor; check the subgroup [@rfc9380]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Truncated &lt;code&gt;hash_to_field&lt;/code&gt; output&lt;/td&gt;
&lt;td&gt;Uniformity margin&lt;/td&gt;
&lt;td&gt;use the full $L$ bytes [@rfc9380]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw password without a slow KDF&lt;/td&gt;
&lt;td&gt;Offline-guessing resistance&lt;/td&gt;
&lt;td&gt;PBKDF2, scrypt, or Argon2 first [@rfc9380]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Vetted RFC 9380 suite for the curve. &lt;code&gt;_RO_&lt;/code&gt; unless a proof licenses &lt;code&gt;_NU_&lt;/code&gt;. Unique, versioned DST, hashed if over 255 bytes. Cofactor cleared and subgroup confirmed. Full &lt;code&gt;L&lt;/code&gt;-byte &lt;code&gt;hash_to_field&lt;/code&gt;. For secrets, a constant-time map and a slow KDF first. For OPRFs, identity rejected on the wire. Anything else is a finding [@rfc9380] [@rfc9497].&lt;/p&gt;
&lt;/blockquote&gt;

RFC 9380 ships official test vectors for every suite. Before you trust an implementation, feed it the standard&apos;s sample inputs and DSTs and confirm the output points match byte for byte. A library that reproduces the vectors implements the pipeline correctly; one that does not should never touch a secret [@rfc9380].
&lt;p&gt;A checklist answers &quot;what to do.&quot; The last thing to handle is the set of &quot;but why can&apos;t I just...&quot; questions that every reviewer and implementer keeps asking, because the answers are where the understanding finally sets.&lt;/p&gt;
&lt;h2&gt;11. FAQ and Common Misuse&lt;/h2&gt;


Because its discrete log is $H_n(m)$, which is public, so the point is not a stranger to anyone. In BLS this is fatal: a signature $[sk]\cdot H(m)$ becomes $[H_n(m)]\cdot PK$, which anyone computes from the public message and public key with no secret key. The same known-discrete-log flaw breaks OPRF obliviousness and a PAKE&apos;s password element [@rfc9380] [@bls01].


Only when the input is public and you need neither constant time nor a random-oracle distribution. Over a secret input the loop count is a function of the secret, so the running time leaks it. That is precisely the Dragonblood attack that recovered WPA3 passwords, and it is why RFC 9380 declines to specify the method [@rfc9380] [@vr20].


Yes. The `_NU_` output is provably not a random oracle: it covers at least one eighth of the group with weights varying by up to a factor of four [@rfc9380]. If your proof assumes a random oracle -- as the BLS, OPRF, and PAKE proofs do -- you need `_RO_`. Use `_NU_` only with a specific proof that non-uniformity is acceptable [@rfc9380].


Always. The DST is domain separation, not decoration: without a unique, protocol-and-version-specific tag, two protocols sharing a curve can be driven to collide. RFC 9380 makes it mandatory and hashes tags longer than 255 bytes [@rfc9380].


You don&apos;t; the curve decides. Curve25519 and edwards25519 use Elligator 2, because they have a point of order 2. BLS12-381 uses isogeny-SSWU, because $A = 0$. The map is a structural consequence of the curve, not a preference [@rfc9380].


No. It is bound to elliptic-curve cryptography and falls to Shor&apos;s algorithm. Post-quantum signatures avoid the primitive entirely because they use no curve, but the consumers that need a point of unknown discrete log -- OPRFs and VRFs, and the systems built on them -- have no settled, efficient post-quantum replacement yet [@rfc9380].


No. `encode_to_curve` (`_NU_`) runs one map and produces a nonuniform point at about half the cost. `hash_to_curve` (`_RO_`) hashes to two field elements, maps each, and adds the points, which is indifferentiable from a random oracle. The extra map is what buys the distribution [@rfc9380].

&lt;h2&gt;The Point Had to Be a Stranger, Arrive on Time, and Look the Part&lt;/h2&gt;
&lt;p&gt;Look back at the failures with the whole picture in hand, and they line up with eerie precision. Each was a wrong answer to exactly one of the three questions. Computing $H(m)\cdot G$ published the discrete log: the point was not a &lt;em&gt;stranger&lt;/em&gt;, and universal forgery followed [@rfc9380] [@bls01]. Hunt-and-peck leaked the password through the clock: the point did not &lt;em&gt;arrive on time&lt;/em&gt;, and Dragonblood read it off the handshake [@vr20]. Reaching for &lt;code&gt;_NU_&lt;/code&gt; where a proof needs &lt;code&gt;_RO_&lt;/code&gt; handed the proof a distribution it never agreed to: the point did not &lt;em&gt;look the part&lt;/em&gt; [@rfc9380]. Three questions, three ways to fail, three real systems that failed them.&lt;/p&gt;
&lt;p&gt;That is the sibling claim to the one Part 15 made about the curve itself. The elliptic-curve arithmetic was sound; the danger was the soft gap around it. Here the gap has a name and a shape. It is the doorway, the single step that carries an untrusted string into the group, and getting it right means answering all three questions at once: unknown discrete log, constant time, and a proof-usable distribution. RFC 9380 is the standard that guarantees all three and encodes which ones you asked for in a suite name you can read at a glance.&lt;/p&gt;
&lt;p&gt;For classical curves, the doorway is finally built to spec. The two-map construction is the provable optimum, the oversampling margin is a proven bound, and a handful of audited libraries carry the whole load quietly under BLS, OPRFs, OPAQUE, Privacy Pass, and WPA3. The open frontier has moved elsewhere: to machine-checked implementations of what already ships, to the stubbornly expensive pairing curves, and above all to a post-quantum world where the unknown-DL consumers have no settled replacement.&lt;/p&gt;
&lt;p&gt;So the next time you call a one-line hash-to-curve API, you will know to ask the three questions it answers for you. Do you know the point&apos;s discrete log? Does your running time depend on the input? Can your proof model the output as a random oracle? For any construction worth shipping, the answers are the same three words this whole field spent two decades earning: no, no, yes.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;hashing-to-elliptic-curves-rfc-9380&quot; keyTerms={[
  { term: &quot;Hash-to-curve&quot;, definition: &quot;A map from an arbitrary byte string to a curve point of unknown discrete log; standardized by RFC 9380.&quot; },
  { term: &quot;BLS signature&quot;, definition: &quot;A pairing-based signature where the signature is the hashed message point scaled by the secret key.&quot; },
  { term: &quot;Try-and-increment&quot;, definition: &quot;Hash with a counter until the result is a valid coordinate; unknown DL but variable time.&quot; },
  { term: &quot;Indifferentiability&quot;, definition: &quot;A formal criterion for when a construction can safely replace an ideal object such as a random oracle.&quot; },
  { term: &quot;Simplified SWU&quot;, definition: &quot;A closed-form map to short-Weierstrass curves with p equal to 3 mod 4 and nonzero A and B.&quot; },
  { term: &quot;Elligator 2&quot;, definition: &quot;An invertible map for curves with a point of order 2, used for Montgomery and Edwards curves.&quot; },
  { term: &quot;hash_to_field&quot;, definition: &quot;The first pipeline stage: expand input and DST into field elements within 2^-128 of uniform.&quot; },
  { term: &quot;Domain Separation Tag&quot;, definition: &quot;A mandatory, protocol-and-version-specific tag mixed into hashing to prevent cross-protocol collisions.&quot; },
  { term: &quot;Cofactor clearing&quot;, definition: &quot;The final stage that forces the mapped point into the prime-order subgroup.&quot; },
  { term: &quot;Oblivious PRF&quot;, definition: &quot;A protocol where the client learns a keyed function of its input while the server learns neither input nor output.&quot; }
]} flashcards={[
  { front: &quot;Why is P = H(m) times G unsafe?&quot;, back: &quot;Its discrete log is public, so anyone forges BLS signatures as Hn(m) times PK with no secret key.&quot; },
  { front: &quot;Why does try-and-increment leak?&quot;, back: &quot;Its iteration count depends on the input, so timing reveals a secret input. This is Dragonblood.&quot; },
  { front: &quot;Why does hash_to_curve run two maps?&quot;, back: &quot;A single map cannot be a random oracle; the sum of two mapped points is indifferentiable from one.&quot; },
  { front: &quot;Which map for BLS12-381?&quot;, back: &quot;Isogeny-SSWU, because A is zero. The curve chooses the map, not you.&quot; },
  { front: &quot;Is hash-to-curve quantum-safe?&quot;, back: &quot;No. It falls to Shor. PQ signatures avoid it; PQ OPRFs and VRFs are unsolved.&quot; }
]} questions={[
  { q: &quot;State the three properties a safe hash-to-curve map must satisfy.&quot;, a: &quot;Unknown discrete log, constant running time, and a distribution indifferentiable from a random oracle.&quot; },
  { q: &quot;Which property does each naive map violate, and what does each leak?&quot;, a: &quot;H(m) times G violates unknown discrete log and leaks universal forgery; try-and-increment violates constant time and leaks a secret input by timing.&quot; },
  { q: &quot;Why is the RO variant the provable optimum?&quot;, a: &quot;A single map cannot be uniform and surjective; the sum of two maps is indifferentiable; RFC 9380 standardizes exactly that.&quot; },
  { q: &quot;What is a DST for, and when must it be hashed?&quot;, a: &quot;Domain separation between protocols; it must be hashed when longer than 255 bytes and unique per protocol and version.&quot; },
  { q: &quot;Where is the open frontier?&quot;, a: &quot;Post-quantum OPRFs and VRFs, formal verification of deployed implementations, and efficient hashing to G2.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>hash-to-curve</category><category>elliptic-curves</category><category>rfc-9380</category><category>bls-signatures</category><category>oprf</category><category>pake</category><category>applied-cryptography</category><category>side-channels</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>