Original text: "Vanity Addresses" by foobar

Compilation: Overnight porridge, the way of DeFi

160 million US dollars disappeared, and Wintermute lost the funds. Wintermute is one of the most astute market-making funds in the industry. One morning in September, when the person in charge of Wintermute woke up, he found that one of their important wallets had lost 9 figures. funds. So what led to Wintermute being stolen? This is caused by poor randomness in a vanity address generator. Black hat hackers brute force the private key & public address pair from scratch, and then a large amount of crypto assets are transferred away.

There is also a story about Indexed Finance being hacked, where they had $16 million stolen in October 2021, and the stolen funds were then moved to addresses starting with 0xba5ed… . What they didn’t know was that the vanity address was also affected by the bad randomness vulnerability that plagued Wintermute, and in September 2022, all the money was stolen again, going to another hacked wallet address. Thieves are ruthless.

So what problems did these talented developers encounter, and what can we learn from them?

On the left is the normal address used for the WETH contract. On the right is a pretty address with 14 leading zeros for MEV robot optimization. The most common type of vanity address is one with many leading zeros.

First of all, what is a vanity address (also known as a vanity address)? A vanity address refers to a public address that a user deliberately creates to associate with their wallet or smart contract. Maybe it starts with 0x0000000, maybe it starts with 0xdeadbeef, maybe it's another regular address. There are several reasons for their popularity:

1. Gas optimization: Wintermute saved $15,000 by using an EOA address with multiple leading zeros. Sounds silly? Many people agree, but this is how the EVM works! If you have a lot of zeros in your address, the transaction gas fee can go down. So if you use a smart contract address with a lot of leading zeros, users will be happy when they interact with it because it saves them money.

The Ethereum Yellow Paper describes how leading zero addresses can enable cheaper gas

2. Agreement brand. Do you know that the 1inch Token contract starts with 0x111111111...?

1inch Token Contract

3. Multi-strand repeatability. In my opinion, this is a top priority and why every protocol should use a vanity address for their deployment. Your application can live on 15 different EVM chains and have the same address everywhere! Wouldn't this be easier for developers and users?

So when is a pretty address safe?

There are two types of Ethereum addresses: externally owned accounts (EOA) and smart contract accounts. If you have used a wallet like MetaMask, each address in it is an EOA, which is used to sign messages and process transactions. Compare this to a smart contract account such as the Uniswap contract, which people can interact with but it cannot take its own actions without being triggered. To sum up, it is very simple. Good-number addresses are not safe for EOA accounts, but they are safe for smart contract accounts.

So why is this so? We’ll explain this in more detail below, but it depends on how the vanity address is generated. For EOA accounts, you cycle through millions of private keys until you find one that corresponds to a nice-looking public address. However, the private key controls the funds within an EOA account, so if the randomness you use to traverse the private key is compromised, your entire account is ruined. On the other hand, creating smart contract vanity addresses only requires traversing public seeds, which do not grant any administrative rights to the smart contract.

This is why Wintermute fails and OpenSea succeeds - generating private keys in insecure memory with insecure software is bad. But generating public seeds this way is pretty nice! Therefore, a good EOA address is a road to bankruptcy, while a smart contract good address is a road to success.

Why does the protocol require a pretty address?

Simpler documentation! You can point to a contract address on all chains;

User verifiable! The same contract address will appear only if and only if the bytecode is matched byte by byte;

Developers can verify! Since identical contract addresses only occur in the case of an exact match, you can catch tricky small modifications in your deployment script;

Easier integration! Other protocols can hardcode your contract address into their multi-chaincode without having to use chainId based if statements.

NOTE: We are about to delve into a detailed instruction manual. Putting all the pieces together for the first time, we are diving deep into the technical space and targeting smart contract developers with experience deploying smart contracts on-chain. If you're interested, keep reading, but if it's not for you, don't worry about keeping up! There's an additional technical challenge at the end (with rewards).

Smart contract beautiful address

There is a way to generate smart contract vanity addresses that are 100% secure, no matter what software you use, it doesn’t matter if the iterative technology is publicly leaked. It’s called the “CREATE2 factory method” and not only does it provide a pretty address, it’s also a foolproof way to ensure you have the same contract deployment address on multiple chains. It also allows others to trustlessly deploy code on your behalf without any private key sharing or nonce assumptions.

First, a quick overview of how to choose a smart contract address. There are two deployment options, CREATE and CREATE2. When you deploy a smart contract directly from EOA, the default process is CREATE. The address is determined by hashing the contract creator address with the contract creator nonce. This nonce refers to how many transactions an address has sent, so a new wallet starts at 0 and is incremented by 1 each time a new transaction is sent. Here is the magic formula for the smart contract address deployed by CREATE:

