At the end of every post sit the last modified time and a content hash (a
string starting with sha256:). They serve one precise purpose: letting anyone who
quotes a post notice whether the content has changed in the meantime and, if it
has, have evidence of it.
The aim is not to claim authorship ("this site wrote it, first"), but two more modest and more verifiable things: being able to check on one's own whether a content has changed and in which window of time it existed, without having to trust anyone. It is the same spirit as the rest of the site, content before technology.
It is worth saying at once, because everything rests on this point: this mechanism does not abolish trust, it shifts it. One moves from "believing everything the site says" to "having to trust the site only for what is irreducibly its own word"; everything mechanical (this text produces this hash, this text existed before a certain block) anyone can check, with universal tools.
A small disclaimer: there is no claim that this mechanism is the best possible, the most robust or the most efficient. It is rather an occasion for a small experiment on one possible use case, a sort of proof of concept built with readily accessible ideas and tools: an encouragement to use possibilities available to everyone, without reinventing the wheel. It can also be read as a small example of using Bitcoin's time chain for a purpose unrelated to the transfer of value.
Verifying in one minute
For those who just want to check, it all comes down to three commands over files that sit at the foot of every post (the "verifiable text", "signature" and "timestamp" items):
sha256sum canonical.txt # does the fingerprint match?
minisign -Vm canonical.txt -P <the site's public key> # is the signature the site's?
ots verify -f canonical.txt <shorthash>.ots # did it exist before that block?
The fourth check needs no command: the btc-anchor line inside the verifiable
text is compared against any block explorer (it is the "not before" bound). What
these checks prove, where the key and the files live, and what the honest limits
of the mechanism are is explained in the rest of the page.
What comes with every post
Besides the date and the hash, the foot of the post carries a few items:
- verify — this page;
- verifiable text — a
canonical.txtfile with the exact bytes that produce the hash; - details (json) — the verification record (
verify.json), the list of all known versions of that post; - timestamp — the
.otsfile that places the version in time (see "the timestamp"); - signature — a
.minisigfile with the site's signature over the verifiable text (see "the signature and identity").
What it is for, concretely
Three purposes must be kept apart, because they back different promises, and each has its own honest limit.
1. Noticing that it changed. For this the hash is enough: noting it down in full (or saving the verifiable text) at the moment of quoting makes a later check immediate. The page shows its short form, which is a label to read, not the one to note down (see "what the hash is"). If the hash differs, the content has changed. It must be said plainly that, on its own, an attentive reader could get there alone: it would be enough to save the text on each visit and recompare it. Here the site does not add the "noticing the change", it adds three smaller things: it defines what the content is (this exact sequence of bytes, not the HTML nor the layout, so a template change is not mistaken for a change of substance); it makes the comparison reproducible with a universal tool, not with the site's code; and it reduces the diligence to copying a short string, light enough to fit in a footnote of a quotation.
2. Being able to prove it to others. Convincing oneself is one thing, proving
it to someone else another. For this one needs to hold the preimage, the exact
text that produces that hash: that is what the canonical.txt file provides.
Whoever keeps it at the moment of quoting can later show "here is the text; recompute
its hash, and you see that today the site declares a different one: it has changed".
The decisive proof, however, is the one the reader kept: the site makes it easy
and standard, it does not replace it. And one limit must be stated clearly: the site
cannot prove, on its own, that it served that very text at that address in the
past (see below, "what is not proven"). A more modest argument in favour of trust
still holds: a hash that does not match the content shown can be exposed instantly
by anyone, so publishing a false one does not pay: it is not proof of good faith,
it is an aligned incentive. There is, finally, a step the reader can add alone at
the moment of quoting: asking a public archive (the Wayback Machine, for instance)
to capture the page. It covers precisely what this mechanism cannot prove, that
this text sat at that address on that date, at the price of trusting the archive: a
third party the site, by choice, keeps out of its own books, but one the reader is
free to call as a witness.
3. Placing it in time. This is the only purpose the reader cannot obtain alone, and also the one that does not ask for trust in the site: the Bitcoin chain acts as a witness of time. One obtains a proven window (the content, in that exact form, existed after a certain block and before another), verifiable by anyone, against a ledger no one controls (the mechanism is in the timestamp, below). It is the most solid part, and also the one with the narrowest claim: it proves the existence of a text within a window, not who wrote it.
What the hash is
A hash is a fingerprint. A function (here SHA-256) turns a text into a fixed-length string: changing even a single comma produces a completely different fingerprint, and two identical copies always yield the same one. There is no way back from the fingerprint to the text, but comparison is immediate.
On the page the hash is in short form (the first 12 digits): handy to read and to compare at a glance, but only a label. For noting down, the full hash is the one to use, and it sits in the verifiable text and in the verification record: a prefix that short, with enough computation, could be made to match across different texts; on the full hash this is out of reach.
How to check the hash
The canonical.txt file contains exactly the bytes the hash is computed over.
Nothing needs to be reconstructed: it is enough to download it and compute its
SHA-256 with any standard tool.
sha256sum canonical.txt
The resulting value matches the current field of the verification record (and,
in the part shown, the short form on the page). Verification rests on a universal
tool, not on this site's code.
How the verifiable text is built
The canonical file is a serialization of the raw Markdown of the post (not of the HTML), in UTF-8, in this form:
liberlume-content-v3
lang: <language>
title: <title>
summary: <summary>
btc-anchor: <height>,<block hash>
prev: <hash of the previous version | genesis>
--- body ---
<post body in raw Markdown>
Normalization rules:
- UTF-8 encoding, Unicode NFC normalization;
- line endings reduced to
\n; titleandsummaryare the front-matter values, trimmed of surrounding whitespace;btc-anchoris the time anchor: the height and hash of a recent Bitcoin block, separated by a comma. It is the part that fixes the lower bound in time, explained further down under the timestamp;previs the link to the previous version: the hash of the version that precedes this one, of the same post; the first version carries the sentinelgenesis. It ties the versions into a chain, explained further down;- the body is the raw Markdown (trailing spaces on a line stay, they are
significant in Markdown); leading and trailing blank lines are removed and the
text ends with exactly one
\n; - the header lines are literal (
lang:,title:,summary:,btc-anchor:,prev:with a single space after the colon) and the separator is the literal--- body ---.
The liberlume-content-v3 prefix is not a secret: it is a public label that
versions the method (-v1 was without the time anchor, -v2 without the version
chain) and separates the domain (this hash means "this site's canonical content", and
nothing else). Documenting the form avoids black boxes: anyone who wishes can
recompute it, but for an ordinary check the file and one command are enough.
The verification record
Next to every post, at the same address, there is the verify.json file. For the
post at /en/example/ it lives at /en/example/verify.json. Without republishing
the text, it lists every known version of that post.
For each version the file keeps two things apart, and the distinction is the point:
declared_dateis the date declared by the author (first publication, or the version): it is information, not proof, and has to be taken on trust;windowis the interval proven by the chain:certainly_after(from the anchor block: the content is certainly after that instant) and, once the mark is confirmed,certainly_before(from the timestamp block: certainly before), with the block heights so it can be rechecked. This one asks no one for trust;editorialis the fingerprint of the content alone: the canonical form without thebtc-anchorandprevlines (recomputable from the verifiable text by removing those two lines). It serves to tell a real change from a re-sealing: two versions with the sameeditorialcarry the same text, only the seal changed.
A legenda in the file recalls this. So even though the page shows only the current
hash, every version can be found in the list with its declared date and its window.
The versions, moreover, are chained: each version, in its verifiable text,
carries the hash of the previous one (the prev line). This way the recorded order
cannot be altered after the fact without the figures ceasing to add up. It is a
detail of robustness, not a claim: as said below, it does not prove the list is
complete.
The corpus checkpoints
The verification records are declared by the site, and nothing would prevent, in
principle, showing different versions of them to different readers. To narrow
this margin too, a checkpoint is published at every change in the state of
the corpus, and periodically in any case: a small file with
the list of the current hashes of all the contents, anchored to a recent
block, signed and timestamped like the posts, and tied to the previous checkpoint
(its prev line). Each checkpoint pins the whole declared bookkeeping to a
proven date: an alternative history would have to reckon with the checkpoints
already issued and kept by whoever downloaded them. They all live at
/checkpoints/: the index, and next to it the files,
the signatures and the timestamps. They do not prove completeness, which stays
out of reach; they narrow the window in which an alternative bookkeeping can
live unexposed.
The signature and identity
Every verifiable text is signed by the site. Next to the canonical.txt there
is a canonical.txt.minisig file: the signature, produced with
minisign, over those exact bytes. With
the site's public key, anyone can check it:
minisign -Vm canonical.txt -P <the site's public key>
What it adds, and what it does not. The signature does not prove who the author is in the real world, and that is not what matters here: it proves that a version comes from the same coherent author (the holder of a stable key, always the same one) and makes impersonation (or rewriting the history) hard for anyone who does not hold that key. It is, again, trust shifted: not "believe me", but "here is the key, check".
For the signature to count, though, one has to know the key is really the site's.
For this the public key is published in a stable way and in several places, and tied
to the domain by two independent paths: a file served over HTTPS
(/.well-known/liberlume-identity, which holds for whoever controls the site's
certificate) and a DNS record of the domain (which holds for whoever controls its
zone). The two paths can be compared, and a disagreement between them is already a
signal. They are not entirely independent, though (whoever controls the DNS can
also obtain a valid certificate): they raise the cost of a substitution, they do
not make it impossible. The more solid defence is another, and it is the date: the
identity declaration below places the key in time, and a substituted key cannot
show a history that old.
The key, in turn, is described in an identity declaration (under /identity/):
a small document that declares it the site's, places it in time with the same
window mechanism as the posts (an anchor to a recent block and a timestamp) and
lists the site's contents at the moment of its birth. This way the key pair too has
a proven date, not just a word.
If one day the key had to change, the new declaration would have to name the old one and be countersigned by it; and between conflicting declarations, the one with the older proven window prevails, because an old timestamp cannot be manufactured after the fact. It is this rule that makes a silent substitution of the key detectable.
One case remains uncovered, and it is fair to say so: the theft of the key. Whoever stole it could sign a seemingly legitimate rotation, countersigned by the authentic key: there would then be two conflicting successions, both formally valid, and cryptography alone would not tell which one belongs to the author. The conflict would at least remain visible, because two rival declarations are an alarm and not a silent substitution, but it could not be resolved on technical grounds. The known remedy is to pre-commit, ahead of time, the fingerprint of a reserve key kept elsewhere: a possible improvement, here declared for now rather than implemented.
The degrees of evidence
It is worth being explicit about how much each element "weighs", from weakest to strongest:
- the noted hash kept by a reader proves something to that reader, not to others;
- the saved verifiable text is the preimage: it is provable to anyone, because anyone can recompute its hash;
- the verification record documents the versions and dates, but it is declared by this site: on its own, it requires trusting the site;
- the signature ties each version to the site's identity (the key): it does not prove who that is in the real world, but it authenticates a coherent author and makes impersonation hard;
- the timestamp (see below) binds each version to a window of time that cannot be forged even by this site. It is the strongest degree of evidence.
In short: the most solid and independent proof is the one a reader kept at the moment of quoting; what the site publishes serves to make it checkable and, with the timestamp, non-repudiable.
What is not proven
So as not to promise more than it keeps, it is worth listing what this mechanism does not demonstrate:
- authorship. It proves that a text existed within a certain window, not who wrote it nor who was first: anyone can compute a hash and timestamp a text, even a copied one.
- that the site served that text at that address in the past. The published hash is a statement in the present; the proof that a given version was online on a given date lies in the evidence the reader kept (or in a third-party archive), not in what the site can reconstruct on its own.
- the completeness of the list of versions. The chain does more than it seems: since each version links the hash of the previous one, the site cannot reorder nor silently remove a version already recorded and witnessed. But two things stay out of reach. First: nothing forces the site to record every change: it may not freeze an intermediate state, and of that no trace remains. Second: from the link one obtains the hash of the previous version, not its text, nor any certainty that the declared list is the true and whole one; whoever arrives for the first time knows only what the site declares now. The chain proves the order and integrity of the listed versions, not that they are all of them. The corpus checkpoints (see above) narrow this margin over time, they do not close it.
- that the page shows the canonical text. The hash and the signature cover the verifiable text, not the served HTML: that the page faithfully mirrors the canonical one can be checked with a comparison, but no automatism guarantees it. The incentive already mentioned applies: a divergence could be exposed instantly by anyone.
- who the author is in the real world. The signature proves that a version comes from the holder of the site's key (a coherent identity, not a legal name); and that the key really is the site's has to be taken on trust, even though the tie to the domain (see "the signature and identity") makes it harder to forge. A part of trust remains: the mechanism reduces it to a minimum and makes it checkable where possible, it does not zero it out.
Caveats
- the hash tells whether content changed, not what changed: to recover the exact text of an earlier version it must have been saved (the verification record, by design, does not republish historical prose).
- at the top of the post is the first publication date, declared by the author; the last modified time, at the foot, is instead the window proven by the chain (the timestamp below), not a declared date.
- a timestamp still pending is not verifiable until a block confirms it; in that window only the lower bound is known ("after …") and the independent evidence is still what the reader keeps.
The timestamp
A fingerprint tells whether a text changed, but not when it existed. To place it in time without having to believe this site, two anchors to the Bitcoin chain are used, an independent and decentralized ledger. Together they pin the content inside a window of time, from one side and the other.
The lower bound, "not before". In the verifiable text, the btc-anchor line
holds the height and hash of a recent Bitcoin block, chosen at the moment of
sealing. A block's hash is unpredictable until the block has been mined: no one
can write it in advance. Finding it inside the fingerprint therefore proves that
the content was sealed not before that block, that is, not before the instant
the block entered the chain.
The upper bound, "not after". Every version is then timestamped with
OpenTimestamps, which anchors the content's fingerprint to a later block. The
timestamp is the .ots file downloadable from the block at the end of the post
(the "timestamp" item) and proves that the content already existed before that
block.
Put together, the two anchors say: the content, in that exact form, was born after the anchor block and before the timestamp block. A closed window, backed not by a declaration but by the chain.
Where the width comes from. A block is not "settled" at the instant it is mined: it becomes so, in practice, once others have accumulated on top of it, because going back would cost redoing all that work. How many blocks suffice is a reasonable threshold, not an absolute; and since blocks arrive roughly every ten minutes, that threshold is also an interval of time. That is where the width of the two sides of the window comes from: neither the anchor nor the timestamp is pinned to the latest block, but to one already buried enough to count as immutable. A tolerance on the times contributes to the width as well: the time written in a block is set by whoever mines it, and the protocol accepts deviations of up to a couple of hours (it only requires it to exceed the median of the previous eleven blocks and not to run more than two hours ahead of the network's clock). The edges of the window should therefore be read with a precision of hours, not minutes.
The OpenTimestamps mark comes in two stages:
- pending: right after publishing, the fingerprint has been taken in by the calendar servers but is not yet anchored to a block;
- confirmed: after a Bitcoin block is confirmed (usually a few hours), the timestamp incorporates the block's attestation and is independently verifiable.
How to check the window
The upper bound is checked with the canonical.txt and the post's .ots file:
ots verify -f canonical.txt <shorthash>.ots
Verifying the block attestation requires access to the Bitcoin blockchain (a node of one's own, or a web verifier).
The lower bound is checked from the btc-anchor line of the verifiable text:
the block height and hash are public data. Looking that height up on any block
explorer, or on one's own node, confirms that the hash matches and shows the time
the block was mined. That time is the lower end of the window. It is a check that
does not go through this site: the datum sits on the Bitcoin chain, the same for
everyone.
A note on the tools: ots is the OpenTimestamps client (the example here is in
Python, pip install opentimestamps-client, but implementations exist for other
environments too); alternatively, opentimestamps.org offers a browser-based
verifier. For the block, any public explorer works, or one's own node, so as not to
depend on anyone.
Three limits, stated plainly: the timestamp proves the existence of a content within a window of time, not authorship; the window has a width (the tolerance of block times, the gap between sealing and confirmation, and the margin of blocks one waits before considering a block settled), it is not an instant; and for posts sealed after their first publication the lower bound starts at the moment of sealing, not at the post's original date.
A note on the starting point
The mechanism described here (signature, version chain, anchor in the preimage) came into force on a certain day, when the site was still young. At that moment all the content already published was re-frozen, chained and signed together, each as the first version of a new chain. For this reason, for the content predating that day, the lower bound proven by the window is that day, not its original date; the first-publication date stays the one declared at the top of the post, to be taken on trust. It is an honest consequence of starting over, not a flaw of the individual content.
Tools
Everything is checked with free tools, independent of this site:
- OpenTimestamps — the timestamp anchored to Bitcoin: the
otsclient (pip install opentimestamps-client, though other implementations exist) and a browser-based verifier are at opentimestamps.org. - minisign — for checking signatures: jedisct1.github.io/minisign.
- The hash is recomputed with
sha256sum(GNU coreutils), present by default on almost every system.