I was skeptical too, but I think it might be possible actually:
Let’s first look at how I think it works currently on a very high level.
A pool contains some amount of ETH and two lists, a list deposits and a list withdrawals.
A user creates a secret note n. He then deposits 1 ETH and adds hash(0, n) to deposits. (With multiple arguments to hash I just mean the hash of the argument tuple.)
When a user wants to withdraw his ETH, he creates a ZKP, that he knows some index i and a note n, such that deposits[i] == hash(0, n). He also submits hash(1, n). The pool checks the proof, checks that hash(1, n) is not in the list of withdrawals (to avoid double spending) and lets him withdraw 1 ETH.
Now let’s see, how this can be extended to (in principle) allow arbitrary deposits and withdrawals. Be careful: In practice it should be carefully considered which deposits and withdrawals are actually allowed, to have good anonymity sets.
A pool again contains some amount of ETH and two lists, a list deposits and a list withdrawals.
A user creates a secret note n. He then deposits x ETH and adds hash(0, n, x) to deposits. He also must submit a ZKP, that he knows a note n, such that hash(0, n, x) matches the hash that he submitted and x matches the amount of ETH that he submitted.
When a user wants to withdraw y ETH, he creates a ZKP, that he knows some index i and a note n and an amount x (x >= y), such that deposits[i] == hash(0, n, x). (Note: He did not reveal the originally deposited amount x.) He also submits hash(1, n). The pool checks the proof, checks that hash(1, n) is not in the list of withdrawals and lets him withdraw y ETH. This would mean that x - y ETH is lost. To avoid that, the user must also create a new note n2 and submit hash(0, n2, x - y) to the list of deposits. Again he also must submit a ZKP, that he knows a note n2, such that hash(0, n2, x - y) matches the hash that he submitted. (This ZKP and the previous ZKP must be submitted as one ZKP, to ensure that they both use the same x.)
To summarize:
- Deposit:
- User sends ETH and the tuple
(x, h, ZKP(h == hash(0, n, x))
- Pool checks the ZKP
- Pool checks that the sent ETH matches
x
- Pool adds
h to deposits.
- Withdrawal:
- User sends
(y, h1, h2, ZKP(hash(0, n, x) ∈ deposits && y <= x && h1 == hash(1, n) && h2 == hash(0, n2, x - y))
- Pool checks the ZKP
- Pool checks that
h1 is not in withdrawals
- Pool adds
h1 to withdrawals
- Pool sends
y ETH
- Pool adds
h2 to deposits
This allows arbitrary deposits and withdrawals. It makes reasoning about anonymity sets somewhat interesting. If only deposits/withdrawals are allowed, that are currently allowed (all to the same pool), I think it at least does not make the anonymity set worse.
My reasoning could contain fatal errors.