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

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

Made with Slides.com