<?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: constant-time</title><description>Posts tagged constant-time.</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/constant-time/rss.xml" rel="self" type="application/rss+xml"/><item><title>How ML-KEM Breaks in Real Life: The Machine Leaked While the Math Held</title><link>https://paragmali.com/blog/how-ml-kem-breaks-in-real-life-the-machine-leaked-while-the-/</link><guid isPermaLink="true">https://paragmali.com/blog/how-ml-kem-breaks-in-real-life-the-machine-leaked-while-the-/</guid><description>ML-KEM shipped with a machine-checked security proof, yet its reference code leaked secret keys through a division instruction. Why every break missed the math.</description><pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate><content:encoded>
ML-KEM (FIPS 203) is the first standardized post-quantum KEM, and as of publication its Module-LWE mathematics is unbroken -- the IND-CCA2 security of its Fujisaki-Okamoto construction is even machine-checked [@fvk], and the two headline quantum &quot;breaks&quot; both failed to withstand expert scrutiny -- Chen withdrew his own claim, and Luo&apos;s was refuted by named experts [@chen2024][@postquantum-luo]. Yet every real attack on it has recovered secret keys anyway: KyberSlash through a variable-time division instruction disclosed in December 2023 [@kyber-commit][@kyberslash-paper], Clangover through a compiler that reintroduced a secret branch [@clangover], and fault attacks like Carry Your Fault straight through the masking meant to stop them [@carry-fault]. The through-line is a law two decades old, first seen when AES was attacked at the implementation and never at the cipher [@bernstein2005]: a security proof constrains values, not execution, so the attacker&apos;s frontier moves outward -- instruction, compiler, construction, countermeasure -- and never has to reach the lattice. This article catalogs each documented break from its primary source and shows you how to choose an ML-KEM implementation that does not leak.
&lt;h2&gt;1. A fortress breached at the doorknob&lt;/h2&gt;
&lt;p&gt;On 13 August 2024, the U.S. National Institute of Standards and Technology published FIPS 203 and gave the world its first standardized post-quantum key-encapsulation mechanism: ML-KEM [@fips203]. It arrived with something almost no cipher in history has had at birth -- a machine-checked mathematical proof that the construction is secure [@fvk]. Eight months earlier, on 1 December 2023, the maintainers of its reference code had quietly committed a patch to stop that same code from handing an attacker the full secret key [@kyber-commit]. Not through any weakness in the lattice mathematics. Through a single division instruction.&lt;/p&gt;
&lt;p&gt;The proof was about the numbers. The attack was about the machine. This article is about the gap between them.&lt;/p&gt;

A KEM is a three-algorithm protocol -- key generation, encapsulation, and decapsulation -- for agreeing on a shared secret over an insecure channel. Encapsulation takes a public key and outputs a ciphertext plus a shared secret; decapsulation takes the ciphertext and the private key and recovers the same shared secret. ML-KEM is the KEM standardized in FIPS 203, derived from CRYSTALS-Kyber, and its security rests on the Module Learning With Errors problem [@fips203].
&lt;p&gt;Here is the thesis this article defends: as of publication, every documented attack on ML-KEM is an implementation attack. Not one has touched the Module-LWE mathematics. The keys have been recovered through a division instruction, a compiler optimization, and a fault injected into a countermeasure -- never through the lattice. This is exactly the class of attack that its structural-mathematics sibling, &quot;How ML-KEM and ML-DSA Would Break,&quot; deliberately sets aside to ask how the primitive itself might one day fall. That article studies the door; this one shows you that so far, every intruder has come through the doorknob.&lt;/p&gt;

There is no documented in-the-wild exploitation of a production ML-KEM deployment as of publication. The &quot;breaks&quot; cataloged here are of three honest kinds: vulnerable code shipping in the reference implementation and downstream libraries; CVE-class advisories such as RUSTSEC-2023-0079 [@rustsec]; and researcher proof-of-concept key recovery on real hardware [@kyberslash-paper]. That is stronger for the thesis, not weaker. The implementation layer broke first, at deployment scale, before the primitive had finished rolling out -- which is precisely what the pattern predicts.
&lt;p&gt;And it has finished rolling out into places that matter. Google&apos;s BoringSSL and the Go standard library&apos;s constant-time &lt;code&gt;crypto/mlkem&lt;/code&gt; both ship ML-KEM today [@kyberslash-site][@go-mlkem], as do &lt;code&gt;filippo.io/mlkem768&lt;/code&gt; and the vectorized AVX2 reference paths [@kyberslash-site]. The common deployment shape is a hybrid key exchange, &lt;code&gt;X25519MLKEM768&lt;/code&gt;, that stays secure if either half holds [@tls-hybrid] -- a hedge, as we will see, against both a future math break and a present implementation bug. When we say the reference code was leaking keys, we mean the code from which production ML-KEM was built.&lt;/p&gt;
&lt;p&gt;The proof was real. So where did the key go? To answer that we have to be precise about what the proof actually promises -- and what it does not.&lt;/p&gt;
&lt;h2&gt;2. The contract: what ML-KEM actually promises&lt;/h2&gt;
&lt;p&gt;A security proof is a legal contract, and like every contract it binds only what it names. Read ML-KEM&apos;s proof carefully and you find it makes a precise, narrow promise about what an adversary can learn from a public key and a set of ciphertexts. It says nothing at all about how many clock cycles decapsulation takes, what assembly the compiler emits, how the power rail fluctuates, or what happens when a cosmic ray flips a carry bit. Those are not loopholes in the contract. They are simply not in it.&lt;/p&gt;
&lt;p&gt;ML-KEM began life as CRYSTALS-Kyber, presented at IEEE EuroS&amp;amp;P in 2018 as &quot;a CCA-secure module-lattice-based KEM&quot; [@kyber2018]. Six years of analysis and a NIST competition later, it became FIPS 203 with only modest changes [@fips203]. Its security rests on one hard problem.&lt;/p&gt;

Module-LWE hides a short secret vector inside a system of noisy linear equations over a polynomial ring modulo a public prime $q = 3329$. Given many pairs $(\mathbf{a}, \mathbf{a}\cdot\mathbf{s} + e)$ with small error $e$, recovering the secret $\mathbf{s}$ is believed hard for both classical and quantum computers. ML-KEM uses a module of rank 2, 3, or 4 to produce its three security levels; the internals of why the problem is hard belong to the companion article on how the lattice itself might fall [@kyber2018][@fips203].
&lt;p&gt;The module rank is the only structural dial, and it defines the three parameter sets.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter set&lt;/th&gt;
&lt;th&gt;Module rank&lt;/th&gt;
&lt;th&gt;FIPS 203 security category&lt;/th&gt;
&lt;th&gt;Typical role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;ML-KEM-512&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Category 1&lt;/td&gt;
&lt;td&gt;Lightweight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML-KEM-768&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Category 3&lt;/td&gt;
&lt;td&gt;Default / recommended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML-KEM-1024&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Category 5&lt;/td&gt;
&lt;td&gt;High assurance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The modulus $q = 3329$ is a public constant baked into the standard.Everyone knows the divisor. What is secret is the numerator the divisor acts on -- the coefficients of the decrypted polynomial. That asymmetry, a public modulus dividing a secret operand, is the entire seed of the first break in the catalog. That distinction between the public and the secret operand is worth holding onto; it returns with a vengeance in Section 4.&lt;/p&gt;
&lt;p&gt;Now picture the whole story as a set of concentric layers around that untouched mathematics.&lt;/p&gt;

flowchart LR
    M[&quot;Layer 0: Module-LWE mathematics (unbroken)&quot;]
    I[&quot;Layer 1: the instruction (KyberSlash)&quot;]
    C[&quot;Layer 2: the compiler (Clangover)&quot;]
    F[&quot;Layer 3: the construction (FO plaintext-checking oracle)&quot;]
    K[&quot;Layer 4: the countermeasures (Carry Your Fault)&quot;]
    M --&amp;gt; I --&amp;gt; C --&amp;gt; F --&amp;gt; K
&lt;p&gt;Why does an active adversary matter here? Because a KEM that only resisted passive eavesdroppers -- the property called &lt;a href=&quot;https://paragmali.com/blog/secure-against-whom-the-security-definitions-every-protocol-/&quot; rel=&quot;noopener&quot;&gt;IND-CPA&lt;/a&gt; -- would be useless for a reused key. Real deployments reuse a decapsulation key across many sessions, and an attacker who can submit chosen or malformed ciphertexts and watch the results needs a stronger guarantee.&lt;/p&gt;

IND-CCA2 is the gold-standard secrecy notion for a KEM: even an adversary who can submit ciphertexts of its choosing and observe how the system responds cannot distinguish the real shared secret from random. It is exactly the notion you need when one long-lived private key decapsulates attacker-supplied ciphertexts, which is how KEMs are used in practice [@fips203].
&lt;p&gt;Kyber reaches IND-CCA2 from a weaker IND-CPA core using a classic construction.&lt;/p&gt;

The FO transform, introduced in 1999 [@fo1999] and given its modern implicit-rejection analysis in 2017 [@hhk2017], upgrades a passively secure encryption scheme to an actively secure KEM. On decapsulation, it decrypts the ciphertext to a candidate message, then re-encrypts that message and checks whether the result matches the ciphertext it received. A genuine ciphertext re-encrypts to itself; a tampered one does not.
&lt;p&gt;What happens on a mismatch is the hinge of the entire article.&lt;/p&gt;

When re-encryption does not match, ML-KEM does not return an error. It returns a pseudorandom shared secret $\bar{K} = J(z, c)$, derived by hashing a secret rejection seed $z$ together with the ciphertext $c$. To the caller, a rejected ciphertext is indistinguishable from an accepted one -- there is no decapsulation-failure signal to query. By construction, the FO transform gives an attacker no caller-visible oracle [@fips203].

flowchart TD
    A[&quot;Ciphertext c arrives at decapsulation&quot;]
    B[&quot;Decrypt c to a candidate message&quot;]
    C[&quot;Re-encrypt the candidate to c-prime&quot;]
    D{&quot;Re-encryption matches?&quot;}
    E[&quot;Return the true shared secret K&quot;]
    F[&quot;Return pseudorandom K-bar equals J of z and c&quot;]
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D
    D --&amp;gt;|yes| E
    D --&amp;gt;|no| F
&lt;p&gt;That construction is not just believed secure; in 2024 a team machine-checked the IND-CCA2 security and correctness of Kyber in EasyCrypt, tying the proof to a real implementation [@fvk]. So the baseline is as strong as cryptography gets: the mathematics is unbroken, and the reduction from it to the KEM has been verified by a computer.&lt;/p&gt;

A security proof is a contract about values; a side channel is a breach in execution.
&lt;p&gt;And there is the first shift in understanding. The proof constrains what an adversary learns from the specified inputs and outputs -- the values. It is silent on cycles, on emitted assembly, on power traces, on a flipped carry bit -- the execution. The key can walk out through the machine while the proof stays perfectly true. The contract is airtight about values. But the contract is run by a machine, and the machine is where this story happens. Before we watch it leak, one piece of history tells us it was always going to.&lt;/p&gt;
&lt;h2&gt;3. The precedent: AES was attacked at the implementation, never at the cipher&lt;/h2&gt;
&lt;p&gt;In April 2005, a widely used, carefully written AES implementation was handing out its key. Not to a cryptanalyst who had found a weakness in the cipher, but to a stopwatch. Daniel J. Bernstein showed that the time AES took to encrypt depended on its secret key, because the standard software used secret-indexed table lookups whose cache behavior varied with the data [@bernstein2005]. The cipher was fine. The code was leaking. The same researcher would, eighteen years later, be among the first to flag the division timing in Kyber [@kyberslash-site].&lt;/p&gt;

A side-channel attack recovers secrets not from the mathematical output of an algorithm but from physical or behavioral byproducts of running it: elapsed time, cache state, power consumption, electromagnetic emissions, or the effect of induced faults. The algorithm can be perfectly secure on paper and still leak through the machine executing it.
&lt;p&gt;A year later, Osvik, Shamir, and Tromer turned Bernstein&apos;s observation into a general methodology, naming the Prime and Probe and Evict and Time techniques that let one process read another&apos;s cache footprint and recover an AES key from a few thousand encryptions [@ost2006]. &lt;a href=&quot;https://paragmali.com/blog/how-aes-breaks-in-real-life-the-attacks-that-never-touched-t/&quot; rel=&quot;noopener&quot;&gt;Cache-timing attacks&lt;/a&gt; became a standard tool, and &quot;How AES Breaks in Real Life,&quot; the earlier article in this series, is largely their story.&lt;/p&gt;
&lt;p&gt;Now the counterweight, and it is the whole point. In all the years since, the AES &lt;em&gt;mathematics&lt;/em&gt; has barely moved. The best known single-key attack on the full cipher is the 2011 &lt;a href=&quot;https://paragmali.com/blog/the-fortress-and-the-afterthought-how-aes-would-break-at-its/&quot; rel=&quot;noopener&quot;&gt;biclique cryptanalysis&lt;/a&gt;, which recovers an AES-128 key in about ${2}^{126.1}$ operations rather than the ${2}^{128}$ of brute force [@biclique2011]. That is a speedup of less than a factor of four against a number with thirty-nine digits -- a result its own authors framed as not threatening the practical use of AES.&lt;/p&gt;
&lt;p&gt;Two decades of the world&apos;s best cryptanalysts, and the cipher&apos;s security margin shrank by two bits, while its implementations leaked keys to a stopwatch in an afternoon.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A strong new primitive is attacked at the implementation first, and often only ever there. The mathematics is the hardest thing to break and the last thing to fall; the code around it is where the keys actually walk out. AES lived this for twenty years. ML-KEM, as of publication, is living it from the moment it was born.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If that law holds, ML-KEM&apos;s first breaks should be implementation breaks -- and the theory behind them should already exist. It does. Years before FIPS 203, researchers had worked out exactly how to attack a CCA-secure lattice KEM through its physics. In 2019, Ravi and colleagues demonstrated generic side-channel key recovery against CCA-secure lattice-based PKE and KEMs, exploiting the decryption-failure structure of these schemes [@ravi2019]. In 2021, Ueno and colleagues published &quot;Curse of Re-encryption,&quot; showing that the FO re-encryption step itself is a generic power and electromagnetic oracle across post-quantum KEMs [@ueno2021].Bernstein bookends both halves of this series: the 2005 cache-timing attack that defined how AES breaks, and the 2023 division-timing flag that opened how ML-KEM breaks. The same person, the same lesson, eighteen years apart.&lt;/p&gt;
&lt;p&gt;So the blueprint for breaking ML-KEM&apos;s implementation was drawn before the standard was finished. If the pattern holds, the first real breaks should show up early, maybe before the ink on the standard is dry. They did. Here is the catalog.&lt;/p&gt;
&lt;h2&gt;4. The catalog: the frontier moves outward&lt;/h2&gt;
&lt;p&gt;Here is the pattern to watch as we walk the documented breaks: each one lands one layer further from the mathematics than the last. The first is a single machine instruction. The second is the compiler. The third is the construction that proves the whole scheme secure. The fourth is the countermeasure built to stop the third. Not one of them reaches the lattice.&lt;/p&gt;

timeline
    title The frontier moves outward, December 2023 to 2025
    2023 : KyberSlash1 patched on 1 December, commit dda29cc : Bernstein flags it possibly exploitable on 15 December : KyberSlash2 disclosed on 30 December
    2024 : Clangover compiler-introduced branch : Carry Your Fault on masked Kyber : FIPS 203 standardized on 13 August
    2025 : KyberSlash paper at IACR TCHES, a CHES best paper
&lt;p&gt;The whole break surface fits in one table, and every number in it comes from a primary source.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Attack&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Measured result&lt;/th&gt;
&lt;th&gt;Defense&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1. Instruction&lt;/td&gt;
&lt;td&gt;KyberSlash&lt;/td&gt;
&lt;td&gt;Secret numerator divided by the public modulus compiles to a variable-time &lt;code&gt;DIV&lt;/code&gt; [@kyberslash-site]&lt;/td&gt;
&lt;td&gt;Full keys in minutes for KyberSlash2, a few hours for KyberSlash1 -- both on the Cortex-M4 and Raspberry Pi 2 (Cortex-A7) [@kyberslash-paper]&lt;/td&gt;
&lt;td&gt;Reciprocal multiplication; never-divided and AVX2 paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Compiler&lt;/td&gt;
&lt;td&gt;Clangover&lt;/td&gt;
&lt;td&gt;Clang re-emits a secret-dependent branch from constant-time source [@clangover]&lt;/td&gt;
&lt;td&gt;ML-KEM-512 key in 5-10 minutes on an i7-13700H [@clangover]&lt;/td&gt;
&lt;td&gt;Verify the binary; verified assembly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Construction&lt;/td&gt;
&lt;td&gt;Plaintext-checking oracle&lt;/td&gt;
&lt;td&gt;Any physical pass/fail leak in FO re-encrypt-and-compare [@ravi2019][@ueno2021]&lt;/td&gt;
&lt;td&gt;Generic key recovery against a reused key&lt;/td&gt;
&lt;td&gt;Constant-time plus masked comparison&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Countermeasure&lt;/td&gt;
&lt;td&gt;Carry Your Fault&lt;/td&gt;
&lt;td&gt;Fault in the A2B carry chain that masking itself added [@carry-fault]&lt;/td&gt;
&lt;td&gt;Keys from arbitrary-order masked Kyber via EM fault on an STM32 [@carry-fault]&lt;/td&gt;
&lt;td&gt;Fault detection and redundancy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Layer 1 -- the instruction: KyberSlash&lt;/h3&gt;
&lt;p&gt;Start with one line of reference C. In &lt;code&gt;poly_tomsg&lt;/code&gt;, the routine that turns a decrypted polynomial back into message bits, the code computes &lt;code&gt;t = (((t &amp;lt;&amp;lt; 1) + KYBER_Q/2)/KYBER_Q) &amp;amp; 1;&lt;/code&gt; [@kyberslash-site]. It divides a &lt;em&gt;secret&lt;/em&gt; numerator &lt;code&gt;t&lt;/code&gt; by the &lt;em&gt;public&lt;/em&gt; modulus &lt;code&gt;KYBER_Q&lt;/code&gt;, which is 3329. On paper that is one arithmetic operation. On silicon it is a catastrophe.&lt;/p&gt;

Code is constant-time, or data-oblivious, when its execution -- the instructions retired, the memory addresses touched, and the total cycle count -- does not depend on any secret value. No secret-dependent branches, no secret-indexed memory, no variable-latency instructions on secret operands. It is the baseline defense against timing side channels, and it is a property of the running binary, not of good intentions.
&lt;p&gt;The trouble is that on many processors, integer division is not constant-time. On many CPU, compiler, and flag combinations, &lt;code&gt;/KYBER_Q&lt;/code&gt; compiles to a hardware &lt;code&gt;DIV&lt;/code&gt; whose cycle count depends on the numerator: on an AMD Zen 2 core the division slows by a cycle once the numerator reaches 8192; on the SiFive U74 RISC-V core the timing shifts at 2048, 4096, and 8192; &lt;code&gt;gcc -Os&lt;/code&gt; emits a &lt;code&gt;DIV&lt;/code&gt; where &lt;code&gt;gcc -O3&lt;/code&gt; emits a constant-time multiplication; and on a Raspberry Pi 2, &lt;code&gt;gcc -Os&lt;/code&gt; -- whose default ABI does not guarantee a hardware divide -- emits a call to a branchy software-division routine instead [@kyberslash-site]. Any one of these makes decapsulation time depend on the secret.&lt;/p&gt;

