Receipts, signatures and reports#
Don’t ask for trust. Hand over a receipt.
vision key new ~/.vision/key.pem --password # Ed25519; publish only the id it prints
vision run examples/plant.vis --receipt out/plant.json \
--sign ~/.vision/key.pem --timestamp --report out/plant.html
vision verify out/plant.json --device intel --trust <id>
vision verify out/plant.json --device numpy # no GPU at all
What each piece proves#
Piece |
What it is |
What it proves |
|---|---|---|
Receipt ( |
a JSON file that binds the source code, parameters, data files, Vision version, every map and every series of measures into one hash |
what was computed |
Signature ( |
Ed25519 over the receipt hash. The key id (128 bits of the SHA-256 of the public key) is what you publish, like the fingerprint of an SSH key |
who issued it |
Time-stamp ( |
an RFC 3161 time-stamping authority attests that the receipt and the signature existed at that moment. Only the hash leaves the machine. Default: DigiCert; |
when |
Report ( |
a self-contained HTML page for a client, auditor or regulator |
readable evidence |
Verification ( |
checks the hash, signature, time-stamp and data files, then re-runs the whole simulation and compares everything bit by bit |
that the receipt is true |
Verifying without a GPU#
With --device numpy, verification runs on the CPU with Python, numpy and cryptography only:
no GPU, no driver and not even the wgpu package. A receipt made on an NVIDIA GPU checks on any
laptop. Tested on Python 3.10 to 3.14 with numpy 2.0 to 2.4.
The report#
The report opens without internet and prints to PDF. It contains:
the measures and their series;
the maps;
the inputs with their hashes;
the proof and how to check it;
what the report proves and what it does not prove. The physical validity of the model is the responsibility of whoever wrote it.
vision verify ... --report check.html writes a report of the verification itself.
Keys#
vision key new key.pemcreates a key in a file readable only by its owner, and refuses to overwrite an existing one.--passwordencrypts it with a password, asked in the terminal or read from theVISION_KEY_PASSWORDenvironment variable.vision key id key.pemprints the id of an existing key.
Certifying a result produced elsewhere#
A customer runs Mojo, PyTorch, CUDA or a closed simulator, and gets a number they have to defend. Vision does not ask them to change: it recomputes the same program in its own arithmetic — the one that is identical on every device — and says exactly how far apart the two results are, in ULP:
vision certify their_result.npy --program heat.vis --param N=512 --param steps=200 --json receipt.json
plate.temperature: differs
12 of 262144 values differ (0.005%)
largest difference: 3 ULP; median 1 ULP
first at 132098: computed 41.7421875 against 41.7421836
computed on NVIDIA GeForce RTX 4050 Laptop GPU; receipt 9f2c1ab1e3d5e7f0
The verdict is one of three, and the third is a finding rather than a rounding:
verdict |
meaning |
|---|---|
|
every value is bit-identical |
|
how many values, what fraction, the largest and the median difference in ULP, and the first one |
|
the shapes or the dtypes disagree — the two stacks are not computing the same thing |
An ULP is the only honest unit here. “1e-6” says nothing for a value near zero, and “0.1%” says nothing about whether the difference could change a decision: one ULP is the smallest step a float32 can take, so 3 ULP is a rounding difference and 10 000 ULP is a different answer wearing the same shape.
The receipt records the hash of the program, the hash of the result it certified, the device that recomputed
it and the verdict — and --sign makes it a signed certificate, so the other party can neither alter the
number nor dispute what was checked.
--device numpy certifies without a GPU, which is the mode an auditor uses.
Limits#
The chain of the time-stamping authority’s certificate up to a root is not validated: the verifier shows the holder and the SHA-256 of the certificate, so it can be pinned.
There is no key revocation.
A receipt forged and re-signed by the key owner still fails, because the verifier re-runs the simulation.