1. The manifest and the root hash
What the manifest is
MANIFEST.lock lists the SHA-256 of every file git tracks in the manifest
roots (package.json, package-lock.json, packages/, spec/,
provisioning/), one per line, sorted by path. Every tracked file counts, not
an allowlist of extensions; an allowlist once missed packages/ui/index.html,
which carries the device frontend's content security policy.
It uses the exact output format of sha256sum, so sha256sum itself can check
it:
f5603a6435f46cecb5040b2afb318027528b4e87b81afade0c260cf7ed7066b2 packages/core/src/entropy/combine.ts
e4c81d6e661b430d874616bb2f2bbf7d5546cfd34097840a4a077991e80ef0dc packages/core/src/entropy/dice.ts
...
The root hash is the SHA-256 of MANIFEST.lock itself. The device shows it
on its lock screen, and each release will publish it. There are no releases
yet.
Checking it
Verify that every file on disk matches the manifest:
$ sha256sum -c MANIFEST.lock
packages/core/src/entropy/combine.ts: OK
packages/core/src/entropy/dice.ts: OK
...
Compute the root hash:
$ sha256sum MANIFEST.lock
f08ead186b9eb2a84e77e5850cccad26b7eb7486757d0911558ff296113e7d0c MANIFEST.lock
Regenerate the manifest from scratch and confirm it matches what is committed:
$ git ls-files -z package.json package-lock.json packages spec provisioning | LC_ALL=C sort -z | xargs -0 sha256sum | sha256sum
f08ead186b9eb2a84e77e5850cccad26b7eb7486757d0911558ff296113e7d0c -
On macOS use shasum -a 256 in place of sha256sum; the values are
identical. If any of the three disagree, stop.
Why sorted concatenation and not a Merkle tree
A Merkle tree's per-file inclusion proofs help nobody who has the whole repository, and no standard command line tool computes a Merkle root over a directory, so you would have to run our implementation to check ours. A sorted concatenation needs three coreutils commands.
LC_ALL=C in the sort is required: locale-aware sorting orders paths
differently between machines, tying the root hash to your language settings.
What the manifest deliberately excludes
The lockfile is covered, so the root hash moves when a dependency does. Not covered:
apps/web, the public website, which never ships to the devicenode_modules, pinned by integrity hash in the lockfile.tsbuildinfoand other incremental build caches, which are not reproducible and not shippeddocs/, which is prose and does not execute
Excluding the website, a networked Next.js application, is a security decision: a change to a marketing page must not alter the hash a user compares before opening a wallet.
Rendered from docs/VERIFICATION.md, the same bytes that ship with the code. SHA-256 15ae2ba12417b4691f63d9a4777eba2c328ff694961efea68f5e7dec4ae2e0bd