#!/usr/bin/env python3
"""verify_inclusion.py — prove one BlueFox receipt is a leaf in the public log.

Download it, run it, edit nothing:

    curl -sS -O https://api.bluefoxedge.ai/verify_inclusion.py
    curl -sS https://api.bluefoxedge.ai/verify_inclusion.py.sha256 | shasum -a 256 -c -
    curl -sS "https://api.bluefoxedge.ai/v1/transparency/inclusion\
?log_id=reliance/v1&chain_self=<your receipt's chain.self>" \\
      | python3 verify_inclusion.py

No receipt of your own yet? This one is real, published, and in the log — the
whole thing runs with no account and nothing installed:

    curl -sS "https://api.bluefoxedge.ai/v1/transparency/inclusion\
?log_id=reliance/v1&chain_self=sha256:05d2133e06c5e642cdd5d558853e3d7d7a543a9a3\
6b141e2e403076173e029da" | python3 verify_inclusion.py

The receipt it belongs to is at https://www.bluefoxedge.ai/specimen-inlog.json,
and the bytes it notarized are at .../specimen-inlog-preimage.txt, so you can
recompute the digest inside it instead of believing this sentence. Note that the
OTHER published receipt — specimen-receipt.json — predates this log and is NOT
in it: it is the signature-and-anatomy exhibit, and asking this endpoint about it
correctly returns a 404.

THE WITNESS HALF (recipe item 11). With the witness's verifier key saved from
the WITNESS'S OWN origin — never from BlueFox — the same run also verifies the
witness cosignature on the checkpoint, and says exactly what that proves:

    curl -sS "https://api.bluefoxedge.ai/v1/transparency/inclusion\
?log_id=reliance/v1&chain_self=<your receipt's chain.self>" \\
      | python3 verify_inclusion.py --witness-keys witness-keys.txt

    # one vkey per line in that file ('#' comments allowed), or inline, repeatable:
    python3 verify_inclusion.py --file inclusion.json \\
      --witness-key '<name>+<8 hex key id>+<base64>' --require-witness

    # the same checkpoint in C2SP tlog-proof form, beside the receipt it covers;
    # --json prints one machine-readable document instead of the report:
    python3 verify_inclusion.py --tlog-proof proof.txt --receipt receipt.json \\
      --witness-keys witness-keys.txt --json
    python3 verify_inclusion.py --tlog-proof proof.txt --entry sha256:<hex>

Exit 0 means the proof checked out on the production log. Exit 4 means every
check passed on a TEST-lane proof — a test-face log, which is not evidence: the
verdict reads VALID-TEST and the exit is never 0, so a script that tests for 0
fails closed. Exit 1 is a refusal and exit 3 means this build cannot judge the
input; on either, the reason is one sentence on stderr — never a stack trace.
A test receipt under a test-log key the test face has since RETIRED verifies
only with ``--test-key-registry FILE`` (the face's saved
/.well-known/test-key-registry.json); a production proof never reads it.

DEPENDENCIES: none. Python 3.8+ standard library only. If the third-party
``cryptography`` package happens to be installed the script uses its Ed25519
verifier; otherwise it falls back to a self-contained RFC 8032 verifier bundled
below. Both paths check the same bytes; ``--pure-ed25519`` forces the bundled one.

═══════════════════════════════════════════════════════════════════════════════
THE RECIPE — what this script checks, and why each step is here
═══════════════════════════════════════════════════════════════════════════════

1. TWO KEY FAMILIES, AND THEY NEVER CROSS — ENFORCED HERE, NOT MERELY STATED.
   Receipts are signed by the receipt key. Checkpoints are signed by the
   inclusion-log key — a DIFFERENT key. Both public keys are published in the
   same key set at https://api.bluefoxedge.ai/.well-known/jwks.json, and each
   entry declares which family it belongs to in a ``bfx:purpose`` member:
   "receipt" or "log". THIS SCRIPT WILL ONLY VERIFY A CHECKPOINT AGAINST A
   "log"-PURPOSE KEY. If a note's key hint resolves to a receipt key, this
   script refuses — it does not verify the signature and call it a log proof.
   That matters because the separation exists to bound blast radius: without
   this check, a compromise of the hot, high-volume receipt key would also buy
   the ability to mint checkpoints that any verifier would accept.

   DO NOT SELECT THE KEY BY POSITION. ``keys[0]`` is the RECEIPT key. A checker
   that reasonably takes the first entry gets the wrong key for this job. Read
   ``bfx:purpose``; never the index, and never the order.

   If a checkpoint fails against the receipt key, that failure is the design
   working, not the log lying. An older key set that carries no ``bfx:purpose``
   member at all is still accepted — the family separation cannot then be
   enforced, so this script says exactly that in the "NOT VERIFIED" paragraph
   rather than implying a check it did not make.

2. PIN THE KEY OUT OF BAND. A verifier MUST NOT follow a key-set address
   carried inside the artifact it is checking — including the ``jwks_url``
   field a receipt carries. Pin the key set out of band at
   https://api.bluefoxedge.ai/.well-known/jwks.json instead. That is what this
   script does: it fetches from the address in its own source (the constant
   JWKS_URL below), an address you can read before you run it, and it never
   reads a key address out of the proof. A pointer that rides inside a
   signed-looking object can aim at anyone's keys, and a checker that follows
   it proves only that the artifact agrees with itself. ``--jwks-file`` and
   ``--jwks-url`` let you supply the key set yourself, from your own record —
   still out of band, still never from the artifact.

3. THE NOTE, BYTE-EXACT. The ``checkpoint`` field is a C2SP signed note,
   verbatim, trailing newline included. Its body is AT LEAST three lines, each
   newline-terminated: the origin, the tree size in decimal, and base64 of the
   32 root bytes. Any further lines are C2SP extension lines — opaque, optional,
   and each one non-empty. Then one blank line. Then the signature line: "— " +
   key name + " " + base64(4-byte key hash || 64-byte Ed25519 signature). The key
   name is the origin string. The key hash is the first 4 bytes of
   sha256(key_name || 0x0A || 0x01 || raw 32-byte public key) — algorithm byte
   0x01. THE SIGNED MESSAGE IS THE WHOLE BODY — every line of it, extension lines
   included, with its trailing newline — but NOT the blank line that separates
   body from signatures. This is where an independent implementation loses an
   afternoon; it is stated here so yours does not. Do NOT hard-code three lines:
   a note with a fourth line is not malformed, and a checker that rejects it
   will start failing on genuine notes the day one is added.

4. PARSE THE NOTE AND COMPARE. The response repeats ``checkpoint_size`` and
   ``checkpoint_root`` beside the note. THEY ARE UNSIGNED — never trusted, only
   cross-checked — and THEY ARE REQUIRED. This script refuses a response that
   omits either one, and refuses one carrying JSON ``null`` in place of either:
   a null is not an absent field, and a cross-check that silently did not run
   reads exactly like one that passed. So it verifies the note's signature
   first, reads the size and root FROM THE NOTE, and requires both fields to be
   present, non-null, and equal to what the note says. A checker that trusts an
   unsigned field it never recomputes is the forgery surface — on this wire,
   the signed bytes are the note, and everything else must match the note or
   the check fails.

5. THE LEAF PRE-IMAGE, UNMISSABLY. leaf = sha256(0x00 || utf8(entry)), where
   ``entry`` is your receipt's FULL ``chain_self`` string — all 71 characters,
   "sha256:" prefix included, lower-case hex. The two mistakes every
   independent implementation is drawn to, named: (a) hashing the entry WITHOUT
   the leading 0x00 domain byte; (b) hashing the 32 DECODED digest bytes
   instead of the prefixed string. The response's own ``leaf`` field is unsigned
   and is held to item 4's rule exactly: REQUIRED, present and non-null, and
   equal to the leaf recomputed here — absent refuses, and so does a null.

   A worked example, whole, so you can reproduce it right here with nothing to
   fetch. For the entry
     sha256:317424f98aca27ba5a4a17cc404e8b4ef0b88a4f91f8bd78e46d23a221d073df
   the correct leaf is
     sha256:909d491086969ff57f6a8779e3c1707d5d52104cf4518230d1296c761cea7c79
   and those two mistakes produce, exactly:
     (a) sha256:c91c9993de98606838ebf33eab993c8eeb2a89839ba215dbb9a45f1c9dc24c60
     (b) sha256:e43acc2dd7708997571d540fcddb523884c1c161e14aa3cd5efbefa809d9ddbf
   If your own implementation prints one of those, you now know which turn you
   missed. (Earlier revisions of this file called that "the published test
   vector" while publishing it at no address — a stranger went looking and found
   four 404s. The entry is stated above instead, which is all the example ever
   needed.)

6. THE PATH WALK. Start at your leaf and apply the path in order
   (leaf-to-root). A step whose ``side`` is "right" means the sibling sits on
   the right: node = sha256(0x01 || node || sibling). A "left" step mirrors it:
   node = sha256(0x01 || sibling || node). After the walk, this script checks
   the SIDE SEQUENCE ITSELF: for a fixed tree size, leaf index <-> side
   sequence is a bijection, so a path whose sides are permuted is a proof of a
   DIFFERENT index than it claims. The expected sequence is recomputed here
   from (checkpoint_size, leaf_index) and must match exactly.

7. ONE ROOT, TWO SPELLINGS. JSON surfaces spell the root "sha256:<64hex>"; the
   note spells base64 of the same 32 bytes. Convert before comparing. Hex is
   lower-case only — this script REJECTS anything else, never
   normalize-and-accept.

8. A PROOF IS A STATEMENT ABOUT A MOMENT. Your proof binds your leaf to the
   checkpoint of size N it was served against. The tree grows on the published
   cadence, so N will be behind the head by the time you re-check — THAT DOES
   NOT STALE YOUR PROOF. The log is append-only: a later checkpoint of size M
   extends the tree your proof lives in, and
   /v1/transparency/consistency?from=N&to=M is the proof of exactly that. Keep
   the checkpoint you verified; carry it forward with consistency proofs;
   re-fetch an inclusion proof only if you lost the receipt's numbers. THERE IS
   A PUBLISHED CHECKER FOR THAT SECOND STEP TOO, so you do not have to
   reimplement RFC 6962's consistency algorithm to use it:
     curl -sS -O https://api.bluefoxedge.ai/verify_consistency.py
   It verifies BOTH signed checkpoint notes and recomputes the walk between
   them, and it takes --from-note so the older end can be the copy YOU saved
   rather than the one we hand you today. WHICH SIZES EXIST to run that proof
   between is one GET, never a guess:
     /v1/transparency/checkpoints/history?log_id=<your log_id>
   lists every size this log ever published, newest first. That list is an
   unsigned convenience index and says so in its own bytes — take the numbers
   you prove with from a signed note, never from a list. That habit — save the
   note every time you verify anything — is what makes the older end evidence
   instead of our word for it.

9. WHAT SUCCESS MEANS — AND WHAT IT DOES NOT. A passing run proves exactly
   three things: your leaf recomputes from the named pre-image; the path walks
   from your leaf to the root inside the signed note; the note's signature
   verifies against the pinned log key. That places your receipt in OUR log as
   of that tree size. It does NOT check the receipt's own signature (that is
   the receipt check — a separate claim, graded separately), and it verifies
   no outside witness UNLESS YOU SAVED THAT WITNESS'S KEY (item 11): the log
   began publishing a witness cosignature beside its own signature on
   2026-09-19, and when the note in hand carries such lines this script
   COUNTS them and names them as NOT checked — without a saved witness key it
   never verifies them — and it verifies one ONLY under a witness key you
   saved yourself, out of band, naming every other line as NOT checked — so
   without a saved witness key the trust a passing run establishes is trust
   in the log key alone; when the note carries none, it says so. This
   script says both of these out loud, every run — two claims, two lines,
   never merged into one green line. It also
   names the BINDING the response claimed and grades the anchor claim on its
   own line, per binding: "anchored" means this log claims a qualifying
   EXTERNAL time anchor (an OpenTimestamps-confirmed Bitcoin commitment, or a
   held qualified timestamp) covers your entry — AT THE SIZE THE RESPONSE'S
   ``anchor`` BLOCK NAMES, which is usually EARLIER than the checkpoint you
   were served against, because the anchor lane lags the log's tip. This
   script prints THAT size and never the tip: asking for the tip's anchor
   answers "pending" for an entry that is genuinely anchored, which is how a
   working Bitcoin lane got written down as vapour. The block is unsigned, so
   the size is printed only after it is bounded by the signed note — it must
   name a tree containing the leaf index this run proved, and not exceed the
   signed tree — and where it does not survive that bound, or is absent, this
   script names NO size rather than guess one. It is a real, followable claim
   this script deliberately does NOT check, and there is a published checker
   that does: verify_anchor.py, which carries no key of ours at all, so it
   ends on Bitcoin where this script ends on our signature; "included" means
   committed under a signed checkpoint, claiming NO external anchor. A checker that accepts
   two values for a field and prints neither teaches a careful reader that
   the word is decorative — that happened, once, and this line is the cure.

10. WHEN, AND WHY IT IS ITS OWN QUESTION. A Merkle log proves ORDER, not TIME:
   the arithmetic in steps 5-8 would be exactly as valid if every entry had been
   added a year earlier or later. If the note carries a ``bfx:issued-at``
   extension line, then the instant the LOG ISSUED THAT NOTE is inside the
   signature and this script prints it. Read that scope narrowly, because we do:
   it is when the log said so. It is NOT when your entry was admitted, and it is
   NOT when whatever your receipt describes actually happened — per-entry timing
   lives in each receipt's own signed ``produced_at``, a different signature
   answering a different question. If the note carries no such line, this script
   says so on its own line and no date anywhere near this proof is signed: any
   you have came from outside the signature and is our word, not proof. Neither
   case ever rides inside the green line.

11. THE WITNESS — VERIFIED ONLY UNDER A KEY YOU SAVED, NEVER UNDER OURS. Since
   2026-09-19 the checkpoint note may carry, after the log's own line, one or
   more C2SP cosignature lines (c2sp.org/tlog-cosignature, cosignature/v1):
   "— " + witness name + " " + base64(4-byte key id || 8-byte big-endian
   timestamp || 64-byte Ed25519 signature) — 76 bytes decoded. THE SIGNED
   MESSAGE IS "cosignature/v1\ntime <timestamp>\n" FOLLOWED BY THE WHOLE NOTE
   BODY — item 3's body, trailing newline included, blank line and signature
   lines excluded. The body ends at the LAST blank line in the note
   (signed-note's rule, and the house parser's): an extra blank line between
   the body and the signatures is an empty line INSIDE the body, which
   tlog-checkpoint forbids, so the note is refused rather than read two ways;
   and a signature line is exactly em dash, space, key name, space, base64 —
   a trailing space is a malformed line and refuses the note. The key id is
   sha256(name || 0x0A || 0x04 || raw 32-byte key)[:4] — algorithm byte 0x04,
   NOT the 0x01 of the log's own line; an implementation that reuses item 3's
   derivation here never matches a line.
   A witness's key is ITS OWN: published at the witness's origin in C2SP vkey
   form, "<name>+<8 hex key id>+base64(0x04 || key)", and this script takes it
   ONLY from you — --witness-key VKEY (repeatable) or --witness-keys FILE (one
   per line, '#' comments and blank lines ignored, a leading UTF-8 byte-order
   mark stripped and CRLF tolerated) — never from the key set, never from the
   note, never from BlueFox. The house's key set carries no witness key and
   never will. At most 64 saved vkeys in all, counted across both flags after
   comments and blank lines: a note carries at most 15 cosignature lines, so a
   larger set is refused at the door (exit 1, reason usage) BEFORE any key is
   self-checked. Every vkey under that bound is self-checked at the door: its
   key id must derive from its own name and key, and a small-order point is
   refused. The witness check runs ONLY AFTER the log's own line verified
   under a "log"-purpose key, and signed-note's rules apply to every line that
   does not carry the log's origin: a line under a key you did not save is
   counted and IGNORED (state witness_key_unknown — it may be perfectly good,
   this script simply cannot say); a line whose name AND key id match a saved
   key MUST verify, and one that does not refuses the WHOLE note (state
   cosignature_invalid, exit 1: either that key signed different bytes or the
   line was forged under its name); an ML-DSA-44 line (type 0x06, 2432 bytes)
   is counted as unsupported and never verified here (a note whose ONLY
   cosignature lines are ML-DSA-44 is witness_key_unknown, and its sentence
   says "no cosignature line under a key you saved" rather than counting zero
   unknown keys beside an empty list); a note carrying no
   cosignature line at all is unwitnessed (an older bundle, or a checkpoint no
   witness cosigned) — "cosignatures is empty, so trust in the log key is
   trust in BlueFox". --require-witness turns unwitnessed and
   witness_key_unknown into a refusal (exit 1, reason witness_required) for a
   holder who demands the witness half; without it the inclusion verdict and
   exit code are exactly what they were, and the witness is an additional
   claim graded on its own line. The 'cosignatures' list beside the note is
   UNSIGNED: it is cross-checked against the note's own lines (a listed line
   the note does not carry refuses) and never judged on its own. ONE WITNESS
   TODAY — a passing witness half says "witnessed by one witness", and what
   that means is exactly this: that witness saw this exact checkpoint and
   found it consistent with the log's history. It says nothing about any
   receipt's contents, and nothing about independence as a cryptographic
   fact. --tlog-proof FILE takes the same checkpoint in C2SP tlog-proof form
   (c2sp.org/tlog-proof@v1: the header line, an optional 'extra' line, 'index
   N' with no leading zeroes, the path hashes in canonical base64, a blank
   line, then the note verbatim with its cosignature lines) with the leaf
   pre-image from --receipt FILE (the receipt's chain.chain_self, found where
   the published receipt checker finds its envelope) or --entry sha256:<hex>;
   the path's sides are not carried in that form and are recomputed from
   (tree size, index) exactly as item 6 does. --json prints one
   machine-readable document (format bluefox-verify-inclusion/json/1) on
   stdout in place of the report, on a refusal too, with the same exit code;
   its witness object is {state, valid, invalid, unknown, unsupported, lines,
   names, cosigned_at, sentence}: the five counts are per LINE and names is
   per line in note order, while cosigned_at (keyed by witness name) and the
   sentence count DISTINCT witnesses — cosignature lines sit outside the log's
   signature, so a valid line pasted twice is two lines and ONE witness. The
   sentence is the one sentence that state owes, the same words from every
   published carrier of this check.
"""
from __future__ import annotations

