<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Parag Mali - tag: side-channel-attacks</title><description>Posts tagged side-channel-attacks.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sun, 19 Jul 2026 05:08:42 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/side-channel-attacks/rss.xml" rel="self" type="application/rss+xml"/><item><title>How Elliptic Curves and Diffie-Hellman Break in Real Life: The Discrete Log Never Fell</title><link>https://paragmali.com/blog/how-elliptic-curves-and-diffie-hellman-break-in-real-life-th/</link><guid isPermaLink="true">https://paragmali.com/blog/how-elliptic-curves-and-diffie-hellman-break-in-real-life-th/</guid><description>No one has solved the discrete log on a strong curve or a 2048-bit group -- yet PS3, Android wallets, TPMs, CurveBall, and Logjam all fell. Here is exactly how.</description><pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate><content:encoded>
On a strong curve or a 2048-bit group, no one has ever solved the discrete logarithm in the field -- yet PlayStation 3 consoles, Android Bitcoin wallets, tamper-resistant TPMs, Windows certificate validation, and live TLS sessions all fell. Every one of those breaks attacked a layer the discrete-log math depends on but does not control: the per-signature nonce (PS3, Android, Minerva, TPM-Fail, LadderLeak), the curve and certificate parameters (CurveBall), or the negotiated strength (Logjam). A *reused* nonce surrenders the key from just two signatures; a *biased* nonce needs a statistical attack over many. The only discrete log ever computed here was Logjam&apos;s, against a downgrade-forced, export-crippled, shared 512-bit prime -- never a strong deployed curve. The fix is never a bigger curve. It is hedged, constant-time signing, validated parameters, and retiring export and shared groups.
&lt;h2&gt;1. Seven Breaks, One Unbroken Problem&lt;/h2&gt;
&lt;p&gt;On a well-chosen elliptic curve, the best attack anyone has ever fielded against the discrete logarithm is a square-root-time random walk that has barely moved since the 1970s [@hac-ch11][@teske-2001]. No deployed P-256, Curve25519, or secp256k1 key, and no standard 2048-bit Diffie-Hellman group, has ever had its discrete log solved in public. By every measurement we can make, the mathematics is holding.&lt;/p&gt;
&lt;p&gt;And yet. PlayStation 3 consoles were jailbroken [@fail0verflow-ps3-2010]. Android Bitcoin wallets were emptied [@bitcoin-android-2013]. Tamper-resistant TPMs certified to government standards surrendered their signing keys [@tpmfail-2020]. Windows accepted forged certificates that chained to a counterfeit authority [@curveball-cve-2020-0601]. Live TLS sessions were downgraded and decrypted in transit [@logjam-weakdh-2015]. Every one of those systems ran the discrete-log math exactly as specified. The math was never where they broke.&lt;/p&gt;
&lt;p&gt;That is the paradox this article resolves, and the resolution fits in one sentence: none of these attacks solved a discrete logarithm. Each attacked a layer the math &lt;em&gt;depends on but does not control&lt;/em&gt;. There are exactly three such layers, and they organize everything that follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The per-signature nonce&lt;/strong&gt; -- the throwaway secret ECDSA needs for every signature. PS3, Android, Minerva, TPM-Fail, and LadderLeak all landed here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The curve and certificate parameters&lt;/strong&gt; -- which curve, and whose generator, you are actually talking to. CurveBall landed here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The negotiated strength&lt;/strong&gt; -- how strong a key exchange two parties agree to use before they exchange anything. Logjam landed here.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The discrete-logarithm assumption protects the private key against being &lt;em&gt;directly computed&lt;/em&gt; -- and nothing else. &quot;Nobody solved the discrete log&quot; and &quot;somebody recovered the private key&quot; are not contradictory statements, because the discrete-log &lt;em&gt;problem&lt;/em&gt; (genuinely hard) and a discrete-log &lt;em&gt;deployment&lt;/em&gt; (a nonce, some parameters, a negotiation) are different things. Every real break in this article lives in the gap between them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A note on scope. This piece treats the hardness of the elliptic-curve discrete logarithm as a black box: assume it holds, and watch everything around it fail. The structural mathematics of curve choice -- why one curve is weak and another is strong, how the number field sieve and Shor&apos;s algorithm actually work -- belongs to a companion article, &quot;How the Discrete Log Would Break: Elliptic Curves, Finite Fields, and Why Curve Choice Is the Weak Part.&quot; Here, we assume the strongbox is uncrackable and ask how the key kept getting out anyway.&lt;/p&gt;
&lt;p&gt;To see why a hard problem protects so little, you first have to see exactly what it promises -- and what it quietly leaves to the deployment.&lt;/p&gt;
&lt;h2&gt;2. What the Math Actually Promises&lt;/h2&gt;
&lt;p&gt;In 1976, Whitfield Diffie and Martin Hellman reduced the ancient problem of exchanging a secret over an open wire to a single piece of hard arithmetic: the discrete logarithm [@diffie-hellman-1976]. Two parties each pick a private exponent, exchange public powers of a shared generator, and combine them into a key that an eavesdropper cannot reconstruct without solving for one of those exponents.&lt;/p&gt;
&lt;p&gt;Nine years later, Victor Miller and, independently, Neal Koblitz moved the same hard problem onto elliptic curves [@miller-1985][@koblitz-1987]. Koblitz argued the curve version was &quot;likely to be harder than the classical discrete logarithm problem&quot; [@koblitz-1987] -- which is why a 256-bit curve buys security that a finite field needs thousands of bits to match. A clean, triumphant origin. So where did the promise leak?&lt;/p&gt;

Given a generator `G` and a public point `Q = dG` on an elliptic curve (or a base `g` and `g^d` in a finite field), recovering the secret integer `d` is the discrete logarithm problem. The security of ECDSA, ECDH, and Diffie-Hellman rests on the assumption that this recovery is computationally infeasible for well-chosen parameters. It is the one thing the math promises -- and, as we will see, the only thing.
&lt;p&gt;Signatures are where the story starts, because signatures introduce a second secret. The Elliptic Curve Digital Signature Algorithm (ECDSA) signs a message hash &lt;code&gt;z&lt;/code&gt; under a long-term private key &lt;code&gt;d&lt;/code&gt; by generating a fresh random value &lt;code&gt;k&lt;/code&gt;, computing a point &lt;code&gt;kG&lt;/code&gt;, taking its x-coordinate as &lt;code&gt;r&lt;/code&gt;, and producing:&lt;/p&gt;
&lt;p&gt;$$s = k^{-1},(z + r,d) \bmod n$$&lt;/p&gt;
&lt;p&gt;where &lt;code&gt;n&lt;/code&gt; is the order of the group. The verifier only ever sees &lt;code&gt;(r, s)&lt;/code&gt;. The private key &lt;code&gt;d&lt;/code&gt; is buried inside, protected by the discrete-log assumption -- nobody can read &lt;code&gt;d&lt;/code&gt; off the signature. But look at what else is in that equation.&lt;/p&gt;

The `k` in ECDSA is a *nonce*: a number used once. It must be secret, statistically uniform, and unique to each signature. It is not the private key, and it is thrown away the instant the signature is produced. It is also the single most load-bearing prerequisite in the whole scheme -- because `k` and `d` appear together in one linear equation, anything an attacker learns about `k` turns directly into knowledge of `d`.
&lt;p&gt;The discrete-log assumption guards &lt;code&gt;d&lt;/code&gt; against being &lt;em&gt;computed&lt;/em&gt; from the public key. It says nothing whatsoever about three things the assumption quietly depends on but does not itself enforce:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The nonce &lt;code&gt;k&lt;/code&gt; must be secret, uniform, and unique.&lt;/strong&gt; The math assumes a perfect &lt;code&gt;k&lt;/code&gt; and never checks that you supplied one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The parameters must be the standard, named ones.&lt;/strong&gt; The math assumes you are on the curve you think you are, with the generator you think you have.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The negotiated strength must not be crippled or shared.&lt;/strong&gt; The math assumes the group you agreed to use is actually strong.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each of those is the deployment&apos;s responsibility, not the primitive&apos;s. And each one became a pillar of failure.&lt;/p&gt;

flowchart TD
    DLP[&quot;(EC)DLP is hard: d cannot be computed from the public key dG&quot;]
    DLP --&amp;gt; N[&quot;Depends on the per-signature nonce k&quot;]
    DLP --&amp;gt; P[&quot;Depends on the curve and certificate parameters&quot;]
    DLP --&amp;gt; G[&quot;Depends on the negotiated group strength&quot;]
    N --&amp;gt; P1[&quot;Pillar 1 nonce: PS3, Android, Minerva, TPM-Fail, LadderLeak&quot;]
    P --&amp;gt; P2[&quot;Pillar 2 validation: CurveBall&quot;]
    G --&amp;gt; P3[&quot;Pillar 3 downgrade: Logjam&quot;]