KYBER_Q is a public integer 3329, but t is secret ... the time taken by division depends on the inputs.
&lt;p&gt;That quote is from the KyberSlash FAQ [@kyberslash-site], and it is the entire attack in one sentence. There was, it turns out, a second copy of the same bug: KyberSlash2 is the identical secret-dependent division in &lt;code&gt;poly_compress&lt;/code&gt; and &lt;code&gt;polyvec_compress&lt;/code&gt;, the compression functions called from &lt;code&gt;indcpa_enc&lt;/code&gt; [@kyberslash-site]. Because decapsulation &lt;em&gt;also&lt;/em&gt; runs encryption during FO re-encryption, both the decryption and the re-encryption paths leak [@pqcforum-kyberslash2]. The measured consequence, from the KyberSlash paper, is blunt: Kyber secret keys are &quot;reliably recovered within minutes for KyberSlash2 and a few hours for KyberSlash1&quot; on an Arm Cortex-M4 and a Raspberry Pi 2 with an Arm Cortex-A7 [@kyberslash-paper].&lt;/p&gt;

flowchart TD
    A[&quot;Secret numerator t from the decrypted polynomial&quot;]
    B[&quot;Public modulus KYBER_Q, the constant 3329&quot;]
    C[&quot;Source computes t divided by KYBER_Q&quot;]
    D[&quot;Compiler emits a hardware DIV on many targets&quot;]
    E[&quot;DIV latency depends on the operand&quot;]
    F[&quot;Decapsulation time leaks bits of the secret&quot;]
    G[&quot;Plaintext-checking oracle, then full key recovery&quot;]
    A --&amp;gt; C
    B --&amp;gt; C
    C --&amp;gt; D --&amp;gt; E --&amp;gt; F --&amp;gt; G
&lt;p&gt;The timeline matters, because it is the proof of the thesis. The reference implementation was patched in &lt;code&gt;pq-crystals/kyber&lt;/code&gt; commit dda29cc on 1 December 2023, with the message &quot;Updated poly_tomsg to prevent a compiler from using DIV,&quot; crediting Cryspen&apos;s Goutam Tamvada, Karthikeyan Bhargavan, and Franziskus Kiefer [@kyber-commit].The leak was found not by an attacker but by a formal-verification team. Cryspen&apos;s engineers noticed the division while building a machine-checked Rust implementation of Kyber [@cryspen]. The people proving the code correct were the ones who saw it leak. Bernstein announced it publicly as a &quot;possibly exploitable&quot; issue on 15 December 2023 [@pqcforum-bernstein]; Ravi and Kannwischer disclosed the compression-function copy, KyberSlash2, on 30 December 2023 [@pqcforum-kyberslash2]. This is eight months &lt;em&gt;before&lt;/em&gt; FIPS 203 existed. The standard had a machine-checked proof and a leaking reference implementation at the same time.KyberSlash carries a CVE-class advisory but no NVD number. The record is RUSTSEC-2023-0079 against the &lt;code&gt;pqc_kyber&lt;/code&gt; crate, rated CVSS 7.4 HIGH, with no patched versions released upstream [@rustsec].&lt;/p&gt;
&lt;p&gt;You can see why the fix looks trivial: never divide by a public constant on a secret. Multiply by its reciprocal instead. The two compute the same value, but only one is data-oblivious.&lt;/p&gt;
&lt;p&gt;{`
const q = 3329;&lt;/p&gt;
&lt;p&gt;// Illustrative model of hardware DIV latency: on several real cores the
// cycle count creeps up as the (secret) dividend grows. Not a measurement.
function divCycles(secretNumerator) {
  return secretNumerator &amp;gt;= 8192 ? 18 : 17;
}&lt;/p&gt;
&lt;p&gt;// Reciprocal multiplication: multiply by a precomputed constant, then
// shift. One fixed cost, whatever the operand.
function reciprocalCycles(_secretNumerator) {
  return 3;
}&lt;/p&gt;
&lt;p&gt;// Both routines decode the same message bit; only the timing differs.
function decodeBit(t) {
  return (((t &amp;lt;&amp;lt; 1) + (q &amp;gt;&amp;gt; 1)) / q) &amp;amp; 1;
}&lt;/p&gt;
&lt;p&gt;const secrets = [1200, 6000, 9000, 15000];
const divCosts = secrets.map(divCycles);
const mulCosts = secrets.map(reciprocalCycles);&lt;/p&gt;
&lt;p&gt;console.log(&quot;secret numerators :&quot;, secrets.join(&quot;, &quot;));
console.log(&quot;DIV cycle model   :&quot;, divCosts.join(&quot;, &quot;), &quot; (varies with the secret)&quot;);
console.log(&quot;reciprocal model  :&quot;, mulCosts.join(&quot;, &quot;), &quot;    (constant)&quot;);
console.log(&quot;decoded bits match:&quot;, secrets.map(decodeBit).join(&quot;, &quot;));
`}&lt;/p&gt;
&lt;p&gt;The obvious fix, then, is to delete the division and write constant-time source. So people did. It was not enough.&lt;/p&gt;
&lt;h3&gt;Layer 2 -- the compiler: Clangover&lt;/h3&gt;
&lt;p&gt;You wrote constant-time C. The compiler disagreed. This is the uncomfortable discovery behind Clangover, published by Antoon Purnal in 2024 [@clangover]. The reference implementation&apos;s &lt;code&gt;poly_frommsg&lt;/code&gt; routine, which expands each message bit into a polynomial coefficient, was written to be branch-free by construction: no &lt;code&gt;if&lt;/code&gt; on a secret, just arithmetic and masks. Inspect the C and it is constant-time.&lt;/p&gt;
&lt;p&gt;Then compile it. Clang versions 15 through 18, under optimization levels &lt;code&gt;-Os&lt;/code&gt;, &lt;code&gt;-O1&lt;/code&gt;, and &lt;code&gt;-O2&lt;/code&gt; or &lt;code&gt;-O3&lt;/code&gt; with &lt;code&gt;-fno-vectorize&lt;/code&gt;, recognize the branch-free idiom and &quot;optimize&quot; it back into a &lt;em&gt;secret-dependent conditional branch&lt;/em&gt; in the emitted x86 assembly [@clangover]. The compiler, trying to be helpful, reintroduced exactly the leak the programmer had carefully removed. End-to-end decapsulation-timing measurements then recover the full ML-KEM-512 secret key in 5 to 10 minutes on an Intel Core i7-13700H compiled with Clang 16.0.6 [@clangover].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Constant-time source is not constant-time binary. A proof about C, or even a careful reading of C, does not bind the machine code an optimizing compiler emits. The property you need holds of the instructions that actually run, and the compiler stands between you and them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The reference maintainers already knew the compiler was an adversary, even if the full extent was not yet clear. Recall the wording of the KyberSlash patch: not &quot;fix a bug&quot; but &quot;prevent a compiler from using DIV&quot; [@kyber-commit].The commit message names the real opponent. The source did not contain a division instruction; it contained a division operator that the maintainers had to actively stop the compiler from turning into a &lt;code&gt;DIV&lt;/code&gt;. The adversary was the toolchain, not the algorithm. Clangover is the general form of that fear: the toolchain can undo any source-level constant-time property you think you have.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every defense so far lived in the source language. KyberSlash was patched in source; the branch-free &lt;code&gt;poly_frommsg&lt;/code&gt; was written in source. Clangover shows the guarantee you actually need lives one level down, in the binary -- and nothing about writing careful C forces the binary to inherit the property. This is the coverage gap that Section 6&apos;s strongest defenses exist to close.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One detail in the Clangover repository ties this layer to the next. Purnal describes his attack, in his own words, as &quot;a plaintext-checking oracle attack a la Ravi et al. and Ueno et al.&quot; [@clangover] -- and so does the KyberSlash lineage. Both breaks call themselves the same thing, which is no coincidence: it is the tell that they are two instantiations of a single, deeper surface. Time to see it.&lt;/p&gt;
&lt;h3&gt;Layer 3 -- the construction: the plaintext-checking oracle&lt;/h3&gt;
&lt;p&gt;The armor is the wound. The re-encrypt-and-compare step that &lt;em&gt;proves&lt;/em&gt; ML-KEM secure against active attackers is the exact code that every documented break pries open. To see why, you have to understand what the attacks are actually querying.&lt;/p&gt;

A plaintext-checking oracle answers one question about a decapsulation: did this ciphertext decrypt to a particular message, or not? Lattice KEMs have a small, secret-dependent decryption-failure event, so an attacker who crafts ciphertexts near that boundary and learns pass-or-fail for each one can, over many queries against a reused key, solve for the secret. The oracle need not be a protocol feature; any physical leak that separates the two cases will serve [@ravi2019][@ueno2021].
&lt;p&gt;This is the surface Ravi and colleagues formalized in 2019, showing generic PC-oracle key recovery against CCA-secure lattice KEMs, and that Ueno and colleagues generalized in 2021 to the FO re-encryption step as a power and electromagnetic oracle across post-quantum schemes [@ravi2019][@ueno2021]. Both predate FIPS 203.&lt;/p&gt;
&lt;p&gt;And now the earlier layers snap into focus: KyberSlash is this oracle instantiated at the &lt;em&gt;instruction&lt;/em&gt; (division timing separates pass from fail), and Clangover is the same oracle instantiated at the &lt;em&gt;compiler&lt;/em&gt; (a reintroduced branch separates pass from fail). Purnal names the lineage outright [@clangover].&lt;/p&gt;

flowchart TD
    A[&quot;Attacker crafts a ciphertext near a decryption-failure boundary&quot;]
    B[&quot;Victim decapsulates with the reused secret key&quot;]
    C[&quot;Re-encrypt-and-compare runs on secret-derived data&quot;]
    D{&quot;Side channel reveals pass or fail?&quot;}
    E[&quot;Record one bit about the secret key&quot;]
    F[&quot;Update the secret-key estimate&quot;]
    G[&quot;Full secret key recovered&quot;]
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D
    D --&amp;gt;|leak observed| E --&amp;gt; F
    F --&amp;gt;|repeat with a new ciphertext| A
    F --&amp;gt;|enough bits gathered| G
&lt;p&gt;Here is the point that has to be stated precisely, because it is the most common misreading of ML-KEM.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Implicit rejection means a re-encryption mismatch returns a pseudorandom shared secret, not an error [@fips203]. There is no caller-visible reject to query, so the FO transform hands an attacker no oracle by construction. Every real oracle in this article is a side-channel leak in the re-encryption and comparison -- timing, power, electromagnetic, or fault -- never a signal the protocol emits.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So the FO transform&apos;s mathematics never failed. It is machine-checked [@fvk]. What &quot;failed&quot; is subtler and more interesting: the construction that earns the security proof is precisely the construction that forces secret-dependent computation to run at decapsulation time. The proof guarantees the &lt;em&gt;values&lt;/em&gt; leak nothing. It cannot guarantee the &lt;em&gt;execution&lt;/em&gt; leaks nothing, because guaranteeing that was never the mathematician&apos;s job -- it is the implementer&apos;s. The armor and the wound are the same lines of code.&lt;/p&gt;
&lt;p&gt;If timing and compiler leaks feed this oracle, the natural next move is to attack a different channel entirely: mask the implementation so that power and electromagnetic measurements reveal nothing either. That worked. Until the defense became the surface.&lt;/p&gt;
&lt;h3&gt;Layer 4 -- the countermeasure: masking, and Carry Your Fault&lt;/h3&gt;
&lt;p&gt;The last layer out is the defense itself. If timing and compiler channels can be closed, the remaining physical threat is an attacker with an oscilloscope or an electromagnetic probe against a device in hand. The standard answer is masking.&lt;/p&gt;

Masking splits every secret into $t+1$ random shares whose combination recovers the secret, so that any $t$ probes of the circuit are statistically independent of it -- the guarantee of the Ishai-Sahai-Wagner probing model. Linear operations stay cheap per share; the expensive part in Kyber is the nonlinear Arithmetic-to-Boolean (A2B) conversion needed because the scheme mixes arithmetic modulo $q$ with bitwise operations. Masking is the primary defense against power and electromagnetic analysis [@masking-kyber].
&lt;p&gt;Masking is not busywork or redundancy.Constant-time execution closes the timing channel; masking closes the power and electromagnetic channel. They defend different physics, so a device facing a physical adversary needs both. Neither subsumes the other [@masking-kyber]. First- and higher-order masked implementations of Kyber are a mature line of work [@masking-kyber]. And this is where the frontier does something remarkable: it marches one layer further and turns the countermeasure into the target.&lt;/p&gt;

A fault-injection attack deliberately corrupts a computation -- with a voltage or clock glitch, a laser, or an electromagnetic pulse -- and infers secrets from how the wrong result differs from the right one. It is a different threat from passive measurement: the attacker actively perturbs the device rather than only observing it [@carry-fault].
&lt;p&gt;Carry Your Fault, published by Kundu and colleagues in 2024, does exactly this [@carry-fault]. The A2B conversion that masking requires is built on an adder with a carry chain. Inject a fault into that carry chain -- the very component added to &lt;em&gt;enable&lt;/em&gt; masking -- and its data-dependent propagation leaks information about the secret. Using belief propagation over the faulted values, the attack recovers keys from Kyber masked to &lt;em&gt;arbitrary order&lt;/em&gt;, validated with electromagnetic fault injection on an STM32 microcontroller [@carry-fault]. Read that again: no masking order defeats it, because the attack feeds on a structure that higher-order masking only makes larger.&lt;/p&gt;
&lt;p&gt;It is not an isolated result. Deep-learning side-channel analysis has defeated a masked &lt;em&gt;hardware&lt;/em&gt; implementation of Kyber [@ji-dubrova], and the surveys of the field confirm that the entire documented corpus of attacks on these schemes is implementation-class -- &lt;a href=&quot;https://paragmali.com/blog/correct-constant-time-and-still-owned-a-field-guide-to-side-/&quot; rel=&quot;noopener&quot;&gt;side channel and fault&lt;/a&gt;, not lattice [@sca-survey].&lt;/p&gt;
&lt;p&gt;So count the layers. The instruction leaked (Layer 1). The compiler leaked (Layer 2). The construction defined the surface (Layer 3). The countermeasure for that surface became the surface (Layer 4). Four layers out from the lattice, and the mathematics has not moved a single bit. That is not luck. It is the shape of the problem -- and the shape points straight at the fix.&lt;/p&gt;
&lt;h2&gt;5. The breakthrough: constant-time or it is not secure&lt;/h2&gt;
&lt;p&gt;Step back and the four layers share one address. Every break lives in the gap between what the specification promises about values and what the machine does during execution. The response to that, the actual breakthrough, is not a new theorem. It is a change in what counts as &quot;done.&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The armor that proves CCA security is exactly the code that leaks. So side-channel resistance cannot be an afterthought bolted onto a correct implementation -- it is a property the implementation must be built and verified to have, at the level of the binary that ships. Constant-time or it is not secure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For decades, constant-time execution was treated as a performance-adjacent nicety, a thing careful cryptographers did by hand and hoped the compiler respected. The ML-KEM breaks retire that attitude. The discipline has three prongs: treat data-obliviousness as a hard security requirement; move the guarantee into formally verified implementations; and build tooling that &lt;em&gt;checks&lt;/em&gt; the property instead of trusting it.&lt;/p&gt;
&lt;p&gt;That last prong is where the KyberSlash authors made their most durable contribution. Ordinary constant-time checkers had a blind spot. The lineage runs from Adam Langley&apos;s ctgrind in 2010 [@ctgrind], which repurposed Valgrind&apos;s uninitialized-memory tracking to flag secret-dependent branches, through TIMECOP&apos;s automated sweep of the SUPERCOP benchmark suite [@timecop], to dudect&apos;s black-box statistical timing test [@dudect] and the ct-verif formal verifier [@ctverif].&lt;/p&gt;
&lt;p&gt;But as the TIMECOP authors themselves note, Valgrind &quot;cannot spot cases where variable-time code is caused by variable-time CPU instructions&quot; [@timecop] -- which is precisely what a &lt;code&gt;DIV&lt;/code&gt; is. So the KyberSlash team patched Valgrind to flag variable-time &lt;em&gt;instructions&lt;/em&gt; operating on secret data, and ran it across more than 1000 implementations in SUPERCOP [@kyberslash-paper]. The tool now catches the exact class of bug that produced it.&lt;/p&gt;
&lt;p&gt;Why was the burden on implementers in the first place? Because the standard never claimed otherwise. As Cryspen puts it plainly:&lt;/p&gt;

The goal of the NIST specification is clarity and succinctness, not performance or even security concerns like side-channel resistance. -- Cryspen
&lt;p&gt;Side-channel resistance is the implementer&apos;s responsibility [@cryspen]. The breakthrough is not pretending otherwise -- it is making that responsibility mechanically checkable, so a leak is caught in continuous integration instead of in a paper two years later. And checking is something you can do yourself. The dudect method needs no source access at all: measure two populations of decapsulation timings, one for a fixed input and one for random inputs, and apply Welch&apos;s t-test. A large t-statistic means the timing depends on the data.&lt;/p&gt;
&lt;p&gt;{`
// dudect-style leak test: compare two timing populations with Welch&apos;s t.
// The dudect threshold: |t| above 10 is strong evidence of a timing leak.
// A deterministic generator keeps the demo reproducible.
let seed = 424242;
function rand() {
  seed = (1103515245 * seed + 12345) &amp;amp; 0x7fffffff;
  return seed / 0x7fffffff;
}&lt;/p&gt;
&lt;p&gt;function welchT(a, b) {
  const mean = xs =&amp;gt; xs.reduce((s, x) =&amp;gt; s + x, 0) / xs.length;
  const vary = (xs, m) =&amp;gt; xs.reduce((s, x) =&amp;gt; s + (x - m) * (x - m), 0) / (xs.length - 1);
  const ma = mean(a), mb = mean(b);
  const va = vary(a, ma), vb = vary(b, mb);
  return (ma - mb) / Math.sqrt(va / a.length + vb / b.length);
}&lt;/p&gt;
&lt;p&gt;function measure(n, leaks) {
  const fixedClass = [], randomClass = [];
  for (let i = 0; i &amp;lt; n; i++) {
    const noise = rand() * 2;
    // A leaking build spends extra cycles on the fixed-input class.
    fixedClass.push(100 + noise + (leaks ? 6 : 0));
    randomClass.push(100 + rand() * 2);
  }
  return welchT(fixedClass, randomClass);
}&lt;/p&gt;
&lt;p&gt;console.log(&quot;leaking build     t =&quot;, measure(4000, true).toFixed(1), &quot; -&amp;gt; leak, |t| well above 10&quot;);
console.log(&quot;constant-time build t =&quot;, measure(4000, false).toFixed(1), &quot; -&amp;gt; no leak&quot;);
`}&lt;/p&gt;
&lt;p&gt;Discipline is a goal; a shipped binary is a fact. So how do you actually build an ML-KEM that does not leak, today?&lt;/p&gt;
&lt;h2&gt;6. State of the art: how a correct ML-KEM is built today&lt;/h2&gt;
&lt;p&gt;Here is the good news the catalog has earned: every one of those breaks has a known, shipping answer, and not one of the answers required touching the mathematics. The state of the art is not a single silver bullet. It is a stack of defensive layers, and it is useful to read them from nearest the math outward.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Division-free constant-time source.&lt;/strong&gt; The innermost fix is the one KyberSlash demanded: never divide by a public constant on a secret operand. Replace &lt;code&gt;/KYBER_Q&lt;/code&gt; with a fixed-point reciprocal multiplication -- multiply by a precomputed $\lceil 2^k / q \rceil$ and shift right by $k$ -- which is constant-cycle and frequently &lt;em&gt;faster&lt;/em&gt; than the &lt;code&gt;DIV&lt;/code&gt; it replaces. Replace secret-dependent branches with bit-masks and conditional moves. This is now the default in maintained libraries: the Go standard library&apos;s &lt;code&gt;crypto/mlkem&lt;/code&gt; [@go-mlkem], Google&apos;s BoringSSL, &lt;code&gt;filippo.io/mlkem768&lt;/code&gt;, and the PQClean AVX2 paths all ship division-free constant-time ML-KEM [@kyberslash-site].The vectorized AVX2 and NEON code paths never issued a scalar &lt;code&gt;DIV&lt;/code&gt; on secret data in the first place, so a KyberSlash-immune implementation existed in parallel the entire time [@kyberslash-site]. The bug was in the portable reference, not in the fast paths people actually deployed on servers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Formally verified implementations.&lt;/strong&gt; Constant-time source is necessary but, as Clangover proved, not sufficient. The stronger layer moves the guarantee into the binary. There are two mature routes. The first is verified assembly: the formosa-crypto libjade project, built with Jasmin and EasyCrypt, ships assembly &quot;connected through computer-verified proofs to a cryptographic security notion,&quot; including constant-time proven of the emitted code itself [@libjade]. That closes the exact gap Clangover opened -- there is no optimizing compiler left to betray you -- though its latest release supports only the AMD64 architecture [@libjade].&lt;/p&gt;
&lt;p&gt;The second route is verified Rust: Cryspen&apos;s libcrux uses the hax toolchain and F* to machine-check correctness, memory safety, and source-level secret-independence, reusing verified code from HACL* [@libcrux][@haclstar].&lt;/p&gt;
&lt;p&gt;There is an important honesty in the libcrux documentation, and it is on-thesis. The repository states that &quot;executables compiled from the code in this repository are not verified to be side-channel resistant, although we try to enforce that the source code is secret-independent&quot; [@libcrux]. In other words, libcrux verifies the source, not yet the binary, and it is still pre-release.The verified-Rust effort that produced libcrux is the same effort that surfaced KyberSlash [@cryspen]. The tooling built to prove the code correct is what found the leak first -- the strongest possible argument for verification as a discipline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hardware timing guarantees.&lt;/strong&gt; As a backstop beneath the software, modern processors offer modes that guarantee operand-independent timing for a listed subset of instructions: Arm&apos;s Data-Independent Timing (DIT) register [@arm-dit] and Intel&apos;s Data Operand Independent Timing Mode (DOITM) [@intel-doitm]. These push part of the constant-time burden from the programmer to the silicon.Both are opt-in: the operating system or program must set the register or mode before the guarantee applies, and only the listed instructions are covered. Section 7 returns to their limits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Binary verification tooling.&lt;/strong&gt; Wrapping the whole stack is the checking discipline from Section 5: dudect for black-box statistical timing tests [@dudect], TIMECOP for automated SUPERCOP-wide sweeps [@timecop], ct-verif for formal constant-time verification [@ctverif], and the KyberSlash Valgrind patch for variable-time instructions on secret data [@kyberslash-paper]. Run them against the artifact you ship.&lt;/p&gt;

