Docs
Verify a Proof Record yourself
A Pudding Proof Record is verifiable end-to-end without trusting Pudding. Every record's fingerprint is placed as a leaf in a Merkle tree; only the tree's root is timestamped by OpenTimestamps and, ultimately, the Bitcoin blockchain. This page tells you exactly how to walk that chain of hashes for any record.
What you need from the record page
- The original file (or the source URL).
- The record's SHA-256 fingerprint.
- The record's inclusion path (list of sibling hashes).
- The batch Merkle root.
- The batch
.otsproof file.
The three steps
- Hash the file. Compute
SHA-256of the file bytes and confirm it equals the record's fingerprint. If the record is of a link rather than a file, use the link recipe below instead. - Walk the inclusion path. Start with the 32-byte fingerprint. For each sibling in the path, concatenate the raw bytes in the recorded order. Sibling on the left means
sha256(sibling ‖ running), sibling on the right meanssha256(running ‖ sibling). The final result must equal the batch Merkle root. - Verify the batch
.otsproof. Download the batch's.otsfile from the record page and runots verify <file>.otsagainst a Bitcoin node (or a trusted block explorer). A successful verification proves the Merkle root existed at the anchored Bitcoin block height and, combined with step 2, that your file existed then too.
Single-record batches
When a batch contains only one record, that record's fingerprint is the batch's Merkle root, so there are no siblings to walk. The record page will say so explicitly and won't show an inclusion path. In that case, skip step 2: after hashing the file (step 1), confirm the fingerprint equals the Merkle root shown on the record page, then go straight to step 3 and ots verify the batch .ots against Bitcoin. The proof timestamps the fingerprint directly.
Records of a link
When someone registers a link instead of a file, the fingerprint is of the link itself: the SHA-256 of the text url: followed by the link, with spaces trimmed from both ends and every letter lowercased, encoded as UTF-8. Use the link exactly as the record page shows it. Compute that in place of step 1, then carry on with steps 2 and 3.
import hashlib
link = "<the link shown on the record page>"
fingerprint = hashlib.sha256(("url:" + link.strip().lower()).encode("utf-8")).hexdigest()
print(fingerprint) # must equal the record's fingerprintA match shows that this exact link was registered by that time. It says nothing about what the page at that link showed then, or shows now.
Reference walker (Python)
import hashlib, json
# 1. paste the record's inclusion path JSON
proof = json.loads("""{
"leaf": "<record fingerprint hex>",
"path": [
{ "hash": "<sibling hex>", "position": "left" },
{ "hash": "<sibling hex>", "position": "right" }
]
}""")
merkle_root_hex = "<batch merkle root hex>"
h = bytes.fromhex(proof["leaf"])
for step in proof["path"]:
s = bytes.fromhex(step["hash"])
pair = s + h if step["position"] == "left" else h + s
h = hashlib.sha256(pair).digest()
assert h.hex() == merkle_root_hex, "inclusion path does not reach the root"
print("root matches, now run: ots verify <batch>.ots")What each step proves
- Step 1 proves the file you have is the exact bytes that were registered, or, for a link record, that the link is the one that was registered.
- Step 2 proves that fingerprint was included in the batch whose root was timestamped.
- Step 3 proves that root existed no later than a specific Bitcoin block.
Questions? Get in touch.
