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 as0x 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
- The expression that produced
epochDays, such asfixed:20708orenv:TRUSSET_KYC_EPOCH_DAYS.
string
epochDaysrendered asYYYY-MM-DD.
boolean
- Present, and
true, only on a bundle built with--dev-unpinned-epoch. Such a bundle cannot be reproduced and the verifier says so.
object
- The party responsible for every assertion:
name,identifierScheme,identifier,jurisdiction,keyId, andpublicKey, the ed25519 verifying key as0x+ 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
proverVersionandproverSha256, the SHA-256 of the prover binary that ran, measured from the running executable.sourceCommitappears when theTRUSSET_KYC_SOURCE_COMMITenvironment variable was set at build time and is advisory.
Leaf fields
Aset_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
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
- The claim
descriptor, thevalidityinterval, and forset_membershipthelistVersionreference. Every leaf of a 3.0.0 manifest carries one.
string
Rescue(LEAF_BIND_TAG ‖ canonical_json(binding)), present exactly whenbindingis. 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 withcreatedAt 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 fortier_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 undersecretsKdfVersion: 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.
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
- docs/ARCHITECTURE.md, the artifact contract
- bundle-format/src/manifest.rs, the schema
- prover/src/encrypt.rs, the
secrets.enclayouts
