Right now, Ethereum privacy is quite lacking. There are two reasons why. First, all of your activity is by default done through a single account, so it is all linkable on-chain. Second, and more insidiously, even if you have multiple accounts that you split your activity between (ideally, the default would be to use a different account for each application), the fact that you need to transfer ETH between accounts to pay for gas on all of them is itself a privacy leak.

This is a situation that could use improvement. Two areas come to mind.

Mixers

We can encourage the development of easy-to-use, and importantly decentralized (ie. not just “trustless”, completely serverless) mixers targeting privacy-preserving transfer of small amounts of ETH, so if you want to send gas payment to another account you can do so without linking the two.

Note that here, one major challenge with (eg. ringsig or zk snark based) smart contract mixers is that if you want to send funds from A to B, B still needs to have ETH to pay gas to submit the proof to receive their funds, and sending that gas would be a privacy leak; this can be solved with a layer-2 protocol where a user can broadcast their proof (including a commitment to what address they want to receive to and what fee they are willing to pay) over something like Whisper, and a specialized set of nodes could accept these proofs, include them into a transaction and pay for the gas, and collect the fee from the recipient. But this protocol needs to be specced out, standardized and implemented…

UX

If we make a default that for every dapp, a user uses a separate account, we have to overcome a few challenges:

Address generation: It would be nice to keep wallet software stateless, so users can easily export and import their keys between wallets; this implies using some deterministic scheme like privkey_for_dapp = hash(master_key + dapp_id). But then what is the dapp_id? How would that work for multi-contract dapps?

Dapp interaction: The most common category here is using ERC20 tokens inside another dapp. What is the workflow by which they would do that? To use KNC on Uniswap, would you first transfer KNC from their “Kyber account” to your “Uniswap account” and then do whatever you wanted to do with Uniswap? Something else? Ideally from a UX point of view, it would still feel like the user makes one operation; the UX of dapps that requires users to sign three transactions in a row to do something honestly really sucks.

To be clear, the issue with using one account - or transacting between multiple accounts you own, effectively making it a single “profile” - makes it easier to identify people in meaningful ways.

For example, I can look at things like where addresses are getting their ETH/tokens from, which leads to eventually to an exchange. Exchanges help identify potential attack vectors for spear phishing, but it also reveals some geographic information. The times that these transactions occur also helps to narrow locale.

I can see what Dapps an entity is using, which helps point me towards where these users may be reachable on social media.

You can even profile what kind of wallet scheme their using. Whether they have a cold storage address, are just using MetaMask or some other hot wallet, etc. This helps to know how difficult it would be to attack the user.

There’s a ton of meta information on top of the obvious X sent Y to Z.

This problem becomes exponentially worse when you add it to things like airdrops or giveaways where many people are associating their social media identity with their address. Anything that can be used to tie an address down to any other identifiable information makes this blockchain meta-information very powerful to attackers.

There’s also the concern for big data mining operations / government conspiracies that may not be so far fetched that are applying deep learning algorithms to all of this to pain a bigger picture.

The problem with mixers/privacy layers/Dapp support is that they aren’t usually trustless and easy. If possible, I’d rather see privacy built into the base protocol and not be an option for users. They should have to explicitly prove a transaction, not explicitly hide them.

This is an idea for CREATE2 which can be applied in some situations. We use the fact that we can precalculate the CREATE2 address - which means we can transfer tokens here and we can even supply a salt! It is applied to ERC20 tokens where we try to semi-interact with contracts in a single TX.

The idea is that an user transfers tokens to the address which is precalculated by seeding the address of the user together with the calldata. Knowing these values proves that the user wants to “deposit” the value which is currently the balance of the precalculated address together with the calldata. Hence the seeded data in CREATE2 is the address of the user and the calldata.

