How the Ethereum Hard Fork Can Fail

in #news8 years ago

 The Ethereum fork will take place in a matter of days. I recently  skimmed through it, so here are the kinds of issues that I worry about  with regard to the hard fork to come, as well as half-baked ideas  related to the decentralized administration of the hard fork. This is a  brain dump of sorts, so treat it as such. Not every potential problem  turns into a real problem. I will scientifically lay everything I worry  about on the table. If we can methodically prove to ourselves that they  are not worth worrying about, then we are done. However, if you have a  large open ETH position and cannot handle dispassionate discussion about  possible problems, it's best to look elsewhere. Ok, so let's delve into possible problems:  

  • At the very highest level, the hard fork comprises the hard fork  policy (which determines how people get remunerated), the client code  (which implements the ethereum protocol in multiple different languages  for different clients), and the refund contract (which takes over the  ether balance of the old DAO, but leaves the old DAO intact).
  • The community itself determined the hard fork policy via a discussion process. I will take this as a given.
  • The intent of the hard fork policy sounds quite reasonable to me. I  will regard all discussion of mechanisms in a policy document as  suggestions -- the masses care about the outcome, not the means.
  • The hard fork policy leaves unaddressed the issue of what happens to  the coins that are lost, abandoned or left unallocated in the trustee  multisig contract. Laying out the charter of the trustees, so the  expectations from them are specified down to every wei, is necessary.
  • The client code changes (geth and parity)  seem very straightforward. This is one of the big advantages of a clean  hard fork. The core implementation is easy to check, and the geth code I  skimmed seems to have the right general shape.
  • That brings us to the new refund contract. It has three components  that I want to treat separately: the refund engine, the token mechanism,  and the enumeration and classification of affected child DAOs.
  • I have not looked into the code for enumerating and classifying the  child DAOs at all. This work involves a modest amount of blockchain  forensics, and some manual verification of tools for parsing the  ethereum blockchain. I assume, based on second-hand reports of people  who have checked its output, that this code works as intended.
  • The refund engine, in isolation from the token mechanism used to keep track of account balances, looks generally OK to me, but it is not written in a manner that I would consider best practice or defense in depth.

Here's the current code: 

// Deployed on mainnet at 0xbf4ed7b27f1d666546e30d74d50d173d20bca754

contract DAO {
    function balanceOf(address addr) returns (uint);
    function transferFrom(address from, address to, uint balance) returns (bool);
    uint public totalSupply;
}

contract WithdrawDAO {
    DAO constant public mainDAO = DAO(0xbb9bc244d798123fde783fcc1c72d3bb8c189413);
    address public trustee = 0xda4a4626d3e16e094de3225a751aab7128e96526;

    function withdraw(){
        uint balance = mainDAO.balanceOf(msg.sender);

        if (!mainDAO.transferFrom(msg.sender, this, balance) || !msg.sender.send(balance))
            throw;
    }

    function trusteeWithdraw() {
        trustee.send((this.balance + mainDAO.balanceOf(this)) - mainDAO.totalSupply());
    }
}

In particular, from least to worst: 

