The ported API
This guide walks through Tessera’s own API as webtessera ports it, and maps every package to its Go counterpart; read it when you need what the safe API leaves out, such as custom storage, migration, antispam, witness policies, key rotation or Static CT.
Every safe API log exposes this API as log.reader and log.appender, so you can start with the safe
API and drop down only where you need to.
Conventions
Names follow Go’s, with functions in camelCase: NewAppender is newAppender. Go’s uint64 is
bigint, durations are milliseconds, errors are thrown with Go’s message text, and context.Context
is an optional trailing AbortSignal. Test for Go’s sentinels with errorIs(err, ErrPushback), never
===, and find an error class anywhere in a cause chain with errorAs(err, ErrInconsistency), both from
webtessera. AGENTS.md §3 has the full mapping, so Tessera’s own
documentation applies to this API once you translate the names.
Write to a log
Create a signer
The log signs its checkpoints with a signed-note signer, whose name is the log’s origin:
// Generate the key pair once. Keep skey secret; publish vkey so that clients can
// verify the log's checkpoints.
const { skey, vkey } = generateKey(undefined, "example.com/my-log");
const signer = newSigner(skey);
generateKey, newSigner and newVerifier come from webtessera/note.
Start an appender
Import the root package and one storage driver, then start an appender on the driver:
import { newAppender, newAppendOptions, newEntry, newPublicationAwaiter } from "webtessera";
// Choose one!
import { newMemoryDriver } from "webtessera/storage/memory";
// import { newIndexedDBDriver } from "webtessera/storage/indexeddb";
// import { newSqliteDriver, fromSqliteSync } from "webtessera/storage/sqlite";
const driver = newMemoryDriver();
const signer = createSigner();
const { appender, shutdown, reader } = await newAppender(driver, newAppendOptions().withCheckpointSigner(signer));
newAppender takes an optional AbortSignal as its last argument, which its background tasks watch.
Add entries
appender.add returns a future. Call it to get the index that the log durably assigned to the entry:
const index = await appender.add(newEntry(data))();
The entry is then sequenced and integrated into the tree, but clients can verify it only once a
published checkpoint commits to it. That happens within the checkpoint interval, ten seconds by
default. To wait for it, for example to return an inclusion proof, use a PublicationAwaiter:
// One awaiter per log, shared by every request: it polls the published checkpoint
// only while somebody is waiting.
const awaiter = newPublicationAwaiter((s) => reader.readCheckpoint(s), 100, signal);
const [index, checkpoint] = await awaiter.await(appender.add(newEntry(data)));
Shut down
Call shutdown() before you abort the signal passed to newAppender. It waits until every entry that
the appender accepted is integrated and covered by a published checkpoint. After it returns, add
fails.
Tune the appender
newAppendOptions() returns an AppendOptions. Its methods mirror Tessera’s With* options; these
are the ones most logs set, and each method’s doc comment covers its parameters:
| Method | What it configures | Default |
|---|---|---|
withCheckpointSigner(signer, ...additional) |
The checkpoint signer, plus signers of the same name, for key rotation. | required |
withCheckpointAsyncSigner(signer, ...additional) |
The same, for asynchronous signers such as the safe API’s LogKey. |
— |
withBatching(maxSize, maxAgeMs) |
When a batch of entries goes to the sequencer. | 256 entries, 250 ms |
withAntispam(inMemEntries, antispam) |
Deduplication of entries already in the log. | off |
withCheckpointInterval(intervalMs) |
How often a new checkpoint is published. | 10 s |
withCheckpointRepublishInterval(intervalMs) |
How often an unchanged checkpoint is published again. | 10 min |
withWitnesses(group, opts) |
The witnesses that must cosign each checkpoint before it is published. | none |
withGarbageCollectionInterval(intervalMs) |
How often obsolete partial tiles and bundles are removed. | 1 min |
Choose a storage driver
Every driver runs the same storage engine, a port of Tessera’s POSIX driver. To keep the log in a browser, use IndexedDB:
// The log survives reloads, and several tabs may share it: Web Locks serialise
// their writes.
const driver = await newIndexedDBDriver({ name: "my-log" }, signal);
const { appender, shutdown } = await newAppender(driver, newAppendOptions().withCheckpointSigner(signer), signal);
On a server or at the edge, use SQLite through the adapter for your engine:
// node:sqlite here; any other engine changes only this line, through its own adapter.
const database = fromSqliteSync(new DatabaseSync(file));
const driver = await newSqliteDriver({ database }, signal);
const { appender, shutdown } = await newAppender(driver, newAppendOptions().withCheckpointSigner(signer), signal);
For any other backend, implement ObjectStore and pass it to newObjectStoreDriver({ store }) from
webtessera/storage/objectstore. Choosing storage covers what each backend
guarantees, the SQLite adapters, and how locking keeps a log from forking.
Read and verify a log
webtessera/client reads any tlog-tiles log, whether webtessera,
Tessera or another implementation wrote it, and verifies what it reads:
const verifier = newVerifier(verifierKey);
const log = newHTTPFetcher(new URL("https://log.example.com/"));
// Fetch the latest checkpoint and check the log's signature on it.
const { checkpoint } = await fetchCheckpoint((s) => log.readCheckpoint(s), verifier, verifier.name());
// Prove that the entry at `index` is committed to by that checkpoint.
const proofs = await newProofBuilder(checkpoint.size, (l, i, p, s) => log.readTile(l, i, p, s));
const proof = await proofs.inclusionProof(index.index);
verifyInclusion(DefaultHasher, index.index, checkpoint.size, DefaultHasher.hashLeaf(data), proof, checkpoint.hash);
verifyInclusion comes from webtessera/merkle/proof, and DefaultHasher from
webtessera/merkle/rfc6962. For a local log, the reader that newAppender returns can stand in for
the HTTP fetcher. To follow a log over time and prove that each checkpoint extends the last, use
newLogStateTracker; a monitor shows how.
Beyond appending
| Task | API |
|---|---|
| Static CT logs | newCertificateTransparencyAppender and withCTLayout (webtessera), with entries from webtessera/ctonly |
| Import an existing tlog-tiles or Static CT log | newMigrationTarget and newMigrationOptions (webtessera) |
| Witness policies | newWitnessGroupFromPolicy (webtessera), passed to withWitnesses |
| Whole-log audits | newFsck (webtessera/fsck), whose bundle hasher defaults to defaultMerkleLeafHasher |
| Tests of your own code | newTestLog (webtessera/testonly), an in-memory log |
Packages
The package is split the way Tessera is split into Go packages, plus the additions marked —:
| Import | Go counterpart | Contents |
|---|---|---|
webtessera |
tessera |
appender, options, publication awaiter, antispam, witnessing, migration |
webtessera/client |
tessera/client |
fetchers, proof building, entry streaming, log state tracking |
webtessera/storage/objectstore |
tessera/storage/posix |
the storage engine, on a six-method key/value contract |
webtessera/storage/memory, webtessera/storage/indexeddb |
— | in-memory and IndexedDB backends |
webtessera/storage/sqlite |
— | the SQLite backend and one adapter per engine |
webtessera/api, webtessera/api/layout |
tessera/api, tessera/api/layout |
tile and entry-bundle formats, tlog-tiles paths |
webtessera/fsck |
tessera/fsck |
whole-log verification |
webtessera/ctonly |
tessera/ctonly |
Static CT API entries |
webtessera/testonly |
tessera/testonly |
an in-memory test log for your own tests |
webtessera/server |
— (safe API) | openServerLog, importLogKey, receipts; refuses to run in browsers |
webtessera/browser |
— (safe API) | openBrowserLog, device keys, receipts |
webtessera/http |
— | serves a log over the tlog-tiles HTTP API, as a fetch-style handler |
webtessera/witness |
— | a tlog-witness server, the other side of the witnessing in webtessera |
webtessera/mirror |
tessera/cmd/experimental/mirror |
copies a log into S3-compatible storage or any ObjectStore |
webtessera/note |
golang.org/x/mod/sumdb/note |
signed notes, signers and verifiers |
webtessera/formats/log |
transparency-dev/formats/log |
checkpoints |
webtessera/formats/proof |
transparency-dev/formats/proof |
C2SP tlog-proof encoding |
webtessera/merkle/* |
transparency-dev/merkle/* |
RFC 6962 hashing, compact ranges, proofs |
The doc comments in each package’s source are its reference.
This page is generated from docs/guides/ported-api.md
. Edit it on GitHub.