I am unshieldingamountunits ofassetfrom the pool todestination, the public destination I authorize. The input notes sit in the commitment tree, and I authorize their spend with the spending key behind them. The value leaving the pool plus my change note equals the value of the inputs, and I am not sanctioned.
What unshielding checks
The circuit bindsdestination to the key holder’s authorization and proves
that the spender’s affilID is absent from the sanctioned set. There is no
pool-side destination registration or proof that the spender owns the public
destination. The outgoing MIP20 transfer remains subject to the token’s policy.
Because an unshield spends notes, the proof also checks input ownership,
commitment-tree membership, nullifier and input lock ID derivation, and value
conservation into an optional change note. The pool separately checks the
spent set and input lock set.
The unshield destination, asset, and amount are public. Which note commitments
were spent, and the earlier activity that produced them, remain private. The
unshielding page describes the lifecycle, and
Privacy describes what each party sees.
Spending notes during unshielding
An unshield uses the same note identifiers as a shielded payment on a different schedule. A payment keeps its nullifiers sealed in the payment effect hash from initiation until anAccept resolution. An unshield publishes its
nullifiers in one transaction. The pool requires each nullifier to be absent
from the spent set and records it during the same execution. Both actions use
Poseidon2(nk, leafIndex, DOMAIN_NULLIFIER) for each input note.
The proof also derives each input lock ID as
Poseidon2(nk, leafIndex, DOMAIN_LOCK). The pool checks that no active hold has
reserved an input and unshield does not modify the input lock set. The circuit
derives the input lock IDs and nullifiers from the same (nk, leafIndex) pairs,
binding the lock check to the notes being spent.
Any remaining value returns as a change note. The change note’s address must
satisfy pk_d = ivk · g_d, so change returns to the same identity. Wallets
should choose a fresh diversifier; the circuit does not enforce its freshness.
Its shieldTimestamp is the minimum of the inputs’ values,
so it retains the age of the oldest input. The input amounts must equal the
public amount plus the change amount, with every amount range-checked to 128
bits. One or two notes can fund one unshield. An unused second input slot
publishes zero for its nullifier and input lock ID. If no value remains, the change note
commitment takes the canonical empty value and the pool appends nothing.
The spender screen
From the incoming viewing key, the circuit derives the identity’s stableaffilID and proves
it absent from the sanctioned set. Unshielding does not require a VASP
policy-wrapper proof. Custody and key protection must account for the spending
key’s ability to authorize a public destination.
Witness
The witness remains with the prover. Its preparation can be delegated through
outsourced proving, but
the key holder must authorize the unshield with a Schnorr signature under
ak.
Public inputs
The table follows the circuit’s twelve public inputs in order, with each array
flattened in place. Its nullifiers prevent successful
resubmission after they enter the spent set.
The pool verifies the proof at
0xF2, records the nullifiers, appends any
nonzero change note commitment, and transfers the MIP20 amount to
destination. If the MIP20 transfer fails, atomic rollback reverses the
complete operation, including the nullifiers and change note commitment.