Working around a strange Python problem

A Python script that worked a few days ago wouldn’t work today. I suspect it might have something to do with my updating everything I could think of in response to recent security breaches.

I got an error message that said

error: The script contains multiple PEP 723 metadata blocks

I searched on what that means, then took out the blank line in my header

#!/usr/bin/env -S uv run --script

# /// script
# dependencies = ["pyperclip"]
# ///

but I still got the same error message.

The script in question copies miscellaneous snippets of text to the clipboard for me. One of those snippets is the header block that uv requires. The script contains the header block marker # ///, and that was the source of the problem.

According to Grok

The PEP 723 parser is purely textual (it uses a regex that scans the whole file). It does not understand Python syntax, so the # /// sequences inside your triple-quoted string are treated as a second metadata block.

It suggested changing my quoted string to use # / / / and then take out the extra spaces:

"""#!/usr/bin/env -S uv run --script
# / / / script
# dependencies = ["numpy", "matplotlib"]
# / / /
import numpy as np
import matplotlib.pyplot as plt
""".replace("# / / /", "# ///")

This is a dirty hack, but it works.

How the eMudrha certificate debacle might have happened

You may have seen references to a password being the number 4. There are countless allusions to this online, but they don’t give the context.

As I mentioned in an earlier post, eMudrha was using the numbers 4, 56, 115, 155, 156, 230, and 237 as secret keys. I haven’t seen details of how this happened, but in this post I’ll give a plausible account of how it might have happened, what someone could have done to produce those results. I’m not trying to do forensic analysis on this specific incident, just exploring what someone might do.

eMudrha was using the NIST elliptic curve P-384, and a secret key should be a random number chosen between 1 and 2384. That upper bound isn’t exactly correct, but it’s close. See this post if you want to know the exact number.

Someone might have chosen numbers up to 384 rather than 2384 out of ignorance. Or they might done so by a typo. Maybe they generated a random value that might have been larger than 2384 and then intended to reduce the number mod 2384, but instead of typing

% 2**384

they typed

% 384

It seems more likely, however, that somehow someone took one byte of a random value because all the keys are less than 28 = 256.

The probability of choosing seven numbers from 1 to 384 and all being less than or equal to 237 is around 0.034. But the same probability for numbers chosen between 1 and 256 is about 0.60.

The number of points on an elliptic curve

An elliptic curve E over a finite field F consists of the set of points (x, y) satisfying the equation

y² = x³ + ax + b

with x and y in F. Typically in cryptography the field F is the integers mod p where p is a very large prime number.

The number of points on E is roughly the number of elements in F. So for a curve over the integers mod p, the curve has roughly p points.

The next section will give an intuitive argument for why the number of points on E should be roughly the number of elements in F. Then the next section will give a rigorous bound. Then after that we’ll work through an example.

Hand-wavy estimate

For a prime p, half the non-zero integers mod p have a square root and half do not. The ones that do have a square root have two square roots. For example, look at the integers mod 7. The numbers 1, 2, and 4 have square roots but the numbers 3, 5, and 6 do not. Here’s a little bit of Python to verify this.

>>> [x**2 % 7 for x in range(1,7)]
[1, 4, 2, 2, 4, 1]

Now if we wanted to count the number of solutions to

y² = x (mod p)

we’d find it equals p. That’s because (0, 0) is a point on the curve, and for (p − 1)/2 values of x there are two values of y, and for the remaining values of x there are no values of y.

To put it another way, if we pick a value of x at random, it will have two square roots or no square roots with roughly equal probability.

Now suppose we pick an x and random and compute x³ + ax + b. We might guess that this process scatters x values in an unbiased way, being no more likely to produce a square than a non-square. Our guess turns out to be approximately correct, though there may be a small bias in favor of squares or non-squares.

Precise bound

Hasse’s theorem gives a precise bound on the number of points on an elliptic curve E. It says

#E = p + 1 − t

where

|t| ≤ 2√p.

So if p is on the order of 2n, the number of points on E is also on the order of 2n because √p is far smaller than p. That’s all you need to know at a high level. The rest of this post will look at a particular example in full detail.

Detailed example

The NIST curve P-384 has been in the news lately. For this curve, the based field is integers modulo a 384-bit prime p and so the number of points on the curve is also 384-bit number. Specifically

p = 2384 − 2128 − 296 + 232 − 1,

and so we know a priori that the number of points on the curve equals 2384 plus or minus a factor less than 2193.

The number of points on the elliptic curve P-384 is known to be

 n = 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643

See NIST SP 800-186, §3.2.1.4⁠.

We can calculate t at a Python REPL.

