<?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: side-channels</title><description>Posts tagged side-channels.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sat, 25 Jul 2026 08:57:49 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/side-channels/rss.xml" rel="self" type="application/rss+xml"/><item><title>Correct, Constant-Time, and Still Owned: A Field Guide to Side Channels, Faults, and Key Custody</title><link>https://paragmali.com/blog/correct-constant-time-and-still-owned-a-field-guide-to-side-/</link><guid isPermaLink="true">https://paragmali.com/blog/correct-constant-time-and-still-owned-a-field-guide-to-side-/</guid><description>Correct, constant-time crypto still leaks keys through timing, cache, speculation, and fault channels, and deployed systems break most often at key custody.</description><pubDate>Tue, 14 Jul 2026 01:57:15 GMT</pubDate><content:encoded>
**Correct algorithms still leak.** Timing, cache, power, electromagnetic, speculative-execution, and induced-fault channels hand attackers keys that the mathematical proof never modeled -- and constant-time coding closes only the channels you can name. But relative to those exotic breaks, and relative to breaking the ciphers themselves, deployed systems fail most often at the least glamorous layer: key and secret management. Put the root key in hardware, use it without exporting it, envelope-wrap and auto-rotate everything else, and keep no long-lived secret sitting in software.
&lt;h2&gt;1. The Algorithm Was Correct. The Key Left Anyway.&lt;/h2&gt;
&lt;p&gt;In early 2024, researchers pulled private keys out of Apple&apos;s newest laptops -- not from buggy code, but from OpenSSL&apos;s Diffie-Hellman, Go&apos;s RSA, and the freshly standardized post-quantum algorithms &lt;a href=&quot;https://paragmali.com/blog/the-thirty-year-migration-ships-in-a-pip-install-how-post-qu/&quot; rel=&quot;noopener&quot;&gt;CRYSTALS-Kyber and CRYSTALS-Dilithium&lt;/a&gt;, every one of them written to run in constant time [@gofetch-2024]. Twenty-eight years earlier, Paul Kocher had recovered a different private key by doing nothing more exotic than watching how long the math took [@kocher-1996]. Same lesson, two eras: the proof modeled the algorithm; the attacker measured the machine.&lt;/p&gt;
&lt;p&gt;This is the twenty-fifth and final part of a field guide that spent twenty-four installments proving, primitive by primitive, that the mathematics holds. Each of those proofs came with a silent condition attached. RSA is secure &lt;em&gt;if&lt;/em&gt; you blind the exponent. Elliptic-curve signatures are unforgeable &lt;em&gt;if&lt;/em&gt; the nonce is uniform and the scalar multiplication is data-independent. AES-GCM is authenticated &lt;em&gt;if&lt;/em&gt; you never reuse a nonce and you compare tags in constant time. Twenty-four times, the phrase &quot;if the caller does X&quot; carried the whole argument. This part is about what happens when that &quot;if&quot; meets a physical machine and a running organization.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The math held; the machine and the operations handed the secret away. A cryptographic proof reasons about an algorithm -- an input/output relation. An attack happens to an implementation -- a physical, timed, power-drawing, speculating, operated process. The distance between those two things is the entire subject of this article.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That distance has a name, the &lt;em&gt;abstraction gap&lt;/em&gt;, and it opens onto five surfaces where a correct algorithm sheds its secret. The first four are the exotic ones that make headlines: timing and cache channels, power and electromagnetic emanations, microarchitectural and transient-execution leaks, and induced faults. The fifth is the unglamorous one that ends most real incidents: the operational handling of keys and secrets themselves. The first four are where clever attackers earn conference papers. The fifth is where ordinary systems quietly die.&lt;/p&gt;
&lt;p&gt;So this is not an encyclopedia of channels. It is an argument, backed by a catalog: every famous break in the record violates exactly one of four invariants -- a secret touched &lt;em&gt;time&lt;/em&gt;, a secret touched an &lt;em&gt;address&lt;/em&gt;, a &lt;em&gt;fault&lt;/em&gt; skipped a check, or a &lt;em&gt;key&lt;/em&gt; outlived or escaped its custody. Seen through those four invariants, the sprawling zoo of attacks collapses into a map that tells you where to spend your defensive budget.&lt;/p&gt;
&lt;p&gt;To see why a correct algorithm leaks, though, you first have to see the gap the proofs never modeled. And it has a birthday.&lt;/p&gt;
&lt;h2&gt;2. Naming the Gap&lt;/h2&gt;
&lt;p&gt;Ask a cryptographer from 1990 what it means for a cryptosystem to be secure and you would get a clean answer: an adversary who sees only the inputs and outputs -- the ciphertexts, the signatures, the public keys -- cannot do better than guessing. The whole edifice of provable security, the IND-CPA and EUF-CMA definitions this series has used throughout, rests on that framing. The adversary is a mathematical object that submits queries and reads replies. It has no stopwatch, no oscilloscope, and no physical access to the box doing the computing.&lt;/p&gt;
&lt;p&gt;That framing quietly omitted an entire dimension. A real computer does not merely map inputs to outputs. It takes &lt;em&gt;time&lt;/em&gt;. It draws &lt;em&gt;power&lt;/em&gt;. It radiates. It caches. It speculates. It can be made to stumble. None of those behaviors appears in the security definition, which means none of them was ever proven absent. The proof was silent about them not because they were safe, but because the model could not see them.&lt;/p&gt;

Information leaked by a computation&apos;s physical or microarchitectural behavior -- its running time, power draw, electromagnetic emanation, or cache state -- rather than by its defined input/output relation. A side channel is real even when the algorithm is mathematically perfect, because it lives in the machine, not the math.
&lt;p&gt;There was already a Cold-War counterexample sitting in the open literature. In 1985, Wim van Eck published the first unclassified demonstration that a video display&apos;s electromagnetic emanations could be intercepted from a distance and its screen contents reconstructed with modest equipment [@van-eck-1985]. Militaries had worried about &quot;compromising emanations&quot; under the codename TEMPEST for decades; van Eck moved the problem into public view. The lesson was blunt: computation emits exploitable physical signals whether or not the theory chooses to look.Van Eck&apos;s result is not itself a cryptographic attack -- it reconstructs a screen, not a key -- but it is the moment compromising emanations entered the open scientific record, which is why it anchors the side-channel lineage [@van-eck-1985].&lt;/p&gt;

flowchart TD
    A[&quot;Algorithm: an input/output relation the proof reasons about&quot;] --&amp;gt;|realized as| B[&quot;Implementation: a physical, timed, operated process&quot;]
    A --&amp;gt; H[&quot;Output the proof models: ciphertext or signature&quot;]
    B --&amp;gt; C[&quot;Leak channel: time and cache addresses&quot;]
    B --&amp;gt; D[&quot;Leak channel: power and EM&quot;]
    B --&amp;gt; E[&quot;Leak channel: speculation and prefetch&quot;]
    B --&amp;gt; F[&quot;Leak channel: induced faults&quot;]
    B --&amp;gt; G[&quot;Leak channel: key custody and secret handling&quot;]
&lt;h3&gt;The eureka: time is an output&lt;/h3&gt;
&lt;p&gt;The gap got its name in 1996. Paul Kocher, then an independent cryptographer, made a single almost impudent observation: if the running time of a correct RSA or Diffie-Hellman exponentiation depends on the bits of the secret exponent, then merely &lt;em&gt;timing&lt;/em&gt; the operation leaks the key [@kocher-1996]. No implementation bug is required. The code can be a flawless transcription of the textbook algorithm. The leak is in the fact that textbook modular exponentiation does more work for a one bit than for a zero bit.&lt;/p&gt;

The distance between an algorithm -- an input/output relation a proof reasons about -- and an implementation, which is a physical, timed, power-drawing, speculating, operated process. Every side-channel and key-management break in this article is a way of falling through that gap.
&lt;p&gt;The canonical exponentiation loop makes the leak concrete. Square-and-multiply walks the secret exponent bit by bit: it squares at every step, and it performs an &lt;em&gt;extra&lt;/em&gt; multiplication only when the current bit is one. The total running time therefore tracks the number of one bits, and finer measurements over chosen inputs recover the bits individually.&lt;/p&gt;

sequenceDiagram
    participant E as Exponent bit
    participant C as CPU
    participant T as Wall-clock time
    E-&amp;gt;&amp;gt;C: current bit is 0
    C-&amp;gt;&amp;gt;C: square only
    C-&amp;gt;&amp;gt;T: short step
    E-&amp;gt;&amp;gt;C: current bit is 1
    C-&amp;gt;&amp;gt;C: square then multiply
    C-&amp;gt;&amp;gt;T: longer step
    Note over C,T: total time tracks the number of one bits in the secret exponent
&lt;p&gt;Kocher&apos;s insight was not a new algorithm. It was the naming of a missing dimension. And once you accept that wall-clock time is an output the proof forgot to model, the same argument applies to every other physical observable: power, electromagnetic field, cache occupancy, speculative footprint, the very correctness of the result. The defensive program for the next thirty years becomes a single sentence: find each channel and remove the secret&apos;s influence over it.Paul Kocher bookends this whole field. He named the timing channel in 1996, co-invented Differential Power Analysis in 1999, and two decades later was a lead author of Spectre in 2018 [@kocher-1996][@dpa-1999][@spectre-2019]. One researcher&apos;s career traces the arc from a stopwatch to speculative execution.&lt;/p&gt;
&lt;p&gt;That program organizes the rest of this article around those four invariants -- time, address, fault, custody. Hold them in mind. If time was an output the proof ignored, so was every other physical observable, and the attackers were only getting started.&lt;/p&gt;
&lt;h2&gt;3. The First Defenses, and Their First Breaks&lt;/h2&gt;
&lt;p&gt;Here is a fact that ought to feel counterintuitive: within one year of Kocher showing you could learn a key by measuring a &lt;em&gt;correct&lt;/em&gt; machine, two teams showed you did not even need the machine to be correct. You could make it compute &lt;em&gt;wrong&lt;/em&gt; on purpose and read the key out of the error.&lt;/p&gt;
&lt;p&gt;The first generation of defenses was a scramble of point countermeasures, one patch per channel. Against timing, Kocher himself proposed &lt;em&gt;blinding&lt;/em&gt;: before the secret operation, randomize the input so its running time carries no information about the true message. For RSA decryption you pick a random $r$, multiply the ciphertext by $r^e$, decrypt, and divide out $r$. The identity that makes it work is&lt;/p&gt;
&lt;p&gt;$$(m \cdot r^e)^d \equiv m^d \cdot r^{ed} \equiv m^d \cdot r \pmod{n},$$&lt;/p&gt;
&lt;p&gt;so multiplying the result by $r^{-1} \bmod n$ recovers $m^d$ while the timing now depends on the random blind, not the secret. Blinding was a proposal in 1996; it became a default only after David Brumley and Dan Boneh showed in 2003 that an OpenSSL RSA key could be recovered &lt;em&gt;over a network&lt;/em&gt; by timing decryptions, ending the comfortable assumption that timing attacks needed a local probe [@brumley-boneh-2003]. OpenSSL turned blinding on by default in response: a theoretical nicety became a shipped mitigation the moment someone proved the threat was remote.&lt;/p&gt;
&lt;h3&gt;Breaking the machine on purpose&lt;/h3&gt;
&lt;p&gt;While the timing people were blinding, the fault people arrived. In 1997, Dan Boneh, Richard DeMillo, and Richard Lipton at Bellcore showed that a single faulty &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 signature&lt;/a&gt; computed with the Chinese Remainder Theorem optimization lets an attacker factor the modulus with one greatest-common-divisor computation [@bellcore-1997]. The RSA-CRT speedup computes the signature modulo each prime separately and recombines the halves; corrupt just one half-computation -- a voltage glitch, a clock spike, a stray cosmic ray -- and the faulty signature $s&apos;$ comes out right modulo one prime but wrong modulo the other. Raising it to the public exponent should return the message $m$; instead $s&apos;^e - m$ shares exactly one of the two prime factors with $n$, so $\gcd(s&apos;^e - m \bmod n, n)$ hands you that factor. One bad signature, and the private key falls out.&lt;/p&gt;

