# The Doorway Into the Group: A Field Guide to Hashing a String Onto an Elliptic Curve (RFC 9380)

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

*Published: 2026-07-12*
*Canonical: https://paragmali.com/blog/the-doorway-into-the-group-a-field-guide-to-hashing-a-string*
*© Parag Mali. All rights reserved.*

---
<TLDR>
**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'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.
</TLDR>

## 1. They Timed the Handshake and Read the Wi-Fi Password

In April 2019, two researchers announced they could recover your Wi-Fi password without guessing it, phishing it, or breaking any cipher. They simply *timed* 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.

Get that one step wrong in the *other* obvious way, computing the point as $H(m)\cdot G$, and you leak a different secret entirely. Now *anyone* 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.

<Definition term="Hash-to-curve">
A function that maps an arbitrary byte string to a point on an elliptic curve, such that the point'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].
</Definition>

The primitive looks trivial. Hash the input, get a point, move on. But "get a point" 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.

> **Key idea:** When you turn a string into a curve point, ask: (1) do you know the point's discrete log; (2) does your running time depend on the input; and (3) is the output distribution one your protocol's proof can treat as a random oracle? Every safe construction answers **no, no, yes**. Every named failure in this article is a wrong answer to exactly one of these.

WPA3-Personal answered the second question wrong. Its handshake, Simultaneous Authentication of Equals (SAE), derives a "password element" 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].

<Mermaid caption="How a data-dependent iteration count in the SAE handshake leaks the password to a passive stopwatch">
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->>STA: Derive password element by hunt-and-peck
    Note over STA: Iteration count depends on the password
    STA->>AP: Send SAE Commit after the loop finishes
    Atk-->>STA: Measure time from trigger to Commit
    Note over Atk: Timing reveals the iteration count
    Atk->>Atk: Offline dictionary attack narrows candidates
</Mermaid>

RFC 9380, the standard that finally settled this primitive, is blunt about why try-and-increment is unsafe.

<PullQuote>
"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." -- RFC 9380, Appendix A [@rfc9380]
</PullQuote>

The story got worse before it got better. The Wi-Fi Alliance's first attempt at a fix switched SAE to Brainpool curves, which *reintroduced* 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 *and* the client's MAC address [@dragonblood-site].<Sidenote>This second act is the whole lesson in miniature: constant time cannot be patched in curve by curve. The disclosure site concludes that "implementing Dragonfly and WPA3 without side-channel leaks is surprisingly hard" [@dragonblood-site].</Sidenote> Two rounds of the same class of bug, in the handshake now standard across WPA3 Wi-Fi.

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

This is the sibling of a claim Part 15 made about the curve itself: [the elliptic-curve discrete-log problem](/blog/the-curve-was-hard-the-gap-was-soft-a-field-guide-to-using-e/) 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 "hash to a point" is an algorithm and not a one-liner, we have to go back to the moment it stopped being free.

## 2. When Hashing to a Group Stopped Being Free

For a generation of cryptographers, "hash into the group" was not a problem worth naming. In a prime field $\mathbb{F}_p^*$, 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.<Sidenote>This is the free lunch elliptic curves took away. In $\mathbb{F}_p^*$ the group *is* 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.</Sidenote>

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. "Hash to a group element" stopped being a reduction and became a search.

Nobody worried, because for a decade nobody needed to hash *to a point*. Ordinary ECDSA and ECDH hash to a *scalar* and multiply a fixed generator, which never leaves the safe world of exponents.<MarginNote>Hashing to a scalar is trivial and always safe: any integer modulo the group order is a valid exponent. Hashing to a *point* is the hard problem, because most field elements are not on the curve.</MarginNote>

Then, around the turn of the millennium, the demand arrived from two directions at once, and both directions needed a point.

<Definition term="BLS signature">
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].
</Definition>

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 *is* 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: `MapToGroup`, a try-and-increment loop [@bls01]. Boneh and Franklin instead gave a *deterministic* 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].

The second direction was password-authenticated key exchange. In 2008 Dan Harkins proposed Simultaneous Authentication of Equals, a balanced PAKE that maps a *password* 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 "Hunting and Pecking with ECC Groups" derivation, and then shipped to consumers in 2018 as the core of WPA3-Personal [@rfc7664] [@vr20].

> **Note:** 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 *be* 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].

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 "everything depends on hash-to-curve" claim is precise, not universal, and it applies to the named constructions in this article: SAE, CPace, OPAQUE's OPRF, the RFC 9497 OPRFs, and BLS.<Sidenote>The lesson for a field guide: SPAKE2 and SRP are the counterexamples that keep "all PAKEs need hash-to-curve" from being true, and they stop you hunting for the primitive where it is not.</Sidenote>

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.

