Manual Proof Verification
A TrustBeat proof bundle is self-contained. Every cryptographic claim inside it can be checked with standard tools — openssl and Python 3 — without calling the TrustBeat API or trusting this website.
Before you start — export your proof bundle
All data used in the steps below comes from a single proof bundle JSON file. To get it, open the verify page, enter your anchor ID, and click Export proof bundle. Save the downloaded trustbeat-proof-*.json as proof.json before running any command below.
The bundle contains everything: the anchored hash, Merkle path, RFC 3161 token, archive stamps, and — for NIS2 or AI Act anchors — the full domain context (log metadata or AI decision metadata).
What's in a proof bundle
| Field | Purpose |
|---|---|
| hash | SHA-256 of your original document (hex) |
| merkle_root | Root of the Merkle batch that was timestamped (hex) |
| proof_path | Sibling hashes needed to re-derive the root from your leaf |
| leaf_index | Position of your hash in the batch tree |
| token | RFC 3161 TimeStampToken from an EU Trusted List QTSP (base64 DER) |
| tsa_serial | Serial number of the TSA token — cross-check after decoding |
| anchored_at | ISO-8601 timestamp from the TSA |
| archive_stamps | Subsequent re-stamps for long-term validity (may be empty) |
| log_context | NIS2 only: original log hash + metadata + combined hash |
| ai_context | AI Act only: input/output hashes + decision metadata + combined hash |
Verify your original data
Confirm what was anchored matches what you have. The method differs by anchor type.
Raw anchor — bundle_type: trustbeat.anchor.proof
SHA-256 your original file and compare to bundle["hash"]. Any difference means the document was modified after anchoring.
sha256sum your-document.pdf # macOS alternative: shasum -a 256 your-document.pdf
Get-FileHash your-document.pdf -Algorithm SHA256
NIS2 log anchor — bundle_type: trustbeat.log.proof
The Merkle leaf is a combined hash, not the raw log hash. Compare bundle["log_context"]["log_hash"] against the SHA-256 of your original log file. bundle["hash"] holds the combined hash — verified in Step 5.
import hashlib
print(hashlib.sha256(open("your-logfile.log","rb").read()).hexdigest())
# compare to bundle["log_context"]["log_hash"]AI Act anchor — bundle_type: trustbeat.ai.proof
Two hashes are anchored: the model input and the model output/decision. Compare each against bundle["ai_context"]["input_hash"] and bundle["ai_context"]["output_hash"]. bundle["hash"] holds the combined hash — verified in Step 6.
import hashlib, json
bundle = json.load(open("proof.json"))
ac = bundle["ai_context"]
print("input_hash :", hashlib.sha256(open("model-input.bin","rb").read()).hexdigest())
print("expected :", ac["input_hash"])
print("output_hash:", hashlib.sha256(open("model-output.bin","rb").read()).hexdigest())
print("expected :", ac["output_hash"])proof.json — nothing is fetched from the internet.Re-derive the Merkle root
Proves your hash was included in the batch that was timestamped.
Start with your hash as the current value. For each step in proof_path:
- side = "left"→
current = SHA-256(sibling ‖ current) - side = "right"→
current = SHA-256(current ‖ sibling)
The final value must equal merkle_root.
import hashlib, json, sys
bundle = json.load(open("proof.json"))
current = bytes.fromhex(bundle["hash"])
for step in bundle["proof_path"]:
sibling = bytes.fromhex(step["sibling"])
if step["side"] == "left":
combined = sibling + current
else:
combined = current + sibling
current = hashlib.sha256(combined).digest()
computed = current.hex()
expected = bundle["merkle_root"]
if computed == expected:
print("✓ Merkle root OK —", computed)
else:
print("✗ MISMATCH")
print(" computed:", computed)
print(" expected:", expected)
sys.exit(1)Verify the RFC 3161 timestamp token
The token was issued by an EU Trusted List QTSP and covers the Merkle root.
The token field is a base64-encoded DER-encoded RFC 3161 TimeStampToken. It binds the merkle_root to a legally presumed timestamp under eIDAS Art. 42.
3a — Decode the token to a file
import base64, json
bundle = json.load(open("proof.json"))
open("token.der", "wb").write(base64.b64decode(bundle["token"]))
print("token.der written")3b — Inspect what the token contains
openssl ts -reply -in token.der -text
Look for Message data — this is the SHA-256 of the merkle_root (TSAs hash the data before signing). Cross-check the Serial Number against bundle["tsa_serial"].
3c — Verify the token signature
Download the TSA certificate chain from the QTSP's trust repository (link in the proof bundle), then:
# Derive the binary digest of the Merkle root that the TSA signed
python3 -c "
import hashlib, json
r = json.load(open('proof.json'))['merkle_root']
open('root.bin','wb').write(hashlib.sha256(bytes.fromhex(r)).digest())
"
# Verify the token signature against the SK TSA certificate chain
openssl ts -verify \
-in token.der \
-digest root.bin \
-CAfile sk-tsa-chain.pemSHA-256(merkle_root_bytes) to the TSA. The token's MessageImprint contains exactly that value.Verify archive stamps (long-term validity)
Each archive stamp extends validity before the previous TSA certificate expires.
The archive_stamps array may be empty for recently anchored proofs. When present, each stamp is an independent RFC 3161 token that covers the covered_hash of the previous round — forming a chain that keeps the proof valid for 30+ years (CAdES-A pattern).
import base64, json
bundle = json.load(open("proof.json"))
for i, stamp in enumerate(bundle.get("archive_stamps", [])):
fname = f"archive_{i+1}.der"
open(fname, "wb").write(base64.b64decode(stamp["token"]))
print(f"Round {stamp['round']} → {fname} (covers {stamp['covered_hash'][:16]}…)")Verify each extracted archive_N.der the same way as Step 3, using the stamp's own covered_hash as the data digest.
Verification checklist
| Step | What you verify | Tool |
|---|---|---|
| 1 | file SHA-256 == bundle.hash | sha256sum / shasum / PowerShell |
| 2 | Merkle path re-derives merkle_root | Python 3 (stdlib only) |
| 3a | token decodes to valid DER | Python 3 |
| 3b | token covers merkle_root | openssl ts -reply -text |
| 3c | token signature valid (SK TSA chain) | openssl ts -verify |
| 4 | archive stamps chain correctly | openssl ts -verify (per stamp) |
| 5* | NIS2 combined hash formula | Python 3 — only for log proofs |
| 6* | AI Act combined hash formula | Python 3 — only for AI Act proofs |
* Step 5 applies only to bundle_type = trustbeat.log.proof · Step 6 applies only to bundle_type = trustbeat.ai.proof
Powered by TrustBeat · eIDAS · RFC 3161 · Verify online · Inspect token