import argparse
import base64
import binascii
import datetime
import hashlib
import json
import re
import sys
import time
import urllib.error
import urllib.request

# ── The pin (recipe item 2). An address in this file's own bytes, readable
#    before you run it — never a pointer taken from the artifact being checked.
JWKS_URL = "https://api.bluefoxedge.ai/.well-known/jwks.json"

DIGEST_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
SIG_LINE_PREFIX = "— "  # em dash + space, per the C2SP signed-note format
NOTE_ED25519_ALG = b"\x01"
MAX_PATH_LENGTH = 64  # the frozen inclusion-path bound; an absurd tree fails fast

# ── Signature-line length classes, per the C2SP signed-note and
#    tlog-cosignature formats: each line's blob is a 4-byte key hint plus the
#    algorithm's signature (plus, for cosignatures, an 8-byte timestamp).
#    68 = the log's own Ed25519 note line (4+64, type 0x01); 76 = an Ed25519
#    witness cosignature (4+8+64, type 0x04); 2432 = an ML-DSA-44 witness
#    cosignature (4+8+2420, type 0x06). A line of any other length is counted
#    and named, never verified, and never a reason to reject the note —
#    signed-note.md: "Verifiers MUST ignore signatures from unknown keys."
SIG_LINE_CLASSES = {
    68: "ed25519_note_signature",
    76: "ed25519_cosignature",
    2432: "ml_dsa_44_cosignature",
}
UNRECOGNISED_SIG_LINE = "unrecognised_signature_line"
# C2SP's own floor is the cap: verifiers MUST accept at least up to 16
# signature lines, and a higher cap buys nothing — an unbounded block is
# refused rather than scanned.
MAX_SIGNATURE_LINES = 16
# 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")

# ── Recipe item 1's lock. The published key set labels each entry's family; a
#    checkpoint is only ever checked against a "log"-purpose key. Order is not
#    the label: keys[0] is the RECEIPT key.
JWK_PURPOSE_CLAIM = "bfx:purpose"
PURPOSE_LOG = "log"
# ── THE TEST LANE (5P125, S125/D115). A TEST face signs its checkpoints under a
#    key designated "test-log" and marks every note with a SIGNED extension
#    line 'bfx:epoch <N>' (the test log restarts by epoch; the reset mechanics,
#    the grace window and the schedule are a later word -- not built here). A
#    test receipt carries a signed 'bfx:test' member naming its log's origin and
#    epoch. The lane is detected from either signed thing; a "log" key is
#    ineligible on the test lane and a "test-log" key on the production lane,
#    by name. A passing test-lane proof says VALID-TEST and exits 4, never 0.
PURPOSE_TEST_LOG = "test-log"
TEST_MARKER = "bfx:test"
INSTANT_RE = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$")
# The test face's retired-test-key registry (--test-key-registry): a key the
# face retired is kept there, not in its key set, so an earlier epoch's
# checkpoints verify only through it. Consulted on the TEST lane only.
TEST_KEY_REGISTRY_FORMAT = "bluefox-test-key-registry/1"
TEST_KEY_REGISTRY_PATH = "/.well-known/test-key-registry.json"

EXIT_OK = 0
EXIT_REFUSED = 1
# A refusal about THIS CHECKER, never a result about the artifact: the input
# names an algorithm this build does not implement. Distinct from EXIT_REFUSED
# so a holder's tooling can tell "checked and failed" from "cannot judge here"
# (argparse owns exit 2).
EXIT_NOT_IMPLEMENTED = 3
# The third verdict: every check passed on a TEST-lane proof, which is not
# evidence. Distinct from EXIT_OK so a "$? == 0" script fails closed.
EXIT_VALID_TEST = 4

# ── Item 11: the witness. tlog-cosignature's wire constants: the key id for an
#    Ed25519 cosignature is sha256(name || 0x0A || 0x04 || key)[:4] (algorithm
#    byte 0x04 — the log's own line keeps 0x01), the signed message starts with
#    the fixed header line, and the u64 timestamp must not exceed 2^63 - 1
#    (C2SP's own MUST, kept lowercase here: the house's key-pointer norm
#    registry counts RFC-2119 prohibitions in this file and pins exactly one).
COSIG_ED25519_ALG = b"\x04"
COSIG_MLDSA44_ALG = b"\x06"
COSIG_V1_HEADER = b"cosignature/v1\n"
MAX_COSIG_TIMESTAMP = 2 ** 63 - 1
# The door's bound on saved witness keys, counted across --witness-key and
# --witness-keys files after comments and blank lines, BEFORE any key is
# self-checked: a note carries at most MAX_SIGNATURE_LINES - 1 cosignature
# lines, so a ring past 64 vkeys buys no verification and costs a point
# decode and an [8]P check per key -- a CPU bound, refused with reason usage.
# 64 is accepted, 65 is refused.
MAX_WITNESS_KEYS = 64
# The witness states (design §3), one of which every passing run names.
WITNESS_WITNESSED = "witnessed"
WITNESS_UNWITNESSED = "unwitnessed"
WITNESS_KEY_UNKNOWN = "witness_key_unknown"
WITNESS_COSIGNATURE_INVALID = "cosignature_invalid"
# The ONE sentence each state owes (design §3): these templates are the
# manifest's "sentences" in tests/fixtures/witness_vectors, byte for byte, and
# every carrier of this check prints the same rendering. witnessed counts
# DISTINCT (name, key id) pairs -- a line repeated under one key is one
# witness; the ignored rider rides a witnessed note only; the ML-DSA rider
# rides any state's sentence, last.
SENTENCE_WITNESSED_ONE = ("witnessed by one witness: {name} cosigned this "
                          "checkpoint at {ts} under the key you saved")
SENTENCE_WITNESSED_MANY = "witnessed by {k} witnesses: {pairs} under the keys you saved"
SENTENCE_WITNESSED_PAIR = "{name} cosigned this checkpoint at {ts}"
SENTENCE_UNWITNESSED = ("no witness cosigned this checkpoint; trust in the log "
                        "key is trust in BlueFox")
SENTENCE_KEY_UNKNOWN = (
    "{n} cosignature line(s) under keys you did not save ({names}) were counted "
    "and NOT verified; save the witness's vkey (published at the witness's own "
    "origin, never by BlueFox) and pass --witness-keys")
# The same state with NO unknown-key line at all (every cosignature line is
# ML-DSA-44, which rides the rider): no count of zero, no empty list.
SENTENCE_KEY_UNKNOWN_NONE = (
    "no cosignature line under a key you saved; save the witness's vkey "
    "(published at the witness's own origin, never by BlueFox) and pass "
    "--witness-keys")
SENTENCE_COSIGNATURE_INVALID = (
    "the cosignature from {name} under the key you saved does not verify over "
    "this checkpoint: either that key signed different bytes or the line was "
    "forged under its name; the whole note is refused")
RIDER_IGNORED = ("; {u} line(s) under keys you did not save ({names}) were "
                 "ignored per signed-note")
RIDER_UNSUPPORTED = ("; {m} ML-DSA-44 (0x06) line(s) ({names}) were counted and "
                     "never verified")
# C2SP tlog-proof (c2sp.org/tlog-proof@v1): the header, and the index line.
TLOG_PROOF_HEADER = "c2sp.org/tlog-proof@v1"
TLOG_INDEX_RE = re.compile(r"^index (0|[1-9][0-9]*)$")
# --json: one document, this format, and these reason codes on a refusal.
JSON_FORMAT = "bluefox-verify-inclusion/json/1"
REASON_INCLUSION = "inclusion"
REASON_WITNESS = "witness"
REASON_WITNESS_REQUIRED = "witness_required"
REASON_USAGE = "usage"
REASON_FILE = "file"
REASON_NOT_IMPLEMENTED = "not_implemented_here"
# The test lane's three codes: a 'bfx:test' member that is not the taught shape;
# a receipt and a note from different lanes (or different test logs); a receipt
# and a note from different EPOCHS of the same test log (the teaching code).
REASON_TEST_LANE = "test_lane"
REASON_TEST_LANE_CROSSED = "test_lane_crossed"
REASON_TEST_LANE_EPOCH = "test_lane_epoch"
DOES_NOT_PROVE = (
    "a passing run proves the leaf, the path and the log's signature over the "
    "note, and -- only under a witness key you saved -- that one witness saw "
    "this exact checkpoint and found it consistent with its history; it does "
    "not verify the receipt's own signature (the receipt check, a different "
    "key and a different question), it says nothing about any receipt's "
    "contents, nothing about the witness's independence as a cryptographic "
    "fact, nothing about any external time anchor, and nothing about when the "
    "entry was admitted or when what the receipt describes happened"
)
INCLUSION_GREEN = (
    "VERIFIED -- log inclusion: the leaf was recomputed from the named "
    "pre-image, the path walked from that leaf to the root inside the "
    "signed checkpoint note, and the note's signature verified against the "
    "pinned log key. Your entry is in this log as of tree size %d."
)
# On the test lane the same green rides between these two: the checks are the
# same checks; the verdict word says what they were run on.
INCLUSION_GREEN_TEST_PREFIX = "VALID-TEST -- "
INCLUSION_GREEN_TEST_SUFFIX = " (test log, epoch %d)"
# Set once by main(): when --json is on, every exit -- a pass, a refusal, the
# honest exit 3 -- prints one JSON document on stdout beside its stderr line.
_JSON = {"on": False}


# ══════════════════════════════════════════════════════════════════════════════
# The G-13 wall: every negative outcome leaves through here — one sentence, a
# non-zero exit, and no traceback. There is no bare ``assert`` anywhere on a
# verification branch in this file, by design: a stranger who catches a forgery
# must be handed a verdict, never a stack trace.
# ══════════════════════════════════════════════════════════════════════════════
def emit_json(passed: bool, exit_code: int, reason: "str | None", sentence: str,
              inclusion: "dict | None", witness: "dict | None") -> None:
    """The one --json document, on stdout, in every outcome. ``inclusion`` is
    present only when the inclusion half proved AND the note was not refused
    whole; ``witness`` is present whenever the witness half ran."""
    document = {
        "format": JSON_FORMAT,
        "passed": passed,
        "exit_code": exit_code,
        "reason_code": reason,
        "sentence": sentence,
        "inclusion": inclusion,
        "witness": witness,
        "does_not_prove": DOES_NOT_PROVE,
    }
    sys.stdout.write(json.dumps(document, indent=2) + "\n")
    sys.stdout.flush()


def refuse(sentence: str, reason: str = REASON_INCLUSION,
           witness: "dict | None" = None,
           inclusion: "dict | None" = None) -> "None":
    sys.stderr.write("NOT PROVED: " + sentence + "\n")
    if _JSON["on"]:
        emit_json(False, EXIT_REFUSED, reason, sentence, inclusion, witness)
    sys.exit(EXIT_REFUSED)


def not_implemented(sentence: str) -> "None":
    """The honest sibling of refuse(): a refusal about this checker, never a
    result about the input. Nothing here says the artifact is bad — it says
    this build cannot judge it. One sentence, exit 3, no traceback."""
    sys.stderr.write("NOT IMPLEMENTED HERE: " + sentence + "\n")
    if _JSON["on"]:
        emit_json(False, EXIT_NOT_IMPLEMENTED, REASON_NOT_IMPLEMENTED, sentence,
                  None, None)
    sys.exit(EXIT_NOT_IMPLEMENTED)


