diff options
| author | J08nY | 2020-12-17 23:41:23 +0100 |
|---|---|---|
| committer | J08nY | 2020-12-17 23:41:23 +0100 |
| commit | c2ad6329efa5d0024dc0b281b86738fbadaf7b92 (patch) | |
| tree | 30020f6bc3f014cd218770a0629b57b13b89249d /pyecsca | |
| parent | e6d9e4882af80560d0353bcd5bd22b438e54c0d7 (diff) | |
| download | pyecsca-c2ad6329efa5d0024dc0b281b86738fbadaf7b92.tar.gz pyecsca-c2ad6329efa5d0024dc0b281b86738fbadaf7b92.tar.zst pyecsca-c2ad6329efa5d0024dc0b281b86738fbadaf7b92.zip | |
Make certain aspects of the library configurable.
Fixes #5.
Diffstat (limited to 'pyecsca')
| -rw-r--r-- | pyecsca/cfg.py | 145 | ||||
| -rw-r--r-- | pyecsca/ec/error.py | 31 | ||||
| -rw-r--r-- | pyecsca/ec/formula.py | 9 | ||||
| -rw-r--r-- | pyecsca/ec/mod.py | 31 | ||||
| -rw-r--r-- | pyecsca/ec/params.py | 14 |
5 files changed, 206 insertions, 24 deletions
diff --git a/pyecsca/cfg.py b/pyecsca/cfg.py new file mode 100644 index 0000000..b1c4007 --- /dev/null +++ b/pyecsca/cfg.py @@ -0,0 +1,145 @@ +from copy import deepcopy +from contextvars import ContextVar, Token + +from public import public + + +@public +class ECConfig(object): + """Configuration for the :py:mod:`pyecsca.ec` package.""" + _no_inverse_action: str = "error" + _non_residue_action: str = "error" + _unsatisfied_formula_assumption_action: str = "error" + _unsatisfied_coordinate_assumption_action: str = "error" + _mod_implementation: str = "gmp" + + @property + def no_inverse_action(self) -> str: + """ + The action to take when a non-invertible element is to be inverted. One of: + + - `"error"`: Raise :py:class:`pyecsca.ec.error.NonInvertibleError`. + - `"warning"`: Raise :py:class:`pyecsca.ec.error.NonInvertibleWarning`. + - `"ignore"`: Ignore the event and compute as if nothing happened.""" + return self._no_inverse_action + + @no_inverse_action.setter + def no_inverse_action(self, value: str): + if value not in ("error", "warning", "ignore"): + raise ValueError("Action has to be one of 'error', 'warning', 'ignore'.") + self._no_inverse_action = value + + @property + def non_residue_action(self) -> str: + """ + The action to take when a the square-root of a non-residue is to be computed. One of: + + - `"error"`: Raise :py:class:`pyecsca.ec.error.NonResidueError`. + - `"warning"`: Raise :py:class:`pyecsca.ec.error.NonResidueWarning`. + - `"ignore"`: Ignore the event and compute as if nothing happened.""" + return self._non_residue_action + + @non_residue_action.setter + def non_residue_action(self, value: str): + if value not in ("error", "warning", "ignore"): + raise ValueError("Action has to be one of 'error', 'warning', 'ignore'.") + self._non_residue_action = value + + @property + def unsatisfied_formula_assumption_action(self) -> str: + """ + The action to take when a formula assumption is unsatisfied during execution. + This works for assumption that can be ignored without a fatal error, + which are those that are not used to compute a value of an undefined parameter. + For example, things of the form `Z1 = 1`. + One of: + + - `"error"`: Raise :py:class:`pyecsca.ec.error.UnsatisfiedAssumptionError`. + - `"warning"`: Raise :py:class:`pyecsca.ec.error.UnsatisfiedAssumptionWarning`. + - `"ignore"`: Ignore the event and compute as if nothing happened. + """ + return self._unsatisfied_formula_assumption_action + + @unsatisfied_formula_assumption_action.setter + def unsatisfied_formula_assumption_action(self, value: str): + if value not in ("error", "warning", "ignore"): + raise ValueError("Action has to be one of 'error', 'warning', 'ignore'.") + self._unsatisfied_formula_assumption_action = value + + @property + def unsatisfied_coordinate_assumption_action(self) -> str: + """ + The action to take when a coordinate assumption is unsatisfied during curve creation. + This works for assumption that can be ignored without a fatal error, + which are those that are not used to compute a value of an undefined parameter. + For example, things of the form `a = -1`. + One of: + + - `"error"`: Raise :py:class:`pyecsca.ec.error.UnsatisfiedAssumptionError`. + - `"warning"`: Raise :py:class:`pyecsca.ec.error.UnsatisfiedAssumptionWarning`. + - `"ignore"`: Ignore the event and compute as if nothing happened. + """ + return self._unsatisfied_coordinate_assumption_action + + @unsatisfied_coordinate_assumption_action.setter + def unsatisfied_coordinate_assumption_action(self, value: str): + if value not in ("error", "warning", "ignore"): + raise ValueError("Action has to be one of 'error', 'warning', 'ignore'.") + self._unsatisfied_coordinate_assumption_action = value + + @property + def mod_implementation(self) -> str: + """ + The selected :py:class:`pyecsca.ec.mod.Mod` implementation. One of: + + - `"gmp"`: Requires the GMP library and `gmpy2` package. + - `"python"`: Doesn't require anything. + """ + return self._mod_implementation + + @mod_implementation.setter + def mod_implementation(self, value: str): + if value not in ("python", "gmp"): + raise ValueError(f"Bad Mod implementaiton, can be one of 'python' or 'gmp'.") + self._mod_implementation = value + + +@public +class Config(object): + """A runtime configuration for the library.""" + ec: ECConfig + """Configuration for the :py:mod:`pyecsca.ec` package.""" + + def __init__(self): + self.ec = ECConfig() + + +_config: ContextVar[Config] = ContextVar("config", default=Config()) + + +@public +def getconfig() -> Config: + return _config.get() + + +@public +def setconfig(cfg: Config) -> Token: + return _config.set(cfg) + + +@public +def resetconfig(token: Token): + _config.reset(token) + + +@public +class TemporaryConfig(object): + def __init__(self): + self.new_config = deepcopy(getconfig()) + + def __enter__(self) -> Config: + self.token = setconfig(self.new_config) + return self.new_config + + def __exit__(self, t, v, tb): + resetconfig(self.token)
\ No newline at end of file diff --git a/pyecsca/ec/error.py b/pyecsca/ec/error.py index f350d9e..8aabe06 100644 --- a/pyecsca/ec/error.py +++ b/pyecsca/ec/error.py @@ -1,5 +1,5 @@ from public import public - +from ..cfg import getconfig @public class NonInvertibleError(ArithmeticError): @@ -11,6 +11,16 @@ class NonInvertibleWarning(UserWarning): pass +def raise_non_invertible(): + """Raise either :py:class:`NonInvertibleError` or :py:class:`NonInvertiblerWarning` or ignore. + Depends on the current config value of `no_inverse_action`.""" + cfg = getconfig() + if cfg.ec.no_inverse_action == "error": + raise NonInvertibleError("Element not invertible.") + elif cfg.ec.no_inverse_action == "warning": + raise NonInvertibleWarning("Element not invertible.") + + @public class NonResidueError(ArithmeticError): pass @@ -21,6 +31,16 @@ class NonResidueWarning(UserWarning): pass +def raise_non_residue(): + """Raise either :py:class:`NonResidueError` or :py:class:`NonResidueWarning` or ignore. + Depends on the current config value of `non_residue_action`.""" + cfg = getconfig() + if cfg.ec.non_residue_action == "error": + raise NonResidueError("No square root exists.") + elif cfg.ec.non_residue_action == "warning": + raise NonResidueWarning("No square root exists.") + + @public class UnsatisfiedAssumptionError(ValueError): pass @@ -29,3 +49,12 @@ class UnsatisfiedAssumptionError(ValueError): @public class UnsatisfiedAssumptionWarning(UserWarning): pass + + +def raise_unsatisified_assumption(action: str, msg: str): + """Raise either :py:class:`UnsatisfiedAssumptionError` or :py:class:`UnsatisfiedAssumptionWarning` or ignore. + Depends on the value of `action`.""" + if action == "error": + raise UnsatisfiedAssumptionError(msg) + elif action == "warning": + raise UnsatisfiedAssumptionWarning(msg) diff --git a/pyecsca/ec/formula.py b/pyecsca/ec/formula.py index 03681ec..741bb99 100644 --- a/pyecsca/ec/formula.py +++ b/pyecsca/ec/formula.py @@ -9,9 +9,10 @@ from public import public from sympy import sympify, FF, symbols, Poly from .context import ResultAction, getcontext, NullContext -from .error import UnsatisfiedAssumptionError +from .error import UnsatisfiedAssumptionError, raise_unsatisified_assumption from .mod import Mod from .op import CodeOp, OpType +from ..cfg import getconfig @public @@ -131,7 +132,7 @@ class Formula(ABC): for coord, value in point.coords.items(): params[coord + str(i + 1)] = value # Validate assumptions and compute formula parameters. - field = int(params[next(iter(params.keys()))].n) # This is nasty... + field = int(params[next(iter(params.keys()))].n) # TODO: This is nasty... for assumption in self.assumptions: assumption_string = unparse(assumption)[1:-2] lhs, rhs = assumption_string.split(" == ") @@ -141,7 +142,9 @@ class Formula(ABC): compiled = compile(assumption, "", mode="eval") holds = eval(compiled, None, alocals) if not holds: - raise UnsatisfiedAssumptionError(f"Unsatisfied assumption in the formula ({assumption_string}).") + # The assumption doesn't hold, see what is the current configured action and do it. + raise_unsatisified_assumption(getconfig().ec.unsatisfied_formula_assumption_action, + f"Unsatisfied assumption in the formula ({assumption_string}).") else: k = FF(field) expr = sympify(f"{rhs} - {lhs}") diff --git a/pyecsca/ec/mod.py b/pyecsca/ec/mod.py index 65018f7..3943b0c 100644 --- a/pyecsca/ec/mod.py +++ b/pyecsca/ec/mod.py @@ -4,9 +4,9 @@ from functools import wraps, lru_cache from abc import abstractmethod from public import public -from .error import NonInvertibleError, NonResidueError +from .error import raise_non_invertible, raise_non_residue from .context import ResultAction - +from ..cfg import getconfig has_gmp = False try: @@ -104,7 +104,7 @@ class RandomModAction(ResultAction): return f"{self.__class__.__name__}({self.order:x})" -_mod_classes = [] +_mod_classes = {} @public @@ -114,8 +114,12 @@ class Mod(object): if cls != Mod: return cls.__new__(cls, *args, **kwargs) if not _mod_classes: - raise ValueError("Cannot find a working Mod class.") - return _mod_classes[-1].__new__(_mod_classes[-1], *args, **kwargs) + raise ValueError("Cannot find any working Mod class.") + selected_class = getconfig().ec.mod_implementation + if selected_class not in _mod_classes: + # Fallback to something + selected_class = next(iter(_mod_classes.keys())) + return _mod_classes[selected_class].__new__(_mod_classes[selected_class], *args, **kwargs) def __init__(self, x, n): self.x = x @@ -245,10 +249,10 @@ class RawMod(Mod): def inverse(self): if self.x == 0: - raise NonInvertibleError("Inverting zero.") + raise_non_invertible() x, y, d = extgcd(self.x, self.n) if d != 1: - raise NonInvertibleError("Element not invertible.") + raise_non_invertible() return RawMod(x, self.n) def is_residue(self): @@ -267,7 +271,7 @@ class RawMod(Mod): if self.x == 0: return RawMod(0, self.n) if not self.is_residue(): - raise NonResidueError("No square root exists.") + raise_non_residue() if self.n % 4 == 3: return self ** int((self.n + 1) // 4) q = self.n - 1 @@ -330,7 +334,7 @@ class RawMod(Mod): return RawMod(pow(self.x, n, self.n), self.n) -_mod_classes.append(RawMod) +_mod_classes["python"] = RawMod @public @@ -430,13 +434,14 @@ if has_gmp: def inverse(self): if self.x == 0: - raise NonInvertibleError("Inverting zero!") + raise_non_invertible() if self.x == 1: return GMPMod(1, self.n) try: res = gmpy2.invert(self.x, self.n) except ZeroDivisionError: - raise NonInvertibleError("Element not invertible.") + raise_non_invertible() + res = 0 return GMPMod(res, self.n) def is_residue(self): @@ -460,7 +465,7 @@ if has_gmp: if self.x == 0: return GMPMod(0, self.n) if not self.is_residue(): - raise NonResidueError("No square root exists.") + raise_non_residue() if self.n % 4 == 3: return self ** int((self.n + 1) // 4) q = self.n - 1 @@ -527,4 +532,4 @@ if has_gmp: return GMPMod(gmpy2.powmod(self.x, gmpy2.mpz(n), self.n), self.n) - _mod_classes.append(GMPMod) + _mod_classes["gmp"] = GMPMod diff --git a/pyecsca/ec/params.py b/pyecsca/ec/params.py index 2ec9706..b9495b8 100644 --- a/pyecsca/ec/params.py +++ b/pyecsca/ec/params.py @@ -11,11 +11,12 @@ from public import public from .coordinates import AffineCoordinateModel, CoordinateModel from .curve import EllipticCurve -from .error import UnsatisfiedAssumptionError +from .error import UnsatisfiedAssumptionError, raise_unsatisified_assumption from .mod import Mod from .model import (CurveModel, ShortWeierstrassModel, MontgomeryModel, EdwardsModel, TwistedEdwardsModel) from .point import Point, InfinityPoint +from ..cfg import getconfig @public @@ -127,15 +128,15 @@ def _create_params(curve, coords, infty): for assumption in coord_model.assumptions: # 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. + # 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 UnsatisfiedAssumptionError( - f"Coordinate model {coord_model} has an unsatisifed assumption on the {param} parameter (= {value}).") + raise_unsatisified_assumption(getconfig().ec.unsatisfied_coordinate_assumption_action, + f"Coordinate model {coord_model} has an unsatisifed assumption on the {param} parameter (= {value}).") except NameError: k = FF(field) assumption_string = unparse(assumption) @@ -148,9 +149,8 @@ def _create_params(curve, coords, infty): 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 + params[param] = Mod(int(root), field) + break else: raise UnsatisfiedAssumptionError(f"Coordinate model {coord_model} has an unsatisifed assumption on the {param} parameter (0 = {expr}).") |
