barryWhiteHat / miximus

A proof of concept trustless ethereum mixer
GNU General Public License v3.0
230 stars 34 forks source link

Internal transfers without revealing the recipient #23

Open HarryR opened 6 years ago

HarryR commented 6 years ago

It would be good to be able to perform an anonymous transfer between two recipients without the ether leaving the contract. As per discussion on https://github.com/AntoineRondelet/snark-mixer/issues/2

This can be achieved by introducing a WithdrawThenDeposit function which does the Withdraw then Deposit logic without the ether leaving the contract. e.g. spend 1 coin, then create another coin with a new owner, with the same value.

However, there are problems: the address that ether is sent to is specified as part of the nullifier, in addition to the salt. If the long-term address of the coin owners is included as part of the nullifier then any coin they own will be revealed, this will link their account to the on-chain actions even if the transactions are submitted by a third party.

One way to get around this is to construct the nullifier using ecrecover, the person submits the (v,r,s) components of the ECDSA signature of all the functions parameters, then the recovered address is combined with the salt to create the nullifier.

Source code refs:

So, the contract functions can be expanded as the recipient no longer needs to be specified in the nullifier - and we can now rely on a signature of the one-time-key for the nullifier to prove ownership of the coin.

However, this raises other questions - how do you find out which coins you have if they haven't been shared with you? One solution to that is to store an encrypted blob along with every Deposit or Transfer which, if you scan all the encrypted blobs using your long-term-key you'll find out which coins you own.

rstormsf commented 6 years ago

Love it.

barryWhiteHat commented 6 years ago

So this is just my initial reply. I will try and talk about what i was thinking when i considered this and ask you some questions ;-)

Firstly a clarification I think its a good idea to move to receiver address outside of the nullifier. I put it there to prevent withdrawal malleability where the transaction broadcaster was able to change the recipient of the coin. This is important for transaction abstraction. However Its better done by passing a public parameter to the zksnark which I think is not mailable tho i am less confident about this.

Doing internal transaction is difficult for two reasons

  1. It seems unlikely that people will use them if you look at some of the privacy analysis of zcash https://smeiklej.com/files/usenix18.pdf that is a reason why I did add multiple balance sizes.
  2. You will have alot of trouble with the nullifiers. You need to nullify previous coins at withdraw time. 2.1. Require that each internal transfer erases the previous leaf. To update the tree you would have to share the updated tree which would mean you would lose the anonymity. 2.2. Add a Patricia tree where you store nullifers and prove that your nullifier does not exist inside this tree. This is a nice solution but its means you will have to make a very deep merkle tree ~100 layers 2.3 Update the nullifiers mapping on each transfer. This is what you propose above?

But how does this idea improve the anonymity properties? How is this different than withdrawing and in the next transaction depositing again? In fact if we remove the receiver from the nullifier we could abstract we could make a contract that would do this automatically.

I think that internal transactions make the most sense when you can merge transactions together.

HarryR commented 6 years ago
  1. It seems unlikely that people will use them if you look at some of the privacy analysis of zcash https://smeiklej.com/files/usenix18.pdf that is a reason why I did add multiple balance sizes.

According to increase of public transactions over time people aren't using zcash for what makes it worth using. Maybe because they allow public transfers and all of the wallets use public transfers, people who don't know better think it's private when it's not... If you don't allow your users to do dumb things, then they won't make mistakes.

2.1. Require that each internal transfer erases the previous leaf. To update the tree you would have to share the updated tree which would mean you would lose the anonymity.

No need, once the nullifier has been used that coin cannot be spent again. This is like a ring signature unique tag in Monero - where to prevent double spends you need to keep the tag of each coin (in this case the nullifier). This is implemented at:

https://github.com/barryWhiteHat/miximus/blob/dd372597090556545e9895ab52204bf5e1b6ac15/contracts/Miximus.sol#L41

The merkle tree remains append-only, and any Withdraw can choose any merkle root as long as it can prove the path.

Add a Patricia tree where you store nullifers and prove that your nullifier does not exist inside this tree. This is a nice solution but its means you will have to make a very deep merkle tree ~100 layers

I think a dictionary of all used nullifiers works well for now.

Update the nullifiers mapping on each transfer. This is what you propose above?

As @AntoineRondelet posted, the Transfer logic is the same as a Withdraw then a Deposit, however it happens in one step.

But how does this idea improve the anonymity properties? How is this different than withdrawing and in the next transaction depositing again?

In the case of withdrawing then depositing again:

In fact if we remove the receiver from the nullifier we could abstract we could make a contract that would do this automatically.

Yup, using a one-time-key for the nullifier, and proof of ownership of the address used in the nullifier via ecrecover, means that:

  1. all input parameters can be bound via a signature
  2. with verifiable input parameters, the key owner can choose who to deposit to - either themselves, or somebody else
barryWhiteHat commented 6 years ago

You don't need to bind the input by signature, You can use a public variable in the zksnark to define where it should go.

I am still unsure if this adds anything to the privacy set. Because you leave the tree and then directly rejoin it. So to an observer its clear that this is just round X of the mixing. But this is the same as depositing and withdrawing again.

Oh is your point that to this is manually is quite difficult you have withdraw and redeposit and you cant withdraw multiple times to the same address AND you have to get gas to pay for all these transactions which is non trivial. So we should make it as easy as possible and allow this remix option so that people cannot mess it up in these ways?

In that case i would argue that they still need to get gas to pay for the remix transaction. And that if we want to have a decently sized anonymity set we would need to never have public transactions and just keep everything private as long as possible. I mean providing internal private transactions that are cheap and easy. With variable amounts possible.