Private Withdrawals

Client-Side Mechanics

All logic regarding which notes to spend and how to handle the change happens locally on the client. This ensures the protocol never knows which specific deposits correspond to a specific withdrawal.

1. UTXO Selection & Coin Logic

When a user requests a withdrawal of amount , the client scans the local wallet for available commitments. It aggregates a set of input notes where the total value satisfies:

i=0nInputiAtarget\sum_{i=0}^{n} Input_{i} \geq A_{target}

2. The "Change" Mechanism

It is rare for a user's available notes to match the withdrawal amount exactly. To handle the surplus, the protocol creates a Change Commitment.

  • Input: Existing private notes (Total: 10 BNB).

  • Action: User withdraws 3 BNB to a public address.

  • Output 1: 3 BNB sent to the public recipient.

  • Output 2: A new private commitment for 7 BNB is generated and added back to the Merkle Tree (owned by the sender).

This ensures that the user's remaining balance stays private and encrypted, breaking the link between the original 10 BNB deposit and the 3 BNB exit.

3. Nullifier Generation (Double-Spend Protection)

Since the private notes are encrypted, the smart contract cannot see which note is being spent. To prevent a user from spending the same note twice, the client generates a Nullifier.

The Nullifier is a deterministic hash derived from the note's secrets. It acts as a unique "fingerprint" for a spent note without revealing the note itself.

Nullifier=PoseidonHash(Commitment,PrivateKey,TokenAddress)\text{Nullifier} = \text{PoseidonHash}(\text{Commitment}, \text{PrivateKey}, \text{TokenAddress})

If a user attempts to reuse a note, the derived Nullifier will be identical. The smart contract tracks all used Nullifiers and will reject any transaction containing a duplicate.


On-Chain Execution

The Smart Contract execution is atomic. It verifies the validity of the spend before releasing any funds.

1. ZK-Proof Verification

The contract first calls the Verifier to validate the Zero-Knowledge Proof. The proof asserts:

  • The user knows the private keys for the input commitments.

  • The input commitments exist in the Merkle Tree history.

  • The sum of inputs equals the sum of outputs (Withdrawal + Change).

  • The Nullifier was computed correctly.

2. Nullifier Enforcement

Before moving funds, the contract checks the global usedNullifiers mapping.

  • Check: Is usedNullifiers[nullifier] false?

  • Set: If false, set usedNullifiers[nullifier] = true.

This operation permanently invalidates the input notes.

3. Fund Release

Once the cryptographic checks pass, the contract interacts with the underlying asset layers:

  • BNB: Performed via low-level .call{value: amount}.

  • ERC-20: Performed via IERC20.transfer.

Simultaneously, if a "Change Commitment" was generated, it is inserted into the Merkle Tree, effectively processing a "Deposit" and a "Withdrawal" in the same transaction.

Last updated