Time-sealed envelopes

An envelope that cannot be opened before a chosen @beat — not by you, not by us, not by anyone holding a single key.

format: beattime-seal-v1 · last updated August 2026

Proof of existence answers “this existed before then.” This layer answers the opposite question: “this cannot be read until then.” Both are free, both are public, and neither requires an account.

1The idea

You seal something now and name the moment it may be read: a letter to a child, a sealed bid, a disclosure with an embargo, an archive that should outlive the person who made it. Until that moment arrives the content is unreadable, and afterwards anyone holding the envelope can open it.

The hard part is not the encryption. It is answering the obvious follow-up question: what stops the people running the service from reading it early? Most “time capsule” products answer with a promise. This one answers with arithmetic.

2Why a threshold, not a promise

The envelope's key is split with Shamir's scheme into n shares, of which any t reconstruct it. Fewer than t reveal nothing at all — not “nothing practical”, literally nothing: every possible key remains equally likely.

The shares are then locked to independent holders, so opening early would require compromising several of them at once:

key ──split──┬─→ share 1 ──sealed to──→ drand (threshold network, League of Entropy)
             ├─→ share 2 ──sealed to──→ an operator's key server
             └─→ share 3 ──handed to──→ you, as a recovery code

             any 2 of 3 reconstruct the key · any 1 reveals nothing

A leak of our share tells an attacker nothing. A compromise of drand's threshold tells an attacker nothing. That is the whole point: no single key holder — including BeatTime — can open an envelope before its time.

3The three kinds of share

KindReleased byWhen
drandthe drand threshold networkat a chosen round
beatan operator's key serverat the chosen @beat
escrownobody — it is handed to youfrom the moment of sealing

The first two use timelock encryption (tlock): a share is encrypted to an identity that only a future signature can unlock, so the key to open it does not exist yet — not anywhere, not to anyone.

The third is a recovery code, printed in Crockford Base32 so it survives being copied by hand from a piece of paper years later:

7F3A-K5MZ-8PQT-…-2WXR

It buys survivability: with it the envelope opens even if every beacon disappears. It costs a little confidentiality, because it is a static secret — so keep it on paper in a safe, not in a cloud folder. We call it a recovery share, never “extra security”.

One rule that quietly matters. Every beacon share must target the same @beat. If they targeted different moments, the holder of the recovery code could open the envelope as soon as the earliest beacon released — and the guarantee would degrade without anyone noticing. The format has no place to put a per-share time, and the validator rejects envelopes that try.

4@beat as the address of a moment

The opening moment is stored as an absolute beat index — the number of 86.4-second beats since 1970-01-01T00:00:00Z. One integer, no timezone, no calendar, no ambiguity a decade from now.

beat_index = unix_microseconds / 86_400_000   (integer division)

Computed on whole microseconds, never by dividing by 86.4 — that value has no exact binary representation and the result drops by one at beat boundaries. Index 0 is midnight UTC on 1 January 1970, so noon UTC is always @500.

5Inside the envelope

The envelope is JSON. The payload can travel inside it or sit alongside it as a separate file, which is what you want for large attachments.

LayerConstruction
Key splitShamir over GF(256), reducing polynomial 0x11B
Key derivationHKDF-SHA256, info beattime-seal-v1|aead
PayloadAES-256-GCM in 64 KiB segments (STREAM construction)
Share sealingBoneh–Franklin IBE on BLS12-381, scheme bls-unchained-g1-rfc9380
Recovery codeCrockford Base32, 14 groups of 4

The payload is segmented rather than encrypted in one piece for a specific reason: a single AES-GCM tag cannot be checked until the whole file has been read, which would mean either buffering gigabytes or handing out unauthenticated plaintext. Each segment carries its own tag, so a stream verifies as it goes; a counter in the nonce prevents reordering and a final-segment flag prevents truncation.

Metadata belongs inside the ciphertext. The envelope header is public from the day it is sealed — years before it opens. A file name in the header would give away exactly the thing the format is meant to protect, so names and MIME types are encrypted with the content. What stays visible is the payload size and the opening moment.

6API

Everything here is public and free, with no account and no key. Note what is not here: there is no endpoint that seals or opens an envelope. Sealing takes a secret share as input, so a server doing it could open every envelope it ever made — and the whole construction would be decoration. That work happens in your client.

EndpointPurpose
GET /api/seal/infoFormat parameters, profiles and the pinned drand chain.
GET /api/seal/beatInstant ↔ beat index. No arguments returns the current beat.
GET /api/seal/round@beat → drand round: the bridge to existing tlock tooling.
GET /api/seal/keyBeatTime's operator public key.
GET /api/seal/share/<beat_index>A released share. 404 before its beat — never a hint.
POST /api/seal/validateCheck an envelope without opening it. Send the header only.
GET /.well-known/beat-key-operators.jsonOperator registry: op, public key, addresses.