def present_field(container: dict, key: str, what: str, required: bool) -> object:
    """ABSENT and PRESENT, told apart — because ``.get()`` cannot tell them apart.

    ``dict.get()`` collapses two different facts into one ``None``: the key is
    MISSING, and the key is THERE carrying JSON ``null``. A gate written
    ``if x is not None:`` therefore skips a PRESENT null exactly as it skips an
    absent key — and on a field this script promises to cross-check against the
    signed artifact, a skipped check reads exactly like a passed one.

    PRESENT-and-null REFUSES, always: nothing here reads a null as "absent".
    ABSENT refuses when the field is required, and is lawfully skippable —
    returning ``None`` — only where this script's own recipe above says the
    field is optional.
    """
    if key not in container:
        if required:
            return refuse(
                "there is no %r field here, and this script cross-checks it "
                "against the signed artifact rather than trusting it -- "
                "refused rather than skipped" % key)
        return None
    value = container[key]
    if value is None:
        return refuse(
            "%s is PRESENT and null. A null is not an absent field: this "
            "script cross-checks that value against the signed artifact, so "
            "it is refused rather than skipped -- a skipped check reads "
            "exactly like a passed one" % what)
    return value


# ══════════════════════════════════════════════════════════════════════════════
# Ed25519 verification (RFC 8032), self-contained. Used when the third-party
# ``cryptography`` package is absent, so this script runs on a bare Python.
# ══════════════════════════════════════════════════════════════════════════════
_P = 2 ** 255 - 19
_L = 2 ** 252 + 27742317777372353535851937790883648493


def _inv(x: int) -> int:
    return pow(x, _P - 2, _P)


_D = (-121665 * _inv(121666)) % _P
_SQRT_M1 = pow(2, (_P - 1) // 4, _P)


def _x_recover(y: int) -> "int | None":
    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
    if x % 2 != 0:
        x = _P - x
    return x


def _point_add(pt1, pt2):
    x1, y1 = pt1
    x2, y2 = pt2
    prod = (_D * x1 * x2 * y1 * y2) % _P
    x3 = ((x1 * y2 + x2 * y1) * _inv(1 + prod)) % _P
    y3 = ((y1 * y2 + x1 * x2) * _inv(1 - prod)) % _P
    return (x3, y3)


def _scalar_mult(pt, scalar: int):
    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) -> bool:
    x, y = pt
    return (-x * x + y * y - 1 - _D * x * x * y * y) % _P == 0


def _decode_point(blob: bytes):
    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)
    if not _on_curve(pt):
        return None
    return pt


def _ed25519_verify_pure(public_raw: bytes, signature: bytes, message: bytes) -> bool:
    """RFC 8032 Ed25519 verify. Returns True/False; never raises, never asserts."""
    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
    left = _scalar_mult(_BASE, scalar_s)
    right = _point_add(point_r, _scalar_mult(point_a, challenge))
    return left == right


def ed25519_verify(public_raw: bytes, signature: bytes, message: bytes,
                   force_pure: bool = False) -> bool:
    """Verify with ``cryptography`` when it is installed, else with the bundled
    RFC 8032 code above. Same bytes, same answer; the fallback exists so a bare
    stdlib Python can still run this script unedited."""
    if not force_pure:
        try:
            from cryptography.exceptions import InvalidSignature
            from cryptography.hazmat.primitives.asymmetric import ed25519 as _ed
        except ImportError:
            pass
        else:
            try:
                _ed.Ed25519PublicKey.from_public_bytes(public_raw).verify(
                    signature, message
                )
                return True
            except InvalidSignature:
                return False
            except Exception:
                return False
    return _ed25519_verify_pure(public_raw, signature, message)


# ══════════════════════════════════════════════════════════════════════════════
# Merkle arithmetic (RFC 6962), recipe items 5 and 6.
# ══════════════════════════════════════════════════════════════════════════════
def leaf_hash(entry: str) -> bytes:
    """sha256(0x00 || utf8(entry)) over the FULL prefixed string — item 5."""
    return hashlib.sha256(b"\x00" + entry.encode("utf-8")).digest()


def node_hash(left: bytes, right: bytes) -> bytes:
    return hashlib.sha256(b"\x01" + left + right).digest()


def split_point(n: int) -> int:
    """Largest power of two strictly less than n (the RFC 6962 MTH split)."""
    k = 1
    while k * 2 < n:
        k *= 2
    return k


def expected_path_sides(tree_size: int, leaf_index: int) -> "list[str]":
    """The ONLY sibling-side sequence RFC 6962 permits for this (size, index),
    leaf-to-root — pure arithmetic, no hashing. Item 6's bijection check."""
    sides = []
    n, m = tree_size, leaf_index
    while n > 1:
        k = split_point(n)
        if m < k:
            sides.append("right")
            n = k
        else:
            sides.append("left")
            n, m = n - k, m - k
    sides.reverse()
    return sides


# ══════════════════════════════════════════════════════════════════════════════
# Encoding helpers — reject, never normalize (item 7).
# ══════════════════════════════════════════════════════════════════════════════
def digest_bytes(value: object, what: str) -> bytes:
    if not isinstance(value, str) or not DIGEST_RE.match(value):
        return refuse(
            "%s is not a 'sha256:' + 64 lower-case hex digest, so there is "
            "nothing this script can safely compare -- upper-case hex and "
            "short digests are rejected, never normalised (got %r)"
            % (what, value)
        )
    return bytes.fromhex(value[len("sha256:"):])


def b64_bytes(text: str, what: str) -> bytes:
    try:
        return base64.b64decode(text, validate=True)
    except (binascii.Error, ValueError):
        return refuse("%s is not valid base64, so the note cannot be read" % what)


# ══════════════════════════════════════════════════════════════════════════════
# The C2SP signed note (item 3). Parsed here byte-exactly, from the note itself
# — never from the unsigned fields beside it.
# ══════════════════════════════════════════════════════════════════════════════
def parse_note(note_text: object) -> dict:
    if not isinstance(note_text, str) or not note_text:
        return refuse(
            "the proof carries no 'checkpoint' note, and the note is the only "
            "signed thing on this wire -- there is nothing here to verify"
        )
    # signed-note: the text is separated from the signature lines by the LAST
    # blank line (the general format lets a body carry blank lines), so the
    # split is rfind -- exactly as the house's cosig_verify.py splits. A
    # tlog-checkpoint body then forbids an empty line, so an EXTRA blank line
    # between the body and the signatures lands in the body as an empty
    # extension line and refuses below, and a blank line inside the signature
    # block turns the lines before it into body text and refuses the same way.
    # Splitting on the FIRST blank line read both as a well-formed note while
    # the house read neither: one note, two verdicts (the red team's finding).
    separator = note_text.rfind("\n\n")
    if separator < 0:
        return refuse(
            "the checkpoint note has no blank line between its body and its "
            "signature, so it is not a C2SP signed note"
        )
    body = note_text[:separator + 1]
    sig_block = note_text[separator + 2:]
    lines = body.splitlines()
    if len(lines) < 3:
        return refuse(
            "the checkpoint note body is %d line(s); a C2SP tlog checkpoint body "
            "is at least three (origin, decimal tree size, base64 root)"
            % len(lines)
        )
    origin, size_line, root_b64 = lines[0], lines[1], lines[2]
    extensions = lines[3:]
    if any(not line for line in extensions):
        return refuse(
            "the checkpoint note body carries an empty extension line, which "
            "C2SP forbids -- extension lines must be non-empty"
        )
    if not size_line.isdigit():
        return refuse(
            "the checkpoint note's second line is not a bare decimal tree size "
            "(got %r)" % size_line
        )
    root = b64_bytes(root_b64, "the checkpoint note's root line")
    if len(root) != 32:
        return refuse(
            "the checkpoint note's root decodes to %d bytes; a sha256 root is 32"
            % len(root)
        )
    signatures = []
    # After a LAST-blank-line split no empty line can sit inside the block (a
    # later blank would have been the separator); splitlines() drops the one
    # trailing empty piece of the final newline.
    for line in sig_block.splitlines():
        if not line.startswith(SIG_LINE_PREFIX):
            return refuse(
                "a signature line in the checkpoint note does not start with the "
                "C2SP marker (em dash + space)"
            )
        # The cap fires BEFORE the blob is even decoded (ns r1 P2): the
        # promise is refusing to SCAN an unbounded block, so the over-cap
        # line's bytes are never touched.
        if len(signatures) >= MAX_SIGNATURE_LINES:
            return refuse(
                "the checkpoint note carries more than %d signature lines; "
                "this checker accepts up to the C2SP floor and refuses to "
                "scan an unbounded block" % MAX_SIGNATURE_LINES
            )
        rest = line[len(SIG_LINE_PREFIX):]
        # Exactly 'em dash, space, key name, space, base64' -- signed-note's
        # key name carries no space, and nothing follows the base64. A
        # trailing space used to read as an empty signature here and as a
        # valid line elsewhere; a malformed signature line refuses the note.
        key_name, separator_space, blob_b64 = rest.partition(" ")
        if not separator_space or not key_name or not blob_b64 or " " in blob_b64:
            return refuse(
                "a signature line in the checkpoint note is not 'em dash, "
                "space, key name, space, base64' (a key name carries no space "
                "and nothing follows the base64, so a trailing space is a "
                "malformed line) -- a malformed signature line refuses the note"
            )
        blob = b64_bytes(blob_b64, "a signature line in the checkpoint note")
        if len(blob) < 5:
            return refuse(
                "a signature line carries %d bytes; the smallest signed-note "
                "line is 4 bytes of key hash plus a signature" % len(blob)
            )
        # A length this build does not recognise is CLASSIFIED, never fatal:
        # the note stays checkable by the log's own line, and the report names
        # what was left unchecked instead of silently dropping it.
        signatures.append(
            {"key_name": key_name, "key_hash": blob[:4], "signature": blob[4:],
             "blob_len": len(blob), "raw": line,
             "line_class": SIG_LINE_CLASSES.get(len(blob),
                                                UNRECOGNISED_SIG_LINE)}
        )
    if not signatures:
        return refuse("the checkpoint note carries no signature line at all")
    return {
        "origin": origin,
        "tree_size": int(size_line),
        "root": root,
        "body": body,
        "extensions": extensions,
        "signatures": signatures,
    }


ISSUED_AT_PREFIX = "bfx:issued-at "


def _issued_at(extensions: "list[str]") -> "str | None":
    """The signed issuance instant, if this note carries one. Read out of the
    SIGNED body's extension lines — never from a field beside the note."""
    for line in extensions:
        if line.startswith(ISSUED_AT_PREFIX):
            return line[len(ISSUED_AT_PREFIX):]
    return None


EPOCH_NAME = "bfx:epoch"
EPOCH_PREFIX = EPOCH_NAME + " "
EPOCH_VALUE_RE = re.compile(r"^[1-9][0-9]*$", re.ASCII)
EPOCH_LINE_MALFORMED = (
    "the checkpoint note's bfx:epoch extension line is not one bare decimal "
    "epoch >= 1, so the note cannot be read")


def _epoch(extensions: "list[str]") -> "int | None":
    """The signed test-lane epoch, if this note carries one -- read out of the
    SIGNED body's extension lines exactly as the issuance instant is. None on a
    production note, which carries no such line, byte for byte. A line is
    PRESENT when it is the bare name or opens with the name and a space; there
    must be EXACTLY ONE such line and its value one bare ASCII decimal >= 1 with
    no leading zero (the one rule on every carrier -- the server, both
    published twins and the kit). Anything else is a malformed signed
    extension, and a malformed signed extension is a malformed note."""
    lines = [line for line in extensions
             if line == EPOCH_NAME or line.startswith(EPOCH_PREFIX)]
    if not lines:
        return None
    value = lines[0][len(EPOCH_PREFIX):] if len(lines) == 1 else ""
    if not EPOCH_VALUE_RE.match(value) or int(value) > 9007199254740991:
        return refuse(EPOCH_LINE_MALFORMED)
    return int(value)


def instant(value: object) -> bool:
    """An RFC 3339 UTC instant, second precision, literal Z -- AND a real
    calendar date: the regex alone admits a 30th of February. A :60 second is
    refused too, as the JavaScript twin's Date cannot read one."""
    if not isinstance(value, str) or not INSTANT_RE.match(value) or value[17:19] >= "60":
        return False
    try:
        time.strptime(value, "%Y-%m-%dT%H:%M:%SZ")
    except ValueError:
        return False
    return True


def load_test_key_registry(path: str) -> dict:
    """The saved registry, through the same door as a receipt file and with the
    published twins' sentences: a missing / unreadable / non-JSON file is
    reason ``file``; a document that is not a bluefox-test-key-registry/1
    object with a keys array (null, a list, another format) is ``test_lane``."""
    try:
        with open(path, "rb") as handle:
            raw = handle.read()
    except FileNotFoundError:
        return refuse(
            "there is no retired-test-key registry file at %s -- save the test "
            "face's %s and name that file" % (path, TEST_KEY_REGISTRY_PATH),
            reason=REASON_FILE)
    except IsADirectoryError:
        return refuse("%s is a directory, not a retired-test-key registry file" % path,
                      reason=REASON_FILE)
    except PermissionError:
        return refuse("%s cannot be read (permission denied)" % path, reason=REASON_FILE)
    except OSError:
        return refuse("%s could not be read, so no retired-test-key registry can be used"
                      % path, reason=REASON_FILE)
    try:
        text = raw.decode("utf-8")
    except UnicodeDecodeError:
        return refuse(
            "%s is not UTF-8 text, so it is not a retired-test-key registry this "
            "recipe can read" % path, reason=REASON_FILE)
    try:
        registry = json.loads(text)
    except ValueError:
        return refuse(
            "%s is not valid JSON, so no retired-test-key registry can be read "
            "from it" % path, reason=REASON_FILE)
    if (not isinstance(registry, dict)
            or registry.get("format") != TEST_KEY_REGISTRY_FORMAT
            or not isinstance(registry.get("keys"), list)):
        return refuse(
            "the retired-test-key registry handed in (%s) is not a %s document -- "
            "save %s from the test face and pass that file"
            % (path, TEST_KEY_REGISTRY_FORMAT, TEST_KEY_REGISTRY_PATH),
            reason=REASON_TEST_LANE)
    return registry


