{ "cells": [ { "cell_type": "markdown", "id": "66c30004-cd2c-4d34-9999-f33f9e6fd5e9", "metadata": {}, "source": [ "# RPA-based reverse-engineering\n", "This notebook showcases the RPA-based reverse-engineering technique for scalar multipliers.\n", "\n", " - [Exploration](#Exploration)\n", " - [Reverse-engineering](#Reverse-engineering)\n", " - [Oracle simulation](#Oracle-simulation)\n", " - [Symmetric noise](#What-about-(symmetric)-noise?)\n", " - [Asymmetric noise](#What-about-(asymmetric)-noise?)\n", " - [Method simulation](#Method-simulation)" ] }, { "cell_type": "code", "id": "11a5f41d-1471-49c3-ba7c-ac87470a31d0", "metadata": {}, "source": [ "import numpy as np\n", "import xarray as xr\n", "import holoviews as hv\n", "import matplotlib.pyplot as plt\n", "from scipy.signal import find_peaks\n", "from functools import partial\n", "from scipy.stats import bernoulli\n", "\n", "from IPython.display import HTML, display\n", "from tqdm.auto import tqdm\n", "import tabulate\n", "\n", "from pyecsca.ec.model import ShortWeierstrassModel\n", "from pyecsca.ec.coordinates import AffineCoordinateModel\n", "from pyecsca.ec.curve import EllipticCurve\n", "from pyecsca.ec.params import DomainParameters, get_params\n", "from pyecsca.ec.formula import FormulaAction\n", "from pyecsca.ec.point import Point\n", "from pyecsca.ec.mod import mod\n", "from pyecsca.ec.mult import *\n", "from pyecsca.misc.utils import silent, TaskExecutor\n", "from pyecsca.sca.trace.process import normalize\n", "from pyecsca.sca.trace.combine import average, subtract\n", "from pyecsca.sca.attack.leakage_model import HammingWeight, NormalNoice\n", "from pyecsca.ec.context import DefaultContext, local\n", "from pyecsca.sca.re.rpa import MultipleContext, rpa_distinguish, RPA\n", "from pyecsca.sca.trace import Trace\n", "\n", "from eval import (eval_tree_symmetric, eval_tree_asymmetric,\n", " success_rate_symmetric, success_rate_asymmetric,\n", " query_rate_symmetric, query_rate_asymmetric,\n", " precise_rate_symmetric, precise_rate_asymmetric,\n", " amount_rate_symmetric, amount_rate_asymmetric,\n", " success_rate_vs_majority_symmetric, success_rate_vs_majority_asymmetric,\n", " success_rate_vs_query_rate_symmetric, load, store)" ], "outputs": [], "execution_count": null }, { "cell_type": "code", "id": "3ff1d591-f922-4c94-9e47-ab053fc21cf1", "metadata": {}, "source": [ "%matplotlib ipympl\n", "hv.extension(\"bokeh\")" ], "outputs": [], "execution_count": null }, { "cell_type": "code", "id": "194fff59-1c4b-473a-9ffc-99b256aecc24", "metadata": {}, "source": [ "model = ShortWeierstrassModel()\n", "coordsaff = AffineCoordinateModel(model)\n", "coords = model.coordinates[\"projective\"]\n", "add = coords.formulas[\"add-2007-bl\"] # The formulas are irrelevant for this method\n", "dbl = coords.formulas[\"dbl-2007-bl\"]\n", "neg = coords.formulas[\"neg\"]\n", "\n", "# A 64-bit prime order curve for testing things out\n", "p = 0xc50de883f0e7b167\n", "a = mod(0x4833d7aa73fa6694, p)\n", "b = mod(0xa6c44a61c5323f6a, p)\n", "gx = mod(0x5fd1f7d38d4f2333, p)\n", "gy = mod(0x21f43957d7e20ceb, p)\n", "n = 0xc50de885003b80eb\n", "h = 1\n", "\n", "# A (0, y) RPA point on the above curve, in affine coords.\n", "P0_aff = Point(coordsaff, x=mod(0, p), y=mod(0x1742befa24cd8a0d, p))\n", "\n", "infty = Point(coords, X=mod(0, p), Y=mod(1, p), Z=mod(0, p))\n", "g = Point(coords, X=gx, Y=gy, Z=mod(1, p))\n", "\n", "curve = EllipticCurve(model, coords, p, infty, dict(a=a,b=b))\n", "params = DomainParameters(curve, g, n, h)\n", "\n", "# And P-256 for eval\n", "p256 = get_params(\"secg\", \"secp256r1\", \"projective\")" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "id": "29fb8683-bcad-4a3e-869b-95f0e4b7bde3", "metadata": {}, "source": [ "## Exploration\n", "First select a bunch of multipliers. We will be trying to distinguish among these." ] }, { "cell_type": "code", "id": "febb198f-4370-4abd-8edc-17c5c1da8d0f", "metadata": {}, "source": [ "multipliers = [\n", " LTRMultiplier(add, dbl, None, False, AccumulationOrder.PeqPR, True, True),\n", " LTRMultiplier(add, dbl, None, True, AccumulationOrder.PeqPR, True, True),\n", " RTLMultiplier(add, dbl, None, False, AccumulationOrder.PeqPR, True),\n", " RTLMultiplier(add, dbl, None, True, AccumulationOrder.PeqPR, False),\n", " SimpleLadderMultiplier(add, dbl, None, True, True),\n", " BinaryNAFMultiplier(add, dbl, neg, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True),\n", " BinaryNAFMultiplier(add, dbl, neg, None, ProcessingDirection.RTL, AccumulationOrder.PeqPR, True),\n", " WindowNAFMultiplier(add, dbl, neg, 3, None, AccumulationOrder.PeqPR, True, True),\n", " WindowNAFMultiplier(add, dbl, neg, 4, None, AccumulationOrder.PeqPR, True, True),\n", " WindowNAFMultiplier(add, dbl, neg, 5, None, AccumulationOrder.PeqPR, True, True),\n", " WindowBoothMultiplier(add, dbl, neg, 3, None, AccumulationOrder.PeqPR, True, True),\n", " WindowBoothMultiplier(add, dbl, neg, 4, None, AccumulationOrder.PeqPR, True, True),\n", " WindowBoothMultiplier(add, dbl, neg, 5, None, AccumulationOrder.PeqPR, True, True),\n", " SlidingWindowMultiplier(add, dbl, 3, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True),\n", " SlidingWindowMultiplier(add, dbl, 4, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True),\n", " SlidingWindowMultiplier(add, dbl, 5, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True),\n", " SlidingWindowMultiplier(add, dbl, 3, None, ProcessingDirection.RTL, AccumulationOrder.PeqPR, True),\n", " SlidingWindowMultiplier(add, dbl, 4, None, ProcessingDirection.RTL, AccumulationOrder.PeqPR, True),\n", " SlidingWindowMultiplier(add, dbl, 5, None, ProcessingDirection.RTL, AccumulationOrder.PeqPR, True),\n", " FixedWindowLTRMultiplier(add, dbl, 3, None, AccumulationOrder.PeqPR, True),\n", " FixedWindowLTRMultiplier(add, dbl, 4, None, AccumulationOrder.PeqPR, True),\n", " FixedWindowLTRMultiplier(add, dbl, 5, None, AccumulationOrder.PeqPR, True),\n", " FixedWindowLTRMultiplier(add, dbl, 8, None, AccumulationOrder.PeqPR, True),\n", " FixedWindowLTRMultiplier(add, dbl, 16, None, AccumulationOrder.PeqPR, True),\n", " FullPrecompMultiplier(add, dbl, None, True, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True, True),\n", " FullPrecompMultiplier(add, dbl, None, False, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True, True),\n", " BGMWMultiplier(add, dbl, 2, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True),\n", " BGMWMultiplier(add, dbl, 3, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True),\n", " BGMWMultiplier(add, dbl, 4, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True),\n", " BGMWMultiplier(add, dbl, 5, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True),\n", " CombMultiplier(add, dbl, 2, None, AccumulationOrder.PeqPR, True),\n", " CombMultiplier(add, dbl, 3, None, AccumulationOrder.PeqPR, True),\n", " CombMultiplier(add, dbl, 4, None, AccumulationOrder.PeqPR, True),\n", " CombMultiplier(add, dbl, 5, None, AccumulationOrder.PeqPR, True)\n", "]\n", "print(len(multipliers))" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "id": "bea70d56-1359-423b-9273-fae5877f6400", "metadata": {}, "source": [ "Then select a random scalar and simulate computation using all of the multipliers, track the multiples, print the projective and affine results." ] }, { "cell_type": "code", "id": "44e7e2e9-e0ad-4c8d-8605-af68f73d73e2", "metadata": {}, "source": [ "scalar = 0b1000000000000000000000000000000000000000000000000\n", "scalar = 0b1111111111111111111111111111111111111111111111111\n", "scalar = 0b1010101010101010101010101010101010101010101010101\n", "scalar = 0b1111111111111111111111110000000000000000000000000\n", "scalar = 123456789123456789\n", "# multiples is a mapping from a multiple (integer) to a set of scalar multipliers that compute said multiple when doing [scalar]P\n", "multiples = {}\n", "\n", "table = [[\"Multiplier\", \"multiples\"]]\n", "\n", "for mult in multipliers:\n", " with local(MultipleContext()) as ctx:\n", " mult.init(params, g)\n", " res = mult.multiply(scalar)\n", " for m in ctx.points.values():\n", " s = multiples.setdefault(m, set())\n", " s.add(mult)\n", " table.append([str(mult), str(list(ctx.points.values()))])\n", "\n", "display(HTML(tabulate.tabulate(table, tablefmt=\"html\", headers=\"firstrow\")))" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "id": "ba284641-c29b-4e42-95ec-aa630e305b10", "metadata": {}, "source": [ "Pick a multiple `k` that is computed by some multiplier for the scalar,\n", "invert it mod n, and do `[k^-1]P0` to obtain a point `P0_target`,\n", "such that, `[k]P0_target = P0` and `P0` has a zero coordinate." ] }, { "cell_type": "code", "id": "a7fb8a3f-7938-493b-88dc-582ba4d8959d", "metadata": {}, "source": [ "k = 108\n", "kinv = mod(k, n).inverse()\n", "P0_target = curve.affine_multiply(P0_aff, int(kinv)).to_model(coords, curve)\n", "\n", "print(\"Original P0\", P0_aff)\n", "print(\"P0_target \", P0_target.to_affine())\n", "print(\"Verify P0 \", curve.affine_multiply(P0_target.to_affine(), k))" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "id": "914a67a5-b6a2-4d02-811f-a423099853c3", "metadata": {}, "source": [ "Now go over the multipliers with P0_target and the original scalar as input.\n", "Then look whether a zero coordinate point was computed.\n", "Also look at whether the multiple \"k\" was computed. These two should be the same." ] }, { "cell_type": "code", "id": "8113cb3f-dc06-4cb7-955c-11cedb4fbdd7", "metadata": {}, "source": [ "table = [[\"Multiplier\", \"zero present\", \"multiple computed\"]]\n", "\n", "for mult in multipliers:\n", " with local(MultipleContext()) as ctx:\n", " mult.init(params, P0_target)\n", " res = mult.multiply(scalar)\n", " zero = any(map(lambda P: P.X == 0 or P.Y == 0, ctx.points.keys()))\n", " multiple = k in ctx.points.values()\n", " table.append([str(mult), f\"{zero}\" if zero else zero, f\"{multiple}\" if multiple else multiple])\n", "\n", "display(HTML(tabulate.tabulate(table, tablefmt=\"unsafehtml\", headers=\"firstrow\", colalign=(\"left\", \"center\", \"center\"))))" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "id": "0d2b9fe8-5064-4887-b782-dcfe9f42d217", "metadata": {}, "source": [ "Now lets look at the relation of multiples to multipliers." ] }, { "cell_type": "code", "id": "67d7705c-6a41-47d9-ad1e-23ea549aaf00", "metadata": { "scrolled": true }, "source": [ "table = [[\"Multiple\", \"Multipliers\"]]\n", "for multiple, mults in multiples.items():\n", " table.append([bin(multiple), [mult.__class__.__name__ for mult in mults]])\n", "\n", "display(HTML(tabulate.tabulate(table, tablefmt=\"html\", headers=\"firstrow\")))" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "id": "9b8e4338-a2a8-468e-8873-c18c77260cfc", "metadata": {}, "source": [ "Note that all of the exploration so far was in a context of a fixed scalar. Even though for a given scalar some multipliers might be indistinguishable from the perspective of the multiples they compute, there may be other scalars that distinguish them." ] }, { "cell_type": "markdown", "id": "e9015f3c-fba6-4614-b722-848b8522d072", "metadata": {}, "source": [ "## Reverse-engineering\n", "\n", "### Oracle simulation\n", "The `simulated_oracle` function simulates an RPA oracle that detect a zero coordinate point in the scalar multiplication.\n", "This can be used by the `rpa_distinguish` function to distinguish the true scalar multiplier. The oracle is parametrized with the simulated multiplier index in the table of multipliers (it simulates this \"real\" multiplier). Furthermore, lets also examine a `noisy_oracle` (with a flip probability) and a `biased_oracle` (with asymmetric flip probability).\n", "\n", "Note that the oracle has two additional parameters `measure_init` and `measure_multiply` which determine whether the oracle considers the zero coordinate point in scalar multiplier initialization (precomputation) and in scalar multiplier multiplication, respectively. This is important for scalar multipliers with precomputation as there one might be able to separate the precomputation and multiplication stages and obtain oracle answers on both separately." ] }, { "cell_type": "code", "id": "9bb61ac5-d837-4287-a5de-a9a63c346acf", "metadata": {}, "source": [ "def simulated_oracle(scalar, affine_point, simulate_mult_id=0, measure_init=True, measure_multiply=True, randomize=False):\n", " real_mult = multipliers[simulate_mult_id]\n", " point = affine_point.to_model(params.curve.coordinate_model, params.curve, randomized=randomize)\n", " \n", " # Simulate the multiplier init\n", " with local(MultipleContext()) as ctx:\n", " real_mult.init(params, point)\n", " init_points = set(ctx.parents.keys())\n", " init_parents = set(sum((ctx.parents[point] for point in init_points), []))\n", " # Did zero happen in some input point during the init?\n", " init_zero = any(map(lambda P: P.X == 0 or P.Y == 0, init_parents))\n", " \n", " # Simulate the multiplier multiply\n", " with local(ctx) as ctx:\n", " real_mult.multiply(scalar)\n", " all_points = set(ctx.parents.keys())\n", " multiply_parents = set(sum((ctx.parents[point] for point in all_points - init_points), []))\n", " # Did zero happen in some input point during the multiply?\n", " multiply_zero = any(map(lambda P: P.X == 0 or P.Y == 0, multiply_parents))\n", " real_result = (init_zero and measure_init) or (multiply_zero and measure_multiply)\n", " return real_result\n", "\n", "def noisy_oracle(oracle, flip_proba=0):\n", " def noisy(*args, **kwargs):\n", " real_result = oracle(*args, **kwargs)\n", " change = bernoulli(flip_proba).rvs()\n", " return bool(real_result ^ change)\n", " return noisy\n", "\n", "def biased_oracle(oracle, flip_0=0, flip_1=0):\n", " def biased(*args, **kwargs):\n", " real_result = oracle(*args, **kwargs)\n", " change = bernoulli(flip_1).rvs() if real_result else bernoulli(flip_0).rvs()\n", " return bool(real_result ^ change)\n", " return biased" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "id": "c1ad6235-52bf-4bab-90ff-55b67165390e", "metadata": {}, "source": [ "We can see how the RPA-RE method distinguishes a given multiplier:" ] }, { "cell_type": "code", "id": "b6c70d89-1c7d-4d7c-bc65-0cf766b86c0a", "metadata": { "scrolled": true }, "source": [ "res = rpa_distinguish(params, multipliers, simulated_oracle)" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "id": "6361c477-5ddf-46ff-8918-864a453b676b", "metadata": {}, "source": [ "Let's see if the result is correct. You can replace the `simulated_oracle` above with `noisy_oracle(simulated_oracle, flip_proba=0.2)` or with `biased_oracle(simulated_oracle, flip_0=0.2, flip_1=0.1)` to see how the process and result changes with noise." ] }, { "cell_type": "code", "id": "1556e604-d0ab-403c-bd44-d53be8e18283", "metadata": {}, "source": [ "print(multipliers[0] in res)" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "id": "4cecc7dc-f609-4073-b0db-ac9631ac3edf", "metadata": {}, "source": [ "We can also have a look at the distinguishing tree that the method builds for this set of multipliers." ] }, { "cell_type": "code", "id": "838ae83b-771d-4e4c-8977-ba997aa4fbb6", "metadata": {}, "source": [ "re = RPA(set(multipliers))\n", "with silent():\n", " re.build_tree(p256, tries=10)\n", "print(re.tree.describe())" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "id": "9a99df60-f6ec-40e1-a43a-d95eb64beb8c", "metadata": {}, "source": [ "We can also look at the rough tree structure." ] }, { "cell_type": "code", "id": "136000fe-4a4a-4261-bacc-a49102080749", "metadata": {}, "source": [ "print(re.tree.render_basic())" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "id": "068e1ba5-9884-4d2b-97f6-d54313daddad", "metadata": {}, "source": [ "#### What about (symmetric) noise?\n", "Now we can examine how the method performs in the presence of noise and with various majority vote parameters. The cells with the `store` and `load` calls can be used to store the results so that different plots can be printed without re-running the evaluation.\n", "\n", "