flowchart TD
    M[&quot;Unbroken Module-LWE mathematics&quot;]
    subgraph timing[&quot;Closes the timing channel&quot;]
        T1[&quot;Division-free constant-time source&quot;]
        T2[&quot;Verified assembly, proven at the binary&quot;]
        T3[&quot;Hardware DIT and DOITM backstop&quot;]
    end
    subgraph power[&quot;Closes the power and EM channel&quot;]
        P1[&quot;Masking with t+1 shares&quot;]
    end
    subgraph fault[&quot;Closes the fault channel&quot;]
        FA[&quot;Redundancy and fault detection&quot;]
    end
    M --&amp;gt; timing
    M --&amp;gt; power
    M --&amp;gt; fault
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Verify the binary you ship, not the source you wrote. KyberSlash lived in a source-level division operator; Clangover lived in compiler output that no source review would catch. Every layer here is ultimately about making a guarantee that holds of the instructions that actually execute [@kyberslash-paper].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Notice what is absent from the entire stack: any change to Module-LWE, any new hardness assumption, any adjustment to the parameter sets. The mathematics was never the problem, so it is not part of the fix. But these layers are not free, and they do not all defend the same thing. Choosing among them is its own problem.&lt;/p&gt;
&lt;h2&gt;7. Competing approaches: what each defense actually buys&lt;/h2&gt;
&lt;p&gt;There is no single winner here, and pretending otherwise is how deployments end up with the wrong protection. Each layer answers a different threat model at a different cost with a different strength of guarantee. Lay them side by side and no column is all-yes.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Defense&lt;/th&gt;
&lt;th&gt;Channel closed&lt;/th&gt;
&lt;th&gt;Guarantee&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Platform scope&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Constant-time source&lt;/td&gt;
&lt;td&gt;Timing&lt;/td&gt;
&lt;td&gt;Self-asserted, source only&lt;/td&gt;
&lt;td&gt;Near zero&lt;/td&gt;
&lt;td&gt;Portable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verified assembly (libjade) [@libjade]&lt;/td&gt;
&lt;td&gt;Timing&lt;/td&gt;
&lt;td&gt;Proven at the binary&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;AMD64 only in latest release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verified Rust (libcrux) [@libcrux]&lt;/td&gt;
&lt;td&gt;Timing (source)&lt;/td&gt;
&lt;td&gt;Machine-checked source, binary not yet claimed&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Portable, pre-release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Masking [@masking-kyber]&lt;/td&gt;
&lt;td&gt;Power and EM&lt;/td&gt;
&lt;td&gt;Probing model of order $t$&lt;/td&gt;
&lt;td&gt;Quadratic, $O(t^2)$&lt;/td&gt;
&lt;td&gt;Portable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault countermeasures [@carry-fault]&lt;/td&gt;
&lt;td&gt;Fault&lt;/td&gt;
&lt;td&gt;Detection and redundancy&lt;/td&gt;
&lt;td&gt;Roughly 2x or more&lt;/td&gt;
&lt;td&gt;Portable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware DIT / DOITM [@arm-dit][@intel-doitm]&lt;/td&gt;
&lt;td&gt;Timing&lt;/td&gt;
&lt;td&gt;Operand-independent for listed instructions&lt;/td&gt;
&lt;td&gt;Near zero&lt;/td&gt;
&lt;td&gt;Specific microarchitectures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Read across the rows and the trade-offs are stark. Constant-time source costs almost nothing but certifies nothing about the binary -- Clangover is the proof. Verified assembly gives the strongest timing guarantee anyone can offer, proven of the shipped code, but libjade&apos;s latest release is AMD64-only [@libjade], and most ML-KEM in the world runs on Arm phones and microcontrollers. Verified Rust is maintainable and machine-checks correctness and safety, but its authors do not yet claim binary-level constant-time [@libcrux].&lt;/p&gt;
&lt;p&gt;The physical-defense rows trade differently. Masking is the &lt;em&gt;only&lt;/em&gt; layer that stops power and electromagnetic differential analysis, but it costs quadratically in the protection order [@masking-kyber] and, as Carry Your Fault showed, does not stop faults at any order [@carry-fault]. Fault countermeasures add redundancy and roughly double the work. Hardware DIT and DOITM are nearly free but cover only listed instructions on specific microarchitectures [@arm-dit][@intel-doitm].&lt;/p&gt;
&lt;p&gt;Which explains why different deployments make opposite choices, and are both right to.&lt;/p&gt;

A server or browser attacker cannot put an oscilloscope on your power rail or fire an electromagnetic pulse at your CPU. For that threat model the whole risk is timing, so a division-free constant-time implementation plus binary scanning is sufficient, and general-purpose stacks like the Go standard library and BoringSSL skip masking entirely [@kyberslash-site]. A smartcard, hardware security module, or embedded device sitting in an adversary&apos;s hand faces power, electromagnetic, and fault attacks directly, so it must add masking and fault countermeasures on top [@masking-kyber][@carry-fault]. Same primitive, same proof, opposite implementations -- because the threat models are opposite.
&lt;p&gt;The deepest tension in the table is between the two things you most want. Formal verification gives the strongest guarantee but is platform-scoped: the proof for x86-64 says nothing about the Arm build. Masking is essential against physical adversaries but is expensive and, alone, incomplete. No single implementation today is verified-constant-time at the binary, masked to high order, fault-resistant, and portable all at once. You assemble the stack from partial guarantees.&lt;/p&gt;
&lt;p&gt;Stack them all and you still hit walls -- and some of those walls are provable. So the honest next question is: what can side channels &lt;em&gt;not&lt;/em&gt; let you close?&lt;/p&gt;
&lt;h2&gt;8. Theoretical limits: what side channels can and cannot close&lt;/h2&gt;
&lt;p&gt;The limits in this story are strikingly asymmetric, and not one of them is a limit on the lattice.&lt;/p&gt;
&lt;p&gt;Timing is the easy one. Secret-independent execution is achievable at essentially zero asymptotic cost, because ML-KEM&apos;s compression divide was never necessary in the first place -- reciprocal multiplication is just as fast and constant-cycle. The &quot;lower bound&quot; on timing safety is therefore not a performance penalty but a matter of discipline plus verification. You can close the timing channel provably and for free.&lt;/p&gt;
&lt;p&gt;Masking is where the hard floors appear. Resisting an adversary who can probe $t$ intermediate values requires at least $t+1$ shares -- the Ishai-Sahai-Wagner probing-model bound -- and Kyber&apos;s nonlinear steps, the Arithmetic-to-Boolean conversions, scale as $\Theta(t^2)$ [@masking-kyber]. High-order masking is genuinely expensive, and no clever engineering buys it cheaply. Then comes the sharpest result of all, the one with the flavor of an impossibility theorem.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Carry Your Fault recovers keys from Kyber masked to arbitrary order [@carry-fault]. There is no protection order you can reach that also buys fault safety, because the attack feeds on the carry chain that masking itself introduces. Masking and fault resistance do not compose for free -- you must add fault countermeasures as a separate layer, and no amount of the first substitutes for the second.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Step back and the shape of the remaining danger becomes clear, and it is not what people expect.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The gap is coverage, not lattice hardness -- and the mathematics was never the problem. The unsolved risks are all gaps in what a guarantee covers: source versus binary (Clangover [@clangover]), platform versus platform (libjade proves AMD64, not your Arm build [@libjade]; DIT and DOITM cover only listed instructions), and channel versus channel (timing is not power is not fault). None of these gaps touches Module-LWE.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That last clause deserves the honesty the series is built on. The series thesis is that the mathematics &lt;em&gt;almost never&lt;/em&gt; causes the break, and &quot;almost never&quot; is doing real work: in other episodes, deployment defects were sometimes structural in a deeper way. For ML-KEM, as of publication, the lattice has simply not been reached -- and the two times anyone reached for it are themselves the strongest evidence.&lt;/p&gt;

In April 2024, Yilei Chen circulated a claimed polynomial-time quantum algorithm for lattice problems. Within days, informal peer review found a flaw: Hongxun Wu and, independently, Thomas Vidick identified a bug in Step 9, and Chen updated the paper to state that the claim does not hold [@chen2024][@quantuminsider]. The author withdrew it himself. In May 2026, Ming-Xing Luo posted a preprint claiming a polynomial-time quantum attack on Module-LWE and ML-KEM [@luo2026]. It has not been peer-reviewed and the author has not withdrawn it -- but named experts identified fatal flaws: Daniel Apon showed the attack&apos;s output is provably independent of the secret key, and Thomas Espitau found its central reduction dimensionally wrong and a supporting result fabricated [@postquantum-luo]. It is a reach that did not land -- and a weaker one than Chen&apos;s, because Chen retracted while Luo&apos;s claim has simply been refuted. ML-KEM is not considered broken.
&lt;p&gt;The genuine lattice question -- whether Module-LWE itself has an efficient attack -- is the subject of the companion article, &quot;How ML-KEM and ML-DSA Would Break: Module-LWE, Lattice Reduction, and the Correlated-Failure Problem.&quot; As of publication it remains open, and open is not the same as broken.&lt;/p&gt;
&lt;p&gt;So the frontier is parked at the countermeasure layer. Not because the mathematics is the next thing to fall, but because closing the last coverage gaps -- binary, platform, channel -- is genuinely unsolved. That is exactly where the research is.&lt;/p&gt;
&lt;h2&gt;9. Open problems: where the frontier is now&lt;/h2&gt;
&lt;p&gt;The unsolved problems in ML-KEM security are, almost without exception, engineering problems. That is itself the thesis, restated as a research agenda.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One artifact, all three guarantees.&lt;/strong&gt; The clearest open problem is a single machine-checked implementation that is provably constant-time &lt;em&gt;and&lt;/em&gt; masked &lt;em&gt;and&lt;/em&gt; fault-resistant, portably, at production scale. Today you assemble those from different tools -- a verified-assembly timing proof here, a masked gadget there, a fault countermeasure bolted on -- and trust that the composition holds. Carry Your Fault is the standing warning that it may not: the pieces can each be correct and still leak at their seams [@carry-fault].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The compiler as an unquantified adversary.&lt;/strong&gt; Clangover is a specific bug in specific Clang versions, but the phenomenon is general [@clangover]. There is no widely deployed guarantee that an optimizing compiler &lt;em&gt;preserves&lt;/em&gt; a source-level constant-time property through arbitrary transformations. &quot;Constant-time preservation&quot; as a compiler discipline is an active research direction, not a solved feature you can switch on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Portable verification.&lt;/strong&gt; The strongest proofs are architecture-bound. Verified assembly covers AMD64; hardware timing modes cover listed instructions on particular cores. Yet the most-deployed ML-KEM in the world runs on Arm phones and Cortex-M microcontrollers -- exactly the targets KyberSlash and Carry Your Fault attacked on real silicon [@carry-fault]. Closing the platform gap between where the proofs are and where the code runs is unsolved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hardware accelerators.&lt;/strong&gt; As ML-KEM moves into dedicated hardware, the side-channel surface moves with it. A masked &lt;em&gt;hardware&lt;/em&gt; implementation of Kyber has already fallen to deep-learning side-channel analysis [@ji-dubrova], and the surveys treat hardware side channels as an open, active area rather than a closed one [@sca-survey].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Migration and &lt;a href=&quot;https://paragmali.com/blog/q-day-has-not-happened-the-incident-already-has-harvest-now-/&quot; rel=&quot;noopener&quot;&gt;captured traffic&lt;/a&gt;.&lt;/strong&gt; The most realistic exposure needs no wild exploit at all.&lt;/p&gt;

An adversary records encrypted traffic today and stores it, betting on decrypting it in the future once cryptanalysis or a quantum computer catches up. Because captured ciphertext cannot be un-sent, any recording made now is a standing liability -- which is the entire motivation for migrating to post-quantum key exchange before a break exists [@cryspen].

Here is the sharp version of why implementation leaks matter even with the mathematics intact. Traffic protected by a hybrid ML-KEM handshake is being recorded now. If the implementation carrying that handshake leaks the key through a side channel, the recorded session is exposed -- no future quantum computer required. A live timing or fault leak in a deployed TLS or SSH path is the realistic threat, and it sits entirely in execution, not in the lattice [@cryspen].
&lt;p&gt;&lt;strong&gt;The lattice itself.&lt;/strong&gt; The one open problem that &lt;em&gt;is&lt;/em&gt; mathematical -- whether Module-LWE has an efficient attack -- is deliberately not this article&apos;s.That question is the whole subject of the companion piece, &quot;How ML-KEM and ML-DSA Would Break,&quot; which takes on how it breaks now, in the implementation, versus how it would break later, in the mathematics. As of publication it is open, and it is handed to the sibling.&lt;/p&gt;
&lt;p&gt;Research aside, you have to ship something on Monday. Here is the decision that survives all of the above.&lt;/p&gt;
&lt;h2&gt;10. What to actually do&lt;/h2&gt;
&lt;p&gt;The one sentence: pick an ML-KEM implementation that never divided on a secret or is formally verified, and verify the binary you actually ship. Everything else is refinement by threat model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;General-purpose software, no local physical adversary.&lt;/strong&gt; If you run on servers, browsers, or backends, an attacker cannot probe your power rail, so timing is your whole threat. Use a division-free constant-time implementation from a maintained source -- the Go standard library&apos;s &lt;code&gt;crypto/mlkem&lt;/code&gt; [@go-mlkem], Google&apos;s BoringSSL, &lt;code&gt;filippo.io/mlkem768&lt;/code&gt;, or an AVX2/NEON path [@kyberslash-site] -- and scan the binary with a patched Valgrind or TIMECOP [@timecop] and dudect [@dudect] in continuous integration. You do not need masking or fault countermeasures for this threat model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maximum timing assurance on x86-64.&lt;/strong&gt; If you want the strongest guarantee available, use verified assembly: libjade&apos;s constant-time property is proven of the emitted code, which is the only thing that closes the Clangover gap outright [@libjade]. Remember its latest release is AMD64-only.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maintainable high-assurance Rust.&lt;/strong&gt; libcrux gives machine-checked correctness and source-level secret-independence [@libcrux], with the caveat that it is pre-release and does not yet claim binary-level constant-time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Embedded, smartcard, or HSM with a physical adversary.&lt;/strong&gt; Stack the layers: division-free constant-time &lt;em&gt;and&lt;/em&gt; masking (sized to your threat, remembering the quadratic cost) &lt;em&gt;and&lt;/em&gt; fault detection (because arbitrary-order masking still falls to Carry Your Fault [@carry-fault]) &lt;em&gt;and&lt;/em&gt; hardware DIT or DOITM where available.&lt;/p&gt;
&lt;p&gt;The library map, at a glance:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Implementation&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Known-good&lt;/td&gt;
&lt;td&gt;Go &lt;code&gt;crypto/mlkem&lt;/code&gt; [@go-mlkem]&lt;/td&gt;
&lt;td&gt;Constant-time, upstreamed from &lt;code&gt;filippo.io/mlkem768&lt;/code&gt;, never divided&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Known-good&lt;/td&gt;
&lt;td&gt;BoringSSL, &lt;code&gt;filippo.io/mlkem768&lt;/code&gt;, PQClean AVX2 [@kyberslash-site]&lt;/td&gt;
&lt;td&gt;Never issued a secret-operand &lt;code&gt;DIV&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Known-good&lt;/td&gt;
&lt;td&gt;formosa-crypto libjade [@libjade]&lt;/td&gt;
&lt;td&gt;Verified-assembly constant-time on AMD64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Known-good, pre-release&lt;/td&gt;
&lt;td&gt;libcrux [@libcrux]&lt;/td&gt;
&lt;td&gt;Verified Rust, source secret-independent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Patched reference&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pq-crystals/kyber&lt;/code&gt; portable C [@kyber-commit]&lt;/td&gt;
&lt;td&gt;Fixed December 2023; downstream forks may still lag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Known-bad&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pqc_kyber&lt;/code&gt; Rust crate [@rustsec]&lt;/td&gt;
&lt;td&gt;RUSTSEC-2023-0079, no patched versions; migrate off it&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; The Rust &lt;code&gt;pqc_kyber&lt;/code&gt; crate carries RUSTSEC-2023-0079 for KyberSlash, rated CVSS 7.4 HIGH, and upstream released no patched versions [@rustsec]. It remains installable today. If it is anywhere in your dependency tree, migrate to the &lt;code&gt;ml-kem&lt;/code&gt; crate or the maintained &lt;code&gt;safe_pqc_kyber&lt;/code&gt; fork [@rustsec]. A deprecated-but-still-live dependency is the whole series thesis in miniature: the mathematics is fine, and the artifact you pulled from the registry is not.&lt;/p&gt;
&lt;/blockquote&gt;

