aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pyecsca/ec/countermeasures.py118
-rw-r--r--test/ec/test_countermeasures.py79
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