aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/references.rst5
-rw-r--r--pyecsca/ec/configuration.py4
-rw-r--r--pyecsca/ec/curve.py17
-rw-r--r--pyecsca/sca/re/rpa.py6
-rw-r--r--pyecsca/sca/re/zvp.py190
-rw-r--r--test/ec/test_mult.py1
-rw-r--r--test/sca/test_zvp.py80
7 files changed, 282 insertions, 21 deletions
diff --git a/docs/references.rst b/docs/references.rst
index 63aaf41..d215948 100644
--- a/docs/references.rst
+++ b/docs/references.rst
@@ -8,3 +8,8 @@ References
.. [HEHCC] Handbook of Elliptic and Hyper-Elliptic Curve Cryptography, https://www.hyperelliptic.org/HEHCC/
.. [HAC] Handbook of Applied Cryptography, https://cacr.uwaterloo.ca/hac/
.. [BBG+17] Sliding right into disaster: Left-to-right sliding windows leak, https://eprint.iacr.org/2017/627.pdf
+.. [RPA] A Refined Power-Analysis Attack on Elliptic Curve Cryptosystems, http://www.goubin.fr/papers/ecc-dpa.pdf
+.. [ZVP] Zero-value point attacks on elliptic curve cryptosystem, https://doi.org/10.1007/10958513_17
+.. [EPA] Exceptional procedure attack on elliptic curve cryptosystems, https://doi.org/10.1007/3-540-36288-6_17
+.. [FFD] A formula for disaster: a unified approach to elliptic curve special-point-based attacks, https://eprint.iacr.org/2021/1595.pdf
+.. [MT1991] Mazur, B., & Tate, J. (1991). The `p`-adic sigma function. Duke Mathematical Journal, 62 (3), 663-688.
diff --git a/pyecsca/ec/configuration.py b/pyecsca/ec/configuration.py
index 03dc86b..6ed2190 100644
--- a/pyecsca/ec/configuration.py
+++ b/pyecsca/ec/configuration.py
@@ -1,5 +1,6 @@
"""Provides a way to work with and enumerate implementation configurations."""
import warnings
+from abc import ABC
from dataclasses import dataclass
from enum import Enum
from itertools import product
@@ -146,7 +147,7 @@ def all_configurations(**kwargs) -> Generator[Configuration, Configuration, None
for subclass in subs:
if subclass.__subclasses__():
result.update(leaf_subclasses(subclass))
- else:
+ elif not issubclass(subclass, ABC):
result.add(subclass)
return result
@@ -240,6 +241,7 @@ def all_configurations(**kwargs) -> Generator[Configuration, Configuration, None
continue
coords_formulas = coords.formulas.values()
mult_classes = leaf_subclasses(ScalarMultiplier)
+
if "scalarmult" in kwargs:
if isinstance(kwargs["scalarmult"], ScalarMultiplier):
mults = [kwargs["scalarmult"]]
diff --git a/pyecsca/ec/curve.py b/pyecsca/ec/curve.py
index fb567e8..8cc7dba 100644
--- a/pyecsca/ec/curve.py
+++ b/pyecsca/ec/curve.py
@@ -2,7 +2,7 @@
from ast import Module
from astunparse import unparse
from copy import copy
-from typing import MutableMapping, Union, List, Optional, Dict
+from typing import MutableMapping, Union, List, Optional, Dict, Set
from public import public
from sympy import FF, sympify
@@ -303,6 +303,21 @@ class EllipticCurve:
f"Wrong encoding type: {hex(encoded[0])}, should be one of 0x04, 0x06, 0x02, 0x03 or 0x00"
)
+ def affine_lift_x(self, x: Mod) -> Set[Point]:
+ """
+ Lift an x-coordinate to the curve.
+
+ :param x: The x-coordinate.
+ :return: Lifted (affine) points, if any.
+ """
+ loc = {**self.parameters, "x": x}
+ ysquared = eval(compile(self.model.ysquared, "", mode="eval"), loc)
+ if not ysquared.is_residue():
+ return set()
+ y = ysquared.sqrt()
+ return {Point(AffineCoordinateModel(self.model), x=x, y=y),
+ Point(AffineCoordinateModel(self.model), x=x, y=-y)}
+
def affine_random(self) -> Point:
"""Generate a random affine point on the curve."""
while True:
diff --git a/pyecsca/sca/re/rpa.py b/pyecsca/sca/re/rpa.py
index 212e147..721498c 100644
--- a/pyecsca/sca/re/rpa.py
+++ b/pyecsca/sca/re/rpa.py
@@ -89,7 +89,7 @@ class MultipleContext(Context):
def rpa_point_0y(params: DomainParameters) -> Optional[Point]:
- """Construct an (affine) RPA point (0, y) for given domain parameters."""
+ """Construct an (affine) [RPA]_ point (0, y) for given domain parameters."""
if isinstance(params.curve.model, ShortWeierstrassModel):
if not params.curve.parameters["b"].is_residue():
return None
@@ -104,7 +104,7 @@ def rpa_point_0y(params: DomainParameters) -> Optional[Point]:
def rpa_point_x0(params: DomainParameters) -> Optional[Point]:
- """Construct an (affine) RPA point (x, 0) for given domain parameters."""
+ """Construct an (affine) [RPA]_ point (x, 0) for given domain parameters."""
if isinstance(params.curve.model, ShortWeierstrassModel):
if (params.order * params.cofactor) % 2 != 0:
return None
@@ -129,7 +129,7 @@ def rpa_point_x0(params: DomainParameters) -> Optional[Point]:
def rpa_distinguish(params: DomainParameters, mults: List[ScalarMultiplier], oracle: Callable[[int, Point], bool]) -> List[ScalarMultiplier]:
"""
Distinguish the scalar multiplier used (from the possible :paramref:`~.rpa_distinguish.mults`) using
- an RPA :paramref:`~.rpa_distinguish.oracle`.
+ an [RPA]_ :paramref:`~.rpa_distinguish.oracle`.
:param params: The domain parameters to use.
:param mults: The list of possible multipliers.
diff --git a/pyecsca/sca/re/zvp.py b/pyecsca/sca/re/zvp.py
index 413e75a..9d1a6d1 100644
--- a/pyecsca/sca/re/zvp.py
+++ b/pyecsca/sca/re/zvp.py
@@ -4,18 +4,23 @@ Provides functionality inspired by the Zero-value point attack.
Zero-Value Point Attacks on Elliptic Curve Cryptosystem, Toru Akishita & Tsuyoshi Takagi , ISC '03
`<https://doi.org/10.1007/10958513_17>`_
"""
-from typing import List
+from typing import List, Set
+from public import public
+import contextlib
-from sympy import symbols
+from sympy import symbols, FF, Poly, Monomial, Symbol, Expr
from ...ec.context import DefaultContext, local
+from ...ec.curve import EllipticCurve
+from ...ec.divpoly import mult_by_n
from ...ec.formula import Formula
-from ...ec.mod import SymbolicMod
+from ...ec.mod import SymbolicMod, Mod
from ...ec.point import Point
from ...misc.cfg import TemporaryConfig
-def unroll_formula(formula: Formula, prime: int) -> List[SymbolicMod]:
+@public
+def unroll_formula(formula: Formula, prime: int) -> List[Poly]:
"""
Unroll a given formula symbolically to obtain symbolic expressions for its intermediate values.
@@ -23,6 +28,7 @@ def unroll_formula(formula: Formula, prime: int) -> List[SymbolicMod]:
:param prime: Field to unroll over, necessary for technical reasons.
:return: List of symbolic intermediate values.
"""
+ field = FF(prime)
inputs = [Point(formula.coordinate_model,
**{var: SymbolicMod(symbols(var + str(i)), prime) for var in formula.coordinate_model.variables})
for i in
@@ -31,4 +37,178 @@ def unroll_formula(formula: Formula, prime: int) -> List[SymbolicMod]:
with local(DefaultContext()) as ctx, TemporaryConfig() as cfg:
cfg.ec.mod_implementation = "symbolic"
formula(prime, *inputs, **params)
- return [op_result.value for op_result in ctx.actions.get_by_index([0])[0].op_results]
+ return [Poly(op_result.value.x, *op_result.value.x.free_symbols, domain=field) for op_result in
+ ctx.actions.get_by_index([0])[0].op_results]
+
+
+def curve_equation(x: Symbol, curve: EllipticCurve, symbolic: bool = True) -> Expr:
+ """
+ Get the "ysquared" curve polynomial in :paramref:`~.x` for the :paramref:`~.curve`,
+ either symbolically or with concrete parameter values.
+
+ :param x:
+ :param curve:
+ :param symbolic:
+ :return:
+ """
+ parameters = {name: symbols(name) if symbolic else curve.parameters[name] for name in curve.model.parameter_names}
+ return eval(compile(curve.model.ysquared, "", mode="eval"), {"x": x, **parameters})
+
+
+def subs_curve_equation(poly: Poly, curve: EllipticCurve) -> Poly:
+ """
+ Substitute in the curve equation "ysquared" for Y repeatedly to
+ eliminate all but singular powers of Y.
+
+ :param poly:
+ :param curve:
+ :return:
+ """
+ terms = []
+ for term in poly.terms():
+ sub = 1
+ new_term = []
+ for power, gen in zip(term[0], poly.gens):
+ if str(gen).startswith("Y"):
+ X = symbols("X" + str(gen)[1:])
+ ysquared = curve_equation(X, curve)
+ sub *= ysquared ** (power // 2)
+ power %= 2
+ new_term.append(power)
+ expr = Monomial(new_term, poly.gens).as_expr() * sub * term[1]
+ terms.append(expr)
+ return Poly(sum(terms), domain=poly.domain)
+
+
+def subs_curve_params(poly: Poly, curve: EllipticCurve) -> Poly:
+ """
+ Substitute in the concrete curve parameters.
+
+ :param poly:
+ :param curve:
+ :return:
+ """
+ for name, value in curve.parameters.items():
+ symbol = symbols(name)
+ if symbol in poly.gens:
+ poly = poly.subs(symbol, value)
+ return poly
+
+
+def subs_dlog(poly: Poly, k: int, curve: EllipticCurve):
+ """
+ Substitute in the multiplication-by-k-map(X1) in place of X2.
+
+ :param poly:
+ :param k:
+ :param curve:
+ :return:
+ """
+ X1, X2 = symbols("X1,X2")
+ if X2 not in poly.gens:
+ return poly
+ max_degree = poly.degree(X2)
+ X2i = poly.gens.index(X2)
+ new_gens = set(poly.gens)
+ new_gens.remove(X2)
+
+ mx, my = mult_by_n(curve, k)
+ u, v = mx[0].subs("x", X1), mx[1].subs("x", X1)
+
+ res = 0
+ for term in poly.terms():
+ powers = list(term[0])
+ u_power = powers[X2i]
+ u_factor = u**u_power
+ v_power = max_degree - u_power
+ v_factor = v**v_power
+ powers[X2i] = 0
+ monom = Monomial(powers, poly.gens).as_expr() * term[1]
+ res += Poly(monom, *new_gens, domain=poly.domain) * u_factor * v_factor
+ return Poly(res, domain=poly.domain)
+
+
+def remove_z(poly: Poly) -> Poly:
+ """
+ Substitute in 1 for all Zs (because we can do that ;).
+
+ :param poly:
+ :return:
+ """
+ for gen in poly.gens:
+ if str(gen).startswith("Z"):
+ poly = poly.subs(gen, 1)
+ return poly
+
+
+def eliminate_y(poly: Poly, curve: EllipticCurve) -> Poly:
+ """
+ Eliminate the remaining Ys (only power 1).
+
+ See [FFD]_ page 11.
+
+ :param poly:
+ :param curve:
+ :return:
+ """
+ Y1, Y2 = symbols("Y1,Y2")
+ Y1i = None
+ with contextlib.suppress(ValueError):
+ Y1i = poly.gens.index(Y1)
+ Y2i = None
+ with contextlib.suppress(ValueError):
+ Y2i = poly.gens.index(Y2)
+ f0 = 0
+ f1 = 0
+ f2 = 0
+ f12 = 0
+ for term in poly.terms():
+ monom = term[0]
+ monom_expr = Monomial(term[0], poly.gens).as_expr() * term[1]
+ y1_present = not (Y1i is None or monom[Y1i] == 0)
+ y2_present = not (Y2i is None or monom[Y2i] == 0)
+ if y1_present and y2_present:
+ f12 += monom_expr.subs(Y1, 1).subs(Y2, 1)
+ elif y1_present and not y2_present:
+ f1 += monom_expr.subs(Y1, 1)
+ elif not y1_present and y2_present:
+ f2 += monom_expr.subs(Y2, 1)
+ elif not y1_present and not y2_present:
+ f0 += monom_expr
+ fe_X1 = curve_equation(symbols("X1"), curve)
+ fe_X2 = curve_equation(symbols("X2"), curve)
+
+ # [FFD] page 11
+ f_prime = fe_X2 * (fe_X1 * 2 * f1 * f12 - 2 * f0 * f2) ** 2 - \
+ (f0 ** 2 + f2 ** 2 * fe_X2 - fe_X1 * (f1 ** 2 + f12 ** 2 * fe_X2)) ** 2
+ return Poly(f_prime, domain=poly.domain)
+
+
+@public
+def zvp_point(poly: Poly, curve: EllipticCurve, k: int) -> Set[Point]:
+ """
+ Find a set of ZVP points for a given intermediate value and dlog relationship.
+
+ :param poly: The polynomial to zero out, obtained as a result of :py:meth:`.unroll_formula`.
+ :param curve: The curve to compute over.
+ :param k: The discrete-log relationship between the two points, i.e. (X2, Y2) = [k](X1, Y1)
+ :return: The set of points (X1, Y1).
+ """
+ # Start with removing all squares of Y1, Y2
+ subbed = subs_curve_equation(poly, curve)
+ # Remove the Zs by setting them to 1
+ removed = remove_z(subbed)
+ # Now remove the rest of the Ys by clever curve equation use
+ eliminated = eliminate_y(removed, curve)
+ # Substitute in the mult-by-k map
+ dlog = subs_dlog(eliminated, k, curve)
+ # Put in concrete curve parameters
+ final = subs_curve_params(dlog, curve)
+ # Find the roots (X1)
+ roots = final.ground_roots()
+ points = set()
+ # Finally lift the roots to find the points (if any)
+ for root, multiplicity in roots.items():
+ pt = curve.affine_lift_x(Mod(int(root), curve.prime))
+ points.update(pt)
+ return points
diff --git a/test/ec/test_mult.py b/test/ec/test_mult.py
index 2a17867..8e5a06e 100644
--- a/test/ec/test_mult.py
+++ b/test/ec/test_mult.py
@@ -18,7 +18,6 @@ from pyecsca.ec.mult import (
AccumulationOrder, ScalarMultiplier, SlidingWindowMultiplier
)
from pyecsca.ec.point import InfinityPoint, Point
-from .utils import cartesian
def get_formulas(coords, *names):
diff --git a/test/sca/test_zvp.py b/test/sca/test_zvp.py
index 3b6b046..76fb7cb 100644
--- a/test/sca/test_zvp.py
+++ b/test/sca/test_zvp.py
@@ -1,13 +1,73 @@
-from pyecsca.sca.re.zvp import unroll_formula
+import pytest
+from pyecsca.sca.re.zvp import unroll_formula, subs_curve_equation, remove_z, eliminate_y, subs_dlog, subs_curve_params, \
+ zvp_point
+from sympy import symbols, Poly
-def test_unroll(secp128r1):
- add = secp128r1.curve.coordinate_model.formulas["add-2007-bl"]
- dbl = secp128r1.curve.coordinate_model.formulas["dbl-2007-bl"]
- neg = secp128r1.curve.coordinate_model.formulas["neg"]
- results = unroll_formula(add, 11)
- assert results is not None
- results = unroll_formula(dbl, 11)
- assert results is not None
- results = unroll_formula(neg, 11)
+
+@pytest.fixture(params=["add-2007-bl", "dbl-2007-bl", "add-2016-rcb"])
+def formula(secp128r1, request):
+ return secp128r1.curve.coordinate_model.formulas[request.param]
+
+
+def test_unroll(secp128r1, formula):
+ results = unroll_formula(formula, secp128r1.curve.prime)
assert results is not None
+ for res in results:
+ assert isinstance(res, Poly)
+
+
+def test_curve_elimination(secp128r1, formula):
+ unrolled = unroll_formula(formula, secp128r1.curve.prime)
+ subbed = subs_curve_equation(unrolled[-1], secp128r1.curve)
+ assert subbed is not None
+ Y1, Y2 = symbols("Y1,Y2")
+
+ # The resulting polynomial should not have Y1 and Y2 in higher powers than 1.
+ for term in subbed.terms():
+ powers = dict(zip(subbed.gens, term[0]))
+ assert powers.get(Y1, 0) in (0, 1)
+ assert powers.get(Y2, 0) in (0, 1)
+
+
+def test_remove_z(secp128r1, formula):
+ unrolled = unroll_formula(formula, secp128r1.curve.prime)
+ removed = remove_z(unrolled[-1])
+ for gen in removed.gens:
+ assert not str(gen).startswith("Z")
+
+
+def test_eliminate_y(secp128r1, formula):
+ unrolled = unroll_formula(formula, secp128r1.curve.prime)
+ subbed = subs_curve_equation(unrolled[-1], secp128r1.curve)
+ eliminated = eliminate_y(subbed, secp128r1.curve)
+ assert eliminated is not None
+ assert isinstance(eliminated, Poly)
+ Y1, Y2 = symbols("Y1,Y2")
+
+ assert Y1 not in eliminated.gens
+ assert Y2 not in eliminated.gens
+
+
+def test_full(secp128r1, formula):
+ unrolled = unroll_formula(formula, secp128r1.curve.prime)
+ subbed = subs_curve_equation(unrolled[-1], secp128r1.curve)
+ removed = remove_z(subbed)
+ eliminated = eliminate_y(removed, secp128r1.curve)
+ dlog = subs_dlog(eliminated, 3, secp128r1.curve)
+ assert dlog is not None
+ assert isinstance(dlog, Poly)
+ X1, X2 = symbols("X1,X2")
+ assert X2 not in dlog.gens
+
+ final = subs_curve_params(dlog, secp128r1.curve)
+ assert final is not None
+ assert isinstance(final, Poly)
+ assert final.gens == (X1,)
+
+
+def test_zvp(secp128r1, formula):
+ unrolled = unroll_formula(formula, secp128r1.curve.prime)
+ poly = unrolled[-2]
+ points = zvp_point(poly, secp128r1.curve, 5)
+ assert isinstance(points, set)