&lt;p&gt;The curve migration itself is not our subject.Miller (1985) and Koblitz (1987) are mentioned here only to fix the primitive&apos;s birth. Whether a &lt;em&gt;particular&lt;/em&gt; curve is structurally weak -- anomalous curves, small embedding degrees, the number field sieve, Shor&apos;s algorithm -- is the structural mathematics deferred to the companion article. This article assumes the curve is strong and attacks everything around it. The math guards the key against being computed. It says nothing about the key being &lt;em&gt;handed over&lt;/em&gt;. Three contracts govern that handover, and each one broke.&lt;/p&gt;
&lt;h2&gt;3. Three Contracts the Deployment Signs and Never Reads&lt;/h2&gt;
&lt;p&gt;Every cryptographic deployment signs a contract it never reads. There are three clauses, one for each layer the math leans on, and the fine print on all three is where real products sank.&lt;/p&gt;
&lt;h3&gt;The nonce contract: k must be fresh&lt;/h3&gt;
&lt;p&gt;Take the ECDSA signing equation and ask the cruelest possible question: what if &lt;code&gt;k&lt;/code&gt; is not unique? Suppose two different messages, with hashes &lt;code&gt;z1&lt;/code&gt; and &lt;code&gt;z2&lt;/code&gt;, are signed under the &lt;em&gt;same&lt;/em&gt; nonce &lt;code&gt;k&lt;/code&gt;. Because &lt;code&gt;r&lt;/code&gt; is the x-coordinate of &lt;code&gt;kG&lt;/code&gt;, the same &lt;code&gt;k&lt;/code&gt; produces the same &lt;code&gt;r&lt;/code&gt; in both signatures. Now write them down side by side:&lt;/p&gt;
&lt;p&gt;$$s_1 = k^{-1}(z_1 + r,d), \qquad s_2 = k^{-1}(z_2 + r,d) \pmod n$$&lt;/p&gt;
&lt;p&gt;Subtract. The &lt;code&gt;rd&lt;/code&gt; term is identical in both, so it cancels, and &lt;code&gt;k&lt;/code&gt; falls straight out:&lt;/p&gt;
&lt;p&gt;$$s_1 - s_2 = k^{-1}(z_1 - z_2), \qquad k = \frac{z_1 - z_2}{s_1 - s_2} \bmod n$$&lt;/p&gt;
&lt;p&gt;Once you know &lt;code&gt;k&lt;/code&gt;, the first equation has exactly one unknown left, and you solve for the private key by hand:&lt;/p&gt;
&lt;p&gt;$$d = \frac{s_1,k - z_1}{r} \bmod n$$&lt;/p&gt;
&lt;p&gt;No lattice. No side channel. No discrete log. &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;Two signatures that share a nonce&lt;/a&gt; are two linear equations in two unknowns, and a middle-schooler with modular arithmetic can finish the job.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The discrete-logarithm assumption protects &lt;code&gt;d&lt;/code&gt; against &lt;em&gt;direct computation from the public key&lt;/em&gt; -- and nothing else. It does not protect &lt;code&gt;d&lt;/code&gt; against being algebraically extracted when the nonce misbehaves. The moment two signatures reuse &lt;code&gt;k&lt;/code&gt;, the strongbox is irrelevant, because the deployment left a second door wide open.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The contract, then, is strict: &lt;code&gt;k&lt;/code&gt; must be secret, statistically uniform, and never reused or partially leaked. Break any word of that clause and the algebra above (or its statistical cousin, later) takes your key.&lt;/p&gt;
&lt;h3&gt;The parameter contract: talk to the curve you think you are&lt;/h3&gt;
&lt;p&gt;The second clause is about identity. A &lt;em&gt;named&lt;/em&gt; curve -- &quot;P-256,&quot; &quot;secp256k1&quot; -- is an agreement to use one specific set of domain parameters, and in particular one specific generator, the base point &lt;code&gt;G&lt;/code&gt; that every public key is measured against.&lt;/p&gt;

Every named elliptic curve fixes a single public point `G` from which all key material is derived: a private key `d` corresponds to the public point `dG`. The generator is not a secret -- it is a published constant. But it is load-bearing: &quot;the public key is `dG`&quot; is only meaningful if both sides agree on which `G`.

A certificate can specify its curve two ways. A *named curve* is an object identifier pointing at standardized domain parameters (fixed `a`, `b`, `p`, `G`, `n`). *Explicit parameters* spell out all of those numbers inline, which lets the certificate carry a *custom* generator. Accepting explicit parameters without checking them against a known named curve means trusting an attacker to tell you what `G` is.
&lt;p&gt;The contract: never trust attacker-supplied explicit parameters. A validator that checks a public &lt;em&gt;point&lt;/em&gt; but not the &lt;em&gt;generator&lt;/em&gt; it was measured against has, in effect, let the attacker choose the ruler and then declared the measurement correct. That is precisely the door CurveBall walked through [@curveball-cve-2020-0601].&lt;/p&gt;
&lt;h3&gt;The negotiation contract: agree to strength, not to a museum piece&lt;/h3&gt;
&lt;p&gt;The third clause governs what two parties agree to &lt;em&gt;before&lt;/em&gt; they exchange anything. In &lt;a href=&quot;https://paragmali.com/blog/nobody-broke-the-discrete-log-a-field-guide-to-diffie-hellma/&quot; rel=&quot;noopener&quot;&gt;ephemeral Diffie-Hellman&lt;/a&gt;, each side picks a fresh secret per connection, which is supposed to guarantee that compromising one session cannot retroactively expose others.&lt;/p&gt;

In ephemeral DH, the private exponents are generated fresh for each connection and discarded afterward, so a key recovered later cannot decrypt past sessions. This property is called forward secrecy. It is the reason &quot;just use a fresh ephemeral key&quot; feels like a complete defense -- and, as Logjam showed, the reason that feeling is dangerously incomplete.

In the 1990s, US export regulations capped the strength of cryptography shipped abroad, producing deliberately weakened cipher suites such as 512-bit `DHE_EXPORT` Diffie-Hellman [@logjam-weakdh-2015]. The policy was abandoned, but the code was not: two decades later, servers and libraries still spoke these crippled suites on request.
&lt;p&gt;The contract: the negotiated group must be strong, fresh, and unshared. The fine print that sank it: many servers ship the &lt;em&gt;same&lt;/em&gt; standardized 512-bit prime, so breaking one group breaks all of them at once [@logjam-weakdh-2015]. A fresh ephemeral exponent does not save you when the prime itself is weak and shared.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contract&lt;/th&gt;
&lt;th&gt;What it requires&lt;/th&gt;
&lt;th&gt;What violates it&lt;/th&gt;
&lt;th&gt;Field break&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Nonce&lt;/td&gt;
&lt;td&gt;&lt;code&gt;k&lt;/code&gt; secret, uniform, unique per signature&lt;/td&gt;
&lt;td&gt;a constant, repeated, or biased &lt;code&gt;k&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PS3, Android, Minerva, TPM-Fail, LadderLeak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parameter&lt;/td&gt;
&lt;td&gt;named curve, standard generator &lt;code&gt;G&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;trusting attacker-supplied explicit parameters&lt;/td&gt;
&lt;td&gt;CurveBall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Negotiation&lt;/td&gt;
&lt;td&gt;strong, fresh, unshared group&lt;/td&gt;
&lt;td&gt;downgrade to a crippled, shared group&lt;/td&gt;
&lt;td&gt;Logjam&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Three contracts, three clauses of fine print. Over fifteen years, every clause was breached in the field -- and the breaches fall into exactly three piles.&lt;/p&gt;
&lt;h2&gt;4. The Failure Catalog: Seven Breaks, Three Layers, One Shape&lt;/h2&gt;
&lt;p&gt;None of the seven incidents below solved a discrete logarithm. Every one of them recovered a private key or forged a trusted one. Watch where each attack actually landed -- because the landing spot, not the marquee name, is the whole lesson.&lt;/p&gt;

timeline
    title EC and DH in the field, the assumption versus the breaks
    1976 : Diffie-Hellman, the assumption is born
    1985-1987 : Miller and Koblitz, the curve migration
    2010 : PS3 constant nonce, reuse
    2013 : Android SecureRandom, reuse
    2015 : Logjam, downgrade plus shared-prime NFS
    2019-2020 : Minerva and TPM-Fail, lattice bias
    2020 : LadderLeak, Bleichenbacher-Fourier sub-bit : CurveBall, unvalidated generator