def registry_log_key(registry: dict, key_name: str, own_ed25519: "list[dict]",
                     claimed_kid: object) -> "tuple[str, bytes, str] | None":
    """The registry row for the note's log key, SELECTED then JUDGED. Selection:
    the first ``keys[]`` object whose kid is the answer's unsigned key_id, or
    whose public key derives the note's own key hint. A selected row is never
    skipped: a purpose other than 'test-log', a public key that does not
    derive the kid, or a missing / impossible retired_at each refuse in its own
    sentence (reason test_lane). Returns (kid, raw public key, retired_at)."""
    hints = {sig["key_hash"] for sig in own_ed25519}

    def lenient_raw(row: dict) -> "bytes | None":
        x_value = row.get("x")
        if not isinstance(x_value, str):
            return None
        try:
            raw = base64.urlsafe_b64decode(x_value + "=" * (-len(x_value) % 4))
        except (binascii.Error, ValueError):
            return None
        return raw if len(raw) == 32 else None

    selected = None
    for row in registry["keys"]:
        if not isinstance(row, dict):
            continue
        if isinstance(claimed_kid, str) and row.get("kid") == claimed_kid:
            selected = row
            break
        raw = lenient_raw(row)
        if raw is not None and note_key_hash(key_name, raw) in hints:
            selected = row
            break
    if selected is None:
        return None
    kid = selected.get("kid")
    shown = kid if isinstance(kid, str) else repr(kid)
    purpose = selected.get(JWK_PURPOSE_CLAIM)
    if purpose != PURPOSE_TEST_LOG:
        designation = ("is designated %r" % purpose if isinstance(purpose, str)
                       else "carries no purpose label")
        return refuse(
            "the retired-test-key registry's entry for key %s %s, not a %s key, "
            "so nothing is pinned from it" % (shown, designation, PURPOSE_TEST_LOG),
            reason=REASON_TEST_LANE)
    raw = lenient_raw(selected)
    x_value = selected.get("x")
    if (raw is None or not isinstance(kid, str)
            or base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii") != x_value
            or hashlib.sha256(raw).hexdigest() != kid):
        return refuse(
            "the retired-test-key registry's entry for key %s carries no public "
            "key that derives that kid, so the registry is not the taught shape "
            "and nothing is pinned from it" % shown, reason=REASON_TEST_LANE)
    retired_at = selected.get("retired_at")
    if not instant(retired_at):
        return refuse(
            "the retired-test-key registry's entry for key %s carries no "
            "retired_at instant (RFC 3339 UTC), so the registry is not the taught "
            "shape and nothing is pinned from it" % shown, reason=REASON_TEST_LANE)
    return kid, raw, retired_at


def note_key_hash(key_name: str, public_raw: bytes) -> bytes:
    """sha256(key_name || 0x0A || 0x01 || raw public key)[:4] — item 3."""
    return hashlib.sha256(
        key_name.encode("utf-8") + b"\n" + NOTE_ED25519_ALG + public_raw
    ).digest()[:4]


# ══════════════════════════════════════════════════════════════════════════════
# The pinned key set (items 1 and 2).
# ══════════════════════════════════════════════════════════════════════════════
def load_key_set(key_set_url: str, key_set_file: "str | None") -> dict:
    if key_set_file:
        try:
            with open(key_set_file, "rb") as handle:
                raw = handle.read()
        except OSError as exc:
            return refuse(
                "the key set file %s could not be read (%s)" % (key_set_file, exc)
            )
    else:
        try:
            with urllib.request.urlopen(key_set_url, timeout=30) as response:
                raw = response.read()
        except (urllib.error.URLError, OSError, ValueError) as exc:
            return refuse(
                "the pinned key set at %s could not be fetched (%s) -- this "
                "script will not fall back to any address found inside the "
                "proof, so it stops here" % (key_set_url, exc)
            )
    try:
        parsed = json.loads(raw.decode("utf-8"))
    except (UnicodeDecodeError, ValueError) as exc:
        return refuse("the key set is not valid JSON (%s)" % exc)
    if not isinstance(parsed, dict) or not isinstance(parsed.get("keys"), list):
        return refuse("the key set has no 'keys' array, so no key can be pinned")
    return parsed


def ed25519_keys(key_set: dict) -> "list[tuple[str, bytes, str | None]]":
    """(kid, raw 32-byte public key, purpose) for every OKP/Ed25519 entry whose
    kid is self-consistent: the published kid IS sha256(raw public key) in hex,
    so an entry that disagrees with its own key material is dropped, not trusted.

    ``purpose`` is the entry's ``bfx:purpose`` member — "log" or "receipt" — or
    None when the entry does not carry one. It is read as a string only; any
    other type is treated as absent rather than coerced, on the same
    reject-never-normalize rule as item 7. Item 1's check consumes it.
    """
    out = []
    for entry in key_set["keys"]:
        if not isinstance(entry, dict):
            continue
        if entry.get("kty") != "OKP" or entry.get("crv") != "Ed25519":
            continue
        # An entry that POSITIVELY declares a foreign algorithm is never a
        # verification candidate, whatever shape its material has — verifying
        # under a key that says it is not Ed25519 would contradict its own
        # declaration (Qodo r2). It stays visible to unimplemented_key_entries,
        # so the mismatch is NAMED (exit 3), never silently used or dropped.
        alg = entry.get("alg")
        if isinstance(alg, str) and alg not in ACCEPTED_ED25519_ALG_SPELLINGS:
            continue
        kid, x_value = entry.get("kid"), entry.get("x")
        if not isinstance(kid, str) or not isinstance(x_value, str):
            continue
        # Strict, and canonical: the same "reject, never normalize" rule item 7
        # applies to hex. A non-canonical base64url spelling of the right bytes
        # is still a spelling we did not publish, so it is dropped rather than
        # quietly accepted — re-encoding and requiring byte equality is the
        # cheapest complete test for that.
        padded = x_value + "=" * (-len(x_value) % 4)
        try:
            raw = base64.b64decode(padded, altchars=b"-_", validate=True)
        except (binascii.Error, ValueError):
            continue
        if len(raw) != 32:
            continue
        if base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii") != x_value:
            continue
        if hashlib.sha256(raw).hexdigest() != kid:
            continue
        purpose = entry.get(JWK_PURPOSE_CLAIM)
        out.append((kid, raw, purpose if isinstance(purpose, str) else None))
    return out


def unimplemented_key_entries(key_set: dict) -> "list[tuple[str, str]]":
    """(kid-or-placeholder, declared algorithm) for every entry that POSITIVELY
    declares an algorithm this build does not implement — a key type other than
    OKP, a curve other than Ed25519, or an ``alg`` outside the two Ed25519
    spellings. Absent members stay absent (the same tolerate-absence rule the
    purpose label follows); only a positive foreign declaration lands here.
    These entries are never verified against — they exist so a mismatch can be
    named as a capability gap instead of misreported as an unpublished key."""
    out = []
    for entry in key_set["keys"]:
        if not isinstance(entry, dict):
            continue
        kty, crv, alg = entry.get("kty"), entry.get("crv"), entry.get("alg")
        foreign = None
        if isinstance(alg, str) and alg not in ACCEPTED_ED25519_ALG_SPELLINGS:
            foreign = alg
        elif isinstance(kty, str) and kty != "OKP":
            foreign = "a %s-family key" % kty
        elif isinstance(crv, str) and crv != "Ed25519":
            foreign = "curve %s" % crv
        if foreign is not None:
            kid = entry.get("kid")
            out.append((kid if isinstance(kid, str) else "<entry without a "
                        "kid>", foreign))
    return out


# ══════════════════════════════════════════════════════════════════════════════
# Input.
# ══════════════════════════════════════════════════════════════════════════════
def read_proof(path: "str | None") -> dict:
    if path:
        try:
            with open(path, "rb") as handle:
                raw = handle.read()
        except OSError as exc:
            return refuse("the proof file %s could not be read (%s)" % (path, exc))
    else:
        raw = sys.stdin.buffer.read()
    if not raw.strip():
        return refuse(
            "no proof was supplied -- pipe the inclusion response in on stdin, "
            "or pass --file"
        )
    try:
        parsed = json.loads(raw.decode("utf-8"))
    except (UnicodeDecodeError, ValueError) as exc:
        return refuse("the proof is not valid JSON (%s)" % exc)
    if not isinstance(parsed, dict):
        return refuse("the proof is not a JSON object")
    # The service wraps its answers in {"data": ..., "meta": ...}; a bare data
    # object is accepted too, so a holder who saved either shape can still run
    # this unedited.
    inner = parsed.get("data")
    if isinstance(inner, dict) and "binding" in inner:
        return inner
    if "binding" in parsed:
        return parsed
    if isinstance(parsed.get("error"), dict) or "type" in parsed:
        detail = parsed.get("detail") or parsed.get("title") or "no detail given"
        return refuse(
            "this is a refusal from the service, not a proof (%s) -- there is "
            "nothing to check" % detail
        )
    return refuse(
        "the JSON carries no 'binding' field, so it is not an inclusion-proof "
        "response from /v1/transparency/inclusion"
    )


# ═════════════════════════════════════════════════════════════════════════════
# The anchor pointer — read, but never believed.
# ═════════════════════════════════════════════════════════════════════════════
def anchor_pointer(proof: dict, tree_size: int, leaf_index: int) -> tuple:
    """Which tree size this response says is ANCHORED, or nothing.

    The anchor lane lags the log's tip: a checkpoint is folded first and
    anchored afterwards, so the size a freshly served proof is served against
    is almost always the one still PENDING, while the anchor that does cover
    the entry sits at an EARLIER size. This script used to build its anchor
    instruction from the tip, and a stranger who ran exactly what it printed
    got "Nothing is on the Bitcoin chain yet" for a receipt that WAS anchored.
    The response names the right size itself, in its ``anchor`` block, and
    this reads it.

    The block is UNSIGNED -- it sits beside the signature, not inside it -- so
    it is read the way item 4 reads every other unsigned field: as a claim to
    be bounded by the signed artifact, never as a fact. A size is usable only
    if it is a plain integer (a JSON ``true`` reads as 1 and a JSON ``1.0``
    compares equal to 1, so neither stands in for a tree size) and only if the
    tree it names could contain the leaf this run PROVED and does not exceed
    the tree the signed note declares:

        leaf_index < target_tree_size <= tree_size

    Without that bound a forged pointer could name a size that really is
    anchored but does not cover your entry -- size 1 is anchored on this log --
    and a stranger sent there would read a green answer about somebody else's
    leaf as proof of their own. That is a false pass, and it would be a worse
    defect than the false negative this cure removes.

    Returns ("named", size), ("unusable", None) or ("absent", None). It never
    refuses: the block is disclosure, and disclosure never changes a verdict.
    """
    block = proof.get("anchor")
    if not isinstance(block, dict):
        return ("absent", None)
    target = block.get("target_tree_size")
    if type(target) is not int:
        return ("unusable", None)
    if not leaf_index < target <= tree_size:
        return ("unusable", None)
    return ("named", target)


# ══════════════════════════════════════════════════════════════════════════════
# The witness (item 11): C2SP cosignature/v1 lines, verified ONLY under a key
# the relying party saved. Nothing here reads a key out of the key set or out
# of the note; the house's own key set carries no witness key and never will.
# ══════════════════════════════════════════════════════════════════════════════
def _is_small_order(pt) -> bool:
    """[8]P == identity, computed — never a from-memory blocklist. A small-order
    public key is no legitimate signer's key, so it is refused at the door."""
    return _scalar_mult(pt, 8) == (0, 1)


def canonical_b64(text: str) -> "bytes | None":
    """Standard-alphabet, padded, CANONICAL base64: decoded only if it re-encodes
    to the same text. A lenient decoder is how two spellings of one key become
    one key, so a non-canonical spelling is refused, never normalised."""
    try:
        raw = base64.b64decode(text, validate=True)
    except (binascii.Error, ValueError):
        return None
    if base64.b64encode(raw).decode("ascii") != text:
        return None
    return raw


def cosig_key_hash(name: str, public_raw: bytes) -> bytes:
    """sha256(name || 0x0A || 0x04 || raw public key)[:4] — item 11. Algorithm
    byte 0x04, never the 0x01 of the log's own line (note_key_hash above)."""
    return hashlib.sha256(
        name.encode("utf-8") + b"\n" + COSIG_ED25519_ALG + public_raw
    ).digest()[:4]


def parse_witness_vkey(text: str) -> dict:
    """One C2SP vkey, self-checked: <name>+<8 hex key id>+base64(0x04 || key).
    Any malformation is a usage refusal — a vkey that fails its own self-check
    is not a key, it is a typo with a key's shape. Type 0x04 only: this build
    verifies Ed25519 cosignature/v1 lines and says so rather than pretending."""
    parts = text.split("+", 2)
    if len(parts) != 3:
        return refuse(
            "the witness key %r is not a vkey -- a vkey is <name>+<8 hex key "
            "id>+<base64(0x04 || key)>, three parts joined by '+'"
            % text[:48], reason=REASON_USAGE)
    name, id_hex, material_b64 = parts
    if not name or any(ch.isspace() for ch in name):
        return refuse(
            "the witness key's name %r is empty or carries whitespace, which "
            "signed-note forbids in a key name" % name[:48], reason=REASON_USAGE)
    if len(id_hex) != 8:
        return refuse(
            "the witness key %r carries a key id of %d characters; a vkey key "
            "id is exactly 8 hex characters" % (name, len(id_hex)),
            reason=REASON_USAGE)
    try:
        embedded = bytes.fromhex(id_hex)
    except ValueError:
        return refuse("the witness key %r carries a key id that is not hex"
                      % name, reason=REASON_USAGE)
    material = canonical_b64(material_b64)
    if material is None or not material:
        return refuse(
            "the witness key %r carries key material that is not canonical "
            "base64" % name, reason=REASON_USAGE)
    alg = material[:1]
    if alg == NOTE_ED25519_ALG:
        return refuse(
            "the witness key %r has signature type byte 0x01, which is a log's "
            "own signed-note key, not a cosignature/v1 witness key (type byte "
            "0x04)" % name, reason=REASON_USAGE)
    if alg == COSIG_MLDSA44_ALG:
        return refuse(
            "the witness key %r has signature type byte 0x06 (ML-DSA-44), "
            "which this build does not verify -- it verifies Ed25519 "
            "cosignature/v1 witness keys (type byte 0x04) only, and will not "
            "pretend otherwise" % name, reason=REASON_USAGE)
    if alg != COSIG_ED25519_ALG or len(material) != 33:
        return refuse(
            "the witness key %r is not an Ed25519 cosignature/v1 vkey -- its "
            "material must be type byte 0x04 followed by a 32-byte key"
            % name, reason=REASON_USAGE)
    public_raw = material[1:]
    point = _decode_point(public_raw)
    if point is None:
        return refuse("the witness key %r does not decode to a curve point"
                      % name, reason=REASON_USAGE)
    if _is_small_order(point):
        return refuse(
            "the witness key %r is a small-order point -- no legitimate "
            "signer's key, refused at the door" % name, reason=REASON_USAGE)
    derived = cosig_key_hash(name, public_raw)
    if derived != embedded:
        return refuse(
            "the witness key %r fails its own self-check: it says key id %s "
            "but sha256(name || 0x0A || 0x04 || key)[:4] is %s -- a vkey that "
            "disagrees with itself is not a key" % (name, id_hex, derived.hex()),
            reason=REASON_USAGE)
    return {"name": name, "key_id": embedded, "public_raw": public_raw}


