Skip to main content

On 16 October, Anthropic published that Claude had worked out where the NIST elliptic curve seeds came from. The answer, after twenty-five years of speculation about hashed in-jokes and NSA backdoors, is lrand48(): the C library’s linear congruential generator, seeded by a Unix timestamp, sampled twenty bytes at a time until a candidate produced a usable curve. Anthropic’s own framing is unambiguous. There is no action to be taken. Most researchers trusted the curves already. Enjoy.

That is the right read on the security question, and we are not going to pretend otherwise. What interested us was the reproduction. If the seeds really are clock readings, then the chain running from a sysadmin’s wall clock in May 1997 to the b coefficient inside the certificate your browser validated thirty seconds ago is now short enough to run on a laptop. So we ran it. Nine of the ten prime curve seeds fell out in an afternoon, we verified the second half of the chain against NIST’s own published numbers, and then we measured what any of it actually touches on 100 Irish domains. The reproduction went better than expected. The measurement is the part worth reading.

TL;DR

  • We independently recovered all 10 NIST and ANSI X9.62 prime curve seeds from scratch, pinning each to an exact srand48() timestamp and candidate index.
  • The ten curves came from six program runs spread across ten and a half months, not ten sittings. Two runs three days apart in May 1997 account for six of them, and inside one of those, prime192v3 was drawn 785 candidates before prime192v2.
  • We verified the other half of the chain too: for 10 of 10 curves the seed derives the published b coefficient, and for all 5 NIST curves our derived c matches the value printed in FIPS 186-2 exactly.
  • The seeds are now explained. The base points are not, except on six curves. Of those six, zero are recommended for TLS by IANA. The two NIST curves IANA does recommend both keep an unexplained base point.
  • Across 100 Irish domains: 98 of 98 reachable servers accept P-256 key exchange, only 86 accept X25519, and 92 of 92 elliptic curve keys in every served chain sit on P-256 or P-384.

What was actually found

FIPS 186-2 publishes, for each prime curve, a 160-bit seed value. Hash the seed with SHA-1, run the result through a short derivation, and you get an integer c that pins the curve’s b coefficient. Publishing the seed is what makes the curve verifiably random: anyone can check that b was not chosen freely. What nobody could check was where the seed itself came from, because the NSA supplied the seeds without explanation. Neal Koblitz and Alfred Menezes wrote the definitive summary of the resulting suspicion in 2015, and Filippo Valsorda later ran a bounty for an answer.

The answer turns out to be that there is no hash at all. The seeds are raw generator output. Each 20-byte candidate is twenty successive draws of lrand48(), of which only the top eight bits of each 48-bit state are kept, and the generator was initialised by srand48() with the clock. Because that generator runs backwards as easily as forwards, recovering the initial state recovers the timestamp.

We reproduced it, and the runs cluster

Our method was deliberately dumb, because dumb is checkable. We pulled the ten prime curve seeds from two independent sources and confirmed they agree: OpenSSL’s crypto/ec/ec_curve.c on master, and NIST’s own FIPS 186-2 PDF. Then we wrote roughly eighty lines of C that, for every Unix timestamp in a one-year window, steps the generator forward and compares each 20-byte candidate against all ten seeds at once. A 160-bit match across 31.5 million timestamps has a false positive expectation indistinguishable from zero, so a hit is a hit.

Once the first timestamp appeared, the shape of the thing changed. Seeds began arriving in groups, because a single run produces candidate after candidate and more than one standard curve was taken from the same run. Six runs account for all ten seeds:

Run (UTC) Curve Candidate
1997-05-15 15:49:03 prime239v1 2,185
prime239v2 2,320
prime239v3 3,048
1997-05-18 14:45:34 P-192 / prime192v1 139
prime192v3 5,228
prime192v2 6,013
1997-08-08 01:12:04 P-521 1,136
1997-08-16 01:52:34 P-256 / prime256v1 1,842
1997-08-30 14:50:08 P-384 2,688
1998-04-03 12:52:07 P-224 7,026

Four details are worth pulling out. First, the six May 1997 curves did not come from six separate sittings; they came from two runs three days apart, each producing three standard curves. Second, inside the 18 May run the curves were not drawn in their published order. prime192v3 is candidate 5,228 and prime192v2 is candidate 6,013, so the ANSI X9.62 numbering does not reflect generation order. Third, the three curves that carry the modern internet, P-521, P-256 and P-384, were generated in three separate August 1997 runs at eight and fourteen day intervals, each producing exactly one curve. Fourth, P-224 is the outlier in every sense: generated alone on 3 April 1998, more than seven months after the rest, at candidate 7,026, the deepest index in the set. That timestamp also independently corroborates Anthropic’s observation that the binary curve runs, read with doubled timestamps, land two weeks after the P-224 run.

The cost is the part that should land. Given a candidate index, scanning an entire year of timestamps takes 0.08 seconds. Once we had one timestamp from a run, the other two curves in that run fell out of a one-second deep scan, and the exhaustive sweep that closed out the remainder cost well under an hour on two cores of an ordinary virtual machine. We then re-derived every seed from its recovered timestamp and candidate index in a separate script that reads none of the search output: 10 of 10 reproduce exactly.

The half of the chain almost nobody checks

Recovering a timestamp proves the seed was generated by a clock. It does not prove the seed is the one that built the curve. That is a separate computation, and we ran it for all ten curves: derive c from the seed per the ANSI X9.62 and FIPS 186-4 procedure, then confirm that b squared times c is congruent to a cubed, modulo p, with a equal to p minus 3.

Ten out of ten verify. Better, FIPS 186-2 prints c for each of the five NIST curves, and our derived value matches NIST’s published value byte for byte in all five cases. One implementation detail is load-bearing and easy to miss: the X9.62 procedure clears the leftmost bit of the first hash block. Drop that one line and four of the ten curves fail to verify, including P-256. If you are writing your own checker, that is where it will break.

A caution for anyone repeating this. Extracting FIPS 186-2 with pdftotext silently interleaves the s= and c= labels in the P-256 block, because of the two-column layout. Trusting the parse produced a confident, wrong mismatch on exactly one curve out of five. We read that block by eye and it matched. Automated extraction from a standards PDF needs a control.

What the explanation does not cover

Every curve also ships a base point G, the fixed generator that every ECDSA public key is a multiple of. The seed says nothing about G. Anthropic explained the base points of six curves: P-192 and the five ANSI X9.62 example curves. The base points of P-224, P-256, P-384 and P-521 remain unaccounted for.

Cross-tabulate that against the IANA TLS supported groups registry and the result is sharper than the headline. Of the six curves whose base points are now explained, five have no TLS code point at all, and the sixth, secp192r1, is marked D for discouraged under RFC 9847. So zero of the base-point-explained curves are usable in a recommended TLS configuration. Meanwhile IANA marks exactly two NIST curves as recommended, secp256r1 and secp384r1, and both of them are in the unexplained set. All ten curves are still compiled into the OpenSSL on our build box, explained and unexplained alike.

That is the real shape of this week’s news. Provenance arrived for the curves nobody deploys. The curves everybody deploys gained a verified seed and kept an unverifiable generator.

What is actually under Irish TLS

Abstract curve politics is cheap, so we measured the same 100 Irish domains we have used for previous audits. Probes ran on 17 October 2026 with OpenSSL 3.0.13. Two domains, an-post.ie and welfare.ie, have no reachable service on port 443; 98 completed a handshake.

  • Leaf keys: 70 RSA, 28 ECDSA. Of the 28, 27 are P-256 and one is P-384.
  • Whole served chains: 92 elliptic curve keys across all 98 chains. 43 P-256, 49 P-384, and nothing else. Every EC key in the corpus, with zero exceptions, sits on a curve whose base point nobody has explained.
  • Key exchange support: we offered exactly one group at a time, so this measures the server’s policy rather than our client’s preference. 98 of 98 accept P-256. 86 accept X25519. 86 accept P-384.
  • The asymmetry is total. Twelve servers accept P-256 and refuse X25519, among them hse.ie, esb.ie, dublincity.ie and irishlife.ie. Not one server does the reverse.
  • When our client offered everything, 82 servers chose X25519, 12 chose P-256, three chose P-521 and one chose P-384.

Read those last two points together. The usual mental model is that X25519 has quietly replaced the NIST curves, and in terms of what gets negotiated that is nearly true. In terms of what is guaranteed to work, it is backwards. P-256 is the only group in this corpus that every server accepts. It is the floor, and the floor is the curve with the unexplained generator.

Our own estate fails the same way

We run this check on ourselves before publishing it about anyone else. All five properties we operate, reptile.haus, praguers.com, micadora.eu, satosh.ie and crionnahealth.reptile.haus, serve a P-256 leaf certificate on a chain built entirely from P-256 and P-384. There is not one key in our estate on a curve whose base point can be accounted for. That is not a finding against our hosting; it is what the public CA ecosystem issues. It is simply worth knowing that the answer is 100 percent and not something smaller.

What your team should actually take from this

Nothing here is a vulnerability, and anyone selling you a migration on the back of it is selling you something. What it is, is a clean worked example of a dependency whose provenance you inherited rather than checked.

  1. Know which curve is your floor, not just your preference. Negotiated group is a weak signal. Probe each server with one group at a time. If P-256 is your only universal option, your resilience story assumes a curve you cannot independently derive.
  2. Inventory the whole chain, not the leaf. Two thirds of our Irish corpus serves an RSA leaf, yet 92 EC keys still appear across the chains. Key algorithm audits that stop at the leaf will miss most of the estate.
  3. Treat verifiability as a property you test, not a label you accept. Verifiably random was always a claim you could check in forty lines of Python. Almost nobody did, for twenty-five years, which is how an entire controversy survived on vibes.
  4. Do not let an automated parse be your only reading of a standard. Our FIPS extraction produced a wrong answer on one curve in five, silently.
  5. Keep the post-quantum plan separate from this. Hybrid groups like X25519MLKEM768 are the live migration; curve provenance is not a reason to touch it either way.

If you want this run against your own estate rather than read about ours, that is the kind of work we do. REPTILEHAUS builds and audits the infrastructure layer for teams who would rather know than assume, across DevOps, security review, Web3 and AI systems. Get in touch and we will show you what your chains are actually built on.

Sources, all primary and all free to fetch: Anthropic’s research note of 16 October 2026; NIST FIPS 186-2; the IANA TLS supported groups registry; RFC 9847; and OpenSSL master. Our reproduction code is roughly 80 lines of C and 40 of Python.

📷 Photo by Immo Wegmann on Unsplash