&lt;p&gt;Here is the entire corpus on one page. The rightmost column is the thesis in a single word, repeated seven times.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Incident&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Pillar&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Effort to break&lt;/th&gt;
&lt;th&gt;What fell&lt;/th&gt;
&lt;th&gt;Discrete log solved?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;PS3&lt;/td&gt;
&lt;td&gt;2010&lt;/td&gt;
&lt;td&gt;Nonce (reuse)&lt;/td&gt;
&lt;td&gt;a constant &lt;code&gt;k&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2 signatures&lt;/td&gt;
&lt;td&gt;ECDSA firmware-signing key&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android wallets&lt;/td&gt;
&lt;td&gt;2013&lt;/td&gt;
&lt;td&gt;Nonce (reuse)&lt;/td&gt;
&lt;td&gt;repeated &lt;code&gt;k&lt;/code&gt; from a broken RNG&lt;/td&gt;
&lt;td&gt;2 reused signatures&lt;/td&gt;
&lt;td&gt;Bitcoin wallet keys&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logjam&lt;/td&gt;
&lt;td&gt;2015&lt;/td&gt;
&lt;td&gt;Downgrade&lt;/td&gt;
&lt;td&gt;forced 512-bit shared prime&lt;/td&gt;
&lt;td&gt;1 NFS precompute per prime&lt;/td&gt;
&lt;td&gt;TLS session secrecy&lt;/td&gt;
&lt;td&gt;Only a crippled, shared 512-bit export prime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPM-Fail&lt;/td&gt;
&lt;td&gt;2019-2020&lt;/td&gt;
&lt;td&gt;Nonce (bias)&lt;/td&gt;
&lt;td&gt;timing leak of nonce bit-length&lt;/td&gt;
&lt;td&gt;many signatures + lattice&lt;/td&gt;
&lt;td&gt;256-bit TPM keys&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minerva&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;Nonce (bias)&lt;/td&gt;
&lt;td&gt;timing leak of nonce bit-length&lt;/td&gt;
&lt;td&gt;about 2100 signatures + lattice&lt;/td&gt;
&lt;td&gt;ECDSA key&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LadderLeak&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;Nonce (bias)&lt;/td&gt;
&lt;td&gt;sub-bit ladder leak&lt;/td&gt;
&lt;td&gt;many signatures + Bleichenbacher/Fourier&lt;/td&gt;
&lt;td&gt;ECDSA key&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CurveBall&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;an unchecked generator&lt;/td&gt;
&lt;td&gt;1 forged certificate&lt;/td&gt;
&lt;td&gt;certificate trust itself&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Six &quot;No&quot;s and one heavily qualified &quot;Yes.&quot; Let us walk the three pillars in turn.&lt;/p&gt;
&lt;h3&gt;Pillar 1a: the nonce, reused (certain, from two signatures)&lt;/h3&gt;
&lt;p&gt;In December 2010, at the 27th Chaos Communication Congress, the group fail0verflow presented &quot;Console Hacking 2010&quot; and revealed that Sony had signed PlayStation 3 firmware using ECDSA with a &lt;em&gt;constant&lt;/em&gt; nonce [@fail0verflow-ps3-2010]. Not a weak random &lt;code&gt;k&lt;/code&gt;. The same &lt;code&gt;k&lt;/code&gt;, every time. Every signature Sony ever produced shared a nonce with every other, which means any two of them collapse to the algebra from Section 3.Keep two PS3 events distinct. fail0verflow&apos;s 27C3 talk exposed the constant-nonce &lt;em&gt;mechanism&lt;/em&gt;. George Hotz released a separate PS3 root key weeks later, in January 2011 [@hotz-ps3-2011]. The teaching case -- a hardcoded constant nonce -- is fail0verflow&apos;s. The console&apos;s chain of trust, the thing Sony spent years engineering, came apart because &lt;a href=&quot;https://paragmali.com/blog/predictable-or-repeated-the-only-two-ways-cryptographic-rand/&quot; rel=&quot;noopener&quot;&gt;a random number generator&lt;/a&gt; was told to always return the same number.&lt;/p&gt;

flowchart LR
    A[&quot;Sig 1: s1 = k^-1 (z1 + r d)&quot;] --&amp;gt; C[&quot;Same k means same r&quot;]
    B[&quot;Sig 2: s2 = k^-1 (z2 + r d)&quot;] --&amp;gt; C
    C --&amp;gt; D[&quot;Subtract, k cancels: k = (z1 - z2) / (s1 - s2) mod n&quot;]
    D --&amp;gt; E[&quot;Solve for d = (s1 k - z1) / r mod n&quot;]
    E --&amp;gt; F[&quot;Private key recovered, no discrete log solved&quot;]
&lt;p&gt;This is not a metaphor. Here is the recovery, executable. Two signatures share a nonce, and a dozen lines of BigInt arithmetic reproduce the private key exactly:&lt;/p&gt;
&lt;p&gt;{`
// secp256k1 group order
const n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n;
const mod = (a, m) =&amp;gt; ((a % m) + m) % m;&lt;/p&gt;
&lt;p&gt;// Modular inverse via the extended Euclidean algorithm
function inv(a, m) {
  let [oldr, r] = [mod(a, m), m];
  let [olds, s] = [1n, 0n];
  while (r !== 0n) {
    const q = oldr / r;
    [oldr, r] = [r, oldr - q * r];
    [olds, s] = [s, olds - q * s];
  }
  return mod(olds, m);
}&lt;/p&gt;
&lt;p&gt;// A victim signs two different messages with the SAME nonce k
const d  = 0x00c0ffee1234567890abcdef00c0ffee1234567890abcdef00c0ffee12345678n; // secret
const k  = 0x0000deadbeef0000feed0000deadbeef0000feed0000deadbeef0000feed0001n; // reused nonce
const r  = mod(0x1122334455667788112233445566778811223344556677881122334455667788n, n);
const z1 = 0x1111111111111111111111111111111111111111111111111111111111111111n;
const z2 = 0x2222222222222222222222222222222222222222222222222222222222222222n;
const s1 = mod(inv(k, n) * mod(z1 + mod(r * d, n), n), n);
const s2 = mod(inv(k, n) * mod(z2 + mod(r * d, n), n), n);&lt;/p&gt;
&lt;p&gt;// The attacker sees only (r, s1, z1) and (r, s2, z2). Recover k, then d.
const kRec = mod((z1 - z2) * inv(mod(s1 - s2, n), n), n);
const dRec = mod((mod(s1 * kRec, n) - z1) * inv(r, n), n);
console.log(&apos;recovered nonce matches:  &apos; + (kRec === k));
console.log(&apos;recovered private key:    &apos; + (dRec === d));
console.log(&apos;d = 0x&apos; + dRec.toString(16));
`}&lt;/p&gt;
&lt;p&gt;Three years later, the same algebra emptied wallets. In August 2013, an advisory on bitcoin.org warned that &quot;a component of Android responsible for generating secure random numbers contains critical weaknesses&quot; that rendered &quot;all Android wallets&quot; vulnerable to theft [@bitcoin-android-2013]. The root cause, per Google&apos;s own postmortem, was that the platform&apos;s &lt;code&gt;SecureRandom&lt;/code&gt; &quot;may not receive cryptographically strong values&quot; because the underlying PRNG was improperly initialized [@android-securerandom-2013].&lt;/p&gt;
&lt;p&gt;The consequence was nonce &lt;em&gt;repetition&lt;/em&gt; across transactions. And because every Bitcoin signature is broadcast to a public ledger, anyone could scan the chain for two signatures sharing an &lt;code&gt;r&lt;/code&gt; and drain the wallet behind them.The bitcoin.org advisory names the flaw and the affected wallets but states no total loss figure [@bitcoin-android-2013]. Any &quot;coins stolen&quot; number you have seen is reported, not established -- treat it accordingly.&lt;/p&gt;
&lt;p&gt;The detector that would have caught both PS3 and Android is embarrassingly short. Group public signatures by &lt;code&gt;r&lt;/code&gt;; any collision is an exposed key:&lt;/p&gt;
&lt;p&gt;{`
const n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n;
const mod = (a, m) =&amp;gt; ((a % m) + m) % m;
function inv(a, m) {
  let [oldr, r] = [mod(a, m), m];
  let [olds, s] = [1n, 0n];
  while (r !== 0n) { const q = oldr / r; [oldr, r] = [r, oldr - q * r]; [olds, s] = [s, olds - q * s]; }
  return mod(olds, m);
}
// Build a public feed in which one signer reused a nonce, then forget the secret
const dSecret = 777n, kReused = 424242n, rReused = 987654321n;
const mk = (z) =&amp;gt; ({ r: rReused, s: mod(inv(kReused, n) * mod(z + mod(rReused * dSecret, n), n), n), z });
const feed = [ { r: 111n, s: 22n, z: 5n }, mk(1001n), { r: 222n, s: 44n, z: 9n }, mk(2002n), { r: 333n, s: 66n, z: 12n } ];&lt;/p&gt;
&lt;p&gt;const seen = new Map();
for (const g of feed) {
  const key = g.r.toString();
  if (seen.has(key)) {
    const a = seen.get(key), b = g;
    const k = mod((a.z - b.z) * inv(mod(a.s - b.s, n), n), n);
    const d = mod((mod(a.s * k, n) - a.z) * inv(a.r, n), n);
    console.log(&apos;NONCE REUSE at r = &apos; + key + &apos;  -&amp;gt;  private key d = &apos; + d.toString());
  } else {
    seen.set(key, g);
  }
}
`}&lt;/p&gt;
&lt;p&gt;Two incidents, two different root causes -- a hardcoded constant and a starved RNG -- and one identical, certain outcome. This is the crudest form of the whole thesis: the deployment lost the key while the discrete log stood untouched.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A &lt;em&gt;repeated&lt;/em&gt; nonce is certain key recovery from just two signatures, by elementary algebra. A &lt;em&gt;biased&lt;/em&gt; nonce -- one that merely leans, statistically, toward certain values -- leaks only a partial statistic and requires a statistical attack over many signatures. &quot;One biased signature leaks your key&quot; is false. The distinction is the difference between the next two subsections, and blurring it is the single most common error in this area.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Reused nonce&lt;/th&gt;
&lt;th&gt;Biased nonce&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;What leaks&lt;/td&gt;
&lt;td&gt;the whole nonce (it repeats)&lt;/td&gt;
&lt;td&gt;a partial statistic about the nonce&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signatures needed&lt;/td&gt;
&lt;td&gt;exactly 2&lt;/td&gt;
&lt;td&gt;many (Minerva about 2100; PuTTY about 60 [@putty-cve-2024-31497])&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attack&lt;/td&gt;
&lt;td&gt;elementary algebra&lt;/td&gt;
&lt;td&gt;Hidden Number Problem (lattice or Fourier)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certainty&lt;/td&gt;
&lt;td&gt;certain&lt;/td&gt;
&lt;td&gt;statistical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Examples&lt;/td&gt;
&lt;td&gt;PS3, Android&lt;/td&gt;
&lt;td&gt;Minerva, TPM-Fail, LadderLeak, PuTTY&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Pillar 1b: the nonce, biased (statistical, over many signatures)&lt;/h3&gt;
&lt;p&gt;Reuse is loud. The subtler failure is a nonce that never repeats but is not quite uniform -- one whose top bit is a little too often zero, say. It turns out that even this surrenders the key, and the theory predicting it predates the exploits by two decades.&lt;/p&gt;