## 3. Two Shortcuts, Two Spilled Secrets

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.

### Shortcut A: compute the point as a known multiple of the generator

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.

RFC 9380 does not hedge about the consequence.

<PullQuote>
"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." -- RFC 9380, Appendix A [@rfc9380]
</PullQuote>

"Catastrophic" 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

$$\sigma = [sk]\cdot H(m) = [sk]\cdot[H_n(m)]\cdot G = [H_n(m)]\cdot([sk]\cdot G) = [H_n(m)]\cdot PK.$$

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.

<RunnableCode lang="js" title="Universal forgery when H(m) has a known discrete log (toy group, illustration only)">{`
// ILLUSTRATION, NOT AN IMPLEMENTATION.
// We model the group as integers mod a small prime order n, with
// "scalar mult" [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; }

const n = 97;          // toy prime group order
const G = 1;           // generator
const sk = 42;         // signer's SECRET key
const PK = scalarMul(sk, G, n);   // public key = [sk]*G

// 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!)

// Honest signer computes sigma = [sk]*H(m)
const legitSig = scalarMul(sk, Hm, n);

// Attacker never sees sk. Uses only Hn(m) and PK:
const forgedSig = scalarMul(Hn_m, PK, n);   // [Hn(m)]*PK

console.log("legit signature :", legitSig);
console.log("forged signature:", forgedSig, "(computed with NO secret key)");
console.log("forgery succeeds:", legitSig === forgedSig);
`}</RunnableCode>

### Shortcut B: search for a valid coordinate

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.

<Definition term="Try-and-increment (hunt-and-peck)">
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.
</Definition>

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

<RunnableCode lang="js" title="The iteration count depends on the input, which is the timing channel (toy field, illustration only)">{`
// 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

function modpow(base, exp, mod) {
  let r = 1; base = base % mod;
  while (exp > 0) {
    if (exp & 1) r = (r * base) % mod;
    base = (base * base) % mod;
    exp = exp >> 1;
  }
  return r;
}
// Euler'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; }

// 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; }

function iterations(seed) {
  for (let ctr = 1; ctr < 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;
}

for (const pw of [11, 23, 37, 58, 71, 90]) {
  console.log("secret input " + pw + " -> " + iterations(pw) + " iterations");
}
console.log("Different inputs, different work: that difference is the leak.");
`}</RunnableCode>

Here is the table that makes the whole article click. Two maps, three properties, and each map fails a different one.

| Map | Unknown discrete log (Prop 1) | Constant time (Prop 2) | Right distribution (Prop 3) | The secret it spills |
|-----|:---:|:---:|:---:|-----|
| $H(m)\cdot G$ | **Violated** (DL is $H_n(m)$) | Satisfied | Satisfied (uniform) | The signing key's power -- universal forgery [@rfc9380] [@bls01] |
| Try-and-increment | Satisfied | **Violated** (loop depends on input) | Roughly satisfied | The secret input -- via handshake timing [@rfc9380] [@vr20] |

<Aside label="Why the leak is conditional on secrecy">
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 "variable time is dangerous when the input is secret," never "variable time is always dangerous" [@rfc9380] [@vr20].
</Aside>

> **Note:** 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 "because the goal is to specify algorithms that can plausibly be computed in constant time" [@rfc9380].

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.

## 4. Moving Each Property Into the Curve

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

<Mermaid caption="Each generation closes the gap the previous one left open, converging on RFC 9380">
flowchart TD
    Naive["Naive maps: known DL or variable time"] -->|"one leaks the DL, one leaks timing"| G1["SvdW 2006: constant time, unknown DL"]
    G1 -->|"cheaper per-curve maps"| G2["Icart and Simplified SWU, 2007 to 2010"]
    G1 -->|"a single map is not a random oracle"| G3["BCIMRT 2010: sum of two maps"]
    G2 -->|"still a single map"| G3
    G3 -->|"map per curve family"| G4["Elligator 2, 2013: Montgomery and Edwards"]
    G3 -->|"pairing curves with A zero"| G5["Isogeny SSWU, 2019: BLS12-381"]
    G4 --> RFC["RFC 9380, 2023: one interface"]
    G5 --> RFC
</Mermaid>

### The first constant-time map: Shallue-van de Woestijne (2006)

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 "SW" in "SSWU." 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.

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

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.

### The wall every single map hits

