Tersign evidence-bundle verifier — release 2026-10-08 These files never change. Content address: https://tersign.ai/verify/sha256/e6847fef96410110f52ebe6e21c8caab6b750a90bf6eca4a4abe4fe731d96f1f/ (sha256 of the SHA256SUMS file at that address) Release label: https://tersign.ai/verify/2026-10-08/ Newest release: https://tersign.ai/verify/latest/ All releases: https://tersign.ai/verify/releases.json The content address covers SHA256SUMS and the files it lists, not this index page. A frozen evidence bundle ships its own copy of verify/. That is fine for a worked example and wrong for evidence handed to you by an interested party: a bundle can ship a checker that blesses it. Fetch the release named in section 0 of the archive's VERIFY.md (archives whose VERIFY.md names /verify/v1/ use that), then diff the two — a difference from that release is itself the finding — except for an archive built before 2026-10-07, which can carry an earlier checker than /verify/v1/ serves. base=https://tersign.ai/verify/sha256/e6847fef96410110f52ebe6e21c8caab6b750a90bf6eca4a4abe4fe731d96f1f OOB=$(mktemp -d) && curl -fsSL "$base/SHA256SUMS" -o "$OOB/SHA256SUMS" && ( cd "$OOB" && echo "${base##*/} SHA256SUMS" | shasum -a 256 -c ) && for f in verify_bundle.py keccak.py secp256k1.py; do curl -fsSL "$base/$f" -o "$OOB/$f"; done && ( cd "$OOB" && shasum -a 256 -c SHA256SUMS ) && python3 "$OOB/verify_bundle.py" --signer
&& diff -r -x SHA256SUMS "$OOB" /verify Each command runs only if the one before it succeeded: the fetched SHA256SUMS must hash to the address, and each file must match it. The fetch goes into the new directory mktemp -d makes, never into the bundle: a file written there is one the bundle's manifest does not list, and its closed file-set check fails on it. The last line prints nothing when the bundle's own verify/ is byte-identical to this copy; an archive built with another release differs by design, and its VERIFY.md names the release it was built with (archives whose VERIFY.md names /verify/v1/ fetch it there; /verify/v1/ serves release 2026-10-07). Python standard library only. No install, no account, and no network once fetched. The address from https://tersign.ai/v1/ledger is the production signer, for real records. A synthetic worked example is counter-signed with a published test key instead; its VERIFY.md names it. Without --signer the verdict is explicitly integrity-only: the bundle is internally consistent and tamper-evident, but the signer identity was read from the bundle itself. With --signer, the verdict names what it binds: every counter-signature and the anchor signature recover to that address. A --signer that is a published test key (the Hardhat/Anvil default dev-mnemonic accounts, private keys 1 to 3) binds nothing, since anyone can sign with it: the verdict stays integrity-only and a SIGNER line says so. The list is not the boundary: any key whose secret someone else knows binds nothing either, and no verifier can tell. The tool also prints an UNAUTHENTICATED line naming the fields nothing signs, the party keys among them unless --party binds them. It never opens the time-stamp proofs (proof.tsr, proof.ots): a PASS prints the anchor's methods as unverified claims and the commands that check the RFC 3161 token against FreeTSA's own CA certificate, fetched from FreeTSA and compared with a pinned SHA-256, never the copy in the archive, which its holder controls. Every line is printed with control and format characters escaped, and the bundle's unsigned claims on the TIME and UNAUTHENTICATED lines are printed quoted. A verdict is never stronger than its checks. 2624e161db0dbc41fd7ae8b5934d39602420cdf1f63b437154b1b2c4ccca6119 verify_bundle.py (51447 bytes) f3fa3c35a309cc7d205b09b52c883a7fa9511f831800c514d5274e100bd0e8ce keccak.py (3197 bytes) e300f067a0135cf62d07e426954ccf91edb317f94eb00fdcab6ef127430ff36c secp256k1.py (8248 bytes)