Given many samples that each leak a few of the most-significant bits of `k`, together with the public signature data, recover the fixed secret `d`. Boneh and Venkatesan defined this problem in 1996 and proved it tractable [@boneh-venkatesan-1996]. Every biased-nonce attack in this article -- Minerva, TPM-Fail, LadderLeak, PuTTY -- is an instance of it.
&lt;p&gt;Nguyen and Shparlinski turned the abstract HNP into a concrete lattice attack on (EC)DSA with partially known nonces [@nguyen-shparlinski-2003], and Brumley and Tuveri showed in 2011 that the necessary bit-leak could be harvested &lt;em&gt;remotely&lt;/em&gt;, over a network, from an OpenSSL implementation whose scalar multiplication was not constant-time [@brumley-tuveri-2011]. Then the field split into two solver regimes, and telling them apart is essential.&lt;/p&gt;
&lt;p&gt;When the &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;timing channel&lt;/a&gt; leaks a &lt;em&gt;few bits&lt;/em&gt; of the nonce -- typically its exact bit-length -- the HNP is solved by &lt;strong&gt;lattice reduction&lt;/strong&gt;. Minerva (CHES 2020) demonstrated this against smartcards and libraries whose scalar multiplication ran in time correlated with the nonce&apos;s length, reporting roughly &quot;2100 signatures to recover the private key on the standard secp256r1 curve&quot; [@minerva-2020].&lt;/p&gt;
&lt;p&gt;TPM-Fail (USENIX Security 2020) carried the same idea into hardware roots of trust. A timing leak in Intel firmware TPMs (CVE-2019-11090 [@cve-2019-11090]) and STMicroelectronics ST33 chips (CVE-2019-16863 [@cve-2019-16863]) recovered a 256-bit ECDSA key from an Intel fTPM in 4 to 20 minutes, and a VPN authentication key remotely in about five hours [@tpmfail-2020].Two honesty notes. Minerva&apos;s project page retracted its original EdDSA claim; the confirmed result is ECDSA-only [@minerva-2020]. And the TPM-Fail targets were not toys -- they held Common Criteria EAL4+ and FIPS 140-2 certification, and the leak survived that scrutiny [@tpmfail-2020].&lt;/p&gt;

flowchart TD
    L[&quot;A timing channel leaks partial information about the nonce k&quot;] --&amp;gt; Q{&quot;How much leaks per signature?&quot;}
    Q --&amp;gt;|&quot;at least 2 bits&quot;| LAT[&quot;Lattice reduction of the HNP: Minerva, TPM-Fail&quot;]
    Q --&amp;gt;|&quot;under 1 bit&quot;| FFT[&quot;Bleichenbacher and Fourier HNP: LadderLeak&quot;]
    LAT --&amp;gt; R[&quot;Private key d recovered over MANY signatures&quot;]
    FFT --&amp;gt; R
&lt;p&gt;The second regime is where people get it wrong. When the leak drops &lt;em&gt;below a single bit&lt;/em&gt; -- LadderLeak revealed the most-significant bit of the nonce &quot;but with probability less than one&quot; -- lattices become intractable, and the attack instead revives a Fourier-analysis method the paper credits to Daniel Bleichenbacher [@ladderleak-2020].&lt;/p&gt;
&lt;p&gt;The LadderLeak authors note that &quot;most of the attacks so far have relied on at least two bits of nonce bias,&quot; precisely the lattice regime. Their contribution was to break below it, over curves sect163r1 and NIST P-192, using the Fourier solver rather than a lattice [@ladderleak-2020]. Calling LadderLeak a lattice attack is the field&apos;s classic mistake.&lt;/p&gt;

&quot;Breaking ECDSA With Less Than One Bit Of Nonce Leakage&quot; -- the title of the LadderLeak paper, and the most compact statement of how little a nonce must betray before the key is gone.
&lt;p&gt;Notice what happened across this subsection. Every one of these attacks beat a signer that had already removed the reuse bug. The leak had moved from how the nonce was &lt;em&gt;generated&lt;/em&gt; to how it was &lt;em&gt;used&lt;/em&gt; -- a variable-time scalar multiplication -- and the cure had to move with it. Hold that thought; it is the pivot of the entire article. But reuse or bias, lattice or Fourier, two signatures or two thousand, the outcome is the same and the discrete logarithm is never touched: the deployment hands over &lt;code&gt;d&lt;/code&gt;, and the strongbox stays locked.&lt;/p&gt;
&lt;h3&gt;Pillar 2: the parameters, unvalidated (CurveBall)&lt;/h3&gt;
&lt;p&gt;The nonce pillar is about a secret the deployment mishandles. The second pillar is about an identity the deployment fails to check. On 14 January 2020, Microsoft patched CVE-2020-0601, reported by the NSA: &quot;a spoofing vulnerability exists in the way Windows CryptoAPI (Crypt32.dll) validates Elliptic Curve Cryptography (ECC) certificates&quot; [@curveball-cve-2020-0601]. It became known as CurveBall, and it is the cleanest example in the whole corpus of a break that has nothing to do with the mathematics of ECC.&lt;/p&gt;
&lt;p&gt;Recall the parameter contract. A public key is the point &lt;code&gt;dG&lt;/code&gt; -- meaningful only if everyone agrees on &lt;code&gt;G&lt;/code&gt;. CryptoAPI, when handed a certificate that specified its curve with &lt;em&gt;explicit&lt;/em&gt; parameters, checked that the certificate&apos;s public &lt;em&gt;point&lt;/em&gt; matched a trusted root&apos;s public key. What it did not check was the &lt;em&gt;generator&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So an attacker could craft a curve with a custom generator &lt;code&gt;G&apos;&lt;/code&gt;, pick any private key &lt;code&gt;d&apos;&lt;/code&gt; they liked, and compute &lt;code&gt;G&apos;&lt;/code&gt; such that &lt;code&gt;d&apos; * G&apos;&lt;/code&gt; equals the exact public point of a &lt;a href=&quot;https://paragmali.com/blog/a-perfect-signature-for-a-certificate-that-should-never-have/&quot; rel=&quot;noopener&quot;&gt;trusted root certificate authority&lt;/a&gt;. The forged certificate then presents the right public point on the wrong curve. Windows, comparing only the point, accepts it -- while a library that pins named curves rejects it instantly, because &lt;code&gt;G&apos;&lt;/code&gt; is not the standard base point.&lt;/p&gt;

flowchart TD
    A[&quot;Attacker picks explicit curve parameters with a custom generator G-prime&quot;] --&amp;gt; B[&quot;Chooses private key d-prime so that d-prime times G-prime equals a trusted root public point Q&quot;]
    B --&amp;gt; C[&quot;Forged certificate presents point Q on the attacker curve&quot;]
    C --&amp;gt; D{&quot;Windows CryptoAPI validation&quot;}
    D --&amp;gt;|&quot;Public point matches a trusted root? YES&quot;| E[&quot;Accepted, but the generator was never checked&quot;]
    D -.-&amp;gt;|&quot;A named-curve-pinning library also checks G&quot;| F[&quot;Rejected, G-prime is not the standard base point&quot;]
    E --&amp;gt; G[&quot;Forged code-signing or TLS certificate chains to a trusted root&quot;]
&lt;p&gt;The ECDLP was never in play. The attacker chose their own private key; they never had to recover anyone&apos;s. The failure is a single missing comparison -- generator against named curve -- buried in a certificate validation stack that a billion machines trusted. CurveBall is a validation bug, not a curve break, and the fix is exactly what the missing check would have been: reject explicit parameters, or fully re-validate them against the claimed named curve.&lt;/p&gt;
&lt;h3&gt;Pillar 3: the negotiation, downgraded (Logjam)&lt;/h3&gt;
&lt;p&gt;The third pillar is the one honest exception, and handled carelessly it looks like a refutation of the whole thesis. In 2015, the Logjam team (CVE-2015-4000) showed that a man-in-the-middle could rewrite a TLS handshake -- &quot;rewriting a ClientHello with DHE replaced by DHE_EXPORT&quot; -- to force the connection onto a 512-bit export-grade Diffie-Hellman group [@cve-2015-4000]. And here, unlike everywhere else, the attacker really does compute a discrete logarithm.&lt;/p&gt;
&lt;p&gt;The trick is that the expensive part of the number field sieve has to be done only once per prime, and servers share primes.&lt;/p&gt;

The fastest known classical algorithm for the finite-field discrete logarithm (and for integer factoring). Its decisive feature here: its costly first stage depends only on the *prime* being attacked, not on any particular exchanged key. Precompute once per prime, and every session under that prime becomes cheap to break. The internals belong to the companion article; what matters here is that one structural fact.
&lt;p&gt;The Logjam researchers found that &quot;8.4% of the Top 1 Million domains were initially vulnerable,&quot; and that because so many servers reused a handful of standardized 512-bit primes, &quot;the first step in the number field sieve ... is dependent only on this prime&quot; [@logjam-weakdh-2015]. One precomputation, amortized across every server that shipped that prime.&lt;/p&gt;

&quot;The first step in the number field sieve ... is dependent only on this prime.&quot; One precomputation breaks every server that shares the group.

sequenceDiagram
    participant C as Client
    participant M as MITM
    participant S as Server
    Note over M: One-time NFS precomputation per shared 512-bit prime
    C-&amp;gt;&amp;gt;M: ClientHello offering DHE
    M-&amp;gt;&amp;gt;S: ClientHello rewritten to DHE_EXPORT
    S-&amp;gt;&amp;gt;M: 512-bit export DH parameters, a shared standardized prime
    M-&amp;gt;&amp;gt;C: forwards the weak parameters
    Note over M: Precomputation done, so this connection solves fast
    M--&amp;gt;&amp;gt;C: session decrypted in near real time

The 512-bit `DHE_EXPORT` groups existed because 1990s US export regulations forbade shipping strong cryptography abroad, mandating deliberately breakable key exchange for foreign markets [@logjam-weakdh-2015]. The policy was abandoned by the early 2000s, but the cipher suites were never removed from server software. Logjam, in 2015, exploited a legal compromise that had been dead for over a decade and a codebase that never got the memo. The math did not decay -- the deployment simply kept a museum piece plugged in.
&lt;p&gt;Read carefully, Logjam is not a counterexample; it is the thesis in its most literal form. A discrete log &lt;em&gt;was&lt;/em&gt; computed, but only against a 512-bit prime that a downgrade forced the connection onto and that policy had left crippled and shared. The deployment set the weak, shared group on the table; the number field sieve merely finished the job. No strong, standard-sized group ever fell. The fix is the same shape as the break: remove export ciphers, negotiate vetted named groups (RFC 7919 [@rfc7919]), and adopt TLS 1.3, which removes static Diffie-Hellman entirely [@rfc8446].&lt;/p&gt;
&lt;p&gt;Three pillars, seven breaks, one shape: every attack hit a layer the discrete-log math depends on but does not control. Name the shape, and you have the whole discipline.&lt;/p&gt;
&lt;h2&gt;5. The Realization: Fix the Contracts, Not the Curve&lt;/h2&gt;
&lt;p&gt;There was no single eureka here. The insight accumulated across fifteen years of incidents and then snapped into one sentence: every field break attacked a layer the discrete-log math &lt;em&gt;depends on but does not control&lt;/em&gt; -- the nonce, then the parameters, then the negotiated strength -- and every fix changed &lt;em&gt;how the primitive is generated, used, validated, or negotiated&lt;/em&gt;, never the curve and never the log.&lt;/p&gt;
&lt;p&gt;The sharpest crystallization hides inside the word &quot;bias.&quot; For years it was treated as one problem with one fix: remove the random number generator, derive the nonce deterministically (RFC 6979 [@rfc6979], later blessed by FIPS 186-5 [@fips-186-5]), and the biased-nonce class is solved. But Minerva, TPM-Fail, and LadderLeak all recovered keys from signers that had &lt;em&gt;already&lt;/em&gt; done that. The reason is that &quot;bias&quot; was concealing two distinct root causes with two distinct cures.&lt;/p&gt;

flowchart TD
    B[&quot;One word: nonce bias&quot;] --&amp;gt; G[&quot;Generation defect: the RNG returns a constant, repeated, or predictable k&quot;]
    B --&amp;gt; U[&quot;Usage defect: variable-time scalar multiplication leaks the bit-length of k&quot;]
    G --&amp;gt; GC[&quot;Cure: deterministic and hedged nonces, RFC 6979 and FIPS 186-5&quot;]
    U --&amp;gt; UC[&quot;Cure: constant-time, exception-free scalar multiplication&quot;]
    GC --&amp;gt; BOTH[&quot;You need BOTH, they are orthogonal&quot;]
    UC --&amp;gt; BOTH
&lt;p&gt;A &lt;em&gt;generation&lt;/em&gt; defect is a bad nonce coming out of the sampler -- a constant (PS3), a repeat (Android), a predictable value. Determinism and hedging cure it. A &lt;em&gt;usage&lt;/em&gt; defect is a perfectly good nonce leaking through the &lt;em&gt;timing&lt;/em&gt; of the scalar multiplication that consumes it -- the bit-length falling out through a side channel (Minerva, TPM-Fail) or a sub-bit correlation (LadderLeak). Only constant-time code cures that. The two cures are orthogonal, and you need both.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Generalize the two-cures fork and you get the entire discipline of this field: four contracts, not one curve. &lt;em&gt;Generate&lt;/em&gt; the nonce so it cannot be predicted or repeated; &lt;em&gt;use&lt;/em&gt; it in constant time so it cannot leak; &lt;em&gt;validate&lt;/em&gt; your parameters so no attacker chooses your generator; &lt;em&gt;negotiate&lt;/em&gt; only strong, unshared groups so no downgrade can strand you. Every documented break is a breach of one of those four. No break in the corpus is a break of the discrete logarithm.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This also dissolves the apparent contradiction the article opened with. &quot;Nobody solved the discrete log&quot; and &quot;somebody recovered the key&quot; only sound incompatible if you conflate the discrete-log &lt;em&gt;problem&lt;/em&gt; with a discrete-log &lt;em&gt;deployment&lt;/em&gt;. The problem is a statement about computational hardness; the deployment is a nonce, a set of parameters, and a negotiation, wrapped around that hardness. The problem stayed hard. The wrapping tore.&lt;/p&gt;

If you remember one operational thing from this article, make it this: deterministic ECDSA and constant-time scalar multiplication are not two names for the same safeguard. A team can adopt RFC 6979, check the box marked &quot;nonce,&quot; and still ship a signer that a co-located attacker times to death. Ask both questions separately -- how is the nonce generated, and how is it used -- because they have separate answers.
&lt;p&gt;If the discipline is &quot;honor the four contracts, not the curve,&quot; what does a deployment that honors all four actually look like in 2026?&lt;/p&gt;
&lt;h2&gt;6. What Correct EC/DH Deployment Looks Like in 2026&lt;/h2&gt;
&lt;p&gt;The state of the art in this field is not a better discrete-log solver -- no such thing exists. It is a &lt;em&gt;deployment discipline&lt;/em&gt; at the three layers the math never controlled. Here it is, pillar by pillar.&lt;/p&gt;
&lt;h3&gt;Nonce: two orthogonal cures, and a primitive that folds in both&lt;/h3&gt;