An attack that induces a computational error -- through a voltage or clock glitch, an electromagnetic or laser pulse, or a software-triggered disturbance like Rowhammer -- so that the faulty output reveals the secret. The Bellcore result is the archetype: one incorrect RSA-CRT signature factors the modulus.
&lt;p&gt;The same year, Eli Biham and Adi Shamir extended the idea from public-key to symmetric ciphers with Differential Fault Analysis: inject faults during a DES computation, difference the correct and faulty ciphertexts, and the secret key emerges [@biham-shamir-1997]. The countermeasures wrote themselves in principle -- &lt;em&gt;verify before you output&lt;/em&gt; a CRT signature, add &lt;em&gt;redundancy&lt;/em&gt; and recompute, sense the glitch and refuse -- but each one added cost and each one had gaps.&lt;/p&gt;
&lt;p&gt;Then, in 1999, Kocher returned with Joshua Jaffe and Benjamin Jun and opened the power rail. Simple and Differential Power Analysis showed that a device&apos;s instantaneous power draw depends on the data it switches, and that correlating power traces across many operations extracts a key even when the per-trace signal is buried in noise [@dpa-1999]. A smartcard running AES or RSA was leaking its key through its power pins to anyone with an oscilloscope and patience.&lt;/p&gt;

A statistical attack that correlates a device&apos;s measured power consumption across many operations with a hypothesized intermediate value, extracting the key even under heavy noise. Its countermeasures are masking (split secrets into random shares) and hiding (flatten or randomize the power signal).
&lt;p&gt;The pattern is the argument. Each attack bred a defense -- blinding, verify-before-output, masking and hiding -- and each defense&apos;s gaps bred the next attack. This is whack-a-mole, and playing it one mole at a time was already visibly losing. Three structural cracks doomed the patch-by-patch approach: the channels were multiplying faster than the patches, each patch protected one primitive rather than the class, and the deepest crack was about to open -- the leak was preparing to jump from the &lt;em&gt;value&lt;/em&gt; of the secret to the &lt;em&gt;addresses&lt;/em&gt; the secret makes the processor touch.&lt;/p&gt;

There is a third way implementations betray correct algorithms that this article names but does not re-derive: the [random-number generator](/blog/predictable-or-repeated-the-only-two-ways-cryptographic-rand/). The 2008 Debian OpenSSL disaster (a commented-out line collapsed key entropy to a few thousand possibilities) [@debian-openssl-2008], the 2010 PlayStation 3 ECDSA break (a reused signing nonce) [@ps3-ecdsa-2010], and 2017&apos;s ROCA (a structured-prime library flaw) [@roca-2017] are all implementation and operational failures, not broken math. They belong to the randomness and [nonce-reuse](/blog/one-number-used-twice-how-a-repeated-nonce-hands-over-your-p/) story told in Parts 2 and 8 of this series. Keep them in view as a fourth failure family; we return to their operational cousin -- key custody -- as this article&apos;s payoff.
&lt;h2&gt;4. The Channels Multiply&lt;/h2&gt;
&lt;p&gt;The next twenty years were a single sentence repeated in new dialects: &lt;em&gt;the secret leaked through a channel the proof never modeled&lt;/em&gt;. What changed each time was the channel. Here is the whole family tree, and the timeline that carries it.&lt;/p&gt;

timeline
    title Thirty years of the abstraction gap
    1985 : van Eck EM emanations
    1996 : Kocher timing attack
    1997 : Bellcore RSA-CRT fault : Biham-Shamir DFA
    1999 : Kocher-Jaffe-Jun DPA
    2003 : Brumley-Boneh remote timing
    2005 : Bernstein AES cache : Percival hyperthreading
    2013 : Lucky Thirteen breaks TLS
    2014 : Yarom-Falkner Flush and Reload : Rowhammer
    2018 : Meltdown and Spectre
    2020 : Minerva : TPM-Fail
    2022 : Hertzbleed via DVFS
    2024 : GoFetch : KyberSlash
&lt;h3&gt;From the value to the address&lt;/h3&gt;
&lt;p&gt;The deepest move in the whole story is the one that happened between 2005 and 2014: the leak stopped being about the &lt;em&gt;value&lt;/em&gt; of the secret and became about the &lt;em&gt;addresses&lt;/em&gt; the secret makes the processor touch. Daniel Bernstein&apos;s 2005 cache-timing attack on AES showed that software using secret-dependent table lookups -- the standard fast AES implementation, indexing precomputed T-tables by key-dependent bytes -- leaks the key through cache timing, because which table entries end up cached depends on which ones the victim accessed [@bernstein-2005].&lt;/p&gt;
&lt;p&gt;The same year, Colin Percival showed that Intel Hyper-Threading&apos;s shared L1 cache lets one thread spy on a co-resident thread&apos;s access pattern, recovering RSA key bits from a concurrent OpenSSL process [@percival-2005]. In 2006, Dag Arne Osvik, Adi Shamir, and Eran Tromer named and formalized the reusable primitives the field still uses [@osvik-shamir-tromer-2006]: Prime+Probe fills (primes) a cache set with the attacker&apos;s own lines, lets the victim run, then re-accesses (probes) those lines and times its own reload -- a slow reload reveals the victim evicted a line, so it needs no shared memory, while Evict+Time measures the victim directly.&lt;/p&gt;

A leak that recovers which memory addresses a victim touched by measuring shared-cache timing -- a hit is fast, a miss is slow. Flush+Reload is the sharpest variant: an attacker flushes a shared line, waits, then times the reload to learn whether the victim accessed it in the interval.
&lt;p&gt;The technique reached its high-resolution form in 2014, when Yuval Yarom and Katrina Falkner delivered Flush+Reload: flush a shared cache line with &lt;code&gt;clflush&lt;/code&gt;, wait, then time the reload to learn whether the victim touched it, all through the last-level cache shared across cores [@flush-reload-2014]. It recovered GnuPG RSA keys across cores and became the workhorse primitive behind the transient-execution attacks that followed. The countermeasure had crystallized into a principle, not a patch: never let a secret decide which address you touch.&lt;/p&gt;
&lt;h3&gt;From local to remote, and a flaw in the spec&lt;/h3&gt;
&lt;p&gt;Parallel to the address story ran the reach story. Brumley and Boneh had already made timing a network threat in 2003. Then, in 2013, Nadhem AlFardan and Kenneth Paterson published &lt;a href=&quot;https://paragmali.com/blog/they-read-your-plaintext-without-breaking-your-cipher-a-fiel/&quot; rel=&quot;noopener&quot;&gt;Lucky Thirteen&lt;/a&gt;, and it stung in a new way: the non-constant-time MAC-and-padding check in TLS and DTLS CBC-mode record processing is a timing oracle, and a man-in-the-middle can recover plaintext from it [@lucky13-2013]. The wound was not one library&apos;s bug. The TLS &lt;em&gt;specification&lt;/em&gt; mandated a MAC-then-pad-then-encrypt construction whose safe verification is genuinely hard to do in constant time. This was a spec-level flaw -- a reminder that &quot;write it in constant time&quot; is sometimes fighting the protocol design itself.&lt;/p&gt;
&lt;h3&gt;Below the instruction set&lt;/h3&gt;
&lt;p&gt;For a decade the discipline looked like it might be enough. Then, on 3 January 2018, the floor gave way. Meltdown and Spectre showed that &lt;em&gt;transient execution&lt;/em&gt; -- the out-of-order and speculative machinery every fast CPU uses -- runs secret-dependent code paths that the architectural program never takes, and leaks their traces through the cache [@meltdown-2018][@spectre-2019][@projectzero-2018]. Meltdown let an unprivileged process transiently read kernel memory. Spectre poisoned branch prediction so that even &lt;em&gt;correct, constant-time&lt;/em&gt; code speculatively executed a secret-dependent path and left it in the cache for a Flush+Reload probe to recover.&lt;/p&gt;

Instructions a CPU runs speculatively or out of order and then discards architecturally, so they never affect the program&apos;s official result -- but whose microarchitectural traces, chiefly cache state, survive and leak. Spectre and Meltdown proved that constant-time source code can be defeated by what the silicon does beneath it.
&lt;p&gt;This was the qualitative break. Constant-time is a property of the instruction stream the programmer wrote. Speculation executes an instruction stream the programmer did &lt;em&gt;not&lt;/em&gt; write, chosen by a branch predictor the attacker can train. The abstraction did not spring a leak; it was breached from underneath.Minerva (2020) is the miniature of the whole pattern: many ECDSA implementations leak the bit-length of the per-signature nonce during a non-constant-time scalar multiplication. Gather a couple thousand signatures, feed the tiny leaks to a lattice attack, and the long-term key falls out. On an Athena IDProtect smartcard the team needed about 2,100 signatures and recovered a P-256 key in minutes [@minerva-2020].&lt;/p&gt;
&lt;h3&gt;The machine keeps inventing channels&lt;/h3&gt;
&lt;p&gt;If Spectre broke constant-time from below through speculation, the next wave broke it from the side, through physics the instruction set never promised to hold still. In 2022, Yingchen Wang, Riccardo Paccagnella, and colleagues published Hertzbleed: modern x86 dynamic voltage and frequency scaling makes the CPU&apos;s clock frequency depend on the data being processed, so a program&apos;s wall-clock time depends on its data values -- turning a power side channel into a &lt;em&gt;remote timing&lt;/em&gt; channel with no power measurement at all [@hertzbleed-2022]. They extracted a full key from a constant-time implementation of the post-quantum candidate SIKE, remotely.SIKE died twice. Hertzbleed broke its constant-time &lt;em&gt;implementation&lt;/em&gt; in 2022 through frequency scaling [@hertzbleed-2022]. Weeks later, Wouter Castryck and Thomas Decru broke the &lt;em&gt;mathematics&lt;/em&gt; of SIDH outright with a classical key-recovery attack, and SIKE was withdrawn [@castryck-decru-2023]. The math break is out of this article&apos;s implementation scope, but the coincidence is a useful reminder that a primitive can fall from either direction.&lt;/p&gt;
&lt;p&gt;Then, in 2024, came the break that opens this article. Apple&apos;s M-series CPUs contain a Data Memory-dependent Prefetcher (DMP) that inspects values loaded from memory and, when one &quot;looks like&quot; a pointer, dereferences it speculatively to prefetch what it points to [@gofetch-2024]. That behavior is precisely the data-to-address coupling constant-time forbids. By crafting chosen inputs so that an intermediate value looks like a pointer only when a guess about a key bit is correct, the GoFetch team achieved end-to-end key extraction from constant-time OpenSSL Diffie-Hellman, Go RSA, and -- the part that should worry every protocol designer -- the post-quantum CRYSTALS-Kyber and CRYSTALS-Dilithium.&lt;/p&gt;