new_address = hash(sender, nonce)

Less common, but more interesting, is the smart contract address deployed using CREATE2, and here is its formula:

new_address = hash(0xFF, sender, salt, bytecode)

The former seems simpler, right? However, let's give an example of where this simplicity can be detrimental compared to the more robust CREATE2 process.

Airy Alice: There is a problem with multi-chain

Imagine a crypto developer named Alice creates two smart contracts: a Uniswap fork called GriddleSwap, and an NFT project called ph00ts. They are all immutable independent primitives, which means there are no external dependencies or cross-chain bridge risks. Alice deploys GriddleSwap to Ethereum using nonce 0, and then deploys ph00ts to Ethereum using nonce 1. Unfortunately, Alice has a short attention span and was distracted on crypto Twitter for a few minutes before deploying her work to Binance Smart Chain (BSC), the second largest smart contract platform.

Oops, messed up the deployment order!

But wait! She messed up the deployment order and deployed ph00ts before GriddleSwap. Since the smart contract address only depends on the creator address, and in the deployed blockchain, Ethereum gridleswap has the exact same address as BSC ph00ts! To make matters worse, Ethereum ph00ts' address is the same as BSC GriddleSwap's address. To think that end users will be confused is an understatement. In fact, it can be abused by malicious deployers to trick people into thinking that the contract behavior on the chain is the same - which is a fair assumption, given the same address!

Careful Alice: There will still be problems

Even if Alice is careful when deploying and never mixes up the order of her nonces, there are other problems. If Alice deploys correctly to Ethereum and BSC, but then makes an unrelated transaction on Polygon, nonce 0 has been used up. She can never deploy GriddleSwap there because her nonce has been incremented. Therefore, deployer private keys must be protected at all costs. If Alice leaks it, a malicious saboteur can make unrelated transactions. If Alice loses it, she will also lose the ability to deploy to that address again on a new chain. This is a permanent vulnerability that relies on an honest individual to protect the private key. If even Bitcoin core developers can’t do it, how can the rest of us do it?

Solution: CREATE2

Thankfully, there's a better way to get consistent addresses across chains—one that doesn't rely on secret private keys, doesn't rely on a single deployer, and is resistant to deployer error along the way. Remember the formula for finding the address of a smart contract deployed using CREATE2:

new_address = hash(0xFF, sender, salt, bytecode)

The first parameter 0xFF is a constant value that can be ignored. The second parameter (sender address) can be made consistent by selecting z0age's CREATE2Factory deployment 0x0000000000FFe8B47B3e2130213B802212439497 in most EVM chains. The third parameter is a user-selected salt, which we can use to find a good address and then keep it unchanged on the chain. The fourth is the contract bytecode, which serves as a useful sanity check to ensure we are deploying the exact same functionality on-chain. All four parameters can remain the same no matter what any single deployer does.

Why is this better? Unlike the private key, the salt selected by the deployer can be made public! Knowing the salt allows you to deploy the contract, but you have zero control over the contract assets or functionality. Because it does not bind any secret information, anyone can deploy the contract to the new chain without revealing or sharing the private key. The bytecode parameter also ensures that these new permissionless deployments will have the same address if and only if the bytecode is the same. Therefore, end users get stronger guarantees without having to make detailed code differences.

For a more in-depth overview, see OpenZeppelin’s popular science article‌.

Create your own beautiful address

Think Proof of Work (PoW) will be useless after the Ethereum merger? Think again! The same GPU capabilities that help find hash preimages with a large number of leading zeroes for Bitcoin blocks are also excellent at finding hash preimages with a large number of leading zeroes for EVM smart contracts. z0age from OpenSea (thanks to his explanation for this post) found a simple setup to create your own vanity address.

1. Use vast.ai‌ to launch a GPU example instance, which attempts about 2 billion times per second and costs about 25 cents/hour:

Image:nvidia / opencl

GPU: 1x RTX 3090

Disk space required to be allocated: 1.83 GB

2. SSH and install rust + create2crunch

sudo apt install build-essential -y; curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y; source "$HOME/.cargo/env"; git clone https://github.com/0age/create2crunch && cd create2crunch; sed -i 's/0x4/0x40/g' src/lib.rs

3. Run a seed search. For environment variables, INIT_CODE_HASH is the keccak256 of the contract creation code. A printout of a sample foundry test can be found here ‌ - make sure to verify it before consuming large amounts of computing resources! LEADING should be the number of leading zero bytes you want, and TOTAL should be the total number of zero bytes you want in the contract address.