RFC 9380 states the obstacle plainly: the deterministic maps "map to some fraction of the points" on the curve, "not the entire curve," which "means that they cannot be used directly to construct a random oracle that outputs points on the curve" [@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's unforgeability, an OPRF's pseudorandomness, a password mapping -- does not go through. This is Property 3, and it is missing.

<Definition term="Indifferentiability">
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 "indifferentiable from a random oracle" when no efficient distinguisher can separate it from a true random oracle, even given access to the underlying building blocks [@mrh04].
</Definition>

Before that wall was scaled, the field spent a few years making the map cheaper.

### Cheaper maps: Ulas, Icart, Simplified SWU (2007-2010)

Ulas simplified SvdW in 2007 (the "U" 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 "Simplified SWU" 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's heavier parameterization.

<Definition term="Simplified SWU (SSWU)">
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].
</Definition>

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.

### The breakthrough: add two maps (2010)

Here is the shift the whole standard turns on. A single deterministic map cannot be a random oracle. But the *sum of two independent maps* can be. Hash the message to two field elements, map each to a point, and add the points:

$$H(m) = f\big(H_0(m)\big) + f\big(H_1(m)\big).$$

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.

> **Key idea:** A single deterministic map can *never* 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 `hash_to_curve` (the `_RO_` suites) costs twice what `encode_to_curve` (the `_NU_` suites) costs: the second map is not waste, it is what buys the distribution [@bcimrt10] [@ffstv13] [@rfc9380].

<Mermaid caption="Why the _RO_ construction costs two maps: hash to two field elements, map each, add, then clear the cofactor">
flowchart TD
    M["msg and DST"] --> HF["hash_to_field to two elements"]
    HF --> Q0["map_to_curve of u0 gives Q0"]
    HF --> Q1["map_to_curve of u1 gives Q1"]
    Q0 --> ADD["add points: Q0 plus Q1"]
    Q1 --> ADD
    ADD --> RO["sum is indifferentiable from a random oracle"]
    RO --> CC["clear_cofactor"]
    CC --> P["point in the prime-order subgroup"]
</Mermaid>

Precision matters here, because it is easy to over-credit. Brier et al. proved the sum-of-two construction indifferentiable *specifically for Icart's map*. The general result -- that summing two copies of essentially *any* deterministic map yields a random oracle -- is due to Farashahi, Fouque, Shparlinski, Tibouchi, and Voloch in 2013.<Sidenote>Attribute the general theorem to FFSTV13, not to BCIMRT. RFC 9380's Appendix A is careful about this: BCIMRT proved it for Icart's method; FFSTV13 "improve the analysis, showing that it applies to essentially all deterministic mappings" [@rfc9380] [@ffstv13].</Sidenote> 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 *your* curve family, and two families were still uncovered.

### Covering the remaining curves: Elligator 2 (2013) and isogeny SSWU (2019)

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.

<Definition term="Elligator 2" sameAs="https://elligator.org/">
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].
</Definition>

Elligator'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.<Sidenote>The elligator.org site records the pivot in its own words: a number of protocols "need a way to map an arbitrary input to an elliptic curve point whose factorisation is unknown ('hashing to an elliptic curve'), such as verifiable random functions (VRFs) and oblivious pseudorandom functions (OPRFs)." A steganography tool became hash-to-curve infrastructure [@elligator-site].</Sidenote> The tool built to *hide* a key exchange became the constant-time inner map for an entire curve family's hashing.

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 *isogenous* 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 "within 9% of the fastest, non-constant-time alternatives" [@wb19].

| Generation | Year | Key idea | Time | What it secured | Status |
|---|---|---|---|---|---|
| $H(m)\cdot G$ | ~2001 | scalar-mult the generator | constant | nothing (DL is known) | never safe as a map [@rfc9380] |
| Try-and-increment | 2001-2008 | search for a valid coordinate | variable | unknown discrete log | deprecated for secret inputs [@bls01] [@rfc9380] |
| SvdW | 2006 | rational parameterization, no loop | constant | constant time by construction | general fallback [@sw06] |
| Ulas / Icart / Simplified SWU | 2007-2010 | cheaper closed-form maps | constant | efficiency, per-curve forms | inner maps [@u07] [@icart09] [@bcimrt10] |
| BCIMRT sum-of-two | 2010 | add two mapped points | constant | random-oracle distribution | the `_RO_` core [@bcimrt10] [@ffstv13] |
| Elligator 2 | 2013 | invertible map for order-2 curves | constant | Montgomery/Edwards coverage | active [@bhkl13] |
| Isogeny SSWU | 2019 | map via an isogenous curve | constant | $A=0$ pairing-curve coverage | active [@wb19] |

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?

## 5. RFC 9380: All Three Properties at Once

In August 2023 the CFRG published RFC 9380, "Hashing to Elliptic Curves," 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.

