.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/contracts/wallets/plot_01_parity_multisig_hack.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code or to run this example in your browser via JupyterLite. .. rst-class:: sphx-glr-example-title .. _sphx_glr_api_gallery_contracts_wallets_plot_01_parity_multisig_hack.py: The Parity multisig hack: an unprotected initializer (July 2017) ================================================================ Parity's multisig wallet kept its logic in a shared library. Each wallet was a small stub holding the ether and the storage, which forwarded every unknown call to the library with ``DELEGATECALL``: the library's code ran on the wallet's storage, as the wallet. The library's ``initWallet``, which writes the list of owners and how many must approve, had no guard against a second call, and the stub forwarded it like any other function. On 19 July 2017 an attacker called it on three wallets, became the sole owner of each, and withdrew 153,037 ether. A wallet's security reduced to .. math:: \text{owners} := \text{whoever called } \texttt{initWallet} \text{ last}. Here each wallet is 2-of-2: a payment needs Schnorr signatures from both owners over the wallet, its nonce and the payment. The attacker needs none of them: re-initializing makes its own key the only owner, with a threshold of one. .. GENERATED FROM PYTHON SOURCE LINES 27-32 .. code-block:: Python import matplotlib.pyplot as plt import blockchainkit as bk from blockchainkit.contracts.visualizers import plot_call_tree .. GENERATED FROM PYTHON SOURCE LINES 33-35 A 2-of-2 wallet behind a shared library --------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 35-50 .. code-block:: Python ALICE, BOB, ATTACKER = 11, 13, 99 # Fixed teaching keys. owners = [bk.crypto.public_key(ALICE), bk.crypto.public_key(BOB)] world = bk.contracts.World() library = world.deploy("parity", bk.contracts.WalletLibrary, name="library") wallet = world.deploy("alice", bk.contracts.Wallet, library, owners, 2, name="wallet") world.fund("alice", 1_000) world.transact("alice", wallet, value=1_000) one = [bk.contracts.approve_action(ALICE, wallet, 0, "execute", "carol", 10)] assert world.transact("alice", wallet, "execute", "carol", 10, one).error # Two needed. both = [bk.contracts.approve_action(k, wallet, 0, "execute", "carol", 10) for k in (ALICE, BOB)] assert world.transact("alice", wallet, "execute", "carol", 10, both).success .. GENERATED FROM PYTHON SOURCE LINES 51-53 The attack: two transactions ---------------------------- .. GENERATED FROM PYTHON SOURCE LINES 53-72 .. code-block:: Python takeover = world.transact("attacker", wallet, "init_wallet", [bk.crypto.public_key(ATTACKER)], 1) nonce = world.view(wallet, "nonce") balance = world.balance(wallet) approval = [bk.contracts.approve_action(ATTACKER, wallet, nonce, "execute", "attacker", balance)] drain = world.transact("attacker", wallet, "execute", "attacker", balance, approval) print("takeover", takeover.success, "drain", drain.success, "stolen", world.balance("attacker")) assert takeover.success and drain.success and world.balance("attacker") == 990 assert world.balance(wallet) == 0 fig, (top, bottom) = plt.subplots(2, 1, figsize=(9, 3.6)) plot_call_tree(takeover, names=world.name, ax=top) top.set_title("1. init_wallet, forwarded to the library: " + top.get_title()) plot_call_tree(drain, names=world.name, ax=bottom) bottom.set_title("2. execute, signed by the new 'owner': " + bottom.get_title()) fig.tight_layout() plt.show() .. image-sg:: /api/gallery/contracts/wallets/images/sphx_glr_plot_01_parity_multisig_hack_001.png :alt: 1. init_wallet, forwarded to the library: Call tree: 2 calls, 32,500 gas, succeeded, 2. execute, signed by the new 'owner': Call tree: 3 calls, 30,600 gas, succeeded :srcset: /api/gallery/contracts/wallets/images/sphx_glr_plot_01_parity_multisig_hack_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none takeover True drain True stolen 990 .. GENERATED FROM PYTHON SOURCE LINES 73-78 Exercise -------- Deploy the wallet on ``PatchedWalletLibrary`` instead and repeat the attack. Which call fails, and with what error? Is every wallet now safe? (The next example answers the second question.) .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.117 seconds) .. _sphx_glr_download_api_gallery_contracts_wallets_plot_01_parity_multisig_hack.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: lite-badge .. image:: images/jupyterlite_badge_logo.svg :target: ../../../../lite/lab/index.html?path=api/gallery/contracts/wallets/plot_01_parity_multisig_hack.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_01_parity_multisig_hack.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_01_parity_multisig_hack.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_01_parity_multisig_hack.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_