def load_witness_keys(inline: "list[str]", files: "list[str]") -> "list[dict]":
    """Every saved witness key: --witness-key texts, then each --witness-keys
    file (one vkey per line; '#' comments and blank lines ignored; a leading
    UTF-8 BOM stripped and CRLF tolerated, so a file saved on Windows reads
    the same here as on every other carrier). The door has a bound: more than
    MAX_WITNESS_KEYS texts in all, counted after comments and blank lines and
    BEFORE any is parsed, is a usage refusal naming the bound. Each text under
    it is self-checked at the door; an exact duplicate is kept once."""
    texts = list(inline)
    for path in files:
        try:
            with open(path, "rb") as handle:
                raw = handle.read()
        except OSError as exc:
            return refuse("the witness key file %s could not be read (%s)"
                          % (path, exc), reason=REASON_FILE)
        try:
            # utf-8-sig: a LEADING U+FEFF (an editor's byte-order mark) is
            # not part of the first key's name; a BOM anywhere else is text.
            text = raw.decode("utf-8-sig")
        except UnicodeDecodeError as exc:
            return refuse("the witness key file %s is not UTF-8 text (%s)"
                          % (path, exc), reason=REASON_FILE)
        for line in text.splitlines():
            line = line.strip()
            if not line or line.startswith("#"):
                continue
            texts.append(line)
    if len(texts) > MAX_WITNESS_KEYS:
        return refuse(
            "you passed %d witness keys across --witness-key and --witness-keys; "
            "this checker takes at most %d saved vkeys (a checkpoint note "
            "carries at most %d cosignature lines, so a larger set is a CPU "
            "bound, not a key ring) -- refused at the door before any key was "
            "checked" % (len(texts), MAX_WITNESS_KEYS, MAX_SIGNATURE_LINES - 1),
            reason=REASON_USAGE)
    keys = []
    for text in texts:
        key = parse_witness_vkey(text.strip())
        if any(k["name"] == key["name"] and k["key_id"] == key["key_id"]
               and k["public_raw"] == key["public_raw"] for k in keys):
            continue
        keys.append(key)
    return keys


def _note_once(names: "list[str]", name: str) -> None:
    """Append ``name`` unless it is already there: the distinct names of a
    class, in note order, for the sentence (the counts stay per line)."""
    if name not in names:
        names.append(name)


def check_witness(note: dict, saved_keys: "list[dict]",
                  force_pure: bool) -> "tuple[dict, dict]":
    """The witness half (item 11), over every signature line that does not
    carry the log's own origin. Called ONLY after the log's own line verified.

    signed-note's rules: a line under a key you did not save is counted
    ``unknown`` and ignored; a line whose name AND key id match a saved key
    MUST verify, else it is ``invalid`` and the whole note is refused by the
    caller; an ML-DSA-44 line is ``unsupported`` — counted, named, never
    verified here. The five counts are per LINE and ``names`` is per line in
    note order; ``cosigned_at`` and the sentence count DISTINCT witnesses --
    cosignature lines sit outside the log's signature, so any holder can paste
    a valid line twice, and two copies of one witness's line are ONE witness.
    Returns (the witness object the JSON document carries, the per-line detail
    the report names)."""
    origin = note["origin"]
    body = note["body"].encode("utf-8")
    valid = invalid = unknown = unsupported = 0
    names = []
    cosigned_at = {}
    seen_witnesses = []  # the (name, key id) pairs that verified, note order
    detail = {"unknown": [], "unsupported": [], "invalid": [], "verified": [],
              "verified_sigs": [], "witness_lines": []}
    for signature in note["signatures"]:
        if signature["key_name"] == origin:
            continue
        name = signature["key_name"]
        names.append(name)
        detail["witness_lines"].append(signature["raw"])
        line_class = signature["line_class"]
        if line_class == "ml_dsa_44_cosignature":
            unsupported += 1
            _note_once(detail["unsupported"], name)
            continue
        matches = [key for key in saved_keys
                   if key["name"] == name and key["key_id"] == signature["key_hash"]]
        if not matches:
            unknown += 1
            _note_once(detail["unknown"], name)
            continue
        verified = False
        timestamp = None
        if line_class == "ed25519_cosignature":
            blob = signature["signature"]  # 8-byte timestamp || 64-byte signature
            timestamp = int.from_bytes(blob[:8], "big")
            if timestamp <= MAX_COSIG_TIMESTAMP:
                message = COSIG_V1_HEADER + b"time %d\n" % timestamp + body
                for key in matches:
                    if ed25519_verify(key["public_raw"], blob[8:], message,
                                      force_pure):
                        verified = True
                        break
        if verified:
            valid += 1
            detail["verified_sigs"].append(signature)
            pair = (name, signature["key_hash"])
            if pair not in seen_witnesses:
                seen_witnesses.append(pair)
                cosigned_at[name] = timestamp
                detail["verified"].append((name, timestamp))
        else:
            invalid += 1
            _note_once(detail["invalid"], name)
    lines = len(names)
    # The sentence's counts are LINES (a line pasted twice counts twice); the
    # names printed beside them stay distinct (#622 Qodo 6).
    detail["unknown_lines"], detail["unsupported_lines"] = unknown, unsupported
    if invalid:
        state = WITNESS_COSIGNATURE_INVALID
    elif valid:
        state = WITNESS_WITNESSED
    elif lines == 0:
        state = WITNESS_UNWITNESSED
    else:
        state = WITNESS_KEY_UNKNOWN
    witness = {
        "state": state, "valid": valid, "invalid": invalid, "unknown": unknown,
        "unsupported": unsupported, "lines": lines, "names": names,
        "cosigned_at": cosigned_at,
        "sentence": pinned_witness_sentence(state, detail),
    }
    return witness, detail


def witness_riders(state: str, detail: dict) -> "list[str]":
    """The clauses that ride a state's sentence, each carrying its own leading
    "; " as the manifest spells it, in the pinned order: on a witnessed note,
    the unknown-key lines that were ignored per signed-note; on ANY state, the
    ML-DSA-44 lines that were counted and never verified. The counts are
    lines; the names are distinct, in note order."""
    riders = []
    if state == WITNESS_WITNESSED and detail["unknown"]:
        riders.append(RIDER_IGNORED.format(
            u=detail["unknown_lines"], names=", ".join(detail["unknown"])))
    if detail["unsupported"]:
        riders.append(RIDER_UNSUPPORTED.format(
            m=detail["unsupported_lines"], names=", ".join(detail["unsupported"])))
    return riders


def pinned_witness_sentence(state: str, detail: dict) -> str:
    """The one sentence each state owes (design §3), the manifest's template
    rendered verbatim. ONE WITNESS TODAY -- and one witness is one witness
    however many times its line was pasted."""
    if state == WITNESS_COSIGNATURE_INVALID:
        head = SENTENCE_COSIGNATURE_INVALID.format(name=", ".join(detail["invalid"]))
    elif state == WITNESS_WITNESSED:
        verified = detail["verified"]
        if len(verified) == 1:
            head = SENTENCE_WITNESSED_ONE.format(name=verified[0][0], ts=verified[0][1])
        else:
            head = SENTENCE_WITNESSED_MANY.format(
                k=len(verified),
                pairs="; ".join(SENTENCE_WITNESSED_PAIR.format(name=name, ts=ts)
                                for name, ts in verified))
    elif state == WITNESS_UNWITNESSED:
        head = SENTENCE_UNWITNESSED
    elif detail["unknown"]:
        head = SENTENCE_KEY_UNKNOWN.format(n=detail["unknown_lines"],
                                           names=", ".join(detail["unknown"]))
    else:
        # witness_key_unknown with no unknown-key line at all: every
        # cosignature line is ML-DSA-44, which the rider names -- the head
        # says so without a count of 0 and an empty list.
        head = SENTENCE_KEY_UNKNOWN_NONE
    return head + "".join(witness_riders(state, detail))


def cross_check_beside_list(proof: dict, note: dict) -> None:
    """Item 11: the 'cosignatures' list beside the note is UNSIGNED. It is
    cross-checked against the note's own signature lines and never judged on
    its own — a listed line the note does not carry refuses. Absent (an answer
    saved before the witness lane) or null is tolerated: the signed note alone
    decides what was signed, and an empty convenience is not a claim."""
    listed = proof.get("cosignatures")
    if listed is None:
        return
    if not isinstance(listed, list):
        return refuse(
            "the response's cosignatures is %s, not a list of the note's "
            "cosignature lines" % type(listed).__name__)
    in_note = {signature["raw"] for signature in note["signatures"]}
    for position, line in enumerate(listed):
        text = line.rstrip("\r\n") if isinstance(line, str) else None
        if text is None or text not in in_note:
            return refuse(
                "cosignatures[%d] beside the note is not a signature line the "
                "signed note carries -- that list is unsigned, it is only ever "
                "cross-checked against the note, and this entry disagrees with "
                "it" % position)


def inclusion_object(result: dict) -> dict:
    """The --json 'inclusion' member: what the inclusion half proved."""
    out = {
        "entry": result["entry"], "leaf": result["leaf"],
        "leaf_index": result["leaf_index"], "tree_size": result["tree_size"],
        "root": result["root"], "origin": result["origin"], "kid": result["kid"],
        "binding": result["binding"], "issued_at": result.get("issued_at"),
    }
    # THE TEST LANE only: the lane and the signed epoch join the object. A
    # production proof's document is byte-identical to the one it always was.
    if result.get("lane") == "test":
        out["lane"] = "test"
        out["epoch"] = result["epoch"]
        out["log_key_state"] = result["log_key_state"]
        out["log_key_retired_at"] = result["log_key_retired_at"]
    return out


def inclusion_green(result: dict) -> str:
    """The green line: INCLUSION_GREEN on production, byte for byte; on the
    test lane the same sentence between the VALID-TEST prefix and the epoch."""
    green = INCLUSION_GREEN % result["tree_size"]
    if result.get("lane") == "test":
        # The internal result carries log_key_state on the test lane always
        # ("published" or "retired"); the retired instant is read only on the
        # retired branch, so no .get()/is-not-None gate (the F-7 class) is born.
        if result.get("log_key_state") == "retired":
            green += " under a test-log key the test face retired at " + str(result["log_key_retired_at"])
        return (INCLUSION_GREEN_TEST_PREFIX + green
                + INCLUSION_GREEN_TEST_SUFFIX % result["epoch"])
    return green


# ══════════════════════════════════════════════════════════════════════════════
# C2SP tlog-proof (c2sp.org/tlog-proof@v1) in, the same check out. Parsed the
# way the kit's tlog.ts parses it: header line; optional 'extra' line (read,
# never trusted); 'index N' with no leading zeroes; zero or more canonical
# base64 32-byte hashes, leaf's sibling first; a blank line; the checkpoint
# note verbatim, cosignature lines included. Sides are not carried: they
# follow from (tree size, index), item 6's arithmetic.
# ══════════════════════════════════════════════════════════════════════════════
def parse_tlog_proof(text: str) -> dict:
    cut = text.find("\n\n")
    if cut < 0:
        return refuse(
            "the tlog-proof has no empty line separating the proof from the "
            "checkpoint, so it is not a %s document" % TLOG_PROOF_HEADER)
    head = text[:cut].split("\n")
    checkpoint = text[cut + 2:]
    if head[0] != TLOG_PROOF_HEADER:
        return refuse(
            "the tlog-proof's first line is %r; a tlog-proof starts with "
            "exactly %r" % (head[0][:48], TLOG_PROOF_HEADER))
    position = 1
    extra = None
    if position < len(head) and head[position].startswith("extra "):
        extra = head[position][len("extra "):]
        if canonical_b64(extra) is None:
            return refuse(
                "the tlog-proof's extra line is not canonical base64 -- it is "
                "unauthenticated and never trusted, but a malformed one is "
                "still a malformed document")
        position += 1
    index_line = head[position] if position < len(head) else ""
    match = TLOG_INDEX_RE.match(index_line)
    if not match:
        return refuse(
            "the tlog-proof's index line is missing or is not 'index N' with N "
            "a decimal carrying no leading zeroes (got %r)" % index_line[:48])
    leaf_index = int(match.group(1))
    if leaf_index >= (1 << MAX_PATH_LENGTH):
        return refuse(
            "the tlog-proof's index %d is past the frozen %d-step inclusion-"
            "path bound" % (leaf_index, MAX_PATH_LENGTH))
    hashes = []
    for offset, line in enumerate(head[position + 1:]):
        raw_hash = canonical_b64(line)
        if raw_hash is None:
            return refuse(
                "tlog-proof hash line %d is not canonical base64 (standard "
                "alphabet, with padding)" % (offset + 1))
        if len(raw_hash) != 32:
            return refuse(
                "tlog-proof hash line %d decodes to %d bytes; a sha256 path "
                "hash is 32" % (offset + 1, len(raw_hash)))
        hashes.append(raw_hash)
    if "\n\n" not in checkpoint:
        return refuse(
            "the checkpoint embedded in the tlog-proof is not a signed note -- "
            "it has no blank line between its body and its signature lines")
    return {"extra": extra, "leaf_index": leaf_index, "hashes": hashes,
            "checkpoint": checkpoint}


def read_tlog_proof(path: str) -> dict:
    try:
        with open(path, "rb") as handle:
            raw = handle.read()
    except OSError as exc:
        return refuse("the tlog-proof file %s could not be read (%s)"
                      % (path, exc), reason=REASON_FILE)
    try:
        text = raw.decode("utf-8")
    except UnicodeDecodeError as exc:
        return refuse("the tlog-proof file %s is not UTF-8 text (%s)"
                      % (path, exc), reason=REASON_FILE)
    return parse_tlog_proof(text)


def _is_signed_envelope(node: object) -> bool:
    return (isinstance(node, dict) and isinstance(node.get("chain"), dict)
            and "signature" in node["chain"])


def _find_envelope(node: object) -> "dict | None":
    if isinstance(node, dict):
        if _is_signed_envelope(node):
            return node
        for child in node.values():
            hit = _find_envelope(child)
            if hit is not None:
                return hit
    elif isinstance(node, list):
        for child in node:
            hit = _find_envelope(child)
            if hit is not None:
                return hit
    return None


def receipt_envelope(path: str) -> dict:
    """The signed envelope read from a saved receipt, found the way the
    published receipt checker finds it: when the file has a 'data' block,
    data.receipt or data.envelope decides and nothing else in the file may
    speak for it; a file with no such block is searched for the first object
    carrying a 'chain' block with a 'signature'."""
    try:
        with open(path, "rb") as handle:
            raw = handle.read()
    except OSError as exc:
        return refuse("the receipt file %s could not be read (%s)" % (path, exc),
                      reason=REASON_FILE)
    try:
        document = json.loads(raw.decode("utf-8"))
    except (UnicodeDecodeError, ValueError) as exc:
        return refuse("the receipt file %s is not valid JSON (%s)" % (path, exc),
                      reason=REASON_FILE)
    if not isinstance(document, dict):
        return refuse("the receipt file is not a JSON object", reason=REASON_USAGE)
    data = document.get("data")
    if isinstance(data, dict):
        named = [slot for slot in ("receipt", "envelope") if slot in data]
        if not named:
            return refuse(
                "the receipt file has a data block with no receipt in it -- "
                "refusing to hunt elsewhere in the file for something to read",
                reason=REASON_USAGE)
        if len(named) > 1:
            return refuse(
                "the receipt file fills both data.receipt and data.envelope -- "
                "name one receipt", reason=REASON_USAGE)
        envelope = data[named[0]]
        if not _is_signed_envelope(envelope):
            return refuse(
                "data.%s in the receipt file is not a signed envelope -- "
                "refusing to read something else in its place" % named[0],
                reason=REASON_USAGE)
    else:
        envelope = _find_envelope(document)
        if envelope is None:
            return refuse(
                "the receipt file carries no signed envelope (an object with a "
                "'chain' block carrying a 'signature'), so there is no "
                "chain_self to read", reason=REASON_USAGE)
    return envelope