The pipeline has three stages [@rfc9380]:

1. **`hash_to_field`** turns the input and a domain tag into one or more field elements, using `expand_message_xmd` for fixed-output hashes like SHA-256 and `expand_message_xof` for extendable-output functions like SHAKE.
2. **`map_to_curve`** 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.
3. **`clear_cofactor`** forces the result into the prime-order subgroup.

<Mermaid caption="The RFC 9380 pipeline, with the branch that distinguishes hash_to_curve from encode_to_curve">
flowchart TD
    IN["input msg and DST"] --> HF["hash_to_field"]
    HF --> BR&#123;"which variant"&#125;
    BR -->|"_RO_ hash_to_curve"| TWO["two field elements, two maps, add"]
    BR -->|"_NU_ encode_to_curve"| ONE["one field element, one map"]
    TWO --> CC["clear_cofactor"]
    ONE --> CC
    CC --> OUT["point in the prime-order subgroup"]
</Mermaid>

### The two-element margin is a proven bound

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.

<Definition term="hash_to_field">
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].
</Definition>

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

### The variant is in the name

The choice between a uniform, proof-usable point and a cheaper nonuniform one is encoded directly in the suite name. `hash_to_curve`, the `_RO_` family, runs the two-element sum-of-two construction and is indifferentiable from a random oracle. `encode_to_curve`, the `_NU_` 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.

RFC 9380 suite identifiers follow the pattern `CURVE_XMD:HASH_MAP_VARIANT_`. Once you can read one, you can read them all.

| Field | In `BLS12381G2_XMD:SHA-256_SSWU_RO_` | What it fixes |
|---|---|---|
| Curve / group | `BLS12381G2` | the target group: BLS12-381, subgroup G2 |
| Expander | `XMD` | `expand_message_xmd` (a fixed-output hash) |
| Hash | `SHA-256` | the underlying hash function |
| Inner map | `SSWU` | Simplified SWU (isogeny variant here, since $A=0$) |
| Variant | `RO` | `_RO_`: uniform, indifferentiable from a random oracle |

Read `edwards25519_XMD:SHA-512_ELL2_RO_` 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.

<RunnableCode lang="js" title="An RFC 9380 suite-ID parser (genuinely runnable)">{`
// Decompose an RFC 9380 suite ID: CURVE_EXPANDER:HASH_MAP_VARIANT_
function parseSuite(id) {
  const [left, right] = id.split(":");
  const leftParts = left.split("_");
  const expander = leftParts.pop();          // XMD or XOF
  const curve = leftParts.join("_");         // e.g. BLS12381G2, edwards25519
  const rp = right.split("_").filter(function (s) { return s.length > 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("_"); // SSWU, ELL2, SVDW
  return { curve: curve, expander: expander, hash: hash, map: map,
           variant: variant,
           uniform: variant === "RO" ? "uniform (random oracle)" : "nonuniform" };
}

["BLS12381G2_XMD:SHA-256_SSWU_RO_",
 "edwards25519_XMD:SHA-512_ELL2_RO_",
 "P256_XMD:SHA-256_SSWU_NU_"].forEach(function (id) {
  console.log(id);
  console.log("   ", JSON.stringify(parseSuite(id)));
});
`}</RunnableCode>

### The two steps you must not skip

Two pipeline stages are easy to forget and dangerous to omit. The first is the domain tag.

<Definition term="Domain Separation Tag (DST)">
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].
</Definition>

The second is cofactor clearing, the final stage that lands the point where the group arithmetic is safe.

<Definition term="Cofactor clearing">
The final pipeline stage: multiply the mapped point (or apply a faster endomorphism-based equivalent) by the curve's cofactor to force it into the prime-order subgroup, closing off small-subgroup attacks. edwards25519 has cofactor 8; BLS12-381's G1 and G2 have large cofactors with fast clearing methods [@rfc9380].
</Definition>

> **Note:** 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].

Step back and the three properties resolve into one sentence. The mapped point had to be a *stranger* -- its discrete log unknown, or forgery follows. It had to *arrive on time* -- in work independent of the input, or the clock leaks the secret. And it had to *look the part* -- drawn from a distribution the proof can treat as a random oracle, or the proof does not hold.

<PullQuote>
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.
</PullQuote>

That framing also closes the loop this article opened. WPA3's answer to Dragonblood is Hash-to-Element, or H2E.

