$160 million gone. Wintermute, among the sharpest market-making funds in the space, woke up one September morning to find nine figures missing from a critical wallet. What caused the compromise? Bad randomness in a vanity address generator. The blackhat had simply replayed the search iteration from the beginning until they recreated the (private key, public address) pair. And then, poof! Millions gone into the ether. All for a couple leading zeroes on the wallet address? Seems … vain.
If karmic justice is your thing, there’s also the story of the Indexed Finance exploiter who hacked $16 million in October 2021, then moved the stolen funds into an address starting with 0xba5ed… (based). Little did they know, that vanity address was subject to the same bad randomness bug that plagued Wintermute, and in September 2022 all that money was stolen yet again, flowing out to yet another hacker. No honor among thieves.
What went so wrong for these wunderkind devs, and what can we learn?
First, what is a vanity address? Vanity addresses are when a user shapes the public address associated with their wallet or smart contract. Maybe it starts with 0x0000000, maybe with 0xdeadbeef, maybe something else. They’re popular for a couple reasons:
Gas optimization. Wintermute saved $15,000 in gas costs just because their EOA had some leading zeros. Sound silly? Many other people agree, but that’s how the EVM works! Transaction gas costs go down if your address has lots of zeros in it. So it’s actually quite conscientious of your users to have a smart contract with leading zeros, saves them money whenever they interact.
Protocol branding. Did you know the 1inch token contract starts with 0x111111111…?
Multichain reproducibility. This is the heavy hitter in my opinion, and why every single protocol should use a vanity address for their deployments. Your app can live on 15 different EVM chains, and have the same address everywhere! Isn’t that easier for both devs and users?
When Is It Safe?
There are two types of Ethereum addresses: externally owned accounts (EOAs) and smart contract accounts. If you’ve used a wallet like MetaMask, each address in there is an EOA. It’s what you sign messages and make transactions from. Compare this with a smart contract account like the Uniswap contract, which people can interact with but it cannot take its own actions without being triggered. The TL;DR is simple - vanity addresses are unsafe for EOAs, but safe for smart contracts.
Why is this? We’ll explain in greater detail below, but it depends on how the vanity address is generated. For EOAs, you cycle through millions of private keys until you find one that corresponds to a public address that’s aesthetically appealing. Yet the private key controls the funds within the EOA, so if the randomness you used to iterate through private keys ever gets compromised then your entire account is ruined. On the other hand, creating a smart contract vanity address only requires iterating through public seeds which do not grant any admin permissions on the smart contract.
This is why Wintermute failed where OpenSea succeeded - it’s not okay to generate private keys in unsafe memory with unsafe software. But it’s perfectly fine to generate public seeds that way! So EOA vanity is the road to bankruptcy, smart contract vanity is the road to success.
Why Protocols Need Vanity Addresses
Simpler docs! You can just point to one contract address on all chains
Verifiable for users! Identical contract addresses only happen if and only if the bytecode is an exact byte-for-byte match
Verifiable for devs! Since identical contract addresses only happen on exact matches, you can catch tricky small modifications in deployment scripts
Simpler integrations! Other protocols can hardcode your contract addresses into their multichain code, instead of having to have if-statements based on the chainId
Thanks for reading The Variable! Subscribe for free to receive new posts
HERE BE DRAGONS: We’re about to dive into a detailed instruction manual of info I’ve compiled from a variety of twitter threads, DMs, and other tribal knowledge. This is the first time all the pieces have been laid out together. We’re headed into deeply technical territory targeted towards a smart contract developer audience who has experience deploying smart contracts onchain. The “Smart Contract Vanity Addresses”. Keep reading if you’re interested, but don’t worry about dropping off if that’s not for you! There’s a bonus technical challenge (with reward) at the end.
Smart Contract Vanity Addresses
There’s an approach to generating vanity smart contract addresses that’s 100% safe. Doesn’t matter which software you use, doesn’t matter if the iteration technique leaks publicly. It’s called the “CREATE2 Factory Method”. Not only does this provide vanity addresses, it’s also a foolproof way to ensure you have the same contract deployment address on multiple chains. And it lets others trustlessly deploy code on your behalf without any private key sharing or nonce assumptions.
First, a quick overview of how smart contract addresses get chosen. There are two deployment options, creatively named CREATE and CREATE2. The default flow is CREATE, when you deploy your smart contract directly from an EOA. The address is determined by hashing the contract creator address with the contract creator nonce. The nonce is how many transactions an address has sent, so a fresh wallet starts at 0 and increments by 1 every time it sends a new transaction. This is the magic formula for smart contract addresses deployed with CREATE:
new_address = hash(sender, nonce)
Less common but more interesting, here’s the formula for smart contract addresses deployed with CREATE2:
new_address = hash(0xFF, sender, salt, bytecode)
The former looks simpler, right? But let’s take an example of where this simplicity can come back to bite us compared to the more robust CREATE2 flow.
Airy Alice: Multichain Gone Wrong
Imagine a crypto dev named Alice who made two smart contracts: a Uniswap fork called GriddleSwap and an NFT project called ph00ts. These are both immutable standalone primitives, meaning no external dependencies or bridge risk. Alice deploys GriddleSwap onto Ethereum with nonce 0, then deploys ph00ts onto Ethereum with nonce 1. Sadly, Alice has a short attention span and gets distracted on crypto twitter for a couple minutes before deploying her work onto the second-largest smart contract platform, Binance Smart Chain.
But wait! She screws up the deployment order and deploys ph00ts before GriddleSwap. Because smart contract addresses depend solely on creator address and nonce within the deployment blockchain, Ethereum GriddleSwap has the exact same address as BSC ph00ts! To add insult to injury, Ethereum ph00ts has the same address as BSC GriddleSwap. To say this would be confusing for end users is an understatement. In fact, it could be abused by malicious deployers to trick people into thinking contract behavior is identical across chains - a fair assumption, given identical addresses!
Attentive Alice: Still Problematic
Even if Alice is conscientious while deploying and never mixes up her nonce order, there are other problems. If Alice deploys onto Ethereum and BSC properly but then makes an unrelated transaction on Polygon, nonce 0 has been used up. She can never deploy GriddleSwap there since her nonce is already incremented. Therefore the deployer private key must be safeguarded at all costs. If Alice leaks it, a griefer can make unrelated transactions. If Alice loses it, she also loses the ability to deploy to that address ever again on new chains. It’s a perpetual vulnerability that depends on a lone honest individual to safeguard private keys. And if Bitcoin core devs can’t do it, how will the rest of us?
CREATE2: The Solution
Thankfully there’s a better way to get consistent addresses across chains - doesn’t depend on secret private keys, doesn’t depend on a single deployer, and resistent to deployer mistakes along the way. Remember the formula for finding the address of a smart contract deployed with CREATE2:
new_address = hash(0xFF, sender, salt, bytecode)
The first argument, 0xFF, is a constant value we can ignore. The second argument, sender address, can be kept consistent by opting into z0age’s CREATE2Factory deployment at 0x0000000000FFe8B47B3e2130213B802212439497 across most EVM chains. The third argument is a user-chosen salt we can use to find a vanity address and then keep constant across chains. The fourth is contract bytecode, which serves as a helpful sanity check that we are deploying the exact same functionality across chains. All four params can be kept identical no matter what any individual deployer does.
Why is this better? Unlike a private key, the deployer-chosen salt can be made public! Knowing the salt enables contract deployment, but has zero control over contract assets or functionality. Because it’s not tied to any secret information, anyone can deploy contracts onto new chains without leaking or sharing private keys. The bytecode parameter also ensures that these new permissionless deployments will have the same address if and only if the bytecode is *identical*. So end users get stronger guarantees without needing to do detailed code diffs.
For a more in-depth overview, see the OpenZeppelin explainer.
Mining Your Own Vanity Address
Thought proof-of-work was dead after the Ethereum merge? Think again! Those same GPU capabilities that help find hash preimages with lots of leading zeroes for Bitcoin blocks are fantastic at finding hash preimages with lots of leading zeroes for EVM smart contracts. z0age from OpenSea (whom this whole article owes a great debt to for his explainers) found a straightforward setup to mine your own vanity addresses.
Spin up a GPU Example instance using vast.ai that makes ~2 *billion* attempts per second and costs ~25 cents / hr:
GPU: 1x RTX 3090
Disk space to allocate: 1.83 GB
SSH in 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
Run the seed search. For environment variables, INIT_CODE_HASH is the keccak256 of the contract creation code. A sample foundry test that prints this out can be found here - make sure to verify it before burning lots of compute resources! LEADING should be the number of leading zero bytes you want, TOTAL should be the number of total 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 published his repo, it was capable of running 1.90 billion attempts per second on the above vastAI hardware. Since then, vectorized went wild on some OpenGL kernels and I observe 2.15 billion attempts per second. This means finding a 5-leading-zero-byte address would take 256^5/(2150000000 * 60) ~= 8 minutes, finding a 6-leading-zero-byte address would take 256^6/(2150000000 * 3600) ~= 36 hours, and finding a 7-leading-zero-byte address would take 256^7/(2150000000 * 86400) ~= 387 days. Note that one byte equals two hexadecimal characters, so a five-leading-byte address will have ten zeros. Of course, this search can be fully parallelized and actual success probabilities over time will follow a Poisson distribution.
Deploying the CREATE2 Factory
Astute readers may have noticed a requirement that the CREATE2 Factory is already live at 0x0000000000FFe8B47B3e2130213B802212439497 across all chains. Bit of a chicken-and-egg problem, how can consistent address deployments depend on a consistent address deployment?
When I initially learned about this approach, I assumed it was simply a private key held secure by smarter people than me (the “Attentive Alice” scenario above). But it’s actually much more robust than this! The “keyless transaction” approach from ENS founder Nick Johnson takes advantage of the fact that you can recover the public address from any transaction signature, without knowing the corresponding private key that signed it. So one can create a transaction (“deploy a create2 factory”) and then invent a fake signature for it, such as a signature consisting of only 2’s. There exists a private key for this spoofed signature, but nobody 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 such an approach, it is a valid transaction, and in fact the only valid transaction that can emanate from this public address.
The result? Anyone can deploy the factory onto new chains with zero proprietary info, while malicious actors are prevented from griefing. Quite the clever technique for creating a single-use EOA that can only ever deploy one transaction.
This can be done 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 to permissionlessly deploy the CREATE2 Factory on any chain of your choice! Of course, if it’s already been deployed then there’s no need to do so again.
Side Note: EIP-155 Requirements are Bad
Brief foray to support my L1 governance escapades, feel free to skip. EIP-155 is a 2016 proposal from Vitalik introducing the concept of “chain IDs” to prevent replay attacks. Each chain would have its own unique identifier - Ethereum is 1, BSC is 56, Polygon is 137 - that would be included into signed transactions to prevent replay attacks. It was quickly adopted by Ethereum and every other EVM chain has followed in its footsteps. This is fine, the problem comes when a select few chains such as Evmos recently decided to explicitly disallow pre-155 transactions, under the bizarre justification that it would’ve prevented an operational error where Optimism sent 20M OP tokens to a nonexistent multisig that Wintermute (yes, them again) claimed to own but had never initialized. However, disabling pre-155 transactions explicitly breaks a whole host of crosschain deployments such as the CREATE2 factory and leading projects such as Seaport. These governance proposals should be immediately rolled back, protective minutiae like this should come from the wallet rather than consensus layer. If multichain is the future, then these unnecessary limitations are a gigantic barrier for top projects to deploy on your blockchain.
The Fun Stuff: Deployment Bounty
Today https://delegate.cash is deployed on 7 different EVM chains (Ethereum, Polygon, Optimism, Celo, Avalanche, Fantom, and Arbitrum) plus the 7 testnets corresponding to those chains. Same contract address for all of them: 0x00000000000076A84feF008CDAbe6409d2FE638B.
Is that enough? No, we need moar chains. Because delegatecash is a standalone primitive with zero dependencies, that means the risk from going multichain is literally zero. Pure upside! So, for the first five unique people who deploy and verify the delegatecash smart contract onto a new chain & corresponding testnet where it didn’t exist prior, I’ll award a $100 USDC bounty!
You’ll want to use the deploy script in the open-source repo here. This may require deploying the CREATE2 Factory if it doesn’t exist there yet, and don’t forget Etherscan verification! Please reply to the tweet announcing this article with proof. We’ll toss in some bragging rights engagement as well. Happy deploying, enjoy the experiential learning!
Thanks for reading The Variable! Subscribe for free to receive new posts
Good writeup thx
The folks at "Profanity Check" on Twitter allows you to check if your private key is compromised and subject to bad randomness. Highly suggest anyone to check this out - very cheap and fast!! https://twitter.com/profanity_check