Contract Addresses: Astral Projection, etc.
RicMoo — March 22, 2018

When an Ethereum smart contract is deployed to the network it is assigned an address, which is used by applications to query and update that contract's state. An aspect that is often over-looked is how this address is assigned, and some of the possibilities with exploiting this process.

First let's examine a short snippet of JavaScript to understand how the address is calculated. If it looks scary, no worries, just skip it.

var deployerAddress = "0x2D2c5e58cfcF102c9B459A26ffEc24BB22CbBE12";
var nonce = '0x0';
// Encode the details using Recursive Length Prefix encoding (RLP).
// RLP only supports sequences of bytes and arrays; for numbers
// we strip leading zeros bytes (i.e. 0 is encoded as '0x')
var encoded = RLP.encode([ deployerAddress, stripZeros(nonce) ]);
// We take the hash of the encoded details
var encodedHash = keccak256(encoded);
// Take the last 20 bytes of the hash of the encoded details
var contractAddress = hexlify(encodedHash.slice(12));
// "0x5FfC014343cd971B7eb70732021E26C35B744cc4"
// Or you could use ethers.utils.getContractAddress(tx)

Without worrying about the various encodings, the important thing is what information is used to determine the contract address; the deploying address of the contract and the nonce of the transaction.

Astral Projection: Being Two Places at Once

When we write applications, we usually begin developing against one of the testnets (e.g. Ropsten) and later move to mainnet (i.e. Homestead). The front-end, when it first launches, determines which network it is on, and looks up which contract address to use.

If we ensure we use the same account and nonce to deploy our contract on both networks, we can skip that first step of looking up the network, since we know the address of our contract is the same, regardless of the network.

This is also useful in situations where an application is entirely offline (e.g. the Firefly Hardware Wallet) and would require the user to somehow specify the network, failing at a future date (when attempting to take the transaction on-chain) if the user had selected the wrong option.

There is also no requirement that the same contract or constructor arguments are deployed to each network. You may wish to deploy a simple proxy wallet at your testnet address on the mainnet, so any mistakes can be recovered.

This is how I deployed the current ENS resolver to both mainnet and the Ropsten testnet at the same address, in each case the constructor arguments were different, since the constructor takes in the ENS contract address.

To ensure the nonce is identical across the networks, create a new account and fund it on each network with enough ether to deploy the contract (with nonce 0). This also mitigates some of the concerns of intentionally replayable transactions.

Intentionally Replayable Transactions

An intentionally replayable transaction is one which can be sent and accepted on any network (e.g. ETH and ETC). This was originally the default behaviour, but once economically supported hard-forks began, the ability for a transaction to be locked to a specific chain was added and became the default, since it protects users from replay attacks.

However, when deploying a contract on multiple networks with the same address, it is sometimes useful to intentionally disable replay protection.

This should be done with extreme caution and only if you understand the implications of disabling EIP-155 replay protection

var ethers = require('ethers');
var wallet = new ethers.Wallet(privateKey);
// Setting the chainId to zero disables EIP-155 Replay Protection
// WARNING: Make sure you know what you are doing with this!!
var tx = {
chainId: 0,
gasLimit: 750000,
gasPrice: 10000000000,
nonce: 0,
data: '0x...'
};
var signedTransaction = wallet.sign(tx);
['ropsten', 'homestead', 'rinkeby'].forEach(function(network) {
var provider = ethers.providers.getDefaultProvider(network);
provider.sendTransaction(signedTransaction);
});

By using a chain ID of 0, any future network or non-public testnet (e.g. local Ganache node) may deploy your contract at the exact same address; the owner just needs to fund the deploying address on that network and replay the signed raw transaction.

Vanity Contract Addresses

While ENS has made vanity addresses somewhat obsolete, they are sometimes still interesting to play with. A vanity address is simply an address that looks like a chosen pattern; for example, if we want an address with the prefix 0xc0ffee. Vanity addresses have traditionally been computed for addresses controlled by private keys.

However, since we know how to compute a contract's address, we can use that to find a vanity address for a contract:

  • Choose a random private key.
  • Compute the private key's address.
  • Compute the contract address for the first transaction from that address.
  • Does the contract address begin with 0xc0ffee? If not go to step #1.

For this article, I put together a quick contract vanity address miner, which will compute the private key for a contract vanity address:

/home/ricmoo> ./vanity c0ffee

The longer the desired prefix for an address, the more private keys that need to be attempted and will fail to match. Each additional character (i.e. nibble) in a prefix will require about 16 times as long to mine. For the 6 nibbles in 0xc0ffee, about 16 million attempts are needed, which takes my MacBook about 4 hours to complete.

Demo: For the price of a cup of coffee, you can caffeinate a nerd

Here is a very quick demonstration of these techniques. The Coffee contract allows anyone to donate ether to fund my caffeine requirements, and when I withdraw I must provide the number of coffees I have purchased with it as well as the milligrams of caffeine ingested.

After 16 hours the contract vanity address miner had found 9 valid private keys which would deploy a contract with the prefix 0xc0ffee. Then I choose the one with the nicest looking checksum address.

Using an intentionally replayable transaction, it has been deployed to Homestead, Ropsten, Rinkeby, Kovan and "Classic", and is available at the contract address

0xc0FFee4499F75B28c7d7dF1d3c036C8dbbB1a17c

To deploy this contract to your own local development network, send 0.0006 ether to the address 0x1ff166CD339C2Af3C6Eb856a849a42aC18b1E42b, and then send the signed raw transaction.

Thanks for reading! I hope you found this useful, or at least interesting. Any feedback is always welcome and feel free to follow me on Twitter for other random thoughts.