6.4 Lattice attacks on ECDSA: the hidden number problem
Why a few leaked bits of each nonce are enough to recover a signing key, and how to make sure yours never leak.
Lattices are not only a foundation for post-quantum cryptography. They are also one of the most effective tools for breaking classical signatures that are implemented carelessly. If an ECDSA implementation leaks even a few bits of each random nonce, through timing, a biased random number generator or a cache side channel, a lattice reduction algorithm such as LLL can combine those scraps into the full private key. The underlying mathematical problem is the hidden number problem (HNP), introduced by Boneh and Venkatesan in 1996. This guide builds the attack lattice step by step, runs it on a toy curve, and ends with the defenses that close the hole.
Why the ECDSA nonce is as secret as the key
Let be a point of prime order , let be the private key and let be the hash of the message. To sign, the signer picks a fresh nonce , sets to the x-coordinate of reduced mod , and computes
The pair is public. Rearranging gives , so anyone who learns a single nonce completely learns the key. The degenerate case is reusing a nonce: two signatures with the same give two linear equations in two unknowns, and simple algebra recovers and then . That is how the fail0verflow team recovered Sony’s PlayStation 3 signing key in 2010, because the same nonce had been used for every signature.
The lattice attack handles the much subtler case where no nonce is ever revealed in full, but each one is slightly predictable: for example, its top few bits are always zero, or a timing measurement tells you that they probably are.
The hidden number problem
Divide the signing equation by . For every signature define the public values and . Then
Without extra information this says nothing: is uniform, so every is consistent. Now suppose the attacker knows that every nonce is small, with , because its top bits are zero. (Known nonzero top bits reduce to this case by subtracting them off, and known low bits by a similar shift.) The hidden number problem asks: given many pairs such that is smaller than , find the hidden number .
Each signature contributes about bits of information about . Since has about bits, you need at least signatures, and a lattice turns that information-theoretic count into an algorithm.
Building the attack lattice
The congruences say that the vector of nonces is close to a lattice point: each equals minus some multiple . The standard way to exploit this is an -dimensional lattice whose basis rows are
Take copies of the -row, one copy of the -row, and subtract copies of each -row. The result is the lattice vector
Every coordinate of is at most , so its length is at most . This is the whole trick: an unusually short vector that carries the key in its second-to-last coordinate.
Why the scaling factors B/n and B?
The two extra coordinates are weights chosen so that every entry of the target vector has the same size as a nonce. The key can be as large as , so the factor shrinks to below . The last coordinate is an embedding constant: it turns the close-vector problem “find the lattice point nearest to ” into a short-vector problem (Kannan’s embedding), and setting it to keeps it on the same scale. If one coordinate were much larger than the others, the target would stop being the shortest vector and reduction would find something else. In code, multiply every entry by to keep the basis integral.
Is really the shortest vector? The lattice has determinant , so by the Gaussian heuristic a random vector in it has length about . Requiring to be smaller and substituting gives roughly
The first term is the information count from above. The second is the price of using a lattice, about two extra bits per signature. It explains why lattice attacks are comfortable with four or more leaked bits per nonce and struggle as the leak approaches one bit.
Solving it: LLL, BKZ, or CVP
When the gap between and the Gaussian heuristic is large, plain LLL finds . With less leakage per signature you need more signatures, the lattice dimension grows, and you move up to BKZ or sieving to solve the underlying shortest vector problem. Equivalently, you can drop the embedding row and run a closest vector algorithm such as Babai’s nearest-plane method with the target .
Two practical details. First, the reduced basis always contains the harmless vector , which is times the -row minus multiples of the -rows. It is as short as and carries no information, so scan the reduced rows for one whose last coordinate is . Second, every candidate is checked for free against the public key: must equal it.
Worked example: a 16-bit toy curve
Take the curve over . It has prime order , and we use the generator . The private key is , giving the public key . A faulty signer draws every nonce below , so the top 8 of 16 bits are always zero. Four signatures:
| i | h | r | s | secret | ||
|---|---|---|---|---|---|---|
| 1 | 37303 | 14851 | 42579 | 24919 | 13276 | 217 |
| 2 | 52577 | 36535 | 44387 | 65082 | 30058 | 196 |
| 3 | 4135 | 46929 | 28026 | 50862 | 19274 | 66 |
| 4 | 7727 | 40682 | 8667 | 31540 | 15309 | 127 |
The attacker sees only the first three columns. The six-dimensional lattice has determinant , so the Gaussian heuristic predicts vectors of length about 962. The target has length about 416, less than half of that, and LLL finds it. With the basis scaled by to integers, the second row of the reduced basis is
Dividing the first four entries by gives the nonces 217, 196, 66 and 127. The fifth entry is , so , and matches . The first reduced row is the harmless . With only three signatures the same attack fails on some seeds, in line with the bound above: bits is barely more than the 16 bits of the key plus the lattice overhead. The whole lattice step fits in a few lines of fpylll:
from fpylll import IntegerMatrix, LLL
n, B = 65293, 2**8 # group order, bound on every nonce
sigs = [(37303, 14851, 42579), (52577, 36535, 44387),
(4135, 46929, 28026), (7727, 40682, 8667)] # (h, r, s)
t = [r * pow(s, -1, n) % n for h, r, s in sigs]
u = [h * pow(s, -1, n) % n for h, r, s in sigs]
m = len(sigs)
# Basis rows, multiplied by n so every entry is an integer.
rows = [[n * n if j == i else 0 for j in range(m)] + [0, 0] for i in range(m)]
rows.append([n * ti for ti in t] + [B, 0])
rows.append([n * ui for ui in u] + [0, B * n])
A = IntegerMatrix.from_matrix(rows)
LLL.reduction(A)
for row in A:
if abs(row[-1]) == B * n: # the embedding coordinate
sign = 1 if row[-1] > 0 else -1
print("candidate d =", sign * row[-2] // B % n)
# Prints 8806 and 52739. Keep the one where d*G equals the public key.On a real 256-bit curve the same code works unchanged in principle. What changes is the number of signatures and the strength of the reduction you need, which is where the research below comes in.
Real-world attacks
- PlayStation 3 (2010). A constant nonce, the zero-bit-of-entropy extreme. No lattice needed.
- TPM-Fail (2019). Moghimi, Sunar, Eisenbarth and Heninger found that TPM 2.0 devices leaked the nonce’s bit length through signing time. An Intel firmware TPM gave up a 256-bit ECDSA key after about 1,300 observations, an STMicroelectronics hardware TPM after fewer than 40,000. They also recovered a VPN server’s key remotely by timing about 45,000 handshakes.
- Minerva (2019–2020). Jancar and co-authors found the same bit-length timing leak in a certified Atmel smartcard and in libgcrypt, wolfSSL, MatrixSSL, SunEC/OpenJDK and Crypto++. Recovering a P-256 key took about 1,200 signatures from library data and 2,100 from the smartcard.
- LadderLeak (2020). Aranha, Novaes, Takahashi, Tibouchi and Yarom exploited a Montgomery-ladder leak in OpenSSL that reveals the top nonce bit only with some probability, less than one bit per signature. That is below what lattices handle well, so they used Bleichenbacher’s Fourier-analysis approach to HNP instead, breaking P-192 and sect163r1.
On the lattice side, Albrecht and Heninger (Eurocrypt 2021) pushed the boundary further by adding a predicate, a cheap check that recognises the correct key, to enumeration and sieving. That solved HNP instances with fewer leaked bits and fewer signatures than were previously thought reachable with lattices.
Defenses
- Deterministic nonces (RFC 6979). Derive from the private key and the message hash with HMAC-DRBG. This removes bad randomness and nonce reuse as failure modes.
- Hedged nonces. Mix fresh randomness into the deterministic derivation. This keeps the RNG safety net and adds protection against fault attacks that target purely deterministic signing.
- Constant-time scalar multiplication. Deterministic nonces do not stop timing leaks. The scalar multiplication must take the same time and memory-access pattern for every , including ones with leading zero bits. Fixing the bit length, for example by adding or before the ladder, closes the Minerva and TPM-Fail leak.
- Unbiased sampling. Reduce a random value modulo only after drawing at least 64 extra bits, or use rejection sampling. A small modular bias is also a hidden-number leak.
- Plan to migrate. ML-DSA removes the ECDSA nonce structure, but its own implementations have side channels of their own, so constant-time engineering still matters.
Common mistakes
- Thinking a small leak is harmless. By the estimate above, four leaked bits per nonce across roughly 150 signatures is enough to recover a 256-bit key.
- Forgetting the scaling. Without the weight, the coordinate dominates and the target is no longer the shortest vector.
- Stopping at the first reduced row. The shortest reduced vector is often the uninformative . Test each row against the public key.
- Treating RFC 6979 as a side-channel fix. It fixes randomness, not timing. A deterministic nonce that leaks its bit length is still attackable.
Further reading
Boneh and Venkatesan introduced the problem in “Hardness of computing the most significant bits of secret keys in Diffie–Hellman and related schemes” (Crypto 1996). Howgrave-Graham and Smart first applied it to DSA with partially known nonces, and Nguyen and Shparlinski analysed DSA and ECDSA in detail. For the recent attacks, see TPM-FAIL, Minerva, LadderLeak and Albrecht–Heninger’s On Bounded Distance Decoding with Predicate. The reduction algorithms themselves are covered in LLL and BKZ.