def receipt_entry(path: str) -> str:
    """The leaf pre-image read from a saved receipt: its chain.chain_self."""
    return receipt_lane(path)["entry"]


def receipt_test_marker(envelope: dict) -> "dict | None":
    """The receipt's signed 'bfx:test' member, or None on a production receipt.
    Present, it must be the taught shape, whole -- {lane: "test", exp: <RFC 3339
    UTC instant>, origin: <the test log's origin>, epoch: <an integer >= 1>} --
    or the receipt is refused rather than read as production. This script does
    not verify the receipt's own signature (the receipt check is a different
    key and a different question); it reads the marker to cross-check the
    lane, the origin and the epoch against the SIGNED note."""
    if TEST_MARKER not in envelope:
        return None
    marker = envelope[TEST_MARKER]
    taught = (isinstance(marker, dict)
              and sorted(marker) == ["epoch", "exp", "lane", "origin"]
              and marker["lane"] == "test"
              and isinstance(marker["exp"], str) and INSTANT_RE.match(marker["exp"]) is not None
              and isinstance(marker["origin"], str) and marker["origin"] != ""
              and isinstance(marker["epoch"], int) and not isinstance(marker["epoch"], bool)
              and marker["epoch"] >= 1)
    if not taught:
        return refuse(
            "this receipt carries a bfx:test member that is not the taught shape "
            "-- a test receipt names {lane: \"test\", exp: <RFC 3339 UTC instant>, "
            "origin: <the test log's origin>, epoch: <an integer >= 1>} inside its "
            "signed envelope, so it is refused rather than read as production",
            reason=REASON_TEST_LANE)
    return marker


def receipt_lane(path: str) -> dict:
    """What check() needs from a saved receipt: its leaf pre-image (entry) and
    its test-lane marker (None on a production receipt)."""
    envelope = receipt_envelope(path)
    entry = envelope["chain"].get("chain_self")
    if not isinstance(entry, str) or not DIGEST_RE.match(entry):
        return refuse(
            "the receipt's chain.chain_self is not 'sha256:' + 64 lower-case "
            "hex, so there is no leaf pre-image to recompute from",
            reason=REASON_USAGE)
    return {"entry": entry, "marker": receipt_test_marker(envelope)}


def proof_from_tlog(tlog: dict, entry: str) -> dict:
    """The response shape check() reads, synthesised from a tlog-proof and the
    leaf pre-image: size and root FROM THE NOTE, the leaf from the entry, the
    path's sides from (size, index), the cosignatures list from the note's
    own non-origin lines. Every field check() cross-checks is therefore the
    note's own, and the note is verified exactly as a served answer's is."""
    note = parse_note(tlog["checkpoint"])
    tree_size, leaf_index = note["tree_size"], tlog["leaf_index"]
    if tree_size > (1 << MAX_PATH_LENGTH):
        return refuse(
            "the signed note declares a tree of %d leaves, past the frozen "
            "%d-step inclusion-path bound -- refusing rather than computing it"
            % (tree_size, MAX_PATH_LENGTH))
    if not 0 <= leaf_index < tree_size:
        return refuse(
            "the tlog-proof's index %d is outside the signed tree of size %d"
            % (leaf_index, tree_size))
    sides = expected_path_sides(tree_size, leaf_index)
    if len(sides) != len(tlog["hashes"]):
        return refuse(
            "the tlog-proof carries %d path hash(es) but leaf %d of a tree of "
            "%d has exactly %d sibling(s) on its path -- sides are not carried "
            "in this form, so the count must match"
            % (len(tlog["hashes"]), leaf_index, tree_size, len(sides)))
    return {
        "binding": "included",
        "entry": entry,
        "leaf_index": leaf_index,
        "leaf": "sha256:" + leaf_hash(entry).hex(),
        "checkpoint": tlog["checkpoint"],
        "checkpoint_size": tree_size,
        "checkpoint_root": "sha256:" + note["root"].hex(),
        "origin": note["origin"],
        "path": [{"side": side, "digest": "sha256:" + digest.hex()}
                 for side, digest in zip(sides, tlog["hashes"])],
        "cosignatures": [signature["raw"] for signature in note["signatures"]
                         if signature["key_name"] != note["origin"]],
    }


# ══════════════════════════════════════════════════════════════════════════════
# The check.
# ══════════════════════════════════════════════════════════════════════════════
def check(proof: dict, key_set: dict, force_pure: bool,
          witness_keys: "list[dict] | None" = None,
          require_witness: bool = False,
          receipt: "dict | None" = None,
          registry_path: "str | None" = None) -> dict:
    """``receipt`` is receipt_lane()'s {entry, marker} when a receipt was handed
    in (--receipt), else None: with it, the proof must be for THIS receipt and
    the receipt's lane, origin and epoch are cross-checked against the SIGNED
    note; without it, the lane is read from the note alone. ``registry_path``
    is --test-key-registry: opened on the test lane only, for the note's log
    key when no 'test-log' key in the key set matches the note's key hint."""
    binding = proof.get("binding")

    # Item 17's case: admitted, not yet folded. Honest, and not a proof.
    if binding == "pending":
        cadence = proof.get("retry_after_seconds")
        when = (
            "in about %s seconds" % cadence
            if isinstance(cadence, int) and cadence > 0
            else "after the published fold cadence"
        )
        return refuse(
            "this entry is admitted to the log but no signed checkpoint covers "
            "it yet, so there is no inclusion proof to check -- ask again %s "
            "(admission is permanent; a pending entry is never dropped)" % when
        )
    if binding not in ("included", "anchored"):
        return refuse(
            "the response's binding is %r; only 'included' or 'anchored' "
            "carries a proof this script can check" % (binding,)
        )

    entry = proof.get("entry")
    if not isinstance(entry, str) or not DIGEST_RE.match(entry):
        return refuse(
            "the response does not echo a well-formed 'entry' (the leaf "
            "pre-image: 'sha256:' + 64 lower-case hex), so the leaf cannot be "
            "recomputed from a named pre-image"
        )
    leaf_index = proof.get("leaf_index")
    if not isinstance(leaf_index, int) or isinstance(leaf_index, bool) or leaf_index < 0:
        return refuse("the response carries no non-negative integer 'leaf_index'")

    # ── Item 3 + item 4: the note is the signed artifact. Parse it, verify it,
    #    and take the size and root FROM IT — the fields beside it are unsigned.
    note = parse_note(proof.get("checkpoint"))
    key_name = note["origin"]
    # THE TEST LANE, detected from the signed things: the note's 'bfx:epoch'
    # line and/or the receipt's 'bfx:test' member. Either one puts the run on
    # the test lane, where only a "test-log" key may verify the note; the
    # cross-checks below then refuse a receipt and a note from different lanes.
    epoch = _epoch(note["extensions"])
    marker = receipt["marker"] if receipt is not None else None
    test_lane = epoch is not None or marker is not None
    wanted_purpose = PURPOSE_TEST_LOG if test_lane else PURPOSE_LOG
    # ── THE LANES NEVER CROSS BY PROOF, read BEFORE the log's own line is
    #    verified (the order every carrier keeps): the key set in hand holds
    #    one lane's log key, and a note from the other lane would otherwise
    #    fail on the key hint with a sentence that teaches nothing. A
    #    production receipt is never placed in a test log and a test receipt
    #    never in the production log; a test receipt is placed only by its own
    #    log, and only by the epoch it names (the test log restarts by epoch).
    if marker is None and epoch is not None and receipt is not None:
        return refuse(
            "this receipt is a production receipt but the signed checkpoint "
            "note carries a test-lane epoch line ('bfx:epoch %d') -- a "
            "production receipt is never placed in a test log, so this is "
            "refused" % epoch, reason=REASON_TEST_LANE_CROSSED)
    if marker is not None:
        if epoch is None:
            return refuse(
                "this test receipt names epoch %d but the signed checkpoint "
                "note carries no 'bfx:epoch' line -- a test receipt is never "
                "placed in the production log, so this is refused"
                % marker["epoch"], reason=REASON_TEST_LANE_CROSSED)
        if marker["origin"] != key_name:
            return refuse(
                "this test receipt names the test log '%s' but the signed "
                "checkpoint note's origin line is '%s' -- a proof from another "
                "log cannot place this receipt, so this is refused"
                % (marker["origin"], key_name), reason=REASON_TEST_LANE_CROSSED)
        if marker["epoch"] != epoch:
            return refuse(
                "this test receipt names epoch %d of the test log '%s' but the "
                "signed checkpoint note carries 'bfx:epoch %d' -- the test log "
                "restarts by epoch, so a proof from another epoch cannot place "
                "this receipt; mint again on the current epoch, or fetch the "
                "proof from the epoch the receipt names"
                % (marker["epoch"], marker["origin"], epoch),
                reason=REASON_TEST_LANE_EPOCH)
    # The retired-test-key registry: opened on the TEST lane only (a production
    # proof never reads it, so a script that always passes the flag does not
    # fail on production), and consulted below only when no eligible key in
    # the key set matches the note.
    registry = (load_test_key_registry(registry_path)
                if test_lane and registry_path is not None else None)
    # A C2SP note may carry MORE signature lines than the log's own: a witness
    # cosignature is an extra line under a different key name, and the format
    # accommodates it without change. Lines that do not name this log's origin
    # are therefore skipped here, not refused — this script checks the LOG's
    # signature and says plainly, every run, that it checked no witness. The
    # day a witness is added, this script keeps working.
    own_lines = [s for s in note["signatures"] if s["key_name"] == key_name]
    if not own_lines:
        return refuse(
            "the checkpoint note carries no signature line under its own origin "
            "%r, so nothing in it is signed by the log this proof claims to be "
            "from" % key_name
        )
    # Only the 68-byte Ed25519 note-signature class is verifiable by this
    # build. Own-origin lines of any other class are counted and named, never
    # verified; a note whose OWN signature exists only in a class this build
    # cannot check gets the honest exit, not a tampering sentence.
    own_ed25519 = [s for s in own_lines
                   if s["line_class"] == "ed25519_note_signature"]
    if not own_ed25519:
        classes = ", ".join(sorted({s["line_class"] for s in own_lines}))
        return not_implemented(
            "the checkpoint note's own-origin signature line(s) are %s, which "
            "this build does not implement -- refused rather than assumed; "
            "nothing here says the note is bad, only that this build cannot "
            "judge it" % classes
        )

    published = ed25519_keys(key_set)
    unimplemented = unimplemented_key_entries(key_set)
    if not published:
        if unimplemented:
            return not_implemented(
                "the pinned key set's usable entries declare %s, which this "
                "build does not implement -- refused rather than assumed; a "
                "checker that implements the declared algorithm can judge "
                "this note"
                % ", ".join(sorted({alg for _, alg in unimplemented}))
            )
        return refuse(
            "the pinned key set publishes no self-consistent Ed25519 key, so "
            "there is nothing to verify the checkpoint against"
        )

    # ── Item 1's lock. When the key set labels its families, ONLY a
    #    "log"-purpose key may verify a checkpoint. An unlabelled entry inside a
    #    labelled set is not eligible either: a key that does not say it signs
    #    the log does not get to sign the log. A set with no labels anywhere is
    #    an older publication — every key stays eligible, and report() discloses
    #    that this separation went unenforced rather than implying it held.
    purpose_labelled = any(purpose is not None for _, _, purpose in published)
    if purpose_labelled:
        candidates = [(kid, raw) for kid, raw, purpose in published
                      if purpose == wanted_purpose]
        ineligible = [(kid, raw, purpose) for kid, raw, purpose in published
                      if purpose != wanted_purpose]
    else:
        candidates = [(kid, raw) for kid, raw, _ in published]
        ineligible = []

    verified_kid = None
    log_key_state, log_key_retired_at = "published", None
    for kid, public_raw in candidates:
        expected_hint = note_key_hash(key_name, public_raw)
        for signature in own_ed25519:
            if signature["key_hash"] != expected_hint:
                continue
            if ed25519_verify(public_raw, signature["signature"],
                              note["body"].encode("utf-8"), force_pure):
                verified_kid = kid
            else:
                return refuse(
                    "the checkpoint note's signature does not verify against "
                    "the pinned log key %s -- these are not bytes that key "
                    "signed" % kid
                )
            break
        if verified_kid:
            break
    if verified_kid is None and registry is not None:
        # THE RETIRED TEST-LOG KEY: no 'test-log' key in the key set matched
        # the note, so the registry row for the note's key (selected, then
        # judged) is the key it verifies under, and the verdict says so.
        row = registry_log_key(registry, key_name, own_ed25519, proof.get("key_id"))
        if row is not None:
            row_kid, row_raw, row_retired_at = row
            hint = note_key_hash(key_name, row_raw)
            for signature in own_ed25519:
                if signature["key_hash"] != hint:
                    continue
                if not ed25519_verify(row_raw, signature["signature"],
                                      note["body"].encode("utf-8"), force_pure):
                    return refuse(
                        "the checkpoint note's signature does not verify against "
                        "the retired test-log key %s the registry names -- these "
                        "are not bytes that key signed" % row_kid)
                verified_kid = row_kid
                log_key_state, log_key_retired_at = "retired", row_retired_at
                break
    if verified_kid is None:
        # No eligible key claimed this note. Before the generic sentence, say
        # the specific thing if it is true: the note IS signed by a published
        # key, just one from the wrong family. That is the H-33 case, and a
        # stranger deserves to be told which family signed it rather than
        # "we do not publish this key" — which would be false. A key of the
        # OTHER LANE's log family is ineligible by name, and the sentence
        # names the lane (the one spelling on both published twins).
        other_lane_log = PURPOSE_LOG if test_lane else PURPOSE_TEST_LOG
        for kid, public_raw, purpose in ineligible:
            hint = note_key_hash(key_name, public_raw)
            if not any(sig["key_hash"] == hint for sig in own_ed25519):
                continue
            if purpose == other_lane_log:
                return refuse(
                    "the key set's %s is designated '%s' and signs the %s log, "
                    "not the %s log this note belongs to, so it is ineligible here"
                    % (kid[:8], purpose,
                       "production" if test_lane else "test",
                       "test" if test_lane else "production"))
            designation = (
                "is designated %r" % purpose if purpose
                else "carries no purpose label"
            )
            return refuse(
                "this checkpoint is signed by the published key %s, which %s "
                "and so is not the inclusion log's key -- the two key families "
                "never cross, and a checkpoint under a receipt key is refused "
                "here rather than verified and reported as a log proof"
                % (kid, designation)
            )
        if unimplemented:
            return not_implemented(
                "no Ed25519 key in the pinned key set matches the checkpoint "
                "note's key hint for origin %r, and the set carries entries "
                "declaring %s -- this build implements Ed25519 only; refused "
                "rather than assumed"
                % (key_name,
                   ", ".join(sorted({alg for _, alg in unimplemented})))
            )
        return refuse(
            "no key in the pinned key set matches the checkpoint note's key "
            "hint for origin %r -- the note was signed by a key we do not "
            "publish, and this script will not go looking for one elsewhere"
            % key_name
        )

    # Item 1's cross-check: the unsigned key_id must name the key the SIGNED
    # note actually selected. Disagreement is a defect, not a detail.
    # PRESENT-and-null refuses and a present value of any type is compared (an
    # isinstance-str gate skipped 12, null, {} and [] alike); absent stays lawful.
    claimed_kid = present_field(proof, "key_id", "key_id", required=False)
    if claimed_kid is not None and claimed_kid != verified_kid:
        return refuse(
            "the response's key_id says %s but the signed note verifies under "
            "%s -- an unsigned field disagreeing with the signed artifact is "
            "exactly the mismatch this check exists to catch"
            % (claimed_kid, verified_kid)
        )

    # ── The proof is for THIS receipt (the lane cross-checks ran above, before
    #    any key was selected; the binding waits for the note to have verified).
    if receipt is not None and receipt["entry"] != entry:
        return refuse(
            "the inclusion answer names entry %s, not this receipt's chain "
            "head %s -- a proof places one receipt, and this is not it"
            % (entry, receipt["entry"]))

    # ── Item 4: the unsigned fields are REQUIRED and must equal the note.
    #    Absent refuses, present-and-null refuses, disagreement refuses.
    tree_size = note["tree_size"]
    if tree_size < 1:
        return refuse("the signed note declares tree size %d" % tree_size)
    # The same frozen bound the shipped construction enforces: fail fast on an
    # absurd tree rather than walking a 2**64-step side sequence to find out.
    if tree_size > (1 << MAX_PATH_LENGTH):
        return refuse(
            "the signed note declares a tree of %d leaves, past the frozen "
            "%d-step inclusion-path bound -- refusing rather than computing it"
            % (tree_size, MAX_PATH_LENGTH)
        )
    claimed_size = present_field(proof, "checkpoint_size",
                                 "the response's checkpoint_size", True)
    # #346's ruling, applied at this site too: a JSON ``true`` reads as 1 and a
    # JSON ``1.0`` compares equal to 1, so neither may stand in for a tree size.
    if type(claimed_size) is not int:
        return refuse(
            "the response's checkpoint_size is %r, which is not a plain "
            "integer -- a JSON true reads as 1 and a JSON 1.0 compares equal "
            "to 1, so neither is accepted where a tree size is meant"
            % (claimed_size,))
    if claimed_size != tree_size:
        return refuse(
            "the response says checkpoint_size %r but the SIGNED note says %d "
            "-- the note is the signed artifact and the unsigned field beside "
            "it does not match it" % (claimed_size, tree_size)
        )
    claimed_root = present_field(proof, "checkpoint_root",
                                 "the response's checkpoint_root", True)
    if digest_bytes(claimed_root, "the response's checkpoint_root") != note["root"]:
        return refuse(
            "the response's checkpoint_root is not the root inside the "
            "SIGNED note -- the note wins, and these disagree"
        )
    if not 0 <= leaf_index < tree_size:
        return refuse(
            "leaf_index %d is outside the signed tree of size %d"
            % (leaf_index, tree_size)
        )
    claimed_origin = present_field(proof, "origin", "origin", required=False)
    if claimed_origin is not None and claimed_origin != key_name:
        return refuse(
            "the response's origin is %r but the signed note's origin line is "
            "%r" % (claimed_origin, key_name)
        )

    # ── Item 5: recompute the leaf from the named pre-image.
    computed_leaf = leaf_hash(entry)
    claimed_leaf = present_field(proof, "leaf",
                                 "the response's leaf", True)
    if digest_bytes(claimed_leaf, "the response's leaf") != computed_leaf:
        return refuse(
            "the leaf recomputed from the echoed entry is not the leaf the "
            "response claims -- sha256(0x00 || utf8(entry)) over the full "
            "'sha256:'-prefixed string is the only construction"
        )

    # ── Item 6: walk the path, then check the side sequence is the only one
    #    RFC 6962 permits for this (size, index).
    path = proof.get("path")
    if not isinstance(path, list):
        return refuse("the response carries no 'path' array to walk")
    if len(path) > MAX_PATH_LENGTH:
        return refuse(
            "the path has %d steps, past the frozen %d-step bound"
            % (len(path), MAX_PATH_LENGTH)
        )
    accumulator = computed_leaf
    walked_sides = []
    for position, step in enumerate(path):
        if not isinstance(step, dict):
            return refuse("path step %d is not an object" % position)
        side = step.get("side")
        if side not in ("left", "right"):
            return refuse(
                "path step %d has side %r; only 'left' and 'right' exist"
                % (position, side)
            )
        sibling = digest_bytes(step.get("digest"), "path step %d's digest" % position)
        accumulator = (
            node_hash(accumulator, sibling) if side == "right"
            else node_hash(sibling, accumulator)
        )
        walked_sides.append(side)

    if accumulator != note["root"]:
        return refuse(
            "the audit path does not walk from your leaf to the root in the "
            "signed note -- this proof does not place this entry in this tree"
        )

    wanted_sides = expected_path_sides(tree_size, leaf_index)
    if walked_sides != wanted_sides:
        return refuse(
            "the path's side sequence is %s but leaf %d of a tree of %d can "
            "only have %s -- a permuted path proves a different index than it "
            "claims" % (",".join(walked_sides) or "empty", leaf_index,
                        tree_size, ",".join(wanted_sides) or "empty")
        )

    # Item 11's first half: the unsigned 'cosignatures' list beside the note
    # must agree with the note's own lines. The note wins; the list is never
    # judged on its own.
    cross_check_beside_list(proof, note)

    # Finding ①: which size the response says is ANCHORED, bounded by the
    # signed note above. Never the pending tip, and never believed unchecked.
    pointer_state, pointer_size = anchor_pointer(proof, tree_size, leaf_index)

    result = {
        "entry": entry,
        "leaf": "sha256:" + computed_leaf.hex(),
        "leaf_index": leaf_index,
        "tree_size": tree_size,
        # The anchored size the response named, and whether it survived the
        # bound: "named" | "unusable" | "absent". report() prints an anchor
        # instruction only for "named" -- a size it could not check against
        # the signed note is not a size a stranger should be sent to.
        "anchor_pointer": pointer_state,
        "anchor_target_tree_size": pointer_size,
        # The claim class the response made — "included" or "anchored" (the
        # only two that reach here). report() names it and grades the anchor
        # claim per binding, because a checker that accepts two values and
        # prints neither teaches a reader that the word is decorative.
        "binding": binding,
        "root": "sha256:" + note["root"].hex(),
        "origin": key_name,
        "kid": verified_kid,
        "log_id": proof.get("log_id"),
        "extensions": note["extensions"],
        "issued_at": _issued_at(note["extensions"]),
        "cosignatures": proof.get("cosignatures"),
        # Item 1: did the pinned key set let us enforce the family separation?
        "purpose_enforced": purpose_labelled,
        # THE TEST LANE: which lane this proof was judged on, the signed epoch
        # (None on production), and the key family the note had to verify under.
        "lane": "test" if test_lane else "production",
        "epoch": epoch,
        "log_purpose": wanted_purpose,
        # the log key's state: "retired" when the registry row verified it
        "log_key_state": log_key_state,
        "log_key_retired_at": log_key_retired_at,
    }

    # ── Item 11: the witness half, ONLY now that the log's own line verified
    #    under an eligible key and the inclusion half proved. Every line that
    #    does not carry the log's origin is classified, counted, and verified
    #    under a saved witness key where one matches -- never under ours.
    witness, detail = check_witness(note, witness_keys or [], force_pure)
    # On the test lane every witness sentence carries the TEST: prefix -- a
    # test checkpoint is never reported as witnessed without it.
    if test_lane:
        witness["sentence"] = "TEST: " + witness["sentence"]
    # Signature lines in the note this run did NOT verify: cosignatures under
    # keys the holder did not save, ML-DSA lines, and any own-origin line whose
    # class this build does not implement. Counted and CLASSIFIED so the wall
    # can name what was left unchecked instead of only counting it. Derived
    # from ONE census so the count and the breakdown cannot drift (ns r2 P3):
    # the census covers exactly the lines this run did not verify.
    checked = own_ed25519 + detail["verified_sigs"]
    census = _class_census(note["signatures"], checked)
    result["unchecked_signature_lines"] = sum(census.values())
    result["unchecked_signature_classes"] = census
    result["witness"] = witness
    result["witness_detail"] = detail
    if witness["state"] == WITNESS_COSIGNATURE_INVALID:
        # signed-note's rule: a line under a saved key that does not verify
        # refuses the WHOLE note -- the inclusion half is not reported as
        # proved on a note this run refused.
        return refuse(witness["sentence"], reason=REASON_WITNESS,
                      witness=witness, inclusion=None)
    if require_witness and witness["state"] in (WITNESS_UNWITNESSED,
                                                WITNESS_KEY_UNKNOWN):
        return refuse(
            "you passed --require-witness and the witness half did not verify "
            "-- %s" % witness["sentence"],
            reason=REASON_WITNESS_REQUIRED, witness=witness,
            inclusion=inclusion_object(result))
    return result