<Aside label="Closing the Dragonblood loop: WPA3's H2E fix">
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 "in constant time" [@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.
</Aside>

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.

## 6. Who Quietly Depends on This Today

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.

<Mermaid caption="Deployed consumers of RFC 9380 hash-to-curve, with OPAQUE and Privacy Pass reaching it indirectly through the OPRF">
flowchart TD
    BLS["BLS signatures on Ethereum"] --> H2C["RFC 9380 hash-to-curve"]
    OPRF["OPRF and VOPRF (RFC 9497)"] -->|"one HashToGroup call"| H2C
    OPAQUE["OPAQUE aPAKE (RFC 9807)"] -->|"indirect"| OPRF
    PP["Privacy Pass (RFC 9578)"] --> OPRF
    CPace["CPace PAKE (draft)"] --> H2C
    SAE["WPA3-Personal SAE"] -->|"H2E"| H2C
</Mermaid>

### BLS signatures and Ethereum consensus

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 `hash_to_point` is not an accessory but the heart of the scheme [@draft-bls-signature-07]. Ethereum's consensus specification fixes this for validator signatures: it adopts the suite `BLS12381G2_XMD:SHA-256_SSWU_RO_` -- the isogeny-SSWU map wrapped in the `_RO_` construction -- so every consensus client hashes exactly this way [@eth-consensus-specs] [@draft-bls-signature-07] [@rfc9380]. The `_RO_` variant is not optional here: the unforgeability proof models the map as a random oracle, so a nonuniform `_NU_` 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].<Sidenote>Unlike ECDSA, BLS is deterministic and uses no per-signature nonce, so it sidesteps the [nonce-reuse failures that have repeatedly broken ECDSA deployments](/blog/one-number-used-twice-how-a-repeated-nonce-hands-over-your-p/) (Part 8). Its one hash-to-curve call becomes the security-critical primitive in place of the nonce [@draft-bls-signature-07].</Sidenote>

### OPRFs, and everything built on them

The second large consumer is the oblivious pseudorandom function and its verifiable and partially-oblivious variants, standardized in RFC 9497 [@rfc9497].

<Definition term="Oblivious Pseudorandom Function (OPRF)" sameAs="https://www.rfc-editor.org/rfc/rfc9497.html">
A two-party protocol in which a client learns $F(k, x)$ for its input $x$ and the server's key $k$, while the server learns neither $x$ nor the result. The client's first step hashes $x$ to a curve point and blinds it; that single `HashToGroup` call is the protocol's entire hash-to-curve surface [@rfc9497].
</Definition>

The dependency is remarkably concentrated. The client's `Blind(input)` computes `HashToGroup(input)` 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].

> **Note:** For an OPRF, the entire hash-to-curve attack surface is the single `HashToGroup` call in the client's `Blind` 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].

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]. [Privacy Pass](/blog/the-age-gate-that-doesnt-know-your-age-how-anonymous-credent/), standardized across RFC 9576, RFC 9577, and RFC 9578 in June 2024, issues anonymous authentication tokens; its privately verifiable issuance is "based on the Oblivious Pseudorandom Function," with token type 0x0001 being a VOPRF over P-384, which is the SSWU path [@rfc9578] [@rfc9576] [@rfc9577].

### PAKEs and Wi-Fi

Two more consumers map a secret rather than a public message. CPace, a balanced PAKE still in CFRG draft with datatracker status "Waiting for IRTF Chair," maps the password to a session generator using SSWU or Elligator 2 depending on the group [@draft-cpace]. And WPA3-Personal's SAE now derives its password element with the constant-time H2E map, the fix that closed Dragonblood [@dragonblood-site].

The whole deployed picture fits in one adoption map.

| Consumer | Where hash-to-curve is called | Suite / map | Standard status |
|---|---|---|---|
| BLS / Ethereum consensus | `hash_to_point(msg)` into G2 | `BLS12381G2_XMD:SHA-256_SSWU_RO_` [@draft-bls-signature-07] | Internet-Draft [@draft-bls-signature-07] |
| OPRF / VOPRF / POPRF | `Blind` calls `HashToGroup` | ristretto255 to Elligator 2; P-curves to SSWU [@rfc9497] | RFC 9497 (2023) [@rfc9497] |
| OPAQUE (aPAKE) | indirect, via its OPRF | inherits the OPRF suite [@rfc9807] | RFC 9807 (2025) [@rfc9807] |
| Privacy Pass | VOPRF token issuance | P-384 VOPRF to SSWU [@rfc9578] | RFC 9576/9577/9578 (2024) [@rfc9578] |
| CPace (balanced PAKE) | password to session generator | SSWU or Elligator 2 by group [@draft-cpace] | draft, "Waiting for IRTF Chair" [@draft-cpace] |
| WPA3-Personal (SAE) | password element (PWE) | H2E, constant-time SSWU [@dragonblood-site] | IEEE 802.11 |