Now let’s say we have an useless dummy contract to show the usage. An user A wants to transfer tokens to B but not directly. This useless SplitContract is deployed and we can now calculate the CREATE2 address where A should deposit: the salt is simply the address of A and B and the init_code simply transfers all tokens at the CREATE2 address to SplitContract. When we created the contract at the address where A deposited to in the SplitContract we hence knew the seed so at this point we also know address B. We can calculate how many tokens were transferred and now transfer all tokens to B. Note that the CREATE2 contract can be selfdestructed immediately after we transferred tokens (hence yielding extra used gas of about 11k (~32k contract deployment, 22k refund, ~1k execution cost).

This is of course rather stupid but it can be expanded. Think of a DDEX: here you can match someones trade by transferring tokens to a certain address. The only thing the user now needs to do is to broadcast that these tokens are deposited and either the maker of this order can now take them or the user includes a fee for someone else to ““mine”” this token on-chain which gives them incentive to pay for this gas. (This fee is hence in the calldata / salt). If the order is not matched the user can go on-chain to withdraw their tokens by simply providing the supplied calldata and showing that the user wants to cancel the order (to prevent the contract to try to match the order again). (Or the order is fulfilled already).

Notice that in the DDEX cases the contract is also only created and selfdestructed so no code is deposited, yielding a low amount of extra gas. The only downside in the implementation-side is that you can’t selfdestruct, create2 it again, and selfdestruct it again in the same tx which means you have to deposit code if you want to call back into this contract for some reason. When thinking about this I can also see why it would be really nice to have some kind of memory between call frames, something EIP 1283 tries to accomplish.

I’m interested also in how this applies to “account contracts”, which are essentially multisigs for a single person but with private keys on different devices + a recovery mechanism.

Account contracts make Ethereum more usable and help safeguard against loss of access to someone’s most important dapps, but they make privacy more challenging due to encouraging users to have a single point of entry to many dapps.

@AtLeastSignificant
I agree that privacy by default is the only real solution. If it’s a choice, there’s always going to be incentives to trade information for access. Trading data for services is the default for major applications on the web today, and it’s what users are accustomed to, so they won’t even question it. Also, if it takes extra effort for developers or users to create privacy, and there are no economic incentives for doing so, then it’s an just an inconvenience at best and a sub optimal game theoretic business decision at worst. Better to have options for privacy than not, but like we see with 2FA and password managers today, they’re the exception and not the norm.

An idea that could help with geographic analysis via IPs and such might be dandelion routing. From what I understand it routes a tx between peers a certain number of times before having that tx broadcast to the network. This way you can’t tell where a tx originated from, but you would still be able to see which address is sending what to whom.

mixers targeting privacy-preserving transfer of small amounts of ETH, so if you want to send gas payment to another account you can do so without linking the two.

This seems like a doable first task. The second part about Whisper etc. etc. seems like there are a lot of rabbit holes to go down. If we get these simple mixers, one can start by having users actually generate multiple accounts and not immediately link them. Still relies on OPSEC of the user, but a good start for simple dApp usage.

On the UX front, this is mainly a middleware and best practices issue. Can we help out wallets / web3 providers succeed at making this easier to set / generate accounts per dapp?

Do we use URL or IPFS hash as dapp identifiers? (think middleware) – or, of course, as you say, the contract address of the dapp.

I think starting with multiple accounts and multiple keys may not be ideal, but all of our single account solutions for securing / generating keys still work.

Privacy by default for everything a la ZEXE would be really nice, but it’s still far away, and not something that could be easily done technologically. What I’m proposing here is some low hanging fruit that can reduce the extent to which users’ activities are all immediately linked to each other. It’s nowhere close to total privacy, but it’s a very significant improvement.

This seems like a doable first task. The second part about Whisper etc. etc. seems like there are a lot of rabbit holes to go down.

True, but making a mixer that doesn’t have the “deanonymize yourself by paying for gas” issue requires a layer 2 messaging protocol for things other than transactions (unless the way we want to solve this is by adding some limited form of base-layer abstraction…)

wonder if we could also use a scheme like @bitgamma was once proposing in another context (Non-wallet usage of keys derived from BIP-32 trees) - so we would use different BIP-32 paths for each dApp.
This would have the advantage that we could directly use it with existing hardware wallets. In the above scheme hardware wallets would need to implement a new function to be compatible. The disadvantage would be that the chance of collisions between dapps is higher. But it would still be better than the status quo.

Hey! This is something that we have started investigating for our smart-contract based wallet Argent. We are working on the mixer but have not yet started tackling the “deanonymize yourself by paying for gas” issue. We were planning on looking at some form or meta-transactions for that which is essentially what you @vbuterin are suggesting. Will let you know if we find something even partly satisfactory.

@jpitts I agree that “account contracts” pose a challenge for privacy but I also think they are nicely positioned to bring solutions to users.

We were planning on looking at some form or meta-transactions for that which is essentially what you @vbuterin are suggesting

I personally hope there could be a coordinated effort to get meta-transactions or whatever other scheme figured out and made in a way that anyone can use. It’s just too useful. Maybe it requires finally getting something like whisper working well; would be good to have more discussion…

They way it is currently written would already allow this use case by defining a “dApp” key type (the document defining all key types is still WIP) and then each dApp gets an identifier.

If it is desired to keep a register of dApps, then simply using the key_index already defined would make the job.

Otherwise, for each dApp a 128-bit GUID can be generated and split in 4 32-bit integers which would be used as sublevels of key_type (hierarchy having no specific meaning, but just being used to get longer IDs). 4 bits of this identifier would probably need to be set to a fixed value since they are interpreted as hardened/unhardened derivation, but we would still have enough bits to avoid collisions.

I have updated the draft to allow the key_index field to encode larger identifiers, spanning across several derivation levels. It is quite generic to allow any kind of identifier to be used. Additional EIPs can define specific use cases, remaining compatible to the EIP-1581 specs.