{ "cells": [ { "cell_type": "markdown", "id": "bafc2f4e-05a3-4120-bcd6-5d1f5fb91cd9", "metadata": {}, "source": [ "# Distinguishing countermeasures by output" ] }, { "cell_type": "code", "execution_count": 204, "id": "33ee6084-2ac3-4f95-9610-0fbc06026538", "metadata": {}, "outputs": [], "source": [ "import io\n", "import random\n", "import itertools\n", "import cypari2\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.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\n", "from pyecsca.ec.mult import LTRMultiplier\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": "code", "execution_count": 205, "id": "b1b9596c-1eba-4ace-af84-8cb279d84cc2", "metadata": {}, "outputs": [], "source": [ "model = ShortWeierstrassModel()\n", "coords = model.coordinates[\"projective\"]" ] }, { "cell_type": "code", "execution_count": 206, "id": "b0afb195-8390-44c5-931e-75a70ccd4e9e", "metadata": {}, "outputs": [], "source": [ "add = coords.formulas[\"add-2015-rcb\"]\n", "dbl = coords.formulas[\"dbl-2015-rcb\"]\n", "mult = LTRMultiplier(add, dbl, complete=False)" ] }, { "cell_type": "code", "execution_count": 207, "id": "52c877e1-5021-4ec2-9daa-dd20bec6bcb2", "metadata": {}, "outputs": [], "source": [ "gsr = GroupScalarRandomization(mult)\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" ] }, { "cell_type": "code", "execution_count": 208, "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": 209, "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": 210, "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": 211, "id": "86532d50-2db7-4370-b449-c545b330a852", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "b0a80fc9b8f14b2992f10c7bb66a9a83", "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:\t326\n", "k + 1n:\t341\n", "k + 2n:\t333\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": 212, "id": "ad421630-606f-4666-9bbf-1a446eec1b59", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "0d0f2e87f3044516924fbd74ba68f983", "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" ] } ], "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": 213, "id": "3ed5d7f3-0ba1-4b62-9635-aeb492499175", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "9eb1217838fa4931bb0f0f9cd86aba09", "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:\t221\n", "k + 1n:\t544\n", "k + 2n:\t235\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": 214, "id": "314447c6-a1fb-4d3a-8988-b34c8912dd5e", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "1c6788e6f3614ea1a7a6c8c6cd761bb5", "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": 215, "id": "f41dfc1d-1017-4aa0-bcd4-6569c53bf81e", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "9290687728e144329fd7dca7dcebabd8", "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": 216, "id": "7255321a-6ad6-4938-8ec9-dd8d977686db", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "63db299beb5b47679019f469b446d2c9", "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:\t324\n", "k + 1n:\t337\n", "k + 2n:\t339\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": 217, "id": "b0146a9a-0803-43c4-ab29-8ba6e15934b5", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "639cadec2edd4719b7bcbd5f6d9b80ae", "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:\t491\n", "k + 1n:\t509\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": 218, "id": "5645ae6f-f5f4-419d-ba47-248532dc2114", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "f3c8933bed464791b7d36ef8dbf8bf2b", "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:\t341\n", "k + 1n:\t308\n", "k + 2n:\t351\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": 219, "id": "c9fc4f35-1c25-4cac-bb63-8bd70263db47", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "85635506a91c4665b65d5377c06549b1", "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": 220, "id": "4fd6b288-08a9-4dbe-9145-e96401805315", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "c46f2d9115ca4f17b229c5b0da6baf7b", "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" ] } ], "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": 221, "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": "7561669af4664bfebcbcf2d5e5629d2e", "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:\t340\n", "k + 1n:\t308\n", "k + 2n:\t352\n", "\n", "k = 1 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "25e114540bef47d89efe10138dbe13cf", "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:\t325\n", "k + 1n:\t337\n", "k + 2n:\t338\n", "\n", "k = 2 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "a8a2dcd9e5954ecd8a0d2f6b54f4d436", "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:\t356\n", "k + 2n:\t332\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": 222, "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": "bc8d0b7c8f414943af7d3f133e43bd78", "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:\t482\n", "k + 1n:\t518\n", "\n", "k = 1 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "df03bde64059413fbe6a5208c458f5f9", "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:\t513\n", "k + 1n:\t487\n", "\n", "k = 2 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d953bea042794219bf4aa32c1d16ad92", "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:\t497\n", "k + 1n:\t503\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": 223, "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": "4f329714588d4127bd4273dda2695222", "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:\t563\n", "k + 1n:\t225\n", "k + 2n:\t212\n", "\n", "k = 1 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "5f8e4efe89454eb5817d889b2fe77210", "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:\t234\n", "k + 1n:\t221\n", "k + 2n:\t545\n", "\n", "k = 2 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "0712286b1d8242c7ae0ea2255eda2f37", "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:\t210\n", "k + 1n:\t578\n", "k + 2n:\t212\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": 224, "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": "9588400d40a041e088d86352e7512a06", "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": "e3014e90730b44209d9bf87cd89598a3", "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": "1fe4739a9dd3482a9e0f1e7d379c57b3", "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": 225, "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": "2101fd299ebd49358729bf7dc7aa204a", "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:\t39\n", "k + 2n:\t961\n", "\n", "k = 1 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "ae5b94fb0d5b4395b98a6f905ea29a80", "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:\t29\n", "k + 2n:\t971\n", "\n", "k = 2 mod 3\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "b3155abae413412bae324c579e5c7de5", "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:\t33\n", "k + 2n:\t967\n" ] } ], "source": [ "test_3n_random_scalar_projected(bt, 1000)" ] }, { "cell_type": "markdown", "id": "43b309af-5683-4384-9623-d7633723177c", "metadata": {}, "source": [ "## Mask recovery\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": 226, "id": "20a26f27-620d-4d7f-92bd-b949482b5c9a", "metadata": {}, "outputs": [], "source": [ "pari = cypari2.Pari(256_000_000, 2_000_000_000)" ] }, { "cell_type": "code", "execution_count": 227, "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": 228, "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": 229, "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": 230, "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": 231, "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": 232, "id": "7ea6d6ae-a6f5-4b53-8c40-787d79970cb6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3752128619\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": 233, "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):\n", " pari = cypari2.Pari(256_000_000, 2_000_000_000)\n", " factors = pari.factor(number)\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": 234, "id": "5f03e586-33df-4525-a722-f5f63d6ca28d", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "e4879058a89a44af9abf2e7a58b1022f", "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": "69ff8872b4454bacb5f1c4a7503e6d3b", "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\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", "\n", "blens = [None for _ in range(tries)]\n", "ts = [None for _ in range(tries)]\n", "\n", "results = []\n", "rs = []\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.append(affine_res)\n", " ctx.actions[0].walk(lambda action: rs.append(int(action.result)) if isinstance(action, RandomModAction) else None)\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", " t = int((dlog - key) / 92)\n", " ts[i] = t\n", " blens[i] = s.bit_length()\n", "\n", "mask_len = max(blens)\n", "print(mask_len)" ] }, { "cell_type": "code", "execution_count": 235, "id": "5fbf8a38-983d-49a6-9cac-5350f960dc3e", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "89d1369c99644e88bd73b6230963716a", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Factoring: 0%| | 0/1000 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "num_workers = 25\n", "\n", "with TaskExecutor(max_workers=num_workers) as pool:\n", " for t in ts:\n", " full = t * (real_n + 92) + key\n", " pool.submit_task(t,\n", " pari_factor,\n", " full)\n", " facts = [None for _ in ss]\n", " for t, future in tqdm(pool.as_completed(), desc=\"Factoring\", total=len(ts)):\n", " result = future.result()\n", " facts[ts.index(t)] = result" ] }, { "cell_type": "code", "execution_count": 236, "id": "0973fe4b-cdf5-4e91-850b-25375eeabb7e", "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Only one candidate, we got the mask: 3223834487 True\n", "Only one candidate, we got the mask: 2377873873 True\n", "Only one candidate, we got the mask: 2549271668 True\n", "Only one candidate, we got the mask: 2536406777 True\n", "Only one candidate, we got the mask: 3474268328 True\n", "Only one candidate, we got the mask: 3550639425 True\n", "Only one candidate, we got the mask: 3814005975 True\n", "Only one candidate, we got the mask: 4238733795 True\n", "Only one candidate, we got the mask: 4168083955 True\n", "Only one candidate, we got the mask: 2761553491 True\n", "Only one candidate, we got the mask: 3411331906 True\n", "Only one candidate, we got the mask: 789326198 True\n", "Only one candidate, we got the mask: 2285458765 True\n", "Only one candidate, we got the mask: 774733453 True\n", "Only one candidate, we got the mask: 3283055299 True\n", "Only one candidate, we got the mask: 2838749009 True\n", "Only one candidate, we got the mask: 3276117366 True\n", "Only one candidate, we got the mask: 860570263 True\n", "Only one candidate, we got the mask: 981664829 True\n", "Only one candidate, we got the mask: 4134679928 True\n", "Only one candidate, we got the mask: 3988645114 True\n", "Only one candidate, we got the mask: 2280222647 True\n", "Only one candidate, we got the mask: 3577824626 True\n", "Only one candidate, we got the mask: 3679892960 True\n", "Only one candidate, we got the mask: 1774221601 True\n", "Only one candidate, we got the mask: 2623580147 True\n", "Only one candidate, we got the mask: 1702008059 True\n", "Only one candidate, we got the mask: 3933544069 True\n", "Only one candidate, we got the mask: 3999529804 True\n", "Only one candidate, we got the mask: 3480801033 True\n", "Only one candidate, we got the mask: 3813421579 True\n", "Only one candidate, we got the mask: 3417626666 True\n", "Only one candidate, we got the mask: 4170438661 True\n", "Only one candidate, we got the mask: 3502288943 True\n", "Only one candidate, we got the mask: 2786122643 True\n", "Only one candidate, we got the mask: 2127512283 True\n", "Only one candidate, we got the mask: 2531786983 True\n", "Only one candidate, we got the mask: 3983366093 True\n", "Only one candidate, we got the mask: 2379294079 True\n", "Only one candidate, we got the mask: 2930272946 True\n", "Only one candidate, we got the mask: 2500319501 True\n", "Only one candidate, we got the mask: 2947684105 True\n", "Only one candidate, we got the mask: 3995028346 True\n", "Only one candidate, we got the mask: 3421022802 True\n", "Only one candidate, we got the mask: 3953171129 True\n", "Only one candidate, we got the mask: 2982511438 True\n", "Only one candidate, we got the mask: 2830285508 True\n", "Only one candidate, we got the mask: 277646521 True\n", "Only one candidate, we got the mask: 3775642326 True\n", "Only one candidate, we got the mask: 3528903061 True\n", "Only one candidate, we got the mask: 2433595133 True\n", "Only one candidate, we got the mask: 2809043104 True\n", "Only one candidate, we got the mask: 3918854258 True\n", "Only one candidate, we got the mask: 2172498737 True\n", "Only one candidate, we got the mask: 2614989645 True\n", "Only one candidate, we got the mask: 3881796054 True\n", "Only one candidate, we got the mask: 3763131597 True\n", "Only one candidate, we got the mask: 3333059164 True\n", "Only one candidate, we got the mask: 781918702 True\n", "Only one candidate, we got the mask: 3898624034 True\n", "Only one candidate, we got the mask: 2695908441 True\n", "Only one candidate, we got the mask: 2428288661 True\n", "Only one candidate, we got the mask: 380310234 True\n", "Only one candidate, we got the mask: 3806008683 True\n", "Only one candidate, we got the mask: 1583055543 True\n", "Only one candidate, we got the mask: 3071695987 True\n", "Only one candidate, we got the mask: 2466420323 True\n", "Only one candidate, we got the mask: 3668827111 True\n", "Only one candidate, we got the mask: 3030308051 True\n", "Only one candidate, we got the mask: 4178268350 True\n", "Only one candidate, we got the mask: 601467334 True\n", "Only one candidate, we got the mask: 1756886305 True\n", "Only one candidate, we got the mask: 3789366239 True\n", "Only one candidate, we got the mask: 1709528826 True\n", "Only one candidate, we got the mask: 4179236943 True\n", "Only one candidate, we got the mask: 3246542896 True\n", "Only one candidate, we got the mask: 1036989838 True\n", "Only one candidate, we got the mask: 2843879303 True\n", "Only one candidate, we got the mask: 2943368159 True\n", "Only one candidate, we got the mask: 3694910341 True\n", "Only one candidate, we got the mask: 3509390042 True\n", "Only one candidate, we got the mask: 3797507269 True\n", "Only one candidate, we got the mask: 3948777957 True\n", "Only one candidate, we got the mask: 3649873740 True\n", "Only one candidate, we got the mask: 2612080546 True\n", "Only one candidate, we got the mask: 3671883118 True\n", "Only one candidate, we got the mask: 2973101524 True\n", "Only one candidate, we got the mask: 3724708289 True\n", "Only one candidate, we got the mask: 4134344947 True\n", "Only one candidate, we got the mask: 3489598522 True\n", "Only one candidate, we got the mask: 3984902078 True\n", "Only one candidate, we got the mask: 4259116327 True\n", "Only one candidate, we got the mask: 3389988907 True\n", "Only one candidate, we got the mask: 868217594 True\n", "Only one candidate, we got the mask: 3729431567 True\n", "Only one candidate, we got the mask: 3542657779 True\n", "Only one candidate, we got the mask: 2695799377 True\n", "Only one candidate, we got the mask: 3972219937 True\n", "Only one candidate, we got the mask: 2701111433 True\n", "Only one candidate, we got the mask: 3806713198 True\n", "Only one candidate, we got the mask: 3503145915 True\n", "Only one candidate, we got the mask: 2397268241 True\n", "Only one candidate, we got the mask: 2919558765 True\n", "Only one candidate, we got the mask: 543703399 True\n", "Total recovered masks: 104 out of 1000\n" ] } ], "source": [ "candidate_amounts = []\n", "for t, blen, r, (primes, powers), result in zip(ts, blens, rs, facts, results):\n", " #print(primes, powers)\n", " #print(s, blen, r, r.bit_length())\n", " candidates = set()\n", " for divisor in divisors(primes, powers):\n", " if blen <= divisor.bit_length() <= mask_len and divisor > t:\n", " candidates.add(divisor)\n", " #print(f\"Candidates: {len(candidates)}, {r in candidates}\")\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)\n", " #print(\"--\")\n", "print(f\"Total recovered masks: {len(list(filter(lambda a: a == 1, candidate_amounts)))} out of {tries}\")" ] }, { "cell_type": "code", "execution_count": 237, "id": "6274ff91-325f-4c6b-a4d7-d66b994d730f", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d557894c3fbd4543b9fd6c240b676212", "version_major": 2, "version_minor": 0 }, "image/png": "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3LlauHCh3WpI0iOPPKLMzEytXLlSe/fu1cCBAxUXF6fjx4/XabyrHWtz587Vyy+/rKVLl2rnzp1q3ry54uPj9dNPP9ll/LKyMvXr10/PPfdcneZ/tRrnzp1Tbm6uUlNTlZubq3feeUcHDhzQ0KFD7VZDkjp37qxFixZp7969+vTTTxUSEqKBAwfq5MmTdqtx0fr167Vjxw4FBATUah9qWmPQoEGVjpe33nrLbuMfPnxY/fr1U9euXbV161Z9+eWXSk1NrfHjx2pS45dzLygo0GuvvSaLxaL77rvPbjWSk5OVkZGhVatWaf/+/Zo0aZLGjRunDRs22KWGYRhKSEjQv/71L7377rvas2ePgoODFRcXV+PPqZp8zk2ePFnvvfee1q1bp23btunEiRO69957a7wPTZqBWuvTp48xduxY288VFRVGQECAkZaW5pB6koz169c7ZOyLiouLDUnGtm3bHFrnxhtvNF599VW7j3vmzBkjNDTUyMzMNPr3729MnDjRbmPPmjXLCA8Pt9t4l5oyZYrRr18/h41flYkTJxo33XSTYbVa7TbmkCFDjIcffrhS27333muMHDnSbjXOnTtnODs7Gxs3bqzU3qtXL+PJJ5+85vEvPdasVqvh7+9vPP/887a206dPG+7u7sZbb711zeP/0pEjRwxJxp49e2o9bk1rXLRr1y5DknH06FGH1SgpKTEkGZs3b7ZrjX//+99Gu3btjH379hnBwcHGiy++WKfxq6uRlJRk3HPPPXUe82rjJyYmGr/97W/tMn51NS51zz33GHfeeadda3Tv3t344x//WKntWo7DS2scOHDAkGTs27fP1lZRUWG0bt3aWL58eZ1qXPo5d/r0acPV1dVYt26drc/+/fsNSUZ2dnadajQlrADW0vnz55WTk6O4uDhbm5OTk+Li4pSdnd2AM7s2JSUlkuSwL8auqKjQmjVrVFZW5pCv2hs7dqyGDBlS6f8Xezp06JACAgLUsWNHjRw5slana65mw4YNioqK0v333682bdrolltu0fLly+02/qXOnz+vVatW6eGHH5bFYrHbuLGxscrKytLBgwclSV988YU+/fRTDR482G41/vOf/6iiouKy1RJPT0+7rspedOTIERUWFlb678rHx0fR0dFN/ni3WCxq0aKFQ8Y/f/68li1bJh8fH4WHh9ttXKvVqgcffFApKSnq3r273ca91NatW9WmTRt16dJFv/vd7/Tdd9/ZZVyr1apNmzapc+fOio+PV5s2bRQdHW33S3x+qaioSJs2bdKYMWPsOm5sbKw2bNig48ePyzAMbdmyRQcPHtTAgQPtMn55ebkkVTrWnZyc5O7uXudj/dLPuZycHF24cKHS8d21a1cFBQU16eO7pgiAtXTq1ClVVFTYvoHkIj8/PxUWFjbQrK6N1WrVpEmT1Ldv30rflGIPe/fu1Q033CB3d3c99thjWr9+vbp162bXGmvWrFFubq7tawDtLTo6Wm+88YYyMjK0ZMkSHTlyRLfddpvOnDljl/H/9a9/acmSJQoNDdWHH36o3/3ud5owYYL+8pe/2GX8S6Wnp+v06dN66KGH7Dru1KlTNXz4cHXt2lWurq665ZZbNGnSJI0cOdJuNby8vBQTE6M//elPOnHihCoqKrRq1SplZ2eroKDAbnUuunhMX0/H+08//aQpU6ZoxIgR8vb2tuvYGzdu1A033CAPDw+9+OKLyszMlK+vr93Gf+655+Ti4qIJEybYbcxLDRo0SG+++aaysrL03HPPadu2bRo8eLAqKiqueezi4mKdPXtWc+bM0aBBg/TRRx9p2LBhuvfee7Vt2zY7zP5yf/nLX+Tl5WX305oLFy5Ut27d1L59e7m5uWnQoEFavHixbr/9druMfzGITZs2TT/88IPOnz+v5557Tv/+97/rdKxX9TlXWFgoNze3y/4h1JSP79poMl8FB8cZO3as9u3b55AVlC5duigvL08lJSX629/+pqSkJG3bts1uIfDYsWOaOHGiMjMza3UNTW38cgWrZ8+eio6OVnBwsN5++227/KvaarUqKipKzz77rCTplltu0b59+7R06VIlJSVd8/iXWrFihQYPHlyn66eu5O2339Zf//pXrV69Wt27d1deXp4mTZqkgIAAu+7HypUr9fDDD6tdu3ZydnZWr169NGLECOXk5NitxvXqwoULeuCBB2QYhpYsWWL38e+44w7l5eXp1KlTWr58uR544AHt3LlTbdq0ueaxc3Jy9NJLLyk3N9euK9eXGj58uO3PYWFh6tmzp2666SZt3bpVAwYMuKaxrVarJOmee+7R5MmTJUkRERHavn27li5dqv79+1/T+FV57bXXNHLkSLv//bhw4ULt2LFDGzZsUHBwsD7++GONHTtWAQEBdjkT4+rqqnfeeUdjxoxRy5Yt5ezsrLi4OA0ePLhON5Y58nOuqWIFsJZ8fX3l7Ox82V1CRUVF8vf3b6BZ1d24ceO0ceNGbdmyRe3bt7f7+G5uburUqZMiIyOVlpam8PBwvfTSS3YbPycnR8XFxerVq5dcXFzk4uKibdu26eWXX5aLi4td/tV+qRYtWqhz58765z//aZfx2rZte1kgvvnmm+16mvmio0ePavPmzXrkkUfsPnZKSoptFTAsLEwPPvigJk+ebPeV2Ztuuknbtm3T2bNndezYMe3atUsXLlxQx44d7VpHku2Yvh6O94vh7+jRo8rMzLT76p8kNW/eXJ06ddKtt96qFStWyMXFRStWrLDL2J988omKi4sVFBRkO9aPHj2qxx9/XCEhIXapUZWOHTvK19fXLse7r6+vXFxc6u14/+STT3TgwAG7H+8//vijpk+frvnz5+vuu+9Wz549NW7cOCUmJuqFF16wW53IyEjl5eXp9OnTKigoUEZGhr777rtaH+vVfc75+/vr/PnzOn36dKX+TfH4rgsCYC25ubkpMjJSWVlZtjar1aqsrCyHXNvmKIZhaNy4cVq/fr3+8Y9/qEOHDvVS12q12q7tsIcBAwZo7969ysvLs72ioqI0cuRI5eXlydnZ2W61Ljp79qwOHz6stm3b2mW8vn37XvZogoMHDyo4ONgu4//S66+/rjZt2mjIkCF2H/vcuXNycqr8V4qzs7Nt1cPemjdvrrZt2+qHH37Qhx9+qHvuucfuNTp06CB/f/9Kx3tpaal27tzZpI73i+Hv0KFD2rx5s1q1alUvde15vD/44IP68ssvKx3rAQEBSklJ0YcffmiXGlX597//re+++84ux7ubm5t69+5db8f7ihUrFBkZadfrMKWf/3u6cOFCvR3vPj4+at26tQ4dOqTdu3fX+Fi/2udcZGSkXF1dKx3fBw4cUH5+fpM6vuuKU8B1kJycrKSkJEVFRalPnz5asGCBysrKNHr0aLvVOHv2bKV/cR45ckR5eXlq2bKlgoKCrnn8sWPHavXq1Xr33Xfl5eVlu97Bx8dHnp6e1zy+JE2bNk2DBw9WUFCQzpw5o9WrV2vr1q12/cvay8vrsusWmzdvrlatWtntesY//OEPuvvuuxUcHKwTJ05o1qxZcnZ21ogRI+wy/uTJkxUbG6tnn31WDzzwgHbt2qVly5Zp2bJldhn/IqvVqtdff11JSUlycbH/oX/33XfrmWeeUVBQkLp37649e/Zo/vz5evjhh+1a58MPP5RhGOrSpYv++c9/KiUlRV27dq3z8Xe1Y23SpEl6+umnFRoaqg4dOig1NVUBAQFKSEiwy/jff/+98vPzbc/luxgO/P39a7wKcaUabdu21W9+8xvl5uZq48aNqqiosB3vLVu2lJub2zXXaNWqlZ555hkNHTpUbdu21alTp7R48WIdP368Vo8autrv6tLg6urqKn9/f3Xp0sUuNVq2bKnZs2frvvvuk7+/vw4fPqwnnnhCnTp1Unx8vF32ISUlRYmJibr99tt1xx13KCMjQ++99562bt1ql324+PlQWlqqdevWad68eTUetzY1+vfvr5SUFHl6eio4OFjbtm3Tm2++qfnz59utxrp169S6dWsFBQVp7969mjhxohISEmp8o8nVPud8fHw0ZswYJScnq2XLlvL29tb48eMVExOjW2+9tcb70WQ15C3ITdnChQuNoKAgw83NzejTp4+xY8cOu46/ZcsWQ9Jlr6SkJLuMX9XYkozXX3/dLuMbhmE8/PDDRnBwsOHm5ma0bt3aGDBggPHRRx/Zbfzq2PsxMImJiUbbtm0NNzc3o127dkZiYqLxz3/+027jG4ZhvPfee0aPHj0Md3d3o2vXrsayZcvsOr5hGMaHH35oSDIOHDhg97ENwzBKS0uNiRMnGkFBQYaHh4fRsWNH48knnzTKy8vtWmft2rVGx44dDTc3N8Pf398YO3ascfr06TqPd7VjzWq1GqmpqYafn5/h7u5uDBgwoFa/w6uN//rrr1f5/qxZs+xS4+LjZap6bdmyxS41fvzxR2PYsGFGQECA4ebmZrRt29YYOnSosWvXrhqPX5Pf1aXq8hiYK9U4d+6cMXDgQKN169aGq6urERwcbDz66KNGYWGhXfdhxYoVRqdOnQwPDw8jPDzcSE9Pt9s+XPTKK68Ynp6edT42rlajoKDAeOihh4yAgADDw8PD6NKlizFv3rxaPVrqajVeeuklo3379oarq6sRFBRkzJgxo1Z/n9Tkc+7HH380fv/73xs33nij0axZM2PYsGFGQUFBjWs0ZRbDsPNj+gEAANCocQ0gAACAyRAAAQAATIYACAAAYDIEQAAAAJMhAAIAAJgMARAAAMBkCIAAAAAmQwAE0GT86le/0qRJk2w/h4SEaMGCBVfcxmKxKD093aHzakxq8jsBAL4KDkCT9fnnn6t58+Z2HfOpp55Senq68vLy7DouADQmBEAATVbr1q0begoA0CRxChhArVmtVs2dO1edOnWSu7u7goKC9Mwzz9jenzJlijp37qxmzZqpY8eOSk1N1YULF2zvP/XUU4qIiNDKlSsVEhIiHx8fDR8+XGfOnLH1KSsr06hRo3TDDTeobdu2VX6p/aWnOw8dOqTbb79dHh4e6tatmzIzMy/b5kpze+ONNzR79mx98cUXslgsslgseuONNyRJp0+f1iOPPKLWrVvL29tbd955p7744otqf0fffvutLBaL3n77bd12223y9PRU7969dfDgQX3++eeKiorSDTfcoMGDB+vkyZO27T7//HP9+te/lq+vr3x8fNS/f3/l5uba3jcMQ0899ZSCgoLk7u6ugIAATZgwodp5vPrqq2rRooWysrIkSX/7298UFhYmT09PtWrVSnFxcSorK6t2ewDXJ1YAAdTatGnTtHz5cr344ovq16+fCgoK9M0339je9/Ly0htvvKGAgADt3btXjz76qLy8vPTEE0/Y+hw+fFjp6enauHGjfvjhBz3wwAOaM2eOLUimpKRo27Ztevfdd9WmTRtNnz5dubm5ioiIqHJOVqtV9957r/z8/LRz506VlJRUul6wJnNLTEzUvn37lJGRoc2bN0uSfHx8JEn333+/PD099cEHH8jHx0evvPKKBgwYoIMHD6ply5bV/q5mzZqlBQsWKCgoSA8//LD++7//W15eXnrppZfUrFkzPfDAA5o5c6aWLFkiSTpz5oySkpK0cOFCGYahefPm6a677tKhQ4fk5eWlv//973rxxRe1Zs0ade/eXYWFhdUG0blz52ru3Ln66KOP1KdPHxUUFGjEiBGaO3euhg0bpjNnzuiTTz4RXwkPmJABALVQWlpquLu7G8uXL6/xNs8//7wRGRlp+3nWrFlGs2bNjNLSUltbSkqKER0dbRiGYZw5c8Zwc3Mz3n77bdv73333neHp6WlMnDjR1hYcHGy8+OKLhmEYxocffmi4uLgYx48ft73/wQcfGJKM9evX12pu4eHhlfp88sknhre3t/HTTz9Var/pppuMV155pcpxjxw5YkgyXn31VVvbW2+9ZUgysrKybG1paWlGly5dqp1fRUWF4eXlZbz33nuGYRjGvHnzjM6dOxvnz5+vsv/F38kTTzxhtG3b1ti3b5/tvZycHEOS8e2331ZbD4A5sAIIoFb279+v8vJyDRgwoNo+a9eu1csvv6zDhw/r7Nmz+s9//iNvb+9KfUJCQuTl5WX7uW3btiouLpb08+rg+fPnFR0dbXu/ZcuW6tKlyxXnFRgYqICAAFtbTExMneZ2qS+++EJnz55Vq1atKrX/+OOPOnz48BW37dmzp+3Pfn5+kqSwsLBKbRf3W5KKioo0Y8YMbd26VcXFxaqoqNC5c+eUn58v6eeVyAULFqhjx44aNGiQ7rrrLt19991ycfn/v87nzZunsrIy7d69Wx07drS1h4eHa8CAAQoLC1N8fLwGDhyo3/zmN7rxxhuvuA8Arj9cAwigVjw9Pa/4fnZ2tkaOHKm77rpLGzdu1J49e/Tkk0/q/Pnzlfq5urpW+tlischqtdp9vnWZ26XOnj2rtm3bKi8vr9LrwIEDSklJueK2v9xPi8VSZdsv9zspKUl5eXl66aWXtH37duXl5alVq1a2OQYGBurAgQP685//LE9PT/3+97/X7bffXukay9tuu00VFRV6++23K83F2dlZmZmZ+uCDD9StWzctXLhQXbp00ZEjR67ymwNwvSEAAqiV0NBQeXp62m4quNT27dsVHBysJ598UlFRUQoNDdXRo0drVeOmm26Sq6urdu7caWv74YcfdPDgwWq3ufnmm3Xs2DEVFBTY2nbs2FHrubm5uamioqJSW69evVRYWCgXFxd16tSp0svX17dW+3Y1n332mSZMmKC77rpL3bt3l7u7u06dOlWpj6enp+6++269/PLL2rp1q7Kzs7V3717b+3369NEHH3ygZ599Vi+88EKlbS0Wi/r27avZs2drz549cnNz0/r16+26DwAaP04BA6gVDw8PTZkyRU888YTc3NzUt29fnTx5Ul999ZXGjBmj0NBQ5efna82aNerdu7c2bdpU64Bxww03aMyYMUpJSVGrVq3Upk0bPfnkk3Jyqv7frHFxcercubOSkpL0/PPPq7S0VE8++WSlPjWZW0hIiI4cOaK8vDy1b99eXl5eiouLU0xMjBISEjR37lx17txZJ06c0KZNmzRs2DBFRUXVav+uJDQ0VCtXrlRUVJRKS0uVkpJSadX1jTfeUEVFhaKjo9WsWTOtWrVKnp6eCg4OrjRObGys3n//fQ0ePFguLi6aNGmSdu7cqaysLA0cOFBt2rTRzp07dfLkSd188812mz+ApoEVQAC1lpqaqscff1wzZ87UzTffrMTERNt1bEOHDtXkyZM1btw4RUREaPv27UpNTa11jeeff1633Xab7r77bsXFxalfv36KjIystr+Tk5PWr1+vH3/8UX369NEjjzxS6dE0NZ3bfffdp0GDBumOO+5Q69at9dZbb8lisej999/X7bffrtGjR6tz584aPny4jh49aruuz15WrFihH374Qb169dKDDz6oCRMmqE2bNrb3W7RooeXLl6tv377q2bOnNm/erPfee++y6xMlqV+/ftq0aZNmzJihhQsXytvbWx9//LHuuusude7cWTNmzNC8efM0ePBgu+4DgMbPYhjc/w8AAGAmrAACAACYDAEQAADAZAiAAAAAJkMABAAAMBkCIAAAgMkQAAEAAEyGAAgAAGAyBEAAAACTIQACAACYDAEQAADAZAiAAAAAJkMABAAAMJn/A1cXgnhL9+q3AAAAAElFTkSuQmCC", "text/html": [ "\n", "