Selective Disclosure
Field-level redaction and selective trees: revealing a verifiable subset of an Aqua tree
Selective disclosure lets a tree's holder reveal a verifiable subset of its content: hidden fields are replaced by salted commitments, and a verifier confirms — with no side channel and no trusted party — that what was disclosed is exactly what the original revision hashes commit to.
Disclosure operates at two levels:
- Field level — a single tree-method revision is replaced by a redacted revision whose leaves are individually disclosed or sealed.
- Tree level — a whole tree is exported as a selective tree in which each revision is fully disclosed, field-redacted, or hidden.
The protocol specification is authoritative; this page is the readable reference. Selective disclosure is specified in 06-selective-disclosure.md.
Precondition: tree method
Only revisions with method: "tree" support field-level redaction — the per-leaf salted commitments of the hashing model are the disclosure mechanism. A scalar revision hashes as one opaque blob and can only be disclosed in full or hidden; a request to field-redact a scalar revision MUST be rejected.
The disclosure unit is the leaf: one entry of the revision's canonical pointer map, including the container-marker leaves (the root "", every object node as {}, every array node as []). Subtree redaction is expressed by redacting every leaf under the subtree's pointer prefix; whole-revision hiding is a tree-level directive.
The redacted revision
1{2 "revision_hash": "0x1620…",3 "leaf_count": 19,4 "leaves": [5 {6 "type": "Disclosed",7 "index": 3,8 "path": "/payloads/signer_did",9 "value": "\"did:key:z6Mk…\"",10 "salt": "0x<64 hex>"11 },12 {13 "type": "Redacted",14 "index": 5,15 "path": "/payloads/prompt_text",16 "value_commit": "0x<64 hex>"17 }18 ]19}(Example elided: a real artifact carries all 19 leaf entries.)
| Field | Meaning |
|---|---|
revision_hash | The original revision's full multihash. The hash algorithm is recovered from its multicodec; a redacted revision carries no algorithm field. |
leaf_count | The total number of leaves of the original revision. MUST be at least 1. |
leaves | One entry per leaf. Every leaf of the original MUST appear, disclosed or redacted. |
Leaf entries are discriminated by type:
| Type | Fields | Meaning |
|---|---|---|
Disclosed | index, path, value, salt | index is the leaf's zero-based position in canonical order; path is the RFC 6901 pointer; value is the compact JSON rendering of the flattened value, carried as a JSON string (a string value keeps its quotes, e.g. "\"alice\""; a number is bare, e.g. "42"; container markers are "{}" / "[]"); salt is the 32-byte per-leaf salt, 0x + lowercase hex. |
Redacted | index, path, value_commit | path is always cleartext, including on redacted leaves. value_commit = `HASH(0x02 |
Unknown members of the redacted-revision container MUST be rejected.
What redaction cannot hide
The pointer paths and leaf_count are cleartext by design (the path is authenticated into the leaf hash). A redacted revision therefore necessarily reveals:
- the complete field-name structure of the revision;
- array cardinalities (element pointers are visible);
- which optional fields were populated (absent fields produce no pointer);
- the total leaf count.
Disclosure policies must treat field presence as public.
Verifying a redacted revision
A verifier MUST, in order:
-
Count —
leaveshas exactlyleaf_countentries; reject aleaf_countof 0. -
Index bijectivity — the
indexvalues are exactly0 … leaf_count − 1: none out of range, none duplicated, none missing. -
Algorithm recovery — decode
revision_hashunder the strict multihash rules; reject any malformation. -
Leaf reconstruction — for each entry, in
indexorder:Codetext1Disclosed: leaf = HASH(0x00 || HASH(0x03 || path) || HASH(0x02 || salt || value))2Redacted: leaf = HASH(0x00 || HASH(0x03 || path) || value_commit)For redacted leaves the label is recomputed from the cleartext path; the commitment is taken as supplied.
-
Root equality — reduce the leaves with the Merkle construction, wrap the root as a multihash, and require byte equality with
revision_hash.
Any tampering — a changed value, salt, commitment, path, count, or index — surfaces as a root mismatch or an index/count violation. There is no finer-grained attribution, and none is needed: the guarantee is all-or-nothing per revision.
What redacted verification cannot check
A redacted revision proves hash integrity only. The following checks are impossible over it — they are not silently assumed to pass; they are outside what a selective artifact can prove:
- template resolution and schema validation of the payload (the payload is partly sealed; under some policies even the naming value is sealed);
- the published
leavesarray check of full verification; - signature verification of the redacted revision itself (a redacted signature revision seals the signature bytes);
- compute, anchor resolution, and every other content-dependent stage.
Consumers MUST NOT present a selectively disclosed artifact as having passed full verification. It proves integrity of the disclosed subset against the original hashes, no more and no less.
Selective trees
1{2 "revisions": {3 "0x1620…a": { "disclosure": "Full", "revision": {} },4 "0x1620…b": { "disclosure": "Redacted", "redacted": {} },5 "0x1620…c": { "disclosure": "Hidden" }6 },7 "file_index": { "0x1620…a": "name" }8}(The nested revision and redacted objects are elided; redacted carries the field-level form above.)
Each revision of the source tree maps to one of three disclosure states:
| State | Content |
|---|---|
Full | The revision verbatim. |
Redacted | The field-level redacted form above. |
Hidden | A bare placeholder carrying no fields at all, for typed object and template revisions. Hidden signature and anchor revisions are omitted from the map entirely rather than leaving a placeholder. |
Production rules
- A revision not named by the disclosure policy defaults to Full.
- Every field-redacted revision that has a
previous_revisionMUST disclose the/previous_revisionleaf (the exporter adds it if the policy did not) — chain linkage stays verifiable. file_indexentries are retained only for Full revisions; redacted and hidden revisions lose their display names.- Hidden placement. A hidden object or template revision keeps its key in the map (its placeholder), so chain continuity through it remains checkable — the loss is content, not linkage. Hidden signature and anchor revisions are omitted entirely, so a producer MUST NOT hide an anchor or signature that any retained revision chains from: the dangling reference makes the artifact fail chain verification (hiding a genesis anchor is the canonical mistake). Signatures and anchors with no dependents are the intended Hidden targets.
- Field-redaction of a signature revision, while mechanically possible for a tree-method signature, destroys its verifiability; disclosure policies MUST keep signature revisions Full (or Hidden as dead ends), and MUST keep anchor revisions Full.
Verifying a selective tree
Per entry:
- Full — recover the algorithm from the map key, recompute the revision hash, and require byte equality with the key. If the revision has a
previous_revision, that link MUST be a key of the selective tree (chain continuity). - Redacted — verify by the five-step procedure above. If a disclosed leaf with path exactly
/previous_revisionis present and its value parses as a revision link, that link MUST be a key of the selective tree. A genesis revision has no/previous_revisionleaf at all — absent fields produce no pointer — so genesis-ness manifests as the leaf's absence. - Hidden — nothing is verified; the entry attests only that a revision with that hash existed at that position.
A missing parent is a chain-break failure. Signature revisions disclosed as Full SHOULD additionally be verified cryptographically by consumers relying on the attestations; the selective-tree integrity procedure above does not include it.
No completeness guarantee
A selective tree carries no commitment to the revision set: nothing proves that all revisions of the source tree appear. An exporter can omit a revision no disclosed revision references, and a verifier cannot detect the omission. Consumers that need completeness must obtain it out of band — for example, an application-level commitment such as an audit round anchor's leaf list.
Disclosure policies
A disclosure policy maps revision links to directives:
1{2 "revisions": {3 "0x1620…a": "Full",4 "0x1620…b": { "FieldRedacted": ["/payloads/signer_did", "/payloads/created_at"] },5 "0x1620…c": "Hidden"6 }7}FieldRedacted lists the pointers to disclose (an allow-list); everything else in that revision is sealed. A listed pointer that is not a leaf of the revision is an error: the redaction — and with it the export — MUST fail. (The presets and profiles below never produce such pointers: they intersect their fixed path sets with the revision's actual leaves before redacting, so an unset optional field is dropped from the list, not an error.) Policies are exporter inputs; they are never serialized into, or committed by, the artifact.
The full preset
The empty policy: every revision defaults to Full. The artifact is the whole tree, merely in selective-tree form.
The pseudonymous preset
A fixed preset for audit trees: reveal structure, identity, and chronology; seal content. Per revision kind:
- Signature and anchor revisions: Full (attestations stay verifiable).
- T1 (
audit_user_turn_marker): Full (the marker carries no sensitive content). - Audit templates T2–T8, matched by naming value: FieldRedacted with the following allow-lists (all pointers under
/payloads/):
| Template | Disclosed paths (beyond forced /previous_revision) |
|---|---|
T2 audit_user_prompt | signer_did, session_id, turn_id, created_at, plus attached_files/<i>/hash for every present index <i> |
T3 audit_agent_thinking | signer_did, turn_id, seq_in_turn, created_at, model_name |
T4 audit_agent_tool_call | signer_did, turn_id, tool_name, risk_level, created_at |
T5 audit_api_response | signer_did, turn_id, method, endpoint, status_code, attested_origin, created_at, request_hash |
T6 audit_tool_result | signer_did, turn_id, tool_name, success, created_at |
T7 audit_hitl_approval | signer_did, turn_id, decision, created_at |
T8 audit_agent_response | signer_did, turn_id, created_at, is_final, model_name |
Everything not listed is sealed — including the revision's structural leaves (/nonce, /local_timestamp, /method, /version, /revision_type, the container markers) and the sensitive payload fields (prompt text, thinking text, tool arguments and results, response bodies, HITL prompt text and rationale, attachment names and sizes, token counts).
The T2 attachment rule is dynamic: every actual path matching /payloads/attached_files/<index>/hash — where <index> is one non-empty all-ASCII-digit segment — is disclosed, so attachments stay hash-referenced while their names, sizes, and count-independent details stay sealed.
Any other revision (non-audit content) is Full: the preset takes no position on templates outside the audit family. Holders of mixed trees should use a profile.
Derivation-aware profiles
A disclosure profile generalizes the preset to a closed world with template-derivation awareness: an object revision whose template family is not named by the profile is redacted, not disclosed (disclosing unknown content is the failure mode being designed out). Revisions are classified by walking the template's verified derivation lineage — resolving each ancestor by hash from verified sources, never trusting self-declared ancestry, and sealing the revision fail-closed if the lineage cannot be resolved. Every field-redacted object revision force-discloses /revision_type (it is a hash, not sensitive, and consumers need the type to interpret the artifact). Family rules apply to every descendant template, and a per-revision Full directive cannot override an inherited family redaction rule. See the specification §5.3 for the full rules.
Security rules
- The
/nonceleaf MUST NOT be disclosed. The per-leaf salts are derived from the revision's 16-byte nonce; disclosing it yields every leaf's salt and makes every sealed value — many of which are low-entropy (booleans, enums, small integers, container markers, the fixedversionandmethodstrings) — trivially brute-forceable. Exporters MUST reject a redaction request that lists the/nonceleaf (the SDK'sredact_revisionrefuses withNonceDisclosureForbidden). The artifact format cannot express the prohibition, so the exporter is the enforcement point. - Guessing resistance for sealed values is bounded by the nonce's 128 bits. Disclosing one leaf's salt does not weaken the others: per-leaf salts are derived through a one-way expansion keyed by the pointer path.
- Selective-tree inputs are attacker-controlled. Verifiers MUST reject a
leaf_countof 0 (an empty leaf list has no defined Merkle root), MUST enforce index bijectivity, and MUST apply the strict multihash decoding rules torevision_hashbefore hashing anything.
SDK API
The disclosure API of aqua-rs-sdk-core:
| API | Purpose |
|---|---|
DisclosurePolicy::pseudonymous(&tree) | Build the pseudonymous preset for an audit tree. |
DisclosurePolicy::full(&tree) | The full preset (every revision disclosed). |
DisclosurePolicy::with_profile(&tree, &profile, &linked_trees) | Build a policy from a derivation-aware closed-world DisclosureProfile. |
DisclosureProfile::audit() | The built-in profile for the audit template family. |
redact_revision(revision, revision_hash, disclosed_paths) | Field-redact one tree-method revision into a RedactedRevision. |
verify_redacted_revision | The five-step redacted-revision verification. |
export_selective_tree | Export a tree under a disclosure policy. |
verify_selective_tree(&selective) | Verify a selective tree's integrity and chain continuity. |
See also
- Hashing and Canonicalization — the leaf commitments, salts, and Merkle construction that redaction is built on
- Data Model — revision kinds, methods, and tree structure
- Verification — full verification and what selective artifacts opt out of
- aqua-rs-sdk-core — installing and using the disclosure API
- Audit Trails for AI Agents — the audit template family the pseudonymous preset targets
