Skip to main content
Every unshield transaction carries an unshield proof. Unshielding spends notes and releases their MIP20 value to an authorized public destination. The key holder authorizes the spend. With custodial keys, the customer’s virtual asset service provider (VASP) can prepare and sign the action. With self-custodied keys, the customer’s wallet must sign. The proof establishes this statement:
I am unshielding amount units of asset from the pool to destination, 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 binds destination 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 an Accept 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 stable affilID 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.

Illustration

The unshield circuit combines input note openings, ownership data, authorization, and public inputs into an unshield proof. The pool checks the input lock IDs, nullifiers, and accepted roots before transferring MIP20 value to the destination.The unshield circuit combines input note openings, ownership data, authorization, and public inputs into an unshield proof. The pool checks the input lock IDs, nullifiers, and accepted roots before transferring MIP20 value to the destination.

Unshield transaction submission

Any public account may submit the prepared unshield transaction and pay gas. The customer, a relayer, or the customer VASP may act as submitter. The unshield proof fixes the destination, amount, inputs, change note, and private checks. The submitter cannot alter those values or authorize another spend.