### What actually ships

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'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 `RistrettoPoint::from_uniform_bytes` [@curve25519-dalek].

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?

## 7. The Decision Matrix

There is no "best" 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.

### Distribution need decides the variant

The `_RO_` versus `_NU_` choice is governed by one question: does your proof need a random oracle? For BLS, OPRFs, and password mapping, it does, so `_RO_` is the default. The `_NU_` variant is an optimization you reach for only with a specific proof that non-uniformity is acceptable, because its output is measurably not uniform.

| Dimension | `hash_to_curve` (`_RO_`) | `encode_to_curve` (`_NU_`) |
|---|---|---|
| Field elements and maps | 2 elements, 2 maps, 1 addition | 1 element, 1 map |
| Relative cost | about twice `_NU_` | baseline, about half of `_RO_` |
| Output distribution | indifferentiable from a random oracle [@rfc9380] | nonuniform: at least one eighth of G, weights vary by up to a factor of four [@rfc9380] |
| Proof compatibility | usable where a proof assumes a random oracle | only where non-uniformity is provably sufficient |
| Decision rule | default for BLS, OPRF, PAKE mapping | optimization only, with a proof |

Getting this backward is a shippable misuse: a `_NU_` map where the proof assumed `_RO_` produces a point from a distribution the proof never agreed to, which is the third of our three failures made concrete [@rfc9380].

### Curve family decides the map

The choice among the inner maps is not a matter of taste at all. It is fixed by the shape of your curve.

<Mermaid caption="The curve family, not preference, selects the inner map">
flowchart TD
    Q&#123;"what curve do you have"&#125;
    Q -->|"short Weierstrass, p is 3 mod 4"| SSWU["Simplified SWU"]
    Q -->|"A is zero: secp256k1, BLS12-381"| ISO["Isogeny SSWU"]
    Q -->|"Montgomery or Edwards"| ELL["Elligator 2"]
    Q -->|"none of the above"| SVDW["SvdW fallback"]
</Mermaid>

| Map | Curve condition | Curves in RFC 9380 | Dominant cost |
|---|---|---|---|
| Simplified SWU | short-Weierstrass, $p \equiv 3 \pmod 4$, $AB \neq 0$ | P-256, P-384, P-521 | about one square root plus selects |
| Isogeny SSWU | $A = 0$ Weierstrass | secp256k1, BLS12-381 G1/G2 | SSWU on $E'$ plus one isogeny evaluation |
| Elligator 2 | curve with a point of order 2 | curve25519/448, edwards25519/448, ristretto255/decaf448 | about one exponentiation |
| SvdW | any curve, characteristic other than 2 or 3 | fallback when none of the above fit | a few exponentiations and inversions |

> **Note:** 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, `_RO_` or `_NU_`, and the default is `_RO_` [@rfc9380].

### The two knobs that are real, but small

Two remaining choices are genuine but low-stakes. The expander, `expand_message_xmd` versus `expand_message_xof`, 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.

<Aside label="BLS: hashing to G1 versus G2">
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].
</Aside>

### The number that ends an old argument

For years, the excuse for variable-time hunt-and-peck was that constant-time maps were too slow. Wahby and Boneh'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].

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 "almost uniform": none of it is arbitrary. The next section proves they are the right choices, not just good ones.

## 8. Why Two Maps, and How Uniform Is "Uniform"

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.

### The lower bound that forces two maps

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 "includes at least one eighth of the points in G," and the probability of points "varies by at most a factor of four" [@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.

### The matching upper bound

Now the other side. Hashing to two field elements, mapping each, and adding the points *is* indifferentiable from a random oracle, proven for Icart'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 `_RO_` construction is the tight, achievable answer to "produce a uniform, proof-usable point," and RFC 9380 standardizes precisely that optimum, no more and no less [@rfc9380].

### Why the oversampling margin is what it is

The same "prove it, do not hope it" discipline governs `hash_to_field`. 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

$$L = \left\lceil \frac{\lceil \log_2 p \rceil + k}{8} \right\rceil,$$

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.

### The one caveat that survives all of this

Here the humility arrives. "Indifferentiable from a random oracle" does not mean "safe to drop into any random-oracle proof."

<Aside label="Indifferentiability is not a universal license (RSS11)">
RFC 9380 cites Ristenpart, Shacham, and Shrimpton for the sharpest limit on the whole approach: "not all security proofs that rely on random oracles continue to hold when those oracles are replaced by indifferentiable functionalities" [@rfc9380]. So `_RO_` is the right default, but a protocol's proof still has to be the composable kind that survives the substitution. Indifferentiability is a strong guarantee, not an unconditional one.
</Aside>

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.<Sidenote>That probability is negligible and it is infeasible to find such an input deliberately, so RFC 9380 says protocols "SHOULD NOT add a special case to detect and 'fix' the identity element." The special case is itself a bug surface, more likely to introduce a flaw than to prevent one [@rfc9380].</Sidenote> The right move is to do nothing special, which is counterintuitive enough that people get it wrong by trying too hard.

> **Note:** 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].

