Yesterday, a hacker pulled off the second biggest heist in the history of digital currencies.
Around 12:00 PST, an unknown attacker exploited a critical flaw in the Parity multi-signature wallet on the Ethereum network, draining three massive wallets of over $31,000,000 worth of Ether in a matter of minutes. Given a couple more hours, the hacker could’ve made off with over $105,000,000 from vulnerable wallets.
But someone stopped them.
Having sounded the alarm bells, a group of benevolent white-hat hackers from the Ethereum community rapidly organized. They analyzed the attack and realized that there was no way to reverse the thefts, yet many more wallets were vulnerable. Time was of the essence, so they saw only one available option: hack the remaining wallets before the attacker did.
By exploiting the same vulnerability, the white-hats hacked all of the remaining at-risk wallets and drained their accounts, effectively preventing the attacker from reaching any of the remaining $77,000,000.
Yes, you read that right.
To prevent the hacker from robbing any more banks, the white-hats wrote software to rob all of the remaining banks in the world. Once the money was safely stolen, they began the process of returning the funds to their respective account holders. The people who had their money saved by this heroic feat are now in the process of retrieving their funds.
It’s an extraordinary story, and it has significant implications for the world of cryptocurrencies.
It’s important to understand that this exploit was not a vulnerability in Ethereum or in Parity itself. Rather, it was a vulnerability in the default smart contract code that the Parity client gives the user for deploying multi-signature wallets.
This is all pretty complicated, so to make the details of this clear for everyone, this post is broken into three parts:
What exactly happened? An explanation of Ethereum, smart contracts, and multi-signature wallets.
How did they do it? A technical explanation of the attack (specifically for programmers).
What now? The attack’s implications about the future and security of smart contracts.
If you are familiar with Ethereum and the crypto world, you can skip to the second section.
There are three building blocks to this story: Ethereum, smart contracts, and digital wallets.
Ethereum is a digital currency invented in 2013 — a full 4 years after the release of Bitcoin. It has since grown to be the second largest digital currency in the world by market cap — $20 billion, compared to Bitcoin’s $40 billion.
Like all cryptocurrencies, Etherium is a descendant of the Bitcoin protocol, and improves on Bitcoin’s design. But don’t be fooled: though it is a digital currency like Bitcoin, Ethereum is much more powerful.
While Bitcoin uses its blockchain to implement a ledger of monetary transactions, Ethereum uses its blockchain to record state transitions in a gigantic distributed computer. Ethereum’s corresponding digital currency, ether is essentially a side effect of powering this massive computer.
To put it another way, Ethereum is literally a computer that spans the entire world. Anyone who runs the Ethereum software on their computer is participating in the operations of this world-computer, the Ethereum Virtual Machine (EVM). Because the EVM was designed to be Turing-complete (ignoring gas limits), it can do almost anything that can be expressed in a computer program.
Let me be emphatic: this is crazy stuff. The crypto world is ebullient about the potential of Ethereum, which has seen its value skyrocket in the last 6 months.
The developer community has rallied behind it, and there’s a lot of excitement about what can be built on top of the EVM — and this brings us to smart contracts.
Smart contracts are simply computer programs that run on the EVM. In many ways they are like normal contracts, except they don’t need lawyers or judges to interpret them. Instead, they are compiled to bytecode and interpreted unambiguously by the EVM. With these programs, you can (among other things) programmatically transfer digital currency based solely on the rules of the contract code.
Of course, there are things normal contracts do that smart contracts can’t — smart contracts can’t easily interact with things that aren’t on the blockchain. But smart contracts can also do things that normal contracts can’t, such as enforce a set of rules entirely through unbreakable cryptography.
This leads us to the notion of wallets. In the world of digital currencies, wallets are how you store your assets. You gain access to your wallet using essentially a secret password, also known as your private key (simplified a bit).
There are many different types of wallets that confer different security properties, such as withdrawal limits. One of the most popular types is the multi-signature wallet.
In a multi-signature wallet, there are several private keys that can unlock the wallet, but just one key is not enough to unlock it. If your multi-signature wallet has 3 keys, for example, you can specify that at least 2 of the 3 keys must be provided to successfully unlock it.
This means that if you, your father, and your mother are each signatories on this wallet, even if a criminal hacked your mother and stole her private key, they could still not access your funds. This leads to much stronger security guarantees, so multi-sigs are a standard in wallet security.
This is the type of wallet the hacker attacked.
So what went wrong? Did they break the private keys? Did they use a quantum computer, or some kind of cutting-edge factoring algorithm?
Nope, all the cryptography was sound. The exploit was almost laughably simple: they found a programmer-introduced bug in the code that let them re-initialize the wallet, almost like restoring it to factory settings. Once they did that, they were free to set themselves as the new owners, and then walk out with everything.
What follows is a technical explanation of exactly what happened. If you’re not a developer, feel free to skip to the next section, since this is going to be programming-heavy.
Ethereum has a fairly unique programming model. On Ethereum, you write code by publishing contracts (which you can think of as objects), and transactions are executed by calling methods on these objects to mutate their state.
In order to run code on Ethereum, you need to first deploy the contract (the deployment is itself a transaction), which costs a small amount of Ether. You then need to call methods on the contract to interact with it, which costs more Ether. As you can imagine, this incentivizes a programmer to optimize their code, both to minimize transactions and minimize computation costs.
One way to reduce costs is to use libraries. By making your contract call out to a shared library that was deployed at a previous time, you don’t have to re-deploy any shared code. In Ethereum, keeping your code DRY will directly save you money.
The default multi-sig wallet in Parity did exactly this. It held a reference to a shared external library which contained wallet initialization logic. This shared library is referenced by the public key of the library contract.
// FIELDS
address constant _walletLibrary = 0xa657491c1e7f16adb39b9b60e87bbb8d93988bc3;
The library is called in several places, via an EVM instruction called DELEGATECALL, which does the following: for whatever method that calls DELEGATECALL, it will call the same method on the contract you're delegating to, but using the context of the current contract. It's essentially like a super call, except without the inheritance part. (The equivalent in JavaScript would be OtherClass.functionName.apply(this, args).)
Here’s an example of this in their multi-sig wallet: the isOwner method just delegates to the shared wallet library's isOwner method, using the current contract's state:
function isOwner(address _addr) constant returns (bool) {
return _walletLibrary.delegatecall(msg.data);
}
This is all innocent enough. The multi-sig wallet itself contained all of the right permission checks, and they were sure to rigorously enforce authorization on all sensitive actions related to the wallet’s state.
But they made one critical mistake.
Solidity allows you to define a “fallback method.” This is the method that gets called when there’s no method that matches a given method name. You define it by not giving it a name: