Documentation (14 pages)
Status: the reproducible-build metadata (cargo-chef recipe + a fixed
SOURCE_DATE_EPOCH, recorded in the manifest) and the verification instructions below are published. The build script defaultsSOURCE_DATE_EPOCHto the source commit time; packages built before 2026-09-25 carry an emptysourceDateEpoch. The signing-identity decision (D-08) is made — minisign with a pinned key (trust/release-key.pub; no third party in the trust path). Release binaries ride the implemented chain (below); the/pkgmanifest slot activates with the deploy-path wiring at the key ceremony. Nothing here is gated behind a third party: the hash comparison works today.
Reproducible Builds
The browser client ships as two artefacts — tallow_web_bg.wasm (the Rust
encryption engine, compiled to WebAssembly) and tallow_web.js (the bindgen
glue that loads it). Both are published with SHA-384 hashes in
/pkg/manifest.json, and the in-page
self-test re-hashes the bytes your browser actually receives
against that manifest.
This page documents how to rebuild those artefacts from source and compare the result, so the hash in the manifest is something you can check rather than something you have to trust.
Building the package
The build is deliberately boring: a pinned toolchain, a fixed source timestamp and a deterministic dependency recipe.
git clone https://github.com/tallowteam/Tallow.gitcd Tallow
# 1. Fixed source timestamp — the build must not depend on the clock.# Our releases use the release commit's time; to reproduce a published# package, use the exact value recorded in its manifest (`sourceDateEpoch`).export SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)
# 2. Deterministic dependency layer (cargo-chef):# the recipe is computed from Cargo.lock + Cargo.toml only, so the# dependency build does not depend on source-file mtimes.cargo install cargo-chef --lockedcargo chef prepare --recipe-path recipe.jsoncargo chef cook --release --target wasm32-unknown-unknown --recipe-path recipe.json
# 3. Build the engine and emit the deployable packagebash scripts/build-browser-wasm.shscripts/build-browser-wasm.sh writes the package into
website/public/pkg/: the wasm, the glue, .br/.gz sidecars, a
CHECKSUM.sha256 and the SRI manifest.json (wasmHash / jsHash, both
sha384-…).
Comparing against the published hashes
# Hashes produced by your buildpython3 - <<'PY'import base64, hashlibfor name in ("tallow_web_bg.wasm", "tallow_web.js"): b = open(f"website/public/pkg/{name}", "rb").read() print(name, "sha384-" + base64.b64encode(hashlib.sha384(b).digest()).decode())PY
# Hashes the site publishescurl -s https://sendtallow.com/pkg/manifest.jsonEquality of both fields (wasmHash, jsHash) is the whole check. A mismatch
is a real signal: either the toolchain drifted or the published artefact is not
the one this source produces — report it (see /.well-known/security.txt).
The comparison proves the bytes you received are the ones the manifest
describes; the rebuild recipe above is the second half — rebuild, compare, and
the pins below are what make the result reproducible. Attesting who built a
published artefact is the job of the signature slot (D-08, still null).
Hash algorithm
The manifest uses SHA-384 (SRI form, sha384-…). Older text in the design
report refers to SHA-256 for the same artefacts; SHA-384 is the shipped format
and the one the self-test verifies. The algorithm in the verification path
is what matters, so the divergence is recorded here rather than papered over.
Signature (minisign, pinned key — D-08)
D-08 is decided: minisign (Ed25519), with the public key pinned in the
source tree at trust/release-key.pub — trust is “pin the key”: no keyring,
no keyserver, no third party in the trust path.
Release binaries ride the implemented chain (it activates at the key
ceremony): each release publishes a MANIFEST.json — every archive with its
sha256 — signed to MANIFEST.json.minisig. Verification:
minisign -Vm MANIFEST.json -x MANIFEST.json.minisig -p trust/release-key.pubscripts/verify-release.sh <release-dir> # end-to-end incl. every artifact hashThe /pkg manifest slot (this page’s subject) activates with the same
ceremony: the deploy-path wiring is the remaining step, and the pin file
carries a fail-closed placeholder until the key exists — releases refuse to
publish unsigned in the meantime. Custody model and the out-of-band channels
(repo file now; CLI constant and DNS TXT queued) live in trust/README.md.
Toolchain pins
| Component | Pin |
|---|---|
| Rust target | wasm32-unknown-unknown |
| wasm-bindgen CLI | must match the locked crate version (the build script enforces it) |
| wasm-opt | -O3 with the listed feature flags (binaryen) |
| Source date | SOURCE_DATE_EPOCH — default: source commit time; recorded in the manifest |