Rox
Register · Developers
PUBLIC DOCS
v1 · api.roxregister.com
Sign in to Platform
OverviewQuickstartsGuidesSDKsAPI referenceErrorsWebhooksJWS vectors
Guides / Selective disclosure
Selective disclosure
Sealing proves a whole document. Selective disclosure goes further: commit individual fields at sealing time, then later prove chosen fields to a third party, a coverage amount to a bank, a graduation date to an employer, without revealing anything else. Field values and field salts never reach Rox at any point; Rox anchors only hashes.
How it works
Each field is hashed with its own fresh 32-byte salt:
salted_hash = SHA-256(field_salt || utf8(name) || 0x1f || canonical_value)
The ordered salted hashes form an RFC 6962 Merkle tree whose root (field_root_hex) is committed inside the record's leaf, and so inside the anchor on RoxChain. The per-field salt is what makes this safe: without it, a verifier cannot brute-force enumerable values (amounts, dates, names) out of the public hashes. Canonical value encoding is identical across the SDKs, so hashes are portable between stacks.
Commit fields at sealing time
Build the field hashes locally and pass them with the seal. Everything else about the seal is unchanged: validated or rejected immediately, then exactly one terminal outcome, anchored or failed.
# field_hashes rides inside the normal /v1/seal body. Only the salted
# hashes travel; values and per-field salts stay with you.
BODY='{
  "record_type": "policy",
  "external_ref": "POL-2026-88123",
  "content_digest_hex": "'"$DIGEST"'",
  "field_hashes": [
    {"name":"policy_no",    "salted_hash_hex":"<sha256-hex>"},
    {"name":"insured_name", "salted_hash_hex":"<sha256-hex>"},
    {"name":"coverage_egp", "salted_hash_hex":"<sha256-hex>"}
  ]
}'
# each salted_hash_hex = SHA-256(field_salt || utf8(name) || 0x1f || canonical_value)
# field_salt: 32 fresh random bytes PER FIELD, retained by you, never sent.
# Sign and send exactly like any other seal (see the first-seal guide).
Retain three things alongside your document: the field values, the field salts, and the order. All three are required to build a reveal later, and none of them can be recovered from Rox, by design.
Reveal a subset, verify offline
A reveal packages the chosen fields with their salts and an inclusion proof against the committed root. The verifier recomputes the salted hash and walks the proof path to the anchored field_root_hex, carried on your record view and inside the record's RRP-1 proof bundle, fully offline; un-revealed fields contribute only opaque sibling hashes.
Node
import { revealPayload, verifyRevealedField } from "@rox-register/sdk";

// Issuer side: build a reveal for exactly the fields you choose.
// Un-revealed fields contribute only opaque sibling hashes to the proof.
const payload = revealPayload({
  fields,                    // the COMPLETE ordered list from sealing time
  fieldSalts,                // the salts hashFields returned
  reveal: ["coverage_egp"],  // any subset
});
// Hand the verifier: the record reference, this payload, and the
// committed field root. As the issuer you read field_root_hex from your
// record view (client.getRecord(...)); it also travels inside the
// record's RRP-1 proof bundle, bound into the anchored leaf.

// Verifier side: fully offline, no Rox involvement.
for (const field of payload.revealed) {
  const ok = verifyRevealedField(field, fieldRootHex);
  // ok === true: this name/value pair was committed, at this position,
  // when the record was sealed. Everything else stays hidden.
}
In Python, recompute with salted_field_hash and compute_field_root (see the Python SDK page); the tree math matches the Node SDK and the server exactly.
Field hashes are not metadata
field_hashes and metadata solve different problems. Metadata is plaintext stored with the record and partially exposed through the record type's public verify fields; it must satisfy the record type schema and must never contain PII (rejected with RR-2003). Field hashes are opaque commitments: the right place for anything sensitive you may want to prove later. When in doubt, commit a field hash and keep the value with you.