WHAT IS A SIGNED RECEIPT?
A receipt is a small document that says: this happened, at this time, and here is the cryptographic signature proving who recorded it. Anyone holding the receipt can check that signature against our published keys — no account, no API key, no permission. If a single character of the receipt were altered, the check fails. That's the whole trick, and it's enough: evidence that doesn't ask you to trust the person handing it over. Verification is always free.
Check one for yourself
Below is a specimen — one real receipt, minted through the live engine under the live signing key and published here as the exhibit. Not a sample, not a mock-up: its signature checks against the same published keys as every receipt the engine mints. The operator minted it; anyone can check it. To be plain about what it is: the subject matter is a test record — a neutral verdict at low confidence — so what the exhibit demonstrates is the signing and the chaining, not a dramatic verdict. The walk below is the check, end to end.
Check it here, in one click
Nothing to install, nothing to save, no terminal. One press checks the real specimen, then checks the same file with one character changed, and shows you both results side by side — a pass and a fail. If your browser cannot do the signature maths it says so plainly, rather than showing you a failure that is not one. The by-hand walk is below, for anyone who would rather run it themselves.
- The specimen: /specimen-receipt.json
- Its successor in the same chain: /specimen-receipt-2.json
- The published keys: api.bluefoxedge.ai/.well-known/jwks.json
The second file is how the chain links: it was minted next, and its chain_prev is byte-for-byte the first specimen's chain_self. Each receipt names the hash of the one before it — that equality is the chain, and you can check it yourself by comparing the two fields across the two files. The check below runs unchanged on either specimen.
The specimen keeps a copy of the source text beside its digests; a free-tier receipt keeps the digests only. The check below runs on either, unchanged — the retention section further down says how long each kind is kept.
What the file carries
- receipt_id
- Which receipt this is.
- produced_at
- When the engine produced it (ISO 8601, UTC).
- schema_version
- The envelope schema the two hashes below commit to.
- content_digest
- sha256 over the receipt's canonical bytes — RFC 8785 (JCS) — with the whole chain block removed. The receipt's own fingerprint, recomputable by anyone.
- chain_prev
- The chain_self of the receipt before this one in its chain, or the literal "GENESIS" at the head.
- chain_self
- "sha256:" plus sha256 over content_digest | chain_prev | schema_version, joined with literal pipes, hashed as UTF-8. This exact string is what gets signed.
- key_id
- sha256 of the raw 32-byte Ed25519 public key that signed it. It matches exactly one kid in the published key set — that match is how you pick the right key.
- signature
- The detached Ed25519 signature over the chain_self string, base64url with the padding stripped. On a keyless receipt key_id and signature are null — such a row is integrity-checkable but not tamper-evident. A receipt comes back keyless only when it was minted through the background pipeline while signing was disabled; an in-request mint never returns unsigned — it refuses instead. The specimen is signed.
The check by hand, in five steps
- Save the specimen next to a terminal — or skip this walk and press the button above.
- Recompute
content_digest: sha256 over the receipt's canonical bytes — RFC 8785 (JCS) — with the wholechainblock removed. - Recompute
chain_self: sha256 overcontent_digest|chain_prev|schema_version, hashed as UTF-8. The pipes are literal. - Fetch the published keys and find the entry whose
kidequals the receipt'skey_id— the kid is itself sha256 of the raw public key, a fact the walk checks rather than assumes. - Check the Ed25519 signature over the recomputed
chain_selfbytes — the one you derived, not the one the file carries. No dependencies — the script below is Python 3.8+ standard library only, and uses thecryptographypackage as a faster path to the same answer only if you already have it.
import base64, hashlib, json, re, sys, urllib.request
# DEPENDENCIES: none. Python 3.8+ standard library only. If the third-party
# cryptography package happens to be installed, its Ed25519 verifier is used;
# otherwise the self-contained RFC 8032 verifier further down checks the same
# bytes and reaches the same answer. Nothing here needs pip.
#
# python3 verify_receipt.py checks specimen-receipt.json
# python3 verify_receipt.py my-receipt.json checks the file you name
# python3 verify_receipt.py --jwks saved-keys.json my-receipt.json
# checks fully offline against keys you saved
# python3 verify_receipt.py --jwks saved-keys.json --bind-record my-record.json my-receipt.json
# also checks that the receipt names YOUR record
#
# Exit 0 means the check passed. Any non-zero exit means it did not, and the
# reason is one sentence -- never a stack trace.
#
# TWO KEY FAMILIES, AND THEY NEVER CROSS. Receipts are signed by the receipt
# key. The transparency log signs its checkpoints with a DIFFERENT key. Both are
# published in the same key set, and every entry declares which family it
# belongs to in a bfx:purpose member. DO NOT SELECT A KEY BY POSITION: keys[0]
# is the receipt key, so a checker that indexes the list instead of reading the
# label is one reordering away from checking the wrong thing. This file checks a
# RECEIPT, so it requires a purpose of "receipt" and refuses a receipt that
# names the log's key. Checkpoints are the other script's job, and it makes the
# mirror-image check: https://api.bluefoxedge.ai/verify_inclusion.py
#
# BINDING A RECEIPT TO A RECORD (--bind-record). The record is a JSON object
# you hold -- your own account of one tool call, as you presented it. Its
# RFC 8785 canonical form is hashed, and the receipt's source row must name
# exactly that digest. The retained presentation, when the tier kept one, must
# agree with the record. The receipt's own verdict word is printed from the
# file, never judged here: a pass says the receipt names your record, and the
# record stays the caller's own account of what happened.
# Pin the key set out of band -- never read a jwks_url out of the receipt.
# That field is unsigned: a forger can point it at keys they control, and a
# checker that follows it will bless the forgery. The explicit User-Agent is
# deliberate too: some edge firewalls refuse the default urllib signature.
#
# --jwks moves the pin FURTHER out of band, never less: name a key-set file
# you saved earlier and the whole check runs offline against those pinned
# bytes -- nothing is fetched at all -- or name an https address you trust
# and keys are fetched from there instead of the default below. Either way
# the choice is the operator's hand, never the receipt's: no field in the
# file being judged can pick its own judge.
JWKS_URL = "https://api.bluefoxedge.ai/.well-known/jwks.json"
PURPOSE_CLAIM = "bfx:purpose"
RECEIPT_PURPOSE = "receipt"
# Read-side, both IANA JOSE spellings, forever: "EdDSA" is what the key set
# publishes today and "Ed25519" is its fully-specified successor (RFC 9864).
ACCEPTED_ED25519_ALG_SPELLINGS = ("Ed25519", "EdDSA")
def not_implemented_here(sentence):
# A refusal about THIS CHECKER, never a result about the receipt: the
# input names an algorithm this build does not implement. One sentence,
# exit 3 (the JS twin's NotImplementedHere), so tooling can tell
# "checked and failed" from "cannot judge here".
sys.stderr.write(sentence + "
")
raise SystemExit(3)
def unb64url(s):
return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
def b64url(raw):
return base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=")
def sha256s(raw):
return "sha256:" + hashlib.sha256(raw).hexdigest()
# RFC 8785 (JCS) canonical JSON, standard library only.
#
# Reproduced from BRIDIE's construction -- a stranger who, sent at this estate
# on 2026-08-15 under a no-install rule, rebuilt this in 25 lines of stdlib and
# got output BYTE-IDENTICAL to the rfc8785 package on the published specimen
# (1,324 bytes from both). Her point stands and is the reason this file no
# longer asks you to install anything: the spec is complete enough to
# reimplement, so the checker should not need a package to prove it.
#
# Three rules do all the work. Object keys sort by UTF-16 code unit -- which is
# what encoding each key to UTF-16 big-endian and comparing those bytes gives
# you, surrogate pairs included. There is no insignificant whitespace. And a
# number that is integral is spelled without its fraction: JCS spells 0.0 as 0,
# and one character breaks a hash. That trap is live in the flagship artifact --
# the specimen's confidence_score is 0.0.
#
# The number domain this accepts is the one the signer itself enforces before
# hashing (finite floats, integers within the JCS safe-integer range). Outside
# that, and in the narrow band where a shortest-round-trip float would have to
# be spelled with an exponent, this REFUSES rather than guessing -- a wrong
# canonical form is a wrong hash, and a wrong hash silently accuses an honest
# receipt. Reject, never normalise.
def jcs(value):
if value is True:
return b"true"
if value is False:
return b"false"
if value is None:
return b"null"
if isinstance(value, str):
try:
return json.dumps(value, ensure_ascii=False,
separators=(",", ":")).encode("utf-8")
except UnicodeEncodeError:
raise SystemExit("this receipt carries text that is not encodable as "
"UTF-8 (a lone surrogate) -- it cannot be canonicalised")
if isinstance(value, int):
if abs(value) > 2 ** 53 - 1:
raise SystemExit("this receipt carries an integer outside the JCS "
"safe-integer range -- refusing rather than hashing a "
"value two implementations would spell differently")
return repr(value).encode("ascii")
if isinstance(value, float):
if value != value or value in (float("inf"), float("-inf")):
raise SystemExit("this receipt carries a non-finite number, which has "
"no JSON canonical form")
text = repr(value)
if "e" in text or "E" in text:
raise SystemExit("this receipt carries a number whose canonical "
"spelling needs an exponent, which this stdlib "
"canonicaliser does not attempt -- refusing rather "
"than risking a wrong hash")
if value.is_integer():
return repr(int(value)).encode("ascii")
return text.encode("ascii")
if isinstance(value, list):
return b"[" + b",".join(jcs(item) for item in value) + b"]"
if isinstance(value, dict):
pairs = []
for key in sorted(value, key=lambda k: k.encode("utf-16-be")):
if not isinstance(key, str):
raise SystemExit("this receipt has a non-text object key -- it is "
"not canonicalisable JSON")
pairs.append(jcs(key) + b":" + jcs(value[key]))
return b"{" + b",".join(pairs) + b"}"
raise SystemExit("this receipt carries a value of a kind JSON has no "
"canonical form for -- it is not a receipt this recipe checks")
def copies(node, key): # every value a receipt's own subtree carries under key
if isinstance(node, dict):
for k, v in node.items():
if k == key:
yield v
yield from copies(v, key)
elif isinstance(node, list):
for item in node:
yield from copies(item, key)
# The file to check: the one you name, or the published specimen if you name
# nothing. Every way this can go wrong below is a sentence and a non-zero exit,
# because handing a stranger a traceback for a missing file is not a verdict --
# it is the vendor's stack frames in place of an answer. Flag parsing is spelled
# out rather than imported: argparse prints its own usage paragraphs and exits 2,
# and this file's contract is one sentence and its own exit, every time.
jwks_source = None
record_path = None
names = []
args = list(sys.argv[1:])
while args:
arg = args.pop(0)
if arg == "--jwks" or arg.startswith("--jwks="):
if jwks_source is not None:
raise SystemExit("--jwks is named twice -- one verdict means one "
"pinned key set, so name exactly one")
if arg == "--jwks":
value = args.pop(0) if args else ""
else:
value = arg[len("--jwks="):]
if not value:
raise SystemExit("--jwks needs a value -- the path of a key-set "
"file you saved earlier, or an https address to "
"fetch the keys from")
jwks_source = value
elif arg == "--bind-record" or arg.startswith("--bind-record="):
if record_path is not None:
raise SystemExit("--bind-record is named twice -- one verdict means one "
"record, so name exactly one")
if arg == "--bind-record":
value = args.pop(0) if args else ""
else:
value = arg[len("--bind-record="):]
if not value:
raise SystemExit("--bind-record needs a value -- the path of the record "
"file this receipt is meant to name")
record_path = value
else:
names.append(arg)
if len(names) > 1:
raise SystemExit("usage: python3 verify_receipt.py [PATH-TO-RECEIPT.json] -- "
"one file at a time, so one verdict means one receipt")
path = names[0] if names else "specimen-receipt.json"
try:
with open(path, encoding="utf-8") as handle:
specimen = json.load(handle)
except FileNotFoundError:
raise SystemExit("there is no file at " + path + " -- name the receipt you "
"want checked, or save it as specimen-receipt.json and pass "
"nothing")
except IsADirectoryError:
raise SystemExit(path + " is a directory, not a receipt file")
except PermissionError:
raise SystemExit(path + " cannot be read (permission denied)")
except UnicodeDecodeError:
raise SystemExit(path + " is not UTF-8 text, so it is not a receipt this "
"recipe can read")
except ValueError as exc:
raise SystemExit(path + " is not valid JSON, so no receipt can be read "
"from it")
except OSError as exc:
raise SystemExit(path + " could not be read (" + str(exc) + ")")
if not isinstance(specimen, dict):
raise SystemExit(path + " holds a " + type(specimen).__name__ + ", not a JSON "
"object -- a receipt is an object")
# The record to bind (--bind-record): read through the same door as the receipt,
# right behind it, so a record that cannot be read is refused before anything
# in the receipt is looked at. It is checked against the envelope only after
# every signed field has held, further down.
record = None
if record_path is not None:
try:
with open(record_path, encoding="utf-8") as handle:
record = json.load(handle)
except FileNotFoundError:
raise SystemExit("there is no record file at " + record_path + " -- name the "
"record this receipt is meant to bind")
except IsADirectoryError:
raise SystemExit(record_path + " is a directory, not a record file")
except PermissionError:
raise SystemExit(record_path + " cannot be read (permission denied)")
except UnicodeDecodeError:
raise SystemExit(record_path + " is not UTF-8 text, so it is not a record this "
"recipe can read")
except OSError:
raise SystemExit(record_path + " could not be read, so no record can be bound")
except ValueError:
raise SystemExit(record_path + " is not valid JSON, so no record can be read "
"from it")
if not isinstance(record, dict):
raise SystemExit(record_path + " holds a " + type(record).__name__ + ", not a "
"JSON object -- a record is an object")
# The signed surface is the envelope: the one dict that carries the chain
# block. Its home differs by door -- data.receipt in an x402 mint response,
# data.envelope in a read-API response or the published specimen, one per
# entry of an export bundle -- so this finds it instead of indexing a path.
#
# WHICH envelope, when a file offers more than one, is a security question,
# not a convenience one. Every door here answers with a "data" mapping and
# puts the envelope inside it, so if the file HAS that mapping, ITS slot
# decides and nothing found elsewhere in the file can speak for it. That is
# what stops a doctored receipt at the slot you read from being excused by a
# genuine envelope tucked somewhere else in the same file. Only a file with no
# such mapping -- an export bundle, or something you assembled yourself --
# falls back to searching, and then you are reading whatever the search found:
# run this once per entry and read each answer. Either way a pass is a
# statement about ONE envelope, never about the file. And a file that HAS a
# data block but names no receipt in it is refused outright rather than
# searched, because that is not a shape any of these doors answers with.
#
# Both routes also return the dict CARRYING the envelope -- data, or the entry
# an envelope was found under -- because that is where the unsigned convenience
# copies live, and it is the fence that keeps the copy check inside ONE receipt,
# so a two-receipt bundle never compares entry A's hashes against entry B's.
# An envelope found directly inside a list has no carrier to name, and then the
# envelope fences itself: only its own copies are checked.
def signed(node):
chain = node.get("chain") if isinstance(node, dict) else None
return isinstance(chain, dict) and "signature" in chain
def slot_in_data(doc):
data = doc.get("data") if isinstance(doc, dict) else None
if not isinstance(data, dict):
return None # no data mapping: search, below
named = [s for s in ("receipt", "envelope") if s in data]
if not named:
# A "data" mapping is how these doors answer, so a file that has one
# and names no receipt inside it is not a receipt response -- and the
# rest of the file does not get to volunteer a substitute.
raise SystemExit("this file has a data block with no receipt in it -- "
"refusing to hunt elsewhere for something to check")
# From here the file HAS named a receipt, so that name binds and the
# search never runs. Naming one and then failing to be one is a refusal,
# not a licence to go looking for something else in the file to bless.
if len(named) > 1:
raise SystemExit("this file fills both data.receipt and data.envelope "
"-- name one receipt, or check them one at a time")
slot = named[0]
if not signed(data[slot]):
raise SystemExit("data." + slot + " is not a signed envelope -- "
"refusing to check something else in its place")
return data, data[slot] # data is the carrier, as below
def find_envelope(node, parent=None):
if isinstance(node, dict):
if signed(node):
return parent, node
for child in node.values():
hit = find_envelope(child, node)
if hit is not None:
return hit
elif isinstance(node, list):
for child in node:
hit = find_envelope(child, None)
if hit is not None:
return hit
return None
found = slot_in_data(specimen) or find_envelope(specimen)
if found is None:
raise SystemExit("no signed envelope found -- not a signed receipt (yet?)")
scope, envelope = found
if scope is None:
scope = envelope
chain = envelope["chain"]
# Everything the recipe is about to read, present AND of the right kind
# before it is read. A receipt missing one of these -- or carrying null or a
# number where text belongs -- is refused in a sentence, never with a
# traceback: handing a stranger a stack trace is the failure this whole
# recipe was rewritten to stop. (key_id must only be PRESENT: a keyless
# receipt carries it as null and reaches its own honest exit further down.)
def needs(field, home, text=True):
if field not in home:
raise SystemExit("this envelope has no " + field + " -- it is not a "
"receipt of the shape this recipe checks")
if text and not isinstance(home[field], str):
raise SystemExit("this envelope's " + field + " is not text -- it is "
"not a receipt of the shape this recipe checks")
needs("chain_prev", chain)
needs("key_id", chain, text=False)
needs("schema_version", envelope)
# 1. content_digest recomputes from the envelope's own bytes: sha256 over the
# RFC 8785 canonical form of the envelope with the whole chain block
# removed. JCS, not json.dumps -- JCS spells 0.0 as 0, and one character
# breaks a hash.
body = {k: v for k, v in envelope.items() if k != "chain"}
digest = sha256s(jcs(body))
# 2. chain_self recomputes: sha256 over digest|chain_prev|schema_version,
# joined with literal pipes, hashed as UTF-8. chain_prev is the previous
# receipt's chain_self, or the literal "GENESIS" at a chain head.
head = sha256s("|".join(
(digest, chain["chain_prev"], body["schema_version"])).encode("utf-8"))
# 3. Every copy of the two hashes in THIS receipt's subtree agrees with the
# recomputation -- the chain block's and the top-level mirrors' alike.
# Presence is checked before agreement: all() is True over an empty
# iterator, so a receipt carrying NO copy of a hash would otherwise sail
# through this step without it ever being compared to anything. A detected
# forgery is a verdict, so it exits in a sentence, never a stack trace.
for key, want in (("content_digest", digest), ("chain_self", head)):
carried = list(copies(scope, key))
if not carried:
raise SystemExit(key + " is absent -- there is nothing here to check")
if not all(got == want for got in carried):
raise SystemExit(
key + " does not recompute from the receipt's own bytes")
if not chain.get("signature"):
raise SystemExit("keyless receipt: steps 1-3 hold, but with key_id and "
"signature null there is no signature to check")
# 4. The signing key is published, and its name is a derived fact:
# kid = sha256 of the raw 32-byte Ed25519 public key. The key is selected by
# kid and then checked against its declared family -- selecting by kid alone
# would let a checkpoint key verify a receipt if the two ever crossed, and
# the whole reason there are two keys is that they must not.
#
# With --jwks the same selection runs against the keys YOU pinned. A saved
# file is read here with no network at all; an https address you named is
# fetched in place of the default. The kid check below is what makes a
# pinned file trustworthy to hold: the key's name is derived from the key's
# own bytes, so a swapped key cannot keep its old name.
key_set_word = "published" if jwks_source is None else "pinned"
if jwks_source is None or jwks_source.startswith("https://"):
key_set_url = JWKS_URL if jwks_source is None else jwks_source
req = urllib.request.Request(key_set_url, headers={"User-Agent": "bluefox-verify/1"})
try:
jwks = json.load(urllib.request.urlopen(req, timeout=30))
except Exception as exc:
raise SystemExit("the " + key_set_word + " key set at " + key_set_url +
" could not be "
"fetched (" + type(exc).__name__ + ") -- this checker will "
"not fall back to any address inside the receipt, so it "
"stops here")
elif jwks_source.startswith("http://"):
raise SystemExit("--jwks " + jwks_source + " is plaintext http -- keys "
"fetched without TLS can be swapped in flight, so save the "
"key set to a file and name that instead, or use https")
else:
try:
with open(jwks_source, encoding="utf-8") as handle:
jwks = json.load(handle)
except FileNotFoundError:
raise SystemExit("there is no key-set file at " + jwks_source + " -- "
"save the published keys first (curl -sSo "
"pinned-jwks.json " + JWKS_URL + ") and name that file")
except IsADirectoryError:
raise SystemExit(jwks_source + " is a directory, not a key-set file")
except PermissionError:
raise SystemExit(jwks_source + " cannot be read (permission denied)")
except UnicodeDecodeError:
raise SystemExit(jwks_source + " is not UTF-8 text, so it is not a key "
"set this recipe can read")
except ValueError:
raise SystemExit(jwks_source + " is not valid JSON, so no key can be "
"pinned from it")
except OSError:
raise SystemExit(jwks_source + " could not be read, so no key can be "
"pinned from it")
if not isinstance(jwks, dict) or not isinstance(jwks.get("keys"), list):
raise SystemExit("the " + key_set_word + " key set has no keys array, so no "
"key can be pinned")
key = next((k for k in jwks["keys"]
if isinstance(k, dict) and k.get("kid") == chain["key_id"]), None)
if key is None:
if jwks_source is None:
raise SystemExit("key_id is not in the published key set")
raise SystemExit("key_id is not in the pinned key set at " + jwks_source +
" -- if that copy predates this receipt, refresh it from " +
JWKS_URL + " and pin the new file")
purpose = key.get(PURPOSE_CLAIM)
# ABSENT is tolerated on purpose; only a POSITIVE wrong designation refuses.
# The published set gained bfx:purpose at the 2026-08-18 FIRE, so every key-set
# file pinned before that day lacks the member -- and the --jwks door exists
# precisely for files saved earlier. Same philosophy as the settlement lane's
# network guard: refuse a wrong answer, never punish an absent one. (H98 read
# the fail-closed suggestion and refuted it against the repo's own genuine
# 2026-08-08 pinned fixture; a stricter world needs a worded tolerance-sunset.)
if isinstance(purpose, str) and purpose != RECEIPT_PURPOSE:
raise SystemExit("this receipt names the " + key_set_word + " key " + chain["key_id"] +
", which is designated " + repr(purpose) + " and not a "
"receipt signer -- the two key families never cross, so "
"this is refused rather than verified")
# The algorithm is READ before it is assumed. An entry that POSITIVELY
# declares something other than Ed25519 (a foreign kty like AKP, a foreign
# crv, or an alg outside the two Ed25519 spellings) is a gap in THIS CHECKER,
# not a defect in the receipt -- absent members stay tolerated, exactly like
# the purpose label above.
entry_alg, entry_kty, entry_crv = key.get("alg"), key.get("kty"), key.get("crv")
foreign_alg = ""
if isinstance(entry_alg, str) and entry_alg not in ACCEPTED_ED25519_ALG_SPELLINGS:
foreign_alg = entry_alg
elif isinstance(entry_kty, str) and entry_kty != "OKP":
foreign_alg = "a " + entry_kty + "-family key"
elif isinstance(entry_crv, str) and entry_crv != "Ed25519":
foreign_alg = "curve " + entry_crv
if foreign_alg:
not_implemented_here("this receipt names the " + key_set_word + " key " + chain["key_id"] +
", whose key set entry declares " + foreign_alg + " -- an "
"algorithm this checker does not implement; refused rather "
"than assumed: nothing here says the receipt is bad, only "
"that this build cannot judge it, and a checker that "
"implements that algorithm can")
# The declared-algorithm cross-binding: an OPTIONAL chain.sig_alg, when
# present, must agree with the resolved key set entry. By here the entry is
# Ed25519-family, so a foreign sig_alg is a DISAGREEMENT in the file -- a
# refusal about the receipt, not a capability gap.
if "sig_alg" in chain and not isinstance(chain["sig_alg"], str):
raise SystemExit("this envelope's chain.sig_alg is not text -- it is not a "
"receipt of the shape this recipe checks")
if isinstance(chain.get("sig_alg"), str) and chain["sig_alg"] not in ACCEPTED_ED25519_ALG_SPELLINGS:
raise SystemExit("this receipt declares sig_alg " + repr(chain["sig_alg"]) + " but its "
"key set entry is an Ed25519 key -- the declared algorithm and the "
"published key disagree, so this is refused rather than verified")
raw_key = unb64url(key["x"])
if hashlib.sha256(raw_key).hexdigest() != chain["key_id"]:
raise SystemExit("kid is not sha256 of the raw public key")
# The length is a declaration too: raw bytes that are not 32 cannot be an
# Ed25519 key, and pushing them into the crypto layer would surface as the
# step-6 tampering sentence -- confidently wrong about a receipt this build
# simply cannot judge.
if len(raw_key) != 32:
not_implemented_here("the resolved key is " + str(len(raw_key)) + " raw bytes, not a "
"32-byte Ed25519 key -- an algorithm this checker does not "
"implement; refused rather than assumed: nothing here says "
"the receipt is bad, only that this build cannot judge it")
# 5. The carried signature string is the one canonical spelling of its bytes
# (base64url, padding stripped) -- flip even a slack bit and this fails.
needs("signature", chain)
try:
sig = unb64url(chain["signature"])
except Exception:
raise SystemExit("this envelope's signature is not base64url text -- it "
"is not a receipt of the shape this recipe checks")
if b64url(sig) != chain["signature"]:
raise SystemExit("signature encoding is not canonical")
# Ed25519 verification (RFC 8032), self-contained, so this file runs on a bare
# Python. You never need to read this block: it is the arithmetic, not the
# recipe. It is here only so that "no install required" is literally true, and
# it is skipped entirely when the cryptography package is present.
P = 2 ** 255 - 19
L = 2 ** 252 + 27742317777372353535851937790883648493
def inv(x):
return pow(x, P - 2, P)
D = (-121665 * inv(121666)) % P
SQRT_M1 = pow(2, (P - 1) // 4, P)
def x_recover(y):
xx = ((y * y - 1) * inv(D * y * y + 1)) % P
x = pow(xx, (P + 3) // 8, P)
if (x * x - xx) % P != 0:
x = (x * SQRT_M1) % P
if (x * x - xx) % P != 0:
return None
return P - x if x % 2 != 0 else x
def point_add(pt1, pt2):
x1, y1 = pt1
x2, y2 = pt2
prod = (D * x1 * x2 * y1 * y2) % P
return (((x1 * y2 + x2 * y1) * inv(1 + prod)) % P,
((y1 * y2 + x1 * x2) * inv(1 - prod)) % P)
def scalar_mult(pt, scalar):
acc = (0, 1)
while scalar > 0:
if scalar & 1:
acc = point_add(acc, pt)
pt = point_add(pt, pt)
scalar >>= 1
return acc
BASE_Y = (4 * inv(5)) % P
BASE = (x_recover(BASE_Y), BASE_Y)
def on_curve(pt):
x, y = pt
return (-x * x + y * y - 1 - D * x * x * y * y) % P == 0
def decode_point(blob):
value = int.from_bytes(blob, "little")
y = value & ((1 << 255) - 1)
sign = value >> 255
if y >= P:
return None
x = x_recover(y)
if x is None:
return None
if x & 1 != sign:
x = P - x
pt = (x, y)
return pt if on_curve(pt) else None
def ed25519_verify_pure(public_raw, signature, message):
if len(public_raw) != 32 or len(signature) != 64:
return False
point_a = decode_point(public_raw)
point_r = decode_point(signature[:32])
if point_a is None or point_r is None:
return False
scalar_s = int.from_bytes(signature[32:], "little")
if scalar_s >= L:
return False
challenge = int.from_bytes(
hashlib.sha512(signature[:32] + public_raw + message).digest(),
"little") % L
return scalar_mult(BASE, scalar_s) == point_add(
point_r, scalar_mult(point_a, challenge))
def ed25519_verify(public_raw, signature, message):
try:
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PublicKey)
except ImportError:
return ed25519_verify_pure(public_raw, signature, message)
try:
Ed25519PublicKey.from_public_bytes(public_raw).verify(signature, message)
return True
except Exception:
return False
# 6. The Ed25519 signature verifies over the RECOMPUTED chain head -- so the
# signature commits to every envelope byte hashed in step 1, not merely to
# a hash string the file happened to carry. A failure HERE is the verdict
# this whole recipe exists to deliver, so it too is a sentence.
if not ed25519_verify(raw_key, sig, head.encode("utf-8")):
raise SystemExit("the signature does not verify over the recomputed chain "
"head -- these are not the bytes the published key signed")
# 7. Unsigned convenience mirrors: the dict carrying the envelope repeats some
# signed fields for readers, and nothing above checked those copies -- they
# sit outside the signature. Mirrors spell the exact bytes of the envelope's
# values (produced_at included: same Z-form, same instant), so a copy that
# disagrees is a REFUSAL, not a warning. The signature held -- but the file
# in your hand is showing a reader different bytes than the ones it signed,
# and a disagreeing mirror is exactly how a doctored copy rides a check
# that reads only the envelope. A warning here asked you to notice what
# this script exists to notice for you, so it stops instead.
if scope is not envelope:
# chain_prev rides along (2E116, S116 -- ll116's L5): it is the one signed
# input to the chain head that steps 1-3 never recompute (a link, not a
# hash), so a top-level copy of it was the one mirror nothing above
# compared -- a one-byte edit of it passed while item 7 of the README said
# otherwise. Same rule, same sentence, one more key.
mirrored = list(body.items()) + [("chain_prev", chain["chain_prev"])]
for key, value in mirrored:
if key in scope and scope[key] != value:
raise SystemExit("unsigned top-level " + key + " does not match the "
"signed envelope's " + key + " -- the signature held, "
"but this file presents altered bytes where a reader "
"reads, and that is a tampered file, not a footnote")
# BINDING (--bind-record). The record is hashed with the same canonicaliser the
# envelope was, and the receipt's first source row must carry that digest --
# that is the bind: a digest the caller presented, named by a signed row. The
# rest reads the receipt's OWN words back: the source row's shape says this was
# a presented digest, the self-disclosure fields say what kind of receipt it
# calls itself, the retained presentation (when the tier kept one) must agree
# with the record, and the verdict word is copied out of the file. Nothing is
# graded here. It runs only when a record was named, after every signed field
# above has held, and each refusal is one sentence.
def scrub(text): # control characters become "?", every printable character stays
return "".join("?" if ord(ch) < 32 or ord(ch) == 127 else ch for ch in text)
def show(value): # a quoted value: text single-quoted (backslash and quote escaped,
if isinstance(value, str): # the one spelling both twins use), anything else in JCS form
backslash = chr(92)
return ("'" + value.replace(backslash, backslash + backslash)
.replace("'", backslash + "'") + "'")
return jcs(value).decode("utf-8")
# The profile's pinned six-key meta, derived from the record -- what a tier
# that keeps meta stored beside the digest. Every key is tested for presence
# before it is read, so a record of another shape is refused in a sentence.
def meta_for(record):
def lacks(key):
raise SystemExit("the record at " + record_path + " is not a tool-call record "
"this recipe can bind -- it lacks " + key)
def text(value): # a count spelled the way both twins spell it: 3 and 3.0 both read 3
return value if isinstance(value, str) else jcs(value).decode("utf-8")
if "profile" not in record:
lacks("profile")
if "session" not in record:
lacks("session")
session = record["session"]
if not isinstance(session, dict) or "ref" not in session:
lacks("session.ref")
if "kind" in record and record["kind"] == "session-close":
if "final_count" not in session:
lacks("session.final_count")
return {"profile": record["profile"], "kind": "session-close", "tool": "-",
"scheme": "none", "seq": text(session["final_count"]),
"session": session["ref"]}
tool = record["tool"] if "tool" in record else None
if not isinstance(tool, dict) or "name" not in tool:
lacks("tool.name")
if "seq" not in session:
lacks("session.seq")
ident = record["identity"] if "identity" in record else None
if isinstance(ident, dict) and "scheme" not in ident:
lacks("identity.scheme")
return {"profile": record["profile"], "kind": "tool-call", "tool": tool["name"],
"scheme": ident["scheme"] if isinstance(ident, dict) else "none",
"seq": text(session["seq"]), "session": session["ref"]}
if record_path is not None:
record_digest = hashlib.sha256(jcs(record)).hexdigest()
sources = envelope.get("sources")
if not (isinstance(sources, list) and sources and isinstance(sources[0], dict)):
raise SystemExit("this envelope has no source row -- there is nothing for a "
"record to bind to")
src0 = sources[0]
if "source_bytes_digest" not in src0 or not isinstance(src0["source_bytes_digest"], str):
raise SystemExit("the source row binds nothing readable -- source_bytes_digest "
"is not text")
if src0["source_bytes_digest"] != "sha256:" + record_digest:
raise SystemExit("this receipt does not name the record at " + record_path +
" -- the record digests to sha256:" + record_digest +
" and the receipt's source row binds " +
scrub(src0["source_bytes_digest"]))
for field, expected in (("hash_alg", "sha256"),
("extraction_method", "client_submission"),
("asserted_by", "as-stated-by-caller")):
value = src0[field] if field in src0 else None
if value != expected:
raise SystemExit("the bound source row's " + field + " is " + show(value) +
", not " + show(expected) + " -- this is not a "
"presented-digest receipt for that record")
if ("relevant_text" in src0 and src0["relevant_text"] is not None
and src0["relevant_text"] != ""):
raise SystemExit("the bound source row retains text -- a presented digest "
"carries none, so this is not a presented-digest receipt for "
"that record")
for field, expected in (("not_an_attestation", True),
("assertion_type", "reliance"),
("edge_role", "evidence-layer")):
value = envelope[field] if field in envelope else None
agrees = value is True if expected is True else value == expected
if not agrees:
raise SystemExit("this receipt's self-disclosure does not read as a "
"reliance receipt -- " + field + " is " + show(value))
details = envelope["details"] if "details" in envelope else None
if not isinstance(details, dict):
details = {}
presentation = details["presentation"] if "presentation" in details else None
presented = isinstance(presentation, dict)
if not presented:
presentation = {}
kept = presentation["value"] if "value" in presentation else None
if not isinstance(kept, dict):
kept = {}
retained = kept["meta"] if "meta" in kept else None
if not isinstance(retained, dict):
retained = {}
expected_meta = meta_for(record)
if presented:
kept_hash = kept["hash"] if "hash" in kept else None
if kept_hash != record_digest:
raise SystemExit("the retained presentation names a different digest than "
"the record -- " + show(kept_hash) + " vs " + record_digest)
by = presentation["asserted_by"] if "asserted_by" in presentation else None
if by != "as-stated-by-caller":
raise SystemExit("the retained presentation is not asserted by the caller "
"-- asserted_by is " + show(by))
if retained and jcs(retained) != jcs(expected_meta):
raise SystemExit("the retained meta does not agree with the record -- "
"retained " + jcs(retained).decode("utf-8") +
" vs expected " + jcs(expected_meta).decode("utf-8"))
produced_at = envelope["produced_at"] if "produced_at" in envelope else None
if not isinstance(produced_at, str) or not re.fullmatch(
"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$", produced_at):
raise SystemExit("this receipt's produced_at is not an RFC 3339 UTC instant -- " +
show(produced_at))
if presented and retained:
variant = " -- the retained meta agrees with the record"
elif presented:
variant = " -- the retained presentation carries no meta, so the digest alone binds"
else:
variant = (" -- the retained presentation is absent, so the digest alone binds "
"via the source row")
print("the record binds: " + scrub(record_path) + " digests to sha256:" +
record_digest + ", the receipt's source row names that digest, and the "
"receipt's own verdict word is " + show(envelope.get("verdict")) + variant)
print("the record is the caller's own account of the call: this run shows the "
"receipt names it, not that the call happened as written")
# WHAT THIS RUN DID NOT CHECK -- printed on every successful run, which is what
# this file's README has always promised and what, until now, only a source
# comment said. One logical line per member, in the order below, always ALL of
# them: the count is the length of this list and is written nowhere as a
# literal, so a slug added here is a slug printed. The source of record is
# RECEIPT_CHECK_UNSUPPORTED_PATHS / RECEIPT_CHECK_DISCLOSURES in app/models.py
# and a test pins this copy against it.
#
# IT PRINTS BEFORE THE VERDICT, deliberately: the verdict lands last where a
# reader looks for it, and a REFUSAL never reaches this point at all -- a file
# that failed to be checked must not emit the vocabulary of a file that was.
NOT_VERIFIED = [
("verdict_recompute",
"It does not re-run any verdict"),
("underlying_claim_truth",
"Proof of reliance, never proof of truth"),
("transparency_log_inclusion",
"It does not check transparency-log inclusion; that check is /verify_inclusion.py, or"
" /verify_receipt.js handed the inclusion proof (--inclusion)"),
("external_witness_or_anchor",
"no cosignature and no external anchor were checked; trust in the log key"
" is trust in BlueFox until a witness lifts it"),
("source_url_resolution",
"The source's URL is not part of the durable envelope"),
("raw_source_bytes",
"each receipt names its sources by content digests"),
("revocation_status",
"Dispute/revocation status is not covered"),
("key_set_provenance",
"the key set was {key_set_word}, taken from {key_set_source}; nothing"
" here proves those keys are BlueFox's current published set"),
("split_view_or_equivocation",
"one file cannot detect a split view; that needs two independently"
" obtained checkpoints and /verify_consistency.py"),
("foreign_signature_relay",
"a foreign signature BlueFox checked at mint time was checked against a"
" key outside its published key set, and this run does not re-check it"),
]
key_set_source_raw = JWKS_URL if jwks_source is None else jwks_source
# ONE LINE PER SLUG MEANS ONE LINE, and the key-set path is the only thing in
# this block a stranger chose. A filename may legally carry a newline or an
# escape, and interpolated raw it could ADD a scope line, or spoof the pass
# line, on a run that SUCCEEDED -- the one place this output is read as
# evidence. Control characters become "?"; every printable character, non-ASCII
# included, is shown exactly as given, because a path you cannot recognise is
# not a disclosure.
key_set_source = "".join(
"?" if ord(ch) < 32 or ord(ch) == 127 else ch for ch in key_set_source_raw)
for slug, sentence in NOT_VERIFIED:
# ORDER MATTERS: the word first, the SOURCE last. Replacement re-scans what
# it has written, and the source is the path you named on the command line
# -- so it goes in last, where nothing can expand anything it contains.
sentence = sentence.replace("{key_set_word}", key_set_word)
sentence = sentence.replace("{key_set_source}", key_set_source)
print("NOT VERIFIED by this run -- " + slug.upper() + ": " + sentence + ".")
# THE OBSERVATION/0 LINE (P117, S117/D015; the listening lane's receipts, U116 spec
# section 13 row 12). When the retained presentation says the presented digest
# names an OBSERVATION RECORD -- meta.profile "observation/<n>" and meta.kind
# "observation", the /0 path's own statement about what the digest names -- one
# more line prints on the pass path, before the register line: the record's
# profile and the receipt's own grade word, both READ from the file and never
# judged here, and where to read what such a receipt does and does not prove:
# the limits register's row for observation/0, rendered verbatim from the spec.
# A receipt without that meta prints nothing new; a refusal never reaches this
# point. Both twins print the identical sentence, byte for byte.
def observation_zero_meta(envelope):
details = envelope["details"] if "details" in envelope else None
presentation = (details["presentation"]
if isinstance(details, dict) and "presentation" in details else None)
value = (presentation["value"]
if isinstance(presentation, dict) and "value" in presentation else None)
meta = value["meta"] if isinstance(value, dict) and "meta" in value else None
if not isinstance(meta, dict) or "profile" not in meta or "kind" not in meta:
return None
profile = meta["profile"]
if (isinstance(profile, str) and profile.startswith("observation/")
and meta["kind"] == "observation"):
return meta
return None
zero_meta = observation_zero_meta(envelope)
if zero_meta is not None:
print("observation/0: the digest presented names an observation record of profile " + show(zero_meta["profile"]) + " and the receipt's own grade word is " + show(envelope.get("verdict")) + " -- its limits register row (What a BlueFox receipt does not prove, observation/0) carries its proves and does_not_prove clauses, lifted verbatim from the spec")
# THE LIMITS REGISTER, IN ONE LINE (ff116, SS116/D006). The scope block above says
# what this RUN did not check; this line says what a PASSING receipt does not
# PROVE, in the words a reader meets -- the not_an_attestation discipline made
# legible where the verdict is read. It prints on the pass path only, right
# before the verdict; a refusal never reaches it. Both published checkers print
# this exact sentence (the parity suite compares them byte for byte), and the
# TS module beside the taught bytes exports it as DOES_NOT_PROVE_LINE so the
# site's register page can quote the same words; a test pins the two equal.
print("does_not_prove: that any claim inside this receipt is true, that the act it describes happened as written, or anything about the content behind a digest -- this check shows only that these bytes were signed under the key set this run used and have not changed since")
print("the check passes: every signed field recomputed from the file's own bytes")Canonical bytes means RFC 8785 — the JSON Canonicalization Scheme — not json.dumps. JCS spells the number 0.0 as 0, and that one character is the difference between a hash that matches and one that never will. The key set's address is written into the code on purpose: a receipt's own jwks_url field sits outside the signature, so a forged file can point it at keys the forger controls. Pin the published address; never read it out of the file you are checking. Stated normatively, for anyone writing a checker of their own: you MUST NOT follow the jwks_url inside a receipt; pin the key set out of band at https://api.bluefoxedge.ai/.well-known/jwks.json.
The signature covers the envelope — the subtree that carries the chain block. Signed facts live in the envelope; the top-level fields beside it (verdict, produced_at, the convenience copies of the two hashes) are unsigned mirrors for quick reading — read anything you rely on from the envelope, and pin the published keys out of band, never from the receipt itself. Served responses spell both timestamps in the same UTC Z form, so a mirror that disagrees with its envelope twin is tampering; the walk above checks exactly what is signed.
A passing check proves the chain head is byte-for-byte what that key recorded — tamper-evidence, never authority. It hands you no verdict, and it asks nothing of us: the keys are published and the math runs anywhere. A failing check names the link that broke — a digest that will not recompute, a hash copy that disagrees, a signature that will not check. The receipt carries everything needed to recompute content_digest and chain_self from its own bytes, and the walk above recomputes both — nothing taken on faith.
How long a receipt is kept
Every receipt answers this itself: it carries a retention_tier and an expires_at — the moment it becomes eligible for purge, computed at mint as produced_at plus the tier's horizon. You can see both in the specimen above. The horizons:
- Tier A — 365 days
- The receipt is kept a year past mint.
- Tier B — 90 days
- Ninety days past mint. The public specimen is tier B — check its two timestamps against this table yourself.
- Tier C — 30 days
- Thirty days — the receipt keeps digests of the source material; the extracted source text itself is not retained at this tier.
A receipt under legal_hold is excluded from the automated purge for as long as the flag stands, whatever its expires_at says. And expiry is about our storage, not your evidence: a receipt you hold keeps verifying against the published keys after we have purged our copy — the check needs the receipt's own bytes and the keys, not us.
We can evidence what was recorded while it was happening. We cannot evidence a month you were not recording.
The full recipe on one page — every rule, the same code, and a place to run the check in your browser: /learn/how-to-verify.
← BlueFox