{ "cells": [ { "cell_type": "markdown", "id": "696b3c3a", "metadata": {}, "source": [ "# The Self-Conscious Statisticians" ] }, { "cell_type": "markdown", "id": "9613f11e", "metadata": {}, "source": [ "Imagine a group of statisticians who are in a weight loss program. Because they're statisticians, they want to quantify the group's progress, but because they're self-conscious, none want to reveal their weight publicly. This is a perfect use-case for Cicada and MPC:" ] }, { "cell_type": "code", "execution_count": 1, "id": "978d8658", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:Sum revealed to player 0: 904.0\n", "INFO:root:Sum revealed to player 1: 904.0\n", "INFO:root:Sum revealed to player 2: 904.0\n", "INFO:root:Sum revealed to player 3: 904.0\n", "INFO:root:Sum revealed to player 4: 904.0\n", "INFO:root:Mean weight revealed to player 0: 180.7978515625\n", "INFO:root:Mean weight revealed to player 1: 180.7978515625\n", "INFO:root:Mean weight revealed to player 2: 180.7978515625\n", "INFO:root:Mean weight revealed to player 3: 180.7978515625\n", "INFO:root:Mean weight revealed to player 4: 180.7978515625\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", " # Each player loads their weight from a file.\n", " weight = numpy.loadtxt(f\"statistician-weight-{communicator.rank}.txt\")\n", " \n", " # Compute the sum of the player weights.\n", " sum_share = protocol.share(src=0, secret=numpy.array(0), shape=())\n", " for rank in communicator.ranks:\n", " weight_share = protocol.share(src=rank, secret=weight, shape=())\n", " sum_share = protocol.add(sum_share, weight_share)\n", " \n", " # Divide by the number of players to obtain the mean weight.\n", " count_share = protocol.share(src=0, secret=numpy.array(communicator.world_size), shape=())\n", " mean_share = protocol.divide(sum_share, count_share)\n", " \n", " # Reveal the total and average weight to the group.\n", " sum = protocol.reveal(sum_share)\n", " mean = protocol.reveal(mean_share)\n", " \n", " log.info(f\"Sum revealed to player {communicator.rank}: {sum}\")\n", " log.info(f\"Mean weight revealed to player {communicator.rank}: {mean}\")\n", " \n", "SocketCommunicator.run(world_size=5, fn=main);" ] }, { "cell_type": "markdown", "id": "fe32e50b", "metadata": {}, "source": [ "If we manually inspect the players' weights, we can see that the result is correct, making allowances for the default 16-bit fixed point precision:" ] }, { "cell_type": "code", "execution_count": 2, "id": "4c04a1bb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Player 0 weight: 130.0\n", "Player 1 weight: 220.0\n", "Player 2 weight: 98.0\n", "Player 3 weight: 241.0\n", "Player 4 weight: 215.0\n", "sum: 904.0\n", "mean: 180.8\n" ] } ], "source": [ "weights = [numpy.loadtxt(f\"statistician-weight-{rank}.txt\") for rank in range(5)]\n", "\n", "for rank, weight in enumerate(weights):\n", " print(f\"Player {rank} weight: {weight}\")\n", "\n", "print(f\"sum: {numpy.sum(weights)}\")\n", "print(f\"mean: {numpy.mean(weights)}\")" ] } ], "metadata": { "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 }