Use
How it works
Orchard account keys are hardened ZIP 32 children of the wallet’s coin-level key:(sk_par, c_par) and indices i_1, i_2, computes both children’s
nk with BLAKE2b, hashes them to tags, and checks them against public tag_1, tag_2 and
domain. The parent key, the indices and the nks stay private.
So a tag can’t be claimed for another wallet’s account without a BLAKE2b or Poseidon collision.
Suppose the BLAKE2b gadget were unsound anyway. The proof can only be used to merge tags, so what
a forger could show is that two tags they know the keys for are siblings, which makes their own
two wallets count once. Merging someone else’s tag needs that tag’s nk, which the forger would
have to find by inverting Poseidon. An unsound gadget would not let anyone be counted more times,
or hide a payment.
A test (against_orchard::accounts_are_children_of_the_coin_key) checks that the in-circuit
derivation equals the orchard crate’s.
Cost
BLAKE2b runs inside the circuit: four compressions, so the circuit isk = 17 (the gadget’s
16-bit XOR table). Proving takes about 72 seconds on the development machine, far larger than
an Action proof. The parameters are cached at ~/.cache/zceipt/kce-params-17.bin and checked
by digest.
Limits
- Hardened derivation only, so it covers accounts of one seed, not independently generated wallets.
- Unaudited gadget and port.
crates/zceipt-kce. The testnet and regtest end-to-end runs tie a sibling proof to real payer tags. The proof’s coin_type is not authenticated by the circuit, which is harmless because a tag is only used next to payer tags checked against a chain.