Skip to main content
This page is for an integrator parsing bundles or a reviewer checking one by hand. After reading it you will know what each file contains and which fields the signature covers. You will also know which fields a version 2 bundle lacks, and which file must never leave the operator’s machine. A bundle is one directory per subject, named by the EIP-55 checksummed wallet address:
The prover writes the directory under output/.tmp/ and renames it into place after the manifest is signed, so a bundle that exists is complete.

kycHash.hex

The subject’s Merkle root as 0x followed by 64 lowercase hex characters, 66 bytes, no newline. It equals manifest.kycHash and the kycHash column of the batch index; the verifier checks the first equality as kyc_hash_file_matches.

manifest.json

Serialised with keys sorted at every depth, two-space indentation and Unix newlines. The verifier and the archive builder re-serialise the parsed document and compare bytes, so the file must not be reformatted. A version 3.0.0 manifest, with the leaves elided:

Top-level fields

Fields marked 2.1 or 3 are absent from older manifests; every one of them is optional-with-omission, which is what keeps older signatures valid under current tooling.
string
3.0.0, 2.1.0 or 2.0.0. The verifier accepts all three.
string
production, or stub for a bundle built with --skip-proofs. Inside the signed payload, so relabelling a stub bundle invalidates its signature.
string
The subject, checksummed.
string
The Merkle root over the leaves.
integer
The batch as-of date, in days since 1970-01-01.
string
Wall-clock UTC timestamp of the run, ISO 8601 at second precision. The only field that differs between two deterministic runs, and the only field the signature does not cover.
boolean
Whether the per-subject key was derived from the master key (true) or drawn from OS entropy (false).
integer
The circuit set the proofs were made under. 3 since tool version 2.1; 2 bundles are verified against the frozen set_membership AIR.
object
library, libraryVersion, field, hash, and securityBits, the advertised floor of 100.
array
One entry per leaf, in the order of the Merkle tree, which is the order of the leaf hashes as bytes. See below.
object
country, investorType, softExpiryDays and hardExpiryDays in cleartext, as the input carried them or as the configured defaults filled them. Covered by the signature, not by any proof.
object
For every list a set_membership leaf used: its Merkle root and the sourceSha256 of the source file.
integer
The layout of secrets.enc. 2 wraps an independent per-subject key under the master key; 1, on bundles from tool version 2.0, derived the box key from the master key directly.
integer
2.1. The root construction. Absent means version 1.
string
2.1. SHA-256 over the canonical projection of the config that entered the key derivation. Lets a bundle be decrypted or re-keyed without out-of-band knowledge of the config that produced it.
string
  1. The expression that produced epochDays, such as fixed:20708 or env:TRUSSET_KYC_EPOCH_DAYS.
string
  1. epochDays rendered as YYYY-MM-DD.
boolean
  1. Present, and true, only on a bundle built with --dev-unpinned-epoch. Such a bundle cannot be reproduced and the verifier says so.
object
  1. The party responsible for every assertion: name, identifierScheme, identifier, jurisdiction, keyId, and publicKey, the ed25519 verifying key as 0x + 64 hex. Recording the key does not make the signature self-authenticating. It tells a counterparty which key to obtain out of band, and the verifier reports when a supplied key disagrees with it.
object
  1. proverVersion and proverSha256, the SHA-256 of the prover binary that ran, measured from the running executable. sourceCommit appears when the TRUSSET_KYC_SOURCE_COMMIT environment variable was set at build time and is advisory.

Leaf fields

A set_membership leaf from the same bundle:
string
The leaf name: the source field for a single-instance predicate, or field@suffix for a later instance.
string
age_threshold, set_membership, tier_threshold or preimage_knowledge. A commitment_only leaf records preimage_knowledge here and carries its mode in disclosure.
integer
1, 2, 3 or 4. Must agree with circuit; the verifier rejects a mismatch.
string
The hiding commitment over the value and the leaf’s nonce.
object
What the proof was made about. commitment always; then minAgeDays and currentEpochDays for age_threshold, listId, setRoot and setDepth for set_membership, minTier for tier_threshold, nothing more for preimage_knowledge.
string
Rescue(PUBLIC_DIGEST_TAG ‖ canonical public bytes), with the byte layout given per circuit on Circuits.
string
Always proofs/<fieldName>.bin. The only manifest field that reaches the filesystem. Every reader requires a plain proofs/<name> entry with no separators, no parent references, no leading dot and at most 255 characters, and refuses anything else.
string
Rescue(PROOF_DIGEST_TAG ‖ proof bytes). Recomputed by the verifier from the file.
string
SHA-256 over the circuit’s canonical parameter encoding. Recomputed by the verifier from params, or for a version 2.0 leaf from publicInputs.
string
The leaf hash; see Concepts.
string
2.1. plaintext, predicate or commitment_only.
object
2.1. The resolved predicate parameters: minAgeDays, minTier as a number, listId with sourceSha256, or maxValueBytes.
object
  1. The claim descriptor, the validity interval, and for set_membership the listVersion reference. Every leaf of a 3.0.0 manifest carries one.
string
  1. Rescue(LEAF_BIND_TAG ‖ canonical_json(binding)), present exactly when binding is. The verifier recomputes it and rejects a leaf whose descriptor does not reproduce the recorded digest, and a leaf that has one without the other.

manifest.sig

Sixty-four raw bytes: an ed25519 signature (RFC 8032, deterministic) over the signing view of the manifest. The signing view is the canonical serialisation with createdAt replaced by the sentinel 1970-01-01T00:00:00Z; the file on disk carries the real timestamp and is otherwise identical. The verifier performs the same substitution before checking, so two runs at different times produce byte-identical signatures. Two consequences follow. A consumer with its own verifier must recompute the signing view by re-serialising the typed document. A verifier that does not know a field will drop it and compute the wrong bytes. Every schema addition therefore requires downstream verifiers to be rebuilt, including the WASM build. And secrets.enc sits outside the signing payload by design, which is what allows master-key rotation to rewrite it without touching the signature.

proofs/

One file per leaf, named by the leaf name, containing the serialised Winterfell proof. Sizes for the shipped parameters run from about 16 KB for tier_threshold to about 50 KB for set_membership; Performance has the measurements. A commitment_only leaf has a zero-byte file, and so does every leaf of a stub bundle. The file exists so every consumer that enumerates proofPath finds one. The verifier accepts an empty proof only for a commitment_only leaf or under --accept-stub, and rejects it on any other leaf of a production bundle.

secrets.enc

The only file that holds plaintext-equivalent material. It carries, for every leaf, the field value, the 32-byte commitment nonce and the circuit name, as a JSON map keyed by leaf name. It is what allows the operator to reopen a commitment or answer “show me the value behind this leaf” to a regulator. Layout under secretsKdfVersion: 2:
secretbox is XSalsa20-Poly1305, byte-compatible with libsodium’s crypto_secretbox_easy. The wrapping key and its nonce derive from the master key through HKDF-SHA256, with the subject, batch date, config hash and KDF version as context. The data nonce derives from the subject key. Decrypting therefore needs the master key plus values that are all readable from manifest.json. Security model has the full chain.
secrets.enc never ships. It is excluded from archives unless --include-secrets is passed, no verification path reads it, and under --no-deterministic it holds the only copy of the subject key. Back it up as operator-private material.

What changes between versions

A 2.0.0 manifest parses into the same structure, re-serialises byte-identically and keeps its signature valid, which the test v200_manifest_reserializes_byte_identically enforces against a real 2.0.0 fixture. A 3.0.0 bundle’s root differs from what 2.1.0 would have produced for the same input, because the binding digest is inside every leaf hash.

In the repository