4

I am writing to inquire feasible strategies to defeat Transaction Ordering Dependence (TOD) bugs.

I learned from the best practice guideline such that we can use a so-called "pre-committed" scheme to defeat TOD attacks: https://consensys.github.io/smart-contract-best-practices/known_attacks/#front-running-aka-transaction-ordering-dependence

However, it is still unclear to me how does the "pre-committed" scheme is implemented. Could anyone shed some lights or some code fragment of such implementations? Also, besides such "pre-committed" scheme, what else can be done to defeat TOD attacks?

======== update ====================

contract TransactionOrdering { 
 uint256 price; address owner; 

 function buy(uint256 amount) {
   cost = price * amount       
   return cost;                
 }                             

  function setPrice(uint256 _price) {
    // owner can set the price.      
    if (msg.sender == owner)         
      price = _price;                
  }                                  
}                                    

Let's suppose when the contract owner see a transaction of buy from the user, then the owner front runs a setPrice transaction to raise the price. Can the pre-committed pattern be used to defeat such attacks? Thanks a lot.

1 Answer 1

3

Let's suppose you have a raffle and can reserve a number

contract Raffle {
    mapping(uint256 => address) reserved;

    event Reserved(uint256 value, address owner);

    function reserve(uint256 value) public {
        require(reserved[value] == address(0), "Already reserved");
        reserved[value] = msg.sender;
        emit Reserved(value, msg.sender);
    }
}

This contract is affected by front running. If you reserve number 42 anybody can see your transaction before it is mined. A malicious actor can submit a transaction reserving the same number with a higher gas price and make your reserve fail.

A common solution is to first commit a hash instead of your data. In a second step you reveal the data that produces the committed hash.

  1. Calculate your hash by calling the digest function with a random nonce, value and address. The nonce is to prevent the attacker to use brute force to try to derive your value from the hash.
  2. Send the hash calculated to the contract. Since commitments are stored by user the attacker can send the same hash but it will not prevent a legitimate user from registering a hash.
  3. Call reveal to show your data and make the reservation. Here all the data is available to the attacker but it cannot make a reservation at this stage, and brute forcing a previously reserved hash should not feasible.

.

contract Raffle {

    mapping(address => bytes32) commitments;
    mapping(uint256 => address) reserved;

    event Reserved(uint256 value, address owner);

    function commit(bytes32 hash) public {
        require(commitments[msg.sender] == bytes32(0), "Already committed");
        commitments[msg.sender] = hash;
    }

    function reveal(uint256 nonce, uint256 value) public {
        bytes32 d = digest(nonce, value, msg.sender);
        require(commitments[msg.sender] == d, "Invalid data");
        require(reserved[value] == address(0), "Already reserved");
        reserved[value] = msg.sender;
        emit Reserved(value, msg.sender);
    }

    function digest(uint256 nonce, uint256 value, address sender) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(nonce, value, sender));
    }
}
5
  • Thank you very much for the answer! Thank makes a lot of sense to me. However, just one further question, can the "pre-committed" pattern be used to defeat potential TOD attack shown in my post? I updated my question. Commented Aug 4, 2019 at 15:13
  • To have a privileged account able to change important parameters at will is a different kind of attack. A solution used by exchanges is to submit a price range. If the price has varied a lot they will discard the operation.
    – Ismael
    Commented Aug 4, 2019 at 15:56
  • thank you very much! I believe this is what I am looking for. Also, could you shed some lights on how exact the raffle contract works? Let's suppose a malicious actor intentionally fails other user's submission of 2 Ether, then would the 2 Ether be returned back to the victim user or not? Commented Aug 5, 2019 at 1:44
  • For failed ethereum transactions the ether is returned minus the fees (for auction type contract fees can be very high). An important cost to consider is the missed opportunity.
    – Ismael
    Commented Aug 5, 2019 at 4:19
  • @Ismael, nice answer. Though, I'd love to hear your opinion on my question too: ethereum.stackexchange.com/questions/93727/… Commented Feb 20, 2021 at 20:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.