>>> n = 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643
>>>  p = 2**384 - 2**128 - 2**96 + 2**32 - 1
>>> t = p + 1 - n
>>> t
1388124618062372383606759648309780106643088307173319169677

The fact that t is positive means the number of points n on our curve is slightly less than p. Here “slightly” is relative. The value of p − n is a huge number, but it’s small relative to p.

We can verify that |t| is less than our a priori bound of 2√p.

>>> from math import sqrt
>>> t/(2*sqrt(p))
0.11057050503394951

So the actual value of t is about nine times smaller than its bound.

Backdoors, Incompetence, and Elliptic Curves

The NIST elliptic curves have been in the news twice lately. A few days ago Anthropic posted a paper on the NIST curve seeds that I wrote about here. Now the curves are in the news again, this time over broken certificates.

My post a few days ago was about the possibility that the NSA had added a backdoor to the NIST curves. Anthropic researchers showed that this wasn’t the case. The NSA had not been able to defend how the curve seeds were chosen because they forgot, but researchers from Anthropic were able to reverse engineer the source of the seeds.

So apparently the NIST curves do not have a backdoor, but as with all encryption, the Achilles heel is human error. eMudrha was using the NIST curve P-384  with extremely small keys: 4, 56, 115, 155, 156, 230, and 237. To understand why this is a problem, I’ll briefly describe how elliptic curve cryptography works.

P-384 is an elliptic curve designed by NIST, with input from the NSA, for use in cryptography. The curve has on the order of 2384 points because the base field is the integers mod

p = 2384 − 2128 − 296 + 232 − 1,

a 384-bit prime number. Given a basepoint b on the curve and an exponent e, it is practically impossible to recover e from be. That’s the discrete logarithm problem [1], the basis for the security of elliptic curve cryptography. If and when cryptographically relevant quantum computers arrive, the discrete logarithm problem will be solvable and elliptic curve cryptography will be broken.

Until the advent of quantum computing, elliptic curve cryptography is presumably safe, unless someone does something negligent, such as using a tiny value of e like 4.

If e is chosen at random from a group with on the order of 2384 elements, you cannot recover e from be without brute force. But apparently someone said

Let’s see. Is e = 1? Is it 2? Is it 3? Is it 4? Why yes: e = 4.

Ordinarily this would be hopeless. You can’t just try 2384 exponents. But someone thought to test whether the keys were horrifically bad, and they were right.

 

[1] The exponentiation here is not over the integers, or over the integers mod p. It’s over the group structure of the elliptic curve, and that is what makes it hard to compute the discrete logarithm.

Deliberate imperfection

I was watching a video by John Higgins this morning about the poem in Ecclesiastes 3, one of the most famous passages in the Bible.

There is a time for everything,
and a season for every activity under the heavens:

a time to be born and a time to die,
a time to plant and a time to uproot,
a time to kill and a time to heal,
a time to tear down and a time to build,
a time to weep and a time to laugh,
a time to mourn and a time to dance,
a time to scatter stones and a time to gather them,
a time to embrace and a time to refrain from embracing,
a time to search and a time to give up,
a time to keep and a time to throw away,
a time to tear and a time to mend,
a time to be silent and a time to speak,
a time to love and a time to hate,
a time for war and a time for peace.

The poem has a great deal of symmetry, from birth and death to war and peace. It has seven couplets, and the number seven in the Bible is symbolic of perfection.

There is a repeated pattern of groups of four words: a time for X, a time for Y. The rhythm reminds me of Beethoven’s fifth symphony, especially the third movement [1].

Higgins points out that there’s something odd in the middle couplet:

a time to scatter stones and a time to gather them,
a time to embrace and a time to refrain from embracing,

It breaks the rhythm, at least in English translation, and presumably in the original language. The poem goes from intransitive adjectival infinitives —to plant, to uproot, etc.—to transitive—to gather stones. Also, the two parts of the couplet do not obviously fit together. There’s a similarity between birth and planting, between killing and tearing down, etc. But what’s the connection between casting stones and embracing?

Higgins explains that this deliberate symmetry breaking fits the theme of the Ecclesiastes as a whole. There is order to the world, and yet we don’t have it all figured out. Even when everything mostly makes sense, there’s something we don’t understand, like the middle couplet.

[1] This has been called the fate motif. There is an apocryphal story that Beethoven described it as such to his secretary and biographer Anton Schindler. Regardless of whether the story is true, the name stuck. Here’s the particular part I had in mind.

Sheet music for Beethoven: Symphony No. 5, Op. 67. Third Movement Horn Theme

Refactoring factorials

Splitting N! into N factors is trivial. It’s defined as the product of N integers, the integers 1 through N.