One boundary is worth stating precisely, because it sets up everything still open. The "unknown discrete log" 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's algorithm on a fault-tolerant quantum computer. Post-quantum *signatures* sidestep the primitive entirely, because they use no curve. But the *consumers that specifically need a point of unknown discrete log* -- OPRFs and VRFs -- have no such escape. That is where the frontier actually is.

## 9. Where It Is Still Moving

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.

### Constant time, all the way down

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 `sgn0`, the zero-guarded inversion `inv0`, 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.

> **Note:** Constant time must be structural in the map and then verified down to the field primitives, not patched in after the fact. Dragonblood's second act (Section 1) is the standing proof: fixing timing curve by curve is how you get burned twice [@rfc9380] [@dragonblood-site].

### The expensive curves

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' "Fast Hashing to G2," but G2 remains the expensive path, and each new pairing-friendly curve keeps the problem alive [@wb19] [@sbcdk09].

### Machine-checked implementations

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

### The genuinely open frontier: post-quantum

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.<Sidenote>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.</Sidenote> There is no drop-in "post-quantum hash-to-curve," because the whole unknown-DL abstraction has to be rebuilt on different assumptions, and the competitive replacement does not yet exist.

### Steady-state engineering

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 "which map for which curve" table a living document rather than a finished one. Given all of it, the practical question remains: what should you actually do on Monday?

## 10. The Decision Rules, Made Operational

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.

**Use a vetted suite, never a hand-rolled map.** 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].

**Default to `_RO_`.** Use `hash_to_curve` (`_RO_`) unless you hold a specific proof that non-uniformity is acceptable, in which case `encode_to_curve` (`_NU_`) halves the cost [@rfc9380].

**Always set a unique DST.** 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 [domain separation](/blog/one-secret-is-not-one-key-the-discipline-of-key-derivation-w/), the same discipline Part 13 develops for keys [@rfc9380].

**Map by curve, not by taste.** 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].

Then the per-protocol specifics:

- **BLS:** use `BLS12381G2_XMD:SHA-256_SSWU_RO_` for minimal-pubkey-size, or the G1 suite for minimal-signature-size; `_RO_` is required for the unforgeability proof; clear the cofactor [@draft-bls-signature-07].
- **OPRF, VOPRF, OPAQUE:** call RFC 9497's `HashToGroup` and `HashToScalar` at the ciphersuite's suite; do not substitute your own map; reject the identity element on the wire [@rfc9497].
- **PAKE mapping a password (SAE, CPace):** use a constant-time map only, never hunt-and-peck a secret, and pre-hash the password with [a slow KDF such as PBKDF2, scrypt, or Argon2](/blog/the-one-job-of-a-password-hash-why-fast-is-the-enemy-and-mem/) before mapping, so an adversary who sees an intermediate value still faces a slow dictionary attack [@rfc9380]. Part 12's password-entropy argument is exactly why this matters.

> **Key idea:** Pick the suite for your curve, default to `_RO_`, 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].

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.

| Misuse | What it violates | The fix |
|---|---|---|
| $P = H(m)\cdot G$ instead of a map | Unknown discrete log | use a real RFC 9380 map [@rfc9380] |
| Hunt-and-peck over a secret | Constant time | constant-time suite; H2E for SAE [@rfc9380] [@dragonblood-site] |
| `_NU_` where the proof needs `_RO_` | Random-oracle distribution | default to `_RO_` [@rfc9380] |
| Missing or shared DST | Domain separation | unique per-protocol, per-version DST [@rfc9380] |
| Skipped cofactor clearing | Subgroup safety | clear the cofactor; check the subgroup [@rfc9380] |
| Truncated `hash_to_field` output | Uniformity margin | use the full $L$ bytes [@rfc9380] |
| Raw password without a slow KDF | Offline-guessing resistance | PBKDF2, scrypt, or Argon2 first [@rfc9380] |

> **Note:** Vetted RFC 9380 suite for the curve. `_RO_` unless a proof licenses `_NU_`. Unique, versioned DST, hashed if over 255 bytes. Cofactor cleared and subgroup confirmed. Full `L`-byte `hash_to_field`. 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].

