Proof specification
How BeatTime's proof-of-existence layer works — precisely enough to verify it yourself, without trusting us.
protocol: beattime-proof-v1 · last updated June 2026
BeatTime can record that a file existed at a point in time — without ever receiving the file. Only its SHA-256 hash is submitted. Every hash is woven into an append-only hash-chain, batched weekly into a Merkle tree, and the weekly root is sealed by three independent means: an Ed25519 signature, the Bitcoin blockchain (via OpenTimestamps), and an independent, bank-issued reference. None of these require trusting BeatTime to check — each can be verified against an outside authority.
your file ──SHA-256──▶ digest (64-hex, computed on your device)
│ the file never leaves your device
▼
append-only hash-chain: linkₙ = SHA-256( linkₙ₋₁ ‖ digest ‖ UTC )
│ one ISO week of stamps
▼
Merkle tree (RFC 6962-style) ───────▶ weekly root
│ frozen when the week closes
┌───────────────┼────────────────────┐
▼ ▼ ▼
Ed25519 Bitcoin independent
signature (OpenTimestamps) bank reference
1Stamping a hash
The unit of proof is a document's SHA-256 digest, written as 64 lowercase
hexadecimal characters. The digest is computed on your device (in the browser via
Web Crypto, or in the app). Only those 64 characters are sent to POST /api/proof/stamp
— never the file, its name, or its size.
Stamping is idempotent and first-seen: the first time a digest is recorded it is
timestamped; re-submitting the same digest always returns that original timestamp (and HTTP 200
rather than 201). A stamp stores the digest, its @beat and UTC time (to centibeat
precision), its position in the hash-chain, and its ISO week.
2The append-only hash-chain
Stamps form a tamper-evident chain in insertion order. Each new link folds in the previous link, so no earlier entry can be altered, reordered or removed without breaking every link after it:
link₀ = "0000…0000" (GENESIS, 64 zero hex chars)
linkₙ = SHA-256( linkₙ₋₁ ‖ digestₙ ‖ utcₙ )
linkₙ₋₁— the previous stamp's chain value, as a 64-char hex string;digestₙ— this stamp's 64-char hex SHA-256;utcₙ— the stamp's UTC time as an ISO-8601 string (the exact bytes used are returned by the API).
The three strings are concatenated and hashed as UTF-8. The result is this stamp's chain value and becomes the input to the next one.
3Weekly Merkle tree
Time is partitioned into ISO weeks (YYYY-Www, Monday→Sunday; a week
closes the following Monday 00:00 UTC). All stamps in a week, ordered by insertion,
form the leaves of a binary Merkle tree built the RFC 6962 way, with domain
separation between leaves and internal nodes:
leaf = SHA-256( 0x00 ‖ digest_bytes ) // digest_bytes = the 32 raw bytes of the document's SHA-256
node = SHA-256( 0x01 ‖ left ‖ right ) // left, right = 32-byte child hashes
Leaves are paired left-to-right at each level. If a level has an odd number of nodes, the lonely node is carried up unchanged (it is not duplicated) — avoiding the well-known second-preimage ambiguity of duplicate-last-node schemes. The single hash remaining at the top is the weekly root.
While a week is open its root is provisional (recomputed as stamps arrive). When the week is closed the root is frozen and stored; from then on it never changes, and the signing and anchoring steps below apply to that frozen value.
4Signing the weekly root
At closing, the frozen root is signed with an Ed25519 private key (held only in the server environment, never in the repository). The signed message is canonical and binds the week to its root, so a signature cannot be replayed onto a different week or root:
message = "beattime-proof-v1|" + week_key + "|" + root_hex (UTF-8 bytes)
signature = Ed25519-Sign( private_key, message ) (returned base64)
The corresponding public key is published (below and via the API) so anyone can verify the signature independently with any standard Ed25519 library.
5External anchoring — Bitcoin & bank reference
The signature proves we attest to the root. The two anchors below give dated records of the root that exist outside our own infrastructure, so the timestamp does not rest on trusting BeatTime:
-
Bitcoin, via OpenTimestamps.
The root is submitted to OpenTimestamps calendars (typically within the hour) and, once it is
included in a Bitcoin block, the
.otsproof upgrades to a Bitcoin attestation. The.otsfile is downloadable and verifiable with the standard OpenTimestamps client against the public Bitcoin blockchain — independent of BeatTime entirely. - An independent bank reference. Each closed week's root is also anchored against an independent, bank-issued reference — a number issued by a regulated financial institution, different for each anchoring, that records the root by a given date in a system we do not control. The individual reference is shown on the public proof page and on each certificate; a redacted statement may be published as supporting evidence.
6Verifying a proof yourself
Call GET /api/proof/verify?digest=<hex>. For a stamped hash it returns the
timestamp, the weekly root, the inclusion proof, the signature + public key, and the OpenTimestamps /
Bitcoin status. Verification has three independent parts:
- Inclusion — recompute the leaf and walk the proof to the weekly root.
- Signature — check the Ed25519 signature over the canonical message of §4.
- Bitcoin — verify the downloaded
.otswith the OpenTimestamps client.
Inclusion proof
The inclusion_proof is an ordered list of sibling hashes, each tagged with the side it
sits on ("L" = sibling is on the left, "R" = on the right). Start from your
leaf and fold in each sibling; the final value must equal week_root:
h = SHA-256( 0x00 ‖ digest_bytes ) # your leaf
for (side, sibling) in inclusion_proof:
if side == "L": h = SHA-256( 0x01 ‖ sibling ‖ h )
else: h = SHA-256( 0x01 ‖ h ‖ sibling )
assert h == week_root
The Python and
PHP SDKs and the
REST API reference wrap these calls; you can also download a self-contained
PDF certificate for any stamped hash at /api/proof/cert/<digest>.
7What a proof does and does not attest
A confirmed proof attests existence by a point in time and integrity: the exact bytes that hash to that digest existed no later than the recorded time, and have not changed since. It does not attest:
- authorship or ownership — anyone can stamp any hash;
- the truth or meaning of the content;
- that it is a qualified electronic timestamp under eIDAS or any specific legal regime — it is a best-effort, public attestation, provided as-is (see the Terms).
Entries are public and permanent (append-only). Do not stamp a hash you need to keep secret — although the hash reveals nothing about the file, the fact that some file with that hash existed becomes public.
8API & public key
| Endpoint | Purpose |
|---|---|
POST /api/proof/stamp | Record a digest (body: {"digest": "<64-hex>"}). |
GET /api/proof/verify | Look up a digest: timestamp, root, inclusion proof, signature, anchors. |
GET /api/proof/cert/<digest> | Download the PDF certificate for a stamped hash. |
GET /api/proof/ots/<week_key> | Download the week's OpenTimestamps .ots proof. |
Full request/response schemas are in the OpenAPI reference. Stamping is rate-limited; verification more generously so.