Skip to main content
Conditional disclosure lets an organization prove facts about one private transaction to an authorized party — only when an M-of-N custodian quorum approves, and only when the transaction qualifies under an on-chain policy (amount above a threshold) — without ever exposing the viewing key, which would reveal the entire payment history. Specified in conditional-disclosure.md.
Testnet only, unaudited. Same gates as the privacy pool: circuit + contract audits and a production trusted setup are mainnet prerequisites.

Who is this for

The two surfaces — and which one to use

Active disclosure (default). Custodians run FROST threshold signatures: the group’s secret key is created by distributed key generation and never exists in one place — not even during signing. A disclosure is a zero-knowledge proof gated by the quorum’s BIP-340 signature. Use this for everything routine. Escrow backstop (exceptional). Shamir shares of the viewing key, for key-loss recovery or lawful non-cooperative access. Combining M shares reconstructs the full viewing key and reveals all incoming payments to the combiner. It is a visible, deliberate escalation — rotate keys afterwards.

1. Custodian ceremony (once)

Each of the N custodians runs the frost-custodian CLI (in sdk/tools/), exchanging the round files through any shared channel. No dealer — the key is born distributed:
Register the policy on the chain(s) you operate on — it binds the group key to a pool and a qualification threshold, immutably (rotation = new ceremony + new policy):

2. A disclosure, end to end

The requester opens a case. A case is (policyId, caseId, requester); caseId is an opaque 32-byte engagement identifier. The context commits to all three and is what custodians sign — it contains no transaction data, so custodians can authorize without learning anything; the circuit enforces that only a qualifying note can satisfy the proof.
M custodians co-sign (any M of the N):
The note owner proves. The proof shows the note is in the pool’s state tree and value > threshold, and discloses (value, label) — the label links to the Deposit event, giving provenance:
The requester submits (the registry binds the request to msg.sender, so a proof + signature cannot be hijacked):
The registry verifies the policy threshold, the context, the BIP-340 quorum signature, the pool root, and the Groth16 proof, then consumes the disclosure nullifier and emits Disclosure(policyId, caseId, requester, label, value, …) — the on-record result.

What each party learns

A second disclosure of the same note under a different case needs a fresh quorum signature (different context → different nullifier). Replaying the same disclosure is rejected by the nullifier registry.

Failure modes to plan for

  • Below-threshold note: the witness builder throws and the circuit is unsatisfiable — a quorum cannot be tricked into over-disclosing, and an owner cannot disclose what does not qualify.
  • Quorum loss: fewer than M live custodians means no disclosures (and no recovery if escrow shares are also lost). Choose N comfortably above M.
  • Owner non-cooperation: the proof path needs the note openings. The escrow backstop is the documented escalation, with its total-read-access cost.