{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Emulation and leakage simulation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook demonstrates the functionality of the `EmulatorTarget` class, which can emulate **pyecsca** generated C implementations for `STM32F3` target using Rainbow as a basis as well as simulate side-channel leakage. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initialisation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pyecsca.ec.mult import LTRMultiplier\n", "from pyecsca.ec.mod import Mod\n", "from pyecsca.ec.point import Point, InfinityPoint\n", "from pyecsca.ec.model import ShortWeierstrassModel\n", "from pyecsca.ec.curve import EllipticCurve\n", "from pyecsca.ec.params import DomainParameters\n", "from pyecsca.ec.key_generation import KeyGeneration\n", "from pyecsca.ec.key_agreement import ECDH_SHA1\n", "from pyecsca.ec.configuration import *\n", "from pyecsca.codegen.client import EmulatorTarget\n", "from pyecsca.codegen.common import Platform\n", "from pyecsca.codegen.common import DeviceConfiguration\n", "from pyecsca.codegen.builder import render\n", "from pyecsca.sca.trace import Trace\n", "from pyecsca.sca.trace.plot import plot_trace\n", "from pyecsca.sca.trace.process import rolling_mean\n", "\n", "\n", "from rainbow import TraceConfig\n", "from rainbow.leakage_models import HammingWeight\n", "\n", "from binascii import hexlify\n", "from random import randbytes, randint\n", "import numpy as np\n", "import holoviews as hv\n", "from subprocess import run\n", "from os.path import join\n", "from copy import copy " ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We first define the elliptic curve parameters we are going to be using for the demonstration." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model = ShortWeierstrassModel()\n", "coords = model.coordinates[\"projective\"]\n", "p = 0xd7d1247f\n", "a = Mod(0xa4a44016, p)\n", "b = Mod(0x73f76716, p)\n", "n = 0xd7d2a475\n", "h = 1\n", "gx, gy, gz = Mod(0x54eed6d7, p), Mod(0x6f1e55ac, p), Mod(1, p)\n", "generator = Point(coords, X=gx, Y=gy, Z=gz)\n", "neutral = InfinityPoint(coords)\n", "\n", "curve = EllipticCurve(model, coords, p, neutral, {\"a\": a, \"b\": b})\n", "params = DomainParameters(curve, generator, n, h)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We create and initialize an instance of the `EmulatorTarget` class with the above EC parameters and the `TraceConfig` class instance, which configures the simulated leakage trace to contain the Hamming Weight of the emulator's register values. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "target = EmulatorTarget(model, coords, trace_config=TraceConfig(register=HammingWeight()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We generate code and build it using **pyecsca** (for more details see `codegen.ipynb` notebook) and load the resulting binary into the emulator. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "platform = Platform.STM32F3\n", "hash_type = HashType.SHA1\n", "mod_rand = RandomMod.REDUCE\n", "mult = Multiplication.BASE\n", "sqr = Squaring.BASE\n", "red = Reduction.BASE\n", "inv = Inversion.GCD\n", "\n", "model = ShortWeierstrassModel()\n", "coords = model.coordinates[\"projective\"]\n", "add = coords.formulas[\"add-1998-cmo\"]\n", "dbl = coords.formulas[\"dbl-1998-cmo\"]\n", "scl = coords.formulas[\"z\"]\n", "formulas = [add, dbl, scl]\n", "scalarmult = LTRMultiplier(add, dbl, scl)\n", "\n", "config = DeviceConfiguration(model, coords, formulas, scalarmult, \n", "\t\t\t\t\t\t\t hash_type, mod_rand, mult, sqr, red,\n", "\t\t\t\t\t\t\t inv, platform, True, True, True)\n", "\n", "directory, elf_name, hex_name = render(config)\n", "\n", "run([\"make\"], cwd=directory)\n", "join(directory, hex_name)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "target.connect(binary=join(directory, elf_name))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For the emulated functions to work correctly, we need to set the parameters of the curve in the emulator." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "target.set_params(params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Emulator functionality" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scalar multiplication" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Perform scalar multiplication on given point with given scalar and compare with pyecsca." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "scalar = randint(128, 255)\n", "point = params.curve.affine_random().to_model(coords, params.curve)\n", "emulatorResult = target.scalar_mult(scalar, point)\n", "print(emulatorResult)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We use pyecsca to validate correctness of the emulator result. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "generator = params.generator\n", "model = params.curve.model\n", "coords = params.curve.coordinate_model\n", "add = coords.formulas[\"add-1998-cmo\"]\n", "dbl = coords.formulas[\"dbl-1998-cmo\"]\n", "scl = coords.formulas[\"z\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mult_sm = LTRMultiplier(add, dbl, scl)\n", "mult_sm.init(params, point)\n", "\n", "pyecscaResult = mult_sm.multiply(scalar)\n", "print(pyecscaResult)\n", "print(emulatorResult.equals(pyecscaResult))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Key generation" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Generate private and public key." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "seed_bytes = randbytes(32)\n", "target.init_prng(seed_bytes)\n", "priv, pub = target.generate()\n", "pub = pub.to_model(coords, params.curve)\n", "\n", "print(\"private key:\", priv)\n", "print(\"public key:\", pub)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We check if we generated valid key pair using pyecsca." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(params.curve.is_on_curve(pub))\n", "pyecscaPub = params.curve.affine_multiply(params.generator.to_affine(), priv).to_model(coords, params.curve)\n", "print(pyecscaPub)\n", "print(pub.equals(pyecscaPub))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Setting private and public key" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "In order to emulate **ECDH** and **ECDSA** algorithms, the emulator needs private and public keys set. This can be done by methods below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Before private:\", target.privkey)\n", "print(\"Before public:\", target.pubkey)\n", "\n", "target.set_privkey(priv)\n", "target.set_pubkey(pub)\n", "\n", "print(\"After private:\", target.privkey)\n", "print(\"After public:\", target.pubkey)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ECDH" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Perform key agreement using ECDH." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "other_priv, other_pub = target.generate()\n", "other_pub = other_pub.to_model(coords, params.curve)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "shared_secret = target.ecdh(pub)\n", "print(\"shared secret:\", hexlify(shared_secret))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Check the result is correct using pyecsca." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mult_ecdh = LTRMultiplier(add, dbl, scl)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ecdh_a = ECDH_SHA1(copy(mult_ecdh), params, pub, other_priv)\n", "ecdh_b = ECDH_SHA1(copy(mult_ecdh), params, other_pub, priv)\n", "ecdh_a_result = ecdh_a.perform()\n", "ecdh_b_result = ecdh_b.perform()\n", "print(hexlify(ecdh_a_result))\n", "print(hexlify(ecdh_b_result)) \n", "print(ecdh_a_result == ecdh_b_result == shared_secret)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ECDSA" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Perform signing over given data and verify the signature." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "message = \"text\"\n", "signed_message = target.ecdsa_sign(message.encode())\n", "res = target.ecdsa_verify(message.encode(), bytes(signed_message))\n", "print(res)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "message = \"text1\"\n", "signed_message = target.ecdsa_sign(message.encode())\n", "message = \"text2\"\n", "res = target.ecdsa_verify(message.encode(), bytes(signed_message))\n", "print(res)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Leakage simulation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While the `EmulatorTarget` performs the above methods, it simulates leakage. The leakage trace is stored in `self.trace` variable. In our case, the trace will contain dictionaries of type `{\"type\": \"code\", \"register\": x}`, where `x` is Hamming Weight of the current register value. For other configurations of the trace, see https://github.com/Ledger-Donjon/rainbow" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Leakage trace of scalar multiplication" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We perform scalar multiplication and look at the sample of the leakage trace in `EmulatorTarget`'s `trace` variable." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "scalar = 229\n", "point = params.curve.affine_random().to_model(coords, params.curve)\n", "target.trace = []\n", "emulatorResult = target.scalar_mult(scalar, point)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(target.trace[0:10])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To use the pyecsca's functionality of working with leakage traces, we transform the trace from dictionary to pyecsca's `Trace` using `EmulatorTarget`'s `process_trace` method. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "trace = target.transform_trace()\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(trace[0:10])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now visualize what the whole trace looks like." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hv.extension(\"bokeh\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot_trace(trace).opts(width=950, height=600)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### SPA" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now analyze the trace and try to gain information about the execution of the algorithm and/or recover the secret scalar." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We apply rolling mean to the trace to smooth it out, reduce noise and make the actions executed during the algorithm better identifiable." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "spa_trace = rolling_mean(trace, 3000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see repeated patterns in the resulting trace. Since the emulation consists of performing either point addition or point doubling repeadetly, we can map their execution to the repeated patterns. We know that the doubling operation will be performed for each bit of the scalar, while the addition operation will be performed only when the currently processed bit of the scalar is equal to one. Using this knowledge, we can see in the trace that the order of executed operations is as follows: *dbl-add-dbl-add-dbl-dbl-dbl-add-dbl-dbl-add*. This means that the scalar used equals *11100101 = 229*." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot_trace(spa_trace).opts(width=950, height=600)" ] } ], "metadata": { "kernelspec": { "display_name": "venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.18" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }