go-ethereum, transactions, hardforks, replay-attack, ethereum-classic – How to Prevent Replay Attacks Post-Hard-Fork

ethereum-classicgo-ethereumhardforksreplay-attacktransactions

The DAO contract was attacked on 17th June 2016 where the attacker drained about USD 50 million worth of ethers using a recursive call vulnerability in The DAO contract code.

The attacker drained the stolen ethers into a few child DAOs and the Robin Hood Group drained the remaining funds into other child DAOs.

The Ethereum blockchain was hard-forked in block #1,920,000 on 20th July 2016 to transfer the ethers from The DAO contract and it's child DAOs into a WithdrawDAO contract.

While the majority of Ethereum nodes clients are running the hard-forked blockchain (ETH), a minority of Ethereum nodes clients are running the non-hard-forked Classic blockchain (ETC).

Ethereum node clients can be configured to use the hard-forked blockchain or the non-hard-forked blockchain. Some nodes have been configured to relay the transactions from the hard-forked blockchain to the non-hard-forked blockchain, and vice versa. Transactions executed on the ETH blockchain have a high likelihood of being replayed on the ETC blockchain, and transactions on the ETC blockchain have a high likelihood of being replayed on the ETH blockchain.

Several split contracts have been deployed at the same contract address on both the hard-forked and non-hard-forked blockchains.

How do you use these split contracts to conditionally send ethers to another accounts in either the hard-forked ETH chain or the non-hard-forked ETC Classic chain?


The DAO Refunds

Best Answer

Update Jun 02 2017

From WARNING: Do NOT Use SafeConditionalHFTransfer! Or Use It Correctly:

The SafeConditionalHFTransfer saved a lot of ethers being moved incorrectly on the wrong chain after The DAO hard fork. So far there has been 20549 txns + 16022 internalTxns passing through the SafeConditionalHFTransfer at 0x1e143b2588705dfea63a17f2032ca123df995ce0. The author contacted me this morning about 67,317.257581981046981598 ETH ~ USD 14,892,596.89 (@ $221.23/ETH) sent incorrectly to the contract.

When using this contract, you have to call the classicTransfer(...) or transfer(...) functions to direct your ETH or ETC to the intended chain. If you send ETH (or ETC) DIRECTLY to the contract address, your ETH (or ETC) will not be redirected to the destination address on the destination chain, but will instead be trapped in this contract FOREVER.

As the recent clients on both the ETH and ETC chains have EIP155 Replay Protection built in, you do NOT have to use this SafeConditionalHFTransfer any more. Just make sure you are using a recent client, with EIP155!

Here are the main clients and the versions implementing EIP155:

This warning has also been placed at the top of the answer to How to conditionally send ethers to another account post-hard-fork to protect yourself from replay attacks.



Update Nov 26 2016 - See @eth's answer to the question above as geth 1.5.3 has implemented replay attack protection.



Summary

  • Use Method 1 below if you want to use geth to transfer ethers to the hard-forked OR non-hard-forked Classic chain via the SafeConditionalHFTransfer contract.
  • Use Method 2 below if you want to use geth to transfer ethers to the hard-forked AND non-hard-forked Classic chain via the ReplaySafeSplitV2 contract.
  • Use Method 3 below if you want to use the Ethereum Wallet to transfer ethers to the hard-forked OR non-hard-forked Classic chain via the SafeConditionalHFTransfer contract.
  • Use Method 4 below if you want to use the Ethereum Wallet to transfer ethers to the hard-forked AND non-hard-forked Classic chain via the ReplaySafeSplitV2 contract. Either account can be your source account so that funds are sent back to your account on one of the chains.
  • The methods above rely on some Ethereum nodes relaying the transactions from the hard-forked chain to the non-hard-forked chain, or vice versa. If transactions stop being relayed to the other network, the transaction will only be executed on the chain your Ethereum node transmits on.
  • WARNINGS
    • Transfer a small test amount first
    • If you want your transactions executed on both chains, transfer only amounts that do not exceed your account balance pre-hard-fork.
  • ISSUES

    • If you transfer an ether amount that exceeds your account balance on one of the chains, the transaction will be rejected on that chain but executed on the other chain. This will cause your transaction nonce to be out of sync in both chains, and prevent you from having transactions automatically executed on both chains. See Problem after withdrawal non hard fork ethereum.
    • If your transactions are not being executed in both chains, your transaction nonce may be out of sync in both chains. If you still want to execute transactions on the OTHER chain, you will have to have your Ethereum node client synced to that OTHER chain.
  • If You Want To Send Pre-Hard-Forked ETC To An Exchange ETC Account

    1. You can use your support-hard-fork or the oppose-hard-fork Ethereum node client to do this.
    2. On the exchange site, get the ETC deposit address.
    3. Use the classicTransfer method in geth or the To Transfer Only On The Non-Hard-Forked Classic Chain in Ethereum Wallet.
    4. Transfer a small test amount first. Check your ETH account on a blockchain explorer (e.g. http://etherscan.io/) and you should notice only a small amount deducted from your account for the cost of gas of the SafeConditionalHFTransfer contract sending back your ETH.
    5. You will have to wait a short while before the ETC transaction appears on your exchange ETC deposit address as the relaying adds a short delay to the transaction appearing on the ETC chain.



Update Aug 25 2016

Ethereum Wallet and Mist Beta 0.8.2 now has replay prevention:

Replay prevention

We added an advanced feature to prevent your transactions on being replayed on other chains, like ethereum classic. This allows you either to prevent that transfer on happening on classic at all, or use that transaction to send the same amount to a different contract, like a newly created account or an exchange. If you want to fully separate all your transactions we recommend you create two new accounts, one for Ethereum proper and the other for Classic, and then move all your funds into them (remember that you need ether to move tokens), making sure that each account has 0 ether on the other chain - doing this once would prevent any future transaction from being replayed. To use this, use the "more options" button on the send page.

This feature also supports splitting tokens, but it's very experimental and will not work on all tokens. Since all of this is done using a contract then first you need to allow that contract to move tokens in your behalf by clicking "Approve token transfer".

As always, these features are experimental and should be tested with small amounts first. Although most transactions are replayed on both chains, some may not for multiple reasons. Also, some exchanges have issues receiving ether from a contract address - if that's your case, contact the exchange.

We've also removed all Fork code from the Mist app, so if you want to use it Ether Classic you'll have to either download Classic Mist directly from their repository or use your own node as the backend for your wallet (both Ethereum Wallet and Mist can connect to any node) as you would do for a private network.

The replay protection contract can be found at 0x1ca4a86bba124426507d1ef67ad271cc5a02820a.



Method 1 - Using geth And SafeConditionalHFTransfer Contract

Make sure that you are running geth version 1.4.10 or later. And run your geth commands with the --support-dao-fork option so that you are on the hard-forked blockchain. To transfer using the transfer(...) or classicTransfer(...) functions:

user@Kumquat:~$ geth console
// Allow chain to sync
var fromAccount = "{from account}";
var toAccount = "{to account}";
var amount = web3.toWei(1.123, "ether");
personal.unlockAccount(fromAccount, "{password}")

var safeConditionalHFTransferAddress = "0x1e143b2588705dfea63a17f2032ca123df995ce0";
var safeConditionalHFTransferABI = [{"constant":false,"inputs":[{"name":"to","type":"address"}],"name":"transfer","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"}],"name":"classicTransfer","outputs":[],"type":"function"},{"inputs":[],"type":"constructor"}];
var safeConditionalHFTransfer = eth.contract(safeConditionalHFTransferABI).at(safeConditionalHFTransferAddress);    

// WARNING - Run the next statement to transfer ETH on the hard-forked chain
// This will only cost some gas on the non-hard-forked ETC Classic chain
// Test with a small amount first
var transfer = safeConditionalHFTransfer.transfer(toAccount, {from: fromAccount, value: amount});
console.log(transfer);

// WARNING - Run the next statement to transfer ETC on the non-hard-forked Classic chain
// This will only cost some gas on the hard-forked ETH chain
// Test with a small amount first
var classicTransfer = safeConditionalHFTransfer.classicTransfer(toAccount, {from: fromAccount, value: amount});
console.log(classicTransfer);



Method 2 - Using geth And ReplaySafeSplitV2 Contract

Make sure that you are running geth version 1.4.10 or later. And run your geth commands with the --support-dao-fork option so that you are on the hard-forked blockchain. To transfer using the split(...) function:

user@Kumquat:~$ geth console
// Allow chain to sync
var fromAccount = "{from account}";
var toAccountFork = "{to account on forked chain}";
var toAccountNoFork = "{to account on non-forked chain}";
var amount = web3.toWei(1.123, "ether");
personal.unlockAccount(fromAccount, "{password}")

var replaySafeSplitV2Address = "0xaBbb6bEbFA05aA13e908EaA492Bd7a8343760477";
var replaySafeSplitV2ABI = [{"constant":false,"inputs":[{"name":"targetFork","type":"address"},{"name":"targetNoFork","type":"address"}],"name":"split","outputs":[{"name":"","type":"bool"}],"type":"function"}];
var replaySafeSplitV2 = eth.contract(replaySafeSplitV2ABI).at(replaySafeSplitV2Address);    

var transfer = replaySafeSplitV2.split(toAccountFork, toAccountNoFork, {from: fromAccount, value: amount});
console.log(transfer);



Method 3 - Using Ethereum Wallet And SafeConditionalHFTransfer Contract

Note that the ReplaySafeSplitV2 contract below has more safety features built in.

Make sure that you have downloaded Ethereum Wallet 0.8.1 or later.

The first time you start Ethereum Wallet 0.8.1, select "Yes" to the question "Do you want to activate the chain in which funds linked to the exploit are restored to a contract where they can be withdrawn by The DAO token holders?". You have now made the choice to use the hard-forked Ethereum chain.

In Ethereum Wallet, select the CONTRACTS page in the top menu. Click on WATCH CONTRACT.

  • Enter a CONTRACT NAME of SafeConditionalHFTransfer
  • Enter a CONTRACT ADDRESS of 0x1e143b2588705dfea63a17f2032ca123df995ce0
  • Enter in JSON INTERFACE the following text [{"constant":false,"inputs":[{"name":"to","type":"address"}],"name":"transfer","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"}],"name":"classicTransfer","outputs":[],"type":"function"},{"inputs":[],"type":"constructor"}]
  • Click OK. Your Watch Contract should look like:

enter image description here


To Transfer Only On The Hard-Forked Chain

This will transfer your ETH to the destination account on the hard-forked chain and will only cost you gas on the non-hard-forked Classic chain.

Select the CONTRACTS page in the top menu. Click on SAFECONDITIONALHFTRANSFER. On the right hand side of the page under WRITE TO CONTRACT, Select function Transfer. Enter your destination address on the hard-fork chain in the To - address field. In Execute from, select your account. Enter the number of ETH under the Send ETHER field. Click on the EXECUTE button, enter your password and confirm. Here is a screen image:

enter image description here


To Transfer Only On The Non-Hard-Forked Classic Chain

This will transfer your ETH to the destination account on the non-hard-forked Classic chain and will only cost you gas on the hard-forked chain.

Select the CONTRACTS page in the top menu. Click on SAFECONDITIONALHFTRANSFER. On the right hand side of the page under WRITE TO CONTRACT, Select function Classic Transfer. Enter your destination address on the non-hard-fork chain in the To - address field. In Execute from, select your account. Enter the number of ETH (ETC) under the Send ETHER field. Click on the EXECUTE button, enter your password and confirm. Here is a screen image:

enter image description here



Method 4 - Using Ethereum Wallet And The ReplaySafeSplitV2 Contract

UPDATE 22:27 Sep 4 2016 This is a new safer version of ReplaySafeSplit as discussed by @chevdor on thedao.slack.com/messages/general in Safer version of the ReplaySafeSplit Smart Contract. This new version has checks that the addresses you specify are not 0x0000...0000 before sending your ethers to the addresses. This section has been updated with the new contract details.

Make sure that you have downloaded Ethereum Wallet 0.8.1 or later.

The first time you start Ethereum Wallet 0.8.1, select "Yes" to the question "Do you want to activate the chain in which funds linked to the exploit are restored to a contract where they can be withdrawn by The DAO token holders?". You have now made the choice to use the hard-forked Ethereum chain.

In Ethereum Wallet, select the CONTRACTS page in the top menu. Click on WATCH CONTRACT.

  • Enter a CONTRACT NAME of ReplaySafeSplitV2
  • Enter a CONTRACT ADDRESS of 0x8201... (this old address was on the ETH chain only. If you used this contract, please delete your Watch Contract and recreate the Watch Contract with the new address below.)
  • Enter a CONTRACT ADDRESS of 0xaBbb6bEbFA05aA13e908EaA492Bd7a8343760477
  • Enter in JSON INTERFACE the following text [{"constant":false,"inputs":[{"name":"targetFork","type":"address"},{"name":"targetNoFork","type":"address"}],"name":"split","outputs":[{"name":"","type":"bool"}],"type":"function"}]
  • Click OK. Your Watch Contract should look like:

enter image description here


To Transfer

This will transfer your ETH to two accounts, the first being the destination account on the hard-forked chain and the second being the destination account on the non-hard-forked Classic chain.

Select the CONTRACTS page in the top menu. Click on REPLAYSAFESPLITV2. On the right hand side of the page under WRITE TO CONTRACT, Select function Split. Enter your destination address on the hard-fork chain in the Target fork - address field. Enter your destination address on the non-hard-fork Classic chain in the Target no fork - address field. In Execute from, select your account. Enter the number of ethers under the Send ETHER field. Click on the EXECUTE button, enter your password and confirm.

Here is a screen image:

enter image description here



SafeConditionalHFTransfer Contract

Note that the ReplaySafeSplitV2 contract below has more safety features built in.

Following is the source code for the SafeConditionalHFTransfer contract (suggested by @shoraibit 26/07/2016). This contract does not depend on the WithdrawDAO contract having a balance over 1,000,000 ethers in the future.

contract ClassicCheck {
    function isClassic() constant returns (bool isClassic);
}

contract SafeConditionalHFTransfer {        
    bool classic;

    function SafeConditionalHFTransfer() {
        classic = ClassicCheck(0x882fb4240f9a11e197923d0507de9a983ed69239).isClassic();
    }

    function classicTransfer(address to) {
        if (!classic) 
            msg.sender.send(msg.value);
        else
            to.send(msg.value);
    }

    function transfer(address to) {
        if (classic)
            msg.sender.send(msg.value);
        else
            to.send(msg.value);
    }            
}

This contract depends on the ClassicCheck contract:

contract ClassicCheck {   
    bool public classic;

    function ClassicCheck() {
        if (address(0xbf4ed7b27f1d666546e30d74d50d173d20bca754).balance > 1000000 ether)
            classic = false;
        else
            classic = true;
    }   

    function isClassic() constant returns (bool isClassic) {
        return classic;
    }
}

When the SafeConditionalHFTransfer contract was deployed, it used the ClassicCheck contract to determine whether the code was being deployed to the hard-forked or non-hard-forked chain. And this check was done when the WithdrawDAO had a balance over 1,000,000 . SafeConditionalHFTransfer should always work as it no longer has to check the balance of the WithdrawDAO balance.



ReplaySafeSplitV2 Contract

UPDATE 22:27 Sep 4 2016 This is a new safer version of ReplaySafeSplit as discussed by @chevdor on thedao.slack.com/messages/general in Safer version of the ReplaySafeSplit Smart Contract. This new version has checks that the addresses you specify are not 0x0000...0000 before sending your ethers to the addresses.

Following is the source code for the ReplaySafeSplitV2 contract:

contract RequiringFunds {
   modifier NeedEth () {
       if (msg.value <= 0 ) throw;
       _
   }
}

contract AmIOnTheFork {
   function forked() constant returns(bool);
}

contract ReplaySafeSplit is RequiringFunds {
   // address private constant oracleAddress = 0x8128B12cABc6043d94BD3C4d9B9455077Eb18807;    // testnet
   address private constant oracleAddress = 0x2bd2326c993dfaef84f696526064ff22eba5b362;   // mainnet

   // Fork oracle to use
   AmIOnTheFork amIOnTheFork = AmIOnTheFork(oracleAddress);

   // Splits the funds into 2 addresses
   function split(address targetFork, address targetNoFork) NeedEth returns(bool) {
       // The 2 checks are to ensure that users provide BOTH addresses
       // and prevent funds to be sent to 0x0 on one fork or the other.
       if (targetFork == 0) throw;
       if (targetNoFork == 0) throw;

       if (amIOnTheFork.forked()                   // if we are on the fork
           && targetFork.send(msg.value)) {        // send the ETH to the targetFork address
           return true;
       } else if (!amIOnTheFork.forked()           // if we are NOT on the fork
           && targetNoFork.send(msg.value)) {      // send the ETH to the targetNoFork address
           return true;
       }

       throw;                                      // don't accept value transfer, otherwise it would be trapped.
   }

   // Reject value transfers.
   function() {
       throw;
   }
}

Following is the VM code from the non-hard-forked Classic blockchain which matches the verified VM code + source code on the hard-forked chain.

user@PussyWillow:~$ geth -exec 'eth.getCode("0xaBbb6bEbFA05aA13e908EaA492Bd7a8343760477")' attach
"0x6060604052361561001f5760e060020a60003504630f2c93298114610028575b6100005b610002565b6100406004356024356000348190116100e157610002565b60408051918252519081900360200190f35b80547f16c72721000000000000000000000000000000000000000000000000000000006060908152600160a060020a0391909116906316c727219060649060209060048187876161da5a03f11561000257505060405151905080156100d25750604051600160a060020a038416908290349082818181858883f193505050505b1561010f575060015b92915050565b82600160a060020a0316600014156100f857610002565b81600160a060020a03166000141561005257610002565b600060009054906101000a9004600160a060020a0316600160a060020a03166316c727216040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151159050801561018c5750604051600160a060020a038316908290349082818181858883f193505050505b15610023575060016100db56"

This contract depends on the AmIOnTheFork contract:

contract AmIOnTheFork {
    bool public forked = false;
    address constant darkDAO = 0x304a554a310c7e546dfe434669c62820b7d83490;
    // Check the fork condition during creation of the contract.
    // This function should be called between block 1920000 and 1921200.
    // Approximately between 2016-07-20 12:00:00 UTC and 2016-07-20 17:00:00 UTC.
    // After that the status will be locked in.
    function update() {
        if (block.number >= 1920000 && block.number <= 1921200) {
            forked = darkDAO.balance < 3600000 ether;
        }
    }
    function() {
        throw;
    }
}