Compatibility with Tessera

webtessera is a translation of Tessera at 4a6d9f9, and a log it writes is byte for byte the log Tessera writes for the same entries. 22 golden fixture files, the golden suite on eight storage backends, Go interop and 86,008 differential records check that claim on every change. Each number below is counted from the repository while this page is built.

Golden fixtures

fixtures/data holds 22 files recorded by running Tessera at 4a6d9f9: paths, tiles, entry bundles, proofs, notes and checkpoints, and complete logs of 0, 1, 2, 255, 256, 257, 1,000 and 5,000 entries written by its POSIX driver, .state files included. The tests compare webtessera’s output with them byte for byte. CI regenerates them from Tessera on every change and fails if any byte differs.

The golden suite

Every storage backend, in every runtime it supports, must store exactly the files Tessera’s POSIX driver stores for the fixture logs: in one batch, in batches, across restarts, and when it carries on a log Go wrote.

BackendNode.jsChromiumworkerdlive server
memory yesyes
IndexedDB yesyes
node:sqlite yes
libSQL yes
rqlite yesyes
sqlite-wasm yes
Cloudflare D1 yes
Durable Object SQL yes
WebCrypto keys yesyesyes

The last row replays Go’s signed notes and checkpoints through non-extractable WebCrypto keys. Ed25519 is deterministic, so they must match to the byte.

Go interop

In CI, Tessera’s own Go code verifies, reproduces byte for byte and carries on logs that webtessera wrote, and webtessera does the same with logs Go wrote, on four backends: memory, indexeddb (fake-indexeddb), sqlite (node:sqlite) and sqlite (libSQL). The harness is interop/, driven by scripts/interop.mjs.

Differential corpora

Go’s verdict, exact error text and output on 86,008 generated inputs, most of them malformed, and on every Unicode code point, in 19 corpora replayed in Node.js, Chromium and workerd. Every difference from Go is a named divergence with a decision record, or a failure.

CorpusWhat Go recordedRecords
differential_api api.HashTile.UnmarshalText and api.EntryBundle.UnmarshalText 108
differential_bundle_hashers the entry-bundle hashers 400
differential_checkpoint formats/log 8,460
differential_checkpoint_publisher AppendOptions.CheckpointPublisher without witnesses 414
differential_compact merkle/compact 6,061
differential_cosig formats/note cosignature/v1 3,636
differential_ct_log A static-ct log of 300 seeded CT entries 308
differential_ed25519 Ed25519 verification as Go's crypto/ed25519.Verify performs it 3,420
differential_gostd the Go standard library behaviour src/internal/gostd stands in for 28,260
differential_layout api/layout 8,664
differential_note_keys encoded note keys 5,260
differential_note_open sumdb/note Open 1,654
differential_note_sign sumdb/note Sign 1,508
differential_proof_consistency merkle/proof VerifyConsistency 4,681
differential_proof_inclusion merkle/proof VerifyInclusion 4,835
differential_proof_nodes merkle/proof Inclusion and Consistency 3,023
differential_rfc6962 merkle/rfc6962 436
differential_unicode unicode/utf8.Valid 4,005
differential_witness_policy NewWitnessGroupFromPolicy 875

Test parity

Every Go test, example, fuzz target and benchmark of Tessera and of the modules it depends on must have a passing TypeScript test of the same name, or an entry in scripts/test-parity-allowlist.json that cites the decision record leaving it out: 20 entries today, citing six records. CI fails on any other.

What is compared

Artefact Fixtures and golden suite Interop harness
Tiles (full and right-edge partial) byte-identical byte-identical, and re-derived by Go’s fsck
Superseded partial tiles and bundles byte-identical (the prefix POSIX wrote) byte-identical
Entry bundles byte-identical byte-identical; every entry checked against the corpus
Signed checkpoint byte-identical byte-identical at every batch, signature verified by Go
.state/treeState, .state/version byte-identical to Go’s byte-identical; each side resumes the other’s
.state/gcState not compared (GC is off in the fixtures; json_test.ts pins its encoding) not compared (GC off)
Set of paths exact exact

Porting status

Each source file mirrors the upstream file of the same name, with its comments carried over. docs/PORTING-MAP.md tracks every Go file:

StatusTessera’s filesIts Go dependencies’ files
Done 53 37
Not ported 54 5

Every divergence from Go, however small, and every file not ported has a decision record: 153 records, of which 147 accepted and six superseded.

CI

ci.yml runs these jobs on every pull request and push, and “CI passed” fails if any of them does; a release is published only after the same jobs pass:

Quality
Lint, Typecheck, Go (gofmt, vet) and Workflows (actionlint).
Test
Unit (Node 22), Unit (Node 24), Browser (Chromium), Workers (workerd) and Services (rqlite, S3).
Compatibility
Fixtures are reproducible, Go and TypeScript interoperate and Upstream test parity.
Package
Build the tarballs and Check the tarballs.
Runtimes
Smoke test (node), Smoke test (bun) and Smoke test (deno).
Examples
Examples.
Site
Landing page.

Runtimes

webtessera runs on Node.js 22.18 or later, Deno 2, Bun, current browsers, and edge runtimes built on web standards. CI runs the unit tests on Node.js 22 and 24, the browser tests in Chromium, the Workers tests in workerd and the services tests against live rqlite and S3-compatible servers, and smoke-tests the built package on Node.js, Bun and Deno.

Reproduce it

bun install
bun run fixtures && git status --porcelain fixtures/data   # nothing printed: the fixtures are Go's
bun run test:unit                                          # includes the golden suite on Node
bun run test:browser                                       # PLAYWRIGHT_CHROMIUM_EXECUTABLE=... to use a local Chromium
bun run test:workers
bun run test:parity                                        # every upstream Go test has a TypeScript counterpart
bun run interop                                            # checks out upstream and builds dist/ itself

bun run interop accepts --seed N, --size1 N --size2 N (for example --size1 3000 --size2 5000 for a quick run), --backend NAME (repeatable), --keep to keep the logs, and --no-build to reuse an existing dist/. It needs Go 1.24 or later on PATH and, the first time, access to the Go module proxy.

The Go tools also run on their own, for example against a log exported from a browser:

cd interop
go run ./verify -dir /path/to/log -vkey "$(cat log.vkey)" -history /path/to/checkpoints
go run ./produce -dir /tmp/log -skey "$SKEY" -seed 1 -from 0 -ends 1,256,1000 -history /tmp/log.history

docs/compatibility.md explains each layer of evidence in full, and how to hold a new backend to it.