The transparency log

The append-only record behind every reaction: what gets written down, in what order, and what makes that order impossible to rewrite in secret.

Follow one reaction

When you react to a page, the extension first reduces the page's URL to a stable target key: a site name plus an identifier, derived the same way by the extension and by the server, so a reaction on one URL variant counts toward the same target as every other. Your accepted reaction is then appended as a new entry to the reaction log, and the live counter you see is updated from that log. Periodically the head of the log is summarized into a compact signed checkpoint, which is published outside our servers and eventually sealed into the Bitcoin blockchain.

  1. You react One tap adds your emoji to a page.
  2. Log entry Your reaction is appended to a public, tamper-evident log.
  3. Signed checkpoint The whole log is regularly summarized into one signed record.
  4. Public anchors Checkpoints are published to GitHub, sealed into Bitcoin, and witnessed by independent logs and archives.
  5. Anyone verifies An open-source tool replays the log and recomputes every count.

The live counter you see on a page is a cache derived from this same log; the sections below take each piece in turn.

The data, piece by piece

The reaction log, the source of truth

Every accepted change to a count is recorded as one entry in an append-only log: adding a reaction (op=1), changing it (op=2), removing it (op=3), or a revocation that reverses an earlier entry (op=4). Entries are hashed with SHA-256 and organized into a Merkle tree, so the entire history can be summarized by a single root hash — altering or dropping a past entry changes that root, and the mismatch is detectable from that point forward. Nothing in the log is ever edited or deleted; every correction, including account erasure, is a new entry that anyone can see.

Revocations are public too. They don't rewrite old entries: an op=4 entry points to the original log sequence and carries a public reason_code, and all of them are published in the log repository as revocations/latest.json, the public revocation feed. What a revocation proves, and what it can't, is spelled out on the verifier page.

The log identifies authors only by pseudonyms that rotate over time and cannot be linked across rotations by design. Who you are never enters the log.

Epochs and epoch keys, in plain words

An epoch is a calendar slice of 30 days. The log already divides time this way: the pseudonym on each entry (user_ref) is only stable within one such slice. An epoch key is the seal the extension signs your reactions with, and it is a fresh seal for every slice.

Why a fresh one: with a single seal for the lifetime of an account, anyone reading the log could gather everything that account did over the years. With a seal that changes every 30 days, only the reactions inside one month can be tied together, exactly as today.

The problem a fresh seal creates: a brand-new key is tied to nothing. On its own, the verifier cannot tell whether a real account stands behind it, or whether the operator minted it out of thin air to pad a count.

The solution has two halves. First, the server grants the new key a permit by signing it, without seeing which key it signs: a blind signature, so the server can vouch for the key without learning it. Second, at the same moment, the server writes into the log "account S received one permit this month". The verifier then counts: the keys carrying a permit in a month never outnumber the permit records for that month, and each permit record points at an account whose enrollment proof shows a provider such as Microsoft or Apple vouched for it.

So the operator can neither hand out extra seals nor hand them to accounts that do not exist, and the link "account S is this particular seal" stays out of public view.

Signed checkpoints: compact commitments

A checkpoint is a signed tree head: the number of entries in the log, the Merkle root hash over all of them, and a timestamp, signed with the project's Ed25519 key. The public half of that key is pinned in the verifier's source, so checking a signature needs nothing from us. The entry count only ever grows, and each checkpoint commits to every entry before it: change any past entry and the root no longer matches.

The counters cache: derived, never authoritative

The number you see next to an emoji is served from a cache, kept so pages load fast. That cache holds no authority of its own. It is a fold of the log: replay every add, change, removal, and revocation, and you get the same totals. Anyone can do that replay from public data, so if the cache ever disagreed with the log, that would be a detectable public event rather than a quiet correction. This is the point of the whole design: the database is a convenience, and the log is the truth.

Account records, kept out of the log on purpose

Accounts are stored separately and never enter the log. Your provider id is kept only as a one-way keyed hash (see Privacy), and the log sees only the rotating pseudonyms described above. That separation is deliberate: verifying a count never requires knowing who reacted, so the proof machinery is built to work without identities.

Built to stop rather than guess