Run `cargo audit` in your project: it flags RUSTSEC-2023-0079 when `pqc_kyber` appears anywhere in the graph [@rustsec]. To find what pulls it in, run `cargo tree -i pqc_kyber`, then swap in the `ml-kem` crate or the `safe_pqc_kyber` fork and re-run the audit until it comes back clean.
&lt;p&gt;Every pitfall in this article maps to a named incident and a concrete fix.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pitfall&lt;/th&gt;
&lt;th&gt;Incident it recreates&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;Dividing a secret by &lt;code&gt;q&lt;/code&gt; in compression or decode&lt;/td&gt;
&lt;td&gt;KyberSlash [@kyberslash-paper]&lt;/td&gt;
&lt;td&gt;Reciprocal multiply; use a never-divided or AVX2 path; scan with patched Valgrind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trusting constant-time C through the compiler&lt;/td&gt;
&lt;td&gt;Clangover [@clangover]&lt;/td&gt;
&lt;td&gt;Verify the binary; prefer verified assembly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Masking without fault countermeasures&lt;/td&gt;
&lt;td&gt;Carry Your Fault [@carry-fault]&lt;/td&gt;
&lt;td&gt;Add fault detection and redundancy as a separate layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Depending on an unpatched crate&lt;/td&gt;
&lt;td&gt;RUSTSEC-2023-0079 [@rustsec]&lt;/td&gt;
&lt;td&gt;Migrate to &lt;code&gt;ml-kem&lt;/code&gt; or &lt;code&gt;safe_pqc_kyber&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Finally, deploy hybrid.The hybrid group &lt;code&gt;X25519MLKEM768&lt;/code&gt; runs a classical elliptic-curve exchange and ML-KEM together. By construction the session stays secure as long as either component does [@tls-hybrid], so it hedges a future lattice break and a present implementation bug in the other component at the same time. A hybrid handshake is the one move that protects you against both the break that has not happened to the math and the break that already happened to the code.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Ship a division-free or formally verified ML-KEM, verify the binary you actually deploy, add masking and fault countermeasures only when a physical adversary can touch the device, and deploy hybrid so you stay secure if either half holds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Do that and you have closed every gap anyone has actually walked through. The questions that remain are the ones readers always ask.&lt;/p&gt;
&lt;h2&gt;11. Frequently asked questions&lt;/h2&gt;
&lt;p&gt;A few misconceptions come up every time this material is discussed. Here they are, answered against the primary sources.&lt;/p&gt;


No. As of publication its Module-LWE mathematics is intact and its IND-CCA2 security has been machine-checked [@fips203][@fvk]. Every documented break is an implementation or side-channel attack against specific code -- a division instruction, a compiler optimization, a fault in a countermeasure -- not a weakness in the primitive itself.


No. Two prominent claims reached for the lattice mathematics and both failed review. Yilei Chen&apos;s 2024 quantum algorithm was withdrawn after Hongxun Wu and Thomas Vidick found a bug in Step 9 [@chen2024]. A 2026 preprint by Ming-Xing Luo was refuted by named experts, who showed the attack&apos;s output is provably independent of the secret key [@luo2026][@postquantum-luo]. No quantum machine has broken ML-KEM, and none is known to exist that could.


It has a CVE-class advisory but no NVD number. The record is RUSTSEC-2023-0079 against the `pqc_kyber` crate, rated CVSS 7.4 HIGH [@rustsec]. The disclosure and reference patches were December 2023 [@kyber-commit], and the full paper appeared at IACR TCHES in 2025 [@kyberslash-paper].


Not by construction. A re-encryption mismatch returns a pseudorandom shared secret, not an error, so there is no caller-visible rejection to query [@fips203]. The oracle in every real attack is a side-channel leak in the re-encryption and comparison -- timing, power, electromagnetic, or fault -- never a signal the protocol emits.


There is no documented in-the-wild exploitation of a production ML-KEM deployment as of publication. The breaks are vulnerable code shipping in libraries, CVE-class advisories, and researcher proof-of-concept key recovery on real hardware [@kyberslash-paper].


No. Masking stops power and electromagnetic analysis but not faults: Carry Your Fault recovers keys from masking of arbitrary order [@carry-fault], and masked hardware has fallen to deep-learning side-channel analysis. A device facing a physical adversary needs constant-time execution and masking and fault countermeasures together.


Yes, with care. Pick an implementation that never divided on a secret or is formally verified, verify the binary you ship, add masking and fault countermeasures only for physical-adversary threat models, and deploy a hybrid group such as `X25519MLKEM768` so you stay secure if either half holds [@tls-hybrid].

&lt;p&gt;Count the layers one last time. The instruction leaked; the compiler leaked; the construction defined the surface; the countermeasure became the surface. Four steps outward from the lattice, and the Module-LWE mathematics never moved -- its security proof machine-checked, its two attempted quantum breaks both failed under expert scrutiny (Chen withdrew his claim; Luo&apos;s was refuted by named experts) [@fvk][@chen2024][@postquantum-luo]. The mathematics almost never caused the break. The deployment did: the reference division, the compiler&apos;s optimization, the unpatched crate, the fault through the mask. The frontier is still marching outward, and the day it finally reaches the lattice is the subject of the companion article, &quot;How ML-KEM and ML-DSA Would Break.&quot; Until then, the same law that spared the AES cipher for two decades while its implementations leaked keys to a stopwatch now governs the first post-quantum KEM.&lt;/p&gt;

