Tessera transparency logs, in TypeScript
webtessera is a faithful TypeScript port of Tessera, the tile-based transparency log, for browsers, edge runtimes and servers, with IndexedDB and SQLite storage. Append an entry and get a receipt that anyone can verify offline. Its logs are byte for byte Tessera’s, so each side reads and extends the other’s.
npm install webtessera webtessera runs on Node.js 22.18 or later, Deno 2, Bun, current browsers, and edge runtimes built on web standards.
GET /checkpoint signed while this page was built webtessera.diagnos.health22ZSz8wsngkMgSw0QM93ZeKmU6+byX7C7Epna4q/xe96I=— webtessera.diagnos.health ZRxw2pzqVI3aHvMr3LYMtvDRcaYBzDDp+PQJOvqIrBrz2Hfp1scLalNNQrrFVbm+IN1khBjUU0nYkfVkzq7dZnyU2wg=
How a log works
-
An entry becomes a leaf. The log hashes each entry into a Merkle tree. Change one byte and every hash above it changes.
rfc6962.DefaultHasherentry 5, then one byte changedhashLeaf("webtessera/ctonly") = 477e2a072f1891d93c61b85a…hashLeaf("webtessera/ctonlz") = d436f2b22bc7bb77ba1e2267…
-
Leaves fill tiles. The tree is stored in tiles of 256 hashes, which never change once full, so any static host or CDN can serve them. This one holds the 22 entries of the log on this page.
tile/0/000.p/2222 of 256 -
A checkpoint commits. The log signs its size and root hash as a signed note, like the one at the top of this page. The signature commits to every entry before it, and the log’s public key checks it.
vkeythe log’s public keywebtessera.diagnos.health+651c70da+AeuC5pEJJuQqdsnZZCqPGkq/x8sfL+E4d5QNYABEV0ge -
A proof replaces trust. Proving that entry 5 is in a tree of 22 takes five hashes, not 22 entries. A log of a billion entries needs about 30.
inclusionProof(5)tree of 22- root
652cfcc2= the checkpoint’s root, ZSz8wsng… - entries 16–21
9a39a835 - entries 8–15
dfcf38bd - entries 0–3
dfc490d9 - entries 6–7
7ffccb44 - entry 4
ddee4e10 - entry 5
477e2a07webtessera/ctonly
- root
-
A receipt travels.
append()returns the index, the proof and the checkpoint as one C2SP tlog-proof. Anyone with the log’s public key and the entry can check it offline.log.append(entry)a receipt, as returnedc2sp.org/tlog-proof@v1index 53e5OEKzR71EEC826t+/duVgiEmQzGsXoZnmyKDSs0V0=f/zLRMAABkldTNcL/DvLOoGqhK4zCIlcZe76Nn8zdmQ=38SQ2QlLe8DcWgXTUWBXEoZU6kWu+3cSbMr/XqDWyPs=3884vSW80bd89Jg1GxipKot2sfq7PN1asZTW3a6X7M4=mjmoNQ05JISxfYcZqqLNA3OHmykwKYnhrf7/MRNld44=webtessera.diagnos.health22ZSz8wsngkMgSw0QM93ZeKmU6+byX7C7Epna4q/xe96I=— webtessera.diagnos.health ZRxw2pzqVI3aHvMr3LYMtvDRcaYBzDDp+PQJOvqIrBrz2Hfp1scLalNNQrrFVbm+IN1khBjUU0nYkfVkzq7dZnyU2wg=
verifyReceipt: entry 5 is in the tree of 22.
Witnesses cosign checkpoints they have checked, so a log cannot show different histories to different readers. Transparency logs, explained covers the design in full.
A log in this tab
This is webtessera running in your browser: a log in memory, signed by a key generated in this tab and seeded with the same 22 entries. Append, fill the tile, then tamper with an entry and watch its proof fail.
This is the log the page was built with. The live log starts as you scroll to it.
tile/0/000.p/2222 of 256Latest entries
- 21webtessera/testonly
- 20webtessera/browser
- 19webtessera/server
- 18webtessera/mirror
- 17webtessera/witness
GET /checkpointsigned at build timewebtessera.diagnos.health22ZSz8wsngkMgSw0QM93ZeKmU6+byX7C7Epna4q/xe96I=— webtessera.diagnos.health ZRxw2pzqVI3aHvMr3LYMtvDRcaYBzDDp+PQJOvqIrBrz2Hfp1scLalNNQrrFVbm+IN1khBjUU0nYkfVkzq7dZnyU2wg=
inclusionProof Verified. Entry 5 is in the tree of 22 entries: 5 hashes recompute the root of the signed checkpoint.
- root
652cfcc2= the checkpoint’s root, ZSz8wsng… - entries 16–21
9a39a835 - entries 8–15
dfcf38bd - entries 0–3
dfc490d9 - entries 6–7
7ffccb44 - entry 4
ddee4e10 - entry 5
477e2a07webtessera/ctonly
Use it
On a server, with any SQLite:
src/README_test.ts lines 58–60 and 232–243 import { DatabaseSync } from "node:sqlite";
import { importLogKey, openServerLog, verifyReceipt } from "webtessera/server";
import { fromSqliteSync } from "webtessera/storage/sqlite";// The key comes from your secret store, never from source code.
const log = await openServerLog({
key: await importLogKey(process.env.LOG_SKEY),
storage: { sqlite: fromSqliteSync(new DatabaseSync("log.db")) },
});
// append resolves once a published checkpoint covers the entry, with a verified receipt.
const entry = new TextEncoder().encode("hello");
const receipt = await log.append(entry);
// Anyone with the log's vkey and the entry can check the receipt, offline.
const { index, checkpoint } = verifyReceipt(receipt.text, { vkey: log.vkey, data: entry }); In a browser, with IndexedDB and a key that never leaves the device:
src/README_test.ts lines 274–278 import { openBrowserLog, openDeviceKey } from "webtessera/browser";
// A key generated on this device and kept in IndexedDB, which no script can export.
const key = await openDeviceKey("device.example/7f3a");
const log = await openBrowserLog({ key });
const receipt = await log.append(new TextEncoder().encode("signed the form")); Both samples run in CI. The safe API guide explains each default; the ported API is Tessera’s own, name for name.
Proven against Tessera
webtessera is a translation of Tessera at 4a6d9f9,
not a reimplementation. Its tests compare 22 golden fixture files recorded from
Tessera byte for byte, and CI regenerates them from Tessera on every change. The golden suite holds eight storage backends to those bytes, Tessera’s Go code and
webtessera verify and extend each other’s logs on four of them, and 86,008 differential records replay Go’s verdicts in Node.js, Chromium and workerd.
How each claim is checked.