.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/crypto/signatures/plot_05_musig.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_crypto_signatures_plot_05_musig.py: MuSig: Schnorr multi-signatures and the rogue-key attack (2018) =============================================================== Because Schnorr signatures are linear, n signers can produce one ordinary signature for the sum of their keys. Summing keys naively is unsafe: an attacker who announces Q_rogue = xG - Q_honest controls the sum alone. Maxwell, Poelstra, Seurin and Wuille's MuSig weights each key by a hash of the whole key list, a_i = H(L, Q_i), so no key can cancel the others. What to look for ---------------- The rogue key takes over a naive aggregate. With MuSig coefficients the attack fails, and three signers produce one 64-byte-style signature that verifies like any other. This is how Taproot multisig spends look like single-signer spends on Bitcoin. The history behind this experiment: :doc:`/history/crypto_breakthroughs`. .. GENERATED FROM PYTHON SOURCE LINES 23-25 The rogue-key attack on naive aggregation ----------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 25-40 .. code-block:: Python import matplotlib.pyplot as plt import blockchainkit as bk curve = bk.crypto.SECP256K1 honest = bk.crypto.public_key(3) attacker_secret = 1234 negated_honest = (honest[0], -honest[1] % curve.p) rogue = bk.crypto.add(bk.crypto.public_key(attacker_secret), negated_honest, curve) naive_aggregate = bk.crypto.add(honest, rogue, curve) assert naive_aggregate == bk.crypto.public_key(attacker_secret) forgery = bk.crypto.sign(b"send everything to Mallory", attacker_secret, nonce=99) assert bk.crypto.verify(b"send everything to Mallory", forgery, naive_aggregate) print("Naive aggregation: the attacker signed alone for the 'joint' key") .. rst-class:: sphx-glr-script-out .. code-block:: none Naive aggregation: the attacker signed alone for the 'joint' key .. GENERATED FROM PYTHON SOURCE LINES 41-43 MuSig coefficients stop it -------------------------- .. GENERATED FROM PYTHON SOURCE LINES 43-47 .. code-block:: Python musig_aggregate = bk.crypto.aggregate_public_keys([honest, rogue]) assert musig_aggregate != bk.crypto.public_key(attacker_secret) assert not bk.crypto.verify(b"send everything to Mallory", forgery, musig_aggregate) .. GENERATED FROM PYTHON SOURCE LINES 48-50 Three honest signers, one signature ----------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 50-58 .. code-block:: Python privates, nonces = [11, 22, 33], [101, 202, 303] publics = [bk.crypto.public_key(x) for x in privates] aggregate = bk.crypto.aggregate_public_keys(publics) signature = bk.crypto.musig_sign(b"spend from the vault", privates, nonces) assert bk.crypto.verify(b"spend from the vault", signature, aggregate) coefficients = bk.crypto.musig_coefficients(publics) print("coefficients a_i:", [hex(a)[:10] + "..." for a in coefficients]) .. rst-class:: sphx-glr-script-out .. code-block:: none coefficients a_i: ['0x99470d0a...', '0xaf556d0d...', '0xe782ef66...'] .. GENERATED FROM PYTHON SOURCE LINES 59-73 .. code-block:: Python fig, ax = plt.subplots(figsize=(7, 3.2)) results = { "naive key,\nforged sig": bk.crypto.verify( b"send everything to Mallory", forgery, naive_aggregate ), "MuSig key,\nforged sig": bk.crypto.verify( b"send everything to Mallory", forgery, musig_aggregate ), "MuSig key,\njoint sig": bk.crypto.verify(b"spend from the vault", signature, aggregate), } ax.bar(results.keys(), [int(v) for v in results.values()], color=["#dc2626", "#16a34a", "#2563eb"]) ax.set(ylabel="verifies", yticks=[0, 1], title="Key aggregation with and without MuSig") fig.tight_layout() .. image-sg:: /api/gallery/crypto/signatures/images/sphx_glr_plot_05_musig_001.png :alt: Key aggregation with and without MuSig :srcset: /api/gallery/crypto/signatures/images/sphx_glr_plot_05_musig_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 74-79 Exercise -------- musig_sign plays all signers in one process. In a real protocol, why must signers exchange commitments to their nonces R_i before revealing them? (MuSig2 later reduced this to two rounds.) .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.261 seconds) .. _sphx_glr_download_api_gallery_crypto_signatures_plot_05_musig.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/crypto/signatures/plot_05_musig.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_05_musig.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_05_musig.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_05_musig.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_