diff options
| author | J08nY | 2025-03-10 18:13:21 +0100 |
|---|---|---|
| committer | J08nY | 2025-03-10 18:13:21 +0100 |
| commit | 65cf6216291214c1a62dfcd41cbe2ca47464bf46 (patch) | |
| tree | 04a7eae9e1dd73e778ee672605e257492c437bfb | |
| parent | d171c72d2983d0cdb77f0d83bc4c2f472701ddcc (diff) | |
| download | pyecsca-65cf6216291214c1a62dfcd41cbe2ca47464bf46.tar.gz pyecsca-65cf6216291214c1a62dfcd41cbe2ca47464bf46.tar.zst pyecsca-65cf6216291214c1a62dfcd41cbe2ca47464bf46.zip | |
| -rw-r--r-- | pyecsca/ec/countermeasures.py | 118 | ||||
| -rw-r--r-- | test/ec/test_countermeasures.py | 79 |
2 files changed, 197 insertions, 0 deletions
diff --git a/pyecsca/ec/countermeasures.py b/pyecsca/ec/countermeasures.py new file mode 100644 index 0000000..ee07c2b --- /dev/null +++ b/pyecsca/ec/countermeasures.py @@ -0,0 +1,118 @@ +"""Provides several countermeasures against side-channel attacks.""" + +from abc import ABC, abstractmethod +from typing import Optional + +from public import public + +from pyecsca.ec.formula import AdditionFormula +from pyecsca.ec.mod import Mod, mod +from pyecsca.ec.mult import ScalarMultiplier +from pyecsca.ec.params import DomainParameters +from pyecsca.ec.point import Point + + +@public +class ScalarMultiplierCountermeasure(ABC): + """ + A scalar multiplier-based countermeasure. + + This class behaves like a scalar multiplier, in fact it wraps one + and provides some scalar-splitting countermeasure. + """ + mult: ScalarMultiplier + params: Optional[DomainParameters] + point: Optional[Point] + + def __init__(self, mult: ScalarMultiplier): + self.mult = mult + + def init(self, params: DomainParameters, point: Point): + self.params = params + self.point = point + + @abstractmethod + def multiply(self, scalar: int) -> Point: + raise NotImplementedError + + +@public +class GroupScalarRandomization(ScalarMultiplierCountermeasure): + rand_bits: int + + def __init__(self, mult: ScalarMultiplier, rand_bits: int = 32): + super().__init__(mult) + self.rand_bits = rand_bits + + def multiply(self, scalar: int) -> Point: + self.mult.init(self.params, self.point) + order = self.params.order + mask = int(Mod.random(1 << self.rand_bits)) + masked_scalar = scalar + mask * order + return self.mult.multiply(masked_scalar) + +@public +class AdditiveSplitting(ScalarMultiplierCountermeasure): + add: Optional[AdditionFormula] + + def __init__(self, mult: ScalarMultiplier, add: Optional[AdditionFormula] = None): + super().__init__(mult) + self.add = add + + def multiply(self, scalar: int) -> Point: + self.mult.init(self.params, self.point) + + order = self.params.order + r = Mod.random(order) + s = scalar - r + R = self.mult.multiply(int(r)) + S = self.mult.multiply(int(s)) + if self.add is None: + return self.mult._add(R, S) # noqa: This is OK. + else: + return self.add(self.params.curve.prime, R, S, **self.params.curve.parameters)[0] + + +@public +class MultiplicativeSplitting(ScalarMultiplierCountermeasure): + rand_bits: int + + def __init__(self, mult: ScalarMultiplier, rand_bits: int = 32): + super().__init__(mult) + self.rand_bits = rand_bits + + def multiply(self, scalar: int) -> Point: + self.mult.init(self.params, self.point) + r = Mod.random(1 << self.rand_bits) + R = self.mult.multiply(int(r)) + + self.mult.init(self.params, R) + kr_inv = scalar * mod(int(r), self.params.order).inverse() + return self.mult.multiply(int(kr_inv)) + +@public +class EuclideanSplitting(ScalarMultiplierCountermeasure): + add: Optional[AdditionFormula] + + def __init__(self, mult: ScalarMultiplier, add: Optional[AdditionFormula] = None): + super().__init__(mult) + self.add = add + + def multiply(self, scalar: int) -> Point: + order = self.params.order + half_bits = order.bit_length() // 2 + r = Mod.random(1 << half_bits) + self.mult.init(self.params, self.point) + R = self.mult.multiply(int(r)) + + k1 = scalar % int(r) + k2 = scalar // int(r) + self.mult.init(self.params, R) + S = self.mult.multiply(k2) + + self.mult.init(self.params, self.point) + T = self.mult.multiply(k1) + if self.add is None: + return self.mult._add(S, T) # noqa: This is OK. + else: + return self.add(self.params.curve.prime, S, T, **self.params.curve.parameters)[0]
\ No newline at end of file diff --git a/test/ec/test_countermeasures.py b/test/ec/test_countermeasures.py new file mode 100644 index 0000000..35ac5e8 --- /dev/null +++ b/test/ec/test_countermeasures.py @@ -0,0 +1,79 @@ +import pytest + +from pyecsca.ec.mult import LTRMultiplier +from pyecsca.ec.countermeasures import GroupScalarRandomization, AdditiveSplitting, MultiplicativeSplitting, \ + EuclideanSplitting + + +@pytest.fixture(params=["add-1998-cmo-2", "add-2015-rcb"]) +def add(secp128r1, request): + return secp128r1.curve.coordinate_model.formulas[request.param] + + +@pytest.fixture(params=["dbl-1998-cmo-2", "dbl-2015-rcb"]) +def dbl(secp128r1, request): + return secp128r1.curve.coordinate_model.formulas[request.param] + +@pytest.fixture() +def mult(secp128r1, add, dbl): + return LTRMultiplier(add, dbl, complete=False) + +@pytest.mark.parametrize( + "num", [325385790209017329644351321912443757746, + 123456789314159265358979323846264338327, + 987654321314159265358979323846264338327, + 786877845665557891354654531354008066400] +) +def test_group_scalar_rand(mult, secp128r1, num): + mult.init(secp128r1, secp128r1.generator) + raw = mult.multiply(num) + + gsr = GroupScalarRandomization(mult) + gsr.init(secp128r1, secp128r1.generator) + masked = gsr.multiply(num) + assert raw.equals(masked) + +@pytest.mark.parametrize( + "num", [325385790209017329644351321912443757746, + 123456789314159265358979323846264338327, + 987654321314159265358979323846264338327, + 786877845665557891354654531354008066400] +) +def test_additive_splitting(mult, secp128r1, num): + mult.init(secp128r1, secp128r1.generator) + raw = mult.multiply(num) + + asplit = AdditiveSplitting(mult) + asplit.init(secp128r1, secp128r1.generator) + masked = asplit.multiply(num) + assert raw.equals(masked) + +@pytest.mark.parametrize( + "num", [325385790209017329644351321912443757746, + 123456789314159265358979323846264338327, + 987654321314159265358979323846264338327, + 786877845665557891354654531354008066400] +) +def test_multiplicative_splitting(mult, secp128r1, num): + mult.init(secp128r1, secp128r1.generator) + raw = mult.multiply(num) + + msplit = MultiplicativeSplitting(mult) + msplit.init(secp128r1, secp128r1.generator) + masked = msplit.multiply(num) + assert raw.equals(masked) + +@pytest.mark.parametrize( + "num", [325385790209017329644351321912443757746, + 123456789314159265358979323846264338327, + 987654321314159265358979323846264338327, + 786877845665557891354654531354008066400] +) +def test_euclidean_splitting(mult, secp128r1, num): + mult.init(secp128r1, secp128r1.generator) + raw = mult.multiply(num) + + esplit = EuclideanSplitting(mult) + esplit.init(secp128r1, secp128r1.generator) + masked = esplit.multiply(num) + assert raw.equals(masked)
\ No newline at end of file |
