VVDex

Proof · Receipts

Receipts

Every evaluation run is sealed in a receipt: a JSON document that records what was asked, what came back and how it was graded, signed by VVDex. Change one byte and the check fails.

Get one

In the web app, open a run and download its receipt. From code: GET /api/v1/evals/{id}/receipt, client.evaluation_receipt(id), or share the run and hand over the link.

The format: vvdex.receipt/v1

FieldWhat it holds
formatAlways vvdex.receipt/v1 for this version.
run_id, nameThe run.
created_at, started_at, finished_atWhen the run was created, started and finished.
platform_versionThe VVDex version that ran it.
forge or datasetFor a Forge exam: exam, version and attempts; each result’s grade carries the digest of the sealed Forge record behind it. For a dataset: its name, row_count and content_digest.
models, judgeEach model’s public identity: provider, model id, display name and self_reported. Never a key, a connection id or an address; a custom endpoint shows only its host.
params, gradersTemperature, token limit, system prompt, repeats, and the graders with their settings (protected grader settings appear only as hashes).
summaryPer model: results, cases scored, passed and failed, pass rate, 95% interval, errors by kind, cost (null when unknown) and latency. Optional: by_difficulty, the same counts and interval per difficulty tier ({tier: {n_scored, n_passed, pass_rate, wilson_low, wilson_high, errors}}), present only when the run’s questions name a difficulty; a question without one counts as unspecified. Receipts made before this field existed simply do not have it, and still verify.
resultsEvery result: the case key, the messages the model received, the expected answer, the model’s output, the grades, the repeat number, latency and tokens — plus its model index, status, error kind and score, and, in a run with difficulty tiers, its difficulty. Those extra fields are not in the leaf but are covered by the digest and signature.
leavesOne SHA-256 per result, in result order, over {case_key, input, expected, output, grades, model, repeat, latency_ms, tokens}.
leaves_rootSHA-256 of the leaf hashes concatenated as hex text.
key_id, signature_algWhich VVDex key signed it, and ed25519.
digestSHA-256 (hex) of the receipt’s canonical JSON without digest and signature.
signatureEd25519 signature over the digest’s hex text, base64.

Canonical JSON means: keys sorted, separators , and : with no spaces, UTF-8 text without \u escapes, no NaN.

Because a receipt contains the prompts and answers of the run, share it only where you would share the run itself.

Verify online

The public check needs no account. It verifies the digest and the signature, recomputes every leaf and the root, checks that each model’s counts and interval (and, when present, its per-difficulty breakdown) follow from the results, and re-grades every deterministic grade it can recompute.

shell
$ curl -s https://vvdexops.com/api/v1/receipts/verify \
    -H "Content-Type: application/json" --data @receipt.json
# → {"valid": true | false, "checks": [{"check", "ok", "detail"}, …]}

$ vvdex receipt verify receipt.json          # exit code 0 = valid, 1 = not valid

Or paste it into Check a receipt.

Verify offline, with the public key

You do not have to ask VVDex to check a signature. Save the public key once; after that, checking needs no network.

shell
$ pip install "vvdex[offline] @ https://vvdexops.com/downloads/vvdex-0.2.0-py3-none-any.whl"
$ vvdex receipt public-key > vvdex-receipts.pem      # or GET /api/v1/receipts/public-key
$ vvdex receipt verify receipt.json --offline --public-key vvdex-receipts.pem

Or with nothing but Python and the cryptography package:

python
import base64, hashlib, json
from cryptography.hazmat.primitives.serialization import load_pem_public_key

def canonical(value):
    return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()

def sha256(data):
    return hashlib.sha256(data).hexdigest()

receipt = json.load(open("receipt.json", encoding="utf-8"))

# 1. the digest covers everything except digest and signature
body = {k: v for k, v in receipt.items() if k not in ("digest", "signature")}
assert sha256(canonical(body)) == receipt["digest"], "changed after signing"

# 2. every result matches its leaf, and the leaves add up to the root
fields = ("case_key", "input", "expected", "output", "grades", "model", "repeat", "latency_ms", "tokens")
for result, leaf in zip(receipt["results"], receipt["leaves"], strict=True):
    assert sha256(canonical({k: result.get(k) for k in fields})) == leaf, "a result was edited"
assert sha256("".join(receipt["leaves"]).encode()) == receipt["leaves_root"]

# 3. VVDex signed that digest (raises InvalidSignature if not)
key = load_pem_public_key(open("vvdex-receipts.pem", "rb").read())
key.verify(base64.b64decode(receipt["signature"]), receipt["digest"].encode())
print("signed by VVDex and unchanged")

The offline check proves the receipt is exactly what VVDex signed. Re-grading answers needs the graders, which run on VVDex’s servers; that part is the online check.

What a receipt is not

A receipt describes one run: these questions, these answers, these grades, on that date. It is not a certificate that a model is fit for any purpose, and it says nothing about how the model behaves on other inputs. For models reached through a custom address or an HTTP app, the model’s name is what its owner reported.