Emulation and leakage simulation

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.

Initialisation

[ ]:
from pyecsca.ec.mult import LTRMultiplier
from pyecsca.ec.mod import Mod
from pyecsca.ec.point import Point, InfinityPoint
from pyecsca.ec.model import ShortWeierstrassModel
from pyecsca.ec.curve import EllipticCurve
from pyecsca.ec.params import DomainParameters
from pyecsca.ec.key_generation import KeyGeneration
from pyecsca.ec.key_agreement import ECDH_SHA1
from pyecsca.ec.configuration import *
from pyecsca.codegen.client import EmulatorTarget
from pyecsca.codegen.common import Platform
from pyecsca.codegen.common import DeviceConfiguration
from pyecsca.codegen.builder import render
from pyecsca.sca.trace import Trace
from pyecsca.sca.trace.plot import plot_trace
from pyecsca.sca.trace.process import rolling_mean


from rainbow import TraceConfig
from rainbow.leakage_models import HammingWeight

from binascii import hexlify
from random import randbytes, randint
import numpy as np
import holoviews as hv
from subprocess import run
from os.path import join
from copy import copy

We first define the elliptic curve parameters we are going to be using for the demonstration.

[ ]:
model = ShortWeierstrassModel()
coords = model.coordinates["projective"]
p = 0xd7d1247f
a = Mod(0xa4a44016, p)
b = Mod(0x73f76716, p)
n = 0xd7d2a475
h = 1
gx, gy, gz = Mod(0x54eed6d7, p), Mod(0x6f1e55ac, p), Mod(1, p)
generator = Point(coords, X=gx, Y=gy, Z=gz)
neutral = InfinityPoint(coords)

curve = EllipticCurve(model, coords, p, neutral, {"a": a, "b": b})
params = DomainParameters(curve, generator, n, h)

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.

[ ]:
target = EmulatorTarget(model, coords, trace_config=TraceConfig(register=HammingWeight()))

We generate code and build it using pyecsca (for more details see codegen.ipynb notebook) and load the resulting binary into the emulator.

[ ]:
platform = Platform.STM32F3
hash_type = HashType.SHA1
mod_rand = RandomMod.REDUCE
mult = Multiplication.BASE
sqr = Squaring.BASE
red = Reduction.BASE
inv = Inversion.GCD

model = ShortWeierstrassModel()
coords = model.coordinates["projective"]
add = coords.formulas["add-1998-cmo"]
dbl = coords.formulas["dbl-1998-cmo"]
scl = coords.formulas["z"]
formulas = [add, dbl, scl]
scalarmult = LTRMultiplier(add, dbl, scl)

config = DeviceConfiguration(model, coords, formulas, scalarmult,
                                                     hash_type, mod_rand, mult, sqr, red,
                                                     inv, platform, True, True, True)

directory, elf_name, hex_name = render(config)

run(["make"], cwd=directory)
join(directory, hex_name)
[ ]:
target.connect(binary=join(directory, elf_name))

For the emulated functions to work correctly, we need to set the parameters of the curve in the emulator.

[ ]:
target.set_params(params)

Emulator functionality

Scalar multiplication

Perform scalar multiplication on given point with given scalar and compare with pyecsca.

[ ]:
scalar = randint(128, 255)
point = params.curve.affine_random().to_model(coords, params.curve)
emulatorResult = target.scalar_mult(scalar, point)
print(emulatorResult)

We use pyecsca to validate correctness of the emulator result.

[ ]:
generator = params.generator
model = params.curve.model
coords = params.curve.coordinate_model
add = coords.formulas["add-1998-cmo"]
dbl = coords.formulas["dbl-1998-cmo"]
scl = coords.formulas["z"]
[ ]:
mult_sm = LTRMultiplier(add, dbl, scl)
mult_sm.init(params, point)

pyecscaResult = mult_sm.multiply(scalar)
print(pyecscaResult)
print(emulatorResult.equals(pyecscaResult))

Key generation

Generate private and public key.

[ ]:
seed_bytes = randbytes(32)
target.init_prng(seed_bytes)
priv, pub = target.generate()
pub = pub.to_model(coords, params.curve)

print("private key:", priv)
print("public key:", pub)

We check if we generated valid key pair using pyecsca.

[ ]:
print(params.curve.is_on_curve(pub))
pyecscaPub = params.curve.affine_multiply(params.generator.to_affine(), priv).to_model(coords, params.curve)
print(pyecscaPub)
print(pub.equals(pyecscaPub))

Setting private and public key

In order to emulate ECDH and ECDSA algorithms, the emulator needs private and public keys set. This can be done by methods below.

[ ]:
print("Before private:", target.privkey)
print("Before public:", target.pubkey)

target.set_privkey(priv)
target.set_pubkey(pub)

print("After private:", target.privkey)
print("After public:", target.pubkey)

ECDH

Perform key agreement using ECDH.

[ ]:
other_priv, other_pub = target.generate()
other_pub = other_pub.to_model(coords, params.curve)
[ ]:
shared_secret = target.ecdh(pub)
print("shared secret:", hexlify(shared_secret))

Check the result is correct using pyecsca.

[ ]:
mult_ecdh = LTRMultiplier(add, dbl, scl)
[ ]:
ecdh_a = ECDH_SHA1(copy(mult_ecdh), params, pub, other_priv)
ecdh_b = ECDH_SHA1(copy(mult_ecdh), params, other_pub, priv)
ecdh_a_result = ecdh_a.perform()
ecdh_b_result = ecdh_b.perform()
print(hexlify(ecdh_a_result))
print(hexlify(ecdh_b_result))
print(ecdh_a_result == ecdh_b_result == shared_secret)

ECDSA

Perform signing over given data and verify the signature.

[ ]:
message = "text"
signed_message = target.ecdsa_sign(message.encode())
res = target.ecdsa_verify(message.encode(), bytes(signed_message))
print(res)
[ ]:
message = "text1"
signed_message = target.ecdsa_sign(message.encode())
message = "text2"
res = target.ecdsa_verify(message.encode(), bytes(signed_message))
print(res)

Leakage simulation

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

Leakage trace of scalar multiplication

We perform scalar multiplication and look at the sample of the leakage trace in EmulatorTarget’s trace variable.

[ ]:
scalar = 229
point = params.curve.affine_random().to_model(coords, params.curve)
target.trace = []
emulatorResult = target.scalar_mult(scalar, point)
[ ]:
print(target.trace[0:10])

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.

[ ]:
trace = target.transform_trace()

[ ]:
print(trace[0:10])

We can now visualize what the whole trace looks like.

[ ]:
hv.extension("bokeh")
[ ]:
plot_trace(trace).opts(width=950, height=600)

SPA

We can now analyze the trace and try to gain information about the execution of the algorithm and/or recover the secret scalar.

We apply rolling mean to the trace to smooth it out, reduce noise and make the actions executed during the algorithm better identifiable.

[ ]:
spa_trace = rolling_mean(trace, 3000)

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.

[ ]:
plot_trace(spa_trace).opts(width=950, height=600)