Here's the question I'd ask about your own pipeline, and it's not the one you think.
You already hash things. Your CI emits a digest for every artifact. Your lockfile is a wall of SHA-256. Your registry gives you a content-addressed tag. Good. That part is solved.
The question is: six months from now, when someone needs to know that a specific build existed on a specific day, who do they have to ask?
Usually the answer is you. Or your CI vendor. Or your artifact store. And that's fine right up until the moment it isn't, because the whole point of the record is to be believed by someone who has a reason not to believe you.
The obvious answer, and where it runs out
Obvious answer: the log has a timestamp. CI records the run, the run has a date, the artifact has a digest, done.
That works for almost everything. Debugging a regression, tracing a bad deploy, figuring out which build shipped on Tuesday. Nobody is adversarial in those situations, so a database row with a created_at on it is completely sufficient.
Where it runs out is when the party relying on the timestamp is not the party who issued it.
Because that log row is mutable by whoever owns the database. Not "will be tampered with," just: nothing about it makes tampering detectable. If I hand you a CSV export of my build history, the strongest thing you can say about it is that I gave it to you. You can't distinguish a real record from one I wrote yesterday. The trust isn't in the data, it's in me, and if I'm the one who benefits from the timestamp, that's the exact place you shouldn't be putting trust.
The retention window is the other half of it. Most CI providers age out logs. Your evidence quietly has a shelf life that's shorter than the thing you might need it for.
What actually fixes it
Put the hash somewhere neither of us controls.
That's it, mechanically. You take the SHA-256 of the bytes, you write that hash into a public blockchain transaction, and now the timestamp is attested by a system that doesn't know either of us and doesn't care who wins. Anyone can go look at the block. The block has a time. The transaction has your hash in it. If the file's hash matches, the file existed before that block was mined.
The file itself never goes anywhere. This is the part people get wrong when they first hear "blockchain" and reach for the door. Nothing is uploaded. A SHA-256 digest is 32 bytes and tells you nothing about the input. You can anchor a signed contract, a model checkpoint, a database dump full of PII, whatever, and the thing on chain is still just 32 bytes.
If you want to see this for yourself with tools you already have:
# what goes on chain
sha256sum dist/myapp-1.4.2.tar.gz
# 9f2c... 64 hex chars. that's the whole payload.
# and the verification, later, from the other side
sha256sum ./the-file-they-sent-me
# match or no match. binary answer. no server involved.
That second command is the important one, and I'll come back to it.
The part that took me a while to actually understand
I spent a while building this stuff, and the thing I got wrong at the start is that I thought the hard problem was the anchoring. It isn't. Anchoring is a solved, boring, well-understood operation. Writing 32 bytes into a chain is not where the difficulty lives.
The hard problem is verification, and specifically: verification by someone who distrusts you.
Think about what a verification flow usually looks like. Vendor gives you a proof. To check the proof, you go to the vendor's website and paste something in, and the vendor's website says "verified." Read that again. The entity whose honesty is in question is the same entity telling you the record is good. That's a closed loop. It has the shape of verification without any of the substance.
So the test isn't "can this be verified." It's "can this be verified when the issuer is offline, hostile, or out of business."
Which means the verification path has to be:
- Public. No account, no API key, no login. If your auditor needs credentials from you to check your evidence, they're checking with your permission, and permission is exactly what's in dispute.
- Local. They should be able to run it on their own machine against their own copy of the file.
- Offline. The chain data is public. Nothing in the check requires my server to be up.
That third one is the one that pushed me toward shipping the verification as a separate installable thing. There's a verify-proof package on PyPI that does the check locally, and a GitHub Action for wiring it into CI. Separate from the service on purpose. If the service disappears, the checker still works, because the checker's job is to compare a hash against public chain data, and neither of those things is mine.
The tradeoff I took, and what it cost
I anchor to two chains: Polygon and Bitcoin.
Polygon is fast and cheap enough that I can offer it unlimited on every plan including the free one. No per-proof charge, no lifetime cap on free proofs. That's not generosity, it's just what the cost structure allows, and pretending otherwise would be silly.
Bitcoin is the opposite. It costs real money per anchor, so it's billed per anchor, ranging from $7.99 down to $3.99 depending on plan.
The reason for two is independence. If your record lives on one chain and someone wants to argue about that chain's consensus, its validators, its history, you're stuck arguing chain politics instead of arguing about your file. Two independent chains means the record survives that argument. Same hash, two places, verifiable separately.
Now the honest part, and it matters: the Bitcoin proof I produce is not cryptographically better than a free one. OpenTimestamps exists, it anchors to Bitcoin, it's free, and its proof is exactly as valid as mine. Anyone telling you their blockchain timestamp is more secure than another correctly-constructed blockchain timestamp is selling you something that doesn't exist. A hash in a block is a hash in a block.
What differs is everything around the proof: organization, retrieval, an API, being able to hand an auditor a URL. That's a platform question, not a cryptography question, and it's worth what it's worth. But if all you need is "this file existed on this date" and you're comfortable managing the proof files yourself, OpenTimestamps does that and costs nothing. Go use it. I'd rather you get a real timestamp somewhere than a fake one from me.
The API surface, since developers always ask
Three endpoints. That's the entire v1.
POST /api/v1/proof # submit a hash, Bearer sk_ auth
GET /api/v1/proof/:id # status, chain_tx, bitcoin_txid. owner only.
GET /api/v1/verify?hash=<sha256> # public, no auth, 120 req/hr per IP
That last line is the design decision the whole product hangs on. It's public and unauthenticated on purpose, because the person most likely to need it is opposing counsel or an auditor, and they are never going to have my API key. Docs at proofledger.io/api.html, OpenAPI spec at /openapi.json.
Note what's not there. No batch endpoint, no list endpoint, no webhooks. It's a small surface because the operation is small.
There's also a public verify URL for every proof, which is the thing you paste into an email when someone asks.
Go run this against whatever you're using now
Concrete, do it today, takes fifteen minutes.
Pick whatever system currently holds your timestamps. CI logs, artifact registry, document management tool, whatever. Then answer these against it:
- Can someone with no account and no credentials from you verify a record? Actually try it. Log out, open a private window, attempt the check.
- If your vendor's servers were down right now, could the check still be performed?
- If the vendor shut down tomorrow, does the evidence survive?
- Who can modify the timestamp field, and would anyone downstream be able to tell?
If the answers are no, no, no, and "me, and no," you don't have evidence. You have a record that's fine for operations and worthless the moment it's contested. Which might be totally acceptable! Most timestamps never get contested. Just know which one you've got before you need it to be the other one.
I'm building the anchoring side of this at proofledger.io, still solo, still figuring parts of it out.
The thing I actually want an answer to: for those of you who've had to produce build provenance for a customer audit or a compliance review, what did they accept as proof? I keep hearing that a signed statement from the vendor was enough, which surprises me every time. Curious whether that's the norm or whether I'm hearing from an unusual slice.
Top comments (0)