Exchanges and bridges holding a shielded deposit ask where the money came from. Today the answer is a viewing key.

One earlier output

For each spent note the receipt names the earlier transaction output that created it, for example the shielding of an exchange withdrawal. The verifier learns that link and nothing else. A batch of hops (shield, self-transfer, payment) proves the whole chain, and the batch’s same-payer proof shows one controller made every hop.

One of a vetted set

An exchange publishes the note commitments of outputs it has screened. The payer proves every spent note came from the list, without saying which one:
--screened-amount additionally shows that at least a given amount came from the list, without showing values. A real testnet example is examples/testnet/screened.

How it works

Both are extra Action proofs under the same rk, against the root of a tree the verifier builds: a one-leaf tree of the named output’s commitment, or the tree of the published list. The verifier fixes the value commitment, so v_old ≥ 1 and the circuit enforces the Merkle path. With a revealed value, the proof’s output is opened with value 0, so the difference is the note’s value. A zero-value note cannot claim any source: the owner of one satisfies the circuit with their own value commitment, and is refused with every one the verifier allows (roundtrip.rs::a_zero_value_note_cannot_claim_any_source).

Limits

  • It covers the spends the receipt proves (the payer’s own), not inputs other parties added.
  • A screened-set proof shows only a lower bound on each note’s value.
Code: crates/zceipt-core/src/screened.rs. See docs/guides/exchange-deposit.md in the repository for the deposit-desk flow.