How did $150m get frozen?
Parity Multi-Sig Exploit Case Study
@tomusdrw
Tomasz Drwięga
24 Nov 2017, Katowice
Agenda
- What happened?
- What is Ethereum?
- How did it happen?
- Will it happen again?
- How can you protect yourself?
Why am I doing this presentation?
- Clarifying all details of the exploit
- Preventing similar issues from happening in other projects in future
- Discussing possible solutions
- Starting discussion about crypto-responsibility in general
I work for Parity Technologies,
but:
- not representing the company
- sharing private opinions
- not trying to defend Parity
I do feel equally responsible, even though I don't work on contracts development.
What does Parity Technologies do?
Core & Protocol
development
in: Rust
parity (ethereum)
parity-bitcoin
parity-polkadot
UI & dapp dev tools + services
in: JavaScript
Contract development
in: Solidity
parity-ipfs
Parity Wallet
@parity/oo7
PICOPS
Registries & Certifiers
secret-store
parity-bridge
parity-whisper
Multi-Sig
What actually happened?
- $150 million dollars worth of Ether (cryptocurrency) stored in MultiSig contracts is locked
- It is not stolen, just provably unaccessible by anyone
How did it happen then?
What is Ethereum?
Decentralized, blockchain-based platform for programmable money.
User A
User B
Priv A
Pub B
Priv B
5 BTC
Simple transfer from one account to another.
To send funds further B needs to "unlock" them with hers Private Key
What is Ethereum?
Decentralized, blockchain-based platform for programmable money.
User A
Contract
Priv A
Pub C
1. A deposits 5 ETH
in the contract
"Anyone can deposit, but only B can withdraw after block #N.
1% Tax is sent to D upon withdrawal."
User B
Priv B
3. Contract sends 4.95 ETH to B
3. Contract sends 0.05 ETH to D
2. B calls function 'withdraw()'
Contracts in Solidity
contract TaxCollector {
address taxAddress;
address withdrawAddress;
// Deploy(create) & initialize the contract
function TaxCollector(address _withdraw, address _tax) public {
withdrawAddress = _withdraw;
taxAddress = _tax;
}
// Fallback function
// (called when 'function name' not provided)
function () public payable {
// no need to do anything, just accept the transfer
}
function withdraw() public {
// only callable by 'B'
require(msg.sender == withdrawAddress);
// Calculate tax
var tax = this.balance / 100;
// Perform the transfers
taxAddress.transfer(tax);
withdrawAddress.transfer(this.balance);
}
}
Multi-Sig Wallet contract
User A
Priv A
MultiSig Contract
Pub C
"A signature of 3 out of 4 is required to spend funds. Owners: [A, B, X, Y]"
"At least 3 of
[A, B, X, Y] required"
5 ETH
5 ETH
A deploys the Contract
Multi-Sig Wallet contract
User A
Priv A
MultiSig Contract
Pub C
A, B & X authorize the withdrawal.
User B
Priv B
User X
Priv X
1. "Send 3 ETH to D"
2. "Send 3 ETH to D"
3. "Send 3 ETH to D"
"Contracts sends 3 ETH to D"
Multi-Sig Wallet contract
- Deploying and interacting contracts costs gas
- Deploying is especially expensive since it occupies a lot of space (potentially forever) in the Ethereum State
- EVM allows unused contracts to SUICIDE (Self-destruct) to free-up the space and get a refund
Timeline
Jan, 2014
Ethereum Whitepaper
Jul, 2015
Jan-Jun, 2015
Frontier Launched
Ethereum Audits
Dec, 2015
Parity (EthCore) Founded
Mar, 2016
Parity 1.0 release after security audit
(& homestead HF)
Jun, 2016
DAO Hack
(& HF)
Dec, 2016
Multi-Sig Librarification
Jul, 2017
Multi-Sig
Hack
Nov, 2017
Multi-Sig
Freeze
(the scale not accurate)
Librarification process
Everyone who wants to use the MultSig needs to pay high fee for deploying the Contract
(~2500k GAS)
IDEA: Let's deploy a Library with code and wallets will just refer to that library.
(~500k GAS)
It's also saves blockchain storage space
(good for the entire ecosystem)
Librarification process
Original MultiSig Contract
(audited)
MultiSig Contract
Library
MultiSig Shell
Shell contract just delegates calls to the library
Similar pattern described at https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd
Librarification process
// Not an actuall code,
// just to give you the idea.
contract Wallet {
function Wallet(
address[] _owners,
uint _required,
uint _daylimit
) {
_callInitWalletFromLibrary(
_owners,
_required,
_dayLimit
);
}
function() payable {
_delegateCallToLibrary(msg.data);
}
}
WalletStub.sol
contract WalletLibrary {
function WalletLibrary() {
address[] owners; owners.push(address(0x0));
init_wallet(owners, 1, 0);
}
function init_wallet(
address[] _owners, uint _required, uint _daylimit
) only_uninitialized public
{
/* Skipped */
}
// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data))
external
{
suicide(_to);
}
function confirm(uint _value, bytes _code) internal
returns (address o_addr)
{
// confirms some operations
}
}
WalletLibrary.sol
Librarification process
- The change (even though it seemed simple) was not properly audited internally.
- Librarification was done as part of a larger JavaScript Pull Request and not reviewed like other critical code (multiple independent reviews).
July multi-sig hack
- Contract "constructor" (init_wallet function) could be called multiple times by anyone (!) because the function was public by default.
- So anyone could become the only owner of the multisig.
- Malicious actor stole $30m from couple wallets.
- White Hat Group (WHG) managed to save other wallets.
Actions taken after July
- Parity Bug Bounty programme
- Multi-Sig code reviewed (internally and by the community), documented and updated in the repo
- Solidity changes require Core Devs and internal Solidity Experts reviews
- Internal dicussions wrt Security Process in Parity
Wallet Freeze in November
- User became an owner of WalletLibrary
- The new owner self-destructed the Library
- All Wallet Stubs now refer to non-existent code
Possible Solutions
- Restoring access to the funds not possible without a Hard Fork
- EIP156 - "Reclaiming of ether in common classes of stuck accounts"
- Re-deploying the Library (resurrecting the contract)
What is a Hard Fork?
- Protocol change to allow irregular state transition
- Regular update procedure (introducing new features)
- Requires software update by miners, services and users
Hard Fork Controversies
- Not always beneficial for all actors
e.g. Issuance Reduction - Ideological issues: Immutability
e.g. DAO Hard Fork - "Code is law unless it's unfair"
- Manual mode of consensus auto-pilot
Could it have been prevented?
Obviously
- The issue was caused by uncareful deployment.
- Deployed code didn't contain all review suggestions and was different than the one that finally got into the repo.
Actions taken
- Solidity Linter
- Contract Testing Framework (Rust)
- Contract Deployment Tooling
- Formal Verification Research
- Dedicated QA Team
- Deployment/Release Process Improvements
- Parity Bug Bounty More Visible
- External Audits Scheduled for January
- Security Grants
Responsibility
- "[Authors] provide the program "as is" without warranty of any kind"
(basically any Open Source license) - Do we need regulations? Do we WANT regulations?
- Is external audit enough? Who would you trust?
- Who is responsible in a decentralized world?
Thank You!
Tomasz Drwięga
@tomusdrw
Parity Technologies
How did $150m get frozen?
By Tomasz Drwięga
How did $150m get frozen?
- 613