Can you still split N! into N factors if you put a minimum size requirement on the factors?

You can’t split 3! into three factors unless one of the factors is 1, but you can split 4! into factors ≥ 2:

4! = 24 = 2 × 2 × 2 × 3

Define t(N) as the largest number such that N! can be expressed as the product of integers greater than or equal to t(N). So based on the discussion above, t(N) ≥ 1 for all N, t(3) = 1 and t(4) = 2. The t(N) are given in OEIS sequence A034258.

Last Thursday an article came out proving an asymptotic estimate for t(N). You can find a preprint on arXiv.

An upper bound on t(N) would be the Nth root of N! because that would correspond to splitting N! into N factors of equal size. But the Nth root of N! will not be an integer for N > 1, so the bound would not be tight.

If we apply Stirling’s approximation for factorial we get

t(N) / N ≤ 1/e + O(log(N) / N).

It was conjectured that for large N

t(N) / N ∼ 1/e − c0 / log(N)

and the new paper proves

t(N) / N ∼ 1/e − c0 / log(N) + O(1 / log1 + c N).

.

A frivolous problem with a deeper lesson

About 14% of English words can be spelled by stringing together chemical symbols. For example, sarcasm can be written

S Ar Ca Sm

using the symbols for sulfur, argon, calcium, and samarium.

How would you write a program to determine whether a word can be written by stringing together chemical element abbreviations ignoring case? Obviously this isn’t a practical problem, but it illustrates a practical problem.

You could solve the problem with a regular expression:

(h|he|li|be|b|c|n|o|f|ne|na|mg|al|si| … |og)+

This solution could either be very efficient or very inefficient, depending on the inner workings of the software used to match the regular expression.

A DFA (deterministic finite automata) regular expression engine would find a match, or report failure, linear time, i.e. the execution time would be proportional to the length of the string being matched. A NFA (nondeterministic finite automata) engine that uses backtracking could take exponential time.

The root of the problem is the ambiguity in dividing words into single letters or pairs of letters.

Many two-letter chemical symbols are made of letters that are themselves chemical symbols. For example, the symbol for cobalt, Co, consists of the symbols for carbon, C, and oxygen, O. A traditional NFA regular expression engine uses backtracking and could take exponentially long to test whether the word

cococo…j

matches a regular expression for concatenated chemical symbols. If there are n copies of co, the engine would try all 2n possibilities of treating each pair as either co (cobalt) or c o (carbon oxygen).

A DFA regular expression engine always runs in O(n) time, but a (traditional) NFA can take exponential time. So why ever use an NFA engine? Because it can match more advanced patterns such as Perl regular expressions (which are not “regular” expressions in the original meaning of the term). Newer NFA engines have added safeguards to eliminate or at least reduce the chance of running into catastrophic backtracking.

Cloud of suspicion lifted from NIST curves

NIST published a set of elliptic curves for cryptography in 2000. The seeds for the curves were supplied by the NSA, and cryptographers suspected the agency may have installed backdoors in the curves. And not without reason: the NSA did insert a backdoor in the Dual_EC_DRBG random number generator.

Two days ago researchers at Anthropic [1] showed that the NIST curves were not seeded with a backdoor. The rest of this post will give the background necessary to appreciate this result.


The security of elliptic curve cryptography depends on the difficult of solving the discrete logarithm problem. In the context of elliptic curve arithmetic, the analog of exponentiation is easy but the analog of taking logarithms is hard.

In general it’s not practical to compute discrete logarithms over large elliptic curves. But it could be easy for someone with inside information to compute discrete logarithms if something about the curve was not “general.”

Specifically, the NIST curves have the form

y² = x³ − 3x + b (mod p)

for various values of p [2], and the suspicion regards the choice of b. For nearly all values of b encryption over the elliptic curve should be secure, as far as we know, but in principle someone could engineer values of b to weaken the encryption.

So where did the b‘s come from? Were they chosen in good faith?

Concern over the choice of seeds, the b‘s, grew following the Snowden revelations. Daniel J. Bernstein emailed Jerry Solinas of the NSA on September 6, 2014 asking where the seeds had come from. Solinas replied in subsequent emails. He said the seeds were generated by hashing the ASCII representation of a humorous English message, but he could no longer remember the exact message or the precise hashing procedure.

On October 5, 2023 Philippo Valsorda announced a bounty for reverse engineering the origin of the NIST elliptic curve seeds. Two days ago researchers from Anthropic claimed the bounty. Valsorda verified the results and awarded the prize. It turns out Solinas found the curve seeds in good faith, but his memory had faded over the years since the curves were published. We now know he used a date, not a phrase, as part of the process of constructing the seeds.

