Cryptographic file provenance using Ed25519 + SHA-256. Sign any file to create a .oproof sidecar that proves who created it and that it hasn't been tampered with. Verification is offline — no server, no blockchain, no account needed.
A companion Chrome extension lets anyone verify .oproof files without installing the CLI.
npm install
npm run build
Requires Node.js >= 22.
npx originproof init
Generates an Ed25519 keypair at ~/.originproof/keys/ and prints your key ID. Idempotent — running it again reuses existing keys.
npx originproof sign photo.jpg
Creates photo.jpg.oproof alongside the original file.
npx originproof verify photo.jpg
Checks photo.jpg against photo.jpg.oproof. Prints one of four results:
| Result | Meaning | Exit code |
|---|---|---|
VERIFIED |
File is authentic and unmodified | 0 |
HASH_MISMATCH |
File has been modified since signing | 2 |
INVALID_SIGNATURE |
Signature doesn't match the embedded public key | 3 |
MALFORMED_PROOF |
Proof file is missing, corrupt, or has an unsupported version | 4 |
Exit code 1 is reserved for operational errors (missing file, uninitialized keystore, etc.). Distinct codes let scripts distinguish failure types.
To verify with a proof at a different path:
npx originproof verify photo.jpg /path/to/photo.jpg.oproof
npm start
Opens a local server at http://localhost:3000 with Sign and Verify tabs.
| Env var | Default | Purpose |
|---|---|---|
PORT |
3000 |
Server port |
CORS_ORIGIN |
(none) | Set to allow cross-origin API access (e.g. http://localhost:5173) |
API routes are rate-limited to 100 requests per 15 minutes per IP.
A .oproof file is JSON:
{
"version": "1.0.0",
"file": {
"name": "photo.jpg",
"size": 2048576,
"algorithm": "sha256",
"hash": "b94d27b..."
},
"signer": {
"keyId": "082788...",
"publicKey": "-----BEGIN PUBLIC KEY-----\n..."
},
"createdAt": "2026-04-09T14:30:00.000Z",
"signature": "7qu1Y1c3..."
}The signature is an Ed25519 signature over the canonical (key-sorted, compact) JSON of everything except the signature field itself. The public key is embedded, so verification is self-contained.
All endpoints return JSON. Verification results always return HTTP 200 — the status code reflects whether the operation succeeded, not whether the file is authentic.
| Method | Path | Request | Response |
|---|---|---|---|
POST |
/api/sign |
multipart/form-data with field file |
ProofRecord JSON |
POST |
/api/verify |
multipart/form-data with fields file and proof |
{ status, message, proof? } |
GET |
/api/identity |
— | { keyId, algorithm, createdAt } |
GET |
/api/health |
— | { status: "ok", version } |
Upload limit: 100 MB.
npm test
Runs 37 tests covering hash, proof serialization, keystore, signing, and verification (including tamper detection, key substitution, and insertion-order invariance).