Reading an .ots file by hand — a forensic walkthrough
You can verify an OpenTimestamps proof file with the official CLI in one command. But if you've ever wanted to look inside an .ots file and confirm the math is real — not just a logo on a website that claims to verify it — this post walks through the bytes one at a time.
Useful if you're:
- A journalist verifying a tipster's anchored evidence
- A forensic analyst on a disputed-image case
- A photographer who wants to see exactly what you're paying for
- An auditor confirming Orphograph's claims aren't marketing
- Curious about how Bitcoin-anchored timestamping actually works
The walkthrough below dissects the sample receipt that ships with the Orphograph open-source verifier. Follow along by downloading it:
curl -O https://orphograph.com/verify/examples/sample/a.ots
That is a real .ots file from a real anchor made on 2026-05-12. Read on for what is inside it.
The file format
OpenTimestamps files are binary. They follow a documented format (spec on github.com/opentimestamps) that's deliberately simple — three sections:
1. Magic header (15 bytes) — identifies the file as an OTS proof
2. The hash being attested (SHA-256, 32 bytes)
3. The merkle path from that hash up to a Bitcoin transaction (variable length; depends on tree depth)
The first two are trivial to read. The third is where the cryptographic magic happens.
Section 1 — the magic header
Open the file in xxd or any hex viewer:
xxd a.ots | head -1
The first 18 bytes are:
00 4f 70 65 6e 54 69 6d 65 73 74 61 6d 70 73 00 00 50
Decode that as ASCII:
.OpenTimestamps..P
Specifically:
- Byte 0:
\x00(null byte — explicit "this is a binary file" marker) - Bytes 1–14:
OpenTimestamps(literal ASCII) - Bytes 15–16:
\x00\x00(separator) - Byte 17:
P(start of "Proof" string)
The full magic constant is:
\x00OpenTimestamps\x00\x00Proof\x00\xbf\x89\xe2\xe8\x84\xe8\x92\x94
That last 8-byte sequence (\xbf\x89...) is a version + format identifier — it tells parsers exactly which OTS spec version this file conforms to. As of 2026, there's only one version in the wild.
If a file claims to be an .ots proof but does not start with this exact sequence, it is not one. The Orphograph open-source verifier checks for exactly this magic before doing anything else.
Section 2 — the hash being attested
Byte 25 of the file is the SHA-256 tag (\x08), followed by 32 bytes — the actual SHA-256 hash of the file you anchored.
For the sample receipt, that hash is:
7accf9e90453280e6fb081fd9d83dfb1eeef3bd64e0d680826989ba79bccac88
You can verify by computing the SHA-256 of the sample's original file:
curl -O https://orphograph.com/verify/examples/sample/sample.txt
shasum -a 256 sample.txt
# 7accf9e90453280e6fb081fd9d83dfb1eeef3bd64e0d680826989ba79bccac88
That's a real hash of a real file matching what's embedded in the .ots proof. No mystery.
This is the input to the OTS proof. The .ots file's job is to prove that this 32-byte fingerprint existed in Bitcoin's blockchain at a specific time.
Section 3 — the merkle path
Everything after byte 57 is the merkle path. This is where the proof structure lives.
Each step in the path is one of:
- SHA-256 op (
\x08) — "hash whatever's accumulated so far with SHA-256" - Append (
\xf0 <length> <bytes>) — concatenate these bytes to the accumulator. - Prepend (
\xf1 <length> <bytes>) — insert these bytes before the accumulator. - Calendar attestation (
\x83\xdf\xe3\x0d\x2e\xf9\x0c\x8e <length> <calendar-URL>) — "this hash got submitted to this calendar; ask them for the upgraded proof" - Bitcoin block attestation (
\x05\x88\x96\x0d\x73\xd7\x19\x01 <varint-block-height>) — "this hash is incorporated in the Bitcoin block at height N"
Walking the path means: start with your file's hash, follow each op, accumulating values, until you arrive at a Bitcoin block header's merkle root.
For the sample file, the calendar attestation reads:
.-https://alice.btc.calendar.opentimestamps.org
That is the protocol saying: this hash was submitted to alice's calendar. For the full Bitcoin merkle proof, ask that calendar for the upgraded version.
When the calendar batches your hash with thousands of others into a Bitcoin transaction (which it does roughly once per hour), the upgraded .ots file gets extended with:
1. The merkle path WITHIN the calendar's batch (from your hash to their root)
2. The Bitcoin transaction ID containing that root
3. The Bitcoin block height where that transaction landed
4. The merkle path WITHIN that block (from the calendar's root transaction up to the block header's merkle root field)
After that "upgrade" runs, the .ots file is self-sufficient — no calendar is needed to verify, just the public Bitcoin chain.
You can trigger the upgrade yourself:
pip install opentimestamps-client
ots upgrade a.ots
Then xxd a.ots | tail will show the new bytes that got appended.
Section 4 — what the math actually proves
Once an .ots file has been upgraded to include the Bitcoin block attestation, here's what its math proves:
Given:
- The file you originally anchored (you have this)
- The .ots proof file (you have this)
- The public Bitcoin blockchain (anyone has this)
You can recompute, deterministically:
1. SHA-256(your file) → must equal the hash in the .ots
2. Walking the merkle path in the .ots → must equal the merkle root of the Bitcoin block at the specified height
3. The Bitcoin block at that height — must be in the longest chain. Check this against any Bitcoin node or any public block explorer.
If all three are true, the proof holds. The file existed at the timestamp of that Bitcoin block. To forge the proof, an attacker would need to:
- Find a SHA-256 collision — 2^128 operations under Grover's algorithm; not done in public, classically.
- And rewrite the Bitcoin block that contains the attestation.
- And rewrite every Bitcoin block after it to maintain the longest-chain property.
The economics of that attack are well-documented. As of 2026, attacking even a single Bitcoin block from years ago would require sustained 51% hash-rate dominance for the entire interval since — measured in tens of billions of dollars of mining infrastructure running flat-out for years.
That's the security backing every .ots proof.
Reading the sample, end-to-end
For the sample file:
1. Magic header — matches the OTS magic constant.
2. Hash — matches SHA-256 of sample.txt.
3. Merkle path — ends with a calendar attestation. The sample was anchored less than an hour before this post was written; the Bitcoin upgrade had not completed at write-time.
4. After ots upgrade — the file gains the full Bitcoin block attestation.
Run the verifier:
python3 verify.py examples/sample/receipt.json --file examples/sample/sample.txt
The expected output is five [OK] lines for the five calendars, plus a "file hash matches" line. The verifier is roughly 100 lines of Python; readers who do not take the claims on faith can read the source directly in verify.py in the same tarball.
Why this matters
Most digital-evidence systems sell trust. Orphograph sells math anyone can verify. No party needs to take the office's word for anything. The .ots file is auditable, the verifier is open source, the protocol is public, and the underlying Bitcoin chain is replicated across the internet.
If anything on the verification path ever stops adding up — the file's hash, the merkle path, the Bitcoin chain — the cryptographic contract is broken and the office wants to know. The security disclosure address is in the README; PGP keys are available on request.
For nearly all users, "click verify" is enough. For the minority who need to confirm the math is real, this post is a starting point.
Orphograph is a Bitcoin file-timestamping service. Three free anchors every 24 hours. Pack of Fifty: $29 one-time, credits never expire. Standing Order: $9/month for unlimited. Open-source verifier at /verify/. Receipts verify against any Bitcoin node, independent of Orphograph. Related: why five calendars and not one. Method overview: the verifier source, annotated.
Published 2026-05-17. More from the blog.
Start here: What this office actually does.
Disclaimer. The office is not a law firm, not a qualified electronic-trust-service provider, and not a financial advisor. The summaries above describe a technical method in plain English; they are not legal advice and do not establish an attorney–client relationship. The doctrine in any particular jurisdiction may differ from the broad sketch above, and statutes are amended over time. A customer with an actual dispute should consult counsel admitted in the relevant jurisdiction.