@stackmerthh/receipt-verifier
v1.1.0
Published
Verify TokenOS DeAI inference receipts offline. Ed25519 over canonical-JSON serialization, no network calls, no platform trust required. Supports receipt-format spec v1.0 + v1.1.
Downloads
90
Maintainers
Readme
@stackmerthh/receipt-verifier
Verify TokenOS DeAI inference receipts offline. Ed25519 over canonical-JSON serialization, no network calls after the one-time pubkey fetch, no platform trust required at verification time.
- ~140 LOC, single TypeScript file
- One runtime dependency:
tweetnacl(pure-JS ed25519, works in browsers + Node + workers) - Spec: RECEIPT_FORMAT_v1.md
- Supports: receipt v1.0 and v1.1 (field-agnostic canonical algorithm)
- License: MIT
What is a TokenOS DeAI receipt?
Every billable inference call made through the TokenOS DeAI platform produces a tamper-evident, ed25519-signed JSON record. The receipt proves:
- The platform metered this exact call (model, tokens, cost, served-at time).
- The receipt body has not been modified since signing.
- The signing key is one the platform has published.
Receipts survive the platform: even if the issuer disappeared, anyone holding the receipt + the published public key can verify it years later from cold storage. They are the unit of trust between buyer / operator / platform / external auditor.
This package is the reference verifier: byte-for-byte identical to the canonicalizer the platform uses to issue receipts, so issuing and verifying can never drift.
Install
npm install @stackmerthh/receipt-verifier
# or
pnpm add @stackmerthh/receipt-verifier
# or
yarn add @stackmerthh/receipt-verifierRequires Node 18+ (or any modern browser).
Quick start
import { verifyReceipt } from '@stackmerthh/receipt-verifier'
// 1. Fetch the platform's published key set (cache aggressively — pubkeys
// only change on rotation).
const keys = await fetch('https://a2e-api.onrender.com/.well-known/tokenos-keys.json')
.then(r => r.json())
// 2. Look up the pubkey for this receipt's signing key.
const pubkeyHex = keys.keys[receipt.signedBy]?.pubkeyHex
if (!pubkeyHex) throw new Error('Unknown signing key — refusing to verify')
// 3. Verify.
const result = verifyReceipt(receipt, pubkeyHex)
if (result.valid) {
console.log('Receipt is genuine')
} else {
console.error(`Receipt invalid: ${result.reason}`)
}That's the whole API. The verification is synchronous, pure, and does no
I/O after the one-time fetch for the key set.
API
verifyReceipt(receipt, pubkeyHex): VerifyResult
Returns a discriminated result:
type VerifyResult =
| { valid: true }
| { valid: false; reason: string }Possible reason values when invalid:
| Reason | Meaning |
|---|---|
| missing_signature | receipt.signature is missing or empty |
| malformed_hex | receipt.signature or pubkeyHex is not valid hex |
| wrong_length | Pubkey is not 32 bytes OR signature is not 64 bytes |
| signature_mismatch | Bytes don't verify — the receipt was tampered with, signed by a different key, or your canonicalization disagrees |
isReceiptValid(receipt, pubkeyHex): boolean
Same as verifyReceipt but returns a plain boolean. For callers that don't
care about the failure reason.
canonicalizeReceipt(receipt): string
Returns the canonical JSON string (the bytes that get signed/verified). Useful when you need to compute the canonical form yourself, e.g. when building a custom issuer or debugging signature drift.
CLI
A one-shot verifier ships with the package:
# Verify a receipt JSON file using the live published key set.
npx tokenos-verify-receipt path/to/receipt.json
# Or with a specific pubkey hex (skip the network call).
npx tokenos-verify-receipt path/to/receipt.json --pubkey 67a161...
# Or with a custom keys endpoint (useful for testing).
npx tokenos-verify-receipt path/to/receipt.json --keys-url https://staging.tokenos.ai/.well-known/tokenos-keys.jsonExits 0 on valid, 1 on invalid. Prints a discriminated result for piping.
How it works (short version)
- The platform signs every receipt with ed25519 over the canonical JSON of
the receipt body. The body excludes
signature(chicken-and-egg) andsignedBy(so key rotation doesn't invalidate old receipts). - Canonical JSON = recursive alphabetic key sort + no whitespace + Date to ISO-8601 string. The algorithm is field-agnostic, so receipts with new fields in future spec minor versions verify with no code change.
- To verify: fetch the published key set once, look up the pubkey by the
receipt's
signedBy, re-canonicalize the body, runed25519_verify.
The complete spec is in RECEIPT_FORMAT_v1.md. A JSON Schema for receipt validation lives at receipt-format-v1.schema.json.
Key rotation
The platform's published key set is a map: every key id ever used maps to its
pubkey hex + a retiredAt timestamp (null for the active key).
{
"active": "platform-key-v1",
"keys": {
"platform-key-v1": {
"id": "platform-key-v1",
"pubkeyHex": "67a16103...",
"retiredAt": null
}
// ...future retired keys live here too with non-null retiredAt
}
}Always look up the pubkey by receipt.signedBy — never assume the active
key signed the receipt. Old receipts are signed by retired keys; they still
verify correctly because retired keys remain in the map.
If the platform's signing key is compromised, it will be retired with its
retiredAt set to the compromise time. Verifiers SHOULD refresh the key set
periodically (24-hour cadence is reasonable) and treat receipts signed by a
compromised key AFTER its retiredAt as untrusted.
Versioning
The package version tracks the receipt-format spec it supports:
| Package version | Supports receipt versions |
|---|---|
| 1.0.x | v1.0.0 |
| 1.1.x | v1.0.0 + v1.1.0 (adds servedAtTier) |
The canonical algorithm is field-agnostic, so a 1.0.x verifier handed a v1.1.0 receipt will compute the right canonical bytes anyway — the version column above tracks the spec version the package was intentionally designed against.
Major-version bumps (2.0.0 etc.) will only happen if the canonical algorithm itself changes; minor-version bumps are additive.
Limitations + non-goals
- Not a replay detector. Receipts are tamper-evident, not transferable
bearer tokens. A verifier that wants to detect replay must compare
inferenceRequestIdagainst its own audit log. - Not an attestation verifier. Phase 3.2 of the TokenOS roadmap adds
attestationProofRefchasing — verifying that the CONFIDENTIAL-tier receipt was actually served on attested TEE hardware. That logic will ship in a separate companion package. - No CRL. Key revocation is via the
retiredAtfield in the published set. There is no separate CRL endpoint.
Contributing + spec changes
The package lives in the TokenOS monorepo at packages/receipt-verifier. Spec changes are coordinated with package releases — see RECEIPT_FORMAT_v1.md §12.
Compatible implementations in other languages (Rust, Go, Python, Java) are welcomed; the canonical algorithm is small enough that a faithful port is straightforward. Open an issue with a link to your implementation and we'll list it here.
License
MIT. See LICENSE.