The NIST curves have some nice properties, such as enabling some clever tricks for arithmetic in their base fields. But adoption of the curves has been held back over suspicion regarding their origin. Now the curves have been exhonorated. As Valdorda notes

… this nicely completes the “rehabilitation arc” of NIST elliptic curve cryptography, which I would now personally recommend over Curve25519/Ed25519/X25519. Just in time for them to be retired in favor of post-quantum signatures.

Related posts

[1] This does not imply that LLMs are necessarily a threat to encryption. The search for the NIST seeds required a lot of brute force. Some cleverness was required in order to come up with possibilities to check by brute force, but even that is sorta brute force at a higher level of abstraction. There has been speculation that labs are looking for weaknesses in cryptographic primitives and protocols, but that’s a different matter than searching for seeds.

[2] The curves P-192, P-224, P-256, P-384, and P-521 are built over the field of integers mod p, where the number of bits in p corresponds to the number in the name. The P-192 curve, for example, has as its base field the integers mod 2192 − 264 − 1, a 192-bit prime.

Monoliths and Lagrange Points

This post began as a thread on X last night. I’ve edited the thread to turn it into a blog post, making some additions and corrections.


I’m watching 2010: The Year We Make Contact. The movie opens saying the monolith is located at the Lagrange point between Jupiter and Io. I got nerd sniped and wondered where that was.

MISSION ANALYSIS: COMPOSITION OF SECOND MONOLITY ... UNKNOWN. POSITION OF SECOND MONOLITH ... LA GRANGE POINT BETWEEN JUPITER AND IO. POSSITION OF U.S.S. DISCOVERY ... ORBIT AROUND IO. CONDITION OF U.S.S. DISCOVERY ... UNKNOWN.

There are five Lagrange points, but presumably the movie refers to L1 or L2 because these are near Io. Lagrange points L3, L4, and L5 are essentially in the orbit of Io, but 60, 180, and 300 degrees out of phase, all far from Io. (The book says the monolith is at L1.)

Lagrange points L1 and L2 are unstable. Maybe this is a plot hole in the movie, unless there’s an explanation coming.

(In general L1, L2, and L3 are unstable. If the ratio of the masses is large enough, L4 and L5 will be stable. More on that here. The Lagrange points L4 and L5 are indeed stable for the Jupiter-Io system. For the Sun-Earth system L1 is moderately stable over a short time span, but L1 is far less stable for the Jupiter-Io system.)

Here’s a plot of the Lagrange points.

Note that L1 and L2 are very close to Io, about 10,000 km from the moon. The L1 and L2 Lagrange points for the Earth-Sun system are not so close to Earth.

(That’s half true. Yes, Io’s L1 point is closer to it than Earth’s L1 point, in absolute distance. But in relative terms, Io’s L1 point is actually further away. I had been misled by seeing an illustration that exaggerated the distance from Earth to L1.)

Related posts

LaTeX package for Pascal-like triangles

When I wrote about the harmonic triangle yesterday, I displayed the triangle left-aligned using fixed-width text because that was easier than producing the format you usually see for Pascal’s triangle and related triangles.

    1
    1/2  1/2
    1/3  1/6   1/3
    1/4  1/12  1/12  1/4
    1/5  1/20  1/30  1/20  1/5
    …

I asked Claude to write a LaTeX package to make it easier to create Pascal-like triangles and it did a great job, including handy features I didn’t think to ask for. You can download it here: numtriangle.sty.

I used this package to create the more attractive display below.

Here’s the LaTeX code that produced the image above.

\documentclass[border=4pt]{standalone}
\usepackage{numtriangle}
\NewTriangle{\Leibniz}{start=1, edges=1/\n, rule=\L - \P, fractions,
  format=\displaystyle, xsep=2.8em, ysep=2.8em}
\begin{document}
\Leibniz[rows=5]
\end{document}

I then compiled the code to an SVG file with the following.

pdflatex leibniz.pdf
pdftocairo -svg leibniz.pdf leibniz.svg

Bell’s triangle is not defined in a way that the LaTeX package supports, but I can produce the triangle by listing the content. I made the triangle left-aligned rather than centered just to show that this can be done.

The LaTeX code was the following:

\ListTriangle[shape=left]{
    1; 1, 2; 2, 3, 5; 5, 7, 10, 15; 15, 20, 27, 37, 52}

Here’s a particularly photogenic image, Pascal’s triangle with odd numbers highlighted, showing the Sierpiński triangle pattern.

The code to produce the image:

\PascalTriangle[rows=16, mod=2, xsep=1.9em, ysep=1.3em,
  cell style={font=\tiny, minimum size=1.2em},
  highlight style={fill=orange!40}]