PASS means. After reading it you will know which anchor to use, what each check establishes, and how to ask what was true at a past block.
The verifier is trusset-verifier-cli, wrapped by npm run verify. It reads a bundle directory or an archive, runs every check even after one fails, and prints a report. It needs no keys, no config and no network.
Two anchors
A bundle verified against nothing proves only its own internal consistency, which anyone who rewrote the whole bundle could also produce. The verifier therefore requires at least one anchor, and checks both when both are given.
With both flags,
anchor is public_key+root and both must hold. The operator’s key is printed by npm run init and npm run show, and recorded in manifest.issuer.publicKey. Recording it there does not make the signature self-authenticating: an adversary who rewrites the manifest rewrites that field too. The verifier reports when a supplied key disagrees with the recorded one, and the signature check governs.
What neither anchor proves
- That the input was true. A valid proof of a false record is a valid proof. The tool trusts what was under
input/. - That the root is on chain. The verifier has no chain access.
--rootis a string you supply; its authority comes from wherever you read it. - That the key is the operator’s. A public key pins whoever holds the private key. Obtaining the right key is out of band.
- Anything about
countryorinvestorType. Those two values travel in cleartext inmanifest.passthrough; their proofs bind them, they do not hide them. See Concepts. - That the claims are current.
overall_passis cryptography only. Whether the subject could rely on the bundle at a given instant is the second question, below. - Recency.
epochDaysis the operator’s chosen batch date, and the verifier does not compare it with today. A counterparty enforces its own staleness window. - Formal zero knowledge. Proof bytes are deterministic in the witness and the nonce; hiding rests on the commitment nonce. Overview states this in full.
- The registry signatures, when read from a directory. Registries under
--registryare validated for structure only. Their signatures are checked when they arrive inside an archive and a--public-keywas supplied.
The check ladder
Every check runs regardless of earlier results, so the report is complete.layout_ok:manifest.json,manifest.sig,kycHash.hexand everyproofs/*.binnamed in the manifest are present, eachproofPathis a plainproofs/<name>entry, andversionis2.0.0,2.1.0or3.0.0.mode_acceptable:modeisproduction, orstubwith--accept-stub.kyc_hash_file_matches:kycHash.hexequalsmanifest.kycHash.signature_valid: with a key, the ed25519 signature verifies over the canonical signing view,createdAtreplaced by the sentinel.root_anchor_valid: with a root, it equalsmanifest.kycHash, case-insensitively.- Per leaf, in order:
bindingDigestrecomputes frombinding, and neither is present without the other.- A declared
listVersion.rootequals the proof’ssetRoot. - The validity interval is well formed.
commitmentandpublicDigestdecode.- The proof file exists and
proofDigestrecomputes from it. circuitandcircuitIdagree.paramsHashrecomputes fromparams.- An empty proof is allowed only for
commitment_onlyor a stub bundle. - The circuit’s
verify()accepts the proof against the public inputs, with the AIR selected bycircuitsVersion. leafHashrecomputes from all of the above.
merkle_root_valid: the leaf hashes recompute tomanifest.kycHashundermerkleVersion.overall_pass: every check above, every leaf passed, and every supplied anchor held.
reason, such as proof digest mismatch, leaf hash mismatch, paramsHash mismatch: ... or empty proof on a proving leaf in a non-stub bundle.
Verifying and being entitled
overall_pass answers whether the bundle holds together. temporal.entitled answers whether the subject could rely on it at the instant asked about. It is true only when four things hold. The bundle passes. The instant could be resolved. The root live at that instant is this bundle’s root, where that was checkable. Every leaf’s claim is effective then.
The instant
--as-of-block requires --chain-id and is exclusive with --as-of-date. The root history and the revocation log are read from --registry <DIR>, default config/registry, or from the archive.
An as-of block question has three possible answers, and only one may be reported as entitlement. When no history was supplied, or the subject had nothing published at or before that height, entitled is false and a note says the answer is unknown rather than false:
entitled is false.
Claim status
Each leaf of a version 3 bundle reports astatus at the instant:
A voidance wins over everything, then the earliest applicable lapse, then the window. Without a revocation log the statuses reflect only what the bundle asserts, and the report says so in
notes.
The report
--json prints the report as one JSON object, or an array when an archive holds several subjects.
string
public_key, root, public_key+root, or none. A report with none never passes.boolean
false whenever the corresponding anchor was not supplied; the human report prints those as [n/a].string
As read from the bundle, for callers that branch on it.
object | null
From the manifest:
name, identifier_scheme, identifier, jurisdiction, key_id, public_key, and public_key_disagrees_with_anchor, true when a supplied --public-key differs from the recorded one. null on a version 2 bundle.object
The instant and its outcome.
as_of is now, date or block, with as_of_epoch_days and as_of_date. A block query adds as_of_chain_id and as_of_block, and resolved_root with resolved_root_matches when a history was searched. root_history_available and revocations_available say what was supplied. entitled is the answer. notes lists everything the verifier could not establish, stated rather than omitted. unpinned_epoch, manifest_as_of_date and epoch_days_source come from the manifest.object
Per leaf:
field, circuit, pass, reason (null on success), and claim.object | null
The descriptor as recorded, resolved at the instant.
status and detail give the outcome. legal_basis, legal_reference, assertion and evidence_class repeat the descriptor. issuing_party and issuing_party_identifier name the warranting party as scheme:identifier. validity_from, validity_until and basis_as_of each come with a _date rendering. For set_membership, list_id, list_version, list_resolved and list_entry_count say whether the registry or archive could resolve the proof’s root back to the list’s values. null on a leaf with no binding block.Verifies and Entitled. In the claim line the tool separates the reference from the assertion with an em dash, which these pages render as a hyphen.
Exit codes
npm run verify checks for a missing anchor itself and exits 1 with an explanation before the binary runs; the binary called directly exits 64 for the same mistake. An archive with several subjects exits 2 if any fails and 3 if any is not entitled.
Verifying from an archive
An archive carries the bundles, the list registries their proofs depend on, the root history and the revocation log, so it verifies on a machine that has nothing else:--subject <0xADDR> selects one. With a public key, the archived registry signatures are verified under it before anything else is believed. The npm wrapper takes the same path with npm run verify -- <wallet> --archive 2026Q3.tka --public-key <hex>, mapping the wallet to --subject.
An archive whose contents no longer hash to its recorded digest is refused before parsing continues:
Older and development bundles
A version 2 bundle verifies exactly as before.issuer is null, every leaf’s claim is null, and entitled depends only on overall_pass. A note says that what each predicate stands for and how long it was asserted are not recorded. A manifest with circuitsVersion: 2 is checked against the frozen set_membership AIR of that version.
A bundle built with --skip-proofs carries mode: "stub" and zero-byte proofs. It fails mode_acceptable unless --accept-stub is passed, and --strict is accepted as a no-op for older scripts. A production bundle with an empty proof on a proving leaf is rejected outright, so the two cannot be confused.
A bundle built with --dev-unpinned-epoch verifies, and the report adds reproducible: [FAIL] built with an unpinned epoch with a note that it is a development artifact, not evidence.
The WASM build
scripts/build.sh also compiles the verifier to target/wasm32-unknown-unknown/release/trusset_verifier.wasm when the target is installed. The wasm feature exposes the whole-bundle entry points verify_strict, verify_permissive, verify_with_anchors and verify_as_of. It also exposes granular ones for a consumer that drives its own check ladder: proof_digest_hex, public_digest_hex, leaf_hash_hex, leaf_hash_hex_bound, binding_digest_hex, merkle_root_hex, merkle_root_hex_v, manifest_signing_bytes, verify_manifest_signature, verify_leaf_stark and verify_leaf_stark_v. scripts/rebuild-wasm.sh produces a reproducible Node.js binding with a provenance record, and tests/e2e/wasm_parity.sh asserts the native and WASM verifiers produce byte-identical reports. A consumer that vendors the WASM must rebuild it whenever the manifest schema gains a field, or its recomputed signing view will drop the field and every signature will fail.
In the repository
- docs/ARCHITECTURE.md, the verify data flow
- verifier/src/lib.rs, the check ladder and the report types
- verifier/src/bin/cli.rs
