sellat-verify
v0.2.0
Published
Offline verifier for SELLAT proof-of-existence artifacts (sellat-proof/2). Zero dependencies.
Maintainers
Readme
sellat-verify
Don't trust SELLAT. Verify.
sellat-verify is a standalone, zero-dependency verifier for
SELLAT proof-of-existence artifacts
(sellat-proof/2). Given a file and its proof.json, it proves — using only
math and the public blockchain — that the exact bytes of that file existed no
later than a specific moment in time.
No SELLAT server, database or goodwill is involved in verification. If sellat.app disappeared tomorrow, every proof it ever issued would still verify. That is the point.
Try it now — on a real proof
This repository ships a real example: a file whose proof was anchored on
Polygon mainnet in transaction
0xdeb68fad….
$ git clone https://github.com/skanthemore/sellat-verify && cd sellat-verify
$ node bin/sellat-verify.mjs example/example.txt example/example.proof.json
✓ schema schema is "sellat-proof/2"
✓ content hash format SHA-256 2ad295bdf52f661a…
✓ file matches proof SHA-256 of the file equals content.hash
✓ leaf recomputation leaf formula reproduces leaf.value
✓ merkle path path folds to root 3c6e1005a0cb2699…
✓ has anchors 1 anchor(s)
✓ anchor[0] payload payload commits to the root (Polygon Mainnet)
✓ anchor[0] on-chain chain 137 confirms payload in block 91797318 (from 0x33167f8e…)
VERIFIED — this exact file existed no later than 2026-08-10T22:30:04.000Z.Change a single byte of example.txt and verification fails.
Too much code? Verify it by hand
This example's batch has a single leaf, so you can check it with nothing but
sha256sum and a block explorer:
# 1. Hash the file
$ sha256sum example/example.txt
2ad295bdf52f661acda66158a7b20fc7c982e190b2645117ea6a77583703e291
# 2. Compute the leaf (single-leaf batch ⇒ the leaf IS the Merkle root)
$ printf '%s' 'sellat-leaf:v2:d268831c-4c70-4226-97d9-bf1cd703d365:2ad295bdf52f661acda66158a7b20fc7c982e190b2645117ea6a77583703e291' | sha256sum
3c6e1005a0cb26993e3b445e5c755d75bc89ab60012cf8670cdd9c279b78b05c- Open the transaction on Polygonscan, view its input data as UTF-8, and read:
sellat:v2:3c6e1005a0cb26993e3b445e5c755d75bc89ab60012cf8670cdd9c279b78b05cThat root sits in Polygon block 91797318, timestamped 2026-08-10 22:30:04 UTC. Nobody — including SELLAT — can rewrite that.
Usage
$ sellat-verify <file> <proof.json> # offline checks + on-chain check
$ sellat-verify <file> <proof.json> --offline # math only, no network, no date
$ sellat-verify <file> <proof.json> --rpc=<url> # use your own JSON-RPC nodeExit code 0 means every check passed. Only the on-chain check proves a
date, the block's time as the chain states it; --offline says so and states
none. By default the on-chain check uses
public RPC endpoints; pass --rpc= to use a node you control, so the
verification trusts nothing chosen by anyone else.
As a library:
import { verifyProofOffline, checkAnchorOnChain } from 'sellat-verify';The proven time is checkAnchorOnChain(...).blockTimestamp, read from the
anchoring block. Never present the proof's own block_timestamp as proven:
a proof.json is plain text, and since 0.2.0 a proof whose
block_timestamp differs from the block's time fails the on-chain check.
verifyProofOffline proves no date at all.
Verify in CI
This repository is also a composite GitHub Action, so a pipeline can prove its own artifacts verify before shipping them:
- uses: skanthemore/sellat-verify@main
with:
file: release/build.tar.gz
proof: release/build.proof.json
# offline: 'true' # math only, skip the on-chain check
# rpc: https://… # or pin the check to a node you controlThe job fails if the proof does not verify. Pair it with
sellat-cli to stamp the
artifact in the same pipeline.
How verification works
SHA-256(file)must equalcontent.hash;- the leaf is recomputed as
SHA-256('sellat-leaf:v2:' + proof_id + ':' + content.hash); - the Merkle path folds the leaf back to
merkle.root; - every anchor's payload must be exactly
sellat:v2:<merkle.root>; - the anchoring transaction is fetched from the chain and must carry that payload in its input data.
Steps 1–4 are pure computation. Step 5 works against any node or explorer. The full format is specified in SPEC.md and is frozen: issued proofs verify forever; format changes require a new schema version.
Trust layers
Artifacts can carry more than one independent layer over the same Merkle
root: EVM anchors (anchors[], e.g. Polygon in minutes) and optional
attestations[] — currently Bitcoin via OpenTimestamps, a standard
.ots file you can verify with OTS tooling, at no cost. Layers verify
separately; none depends on SELLAT or on each other. See SPEC.md §7.
What a proof does and does not claim
A sellat-proof/2 artifact proves existence and integrity at a point in time: these exact bytes existed no later than the anchor's block timestamp. It deliberately does not claim authorship, ownership, or that the content is true or authentic. In a world where anything can be fabricated now, the only unforgeable thing is the past — that is exactly what this verifies, and nothing more.
Getting a proof
Proofs come from the SELLAT API v2 (or the web app). From your own systems — only the SHA-256 leaves your machine:
$ curl https://sellat.app/api/v2/proofs \
-H "Authorization: Bearer $SELLAT_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"hash\":\"$(sha256sum build.tar.gz | cut -d' ' -f1)\",\"name\":\"build.tar.gz\"}"
# once anchored (a few minutes), the portable proof is public — no key needed
$ curl -o build.proof.json https://sellat.app/api/v2/proof/<id>.json
$ sellat-verify build.tar.gz build.proof.json- API reference: https://sellat.app/en/developers/docs
- OpenAPI 3.1: https://sellat.app/developers/openapi.json — import it into Postman, Insomnia or an SDK generator.
About
Built by SELLAT — proof of existence for one file or a
million, on the same engine. Every proof, created on the web or through the
API, has its proof.json; this verifier and the format specification are the
stable, public reference.
MIT licensed. Issues and PRs welcome.
