aboutsummaryrefslogtreecommitdiff
path: root/pyecsca/ec/params.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyecsca/ec/params.py')
-rw-r--r--pyecsca/ec/params.py130
1 files changed, 116 insertions, 14 deletions
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: