Skip to content

Verifying a Jithox receipt offline

Tool executions on the Jithox MCP fleet are signed. This page shows how anyone verifies one of those signatures without contacting us at verification time, what a valid signature proves, and what it does not.

The short answer

The public key does not travel inside the receipt you are checking. It is published at a separate endpoint, and that distinction is the whole point: a key shipped in the same payload you are trying to verify anchors nothing, because anyone who can rewrite the payload can rewrite the key next to it. You fetch the key once over a channel you already trust, and from then on verification is a local computation.

Algorithm: Ed25519, detached signature over a canonicalised payload.

The four public artefacts

Verification contract
The active key set, the algorithm, the canonicalisation rules per receipt version, and the rotation policy — machine-readable, in one document.
Public key (PEM)
The key that signs today's receipts. Any specific key is also retrievable by its id.
Standalone verifier
Node, built-in modules only, no dependencies. It opens no sockets — verification works air-gapped.
A signed sample
A real receipt, so you can prove the verifier accepts a good one and rejects a tampered one before trusting either.

Verify one in four commands

curl -sS -O https://mcp.jithox.com/receipts/sample.json
curl -sS -O https://mcp.jithox.com/receipts/public-key.pem
curl -sS -O https://mcp.jithox.com/receipts/verify-receipt.mjs
node verify-receipt.mjs sample.json public-key.pem
# VALID

A check that never rejects anything proves nothing, so flip a single character of resultHash:

node verify-receipt.mjs tampered.json public-key.pem
# INVALID (signature does not match payload)

Where your own receipt lives

The complete signed receipt comes back in the tool result's _meta.receipt — every signed field plus the signature. Write it to a file and the verifier above accepts it directly. The Trust envelope additionally carries a receiptEnvelope naming the receipt version, the canonicalisation version, the algorithm, the key id, the exact signed payload, the signature, and the URLs for the public key and this contract — so you no longer have to know where to look.

The key identifies itself

The key id is derived from the key: a SHA-256 of its SPKI. The verifier recomputes it from the PEM you hand it and refuses the receipt when it does not match. You therefore cannot be talked into verifying against the wrong key, and we cannot silently swap keys underneath a receipt.

Three canonicalisations, domain-separated

Each receipt version has its own context string, so the versions cannot be confused with one another. Relabelling a v2 receipt as v3 does not produce a v3 receipt — it produces a failure. That fails closed rather than quietly passing, and it is tested rather than assumed.

Rotation

Each key has a unique id. On rotation the new key is added to the verification contract and previous public keys stay listed for at least 24 months. Receipts are never re-signed and never rewritten, so an old receipt keeps verifying against the key that signed it.

What a valid signature means

This exact receipt payload was produced by the Jithox signing key at issuance time and has not been altered since.

What it does not mean

  • It is not a legal, fiscal or compliance guarantee.
  • It is not proof that an underlying VAT or VIES answer is still current — a registry result is true at the moment it was fetched, and nothing more.
  • It is not proof of payment beyond the referenced rail record.
  • It does not vouch for the correctness of an upstream authority's data.
  • There is no endpoint that hands you a past receipt by its id. Keep the receipt when you receive it — we do not hand it back later.

Decision evidence (beslisbewijs)

The receipts above sign tool executions on the MCP fleet. A separate statement signs a Jithox decision: check_payment_change on jithox.com/api/mcp can answer with an evidence field next to its verdict. It is a compact JWS (EdDSA, Ed25519) holding the verdict, the rules that led to it, a salted SHA-256 digest of the input, the time and the key id — never an account number, a name or an invoice. The field is there only while a signing key is configured: when the key set below is empty, answers carry no evidence, and nothing unsigned is sent in its place.

Its keys are published at /.well-known/jwks.json, a separate key set from the fleet receipts. Any JOSE library verifies it offline against that set. Or paste it here:

curl -sS -X POST https://jithox.com/api/v1/evidence/verify \
  -H 'content-type: application/json' \
  -d '{"jws":"<evidence.jws>"}'
# {"ok":true,"status":"valid", ...}   or "invalid", or "unknown_key"

A valid token means: Jithox gave this verdict, from these checks, about the input behind the digest, at that time, and nothing in the token has changed since. It does not say who owns an account, that an account exists, or that an invoice is correct.

Share only evidence.jws. evidence.inputSalt together with the token lets anyone who can guess the account recover it: keep it with your own copy of the input, as private as the input. The input and the salt together let you show later which input the decision was about.

Related

Trust and controlled execution — the mechanisms and their limits. Invalid, invalid input, or VIES unavailable — what a VAT check actually returns.