<Spoiler kind="hint" label="A quick way to sanity-check any hash-to-curve library">
RFC 9380 ships official test vectors for every suite. Before you trust an implementation, feed it the standard'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].
</Spoiler>

A checklist answers "what to do." The last thing to handle is the set of "but why can't I just..." questions that every reviewer and implementer keeps asking, because the answers are where the understanding finally sets.

## 11. FAQ and Common Misuse

<FAQ title="Frequently asked questions">
<FAQItem question="Why can't I just compute H(m) times G?">
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's password element [@rfc9380] [@bls01].
</FAQItem>
<FAQItem question="Isn't try-and-increment fine?">
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].
</FAQItem>
<FAQItem question="RO or NU, does it actually matter?">
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].
</FAQItem>
<FAQItem question="Do I really need a domain-separation tag?">
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].
</FAQItem>
<FAQItem question="Which map do I pick for Curve25519 or for BLS12-381?">
You don'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].
</FAQItem>
<FAQItem question="Is hash-to-curve quantum-safe?">
No. It is bound to elliptic-curve cryptography and falls to Shor'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].
</FAQItem>
<FAQItem question="Are encode_to_curve and hash_to_curve the same thing?">
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].
</FAQItem>
</FAQ>

## The Point Had to Be a Stranger, Arrive on Time, and Look the Part

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 *stranger*, and universal forgery followed [@rfc9380] [@bls01]. Hunt-and-peck leaked the password through the clock: the point did not *arrive on time*, and Dragonblood read it off the handshake [@vr20]. Reaching for `_NU_` where a proof needs `_RO_` handed the proof a distribution it never agreed to: the point did not *look the part* [@rfc9380]. Three questions, three ways to fail, three real systems that failed them.

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.

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.

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

<StudyGuide slug="hashing-to-elliptic-curves-rfc-9380" keyTerms={[
  { term: "Hash-to-curve", definition: "A map from an arbitrary byte string to a curve point of unknown discrete log; standardized by RFC 9380." },
  { term: "BLS signature", definition: "A pairing-based signature where the signature is the hashed message point scaled by the secret key." },
  { term: "Try-and-increment", definition: "Hash with a counter until the result is a valid coordinate; unknown DL but variable time." },
  { term: "Indifferentiability", definition: "A formal criterion for when a construction can safely replace an ideal object such as a random oracle." },
  { term: "Simplified SWU", definition: "A closed-form map to short-Weierstrass curves with p equal to 3 mod 4 and nonzero A and B." },
  { term: "Elligator 2", definition: "An invertible map for curves with a point of order 2, used for Montgomery and Edwards curves." },
  { term: "hash_to_field", definition: "The first pipeline stage: expand input and DST into field elements within 2^-128 of uniform." },
  { term: "Domain Separation Tag", definition: "A mandatory, protocol-and-version-specific tag mixed into hashing to prevent cross-protocol collisions." },
  { term: "Cofactor clearing", definition: "The final stage that forces the mapped point into the prime-order subgroup." },
  { term: "Oblivious PRF", definition: "A protocol where the client learns a keyed function of its input while the server learns neither input nor output." }
]} flashcards={[
  { front: "Why is P = H(m) times G unsafe?", back: "Its discrete log is public, so anyone forges BLS signatures as Hn(m) times PK with no secret key." },
  { front: "Why does try-and-increment leak?", back: "Its iteration count depends on the input, so timing reveals a secret input. This is Dragonblood." },
  { front: "Why does hash_to_curve run two maps?", back: "A single map cannot be a random oracle; the sum of two mapped points is indifferentiable from one." },
  { front: "Which map for BLS12-381?", back: "Isogeny-SSWU, because A is zero. The curve chooses the map, not you." },
  { front: "Is hash-to-curve quantum-safe?", back: "No. It falls to Shor. PQ signatures avoid it; PQ OPRFs and VRFs are unsolved." }
]} questions={[
  { q: "State the three properties a safe hash-to-curve map must satisfy.", a: "Unknown discrete log, constant running time, and a distribution indifferentiable from a random oracle." },
  { q: "Which property does each naive map violate, and what does each leak?", a: "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." },
  { q: "Why is the RO variant the provable optimum?", a: "A single map cannot be uniform and surjective; the sum of two maps is indifferentiable; RFC 9380 standardizes exactly that." },
  { q: "What is a DST for, and when must it be hashed?", a: "Domain separation between protocols; it must be hashed when longer than 255 bytes and unique per protocol and version." },
  { q: "Where is the open frontier?", a: "Post-quantum OPRFs and VRFs, formal verification of deployed implementations, and efficient hashing to G2." }
]} />
