In-depth articles on technology shaping what comes next.

NIST Curve Seeds Solved: 5 Myths About ECDSA Origins

The NIST ECDSA curve seeds were lrand48(time) all along. Five myths about nothing-up-my-sleeve numbers, backdoors, and LLM crypto auditing.

A 1990s CRT terminal emitting glowing particles that dissolve into fluttering calendar pages.
The seeds were timestamps all along — the clock on a workstation in 1997.

For twenty-five years, the NIST ECDSA curve seeds were the closest thing applied cryptography had to an unsolved cold case. Five seemingly random 160-bit values, handed to NIST by the NSA with zero explanation, underpinning signatures on everything from your TLS connections to Bitcoin transactions. This week the case closed, and the answer is almost embarrassing: the seeds were output from the C library's lrand48() generator, seeded with Unix timestamps from 1997 and 1998. That's it. No hash of a secret passphrase, no exotic machinery — srand48(time(NULL)), the exact pattern every security course tells you never to use.

The discovery came from an Anthropic research model, pointed at the problem by a curious employee, which recognized the statistical signature of a 1990s linear congruential generator in the published constants and ran the state backwards until it landed on calendar dates. I've spent enough years auditing cryptographic systems to know this matters — not because the curves are broken (they aren't), but because it demolishes several things our industry confidently believed. Let's take the myths one at a time.

Myth 1: Unexplained Constants Mean a Hidden Backdoor

The suspicion was never crazy. When NIST published the FIPS 186-2 curves in 2000, each came with a seed whose hash determines the curve's coefficient b. NIST said the seeds guaranteed the curves were 'verifiably random' — but couldn't or wouldn't say how the seeds themselves were generated. Koblitz and Menezes wrote a whole paper on the controversy, pointedly titled 'A Riddle Wrapped in an Enigma.' The community's best lead was Jerry Solinas at the NSA, who recalled in 2015 that the input was a hash of a humorous phrase, something like 'Give Bob and Jerry a raise.' He died in 2023 without anyone recovering the details.

Here's the uncomfortable truth for the conspiracy-minded, myself included at times: unexplained usually means undocumented, not malicious. The actual generation process was a developer running a small C program, letting the default PRNG seed itself from the wall clock, and taking the first candidate that produced a usable curve. P-256's published seed was candidate number 1,842 from a run started at 1:52 AM on August 16, 1997. That isn't the workflow of an agency planting a number-theoretic trapdoor. That's an engineer at one in the morning running a loop until it prints something valid.

I've seen this pattern in incident reviews more times than I can count. A mysterious config value, an undocumented magic constant, a deployment step nobody can explain — and the postmortem reveals it was a tired engineer's reasonable-at-the-time shortcut, never written down. The NSA's real sin here wasn't backdooring the curves. It was shipping cryptographic constants to the entire world without a README.

Myth 2: The Generation Method Proves Selection Was Honest

Now the overcorrection. Plenty of commentary this week treats 'it's just timestamps' as total vindication. It isn't, and the researchers behind the work are careful to say so. Knowing how the candidates were generated tells you nothing about how the final curves were selected — and the selection happened over months, from May 1997 to April 1998.

The evidence cuts both ways. In P-256's run, candidate 876 also produced a prime-order curve of the right size, built with the same parameter conventions — and it was skipped. The published curve was candidate 1,842. P-224's run passed over three viable curves; the ANSI X9.62 runs skipped seven more. We cannot see the discarded candidates' full history, only the ones reachable in the recorded runs. If someone wanted a curve with a specific rare property, they could in principle have searched over timestamps until one turned up, then stayed quiet about the timestamp entirely. 'Doing it and saying nothing' was always an option.

My read, having watched weak-curve research for years: the backdoor hypothesis is badly wounded but not formally dead. The skipped candidates are few enough that cherry-picking would imply weak curves are common and easy to find — which two decades of public cryptanalysis say they aren't. No one has ever published a distinguisher separating these curves from random ones. But 'no known weakness' and 'provably no weakness' are different statements, and the honest position is the first one. This is exactly the specification-implementation gap playing out at the standards level: the spec said 'verifiably random,' and the implementation was 'whatever the clock said.'

