aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJ08nY2020-12-16 00:29:45 +0100
committerJ08nY2020-12-16 00:29:45 +0100
commit9421aecd67f7ac0efb2d728fb31bb14f500361be (patch)
treeb1712af506256143bf21807d0088a9110bf64fa0
parentc1a3bdc558725ad9792f874c914eb42319aed84c (diff)
downloadpyecsca-9421aecd67f7ac0efb2d728fb31bb14f500361be.tar.gz
pyecsca-9421aecd67f7ac0efb2d728fb31bb14f500361be.tar.zst
pyecsca-9421aecd67f7ac0efb2d728fb31bb14f500361be.zip
-rw-r--r--Pipfile28
-rw-r--r--README.md1
-rw-r--r--docs/index.rst2
-rw-r--r--pyecsca/ec/curve.py2
-rw-r--r--pyecsca/ec/params.py130
-rw-r--r--setup.py1
-rw-r--r--test/data/curves.json37
-rw-r--r--test/ec/test_params.py13
8 files changed, 170 insertions, 44 deletions
diff --git a/Pipfile b/Pipfile
deleted file mode 100644
index 201a0a9..0000000
--- a/Pipfile
+++ /dev/null
@@ -1,28 +0,0 @@
-[[source]]
-name = "pypi"
-url = "https://pypi.org/simple"
-verify_ssl = true
-
-[dev-packages]
-nose2 = "*"
-green = "*"
-mypy = "*"
-flake8 = "*"
-sphinx = "*"
-sphinx-autodoc-typehints = "*"
-parameterized = "*"
-coverage = "*"
-
-[packages]
-numpy = "*"
-scipy = "*"
-atpublic = "*"
-matplotlib = "*"
-cython = "*"
-fastdtw = "*"
-asn1crypto = "*"
-h5py = "*"
-bokeh = "*"
-
-[requires]
-python_version = "3.8"
diff --git a/README.md b/README.md
index 38c131a..accb6e7 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ and ECC simulation in the [*pyecsca.ec*](pyecsca/ec) package.
- [Numpy](https://www.numpy.org/)
- [Scipy](https://www.scipy.org/)
+ - [sympy](https://sympy.org/)
- [atpublic](https://public.readthedocs.io/)
- [fastdtw](https://github.com/slaypni/fastdtw)
- [asn1crypto](https://github.com/wbond/asn1crypto)
diff --git a/docs/index.rst b/docs/index.rst
index dd0bc67..6052cf5 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -63,6 +63,7 @@ Requirements
- Numpy_
- Scipy_
+ - sympy_
- atpublic_
- fastdtw_
- asn1crypto_
@@ -139,6 +140,7 @@ this support is very appreciated.
.. _Numpy: https://www.numpy.org
.. _Scipy: https://www.scipy.org
+.. _sympy: https://sympy.org/
.. _matplotlib: https://matplotlib.org/
.. _atpublic: https://public.readthedocs.io/
.. _fastdtw: https://github.com/slaypni/fastdtw
diff --git a/pyecsca/ec/curve.py b/pyecsca/ec/curve.py
index 9321826..44358d0 100644
--- a/pyecsca/ec/curve.py
+++ b/pyecsca/ec/curve.py
@@ -23,7 +23,7 @@ class EllipticCurve(object):
prime: int, neutral: Point, parameters: MutableMapping[str, Union[Mod, int]]):
if coordinate_model not in model.coordinates.values() and not isinstance(coordinate_model, AffineCoordinateModel):
raise ValueError
- if set(model.parameter_names).symmetric_difference(parameters.keys()):
+ if set(model.parameter_names).union(coordinate_model.parameters).symmetric_difference(parameters.keys()):
raise ValueError
self.model = model
self.coordinate_model = coordinate_model
diff --git a/pyecsca/ec/params.py b/pyecsca/ec/params.py
index fea5988..0522e7b 100644
--- a/pyecsca/ec/params.py
+++ b/pyecsca/ec/params.py
@@ -1,8 +1,10 @@
import json
+from sympy import Poly, PythonFiniteField, symbols, sympify
+from ast import unparse
from io import RawIOBase, BufferedIOBase
from os.path import join
from pathlib import Path
-from typing import Optional, Dict, Union, BinaryIO
+from typing import Optional, Dict, Union, BinaryIO, List, Callable
from pkg_resources import resource_listdir, resource_isdir, resource_stream
from public import public
@@ -58,6 +60,34 @@ class DomainParameters(object):
return f"{self.__class__.__name__}({self.curve!r}, {self.generator!r}, {self.order}, {self.cofactor})"
+@public
+class DomainParameterCategory(object):
+ """A category of domain parameters."""
+ name: str
+ description: str
+ curves: List[DomainParameters]
+
+ def __init__(self, name: str, description: str, curves: List[DomainParameters]):
+ self.name = name
+ self.description = description
+ self.curves = curves
+
+ def __str__(self):
+ return f"{self.__class__.__name__}({self.name})"
+
+ def __iter__(self):
+ yield from self.curves
+
+ def __contains__(self, item):
+ return item in self.curves
+
+ def __len__(self):
+ return len(self.curves)
+
+ def __getitem__(self, item):
+ return self.curves[item]
+
+
def _create_params(curve, coords, infty):
if curve["field"]["type"] == "Binary":
raise ValueError("Binary field curves are currently not supported.")
@@ -94,13 +124,34 @@ def _create_params(curve, coords, infty):
raise ValueError("Coordinate model not supported for curve.")
coord_model = model.coordinates[coords]
for assumption in coord_model.assumptions:
- alocals: Dict[str, Union[Mod, int]] = {}
- compiled = compile(assumption, "", mode="exec")
- exec(compiled, None, alocals)
- for param, value in alocals.items():
- if params[param] != value:
- raise ValueError(
- f"Coordinate model {coord_model} has an unsatisifed assumption on the {param} parameter (= {value}).")
+ # Try to execute assumption, if it works, check with curve parameters
+ # if it doesn't work, move all over to rhs and construct a sympy polynomial of it
+ # then find roots and take first one for new value for new coordinate parameter.
+ try:
+ alocals: Dict[str, Union[Mod, int]] = {}
+ compiled = compile(assumption, "", mode="exec")
+ exec(compiled, None, alocals)
+ for param, value in alocals.items():
+ if params[param] != value:
+ raise ValueError(
+ f"Coordinate model {coord_model} has an unsatisifed assumption on the {param} parameter (= {value}).")
+ except NameError:
+ k = PythonFiniteField(field)
+ assumption_string = unparse(assumption)
+ lhs, rhs = assumption_string.split(" = ")
+ expr = sympify(f"{rhs} - {lhs}")
+ for curve_param, value in params.items():
+ expr = expr.subs(curve_param, k(value))
+ if len(expr.free_symbols) > 1 or (param := str(expr.free_symbols.pop())) not in coord_model.parameters:
+ raise ValueError(f"This coordinate model couldn't be loaded due to unsupported asusmption ({assumption_string}).")
+ poly = Poly(expr, symbols(param), domain=k)
+ roots = poly.ground_roots()
+ for root in roots.keys():
+ if root >= 0:
+ params[param] = Mod(int(root), field)
+ break
+ else:
+ raise ValueError(f"Coordinate model {coord_model} has an unsatisifed assumption on the {param} parameter (0 = {expr}).")
# Construct the point at infinity
infinity: Point
@@ -132,28 +183,79 @@ def _create_params(curve, coords, infty):
@public
+def load_category(file: Union[str, Path, BinaryIO], coords: Union[str, Callable[[str], str]],
+ infty: Union[bool, Callable[[str], bool]] = True) -> DomainParameterCategory:
+ """
+ Load a category of domain parameters containing several curves from a JSON file.
+
+ :param file: The file to load from.
+ :param coords: The name of the coordinate system to use. Can be a callable that takes
+ as argument the name of the curve and produces the coordinate system to use for that curve.
+ :param infty: Whether to use the special :py:class:InfinityPoint (`True`) or try to use the
+ point at infinity of the coordinate system. Can be a callable that takes
+ as argument the name of the curve and returns the infinity option to use for that curve.
+ :return: The category.
+ """
+ if isinstance(file, (str, Path)):
+ with open(file, "rb") as f:
+ data = json.load(f)
+ elif isinstance(file, (RawIOBase, BufferedIOBase, BinaryIO)):
+ data = json.load(file)
+ else:
+ raise TypeError
+
+ curves = []
+ for curve_data in data["curves"]:
+ curve_coords = coords(curve_data["name"]) if callable(coords) else coords
+ curve_infty = infty(curve_data["name"]) if callable(infty) else infty
+ try:
+ curve = _create_params(curve_data, curve_coords, curve_infty)
+ except ValueError:
+ continue
+ curves.append(curve)
+
+ return DomainParameterCategory(data["name"], data["desc"], curves)
+
+
+@public
def load_params(file: Union[str, Path, BinaryIO], coords: str, infty: bool = True) -> DomainParameters:
"""
+ Load a curve from a JSON file.
- :param input:
+ :param file: The file to load from.
:param coords: The name of the coordinate system to use.
:param infty: Whether to use the special :py:class:InfinityPoint (`True`) or try to use the
point at infinity of the coordinate system.
:return: The curve.
"""
- curve = None
if isinstance(file, (str, Path)):
with open(file, "rb") as f:
curve = json.load(f)
elif isinstance(file, (RawIOBase, BufferedIOBase, BinaryIO)):
curve = json.load(file)
- if curve["field"]["type"] == "Binary":
- raise ValueError("Binary field curves are currently not supported.")
- if curve["field"]["type"] == "Extension":
- raise ValueError("Extension field curves are currently not supported.")
+ else:
+ raise TypeError
return _create_params(curve, coords, infty)
+@public
+def get_category(category: str, coords: Union[str, Callable[[str], str]],
+ infty: Union[bool, Callable[[str], bool]] = True) -> DomainParameterCategory:
+ """
+
+ :param category:
+ :param coords:
+ :param infty:
+ :return:
+ """
+ listing = resource_listdir(__name__, "std")
+ categories = list(entry for entry in listing if resource_isdir(__name__, join("std", entry)))
+ if category not in categories:
+ raise ValueError("Category {} not found.".format(category))
+ json_path = join("std", category, "curves.json")
+ with resource_stream(__name__, json_path) as f:
+ return load_category(f, coords, infty)
+
@public
def get_params(category: str, name: str, coords: str, infty: bool = True) -> DomainParameters:
diff --git a/setup.py b/setup.py
index 9f9a7ab..87ca2d9 100644
--- a/setup.py
+++ b/setup.py
@@ -28,6 +28,7 @@ setup(
install_requires=[
"numpy",
"scipy",
+ "sympy",
"atpublic",
"cython",
"fastdtw",
diff --git a/test/data/curves.json b/test/data/curves.json
new file mode 100644
index 0000000..978ab36
--- /dev/null
+++ b/test/data/curves.json
@@ -0,0 +1,37 @@
+{
+ "name": "test",
+ "desc": "Test description",
+ "curves": [
+ {
+ "form": "Edwards",
+ "name": "small-8bits-edwards-curve-c1-d-has-sqrt",
+ "category": "test",
+ "desc": "A small 8-bit Edwards curve with with c = 1 and d has sqrt.",
+ "field": {
+ "type": "Prime",
+ "p": "0xDF",
+ "bits": 8
+ },
+ "params": {
+ "c": {
+ "raw": "0x1"
+ },
+ "d": {
+ "raw": "0xF"
+ }
+ },
+ "generator": {
+ "x": {
+ "raw": "0xC3"
+ },
+ "y": {
+ "raw": "0xB4"
+ }
+ },
+ "r": "0x62",
+ "s": "0xB0",
+ "order": "0x1F0",
+ "cofactor": "0x1"
+ }
+ ]
+}
diff --git a/test/ec/test_params.py b/test/ec/test_params.py
index 13e45ec..77f5418 100644
--- a/test/ec/test_params.py
+++ b/test/ec/test_params.py
@@ -3,7 +3,7 @@ from unittest import TestCase
from parameterized import parameterized
from pyecsca.ec.coordinates import AffineCoordinateModel
-from pyecsca.ec.params import get_params, load_params
+from pyecsca.ec.params import get_params, load_params, load_category, get_category
class DomainParameterTests(TestCase):
@@ -35,6 +35,13 @@ class DomainParameterTests(TestCase):
except NotImplementedError:
pass
+ @parameterized.expand([
+ ("anssi", "projective"),
+ ("brainpool", lambda name: "projective" if name.endswith("r1") else "jacobian")
+ ])
+ def test_get_category(self, name, coords):
+ get_category(name, coords)
+
def test_load_params(self):
params = load_params("test/data/curve.json", "projective")
try:
@@ -42,6 +49,10 @@ class DomainParameterTests(TestCase):
except NotImplementedError:
pass
+ def test_load_category(self):
+ category = load_category("test/data/curves.json", "yz")
+ self.assertEqual(len(category), 1)
+
@parameterized.expand([
("no_category/some", "else"),
("secg/no_curve", "else"),