sequenceDiagram
    participant A as Attacker
    participant M as Victim memory
    participant DMP as Data-memory prefetcher
    participant Cache as Cache state
    A-&amp;gt;&amp;gt;M: craft input so a value looks pointer-like only when the key-bit guess is right
    M-&amp;gt;&amp;gt;DMP: value flows through a constant-time load
    DMP-&amp;gt;&amp;gt;DMP: value resembles a pointer, so dereference it
    DMP-&amp;gt;&amp;gt;Cache: prefetch pulls the pointed-to line into the cache
    A-&amp;gt;&amp;gt;Cache: time the reload to confirm the guess
    Note over A,Cache: data influenced an address, the exact coupling constant-time forbids
&lt;p&gt;The same year, KyberSlash showed the discipline is unfinished in a humbler way. Several Kyber implementations contained a single line that divides a &lt;em&gt;secret&lt;/em&gt; numerator by a &lt;em&gt;public&lt;/em&gt; denominator; because integer division takes a data-dependent number of cycles on many CPUs, that one statement leaks ML-KEM secret bits into timing, sometimes exploitably [@kyberslash-2024]. Not an exotic prefetcher -- just a division, in a freshly standardized algorithm, that no one flagged.&lt;/p&gt;
&lt;h3&gt;The fault lineage, in parallel&lt;/h3&gt;
&lt;p&gt;Running alongside the leakage story the whole time was the fault story, and it too grew more remote. Classical fault injection meant physically glitching a chip with voltage, clock, electromagnetic, or laser pulses. In 2014, Rowhammer changed the threat model: repeatedly activating one DRAM row flips bits in physically adjacent rows &lt;em&gt;without accessing them&lt;/em&gt;, a hardware fault triggered entirely in software [@rowhammer-2014]. Plundervolt then showed you could undervolt an Intel CPU from software to induce faults inside an SGX enclave, corrupting cryptographic computations from the operating system [@plundervolt-2019]. The fault, like the leak, had escaped the need for physical possession.&lt;/p&gt;
&lt;h3&gt;One lens for the whole catalog&lt;/h3&gt;
&lt;p&gt;Set every named break side by side and re-read each as exactly one violated invariant. The zoo becomes a table.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Break (year)&lt;/th&gt;
&lt;th&gt;Surface / channel&lt;/th&gt;
&lt;th&gt;Root cause&lt;/th&gt;
&lt;th&gt;Violated invariant&lt;/th&gt;
&lt;th&gt;Lesson&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Kocher timing (1996) [@kocher-1996]&lt;/td&gt;
&lt;td&gt;Timing&lt;/td&gt;
&lt;td&gt;Secret-dependent modular-exponentiation time&lt;/td&gt;
&lt;td&gt;secret touched time&lt;/td&gt;
&lt;td&gt;time is an unmodeled output; blind the operand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bellcore RSA-CRT (1997) [@bellcore-1997]&lt;/td&gt;
&lt;td&gt;Fault&lt;/td&gt;
&lt;td&gt;One faulty CRT signature, then a single GCD factors n&lt;/td&gt;
&lt;td&gt;fault skipped a check&lt;/td&gt;
&lt;td&gt;verify before output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DFA (1997) [@biham-shamir-1997]&lt;/td&gt;
&lt;td&gt;Fault&lt;/td&gt;
&lt;td&gt;Fault plus differencing recovers the DES key&lt;/td&gt;
&lt;td&gt;fault skipped a check&lt;/td&gt;
&lt;td&gt;redundancy and verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DPA (1999) [@dpa-1999]&lt;/td&gt;
&lt;td&gt;Power&lt;/td&gt;
&lt;td&gt;Data-dependent power draw over many traces&lt;/td&gt;
&lt;td&gt;secret touched power&lt;/td&gt;
&lt;td&gt;masking and hiding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brumley-Boneh (2003) [@brumley-boneh-2003]&lt;/td&gt;
&lt;td&gt;Remote timing&lt;/td&gt;
&lt;td&gt;OpenSSL RSA decryption timing over a network&lt;/td&gt;
&lt;td&gt;secret touched time, remotely&lt;/td&gt;
&lt;td&gt;blinding on by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bernstein AES cache (2005) [@bernstein-2005]&lt;/td&gt;
&lt;td&gt;Cache&lt;/td&gt;
&lt;td&gt;Secret-indexed T-table lookups&lt;/td&gt;
&lt;td&gt;secret touched an address&lt;/td&gt;
&lt;td&gt;constant-time, AES-NI, bitslicing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Percival (2005) [@percival-2005]&lt;/td&gt;
&lt;td&gt;Cache under SMT&lt;/td&gt;
&lt;td&gt;Shared L1 under Hyper-Threading&lt;/td&gt;
&lt;td&gt;secret touched an address&lt;/td&gt;
&lt;td&gt;co-residency threat model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lucky Thirteen (2013) [@lucky13-2013]&lt;/td&gt;
&lt;td&gt;Timing&lt;/td&gt;
&lt;td&gt;Non-constant-time MAC-and-pad check in TLS CBC&lt;/td&gt;
&lt;td&gt;secret touched time&lt;/td&gt;
&lt;td&gt;constant-time MAC verification, a spec fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flush+Reload (2014) [@flush-reload-2014]&lt;/td&gt;
&lt;td&gt;Cache&lt;/td&gt;
&lt;td&gt;Flush plus reload on shared last-level cache&lt;/td&gt;
&lt;td&gt;secret touched an address&lt;/td&gt;
&lt;td&gt;high-resolution shared-cache primitive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rowhammer (2014) [@rowhammer-2014]&lt;/td&gt;
&lt;td&gt;Fault, software-triggered&lt;/td&gt;
&lt;td&gt;DRAM disturbance flips adjacent-row bits&lt;/td&gt;
&lt;td&gt;fault skipped a check, no physical access&lt;/td&gt;
&lt;td&gt;software can induce hardware faults&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meltdown and Spectre (2018) [@meltdown-2018][@spectre-2019]&lt;/td&gt;
&lt;td&gt;Transient execution&lt;/td&gt;
&lt;td&gt;Speculation runs secret-dependent paths, leaks via cache&lt;/td&gt;
&lt;td&gt;secret touched an address below the ISA&lt;/td&gt;
&lt;td&gt;constant-time broken beneath the ISA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minerva (2020) [@minerva-2020]&lt;/td&gt;
&lt;td&gt;Timing&lt;/td&gt;
&lt;td&gt;Non-constant-time ECDSA nonce bit-length plus lattice&lt;/td&gt;
&lt;td&gt;secret touched time&lt;/td&gt;
&lt;td&gt;constant-time scalar multiplication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPM-Fail (2020) [@tpm-fail-2020]&lt;/td&gt;
&lt;td&gt;Timing&lt;/td&gt;
&lt;td&gt;Secret-dependent ECDSA time in a certified TPM&lt;/td&gt;
&lt;td&gt;secret touched time&lt;/td&gt;
&lt;td&gt;certified is not side-channel-safe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hertzbleed (2022) [@hertzbleed-2022]&lt;/td&gt;
&lt;td&gt;Power to remote timing via DVFS&lt;/td&gt;
&lt;td&gt;Frequency scaling makes wall-time data-dependent&lt;/td&gt;
&lt;td&gt;secret touched time, remotely&lt;/td&gt;
&lt;td&gt;power channels can go remote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GoFetch (2024) [@gofetch-2024]&lt;/td&gt;
&lt;td&gt;Cache via DMP prefetcher&lt;/td&gt;
&lt;td&gt;Prefetcher dereferences pointer-like data&lt;/td&gt;
&lt;td&gt;secret touched an address below the ISA&lt;/td&gt;
&lt;td&gt;constant-time unfinished, reaches PQC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KyberSlash (2024) [@kyberslash-2024]&lt;/td&gt;
&lt;td&gt;Timing via division&lt;/td&gt;
&lt;td&gt;Secret numerator over public denominator, variable-time divide&lt;/td&gt;
&lt;td&gt;secret touched time&lt;/td&gt;
&lt;td&gt;constant-time incompletely applied to ML-KEM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storm-0558 (2023) [@csrb-storm0558]&lt;/td&gt;
&lt;td&gt;Operational key custody&lt;/td&gt;
&lt;td&gt;Unrotated, software-resident 2016 signing key&lt;/td&gt;
&lt;td&gt;a key outlived and escaped its custody&lt;/td&gt;
&lt;td&gt;custody, not math&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; Every row above reduces to one of four sentences. A secret touched &lt;em&gt;time&lt;/em&gt;. A secret touched an &lt;em&gt;address&lt;/em&gt;. A &lt;em&gt;fault&lt;/em&gt; skipped a check. A &lt;em&gt;key&lt;/em&gt; outlived or escaped its custody. This is not a mnemonic; it is the map. Each invariant names both the break and the defense that closes it, and the last one -- custody -- is where the whole article is heading.&lt;/p&gt;
&lt;/blockquote&gt;

You can only close the channels you model, and the machine keeps inventing new ones.
&lt;p&gt;Read the catalog through those four invariants and the pattern snaps into focus. Three of the four -- time, address, fault -- are the exotic surfaces, and they share one defense strategy. The fourth is different in kind, and it is where deployed systems actually break. That split is the breakthrough.&lt;/p&gt;
&lt;h2&gt;5. Two Breakthroughs That Arrive Together&lt;/h2&gt;
&lt;p&gt;After a decade of one-patch-per-channel, the field found the single rule that closes an entire family of channels at the source. And in almost the same breath, it learned the rule was necessary but not sufficient -- which forced a second, operational breakthrough to sit beside the first. The two arrive together because each is the other&apos;s admission of limits.&lt;/p&gt;
&lt;h3&gt;The software breakthrough: constant-time as a discipline&lt;/h3&gt;
&lt;p&gt;State it precisely, because the precision is the whole point. Constant-time code has &lt;em&gt;no secret-dependent branches, no secret-dependent memory indices, and no data-dependent variable-time instructions&lt;/em&gt;. That is it. Crucially -- and this is the single most misunderstood idea in the field -- constant-time does not mean &lt;em&gt;fixed-duration&lt;/em&gt; execution. It means &lt;em&gt;data-independent&lt;/em&gt; execution: the same instruction stream and the same sequence of memory addresses regardless of the secret&apos;s value. A routine can finish in wildly different absolute times on different inputs and still be constant-time, so long as the variation never depends on a secret.&lt;/p&gt;