The seeds were generated the way a grad student would do it: a stock PRNG and the system clock. The scandal was never the math. It was the missing documentation.

Myth 3: Weak Old PRNGs Are Harmless If You Only Use Them Once

This is the myth that should make every engineer who has ever typed srand(time(NULL)) sit up. The entire twenty-five-year veil of mystery existed because of one design property of lrand48(): it leaks its own structure. The generator returns only the top 31 bits of a 48-bit state, so its output words always have a zero top bit. Written as 32-bit words, four of the ten binary-curve seeds show all-zero top bits across twenty consecutive words — an event with probability around one in 58,000 under a uniform distribution. The signature was visible to the naked eye for anyone who thought to look.

From there the attack is embarrassingly cheap. To recover the binary-curve states, you take the first output word, brute-force the 2^17 hidden low bits, and check whether the next four words follow. That's a few milliseconds of compute. For the prime curves the seeds kept fewer bits per output, so recovering the state required the classic truncated-LCG attack: model it as a lattice problem and hit it with LLL basis reduction. Textbook stuff, literally. Once you have the state, an LCG runs backwards as easily as forwards, so you step it back until it shows the srand48 initialization form — low bits equal to 0x330E — and read off the timestamp it was seeded with.

# The whole mystery, reproduced
from datetime import datetime, timezone
def unix(when):
return int(datetime.fromisoformat(when).replace(tzinfo=timezone.utc).timestamp())
def candidate(t, n, per, shift, width):
x, out = (t << 16) | 0x330E, ""   # state set by srand48(t)
for i in range(per * n):
x = (0x5DEECE66D * x + 0xB) % 2**48   # one lrand48 step
if i >= per * (n - 1):
out += "%0*X" % (width, x >> shift)
return out
# P-256: run started 1997-08-16 01:52:34 UTC, candidate 1842
print(candidate(unix("1997-08-16 01:52:34"), 1842, 20, 40, 2))
# -> C49D360886E704936A6678E1139D26B7819F7E90  (the published NIST seed)

The lesson generalizes beyond standards bodies. Any 'random-looking' value you emit — session tokens, request IDs, salts, filenames — carries the fingerprint of the generator that made it. If that generator is a linear congruential engine or a Mersenne Twister, a handful of outputs reconstructs the internal state, and everything you generated before and after becomes predictable. A single use, twenty-five years ago, in a file meant to look like noise, was fully inverted. When I review code that uses non-cryptographic RNGs for anything an attacker can observe, this is the war story I'm citing from now on.

A glowing chain of gears with one highlighted gear, echoed faintly in both directions.
A linear congruential generator runs backwards as easily as forwards — expose one link and the whole chain falls.

Myth 4: Nothing-Up-My-Sleeve Numbers Were Ever the Real Protection

The 'nothing-up-my-sleeve' idea — derive constants from something obviously innocent, like digits of pi — exists to bound the designer's degrees of freedom. If I can show you my constant falls out of a public, rigid procedure, I had no room to smuggle in a trapdoor. The NIST seeds failed this test procedurally: the procedure was secret, so the bound on degrees of freedom was unknown. But here's what this episode really exposes — the sleeve test is weaker than we pretended even when it works.

Consider what 'verifiably random from a timestamp' actually gives you. A timestamp is about 31 bits of entropy for a plausible window. Anyone — including the original author — can grind through millions of timestamps, generating millions of candidate curves, and publish only the one they want, along with its innocent-looking seed. If curves with a secret weakness existed at rate one in a million, timestamp-grinding finds one in an afternoon. The seed proves where the number came from and says nearly nothing about why that one was chosen. The same applies to hash-of-a-passphrase schemes, which is why the rumor about Solinas's joke phrase was never fully reassuring either.

The stronger modern practice is rigid curve construction: pick the smallest coefficient satisfying a public list of security criteria, with the selection rules fixed before the search starts. That's the philosophy behind Curve25519 and why Dan Bernstein's rigidity arguments reshaped how the community evaluates curves. The actual protection against malicious constants was never a cute derivation story. It's minimizing the search space, publishing the criteria in advance, and having enough independent eyes on the math that a hidden property can't stay hidden. Auditing constants means auditing the process that filtered them — which is why the unanswered questions here (the skipped candidates, the unexplained base points for P-256 and friends, the still-unidentified B-163 seed) deserve to stay open on the record rather than get memory-holed by the celebration.

Myth 5: LLMs Just Regurgitate — They Can't Do Original Cryptanalysis

This is the myth that stings the most, because I believed a version of it. The open problem sat unsolved for a quarter century despite a public bounty of $36,864 and serious attention from skilled amateurs. Several researchers at Anthropic had chased it themselves, following the 'hash of a funny phrase' lead, and come up empty. Then an internal research model, asked without being handed the rumor, tried a different angle in under half an hour: instead of guessing the input to a hash, it asked what software would have generated the seeds, hypothesized a 1990s C library PRNG, wrote a scanner that slid across each seed testing 34 combinations of generator and output slice, spotted the zero-top-bit pattern, ran the lattice attack on the truncated outputs, and walked the states back to calendar dates.

Notice what that workflow is. It's not recall — no training document contained the answer, because the answer didn't exist anywhere. It's hypothesis generation plus tool use plus verification, which is the actual shape of cryptanalytic work. The model even checked its own sanity mid-run: is this a simulated test environment, would such an obvious thing really sit unfound for 25 years, does a 160-bit match from a 32-bit seed rule out coincidence? That self-skepticism is what I'd want from a human analyst, and frankly some human analysts skip it. We've been testing the myths around what these models actually do on this site for a while, and this is a clean data point: LLMs can now execute multi-step forensic reasoning against real-world artifacts, not just benchmarks.

The fair counterpoint from practitioners is about provenance. The model didn't invent LCG state recovery or LLL — it stands on decades of published cryptanalysis by named and unnamed researchers, and it can't tell you whose papers shaped its approach. That bothers me less than it bothers some. The scanner, the verification code, and the reproduction script are public; the humans on the team directed the question and validated the result. Credit the division of labor honestly: the machine supplied tireless hypothesis search, the humans supplied judgment and accountability. The $36,864 bounty going to the Internet Security Research Group was a classy way to settle it.

A magnifying lens over a dark field of glowing dots, with one cluster illuminated.
Hypothesis search at machine speed: the pattern was in the noise the whole time.

What This Changes for Security Audits

So where does this leave a working engineer? A few concrete shifts in how I'd approach audits from here. First, treat every magic constant in a protocol or codebase as a forensic artifact, not a fact of nature. Ask what generator could have produced it, check for structural fingerprints — biased top bits, LCG-correlated successive words, timestamp-plausible state recoveries — before accepting 'it's random' as an answer. The tooling for this is now trivially available.

Second, if you publish constants, publish the generator. Source code, exact inputs, exact environment. 'Verifiably random' without a reproducible recipe buys you twenty-five years of suspicion and a bounty on your legacy. Third, keep using P-256 — the finding raises confidence in the curves rather than lowering it, and the real threat model for elliptic curves is quantum hardware, not a 1997 workstation's clock. But read the open questions honestly: unexplained base points and skipped candidates are loose ends, and mature security work means keeping them on the books.

The deepest takeaway is about institutional memory. Jerry Solinas remembered a joke about raises that turned out to be wrong. The archives were, in his own words, probably sitting on a hard drive near the Ark of the Covenant. The only reason we know the truth today is that the output itself retained enough structure to confess. Your systems are writing their own confessions right now, in every ID and token and constant you ship. Make sure they'd confess something you're proud of.