Anchoring
The registry’s whole value is a date. And until you can check that date without asking us, it is our word.
So every day the registry publishes a receipt: a short text file with a cryptographic summary of every version of every record it holds, and of every version of every agent’s instructions. That receipt is stamped into the Bitcoin blockchain through OpenTimestamps, and independently by two RFC 3161 timestamp authorities. None of the three is run by us.
From then on the date is not ours to move. If we changed a registration date tomorrow, yesterday’s receipt would stop matching, and yesterday’s receipt is already outside our reach.
What this proves, and what it does not
Section titled “What this proves, and what it does not”| It proves | It does not prove |
|---|---|
| That this data existed before a specific Bitcoin block | That whoever registered is the rightful owner |
| That its content was exactly that on that date | That the declared registration date is true, only that it existed before the anchor |
| That later edits did not erase the history | Anything about records created after the most recent anchor |
Anchors are daily, so between registering and anchoring there is a window of up to 24 hours in which you still have to take our word for it. We would rather say so than let you find out.
The receipt
Section titled “The receipt”A plain text file, and these bytes are the artefact: not a rendering of it, the thing itself. One trailing space would change its digest and break every proof.
TheHumanBehind registry anchor
anchor_version: 2
recipe_version: 3
cutoff_utc: 2026-08-11T03:45:00.000000Z
records: 101
versions: 128
agent_versions: 14
merkle_root: 3a02599887336f2ab822d678b50edb05bad62eff392ba826093960faaaa543cb
prev_receipt_sha256: genesis
recipe: https://thehumanbehind.com/docs/anchoring
prev_receipt_sha256 is the SHA-256 of the previous day’s receipt, which chains
the series: a receipt cannot be replaced without breaking every one that came
after it. The first receipt of all says genesis.
records counts distinct registry codes, versions counts versions of record
content, and agent_versions counts versions of agent instructions. The total
number of leaves in the tree is versions + agent_versions, which is what you
need to know how many leaves there should have been.
Two receipt formats, both alive
Section titled “Two receipt formats, both alive”Anchors from 5 to 10 August 2026 carry format 1, which has no
recipe_version and no agent_versions line and says anchor_version: 1. It is
kept byte for byte because those receipts are already stamped.
Every line, in both formats, is key: value, one per line, and is meant to be
read by prefix. A reader written for format 1 finds everything it knew about
inside a format 2 receipt; it simply ignores two lines it has never seen. If you
parse receipts by line number instead, this is the change that will break you.
recipe_version moved INSIDE the stamped bytes on purpose. Until format 2 it
lived only in a column of our database. Inside the receipt, it is Bitcoin that
seals it.
Every receipt is served forever at
{SITE.platformUrl}/api/v0/anchors/{"{date}"}.txt, and the list of anchors, with
their Bitcoin block, at {SITE.platformUrl}/api/v0/anchors.json.
The recipe
Section titled “The recipe”This is a contract. A proof stamped today has to verify in ten years, so none of what follows can ever change. If a new version is ever needed, it gets its own prefix and the previous one keeps working forever, so no proof is ever orphaned.
Three recipes have existed, and all three are alive. Every anchor publishes its
own recipe_version in /api/v0/anchors.json and is verified with the recipe it
was sealed with.
| Recipe | Anchors | Leaf preimage |
|---|---|---|
| v1 | only 5 Aug 2026 | THB-ANCHOR-v1|<registry_code>|<version>|<content_hash> |
| v2 | 6 to 10 Aug 2026 | THB-ANCHOR-v2|<registry_code>|<version>|<content_hash>|<leaf_salt> |
| v3 | from 11 Aug 2026 | THB-ANCHOR-v3|<kind>|<registry_code>|<version>|<digest>|<leaf_salt> |
1 · The leaf
Section titled “1 · The leaf”Since v3 a leaf can attest to two different things, and it says which one in field 2, before any other data:
THB-ANCHOR-v3|record|<registry_code>|<version>|<content_hash>|<leaf_salt>
THB-ANCHOR-v3|agent|<registry_code>|<version>|<agent_sha256>|<leaf_salt>
The leaf is the SHA-256 of those bytes, UTF-8.
A record leaf attests that a record’s public content was exactly that at
that version. An agent leaf attests that a set of agent instructions
existed and was exactly those, at that version.
The kind is in there because without it a proof of instructions would be indistinguishable from a proof of record content, and anyone could present the first as if it were the second. It sits in field 2, ahead of every value that comes from a user, so two leaves of different kinds always differ no matter what the rest holds.
The text of the instructions never enters the leaf. Its hash does. So the proof survives even if that text one day has to be withdrawn for containing personal data: the digest, the date, the version and the salt stay, and the inclusion path keeps verifying.
Every leaf commits to the registry code, not to any internal identifier, for two reasons: internal identifiers have no business leaving the database, and you have to be able to recompute your own leaf from what you already hold. All the values are in your record’s downloadable proof.
The version number is in there because without it two versions of the same record with identical content would collapse into a single leaf.
leaf_salt is 16 random bytes, in lowercase hex, and it is what makes this
scheme private. Each version gets its own, it never leaves our server, and you
receive yours, and only yours, inside your proof.
It is there because a hash only hides its input when that input is hard to guess. Registry codes are sequential and a record has few fields, so an unblinded leaf would be a commitment over low-entropy data, and those can be attacked by trying candidates until one matches. The 16 random bytes remove that possibility: there is nothing left to try. It matters more for instructions than for record content: prompt templates repeat between clients, so unblinded they would be very guessable indeed.
2 · The order
Section titled “2 · The order”Ascending by registry_code, then by the kind’s rank (record = 0,
agent = 1), then by version as a number.
The rank is a number and not the word, deliberately: agent sorts before
record alphabetically, so ordering by the literal would put a record’s
instructions ahead of its content, and the published order would depend on how
the kinds happen to be spelled.
So a record with two content versions and two instruction versions contributes, in this order:
THB-2026-00144 · record · 1
THB-2026-00144 · record · 2
THB-2026-00144 · agent · 1
THB-2026-00144 · agent · 2
Versions are compared as numbers: sorted as text, version 10 would land between 1 and 2.
Registry codes are compared as text, by their bytes. Until 31 July 2026 the
number was fixed width and that was the same as comparing numbers; it no longer
is, because the width became a minimum rather than a fixed size, so past 99.999
records in one year THB-2026-100000 sorts before THB-2026-99999. That is
fine: the rule is byte order on the code, which is well defined and reproducible
by anyone. This page used to claim the two orders were equivalent, and that
sentence was wrong.
3 · The tree
Section titled “3 · The tree”A binary Merkle tree. Each interior node is the SHA-256 of its two children concatenated as raw bytes, not as hexadecimal text.
When a level holds an odd number of nodes, the last one is promoted unchanged to the next level. It is not duplicated. Duplicating it is what Bitcoin does, and it is what made CVE-2012-2459 possible: with duplication, two different sets of leaves can produce the same root, which in a registry would mean two different histories behind one proof.
The root of the top level is the merkle_root in the receipt.
4 · Your inclusion path
Section titled “4 · Your inclusion path”Your record’s proof file gives you the siblings needed to climb from your leaf to
the root, each with the side it goes on. Start from your leaf, and at each step
hash the pair in the stated order. If you land on the receipt’s merkle_root,
your record was in that anchor.
The same code verifies both kinds of leaf, because the only difference is what
the preimage says. If your record is an agent, anchor.agent_instructions in
your proof carries one entry per anchored version of your instructions, each with
its own preimage, its own salt and its own path, all climbing to the same root.
import hashlib
# Straight from your proof.json: `leaf_preimage` comes already joined, so there
# is nothing to assemble. This one is an instructions leaf.
preimage = "THB-ANCHOR-v3|agent|THB-2026-00144|2|0acb5e6a…50|5051…5f"
path = [ # also from your proof.json, in order
("b1ef4d226bd74200ffdcc71b40b0b361280c96c5005fab3c3e8cb511e4b0044e", "right"),
# ...
]
current = hashlib.sha256(preimage.encode()).digest()
for sibling_hex, side in path:
sibling = bytes.fromhex(sibling_hex)
pair = sibling + current if side == "left" else current + sibling
current = hashlib.sha256(pair).digest()
print(current.hex()) # must equal the merkle_root in that day's receipt
Checking one version
Section titled “Checking one version”Everything above proves where a record stands today. What a claim usually turns on is what it said before, and when it changed. Both are kept, and both are anchored.
There are two series, not one
Section titled “There are two series, not one”A record has two append-only ledgers, and each one produces its own leaves:
| Series | One version each time… | Leaf kind |
|---|---|---|
| Content | any of the eight sealed fields changes | record |
| Instructions | an agent’s instructions are replaced | agent |
A version’s leaf is the same recipe you already read, with that version’s number in field 4. Nothing special happens for old versions: version 1 of a record is a leaf of the tree in exactly the way version 9 is, and it stays in every anchor from the day it was written onwards.
Recomputing an old version’s hash, with nothing but a browser
Section titled “Recomputing an old version’s hash, with nothing but a browser”The public history of a verified record is open:
curl {SITE.platformUrl}/api/v0/records/laia-torres/history.json
Every entry of the content series carries the exact snapshot that was hashed.
Hash it the way verifying a seal describes and you
must get that version’s content_hash back. No account, no permission, nothing
to take on trust.
The instruction series publishes how many versions there have been and when, and nothing else. Not the text, which is the author’s, and not its fingerprint either: agent instructions are often templates that repeat between clients, so a published digest would let anyone holding a candidate confirm that this record uses it. That is a confirmation oracle, and the whole reason the v1 recipe had to be replaced. The digest travels to the record’s owner, inside their own proof, and nowhere else: the database does not hand that column to anonymous readers either, because a promise the database does not keep is not a promise.
Following one version all the way to Bitcoin
Section titled “Following one version all the way to Bitcoin”For that you need the leaf, and the leaf needs the salt, and the salts are never public — if they were, blinding would buy nothing and the attack that killed v1 would be back.
So the complete path lives in the owner’s proof.json, which now carries one
entry per version, in two places that mirror the two series:
- Content versions are in
history.versions, each with thesnapshotthat was hashed and aleafobject holding thepreimagealready assembled, the leafhashand theinclusion_path. - Instruction versions are in
anchor.agent_instructions.versions, each with itsleaf_preimage, itsleafand itsinclusion_path. Their entries inhistory.versionscarryleaf: nulland aproof_inpointing here, so the same proof is not written down twice.
That file is the owner’s to publish wherever they like, and whoever receives it
needs nothing from us to check it: recompute each leaf from its preimage, walk
its path, land on the anchor’s Merkle root, and check that root against the
receipt and the .ots, both public. The only thing you take from anyone is the
file itself.
Versions written after the last anchor come back as pending_next_anchor with no
path, because they genuinely are not in that tree yet. Their salt and preimage
are handed over anyway, so they can be kept today and verified against tomorrow’s
anchor.
Verifying the anchor itself
Section titled “Verifying the anchor itself”Two ways. Use the first if you want it over in a minute, the second if you want it to work in twenty years.
The easy way
Section titled “The easy way”Download the receipt and its proof:
curl -O {SITE.platformUrl}/api/v0/anchors/2026-08-06.txt
curl -O {SITE.platformUrl}/api/v0/anchors/2026-08-06.ots
Drop both files on opentimestamps.org. It will tell you which Bitcoin block contains that receipt. The hash is computed in your browser; the file never leaves your machine.
The way that outlives the tools
Section titled “The way that outlives the tools”That website runs on a JavaScript library that has not had a functional commit since 2022. So the anchor list also publishes, for every anchor, the Bitcoin block height and that block’s Merkle root. Look the block up in any explorer, compare the Merkle root, and you are done. No tooling, no library, no website that has to still exist.
With the OpenTimestamps client installed, the same two values come from:
ots --no-bitcoin verify 2026-08-06.txt.ots
The RFC 3161 tokens
Section titled “The RFC 3161 tokens”The same receipt, stamped by two independent authorities. They verify instantly, with no blockchain and no waiting:
curl -O "{SITE.platformUrl}/api/v0/anchors/2026-08-06.tsr?tsa=freetsa"
openssl ts -verify -data 2026-08-06.txt -in 2026-08-06.tsr -CAfile cacert.pem
They exist alongside the Bitcoin anchor, not instead of it, because the two fail in opposite ways. If the OpenTimestamps calendars vanished before a pending proof was completed, the tokens would still stand. If an authority shut down or its key were compromised, Bitcoin would still stand.
Why it takes hours to confirm
Section titled “Why it takes hours to confirm”OpenTimestamps does not put one transaction on the chain per file. Calendars
collect thousands of digests, build one tree, and publish a single root. So a
fresh proof says pending for a few hours until that transaction confirms, and
the registry upgrades it automatically once it does. confirmed: false in the
anchor list is simply a proof on its way: the normal state for part of the day.
Privacy
Section titled “Privacy”The calendars never see your data. Before anything is sent, the client appends 16 random bytes to the digest and hashes it again, so what travels is an opaque value from which neither the receipt nor its hash can be recovered.
The receipt itself contains no personal data either: only counts and one root.
And the siblings in your inclusion path reveal nothing about the records they
belong to, because each leaf is blinded with its own salt. That is the whole
job of leaf_salt: without 128 bits of randomness that exist nowhere public,
there is no candidate to test against a sibling, so a path proves your record is
in the tree without saying anything about anyone else’s.
What the counts do disclose, and we would rather say it
Section titled “What the counts do disclose, and we would rather say it”The receipt publishes how many records, how many content versions and how many instruction versions the registry held that day. Registry codes are sequential and public, so the totals were never a secret, and with blinded leaves a count confirms nothing about any particular record.
But while the registry is small, a change in agent_versions from one day to the
next says that somebody edited their agent’s instructions that day. Not who,
not what: the text stays private and the hash stays blinded. The fact and the
date of an edit are public. If that matters to you, it is worth knowing before
you register, not after.