Skip to main content
This page is for the operator who has issued bundles and must keep them interpretable, and for the counterparty handed an archive. After reading it you will know what each registry file records and what the writers refuse to do. You will also know why a lapse and a voidance are different records, and what an archive contains and leaves out. A bundle carries proofs but not everything a reader needs to interpret them. Three things live outside it. Which values a list root stood for. Which root was on chain at a given block. Whether the operator has since declared a claim ended or never valid. Version 3 keeps those in three registries that are signed and append-only, and packages them with the bundles in an archive.

The registry directory

The location is security.registryDir in config.json, default config/registry; the native binaries take --registry <DIR>. Only trusset-registry writes here, and every write is signed with TRUSSET_KYC_SIGNING_KEY, the same key that signs manifests. A counterparty who trusts an operator’s bundles therefore trusts the registries without pinning a second key. Each .sig is a detached 64-byte ed25519 signature over the document’s canonical bytes: compact JSON with keys sorted at every depth. The readable file is pretty-printed; verifiers and the archive builder recompute the canonical bytes from the parsed document rather than from the file. Files are written atomically. Append-only holds by construction. A list version can only be added after the current one, an anchor cannot replace an anchor at the same height, and a revocation is only ever pushed. The alternative, letting an operator rewrite the file that establishes what was true when, would defeat the point of keeping it.
The verifier checks the registry signatures when it reads them from an archive and a --public-key was supplied. When it reads them from a --registry directory, it checks their structure only. The signatures exist for the archive path, where the registries leave the operator’s machine.

List versions

lists/<listId>.json holds every version of an allowlist, with its values, in order:
string[]
The entries, NFC-normalised, in source order. This is the field that makes an old setRoot interpretable years later.
string
The Rescue Merkle root of the list, padded to a power of two by duplicating the last leaf. This is the setRoot public input of every proof made against the version.
integer
The tree depth, which becomes the proof’s setDepth. At most 32.
string
SHA-256 over the values joined by newlines. Changes only when the set changes.
string
SHA-256 over the source file’s verbatim bytes, comments included. Changes when a comment is edited.
integer
First epochDays on which the version was authoritative, inclusive.
integer
Last day, inclusive. Absent on the current version only.
string
The tool that built the commitment, the wall-clock time of the record, and the operator’s free-text reason for the rotation.

Rotating a list

  1. Edit the source file, for example config/list_commitments/country_allowlist.source.txt.
  2. Record the new version with the day it becomes authoritative:
The previous version’s window closes on the day before the new one opens, so the windows abut with no gap and no overlap. Its values, root and hashes stay in the file forever. The writer refuses three things. Removing or reordering a version. Recording a version whose values are unchanged, because two versions sharing a root could not be told apart by a proof. An effective date on or before the current version’s start. --effective-date defaults to today. A batch resolves the version authoritative on its as-of date, not the newest one. A batch dated before a rotation keeps using the older version, and a batch dated before the first version fails with a message naming the earliest effective date. Each set_membership leaf records the version it used in binding.listVersion, and verification resolves the list from the leaf, never from current config. Rotating a list neither invalidates any kycHash nor requires re-proving.

Root anchors

root_history.json records where a subject’s root was published. The tool has no chain access, so this is the operator stating, under signature, what they published and where.
string, string, integer
Read from the bundle’s manifest, so an anchor is tied to a specific issuance.
integer
Where and when the root was published. The timestamp, in Unix seconds, is what lets an as-of query be phrased as a date. An as-of block check evaluates validity windows on the day of that timestamp.
string
Optional, recorded verbatim from --tx.
string
SHA-256 of the manifest.json the root came from.
Record an anchor whenever you publish a root:
Recording the same publication twice is a no-op. Recording a different root for the same subject at the same height on the same chain is refused. A correction is a new anchor at the block where the correct root was published. An as-of block query resolves to the latest anchor for that subject at or below the requested height. If there is none, the subject had no root on chain then, and the verifier reports it as not entitled and says why.

Revocations

A signed manifest cannot be edited, so ending a claim is a separate record in revocations.json. The tool refuses to blur the two legally distinct ways a claim stops being usable: The last row is deliberate. A voided bundle keeps verifying because the proof is a true record of what the operator asserted. Destroying it to express that the assertion was wrong would destroy the evidence of the misstatement. overall_pass stays true and temporal.entitled goes false.
Without --leaf, the record applies to every leaf of the bundle. The leaf name must exist in the manifest.
string
A revocation is always about one specific issuance, identified by its root, never about a subject in the abstract.
string
The leaf it applies to. Absent means every leaf.
string, integer
lapsed with the first day the claim no longer holds, or void_ab_initio with no date.
integer
The day the operator made the declaration, which is not the day the claim ended. A lapse discovered late has declaredAt well after effectiveFrom, and that gap is itself audit-relevant.
string
A short machine-readable reason, optional free text, the key id from the bundle’s manifest, and the wall-clock time of the record.
When a verifier evaluates a leaf, a voidance wins over everything, then the earliest applicable lapse, then the leaf’s own validity window. Verification has the resulting statuses.

Archives

An archive is one JSON file, by convention with the .tka extension, that verifies on a bare machine: no config, no registry, no output tree, no network, no chain. It carries the bundles, every version of every list their proofs depend on, the root history, the revocation log with all their signatures, the issuer identity, and the toolchain block.
--all <dir> takes every directory under <dir> that contains a manifest.json; --bundle <dir> names directories one by one and can be repeated. Building needs no key material: the registries carry their own detached signatures and the manifests carry theirs. The builder refuses three things. A manifest whose bytes do not match their own canonical serialisation. Bundles that name different issuers in one archive. A batch that depends on a list with no registry.

Contents

Two archives of the same content compare equal on digest regardless of when they were built. Any modification is refused at parse time with archive integrity check failed, exit code 66, before any of the contents are believed. secrets.enc is excluded unless --include-secrets is passed. An archive is usually handed to a counterparty, and the witness blob is operator-private even though it is encrypted.

Reading one back

Verifying from an archive is on Verification. Store the file wherever you keep things you may have to produce in five years.

Inspecting the registries

prints every list version with its window, every anchor, and every revocation in the working tree. npm run show prints the same summary after the resolved configuration.

In the repository