{ "cells": [ { "cell_type": "markdown", "id": "bafc2f4e-05a3-4120-bcd6-5d1f5fb91cd9", "metadata": {}, "source": [ "# Simulation of countermeasures\n", "\n", "This notebook contains simulations of scalar randomization countermeasures acting under the five tests:\n", " - [The 3n test](#3n-test)\n", " - [The $n + \\epsilon$ test](#Mask-recovery-(n+ϵ-test)) including mask recovery\n", " - [The $k = 10$ test](#k=10-test)\n", " - [The composite test](#Composite-test)\n", " - [The EPA test](#EPA-test)" ] }, { "cell_type": "code", "execution_count": 8, "id": "df261585-28d8-4df6-8b76-0d4bf1930608", "metadata": {}, "outputs": [], "source": [ "import io\n", "import math\n", "import random\n", "import itertools\n", "import warnings\n", "\n", "import cypari2\n", "from cysignals.alarm import alarm, AlarmInterrupt\n", "\n", "from matplotlib import pyplot as plt\n", "from collections import Counter\n", "from tqdm.auto import tqdm, trange\n", "\n", "from pyecsca.misc.cfg import TemporaryConfig\n", "from pyecsca.misc.utils import TaskExecutor\n", "from pyecsca.ec.mod import mod, RandomModAction\n", "from pyecsca.ec.point import Point\n", "from pyecsca.ec.model import ShortWeierstrassModel\n", "from pyecsca.ec.params import load_params_ectester, get_params\n", "from pyecsca.ec.mult import LTRMultiplier, RTLMultiplier, ScalarMultiplicationAction\n", "from pyecsca.ec.context import local, DefaultContext\n", "from pyecsca.ec.countermeasures import GroupScalarRandomization, AdditiveSplitting, MultiplicativeSplitting, EuclideanSplitting, BrumleyTuveri\n", "\n", "%matplotlib ipympl" ] }, { "cell_type": "markdown", "id": "4b408b91-04dd-40a4-b4f2-1777111f1c80", "metadata": {}, "source": [ "Let's first initialize some useful objects. We will be working with projective coordinates and the `add-2007-bl` and `dbl-2007-bl` formulas, though any formulas would work. However, one obtains different results if working with complete formulas." ] }, { "cell_type": "code", "execution_count": 9, "id": "b0afb195-8390-44c5-931e-75a70ccd4e9e", "metadata": {}, "outputs": [], "source": [ "model = ShortWeierstrassModel()\n", "coords = model.coordinates[\"projective\"]" ] }, { "cell_type": "code", "execution_count": 10, "id": "7e762e13-95dd-4b57-9f49-57dc0cb115ec", "metadata": {}, "outputs": [], "source": [ "add = coords.formulas[\"add-2007-bl\"]\n", "dbl = coords.formulas[\"dbl-2007-bl\"]\n", "ltr = LTRMultiplier(add, dbl, complete=False)\n", "rtl = RTLMultiplier(add, dbl, complete=False)\n", "mult = ltr" ] }, { "cell_type": "code", "execution_count": 11, "id": "1c6f853d-df28-49f8-9ae3-3cf9e21fc219", "metadata": {}, "outputs": [], "source": [ "gsr = GroupScalarRandomization(mult)\n", "gsr160 = GroupScalarRandomization(mult, 160)\n", "asplit = AdditiveSplitting(mult)\n", "msplit = MultiplicativeSplitting(mult)\n", "esplit = EuclideanSplitting(mult)\n", "bt = BrumleyTuveri(mult)" ] }, { "cell_type": "markdown", "id": "27626337-dcbc-497c-a54e-02d50e2b8f34", "metadata": {}, "source": [ "## 3n test\n", "\n", "In the 3n test the target is given domain parameters of order $3n$ but claimed order $n$. The target is then given a point of order $3n$ for scalar multiplication and the results are observed." ] }, { "cell_type": "code", "execution_count": 5, "id": "c3088419-161b-4193-a1b6-6f623f217fcd", "metadata": {}, "outputs": [], "source": [ "key3n = 0x20959f2b437de1e522baf6d814911938157390d3ea5118660b852ab0d5387006\n", "params3n = load_params_ectester(io.BytesIO(b\"0xc381bb0394f34b5ed061c9107b66974f4d0a8ec89b9fe73b98b6d1368c7d974d,0x5ca6c5ee0a10097af291a8f125303fb1a3e35e8100411902245d691e0e5cb497,0x385a5a8bb8af94721f6fd10b562606d9b9df931f7fd966e96859bb9bd7c05836,0x4616af1898b92cac0f902a9daee24bbae63571cead270467c6a7886ced421f5e,0x34e896bdb1337e0ae5960fa3389fb59c2c8d6c7dbfd9aac33a844f8f98e433ef,0x412b3e5686fbc3ca4575edb0292232702ae721a7d4a230cc170a5561aa70e00f,0x01\"), \"projective\")\n", "bits3n = params3n.full_order.bit_length()\n", "point3n = Point(X=mod(0x4a48addb2e471767b7cd0f6f1d4c27fe46f4a828fc20f950bd1f72c939b36a84, params3n.curve.prime),\n", " Y=mod(0x13384d38c353f862832c0f067e46a3e510bb6803c20745dfb31929f4a18d890d, params3n.curve.prime),\n", " Z=mod(1, params3n.curve.prime), model=coords)" ] }, { "cell_type": "code", "execution_count": 6, "id": "a8dde7e6-cd48-4f99-9677-23a19e4c2e5b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "prime:\t0xc381bb0394f34b5ed061c9107b66974f4d0a8ec89b9fe73b98b6d1368c7d974d\n", "a:\t0x5ca6c5ee0a10097af291a8f125303fb1a3e35e8100411902245d691e0e5cb497\n", "b:\t0x385a5a8bb8af94721f6fd10b562606d9b9df931f7fd966e96859bb9bd7c05836\n", "G:\t[0x4616af1898b92cac0f902a9daee24bbae63571cead270467c6a7886ced421f5e,\n", "\t 0x34e896bdb1337e0ae5960fa3389fb59c2c8d6c7dbfd9aac33a844f8f98e433ef]\n", "n:\t0x412b3e5686fbc3ca4575edb0292232702ae721a7d4a230cc170a5561aa70e00f\n", "3n:\t0xc381bb0394f34b5ed061c9107b66975080b564f77de69264451f0024ff52a02d\n", "\n", "P:\t[0x4a48addb2e471767b7cd0f6f1d4c27fe46f4a828fc20f950bd1f72c939b36a84,\n", "\t 0x13384d38c353f862832c0f067e46a3e510bb6803c20745dfb31929f4a18d890d]\n" ] } ], "source": [ "print(f\"prime:\\t0x{params3n.curve.prime:x}\")\n", "print(f\"a:\\t0x{params3n.curve.parameters['a']:x}\")\n", "print(f\"b:\\t0x{params3n.curve.parameters['b']:x}\")\n", "print(f\"G:\\t[0x{params3n.generator.X:x},\\n\\t 0x{params3n.generator.Y:x}]\")\n", "print(f\"n:\\t0x{params3n.order:x}\")\n", "print(f\"3n:\\t0x{3 * params3n.order:x}\")\n", "print(f\"\\nP:\\t[0x{point3n.X:x},\\n\\t 0x{point3n.Y:x}]\")" ] }, { "cell_type": "code", "execution_count": 7, "id": "cd6f8500-7509-45b0-8b23-471ee5014f42", "metadata": {}, "outputs": [], "source": [ "def generate_scalars_mod3(rem, samples):\n", " scalars = []\n", " while True:\n", " scalar = random.randint(0, params3n.full_order)\n", " if scalar % 3 == rem:\n", " scalars.append(scalar)\n", " if len(scalars) == samples:\n", " break\n", " return scalars\n", "\n", "def test_3n(countermeasure, scalars):\n", " ctr = Counter()\n", " for k in tqdm(scalars, leave=False):\n", " mult.init(params3n, point3n)\n", " kP = mult.multiply(k).to_affine()\n", " mult.init(params3n, point3n)\n", " knP = mult.multiply(k + params3n.full_order).to_affine()\n", " mult.init(params3n, point3n)\n", " k2nP = mult.multiply(k + 2 * params3n.full_order).to_affine()\n", "\n", " countermeasure.init(params3n, point3n)\n", " res = countermeasure.multiply(k)\n", " aff = res.to_affine()\n", " if aff.equals(kP):\n", " ctr[\"k\"] += 1\n", " elif aff.equals(knP):\n", " ctr[\"k + 1n\"] += 1\n", " elif aff.equals(k2nP):\n", " ctr[\"k + 2n\"] += 1\n", " else:\n", " ctr[aff] += 1\n", " for name, count in sorted(ctr.items()):\n", " print(f\"{name}:\\t{count}\")\n", "\n", "def test_3n_fixed_scalar(countermeasure, samples):\n", " test_3n(countermeasure, [key3n for _ in range(samples)])\n", "\n", "def test_3n_random_scalar(countermeasure, samples):\n", " test_3n(countermeasure, [random.randint(0, params3n.full_order) for _ in range(samples)])\n", "\n", "def test_3n_random_scalar_projected(countermeasure, samples):\n", " print(\"k = 0 mod 3\")\n", " test_3n(countermeasure, generate_scalars_mod3(0, samples))\n", " print()\n", " print(\"k = 1 mod 3\")\n", " test_3n(countermeasure, generate_scalars_mod3(1, samples))\n", " print()\n", " print(\"k = 2 mod 3\")\n", " test_3n(countermeasure, generate_scalars_mod3(2, samples))" ] }, { "cell_type": "markdown", "id": "46b8f74a-433d-48c9-b5b9-6bb7d2731246", "metadata": {}, "source": [ "### Fixed scalar experiments" ] }, { "cell_type": "markdown", "id": "fc82d4b9-91cd-423c-83aa-89721efa1ae9", "metadata": {}, "source": [ "#### Group scalar randomization" ] }, { "cell_type": "code", "execution_count": 8, "id": "86532d50-2db7-4370-b449-c545b330a852", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "7c11b83ca4fa40eaac982481494bd7ce", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t354\n", "k + 1n:\t318\n", "k + 2n:\t328\n" ] } ], "source": [ "test_3n_fixed_scalar(gsr, 1000)" ] }, { "cell_type": "markdown", "id": "aba3e713-6246-435d-93af-2e8b42ee9582", "metadata": {}, "source": [ "#### Additive splitting" ] }, { "cell_type": "code", "execution_count": 9, "id": "ad421630-606f-4666-9bbf-1a446eec1b59", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "4ac4f4baa0864f4ab34d422e13507754", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t508\n", "k + 1n:\t492\n" ] } ], "source": [ "test_3n_fixed_scalar(asplit, 1000)" ] }, { "cell_type": "markdown", "id": "a1284a11-4ace-437d-9826-2035cce36756", "metadata": {}, "source": [ "#### Multiplicative splitting" ] }, { "cell_type": "code", "execution_count": 10, "id": "3ed5d7f3-0ba1-4b62-9635-aeb492499175", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "4193d9eadc644cad957e15d9c9ca98a7", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t192\n", "k + 1n:\t575\n", "k + 2n:\t233\n" ] } ], "source": [ "test_3n_fixed_scalar(msplit, 1000)" ] }, { "cell_type": "markdown", "id": "5e40f8e9-d26f-41e7-adbf-a0fbdb680677", "metadata": {}, "source": [ "#### Euclidean splitting" ] }, { "cell_type": "code", "execution_count": 11, "id": "314447c6-a1fb-4d3a-8988-b34c8912dd5e", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "8fce34bb47b44e17a2eaf9b20041e5db", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t1000\n" ] } ], "source": [ "test_3n_fixed_scalar(esplit, 1000)" ] }, { "cell_type": "markdown", "id": "ff7185a2-3cd9-44d7-b1a9-982eaed561dc", "metadata": {}, "source": [ "#### Brumley and Tuveri bit-length fixing" ] }, { "cell_type": "code", "execution_count": 12, "id": "f41dfc1d-1017-4aa0-bcd4-6569c53bf81e", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "f6bb27fe24f7463e85b1b37848f5444d", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k + 2n:\t1000\n" ] } ], "source": [ "test_3n_fixed_scalar(bt, 1000)" ] }, { "cell_type": "markdown", "id": "28915553-b5e6-4108-bc23-e5844b6a63b8", "metadata": {}, "source": [ "### Random scalar experiments" ] }, { "cell_type": "markdown", "id": "566ddd10-2d0e-4b32-9b27-60770ab68155", "metadata": {}, "source": [ "#### Group scalar randomization" ] }, { "cell_type": "code", "execution_count": 13, "id": "7255321a-6ad6-4938-8ec9-dd8d977686db", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "0c52e094bfd84c70874bd1444cec7c18", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t331\n", "k + 1n:\t339\n", "k + 2n:\t330\n" ] } ], "source": [ "test_3n_random_scalar(gsr, 1000)" ] }, { "cell_type": "markdown", "id": "376c0da8-b92d-4151-ac30-839ab5c0ceae", "metadata": {}, "source": [ "#### Additive splitting" ] }, { "cell_type": "code", "execution_count": 14, "id": "b0146a9a-0803-43c4-ab29-8ba6e15934b5", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "c98aabf325d14c85844ce03540166769", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t499\n", "k + 1n:\t501\n" ] } ], "source": [ "test_3n_random_scalar(asplit, 1000)" ] }, { "cell_type": "markdown", "id": "96823352-cd63-4cf8-8ab7-cf6e025837b9", "metadata": {}, "source": [ "#### Multiplicative splitting" ] }, { "cell_type": "code", "execution_count": 15, "id": "5645ae6f-f5f4-419d-ba47-248532dc2114", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "ad009d473f5345f89f72f5d892886672", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t313\n", "k + 1n:\t352\n", "k + 2n:\t335\n" ] } ], "source": [ "test_3n_random_scalar(msplit, 1000)" ] }, { "cell_type": "markdown", "id": "1784d763-932a-4f66-a1da-d7ef51cbc88a", "metadata": {}, "source": [ "#### Euclidean splitting" ] }, { "cell_type": "code", "execution_count": 16, "id": "c9fc4f35-1c25-4cac-bb63-8bd70263db47", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "59c26f78ce4d48e6b0f48c56cb2a1957", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t1000\n" ] } ], "source": [ "test_3n_random_scalar(esplit, 1000)" ] }, { "cell_type": "markdown", "id": "8917dfd5-1548-414b-846a-685857bfb427", "metadata": {}, "source": [ "#### Brumley and Tuveri bit-length fixing" ] }, { "cell_type": "code", "execution_count": 17, "id": "4fd6b288-08a9-4dbe-9145-e96401805315", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "0a9b4d4abf5544658185577fb6a6d7c8", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k + 1n:\t28\n", "k + 2n:\t972\n" ] } ], "source": [ "test_3n_random_scalar(bt, 1000)" ] }, { "cell_type": "markdown", "id": "ee9e23f6-edd9-4fc2-9e7c-b5c176991071", "metadata": {}, "source": [ "### Random scalar experiments projected to scalar divisor classes mod 3" ] }, { "cell_type": "markdown", "id": "e17fcb12-1e02-4bcf-a2b7-785c16c03028", "metadata": {}, "source": [ "#### Group scalar randomization" ] }, { "cell_type": "code", "execution_count": 18, "id": "6c46fdbb-2ffb-4169-8e00-6d93b8407ee5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "k = 0 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d99eea3415564fac926c237689f65b3c", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t312\n", "k + 1n:\t357\n", "k + 2n:\t331\n", "\n", "k = 1 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "9d75e1bf8df240bf9c19d3885d253186", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t291\n", "k + 1n:\t358\n", "k + 2n:\t351\n", "\n", "k = 2 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d43f5b16d2d444cda559bf18976a2e2a", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t342\n", "k + 1n:\t338\n", "k + 2n:\t320\n" ] } ], "source": [ "test_3n_random_scalar_projected(gsr, 1000)" ] }, { "cell_type": "markdown", "id": "b9d635c8-f788-4700-876b-ac48e89557a7", "metadata": {}, "source": [ "#### Additive splitting" ] }, { "cell_type": "code", "execution_count": 19, "id": "344a4f90-3470-40e9-a75f-b925a88c2480", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "k = 0 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "e60f8fd6e60f4edc8c841a194e874a70", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t500\n", "k + 1n:\t500\n", "\n", "k = 1 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "f08cebf9ab274dad8164b57f18417627", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t528\n", "k + 1n:\t472\n", "\n", "k = 2 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "772c89d8a3a048f5934cb451f0b12be3", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t531\n", "k + 1n:\t469\n" ] } ], "source": [ "test_3n_random_scalar_projected(asplit, 1000)" ] }, { "cell_type": "markdown", "id": "78aaf83f-a689-40ad-ae88-58ab20e5e6f9", "metadata": {}, "source": [ "#### Multiplicative splitting" ] }, { "cell_type": "code", "execution_count": 20, "id": "616a7726-01e6-4e9c-b7f2-fe8f14b60071", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "k = 0 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "0dfabf99286f48c9b2015742783669b9", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t571\n", "k + 1n:\t190\n", "k + 2n:\t239\n", "\n", "k = 1 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d310342f047747a097f635e63540c42a", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t217\n", "k + 1n:\t232\n", "k + 2n:\t551\n", "\n", "k = 2 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "5581e145df664b6c87957f1791aea1b3", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t209\n", "k + 1n:\t546\n", "k + 2n:\t245\n" ] } ], "source": [ "test_3n_random_scalar_projected(msplit, 1000)" ] }, { "cell_type": "markdown", "id": "018c8f31-d7c3-497b-bdaf-580c6465753f", "metadata": {}, "source": [ "#### Euclidean splitting" ] }, { "cell_type": "code", "execution_count": 21, "id": "adced4e4-37a7-43ed-97b5-01cb5d274d6b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "k = 0 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "29f009a782ee4dd4bff9938d655db831", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t1000\n", "\n", "k = 1 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "f2d438308994479b8e1086621b24466e", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t1000\n", "\n", "k = 2 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "00f137d1135741ada0978bd00c9ff194", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k:\t1000\n" ] } ], "source": [ "test_3n_random_scalar_projected(esplit, 1000)" ] }, { "cell_type": "markdown", "id": "8d7b0a42-7be7-464d-932d-8386ad912034", "metadata": {}, "source": [ "#### Brumley and Tuveri bit-length fixing" ] }, { "cell_type": "code", "execution_count": 22, "id": "fe8d8295-3e69-4b60-b8c3-5710deaeb0b3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "k = 0 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "4448b9a556cc4acd8d31e60277e09488", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k + 1n:\t35\n", "k + 2n:\t965\n", "\n", "k = 1 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "1dbe7fc3fbbf4f92a889ca37b66a27b3", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k + 1n:\t36\n", "k + 2n:\t964\n", "\n", "k = 2 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "56b3afd278b8491e80dabc3b1d676631", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "k + 1n:\t37\n", "k + 2n:\t963\n" ] } ], "source": [ "test_3n_random_scalar_projected(bt, 1000)" ] }, { "cell_type": "markdown", "id": "43b309af-5683-4384-9623-d7633723177c", "metadata": {}, "source": [ "## Mask recovery ($n + \\epsilon$ test)\n", "Using a composite order curve we can recover the size and the actual mask values (in a known key scenario) in both GSR and multiplicative splitting. However, real-world targets do not like composite order curves and may either check the order or otherwise fail to compute on such curves. Thus, we lie to them and set the order to the next-prime of the true order, in this case $n + 92$." ] }, { "cell_type": "code", "execution_count": 23, "id": "20a26f27-620d-4d7f-92bd-b949482b5c9a", "metadata": {}, "outputs": [], "source": [ "pari = cypari2.Pari(256_000_000, 2_000_000_000)" ] }, { "cell_type": "code", "execution_count": 24, "id": "144340bd-5372-4beb-a46e-fd60c596b254", "metadata": {}, "outputs": [], "source": [ "real_n = 0xa9fa3419aca88bade2cba14e317816c6828910c6ce04fcd2a2e857d25df50775\n", "# = 2898786277 * 2916913393 * 3067509271 * 3248233993 * 3894099889 * 4099407227 * 4101666977 * 13936975277\n", "real_n_facts = pari.factor(real_n)\n", "params92pn = load_params_ectester(io.BytesIO(b\"0xa9fa3419aca88bade2cba14e317816c79d52481d463dc9bcb12c37f45aa3b4e1,0x2ea3bfe6659f8e035735349b91fbfa2baf0cf8e640315f0fe03c1136813dec99,0x2b07c518e04b02158651e3dbbef7720015dd496bf15af02f8439f8e1503b8370,0x90fb04b1af19e8e20396ac052f260a9fb5f736b97e3cd4af08fe81a1e75dac6d,0x2302bcf700d3d5899f04d0c7441f5017c9758bfafd6ce15dbe36fb4eea76baec,0xa9fa3419aca88bade2cba14e317816c6828910c6ce04fcd2a2e857d25df507d1,0x01\"), \"projective\")\n", "e = pari.ellinit([int(params92pn.curve.parameters[\"a\"]), int(params92pn.curve.parameters[\"b\"])], int(params92pn.curve.prime))\n", "e[15][0] = real_n" ] }, { "cell_type": "code", "execution_count": 25, "id": "f103129c-17d3-4217-999b-94ecb4ec523d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "prime:\t0xa9fa3419aca88bade2cba14e317816c79d52481d463dc9bcb12c37f45aa3b4e1\n", "a:\t0x2ea3bfe6659f8e035735349b91fbfa2baf0cf8e640315f0fe03c1136813dec99\n", "b:\t0x2b07c518e04b02158651e3dbbef7720015dd496bf15af02f8439f8e1503b8370\n", "G:\t[0x90fb04b1af19e8e20396ac052f260a9fb5f736b97e3cd4af08fe81a1e75dac6d,\n", "\t 0x2302bcf700d3d5899f04d0c7441f5017c9758bfafd6ce15dbe36fb4eea76baec]\n", "n+92:\t0xa9fa3419aca88bade2cba14e317816c6828910c6ce04fcd2a2e857d25df507d1 (fake order, given to the target, prime)\n", "n:\t0xa9fa3419aca88bade2cba14e317816c6828910c6ce04fcd2a2e857d25df50775 (real order, composite)\n" ] } ], "source": [ "print(f\"prime:\\t0x{params92pn.curve.prime:x}\")\n", "print(f\"a:\\t0x{params92pn.curve.parameters['a']:x}\")\n", "print(f\"b:\\t0x{params92pn.curve.parameters['b']:x}\")\n", "print(f\"G:\\t[0x{params92pn.generator.X:x},\\n\\t 0x{params92pn.generator.Y:x}]\")\n", "print(f\"n+92:\\t0x{params92pn.order:x} (fake order, given to the target, prime)\")\n", "print(f\"n:\\t0x{real_n:x} (real order, composite)\")" ] }, { "cell_type": "markdown", "id": "322d2e68-5259-4ea6-9748-2b0aa21b557f", "metadata": {}, "source": [ "### Group scalar randomization\n", "In GSR getting the mask out this way is quite simple. The target believes it is operating on a curve of order $n+92$ so it will use that value multiplied with the mask to randomize the scalar. Thus as a result we get:\n", "$$ P = [k + r(n + 92)]G $$\n", "\n", "However, the curve is truly of order $n$, thus arithmetic on its group will make this equal to: \n", "$$ P = [k + r 92]G $$\n", "\n", "Since this is a composite order curve, we can solve the dlog and obtain $k + r 92$ and since we assume we know $k$ we can easily compute both the mask size and mask value $r$." ] }, { "cell_type": "code", "execution_count": 26, "id": "08d99bd5-2b87-4a04-995d-7a87f9b67102", "metadata": {}, "outputs": [], "source": [ "key = 0x20959f2b437de1e522baf6d814911938157390d3ea5118660b852ab0d5387006 # any key works ofc\n", "gsr.init(params92pn, params92pn.generator)\n", "res = gsr.multiply(key)" ] }, { "cell_type": "code", "execution_count": 27, "id": "2a869bed-8e21-46af-8f70-065f4afd6a82", "metadata": {}, "outputs": [], "source": [ "affine_gen = params92pn.generator.to_affine()\n", "affine_res = res.to_affine()" ] }, { "cell_type": "code", "execution_count": 28, "id": "e440399a-bc01-488b-8822-08cc0bf1672d", "metadata": {}, "outputs": [], "source": [ "dlog = pari.elllog(e,\n", " [int(affine_res.x), int(affine_res.y)],\n", " [int(affine_gen.x), int(affine_gen.y)],\n", " real_n)" ] }, { "cell_type": "code", "execution_count": 29, "id": "7ea6d6ae-a6f5-4b53-8c40-787d79970cb6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2799400670\n", "32\n" ] } ], "source": [ "mask = int((dlog - key) / 92)\n", "mask_len = mask.bit_length()\n", "print(mask)\n", "print(mask_len)" ] }, { "cell_type": "markdown", "id": "d40ec035-0656-4eda-8ef4-c14f9d53f49f", "metadata": {}, "source": [ "### Multiplicative splitting\n", "In multiplicative splitting the situation is a bit more complicated. Doing the same computation, where the target thinks the curve order is $n+92$ leads to:\n", "$$ P = [k r^{-1}\\pmod{n+92}][r \\mod n]G $$\n", "\n", "Since the curve is composite order we can easily compute the dlog $d$ of P to G, we get:\n", "$$ d = (k r^{-1})\\pmod{n+92}\\: r = k + t (n + 92) $$\n", "\n", "However, the dlog is computed $\\mod n$ so we really get: $ d = k + t 92$. We extract the $t$ out of this.\n", "Note that $t$ will have roughly the same size as the mask $r$, since at the left side we have $(k r^{-1}) {\\mod (n+92)}$\n", "of size $n$ and $r$ of size of the mask and on the right size we have $t n$ that dominates.\n", "Thus at this point we have recovered the mask size.\n", "However, $t$ is always smaller than $r$, sometimes also in bitsize.\n", "\n", "Now that we have $s$ we can go back to the original equation and get:\n", "$$ (k r^{-1})\\pmod{n+92}\\: r = k + t (n + 92) $$\n", "\n", "We can then factor this value, lets call it $full$, and look for divisors that are larger than $t$ but smaller than the mask length\n", "that we recovered before. There may be multiple candidates here and we don't know how to distinguish between\n", "them. It holds for all of the candidates $c$ that the rest of the value $full$ is equal to the inverse of $c \\mod (n+92)$.\n", "However, sometimes there is only one candidate, which is equal to the true mask value $r$." ] }, { "cell_type": "code", "execution_count": 30, "id": "b5f398fc-90d7-455e-97bd-62b682d55961", "metadata": {}, "outputs": [], "source": [ "def divisors(primes, powers):\n", " for comb in itertools.product(*[range(power+1) for power in powers]):\n", " value = 1\n", " for prime, power in zip(primes, comb):\n", " value *= prime**power\n", " yield value\n", "\n", "def pari_factor(number, flag=1+8, duration=5*60):\n", " # Use the 1 + 8 option to factorint to skip some costly factorization methods, we don't need the huge factors.\n", " # This means that some factors may remain, which in turn means that sometimes we may not find the true mask.\n", " pari = cypari2.Pari(256_000_000, 2_000_000_000)\n", " with warnings.catch_warnings(category=RuntimeWarning):\n", " warnings.simplefilter(\"ignore\")\n", " try:\n", " alarm(duration)\n", " factors = pari.factorint(number, flag)\n", " except AlarmInterrupt:\n", " return None\n", " primes = list(map(int, factors[0]))\n", " powers = list(map(int, factors[1]))\n", " return primes, powers\n", "\n", "def pari_dlog(params, P, G, real_n, facts_str):\n", " pari = cypari2.Pari(256_000_000, 2_000_000_000)\n", " e = pari.ellinit([int(params.curve.parameters[\"a\"]), int(params.curve.parameters[\"b\"])], int(params.curve.prime))\n", " e[15][0] = real_n\n", " facts = pari(facts_str)\n", " dlog = pari.elllog(e, P, G, facts)\n", " return int(dlog)" ] }, { "cell_type": "code", "execution_count": 31, "id": "5f03e586-33df-4525-a722-f5f63d6ca28d", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "28ab3630b5084b6aa80ae732f85fd8c9", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Collecting scalarmults: 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "b79a4b8ae44c494e81087a5c0e413ec9", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Computing dlogs: 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "32 True\n" ] } ], "source": [ "key = 0x20959f2b437de1e522baf6d814911938157390d3ea5118660b852ab0d5387006 # any key works\n", "msplit = MultiplicativeSplitting(mult, rand_bits=32) # change the mask size here to your liking\n", "tries = 1000\n", "num_workers = 30\n", "\n", "blens = [None for _ in range(tries)]\n", "dlogs = [None for _ in range(tries)]\n", "ts = [None for _ in range(tries)]\n", "\n", "results = [None for _ in range(tries)]\n", "rs = [None for _ in range(tries)]\n", "\n", "with TaskExecutor(max_workers=num_workers) as pool:\n", " for i in trange(tries, desc=\"Collecting scalarmults\"):\n", " msplit.init(params92pn, params92pn.generator)\n", " with local(DefaultContext()) as ctx:\n", " res = msplit.multiply(key)\n", " \n", " affine_res = res.to_affine()\n", " affine_gen = params92pn.generator.to_affine()\n", " results[i] = affine_res\n", " \n", " rval = []\n", " ctx.actions[0].walk(lambda action: rval.append(int(action.result)) if isinstance(action, RandomModAction) else None)\n", " rs[i] = rval[0]\n", " \n", " pool.submit_task(i,\n", " pari_dlog,\n", " params92pn,\n", " [int(affine_res.x), int(affine_res.y)],\n", " [int(affine_gen.x), int(affine_gen.y)],\n", " real_n,\n", " repr(real_n_facts))\n", " \n", " for i, future in tqdm(pool.as_completed(), desc=\"Computing dlogs\", total=len(pool.tasks)):\n", " dlog = future.result()\n", " val = dlog - key\n", " if val % 92 != 0:\n", " print(f\"Bad val! {i}\")\n", " t = val // 92\n", " ts[i] = t\n", " dlogs[i] = dlog\n", " blens[i] = t.bit_length()\n", "\n", "mask_len = max(blens)\n", "print(mask_len, mask_len == msplit.rand_bits)" ] }, { "cell_type": "code", "execution_count": 32, "id": "5fbf8a38-983d-49a6-9cac-5350f960dc3e", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "239ea218388d4c988e39e3ec54765325", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Factoring: 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with TaskExecutor(max_workers=num_workers) as pool:\n", " fulls = []\n", " for t in ts:\n", " full = t * (real_n + 92) + key\n", " fulls.append(full)\n", " pool.submit_task(t,\n", " pari_factor,\n", " full, flag=0)\n", " facts = [None for _ in ts]\n", " for t, future in tqdm(pool.as_completed(), desc=\"Factoring\", total=len(ts)):\n", " result = future.result()\n", " if result is None:\n", " print(\"Timed out.\")\n", " facts[ts.index(t)] = result" ] }, { "cell_type": "code", "execution_count": 33, "id": "0973fe4b-cdf5-4e91-850b-25375eeabb7e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Only one candidate, we got the mask: 4210661147 True \n", "Only one candidate, we got the mask: 1369579171 True \n", "Only one candidate, we got the mask: 2876457637 True \n", "Only one candidate, we got the mask: 2280856345 True \n", "Only one candidate, we got the mask: 827717755 True \n", "Only one candidate, we got the mask: 4072523937 True \n", "Only one candidate, we got the mask: 758839701 True \n", "Only one candidate, we got the mask: 469568509 True \n", "Only one candidate, we got the mask: 3990260303 True \n", "Only one candidate, we got the mask: 1505311001 True \n", "Only one candidate, we got the mask: 2299553601 True \n", "Only one candidate, we got the mask: 697503202 True \n", "Only one candidate, we got the mask: 4252750043 True \n", "Only one candidate, we got the mask: 2628411277 True \n", "Only one candidate, we got the mask: 4059149439 True \n", "Only one candidate, we got the mask: 1361652212 True \n", "Only one candidate, we got the mask: 3472878829 True \n", "Only one candidate, we got the mask: 3240581563 True \n", "Only one candidate, we got the mask: 1582029434 True \n", "Only one candidate, we got the mask: 2967829579 True \n", "Only one candidate, we got the mask: 3234050763 True \n", "Only one candidate, we got the mask: 3103898617 True \n", "Only one candidate, we got the mask: 4018870174 True \n", "Only one candidate, we got the mask: 3951669678 True \n", "Only one candidate, we got the mask: 1701217031 True \n", "Only one candidate, we got the mask: 1923781246 True \n", "Only one candidate, we got the mask: 715371301 True \n", "Only one candidate, we got the mask: 1694682533 True \n", "Only one candidate, we got the mask: 672000560 True \n", "Only one candidate, we got the mask: 3653613454 True \n", "Only one candidate, we got the mask: 2868494114 True \n", "Only one candidate, we got the mask: 3355886434 True \n", "Only one candidate, we got the mask: 1115372107 True \n", "Only one candidate, we got the mask: 4067804612 True \n", "Only one candidate, we got the mask: 2494089869 True \n", "Only one candidate, we got the mask: 2412619023 True \n", "Only one candidate, we got the mask: 2564194723 True \n", "Only one candidate, we got the mask: 3176531279 True \n", "Only one candidate, we got the mask: 992627261 True \n", "Only one candidate, we got the mask: 3962231108 True \n", "Only one candidate, we got the mask: 3275516659 True \n", "Only one candidate, we got the mask: 1873987541 True \n", "Only one candidate, we got the mask: 2560305331 True \n", "Only one candidate, we got the mask: 2759325890 True \n", "Only one candidate, we got the mask: 3862931039 True \n", "Only one candidate, we got the mask: 3793366939 True \n", "Only one candidate, we got the mask: 1269186792 True \n", "Only one candidate, we got the mask: 3725047516 True \n", "Only one candidate, we got the mask: 3894553021 True \n", "Only one candidate, we got the mask: 3565878235 True \n", "Only one candidate, we got the mask: 2704254326 True \n", "Only one candidate, we got the mask: 3615800003 True \n", "Only one candidate, we got the mask: 2511635871 True \n", "Only one candidate, we got the mask: 1357715977 True \n", "Only one candidate, we got the mask: 714055771 True \n", "Only one candidate, we got the mask: 3697420789 True \n", "Only one candidate, we got the mask: 2446539037 True \n", "Only one candidate, we got the mask: 516207109 True \n", "Only one candidate, we got the mask: 3120056087 True \n", "Only one candidate, we got the mask: 1489074367 True \n", "Only one candidate, we got the mask: 3667238122 True \n", "Only one candidate, we got the mask: 735876437 True \n", "Only one candidate, we got the mask: 4153656518 True \n", "Only one candidate, we got the mask: 4181001696 True \n", "Only one candidate, we got the mask: 2858950415 True \n", "Only one candidate, we got the mask: 3168491743 True \n", "Only one candidate, we got the mask: 2798185807 True \n", "Only one candidate, we got the mask: 3910136345 True \n", "Only one candidate, we got the mask: 3655163311 True \n", "Only one candidate, we got the mask: 3120036853 True \n", "Only one candidate, we got the mask: 2296561369 True \n", "Only one candidate, we got the mask: 3545333609 True \n", "Only one candidate, we got the mask: 4022472467 True \n", "Only one candidate, we got the mask: 696950257 True \n", "Only one candidate, we got the mask: 2344880401 True \n", "Only one candidate, we got the mask: 1304461293 True \n", "Only one candidate, we got the mask: 2488303303 True \n", "Only one candidate, we got the mask: 897969179 True \n", "Only one candidate, we got the mask: 2428227311 True \n", "Only one candidate, we got the mask: 4182615903 True \n", "Only one candidate, we got the mask: 3959420453 True \n", "Only one candidate, we got the mask: 4152575027 True \n", "Only one candidate, we got the mask: 3167944513 True \n", "Only one candidate, we got the mask: 3958064807 True \n", "Only one candidate, we got the mask: 2182301917 True \n", "Only one candidate, we got the mask: 3805873117 True \n", "Only one candidate, we got the mask: 2335551644 True \n", "Only one candidate, we got the mask: 2723259443 True \n", "Only one candidate, we got the mask: 2605906789 True \n", "Only one candidate, we got the mask: 3356121539 True \n", "Only one candidate, we got the mask: 4211793379 True \n", "Only one candidate, we got the mask: 4222717338 True \n", "Only one candidate, we got the mask: 3519058663 True \n", "Only one candidate, we got the mask: 1864127552 True \n", "Only one candidate, we got the mask: 2154621938 True \n", "Only one candidate, we got the mask: 3234236295 True \n", "Only one candidate, we got the mask: 3315764587 True \n", "Only one candidate, we got the mask: 1583475496 True \n", "Only one candidate, we got the mask: 3382760512 True \n", "Only one candidate, we got the mask: 3440150257 True \n", "Only one candidate, we got the mask: 335395484 True \n", "Only one candidate, we got the mask: 2845782101 True \n", "Only one candidate, we got the mask: 3834304354 True \n", "Only one candidate, we got the mask: 1709582813 True \n", "Only one candidate, we got the mask: 3658374553 True \n", "Only one candidate, we got the mask: 380353117 True \n", "Only one candidate, we got the mask: 2648379201 True \n", "Only one candidate, we got the mask: 518790521 True \n", "Total recovered masks: 108 out of 1000\n" ] } ], "source": [ "candidate_amounts = []\n", "i = 0\n", "for dlog, t, blen, r, fact, full, result in zip(dlogs, ts, blens, rs, facts, fulls, results):\n", " if fact is None:\n", " continue\n", " \n", " (primes, powers) = fact\n", " complete = True\n", " for prime in primes:\n", " if not pari.isprime(prime):\n", " complete = False\n", " break\n", "\n", " i += 1\n", " candidates = set()\n", " for divisor in divisors(primes, powers):\n", " if divisor.bit_length() <= mask_len and divisor >= t:\n", " candidates.add(divisor)\n", " candidate_amounts.append(len(candidates))\n", " if len(candidates) == 1:\n", " candidate = candidates.pop()\n", " print(\"Only one candidate, we got the mask:\", candidate, candidate == r, \"incomplete factoring this may not be the r\" if not complete else \"\")\n", "print(f\"Total recovered masks: {len(list(filter(lambda a: a == 1, candidate_amounts)))} out of {i}\")" ] }, { "cell_type": "code", "execution_count": 34, "id": "6274ff91-325f-4c6b-a4d7-d66b994d730f", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "f798486616e84295ad961e7cc4aefa7e", "version_major": 2, "version_minor": 0 }, "image/png": "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", "text/html": [ "\n", "