Released shares are immutable and served with a one-year cache header. Mirroring them is encouraged rather than tolerated: once a share is public it is ordinary public data, and every extra copy makes the envelopes depend less on any one server still being online.

Addresses in the registry are a hint. The source of truth is the public key recorded in the envelope itself, so a share fetched from anywhere — a mirror, an archive, a stranger's USB stick — is verified locally against it.

The holders in that registry right now — read live from it, so this list and the JSON cannot disagree:

OperatorPublic key (BLS12-381 on G2, base64)
beattime jN8nAWDv7kfa7qXX8LvxBniOi3EksnRr6YA474t8VtYzrIHEttBwikf+0RWwutscBhukGwik6kjOumfo27iT7VXjPytmIOBAVFcO4538Vy3SIfewNy0LhuEGCERv3baD
beatkey-konveria
Polska
gx2ddUgSltcankuX20EsIFfRfkzAPkLMSRjdCxr67v6MVdNFPJ4U1imLdBNNdJe8FwyfIPEhKLPCMGQkD55TkaBh3TOsCYVoALwDogncHnOgdaaDHUaLiOF7LXs7Zktu

2 independent holders of the beat share, alongside drand and the recovery code. They are separate organisations on separate hosting — two shares with one hosting provider would look independent and would not be, so that is checked before an operator is added.

7Try it in five minutes

No server seals an envelope for you — not this one, not anybody's. The input to sealing is a secret share, so a server able to seal would be able to open everything it ever sealed. What this service publishes is the public half: the parameters, the time mapping, the operator registry and the shares that are released once their moment has passed.

The sealing itself has to happen on the machine the contents are already on. If you would rather not write that code, the capsule tool is exactly it — the same format, implemented in the page and running in your browser. Nothing it produces is transmitted anywhere; open the network tab and watch it stay quiet.

Or watch the mechanism work from a terminal right now, without writing anything:

What moment is this, as a number?

curl -s "https://beattime.live/api/seal/beat?utc=2030-01-01T00:00:00Z"

Comes back with the absolute beat index — one integer that names that moment for everyone, with no timezone and no calendar to misread a decade from now.

Which drand round would a share be sealed to?

curl -s "https://beattime.live/api/seal/round?beat=<index from above>"

That round number is what existing tlock tooling takes. This endpoint is the entire bridge between @beat and the wider timelock ecosystem.

A share that has not been released yet

curl -i "https://beattime.live/api/seal/share/<a future index>"

404. Not “not yet”, not a countdown, not a partial answer — a future share is indistinguishable from one that never existed. Ask for a beat that has already passed and the same call returns the signature.

Check it yourself, without trusting the answer

A share is only worth what it verifies against, so verify it against the key in the registry rather than against the server that handed it to you:

git clone https://github.com/DeiFlagellum/beat-key && cd beat-key
go run ./cmd/verify-share -pub <public_key> -beat <index> -sig <signature>

The tool recomputes the identity from the beat index instead of believing the one in the response — a server returning a signature over some other identity would hand you a share that verifies but opens nothing.

Writing a client? The order of calls for sealing and opening, the exact key derivation, and the four mistakes that are easy to make — a shared beat index across beacons, integer microseconds instead of dividing by 86.4, the off-by-one in drand rounds, and metadata that belongs inside the ciphertext — are all in the format contract. Start from /api/seal/info; everything you need to build an envelope is in that one response.

8Run your own key server

Anyone can hold a share. The key server is a container that publishes one thing — a signature over a beat identity, and only once that beat has passed. It generates its own key on first start, inside the container, with no import path: nobody can hand you a key and you cannot accidentally deploy someone else's.

docker run -e BEAT_KEY_OP=your-org -v beat-key:/data \
  ghcr.io/deiflagellum/beat-key@sha256:9913de07399a36a3a007f7ac95a5597a…

It holds no user data — no ciphertext ever passes through it, so there is nothing to retain and nothing to report in a breach. It needs no uptime guarantee either: the signatures are deterministic, so downtime means envelopes open later, never that anything is lost. The real request to an operator is “keep 32 bytes safe for years.”

The image is public and built from public source at github.com/DeiFlagellum/beat-key, and that is deliberate rather than incidental: an operator has to be able to rebuild it and compare. An image you cannot rebuild is an image whose author you have to trust — and then the operator's share would not be independent at all.

9What this does not protect against

The honest list, because a security claim without its limits is marketing.

← proof specification · API reference