aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--docs/index.rst2
-rw-r--r--pyecsca/sca/re/rpa.py90
-rw-r--r--pyecsca/sca/re/zvp.py128
-rw-r--r--test/sca/test_zvp.py59
5 files changed, 260 insertions, 21 deletions
diff --git a/README.md b/README.md
index 473790c..e4e0155 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ It is currently in an alpha stage of development and thus only provides:
- [smartleia](https://pypi.org/project/smartleia/)
- **Faster arithmetic:**
- [gmpy2](https://gmpy2.readthedocs.io/) (and also GMP library)
-
+ - [cypari2](https://cypari2.readthedocs.io/) (and also PARI library)
*pyecsca* contains data from the [Explicit-Formulas Database](https://www.hyperelliptic.org/EFD/index.html) by Daniel J. Bernstein and Tanja Lange.
The data was partially changed, to make working with it easier. It is available on Github at [crocs-muni/efd](https://github.com/crocs-muni/efd).
diff --git a/docs/index.rst b/docs/index.rst
index 64e84bc..88b9292 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -154,6 +154,7 @@ Requirements
- **Faster arithmetic:**
- gmpy2_ (and also GMP library)
+ - cypari2_ (and also PARI library)
*pyecsca* contains data from the `Explicit-Formulas Database`_ by Daniel J. Bernstein and Tanja Lange.
The data was partially changed, to make working with it easier. It is available on Github at `crocs-muni/efd`_.
@@ -235,6 +236,7 @@ Development was supported by the Masaryk University grant `MUNI/C/1707/2018 <htt
.. _pyscard: https://pyscard.sourceforge.io/
.. _leia: https://pypi.org/project/smartleia/
.. _gmpy2: https://gmpy2.readthedocs.io/
+.. _cypari2: https://cypari2.readthedocs.io/
.. _pytest: https://pytest.org
.. _mypy: http://mypy-lang.org/
.. _flake8: https://flake8.pycqa.org/
diff --git a/pyecsca/sca/re/rpa.py b/pyecsca/sca/re/rpa.py
index 6d2218b..5bbd145 100644
--- a/pyecsca/sca/re/rpa.py
+++ b/pyecsca/sca/re/rpa.py
@@ -21,7 +21,11 @@ from ...ec.formula import (
LadderFormula,
)
from ...ec.mod import Mod
-from ...ec.mult import ScalarMultiplicationAction, PrecomputationAction, ScalarMultiplier
+from ...ec.mult import (
+ ScalarMultiplicationAction,
+ PrecomputationAction,
+ ScalarMultiplier,
+)
from ...ec.params import DomainParameters
from ...ec.model import ShortWeierstrassModel, MontgomeryModel
from ...ec.point import Point
@@ -38,6 +42,8 @@ class MultipleContext(Context):
points: MutableMapping[Point, int]
"""The mapping of points to the multiples they represent (e.g., base -> 1)."""
parents: MutableMapping[Point, List[Point]]
+ """The mapping of points to the formula types they are a result of."""
+ formulas: MutableMapping[Point, str]
"""The mapping of points to their parent they were computed from."""
inside: bool
@@ -45,6 +51,7 @@ class MultipleContext(Context):
self.base = None
self.points = {}
self.parents = {}
+ self.formulas = {}
self.inside = False
def enter_action(self, action: Action) -> None:
@@ -56,10 +63,12 @@ class MultipleContext(Context):
self.base = action.point
self.points = {self.base: 1}
self.parents = {self.base: []}
+ self.formulas = {self.base: ""}
else:
self.base = action.point
self.points = {self.base: 1}
self.parents = {self.base: []}
+ self.formulas = {self.base: ""}
self.inside = True
def exit_action(self, action: Action) -> None:
@@ -71,33 +80,40 @@ class MultipleContext(Context):
out = action.output_points[0]
self.points[out] = 2 * self.points[inp]
self.parents[out] = [inp]
+ self.formulas[out] = action.formula.shortname
elif isinstance(action.formula, TriplingFormula):
inp = action.input_points[0]
out = action.output_points[0]
self.points[out] = 3 * self.points[inp]
self.parents[out] = [inp]
+ self.formulas[out] = action.formula.shortname
elif isinstance(action.formula, AdditionFormula):
one, other = action.input_points
out = action.output_points[0]
self.points[out] = self.points[one] + self.points[other]
self.parents[out] = [one, other]
+ self.formulas[out] = action.formula.shortname
elif isinstance(action.formula, NegationFormula):
inp = action.input_points[0]
out = action.output_points[0]
self.points[out] = -self.points[inp]
self.parents[out] = [inp]
+ self.formulas[out] = action.formula.shortname
elif isinstance(action.formula, DifferentialAdditionFormula):
_, one, other = action.input_points
out = action.output_points[0]
self.points[out] = self.points[one] + self.points[other]
self.parents[out] = [one, other]
+ self.formulas[out] = action.formula.shortname
elif isinstance(action.formula, LadderFormula):
_, one, other = action.input_points
dbl, add = action.output_points
self.points[add] = self.points[one] + self.points[other]
self.parents[add] = [one, other]
+ self.formulas[add] = action.formula.shortname
self.points[dbl] = 2 * self.points[one]
self.parents[dbl] = [one]
+ self.formulas[dbl] = action.formula.shortname
def __repr__(self):
return f"{self.__class__.__name__}({self.base!r}, multiples={self.points.values()!r})"
@@ -111,10 +127,15 @@ def rpa_point_0y(params: DomainParameters) -> Optional[Point]:
return None
y = params.curve.parameters["b"].sqrt()
# TODO: We can take the negative as well.
- return Point(AffineCoordinateModel(params.curve.model), x=Mod(0, params.curve.prime), y=y)
+ return Point(
+ AffineCoordinateModel(params.curve.model), x=Mod(0, params.curve.prime), y=y
+ )
elif isinstance(params.curve.model, MontgomeryModel):
- return Point(AffineCoordinateModel(params.curve.model), x=Mod(0, params.curve.prime),
- y=Mod(0, params.curve.prime))
+ return Point(
+ AffineCoordinateModel(params.curve.model),
+ x=Mod(0, params.curve.prime),
+ y=Mod(0, params.curve.prime),
+ )
else:
raise NotImplementedError
@@ -134,10 +155,15 @@ def rpa_point_x0(params: DomainParameters) -> Optional[Point]:
if not roots:
return None
x = Mod(int(next(iter(roots.keys()))), params.curve.prime)
- return Point(AffineCoordinateModel(params.curve.model), x=x, y=Mod(0, params.curve.prime))
+ return Point(
+ AffineCoordinateModel(params.curve.model), x=x, y=Mod(0, params.curve.prime)
+ )
elif isinstance(params.curve.model, MontgomeryModel):
- return Point(AffineCoordinateModel(params.curve.model), x=Mod(0, params.curve.prime),
- y=Mod(0, params.curve.prime))
+ return Point(
+ AffineCoordinateModel(params.curve.model),
+ x=Mod(0, params.curve.prime),
+ y=Mod(0, params.curve.prime),
+ )
else:
raise NotImplementedError
@@ -150,8 +176,15 @@ def rpa_input_point(k: Mod, rpa_point: Point, params: DomainParameters) -> Point
@public
-def rpa_distinguish(params: DomainParameters, mults: List[ScalarMultiplier], oracle: Callable[[int, Point], bool],
- bound: Optional[int] = None, majority: int = 1, use_init: bool = True, use_multiply: bool = True) -> List[ScalarMultiplier]:
+def rpa_distinguish(
+ params: DomainParameters,
+ mults: List[ScalarMultiplier],
+ oracle: Callable[[int, Point], bool],
+ bound: Optional[int] = None,
+ majority: int = 1,
+ use_init: bool = True,
+ use_multiply: bool = True,
+) -> List[ScalarMultiplier]:
"""
Distinguish the scalar multiplier used (from the possible :paramref:`~.rpa_distinguish.mults`) using
an [RPA]_ :paramref:`~.rpa_distinguish.oracle`.
@@ -198,16 +231,30 @@ def rpa_distinguish(params: DomainParameters, mults: List[ScalarMultiplier], ora
# Take the computed points during init
init_points = set(init_context.parents.keys())
# And get their parents (inputs to formulas)
- init_parents = set(sum((init_context.parents[point] for point in init_points), []))
+ init_parents = set(
+ sum((init_context.parents[point] for point in init_points), [])
+ )
# Go over the parents and map them to multiples of the base (plus-minus sign)
- init_multiples = set(map(lambda v: Mod(v, params.order), (init_context.points[parent] for parent in init_parents)))
+ init_multiples = set(
+ map(
+ lambda v: Mod(v, params.order),
+ (init_context.points[parent] for parent in init_parents),
+ )
+ )
init_multiples |= set(map(lambda v: -v, init_multiples))
# Now do the multiply and repeat the above, but only consider new computed points
with local(init_context) as ctx:
mult.multiply(scalar)
all_points = set(ctx.parents.keys())
- multiply_parents = set(sum((ctx.parents[point] for point in all_points - init_points), []))
- multiply_multiples = set(map(lambda v: Mod(v, params.order), (ctx.points[parent] for parent in multiply_parents)))
+ multiply_parents = set(
+ sum((ctx.parents[point] for point in all_points - init_points), [])
+ )
+ multiply_multiples = set(
+ map(
+ lambda v: Mod(v, params.order),
+ (ctx.points[parent] for parent in multiply_parents),
+ )
+ )
multiply_multiples |= set(map(lambda v: -v, multiply_multiples))
used = set()
if use_init:
@@ -218,7 +265,13 @@ def rpa_distinguish(params: DomainParameters, mults: List[ScalarMultiplier], ora
tree = build_distinguishing_tree(mults_to_multiples)
log("Built distinguishing tree.")
- log(RenderTree(tree).by_attr(lambda n: n.name if n.name else [mult.__class__.__name__ for mult in n.cfgs]))
+ log(
+ RenderTree(tree).by_attr(
+ lambda n: n.name
+ if n.name
+ else [mult.__class__.__name__ for mult in n.cfgs]
+ )
+ )
if tree is None or not tree.children:
tries += 1
continue
@@ -237,8 +290,13 @@ def rpa_distinguish(params: DomainParameters, mults: List[ScalarMultiplier], ora
break
log(f"Oracle response -> {response}")
for mult in mults:
- log(mult.__class__.__name__, best_distinguishing_multiple in mults_to_multiples[mult])
- response_map = {child.oracle_response: child for child in current_node.children}
+ log(
+ mult.__class__.__name__,
+ best_distinguishing_multiple in mults_to_multiples[mult],
+ )
+ response_map = {
+ child.oracle_response: child for child in current_node.children
+ }
current_node = response_map[response]
mults = current_node.cfgs
log([mult.__class__.__name__ for mult in mults])
diff --git a/pyecsca/sca/re/zvp.py b/pyecsca/sca/re/zvp.py
index 6b1beac..2146966 100644
--- a/pyecsca/sca/re/zvp.py
+++ b/pyecsca/sca/re/zvp.py
@@ -3,16 +3,29 @@ Provides functionality inspired by the Zero-value point attack [ZVP]_.
Implements ZVP point construction from [FFD]_.
"""
-from typing import List, Set, Tuple, Dict
+from typing import List, Set, Tuple, Dict, Type, Mapping
from public import public
from astunparse import unparse
from sympy import FF, Poly, Monomial, Symbol, Expr, sympify, symbols, div
+from tqdm.auto import tqdm
+from .rpa import MultipleContext
+from ...ec.context import local
from ...ec.curve import EllipticCurve
from ...ec.divpoly import mult_by_n
-from ...ec.formula import Formula
+from ...ec.formula import (
+ Formula,
+ AdditionFormula,
+ DoublingFormula,
+ DifferentialAdditionFormula,
+ LadderFormula,
+ NegationFormula,
+)
+from ...ec.formula.fake import FakePoint, FakeFormula
from ...ec.mod import Mod
+from ...ec.mult import ScalarMultiplier
+from ...ec.params import DomainParameters
from ...ec.point import Point
@@ -183,7 +196,7 @@ def is_homogeneous(polynomial: Poly, weighted_variables: List[Tuple[str, int]])
@public
-def compute_factor_set(formula: Formula, affine: bool = True) -> Set[Poly]:
+def compute_factor_set(formula: Formula, affine: bool = True, filter_nonhomo: bool = True) -> Set[Poly]:
"""
Compute a set of factors present in the :paramref:`~.compute_factor_set.formula`.
@@ -192,7 +205,8 @@ def compute_factor_set(formula: Formula, affine: bool = True) -> Set[Poly]:
:return: The set of factors present in the formula.
"""
unrolled = unroll_formula(formula)
- unrolled = filter_out_nonhomogenous_polynomials(formula, unrolled)
+ if filter_nonhomo:
+ unrolled = filter_out_nonhomogenous_polynomials(formula, unrolled)
if affine:
unrolled = map_to_affine(formula, unrolled)
@@ -496,3 +510,109 @@ def zvp_points(poly: Poly, curve: EllipticCurve, k: int, n: int) -> Set[Point]:
if res == 0:
points.add(point)
return points
+
+
+def addition_chain(
+ scalar: int,
+ params: DomainParameters,
+ mult_class: Type[ScalarMultiplier],
+ mult_factory,
+) -> List[Tuple[str, Tuple[int, ...]]]:
+ """
+ Compute the addition chain for a given scalar and multiplier.
+
+ :param scalar: The scalar to compute for.
+ :param params: The domain parameters to use.
+ :param mult_class: The class of the scalar multiplier to use.
+ :param mult_factory: A callable that takes the formulas and instantiates the multiplier.
+ :return: A list of tuples, where the first element is the formula shortname (e.g. "add") and the second is a tuple of the dlog
+ relationships to the input of the input points to the formula.
+ """
+ formula_classes: List[Type[Formula]] = list(
+ filter(
+ lambda klass: klass in mult_class.requires,
+ [
+ AdditionFormula,
+ DifferentialAdditionFormula,
+ DoublingFormula,
+ LadderFormula,
+ NegationFormula,
+ ],
+ )
+ )
+ formulas = []
+ for formula in formula_classes:
+ for subclass in formula.__subclasses__():
+ if issubclass(subclass, FakeFormula):
+ formulas.append(subclass(params.curve.coordinate_model))
+ mult = mult_factory(*formulas)
+ mult.init(params, FakePoint(params.curve.coordinate_model))
+
+ with local(MultipleContext()) as mctx:
+ mult.multiply(scalar)
+
+ chain = []
+ for point, parents in mctx.parents.items():
+ if not parents:
+ continue
+ formula_type = mctx.formulas[point]
+ ks = tuple(mctx.points[parent] for parent in parents)
+ chain.append((formula_type, ks))
+ return chain
+
+
+def precomp_zvp_points(
+ chain: List[Tuple[str, Tuple[int, ...]]],
+ formulas: Mapping[str, Formula],
+ params: DomainParameters,
+ bound: int = 25,
+) -> List[Mapping[Poly, Set[Point]]]:
+ """
+
+ :param chain:
+ :param formulas:
+ :param params:
+ :param bound:
+ :return:
+ """
+ factor_sets = {
+ formula: compute_factor_set(formula) for formula in formulas.values()
+ }
+ # A bit of a hack to rename the poly variables for double as zvp_points expects that.
+ for formula in formulas.values():
+ if isinstance(formula, DoublingFormula):
+ fset = factor_sets[formula]
+ new_fset = set()
+ for poly in fset:
+ pl = poly.copy()
+ for symbol in poly.free_symbols:
+ original = str(symbol)
+ if original.endswith("1"):
+ new = original.replace("1", "2")
+ pl = pl.subs(original, new)
+ new_fset.add(pl)
+ factor_sets[formula] = new_fset
+ result: List[Mapping[Poly, Set[Point]]] = []
+ for op, ks in chain:
+ if op not in formulas:
+ continue
+ formula = formulas[op]
+ factor_set = factor_sets[formula]
+ if len(ks) == 1:
+ k = ks[0]
+ else:
+ # zvp_points assumes (P, [k]P)
+ ks_mod = list(map(lambda v: Mod(v, params.order), ks))
+ k = int(ks_mod[1] / ks_mod[0])
+ points = {}
+
+ for poly in factor_set:
+ only_1 = all((not str(gen).endswith("2")) for gen in poly.gens) # type: ignore[attr-defined]
+ only_2 = all((not str(gen).endswith("1")) for gen in poly.gens) # type: ignore[attr-defined]
+ # This is the hard case where a dlog needs to be substituted, so bound it.
+ if not (only_1 or only_2) and k > bound:
+ continue
+
+ points[poly] = zvp_points(poly, params.curve, k, params.order)
+ result.append(points)
+ return result
diff --git a/test/sca/test_zvp.py b/test/sca/test_zvp.py
index ceb8941..d1b7930 100644
--- a/test/sca/test_zvp.py
+++ b/test/sca/test_zvp.py
@@ -1,7 +1,11 @@
import pytest
from pyecsca.ec.coordinates import AffineCoordinateModel
+from pyecsca.ec.curve import EllipticCurve
from pyecsca.ec.mod import Mod
+from pyecsca.ec.model import ShortWeierstrassModel
+from pyecsca.ec.mult import LTRMultiplier, AccumulationOrder
+from pyecsca.ec.params import DomainParameters
from pyecsca.ec.point import Point
from pyecsca.sca.re.zvp import (
unroll_formula,
@@ -13,6 +17,8 @@ from pyecsca.sca.re.zvp import (
subs_curve_params,
zvp_points,
compute_factor_set,
+ addition_chain,
+ precomp_zvp_points,
)
from pyecsca.ec.context import local, DefaultContext
from sympy import symbols, Poly, sympify, FF
@@ -233,3 +239,56 @@ def test_points(secp128r1, poly_str, point, k):
poly = Poly(poly_expr, domain=FF(secp128r1.curve.prime))
res = zvp_points(poly, secp128r1.curve, k, secp128r1.order)
assert pt in res
+
+
+def test_addition_chain(secp128r1):
+ res = addition_chain(
+ 78699,
+ secp128r1,
+ LTRMultiplier,
+ lambda add, dbl: LTRMultiplier(
+ add, dbl, None, False, AccumulationOrder.PeqPR, True, True
+ ),
+ )
+ assert res is not None
+ assert len(res) == 25
+
+
+@pytest.fixture()
+def small_params():
+ model = ShortWeierstrassModel()
+ coords = model.coordinates["projective"]
+ p = 0xC50DE883F0E7B167
+ a = Mod(0x4833D7AA73FA6694, p)
+ b = Mod(0xA6C44A61C5323F6A, p)
+ gx = Mod(0x5FD1F7D38D4F2333, p)
+ gy = Mod(0x21F43957D7E20CEB, p)
+ n = 0xC50DE885003B80EB
+ h = 1
+ infty = Point(coords, X=Mod(0, p), Y=Mod(1, p), Z=Mod(0, p))
+ g = Point(coords, X=gx, Y=gy, Z=Mod(1, p))
+
+ curve = EllipticCurve(model, coords, p, infty, dict(a=a, b=b))
+ params = DomainParameters(curve, g, n, h)
+ return params
+
+
+def test_precomp(small_params):
+ chain = addition_chain(
+ 123456789,
+ small_params,
+ LTRMultiplier,
+ lambda add, dbl: LTRMultiplier(
+ add, dbl, None, False, AccumulationOrder.PeqRP, True, True
+ ),
+ )
+ res = precomp_zvp_points(
+ chain,
+ {
+ "add": small_params.curve.coordinate_model.formulas["add-2007-bl"],
+ "dbl": small_params.curve.coordinate_model.formulas["dbl-2007-bl"],
+ },
+ small_params,
+ )
+ assert res is not None
+ assert len(res) == len(chain)