I don't understand why the trustee address is not marked "constant".  I realize that there is no code that can change the trustee address.  But then why is the DAO address marked constant? Inconsistencies like  this erode trust and telegraph that we have learned nothing from the DAO  disaster. Document all intent and assumptions in the code, that's the  right place to do it.
The trusteeWithdraw() function seems to be intended solely for the  trustee multisig's use. So, it should have a function modifier that  rejects calls unless they come from the trustee address. The lack of  such modifiers is a cavalier coding practice that reminded me of the  time when I sold my motorbike and bought a used car in grad school. I  noticed that the air intake for the cabin was right behind the engine  block, in a spot where any oil leak would turn into noxious fumes. I  thought "wow, these German engineers are so good, so amazingly  confident, that they purposefully put the intake in that spot, to show  the world how convinced they are that the gaskets will never leak." So  it was kind of funny (not) when my engine leaked oil and I ended up  breathing noxious fumes until I had to give the car to a charity for the  blind. Long story short, there is no room for this kind of cavalier  behavior in a post-DAO world. Put the modifier there already.
The critical weakness of the refund engine is its dependence on the inviolability of the DAO's token management, discussed next.
 


  • The current implementation relies on the integrity of the old DAO's token management.  I find this quite dangerous. Philosophically, if we were unrolling the  hard fork solely because "a hacker took advantage of a small reentrancy  bug," then it would make sense to rely on the old token accounting --  after all, the old DAO would be perfectly fine, save for one minor  glitch, and we'd be fixing just that one minor glitch. But that's not  true and that's not why I advocated a hard fork. In fact, this kind of  case-by-case, narrow thinking is precisely what got us into trouble. I  believe the hard fork is called for because the old DAO is bug-ridden at  multiple levels. Consequently, my starting assumption, that we treat  the entirety of the old DAO as untrustworthy, motivates a different  strategy.
  • As a result, if I were devising the hard fork, I would freeze the  DAO token balances at a certain block, and then build a list of  refundable addresses based on the frozen balances. DAO tokens traded  after the freeze point would have 0 redeemable value from the refund  contract.
  • I made this suggestion to the folks working on the hard fork, but  they favored the current approach instead. To be fair, the current  approach is simple, requires no trust in a party who will perform the  enumeration (it requires trust in the DAO token code instead, and a  trustee for enumerating childDAOs, a simpler task), and permits trading  of tokens until the final refund. But it is open to attacks that can  manufacture DAO tokens. My suggestion does not rely on the old DAO at  all, but its simplest implementation would probably rely on a trustee  who will perform the enumeration.
  • I did not fight hard to get the hard fork folks adopt my approach,  for three reasons: first, I do not know of an exploit that can create  tokens in the main DAO and exploit this refund contract. Second, the  practical form of my suggestion ends up relying on a trusted party to  compute the balances -- and it is difficult to find such a party that  will take on the responsibility for no compensation. Finally, if a  high-profile Ethereum Dapp using basic patterns is unable to handle  simple token creation and transfer, then a wakeup call is necessary  anyway -- a failed hard fork would be quite dire, but survivable, maybe.  Bonus reason: I do this stuff just because it's fun, in between real  research. I said it once to the right people -- repeating it would stop  being fun.
  • If we are going to instead push ahead with the current fork strategy  that relies on The DAO's accounting of its tokens, it would be prudent  to construct a proof that the token management of the current DAO is  correct, and cannot be abused to issue spurious tokens.
  • There is, approximately, $115M at stake. This immediately creates a  lucrative bug bounty, albeit for illegal gains: everyone has an interest  in looking carefully at this code to find its flaws. Yet no one has any incentive to reveal what they find.  There is no one who can offer $115M or even $11.5M to a hacker in  monetary compensation. This should be quite worrying -- a bright hacker  who identifies a flaw might choose to exercise the flaw and collect the  cash, instead of letting people know. (Incidentally, this is true for  Bitcoin as well. A coin without a substantial bug bounty is a vulnerable  coin).
  • Regardless, it is possible to compete with even $115M in  monetary compensation: people are generally nice and they value  intangible assets much more than money. In particular, if the community  commits to granting "uber-hero" status to anyone who identifies a bug in  the hard fork, it might entice an idealistic person to avoid the hassle  of laundering illegal gains.  For instance, part of the uber-hero  treatment would be an invite to Devcon2 to give a keynote. The nice  thing about this is that it's almost free, and it also helps  differentiate the ether community from others whose first reaction to  any bug report is to deny and attack the researchers. And it's the only  way of competing with a large pot of coins.
  • If the trustees do their jobs correctly, any bugs that affect the  token accounting in the child DAOs ought to be inconsequential.
  • How likely is it that there are attacks against the token accounting  mechanism in the main DAO? Not likely. But it's not impossible unless  there is an impossibility proof.
  • Since DAO tokens are being traded right now, the attack could already have surfaced. But if I were the attacker,  I'd wait to unleash the attack when the tokens are redeemable for  ether, so I can extract much more cash, instead of tanking the thin DAO  token market.
  • There may be bytecode/EVM-level attacks that might enable one to  raid the refund contract. It seems unlikely that there exist such bugs,  but there could well be some. Low level bugs would affect much more than  the refund contract. But because of the implicit bug bounty in the  refund contract, we might see such a bug surface now, through this hard  fork episode.
  • I have written about replay attacks.  Following a fork, one can interact with a smart contract on one chain,  and replay it on the other chain. For instance, I can play tic-tac-toe  with you to a draw on one chain, replay your moves on the other chain,  change my countermoves, and win. But these attacks extract only as much  money as there exists on the minority chain, and help make it die out  faster. In general, there may be vulnerable contracts out there that  manage pre-fork money. Talk to an expert if you're writing or you have  written a contract that handles large amounts of cash on how to make it  secure against replay attacks.
  • The incentive structures are lined up  for people to coalesce onto the new chain, with the economic majority,  fairly quickly. If all players were maximizing ether value, we'd see the  minority chain wither and die quickly. However, there are a large  number of people who see Ethereum as a threat to other cryptocurrencies  (for entirely flawed reasons) and want it to fail. Further, some people  specifically want Ethereum's hard fork to fail. These folks might well  subsidize mining on the minority fork for some time. If this were to  happen, sit tight and do not panic because the predictions, made for  rational parties, do not hold in the presence of economically irrational  players subsidized exogeneously. Use the majority chain and ignore the  minority chain's hashpower until the subsidizing party gives up or runs out of cash.  Some people, especially in Bitcoin-land, play up the importance of  mining power, mostly because they are thoroughly confused, as seen here and also here.  The moment when there are no exchanges on the minority chain, it  becomes a harmless testnet.  Remember that miners are worthless without  economic power, which comes from exchanges.

This is a comprehensive list of my concerns about the hard fork at  the moment. Remember that not every concern points out a genuine  problem: distributed systems researchers are, by nature, a paranoid  bunch. As long as every concern is systematically evaluated, we will be  able to ferret out all bugs and ensure an orderly fork. I expect most,  if not all, of my concerns to be misplaced, and I would indeed be  delighted if they were. I hope this glimpse into how a distributed systems researcher thinks about issues is received in the spirit it's intended. Note that I did something in this post I don't often do: I literally shared my fears, uncertainties and doubts. But uncertainty is precisely what drives good engineering, good system design and bug finding. And if the readers of this blog know one thing, it's this: distributed systems work only to the extent that they have a principled reason, a proof, for why they should do the things they should do. What they do on a sunny day, with the wind on their back and with nary an attacker in sight, is immaterial. I do not currently have a strong reason, a proof, to believe that this hard fork code would withstand an attacker as is, but I hope we can arrive at one before the fork. At a higher level, I am convinced that the hard fork is the simplest path forward, that the risks are manageable, and, above all, I have full faith in the Ethereum community's ability to engage in civil, technical discussions to address potential problems. Ultimately, it is the communities that provide value to ink on paper or numbers on a ledger.   

Follow Up

I circulated the draft of this post among some friends and received some feedback, for which I'm grateful. Specifically, Phil Daian and Ittay Eyal provided invaluable feedback. Christoph Jentsch provided insightful commentary on a draft of this post, and pointed out the list of functions touched by the refund contract. Note, however, that even though the refund contract invokes only a subset of The DAO, it is implicitly reliant, through data-dependencies, on the correctness of the rest of the functions in The DAO that manipulate the data fields used in those functions. So the trusted computing base of the refund contract is substantially larger than the subset that Christoph has identified. To my delight, Vitalik responded with a well-thought-out proof sketch for why the DAO token creation cannot be abused to generate fake DAO tokens. I'll let him chime in below with his proof sketch, if he so chooses. That responds to my call for a proof above. I still prefer my enumerated address technique, but I feel much better about the impending hard fork. Coincidentally, a hard fork bug bounty was introduced today. And Christoph has pointed out that there is already a more general bug bounty in place. These are great developments 

Read full here