def _class_census(signatures: "list[dict]",
                  checked: "list[dict]") -> "dict[str, int]":
    """{line_class: count} over the signature lines NOT verified by this run.
    ``checked`` is the own-origin Ed25519 set plus every cosignature line that
    verified under a saved witness key; everything else is unchecked."""
    counts: "dict[str, int]" = {}
    for signature in signatures:
        if any(signature is c for c in checked):
            continue
        line_class = signature["line_class"]
        counts[line_class] = counts.get(line_class, 0) + 1
    return counts


def _beside_list_clause(result: dict, detail: dict) -> str:
    """Item 11's convenience list, the other way round: a listed line the note
    lacks REFUSES (cross_check_beside_list); a note line the unsigned list
    omits moves no verdict -- the signed note decides -- but a reader who
    counted the list would under-count, so the kv witness line says so. ""
    when the list is absent, null, or complete."""
    listed = result.get("cosignatures")
    if not isinstance(listed, list) or not detail["witness_lines"]:
        return ""
    have = {line.rstrip("\r\n") for line in listed if isinstance(line, str)}
    omitted = sum(1 for raw in detail["witness_lines"] if raw not in have)
    if not omitted:
        return ""
    return ("; the unsigned cosignatures list beside the note omits {omitted} of "
            "these {total} line(s) -- the signed note decides, the list is a "
            "convenience".format(omitted=omitted, total=len(detail["witness_lines"])))


