Here's the question worth asking about your own setup, and I don't think most of us do: when your verification step prints PASS, which part of that did your machine actually compute, and which part did it take somebody's word for?
Real question. There's a real answer, and you can get it today without reading a spec.
I'll use blockchain timestamp proofs as the example, because the trust boundary is unusually easy to see there. Same reasoning applies to a signature checker, a provenance attestation, a license validator, anything shaped like "external party says yes."
What a timestamp proof has to contain
The claim is narrow. This hash existed no later than this point in time. That's it.
For you to check that claim yourself, the proof file has to carry enough material to rebuild it from scratch: the leaf hash, the sibling hashes along the Merkle path, which side each sibling sits on, the resulting root, and where that root got anchored.
The check is a fold. In the abstract:
def fold(leaf, path):
node = leaf
for sibling, side in path:
if side == "left":
node = sha256(sibling + node)
else:
node = sha256(node + sibling)
return node
# then: fold(sha256(file_bytes), path) == claimed_root
That's the whole mechanism. You hash the file, you walk up the tree combining with each recorded sibling, and you land on a root. If the root you computed matches the root in the proof, the file's hash was genuinely in that tree.
Two places this goes wrong in practice, and both are worth knowing because they look like "the proof is bad" when actually your reader is bad.
Ordering. sha256(a + b) and sha256(b + a) are different values. Some schemes record which side each sibling goes on. Some sort the pair and skip recording it. Both work, neither is wrong, but the proof has to tell you which one it is, or you can't reproduce the root and you'll never know whether you got the math wrong or the issuer lied.
Encoding. Hashes travel as hex strings in JSON and get combined as raw digest bytes. Hash the hex string of a sibling instead of decoding it first and you get a root that's wrong in a way that looks exactly like tampering. If you've ever written a verifier from scratch, you've hit this.
Neither of those is exotic. They're just the kind of detail that gets skipped when someone hands you a green checkmark instead of a computation.
The obvious answer, and where it runs out
Obvious answer: the vendor gives you a verification page or a /verify endpoint. Paste the proof, get a green check.
That check is worth nothing. Not because the vendor is lying, but because the endpoint could return {"valid": true} unconditionally and you would not be able to tell. You're asking the party who issued the claim whether the claim is good. If the issuer is the thing you're protecting against, and in a dispute it usually is, this is circular.
Fine. So you go get an open-source CLI instead.
Here's the part that surprised me the first time I looked closely: open source doesn't answer the question either. Plenty of tools labeled "verifier" are thin API clients. They read your proof file, POST it somewhere, and pretty-print the response. The source being public tells you that's what's happening only if you go read it. The word "verify" in the command name tells you nothing at all. It's the same circular check with a nicer interface, and now it feels local because it's running in your terminal.
So the distinction that matters isn't open versus closed, or CLI versus web. It's whether the arithmetic happens on your machine.
The test
Turn off your network. Run the verify command.
If it still gives you a verdict, the math ran locally and you now know it. If it errors out about a connection, you were never verifying anything. You were asking.
That's it. That's the whole test, and it takes less effort than reading the source, though you should eventually do that too.
The follow-on test is for the commands that legitimately do need the network. Watch what they send. Anchoring a new hash has to talk to something, that's unavoidable, but what crosses the wire should be the digest and never the file. You can see this in a local proxy, or by reading the request construction in the source. If a tool uploads the document to timestamp it, the privacy story is gone no matter what the README says.
As a concrete example of the shape I'm describing: verify-proof is a free MIT-licensed Python tool published by Fulcrum Enterprises LLC. pip install verify-proof, three subcommands, hash to SHA-256 a file, verify to check a proof against the hash and its Merkle path, and create to anchor a hash through ProofLedger. Verification recomputes the hash and walks the path itself rather than asking an API whether the proof is good, so it passes the unplug test. No key, no account. Only create uses the network, and it sends the hash, not the file. It handles proofs anchored to Polygon and to Bitcoin, including legacy ones from the retired ProofAnchor service.
There's also an optional MCP server, pip install verify-proof[mcp], built on FastMCP, exposing five tools so you can verify or create inside a conversation in Claude Desktop or Cursor. Worth being honest about the tradeoff there: the computation is still local, but you're reading a model's summary of the result instead of an exit code. Convenient for poking at a pile of proofs. For anything you'd actually rely on, run the CLI and look at the output with your own eyes.
What a local pass still doesn't buy you
Two limits, and they're not small.
A clean local verification proves your file's hash was in a tree with that root. It does not prove the root ever hit the chain. That part you check yourself, by taking the transaction reference out of the proof and looking it up on a block explorer. No tool should be the only thing telling you that, including a local one.
And the claim stays narrow the whole way through. A verified proof says a particular hash existed by a particular time. It says nothing about who made the file, whether the contents are true, or whether anyone had authority to sign it. If someone tells you a timestamp proves authenticity, they've widened the claim past what the math supports.
Do this today
Grab a proof you already have from whatever service issued it. Disconnect, run the verify, see what happens. Then reconnect, pull the anchor transaction out of the proof file, and look it up on a block explorer without the vendor's help. Both of those together tell you whether you're holding evidence or a receipt.
If you want to look at how a local verifier is put together, the source is at https://github.com/Fulcrum-Enterprises/verify-proof.
Top comments (0)