The log is built to fail safe. If the next record it is about to sign is not internally consistent, it signs nothing and stops advancing, instead of committing to a checkpoint that would not verify. A paused log is a visible state that anyone watching can see and an operator can repair; a wrongly signed one cannot be taken back once it is anchored, so the design always takes the recoverable failure. Every checkpoint already published stays valid through such a pause, because the log is only ever appended to and never rewritten in place: a halt protects the history you already checked instead of putting it at risk.

Where the checkpoints live: emojery-log

Checkpoints aren't kept on our servers. They're published to a second, dedicated public repository, emojery-log, kept apart from the code so the anchor isn't something the backend can quietly overwrite:

checkpoints/latest.json         newest signed tree head (moving pointer)
checkpoints/<YYYY-MM-DD>.ndjson  daily shard: one signed checkpoint per line
entries/manifest/<first>.ndjson  the SHA-256 of every chunk of raw log entries, published as checkpoints cover them
entries/mirrors.json            where the chunk bodies are served from
revocations/latest.json         every revocation tombstone the log holds
ots/<tree_size>.pending.json    OpenTimestamps receipt written the moment a root is submitted
ots/<tree_size>.ots             that proof once Bitcoin has sealed it (hours later)
ots/<tree_size>.json            the matured proof's signed checkpoint + Bitcoin block height
ots/latest.json                 pointer to the newest matured proof
rekor/<tree_size>.json          checkpoint root mirrored into the Sigstore Rekor public log
swh/latest.json                 Software Heritage archival record: the resolvable swh:1:rev:<commit> for the archived log

The repo holds the signed checkpoints, their OpenTimestamps Bitcoin proofs, and a mirror of the raw log entries themselves. The entries are also served live by the public API; anyone checking a count refetches them from either source, recomputes the Merkle root, and compares it against the signed checkpoint here. Because the entries are in the repo, a plain git clone carries the complete log. It stays independently archivable and verifiable even if our API disappeared, and the verifier can audit a clone or mirror entirely offline.

Because the signed root is mirrored here, the backend can't show one history to your browser and a different one to everyone else: that "split view" is caught the instant someone compares the API's checkpoint against the copy in emojery-log. The repo is append-only by convention — a force-push or rewrite is itself the tamper signal, because third-party mirrors retain its history. We don't just assume that mirroring happens: Emojery actively asks Software Heritage, an independent public-good archive operated out of the EU, to re-archive the log, and records the archived commit's resolvable identifier, so the preservation is something you can check rather than take on faith.

Where each piece lives

What each anchor proves

Anchor What it proves What the guarantee rests on
GitHub repository The checkpoint was committed on a given date. Nothing on its own: a commit date can be rewritten with a force-push. The weakest of the four, which is why the others exist.
Sigstore Rekor An independent organization saw this exact checkpoint at a given time. Confirmed in seconds. Trust in the Rekor operator, and on their log being publicly checkable too.
Bitcoin via OpenTimestamps This checkpoint existed before a given block. Confirmed in hours. No one: forging it means redoing the proof of work of the whole Bitcoin network.
Software Heritage The whole log repository looked like this at a given time, in a copy we don't hold. An independent archive in another jurisdiction, with a permanent identifier per snapshot.

Rekor and Bitcoin insure each other. One is fast and rests on an organization; the other is slow and rests on mathematics. They fail in different ways, so a reader who distrusts one still has the other.

Software Heritage insures something different. Rekor and Bitcoin already fix that each checkpoint existed; the archive keeps the contents behind it, a full copy of the repository for the day GitHub rewrites or loses it. Because the log is append-only, the most a snapshot can miss is what was appended after it was taken.

Each hop outward crosses a boundary we have progressively less power over. Rewriting history would mean altering our own database, a public API anyone can fetch, a public git history that third parties archive, and a Bitcoin block: each step is more visible and less possible than the last.

Check the record

Every piece above is an input to the open-source emojery-verifier, which replays the log, recomputes the counts, and checks every signature and anchor. What it verifies, step by step, is on the verifier page.

If this page starts a level below where you wanted to, a plain-language tour of the whole system covers the same ground with no hashes in it.