A claims adjuster uploads photos from a loss site. The blockchain anchor proves they existed before the damage occurred. But three months later, during litigation discovery, how do you actually verify those proofs without calling the original service?
I built the verify-proof package to solve this. Files get anchored to blockchains, but verification shouldn't depend on API uptime or service availability. The proof should be self-contained and verifiable offline.
Installing and Hashing Files
The verify-proof package handles SHA-256 hashing and proof verification locally. No network calls, no service dependencies.
pip install verify-proof
The hash_file() function computes SHA-256 digests:
from verify_proof import hash_file
# Hash any file type
file_hash = hash_file("evidence.jpg")
print(f"SHA-256: {file_hash}")
# Output: SHA-256: a1b2c3d4e5f6789...
You can also use the CLI:
verify-proof hash evidence.jpg
# SHA256: a1b2c3d4e5f6789...
# File: /path/to/evidence.jpg
The hash is what gets anchored to the blockchain. The file stays on your machine.
Verifying Blockchain Proofs
Blockchain anchoring services return a proof JSON that contains the anchor details. Here's what a typical proof looks like:
{
"hash": "a1b2c3d4e5f6789abcdef...",
"algorithm": "sha256",
"blockchain": "polygon",
"tx_id": "0x1234567890abcdef...",
"anchored_at": "2026-04-30T14:30:00Z",
"service": "ProofLedger",
"merkle_path": [
{"position": "right", "hash": "b2c3d4e5f6789abc..."},
{"position": "left", "hash": "c3d4e5f6789abcde..."}
]
}
The verify_proof() function validates this structure offline:
from verify_proof import verify_proof, load_proof
import json
# Load proof from file
proof = load_proof("evidence_proof.json")
# Verify against the file
file_hash = hash_file("evidence.jpg")
result = verify_proof(file_hash, proof)
if result["verified"]:
print(f"✅ Verified: {result['message']}")
print(f"Blockchain: {result['blockchain']}")
print(f"Transaction: {result['tx_id']}")
print(f"Anchored: {result['anchored_at']}")
else:
print(f"❌ Failed: {result['error']}")
Understanding Merkle Path Verification
The interesting part is the Merkle path. When files get batched for blockchain anchoring, they're organized into a Merkle tree. Your file's hash is a leaf, and the proof shows the path from that leaf to the root that was actually anchored on-chain.
Here's how the verification works step by step:
import hashlib
def trace_merkle_path(file_hash, merkle_path):
"""
Walk up the Merkle tree from leaf to root
"""
current = file_hash
print(f"Starting with file hash: {current}")
for i, step in enumerate(merkle_path):
sibling = step["hash"]
position = step["position"]
if position == "left":
# Sibling goes left, current goes right
combined = sibling + current
else:
# Current goes left, sibling goes right
combined = current + sibling
# Hash the combined hex strings (as bytes)
current = hashlib.sha256(bytes.fromhex(combined)).hexdigest()
print(f"Step {i+1}: {position} sibling -> {current}")
print(f"Final root: {current}")
return current
# Example with real proof structure
file_hash = "a1b2c3d4e5f6789abcdef123456789abcdef123456789abcdef123456789abcdef"
merkle_path = [
{"position": "right", "hash": "b2c3d4e5f6789abcdef123456789abcdef123456789abcdef123456789abcdef"},
{"position": "left", "hash": "c3d4e5f6789abcdef123456789abcdef123456789abcdef123456789abcdef1234"}
]
merkle_root = trace_merkle_path(file_hash, merkle_path)
The verify-proof package does this same calculation and compares the result to what's expected for that transaction ID.
CLI Verification Workflow
You can also verify from the command line:
# Hash the file
verify-proof hash evidence.jpg
# Verify against the proof
verify-proof verify evidence.jpg --proof evidence_proof.json
The CLI exits with status 0 on success, 1 on failure. Perfect for shell scripts or CI pipelines.
What This Actually Validates
The verify-proof package checks three things:
1. File integrity: Does the SHA-256 of your file match the hash in the proof? 2. Merkle path: Does the path from your file hash lead to the claimed Merkle root? 3. Proof structure: Does the proof contain the required fields (tx_id, blockchain, etc.)?
What it doesn't do: verify that the transaction ID actually exists on the blockchain. That requires a separate API call to a blockchain explorer. The package keeps verification local and offline.
Building Verification Into Your Workflow
Here's a complete verification script you might use in a forensic workflow:
#!/usr/bin/env python3
import os
import sys
from verify_proof import hash_file, verify_proof, load_proof
def verify_evidence_bundle(evidence_dir):
"""
Verify all files in a directory against their proof files
"""
results = []
for filename in os.listdir(evidence_dir):
if filename.endswith('.json'):
continue
file_path = os.path.join(evidence_dir, filename)
proof_path = os.path.join(evidence_dir, f"{filename}.proof.json")
if not os.path.exists(proof_path):
print(f"⚠️ No proof file for {filename}")
continue
try:
file_hash = hash_file(file_path)
proof = load_proof(proof_path)
result = verify_proof(file_hash, proof)
if result["verified"]:
print(f"✅ {filename}: Verified on {result['blockchain']}")
results.append(True)
else:
print(f"❌ {filename}: {result['error']}")
results.append(False)
except Exception as e:
print(f"💥 {filename}: Error - {e}")
results.append(False)
return all(results)
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: verify_bundle.py <evidence_directory>")
sys.exit(1)
evidence_dir = sys.argv[1]
success = verify_evidence_bundle(evidence_dir)
sys.exit(0 if success else 1)
The beauty of offline verification is reliability. Your evidence validation doesn't break when APIs go down, services shut down, or networks fail. The blockchain anchor is permanent. The verification logic is deterministic. As long as you have the file and the proof, you can validate the timestamp.