.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/wallets/custody/plot_01_p2sh_multisig.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_wallets_custody_plot_01_p2sh_multisig.py: Pay-to-script-hash and multisig (BIP16, 2012) ============================================= One key is one point of failure. A coin locked to 2 of 3 keys survives the loss or theft of one, but before 2012 the payer had to write the whole locking script, and pay for its size. Gavin Andresen's BIP16 locks a coin to the hash of a *redeem script* instead, .. math:: \texttt{OP\_HASH256}\ \langle H(\text{redeem}) \rangle\ \texttt{OP\_EQUAL}, and the spender reveals the script and satisfies it. Validation runs in two stages: the revealed script must match the hash, and then it runs on the remaining pushes. The payer sees only a short address starting with 3. The teaching interpreter has no ``OP_CHECKMULTISIG``, so m-of-n is written as one branch per set of m signers, and its size shows why scripts are capped at 520 bytes. .. GENERATED FROM PYTHON SOURCE LINES 23-30 .. code-block:: Python from itertools import combinations from math import comb import matplotlib.pyplot as plt import blockchainkit as bk .. GENERATED FROM PYTHON SOURCE LINES 31-33 A 2-of-3 wallet --------------- .. GENERATED FROM PYTHON SOURCE LINES 33-58 .. code-block:: Python keys = [3, 5, 7] redeem = bk.wallets.multisig_redeem_script(2, [bk.crypto.public_key(k) for k in keys]) locking = bk.wallets.p2sh_locking(redeem) print("address:", bk.wallets.p2sh_address(redeem)) print( "redeem script:", len(bk.wallets.serialize_script(redeem)), "bytes; locking script:", len(bk.wallets.serialize_script(locking)), "bytes", ) message = b"spend the coin to bob" for signers in combinations(range(3), 2): signatures = {i: bk.crypto.sign(message, keys[i], nonce=40 + i) for i in signers} spend = bk.wallets.p2sh_unlocking(redeem, bk.wallets.multisig_unlocking(2, 3, signatures)) result = bk.wallets.verify_p2sh(spend, locking, message=message) print("signers", signers, "valid:", result.valid) assert result.valid # One key alone cannot spend: there is no branch with a single signature. lone = {0: bk.crypto.sign(message, 3, nonce=1), 1: bk.crypto.sign(message, 99, nonce=2)} spend = bk.wallets.p2sh_unlocking(redeem, bk.wallets.multisig_unlocking(2, 3, lone)) assert not bk.wallets.verify_p2sh(spend, locking, message=message).valid .. rst-class:: sphx-glr-script-out .. code-block:: none address: 35HqWSBJSBfhSA29kcdKiFRxj2a77AvwgA redeem script: 411 bytes; locking script: 35 bytes signers (0, 1) valid: True signers (0, 2) valid: True signers (1, 2) valid: True .. GENERATED FROM PYTHON SOURCE LINES 59-61 The cost of writing m-of-n without OP_CHECKMULTISIG --------------------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 61-82 .. code-block:: Python sizes = {} for n in range(1, 6): publics = [bk.crypto.public_key(k) for k in range(2, 2 + n)] for m in range(1, n + 1): script = bk.wallets.multisig_redeem_script(m, publics) sizes[m, n] = len(bk.wallets.serialize_script(script)) assert sizes[2, 3] <= 520 < sizes[3, 5] fig, ax = plt.subplots(figsize=(6, 4)) for n in range(1, 6): ax.plot(range(1, n + 1), [sizes[m, n] for m in range(1, n + 1)], "o-", label=f"n = {n}") ax.axhline( bk.constants.MAX_SCRIPT_ELEMENT_SIZE, color="#dc2626", linestyle="--", label="520-byte limit" ) ax.set(xlabel="threshold m", ylabel="redeem script (bytes)") ax.set_title(f"One branch per signer set: C(5, 2) = {comb(5, 2)} branches") ax.legend() fig.tight_layout() plt.show() .. image-sg:: /api/gallery/wallets/custody/images/sphx_glr_plot_01_p2sh_multisig_001.png :alt: One branch per signer set: C(5, 2) = 10 branches :srcset: /api/gallery/wallets/custody/images/sphx_glr_plot_01_p2sh_multisig_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 83-88 Exercise -------- Which (m, n) wallets fit in 520 bytes? Bitcoin's ``OP_CHECKMULTISIG`` needs about 34 bytes per compressed key: how large would 3-of-5 be? A worked solution is in :doc:`/exercises/wallets`. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.173 seconds) .. _sphx_glr_download_api_gallery_wallets_custody_plot_01_p2sh_multisig.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/wallets/custody/plot_01_p2sh_multisig.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_01_p2sh_multisig.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_01_p2sh_multisig.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_01_p2sh_multisig.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_