The machine leaked while the math held. That is not a footnote to ML-KEM&apos;s security -- as of publication, it is the entire story of how it breaks.
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;how-ml-kem-breaks-in-real-life&quot; keyTerms={[
  { term: &quot;Key Encapsulation Mechanism (KEM)&quot;, definition: &quot;A protocol for agreeing on a shared secret; ML-KEM is the FIPS 203 standard.&quot; },
  { term: &quot;Module-LWE&quot;, definition: &quot;The lattice problem ML-KEM&apos;s security rests on; unbroken as of publication.&quot; },
  { term: &quot;Fujisaki-Okamoto transform&quot;, definition: &quot;The re-encrypt-and-compare construction that makes ML-KEM CCA-secure.&quot; },
  { term: &quot;Implicit rejection&quot;, definition: &quot;On a re-encryption mismatch, decapsulation returns a pseudorandom secret, not an error.&quot; },
  { term: &quot;KyberSlash&quot;, definition: &quot;A December 2023 secret-dependent division-timing leak in reference Kyber code.&quot; },
  { term: &quot;Clangover&quot;, definition: &quot;A compiler-introduced secret branch that defeats source-level constant-time code.&quot; },
  { term: &quot;Plaintext-checking oracle&quot;, definition: &quot;A pass-or-fail side-channel leak that reconstructs a reused secret key.&quot; },
  { term: &quot;Constant-time execution&quot;, definition: &quot;Behavior independent of secret data; a property of the shipped binary, not the source.&quot; },
  { term: &quot;Masking&quot;, definition: &quot;Splitting secrets into t+1 shares to resist power and electromagnetic analysis.&quot; },
  { term: &quot;Carry Your Fault&quot;, definition: &quot;A fault on the A2B carry chain that defeats masking of arbitrary order.&quot; }
]} questions={[
  { q: &quot;Why does a machine-checked security proof not prevent key recovery?&quot;, a: &quot;The proof constrains values, not execution; keys leak through timing, compiler output, power, or faults.&quot; },
  { q: &quot;What single surface underlies KyberSlash, Clangover, and the PC-oracle attacks?&quot;, a: &quot;The Fujisaki-Okamoto re-encrypt-and-compare step, which must run on secret-derived data.&quot; },
  { q: &quot;Does masking alone protect an embedded device?&quot;, a: &quot;No; it stops power and EM analysis but not faults, so fault countermeasures are also required.&quot; },
  { q: &quot;What deployment hedges both a math break and an implementation bug?&quot;, a: &quot;A hybrid handshake such as X25519MLKEM768, which stays secure if either component holds.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>post-quantum-cryptography</category><category>ml-kem</category><category>kyber</category><category>side-channel-attacks</category><category>constant-time</category><category>kyberslash</category><category>fujisaki-okamoto</category><category>implementation-security</category><author>noreply@paragmali.com (Parag Mali)</author></item><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 Curve Was Hard; The Gap Was Soft: A Field Guide to Using Elliptic Curves Safely</title><link>https://paragmali.com/blog/the-curve-was-hard-the-gap-was-soft-a-field-guide-to-using-e/</link><guid isPermaLink="true">https://paragmali.com/blog/the-curve-was-hard-the-gap-was-soft-a-field-guide-to-using-e/</guid><description>Textbook ECC is hard; deployed ECC broke anyway. A field guide to P-256, Curve25519, Ristretto255, invalid-curve attacks, cofactors, and constant-time.</description><pubDate>Sat, 11 Jul 2026 16:08:14 GMT</pubDate><content:encoded>
**The elliptic-curve discrete-log problem was never the weak point.** On a well-chosen curve, ECDLP is the hardest discrete log we know how to build -- which is why a 256-bit curve buys what a 3072-bit RSA modulus does -- and yet across three decades of deployment, almost no real-world break ever solved one. They did not have to. Every named disaster lives in the gap between a hard curve and safe code: the server that fed an attacker&apos;s off-curve point into scalar multiplication and leaked its private key (invalid-curve attacks); the low-order point that clamping never filtered (the Monero cofactor bug); the certificate whose *base point* was never checked (Curveball, CVE-2020-0601); the microsecond of branch timing in a non-constant-time ladder (Brumley-Tuveri). The evolution of curves -- NIST short-Weierstrass (P-256, secp256k1), then Curve25519/X25519, then Ed25519, then the Ristretto255 prime-order abstraction -- is one sustained move: take the safety burden off the implementer and bake it into the curve, the group, and the encoding, so the simple implementation becomes the secure one. Using elliptic curves safely is a stack: the right curve for the job (X25519 for key agreement, Ed25519 for new signatures, P-256 only where FIPS compels it, secp256k1 only for blockchain), validate every untrusted point, multiply in constant time, and prefer constructions where the dangerous case cannot be reached. Remove any layer and the gap reopens.
&lt;h2&gt;1. Two Lines of Code&lt;/h2&gt;
&lt;p&gt;In January 2020, Microsoft patched a vulnerability the U.S. National Security Agency had quietly reported to it -- an unusually public gesture from an agency that normally hoards such findings. The bug was almost embarrassing in its simplicity. Windows would check a TLS certificate&apos;s elliptic-curve signature against a trusted root, and it would never check the curve&apos;s base point. If you supplied a certificate that carried its own curve parameters -- with a generator you had chosen yourself -- Windows would trust the forged chain as though a real &lt;a href=&quot;https://paragmali.com/blog/a-perfect-signature-for-a-certificate-that-should-never-have/&quot; rel=&quot;noopener&quot;&gt;certificate authority&lt;/a&gt; had signed it [@cve-2020-0601]. Nobody factored a modulus. Nobody solved a discrete logarithm. The math of P-256 stood untouched. The attacker handed the code one parameter it forgot to validate, and the industry named the result Curveball.&lt;/p&gt;
&lt;p&gt;Now go one layer down and back five years. In 2015, researchers recovered a TLS server&apos;s ECDH &lt;em&gt;private key&lt;/em&gt; -- the actual secret -- by feeding it points that were not on the intended curve at all. The server&apos;s own scalar-multiplication routine did the leaking, one small residue at a time, until the whole key fell out [@jager-2015]. Two breaks, at two different layers of the same stack, with exactly one shape. In neither case did anyone attack the problem the textbooks call hard.&lt;/p&gt;
&lt;p&gt;That is the claim this article proves. &lt;strong&gt;ECDLP-hardness is not the same thing as ECC-security, and every deployed elliptic-curve break lives in the gap between them.&lt;/strong&gt; The discrete logarithm on a good curve is the strongest one-way function in wide use. The security of a system built on it is a different property entirely -- one that depends on what your code does with a point before it has decided the point is legitimate, and whether it does the arithmetic in a time an attacker cannot measure.&lt;/p&gt;

The private curve stayed hard; the code around it confessed.
&lt;p&gt;So here is the diagnostic question the rest of this piece hands you, the one to ask of any elliptic-curve code you own: &lt;em&gt;when a point you did not generate arrives, what does your code do with it before it has checked that the point is on the intended curve and in the right prime-order subgroup -- and does it do that arithmetic in constant time?&lt;/em&gt; Hold that question. The names you already fear -- invalid-curve attacks, Curveball, twist attacks, small-subgroup leaks, the cofactor, timing side channels -- are all one sentence underneath it. One mechanism explains the entire catalog, and the thirty-year evolution of curves is a single sustained campaign to put that mechanism out of reach.&lt;/p&gt;
&lt;p&gt;To see how an unchecked point hands over a private key -- and why &quot;just use Curve25519&quot; is a real answer but not the whole one -- you first have to see what an elliptic curve actually &lt;em&gt;is&lt;/em&gt;, and what makes its discrete log hard.&lt;/p&gt;
&lt;h2&gt;2. What an Elliptic Curve Actually Is, and Where It Came From&lt;/h2&gt;
&lt;p&gt;In 1976, Whitfield Diffie and Martin Hellman handed the world a template rather than a cryptosystem [@diffie-hellman-1976]. Pick a mathematical group in which one operation is easy to compute and its inverse is hard. Publish the result of applying that operation $k$ times to a fixed element -- call it $[k]G$. Keep $k$ secret. Anyone can combine public values to agree on a shared secret; no eavesdropper can run the operation backward. The template was sound. The group they had, the integers modulo a large prime, was not thrifty: because sub-exponential algorithms (index calculus) attack discrete logs in that group, safety demanded enormous keys. The obvious question hung in the air for nine years. What if you kept the template and changed the group?&lt;/p&gt;
&lt;h3&gt;Two inventors, no shared citation&lt;/h3&gt;
&lt;p&gt;The answer arrived twice, independently, from two people who did not cite each other. Victor Miller proposed it at CRYPTO &apos;85 [@miller-1985]; Neal Koblitz published the same idea in &lt;em&gt;Mathematics of Computation&lt;/em&gt; in 1987 [@koblitz-1987]. Both suggested using the group of points on an elliptic curve over a finite field. The decisive property: for a well-chosen curve, the best algorithm anyone knows is &lt;em&gt;generic&lt;/em&gt; -- it works in any group and costs about $\sqrt{n}$ operations for a group of size $n$ -- rather than the sub-exponential index calculus that makes ordinary discrete logs cheap. No shortcut specific to the curve exists. That single absence is the whole reason elliptic-curve cryptography exists.Miller (1985) and Koblitz (1987) proposed elliptic-curve cryptography independently and neither cited the other. It is one of cryptography&apos;s cleaner cases of simultaneous invention -- the idea was ready to be found.&lt;/p&gt;
&lt;h3&gt;The primitive, kept tight&lt;/h3&gt;
&lt;p&gt;Here is the object itself, because this &lt;em&gt;is&lt;/em&gt; the primitive and everything later depends on reading it correctly. Take the equation&lt;/p&gt;
&lt;p&gt;$$y^2 = x^3 + ax + b$$&lt;/p&gt;
&lt;p&gt;over a finite field $\mathbb{F}_p$ (the integers modulo a prime $p$). The set of solutions $(x, y)$, together with one extra &quot;point at infinity&quot; that acts as a zero, forms a finite abelian group. The group operation is a geometric rule called chord-and-tangent addition: to add two points, draw the line through them, find the third place it meets the curve, and reflect over the $x$-axis. To double a point, use the tangent line instead. The rule looks whimsical and turns out to be associative -- the proof runs through divisors and Riemann-Roch, which we cheerfully defer to Silverman.&lt;/p&gt;

Repeated point addition: adding a base point G to itself k times, written [k]G. Computed in about log k doublings and additions by the double-and-add method, it is the easy, forward direction of the elliptic-curve trapdoor -- fast to compute, believed hard to reverse.
&lt;p&gt;Scalar multiplication is that forward direction: $[k]G = G + G + \dots + G$, $k$ times. Because you can double repeatedly, computing $[k]G$ for a 256-bit $k$ takes a few hundred operations, not $2^{256}$ of them. Reversing it -- recovering $k$ from $G$ and $Q = [k]G$ -- is the hard problem the whole field rests on.&lt;/p&gt;

Given a base point G on an elliptic curve and a second point Q = [k]G, recover the scalar k. This is the one-way assumption elliptic-curve cryptography depends on: easy to compute [k]G in the forward direction, believed infeasible to invert for a well-chosen curve.
&lt;p&gt;Feel the asymmetry directly. The demonstration below computes $[13]G$ on a tiny curve in a handful of steps, then tries to walk it back. At these toy sizes the reversal is trivial; at 256 bits the same reversal is roughly $2^{128}$ operations, which is why the forward arrow is a trapdoor and the backward arrow is a wall.&lt;/p&gt;
&lt;p&gt;{`
// Toy curve y^2 = x^3 + 2x + 2 over F_17. Illustrative sizes, NOT secure.
const p = 17n, a = 2n;
function mod(x){ return ((x % p) + p) % p; }
function inv(x){ // modular inverse via Fermat: x^(p-2) mod p
  let r = 1n, b = mod(x), e = p - 2n;
  while (e &amp;gt; 0n){ if (e &amp;amp; 1n) r = mod(r&lt;em&gt;b); b = mod(b&lt;/em&gt;b); e &amp;gt;&amp;gt;= 1n; }
  return r;
}
function add(P, Q){
  if (P === null) return Q;
  if (Q === null) return P;
  const [x1,y1] = P, [x2,y2] = Q;
  if (x1 === x2 &amp;amp;&amp;amp; mod(y1 + y2) === 0n) return null; // P + (-P) = O
  let s;
  if (x1 === x2 &amp;amp;&amp;amp; y1 === y2) s = mod((3n&lt;em&gt;x1&lt;/em&gt;x1 + a) * inv(2n&lt;em&gt;y1)); // tangent (double)
  else s = mod((y2 - y1) * inv(x2 - x1));                          // chord (add)
  const x3 = mod(s&lt;/em&gt;s - x1 - x2);
  const y3 = mod(s*(x1 - x3) - y1);
  return [x3, y3];
}
function mul(k, G){ // double-and-add: the FORWARD (easy) direction
  let R = null, base = G;
  while (k &amp;gt; 0n){ if (k &amp;amp; 1n) R = add(R, base); base = add(base, base); k &amp;gt;&amp;gt;= 1n; }
  return R;
}
const G = [5n, 1n];         // generator; this subgroup has prime order 19
const secret = 13n;         // the &quot;private key&quot;
const Q = mul(secret, G);   // the &quot;public key&quot; [13]G, computed in ~5 steps
console.log(&apos;public point [13]G =&apos;, Q[0].toString() + &apos;,&apos; + Q[1].toString());&lt;/p&gt;
&lt;p&gt;// Now INVERT it: recover the secret from G and Q. The only general method on a
// real curve is search -- trivial here, about 2^128 work at 256 bits.
let found = null, R = null;
for (let k = 1n; k &amp;lt;= 19n; k++){
  R = add(R, G);
  if (R &amp;amp;&amp;amp; R[0] === Q[0] &amp;amp;&amp;amp; R[1] === Q[1]){ found = k; break; }
}
console.log(&apos;recovered secret by brute force =&apos;, found.toString());
`}&lt;/p&gt;
&lt;h3&gt;Why the keys shrink&lt;/h3&gt;
&lt;p&gt;That missing shortcut is not an academic footnote; it is the entire economic case for the primitive. For discrete logs modulo a prime, and for RSA, index-calculus methods run in sub-exponential time, so defenders must inflate the modulus to stay ahead -- 3072 bits to reach roughly 128-bit security. On a well-chosen prime-field curve, no such method is known, so the attacker is stuck paying the generic $\sqrt{n}$ price. A 256-bit curve therefore delivers about 128 bits of security [@miller-1985], [@safecurves-rho]. Same protection, an order of magnitude smaller keys, faster operations. Hold onto one detail for later: this economic win, the thing everyone remembers about elliptic curves, is the one property that has &lt;em&gt;never&lt;/em&gt; been what broke.The absence of a sub-exponential attack on prime-field curves is the whole argument. It is why 256-bit ECC stands in for a 3072-bit RSA key -- and it is a statement about what we do not know how to do, not a theorem.&lt;/p&gt;
&lt;h3&gt;One clean curve, and the seeds of trouble&lt;/h3&gt;
&lt;p&gt;Standardization made the primitive interoperable. ECDSA was standardized as a digital-signature algorithm (ANSI X9.62) and folded into NIST&apos;s FIPS 186 series [@fips-186-5]; the P-curve we still call P-256 dates to FIPS 186-2 in 2000. The Standards for Efficient Cryptography Group published the matching &lt;code&gt;secp&lt;/code&gt; parameter sets -- including secp256r1, which &lt;em&gt;is&lt;/em&gt; P-256, and its efficiency-minded sibling secp256k1 [@sec2-v2]. The result was a single, clean, interoperable curve with prime order and a genuine strength most of its successors would come to envy. Which raises the puzzle that drives the next two sections: if P-256 is that good, why do the very people who designed its replacement consider &quot;just implement P-256&quot; to be dangerous advice?&lt;/p&gt;
&lt;h2&gt;3. The NIST Curves, and What They Cost&lt;/h2&gt;
&lt;p&gt;P-256 did one big thing exactly right, and three smaller things in a way that made the &lt;em&gt;simple&lt;/em&gt; implementation &lt;em&gt;insecure&lt;/em&gt;. Both halves are true at once, and holding them together is the beginning of expertise here.&lt;/p&gt;
&lt;h3&gt;What went right&lt;/h3&gt;
&lt;p&gt;P-256 is the curve $y^2 = x^3 - 3x + b$ over the special prime $p = 2^{256} - 2^{224} + 2^{192} + 2^{96} - 1$, and its single most underrated property is boring: its group has &lt;strong&gt;prime order&lt;/strong&gt;, cofactor 1 [@sec2-v2]. There is no smaller subgroup for an attacker to trap you in, no cofactor to clear, no torsion points hiding in the corners. Add to that an enormous interoperable deployment -- P-256 is the elliptic curve behind most of the web&apos;s ECC certificates [@ssl-pulse] -- and you have a curve whose &lt;em&gt;group-level&lt;/em&gt; security is excellent. Remember this when Curve25519 arrives two sections from now carrying a cofactor of 8.&lt;/p&gt;

The ratio of a curve&apos;s full group order to the prime order of the subgroup used for cryptography. P-256 has cofactor 1 (its whole group has prime order), while edwards25519 has cofactor 8, meaning small-order &quot;torsion&quot; points exist alongside the useful prime-order subgroup and must be handled with care.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Cofactor 1 means P-256 has no small-order subgroups at all. That single fact rules out an entire family of attacks at the group level. Keep it in mind when the next generation trades it away for other advantages -- prime order is exactly what Curve25519 gives up and what Ristretto255 later works hard to rebuild.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Cost one: nobody can explain the seed&lt;/h3&gt;
&lt;p&gt;The coefficient $b$ and the base point were produced from a seed run through SHA-1 -- &quot;verifiably random,&quot; in the standard&apos;s language, meaning anyone can rerun the hash and confirm the parameters were not obviously cooked. The catch is that the &lt;em&gt;seed itself&lt;/em&gt; is an unexplained string of bytes. Nobody outside its authors can say why that seed and not another. SafeCurves calls this failure &lt;strong&gt;rigidity&lt;/strong&gt;: a curve is rigid only when its parameters follow from stated principles with no unexplained freedom, and the NIST P-curves are not [@safecurves-index]. This is a trust concern, not a break -- but trust is a load-bearing beam, as the next beat shows.The P-256 parameters are &quot;verifiably random&quot; from a seed, yet the seed is never justified. That gap -- reproducible but unexplained -- is exactly what rigidity objects to. You can check the arithmetic and still not know why these constants.&lt;/p&gt;
&lt;h3&gt;Cost two: the addition law has holes&lt;/h3&gt;
&lt;p&gt;The classic short-Weierstrass addition formulas are not total. They have exceptional cases -- adding a point to its own negation, doubling a point whose $y$-coordinate is zero, anything involving the point at infinity -- where the generic slope formula divides by zero and returns nonsense. A working implementation must branch around those cases. And a branch whose direction depends on secret data is either wrong on the rare input that hits it, or observable through timing, or both. This incomplete addition law is the seed of the timing break we meet in the next section.&lt;/p&gt;
&lt;h3&gt;Cost three: the formula never looks at b&lt;/h3&gt;
&lt;p&gt;Here is the sharp one. Look back at the addition code in Section 2 and notice what is missing: the coefficient $b$ never appears. The short-Weierstrass scalar-multiplication formulas depend on $a$, on the coordinates, on the prime -- but not on $b$. So if you hand the routine a point that satisfies some &lt;em&gt;other&lt;/em&gt; curve $y^2 = x^3 + ax + c$ with a different constant term, the arithmetic proceeds without complaint, silently computing on that other, possibly far weaker, curve [@safecurves-twist]. The curve has no twist security to catch the mistake. This omission is the seed of the invalid-curve break, and we detonate it in Section 4.&lt;/p&gt;
&lt;h3&gt;The Dual EC shadow&lt;/h3&gt;
&lt;p&gt;There is one more reason the community distrusts NIST-supplied constants, and precision matters because it is widely misremembered. In 2007, Dan Shumow and Niels Ferguson showed that the Dual EC DRBG random-number generator &lt;em&gt;could&lt;/em&gt; conceal a back door: whoever chose its two special points might hold a secret relationship between them that predicts the generator&apos;s output [@shumow-ferguson-2007]. Edward Snowden&apos;s 2013 disclosures and, in 2015, a backdoor found inside Juniper&apos;s ScreenOS gave that theoretical worry a concrete afterlife.&lt;/p&gt;
&lt;p&gt;But be exact about what this does and does not implicate. &lt;strong&gt;No weakness has ever been demonstrated in the P-256 curve itself.&lt;/strong&gt; Dual EC poisoned trust in &lt;em&gt;NIST-supplied constants as a category&lt;/em&gt; -- which is precisely why so many engineers now prefer rigid curves -- but it is a trust-and-rigidity argument, not a proven backdoor in the P-curves.Dual EC DRBG is a random-number-generator story, not a P-curve story. It shows NIST-chosen constants &lt;em&gt;could&lt;/em&gt; be poisoned; it does not show that P-256&apos;s were. Conflating the two overstates the case and is a common error. And notice the shape already forming: prime-order, FIPS-mandated, mathematically strong -- yet the &lt;em&gt;simple&lt;/em&gt; implementation is the insecure one.&lt;/p&gt;
&lt;h3&gt;The efficiency sibling&lt;/h3&gt;
&lt;p&gt;One more curve belongs on the table. secp256k1 is the SECG&apos;s efficiency variant: the curve $y^2 = x^3 + 7$ over $p = 2^{256} - 2^{32} - 977$, with $j$-invariant 0 and an endomorphism (the GLV method) that speeds scalar multiplication [@sec2-v2]. It is the curve Bitcoin chose, and later the base for Taproot&apos;s BIP-340 Schnorr signatures [@bip-340]. Keep its domain straight throughout this article: secp256k1 owns the blockchain world and is essentially absent from web PKI and TLS -- a blockchain-interoperability answer, never a general-purpose competitor to the curves that carry your HTTPS traffic.&lt;/p&gt;
&lt;p&gt;Two of those three costs -- the unused $b$ and the branchy ladder -- are not abstractions. Each became a remote private-key recovery against real servers. To see how, you need the one mechanism that sits underneath the entire failure catalog.&lt;/p&gt;
&lt;h2&gt;4. The Failure Catalog: One Atom, Many Surfaces&lt;/h2&gt;
&lt;p&gt;Here is the mechanism the whole catalog reuses. Call it the atom: &lt;em&gt;a point you did not generate, fed into scalar multiplication before you validate it -- or multiplied in variable time -- is a key-recovery oracle.&lt;/em&gt; The discrete logarithm is never solved. The implementation simply computes on the attacker&apos;s terms and leaks the answer, one small piece at a time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The atom of every deployed elliptic-curve break: a point you did not generate, used before it is validated -- or a secret scalar multiplied in a time an attacker can measure -- turns your own scalar-multiplication routine into an oracle that hands back the private key. ECDLP stays intact; the code around it does the leaking.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Deriving the atom: invalid-curve attacks&lt;/h3&gt;
&lt;p&gt;Recall Cost three from the last section: the short-Weierstrass formulas never read $b$. Ingrid Biehl, Bernd Meyer, and Volker Muller turned that omission into an attack at CRYPTO 2000 [@biehl-meyer-muller-2000]. Send the victim a point that satisfies a &lt;em&gt;different&lt;/em&gt; curve $y^2 = x^3 + ax + c$ -- one you chose specifically because its group has small-order subgroups. The victim&apos;s routine, blind to the constant term, computes your scalar multiple on that weak curve. Its response now lives in a small subgroup, so it reveals the secret scalar modulo a small number. SafeCurves states the mechanism about as plainly as it can be stated:&lt;/p&gt;

The standard formulas for scalar multiplication on short Weierstrass curves do not involve the constant coefficient b, so they automatically also work for y^2 = x^3 + ax + c ... Bob will successfully compute n(x,y) without realizing that anything is amiss.

Feeding an implementation a point that is not on the intended curve into scalar-multiplication formulas that ignore the coefficient b. The operation runs on a different, attacker-chosen curve whose group has small subgroups, so the response leaks the private scalar modulo small numbers -- with the intended curve&apos;s discrete-log hardness never touched.

flowchart TD
    A[&quot;Attacker sends a point not on the intended curve&quot;] --&amp;gt; B[&quot;Scalar-multiply formula ignores coefficient b&quot;]
    B --&amp;gt; C[&quot;Arithmetic runs on a different, weaker curve&quot;]
    C --&amp;gt; D[&quot;That curve has small-order subgroups&quot;]
    D --&amp;gt; E[&quot;Response reveals the secret modulo a small number&quot;]
    E --&amp;gt; F[&quot;Repeat with several coprime small orders&quot;]
    F --&amp;gt; G[&quot;Chinese Remainder Theorem reassembles the whole key&quot;]
&lt;p&gt;The demonstration below runs the leak on a toy curve whose order factors as $105 = 3 \times 5 \times 7$. The attacker sends three low-order points, harvests three residues, and the Chinese Remainder Theorem stitches them into the secret. Nothing here is a discrete-log computation; it is bookkeeping.&lt;/p&gt;
&lt;p&gt;{`
// Toy curve y^2 = x^3 + x + 1 over F_101. Group order 105 = 3&lt;em&gt;5&lt;/em&gt;7.
// Illustrative ONLY -- a real curve has PRIME order and ~2^256 points.
const p = 101n, a = 1n;
const mod = x =&amp;gt; ((x % p) + p) % p;
function inv(x){ let r=1n,b=mod(x),e=p-2n; while(e&amp;gt;0n){ if(e&amp;amp;1n) r=mod(r&lt;em&gt;b); b=mod(b&lt;/em&gt;b); e&amp;gt;&amp;gt;=1n;} return r; }
function add(P,Q){
  if(P===null) return Q; if(Q===null) return P;
  const [x1,y1]=P,[x2,y2]=Q;
  if(x1===x2 &amp;amp;&amp;amp; mod(y1+y2)===0n) return null;      // P + (-P) = O
  const s = (x1===x2 &amp;amp;&amp;amp; y1===y2)
    ? mod((3n&lt;em&gt;x1&lt;/em&gt;x1+a)&lt;em&gt;inv(2n&lt;/em&gt;y1))                 // tangent (double)
    : mod((y2-y1)&lt;em&gt;inv(x2-x1));                     // chord (add)
  const x3 = mod(s&lt;/em&gt;s-x1-x2);
  return [x3, mod(s*(x1-x3)-y1)];
}
function mul(k,P){ let R=null,b=P; while(k&amp;gt;0n){ if(k&amp;amp;1n) R=add(R,b); b=add(b,b); k&amp;gt;&amp;gt;=1n;} return R; }&lt;/p&gt;
&lt;p&gt;const k = 47n;                    // the victim&apos;s PRIVATE scalar
// Attacker-crafted low-order points (orders 3, 5, 7). None is the real base point.
const traps = [ {P:[28n,8n], t:3n}, {P:[86n,67n], t:5n}, {P:[72n,5n], t:7n} ];&lt;/p&gt;
&lt;p&gt;const residues = [], moduli = [];
for (const {P,t} of traps){
  const Q = mul(k, P);            // victim naively returns [k]P = [k mod t]P
  let R = null;
  for (let i=0n;i&amp;lt;t;i++){         // attacker matches against the tiny orbit
    const same = (R===null&amp;amp;&amp;amp;Q===null) || (R&amp;amp;&amp;amp;Q&amp;amp;&amp;amp;R[0]===Q[0]&amp;amp;&amp;amp;R[1]===Q[1]);
    if (same){ residues.push(i); moduli.push(t); break; }
    R = add(R,P);
  }
}
console.log(&apos;leaked:&apos;, residues.map((r,i)=&amp;gt;&apos;k mod &apos;+moduli[i]+&apos; = &apos;+r).join(&apos;,  &apos;));&lt;/p&gt;
&lt;p&gt;// Chinese Remainder Theorem stitches the residues into k mod 105.
let M=1n; for(const m of moduli) M*=m;
let x=0n;
for(let i=0;i&amp;lt;residues.length;i++){
  const Mi=M/moduli[i];
  let mi=1n; for(let j=1n;j&amp;lt;moduli[i];j++){ if((Mi*j)%moduli[i]===1n){ mi=j; break; } }
  x += residues[i]&lt;em&gt;Mi&lt;/em&gt;mi;
}
console.log(&apos;CRT-reassembled secret =&apos;, (((x%M)+M)%M).toString(), &apos; (true k = &apos;+k+&apos;)&apos;);
`}&lt;/p&gt;
&lt;h3&gt;Surface one: small-subgroup attacks&lt;/h3&gt;
&lt;p&gt;You do not even need to leave the intended curve to run the atom. Chae Hoon Lim and Pil Joong Lee showed this at CRYPTO &apos;97 [@lim-lee-1997]. If the curve&apos;s group is not of prime order -- if it has small-order subgroups -- then an attacker submits an element of small order $t$, and the victim&apos;s response is forced into that tiny subgroup, revealing the secret modulo $t$. Collect a few coprime $t$ values, apply the Chinese Remainder Theorem, and the full scalar falls out. This is the conceptual root of the rule &quot;validate the subgroup, not just the curve,&quot; and it is exactly why prime order mattered so much back in Section 3. It also plants a flag we return to: any curve with a cofactor greater than 1 has these small subgroups by construction.&lt;/p&gt;

Submitting a group element of small order so that the victim&apos;s scalar multiplication lands in a tiny subgroup, making the response reveal the secret scalar modulo that small order. Repeated across several coprime small orders, the Chinese Remainder Theorem reassembles the full private key. It is the reason implementations must check that inputs lie in the correct prime-order subgroup.
&lt;h3&gt;Surface two: the atom made remote&lt;/h3&gt;
&lt;p&gt;Theory became a remote server compromise in 2015, when Tibor Jager, Jorg Schwenk, and Juraj Somorovsky recovered real TLS servers&apos; ECDH private keys [@jager-2015]. The conditions were mundane: a server that reused a single ECDH key pair across handshakes and did not validate the incoming peer point. Each handshake fed the server another attacker-chosen invalid point; each response leaked another small residue; a few thousand handshakes later, the CRT delivered the private key. No discrete logarithm was ever computed. This is the atom of Section 4, wearing a TLS badge.The practical TLS result is Jager, Schwenk, and Somorovsky&apos;s ESORICS 2015 paper, &quot;Practical Invalid Curve Attacks on TLS-ECDH&quot; -- distinct from the same trio&apos;s separate USENIX Security 2015 work. The theoretical root is Biehl-Meyer-Muller (2000). Getting the citation right matters, because the two 2015 papers are often conflated.&lt;/p&gt;
&lt;h3&gt;Surface three: the cofactor, in production&lt;/h3&gt;
&lt;p&gt;Now the promised return to cofactors. The curve edwards25519 (the signing sibling of Curve25519) has cofactor 8, which means low-order &quot;torsion&quot; points exist. Monero&apos;s ring-signature scheme used &lt;em&gt;key images&lt;/em&gt; to prevent double-spending: each spent output should map to exactly one key image. The bug was that the code never required a key image to lie in the prime-order subgroup, so an attacker could add a low-order torsion point to a legitimate key image, producing a different but still valid-looking image for the same output -- and spend it twice [@loup-vaillant-cofactor]. The fix was to demand that every key image $I$ satisfy $[\ell]I = \mathcal{O}$, where $\ell$ is the prime subgroup order: multiply by the group order and you must land on the identity, proving no torsion component is hiding inside.&lt;/p&gt;
&lt;p&gt;The precise lesson here is the one most often gotten wrong, so it gets its own box.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Curve25519 secret scalars are &quot;clamped&quot; -- specific bits are forced -- and engineers routinely mistake this for input validation. It is not. Clamping constrains &lt;em&gt;your own&lt;/em&gt; secret scalar so that it is a multiple of the cofactor and sits in a fixed range. It does nothing to a malicious low-order &lt;em&gt;input&lt;/em&gt; point someone else hands you. The Monero cofactor bug is exactly this confusion. If a protocol needs a prime-order group, validate or clear the cofactor on inputs -- or use a construction that removes the choice entirely.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Surface four: Curveball, one layer up&lt;/h3&gt;
&lt;p&gt;The atom does not care which layer it runs on. In Curveball (CVE-2020-0601), the mishandled point was not a peer&apos;s ephemeral key but the curve&apos;s own &lt;em&gt;base point&lt;/em&gt;. Windows CryptoAPI accepted certificates carrying explicit curve parameters and trusted the generator inside them without checking it against the named curve&apos;s real generator. Choose your own generator with a known relationship to a trusted CA&apos;s public key, and you forge a signing chain [@cve-2020-0601]. Same disease, higher in the stack: a parameter that should have been validated -- here, the generator itself -- was taken on faith.&lt;/p&gt;
&lt;h3&gt;Surface five: the clock, with no malicious point at all&lt;/h3&gt;
&lt;p&gt;The final surface uses no bad input whatsoever. In 2011, Billy Bob Brumley and Nicola Tuveri recovered an OpenSSL ECDSA private key from a &lt;em&gt;remote&lt;/em&gt; server purely by timing its scalar multiplications [@brumley-tuveri-2011]. OpenSSL&apos;s ladder for binary-field curves ran in data-dependent time; the timing leaked the bit-length of each per-signature nonce, and a lattice computation turned a few hundred such leaks into the key. The underlying flaw earned CVE-2011-1945, whose description is blunt: the implementation &quot;does not properly implement curves over binary fields, which makes it easier for ... attackers to determine private keys via a timing attack and a lattice calculation&quot; [@cve-2011-1945]. This is the atom&apos;s other face: arithmetic on a secret, performed in observable time, is an oracle even when every input is honest.&lt;/p&gt;
&lt;h3&gt;The one family this article does not re-derive&lt;/h3&gt;

There is a whole lineage of ECDSA breaks -- fail0verflow&apos;s PlayStation 3 key recovery, biased-nonce lattice attacks, LadderLeak, Minerva, TPM-Fail -- that shares the atom&apos;s DNA but belongs to a sibling story about randomness and leakage rather than point validation [@minerva-2020], [@ladderleak-2020]. The result is quick to state: a repeated or biased per-signature nonce leaks the ECDSA private key through a lattice, deterministic nonces (RFC 6979) fix the generation half, and constant-time arithmetic fixes the leakage half [@rfc6979]. The lattice machinery is owned by this series&apos; dedicated nonce-reuse post, so this article signposts it and moves on.
&lt;h3&gt;The pattern, stated once&lt;/h3&gt;
&lt;p&gt;Read the six surfaces together and one sentence covers all of them: &lt;em&gt;the implementation touched a value it should have rejected, or acted in a time it should have hidden.&lt;/em&gt; The leaked bit is never &quot;the discrete logarithm.&quot; It is &quot;was this point on the curve,&quot; or &quot;was this element in the right subgroup,&quot; or &quot;how long did that multiply take,&quot; or &quot;do I trust this generator.&quot; The table below is the evidence for the entire thesis. Every row is the same atom on a different surface.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attack&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Surface&lt;/th&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Root cause&lt;/th&gt;
&lt;th&gt;The rule it teaches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Small-subgroup, Lim-Lee [@lim-lee-1997]&lt;/td&gt;
&lt;td&gt;1997&lt;/td&gt;
&lt;td&gt;Subgroup&lt;/td&gt;
&lt;td&gt;Response residue&lt;/td&gt;
&lt;td&gt;Non-prime-order group element accepted&lt;/td&gt;
&lt;td&gt;Validate subgroup membership&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid-curve, Biehl-Meyer-Muller [@biehl-meyer-muller-2000]&lt;/td&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;Off-curve point&lt;/td&gt;
&lt;td&gt;Response residue&lt;/td&gt;
&lt;td&gt;Formula ignores the coefficient b&lt;/td&gt;
&lt;td&gt;Check the point is on the intended curve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote timing, Brumley-Tuveri [@brumley-tuveri-2011], [@cve-2011-1945]&lt;/td&gt;
&lt;td&gt;2011&lt;/td&gt;
&lt;td&gt;Scalar-mult timing&lt;/td&gt;
&lt;td&gt;Wall-clock time&lt;/td&gt;
&lt;td&gt;Non-constant-time ladder&lt;/td&gt;
&lt;td&gt;Multiply in constant time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Practical invalid-curve TLS, Jager et al. [@jager-2015]&lt;/td&gt;
&lt;td&gt;2015&lt;/td&gt;
&lt;td&gt;Off-curve point, reused key&lt;/td&gt;
&lt;td&gt;Response residue&lt;/td&gt;
&lt;td&gt;No peer-point validation&lt;/td&gt;
&lt;td&gt;Validate points, do not reuse ECDH keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cofactor key image, Monero [@loup-vaillant-cofactor]&lt;/td&gt;
&lt;td&gt;2017&lt;/td&gt;
&lt;td&gt;Cofactor torsion&lt;/td&gt;
&lt;td&gt;Forged key image&lt;/td&gt;
&lt;td&gt;Key image not in prime-order subgroup&lt;/td&gt;
&lt;td&gt;Enforce prime order, clamping is not validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Curveball [@cve-2020-0601]&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;PKI base point&lt;/td&gt;
&lt;td&gt;Forged certificate chain&lt;/td&gt;
&lt;td&gt;Explicit generator trusted&lt;/td&gt;
&lt;td&gt;Use named curves, never trust explicit parameters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECDSA-nonce family [@minerva-2020], [@ladderleak-2020]&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;Nonce leakage&lt;/td&gt;
&lt;td&gt;Timing then lattice&lt;/td&gt;
&lt;td&gt;Biased or leaked nonce&lt;/td&gt;
&lt;td&gt;Deterministic nonces plus constant time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Six surfaces, one disease. And notice what the cure was &lt;em&gt;not&lt;/em&gt;: it was not a warning label stapled to P-256. It was a decade-long engineering program to build curves where the dangerous case cannot be reached at all -- and that program, not the catalog, is the most instructive part of the story.&lt;/p&gt;
&lt;h2&gt;5. The Evolution: Moving Safety Off the Implementer&lt;/h2&gt;
&lt;p&gt;Read the last section again as a to-do list for curve designers. Each generation that follows crosses off one entry -- and here is the pattern that makes the story an argument rather than a parade of curves: they do it not by warning implementers to be careful, but by making the mistake unreachable. It is an attacker&apos;s kill-chain run in reverse. Where an intruder normally advances stage by stage, here the &lt;em&gt;defenders&lt;/em&gt; advance, retiring one failure class per curve generation until the dangerous case no longer exists to reach.&lt;/p&gt;

flowchart LR
    G1[&quot;Gen 1 NIST short-Weierstrass&quot;] --&amp;gt; F1[&quot;Leaves timing and invalid-curve to the implementer&quot;]
    F1 --&amp;gt; G2[&quot;Gen 2 Curve25519 ladder&quot;]
    G2 --&amp;gt; R2[&quot;Retires branch timing and invalid-curve for DH&quot;]
    R2 --&amp;gt; G3[&quot;Gen 3 Ed25519 Edwards law&quot;]
    G3 --&amp;gt; R3[&quot;Retires incomplete addition and the nonce disaster&quot;]
    R3 --&amp;gt; G4[&quot;Gen 4 Ristretto255 encoding&quot;]
    G4 --&amp;gt; R4[&quot;Retires the cofactor class at the type level&quot;]
&lt;p&gt;The chronology underneath that progression spans half a century, from a template to a post-quantum hybrid.&lt;/p&gt;

flowchart LR
    A[&quot;1976 Diffie-Hellman template&quot;] --&amp;gt; B[&quot;1985-87 Miller and Koblitz propose ECC&quot;]
    B --&amp;gt; C[&quot;2000 NIST P-curves standardized&quot;]
    C --&amp;gt; D[&quot;2006 Curve25519 constant-time ladder&quot;]
    D --&amp;gt; E[&quot;2011 Ed25519 complete Edwards law&quot;]
    E --&amp;gt; F[&quot;2015-2023 Ristretto255 prime-order abstraction&quot;]
    F --&amp;gt; G[&quot;2024-2026 X25519 plus ML-KEM hybrid&quot;]
&lt;h3&gt;Generation one, and its own rescue&lt;/h3&gt;
&lt;p&gt;The NIST and SECG short-Weierstrass curves gave the world prime order, which we have already granted is a real strength. But their exceptional-case addition law and their branchy, easily-non-constant-time ladders left the two most dangerous jobs -- constant-time arithmetic and on-curve validation -- squarely with the implementer. Section 4 is the proof of what happens when an implementer drops them.&lt;/p&gt;
&lt;p&gt;The honest rebuttal belongs right here, though, because it is the reason &quot;P-256 is broken&quot; is the wrong sentence. In 2016, Joost Renes, Craig Costello, and Lejla Batina published efficient &lt;em&gt;complete&lt;/em&gt; addition formulas that work for every prime-order short-Weierstrass curve, with no exceptional cases to branch around [@rcb-2016]. Given those formulas and a vetted constant-time field implementation, P-256 is perfectly safe -- &lt;em&gt;harder to implement safely&lt;/em&gt;, not broken. Its danger is that the naive implementation is the insecure one, and for two decades the naive implementation is what shipped.&lt;/p&gt;
&lt;h3&gt;Generation two: the ladder that is constant-time by construction&lt;/h3&gt;
&lt;p&gt;In 2006, Daniel J. Bernstein introduced Curve25519, the Montgomery-form curve $y^2 = x^3 + 486662x^2 + x$ over the prime $2^{255} - 19$ [@curve25519-2006]. The form and its ladder trace back to Peter Montgomery&apos;s 1987 work on factorization, revived here for an entirely new purpose [@montgomery-1987]. Two design choices retire whole rows of the failure catalog for free.&lt;/p&gt;
&lt;p&gt;The first is the &lt;strong&gt;Montgomery ladder&lt;/strong&gt;: a scalar-multiplication method whose sequence of field operations is identical regardless of the secret bits. There is no secret-dependent branch to time, so the timing surface -- Brumley-Tuveri&apos;s surface -- closes by construction rather than by careful coding. Key agreement uses only the $x$-coordinate, so a peer sends 32 bytes and the $y$-coordinate never enters the arithmetic.&lt;/p&gt;

A scalar-multiplication algorithm that performs the same sequence of field operations for every bit of the secret -- one conditional swap plus a combined add-and-double per bit -- so its running time and memory-access pattern are independent of the scalar. It is constant-time by construction rather than by defensive programming.
&lt;p&gt;The second is &lt;strong&gt;twist security&lt;/strong&gt;. When someone hands you an $x$-coordinate, it corresponds either to a point on Curve25519 or to a point on its quadratic twist. Bernstein chose the parameters so that &lt;em&gt;both&lt;/em&gt; the curve and its twist have near-prime order [@curve25519-2006], [@safecurves-twist]. A hostile off-curve input therefore still lands you in a group where the discrete log is hard, and the invalid-curve attack of Section 4 is defused -- without any explicit on-curve check in the fast path.&lt;/p&gt;

A property of a curve whose quadratic twist also has near-prime order. Because a hostile input maps to a point on either the curve or its twist, and both groups are cryptographically hard, an off-curve input cannot be steered into a weak subgroup -- so invalid-curve attacks are defeated without an explicit on-curve validation step.
&lt;p&gt;The new cost is the seed of the next two generations: Curve25519 does not have prime order. Its group has cofactor 8, so low-order points exist.Bernstein states the orders exactly: the curve over its base field has $8 \times \ell$ points, where $\ell$ is the prime $2^{252} + 27742317\ldots$, and its twist has $4 \times \ell&apos;$ points for a different prime $\ell&apos;$ [@curve25519-2006]. That factor of 8 is the cofactor the next two generations spend their effort taming. To keep secret scalars inside the safe range, X25519 &lt;strong&gt;clamps&lt;/strong&gt; them. And clamping is the single most misunderstood step in the whole subject.&lt;/p&gt;

Forcing specific bits of an X25519 secret scalar: clearing the low three bits so the scalar is a multiple of the cofactor 8, clearing the high bit, and setting the second-highest bit so every scalar has the same length. Clamping constrains your own secret to sidestep small-subgroup and timing issues on your side. It is emphatically not validation of an input point someone else sends you.
&lt;p&gt;Clamping clears the low three bits (making your scalar a multiple of 8, so multiplying a low-order input contributes nothing on &lt;em&gt;your&lt;/em&gt; side), clears the top bit, and sets the second-highest bit so the ladder runs a fixed number of steps [@rfc7748].The X25519 clamp: &lt;code&gt;scalar[0] &amp;amp;= 248&lt;/code&gt; clears the low three bits, &lt;code&gt;scalar[31] &amp;amp;= 127&lt;/code&gt; clears bit 255, and &lt;code&gt;scalar[31] |= 64&lt;/code&gt; sets bit 254 [@rfc7748]. It protects you, the holder of the secret. It does nothing about a malicious low-order point arriving from outside -- which is why the Monero bug happened on a curve whose users clamped diligently.&lt;/p&gt;
&lt;h3&gt;Generation three: a complete addition law, and deterministic nonces&lt;/h3&gt;
&lt;p&gt;Curve25519 fixed key agreement, but signatures still lived on the branchy Weierstrass world or on Curve25519&apos;s cofactor. Ed25519 (2011) moved signing onto &lt;strong&gt;edwards25519&lt;/strong&gt;, the twisted-Edwards curve birationally equivalent to Curve25519 [@ed25519-2011]. The payoff is Harold Edwards&apos; 2007 normal form: a single addition formula with &lt;em&gt;no exceptional cases&lt;/em&gt; at all [@edwards-2007]. Doubling, adding, the identity -- one formula handles them, so the code has no secret-dependent branch and is naturally constant-time at the group-law level, retiring the incomplete-addition surface. Ed25519 also pairs the curve with &lt;strong&gt;deterministic nonces&lt;/strong&gt;, derived by hashing the message and a secret, which kill the catastrophic ECDSA per-signature-randomness disaster by construction (the nonce can no longer be repeated or biased by a &lt;a href=&quot;https://paragmali.com/blog/predictable-or-repeated-the-only-two-ways-cryptographic-rand/&quot; rel=&quot;noopener&quot;&gt;weak RNG&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The cofactor did not vanish, though. edwards25519 still has cofactor 8, which is why signature malleability, &lt;a href=&quot;https://paragmali.com/blog/the-signature-was-valid-the-message-was-forged-a-field-guide/&quot; rel=&quot;noopener&quot;&gt;non-canonical encodings&lt;/a&gt;, and disagreements about whether verification should be &quot;cofactored&quot; are real concerns -- and precisely why RFC 8032 specifies canonical-encoding checks, bounds on the signature scalar, and cofactor handling [@rfc8032]. The nonce disaster itself is this series&apos; nonce-reuse post&apos;s territory; here it is enough that determinism closes the generation half of it.&lt;/p&gt;
&lt;h3&gt;Generation four: quotient the cofactor out of existence&lt;/h3&gt;
&lt;p&gt;The fourth generation asks a sharper question. Instead of patching every protocol to handle cofactor 8, what if the cofactor simply did not exist at the level you program against? Mike Hamburg&apos;s Decaf (2015) showed how: build a genuine prime-order group &lt;em&gt;in the encoding&lt;/em&gt; on top of a cofactor curve, so that the torsion points are quotiented away and never appear as elements [@decaf-2015]. Ristretto255 is the cofactor-8 instantiation for edwards25519, standardized in RFC 9496 in 2023 [@rfc9496]. The consequence is the cleanest safety property in the whole article: &lt;strong&gt;decoding is validation.&lt;/strong&gt; A sequence of bytes either decodes to a unique, legitimate prime-order element or it is rejected. There is no low-order point to submit, because low-order points are not representable.&lt;/p&gt;

flowchart TD
    A[&quot;Wire bytes for a ristretto255 element&quot;] --&amp;gt; B{&quot;Valid canonical encoding?&quot;}
    B --&amp;gt;|No| C[&quot;Reject: not a group element&quot;]
    B --&amp;gt;|Yes| D[&quot;Decode yields a unique prime-order point&quot;]
    D --&amp;gt; E[&quot;No cofactor, no torsion, no small subgroup&quot;]
    E --&amp;gt; F[&quot;Element is safe to use directly&quot;]

A group of prime order with no cofactor, constructed over the cofactor-8 curve edwards25519 by encoding equivalence classes of points rather than raw points. Because only valid prime-order elements have encodings, decoding a byte string is itself the validation step, and the entire cofactor and small-subgroup failure class disappears at the type level.
&lt;p&gt;Curve448 and its prime-order sibling decaf448 play the conservative role: a larger, roughly 224-bit-security option standardized in the same family but far more thinly deployed than the 25519 curves [@ed448-goldilocks-2015], [@rfc9496]. Treat it as the extra-margin choice, not a rival to X25519&apos;s ubiquity.&lt;/p&gt;
&lt;h3&gt;The through-line&lt;/h3&gt;
&lt;p&gt;These four generations are not a strict succession where each kills the last. They coexist: P-256 remains FIPS-mandated, X25519 and Ed25519 split the key-agreement and signature jobs between them, and ristretto255 sits underneath new protocols that need a clean prime-order group. What each generation truly superseded was not its predecessor curve but the &lt;em&gt;burden on the person writing the code&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Each generation took a rule off the implementer and baked it into the curve, the group, or the encoding. Constant-time became the ladder&apos;s default. On-curve validation became twist security. Exception-free addition became the Edwards law. Cofactor handling became the ristretto255 encoding. The simple implementation kept becoming the secure one.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Curve&lt;/th&gt;
&lt;th&gt;Form&lt;/th&gt;
&lt;th&gt;Field prime&lt;/th&gt;
&lt;th&gt;Cofactor&lt;/th&gt;
&lt;th&gt;Constant-time&lt;/th&gt;
&lt;th&gt;Twist secure&lt;/th&gt;
&lt;th&gt;Coordinates&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;P-256 (secp256r1)&lt;/td&gt;
&lt;td&gt;Short Weierstrass&lt;/td&gt;
&lt;td&gt;2^256 - 2^224 + 2^192 + 2^96 - 1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;With care (complete formulas)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Full (x, y)&lt;/td&gt;
&lt;td&gt;FIPS certificates, ECDSA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;secp256k1&lt;/td&gt;
&lt;td&gt;Short Weierstrass&lt;/td&gt;
&lt;td&gt;2^256 - 2^32 - 977&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;With care&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Full (x, y)&lt;/td&gt;
&lt;td&gt;Blockchain (Bitcoin, BIP-340)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Curve25519 / X25519&lt;/td&gt;
&lt;td&gt;Montgomery&lt;/td&gt;
&lt;td&gt;2^255 - 19&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;By construction&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;x-only (32 bytes)&lt;/td&gt;
&lt;td&gt;Key agreement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edwards25519 / Ed25519&lt;/td&gt;
&lt;td&gt;Twisted Edwards&lt;/td&gt;
&lt;td&gt;2^255 - 19&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;By construction&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;Compressed point&lt;/td&gt;
&lt;td&gt;Signatures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ristretto255&lt;/td&gt;
&lt;td&gt;Prime-order over edwards25519&lt;/td&gt;
&lt;td&gt;2^255 - 19&lt;/td&gt;
&lt;td&gt;1 (effective)&lt;/td&gt;
&lt;td&gt;By construction&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;32-byte encoding&lt;/td&gt;
&lt;td&gt;New prime-order protocols&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Four generations, each erasing one line of the failure catalog. Someone still had to name the law they were all obeying -- and to say out loud that a hard curve and a safe cryptosystem were never the same thing.&lt;/p&gt;
&lt;h2&gt;6. ECDLP-Hardness Is Not ECC-Security&lt;/h2&gt;
&lt;p&gt;In 2013, Daniel J. Bernstein and Tanja Lange put the entire argument of this article onto one website, with a claim engineered to provoke: if you implement the standard curves, chances are you are doing it wrong [@safecurves-index]. The website was SafeCurves, and it is the moment the field said the quiet part out loud.&lt;/p&gt;

There is a gap between ECDLP difficulty and ECC security ... if you implement the standard curves, chances are you&apos;re doing it wrong.
&lt;p&gt;SafeCurves names four concrete places that gap opens: implementations that return wrong results on rare curve points, that leak secrets when fed off-curve inputs, that branch in secret-dependent time, and that leak through cache-timing [@safecurves-index]. Every one of those is a surface from Section 4. And the criteria SafeCurves proposes -- rigidity, twist security, complete addition, ladder support, a handled cofactor -- are not a beauty contest. They are a single design program stated as a checklist: &lt;em&gt;choose the curve so that the easy implementation is the safe one.&lt;/em&gt; That is the whole of Section 5 compressed into one sentence.&lt;/p&gt;
&lt;p&gt;Which lets us state the thesis in its most general form.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Security is a property of the curve, the group, and the implementation together -- not of ECDLP alone. A hard discrete logarithm is necessary and nowhere near sufficient. Every generation in the previous section is this one sentence, applied to a different part of the stack.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The honest tension, handled carefully&lt;/h3&gt;
&lt;p&gt;It would be easy to read SafeCurves as a verdict that the NIST curves are unsafe. That reading is wrong, and getting it right is part of being an expert here rather than a partisan.&lt;/p&gt;

SafeCurves is a strong, deliberately opinionated argument from around 2013 to 2015, not a neutral consensus standard. NIST P-256 and P-384 do fail several of its criteria -- rigidity, twist security, complete addition in the naive formulas -- and yet they remain FIPS-mandated and, implemented with care, secure. The Renes-Costello-Batina complete formulas close the completeness gap in software [@rcb-2016], and vetted constant-time libraries close the rest. The defensible position is precise: P-256 is *harder to implement safely* than Curve25519, not *broken*. &quot;Prefer 25519 for new designs&quot; and &quot;P-256 is unsafe&quot; are different claims, and only the first is true [@safecurves-index].
&lt;p&gt;The scorecard below is the honest version. Read a &quot;No&quot; or a &quot;With care&quot; as &quot;the naive implementation needs help here,&quot; never as &quot;this curve is broken.&quot; Prime order is where the NIST curves quietly win, and it is exactly what the 25519 curves trade away.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Curve&lt;/th&gt;
&lt;th&gt;Rigidity&lt;/th&gt;
&lt;th&gt;Twist security&lt;/th&gt;
&lt;th&gt;Complete addition&lt;/th&gt;
&lt;th&gt;Ladder-friendly&lt;/th&gt;
&lt;th&gt;Prime order&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;P-256&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;With care&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;secp256k1&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;With care&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Curve25519 / X25519&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (cofactor 8)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ed25519&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;No (cofactor 8)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ristretto255&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The footnote that keeps this table honest: every &quot;No&quot; for the NIST curves is a statement about the &lt;em&gt;default&lt;/em&gt; implementation, not the mathematics. secp256k1 is marked &quot;Partly&quot; rigid because its parameters follow a stated efficiency rationale rather than an unexplained seed. Give P-256 complete formulas and a constant-time field, and its row stops mattering. That is why the state of the art is not a single crowned curve but a matter of knowing which curve already does which job for you -- and in 2026, that picture is unusually settled.&lt;/p&gt;
&lt;h2&gt;7. State of the Art (2024-2026): Which Curve Does Which Job&lt;/h2&gt;
&lt;p&gt;There is no single best curve to crown. The state of the art is a small, stable set, each member owning a job -- plus one transition quietly rewriting every handshake on the internet.&lt;/p&gt;
&lt;h3&gt;X25519 owns key agreement&lt;/h3&gt;
&lt;p&gt;For ephemeral key agreement, X25519 is the default answer. It is constant-time by construction, twist-secure, and moves 32 bytes per side; it is a standardized TLS 1.3 named group [@rfc8446], the Diffie-Hellman step in WireGuard&apos;s Noise handshake [@wireguard-protocol], and the key exchange in Signal, all built on the same Curve25519 arithmetic [@rfc7748]. For plain Diffie-Hellman you need no explicit point-validation code, because twist security already handles hostile inputs -- the operational rules reduce to rejecting the all-zero output and running the shared secret through a KDF rather than using it raw.&lt;/p&gt;
&lt;h3&gt;P-256 ECDSA owns FIPS certificates&lt;/h3&gt;
&lt;p&gt;P-256 is co-dominant, and for a specific reason: it is FIPS-mandatory for certificates [@fips-186-5]. Among the curves used in web certificates it is overwhelmingly the one [@ssl-pulse], now implemented with complete formulas or vetted constant-time assembly plus point validation on untrusted inputs [@sp-800-56a-r3]. Where a compliance regime names the curve, P-256 done carefully is exactly right.&lt;/p&gt;
&lt;h3&gt;Ed25519 is now FIPS-approved too&lt;/h3&gt;
&lt;p&gt;The important recent shift for signatures is regulatory. FIPS 186-5, published February 3, 2023, approves EdDSA alongside ECDSA [@fips-186-5], so Ed25519 is now both the technically cleaner option and a compliant one. It is standard in OpenSSH, in Git commit signing, and in package-signing tools such as minisign and OpenBSD&apos;s signify. Ed25519 needs about 87,500 CPU cycles to sign and about 273,000 to verify -- near 134,000 per signature when many are checked in one batch [@ed25519-2011] -- with the eBACS suite serving as the live cross-CPU benchmark authority [@bench-cr-yp-to]. The state-of-the-art guidance: Ed25519 for new designs, ECDSA where compliance compels it [@rfc8032].&lt;/p&gt;
&lt;h3&gt;secp256k1 owns blockchain, and only blockchain&lt;/h3&gt;
&lt;p&gt;secp256k1 carries an enormous deployment -- Bitcoin, Ethereum, and their descendants -- through ECDSA and, since Taproot, BIP-340 Schnorr signatures [@bip-340]. It is nonetheless essentially absent from web PKI and TLS. On any decision about a certificate or a general key exchange, secp256k1 is not a candidate; it is the answer only when the job is blockchain interoperability.&lt;/p&gt;
&lt;h3&gt;Ristretto255 is the prime-order substrate for new protocols&lt;/h3&gt;
&lt;p&gt;When a protocol needs a clean prime-order group -- password-authenticated key exchange, verifiable random functions, oblivious pseudorandom functions, threshold signatures, zero-knowledge systems -- ristretto255 (RFC 9496) is the current substrate [@rfc9496]. It is the recommended ciphersuite of the FROST threshold-Schnorr standard, which is defined generically over any prime-order group and also specifies Ed25519, Ed448, P-256, and secp256k1 suites [@rfc9591]; and it ships in production inside Signal&apos;s libsignal, whose zkgroup and zkcredential modules build on ristretto255 points [@libsignal].&lt;/p&gt;
&lt;h3&gt;The post-quantum overlay: ECC wrapped, not replaced&lt;/h3&gt;
&lt;p&gt;The headline of 2024 to 2026 is not a new curve; it is a wrapper. The hybrid &lt;strong&gt;X25519 plus ML-KEM-768&lt;/strong&gt; combines the classical curve with the &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;NIST-standardized lattice KEM&lt;/a&gt;, and it is now shipping by default in Chrome and across Cloudflare&apos;s network [@cloudflare-pq-2024], and available in OpenSSH 9.9, released September 2024 [@openssh-9.9]. The motive is &quot;harvest now, decrypt later&quot;: an adversary can record today&apos;s X25519 traffic and wait for a quantum computer, so the hybrid forces them to break &lt;em&gt;both&lt;/em&gt; the curve and the lattice.Post-quantum-encrypted traffic on Cloudflare&apos;s network nearly doubled across 2025, from about 29% of human-generated web requests at the start of the year to roughly 52% by early December [@cloudflare-radar-2025], with a live tracker following the trend [@cloudflare-radar-pq-tracker].&lt;/p&gt;
&lt;p&gt;The enabling techniques are themselves state of the art. The Montgomery ladder, the Renes-Costello-Batina complete formulas [@rcb-2016], and machine-checked, correct-by-construction field arithmetic (fiat-crypto, whose code for Curve25519 and P-256 ships inside BoringSSL) are what make these curves safe in practice rather than merely on paper [@fiat-crypto].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; It is not a single best curve. It is &quot;the right curve for the job&quot; -- X25519 for key agreement, Ed25519 for new signatures, P-256 where FIPS compels it, secp256k1 only for blockchain, ristretto255 for new prime-order protocols -- plus the hybrid transition that wraps the classical curve in a post-quantum KEM.&lt;/p&gt;
&lt;/blockquote&gt;

FIPS 186-5 approves EdDSA, so Ed25519 is now a compliant signature choice [@fips-186-5]; ECDSA over P-256 remains mandated for certificates; and SP 800-56A Rev. 3 requires point and subgroup validation for untrusted inputs in key establishment [@sp-800-56a-r3]. Compliance and good engineering point the same way more often than they used to.
&lt;p&gt;&quot;Which curve owns which job&quot; is a summary, not a decision procedure. When the jobs overlap -- when a compliance regime, a blockchain, and a threshold protocol pull in three directions at once -- you need the head-to-head.&lt;/p&gt;
&lt;h2&gt;8. The Decision Matrix&lt;/h2&gt;
&lt;p&gt;The deployed curves do not all compete for the same job, so the useful comparison is per task -- and it hinges on one axis the benchmarks never advertise: &lt;em&gt;how much of the failure catalog you must defend against by hand.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Key agreement: X25519 unless something forces your hand&lt;/h3&gt;
&lt;p&gt;Default to X25519. Its twist security and constant-time ladder mean the dangerous inputs are already handled, so your implementation burden is low: reject the all-zero shared secret and run the result through a KDF. Choose P-256 ECDH only where a compliance regime mandates it -- and then take on the full burden the curve leaves you: validate the peer point is on the curve &lt;em&gt;and&lt;/em&gt; in the prime-order subgroup, use complete formulas or vetted constant-time code, and never feed the raw x-coordinate to anything but a KDF [@sp-800-56a-r3], [@rcb-2016]. Reach for X448 only when a policy demands a larger margin.&lt;/p&gt;
&lt;p&gt;| Curve | Form | Security | Cofactor | Constant-time | Twist secure | Element | Safety burden | Compliance |
| --- | --- | --- | --- | --- | --- | --- | --- |
| X25519 | Montgomery | ~128-bit | 8 | By construction | Yes | 32 B | Low: reject zero, KDF the output | Not required for certificates |
| P-256 ECDH | Short Weierstrass | ~128-bit | 1 | With care | No | 33 B compressed | High: validate point and subgroup, constant-time | FIPS-mandated |
| X448 | Montgomery | ~224-bit | 4 | By construction | Yes | 56 B | Low | Extra-margin option |&lt;/p&gt;
&lt;p&gt;The one rule that survives every row: never use the raw shared x-coordinate as a key. Run it through a KDF, as this series&apos; key-derivation installment argues.&lt;/p&gt;
&lt;h3&gt;Signatures: Ed25519 for new work, ECDSA where mandated&lt;/h3&gt;
&lt;p&gt;For new designs, Ed25519 wins on the axis that matters -- it removes the per-signature randomness that has destroyed more ECDSA keys than any cryptanalysis, and its complete addition law removes the exceptional cases [@rfc8032]. Use ECDSA over P-256 only where a standard names it, and then defend it deliberately: RFC 6979 deterministic nonces, constant-time scalar multiplication, and point validation [@rfc6979]. BIP-340 Schnorr is the right choice for new Bitcoin work and nowhere else [@bip-340].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scheme&lt;/th&gt;
&lt;th&gt;Nonce&lt;/th&gt;
&lt;th&gt;Nonce-failure blast radius&lt;/th&gt;
&lt;th&gt;Addition law&lt;/th&gt;
&lt;th&gt;Malleability rule&lt;/th&gt;
&lt;th&gt;Sizes&lt;/th&gt;
&lt;th&gt;Batch verify&lt;/th&gt;
&lt;th&gt;Compliance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Ed25519&lt;/td&gt;
&lt;td&gt;Deterministic (hash-based)&lt;/td&gt;
&lt;td&gt;None by construction&lt;/td&gt;
&lt;td&gt;Complete Edwards&lt;/td&gt;
&lt;td&gt;Enforce canonical S and cofactor checks&lt;/td&gt;
&lt;td&gt;64 B sig, 32 B key&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;FIPS 186-5 approved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECDSA P-256&lt;/td&gt;
&lt;td&gt;Random, or RFC 6979 deterministic&lt;/td&gt;
&lt;td&gt;Catastrophic: a reused or biased nonce leaks the key&lt;/td&gt;
&lt;td&gt;Incomplete Weierstrass&lt;/td&gt;
&lt;td&gt;Low-S recommended&lt;/td&gt;
&lt;td&gt;~64-72 B sig, 33 B key&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;FIPS-mandated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BIP-340 Schnorr&lt;/td&gt;
&lt;td&gt;Deterministic with auxiliary randomness&lt;/td&gt;
&lt;td&gt;Reduced versus ECDSA&lt;/td&gt;
&lt;td&gt;secp256k1&lt;/td&gt;
&lt;td&gt;Fixed by encoding&lt;/td&gt;
&lt;td&gt;64 B sig, 32 B x-only key&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Bitcoin only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Group abstraction: ristretto255 over hand-rolled cofactor clearing&lt;/h3&gt;
&lt;p&gt;If you are building a new prime-order protocol -- a PAKE, a VRF, an OPRF, a threshold scheme, a zero-knowledge system -- build it on ristretto255 rather than hand-rolling cofactor clearing over raw edwards25519 [@rfc9496]. Decode-is-validation removes the last implementation choice that could go wrong, which is the entire point of Generation four.&lt;/p&gt;
&lt;h3&gt;The cross-primitive view&lt;/h3&gt;
&lt;p&gt;Two comparisons sit outside this table but belong in your head. Against &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&lt;/a&gt;, elliptic curves do the same public-key jobs with smaller keys and faster operations, and they share the reframe this series keeps returning to: the math was never the thing that broke. Against post-quantum schemes, ECC is not yet replaced -- the classical curve is the defense-in-depth half of a hybrid, present precisely because a lattice KEM is young and a recorded handshake is forever. Neither comparison changes a single row above; both change how long these rows stay valid.&lt;/p&gt;
&lt;p&gt;Every row here assumes the curve&apos;s discrete log is genuinely hard. That assumption has a precise boundary -- and a hard expiration date.&lt;/p&gt;
&lt;h2&gt;9. Where the Hardness Lives, and Its Ceiling&lt;/h2&gt;
&lt;p&gt;Here is the uncomfortable pair of facts a first course tends to skip. For a well-chosen curve, the security level is &lt;em&gt;exactly&lt;/em&gt; the generic bound -- there is no hidden margin above it, but nothing better is known either. And that entire edifice falls to a quantum computer in polynomial time.&lt;/p&gt;
&lt;h3&gt;The generic bound, matched from both sides&lt;/h3&gt;
&lt;p&gt;The best classical attacks on a well-chosen curve are &lt;em&gt;generic&lt;/em&gt; -- they work in any group and know nothing special about elliptic curves. Pollard rho finds a discrete log in about $\sqrt{n}$ group operations for a group of order $n$, using parallel collision search with distinguished points and almost no memory [@safecurves-rho]. Pohlig-Hellman reduces the problem to the largest prime factor of the group order, which is the deep reason prime order matters: a smooth order is a soft target. Against those upper bounds sits a matching lower bound -- Shoup (1997) and Nechaev (1994) proved that &lt;em&gt;any&lt;/em&gt; generic algorithm needs $\Omega(\sqrt{n})$ operations. Upper and lower bounds meet.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For a well-chosen curve, the security level is the generic square-root bound itself: about $\sqrt{n}$ work, matched by a proven lower bound. That is solid -- but it means there is no safety margin hiding above the number, and, as we are about to see, a quantum cliff waiting below it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That closed gap is genuinely unusual among cryptographic assumptions, and it is the whole reason a 256-bit curve is trusted at the 128-bit level.Most hardness assumptions leave a gap between the best known attack and the best provable lower bound. For generic ECDLP on a well-chosen curve, the two coincide at $\sqrt{n}$. You are trusting a bound that is tight, not merely unbroken. The reason no &lt;em&gt;better&lt;/em&gt; attack exists is the absence we opened with: no index-calculus method is known that beats $\sqrt{n}$ on prime-field curves. Semaev&apos;s summation polynomials and the programs built on them have not produced one. The economic case of Section 2 is really this limit, seen from the other side.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symmetric security&lt;/th&gt;
&lt;th&gt;Elliptic-curve size&lt;/th&gt;
&lt;th&gt;RSA / finite-field DH modulus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;112-bit&lt;/td&gt;
&lt;td&gt;224-bit&lt;/td&gt;
&lt;td&gt;2048-bit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;128-bit&lt;/td&gt;
&lt;td&gt;256-bit&lt;/td&gt;
&lt;td&gt;3072-bit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192-bit&lt;/td&gt;
&lt;td&gt;384-bit&lt;/td&gt;
&lt;td&gt;7680-bit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;256-bit&lt;/td&gt;
&lt;td&gt;512-bit&lt;/td&gt;
&lt;td&gt;15360-bit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The table is the standard security-level mapping, and its middle row is the sentence everyone quotes: 256-bit ECC stands in for 3072-bit RSA at roughly 128-bit strength, because the curve attacker pays $\sqrt{n}$ while the RSA attacker enjoys sub-exponential index calculus [@safecurves-rho], [@miller-1985].&lt;/p&gt;
&lt;h3&gt;What &quot;well-chosen&quot; rules out&lt;/h3&gt;
&lt;p&gt;The generic bound holds only for curves that avoid a short list of special structures, and each item on that list is a curve that &lt;em&gt;looks&lt;/em&gt; fine but collapses. The MOV reduction (Menezes, Okamoto, and Vanstone, 1993) maps a curve with low embedding degree -- supersingular curves are the extreme case -- into a finite field where index calculus applies, collapsing its ECDLP [@mov-1993]. Anomalous, or trace-one, curves, where the number of points equals the field prime, admit an additive transfer that solves the discrete log in &lt;em&gt;polynomial&lt;/em&gt; time (Smart; independently Satoh-Araki and Semaev) [@smart-1999]. And the small-subgroup and twist conditions of Section 4 round out the list. &quot;Well-chosen&quot; is exactly &quot;avoids these.&quot;&lt;/p&gt;
&lt;h3&gt;The one bound that is not the square root&lt;/h3&gt;
&lt;p&gt;Every limit so far is classical. The exception is the one that sets the deadline. Shor&apos;s algorithm (1997) solves both factoring and discrete logarithms -- including ECDLP -- in polynomial time on a fault-tolerant quantum computer [@shor-1997]. Resource estimates make it concrete: at equal classical security ECC is the easier quantum target -- breaking a 256-bit curve needs roughly 2,330 logical qubits and about $1.3 \times 10^{11}$ Toffoli gates, versus roughly 6,100 logical qubits for RSA-3072, so ECC&apos;s smaller keys are the smaller quantum target and fall first [@roetteler-2017]. No choice of curve escapes this; it is a property of the group structure Shor exploits, not of any parameter.&lt;/p&gt;

This is why today&apos;s ECDH traffic is already on a clock, before any quantum computer exists. An adversary records your X25519 handshakes now and decrypts them the day a machine can run Shor at scale. The recorded session is not refreshed; its forward secrecy is only as good as the assumption that the curve stays hard forever -- which Shor says it will not [@shor-1997]. That single fact, not any classical advance, is what put the hybrid transition of Section 7 on every roadmap.
&lt;p&gt;If the mathematics is this settled -- a closed classical bound and a known quantum ceiling -- then everything still open lives in the code, the trust, and the migration. That is where the real frontier is.&lt;/p&gt;
&lt;h2&gt;10. What Remains Genuinely Unsettled&lt;/h2&gt;
&lt;p&gt;The core science is settled, and the failure catalog and its defenses are not in dispute. What is open sits at the edges -- and two of these are live in your dependency tree right now.&lt;/p&gt;
&lt;h3&gt;Constant-time is a perpetually moving target&lt;/h3&gt;
&lt;p&gt;Montgomery ladders and complete formulas close the obvious channels, and still the leaks keep reappearing at finer grain. Minerva recovered secp256r1 private keys from about 2,100 signatures by measuring how the nonce&apos;s bit-length shifted the signing time [@minerva-2020]. TPM-Fail pulled 256-bit ECDSA keys out of an Intel firmware TPM and an STMicroelectronics TPM despite their certifications [@tpm-fail-2020]. LadderLeak broke ECDSA with less than one bit of nonce leakage [@ladderleak-2020]. There is no general, machine-checkable guarantee of constant-time behavior that survives arbitrary compilers and microarchitectures. The lattice step that turns these leaks into keys belongs to this series&apos; &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 post&lt;/a&gt;; the open problem here is the leakage itself.&lt;/p&gt;
&lt;h3&gt;The NIST-curve trust question is sociological, not solved&lt;/h3&gt;
&lt;p&gt;No weakness has ever been demonstrated in P-256 or P-384, and they remain FIPS-mandated [@safecurves-index]. Yet Dual EC DRBG proved that NIST-supplied constants &lt;em&gt;could&lt;/em&gt; be poisoned, which is why a large part of the community prefers rigid curves for new work [@shumow-ferguson-2007]. This is not the kind of question a proof can close -- you cannot prove the absence of a motive. The practical resolution is a posture, not a theorem: prefer 25519 for new designs, use P-256 with care where compliance compels it.&lt;/p&gt;
&lt;h3&gt;The cofactor long tail&lt;/h3&gt;
&lt;p&gt;RFC 9496 eliminates the cofactor failure class -- for the protocols that adopt it [@rfc9496]. The long tail of deployed EdDSA and X25519 protocols that predate or simply ignore ristretto255 remains a live source of subgroup bugs, with Monero the canonical example [@loup-vaillant-cofactor]. This is a migration problem, not a research problem, which somehow makes it more stubborn rather than less.&lt;/p&gt;
&lt;h3&gt;The post-quantum migration, past the easy part&lt;/h3&gt;
&lt;p&gt;Hybrid key agreement is deploying fast [@cloudflare-radar-2025], but the interesting questions start after the first hybrid ships: how long ECC&apos;s small keys keep it useful inside hybrids, whether and when to drop the classical half, which combiner and negotiation are right, and the genuinely harder migration on the signature side, where the post-quantum replacements are large and awkward.&lt;/p&gt;
&lt;h3&gt;Formal verification at scale&lt;/h3&gt;
&lt;p&gt;fiat-crypto ships proven-correct field arithmetic for Curve25519 and P-256 inside BoringSSL, which is a real milestone [@fiat-crypto]. But end-to-end verification -- field, then group law, then protocol, then constant-time behavior on the actual target instruction set -- is not yet routine. The field-arithmetic layer is solved; the stack above it is not.&lt;/p&gt;
&lt;p&gt;There is also a structural non-problem worth naming, so you can stop worrying about it: no one has found a faster-than-square-root attack on prime-field ECDLP, and the summation-polynomial work that raised hopes lives in binary and composite fields -- one more reason NIST now steers new work to prime-field curves: SP 800-186 deprecates the binary and Koblitz curves, retaining them only for legacy interoperability, and FIPS 186-5 dropped them from approval for new signatures [@sp-800-186], [@fips-186-5].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Of the five open fronts, two are the ones likely to be in &lt;em&gt;your&lt;/em&gt; stack today: a non-constant-time scalar multiplication somewhere in a dependency, and a protocol built on raw edwards25519 that never adopted ristretto255. The other three are the industry&apos;s problem. These two are yours.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Enough theory and frontier. Here is the entire article compressed into rules you can apply on Monday -- each one a named break from the catalog, turned inside out.&lt;/p&gt;
&lt;h2&gt;11. The Practical Guide and the Misuse Catalog&lt;/h2&gt;
&lt;p&gt;Every rule below is one row of the failure catalog, inverted. If you remember nothing else from this article, remember the decision tree.&lt;/p&gt;

flowchart TD
    A{&quot;What is the job?&quot;} --&amp;gt; B[&quot;Key agreement&quot;]
    A --&amp;gt; C[&quot;Signature&quot;]
    A --&amp;gt; D[&quot;Prime-order protocol&quot;]
    A --&amp;gt; E[&quot;Long-lived confidentiality&quot;]
    B --&amp;gt; B1{&quot;FIPS-mandated curve?&quot;}
    B1 --&amp;gt;|No| B2[&quot;X25519, reject zero output, KDF the result&quot;]
    B1 --&amp;gt;|Yes| B3[&quot;P-256 ECDH, validate point and subgroup, constant-time, KDF&quot;]
    C --&amp;gt; C1{&quot;New design?&quot;}
    C1 --&amp;gt;|Yes| C2[&quot;Ed25519, enforce canonical S, strict library&quot;]
    C1 --&amp;gt;|Mandated| C3[&quot;ECDSA P-256, RFC 6979 nonce, constant-time, validate points&quot;]
    D --&amp;gt; D1[&quot;Build on ristretto255 or decaf448&quot;]
    E --&amp;gt; E1[&quot;Wrap X25519 in the ML-KEM-768 hybrid&quot;]
&lt;h3&gt;The decision rules, in words&lt;/h3&gt;
&lt;p&gt;For &lt;strong&gt;key agreement&lt;/strong&gt;, reach for X25519 by default: constant-time by construction, twist security handling hostile inputs, and one operational rule -- run the shared secret through a KDF and reject the all-zero output, never using the raw x-coordinate as a key [@rfc7748]. Use P-256 ECDH only where FIPS compels it, and then validate the peer point on the curve &lt;em&gt;and&lt;/em&gt; in the prime-order subgroup, use complete formulas or a vetted constant-time library, and KDF the result [@sp-800-56a-r3], [@rcb-2016].&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;signatures&lt;/strong&gt;, choose Ed25519 for new designs -- deterministic nonces, a complete addition law, and a strict library that enforces the canonical-S check [@rfc8032]. Use ECDSA over P-256 only where mandated, with RFC 6979 deterministic nonces, constant-time scalar multiplication, and point validation [@rfc6979].&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;point handling&lt;/strong&gt;, always validate untrusted points -- on the curve and in the right subgroup -- remember that clamping is not validation, and never accept explicit curve parameters from a certificate or message; use named curves only, which is the whole lesson of Curveball [@cve-2020-0601]. For &lt;strong&gt;new prime-order protocols&lt;/strong&gt;, build on ristretto255 or decaf448 instead of hand-rolling cofactor clearing [@rfc9496]. For &lt;strong&gt;long-lived confidentiality&lt;/strong&gt;, deploy the X25519 plus ML-KEM-768 hybrid now rather than waiting for a pure post-quantum replacement.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;Key parameters&lt;/th&gt;
&lt;th&gt;Because (the named break)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Key agreement (default)&lt;/td&gt;
&lt;td&gt;X25519&lt;/td&gt;
&lt;td&gt;Reject zero output, KDF the secret&lt;/td&gt;
&lt;td&gt;Raw-x misuse, twist security handles inputs [@rfc7748]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key agreement (FIPS)&lt;/td&gt;
&lt;td&gt;P-256 ECDH&lt;/td&gt;
&lt;td&gt;Validate point and subgroup, constant-time&lt;/td&gt;
&lt;td&gt;Invalid-curve TLS recovery [@jager-2015], timing [@brumley-tuveri-2011]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signature (new)&lt;/td&gt;
&lt;td&gt;Ed25519&lt;/td&gt;
&lt;td&gt;Canonical S, strict library&lt;/td&gt;
&lt;td&gt;Nonce disaster and malleability [@rfc8032]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signature (mandated)&lt;/td&gt;
&lt;td&gt;ECDSA P-256&lt;/td&gt;
&lt;td&gt;RFC 6979 nonce, constant-time, validate&lt;/td&gt;
&lt;td&gt;Nonce reuse or bias [@rfc6979]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prime-order protocol&lt;/td&gt;
&lt;td&gt;ristretto255 / decaf448&lt;/td&gt;
&lt;td&gt;Decode is validation&lt;/td&gt;
&lt;td&gt;Cofactor double-spend [@loup-vaillant-cofactor]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certificates&lt;/td&gt;
&lt;td&gt;Named curves only&lt;/td&gt;
&lt;td&gt;Reject explicit parameters&lt;/td&gt;
&lt;td&gt;Curveball [@cve-2020-0601]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-lived secrecy&lt;/td&gt;
&lt;td&gt;X25519 + ML-KEM-768&lt;/td&gt;
&lt;td&gt;Hybrid combiner now&lt;/td&gt;
&lt;td&gt;Harvest-now-decrypt-later [@shor-1997]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Why &quot;clamping is not validation&quot; is a rule, not a slogan&lt;/h3&gt;
&lt;p&gt;The single most common confusion deserves a demonstration rather than an assertion. On a curve with a cofactor, an attacker can add a low-order point to a legitimate one. Clamping your secret scalar hides that tampering inside a Diffie-Hellman product -- so clamping alone cannot even detect it -- while an explicit prime-order subgroup check catches it immediately.&lt;/p&gt;
&lt;p&gt;{`
// Toy curve y^2 = x^3 + x over F_53. Group order 68 = 4 * 17 (cofactor 4, prime l = 17).
const p = 53n, a = 1n, ELL = 17n;
const mod = x =&amp;gt; ((x % p) + p) % p;
function inv(x){ let r=1n,b=mod(x),e=p-2n; while(e&amp;gt;0n){ if(e&amp;amp;1n) r=mod(r&lt;em&gt;b); b=mod(b&lt;/em&gt;b); e&amp;gt;&amp;gt;=1n;} return r; }
const eq = (P,Q) =&amp;gt; (P===null&amp;amp;&amp;amp;Q===null) || (P&amp;amp;&amp;amp;Q&amp;amp;&amp;amp;P[0]===Q[0]&amp;amp;&amp;amp;P[1]===Q[1]);
function add(P,Q){
  if(P===null) return Q; if(Q===null) return P;
  const [x1,y1]=P,[x2,y2]=Q;
  if(x1===x2 &amp;amp;&amp;amp; mod(y1+y2)===0n) return null;
  const s=(x1===x2&amp;amp;&amp;amp;y1===y2)? mod((3n&lt;em&gt;x1&lt;/em&gt;x1+a)&lt;em&gt;inv(2n&lt;/em&gt;y1)) : mod((y2-y1)&lt;em&gt;inv(x2-x1));
  const x3=mod(s&lt;/em&gt;s-x1-x2); return [x3, mod(s*(x1-x3)-y1)];
}
function mul(k,P){ let R=null,b=P; while(k&amp;gt;0n){ if(k&amp;amp;1n) R=add(R,b); b=add(b,b); k&amp;gt;&amp;gt;=1n;} return R; }&lt;/p&gt;
&lt;p&gt;const P = [6n,13n];                 // legitimate prime-order point (order 17)
const Low = [0n,0n];                // low-order point (order 2, divides the cofactor 4)
const tampered = add(P, Low);       // attacker adds a low-order component
console.log(&apos;P and P+Low are distinct encodings?&apos;, !eq(P, tampered));&lt;/p&gt;
&lt;p&gt;// Clamping forces the secret to a multiple of the cofactor, so it HIDES the tamper:
const kClamped = 4n * 5n;           // 20, a multiple of the cofactor 4
console.log(&apos;clamped [k]P equals clamped &lt;a href=&quot;P+Low&quot; rel=&quot;noopener&quot;&gt;k&lt;/a&gt;?&apos;, eq(mul(kClamped,P), mul(kClamped,tampered)));&lt;/p&gt;
&lt;p&gt;// The real defence is an explicit prime-order subgroup check [l]X == identity:
console.log(&apos;subgroup check -- [l]P is identity?&apos;, mul(ELL,P) === null);
console.log(&apos;subgroup check -- &lt;a href=&quot;P+Low&quot; rel=&quot;noopener&quot;&gt;l&lt;/a&gt; is identity?&apos;, mul(ELL,tampered) === null);
`}&lt;/p&gt;
&lt;p&gt;The clamped multiplication reports the tampered point as identical, and only the $[\ell]X = \mathcal{O}$ subgroup check flags it. That is the Monero bug in nine lines: the code that needed to reject the tampered element was not the clamp, it was the subgroup test.&lt;/p&gt;

On most systems you can see the real curves in seconds. `ssh -Q key` and `ssh -Q kex` list your SSH key and key-exchange algorithms -- look for `ssh-ed25519` and a hybrid like `mlkem768x25519-sha256`. For TLS, `openssl s_client -connect example.com:443 -tls1_3` reports the negotiated group, often `X25519` or the hybrid `X25519MLKEM768`. Versions differ, but these tell you which curve and which post-quantum wrapper your traffic actually uses today, rather than which one you assume it does.
&lt;h3&gt;The misuse catalog&lt;/h3&gt;
&lt;p&gt;Each antipattern below is exactly one violated rule, and each maps to a named break you have now met:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Using the raw ECDH x-coordinate as a key.&lt;/strong&gt; Run it through a KDF instead; the shared secret is an input to key derivation, not a key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treating clamping as validation.&lt;/strong&gt; Clamping constrains your secret; it does not filter a hostile input point (Monero [@loup-vaillant-cofactor]).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skipping point validation on Weierstrass curves.&lt;/strong&gt; The formula ignores $b$, so an off-curve point is processed on a weaker curve (invalid-curve, Jager et al. [@jager-2015]).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accepting explicit curve parameters.&lt;/strong&gt; Trusting a generator you did not choose forges chains (Curveball [@cve-2020-0601]).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-constant-time or unvalidated P-256.&lt;/strong&gt; Data-dependent timing is an oracle even with honest inputs (Brumley-Tuveri [@brumley-tuveri-2011]).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ignoring the cofactor or non-canonical encodings in edwards25519 protocols.&lt;/strong&gt; Low-order points and duplicate encodings break uniqueness (Monero [@loup-vaillant-cofactor]).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rolling your own curve arithmetic.&lt;/strong&gt; Use fiat-crypto-backed libraries; hand-written field code is where constant-time dies [@fiat-crypto].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reusing or biasing ECDSA nonces.&lt;/strong&gt; A repeated or leaky nonce hands over the key through a lattice (RFC 6979 fixes the generation half [@rfc6979]).&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If your time is limited, hunt these first: the raw ECDH x-coordinate used directly as a key; a P-256 ECDH path that neither validates the peer point nor runs in constant time; and any code path that accepts explicit curve parameters instead of a named curve. Those three account for most real elliptic-curve findings in production code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Read the whole failure catalog again as this checklist, and one sentence remains true of every row.&lt;/p&gt;
&lt;h2&gt;12. The Curve Was Hard; The Gap Was Soft&lt;/h2&gt;
&lt;p&gt;Return to where we started. The NSA phoned Microsoft, and nobody solved a discrete logarithm [@cve-2020-0601]. That is not an anomaly; it is the whole pattern in one image. Across three decades of deployed elliptic-curve cryptography, almost no real break ever inverted the function the textbooks call hard. Attackers fed the implementation a point it should have rejected -- off the curve, or of small order, or a certificate&apos;s own untrusted generator (invalid-curve, small-subgroup, Curveball) [@jager-2015]. They added a low-order component that clamping never filtered (Monero) [@loup-vaillant-cofactor]. Or they simply read the clock while the code multiplied a secret (Brumley-Tuveri, and the Minerva-TPM-Fail-LadderLeak lineage) [@brumley-tuveri-2011].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The curve was hard; the gap was soft. ECDLP-hardness bought the small keys and the speed, and it held every single time. The breaks all lived in the code, the trust, and the encoding around the curve -- the space between a hard problem and a safe system.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So &quot;using elliptic curves safely&quot; is not one decision but a stack: the right curve for the job, point and subgroup validation on every untrusted input, constant-time scalar multiplication, and -- the deepest lesson the evolution kept teaching -- a preference for constructions where the dangerous case cannot be reached at all, as ristretto255 makes literal [@rfc9496]. Remove any single layer and the gap reopens. And the horizon is honest about its own limit: the destination is not a better curve, because no curve survives Shor [@shor-1997]. It is wrapping the curve in a post-quantum hybrid and buying time.&lt;/p&gt;

The private curve stayed hard; the code around it confessed.
&lt;p&gt;Which makes the diagnostic question we opened with finally answerable. When a point you did not generate arrives, what does your code reveal about it -- through its result, its timing, or the parameters it trusts? Using elliptic curves safely is the discipline of making that answer &lt;em&gt;nothing an attacker can measure.&lt;/em&gt;&lt;/p&gt;


No weakness has ever been shown in P-256 or P-384. The real concern is *rigidity*: their generator and coefficient come from an unexplained seed, and the Dual EC DRBG affair proved that NIST-supplied constants *could* be poisoned in some other primitive. That history is why many engineers prefer rigid curves like Curve25519 for new designs. But &quot;prefer 25519&quot; is a different claim from &quot;P-256 is broken,&quot; and only the first one is supported.


No -- it is *harder to implement safely*, not broken. Its naive addition formulas have exceptional cases and it lacks twist security, so a careless implementation can leak. Complete addition formulas (Renes-Costello-Batina) and vetted constant-time libraries close those gaps. Implemented carefully, P-256 is secure and remains FIPS-mandated for certificates.


For plain Diffie-Hellman, no on-curve check is required: Curve25519&apos;s twist security means a hostile input still lands in a hard group, so you just reject the all-zero output and run the result through a KDF. But low-order points still matter for protocols that need a genuine prime-order group. Validate or clear the cofactor there, or better, build on ristretto255 and let decoding do the validation.


No. Clamping constrains *your own* secret scalar so a low-order component contributes nothing to your Diffie-Hellman product. It does nothing to validate a malicious input point, and it does not make a tampered point canonical. The Monero cofactor bug is exactly this confusion -- the fix was an explicit prime-order subgroup check, not clamping.


Only if you are doing blockchain interoperability. secp256k1 is the Bitcoin and Ethereum curve; it is essentially absent from web PKI and TLS. For general key agreement use X25519, and for certificates use P-256. Reaching for secp256k1 outside the blockchain context is almost always a sign something is being reinvented.


Use Ed25519 for new work: deterministic nonces remove the per-signature randomness that has destroyed more ECDSA keys than any cryptanalysis, the addition law is complete and constant-time, and EdDSA has been FIPS-approved since 186-5. Use ECDSA over P-256 only where a standard mandates it, and then with RFC 6979 deterministic nonces, constant-time scalar multiplication, and point validation.


No. Shor&apos;s algorithm breaks the elliptic-curve discrete log in polynomial time on a fault-tolerant quantum computer, and at equal classical security ECC is the easier quantum target -- a 256-bit curve needs about 2,330 logical qubits versus roughly 6,100 for RSA-3072, so it falls first. Because recorded traffic can be decrypted later, deploy the X25519 plus ML-KEM-768 hybrid now rather than waiting for a pure post-quantum replacement.

&lt;p&gt;&amp;lt;StudyGuide slug=&quot;elliptic-curves-real-world-p256-curve25519&quot; keyTerms={[
  { term: &quot;ECDLP&quot;, definition: &quot;The elliptic-curve discrete-log problem: given a base point G and Q equal to k times G, recover the scalar k. Easy to compute forward, believed infeasible to invert on a well-chosen curve. It is the one-way assumption all of elliptic-curve cryptography rests on.&quot; },
  { term: &quot;Scalar multiplication&quot;, definition: &quot;Repeated point addition, adding G to itself k times, written as k times G. Computed in about log k steps by double-and-add. It is the fast forward direction of the trapdoor.&quot; },
  { term: &quot;Cofactor&quot;, definition: &quot;The ratio of a curve&apos;s full group order to the prime order of the subgroup used for cryptography. P-256 has cofactor 1 (prime order); edwards25519 has cofactor 8, so low-order torsion points exist and must be handled.&quot; },
  { term: &quot;Invalid-curve attack&quot;, definition: &quot;Feeding an implementation a point not on the intended curve into formulas that ignore the coefficient b, so the arithmetic runs on a weaker curve and leaks the secret modulo small numbers, with the intended curve untouched.&quot; },
  { term: &quot;Small-subgroup attack&quot;, definition: &quot;Submitting a low-order element so the response reveals the secret modulo that small order. Repeated over coprime orders, the Chinese Remainder Theorem reassembles the full private key.&quot; },
  { term: &quot;Twist security&quot;, definition: &quot;A property of a curve whose quadratic twist also has near-prime order, so a hostile off-curve input still lands in a hard group. Invalid-curve attacks are defeated without an explicit on-curve check.&quot; },
  { term: &quot;Montgomery ladder&quot;, definition: &quot;A scalar-multiplication method whose sequence of field operations is identical for every secret bit, so it is constant-time by construction rather than by defensive coding.&quot; },
  { term: &quot;Clamping&quot;, definition: &quot;Forcing specific bits of an X25519 secret scalar so it is a multiple of the cofactor and a fixed length. It constrains your own secret and is emphatically not validation of an input point.&quot; },
  { term: &quot;Prime-order group (ristretto255)&quot;, definition: &quot;A prime-order group built in the encoding over cofactor-8 edwards25519, so decoding a byte string is itself the validation step and the cofactor failure class disappears at the type level (RFC 9496).&quot; }
]} questions={[
  { q: &quot;Why is ECDLP-hardness not the same as ECC-security?&quot;, a: &quot;A hard discrete log is necessary but not sufficient. Security is a property of the curve, the group, and the implementation together. Every deployed break exploited the implementation or the encoding, not the discrete log.&quot; },
  { q: &quot;What single mechanism explains the whole failure catalog?&quot;, a: &quot;The atom: a point you did not generate, used before it is validated, or a secret multiplied in observable time, turns your own scalar-multiplication routine into an oracle that leaks the key one small piece at a time.&quot; },
  { q: &quot;Why is clamping not point validation?&quot;, a: &quot;Clamping constrains your own secret scalar so a low-order input contributes nothing to your Diffie-Hellman product, but it neither validates nor canonicalizes an input point. A protocol that relies on point uniqueness needs an explicit prime-order subgroup check.&quot; },
  { q: &quot;Which curve should you use for which job?&quot;, a: &quot;X25519 for key agreement, Ed25519 for new signatures, P-256 only where FIPS compels it, secp256k1 only for blockchain, and ristretto255 for new prime-order protocols. Long-lived confidentiality gets the X25519 plus ML-KEM-768 hybrid.&quot; },
  { q: &quot;Why does even correctly implemented ECC have an expiration date?&quot;, a: &quot;For a well-chosen curve the classical security level is exactly the generic square-root bound, with no margin above it, and Shor&apos;s algorithm breaks the discrete log in polynomial time on a quantum computer. The answer is to wrap ECC in a post-quantum hybrid.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>elliptic-curve-cryptography</category><category>curve25519</category><category>p-256</category><category>ristretto255</category><category>invalid-curve-attack</category><category>constant-time</category><category>post-quantum-crypto</category><category>applied-cryptography</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>