{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "name": "Untitled1.ipynb", "provenance": [], "authorship_tag": "ABX9TyNZw7HlF4CWWtCFJDTkZIXo", "include_colab_link": true }, "kernelspec": { "name": "python3", "display_name": "Python 3" }, "language_info": { "name": "python" } }, "cells": [ { "cell_type": "markdown", "metadata": { "id": "view-in-github", "colab_type": "text" }, "source": [ "\"Open" ] }, { "cell_type": "code", "metadata": { "id": "8Dfg3FTTIgO6" }, "source": [ "import numpy as np\n", "from numpy.random import PCG64, SeedSequence, Generator, default_rng\n", "\n", "def new_list(n: int, frac: float, rng):\n", " \"\"\"\n", " Returns a new list of floor(frac*(2**n)) random n-bit numbers.\n", " \"\"\"\n", " return list(set(rng.integers(2**n, size=int(np.floor(frac*(2**n))))))\n", "\n", "def new_fixed(n: int, rng):\n", " \"\"\"\n", " Returns a single n-bit random number.\n", " \"\"\"\n", " return [rng.integers(2**n)]\n", "\n", "def get_rngs(num: int, entropy=0x87351080e25cb0fad77a44a3be03b492):\n", " \"\"\"\n", " Creates a list of 'num' independent random number generators.\n", " 'entropy' is an initial seed that can be kept fixed to reproduce runs.\n", " \"\"\"\n", " return [Generator(PCG64(SeedSequence((entropy, i))))\n", " for i in range(num)]\n", "\n", "def pairxy(x, y, n: int, frac: float, rngs):\n", " \"\"\"\n", " Takes lists x (of length q1) and y (of length q2) of n-bit numbers.\n", " Returns three dictionaries: pairs, red, blue.\n", " pairs[i] corresponds to the i-th query, and stores the following:\n", " - pairs[i][0], pairs[i][1] stores the i-th plaintext.\n", " - pairs[i][2] stores X^i + Y^i.\n", " - pairs[i][3], pairs[i][4] stores the i-th ciphertext.\n", " red[v] stores the query indices i such that v^i = v.\n", " blue[u] stores the query indices i such that u^i = u.\n", " \"\"\"\n", " q1 = len(x)\n", " q2 = len(y)\n", " pairs = {}\n", " red = {}\n", " blue = {}\n", " domxy = set() # a table of (x, y) pairs already assigned\n", " for i in range(int(np.floor(frac*(2**n)))):\n", " xi = rngs[2].integers(q1)\n", " yi = rngs[3].integers(q2)\n", " while (xi, yi) in domxy: # check for duplicate (x, y) pairs\n", " xi = rngs[2].integers(q1)\n", " yi = rngs[3].integers(q2)\n", " domxy.add((xi, yi))\n", " ui = rngs[4].integers(2**n)\n", " vi = rngs[5].integers(2**n)\n", " if vi in red:\n", " red[vi].append(i)\n", " else:\n", " red[vi] = [i]\n", " if ui in blue:\n", " blue[ui].append(i)\n", " else:\n", " blue[ui] = [i]\n", " pairs[i] = (xi, yi, (x[xi] ^ y[yi]) % (2**n), ui, vi)\n", " return pairs, red, blue\n", "\n", "def dfs(node, pairs, red, blue, components, checked):\n", " \"\"\"\n", " Helper function of compute_graph().\n", " Recursively performs a depth-first search\n", " within a component of G_\\tau.\n", " \"\"\"\n", " if not node in checked:\n", " checked.add(node)\n", " for i in red[pairs[node][4]]:\n", " if not i in checked:\n", " components[node].add(i)\n", " components[i] = components[node]\n", " dfs(i, pairs, red, blue, components, checked)\n", " for i in blue[pairs[node][3]]:\n", " if not i in checked:\n", " components[node].add(i)\n", " components[i] = components[node]\n", " dfs(i, pairs, red, blue, components, checked)\n", "\n", "def compute_graph(pairs, red, blue):\n", " \"\"\"\n", " Computes a dictionary components which maps each query\n", " to the component (stored as a set) of G_\\tau it belongs to.\n", " \"\"\"\n", " q = len(pairs)\n", " components = {}\n", " checked = set()\n", " for i in pairs:\n", " if not i in checked:\n", " components[i] = {i}\n", " dfs(i, pairs, red, blue, components, checked)\n", " return components\n", "\n", "def list_comps(components):\n", " \"\"\"\n", " Converts the dictionary 'components' from compute_graph()\n", " into a (duplicate-free) list of components stored as lists.\n", " \"\"\"\n", " frozens = {frozenset(components[comp]) for comp in components}\n", " # We use frozenset to remove duplicates since sets can't contain sets\n", " all_comps = [list(frozen) for frozen in frozens]\n", " return all_comps\n", "\n", "def propagate_uv(node, pairs, red, blue, value, colour,\n", " n, u, v, domu, domv, visited):\n", " \"\"\"\n", " Helper function of set_uv().\n", " Recursively computes (U, V) values within a component.\n", " 'colour' is the edge colour by which this node was reached.\n", " When 'colour' is 'blue', propagating to red neighbours is\n", " enough, since blue neighbours are also blue neighbours of the\n", " previous node (from where the recursive call was received) and\n", " will be processed from there.\n", " \"\"\"\n", " visited.add(node)\n", " if colour == 'blue':\n", " u[node] = value\n", " v[node] = (value ^ pairs[node][2]) % (2**n)\n", " domv.add(v[node])\n", " for neighbour in set(red[pairs[node][4]]) - visited:\n", " propagate_uv(neighbour, pairs, red, blue, v[node],\n", " 'red', n, u, v, domu, domv, visited)\n", " elif colour == 'red':\n", " v[node] = value\n", " u[node] = (value ^ pairs[node][2]) % (2**n)\n", " domu.add(u[node])\n", " for neighbour in set(blue[pairs[node][3]]) - visited:\n", " propagate_uv(neighbour, pairs, red, blue, u[node],\n", " 'blue', n, u, v, domu, domv, visited)\n", "\n", "def set_uv(comp, pairs, red, blue, value, n, u, v, domu, domv):\n", " \"\"\"\n", " Takes a list-component 'comp' and assigns 'value' to U^i\n", " for i = comp[0], then recursively computes the rest of the\n", " (U, V) values in comp.\n", " u, v are dictionaries that map nodes to the assigned (U, V) values.\n", " domu, domv are sets that keep track of the assigned (U, V) values.\n", " \"\"\"\n", " root = comp[0]\n", " visited = {root}\n", " u[root] = value\n", " v[root] = (value ^ pairs[root][2]) % (2**n)\n", " domu.add(value)\n", " domv.add(v[root])\n", " for neighbour in set(red[pairs[root][4]]) - visited:\n", " propagate_uv(neighbour, pairs, red, blue, v[root],\n", " 'red', n, u, v, domu, domv, visited)\n", " for neighbour in set(blue[pairs[root][3]]) - visited:\n", " propagate_uv(neighbour, pairs, red, blue, value,\n", " 'blue', n, u, v, domu, domv, visited)\n", "\n", "def propagate_banned(node, pairs, red, blue, n, sum,\n", " colour, domu, domv, banned, visited):\n", " \"\"\"\n", " Helper function of banned_values().\n", " Recursively computes the banned values for a component.\n", " \"\"\"\n", " visited.add(node)\n", " sum = (sum ^ pairs[node][2]) % (2**n)\n", " if colour == 'blue':\n", " banned |= {(value ^ sum) % (2**n) for value in domv}\n", " for neighbour in set(red[pairs[node][4]]) - visited:\n", " propagate_banned(neighbour, pairs, red, blue, n, sum,\n", " 'red', domu, domv, banned, visited)\n", " if colour == 'red':\n", " banned |= {(value ^ sum) % (2**n) for value in domu}\n", " for neighbour in set(blue[pairs[node][3]]) - visited:\n", " propagate_banned(neighbour, pairs, red, blue, n, sum,\n", " 'blue', domu, domv, banned, visited)\n", "\n", "def banned_values(comp, pairs, red, blue, n, domu, domv):\n", " \"\"\"\n", " Recursively computes the banned values for a list-component 'comp'.\n", " \"\"\"\n", " root = comp[0]\n", " visited = {root}\n", " sum = pairs[root][2]\n", " banned = domu | {(value ^ sum) % (2**n) for value in domv}\n", " for neighbour in set(red[pairs[root][4]]) - visited:\n", " propagate_banned(neighbour, pairs, red, blue, n, sum, 'red',\n", " domu, domv, banned, visited)\n", " for neighbour in set(blue[pairs[root][3]]) - visited:\n", " propagate_banned(neighbour, pairs, red, blue, n, 0, 'blue',\n", " domu, domv, banned, visited)\n", " return banned\n", "\n", "def partial_rho(n, q3a, q3b, q4a, q4b, options_count):\n", " \"\"\"\n", " Helper function for find_rho().\n", " Computes the part of 'rho' contributed by a component.\n", " q3a: number of unique U values assigned so far.\n", " q3b: number of unique U values to be assigned in this component.\n", " q4a: number of unique V values assigned so far.\n", " q4b: number of unique V values to be assigned in this component.\n", " options_count: number of choices for U^i in this component.\n", " \"\"\"\n", " nn = 2**n\n", " denominator = 1\n", " for i in range(q3a, q3b):\n", " denominator *= nn - i\n", " for i in range(q4a, q4b):\n", " denominator *= nn - i\n", " numerator = nn**(q3b-q3a + q4b-q4a - 1) * options_count\n", " return (numerator/denominator)\n", "\n", "def find_rho(all_comps, pairs, red, blue, n, rng):\n", " \"\"\"\n", " Computes an estimate of rho by simulating the initial U values\n", " of each component.\n", " \"\"\" \n", " comp0 = all_comps[0]\n", " u = {}\n", " v = {}\n", " domu = set()\n", " domv = set()\n", " u0 = rng.integers(2**n)\n", " set_uv(comp0, pairs, red, blue, u0, n, u, v, domu, domv)\n", " q3b = len(domu)\n", " q4b = len(domv)\n", " rho = partial_rho(n, 0, q3b, 0, q4b, 2**n)\n", " min_options = 2**n\n", " for comp in all_comps[1:]:\n", " q3a = q3b\n", " q4a = q4b\n", " banned = banned_values(comp, pairs, red, blue, n, domu, domv)\n", " options = set(range(2**n)) - banned\n", " u_next = rng.choice(list(options))\n", " set_uv(comp, pairs, red, blue, u_next, n, u, v, domu, domv)\n", " q3b = len(domu)\n", " q4b = len(domv)\n", " rho *= partial_rho(n, q3a, q3b, q4a, q4b, len(options))\n", " if len(options) < min_options:\n", " min_options = len(options)\n", " return min_options, rho\n", "\n", "def rho_simulation(n=12, frac=0.1,\n", " entropy=0x87351080e25cb0fad77a44a3be03b492, num=100):\n", " \"\"\"\n", " Simulates 'num' runs of the whole game, estimating 'rho' each time.\n", " Also printed is 'min_options', the lowest number of options\n", " encountered while looping over the components assigning (U, V) values.\n", " \"\"\"\n", " rngs = get_rngs(7, entropy)\n", " for i in range(num):\n", " x = new_list(n, frac, rngs[0])\n", " y = new_list(n, frac, rngs[1])\n", " pairs, red, blue = pairxy(x, y, n, frac, rngs)\n", " components = compute_graph(pairs, red, blue)\n", " all_comps = list_comps(components)\n", " try:\n", " min_options, rho = find_rho(all_comps, pairs,\n", " red, blue,\n", " n, rngs[6])\n", " print(i, min_options, min_options/(2**n), rho)\n", " except RecursionError:\n", " print('Bad event at ', i)\n", "\n", "def rho_simulation_fixed_x(n=12, frac=0.1,\n", " entropy=0x87351080e25cb0fad77a44a3be03b492,\n", " num=100):\n", " \"\"\"\n", " The same as rho_simulation(), except 'x' contains a single value.\n", " This forces the X^i + Y^i to be all distinct, possibly pushing up\n", " the size of the banned set.\n", " \"\"\"\n", " rngs = get_rngs(7, entropy)\n", " for i in range(num):\n", " x = new_fixed(n, rngs[0])\n", " y = new_list(n, 1.0, rngs[1])\n", " pairs, red, blue = pairxy(x, y, n, frac, rngs)\n", " try:\n", " assert len({pairs[i][2] for i in pairs}) == len(pairs)\n", " except AssertionError:\n", " print(len({pairs[i][2] for i in pairs}), len(pairs))\n", " components = compute_graph(pairs, red, blue)\n", " all_comps = list_comps(components)\n", " try:\n", " min_options, rho = find_rho(all_comps, pairs,\n", " red, blue, n, \n", " rngs[6])\n", " print(i, min_options, min_options/(2**n), rho)\n", " except RecursionError:\n", " print('Bad event at ', i)" ], "execution_count": 3, "outputs": [] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "_7bAGFEmYckk", "outputId": "7424b07f-391e-43d0-944f-eec766499094" }, "source": [ "rho_simulation(n=12, frac=0.25, num=10)\n", "rho_simulation_fixed_x(n=12, frac=0.25, num=10)" ], "execution_count": 4, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "0 893 0.218017578125 1.0682917694444383\n", "1 947 0.231201171875 0.948372729439145\n", "2 929 0.226806640625 1.0400726777060205\n", "3 900 0.2197265625 1.0973213263480828\n", "4 1233 0.301025390625 1.2389042357830078\n", "5 1133 0.276611328125 1.0610018623915503\n", "6 967 0.236083984375 0.9461501682073044\n", "7 1148 0.2802734375 0.963113000589771\n", "8 959 0.234130859375 0.989419977617941\n", "9 914 0.22314453125 0.9087123794140947\n", "0 919 0.224365234375 1.0730171975503486\n", "1 913 0.222900390625 0.9379629230928876\n", "2 908 0.2216796875 0.9788370762003549\n", "3 888 0.216796875 1.0583774470157234\n", "4 1252 0.3056640625 1.062210605645845\n", "5 1200 0.29296875 1.03535738760001\n", "6 969 0.236572265625 1.0657480904363512\n", "7 1132 0.2763671875 0.981670134969622\n", "8 944 0.23046875 0.9160442814645885\n", "9 873 0.213134765625 0.8827192341701107\n" ] } ] } ] }