If your withdrawal proof fails, do not rush to blame the wallet. Privacy Pools withdrawals must pass multiple layers of on-chain verification. If any layer does not match, the withdrawal will fail. Check the following 4 data points in order. This will help you find most failure causes.
Item 1: State tree root — is your deposit still on the tree?
A withdrawal proof must prove that your deposit commitment currently exists in the State Tree. This tree is append-only: when you deposit, a leaf is added; when you withdraw, the corresponding nullifier is used, but the leaf itself remains on the tree.
What to do: Confirm that the state tree root (stateRoot) you use for withdrawal is not outdated.
How to do it: Go to a block explorer and check the confirmation time of your most recent deposit transaction. The Privacy Pools contract records several recent state tree roots. The stateRoot bound in your withdrawal proof must be one of the roots accepted by the contract. If you wait too long to withdraw, the state tree root may have rolled to a new version, and your proof will become invalid.
Done when: Find the contract's "recent N state tree roots" list and confirm the root used in your proof is still in that list.
Item 2: Association set tree root — does the ASP version match?
Besides proving "the money is mine," a withdrawal proof must also prove "this deposit is clean" — meaning it belongs to the compliance set published by the Association Set Provider (ASP).
What to do: Confirm that the association set tree root (aspRoot) used in your proof is the latest version currently published by the ASP.
How to do it: The ASP updates the association set regularly (for example, adding newly compliant addresses or removing newly discovered illegal addresses). Each update publishes a new tree root on-chain. The aspRoot bound in your withdrawal proof must match the ASP tree root currently recorded in the contract.
Done when: Open the Privacy Pools dApp or a block explorer, check the current ASP tree root, and compare it with the one used when you generated your proof.
Item 3: The "processor" bound in context — prevent someone from front-running your withdrawal
In the Privacy Pools withdrawal circuit, there is a public signal called context. It "binds" withdrawal parameters (recipient address, fee, etc.) into the proof. During contract verification, the contract checks whether the transaction sender (msg.sender) is the processor bound in context.
What to do: Confirm that your withdrawal method (direct withdrawal or relay withdrawal) matches the correct processor address.
How to do it:
- If you use direct withdrawal (you pay gas), the processor should be your own wallet address.
- If you use relay withdrawal (a relayer submits for you), the processor should be the Entrypoint contract address.
If this does not match, the contract verification will revert.
Done when: Open your withdrawal transaction record and confirm the sender address is the same as the processor bound in the proof.
Item 4: Nullifier — don't spend the same money twice
Each deposit has a unique nullifier (the "destruction credential" in zero-knowledge proofs). During withdrawal, the hash of this nullifier (nullifierHash) is published on-chain to mark that the money has already been spent.
What to do: Confirm that the nullifier used in this withdrawal has not been consumed yet.
How to do it: Check the contract's nullifier mapping to see whether the nullifierHash for this deposit has already been recorded. If it has already been recorded, the money was withdrawn before and cannot be withdrawn a second time.
Done when: In the contract's nullifier list, you cannot find a record of this nullifierHash.
How to verify after checking
After going through all 4 items, regenerate the withdrawal proof and submit it again. If it succeeds, the problem was in one of the steps you just checked. If it still fails, check the exact revert reason of the failed transaction on a block explorer. It usually tells you directly which condition was not met.


