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

  1. An entry becomes a leaf. The log hashes each entry into a Merkle tree. Change one byte and every hash above it changes.

    rfc6962.DefaultHasher entry 5, then one byte changed
    hashLeaf("webtessera/ctonly")  = 477e2a072f1891d93c61b85a…hashLeaf("webtessera/ctonlz")  = d436f2b22bc7bb77ba1e2267…
  2. 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
  3. 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.

    vkey the log’s public key
    webtessera.diagnos.health+651c70da+AeuC5pEJJuQqdsnZZCqPGkq/x8sfL+E4d5QNYABEV0ge
  4. 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
    1. root652cfcc2= the checkpoint’s root, ZSz8wsng…
    2. entries 16–219a39a835
    3. entries 8–15dfcf38bd
    4. entries 0–3dfc490d9
    5. entries 6–77ffccb44
    6. entry 4ddee4e10
    7. entry 5477e2a07webtessera/ctonly
  5. 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 returned
    c2sp.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 256

Latest entries

  1. 21webtessera/testonly
  2. 20webtessera/browser
  3. 19webtessera/server
  4. 18webtessera/mirror
  5. 17webtessera/witness
GET /checkpointsigned at build time
webtessera.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.

  1. root652cfcc2= the checkpoint’s root, ZSz8wsng…
  2. entries 16–219a39a835
  3. entries 8–15dfcf38bd
  4. entries 0–3dfc490d9
  5. entries 6–77ffccb44
  6. entry 4ddee4e10
  7. entry 5477e2a07webtessera/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.