{ "cells": [ { "cell_type": "markdown", "id": "696b3c3a", "metadata": {}, "source": [ "# Random Seeds\n", "\n", "Random values are used widely in Secure Multiparty Computation to mask secret information, and Cicada is no exception. For example, the shares of an additively shared secret are random numbers carefully chosen so that they sum to the secret value. Thus, it should come as no surprise that random seeds play a critical role in the tradeoff between privacy and repeatability for debugging." ] }, { "cell_type": "raw", "id": "fda958b1", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "For example, when you create an instance of :class:`~cicada.additive.AdditiveProtocolSuite`, the seed used to generate secret shares defaults to a random value, and is guaranteed to be different even on forked processes. This ensures that, by default, every player's shares are unique, every time a Cicada program is run. In the following example, we run the same function twice in a row, but note that every player has different shares for each run, even though the secret shared value `42` is the same." ] }, { "cell_type": "code", "execution_count": 1, "id": "08adff83", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:********************************************************************************\n", "INFO:root:Player 0 share: cicada.additive.AdditiveArrayShare(storage=16755233681110755624)\n", "INFO:root:Player 1 share: cicada.additive.AdditiveArrayShare(storage=8296785773883102793)\n", "INFO:root:Player 2 share: cicada.additive.AdditiveArrayShare(storage=11841468692427997209)\n", "INFO:root:********************************************************************************\n", "INFO:root:Player 0 share: cicada.additive.AdditiveArrayShare(storage=12750284787561461432)\n", "INFO:root:Player 1 share: cicada.additive.AdditiveArrayShare(storage=2621599907541443234)\n", "INFO:root:Player 2 share: cicada.additive.AdditiveArrayShare(storage=3074859378609399403)\n" ] } ], "source": [ "import logging\n", "\n", "import numpy\n", "\n", "from cicada.additive import AdditiveProtocolSuite\n", "from cicada.communicator import SocketCommunicator\n", "from cicada.logger import Logger\n", "\n", "logging.basicConfig(level=logging.INFO)\n", "\n", "def main(communicator):\n", " log = Logger(logging.getLogger(), communicator)\n", " protocol = AdditiveProtocolSuite(communicator)\n", " \n", " log.info(\"*\" * 80, src=0)\n", " value = numpy.array(42) if communicator.rank == 0 else None\n", " value_share = protocol.share(src=0, secret=value, shape=())\n", " log.info(f\"Player {communicator.rank} share: {value_share}\")\n", "\n", "SocketCommunicator.run(world_size=3, fn=main);\n", "SocketCommunicator.run(world_size=3, fn=main);" ] }, { "cell_type": "raw", "id": "82db4624", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "This is ideal for privacy, but can make debugging programs very difficult, especially when contributing to Cicada itself. However, we can specify an explicit random seed when we create the :class:`~cicada.additive.AdditiveProtocolSuite` object:" ] }, { "cell_type": "code", "execution_count": 2, "id": "2e3e2427", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:********************************************************************************\n", "INFO:root:Player 0 share: cicada.additive.AdditiveArrayShare(storage=15267930134754468249)\n", "INFO:root:Player 1 share: cicada.additive.AdditiveArrayShare(storage=7969444847926663136)\n", "INFO:root:Player 2 share: cicada.additive.AdditiveArrayShare(storage=13656113164740724241)\n", "INFO:root:********************************************************************************\n", "INFO:root:Player 0 share: cicada.additive.AdditiveArrayShare(storage=15267930134754468249)\n", "INFO:root:Player 1 share: cicada.additive.AdditiveArrayShare(storage=7969444847926663136)\n", "INFO:root:Player 2 share: cicada.additive.AdditiveArrayShare(storage=13656113164740724241)\n" ] } ], "source": [ "def main(communicator):\n", " log = Logger(logging.getLogger(), communicator)\n", " protocol = AdditiveProtocolSuite(communicator, seed=123)\n", " \n", " log.info(\"*\" * 80, src=0)\n", " value = numpy.array(42) if communicator.rank == 0 else None\n", " value_share = protocol.share(src=0, secret=value, shape=())\n", " log.info(f\"Player {communicator.rank} share: {value_share}\")\n", "\n", "SocketCommunicator.run(world_size=3, fn=main);\n", "SocketCommunicator.run(world_size=3, fn=main);" ] }, { "cell_type": "markdown", "id": "5697d11b", "metadata": {}, "source": [ "Now, each player still produces unique shares, but the shares are repeated on subsequent runs, which is extremely helpful for debugging.\n", "\n", "However, for the toughest problems, even this may not be enough - if you are verifying your code by hand, the large random field values used as shares are still a distraction. In this case, you can further modify the random behavior of the protocol by specifying an explicit `seed_offset` during construction:" ] }, { "cell_type": "code", "execution_count": 3, "id": "2b4a18e5", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:********************************************************************************\n", "INFO:root:Player 0 share: cicada.additive.AdditiveArrayShare(storage=131072)\n", "INFO:root:Player 1 share: cicada.additive.AdditiveArrayShare(storage=0)\n", "INFO:root:Player 2 share: cicada.additive.AdditiveArrayShare(storage=0)\n", "INFO:root:********************************************************************************\n", "INFO:root:Player 0 share: cicada.additive.AdditiveArrayShare(storage=131072)\n", "INFO:root:Player 1 share: cicada.additive.AdditiveArrayShare(storage=0)\n", "INFO:root:Player 2 share: cicada.additive.AdditiveArrayShare(storage=0)\n" ] } ], "source": [ "def main(communicator):\n", " log = Logger(logging.getLogger(), communicator)\n", " protocol = AdditiveProtocolSuite(communicator, seed=123, seed_offset=0)\n", " \n", " log.info(\"*\" * 80, src=0)\n", " value = numpy.array(2) if communicator.rank == 0 else None\n", " value_share = protocol.share(src=0, secret=value, shape=())\n", " log.info(f\"Player {communicator.rank} share: {value_share}\")\n", "\n", "SocketCommunicator.run(world_size=3, fn=main);\n", "SocketCommunicator.run(world_size=3, fn=main);" ] }, { "cell_type": "markdown", "id": "a1e5a543", "metadata": {}, "source": [ "... now, every player's share of a secret value is zero, except for the player supplying the secret, where the share is the secret itself; this greatly simplifies debugging. Of course, all privacy has been eliminated, so you should be careful not do this in production!" ] } ], "metadata": { "celltoolbar": "Raw Cell Format", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.2" } }, "nbformat": 4, "nbformat_minor": 5 }