Verify a receipt
Drop a receipt and the public key it was signed with. The check runs in this tab, against the same function the command-line tool calls. Nothing is uploaded, nothing is stored, and there is no account and no toolchain to install.
1. The document
receipt.json, a key file, or a checkpoint here — files are read in the page and routed by shape. Or paste below.Exactly what gx receipt show or GET /receipts/{tid} hands back. Paste the file's text, not a parsed object.
This is the key you are checking the receipt against. Getting it from the same party that gave you the receipt proves nothing about that party; it proves the file has not changed since they signed it.
Without a checkpoint, the ledger half of the answer is not checked and the verdict says so. The two checkpoint-key boxes move together: one without the other is refused rather than half-checked.
No receipt of your own? These three load real files out of the test suite and produce the three different answers. None of them is staged for this page:
2. The answer
Give the page a receipt and a public key and press Verify.
The exact JSON the engine returned, unedited — the page's words above are a reading of these bytes and nothing more:
// no verification has run in this tab yet
Three answers, not two
A verifier with only pass and fail has to file "I could not read this" under one of them, and both filings are wrong. Filed as a pass it is fail-open. Filed as a fail it accuses somebody of editing a document when the real cause may be that this build is older than the file, or that you pasted the wrong key. This page keeps the third answer separate, and never says refuted about an input it merely could not read.
| What the engine returned | What this page calls it | Why |
|---|---|---|
| valid: true | Verified | Every check that could run, ran and passed. |
| no valid signature under key … | Refuted | AC-019. The signed bytes and the signature disagree: the document was edited, or this is not the key that signed it. The engine deliberately does not say which part failed — naming it would tell a forger what to fix. |
| checks.canonical_cid: false | Refuted | The receipt's own content address does not match the bytes it commits to. |
| checks.inclusion: "refuted" | Refuted | The inclusion proof was walked against the checkpoint and contradicted. |
| checks.inclusion: "unanchored" | Not checkable | The signature held. No checkpoint was supplied, so the ledger claim was not checked at all. A skip and a pass must not share a face. |
| checks.inclusion: "unbridged" | Not checkable | The checkpoint names a tree size the receipt's proof does not, and nothing bridged them. Not a pass, and not a refutation either. |
| the receipt is not a legal one: … | Not checkable | Well-formed, but not a shape this build accepts. Very often an older or newer receipt format — an accusation of tampering here would send you hunting for an attacker who does not exist. |
| receipt: … / public_key_base64: … / checkpoint_json: … | Not checkable | The page could not read one of the inputs you gave it. That is a fact about the input, not about anybody's honesty. |
| anything else | Not checkable | An unrecognised refusal is reported as unread, never as refuted. The engine's exact words are always printed above so you can see what this page could not classify. |
What this page does not check
Every check has a blind spot, and one that hides its blind spots is worse than one that has more of them.
- Whether the key is the right key. The page checks a receipt against the key you gave it. If you took that key from the same party that gave you the receipt, a verified answer means the file is unaltered since signing — it does not mean the signer is who they say they are. Key distribution is a separate problem and this page does not solve it.
- Revocation. Nothing here consults a revocation list. A receipt signed with a key that was
later revoked reads as Verified on this page. The library has a road for this
(
verify_offline_consulting) and this page does not take it, because there is no list to consult without a network and this page opens no socket. - Whether the checkpoint is this log's checkpoint, unless you supply its key. Without the two
checkpoint-key boxes filled, the inclusion arithmetic is run against a head nobody vouched for, and
the answer says
anchor_authenticated: falseon every run so you cannot miss it. - Anything about the world the receipt describes. A receipt is evidence that a record has not changed since it was signed. It is not evidence that the record was true when it was written.
- Time. There is no clock in the verifier. Nothing here checks whether a receipt is stale.
How to check this page
Taking a verification page on trust is the failure it exists to prevent, so here is what to compare and how.
The verifier is the shipped engine, not a re-implementation
The WebAssembly module below is built from sdk/wasm-verify, which calls
gx_witness::receipt::verify_offline — the one function gx receipt verify
--offline calls. That crate constructs no verdict and mints no content address of its own; it
is a projection of the engine through wasm-bindgen. The JavaScript in this page moves
strings in and renders JSON out. It contains no cryptography.
wasm module sha256 8f7064b675cc89e502ae63422b08532f28a83616fe60968e3b66345a0d772776
bindgen glue sha256 4a925bdb24c740ad231db54b677da0d575bffe7821f701dbddcc817a63cdeebd
# reproduce both, from a checkout:
sdk/wasm-verify/build.sh
sha256sum sdk/typescript/src/wasm-gen/wasm_verify_bg.wasm
node web_verify/build/build_verify.mjs
Get the same answer a second way
Run the same three files through the command-line tool and compare. If the two disagree, one of them is wrong and we want to know which — that disagreement is a bug report we would rather receive than not.
gx receipt verify --offline receipt.json \ --key-id <KEY_ID> --public-key <PUBLIC_KEY_BASE64> \ [--checkpoint checkpoint.json --checkpoint-key ledger.pub]
One known difference, stated rather than hidden. The command-line tool reports
through an exit code, and exit 7 covers both "this build cannot read the document" and
"somebody edited it" — the two answers this page keeps apart. The information is in the tool's JSON
body (checks.signature separates them) but not in its exit code. This page reads the
body, so where the exit code collapses three answers into two, the page does not.
Watch it work with the network off
Save this page (it is a single file, with the verifier inside it), disconnect,
reopen it from disk and verify again. The counter at the top of the page stays at zero because the
page has nothing to call: the module imports exactly one function from the host,
__wbindgen_init_externref_table, and the build refuses to produce this page if that ever
stops being true. No entropy source, no clock, no socket.