.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/contracts/payments/plot_03_commit_reveal.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_payments_plot_03_commit_reveal.py: Commit-reveal schemes and on-chain randomness (2016) ==================================================== Every node must compute the same result, so a contract has no secret source of randomness. Early lotteries drew from the previous block's hash, but a contract called in the same block reads the same hash, so an attacker computes the draw first and plays only when it wins. (A miner could also discard a block whose hash it disliked.) A *commit-reveal* scheme removes the shortcut. Each player first publishes :math:`c_i = H(\text{player}, s_i, r_i)` for a secret :math:`s_i` and a random salt :math:`r_i`; once every commitment is in, the players reveal, and the draw combines all secrets: .. math:: \text{winner} = \Big(\bigoplus_i s_i\Big) \bmod n. Nobody can choose a secret after seeing the others. The last player to reveal does see the result first, and may withhold; in this contract that costs nothing and raises his chance from 1/4 to 7/16. Penalties for not revealing (as in RANDAO) and verifiable delay functions aim to remove the bias. .. GENERATED FROM PYTHON SOURCE LINES 28-34 .. code-block:: Python import random import matplotlib.pyplot as plt import blockchainkit as bk .. GENERATED FROM PYTHON SOURCE LINES 35-37 Predicting the block-hash lottery --------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 37-52 .. code-block:: Python world = bk.contracts.World(seed=4) world.fund("house", 10_000) world.fund("eve", 100) lottery = world.deploy("house", bk.contracts.BlockhashLottery, 1, value=1_000) predictor = world.deploy("eve", bk.contracts.LotteryPredictor) wins = 0 for _ in range(20): world.advance() if world.balance(lottery) == 0: world.fund(lottery, 1_000) wins += bool(world.transact("eve", predictor, "attack", lottery, 1, value=1).result) print("predictor wins", wins, "of 20") assert wins == 20 .. rst-class:: sphx-glr-script-out .. code-block:: none predictor wins 20 of 20 .. GENERATED FROM PYTHON SOURCE LINES 53-55 A commit-reveal lottery ----------------------- .. GENERATED FROM PYTHON SOURCE LINES 55-76 .. code-block:: Python rng = random.Random(7) players = ["alice", "bob", "carol", "dave"] secrets = {p: (rng.randrange(2**64), rng.randrange(2**64)) for p in players} world = bk.contracts.World() for p in players: world.fund(p, 10) game = world.deploy("house", bk.contracts.CommitRevealLottery, 10, 5, 5) for p in players: world.transact(p, game, "commit", bk.contracts.commitment(p, *secrets[p]), value=10) world.advance(6) for p in players: assert world.transact(p, game, "reveal", *secrets[p]).success world.advance(5) winner = world.transact("anyone", game, "settle").result seed = 0 for secret, _ in secrets.values(): seed ^= secret assert winner == players[seed % len(players)] and world.balance(winner) == 40 print("winner", winner) .. rst-class:: sphx-glr-script-out .. code-block:: none winner bob .. GENERATED FROM PYTHON SOURCE LINES 77-83 The last revealer's option -------------------------- Dave reveals last, so he computes the draw with and without his secret. In this contract a player who withholds stays in the draw, and his stake is in the pot either way: withholding is free. Dave reveals only when that makes him win, and wins with probability 1/4 + 3/4 * 1/4 = 7/16. .. GENERATED FROM PYTHON SOURCE LINES 83-121 .. code-block:: Python def dave_wins(others, mine, strategic): revealed = others[0] ^ others[1] ^ others[2] if not strategic or (revealed ^ mine) % 4 == 3: return (revealed ^ mine) % 4 == 3 return revealed % 4 == 3 # Withheld: the draw uses the other three secrets. trials = 20_000 games = [([rng.randrange(2**64) for _ in range(3)], rng.randrange(2**64)) for _ in range(trials)] honest = sum(dave_wins(o, m, False) for o, m in games) / trials strategic = sum(dave_wins(o, m, True) for o, m in games) / trials print(f"Dave wins {honest:.3f} revealing always, {strategic:.3f} revealing only to win") assert abs(honest - 1 / 4) < 0.02 and abs(strategic - 7 / 16) < 0.02 # The contract agrees: when Dave withholds, the draw uses the three revealed secrets. world = bk.contracts.World() for p in players: world.fund(p, 10) game = world.deploy("house", bk.contracts.CommitRevealLottery, 10, 5, 5) for p in players: world.transact(p, game, "commit", bk.contracts.commitment(p, *secrets[p]), value=10) world.advance(6) for p in players[:-1]: world.transact(p, game, "reveal", *secrets[p]) world.advance(5) revealed = secrets["alice"][0] ^ secrets["bob"][0] ^ secrets["carol"][0] assert world.transact("anyone", game, "settle").result == players[revealed % 4] fig, ax = plt.subplots(figsize=(7, 4)) labels = ["fair share", "last revealer\nwithholding", "eve, block-hash\nlottery"] ax.bar(labels, [honest, strategic, wins / 20], color=["#16a34a", "#d97706", "#dc2626"]) ax.set(ylabel="win rate", ylim=(0, 1.05), title="Commit-reveal removes prediction, not all bias") fig.tight_layout() plt.show() .. image-sg:: /api/gallery/contracts/payments/images/sphx_glr_plot_03_commit_reveal_001.png :alt: Commit-reveal removes prediction, not all bias :srcset: /api/gallery/contracts/payments/images/sphx_glr_plot_03_commit_reveal_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none Dave wins 0.252 revealing always, 0.440 revealing only to win .. GENERATED FROM PYTHON SOURCE LINES 122-127 Exercise -------- Change the rules so that a player who does not reveal is excluded from the draw and loses the stake to the others. What is Dave's best strategy now, and his win rate? A worked solution is in :doc:`/exercises/contracts`. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.055 seconds) .. _sphx_glr_download_api_gallery_contracts_payments_plot_03_commit_reveal.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/payments/plot_03_commit_reveal.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_03_commit_reveal.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_03_commit_reveal.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_03_commit_reveal.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_