Thanks @vbuterin!
In the paper we acknowledge the fact that users of the mixer are distinguishable from users who do not use the mixer (as they need to call the mix
function of the mixer).
However, our claim is that ZETH blurs the transaction graph well enough to be considered in cases where we need to obfuscate:
- the relationship between sender and recipient of a transaction
- the value of a payment
- the total wealth of users
To support this claim it was important to follow the statement and the construct detailed in Zerocash that allows to execute payments of arbitrary denominations “directly in the mixer” (by this I mean that the proof generated by the sender ensures that the system remains sound - no value is created, no double spend occurs and so forth. That way the mixer only processes obfuscated data while being sure the system remains sound.).
The rational behind this was to avoid to “bounce on the Ethereum public state” every time a payment was received and needed to be done.
In fact, the mixer contract in charge of maintaining the merkle tree of commitment and the nullifiers, and in charge of calling the SNARK verifier contract only implements a unique mix
function (we deliberately moved away from functions like deposit
, and withdraw
). This mix
function can be used to add public value to the mix (deposit), remove public value form the mix (withdraw), transfer zethNotes (transfer), or do all three at the same time.
The proof verified in a mix
call is a proof generated for a statement similar to the one of Zcash-Sprout (but simplified for the PoC). This statement includes the check of a joinsplit equation which allows to “pour” some notes in some others (see section 3.4.3 of the paper).
These newly created notes (the output of the joinsplit) are not necessarily dedicated to the same users. Moreover, the statement used in zeth supports the use of dummy notes of values 0 (via the addition of extra constraints in the R1CS in the form v_i · (1−e) = 0, where v_i is the value of the note_i, and where e is a boolean used to enforce a merkle root equality check between the merkle root given in the instance (rt) and the merkle root rt' obtained from the check of the merkle path mkPath_i of cm_i (commitment of note_i). See 3.4.3 of the paper).
With this possibility to use deposited funds on the mixer as inputs to the joinsplit, someone who has deposited or received funds from someone else can use these to pay someone else without ever needing to withdraw from the mixer.
Zeth becomes a layer of obfuscation seating on top of Ethereum where new notes can be created and destroyed to transfer value. This requires to pay for the gas to execute the mix
function but does not leak any other data. Only meaningful leakages happen when public funds are added to the mix or when obfuscated funds are removed from the mix.
The claim here is that state transition encoded by the mix
function of the Mixer smart contract is not equal to the state transition resulting from plain Ethereum transactions in term of information leakage.
Moreover, to protect the sender and recipient relationship, we leveraged Ethereum events as a way to implement an encrypted broadcast to send the value of the zethNotes to the intended recipient. The sender encrypts a zethNote under the recipient’s key (here we need to have an IK-CCA scheme), and adds the ciphertexts as an argument to the mix
function of the mixer.
The mixer mix
function’s signature looks like:
Mix(π,rt,v_{in},v_{out},{cm^{new}_i}^M_{i=1},{sn_i}^N_{i=1},{c_i}^M_{i=1})
Where:
-
rt is one of the root that has fingerprinted the merkle tree of commitment maintained by the mixer contract (this is important to support multiple simultaneous transactions related to the same mixer)
-
v_{in} is the publicly deposited value
-
v_{out} is the publicly wthdrawn value
- The {cm^{new}_i}'s are the newly computed commitments (commitments to the newly created zethNotes)
- The {sn_i}'s are the nullifiers (serial numbers of notes being spent --> Used to prevent double spendings)
- The {c_i}'s are the encrypted newly created zethNotes
Here’s a screenshot of the pseudocode of the Mix function.
Now, let’s consider an example with Alice, Bob and Charlie being 3 users of the system:
Note: Alice and Bob are free to spread their wealth as they prefer across all their notes. Alice deposit could have translated in 3 zethNotes (zethNote1.val = 0, zethNote2.val = 7, zethNote3.val = 2ETH) for instance. Same for Bob. In order to deposit their funds, Alice and Bob can generate dummy notes as input to the joinsplit.
-
- Alice wants to pay Bob 2ETH via the Mixer
- Alice:
- Public funds: 1ETH - 2 * gasEpsilon
- Obfuscated funds: 7ETH [4 zethNotes (zethNote1.val = zethNote3.val = 3ETH, zethNote4.val = 1ETH, zethNote5 = 0ETH)]
- Total wealth: 8ETH - 2 * gasEpsilon
- Bob:
- Public funds: 1ETH - gasEpsilon
- Obfuscated funds: 3.4ETH [4 zethNotes (zethNote1.val = 1, zethNote2.val = 0.3, zethNote3.val = 0.1, zethNote4.val = 2ETH)]
- Total wealth: 4.4ETH - gasEpsilon
- Charlie:
- Public funds: 0ETH
- Obfuscated funds: 0 zethNotes
- Total wealth: 0ETH
- Mixer:
- Public funds: Bal + 9 + 1.4 ETH
Note: Alice’s has used her note zethNote2
along with dummy inputs to create the note zethNote4.val = 2ETH
for Bob along with her change zethNote4.val = 1ETH, zethNote5 = 0ETH
. Again, Alice could have created notes zethNote4.val = 1.9ETH, zethNote5 = 0.1ETH
for Bob, and taken a change in the form of a unique note zethNote4.val = 1ETH
for instance.
- The ethereum balance of the mixer remains constant.
- Alice’s ethereum balance get decremented by the gas cost of the
mix
call (this leakage does not leak anything else)
- Bob’s ethereum balance remains the same (to receive a payment, Bob listens to emitted events by the Mixer and tries to decrypt their payload.). This is the encrypted broadcast.
Thus, Alice has just paid Bob without requiring any withdraw call from Bob, and without exposing their relationship.
As mentioned in the paper, we encourage users to send periodic dummy payments to themselves. While this costs a bit of gas, this introduces some noise in the system.
Namely, here Charlie, cannot decrypt any of the mixer events payload, so he can only guess that the recipient was either Alice or Bob (assuming the encryption scheme is IK-CCA). Thus, despite Alice’s call to the mixer, no information is gained.
Now, Bob can either withdraw some funds back from the mixer, or pay someone else. If he decides to withdraw more than the value he initially deposited, then Charlie learns some information and knows that he has received funds from someone else on the network (here this someone else is only Alice, but it might not be as obvious in a network with a large set of users). In fact, Charlie learns that Bob’s received funds were at least r = w - d where w denote the value withdrawn and d the total value initially deposited by Bob.
We propose some best practices and present some limitations in section 4 of the paper. One of the best practice being to keep the funds as long as possible on the mixer, and use these to do payments. This yields long chains of payments and makes it harder to follow.
Last but not least, the use of Ethereum events along with the possibility to create M notes (for potentially M distinct users) present the nice property that Alice can pay up to 3 recipients in a single transaction. Thus, this might be a nice way to have scalable systems as it could limit the number of transactions sent to the system (However, checking merkle paths remains a big performance bottleneck.)