def report(result: dict) -> None:
    """The G-4 wall: exactly what was verified, and — separately, never merged
    into the same line — exactly what was not."""
    extra = result.get("cosignatures") or []
    unchecked = result.get("unchecked_signature_lines") or 0
    classes = result.get("unchecked_signature_classes") or {}
    witness = result["witness"]
    detail = result["witness_detail"]
    state = witness["state"]
    breakdown = ", ".join("%d %s" % (count, name)
                          for name, count in sorted(classes.items()))
    riders = witness_riders(state, detail)
    beside = _beside_list_clause(result, detail)
    # Item 11: the witness clause of the NOT VERIFIED paragraph tells the truth
    # per state. witnessed: the witness half is graded on its own VERIFIED
    # line, and anything left unverified is still named here. unwitnessed with
    # nothing else in the note: the sentence that was always here. Otherwise
    # (unknown keys, ML-DSA lines, own-origin lines of another class): what
    # was counted and NOT checked, named, with what a holder can do about it.
    if state == WITNESS_WITNESSED:
        witness_text = (
            "the witness half is graded on its own VERIFIED line above, and it "
            "says nothing about the receipt's contents")
        if unchecked:
            witness_text += (
                " -- %d further signature line(s) in the note (%s) were counted "
                "and NOT verified" % (unchecked, breakdown))
            if riders:
                # each rider carries its own leading "; "; the first becomes ": "
                witness_text += ":" + "".join(riders)[1:]
    elif not extra and not unchecked:
        witness_text = ("cosignatures is empty, so trust in the log key is trust in "
                        "BlueFox")
    else:
        witness_text = (
            "this run checked the log's own signature ONLY and did not check "
            "%d further signature line(s) in the note%s or %d cosignature(s) "
            "beside it, so no outside witness has been verified here"
            % (unchecked, " (%s)" % breakdown if breakdown else "", len(extra))
        )
        # the state's ONE pinned sentence, riders included, verbatim
        witness_text += " -- " + witness["sentence"]
    print("  entry (leaf pre-image) : %s" % result["entry"])
    print("  leaf                   : %s" % result["leaf"])
    print("  leaf index             : %d of a tree of %d"
          % (result["leaf_index"], result["tree_size"]))
    print("  root (signed note)     : %s" % result["root"])
    print("  log origin             : %s" % result["origin"])
    if result.get("lane") == "test":
        print("  lane                   : TEST, epoch %d" % result["epoch"])
    purpose_shown = (result.get("log_purpose", PURPOSE_LOG) if result.get("purpose_enforced")
                     else "UNLABELLED")
    if result.get("log_key_retired_at") is not None:
        purpose_shown += ", retired at %s" % result["log_key_retired_at"]
    print("  log key (pinned)       : %s  [purpose: %s]" % (result["kid"], purpose_shown))
    shown_state = ("TEST: " + state) if result.get("lane") == "test" else state
    # Item 11: the witness state, always, so a reader never has to infer it.
    if state == WITNESS_WITNESSED:
        stamps = ", ".join("%s at %d%s" % (name, stamp, _utc_suffix(stamp))
                           for name, stamp in detail["verified"])
        print("  witness                : %s  [%d of %d cosignature line(s) "
              "verified under a key you saved: %s%s]"
              % (shown_state, witness["valid"], witness["lines"], stamps, beside))
    elif state == WITNESS_UNWITNESSED:
        print("  witness                : %s  [the note carries no "
              "cosignature line]" % shown_state)
    else:
        print("  witness                : %s  [%d cosignature line(s), none "
              "under a key you saved: %s%s]"
              % (shown_state, witness["lines"], ", ".join(witness["names"]), beside))
    # The binding, named — never accepted-and-unsaid (item 9's last sentence).
    if result.get("binding") == "anchored":
        # Printed one line under "leaf index : N of a tree of M", the words
        # "covers this tree size" read as M -- which is the size that is NOT
        # anchored yet. When the response names the anchored size, say it.
        if result.get("anchor_pointer") == "named":
            print("  binding                : anchored   [this log claims an "
                  "EXTERNAL time anchor covers tree size %d]"
                  % result["anchor_target_tree_size"])
        else:
            print("  binding                : anchored   [this log claims an "
                  "EXTERNAL time anchor covers this tree size]")
    else:
        print("  binding                : included   [committed under a signed "
              "checkpoint; NO external anchor claimed for this tree size]")
    # Item 10: which note form was verified, always, so a reader never has to
    # guess whether the absence of a timestamp means "old note" or "not checked".
    if result.get("issued_at"):
        print("  note issued at (SIGNED): %s  [inside the signature]"
              % result["issued_at"])
    elif result.get("extensions"):
        print("  note form              : %d extension line(s), no %s: %s"
              % (len(result["extensions"]), ISSUED_AT_PREFIX.strip(),
                 "; ".join(result["extensions"])))
    else:
        print("  note form              : three-line body, carrying NO signed "
              "issuance time")
    print("")
    print(inclusion_green(result))
    # Item 11: the witness half, on its OWN line, only when it verified -- and
    # with its scope stated in the same breath. ONE WITNESS TODAY.
    if state == WITNESS_WITNESSED:
        print(
            "VERIFIED -- the witness: %s. A cosignature says that witness saw "
            "this exact checkpoint and found it consistent with its history; "
            "it says nothing about any receipt's contents and nothing about "
            "independence as a cryptographic fact."
            % witness["sentence"]
        )
    if result.get("issued_at"):
        print(
            "WHAT THE SIGNED TIME DOES AND DOES NOT SAY: %s is when the LOG "
            "ISSUED THIS NOTE, signed. It is not when your entry was admitted, "
            "and it is not when whatever your receipt describes happened. The "
            "log proves the ORDER of its entries and now also proves when it "
            "said so; per-entry timing lives in each receipt's own signed "
            "produced_at." % result["issued_at"]
        )
    else:
        print(
            "NOT PROVED by this run -- WHEN: this note carries no signed "
            "issuance time, so nothing here places this tree in time. The log "
            "proves ORDERING, not timing. Any date you have for this checkpoint "
            "came from outside the signature and is our word, not proof."
        )
    # Item 1: if the pinned key set carried no family labels, this run could not
    # enforce the receipt-key/log-key separation. Say so here, in the paragraph
    # that already exists for exactly this purpose, rather than letting a green
    # line imply a check that did not happen.
    if result.get("purpose_enforced"):
        family = ""
    else:
        family = (
            " This key set carries no %s labels, so the receipt-key/log-key "
            "separation was NOT enforced by this run: the note was checked "
            "against whichever published key its own hint named, whatever that "
            "key is for. Re-pin from %s for a labelled set that restores the "
            "check." % (JWK_PURPOSE_CLAIM, JWKS_URL)
        )
    if state == WITNESS_WITNESSED:
        print(
            "NOT VERIFIED by this run -- separate claims, graded separately: "
            "the receipt's own signature (that is the receipt check, a "
            "different key and a different question); %s.%s"
            % (witness_text, family)
        )
    else:
        print(
            "NOT VERIFIED by this run -- separate claims, graded separately: "
            "the receipt's own signature (that is the receipt check, a "
            "different key and a different question), and any outside witness "
            "-- %s.%s" % (witness_text, family)
        )
    # The anchor claim, graded per binding — the blanket "the log's anchors"
    # clause used to sit unexplained in the paragraph above, beside a response
    # saying binding "anchored", and a careful auditor read the word as
    # decorative. The claim and the non-check are now stated together.
    if result.get("binding") == "anchored":
        # WHICH SIZE, and why it is not the one at the top of this report. The
        # paragraph stays ONE line (G-4: one claim, one line) and varies in
        # exactly two places: the clause naming what the anchor covers, and
        # the instruction at the end. A size is named only when the response
        # gave one AND it survived the bound in anchor_pointer() -- otherwise
        # this script names no size at all rather than guess the tip, because
        # the tip is the size most likely to still be pending.
        state = result.get("anchor_pointer")
        target = result.get("anchor_target_tree_size")
        log_id = result.get("log_id") or "reliance/v1"
        if state == "named":
            covers = (
                "covers your entry -- at TREE SIZE %d, the size this response "
                "names as anchored in its own \"anchor\" block. That is an "
                "EARLIER size than the checkpoint your proof was served "
                "against (%d), and that is normal rather than a "
                "contradiction: the anchor lane lags the log's tip, so the "
                "newest checkpoint is usually still pending. CHECK THE "
                "ANCHORED SIZE, NOT THE TIP -- asking for the tip's anchor "
                "answers \"pending\" for an entry that is genuinely anchored. "
                "That size came from an UNSIGNED field beside the signature, "
                "so this script did not simply believe it: it is printed only "
                "because it survived a check against the signed note -- a "
                "tree of %d does contain the leaf index %d this run proved, "
                "and it does not exceed the signed tree of %d -- so a tampered "
                "pointer cannot send you at an anchor that does not cover you"
                % (target, result["tree_size"], target, result["leaf_index"],
                   result["tree_size"])
            )
            follow = (
                "THERE IS A PUBLISHED CHECKER FOR THAT STEP TOO, so following "
                "it is one command rather than an afternoon: curl -sS -O "
                "https://api.bluefoxedge.ai/verify_anchor.py then curl -sS "
                "\"https://api.bluefoxedge.ai/v1/transparency/anchor?log_id=%s"
                "&tree_size=%d\" | python3 verify_anchor.py. It carries NO key "
                "of ours -- it fetches no key set and checks no signature -- "
                "so where this run ends on our signature, that one ends on "
                "Bitcoin." % (log_id, target)
            )
        else:
            if state == "unusable":
                covers = (
                    "covers your entry. This response carries an \"anchor\" "
                    "block, but the size it names DID NOT SURVIVE the check "
                    "against the signed note -- to be usable it must be a "
                    "plain integer naming a tree that contains the leaf index "
                    "%d this run proved and does not exceed the signed tree of "
                    "%d -- so this script names no size to check rather than "
                    "send you at an anchor that may not cover your entry"
                    % (result["leaf_index"], result["tree_size"])
                )
            else:
                covers = (
                    "covers your entry. This response names no anchored size "
                    "-- it carries no \"anchor\" block -- and this script "
                    "will not guess one: the checkpoint your proof was served "
                    "against is the size most likely to be still pending, so "
                    "guessing it is how a genuinely anchored entry comes back "
                    "\"pending\""
                )
            follow = (
                "THERE IS A PUBLISHED CHECKER FOR THAT STEP TOO, but it needs "
                "a size: /v1/transparency/anchors reports this log's anchor "
                "lane and which sizes are confirmed, and curl -sS -O "
                "https://api.bluefoxedge.ai/verify_anchor.py checks one of "
                "them. It carries NO key of ours -- it fetches no key set and "
                "checks no signature -- so where this run ends on our "
                "signature, that one ends on Bitcoin."
            )
        print(
            "NOT VERIFIED by this run -- THE ANCHOR: this response claims "
            "binding \"anchored\", which in this log means a qualifying "
            "EXTERNAL time anchor (an OpenTimestamps-confirmed Bitcoin "
            "commitment, or a held qualified timestamp) %s. This script did "
            "not check that anchor: it verifies the leaf, the path, and the "
            "log's signature over the note, and nothing else. The claim is "
            "followable -- /v1/transparency/anchor names the anchor for a "
            "checkpoint root and /v1/transparency/anchor/proof serves the raw "
            "timestamp proof bytes -- and until you follow it, \"anchored\" "
            "is our word. %s" % (covers, follow)
        )
    else:
        print(
            "NOT VERIFIED by this run -- THE ANCHOR: this response claims "
            "binding \"included\", which means committed under a signed "
            "checkpoint and claims NO external anchor for this tree size. If "
            "you need one, /v1/transparency/anchors reports the lane's state "
            "for this log."
        )


def _utc_suffix(timestamp: int) -> str:
    """" (YYYY-MM-DDTHH:MM:SSZ)" for a POSIX second, or "" when the platform
    cannot render it -- the integer is the fact, the rendering is a courtesy."""
    try:
        moment = datetime.datetime.fromtimestamp(timestamp, datetime.timezone.utc)
    except (OverflowError, OSError, ValueError):
        return ""
    return moment.strftime(" (%Y-%m-%dT%H:%M:%SZ)")


def success_sentence(result: dict) -> str:
    """The --json 'sentence' on a pass: the inclusion verdict, then the witness
    state's own sentence."""
    return "%s The witness: %s." % (
        inclusion_green(result), result["witness"]["sentence"])


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="verify_inclusion.py",
        description=(
            "Check a BlueFox inclusion proof: recompute the leaf from the "
            "named pre-image, walk the audit path to the root inside the "
            "signed checkpoint note, and verify that note against the log key "
            "pinned in this script. Exit 0 = proved; exit 4 = proved on the "
            "TEST lane (a test-face log, which is not evidence); any other "
            "non-zero = not proved, with the reason in one sentence on stderr."
        ),
        epilog=(
            "The proof comes in on stdin, or from --file. The log key comes "
            "from %s (pinned in this file), or from --jwks-file / --jwks-url "
            "if you keep your own copy -- never from a pointer inside the "
            "proof. The log signs checkpoints with a DIFFERENT key than it "
            "signs receipts with; both live in that same key set, and each "
            "entry says which family it is in under '%s' -- 'log' or "
            "'receipt'. This script verifies a checkpoint against a 'log' key "
            "ONLY, and refuses a note signed by a receipt key rather than "
            "reporting it as a log proof. If you write your own checker, READ "
            "THAT LABEL AND NOT THE POSITION: keys[0] is the receipt key, so "
            "taking the first entry gets you the wrong one. Read the recipe at "
            "the top of this file for the full construction, including the two "
            "leaf-pre-image mistakes and their exact wrong digests."
            % (JWKS_URL, JWK_PURPOSE_CLAIM)
        ),
    )
    parser.add_argument("--file", metavar="PATH", default=None,
                        help="read the proof from PATH instead of stdin")
    parser.add_argument("--jwks-file", "--jwks", metavar="PATH",
                        dest="key_set_file", default=None,
                        help="read the key set from PATH instead of fetching "
                             "it. --jwks is the same flag: one spelling works "
                             "across every published checker, so nobody is "
                             "wrong at a front door")
    parser.add_argument("--jwks-url", metavar="URL", dest="key_set_url",
                        default=JWKS_URL,
                        help="fetch the key set from URL (default: the pinned "
                             "address above)")
    parser.add_argument("--pure-ed25519", action="store_true",
                        help="always use the bundled RFC 8032 verifier, even "
                             "if the cryptography package is installed")
    parser.add_argument("--witness-key", metavar="VKEY", dest="witness_key",
                        action="append", default=[],
                        help="a witness's verifier key in C2SP vkey form "
                             "(<name>+<8 hex key id>+<base64(0x04 || key)>), "
                             "saved by YOU from the witness's own origin -- "
                             "never from BlueFox. Repeatable")
    parser.add_argument("--witness-keys", metavar="PATH", dest="witness_keys_file",
                        action="append", default=[],
                        help="a file of witness vkeys, one per line; '#' "
                             "comments and blank lines are ignored, a leading "
                             "UTF-8 BOM is stripped and CRLF is tolerated. "
                             "Repeatable; at most %d keys in all across both "
                             "flags" % MAX_WITNESS_KEYS)
    parser.add_argument("--require-witness", action="store_true",
                        help="refuse (exit 1, reason witness_required) unless "
                             "at least one cosignature line verified under a "
                             "saved witness key")
    parser.add_argument("--tlog-proof", metavar="PATH", dest="tlog_proof",
                        default=None,
                        help="read a c2sp.org/tlog-proof@v1 document from PATH "
                             "instead of an inclusion answer; needs --receipt "
                             "or --entry for the leaf pre-image")
    parser.add_argument("--receipt", metavar="PATH", dest="receipt", default=None,
                        help="with --tlog-proof: the saved receipt whose "
                             "chain.chain_self is the leaf pre-image. Beside "
                             "--file (or stdin): the receipt the answer is for, "
                             "so its lane, test-log origin and epoch are "
                             "cross-checked against the signed note")
    parser.add_argument("--entry", metavar="DIGEST", dest="entry", default=None,
                        help="with --tlog-proof: the leaf pre-image by hand, "
                             "'sha256:' + 64 lower-case hex")
    parser.add_argument("--test-key-registry", metavar="PATH", dest="test_key_registry",
                        default=None,
                        help="the test face's retired-test-key registry you saved "
                             "(%s): a TEST-lane note under a 'test-log' key the "
                             "face has retired verifies through it. Read on the "
                             "test lane only; ignored on a production proof"
                             % TEST_KEY_REGISTRY_PATH)
    parser.add_argument("--json", action="store_true",
                        help="print one JSON document (format %s) on stdout in "
                             "place of the report -- on a refusal too, with the "
                             "same exit code" % JSON_FORMAT)
    return parser


def main(argv: "list[str] | None" = None) -> int:
    args = build_parser().parse_args(argv)
    _JSON["on"] = bool(args.json)
    try:
        # The door: every saved witness key is self-checked before any proof
        # is read, so a typo with a key's shape is named as a typo.
        witness_keys = load_witness_keys(args.witness_key, args.witness_keys_file)
        receipt = None
        if args.tlog_proof:
            if args.file:
                return refuse(
                    "pass the inclusion answer (--file or stdin) OR "
                    "--tlog-proof, not both -- one proof per run",
                    reason=REASON_USAGE)
            if args.receipt and args.entry:
                return refuse(
                    "pass --receipt or --entry, not both -- the leaf pre-image "
                    "has one source per run", reason=REASON_USAGE)
            if args.entry is not None:
                if not DIGEST_RE.match(args.entry):
                    return refuse(
                        "--entry is not 'sha256:' + 64 lower-case hex (the "
                        "receipt's full chain_self string)", reason=REASON_USAGE)
                entry = args.entry
            elif args.receipt:
                receipt = receipt_lane(args.receipt)
                entry = receipt["entry"]
            else:
                return refuse(
                    "--tlog-proof needs the leaf pre-image: pass --receipt FILE "
                    "(the receipt's chain.chain_self is read from it) or "
                    "--entry sha256:<hex>", reason=REASON_USAGE)
            proof = proof_from_tlog(read_tlog_proof(args.tlog_proof), entry)
        else:
            if args.entry:
                return refuse(
                    "--entry goes with --tlog-proof; an inclusion answer already "
                    "names its entry", reason=REASON_USAGE)
            if args.receipt and not args.file:
                return refuse(
                    "--receipt goes with --tlog-proof, or with --file (a saved "
                    "inclusion answer); with the answer on stdin there is no "
                    "answer file to pair it with, so pass --file",
                    reason=REASON_USAGE)
            proof = read_proof(args.file)
            # --receipt beside a saved inclusion answer (--file): the answer
            # still names its entry; the receipt is read for the binding and
            # the lane, origin and epoch cross-checks against the signed note.
            if args.receipt:
                receipt = receipt_lane(args.receipt)
        key_set = load_key_set(args.key_set_url, args.key_set_file)
        result = check(proof, key_set, args.pure_ed25519, witness_keys,
                       args.require_witness, receipt, args.test_key_registry)
    except SystemExit:
        raise
    except KeyboardInterrupt:
        return refuse("interrupted before the check finished")
    except Exception as exc:  # the G-13 backstop: a sentence, never a traceback
        return refuse(
            "this proof could not be checked -- %s: %s"
            % (type(exc).__name__, exc)
        )
    exit_code = EXIT_VALID_TEST if result.get("lane") == "test" else EXIT_OK
    if args.json:
        emit_json(True, exit_code, None, success_sentence(result),
                  inclusion_object(result), result["witness"])
    else:
        report(result)
    return exit_code


if __name__ == "__main__":
    sys.exit(main())
