3. The verification report
make verify performs five checks and writes verification-report.json.
The daemon refuses to start if that report is missing, stale, or failing
(INV-BUILD-1), so a spec that stops matching its code stops the device booting.
| Check | What it asserts |
|---|---|
| Coverage | Every runtime export from the entry points of packages/core, the daemon and the ui appears in some spec's covers list; type-only exports are exempt. An uncovered export, or a covers entry naming a symbol that no longer exists, is a hard failure. |
| Invariant binding | Every declared invariant names at least one test, that test exists, and it passed. make verify runs the whole suite first, so failing vector and differential tests fail here. |
| Vectors | Every vector file a spec declares in spec/vectors/ exists and still matches its pinned SHA-256. The vectors themselves run in the suite. |
| Differential | core.address.derive, core.derive.hd and core.psbt.sign each still declare a differential oracle, and critical-tier oracles declare at least 100 cases. Agreement with bitcoinjs-lib is tested in the suite. |
| Integrity | Every file MANIFEST.lock lists exists and matches its hash, and the lock is sorted with no paths differing only by case. A tracked file missing from the lock is not caught here. The root hash is computed for the report, not compared with anything. |
It then writes the report: assurance tier, root hash, spec and invariant
counts, specs per assurance tier, each check's status, coverage, the invariant
bindings, a summary of each spec, and an overall passed. It holds no test
results and no dependency tree hash. That is output, not a sixth check: a
report cannot certify itself.
Vectors and differential report not-applicable, never passed, when no spec
declares any. Coverage, invariant binding and integrity report only passed or
failed, so an empty run of one of those would read as passed.
The report's tier is not measured from the build: the verifier writes the
literal signer, and the tier is not an input to the root hash. There is one
tier anyway: packages/ holds core, daemon, ui and verify,
packages/wallet does not exist, and phase 5 has not opened. The field cannot
hold a second value, and the lock screen distinguishes nothing by it.
Reading it yourself
verification-report.json is plain JSON. The fields that matter:
rootHash: compare with the lock screen, and with a published release once one existstier:signer, and onlysigner, todaycoverage.uncovered: must be emptyinvariants[].status: every one must bepassed. Each entry is one binding, an invariantidand one test selector intest, so an invariant bound to several tests has several entries; there is notestsarray
No field covers how node_modules resolved. The report has never carried a
dependencyTreeHash, and nothing in packages/, tools/ or the Makefile
computes one. That is a gap; standing in for it are make repro-check, which
builds twice from a clean tree and compares the output, and the SBOM.
A passing report is only as trustworthy as the build that produced it, which is why step 2 comes first.
Rendered from docs/VERIFICATION.md, the same bytes that ship with the code. SHA-256 15ae2ba12417b4691f63d9a4777eba2c328ff694961efea68f5e7dec4ae2e0bd