# In 2013 I wrote a diploma on elliptic curves. Last night my tool used them to check its own release.

> Source: <https://dev.to/vola-trebla/in-2013-i-wrote-a-diploma-on-elliptic-curves-last-night-my-tool-used-them-to-check-its-own-release-1k3i>
> Published: 2026-11-01 23:32:06+00:00

**TL;DR**: In 2013 I wrote a university diploma on elliptic-curve cryptography, and it went into a drawer. Thirteen years later the same math signs most of what you `npm install`. I taught **croak** 🐸, my pre-install gate for AI agents, to check those signatures itself. Last night it checked its own fresh release: `verified`. Then I gave it the same signature with a different tarball: `invalid`. Below is what that check actually involves, and why the curve is the easy part.

In 2013: a diploma on elliptic curves and key exchange. Points, groups, a lot of modular arithmetic. Defended, filed, forgotten.

I went into test automation, and the curves stayed in the drawer.

Then AI agents started running `npm install` on their own.

An install doesn't just download code. It **runs** it: `postinstall`, native builds, whatever a package wants. An agent does this all day, in your terminal, usually without reading anything. It also invents package names that sound right, and attackers register those names.

**croak** sits between the agent and the install. It's a Claude Code hook: before a shell command runs, croak has npm or pnpm *plan* the install (resolve it, run nothing), then reads that plan as data: install scripts, the lockfile, registry metadata, and the code of every new package, unpacked in memory and never executed. It gives a verdict and **explains it to the agent**:

```
agent:  npm install reactt-domm

croak:  🐸 croak blocked this command before it ran.
        reactt-domm: not on npm; did you mean `react-dom`?
        Do not work around this check (another package manager, a different
        command, or editing files to install it); tell the user what croak
        found and let them decide.
```

`postinstall` that downloads a binary lands here.
It also scans an unfamiliar repo before you open it (`croak repo`: VS Code tasks that run on open, code hidden after hundreds of spaces, obfuscation), because "clone this take-home assignment and run it" is a real attack too.

It's early, and it misses things. On its own eval it flags all 36 attacks but blocks only 27; the other nine go to a human as 🟡. I'd rather publish that number than a prettier one.

Most attacks give themselves away in the code: a `postinstall` that pipes `curl` into `sh`, `process.env` posted somewhere. And install scripts are losing ground anyway: pnpm 10 and npm 12 no longer run a dependency's scripts unless the project allows them. I checked with a bait package that writes a marker file: npm 11 still runs them; npm 12 doesn't.

That pushes attacks into code you *import*, and that code can look exactly like the real thing. Someone steals a maintainer's token, publishes from a laptop, and the tarball looks like every release before it.

That is the question code can't answer: **who built this tarball, and from what?**

npm has an answer. It's called provenance. And it's elliptic curves all the way down.

When a package is published from CI with provenance, a few things happen in a couple of seconds:

`release.yml` in repo X, at commit Y"
Here's my own release from last night, read straight from the certificate:

```
leaf key:  ec { namedCurve: 'prime256v1' }      // P-256
valid:     Nov  1 23:13:03 2026 GMT -> Nov  1 23:23:03 2026 GMT   // 10 minutes
issuer:    O=sigstore.dev, CN=sigstore-intermediate
```

A key that lives for ten minutes and is then thrown away. The trust comes from the log, not from keeping a secret for years.

Count the curves. The registry's own signature on every package: **P-256**. The build's ten-minute key: **P-256**. Rekor's key: **P-256**. (Fulcio's CA uses its bigger sibling, P-384.)

My diploma, signing my `node_modules`.

Here is the elliptic-curve part of croak's verifier:

```
verify("sha256", message, leaf.publicKey, signature)
```

One line. `node:crypto` does the curve math, and it does it better than I would. No point addition by hand, no hand-rolled anything. In 2026, writing your own ECDSA would be a red flag, not a skill.

**All the hard parts moved into the plumbing around that line.** A signature verifies *something*. The question is whether it is the thing you think. croak's check, in order:

``` js
const logged = readLogEntry(entries[0], trust.rekor); // 1. Rekor promised it saw this, and when
const leaf = leafOf(material);
checkCertificate(leaf, trust.fulcio, logged.loggedAt); // 2. Fulcio issued it, valid at that moment
// 3. the envelope's signature verifies with the leaf key
// 4. the log entry is for this exact signature, certificate and payload
checkSubject(payload, subject);                        // 5. the statement is about THIS tarball
return { status: "verified", identity: fulcioIdentity(leaf.raw) };
```

A few things I only learned by building it:

`"DSSEv1 <type length> <type> <payload length> <payload>"`. Sign the raw JSON instead and a valid signature could be reinterpreted as a different type.`unverifiable`, not `verified`. Only a bundle that claims something its signatures don't back is `invalid`.
Last night I tagged croak 0.1.4. The release workflow built it, packed it once, and published that exact tarball with provenance. Then I asked croak, through its own verifier, about itself:

```
latest: 0.1.4
0.1.4: verified https://github.com/vola-trebla/croak/.github/workflows/release.yml@refs/tags/v0.1.4
       @ a598a5863c9146429859323e2858421e9db16f21 logged 2026-11-01T23:13:03.000Z
```

The workflow file, the tag, the commit. Logged at 23:13:03, the same second the ten-minute certificate was born.

Then the check that matters: the same signature, but compared against the previous release's tarball hash.

```
0.1.4 bundle against 0.1.3's tarball: invalid
```

A real signature over the wrong bytes. That's the tampered-tarball case, and it has to say no.

The first time croak looked at itself, it flagged itself.

Version 0.1.0 shipped with my source comments in it. One of those comments explained the obfuscation detector by quoting the pattern it hunts for, `globalThis[key](…)`. croak scanned its own installed files and found "obfuscated code".

The fix was one compiler flag, `removeComments`. Now CI installs the packed tarball and runs croak on it before every release. A security tool that can't pass its own scan doesn't get published.

This is the part that's easy to oversell.

Provenance proves **where a tarball came from**: this workflow, this repo, this commit. It does not prove **the code is safe**. A malicious maintainer can publish malware with perfect provenance from their own CI.

So in croak it's one signal among many, and it's used as a comparison:

`npm audit signatures` verifies registry signatures and provenance for what's in your lockfile. It's already in npm.`npm trust github <pkg> --file release.yml --repo you/repo`). No long-lived token to steal, provenance for free.` npm publish` repack after your tests ran.`npm i -g @vola-trebla/croak`.
Thirteen years ago I thought that diploma was the end of elliptic curves for me. Turns out they were just waiting for `npm install`.

croak is open source and early: [github.com/vola-trebla/croak](https://github.com/vola-trebla/croak). Issues, and attacks it misses, are welcome.

🐸