Code whose instruction sequence and memory-address trace do not depend on secret values. It is NOT fixed-duration execution. The rule has three clauses: no branch on a secret, no memory index computed from a secret, and no variable-latency instruction (early-exit compare, data-dependent division) applied to a secret.
&lt;p&gt;In practice the discipline replaces three forbidden patterns. A secret-dependent &lt;code&gt;if&lt;/code&gt; becomes a branchless select computed with masks and arithmetic. A secret-indexed table lookup, &lt;code&gt;table[secret]&lt;/code&gt;, becomes a full scan of the table with a constant-time select, or is dissolved entirely by &lt;em&gt;bitslicing&lt;/em&gt; the algorithm into boolean operations that touch no data-dependent address [@bernstein-2005]. A variable-time operation on a secret -- an early-exit &lt;code&gt;memcmp&lt;/code&gt; on a MAC, a division with a secret numerator -- is banned outright. Whole primitives were redesigned to make the discipline natural: &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;Curve25519&lt;/a&gt;&apos;s Montgomery ladder runs the identical steps for every bit of the scalar, so there is no secret-dependent path left to leak.&lt;/p&gt;
&lt;p&gt;The comparison case is worth seeing in code, because it is the exact mistake behind Lucky Thirteen and a hundred MAC-verification bugs.&lt;/p&gt;
&lt;p&gt;{`
// Early-exit compare: returns as soon as two bytes differ.
// The iteration count reveals how long a prefix matched -- a timing leak.
function earlyExitEqual(a, b) {
  if (a.length !== b.length) return false;
  for (let i = 0; i &amp;lt; a.length; i++) {
    if (a[i] !== b[i]) return false;   // stops at the first difference
  }
  return true;
}&lt;/p&gt;
&lt;p&gt;// Constant-time compare: always scans every byte, accumulating differences.
// Running time depends only on length, never on the secret contents.
function constantTimeEqual(a, b) {
  if (a.length !== b.length) return false;
  let diff = 0;
  for (let i = 0; i &amp;lt; a.length; i++) {
    diff |= a[i] ^ b[i];               // no early exit, no branch on the data
  }
  return diff === 0;
}&lt;/p&gt;
&lt;p&gt;const tag      = [1, 2, 3, 4, 5, 6, 7, 8];
const nearMiss = [1, 2, 3, 4, 5, 6, 7, 9]; // matches 7 of 8 bytes
const farMiss  = [9, 2, 3, 4, 5, 6, 7, 8]; // differs at the very first byte
// earlyExitEqual iterates 8 times for nearMiss but only once for farMiss:
// that difference is the leak an attacker times to forge a tag byte by byte.
console.log(&apos;constant-time, near miss:&apos;, constantTimeEqual(tag, nearMiss));
console.log(&apos;constant-time, far miss :&apos;, constantTimeEqual(tag, farMiss));
`}&lt;/p&gt;
&lt;p&gt;The other half of the breakthrough is that the property became &lt;em&gt;checkable&lt;/em&gt;, and even &lt;em&gt;provable&lt;/em&gt;. Oscar Reparaz and colleagues built dudect, which runs a routine on fixed-versus-random secret inputs and applies a Welch t-test: a stable t-statistic past the usual threshold flags a data-dependent timing leak, while a genuinely constant-time routine stays flat [@dudect-2017]. On the formal side, ct-verif encodes constant-timeness as a property over pairs of executions differing only in secrets and discharges it mechanically [@ct-verif-2016]. Constant-time stopped being a matter of the author&apos;s vigilance and became something a machine could test in CI or prove outright.&lt;/p&gt;
&lt;p&gt;For the physical surface -- power, electromagnetic, glitch -- the corresponding discipline is masking, and it too became principled rather than ad hoc.&lt;/p&gt;

Masking splits every secret intermediate into $d+1$ random shares so that any $d$ probed values are statistically independent of the secret; the computation runs on shares. Threshold implementations add non-completeness and uniformity so the construction stays secure even under hardware glitches, which transiently leak the unmasked value [@threshold-impl-2006].
&lt;p&gt;But masking closes only the physical channel, and constant-time closes only the modeled ISA-level channels. Spectre, Hertzbleed, GoFetch, and KyberSlash all live &lt;em&gt;below&lt;/em&gt; the abstraction the discipline reasons about. You can be perfectly constant-time in the instruction stream you wrote and still leak, because the silicon runs an instruction stream you did not write. That is the wall the software breakthrough hits -- and it is the reason for the second breakthrough.&lt;/p&gt;
&lt;h3&gt;The operational breakthrough: key custody as an architecture&lt;/h3&gt;
&lt;p&gt;If you can never prove the &lt;em&gt;whole machine&lt;/em&gt; leak-free, stop trying to eliminate leakage and instead bound what a leak can cost. That is the pivot. Concretely: put the root key inside a hardware boundary that generates and &lt;em&gt;uses&lt;/em&gt; it without ever exporting it, and reach your data through an envelope.&lt;/p&gt;

A tamper-resistant boundary -- a dedicated appliance, a cloud Managed HSM, a TPM, or an embedded secure element -- that generates and uses keys without releasing them in plaintext. Software sends in a request (sign this, unwrap that) and gets back a result, never the key bytes. FIPS 140-3 validates such modules, including some non-invasive side-channel requirements.

A pattern in which a cheap Data-Encryption Key (DEK) encrypts the bulk data, and a Key-Encryption Key (KEK) that never leaves the HSM or KMS wraps the DEK. The wrapped DEK is stored right beside the ciphertext; only the KMS can unwrap it. Standardized authenticated key wrapping (NIST SP 800-38F) supplies the wrap operation [@nist-sp800-38f].
&lt;p&gt;The economics are what make it deployable. A hardware module is slow and expensive, so you do not run all your data through it -- you run one small wrap and unwrap through it, and let a cheap symmetric DEK do the bulk work. A leaked DEK exposes only the data it wrapped; the KEK, and therefore every other DEK, stays safe inside the boundary. Rotation becomes automatic and its blast radius bounded. This is the move from &quot;make the code not leak&quot; to &quot;make a leak not matter,&quot; and it maps directly onto how NIST SP 800-57 tells you to think about key lifecycles and crypto-periods [@nist-sp800-57].&lt;/p&gt;

flowchart LR
    D[&quot;Plaintext data&quot;] --&amp;gt;|&quot;encrypt with&quot;| DEK[&quot;Data-Encryption Key (DEK)&quot;]
    DEK --&amp;gt;|&quot;wrap with KEK&quot;| WD[&quot;Wrapped DEK, stored beside ciphertext&quot;]
    KEK[&quot;Key-Encryption Key (KEK)&quot;] --&amp;gt; HSM[&quot;Inside HSM or cloud KMS, never exported&quot;]
    WD -.-&amp;gt;|&quot;ask KMS to unwrap&quot;| HSM
    HSM -.-&amp;gt;|&quot;plaintext DEK, policy permitting&quot;| App[&quot;Application decrypts the data&quot;]
&lt;h3&gt;The junction&lt;/h3&gt;
&lt;p&gt;The two breakthroughs are two chains of the same story, and they meet at one move. Chain A drives the code toward not leaking, generation by generation, until it hits the microarchitectural wall. Chain B accepts that wall and shrinks what any residual leak is worth. The thesis of this whole series is the junction of the two: the math held; Chain A closes the channels you can model, and Chain B bounds the cost of the ones you cannot.&lt;/p&gt;

flowchart TD
    A0[&quot;A0: correctness is enough&quot;] --&amp;gt; A1[&quot;A1: point patches, blinding, verify, masking&quot;]
    A1 --&amp;gt; A2[&quot;A2: constant-time discipline&quot;]
    A2 --&amp;gt; A3[&quot;A3: verified plus hardware-assisted constant-time&quot;]
    B0[&quot;B0: keys as data, hardcoded or in config&quot;] --&amp;gt; B1[&quot;B1: OS or software keystore&quot;]
    B1 --&amp;gt; B2[&quot;B2: TPM, HSM, secure element&quot;]
    B2 --&amp;gt; B3[&quot;B3: cloud KMS, envelope, automatic rotation&quot;]
    A3 --&amp;gt; T[&quot;Thesis: the math held. Close the modeled channels, bound the cost of the rest&quot;]
    B3 --&amp;gt; T
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Defense evolves from &quot;make the code not leak&quot; to &quot;make a leak not matter.&quot; The first is Chain A and it is necessary. The second is Chain B and it is what saves you when Chain A inevitably has a gap the machine invented last week.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Both breakthroughs are engineering realities you can deploy today: verified constant-time libraries on one side, cloud KMS with envelope encryption and automatic rotation on the other. So what does the 2024 to 2026 state of the art actually look like when you ship both at once -- and where is it still losing?&lt;/p&gt;
&lt;h2&gt;6. The State of the Art, 2024 to 2026&lt;/h2&gt;
&lt;p&gt;There is no single best implementation. There are two parallel frontiers, and the 2024-to-2026 practitioner ships both at once. Call them Chain A, leakage defense, and Chain B, key custody.&lt;/p&gt;
&lt;h3&gt;Chain A: the leakage-defense frontier&lt;/h3&gt;
&lt;p&gt;At the base sits the constant-time-by-construction library. libsodium, Google&apos;s BoringSSL, Thomas Pornin&apos;s BearSSL, and Rust&apos;s ring obey the three-clause rule everywhere a secret is touched. This is table stakes now, not a differentiator -- if a general-purpose crypto library is not constant-time by default in 2026, it is not a serious library.&lt;/p&gt;
&lt;p&gt;Above that sits the highest-assurance floor: &lt;em&gt;formally verified&lt;/em&gt; constant-time crypto, where a machine-checked proof replaces human review.&lt;/p&gt;

