Verification
Table of Contents
Verify a receipt's Ed25519 signature end to end: fetch the key, canonicalize, verify, cross-check DNS. A receipt's signature lets you prove, years later, that the receipt bytes you stored are the bytes the service produced. Verification needs no trust in us - only the two public key channels: The example receipt in POST /v1/compress verifies with exactly this snippet - save it as Run against the worked-example receipt on the compress page, this prints Receipts embed the GET https://nanotoll.dev/.well-known/nanotoll-verify.json - the public key and its key_id (see key distribution)._nanotoll-verify.nanotoll.dev carrying the SHA-256 fingerprint of that key.The check
receipt.json and run:import hashlib, json, urllib.request
from nacl.signing import VerifyKey
receipt = json.load(open("receipt.json")) # the full API response receipt
wk = json.load(urllib.request.urlopen(
"https://nanotoll.dev/.well-known/nanotoll-verify.json"))
key_id = receipt["key_id"]
entry = next(k for k in wk["keys"] if k["key_id"] == key_id)
key = bytes.fromhex(entry["public_key"])
# Cross-check against the DNS TXT fingerprint:
# dig TXT _nanotoll-verify.nanotoll.dev +short
# must equal sha256(key) hex.
print("sha256(key):", hashlib.sha256(key).hexdigest())
sig = bytes.fromhex(receipt.pop("receipt_signature"))
receipt.pop("key_id")
# Canonical form: the received values, recursively key-sorted, compact.
# Serialize exactly what you received - do not reformat or round numbers.
payload = json.dumps(receipt,
sort_keys=True, separators=(",", ":")).encode()
VerifyKey(key).verify(payload, sig)
print("signature OK")signature OK: the example is a real captured response, not an illustration.What a key_id change means
key_id that signed them. Store the key_id alongside your receipts - it is part of the record, and it is what you check against the well-known file when you verify. If a receipt carries a key_id that is not in the well-known file, treat the receipt as unverified.