Source code for pyecsca.ec.configuration

"""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
from typing import (
    get_type_hints,
    Union,
    get_origin,
    get_args,
    Generator,
    FrozenSet,
)

from public import public

from .coordinates import CoordinateModel
from .formula import Formula
from .model import CurveModel
from .mult import ScalarMultiplier


[docs] @public class EnumDefine(Enum): def __str__(self): return self.value def __repr__(self): return self.value
[docs] @classmethod def names(cls): return [e.name for e in cls]
[docs] @classmethod def values(cls): return [e.value for e in cls]
[docs] @public class Multiplication(EnumDefine): """Base multiplication algorithm to use.""" TOOM_COOK = "MUL_TOOM_COOK" KARATSUBA = "MUL_KARATSUBA" COMBA = "MUL_COMBA" BASE = "MUL_BASE"
[docs] @public class Squaring(EnumDefine): """Base squaring algorithm to use.""" TOOM_COOK = "SQR_TOOM_COOK" KARATSUBA = "SQR_KARATSUBA" COMBA = "SQR_COMBA" BASE = "SQR_BASE"
[docs] @public class Reduction(EnumDefine): """Modular reduction method used.""" BARRETT = "RED_BARRETT" MONTGOMERY = "RED_MONTGOMERY" BASE = "RED_BASE"
[docs] @public class Inversion(EnumDefine): """Inversion algorithm used.""" GCD = "INV_GCD" EULER = "INV_EULER"
[docs] @public class HashType(EnumDefine): """Hash algorithm used in ECDH and ECDSA.""" NONE = "HASH_NONE" SHA1 = "HASH_SHA1" SHA224 = "HASH_SHA224" SHA256 = "HASH_SHA256" SHA384 = "HASH_SHA384" SHA512 = "HASH_SHA512"
[docs] @public class RandomMod(EnumDefine): """Method of sampling a uniform integer modulo order.""" SAMPLE = "MOD_RAND_SAMPLE" REDUCE = "MOD_RAND_REDUCE"
[docs] @public @dataclass(frozen=True) class Configuration: """An ECC implementation configuration.""" model: CurveModel coords: CoordinateModel formulas: FrozenSet[Formula] scalarmult: ScalarMultiplier hash_type: HashType mod_rand: RandomMod mult: Multiplication sqr: Squaring red: Reduction inv: Inversion
[docs] @public def all_configurations(**kwargs) -> Generator[Configuration, None, None]: """ Get all implementation configurations that match the given `kwargs`. The keys in :paramref:`~.all_configurations.kwargs` should be some of the attributes in the :py:class:`Configuration`, and the values limit the returned configurations to configuration matching them. .. note:: The ``formulas`` attribute is unsupported and formulas should be provided using the ``scalarmult`` attribute, which is either a subclass of the :py:class:`~.ScalarMultiplier` class or an instance of it or a dictionary giving arguments to a constructor of some :py:class:`~.ScalarMultiplier` subclass. .. warning:: The returned number of configurations might be quite large and take up significant memory space. Use this generator and do not store the results. :param kwargs: The configuration parameters to match. :return: A generator of the configurations. """ def is_optional(arg_type): return ( get_origin(arg_type) == Union and len(get_args(arg_type)) == 2 and get_args(arg_type)[1] is type(None) # noqa ) def leaf_subclasses(cls): subs = cls.__subclasses__() result = set() for subclass in subs: if subclass.__subclasses__(): result.update(leaf_subclasses(subclass)) elif ABC not in subclass.__bases__: result.add(subclass) return result def independents(kwargs): options = { "hash_type": HashType, "mod_rand": RandomMod, "mult": Multiplication, "sqr": Squaring, "red": Reduction, "inv": Inversion, } keys = list(filter(lambda key: key not in kwargs, options.keys())) values = [options[key] for key in keys] fixed_args = {key: kwargs[key] for key in kwargs if key in options} for value_choice in product(*values): yield dict(zip(keys, value_choice), **fixed_args) def multipliers(mult_classes, coords_formulas, fixed_args=None): for mult_cls in mult_classes: if ( fixed_args is not None and "cls" in fixed_args and mult_cls != fixed_args["cls"] ): continue arg_options = {} for name, required_type in get_type_hints(mult_cls.__init__).items(): if fixed_args is not None and name in fixed_args: arg_options[name] = [fixed_args[name]] continue if is_optional(required_type): opt_type = get_args(required_type)[0] if issubclass(opt_type, Formula): options = [ formula for formula in coords_formulas if isinstance(formula, opt_type) ] + [None] else: options = [None] # TODO: anything here? elif get_origin(required_type) is None and issubclass( required_type, Formula ): options = [ formula for formula in coords_formulas if isinstance(formula, required_type) ] elif get_origin(required_type) is None and issubclass( required_type, bool ): options = [True, False] elif get_origin(required_type) is None and issubclass( required_type, Enum ): options = list(required_type) elif ( get_origin(required_type) is None and issubclass(required_type, int) and name == "width" ): # Magic numbers from library analysis, comb/window width options = [4, 5, 6, 7] elif ( get_origin(required_type) is None and issubclass(required_type, int) and name == "m" ): # Magic numbers from library analysis, comb/window width options = [2**4, 2**5, 2**6, 2**7] else: warnings.warn( RuntimeWarning(f"Unknown scalarmult option range = {name}") ) options = [] arg_options[name] = options keys = arg_options.keys() values = arg_options.values() for combination in product(*values): try: mult = mult_cls(**dict(zip(keys, combination))) except Exception: continue yield mult for model_cls in leaf_subclasses(CurveModel): model = model_cls() if "model" in kwargs and model != kwargs["model"]: continue for coords in model.coordinates.values(): if "coords" in kwargs and coords != kwargs["coords"]: continue coords_formulas = coords.formulas.values() mult_classes = leaf_subclasses(ScalarMultiplier) if "scalarmult" in kwargs: if isinstance(kwargs["scalarmult"], ScalarMultiplier): mults = [kwargs["scalarmult"]] if not set(kwargs["scalarmult"].formulas.values()).issubset( coords_formulas ): continue elif isinstance(kwargs["scalarmult"], type) and issubclass( kwargs["scalarmult"], ScalarMultiplier ): mult_classes = list( filter( lambda mult: issubclass(mult, kwargs["scalarmult"]), mult_classes, ) ) mults = multipliers(mult_classes, coords_formulas) else: mults = multipliers( mult_classes, coords_formulas, kwargs["scalarmult"] ) else: mults = multipliers(mult_classes, coords_formulas) for mult in mults: formulas = frozenset(mult.formulas.values()) for independent_args in independents(kwargs): yield Configuration( model, coords, formulas, mult, **independent_args )