Cryptographic code whose secret-independence is established by a machine-checked mathematical proof, not by review or testing. HACL* and EverCrypt (written in a subset of F*) and the Jasmin and libjade toolchain (with EasyCrypt) are the two production lineages; the proof itself is the constant-time argument, discharged once and re-checked automatically [@hacl-star].
&lt;p&gt;This is not a laboratory curiosity. HACL* ships verified Curve25519 in Mozilla Firefox through NSS, and a formally verified Curve25519 rode into the Linux kernel alongside WireGuard; verified SHA-3 and HMAC from the same project ship inside CPython [@hacl-star]. Proof-carrying crypto is in the browser and the operating system you are reading this on.&lt;/p&gt;
&lt;p&gt;Verifying and testing the property is its own layered toolchain, and no single tool dominates. A 2023 systematic evaluation quantified the trade-offs across real libraries [@sidetool-eval-2023]; the shape of the field is a ladder from cheap-and-empirical to expensive-and-proven.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;Level analyzed&lt;/th&gt;
&lt;th&gt;Guarantee&lt;/th&gt;
&lt;th&gt;Catches compiler-introduced leaks&lt;/th&gt;
&lt;th&gt;Effort&lt;/th&gt;
&lt;th&gt;Microarchitecture&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;dudect [@dudect-2017]&lt;/td&gt;
&lt;td&gt;Welch t-test on timing&lt;/td&gt;
&lt;td&gt;Black-box, runtime&lt;/td&gt;
&lt;td&gt;Empirical, tested paths&lt;/td&gt;
&lt;td&gt;Only if exercised&lt;/td&gt;
&lt;td&gt;Very low, CI drop-in&lt;/td&gt;
&lt;td&gt;Not modeled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ctgrind / TIMECOP&lt;/td&gt;
&lt;td&gt;Dynamic taint over Valgrind&lt;/td&gt;
&lt;td&gt;Binary, dynamic&lt;/td&gt;
&lt;td&gt;Empirical, executed paths&lt;/td&gt;
&lt;td&gt;Yes, when observed&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Not modeled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ct-verif [@ct-verif-2016]&lt;/td&gt;
&lt;td&gt;Formal self-composition&lt;/td&gt;
&lt;td&gt;LLVM IR&lt;/td&gt;
&lt;td&gt;All paths at the IR&lt;/td&gt;
&lt;td&gt;No, runs before compilation&lt;/td&gt;
&lt;td&gt;Medium, needs annotations&lt;/td&gt;
&lt;td&gt;Not modeled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binsec/Rel [@binsec-rel]&lt;/td&gt;
&lt;td&gt;Relational symbolic execution&lt;/td&gt;
&lt;td&gt;Final binary&lt;/td&gt;
&lt;td&gt;All paths, bounded depth&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;High, setup and path explosion&lt;/td&gt;
&lt;td&gt;Not modeled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HACL* / Jasmin [@hacl-star]&lt;/td&gt;
&lt;td&gt;Proof in F* or EasyCrypt&lt;/td&gt;
&lt;td&gt;Source down to C or assembly&lt;/td&gt;
&lt;td&gt;Whole-program theorem&lt;/td&gt;
&lt;td&gt;Verified assembly, yes&lt;/td&gt;
&lt;td&gt;Very high, proof engineering&lt;/td&gt;
&lt;td&gt;Not modeled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Read the last column: every tool shares the same blind spot. None models the microarchitecture, because none can -- a limit we return to in Section 8. The empirical tools like dudect are the ones you actually gate CI with, and the Welch t-test at their heart is simple enough to see in a few lines.&lt;/p&gt;
&lt;p&gt;{&lt;code&gt;// Simulate timing samples for two input classes: a &quot;fixed&quot; secret and &quot;random&quot; secrets. // A variable-time operation reacts differently to the two classes; a constant-time one does not. function mean(xs) { return xs.reduce((s, x) =&amp;gt; s + x, 0) / xs.length; } function variance(xs, m) {   return xs.reduce((s, x) =&amp;gt; s + (x - m) * (x - m), 0) / (xs.length - 1); } // Welch t-statistic for two samples with unequal variance. function welchT(a, b) {   const ma = mean(a), mb = mean(b);   const va = variance(a, ma), vb = variance(b, mb);   return (ma - mb) / Math.sqrt(va / a.length + vb / b.length); } function samples(n, base, classShift) {   const out = [];   for (let i = 0; i &amp;lt; n; i++) out.push(base + Math.random() * 5 + classShift);   return out; } const N = 5000; // Variable-time op: the random class runs a few units longer on average, a real leak. const leakFixed = samples(N, 100, 0), leakRandom = samples(N, 100, 8); // Constant-time op: both classes share one distribution, no leak. const ctFixed = samples(N, 100, 0), ctRandom = samples(N, 100, 0); console.log(&apos;variable-time absolute t:&apos;, Math.abs(welchT(leakFixed, leakRandom)).toFixed(1), &apos;past 4.5 threshold, flagged&apos;); console.log(&apos;constant-time absolute t:&apos;, Math.abs(welchT(ctFixed, ctRandom)).toFixed(1), &apos;below 4.5, clean&apos;);&lt;/code&gt;}&lt;/p&gt;
&lt;p&gt;Beneath the software sits hardware backing. Intel&apos;s Data Operand Independent Timing Mode (DOITM) and Arm&apos;s Data Independent Timing (DIT) bit promise that an enumerated set of instructions runs with data-independent latency, re-establishing at the silicon level the guarantee the source could only assert [@intel-doitm][@arm-dit]. But their coverage is exactly their limit. The lists are vendor-specific and enumerated, and they pointedly exclude integer and floating-point division and square root -- which is why KyberSlash&apos;s secret division leaks -- and they do not model the prefetcher or frequency scaling, which is why GoFetch and Hertzbleed operate outside their guarantee entirely [@kyberslash-2024][@gofetch-2024].&lt;/p&gt;
&lt;p&gt;For the physical surface -- power, electromagnetic, glitch -- masking and threshold implementations plus on-die fault sensors remain the answer, and the live research frontier is masking the post-quantum algorithms at practical cost.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Reach for a verified or constant-time-first library -- libsodium, BoringSSL, BearSSL, ring, or the verified HACL* and libcrux -- and gate it in CI with dudect or TIMECOP [@hacl-star][@dudect-2017]. Home-rolled constant-time code is wrong until a tool says otherwise, and even expert-written code needs re-checking after every compiler upgrade, because optimizers reintroduce the branches you removed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Chain B: the key-custody frontier&lt;/h3&gt;
&lt;p&gt;The operational state of the art is cloud KMS plus envelope encryption plus automatic rotation. AWS KMS, Azure Key Vault and Managed HSM, and Google Cloud KMS all keep a small number of Key-Encryption Keys inside a hardware boundary and wrap an unbounded number of cheap Data-Encryption Keys under them, using the authenticated key-wrapping of NIST SP 800-38F and the lifecycle discipline of NIST SP 800-57 [@nist-sp800-38f][@nist-sp800-57].AWS puts a concrete number on the crypto-period: keys that AWS manages rotate on a required schedule of every year, approximately 365 days, and customer-managed keys support optional automatic rotation on a period you choose [@aws-kms]. Rotation is O(1) to request; the hard part is re-wrapping and inventorying what each key protects.&lt;/p&gt;
&lt;p&gt;For root keys -- certificate authorities, code-signing, payment keys -- the anchor is a FIPS 140-3 validated module.&lt;/p&gt;

The current U.S. standard for validating cryptographic modules, superseding FIPS 140-2. Unlike its predecessor&apos;s reputation, FIPS 140-3 explicitly addresses non-invasive security -- that is, side-channel resistance -- among its requirements, across four ascending security levels with increasing physical tamper response [@fips-140-3].
&lt;p&gt;The third pillar is the least technical and the most often fatal: secrets management and non-human-identity hygiene. Application secrets -- API keys, database credentials, tokens, signing keys -- must stay out of code, config files, container images, and CI logs. OWASP made cryptographic-usage and keying failures the number-two web-application risk in 2021, and the 2025 OWASP Non-Human Identities Top 10 put improper offboarding (NHI1) and secret leakage (NHI2) at the very top of the list, with long-lived secrets also named (NHI7) [@owasp-a02-2021][@owasp-nhi-2025]. The tooling exists -- secrets managers that issue short-lived attested credentials, CI secret-scanning, workload federation -- but it demands organizational discipline no library can supply.&lt;/p&gt;
&lt;h3&gt;The concrete arc: Storm-0558 to Managed HSM&lt;/h3&gt;
&lt;p&gt;The clearest picture of Chain B&apos;s state of the art is the failure that forced it and the remedy that answered it. In 2023, the threat actor Storm-0558 forged Microsoft consumer authentication tokens using a 2016 signing key that was software-resident, held outside any HSM, and had never been rotated; a token-validation flaw let that consumer key mint enterprise tokens, and the actor downloaded roughly 60,000 State Department emails (about ten accounts), part of an intrusion that reached around two dozen organizations [@csrb-storm0558][@msrc-storm0558]. The U.S. Cyber Safety Review Board judged the intrusion preventable. No cipher was broken. A key simply outlived and escaped its custody.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s remedy, under its Secure Future Initiative, is the Chain B playbook made concrete: the MSA and Entra ID signing keys moved into Azure Managed HSM with automatic rotation, and the signing service itself moved into confidential-computing VMs [@microsoft-sfi-2024]. Put the root key in hardware, use it without exporting it, rotate it automatically. That is the entire prescription, and it is exactly what was missing in 2016.&lt;/p&gt;
&lt;p&gt;The honest caveat closes the section. Hardware custody is not a magic floor. A 2025 taxonomy catalogs how cloud HSM and TPM custody actually fails in practice, and TPM-Fail already showed that certified modules leak keys through timing [@hsm-tpm-failures-2025][@tpm-fail-2020]. Hardware moves the trust boundary; it does not remove it.&lt;/p&gt;

A few commands turn the abstract advice into an audit. Confirm a KMS key actually rotates with `aws kms get-key-rotation-status --key-id KEY_ID`. List which principals can call decrypt on it by reading the key policy with `aws kms get-key-policy --key-id KEY_ID --policy-name default`. Scan a repository&apos;s history for committed secrets with `gitleaks detect --source .` or `trufflehog filesystem .`. Gate a C library&apos;s tag comparison in CI by building it under `valgrind` with the TIMECOP macros, or by wiring a dudect harness around the routine. None of these is exotic; all of them catch the mistakes that end real incidents.
&lt;p&gt;You now know the tools on both frontiers. The hard part is the two decisions nobody hands you: where do you put the defense, and where do you put the key?&lt;/p&gt;
&lt;h2&gt;7. Where Do You Put the Defense, and Where the Key?&lt;/h2&gt;
&lt;p&gt;Every choice in this field trades assurance against cost, portability, and threat model. Two decision axes organize the whole thing, and the good news is that within each axis the options mostly &lt;em&gt;compose&lt;/em&gt; rather than compete.&lt;/p&gt;
&lt;h3&gt;Axis one: defense placement&lt;/h3&gt;
&lt;p&gt;The four leakage defenses are not rivals fighting for one slot. They stack. Ship verified constant-time as the floor, enable hardware data-independent-timing as backing, and add masking only when the adversary can physically hold and instrument the device.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Assurance&lt;/th&gt;
&lt;th&gt;Runtime cost&lt;/th&gt;
&lt;th&gt;Portability&lt;/th&gt;
&lt;th&gt;Defends against&lt;/th&gt;
&lt;th&gt;Blind to&lt;/th&gt;
&lt;th&gt;Best suited for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Software constant-time&lt;/td&gt;
&lt;td&gt;Review-level&lt;/td&gt;
&lt;td&gt;Near-constant factor&lt;/td&gt;
&lt;td&gt;High, any CPU&lt;/td&gt;
&lt;td&gt;Timing and cache&lt;/td&gt;
&lt;td&gt;Microarchitecture: Spectre, DVFS, DMP&lt;/td&gt;
&lt;td&gt;Every deployment, the floor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verified constant-time [@hacl-star]&lt;/td&gt;
&lt;td&gt;Machine-checked proof&lt;/td&gt;
&lt;td&gt;Same as software&lt;/td&gt;
&lt;td&gt;High, generated C&lt;/td&gt;
&lt;td&gt;Timing and cache, proven&lt;/td&gt;
&lt;td&gt;Below the verified boundary; microarchitecture&lt;/td&gt;
&lt;td&gt;Long-lived high-assurance primitives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware DIT / DOITM [@intel-doitm][@arm-dit]&lt;/td&gt;
&lt;td&gt;Vendor guarantee, listed instructions&lt;/td&gt;
&lt;td&gt;Near zero for covered instructions&lt;/td&gt;
&lt;td&gt;Low, vendor-specific&lt;/td&gt;
&lt;td&gt;Operand timing of listed instructions&lt;/td&gt;
&lt;td&gt;Prefetcher, frequency scaling, division&lt;/td&gt;
&lt;td&gt;Defense-in-depth beneath software&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Masking / threshold [@isw-2003]&lt;/td&gt;
&lt;td&gt;Provable order-d in the probing model&lt;/td&gt;
&lt;td&gt;Quadratic in the order&lt;/td&gt;
&lt;td&gt;Hardware-oriented, costly in software&lt;/td&gt;
&lt;td&gt;Power, EM, glitch faults&lt;/td&gt;
&lt;td&gt;Higher-order analysis; cost&lt;/td&gt;
&lt;td&gt;Physical-possession adversary&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The table&apos;s lesson is the composition, not any single winner. Each defense covers what the one above it cannot; masking, in particular, is the only thing that helps once an attacker owns the silicon, and you pay for it by the square of the order. None of them closes the microarchitectural channels alone -- which is the structural reason Chain B has to exist.&lt;/p&gt;
&lt;h3&gt;Axis two: the key-custody spectrum&lt;/h3&gt;
&lt;p&gt;Custody is a ladder, and the single most useful skill in this whole article is matching the rung to the adversary and the asset value.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rung&lt;/th&gt;
&lt;th&gt;Key location&lt;/th&gt;
&lt;th&gt;Exportable?&lt;/th&gt;
&lt;th&gt;Blast radius on host compromise&lt;/th&gt;
&lt;th&gt;Cost / latency&lt;/th&gt;
&lt;th&gt;Use when&lt;/th&gt;
&lt;th&gt;Anchor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Hardcoded, config, env var, repo&lt;/td&gt;
&lt;td&gt;Yes, in plaintext&lt;/td&gt;
&lt;td&gt;Total, the key is the leak&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Never, an anti-pattern&lt;/td&gt;
&lt;td&gt;Storm-0558 [@csrb-storm0558]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;OS or software keystore&lt;/td&gt;
&lt;td&gt;Yes, to root or admin&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low-value single-host secrets&lt;/td&gt;
&lt;td&gt;OWASP A02 [@owasp-a02-2021]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;TPM or secure element&lt;/td&gt;
&lt;td&gt;No, use without export&lt;/td&gt;
&lt;td&gt;Low, but see TPM-Fail&lt;/td&gt;
&lt;td&gt;Low to medium&lt;/td&gt;
&lt;td&gt;Device-bound keys, measured boot&lt;/td&gt;
&lt;td&gt;TPM-Fail [@tpm-fail-2020]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Cloud KMS plus envelope&lt;/td&gt;
&lt;td&gt;KEK no, DEK wrapped&lt;/td&gt;
&lt;td&gt;Bounded to unwrapped DEKs&lt;/td&gt;
&lt;td&gt;One KMS round-trip per DEK&lt;/td&gt;
&lt;td&gt;Data at rest at scale&lt;/td&gt;
&lt;td&gt;AWS KMS [@aws-kms]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Dedicated or Managed HSM&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;td&gt;Higher&lt;/td&gt;
&lt;td&gt;Root, CA, code-signing, payment keys&lt;/td&gt;
&lt;td&gt;FIPS 140-3 [@fips-140-3]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Threshold or MPC custody&lt;/td&gt;
&lt;td&gt;No single holder&lt;/td&gt;
&lt;td&gt;Requires several compromises&lt;/td&gt;
&lt;td&gt;Highest, protocol rounds&lt;/td&gt;
&lt;td&gt;No single point of trust, see Part 22&lt;/td&gt;
&lt;td&gt;NIST SP 800-57 [@nist-sp800-57]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Here is the capstone&apos;s punchline, sitting in one table. The exotic Chain-A attacks -- a timing leak against a certified TPM at rung 2, say -- are real but rare. The overwhelming majority of real-world breaks cluster at rungs 0 and 1: keys hardcoded, sprawled across config and CI logs, software-resident, and never rotated. Storm-0558 was a rung-0 failure with a nation-state on the other end. The state-of-the-art move for almost everyone is simply to climb to rung 3 or 4 with automatic rotation, and to accept the custody regress that waits at the top.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A key in source, in a config file, in an environment variable baked into an image, or in a repository is not &quot;low assurance.&quot; It is the leak, waiting for any code, log, or memory disclosure to complete it. There is no asset value low enough to justify rung 0. Move to a secrets manager and rotate.&lt;/p&gt;
&lt;/blockquote&gt;

It is tempting to read a Common Criteria or FIPS certificate as &quot;side-channel-proof.&quot; It is not. TPM-Fail recovered 256-bit ECDSA keys from an Intel firmware TPM and from a Common Criteria EAL4+ and FIPS 140-2 Level 2 certified STMicroelectronics chip, in minutes locally and about five hours across a network [@tpm-fail-2020]. Minerva broke FIPS-certified smartcards from a couple thousand signatures [@minerva-2020]. Certification validates a *modeled* threat surface against a defined protection profile; it bounds process, not physics. When the modeled surface omits the channel the attacker uses, the certificate is silent.

&quot;Certification has failed to protect the product against an attack that is considered by the protection profile.&quot; -- Moghimi et al., the TPM-Fail disclosure [@tpm-fail-2020]
&lt;p&gt;These tables tell you what to reach for. But can leakage ever be &lt;em&gt;eliminated&lt;/em&gt;, or are we only ever bounding its cost? The honest answer is uncomfortable, and it comes in two parts.&lt;/p&gt;
&lt;h2&gt;8. Can Leakage Be Eliminated?&lt;/h2&gt;
&lt;p&gt;The field owes you two very different answers. One is a clean, provable bound. The other is an honest impossibility that is not a theorem at all but a structural fact about how computers are specified.&lt;/p&gt;
&lt;h3&gt;The masking cost curve is a real bound, and it is not a wall&lt;/h3&gt;
&lt;p&gt;For the physical channel, the mathematics is unusually crisp. In the Ishai-Sahai-Wagner probing model, protecting a secret against an adversary who can observe up to $d$ internal wires requires at least $d+1$ random shares, and that bound is tight -- you cannot buy order-$d$ security with fewer [@isw-2003]. Securely masking a single multiplication in that model costs $O(d^2)$ in circuit size and randomness. So security grows linearly in the order while cost grows quadratically: doubling your resistance roughly quadruples the work.&lt;/p&gt;
&lt;p&gt;Under a physically realistic noisy-leakage model, the picture gets better in one specific way. Duc, Dziembowski, and Faust proved that masking&apos;s security amplifies &lt;em&gt;exponentially&lt;/em&gt; in the number of shares, provided each share carries enough noise, by reducing noisy leakage back to the clean probing model [@ddf-2014]. Put the two results together and you get the defining shape of the whole physical-defense story: leakage is a &lt;em&gt;cost curve, not a wall&lt;/em&gt;. You can push the attacker&apos;s required number of traces arbitrarily high by paying $O(d^2)$ and ensuring noise. You can never push it to zero.&lt;/p&gt;
&lt;h3&gt;Whole-machine constant-time is an impossibility, not a theorem&lt;/h3&gt;
&lt;p&gt;For the timing and cache channels, there is no comparable happy bound, and the reason is almost embarrassingly simple. &lt;em&gt;Constant-time cannot be guaranteed at the microarchitectural level because the instruction set architecture does not specify timing.&lt;/em&gt; A constant-time proof is a statement about ISA-visible behavior. The silicon underneath is free to introduce data-dependence the ISA never promised to forbid, and it repeatedly does.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The ISA does not specify timing, so a constant-time proof only constrains architecture-visible behavior. You can only close the channels you model, and the set of channels is open-ended and adversary-discovered. Speculation, frequency scaling, and data-memory-dependent prefetching each reopened it after the proof was done.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is why Spectre, Hertzbleed, GoFetch, and KyberSlash are not a series of fixable bugs but a series of demonstrations of the same structural gap. Each exploited a microarchitectural behavior -- speculation, DVFS, a prefetcher, variable-time division -- that no source-level or even ISA-level guarantee covered. There is no proof that a real machine leaks nothing through timing, and, because the ISA is silent on timing, none is possible with today&apos;s hardware contracts. The gap is not small and closing. It is structurally open until a complete, cross-vendor data-independent-timing contract exists, which DIT and DOITM only partially provide.&lt;/p&gt;
&lt;h3&gt;The custody regress&lt;/h3&gt;
&lt;p&gt;The operational half has its own impossibility, and it is the reason this article lands on key custody rather than on some final leak-proof coprocessor. Someone must ultimately hold a root secret. Hardware roots -- HSMs, TPMs, Managed HSMs -- &lt;em&gt;move&lt;/em&gt; the trust boundary inward and defend it far better, but they do not &lt;em&gt;remove&lt;/em&gt; it, and that smaller boundary has its own documented failure modes, from TPM-Fail&apos;s timing leak to the 2025 taxonomy of cloud HSM and TPM failures [@tpm-fail-2020][@hsm-tpm-failures-2025]. There is no keyless bottom. There is only a smaller, better-defended custodian, and certification of that custodian is necessary but never sufficient.&lt;/p&gt;

Hardware moves the trust boundary; it does not remove it.
&lt;p&gt;So perfection is off the table on both halves. What replaces it is not despair but a different target.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The honest goal is not zero leakage and not an unbreakable key. It is cost-optimality: make leakage so expensive to exploit, and each secret so short-lived and tightly bounded, that no leak is worth the attacker&apos;s effort. Masking raises the price of a power trace; short key lifetimes shrink the value of any single leaked bit; envelope encryption bounds the blast radius. You are not building a wall. You are pricing the attacker out.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The purely theoretical program of leakage-resilient cryptography pushes on the same questions from the model side, but relative to deployed masking and hardware custody it remains largely academic. If perfection is unreachable, the frontier turns into a list of concrete engineering fights -- and, refreshingly, they are winnable one channel at a time.&lt;/p&gt;
&lt;h2&gt;9. The 2026 Frontier&lt;/h2&gt;
&lt;p&gt;Name the open problems precisely and the field stops looking like whack-a-mole and starts looking like a research agenda. Each of these is calibrated engineering, not undetectable magic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A complete microarchitectural timing contract.&lt;/strong&gt; DIT and DOITM enumerate data-independent instructions, but they are vendor-specific and exclude division, square root, the prefetcher, and frequency scaling [@intel-doitm][@arm-dit]. Every constant-time proof is therefore conditional on an unmodeled microarchitecture, which is exactly the seam GoFetch and Hertzbleed pried open. The open problem is an ISA-level guarantee that extends a constant-time proof &lt;em&gt;below&lt;/em&gt; the ISA, across vendors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Speculative constant-time at scale.&lt;/strong&gt; Spectre-class transient execution breaks architecturally constant-time code from beneath [@spectre-2019]. Retpoline, LFENCE barriers, and Speculative Load Hardening are point defenses with real, often double-digit-percent overhead [@llvm-slh]. A scalable, low-cost compiler that guarantees data-independence even under speculation, for whole libraries, does not yet exist.The community conjecture is that without hardware speculation-taint tracking, a software-only speculative-constant-time pass cannot be both complete and cheap. You either pay in overhead or accept coverage gaps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Whole-library verification through the compiler.&lt;/strong&gt; Compiler lowering reintroduces leaks that IR-level proofs miss, which is the entire motivation for binary-level tools like Binsec/Rel [@binsec-rel]. Today we have per-primitive proofs and per-binary bounded checks; 2024 work such as CT-Prover targets the scalability wall, and benchmarks map the tool terrain [@ct-prover-2024][@sidetool-eval-2023]. A push-button, whole-library, binary-level verifier usable by non-experts remains out of reach -- and usability studies show even experts misconfigure the current tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Constant-time and fault-resistant post-quantum code.&lt;/strong&gt; KyberSlash and GoFetch proved the discipline is unfinished for the freshly standardized lattice schemes [@kyberslash-2024][@gofetch-2024]. Shipping ML-KEM and ML-DSA implementations that are simultaneously constant-time, masked against physical attack, and fast across diverse CPUs -- and proving it -- is an active frontier, because the arithmetic-heavy PQC code has larger, fresher leakage surfaces than classical crypto.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Secretless architectures and minimal key lifetimes.&lt;/strong&gt; The dominant real-world failure surface is long-lived, sprawled, unrotated secrets [@owasp-nhi-2025]. Rotation, workload attestation, and short-lived derived credentials shrink the custody surface, but the custody regress forbids a truly secretless bottom. The open question is quantitative: how small can the irreducible custodian be made?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Post-quantum key management at fleet scale.&lt;/strong&gt; Larger keys, hybrid classical-and-PQ modes, and harvest-now-decrypt-later urgency make the key lifecycle harder precisely as it becomes critical. Zero-downtime migration across a whole estate, with a cryptographic bill of materials for inventory, anchored on the lifecycle discipline of NIST SP 800-57, is an open operational problem handed off to the crypto-agility part of this series [@nist-sp800-57].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every item above is a bounded engineering fight with measurable partial results, not a wish for undetectable magic. That framing matters: it tells you which problems to fund and which &quot;solutions&quot; to distrust. Anyone selling a complete, cheap, whole-machine leak-free implementation today is selling the impossibility from Section 8.&lt;/p&gt;
&lt;/blockquote&gt;

This capstone deliberately defers depth to its siblings. Randomness and RNG failures live in Part 2; padding oracles and the Lucky Thirteen mechanics in Part 6; nonce reuse and the Minerva and PS3 stories in Part 8; RSA-CRT and Bleichenbacher detail in Part 14; scalar-multiplication leakage of the nonce in Part 15; deterministic-versus-hedged signing and its fault risk in Part 17; threshold and MPC custody, the top rung of the spectrum, in Part 22; ML-KEM and ML-DSA internals in Part 23; and rotation-at-fleet-scale, crypto-agility, and the cryptographic bill of materials in Part 24. Read as a set, the twenty-five parts are one argument.
&lt;p&gt;The research agenda is clear. The deployment agenda is clearer still. Here is what to do on Monday.&lt;/p&gt;
&lt;h2&gt;10. What To Do on Monday&lt;/h2&gt;
&lt;p&gt;Everything above collapses into a handful of rules you can apply without a research budget.&lt;/p&gt;
&lt;h3&gt;The constant-time checklist&lt;/h3&gt;
&lt;p&gt;Anything that touches a secret gets three prohibitions: no branch on it, no memory index computed from it, no variable-time operation applied to it -- and &quot;variable-time&quot; specifically includes integer division and early-exit comparison. Test the result with dudect or TIMECOP in CI, prefer a verified library over your own code, and re-check after every toolchain upgrade, because compilers betray branchless code back into branches. That single decision rule fits in a diagram.&lt;/p&gt;

flowchart TD
    S[&quot;A value in your code&quot;] --&amp;gt; Q{&quot;Does it touch a secret?&quot;}
    Q --&amp;gt;|no| OK[&quot;Ordinary code is fine&quot;]
    Q --&amp;gt;|yes| R[&quot;Forbid all three&quot;]
    R --&amp;gt; B[&quot;No branch on it&quot;]
    R --&amp;gt; M[&quot;No memory index from it&quot;]
    R --&amp;gt; V[&quot;No variable-time op: no divide, no early-exit compare&quot;]
&lt;h3&gt;Threat-model routing for leakage defense&lt;/h3&gt;
&lt;p&gt;Match the defense to the adversary. Everyone starts from the same floor and adds as the threat model widens.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Adversary&lt;/th&gt;
&lt;th&gt;Add to the floor&lt;/th&gt;
&lt;th&gt;Because&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Remote network&lt;/td&gt;
&lt;td&gt;Verified or constant-time crypto, constant-time MAC checks, no padding oracles&lt;/td&gt;
&lt;td&gt;This is the floor for everyone; it closes Lucky Thirteen and the timing family&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Co-tenant, shared cache (cloud)&lt;/td&gt;
&lt;td&gt;Cache-hardening, per-tenant isolation, hardware CT where honored&lt;/td&gt;
&lt;td&gt;Prime+Probe and Flush+Reload cross tenant boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Physical possession (smartcard, wallet, IoT)&lt;/td&gt;
&lt;td&gt;Masking or threshold implementations, fault sensors, a secure element&lt;/td&gt;
&lt;td&gt;The attacker owns and can instrument the silicon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assume speculation, DVFS, DMP exist&lt;/td&gt;
&lt;td&gt;Treat CT as necessary-not-sufficient; isolate secrets across process and VM boundaries; keep key lifetimes short&lt;/td&gt;
&lt;td&gt;These channels live below the ISA and cannot be closed in source&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;The key-management playbook&lt;/h3&gt;
&lt;p&gt;The custody decision is a lookup table keyed on asset and threat. A low-value single-host secret belongs in the OS keystore. A device-bound key with measured boot belongs in a TPM or secure element. Lots of data at rest belongs behind cloud KMS with envelope encryption and automatic rotation [@aws-kms]. A root, certificate-authority, code-signing, or payment key belongs in a dedicated or Managed FIPS 140-3 HSM [@fips-140-3].&lt;/p&gt;
&lt;p&gt;A key that must survive with no single point of trust belongs in &lt;a href=&quot;https://paragmali.com/blog/nobody-broke-shamir-a-field-guide-to-secret-sharing-and-thre/&quot; rel=&quot;noopener&quot;&gt;threshold or MPC custody&lt;/a&gt;, the subject of Part 22. And any application secret belongs in a secrets manager issuing short-lived attested credentials, with CI secret-scanning behind it -- never hardcoded, always rotated, and stale non-human identities always offboarded [@owasp-nhi-2025][@nist-sp800-57].&lt;/p&gt;
&lt;p&gt;One caution about rotation, because it is oversold. Rotation bounds the blast radius of a leaked key -- it caps how much a single compromised key is worth and for how long -- but it is not a substitute for custody. Storm-0558&apos;s key was both never rotated &lt;em&gt;and&lt;/em&gt; software-resident; rotation alone would have shortened the exposure window, but keeping the key in an HSM is what stops the export in the first place. Rotate &lt;em&gt;and&lt;/em&gt; custody; the two do different jobs.&lt;/p&gt;
&lt;h3&gt;The common-misuse catalog&lt;/h3&gt;
&lt;p&gt;Most real leaks are not exotic. They are the same dozen mistakes, made again.One misuse deserves special note because the fix for a &lt;em&gt;different&lt;/em&gt; problem creates it: &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;deterministic ECDSA&lt;/a&gt; (RFC 6979) removes the nonce-RNG risk [@rfc6979], but a fault during signing then yields two signatures on the same nonce, which recovers the key [@poddebniak-2018]. Hedged signing -- mixing in fresh randomness -- restores fault resistance without reopening the RNG risk. Part 17 treats this trade in full.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Misuse&lt;/th&gt;
&lt;th&gt;Why it leaks&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memcmp&lt;/code&gt; on a MAC or tag&lt;/td&gt;
&lt;td&gt;Early-exit timing oracle, the Lucky Thirteen class [@lucky13-2013]&lt;/td&gt;
&lt;td&gt;Constant-time compare (CRYPTO_memcmp, sodium_memcmp)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret-indexed table lookup&lt;/td&gt;
&lt;td&gt;Cache-timing leak of the accessed address [@bernstein-2005]&lt;/td&gt;
&lt;td&gt;AES-NI or bitslicing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret-dependent division or branch&lt;/td&gt;
&lt;td&gt;Variable-time; division is not a data-independent instruction [@kyberslash-2024]&lt;/td&gt;
&lt;td&gt;Replace with multiply, mask, or branchless select&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiler betrayal&lt;/td&gt;
&lt;td&gt;Optimizer restores a branch, or elides a memset that scrubs a secret&lt;/td&gt;
&lt;td&gt;Verify the binary, use secure-zero intrinsics, re-check after upgrades&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skipping RSA-CRT verification&lt;/td&gt;
&lt;td&gt;Reopens the Bellcore fault; one bad signature factors n [@bellcore-1997]&lt;/td&gt;
&lt;td&gt;Verify the signature before releasing it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Home-rolled constant-time&lt;/td&gt;
&lt;td&gt;Assume it is not constant-time until a tool proves otherwise&lt;/td&gt;
&lt;td&gt;dudect or TIMECOP, plus a verified library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardcoded or sprawled secrets&lt;/td&gt;
&lt;td&gt;Rung 0; the key is the leak [@csrb-storm0558]&lt;/td&gt;
&lt;td&gt;Secrets manager, rotation, offboarding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reusing a KEK as a DEK&lt;/td&gt;
&lt;td&gt;Couples bulk data directly to the root key&lt;/td&gt;
&lt;td&gt;Keep DEK and KEK distinct; wrap, do not encrypt with the KEK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-constant-time post-quantum code&lt;/td&gt;
&lt;td&gt;Fresh, unhardened leakage surface [@gofetch-2024]&lt;/td&gt;
&lt;td&gt;Patched, verified PQC libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The envelope pattern at the bottom of the custody advice is worth seeing in code, because the blast-radius argument is the whole point.&lt;/p&gt;
&lt;p&gt;{`
// Illustrative only. Real systems use AES-GCM to encrypt and AES-KW to wrap,
// and the KEK lives inside an HSM or cloud KMS that performs wrap and unwrap on request.
function xorCrypt(bytes, key) { return bytes.map((b, i) =&amp;gt; b ^ key[i % key.length]); }
function randomKey(n) { return Array.from({ length: n }, () =&amp;gt; Math.floor(Math.random() * 256)); }&lt;/p&gt;
&lt;p&gt;// The KEK never leaves this boundary; software only sends wrap and unwrap requests.
const KEK = randomKey(16);
function wrap(dek)  { return xorCrypt(dek, KEK); }  // stands in for a KMS wrap call
function unwrap(wd) { return xorCrypt(wd, KEK); }   // stands in for a KMS unwrap call&lt;/p&gt;
&lt;p&gt;// Encrypt: fresh DEK per object, encrypt the data, wrap the DEK, store the wrapped DEK beside it.
const data = [72, 105, 33];
const dek = randomKey(16);
const ciphertext = xorCrypt(data, dek);
const wrappedDek = wrap(dek);   // this, not the plaintext DEK, is stored with the ciphertext&lt;/p&gt;
&lt;p&gt;// Decrypt: ask the boundary to unwrap the DEK, use it, discard it.
const plaintext = xorCrypt(ciphertext, unwrap(wrappedDek));
console.log(&apos;round-trips:&apos;, plaintext.join(&apos;,&apos;) === data.join(&apos;,&apos;));
// Blast radius: a leaked wrappedDek without the KEK reveals nothing; a leaked DEK exposes only its one object.
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Print the table above and run it against any code that touches a key. Every row has ended a real system. The first three are the constant-time family, the next four are custody, and the last two are the modern frontier. If your review cannot rule out all nine, you have not finished reviewing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Ship verified constant-time as the floor, back it with hardware timing modes and, for physical adversaries, masking -- then spend the rest of your budget where systems actually break, on key custody. Close the channels you can model; bound the cost of the ones you cannot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;11. Seven Beliefs That Ship to Production and Get People Owned&lt;/h2&gt;


No, and this is the single most consequential misunderstanding in the field. Constant-time means *data-independent* execution: the same instruction stream and the same memory-address trace regardless of the secret&apos;s value. It does not mean fixed duration. A routine can run for different absolute times on different inputs and still be constant-time, as long as the variation never depends on a secret. Conversely, padding a function to a fixed wall-clock time does not make it constant-time if it still branches on a secret underneath.


No. Certification validates a modeled threat surface, not physics. TPM-Fail recovered 256-bit ECDSA keys from an Intel firmware TPM and a Common Criteria EAL4+ and FIPS-certified STMicroelectronics chip via a timing-and-lattice attack -- minutes locally, hours remotely [@tpm-fail-2020]. A certificate tells you the module passed a defined evaluation; it does not tell you the evaluation covered the channel your attacker will use.


Necessary, not sufficient. Constant-time is a property of the instruction stream you wrote; the silicon runs one you did not. Spectre executes secret-dependent paths speculatively, Hertzbleed turns data-dependent frequency into remote timing, GoFetch&apos;s prefetcher dereferences pointer-like secrets, and KyberSlash leaks through a variable-time division -- all beneath the constant-time abstraction [@spectre-2019][@hertzbleed-2022][@gofetch-2024][@kyberslash-2024]. Constant-time is the floor, not the ceiling.


Mostly true, and you should threat-model it honestly: if no adversary can hold or instrument your device, the classical power and EM channels are lower priority than the remote ones. But the boundary is not absolute. Hertzbleed turned a power side channel into a *remote* timing attack by exploiting dynamic frequency scaling, extracting a key from constant-time SIKE over a network with no physical probe at all [@hertzbleed-2022].


Rotation bounds the blast radius of a leak; it does not prevent the leak. It caps how long a compromised key stays valid and how much it protects, which is real and valuable. But it is not custody. Storm-0558&apos;s key was never rotated *and* software-resident; rotation would have shortened the window, while hardware custody would have stopped the export that started the whole incident [@csrb-storm0558]. Do both.


No. It is custody plus performance, not redundancy. A cheap Data-Encryption Key does the bulk work while the Key-Encryption Key that wraps it never leaves the HSM or KMS, using standardized key wrapping [@nist-sp800-38f]. The payoff is a bounded blast radius: a leaked DEK exposes only the one object it encrypted, and the root key stays in hardware. Double encryption gives you two layers of the same risk; envelope encryption gives you a small, cheap, disposable key over a small, protected, permanent one.


No -- they are broken implementations and mismanaged keys. Scope the claim carefully: relative to breaking the primitives and relative to the exotic side channels this article catalogs, operational key and secret management is the dominant failure surface, evidenced by cryptographic failures ranking as the number-two web risk and by Storm-0558&apos;s uncracked-but-stolen key [@owasp-a02-2021][@csrb-storm0558]. It is *not* a claim that key management outranks every software vulnerability class -- memory-safety bugs lead by raw count. The math almost never breaks. The handling of the math does.

&lt;h2&gt;The Proof Was Conditional&lt;/h2&gt;
&lt;p&gt;Twenty-four times, this series proved that the mathematics holds. Every one of those proofs carried a silent condition: supply a fresh nonce, validate the point, derive a uniform key, compare tags in constant time, keep the secret in its custody. This final part is the condition, standing alone and made visible. The math held. The machine and the operations are what handed the secret away.&lt;/p&gt;
&lt;p&gt;You now have the lens to read any future break the same way, without waiting for someone to explain it. When the next headline attack lands, ask which of four invariants it violated. Did a secret touch &lt;em&gt;time&lt;/em&gt; -- a branch, a division, a data-dependent frequency? Did a secret touch an &lt;em&gt;address&lt;/em&gt; -- a table index, a cache line, a prefetched pointer? Did a &lt;em&gt;fault&lt;/em&gt; skip a check -- a glitch, a hammered row, an undervolted enclave? Or did a &lt;em&gt;key&lt;/em&gt; outlive or escape its custody -- hardcoded, sprawled, unrotated, software-resident? Kocher&apos;s stopwatch in 1996 and Apple&apos;s prefetcher in 2024 are the same sentence in two dialects, and Storm-0558 is the fourth invariant in its purest form: no cipher broken, a key simply gone.&lt;/p&gt;

This article has claimed that key and secret management is where deployed systems most often break, and that claim needs its fences. It is scoped: *relative to breaking the primitives, and relative to the exotic side channels catalogued here*, operational custody is the dominant failure surface -- supported by cryptographic failures ranking as the number-two web-application risk and by nation-state incidents like Storm-0558 that cracked no math [@owasp-a02-2021][@csrb-storm0558]. It is *not* a measured majority over all software vulnerability classes; memory-safety bugs lead by raw count. The umbrella term is &quot;key and secret management,&quot; and the honest version of the thesis never says &quot;most of all breaches&quot; -- it says &quot;the dominant *operational* failure surface for cryptographic systems.&quot;
&lt;p&gt;So end the series where the evidence points. Spend real effort on the exotic channels, because verified constant-time and hardware timing modes are now deployable and the microarchitecture will keep testing them. But spend the larger share of your budget on the unglamorous layer, because that is where the incidents actually cluster: put the root key in hardware, use it without exporting it, envelope-wrap and auto-rotate everything else, and keep no long-lived secret sitting in software. The twenty-five parts of this guide are one argument, and this is its last line.&lt;/p&gt;

The math held. Make sure the machine, and the people running it, do not hand the secret away.
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;secure-implementation-side-channels-key-custody&quot; keyTerms={[
  { term: &quot;Side channel&quot;, definition: &quot;Information leaked by a computation&apos;s physical or microarchitectural behavior, not its input/output relation.&quot; },
  { term: &quot;Abstraction gap&quot;, definition: &quot;The distance between an algorithm as an I/O relation and an implementation as a physical, timed, operated process.&quot; },
  { term: &quot;Constant-time&quot;, definition: &quot;Data-independent execution: instruction and address traces do not depend on secrets. Not fixed duration.&quot; },
  { term: &quot;Differential Power Analysis&quot;, definition: &quot;Correlating power draw across many traces to extract a key even under heavy noise.&quot; },
  { term: &quot;Fault attack&quot;, definition: &quot;Inducing a computational error so the faulty output reveals the secret, as in Bellcore RSA-CRT.&quot; },
  { term: &quot;Cache side channel&quot;, definition: &quot;Recovering which addresses a victim touched by timing shared-cache hits and misses, as in Flush and Reload.&quot; },
  { term: &quot;Transient execution&quot;, definition: &quot;Speculatively executed, later-discarded instructions whose cache traces still leak, as in Spectre.&quot; },
  { term: &quot;Masking&quot;, definition: &quot;Splitting a secret into d+1 random shares so any d observed values are independent of it.&quot; },
  { term: &quot;HSM&quot;, definition: &quot;A tamper-resistant boundary that uses keys without exporting them in plaintext.&quot; },
  { term: &quot;Envelope encryption&quot;, definition: &quot;A cheap DEK encrypts data; a KEK inside the HSM wraps the DEK; the wrapped DEK is stored beside the ciphertext.&quot; }
]} flashcards={[
  { front: &quot;The four invariants a break violates&quot;, back: &quot;A secret touched time; a secret touched an address; a fault skipped a check; a key outlived or escaped its custody.&quot; },
  { front: &quot;Why is constant-time necessary but not sufficient?&quot;, back: &quot;The ISA does not specify timing, so speculation, frequency scaling, prefetchers, and division reintroduce data-dependence below it.&quot; },
  { front: &quot;The masking cost curve&quot;, back: &quot;Order-d security needs at least d+1 shares; a masked multiply costs O(d^2). Leakage is a cost curve, not a wall.&quot; },
  { front: &quot;The capstone rule&quot;, back: &quot;Verified constant-time as the floor, hardware modes and masking as backing, then spend the rest on key custody.&quot; }
]} questions={[
  { q: &quot;Why does square-and-multiply leak the exponent through timing?&quot;, a: &quot;A one bit triggers an extra multiply, so total time tracks the number of one bits in the secret exponent.&quot; },
  { q: &quot;What made Storm-0558 a custody failure rather than a cryptographic one?&quot;, a: &quot;A 2016 software-resident, never-rotated signing key was stolen and used to forge tokens; no cipher was broken.&quot; },
  { q: &quot;How does GoFetch break constant-time code?&quot;, a: &quot;Apple&apos;s data-memory-dependent prefetcher dereferences pointer-like values, coupling data to addresses below the ISA.&quot; },
  { q: &quot;What does rotation fix, and what does it not fix?&quot;, a: &quot;It bounds a leaked key&apos;s blast radius and lifetime; it does not stop the export, which is custody&apos;s job.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>constant-time</category><category>side-channels</category><category>key-management</category><category>fault-attacks</category><category>cryptographic-engineering</category><category>hsm</category><category>spectre</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>