export FACTORY="0x0000000000ffe8b47b3e2130213b802212439497"; export CALLER="0x0000000000000000000000000000000000000000"; export INIT_CODE_HASH="0xabc...def"; export LEADING=5; export TOTAL=7; cargo run --release $FACTORY $CALLER $INIT_CODE_HASH 0 $LEADING $TOTAL

When z0age first released his repo, it was capable of running 1.9 billion attempts per second on the aforementioned vastAI hardware. Since then, vectorization has gone crazy on some OpenGL cores, and I've observed 2.15 billion attempts per second. This means that finding an address with 5 leading zero bytes would take 256^5/(2150000000 * 60) ~= 8 minutes and finding an address with 6 leading zero bytes would take 256^6/(2150000000 * 3600) ~ = 36 hours. A 7 leading zero byte address takes 256^7/(2150000000 * 86400) ~= 387 days. Note that one byte is equal to two hexadecimal characters, so a 5 leading byte address will have 10 zeros. Of course, this search can be fully parallelized, and the actual probability of success over time will follow a Poisson distribution.

Deploy the CREATE2 factory

Astute readers may have noticed that the CREATE2 Factory already exists at 0x0000000000FFe8B47B3e2130213B802212439497 on all chains. It's a bit of a chicken-and-egg problem, how does consistent address deployment depend on consistent address deployment?

When I first learned about this approach, I thought it was just a private key held by someone smarter than me (the "observant Alice" scenario above). But it's actually much more robust than that! ENS founder Nick Johnson's "keyless transactions" approach exploits the fact that you can recover the public address from any transaction signature without knowing the corresponding private key that signed it . Therefore, it is possible to create a transaction ("deploy a create2 factory") and then invent a forged signature for it, such as one consisting of only 2's. The private key for this forged signature exists, but no one knows what it is. But we can recover the public address corresponding to the "keyless signature", send it some ETH, and then submit the signed transaction to the mempool. Despite the obscurity of this method, it is a valid transaction, and in fact the only valid transaction that can be sent from this public address.

The result? Anyone can deploy a factory onto a new chain without any proprietary information, while preventing malicious actors from causing damage. Creating a single-purpose EOA that can deploy only one transaction is a very clever technique.

Specific addresses and contracts created during keyless transactions

This can be accomplished with three simple "forge cast" commands. The bytecode is too long to copy here, but you can follow the instructions at https://github.com/ProjectOpenSea/seaport/blob/main/docs/Deployment.md‌ without permission on any chain of your choice Deploy CREATE2 Factory easily!

Of course, if it has already been deployed, there is no need to deploy it again.

Side note: EIP-155 requirements are terrible

A brief attempt to support my L1 governance adventures, feel free to skip ahead. EIP-155‌ is a proposal put forward by Vitalik in 2016, which introduces the concept of "chain ID" to prevent replay attacks. Each chain will have its own unique identifier - 1 for Ethereum, 56 for BSC and 137 for Polygon - which will be included in signed transactions to prevent replay attacks, which was quickly adopted by Ethereum , and all other EVM chains have followed this proposal. This is great, but the problem comes when a few chains are selected, such as Evmos which recently decided to explicitly ban transactions before EIP-155 ‌, which, for strange reasons, prevents an operational error where Optimism sent 20 million OP Tokens to a multisig (yes, them again) address that Wintermute doesn't exist, claims to own but was never initialized. However, disabling pre-155 transactions would significantly break a whole set of cross-chain deployments, such as the CREATE2 factory and leading projects like Seaport. These governance proposals should be rolled back immediately, and protection details like this should come from the wallet rather than the consensus layer. If multi-chain is the future, then these unnecessary restrictions will be a huge obstacle for top projects to deploy on your blockchain.

Fun stuff: deploying bounties

Today https://delegate.cash is deployed on 7 different EVM chains (Ethereum, Polygon, Optimism, Celo, Avalanche, Fantom and Arbitrum) and 7 testnets corresponding to these chains. The contract address for all of these is the same: 0x00000000000076A84feF008CDAbe6409d2FE638B.

So is this enough? No, we need more chains. Because delegatecash is an independent primitive with zero dependencies, this means that the risk of multiple chains is effectively zero. This is pure benefit! Therefore, for the first 5 people to deploy and verify the delegatecash smart contract to the new chain and corresponding testnet, I will award a 100 USDC bonus!

You need to use the deployment script from the open source repository here. This may require deploying the CREATE2 Factory if it doesn't exist, and don't forget Etherscan verification! Happy deployment and enjoy experiential learning!