Derive the nonce `k` deterministically from the private key and the message hash via an HMAC-based construction, so the signer &quot;do[es] not need access to a source of high-quality randomness&quot; [@rfc6979]. Identical key-and-message pairs always yield the same `k`; distinct messages yield unrelated nonces. This closes the *generation* defect -- the PS3 and Android reuse classes cannot occur.
&lt;p&gt;RFC 6979 also quietly records the deeper lesson. It insists on comparing each candidate nonce against the group order rather than reducing it modulo the order, warning that a &quot;simple modular reduction would induce biases that would be detrimental to signature security&quot; [@rfc6979]. That is a standards document, in 2013, acknowledging that &lt;em&gt;bias&lt;/em&gt;, not just reuse, is fatal. You can watch the effect yourself:&lt;/p&gt;
&lt;p&gt;{&lt;code&gt;// A raw sampler emits values in [0, 256). We need a nonce in [0, q). const q = 200n, B = 256n, trials = 400000; const raw = () =&amp;gt; BigInt(Math.floor(Number(B) * Math.random())); let biasedHigh = 0, fairHigh = 0; const half = q / 2n; for (let i = 0; i &amp;lt; trials; i++) {   const reduced = raw() % q;              // reduce mod q -&amp;gt; BIASED   if (reduced &amp;gt;= half) biasedHigh++;   let fair; do { fair = raw(); } while (fair &amp;gt;= q);  // compare to q, retry -&amp;gt; FAIR   if (fair &amp;gt;= half) fairHigh++; } console.log(&apos;reduce mod q -&amp;gt; P(nonce in top half): &apos; + (biasedHigh / trials).toFixed(4)); console.log(&apos;compare to q -&amp;gt; P(nonce in top half): &apos; + (fairHigh / trials).toFixed(4)); console.log(&apos;That gap from 0.5 is a free bit for a Hidden Number Problem attacker.&apos;);&lt;/code&gt;}&lt;/p&gt;
&lt;p&gt;Determinism, though, fixes only generation. On hardware it also &lt;em&gt;adds&lt;/em&gt; a risk.&lt;/p&gt;

Compute the nonce from the private key, the message hash, *and* a few bytes of fresh randomness. A dead RNG falls back to the safe deterministic core; a differential fault, which needs two identical signings to differ, is defeated by the fresh input. FIPS 186-5 recommends this for hardware because bare determinism is &quot;of particular concern for ... deterministic signature schemes, as well as embedded or IoT devices and smartcards&quot; [@fips-186-5].

Compute `kG` with a data-independent trace of instructions, branches, and memory accesses, so the runtime never depends on the bit-length or value of `k`. This is the *usage* cure -- the only defense against Minerva, TPM-Fail, and LadderLeak, whose leaks all live in the multiplication, not the sampler. Its precursor is Brumley and Tuveri&apos;s 2011 remote attack on a non-constant-time OpenSSL ladder [@brumley-tuveri-2011].
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Hedged signing and constant-time multiplication are the two cures of the fork, and they do not substitute for each other. A hardware signer needs &lt;em&gt;both&lt;/em&gt;: hedge the generation, and make the usage constant-time. Checking one box leaves the other break wide open.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;New designs can sidestep the whole fork by choosing a misuse-resistant primitive. Ed25519 (RFC 8032) derives its nonce as a hash of the secret and message -- deterministic by construction, no per-signature RNG -- and signs with a single &lt;em&gt;complete&lt;/em&gt;, constant-time addition law that has no exceptional cases to mishandle [@rfc8032]. It folds both cures into the primitive and removes the dangerous choices from the caller&apos;s hands.Misuse-resistant is not misuse-proof. PuTTY derived P-521 ECDSA nonces such that &quot;the first 9 bits of each ECDSA nonce are zero,&quot; allowing &quot;full secret key recovery in roughly 60 signatures&quot; (CVE-2024-31497) [@putty-cve-2024-31497]. Determinism implemented carelessly re-introduced exactly the bias it was meant to prevent.&lt;/p&gt;
&lt;h3&gt;Validation: reject what you cannot vouch for&lt;/h3&gt;
&lt;p&gt;The CurveBall cure is direct: bind a certificate&apos;s curve to the standard parameters of its named curve, and reject explicit parameters or fully re-validate them. The January 2020 patch made &lt;code&gt;crypt32.dll&lt;/code&gt; check the generator, not merely the point [@curveball-cve-2020-0601].&lt;/p&gt;
&lt;p&gt;For key &lt;em&gt;exchange&lt;/em&gt; with static keys, the discipline is fuller. Before using a received point, confirm it is not the identity, lies on the curve, and sits in the correct prime-order subgroup -- the full public-key validation of NIST SP 800-56A, which blocks &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;invalid-curve and small-subgroup attacks&lt;/a&gt; that would otherwise bleed a static key out one query at a time [@sp-800-56a-r3].&lt;/p&gt;
&lt;p&gt;The most durable answer is to make invalid inputs unrepresentable. X25519 works on a Montgomery curve chosen so that &lt;em&gt;every&lt;/em&gt; 32-byte string is an acceptable public key; the RFC even instructs implementations to &quot;accept non-canonical values,&quot; and to run the conditional swap &quot;in constant time&quot; [@rfc7748]. There is no explicit-parameter path to abuse and no on-curve check to forget.This is the &quot;make invalid states unrepresentable&quot; principle applied to cryptography: the safest validation code is the code you deleted because the type system already guarantees the property. But note the boundary carefully.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; X25519&apos;s scalar &quot;clamping&quot; fixes the cofactor and twist problems for ephemeral key exchange. It does &lt;em&gt;not&lt;/em&gt; replace named-curve pinning in the X.509 world, and some protocols still need a contributory-behavior check on the shared secret. Twist-secure curves solve the problems they were designed for -- not every parameter problem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Negotiation: remove the weak option entirely&lt;/h3&gt;
&lt;p&gt;The downgrade pillar&apos;s fix is structural. RFC 7919 replaced ad-hoc, shared Diffie-Hellman primes with a menu of vetted named groups negotiated like curves [@rfc7919]. TLS 1.3 went further and deleted the weak options outright, so there is nothing to downgrade &lt;em&gt;to&lt;/em&gt;.&lt;/p&gt;

&quot;Static RSA and Diffie-Hellman cipher suites have been removed; all public-key based key exchange mechanisms now provide forward secrecy.&quot; -- RFC 8446, the TLS 1.3 specification [@rfc8446].
&lt;p&gt;Because TLS 1.3 permits only strong ephemeral groups and binds the negotiation into the handshake transcript, the Logjam rewrite is not merely discouraged -- it is structurally impossible [@rfc8446]. The end state of the negotiation pillar is a protocol in which the crippled, shared 512-bit group Logjam relied on simply cannot be selected.&lt;/p&gt;
&lt;p&gt;Each of these is a choice, and where there is a choice, engineers argue. Which discipline actually wins in which threat model?&lt;/p&gt;
&lt;h2&gt;7. Competing Approaches: Which Defense Answers Which Adversary&lt;/h2&gt;
&lt;p&gt;These defenses are not rungs on one ladder. They are answers to different adversaries, and the honest way to compare them is one pillar at a time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A constant-time multiplier and a named-curve pin do not compete -- they defend different layers, and a real system needs one from each pillar. The comparisons below rank options &lt;em&gt;within&lt;/em&gt; a pillar. Picking the best nonce discipline does nothing for your parameter validation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Nonce: which signing discipline stops which adversary&lt;/h3&gt;
&lt;p&gt;The load-bearing insight is that no single column stops every row. Generation cures (deterministic [@rfc6979], hedged [@fips-186-5]) and the usage cure (constant-time) are orthogonal, which is why hedged-plus-constant-time became the practitioner default and why Ed25519 [@rfc8032] folds most of both into the primitive.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Adversary / failure&lt;/th&gt;
&lt;th&gt;Randomized (bare RNG)&lt;/th&gt;
&lt;th&gt;Deterministic (RFC 6979)&lt;/th&gt;
&lt;th&gt;Hedged (FIPS 186-5)&lt;/th&gt;
&lt;th&gt;Constant-time mult&lt;/th&gt;
&lt;th&gt;Ed25519&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;RNG returns constant or repeat (PS3, Android)&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;stops&lt;/td&gt;
&lt;td&gt;stops&lt;/td&gt;
&lt;td&gt;orthogonal&lt;/td&gt;
&lt;td&gt;stops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Differential fault on a deterministic signer&lt;/td&gt;
&lt;td&gt;randomness helps&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;stops&lt;/td&gt;
&lt;td&gt;orthogonal&lt;/td&gt;
&lt;td&gt;inherits the caveat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timing leak of nonce bit-length (Minerva, TPM-Fail)&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;stops&lt;/td&gt;
&lt;td&gt;stops (uniform ladder)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sub-bit ladder leak (LadderLeak)&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;stops&lt;/td&gt;
&lt;td&gt;stops (uniform ladder)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Read the rows, not the columns: reuse fails only the first row and is certain from two signatures; the timing leaks fail three of the five columns and are cured on a different axis entirely. That is the two-cures fork, rendered as a matrix.&lt;/p&gt;
&lt;h3&gt;Validation: three philosophies of not trusting your input&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Named-curve pinning&lt;/th&gt;
&lt;th&gt;Full validation (SP 800-56A)&lt;/th&gt;
&lt;th&gt;All-inputs-valid (X25519)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Philosophy&lt;/td&gt;
&lt;td&gt;reject non-standard parameters&lt;/td&gt;
&lt;td&gt;validate every received input&lt;/td&gt;
&lt;td&gt;make invalid inputs unrepresentable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stops CurveBall (unvalidated generator)&lt;/td&gt;
&lt;td&gt;yes, its exact fix&lt;/td&gt;
&lt;td&gt;yes, re-derives the generator&lt;/td&gt;
&lt;td&gt;yes, no explicit-parameter path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stops invalid-curve / small-subgroup&lt;/td&gt;
&lt;td&gt;partial&lt;/td&gt;
&lt;td&gt;yes, on-curve plus subgroup&lt;/td&gt;
&lt;td&gt;yes, twist-secure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-operation cost&lt;/td&gt;
&lt;td&gt;O(1) table compare&lt;/td&gt;
&lt;td&gt;O(1) up to one scalar mult&lt;/td&gt;
&lt;td&gt;one constant-time ladder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best suited for&lt;/td&gt;
&lt;td&gt;X.509, code-signing, PKI&lt;/td&gt;
&lt;td&gt;static-key ECDH, HSMs&lt;/td&gt;
&lt;td&gt;ephemeral ECDH&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Named-curve pinning is the precise CurveBall fix [@curveball-cve-2020-0601]; SP 800-56A validation is the belt-and-suspenders choice for static keys [@sp-800-56a-r3]; X25519 deletes the problem by construction [@rfc7748]. They converge on stopping CurveBall and diverge on everything else.&lt;/p&gt;
&lt;h3&gt;Downgrade: hardening, deletion, or hedging against tomorrow&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Config hardening + FFDHE&lt;/th&gt;
&lt;th&gt;TLS 1.3&lt;/th&gt;
&lt;th&gt;Hybrid post-quantum&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Removes the 512-bit export rung&lt;/td&gt;
&lt;td&gt;yes, within FFDHE&lt;/td&gt;
&lt;td&gt;yes, structurally&lt;/td&gt;
&lt;td&gt;yes, inherits 1.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Removes shared or unknown primes&lt;/td&gt;
&lt;td&gt;yes, named menu&lt;/td&gt;
&lt;td&gt;yes, fixed groups&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downgrade protection in the handshake&lt;/td&gt;
&lt;td&gt;no, still in-band&lt;/td&gt;
&lt;td&gt;yes, transcript-bound&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Defends against a future Shor adversary&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes, if ML-KEM holds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Residual weakness&lt;/td&gt;
&lt;td&gt;the legacy long tail&lt;/td&gt;
&lt;td&gt;non-TLS 1024-bit primes&lt;/td&gt;
&lt;td&gt;a KEM is not a NIKE, young PQC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;RFC 7919 named groups [@rfc7919] are the transitional hardening for TLS 1.2; TLS 1.3 is the structural end state [@rfc8446]; hybrid X25519 with &lt;a href=&quot;https://paragmali.com/blog/one-event-three-assumptions-five-answers-a-field-guide-to-th/&quot; rel=&quot;noopener&quot;&gt;ML-KEM&lt;/a&gt; (RFC 9954 [@rfc9954], ML-KEM standardized as FIPS 203 [@fips-203]) is the forward hedge against a quantum adversary. Only the last column defends against a break that would be genuine mathematics rather than a deployment slip.&lt;/p&gt;
&lt;p&gt;Every column in every table changed the deployment, not the curve. So is the discrete log itself simply unbreakable? Almost -- and the &quot;almost&quot; is the most honest paragraph in this article.&lt;/p&gt;
&lt;h2&gt;8. Theoretical Limits: What Is Provably True on Both Sides&lt;/h2&gt;
&lt;p&gt;This is the honest concession the thesis has been earning the right to make. There are two provable sides here, and the argument needs both.&lt;/p&gt;
&lt;h3&gt;The hardness side: no room for a strong curve to fall&lt;/h3&gt;
&lt;p&gt;On a well-chosen group, the discrete logarithm is genuinely hard, and we can say so with unusual precision. The best classical attack is Pollard&apos;s rho, a square-root-time random walk that needs negligible memory [@teske-2001]. In the strict generic model its expected cost is about $\sqrt{\pi n/2}$, roughly 1.25 times $\sqrt{n}$ group operations; on an elliptic curve, the negation map (where $P$ and $-P$ share an x-coordinate) trims that to about 0.886 times $\sqrt{n}$. Either way the exponent is the same, matching baby-step giant-step&apos;s $O(\sqrt{n})$ time [@hac-ch11].&lt;/p&gt;
&lt;p&gt;Against that upper bound, Shoup proved a generic &lt;em&gt;lower&lt;/em&gt; bound of $\Omega(\sqrt{p})$: no algorithm restricted to the group operation can do better [@shoup-1997]. The two meet at $\Theta(\sqrt{n})$. The generic gap is essentially closed -- which is exactly why a 256-bit curve delivers around 128-bit security, and why no strong deployed curve&apos;s discrete log has fallen.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Best classical DL attack&lt;/td&gt;
&lt;td&gt;Pollard&apos;s rho, about 1.25 times sqrt(n) generic; 0.886 times sqrt(n) on curves&lt;/td&gt;
&lt;td&gt;negligible space&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Baby-step giant-step&lt;/td&gt;
&lt;td&gt;O(sqrt(n)) time and O(sqrt(n)) space&lt;/td&gt;
&lt;td&gt;the space-heavy twin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generic lower bound&lt;/td&gt;
&lt;td&gt;Omega(sqrt(p)), provably no faster generic algorithm&lt;/td&gt;
&lt;td&gt;Shoup 1997&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finite-field DH (non-generic)&lt;/td&gt;
&lt;td&gt;sub-exponential via the number field sieve&lt;/td&gt;
&lt;td&gt;why DH needs about 3072-bit primes (2048-bit minimum)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strong deployed curve or standard group ever solved&lt;/td&gt;
&lt;td&gt;none, only Logjam&apos;s crippled shared 512-bit prime&lt;/td&gt;
&lt;td&gt;the thesis in one row&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The last two rows carry the concession. Finite-field Diffie-Hellman is &lt;em&gt;not&lt;/em&gt; generic: the number field sieve solves it in sub-exponential time, which is why finite-field DH needs primes of about 3072 bits to match the security a 256-bit curve gives (2048 bits is the absolute minimum), and why Logjam&apos;s downgrade to 512 bits was finishable at all [@logjam-weakdh-2015]. The number-field-sieve mathematics belongs to the companion article.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Logjam is the only break in this corpus that reached the mathematics, and the reason is structural, not accidental. A finite field carries extra arithmetic structure that a generic group lacks, which lets the number field sieve run in sub-exponential time, faster than the square-root barrier that protects a strong elliptic curve. That structural gap is the sole opening the discrete log ever offered, and even it was not enough alone: the computation became feasible only after the prime was cut to 512 export-grade bits. Raise the group back to a standard 2048-bit prime, or move to a curve where no such shortcut exists, and the opening closes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The leakage side: no room to hide a little bias&lt;/h3&gt;
&lt;p&gt;The other provable side indicts the deployment just as sharply. The Hidden Number Problem makes partial leakage fatal by construction: Boneh and Venkatesan proved that recovering a secret from many small most-significant-bit leaks is tractable [@boneh-venkatesan-1996], and Nguyen and Shparlinski turned that into a working lattice attack on (EC)DSA with partial nonces [@nguyen-shparlinski-2003].&lt;/p&gt;
&lt;p&gt;There is no &quot;a little bias is tolerable&quot; regime: any &lt;em&gt;consistent&lt;/em&gt; partial leak of &lt;code&gt;k&lt;/code&gt;, integrated over enough signatures, provably recovers &lt;code&gt;d&lt;/code&gt;. Two or more leaked bits fall to a lattice; a sub-bit leak falls to Bleichenbacher&apos;s Fourier method [@ladderleak-2020]. &quot;&lt;code&gt;k&lt;/code&gt; is secret, uniform, and unique&quot; is not a best practice -- it is a hard boundary.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The math is safe at the sizes we deploy (the generic gap is closed), and leakage is fatal at any size (the HNP admits no safe margin). Both proofs point the same way: the primitive is not the weak part. The nonce, the parameters, and the negotiation are.&lt;/p&gt;
&lt;/blockquote&gt;

Two clouds sit on the horizon. First, the shared-prime structure Logjam exploited at 512 bits revives at 1024. The Logjam team estimated that precomputing the single most common 1024-bit prime would expose roughly 18% of the top HTTPS sites; a second common prime would reach about 66% of IPsec VPNs and around 26% of SSH servers [@logjam-weakdh-2015] -- the same trick, resized, and a resourced adversary&apos;s project. Second, a large fault-tolerant quantum computer running Shor&apos;s algorithm would break the discrete logarithm outright, curve and finite field alike. That is the one future break that *is* the mathematics, and it is the subject of the companion article, not this one. The deployment answer, already shipping, is hybrid key exchange.
&lt;p&gt;So where, concretely, does EC and DH still break tomorrow?&lt;/p&gt;
&lt;h2&gt;9. Where EC and DH Still Break in the Field&lt;/h2&gt;
&lt;p&gt;The incidents are settled. The &lt;em&gt;classes&lt;/em&gt; are not. Every open problem below is a documented pattern waiting for its next instance, and each one maps to a pillar you have already met.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The next SecureRandom (nonce generation, Pillar 1).&lt;/strong&gt; A signer needs a unique, secret nonce at the exact moment its entropy is weakest: first boot, a headless IoT device with no entropy events, or a freshly cloned virtual machine carrying a duplicated seed. This is the mechanism behind Android 2013 [@android-securerandom-2013], and it recurs whenever hardware is provisioned faster than it can gather randomness.Cloned-VM state duplication is an especially nasty modern form: two hosts that never coordinated can emit the &lt;em&gt;same&lt;/em&gt; &quot;random&quot; nonce because they booted from one snapshot. The signing path can be hedged [@fips-186-5], but key generation and ephemeral key exchange still need a live, well-seeded source.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The next Minerva or LadderLeak (nonce usage, Pillar 1).&lt;/strong&gt; Constant-time scalar multiplication must be re-established against every new microarchitecture -- caches, execution ports, prefetchers, simultaneous multithreading -- and on every fragile incomplete-addition curve. The bit-length leak of Minerva [@minerva-2020] and the sub-bit leak of LadderLeak [@ladderleak-2020] both post-date deterministic nonces precisely because the leak had moved to usage. Formally verified primitives close the &lt;em&gt;known&lt;/em&gt; channels on the &lt;em&gt;chosen&lt;/em&gt; curve; FIPS-mandated P-curves and secp256k1 keep hand-written code in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The long tail of shared primes (downgrade, Pillar 3).&lt;/strong&gt; TLS 1.3 closes the web protocol layer, but export-grade and shared standardized primes outlived their policy elsewhere. IKEv1 VPNs and older SSH still ship shared 1024-bit Diffie-Hellman groups, and a single precomputation per shared prime amortizes across every server using it [@logjam-weakdh-2015]. The consensus recommendation is blunt: retire 1024-bit DH now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The next CurveBall (validation, Pillar 2).&lt;/strong&gt; CurveBall was one unchecked generator in one library [@curveball-cve-2020-0601]; the class is &quot;somewhere in a large validation stack, attacker-supplied parameters are trusted.&quot; Every new parser, certificate profile, or post-quantum key format reopens the class, and a single such bug in a widely embedded validator forges trust across everything that depends on it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Post-quantum migration ahead of Shor (cross-pillar).&lt;/strong&gt; Hybrid key exchange -- running X25519 alongside ML-KEM so the session stays secure if either component holds -- is standardized (RFC 9954 [@rfc9954]; concrete groups such as X25519MLKEM768 in the ongoing ECDHE-MLKEM work [@draft-ietf-tls-ecdhe-mlkem]; ML-KEM standardized as FIPS 203 [@fips-203]) and is already deployed at scale: it protected a majority of the human web traffic Cloudflare measured by late 2025 [@cloudflare-pq-radar]. But the migration carries a trap that has nothing to do with the discrete log.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; ML-KEM is a key-&lt;em&gt;encapsulation&lt;/em&gt; mechanism, not a non-interactive key agreement. It does not reproduce X25519&apos;s static Diffie-Hellman semantics, where two long-term public keys combine without any live exchange. Systems that quietly assume &quot;post-quantum DH&quot; is a drop-in replacement for classical DH will find the shape of the primitive has changed underneath them. Migrate deliberately, not by search-and-replace.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;None of these needs new mathematics to break you. Each needs exactly one deployment mistake. Here is how not to make it.&lt;/p&gt;
&lt;h2&gt;10. What to Do on Monday&lt;/h2&gt;
&lt;p&gt;Every item below maps to a specific incident in this article. Do these, and you do not ship the next one.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Never put a bare runtime RNG in the signing path.&lt;/strong&gt; Use deterministic (RFC 6979 [@rfc6979]) or, on hardware, hedged (FIPS 186-5 [@fips-186-5]) nonces. &lt;em&gt;Prevents:&lt;/em&gt; PS3, Android.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Demand constant-time, exception-free scalar multiplication.&lt;/strong&gt; This is a separate control from the nonce discipline, not a duplicate of it. &lt;em&gt;Prevents:&lt;/em&gt; Minerva, TPM-Fail, LadderLeak.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prefer misuse-resistant primitives.&lt;/strong&gt; Ed25519 for new signing [@rfc8032], X25519 for ephemeral key exchange [@rfc7748]; for static-key ECDH, run full public-key validation per SP 800-56A [@sp-800-56a-r3]. &lt;em&gt;Prevents:&lt;/em&gt; whole classes of implementer error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reject explicit curve parameters; require named curves.&lt;/strong&gt; Pin the curve, and the generator with it. &lt;em&gt;Prevents:&lt;/em&gt; CurveBall [@curveball-cve-2020-0601].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disable export ciphers and use vetted groups.&lt;/strong&gt; RFC 7919 named groups [@rfc7919] for residual TLS 1.2 DHE, or move to TLS 1.3 [@rfc8446]. &lt;em&gt;Prevents:&lt;/em&gt; Logjam.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never assume a fresh ephemeral rescues a shared prime.&lt;/strong&gt; The number-field-sieve precomputation is per-prime, not per-connection [@logjam-weakdh-2015]. &lt;em&gt;Prevents:&lt;/em&gt; the subtle half of Logjam.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan the post-quantum migration as hybrid, not pure.&lt;/strong&gt; Run a classical curve alongside ML-KEM (RFC 9954 [@rfc9954]); remember a KEM is not a NIKE. &lt;em&gt;Prevents:&lt;/em&gt; harvest-now-decrypt-later exposure.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; New signing design, free to pick the curve: Ed25519. Must use ECDSA on a NIST or secp256k1 curve: hedged RFC 6979 &lt;em&gt;and&lt;/em&gt; a vetted constant-time multiply. Hardware or embedded signer: hedged, never bare-deterministic. Ephemeral key exchange: X25519. Static-key ECDH: full SP 800-56A validation. Certificate or PKI validation: pin named curves. Any TLS: version 1.3, and if you are stuck on 1.2 DHE, RFC 7919 groups with export ciphers off. Long-lived confidentiality: hybrid X25519 with ML-KEM.&lt;/p&gt;
&lt;/blockquote&gt;

1. **Nonce:** For every signature, can you show the nonce is both unpredictable (hedged or deterministic) *and* consumed in constant time? If you can only answer one half, you are exposed to the other.
2. **Parameters:** Does your certificate and key-exchange path reject attacker-supplied explicit parameters and validate every received point against the named curve?
3. **Negotiation:** Can a man-in-the-middle force any weaker group or version than you intend? If you cannot prove &quot;no,&quot; assume &quot;yes.&quot;
&lt;p&gt;Notice that not one item on this list is &quot;use a bigger curve.&quot; For every break except Logjam&apos;s downgrade, key size is irrelevant -- a 384-bit curve reuses a nonce exactly as fatally as a 256-bit one. The controls are about generation, usage, validation, and negotiation, because those are the four contracts, and the contracts are where reality broke.&lt;/p&gt;
&lt;p&gt;Two questions decide whether you got this right, and they happen to be the two most-repeated errors in the field.&lt;/p&gt;
&lt;h2&gt;11. FAQ: Misconceptions, Precisely Corrected&lt;/h2&gt;

Once, and only against a deliberately crippled target. Logjam computed discrete logarithms against a 512-bit export-grade Diffie-Hellman prime -- one that a man-in-the-middle downgrade could select and that many servers had in common [@logjam-weakdh-2015][@cve-2015-4000]. No strong deployed curve -- P-256, Curve25519, secp256k1 -- and no standard-sized group of 2048 bits or more has ever had its discrete log solved in public. The ceiling of the entire corpus is a 512-bit prime that policy left broken.

No, and this is the most common error. A *repeated* nonce recovers the key with certainty from exactly two signatures, by elementary algebra. A merely *biased* nonce emits only a faint statistical signal per signature, so recovery takes many of them plus a dedicated lattice or Fourier attack -- about 2100 for Minerva on secp256r1 [@minerva-2020], roughly 60 for PuTTY&apos;s nine-zero-bit bias [@putty-cve-2024-31497]. One biased signature leaks almost nothing.

No. LadderLeak&apos;s leak is under a single bit per signature, which lattice methods cannot exploit; it revives Bleichenbacher&apos;s Fourier-analysis solver instead [@ladderleak-2020]. The lattice regime is Minerva and TPM-Fail, whose leaks are two bits or more [@minerva-2020][@tpmfail-2020]. Calling all three &quot;lattice attacks&quot; collapses a distinction the attacks themselves are built on.

No. Padding oracles are an RSA and CBC-mode phenomenon; there is no elliptic-curve or Diffie-Hellman padding-oracle exemplar, and you should not go looking to manufacture one. The EC and DH attack surface is exactly the three pillars here -- the nonce, parameter validation, and downgrade. RSA&apos;s padding oracles, and the fact that RSA signing has no per-signature secret nonce to reuse, are covered in the companion piece &quot;How RSA Breaks in Real Life.&quot;

Only against half the problem. RFC 6979 removes the random number generator from the signing path and eliminates the reuse class [@rfc6979], but it does nothing about a timing leak during nonce *usage* -- which is exactly how Minerva, TPM-Fail, and LadderLeak recovered keys from deterministic signers. The complete answer is hedged signing plus constant-time scalar multiplication [@fips-186-5], the two orthogonal cures.

Almost never. The nonce, validation, and downgrade defects are all indifferent to key size: a forged CurveBall certificate is accepted no matter how large the curve is, because the flaw is a skipped generator check rather than a hard curve [@curveball-cve-2020-0601], and a reused or leaked nonce surrenders the private key whether that key is 256 or 384 bits. The one place size mattered was the export-group downgrade, and there the fix was to stop negotiating the 512-bit group at all [@logjam-weakdh-2015], not to enlarge it.

The primitive, no. The discrete logarithm on a strong group remains as hard as it was in the 1970s. What broke, repeatedly, was the deployment around it. Fix the nonce path, validate your parameters against named curves, and retire export and shared groups, and you have closed every break in this article. CurveBall was a validation bug, not a curve break [@curveball-cve-2020-0601] -- and that distinction is the whole point.
&lt;p&gt;Half a century after Diffie and Hellman reduced key exchange to one hard problem, the scoreboard is unambiguous: seven documented field breaks across three deployment layers, and a discrete-log ceiling that only ever reached a deliberately crippled, shared 512-bit export group -- never a strong deployed curve, never a standard-sized group. The mathematics kept its promise. The deployments did not.&lt;/p&gt;

The discrete log never fell. The nonce did, the parameters did, and the negotiation did -- and every one of those is the deployment&apos;s job, not the curve&apos;s.
&lt;p&gt;For the other half of the story -- how a curve itself could be structurally weak, how the number field sieve and Shor&apos;s algorithm actually break the math -- see the structural companion, &quot;How the Discrete Log Would Break.&quot; For the primitive that has no per-signature nonce to reuse and its own &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;padding-oracle history&lt;/a&gt;, see the series sibling, &quot;How RSA Breaks in Real Life.&quot; This article was about the gap between a hard problem and a shipped product. That gap is where the keys keep getting out.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;how-ecc-and-dh-break-in-real-life&quot; keyTerms={[
  { term: &quot;Per-signature nonce (k)&quot;, definition: &quot;The once-used secret in ECDSA that must be secret, uniform, and unique per signature; the load-bearing prerequisite behind PS3, Android, Minerva, TPM-Fail, and LadderLeak.&quot; },
  { term: &quot;Nonce reuse vs. bias&quot;, definition: &quot;A repeated nonce recovers the key with certainty from two signatures; a biased nonce needs a statistical Hidden Number Problem attack over many signatures.&quot; },
  { term: &quot;Hidden Number Problem&quot;, definition: &quot;Recovering a fixed secret from many samples that each leak a few most-significant bits; the framework behind every biased-nonce attack.&quot; },
  { term: &quot;Two-cures fork&quot;, definition: &quot;Determinism and hedging fix nonce generation; constant-time scalar multiplication fixes nonce usage. The two are orthogonal and both are required.&quot; },
  { term: &quot;CurveBall (CVE-2020-0601)&quot;, definition: &quot;A Windows CryptoAPI bug that matched a certificate public point but never checked the generator against the named curve; a validation bug, not a curve break.&quot; },
  { term: &quot;Logjam (CVE-2015-4000)&quot;, definition: &quot;A TLS downgrade to a shared 512-bit export Diffie-Hellman prime, breakable because the number field sieve precomputes once per prime; the only discrete log in the corpus.&quot; },
  { term: &quot;Named curve vs. explicit parameters&quot;, definition: &quot;Referencing standardized domain parameters by identifier versus trusting attacker-supplied inline parameters, including a custom generator.&quot; }
]} questions={[
  { q: &quot;Why does a reused nonce recover the key from only two signatures?&quot;, a: &quot;Two signatures with the same nonce share r; subtracting the signing equations cancels the private-key term, yielding the nonce, and one equation then solves for the key.&quot; },
  { q: &quot;Why is LadderLeak not a lattice attack?&quot;, a: &quot;Its leak is under one bit per signature, which lattices cannot exploit, so it uses Bleichenbacher&apos;s Fourier method instead. Lattices are the Minerva and TPM-Fail regime.&quot; },
  { q: &quot;Is Logjam a counterexample to the thesis?&quot;, a: &quot;No. The only discrete log computed landed on a downgrade-forced, crippled, shared 512-bit prime; no strong or standard-sized group ever fell.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>elliptic-curve-cryptography</category><category>ecdsa</category><category>diffie-hellman</category><category>nonce-reuse</category><category>side-channel-attacks</category><category>tls-security</category><category>post-quantum</category><category>applied-cryptography</category><author>noreply@paragmali.com (Parag Mali)</author></item><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></channel></rss>