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.