diff options
80 files changed, 2996 insertions, 1074 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e940b0b..2ac2274 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,4 +19,4 @@ repos: rev: 3.9.0 hooks: - id: flake8 - args: ["--ignore=E501,F405,F403,F401,E126"] + args: ["--extend-ignore=E501,F405,F403,F401,E126,E203"] @@ -25,10 +25,16 @@ typecheck-all: mypy pyecsca test --ignore-missing-imports --show-error-codes codestyle: - flake8 --ignore=E501,F405,F403,F401,E126 pyecsca + flake8 --extend-ignore=E501,F405,F403,F401,E126,E203 pyecsca codestyle-all: - flake8 --ignore=E501,F405,F403,F401,E126 pyecsca test + flake8 --extend-ignore=E501,F405,F403,F401,E126,E203 pyecsca test + +black: + black pyecsca + +black-all: + black pyecsca test perf: ${PERF_SCRIPTS} mkdir -p .perf @@ -41,4 +47,22 @@ docs: $(MAKE) -C docs apidoc $(MAKE) -C docs html -.PHONY: test test-plots test-all typecheck typecheck-all codestyle codestyle-all perf doc-coverage docs +help: + @echo "pyecsca, Python Elliptic Curve cryptography Side-Channel Analysis toolkit." + @echo + @echo "Available targets:" + @echo " - test: Test pyecsca." + @echo " - test-plots: Test pyecsca and produce debugging plots." + @echo " - test-all: Test pyecsca but also run slow (and disabled) tests." + @echo " - typecheck: Use mypy to verify the use of types in pyecsca." + @echo " - typecheck-all: Use mypy to verify the use of types in pyecsca and in tests." + @echo " - codestyle: Use flake8 to check codestyle in pyecsca." + @echo " - codestyle-all: Use flake8 to check codestyle in pyecsca and in tests." + @echo " - black: Run black on pyecsca sources (will transform them inplace)." + @echo " - black-all: Run black on pyecsca sources and tests (will transform them inplace)." + @echo " - perf: Run performance measurements (prints results and stores them in .perf/)." + @echo " - doc-coverage: Use interrogate to check documentation coverage of public API." + @echo " - docs: Build docs using sphinx." + @echo " - help: Show this help." + +.PHONY: test test-plots test-all typecheck typecheck-all codestyle codestyle-all black black-all perf doc-coverage docs @@ -1,6 +1,6 @@ #  -[](https://neuromancer.sk/pyecsca/) [](https://github.com/J08nY/pyecsca/blob/master/LICENSE)   [](https://codecov.io/gh/J08nY/pyecsca)  +[](https://neuromancer.sk/pyecsca/) [](https://github.com/J08nY/pyecsca/blob/master/LICENSE)   [](https://codecov.io/gh/J08nY/pyecsca)  [](https://deepsource.io/gh/J08nY/pyecsca/?ref=repository-badge) **Py**thon **E**lliptic **C**urve cryptography **S**ide-**C**hannel **A**nalysis toolkit. @@ -60,6 +60,8 @@ It also supports working with [Riscure](https://www.riscure.com) Inspector trace ### Testing & Development +See the [Makefile](Makefile) for tests, performance measurement, codestyle and type checking commands. + - [nose2](https://nose2.readthedocs.io) - [green](https://github.com/CleanCut/green) - [mypy](http://mypy-lang.org/) @@ -69,6 +71,7 @@ It also supports working with [Riscure](https://www.riscure.com) Inspector trace - [interrogate](https://interrogate.readthedocs.io/) - [pyinstrument](https://github.com/joerick/pyinstrument/) - [pre-commit](https://pre-commit.com/) at `.pre-commit-config.yaml` + - [black](https://github.com/psf/black) ### Docs diff --git a/docs/index.rst b/docs/index.rst index 4ec985c..bde29be 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,6 +13,8 @@ pyecsca [pɪɛtska] .. image:: https://img.shields.io/codecov/c/gh/J08nY/pyecsca?color=brightgreen&logo=codecov :target: https://codecov.io/gh/J08nY/pyecsca .. image:: https://img.shields.io/static/v1?label=mypy&message=No%20issues&color=brightgreen +.. image:: https://deepsource.io/gh/J08nY/pyecsca.svg/?label=active+issues&show_trend=true + :target: https://deepsource.io/gh/J08nY/pyecsca/?ref=repository-badge **Py**\ thon **E**\ lliptic **C**\ urve cryptography **S**\ ide-**C**\ hannel **A**\ nalysis toolkit. @@ -107,6 +109,8 @@ Testing - coverage_ - interrogate_ - pyinstrument_ + - pre-commit_ + - black_ Docs ---- @@ -169,6 +173,8 @@ this support is very appreciated. .. _coverage: https://coverage.readthedocs.io/ .. _interrogate: https://interrogate.readthedocs.io/ .. _pyinstrument: https://github.com/joerick/pyinstrument/ +.. _pre-commit: https://pre-commit.com +.. _black: https://github.com/psf/black .. _sphinx: https://www.sphinx-doc.org/ .. _sphinx-autodoc-typehints: https://pypi.org/project/sphinx-autodoc-typehints/ .. _nbsphinx: https://nbsphinx.readthedocs.io/ diff --git a/pyecsca/ec/configuration.py b/pyecsca/ec/configuration.py index 04f8fc7..bf20ec7 100644 --- a/pyecsca/ec/configuration.py +++ b/pyecsca/ec/configuration.py @@ -34,6 +34,7 @@ class EnumDefine(Enum): @public class Multiplication(EnumDefine): """Base multiplication algorithm to use.""" + TOOM_COOK = "MUL_TOOM_COOK" KARATSUBA = "MUL_KARATSUBA" COMBA = "MUL_COMBA" @@ -43,6 +44,7 @@ class Multiplication(EnumDefine): @public class Squaring(EnumDefine): """Base squaring algorithm to use.""" + TOOM_COOK = "SQR_TOOM_COOK" KARATSUBA = "SQR_KARATSUBA" COMBA = "SQR_COMBA" @@ -52,6 +54,7 @@ class Squaring(EnumDefine): @public class Reduction(EnumDefine): """Modular reduction method used.""" + BARRETT = "RED_BARRETT" MONTGOMERY = "RED_MONTGOMERY" BASE = "RED_BASE" @@ -60,6 +63,7 @@ class Reduction(EnumDefine): @public class Inversion(EnumDefine): """Inversion algorithm used.""" + GCD = "INV_GCD" EULER = "INV_EULER" @@ -67,6 +71,7 @@ class Inversion(EnumDefine): @public class HashType(EnumDefine): """Hash algorithm used in ECDH and ECDSA.""" + NONE = "HASH_NONE" SHA1 = "HASH_SHA1" SHA224 = "HASH_SHA224" @@ -78,6 +83,7 @@ class HashType(EnumDefine): @public class RandomMod(EnumDefine): """Method of sampling a uniform integer modulo order.""" + SAMPLE = "MOD_RAND_SAMPLE" REDUCE = "MOD_RAND_REDUCE" @@ -86,6 +92,7 @@ class RandomMod(EnumDefine): @dataclass(frozen=True) class Configuration(object): """An ECC implementation configuration.""" + model: CurveModel coords: CoordinateModel formulas: FrozenSet[Formula] @@ -120,8 +127,11 @@ def all_configurations(**kwargs) -> Generator[Configuration, Configuration, None """ def is_optional(arg_type): - return get_origin(arg_type) == Union and len(get_args(arg_type)) == 2 and \ - get_args(arg_type)[1] == type(None) # noqa + return ( + get_origin(arg_type) == Union + and len(get_args(arg_type)) == 2 + and get_args(arg_type)[1] == type(None) # noqa + ) def leaf_subclasses(cls): subs = cls.__subclasses__() @@ -140,7 +150,7 @@ def all_configurations(**kwargs) -> Generator[Configuration, Configuration, None "mult": Multiplication, "sqr": Squaring, "red": Reduction, - "inv": Inversion + "inv": Inversion, } keys = list(filter(lambda key: key not in kwargs, options.keys())) values = [options[key] for key in keys] @@ -150,7 +160,11 @@ def all_configurations(**kwargs) -> Generator[Configuration, Configuration, None 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"]: + 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(): @@ -160,17 +174,30 @@ def all_configurations(**kwargs) -> Generator[Configuration, Configuration, None 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] + 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): + 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, - int) and name == "width": + elif ( + get_origin(required_type) is None + and issubclass(required_type, int) + and name == "width" + ): options = [3, 5] else: options = [] @@ -198,19 +225,29 @@ def all_configurations(**kwargs) -> Generator[Configuration, Configuration, None if "scalarmult" in kwargs: if isinstance(kwargs["scalarmult"], ScalarMultiplier): mults = [kwargs["scalarmult"]] - if not set(kwargs["scalarmult"].formulas.values()).issubset(coords_formulas): + if not set(kwargs["scalarmult"].formulas.values()).issubset( + coords_formulas + ): continue - elif isinstance(kwargs["scalarmult"], type) and issubclass(kwargs["scalarmult"], - ScalarMultiplier): + elif isinstance(kwargs["scalarmult"], type) and issubclass( + kwargs["scalarmult"], ScalarMultiplier + ): mult_classes = list( - filter(lambda mult: issubclass(mult, kwargs["scalarmult"]), - mult_classes)) + filter( + lambda mult: issubclass(mult, kwargs["scalarmult"]), + mult_classes, + ) + ) mults = multipliers(mult_classes, coords_formulas) else: - mults = multipliers(mult_classes, coords_formulas, kwargs["scalarmult"]) + 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) + yield Configuration( + model, coords, formulas, mult, **independent_args + ) diff --git a/pyecsca/ec/context.py b/pyecsca/ec/context.py index 3b38584..5bef891 100644 --- a/pyecsca/ec/context.py +++ b/pyecsca/ec/context.py @@ -23,6 +23,7 @@ from public import public @public class Action(object): """An Action.""" + inside: bool def __init__(self): @@ -41,6 +42,7 @@ class Action(object): @public class ResultAction(Action): """An action that has a result.""" + _result: Any = None _has_result: bool = False @@ -60,7 +62,12 @@ class ResultAction(Action): return result def __exit__(self, exc_type, exc_val, exc_tb): - if not self._has_result and exc_type is None and exc_val is None and exc_tb is None: + if ( + not self._has_result + and exc_type is None + and exc_val is None + and exc_tb is None + ): raise RuntimeError("Result unset on action exit") super().__exit__(exc_type, exc_val, exc_tb) @@ -167,6 +174,7 @@ class NullContext(Context): @public class DefaultContext(Context): """A context that traces executions of actions in a tree.""" + actions: Tree current: List[Action] @@ -190,6 +198,7 @@ class DefaultContext(Context): @public class PathContext(Context): """A context that traces targeted actions.""" + path: List[int] current: List[int] current_depth: int @@ -212,7 +221,7 @@ class PathContext(Context): else: self.current[self.current_depth] += 1 self.current_depth += 1 - if self.path == self.current[:self.current_depth]: + if self.path == self.current[: self.current_depth]: self.value = action def exit_action(self, action: Action) -> None: @@ -221,10 +230,14 @@ class PathContext(Context): self.current_depth -= 1 def __repr__(self): - return f"{self.__class__.__name__}({self.current!r}, depth={self.current_depth!r})" + return ( + f"{self.__class__.__name__}({self.current!r}, depth={self.current_depth!r})" + ) -_actual_context: ContextVar[Context] = ContextVar("operational_context", default=NullContext()) +_actual_context: ContextVar[Context] = ContextVar( + "operational_context", default=NullContext() +) class _ContextManager(object): diff --git a/pyecsca/ec/coordinates.py b/pyecsca/ec/coordinates.py index 7c76746..24a0b1a 100644 --- a/pyecsca/ec/coordinates.py +++ b/pyecsca/ec/coordinates.py @@ -8,14 +8,23 @@ from typing import List, Any, MutableMapping from pkg_resources import resource_listdir, resource_isdir, resource_stream from public import public -from .formula import (Formula, EFDFormula, AdditionEFDFormula, DoublingEFDFormula, - TriplingEFDFormula, DifferentialAdditionEFDFormula, LadderEFDFormula, - ScalingEFDFormula, NegationEFDFormula) +from .formula import ( + Formula, + EFDFormula, + AdditionEFDFormula, + DoublingEFDFormula, + TriplingEFDFormula, + DifferentialAdditionEFDFormula, + LadderEFDFormula, + ScalingEFDFormula, + NegationEFDFormula, +) @public class CoordinateModel(object): """A coordinate system for a particular model(form) of an elliptic curve.""" + name: str """Name of the coordinate model""" full_name: str @@ -37,7 +46,7 @@ class CoordinateModel(object): """Formulas available on the coordinate system.""" def __repr__(self): - return f"{self.__class__.__name__}(\"{self.name}\" on {self.curve_model.name})" + return f'{self.__class__.__name__}("{self.name}" on {self.curve_model.name})' @public @@ -61,7 +70,6 @@ class AffineCoordinateModel(CoordinateModel): class EFDCoordinateModel(CoordinateModel): - def __init__(self, dir_path: str, name: str, curve_model: Any): self.name = name self.curve_model = curve_model @@ -89,7 +97,7 @@ class EFDCoordinateModel(CoordinateModel): "diffadd": DifferentialAdditionEFDFormula, "ladder": LadderEFDFormula, "scaling": ScalingEFDFormula, - "negation": NegationEFDFormula + "negation": NegationEFDFormula, } cls = formula_types.get(formula_type, EFDFormula) self.formulas[fname] = cls(join(dir_path, fname), fname, self) @@ -118,7 +126,8 @@ class EFDCoordinateModel(CoordinateModel): self.parameters.append(line[10:]) elif line.startswith("assume"): self.assumptions.append( - parse(line[7:].replace("^", "**"), mode="exec")) + parse(line[7:].replace("^", "**"), mode="exec") + ) line = f.readline().decode("ascii").rstrip() def __eq__(self, other): diff --git a/pyecsca/ec/curve.py b/pyecsca/ec/curve.py index 4cfb978..bfa58c8 100644 --- a/pyecsca/ec/curve.py +++ b/pyecsca/ec/curve.py @@ -16,6 +16,7 @@ from .point import Point, InfinityPoint @public class EllipticCurve(object): """An elliptic curve.""" + model: CurveModel """The model of the curve.""" coordinate_model: CoordinateModel @@ -27,11 +28,23 @@ class EllipticCurve(object): neutral: Point """The neutral point on the curve.""" - def __init__(self, model: CurveModel, coordinate_model: CoordinateModel, - prime: int, neutral: Point, parameters: MutableMapping[str, Union[Mod, int]]): - if coordinate_model not in model.coordinates.values() and not isinstance(coordinate_model, AffineCoordinateModel): + def __init__( + self, + model: CurveModel, + coordinate_model: CoordinateModel, + 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).union(coordinate_model.parameters).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 @@ -52,8 +65,11 @@ class EllipticCurve(object): raise ValueError("Coordinate model of point is not affine.") if point.coordinate_model.curve_model != self.model: raise ValueError("Curve model of point does not match the curve.") - locls = {var + str(i + 1): point.coords[var] - for i, point in enumerate(points) for var in point.coords} + locls = { + var + str(i + 1): point.coords[var] + for i, point in enumerate(points) + for var in point.coords + } locls.update(self.parameters) for line in formulas: exec(compile(line, "", mode="exec"), None, locls) @@ -228,7 +244,9 @@ class EllipticCurve(object): else: raise NotImplementedError else: - raise ValueError(f"Wrong encoding type: {hex(encoded[0])}, should be one of 0x04, 0x06, 0x02, 0x03 or 0x00") + raise ValueError( + f"Wrong encoding type: {hex(encoded[0])}, should be one of 0x04, 0x06, 0x02, 0x03 or 0x00" + ) def affine_random(self) -> Point: """Generate a random affine point on the curve.""" @@ -246,7 +264,12 @@ class EllipticCurve(object): def __eq__(self, other): if not isinstance(other, EllipticCurve): return False - return self.model == other.model and self.coordinate_model == other.coordinate_model and self.prime == other.prime and self.parameters == other.parameters + return ( + self.model == other.model + and self.coordinate_model == other.coordinate_model + and self.prime == other.prime + and self.parameters == other.parameters + ) def __str__(self): return "EllipticCurve" diff --git a/pyecsca/ec/formula.py b/pyecsca/ec/formula.py index d0435f8..c0505e1 100644 --- a/pyecsca/ec/formula.py +++ b/pyecsca/ec/formula.py @@ -21,6 +21,7 @@ from ..misc.cfg import getconfig @public class OpResult(object): """A result of an operation.""" + parents: Tuple op: OpType name: str @@ -44,6 +45,7 @@ class OpResult(object): @public class FormulaAction(ResultAction): """An execution of a formula, on some input points and parameters, with some outputs.""" + formula: "Formula" """The formula that was executed.""" inputs: MutableMapping[str, Mod] @@ -95,6 +97,7 @@ class FormulaAction(ResultAction): @public class Formula(ABC): """A formula operating on points.""" + name: str """Name of the formula.""" shortname: ClassVar[str] @@ -131,7 +134,9 @@ class Formula(ABC): raise ValueError(f"Wrong coordinate model of point {point}.") for coord, value in point.coords.items(): if not isinstance(value, Mod) or value.n != field: - raise ValueError(f"Wrong coordinate input {coord} = {value} of point {i}.") + raise ValueError( + f"Wrong coordinate input {coord} = {value} of point {i}." + ) params[coord + str(i + 1)] = value def __validate_assumptions(self, field, params): @@ -146,16 +151,22 @@ class Formula(ABC): holds = eval(compiled, None, alocals) if not holds: # 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}).") + 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}", evaluate=False) 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 self.parameters: + if ( + len(expr.free_symbols) > 1 + or (param := str(expr.free_symbols.pop())) not in self.parameters + ): raise ValueError( - f"This formula couldn't be executed due to an unsupported assumption ({assumption_string}).") + f"This formula couldn't be executed due to an unsupported assumption ({assumption_string})." + ) def resolve(expression): if not expression.args: @@ -178,7 +189,9 @@ class Formula(ABC): params[param] = Mod(int(root), field) break else: - raise UnsatisfiedAssumptionError(f"Unsatisfied assumption in the formula ({assumption_string}).") + raise UnsatisfiedAssumptionError( + f"Unsatisfied assumption in the formula ({assumption_string})." + ) def __call__(self, field: int, *points: Any, **params: Mod) -> Tuple[Any, ...]: """ @@ -190,6 +203,7 @@ class Formula(ABC): :return: The resulting point(s). """ from .point import Point + self.__validate_params(field, params) self.__validate_points(field, points, params) self.__validate_assumptions(field, params) @@ -201,7 +215,9 @@ class Formula(ABC): # TODO: This is not general enough, if for example the op is `t = 1/2`, it will be float. # Temporarily, add an assertion that this does not happen so we do not give bad results. if isinstance(op_result, float): - raise AssertionError(f"Bad stuff happened in op {op}, floats will pollute the results.") + raise AssertionError( + f"Bad stuff happened in op {op}, floats will pollute the results." + ) if not isinstance(op_result, Mod): op_result = Mod(op_result, field) action.add_operation(op, op_result) @@ -285,8 +301,14 @@ class Formula(ABC): @property def num_addsubs(self) -> int: """Number of additions and subtractions.""" - return len(list( - filter(lambda op: op.operator == OpType.Add or op.operator == OpType.Sub, self.code))) + return len( + list( + filter( + lambda op: op.operator == OpType.Add or op.operator == OpType.Sub, + self.code, + ) + ) + ) class EFDFormula(Formula): @@ -313,7 +335,10 @@ class EFDFormula(Formula): self.parameters.append(line[10:]) elif line.startswith("assume"): self.assumptions.append( - parse(line[7:].replace("=", "==").replace("^", "**"), mode="eval")) + parse( + line[7:].replace("=", "==").replace("^", "**"), mode="eval" + ) + ) elif line.startswith("unified"): self.unified = True line = f.readline().decode("ascii").rstrip() @@ -321,7 +346,9 @@ class EFDFormula(Formula): def __read_op3_file(self, path): with resource_stream(__name__, path) as f: for line in f.readlines(): - code_module = parse(line.decode("ascii").replace("^", "**"), path, mode="exec") + code_module = parse( + line.decode("ascii").replace("^", "**"), path, mode="exec" + ) self.code.append(CodeOp(code_module)) @property @@ -334,19 +361,29 @@ class EFDFormula(Formula): @property def inputs(self): - return set(var + str(i) for var, i in product(self.coordinate_model.variables, - range(1, 1 + self.num_inputs))) + return set( + var + str(i) + for var, i in product( + self.coordinate_model.variables, range(1, 1 + self.num_inputs) + ) + ) @property def outputs(self): - return set(var + str(i) for var, i in product(self.coordinate_model.variables, - range(self.output_index, - self.output_index + self.num_outputs))) + return set( + var + str(i) + for var, i in product( + self.coordinate_model.variables, + range(self.output_index, self.output_index + self.num_outputs), + ) + ) def __eq__(self, other): if not isinstance(other, EFDFormula): return False - return self.name == other.name and self.coordinate_model == other.coordinate_model + return ( + self.name == other.name and self.coordinate_model == other.coordinate_model + ) def __hash__(self): return hash(self.name) + hash(self.coordinate_model) @@ -355,6 +392,7 @@ class EFDFormula(Formula): @public class AdditionFormula(Formula, ABC): """A formula that adds two points.""" + shortname = "add" num_inputs = 2 num_outputs = 1 @@ -368,6 +406,7 @@ class AdditionEFDFormula(AdditionFormula, EFDFormula): @public class DoublingFormula(Formula, ABC): """A formula that doubles a point.""" + shortname = "dbl" num_inputs = 1 num_outputs = 1 @@ -381,6 +420,7 @@ class DoublingEFDFormula(DoublingFormula, EFDFormula): @public class TriplingFormula(Formula, ABC): """A formula that triples a point.""" + shortname = "tpl" num_inputs = 1 num_outputs = 1 @@ -394,6 +434,7 @@ class TriplingEFDFormula(TriplingFormula, EFDFormula): @public class NegationFormula(Formula, ABC): """A formula that negates a point.""" + shortname = "neg" num_inputs = 1 num_outputs = 1 @@ -407,6 +448,7 @@ class NegationEFDFormula(NegationFormula, EFDFormula): @public class ScalingFormula(Formula, ABC): """A formula that somehow scales the point (to a given representative of a projective class).""" + shortname = "scl" num_inputs = 1 num_outputs = 1 @@ -423,6 +465,7 @@ class DifferentialAdditionFormula(Formula, ABC): A differential addition formula that adds two points with a known difference. The first input point is the difference of the third input and the second input (`P[0] = P[2] - P[1]`). """ + shortname = "dadd" num_inputs = 3 num_outputs = 1 @@ -441,6 +484,7 @@ class LadderFormula(Formula, ABC): The first output point is the doubling of the second input point (`O[0] = 2 * P[1]`). The second output point is the addition of the second and third input points (`O[1] = P[1] + P[2]`). """ + shortname = "ladd" num_inputs = 3 num_outputs = 2 diff --git a/pyecsca/ec/key_agreement.py b/pyecsca/ec/key_agreement.py index c9e4a01..b58374f 100644 --- a/pyecsca/ec/key_agreement.py +++ b/pyecsca/ec/key_agreement.py @@ -16,12 +16,19 @@ from .point import Point @public class ECDHAction(ResultAction): """An ECDH key exchange.""" + params: DomainParameters hash_algo: Optional[Any] privkey: Mod pubkey: Point - def __init__(self, params: DomainParameters, hash_algo: Optional[Any], privkey: Mod, pubkey: Point): + def __init__( + self, + params: DomainParameters, + hash_algo: Optional[Any], + privkey: Mod, + pubkey: Point, + ): super().__init__() self.params = params self.hash_algo = hash_algo @@ -35,14 +42,21 @@ class ECDHAction(ResultAction): @public class KeyAgreement(object): """An EC based key agreement primitive. (ECDH)""" + mult: ScalarMultiplier params: DomainParameters pubkey: Point privkey: Mod hash_algo: Optional[Any] - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, privkey: Mod, - hash_algo: Optional[Any] = None): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + pubkey: Point, + privkey: Mod, + hash_algo: Optional[Any] = None, + ): self.mult = mult self.params = params self.pubkey = pubkey @@ -65,7 +79,9 @@ class KeyAgreement(object): :return: The shared secret. """ - with ECDHAction(self.params, self.hash_algo, self.privkey, self.pubkey) as action: + with ECDHAction( + self.params, self.hash_algo, self.privkey, self.pubkey + ) as action: affine_point = self.perform_raw() x = int(affine_point.x) p = self.params.curve.prime @@ -80,8 +96,13 @@ class KeyAgreement(object): class ECDH_NONE(KeyAgreement): """Raw x-coordinate ECDH.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, - privkey: Mod): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + pubkey: Point, + privkey: Mod, + ): super().__init__(mult, params, pubkey, privkey) @@ -89,8 +110,13 @@ class ECDH_NONE(KeyAgreement): class ECDH_SHA1(KeyAgreement): """ECDH with SHA1 of x-coordinate.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, - privkey: Mod): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + pubkey: Point, + privkey: Mod, + ): super().__init__(mult, params, pubkey, privkey, hashlib.sha1) @@ -98,8 +124,13 @@ class ECDH_SHA1(KeyAgreement): class ECDH_SHA224(KeyAgreement): """ECDH with SHA224 of x-coordinate.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, - privkey: Mod): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + pubkey: Point, + privkey: Mod, + ): super().__init__(mult, params, pubkey, privkey, hashlib.sha224) @@ -107,8 +138,13 @@ class ECDH_SHA224(KeyAgreement): class ECDH_SHA256(KeyAgreement): """ECDH with SHA256 of x-coordinate.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, - privkey: Mod): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + pubkey: Point, + privkey: Mod, + ): super().__init__(mult, params, pubkey, privkey, hashlib.sha256) @@ -116,8 +152,13 @@ class ECDH_SHA256(KeyAgreement): class ECDH_SHA384(KeyAgreement): """ECDH with SHA384 of x-coordinate.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, - privkey: Mod): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + pubkey: Point, + privkey: Mod, + ): super().__init__(mult, params, pubkey, privkey, hashlib.sha384) @@ -125,6 +166,11 @@ class ECDH_SHA384(KeyAgreement): class ECDH_SHA512(KeyAgreement): """ECDH with SHA512 of x-coordinate.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, - privkey: Mod): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + pubkey: Point, + privkey: Mod, + ): super().__init__(mult, params, pubkey, privkey, hashlib.sha512) diff --git a/pyecsca/ec/key_generation.py b/pyecsca/ec/key_generation.py index d506585..a08c767 100644 --- a/pyecsca/ec/key_generation.py +++ b/pyecsca/ec/key_generation.py @@ -15,6 +15,7 @@ from .point import Point @public class KeygenAction(ResultAction): """A key generation.""" + params: DomainParameters def __init__(self, params: DomainParameters): @@ -28,11 +29,14 @@ class KeygenAction(ResultAction): @public class KeyGeneration(object): """Key generator.""" + mult: ScalarMultiplier params: DomainParameters affine: bool - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, affine: bool = False): + def __init__( + self, mult: ScalarMultiplier, params: DomainParameters, affine: bool = False + ): """ :param mult: The scalar multiplier to use during key generation. :param params: The domain parameters over which to generate the keypair. diff --git a/pyecsca/ec/mod.py b/pyecsca/ec/mod.py index 8aca8a9..8485cbe 100644 --- a/pyecsca/ec/mod.py +++ b/pyecsca/ec/mod.py @@ -125,6 +125,7 @@ def _check(func): @public class RandomModAction(ResultAction): """A random sampling from Z_n.""" + order: int def __init__(self, order: int): @@ -141,6 +142,7 @@ _mod_classes: Dict[str, Type] = {} @public class Mod(object): """An element x of ℤₙ.""" + x: Any n: Any @@ -153,7 +155,9 @@ class Mod(object): 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) + return _mod_classes[selected_class].__new__( + _mod_classes[selected_class], *args, **kwargs + ) @_check def __add__(self, other): @@ -254,6 +258,7 @@ class Mod(object): @public class RawMod(Mod): """An element x of ℤₙ (implemented using Python integers).""" + x: int n: int @@ -462,6 +467,7 @@ def _symbolic_check(func): @public class SymbolicMod(Mod): """A symbolic element x of ℤₙ (implemented using sympy).""" + x: Expr n: int @@ -489,10 +495,10 @@ class SymbolicMod(Mod): return -self + other def __neg__(self): - return self.__class__(- self.x, self.n) + return self.__class__(-self.x, self.n) def inverse(self): - return self.__class__(self.x**(-1), self.n) + return self.__class__(self.x ** (-1), self.n) def sqrt(self): raise NotImplementedError @@ -569,6 +575,7 @@ if has_gmp: @public class GMPMod(Mod): """An element x of ℤₙ. Implemented by GMP.""" + x: gmpy2.mpz n: gmpy2.mpz diff --git a/pyecsca/ec/model.py b/pyecsca/ec/model.py index 46b03b7..861dfca 100644 --- a/pyecsca/ec/model.py +++ b/pyecsca/ec/model.py @@ -14,6 +14,7 @@ from .coordinates import EFDCoordinateModel, CoordinateModel @public class CurveModel(object): """A model(form) of an elliptic curve.""" + name: str shortname: str coordinates: MutableMapping[str, CoordinateModel] @@ -134,6 +135,7 @@ class MontgomeryModel(EFDCurveModel): B y^2 = x^3 + A x^2 + x """ + def __init__(self): super().__init__("montgom") diff --git a/pyecsca/ec/mult.py b/pyecsca/ec/mult.py index 6fd6feb..7614dd4 100644 --- a/pyecsca/ec/mult.py +++ b/pyecsca/ec/mult.py @@ -8,8 +8,15 @@ from typing import Mapping, Tuple, Optional, MutableMapping, ClassVar, Set, Type from public import public from .context import ResultAction, Action -from .formula import (Formula, AdditionFormula, DoublingFormula, DifferentialAdditionFormula, - ScalingFormula, LadderFormula, NegationFormula) +from .formula import ( + Formula, + AdditionFormula, + DoublingFormula, + DifferentialAdditionFormula, + ScalingFormula, + LadderFormula, + NegationFormula, +) from .naf import naf, wnaf from .params import DomainParameters from .point import Point @@ -18,6 +25,7 @@ from .point import Point @public class ScalarMultiplicationAction(ResultAction): """A scalar multiplication of a point on a curve by a scalar.""" + point: Point scalar: int @@ -33,6 +41,7 @@ class ScalarMultiplicationAction(ResultAction): @public class PrecomputationAction(Action): """A precomputation of a point in scalar multiplication.""" + params: DomainParameters point: Point @@ -51,6 +60,7 @@ class ScalarMultiplier(ABC): of the point at infinity. :param formulas: Formulas this instance will use. """ + requires: ClassVar[Set[Type]] # Type[Formula] but mypy has a false positive """The set of formulas that the multiplier requires.""" optionals: ClassVar[Set[Type]] # Type[Formula] but mypy has a false positive @@ -64,8 +74,16 @@ class ScalarMultiplier(ABC): _initialized: bool = False def __init__(self, short_circuit: bool = True, **formulas: Optional[Formula]): - if len(set(formula.coordinate_model for formula in formulas.values() if - formula is not None)) != 1: + if ( + len( + set( + formula.coordinate_model + for formula in formulas.values() + if formula is not None + ) + ) + != 1 + ): raise ValueError self.short_circuit = short_circuit self.formulas = {k: v for k, v in formulas.items() if v is not None} @@ -78,7 +96,9 @@ class ScalarMultiplier(ABC): return copy(other) if other == self._params.curve.neutral: return copy(one) - return self.formulas["add"](self._params.curve.prime, one, other, **self._params.curve.parameters)[0] + return self.formulas["add"]( + self._params.curve.prime, one, other, **self._params.curve.parameters + )[0] def _dbl(self, point: Point) -> Point: if "dbl" not in self.formulas: @@ -86,12 +106,16 @@ class ScalarMultiplier(ABC): if self.short_circuit: if point == self._params.curve.neutral: return copy(point) - return self.formulas["dbl"](self._params.curve.prime, point, **self._params.curve.parameters)[0] + return self.formulas["dbl"]( + self._params.curve.prime, point, **self._params.curve.parameters + )[0] def _scl(self, point: Point) -> Point: if "scl" not in self.formulas: raise NotImplementedError - return self.formulas["scl"](self._params.curve.prime, point, **self._params.curve.parameters)[0] + return self.formulas["scl"]( + self._params.curve.prime, point, **self._params.curve.parameters + )[0] def _ladd(self, start: Point, to_dbl: Point, to_add: Point) -> Tuple[Point, ...]: if "ladd" not in self.formulas: @@ -101,7 +125,13 @@ class ScalarMultiplier(ABC): return to_dbl, to_add if to_add == self._params.curve.neutral: return self._dbl(to_dbl), to_dbl - return self.formulas["ladd"](self._params.curve.prime, start, to_dbl, to_add, **self._params.curve.parameters) + return self.formulas["ladd"]( + self._params.curve.prime, + start, + to_dbl, + to_add, + **self._params.curve.parameters, + ) def _dadd(self, start: Point, one: Point, other: Point) -> Point: if "dadd" not in self.formulas: @@ -111,12 +141,16 @@ class ScalarMultiplier(ABC): return copy(other) if other == self._params.curve.neutral: return copy(one) - return self.formulas["dadd"](self._params.curve.prime, start, one, other, **self._params.curve.parameters)[0] + return self.formulas["dadd"]( + self._params.curve.prime, start, one, other, **self._params.curve.parameters + )[0] def _neg(self, point: Point) -> Point: if "neg" not in self.formulas: raise NotImplementedError - return self.formulas["neg"](self._params.curve.prime, point, **self._params.curve.parameters)[0] + return self.formulas["neg"]( + self._params.curve.prime, point, **self._params.curve.parameters + )[0] def init(self, params: DomainParameters, point: Point): """ @@ -129,7 +163,10 @@ class ScalarMultiplier(ABC): :param point: The point to initialize the multiplier with. """ coord_model = set(self.formulas.values()).pop().coordinate_model - if params.curve.coordinate_model != coord_model or point.coordinate_model != coord_model: + if ( + params.curve.coordinate_model != coord_model + or point.coordinate_model != coord_model + ): raise ValueError self._params = params self._point = point @@ -156,14 +193,21 @@ class LTRMultiplier(ScalarMultiplier): The `always` parameter determines whether the double and add always method is used. """ + requires = {AdditionFormula, DoublingFormula} optionals = {ScalingFormula} always: bool complete: bool - def __init__(self, add: AdditionFormula, dbl: DoublingFormula, - scl: ScalingFormula = None, always: bool = False, complete: bool = True, - short_circuit: bool = True): + def __init__( + self, + add: AdditionFormula, + dbl: DoublingFormula, + scl: ScalingFormula = None, + always: bool = False, + complete: bool = True, + short_circuit: bool = True, + ): super().__init__(short_circuit=short_circuit, add=add, dbl=dbl, scl=scl) self.always = always self.complete = complete @@ -201,12 +245,19 @@ class RTLMultiplier(ScalarMultiplier): The `always` parameter determines whether the double and add always method is used. """ + requires = {AdditionFormula, DoublingFormula} optionals = {ScalingFormula} always: bool - def __init__(self, add: AdditionFormula, dbl: DoublingFormula, - scl: ScalingFormula = None, always: bool = False, short_circuit: bool = True): + def __init__( + self, + add: AdditionFormula, + dbl: DoublingFormula, + scl: ScalingFormula = None, + always: bool = False, + short_circuit: bool = True, + ): super().__init__(short_circuit=short_circuit, add=add, dbl=dbl, scl=scl) self.always = always @@ -240,11 +291,17 @@ class CoronMultiplier(ScalarMultiplier): https://link.springer.com/content/pdf/10.1007/3-540-48059-5_25.pdf """ + requires = {AdditionFormula, DoublingFormula} optionals = {ScalingFormula} - def __init__(self, add: AdditionFormula, dbl: DoublingFormula, scl: ScalingFormula = None, - short_circuit: bool = True): + def __init__( + self, + add: AdditionFormula, + dbl: DoublingFormula, + scl: ScalingFormula = None, + short_circuit: bool = True, + ): super().__init__(short_circuit=short_circuit, add=add, dbl=dbl, scl=scl) def multiply(self, scalar: int) -> Point: @@ -270,12 +327,19 @@ class LadderMultiplier(ScalarMultiplier): """ Montgomery ladder multiplier, using a three input, two output ladder formula. """ + requires = {LadderFormula} optionals = {DoublingFormula, ScalingFormula} complete: bool - def __init__(self, ladd: LadderFormula, dbl: DoublingFormula = None, scl: ScalingFormula = None, - complete: bool = True, short_circuit: bool = True): + def __init__( + self, + ladd: LadderFormula, + dbl: DoublingFormula = None, + scl: ScalingFormula = None, + complete: bool = True, + short_circuit: bool = True, + ): super().__init__(short_circuit=short_circuit, ladd=ladd, dbl=dbl, scl=scl) self.complete = complete if (not complete or short_circuit) and dbl is None: @@ -311,12 +375,19 @@ class SimpleLadderMultiplier(ScalarMultiplier): """ Montgomery ladder multiplier, using addition and doubling formulas. """ + requires = {AdditionFormula, DoublingFormula} optionals = {ScalingFormula} complete: bool - def __init__(self, add: AdditionFormula, dbl: DoublingFormula, scl: ScalingFormula = None, - complete: bool = True, short_circuit: bool = True): + def __init__( + self, + add: AdditionFormula, + dbl: DoublingFormula, + scl: ScalingFormula = None, + complete: bool = True, + short_circuit: bool = True, + ): super().__init__(short_circuit=short_circuit, add=add, dbl=dbl, scl=scl) self.complete = complete @@ -349,12 +420,19 @@ class DifferentialLadderMultiplier(ScalarMultiplier): """ Montgomery ladder multiplier, using differential addition and doubling formulas. """ + requires = {DifferentialAdditionFormula, DoublingFormula} optionals = {ScalingFormula} complete: bool - def __init__(self, dadd: DifferentialAdditionFormula, dbl: DoublingFormula, - scl: ScalingFormula = None, complete: bool = True, short_circuit: bool = True): + def __init__( + self, + dadd: DifferentialAdditionFormula, + dbl: DoublingFormula, + scl: ScalingFormula = None, + complete: bool = True, + short_circuit: bool = True, + ): super().__init__(short_circuit=short_circuit, dadd=dadd, dbl=dbl, scl=scl) self.complete = complete @@ -388,13 +466,22 @@ class BinaryNAFMultiplier(ScalarMultiplier): """ Binary NAF (Non Adjacent Form) multiplier, left-to-right. """ + requires = {AdditionFormula, DoublingFormula, NegationFormula} optionals = {ScalingFormula} _point_neg: Point - def __init__(self, add: AdditionFormula, dbl: DoublingFormula, - neg: NegationFormula, scl: ScalingFormula = None, short_circuit: bool = True): - super().__init__(short_circuit=short_circuit, add=add, dbl=dbl, neg=neg, scl=scl) + def __init__( + self, + add: AdditionFormula, + dbl: DoublingFormula, + neg: NegationFormula, + scl: ScalingFormula = None, + short_circuit: bool = True, + ): + super().__init__( + short_circuit=short_circuit, add=add, dbl=dbl, neg=neg, scl=scl + ) def init(self, params: DomainParameters, point: Point): with PrecomputationAction(params, point): @@ -425,6 +512,7 @@ class WindowNAFMultiplier(ScalarMultiplier): """ Window NAF (Non Adjacent Form) multiplier, left-to-right. """ + requires = {AdditionFormula, DoublingFormula, NegationFormula} optionals = {ScalingFormula} _points: MutableMapping[int, Point] @@ -432,10 +520,19 @@ class WindowNAFMultiplier(ScalarMultiplier): precompute_negation: bool = False width: int - def __init__(self, add: AdditionFormula, dbl: DoublingFormula, - neg: NegationFormula, width: int, scl: ScalingFormula = None, - precompute_negation: bool = False, short_circuit: bool = True): - super().__init__(short_circuit=short_circuit, add=add, dbl=dbl, neg=neg, scl=scl) + def __init__( + self, + add: AdditionFormula, + dbl: DoublingFormula, + neg: NegationFormula, + width: int, + scl: ScalingFormula = None, + precompute_negation: bool = False, + short_circuit: bool = True, + ): + super().__init__( + short_circuit=short_circuit, add=add, dbl=dbl, neg=neg, scl=scl + ) self.width = width self.precompute_negation = precompute_negation diff --git a/pyecsca/ec/op.py b/pyecsca/ec/op.py index 2401624..e500914 100644 --- a/pyecsca/ec/op.py +++ b/pyecsca/ec/op.py @@ -1,8 +1,23 @@ """ This module provides a class for a code operation. """ -from ast import (Module, walk, Name, BinOp, UnaryOp, Constant, Mult, Div, Add, Sub, Pow, Assign, - operator as ast_operator, unaryop as ast_unaryop, USub) +from ast import ( + Module, + walk, + Name, + BinOp, + UnaryOp, + Constant, + Mult, + Div, + Add, + Sub, + Pow, + Assign, + operator as ast_operator, + unaryop as ast_unaryop, + USub, +) from enum import Enum from types import CodeType from typing import FrozenSet, cast, Any, Optional, Union @@ -15,6 +30,7 @@ from .mod import Mod @public class OpType(Enum): """A type of binary and unary operators.""" + Add = (2, "+") Sub = (2, "-") Neg = (1, "-") @@ -33,6 +49,7 @@ class OpType(Enum): @public class CodeOp(object): """An operation that can be executed.""" + result: str """The result variable of the operation (e.g. the `r` in `r = 2*a`).""" parameters: FrozenSet[str] @@ -90,7 +107,9 @@ class CodeOp(object): else: return None - def __to_op(self, op: Optional[Union[ast_operator, ast_unaryop]], left: Any, right: Any) -> OpType: + def __to_op( + self, op: Optional[Union[ast_operator, ast_unaryop]], left: Any, right: Any + ) -> OpType: if isinstance(op, Mult): return OpType.Mult elif isinstance(op, Div): diff --git a/pyecsca/ec/params.py b/pyecsca/ec/params.py index 6583bae..370b8eb 100644 --- a/pyecsca/ec/params.py +++ b/pyecsca/ec/params.py @@ -17,8 +17,13 @@ from .coordinates import AffineCoordinateModel, CoordinateModel from .curve import EllipticCurve from .error import UnsatisfiedAssumptionError, raise_unsatisified_assumption from .mod import Mod -from .model import (CurveModel, ShortWeierstrassModel, MontgomeryModel, EdwardsModel, - TwistedEdwardsModel) +from .model import ( + CurveModel, + ShortWeierstrassModel, + MontgomeryModel, + EdwardsModel, + TwistedEdwardsModel, +) from .point import Point, InfinityPoint from ..misc.cfg import getconfig @@ -26,6 +31,7 @@ from ..misc.cfg import getconfig @public class DomainParameters(object): """Domain parameters which specify a subgroup on an elliptic curve.""" + curve: EllipticCurve generator: Point order: int @@ -33,8 +39,15 @@ class DomainParameters(object): name: Optional[str] category: Optional[str] - def __init__(self, curve: EllipticCurve, generator: Point, order: int, - cofactor: int, name: Optional[str] = None, category: Optional[str] = None): + def __init__( + self, + curve: EllipticCurve, + generator: Point, + order: int, + cofactor: int, + name: Optional[str] = None, + category: Optional[str] = None, + ): self.curve = curve self.generator = generator self.order = order @@ -45,7 +58,12 @@ class DomainParameters(object): def __eq__(self, other): if not isinstance(other, DomainParameters): return False - return self.curve == other.curve and self.generator == other.generator and self.order == other.order and self.cofactor == other.cofactor + return ( + self.curve == other.curve + and self.generator == other.generator + and self.order == other.order + and self.cofactor == other.cofactor + ) def __get_name(self): if self.name and self.category: @@ -69,6 +87,7 @@ class DomainParameters(object): @public class DomainParameterCategory(object): """A category of domain parameters.""" + name: str description: str curves: List[DomainParameters] @@ -119,7 +138,9 @@ def _create_params(curve, coords, infty): param_names = ["a", "d"] else: raise ValueError("Unknown curve model.") - params = {name: Mod(int(curve["params"][name]["raw"], 16), field) for name in param_names} + params = { + name: Mod(int(curve["params"][name]["raw"], 16), field) for name in param_names + } # Check coordinate model name and assumptions coord_model: CoordinateModel @@ -139,8 +160,10 @@ def _create_params(curve, coords, infty): exec(compiled, None, alocals) for param, value in alocals.items(): if params[param] != value: - raise_unsatisified_assumption(getconfig().ec.unsatisfied_coordinate_assumption_action, - 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,15 +171,23 @@ def _create_params(curve, coords, infty): 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 an unsupported assumption ({assumption_string}).") + 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 an unsupported assumption ({assumption_string})." + ) poly = Poly(expr, symbols(param), domain=k) roots = poly.ground_roots() for root in roots.keys(): 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}).") + raise UnsatisfiedAssumptionError( + f"Coordinate model {coord_model} has an unsatisifed assumption on the {param} parameter (0 = {expr})." + ) # Construct the point at infinity infinity: Point @@ -170,26 +201,35 @@ def _create_params(curve, coords, infty): infinity_coords = {} for coordinate in coord_model.variables: if coordinate not in ilocals: - raise ValueError(f"Coordinate model {coord_model} requires infty option.") + raise ValueError( + f"Coordinate model {coord_model} requires infty option." + ) value = ilocals[coordinate] if isinstance(value, int): value = Mod(value, field) infinity_coords[coordinate] = value infinity = Point(coord_model, **infinity_coords) elliptic_curve = EllipticCurve(model, coord_model, field, infinity, params) # type: ignore[arg-type] - affine = Point(AffineCoordinateModel(model), - x=Mod(int(curve["generator"]["x"]["raw"], 16), field), - y=Mod(int(curve["generator"]["y"]["raw"], 16), field)) + affine = Point( + AffineCoordinateModel(model), + x=Mod(int(curve["generator"]["x"]["raw"], 16), field), + y=Mod(int(curve["generator"]["y"]["raw"], 16), field), + ) if not isinstance(coord_model, AffineCoordinateModel): generator = affine.to_model(coord_model, elliptic_curve) else: generator = affine - return DomainParameters(elliptic_curve, generator, order, cofactor, curve["name"], curve["category"]) + return DomainParameters( + elliptic_curve, generator, order, cofactor, curve["name"], curve["category"] + ) @public -def load_category(file: Union[str, Path, BinaryIO], coords: Union[str, Callable[[str], str]], - infty: Union[bool, Callable[[str], bool]] = True) -> DomainParameterCategory: +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. @@ -223,7 +263,9 @@ def load_category(file: Union[str, Path, BinaryIO], coords: Union[str, Callable[ @public -def load_params(file: Union[str, Path, BinaryIO], coords: str, infty: bool = True) -> DomainParameters: +def load_params( + file: Union[str, Path, BinaryIO], coords: str, infty: bool = True +) -> DomainParameters: """ Load a curve from a JSON file. @@ -245,8 +287,11 @@ def load_params(file: Union[str, Path, BinaryIO], coords: str, infty: bool = Tru @public -def get_category(category: str, coords: Union[str, Callable[[str], str]], - infty: Union[bool, Callable[[str], bool]] = True) -> DomainParameterCategory: +def get_category( + category: str, + coords: Union[str, Callable[[str], str]], + infty: Union[bool, Callable[[str], bool]] = True, +) -> DomainParameterCategory: """ Retrieve a category from the std-curves database at https://github.com/J08nY/std-curves. @@ -259,7 +304,9 @@ def get_category(category: str, coords: Union[str, Callable[[str], str]], :return: The category. """ listing = resource_listdir(__name__, "std") - categories = list(entry for entry in listing if resource_isdir(__name__, join("std", entry))) + categories = list( + entry for entry in listing if resource_isdir(__name__, join("std", entry)) + ) if category not in categories: raise ValueError(f"Category {category} not found.") json_path = join("std", category, "curves.json") @@ -268,7 +315,9 @@ def get_category(category: str, coords: Union[str, Callable[[str], str]], @public -def get_params(category: str, name: str, coords: str, infty: bool = True) -> DomainParameters: +def get_params( + category: str, name: str, coords: str, infty: bool = True +) -> DomainParameters: """ Retrieve a curve from a set of stored parameters. Uses the std-curves database at https://github.com/J08nY/std-curves. @@ -281,7 +330,9 @@ def get_params(category: str, name: str, coords: str, infty: bool = True) -> Dom :return: The curve. """ listing = resource_listdir(__name__, "std") - categories = list(entry for entry in listing if resource_isdir(__name__, join("std", entry))) + categories = list( + entry for entry in listing if resource_isdir(__name__, join("std", entry)) + ) if category not in categories: raise ValueError(f"Category {category} not found.") json_path = join("std", category, "curves.json") diff --git a/pyecsca/ec/point.py b/pyecsca/ec/point.py index 6ba6469..1d074fe 100644 --- a/pyecsca/ec/point.py +++ b/pyecsca/ec/point.py @@ -19,11 +19,14 @@ if TYPE_CHECKING: @public class CoordinateMappingAction(ResultAction): """A mapping of a point from one coordinate system to another one, usually one is an affine one.""" + model_from: CoordinateModel model_to: CoordinateModel point: "Point" - def __init__(self, model_from: CoordinateModel, model_to: CoordinateModel, point: "Point"): + def __init__( + self, model_from: CoordinateModel, model_to: CoordinateModel, point: "Point" + ): super().__init__() self.model_from = model_from self.model_to = model_to @@ -36,6 +39,7 @@ class CoordinateMappingAction(ResultAction): @public class Point(object): """A point with coordinates in a coordinate model.""" + coordinate_model: CoordinateModel coords: Mapping[str, Mod] field: int @@ -51,7 +55,9 @@ class Point(object): field = value.n else: if field != value.n: - raise ValueError(f"Mismatched coordinate field of definition, {field} vs {value.n}.") + raise ValueError( + f"Mismatched coordinate field of definition, {field} vs {value.n}." + ) self.field = field if field is not None else 0 def __getattribute__(self, name): @@ -65,7 +71,9 @@ class Point(object): def to_affine(self) -> "Point": """Convert this point into the affine coordinate model, if possible.""" affine_model = AffineCoordinateModel(self.coordinate_model.curve_model) - with CoordinateMappingAction(self.coordinate_model, affine_model, self) as action: + with CoordinateMappingAction( + self.coordinate_model, affine_model, self + ) as action: if isinstance(self.coordinate_model, AffineCoordinateModel): return action.exit(copy(self)) ops = [] @@ -91,11 +99,15 @@ class Point(object): result[op.result] = locls[op.result] return action.exit(Point(affine_model, **result)) - def to_model(self, coordinate_model: CoordinateModel, curve: "EllipticCurve") -> "Point": + def to_model( + self, coordinate_model: CoordinateModel, curve: "EllipticCurve" + ) -> "Point": """Convert an affine point into a given coordinate model, if possible.""" if not isinstance(self.coordinate_model, AffineCoordinateModel): raise ValueError - with CoordinateMappingAction(self.coordinate_model, coordinate_model, self) as action: + with CoordinateMappingAction( + self.coordinate_model, coordinate_model, self + ) as action: ops = [] for s in coordinate_model.satisfying: try: @@ -114,7 +126,10 @@ class Point(object): result[var] = locls[var] elif var == "X": result[var] = self.coords["x"] - if isinstance(coordinate_model, EFDCoordinateModel) and coordinate_model.name == "inverted": + if ( + isinstance(coordinate_model, EFDCoordinateModel) + and coordinate_model.name == "inverted" + ): result[var] = result[var].inverse() elif var == "Y": result[var] = self.coords["y"] @@ -124,11 +139,13 @@ class Point(object): elif coordinate_model.name == "yz": result[var] = result[var] * curve.parameters["r"] elif coordinate_model.name == "yzsquared": - result[var] = result[var]**2 * curve.parameters["r"] + result[var] = result[var] ** 2 * curve.parameters["r"] elif var.startswith("Z"): result[var] = Mod(1, curve.prime) elif var == "T": - result[var] = Mod(int(self.coords["x"] * self.coords["y"]), curve.prime) + result[var] = Mod( + int(self.coords["x"] * self.coords["y"]), curve.prime + ) else: raise NotImplementedError return action.exit(Point(coordinate_model, **result)) @@ -201,7 +218,9 @@ class InfinityPoint(Point): def to_affine(self) -> "InfinityPoint": return InfinityPoint(AffineCoordinateModel(self.coordinate_model.curve_model)) - def to_model(self, coordinate_model: CoordinateModel, curve: "EllipticCurve") -> "InfinityPoint": + def to_model( + self, coordinate_model: CoordinateModel, curve: "EllipticCurve" + ) -> "InfinityPoint": return InfinityPoint(coordinate_model) def equals_affine(self, other: "Point") -> bool: diff --git a/pyecsca/ec/signature.py b/pyecsca/ec/signature.py index 9df6599..0e6f546 100644 --- a/pyecsca/ec/signature.py +++ b/pyecsca/ec/signature.py @@ -18,12 +18,20 @@ from .point import Point @public class SignatureResult(object): """An ECDSA signature result (r, s).""" + r: int s: int - def __init__(self, r: int, s: int, data: Optional[bytes] = None, digest: Optional[bytes] = None, - nonce: Optional[int] = None, privkey: Optional[Mod] = None, - pubkey: Optional[Point] = None): + def __init__( + self, + r: int, + s: int, + data: Optional[bytes] = None, + digest: Optional[bytes] = None, + nonce: Optional[int] = None, + privkey: Optional[Mod] = None, + pubkey: Optional[Point] = None, + ): self.r = r self.s = s @@ -55,12 +63,12 @@ class SignatureResult(object): @public class ECDSAAction(Action): """An ECDSA action base class.""" + params: DomainParameters hash_algo: Optional[Any] msg: bytes - def __init__(self, params: DomainParameters, hash_algo: Optional[Any], - msg: bytes): + def __init__(self, params: DomainParameters, hash_algo: Optional[Any], msg: bytes): super().__init__() self.params = params self.hash_algo = hash_algo @@ -73,10 +81,16 @@ class ECDSAAction(Action): @public class ECDSASignAction(ECDSAAction): """An ECDSA signing.""" + privkey: Mod - def __init__(self, params: DomainParameters, hash_algo: Optional[Any], msg: bytes, - privkey: Mod): + def __init__( + self, + params: DomainParameters, + hash_algo: Optional[Any], + msg: bytes, + privkey: Mod, + ): super().__init__(params, hash_algo, msg) self.privkey = privkey @@ -87,11 +101,18 @@ class ECDSASignAction(ECDSAAction): @public class ECDSAVerifyAction(ECDSAAction): """An ECDSA verification.""" + signature: SignatureResult pubkey: Point - def __init__(self, params: DomainParameters, hash_algo: Optional[Any], msg: bytes, - signature: SignatureResult, pubkey: Point): + def __init__( + self, + params: DomainParameters, + hash_algo: Optional[Any], + msg: bytes, + signature: SignatureResult, + pubkey: Point, + ): super().__init__(params, hash_algo, msg) self.signature = signature self.pubkey = pubkey @@ -103,6 +124,7 @@ class ECDSAVerifyAction(ECDSAAction): @public class Signature(object): """An EC based signature primitive. (ECDSA)""" + mult: ScalarMultiplier params: DomainParameters add: Optional[AdditionFormula] @@ -110,10 +132,15 @@ class Signature(object): privkey: Optional[Mod] hash_algo: Optional[Any] - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, - add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[Mod] = None, - hash_algo: Optional[Any] = None): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, + privkey: Optional[Mod] = None, + hash_algo: Optional[Any] = None, + ): if pubkey is None and privkey is None: raise ValueError if add is None: @@ -153,8 +180,9 @@ class Signature(object): affine_point = point.to_affine() r = Mod(int(affine_point.x), self.params.order) s = nonce.inverse() * (Mod(z, self.params.order) + r * self.privkey) - return SignatureResult(int(r), int(s), digest=digest, nonce=int(nonce), - privkey=self.privkey) + return SignatureResult( + int(r), int(s), digest=digest, nonce=int(nonce), privkey=self.privkey + ) def sign_hash(self, digest: bytes, nonce: Optional[int] = None) -> SignatureResult: """Sign already hashed data.""" @@ -203,7 +231,9 @@ class Signature(object): """Verify data.""" if not self.can_verify or self.pubkey is None: raise RuntimeError("This instance cannot verify.") - with ECDSAVerifyAction(self.params, self.hash_algo, data, signature, self.pubkey): + with ECDSAVerifyAction( + self.params, self.hash_algo, data, signature, self.pubkey + ): if self.hash_algo is None: digest = data else: @@ -215,9 +245,14 @@ class Signature(object): class ECDSA_NONE(Signature): """ECDSA with raw message input.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, - add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[Mod] = None): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, + privkey: Optional[Mod] = None, + ): super().__init__(mult, params, add, pubkey, privkey) @@ -225,9 +260,14 @@ class ECDSA_NONE(Signature): class ECDSA_SHA1(Signature): """ECDSA with SHA1.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, - add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[Mod] = None): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, + privkey: Optional[Mod] = None, + ): super().__init__(mult, params, add, pubkey, privkey, hashlib.sha1) @@ -235,9 +275,14 @@ class ECDSA_SHA1(Signature): class ECDSA_SHA224(Signature): """ECDSA with SHA224.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, - add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[Mod] = None): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, + privkey: Optional[Mod] = None, + ): super().__init__(mult, params, add, pubkey, privkey, hashlib.sha224) @@ -245,9 +290,14 @@ class ECDSA_SHA224(Signature): class ECDSA_SHA256(Signature): """ECDSA with SHA256.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, - add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[Mod] = None): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, + privkey: Optional[Mod] = None, + ): super().__init__(mult, params, add, pubkey, privkey, hashlib.sha256) @@ -255,9 +305,14 @@ class ECDSA_SHA256(Signature): class ECDSA_SHA384(Signature): """ECDSA with SHA384.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, - add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[Mod] = None): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, + privkey: Optional[Mod] = None, + ): super().__init__(mult, params, add, pubkey, privkey, hashlib.sha384) @@ -265,7 +320,12 @@ class ECDSA_SHA384(Signature): class ECDSA_SHA512(Signature): """ECDSA with SHA512.""" - def __init__(self, mult: ScalarMultiplier, params: DomainParameters, - add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[Mod] = None): + def __init__( + self, + mult: ScalarMultiplier, + params: DomainParameters, + add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, + privkey: Optional[Mod] = None, + ): super().__init__(mult, params, add, pubkey, privkey, hashlib.sha512) diff --git a/pyecsca/ec/transformations.py b/pyecsca/ec/transformations.py index 986064b..0987235 100644 --- a/pyecsca/ec/transformations.py +++ b/pyecsca/ec/transformations.py @@ -22,8 +22,12 @@ def __M_map(params, param_names, map_parameters, map_point, model): else: neutral = map_point(param_one, param_other, params.curve.neutral, aff) curve = EllipticCurve(model, aff, params.curve.prime, neutral, parameters) - return DomainParameters(curve, map_point(param_one, param_other, params.generator, aff), params.order, - params.cofactor) + return DomainParameters( + curve, + map_point(param_one, param_other, params.generator, aff), + params.order, + params.cofactor, + ) @public @@ -35,7 +39,8 @@ def M2SW(params: DomainParameters) -> DomainParameters: :return: The converted domain parameters. """ if not isinstance(params.curve.model, MontgomeryModel) or not isinstance( - params.curve.coordinate_model, AffineCoordinateModel): + params.curve.coordinate_model, AffineCoordinateModel + ): raise ValueError def map_parameters(A, B): @@ -48,7 +53,9 @@ def M2SW(params: DomainParameters) -> DomainParameters: v = pt.y / B return Point(aff, x=u, y=v) - return __M_map(params, ("a", "b"), map_parameters, map_point, ShortWeierstrassModel()) + return __M_map( + params, ("a", "b"), map_parameters, map_point, ShortWeierstrassModel() + ) @public @@ -60,7 +67,8 @@ def M2TE(params: DomainParameters) -> DomainParameters: :return: The converted domain parameters. """ if not isinstance(params.curve.model, MontgomeryModel) or not isinstance( - params.curve.coordinate_model, AffineCoordinateModel): + params.curve.coordinate_model, AffineCoordinateModel + ): raise ValueError def map_parameters(A, B): @@ -85,7 +93,8 @@ def TE2M(params: DomainParameters) -> DomainParameters: :return: The converted domain parameters. """ if not isinstance(params.curve.model, TwistedEdwardsModel) or not isinstance( - params.curve.coordinate_model, AffineCoordinateModel): + params.curve.coordinate_model, AffineCoordinateModel + ): raise ValueError def map_parameters(a, d): @@ -110,16 +119,25 @@ def SW2M(params: DomainParameters) -> DomainParameters: :return: The converted domain parameters. """ if not isinstance(params.curve.model, ShortWeierstrassModel) or not isinstance( - params.curve.coordinate_model, AffineCoordinateModel): + params.curve.coordinate_model, AffineCoordinateModel + ): raise ValueError ax = symbols("α") field = FF(params.curve.prime) - rhs = Poly(ax ** 3 + field(int(params.curve.parameters["a"])) * ax + field(int(params.curve.parameters["b"])), ax, domain=field) + rhs = Poly( + ax ** 3 + + field(int(params.curve.parameters["a"])) * ax + + field(int(params.curve.parameters["b"])), + ax, + domain=field, + ) roots = rhs.ground_roots() if not roots: - raise ValueError("Curve cannot be transformed to Montgomery model (x^3 + ax + b has no root).") + raise ValueError( + "Curve cannot be transformed to Montgomery model (x^3 + ax + b has no root)." + ) alpha = Mod(int(next(iter(roots.keys()))), params.curve.prime) - beta = (3 * alpha**2 + params.curve.parameters["a"]).sqrt() + beta = (3 * alpha ** 2 + params.curve.parameters["a"]).sqrt() def map_parameters(a, b): A = (3 * alpha) / beta @@ -143,16 +161,25 @@ def SW2TE(params: DomainParameters) -> DomainParameters: :return: The converted domain parameters. """ if not isinstance(params.curve.model, ShortWeierstrassModel) or not isinstance( - params.curve.coordinate_model, AffineCoordinateModel): + params.curve.coordinate_model, AffineCoordinateModel + ): raise ValueError ax = symbols("α") field = FF(params.curve.prime) - rhs = Poly(ax ** 3 + field(int(params.curve.parameters["a"])) * ax + field(int(params.curve.parameters["b"])), ax, domain=field) + rhs = Poly( + ax ** 3 + + field(int(params.curve.parameters["a"])) * ax + + field(int(params.curve.parameters["b"])), + ax, + domain=field, + ) roots = rhs.ground_roots() if not roots: - raise ValueError("Curve cannot be transformed to Montgomery model (x^3 + ax + b has no root).") + raise ValueError( + "Curve cannot be transformed to Montgomery model (x^3 + ax + b has no root)." + ) alpha = Mod(int(next(iter(roots.keys()))), params.curve.prime) - beta = (3 * alpha**2 + params.curve.parameters["a"]).sqrt() + beta = (3 * alpha ** 2 + params.curve.parameters["a"]).sqrt() def map_parameters(a, b): a = 3 * alpha + 2 * beta diff --git a/pyecsca/misc/cfg.py b/pyecsca/misc/cfg.py index df3cce1..8a5a9ee 100644 --- a/pyecsca/misc/cfg.py +++ b/pyecsca/misc/cfg.py @@ -11,6 +11,7 @@ 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" @@ -111,6 +112,7 @@ class ECConfig(object): @public class Config(object): """A runtime configuration for the library.""" + ec: ECConfig """Configuration for the :py:mod:`pyecsca.ec` package.""" diff --git a/pyecsca/sca/re/rpa.py b/pyecsca/sca/re/rpa.py index 3dc00e2..ab038e8 100644 --- a/pyecsca/sca/re/rpa.py +++ b/pyecsca/sca/re/rpa.py @@ -7,8 +7,15 @@ This module provides functionality inspired by the Refined-Power Analysis attack from public import public from typing import MutableMapping, Optional -from ...ec.formula import FormulaAction, DoublingFormula, AdditionFormula, TriplingFormula, NegationFormula, \ - DifferentialAdditionFormula, LadderFormula +from ...ec.formula import ( + FormulaAction, + DoublingFormula, + AdditionFormula, + TriplingFormula, + NegationFormula, + DifferentialAdditionFormula, + LadderFormula, +) from ...ec.mult import ScalarMultiplicationAction, PrecomputationAction from ...ec.point import Point from ...ec.context import Context, Action @@ -17,6 +24,7 @@ from ...ec.context import Context, Action @public class MultipleContext(Context): """A context that traces the multiples of points computed.""" + base: Optional[Point] points: MutableMapping[Point, int] inside: bool @@ -51,7 +59,7 @@ class MultipleContext(Context): elif isinstance(action.formula, NegationFormula): inp = action.input_points[0] out = action.output_points[0] - self.points[out] = - self.points[inp] + self.points[out] = -self.points[inp] elif isinstance(action.formula, DifferentialAdditionFormula): diff, one, other = action.input_points out = action.output_points[0] diff --git a/pyecsca/sca/scope/base.py b/pyecsca/sca/scope/base.py index 68d60dd..595fe32 100644 --- a/pyecsca/sca/scope/base.py +++ b/pyecsca/sca/scope/base.py @@ -12,6 +12,7 @@ from ..trace import Trace @public class SampleType(Enum): """The sample unit.""" + Raw = auto() Volt = auto() @@ -29,7 +30,9 @@ class Scope(object): """A list of channels available on this scope.""" raise NotImplementedError - def setup_frequency(self, frequency: int, pretrig: int, posttrig: int) -> Tuple[int, int]: + def setup_frequency( + self, frequency: int, pretrig: int, posttrig: int + ) -> Tuple[int, int]: """ Setup the frequency and sample count for the measurement. The scope might not support the requested values and will adjust them to get the next best frequency and the largest @@ -42,8 +45,9 @@ class Scope(object): """ raise NotImplementedError - def setup_channel(self, channel: str, coupling: str, range: float, offset: float, - enable: bool) -> None: + def setup_channel( + self, channel: str, coupling: str, range: float, offset: float, enable: bool + ) -> None: """ Setup a channel to use the coupling method and measure the given voltage range. @@ -55,8 +59,15 @@ class Scope(object): """ raise NotImplementedError - def setup_trigger(self, channel: str, threshold: float, direction: str, delay: int, - timeout: int, enable: bool) -> None: + def setup_trigger( + self, + channel: str, + threshold: float, + direction: str, + delay: int, + timeout: int, + enable: bool, + ) -> None: """ Setup a trigger on a particular `channel`, the channel has to be set up and enabled. The trigger will fire based on the `threshold` and `direction`, if enabled, the trigger diff --git a/pyecsca/sca/scope/chipwhisperer.py b/pyecsca/sca/scope/chipwhisperer.py index c5c21de..05c8e69 100644 --- a/pyecsca/sca/scope/chipwhisperer.py +++ b/pyecsca/sca/scope/chipwhisperer.py @@ -27,18 +27,29 @@ class ChipWhispererScope(Scope): # pragma: no cover def channels(self) -> Sequence[str]: return [] - def setup_frequency(self, frequency: int, pretrig: int, posttrig: int) -> Tuple[int, int]: + def setup_frequency( + self, frequency: int, pretrig: int, posttrig: int + ) -> Tuple[int, int]: if pretrig != 0: raise ValueError("ChipWhisperer does not support pretrig samples.") self.scope.clock.clkgen_freq = frequency self.scope.samples = posttrig return self.scope.clock.freq_ctr, self.scope.samples - def setup_channel(self, channel: str, coupling: str, range: float, offset: float, enable: bool) -> None: + def setup_channel( + self, channel: str, coupling: str, range: float, offset: float, enable: bool + ) -> None: pass - def setup_trigger(self, channel: str, threshold: float, direction: str, delay: int, - timeout: int, enable: bool) -> None: + def setup_trigger( + self, + channel: str, + threshold: float, + direction: str, + delay: int, + timeout: int, + enable: bool, + ) -> None: if enable: self.triggers.add(channel) elif channel in self.triggers: @@ -55,11 +66,16 @@ class ChipWhispererScope(Scope): # pragma: no cover def capture(self, timeout: Optional[int] = None) -> bool: return not self.scope.capture() - def retrieve(self, channel: str, type: SampleType, dtype=np.float16) -> Optional[Trace]: + def retrieve( + self, channel: str, type: SampleType, dtype=np.float16 + ) -> Optional[Trace]: data = self.scope.get_last_trace() if data is None: return None - return Trace(np.array(data, dtype=dtype), {"sampling_frequency": self.scope.clock.clkgen_freq, "channel": channel}) + return Trace( + np.array(data, dtype=dtype), + {"sampling_frequency": self.scope.clock.clkgen_freq, "channel": channel}, + ) def stop(self) -> None: pass diff --git a/pyecsca/sca/scope/picoscope_alt.py b/pyecsca/sca/scope/picoscope_alt.py index 544ef35..577c7dc 100644 --- a/pyecsca/sca/scope/picoscope_alt.py +++ b/pyecsca/sca/scope/picoscope_alt.py @@ -39,20 +39,31 @@ class PicoScopeAlt(Scope): # pragma: no cover def channels(self) -> Sequence[str]: return list(self.ps.CHANNELS.keys()) - def setup_frequency(self, frequency: int, pretrig: int, posttrig: int) -> Tuple[int, int]: + def setup_frequency( + self, frequency: int, pretrig: int, posttrig: int + ) -> Tuple[int, int]: samples = pretrig + posttrig actual_frequency, max_samples = self.ps.setSamplingFrequency(frequency, samples) if max_samples < samples: - self.trig_ratio = (pretrig / samples) + self.trig_ratio = pretrig / samples samples = max_samples self.frequency = actual_frequency return actual_frequency, samples - def setup_channel(self, channel: str, coupling: str, range: float, offset: float, enable: bool) -> None: + def setup_channel( + self, channel: str, coupling: str, range: float, offset: float, enable: bool + ) -> None: self.ps.setChannel(channel, coupling, range, offset, enable) - def setup_trigger(self, channel: str, threshold: float, direction: str, delay: int, - timeout: int, enable: bool) -> None: + def setup_trigger( + self, + channel: str, + threshold: float, + direction: str, + delay: int, + timeout: int, + enable: bool, + ) -> None: self.ps.setSimpleTrigger(channel, threshold, direction, delay, timeout, enable) def setup_capture(self, channel: str, enable: bool) -> None: @@ -69,14 +80,23 @@ class PicoScopeAlt(Scope): # pragma: no cover return False return True - def retrieve(self, channel: str, type: SampleType, dtype=np.float32) -> Optional[Trace]: + def retrieve( + self, channel: str, type: SampleType, dtype=np.float32 + ) -> Optional[Trace]: if type == SampleType.Raw: data = self.ps.getDataRaw(channel).astype(dtype=dtype, copy=False) else: data = self.ps.getDataV(channel, dtype=dtype) if data is None: return None - return Trace(data, {"sampling_frequency": self.frequency, "channel": channel, "sample_type": type}) + return Trace( + data, + { + "sampling_frequency": self.frequency, + "channel": channel, + "sample_type": type, + }, + ) def stop(self) -> None: self.ps.stop() diff --git a/pyecsca/sca/scope/picoscope_sdk.py b/pyecsca/sca/scope/picoscope_sdk.py index 8101f94..6237bb8 100644 --- a/pyecsca/sca/scope/picoscope_sdk.py +++ b/pyecsca/sca/scope/picoscope_sdk.py @@ -11,6 +11,7 @@ import numpy as np from picosdk.errors import CannotFindPicoSDKError from picosdk.functions import assert_pico_ok from picosdk.library import Library + try: from picosdk.ps3000 import ps3000 except CannotFindPicoSDKError as exc: @@ -33,8 +34,12 @@ from .base import Scope, SampleType from ..trace import Trace -def adc2volt(adc: Union[np.ndarray, ctypes.c_int16], - volt_range: float, adc_minmax: int, dtype=np.float32) -> Union[np.ndarray, float]: # pragma: no cover +def adc2volt( + adc: Union[np.ndarray, ctypes.c_int16], + volt_range: float, + adc_minmax: int, + dtype=np.float32, +) -> Union[np.ndarray, float]: # pragma: no cover """ Convert raw adc values to volts. @@ -51,8 +56,9 @@ def adc2volt(adc: Union[np.ndarray, ctypes.c_int16], raise ValueError -def volt2adc(volt: Union[np.ndarray, float], - volt_range: float, adc_minmax: int, dtype=np.float32) -> Union[np.ndarray, ctypes.c_int16]: # pragma: no cover +def volt2adc( + volt: Union[np.ndarray, float], volt_range: float, adc_minmax: int, dtype=np.float32 +) -> Union[np.ndarray, ctypes.c_int16]: # pragma: no cover """ Convert volt values to raw adc values. @@ -72,6 +78,7 @@ def volt2adc(volt: Union[np.ndarray, float], @public class PicoScopeSdk(Scope): # pragma: no cover """A PicoScope based scope.""" + MODULE: Library PREFIX: str CHANNELS: Mapping @@ -80,12 +87,7 @@ class PicoScopeSdk(Scope): # pragma: no cover MIN_ADC_VALUE: int COUPLING: Mapping TIME_UNITS: Mapping - TRIGGERS: Mapping = { - "above": 0, - "below": 1, - "rising": 2, - "falling": 3 - } + TRIGGERS: Mapping = {"above": 0, "below": 1, "rising": 2, "falling": 3} _variant: Optional[str] def __init__(self, variant: Optional[str] = None): @@ -112,28 +114,52 @@ class PicoScopeSdk(Scope): # pragma: no cover return self._variant info = (ctypes.c_int8 * 6)() size = ctypes.c_int16() - assert_pico_ok(self.__dispatch_call("GetUnitInfo", self.handle, ctypes.byref(info), 6, - ctypes.byref(size), 3)) - self._variant = "".join(chr(i) for i in info[:size.value]) + assert_pico_ok( + self.__dispatch_call( + "GetUnitInfo", self.handle, ctypes.byref(info), 6, ctypes.byref(size), 3 + ) + ) + self._variant = "".join(chr(i) for i in info[: size.value]) return self._variant - def setup_frequency(self, frequency: int, pretrig: int, posttrig: int) -> Tuple[int, int]: + def setup_frequency( + self, frequency: int, pretrig: int, posttrig: int + ) -> Tuple[int, int]: return self.set_frequency(frequency, pretrig, posttrig) - def set_channel(self, channel: str, enabled: bool, coupling: str, range: float, offset: float): + def set_channel( + self, channel: str, enabled: bool, coupling: str, range: float, offset: float + ): if offset != 0.0: raise ValueError("Nonzero offset not supported.") assert_pico_ok( - self.__dispatch_call("SetChannel", self.handle, self.CHANNELS[channel], enabled, - self.COUPLING[coupling], self.RANGES[range])) + self.__dispatch_call( + "SetChannel", + self.handle, + self.CHANNELS[channel], + enabled, + self.COUPLING[coupling], + self.RANGES[range], + ) + ) self.ranges[channel] = range - def setup_channel(self, channel: str, coupling: str, range: float, offset: float, enable: bool): + def setup_channel( + self, channel: str, coupling: str, range: float, offset: float, enable: bool + ): self.set_channel(channel, enable, coupling, range, offset) - def _set_freq(self, frequency: int, pretrig: int, posttrig: int, period_bound: float, - timebase_bound: int, - low_freq: int, high_freq: int, high_subtract: int) -> Tuple[int, int]: + def _set_freq( + self, + frequency: int, + pretrig: int, + posttrig: int, + period_bound: float, + timebase_bound: int, + low_freq: int, + high_freq: int, + high_subtract: int, + ) -> Tuple[int, int]: samples = pretrig + posttrig period = 1 / frequency if low_freq == 0 or period > period_bound: @@ -145,8 +171,18 @@ class PicoScopeSdk(Scope): # pragma: no cover tb = timebase_bound actual_frequency = low_freq // 2 ** tb max_samples = ctypes.c_int32() - assert_pico_ok(self.__dispatch_call("GetTimebase", self.handle, tb, samples, None, 0, - ctypes.byref(max_samples), 0)) + assert_pico_ok( + self.__dispatch_call( + "GetTimebase", + self.handle, + tb, + samples, + None, + 0, + ctypes.byref(max_samples), + 0, + ) + ) if max_samples.value < samples: pretrig = max_samples.value * (pretrig // samples) posttrig = max_samples.value - pretrig @@ -158,20 +194,43 @@ class PicoScopeSdk(Scope): # pragma: no cover self.timebase = tb return actual_frequency, samples - def set_frequency(self, frequency: int, pretrig: int, posttrig: int) -> Tuple[int, int]: + def set_frequency( + self, frequency: int, pretrig: int, posttrig: int + ) -> Tuple[int, int]: raise NotImplementedError - def setup_trigger(self, channel: str, threshold: float, direction: str, delay: int, - timeout: int, enable: bool): + def setup_trigger( + self, + channel: str, + threshold: float, + direction: str, + delay: int, + timeout: int, + enable: bool, + ): self.set_trigger(direction, enable, threshold, channel, delay, timeout) - def set_trigger(self, type: str, enabled: bool, value: float, channel: str, - delay: int, timeout: int): + def set_trigger( + self, + type: str, + enabled: bool, + value: float, + channel: str, + delay: int, + timeout: int, + ): assert_pico_ok( - self.__dispatch_call("SetSimpleTrigger", self.handle, enabled, - self.CHANNELS[channel], - volt2adc(value, self.ranges[channel], self.MAX_ADC_VALUE), - self.TRIGGERS[type], delay, timeout)) + self.__dispatch_call( + "SetSimpleTrigger", + self.handle, + enabled, + self.CHANNELS[channel], + volt2adc(value, self.ranges[channel], self.MAX_ADC_VALUE), + self.TRIGGERS[type], + delay, + timeout, + ) + ) def setup_capture(self, channel: str, enable: bool): self.set_buffer(channel, enable) @@ -184,22 +243,44 @@ class PicoScopeSdk(Scope): # pragma: no cover del self.buffers[channel] buffer = (ctypes.c_int16 * self.samples)() assert_pico_ok( - self.__dispatch_call("SetDataBuffer", self.handle, self.CHANNELS[channel], - ctypes.byref(buffer), self.samples)) + self.__dispatch_call( + "SetDataBuffer", + self.handle, + self.CHANNELS[channel], + ctypes.byref(buffer), + self.samples, + ) + ) self.buffers[channel] = buffer else: assert_pico_ok( - self.__dispatch_call("SetDataBuffer", self.handle, self.CHANNELS[channel], - None, self.samples)) + self.__dispatch_call( + "SetDataBuffer", + self.handle, + self.CHANNELS[channel], + None, + self.samples, + ) + ) del self.buffers[channel] def arm(self): if self.samples is None or self.timebase is None: raise ValueError assert_pico_ok( - self.__dispatch_call("RunBlock", self.handle, self.pretrig, self.posttrig, - self.timebase, 0, - None, 0, None, None)) + self.__dispatch_call( + "RunBlock", + self.handle, + self.pretrig, + self.posttrig, + self.timebase, + 0, + None, + 0, + None, + None, + ) + ) def capture(self, timeout: Optional[int] = None) -> bool: start = time_ns() @@ -209,25 +290,48 @@ class PicoScopeSdk(Scope): # pragma: no cover check = ctypes.c_int16(0) while ready.value == check.value: sleep(0.001) - assert_pico_ok(self.__dispatch_call("IsReady", self.handle, ctypes.byref(ready))) + assert_pico_ok( + self.__dispatch_call("IsReady", self.handle, ctypes.byref(ready)) + ) if timeout is not None and (time_ns() - start) / 1e6 >= timeout: return False return True - def retrieve(self, channel: str, type: SampleType, dtype=np.float32) -> Optional[Trace]: + def retrieve( + self, channel: str, type: SampleType, dtype=np.float32 + ) -> Optional[Trace]: if self.samples is None: raise ValueError actual_samples = ctypes.c_int32(self.samples) overflow = ctypes.c_int16() assert_pico_ok( - self.__dispatch_call("GetValues", self.handle, 0, ctypes.byref(actual_samples), 1, - 0, 0, ctypes.byref(overflow))) + self.__dispatch_call( + "GetValues", + self.handle, + 0, + ctypes.byref(actual_samples), + 1, + 0, + 0, + ctypes.byref(overflow), + ) + ) arr = np.array(self.buffers[channel], dtype=dtype) if type == SampleType.Raw: data = arr else: - data = cast(np.ndarray, adc2volt(arr, self.ranges[channel], self.MAX_ADC_VALUE, dtype=dtype)) - return Trace(data, {"sampling_frequency": self.frequency, "channel": channel, "sample_type": type}) + data = cast( + np.ndarray, + adc2volt(arr, self.ranges[channel], self.MAX_ADC_VALUE, dtype=dtype), + ) + return Trace( + data, + { + "sampling_frequency": self.frequency, + "channel": channel, + "sample_type": type, + }, + ) def stop(self): assert_pico_ok(self.__dispatch_call("Stop")) @@ -243,23 +347,29 @@ class PicoScopeSdk(Scope): # pragma: no cover if isinstance(ps3000, CannotFindPicoSDKError): + @public class PS3000Scope(PicoScopeSdk): # pragma: no cover """A PicoScope 3000 series oscilloscope is not available. (Install `libps3000`).""" + def __init__(self, variant: Optional[str] = None): super().__init__(variant) raise ps3000 + + else: # pragma: no cover + @public class PS3000Scope(PicoScopeSdk): # type: ignore """A PicoScope 3000 series oscilloscope.""" + MODULE = ps3000 PREFIX = "ps3000" CHANNELS = { "A": ps3000.PS3000_CHANNEL["PS3000_CHANNEL_A"], "B": ps3000.PS3000_CHANNEL["PS3000_CHANNEL_B"], "C": ps3000.PS3000_CHANNEL["PS3000_CHANNEL_C"], - "D": ps3000.PS3000_CHANNEL["PS3000_CHANNEL_D"] + "D": ps3000.PS3000_CHANNEL["PS3000_CHANNEL_D"], } RANGES = { @@ -276,48 +386,57 @@ else: # pragma: no cover 50.0: ps3000.PS3000_VOLTAGE_RANGE["PS3000_50V"], 100.0: ps3000.PS3000_VOLTAGE_RANGE["PS3000_100V"], 200.0: ps3000.PS3000_VOLTAGE_RANGE["PS3000_200V"], - 400.0: ps3000.PS3000_VOLTAGE_RANGE["PS3000_400V"] + 400.0: ps3000.PS3000_VOLTAGE_RANGE["PS3000_400V"], } MAX_ADC_VALUE = 32767 MIN_ADC_VALUE = -32767 - COUPLING = { - "AC": ps3000.PICO_COUPLING["AC"], - "DC": ps3000.PICO_COUPLING["DC"] - } + COUPLING = {"AC": ps3000.PICO_COUPLING["AC"], "DC": ps3000.PICO_COUPLING["DC"]} def get_variant(self): if self._variant is not None: return self._variant info = (ctypes.c_int8 * 6)() size = ctypes.c_int16(6) - assert_pico_ok(self.__dispatch_call("GetUnitInfo", self.handle, ctypes.byref(info), size, 3)) - self._variant = "".join(chr(i) for i in info[:size.value]) + assert_pico_ok( + self.__dispatch_call( + "GetUnitInfo", self.handle, ctypes.byref(info), size, 3 + ) + ) + self._variant = "".join(chr(i) for i in info[: size.value]) return self._variant - def set_frequency(self, frequency: int, pretrig: int, posttrig: int): # TODO: fix + def set_frequency( + self, frequency: int, pretrig: int, posttrig: int + ): # TODO: fix raise NotImplementedError if isinstance(ps4000, CannotFindPicoSDKError): + @public class PS4000Scope(PicoScopeSdk): # pragma: no cover """A PicoScope 4000 series oscilloscope is not available. (Install `libps4000`).""" + def __init__(self, variant: Optional[str] = None): super().__init__(variant) raise ps4000 + + else: # pragma: no cover + @public class PS4000Scope(PicoScopeSdk): # type: ignore """A PicoScope 4000 series oscilloscope.""" + MODULE = ps4000 PREFIX = "ps4000" CHANNELS = { "A": ps4000.PS4000_CHANNEL["PS4000_CHANNEL_A"], "B": ps4000.PS4000_CHANNEL["PS4000_CHANNEL_B"], "C": ps4000.PS4000_CHANNEL["PS4000_CHANNEL_C"], - "D": ps4000.PS4000_CHANNEL["PS4000_CHANNEL_D"] + "D": ps4000.PS4000_CHANNEL["PS4000_CHANNEL_D"], } RANGES = { @@ -333,46 +452,54 @@ else: # pragma: no cover 10.0: ps4000.PS4000_RANGE["PS4000_10V"], 20.0: ps4000.PS4000_RANGE["PS4000_20V"], 50.0: ps4000.PS4000_RANGE["PS4000_50V"], - 100.0: ps4000.PS4000_RANGE["PS4000_100V"] + 100.0: ps4000.PS4000_RANGE["PS4000_100V"], } MAX_ADC_VALUE = 32764 MIN_ADC_VALUE = -32764 - COUPLING = { - "AC": ps4000.PICO_COUPLING["AC"], - "DC": ps4000.PICO_COUPLING["DC"] - } + COUPLING = {"AC": ps4000.PICO_COUPLING["AC"], "DC": ps4000.PICO_COUPLING["DC"]} def set_frequency(self, frequency: int, pretrig: int, posttrig: int): variant = self.get_variant() if variant in ("4223", "4224", "4423", "4424"): - return self._set_freq(frequency, pretrig, posttrig, 50e-9, 2, 80_000_000, 20_000_000, 1) + return self._set_freq( + frequency, pretrig, posttrig, 50e-9, 2, 80_000_000, 20_000_000, 1 + ) elif variant in ("4226", "4227"): - return self._set_freq(frequency, pretrig, posttrig, 32e-9, 3, 250_000_000, 31_250_000, - 2) + return self._set_freq( + frequency, pretrig, posttrig, 32e-9, 3, 250_000_000, 31_250_000, 2 + ) elif variant == "4262": - return self._set_freq(frequency, pretrig, posttrig, 0, 0, 0, 10_000_000, -1) + return self._set_freq( + frequency, pretrig, posttrig, 0, 0, 0, 10_000_000, -1 + ) if isinstance(ps5000, CannotFindPicoSDKError): + @public class PS5000Scope(PicoScopeSdk): # pragma: no cover """A PicoScope 5000 series oscilloscope is not available. (Install `libps5000`).""" + def __init__(self, variant: Optional[str] = None): super().__init__(variant) raise ps5000 + + else: # pragma: no cover + @public class PS5000Scope(PicoScopeSdk): # type: ignore """A PicoScope 5000 series oscilloscope.""" + MODULE = ps5000 PREFIX = "ps5000" CHANNELS = { "A": ps5000.PS5000_CHANNEL["PS5000_CHANNEL_A"], "B": ps5000.PS5000_CHANNEL["PS5000_CHANNEL_B"], "C": ps5000.PS5000_CHANNEL["PS5000_CHANNEL_C"], - "D": ps5000.PS5000_CHANNEL["PS5000_CHANNEL_D"] + "D": ps5000.PS5000_CHANNEL["PS5000_CHANNEL_D"], } RANGES = { @@ -387,39 +514,44 @@ else: # pragma: no cover 5.00: 8, 10.0: 9, 20.0: 10, - 50.0: 11 + 50.0: 11, } MAX_ADC_VALUE = 32512 MIN_ADC_VALUE = -32512 - COUPLING = { - "AC": 0, - "DC": 1 - } + COUPLING = {"AC": 0, "DC": 1} def set_frequency(self, frequency: int, pretrig: int, posttrig: int): - return self._set_freq(frequency, pretrig, posttrig, 4e-9, 2, 1_000_000_000, 125_000_000, 2) + return self._set_freq( + frequency, pretrig, posttrig, 4e-9, 2, 1_000_000_000, 125_000_000, 2 + ) if isinstance(ps6000, CannotFindPicoSDKError): + @public class PS6000Scope(PicoScopeSdk): # pragma: no cover """A PicoScope 6000 series oscilloscope is not available. (Install `libps6000`).""" + def __init__(self, variant: Optional[str] = None): super().__init__(variant) raise ps6000 + + else: # pragma: no cover + @public class PS6000Scope(PicoScopeSdk): # type: ignore """A PicoScope 6000 series oscilloscope.""" + MODULE = ps6000 PREFIX = "ps6000" CHANNELS = { "A": ps6000.PS6000_CHANNEL["PS6000_CHANNEL_A"], "B": ps6000.PS6000_CHANNEL["PS6000_CHANNEL_B"], "C": ps6000.PS6000_CHANNEL["PS6000_CHANNEL_C"], - "D": ps6000.PS6000_CHANNEL["PS6000_CHANNEL_D"] + "D": ps6000.PS6000_CHANNEL["PS6000_CHANNEL_D"], } RANGES = { @@ -434,7 +566,7 @@ else: # pragma: no cover 5.00: ps6000.PS6000_RANGE["PS6000_5V"], 10.0: ps6000.PS6000_RANGE["PS6000_10V"], 20.0: ps6000.PS6000_RANGE["PS6000_20V"], - 50.0: ps6000.PS6000_RANGE["PS6000_50V"] + 50.0: ps6000.PS6000_RANGE["PS6000_50V"], } MAX_ADC_VALUE = 32512 @@ -443,16 +575,31 @@ else: # pragma: no cover COUPLING = { "AC": ps6000.PS6000_COUPLING["PS6000_AC"], "DC": ps6000.PS6000_COUPLING["PS6000_DC_1M"], - "DC_50": ps6000.PS6000_COUPLING["PS6000_DC_50R"] + "DC_50": ps6000.PS6000_COUPLING["PS6000_DC_50R"], } def open(self): assert_pico_ok(ps6000.ps6000OpenUnit(ctypes.byref(self.handle), None)) - def set_channel(self, channel: str, enabled: bool, coupling: str, range: float, offset: float): - assert_pico_ok(ps6000.ps6000SetChannel(self.handle, self.CHANNELS[channel], enabled, - self.COUPLING[coupling], self.RANGES[range], offset, - ps6000.PS6000_BANDWIDTH_LIMITER["PS6000_BW_FULL"])) + def set_channel( + self, + channel: str, + enabled: bool, + coupling: str, + range: float, + offset: float, + ): + assert_pico_ok( + ps6000.ps6000SetChannel( + self.handle, + self.CHANNELS[channel], + enabled, + self.COUPLING[coupling], + self.RANGES[range], + offset, + ps6000.PS6000_BANDWIDTH_LIMITER["PS6000_BW_FULL"], + ) + ) def set_buffer(self, channel: str, enable: bool): if self.samples is None: @@ -462,15 +609,24 @@ else: # pragma: no cover del self.buffers[channel] buffer = (ctypes.c_int16 * self.samples)() assert_pico_ok( - ps6000.ps6000SetDataBuffer(self.handle, self.CHANNELS[channel], - ctypes.byref(buffer), self.samples, 0)) + ps6000.ps6000SetDataBuffer( + self.handle, + self.CHANNELS[channel], + ctypes.byref(buffer), + self.samples, + 0, + ) + ) self.buffers[channel] = buffer else: assert_pico_ok( - ps6000.ps6000SetDataBuffer(self.handle, self.CHANNELS[channel], - None, self.samples, 0)) + ps6000.ps6000SetDataBuffer( + self.handle, self.CHANNELS[channel], None, self.samples, 0 + ) + ) del self.buffers[channel] def set_frequency(self, frequency: int, pretrig: int, posttrig: int): - return self._set_freq(frequency, pretrig, posttrig, 3.2e-9, 4, 5_000_000_000, 156_250_000, - 4) + return self._set_freq( + frequency, pretrig, posttrig, 3.2e-9, 4, 5_000_000_000, 156_250_000, 4 + ) diff --git a/pyecsca/sca/target/ISO7816.py b/pyecsca/sca/target/ISO7816.py index 233685e..1a9c8b8 100644 --- a/pyecsca/sca/target/ISO7816.py +++ b/pyecsca/sca/target/ISO7816.py @@ -14,6 +14,7 @@ from .base import Target @dataclass class CommandAPDU(object): # pragma: no cover """A command APDU that can be sent to an ISO7816-4 target.""" + cls: int ins: int p1: int @@ -28,30 +29,61 @@ class CommandAPDU(object): # pragma: no cover return bytes([self.cls, self.ins, self.p1, self.p2]) elif self.ne <= 256: # Case 2s - return bytes([self.cls, self.ins, self.p1, self.p2, self.ne if self.ne != 256 else 0]) + return bytes( + [ + self.cls, + self.ins, + self.p1, + self.p2, + self.ne if self.ne != 256 else 0, + ] + ) else: # Case 2e - return bytes([self.cls, self.ins, self.p1, self.p2]) + (self.ne.to_bytes(2, "big") if self.ne != 65536 else bytes([0, 0])) + return bytes([self.cls, self.ins, self.p1, self.p2]) + ( + self.ne.to_bytes(2, "big") if self.ne != 65536 else bytes([0, 0]) + ) elif self.ne is None or self.ne == 0: if len(self.data) <= 255: # Case 3s - return bytes([self.cls, self.ins, self.p1, self.p2, len(self.data)]) + self.data + return ( + bytes([self.cls, self.ins, self.p1, self.p2, len(self.data)]) + + self.data + ) else: # Case 3e - return bytes([self.cls, self.ins, self.p1, self.p2, 0]) + len(self.data).to_bytes(2, "big") + self.data + return ( + bytes([self.cls, self.ins, self.p1, self.p2, 0]) + + len(self.data).to_bytes(2, "big") + + self.data + ) else: if len(self.data) <= 255 and self.ne <= 256: # Case 4s - return bytes([self.cls, self.ins, self.p1, self.p2, len(self.data)]) + self.data + bytes([self.ne if self.ne != 256 else 0]) + return ( + bytes([self.cls, self.ins, self.p1, self.p2, len(self.data)]) + + self.data + + bytes([self.ne if self.ne != 256 else 0]) + ) else: # Case 4e - return bytes([self.cls, self.ins, self.p1, self.p2, 0]) + len(self.data).to_bytes(2, "big") + self.data + (self.ne.to_bytes(2, "big") if self.ne != 65536 else bytes([0, 0])) + return ( + bytes([self.cls, self.ins, self.p1, self.p2, 0]) + + len(self.data).to_bytes(2, "big") + + self.data + + ( + self.ne.to_bytes(2, "big") + if self.ne != 65536 + else bytes([0, 0]) + ) + ) @public @dataclass class ResponseAPDU(object): """A response APDU that can be received from an ISO7816-4 target.""" + data: bytes sw: int @@ -90,6 +122,7 @@ class ISO7816Target(Target, ABC): @public class ISO7816: """A bunch of ISO7816-4 constants (status words).""" + SW_FILE_FULL = 0x6A84 SW_UNKNOWN = 0x6F00 SW_CLA_NOT_SUPPORTED = 0x6E00 diff --git a/pyecsca/sca/target/PCSC.py b/pyecsca/sca/target/PCSC.py index a377751..4b2620c 100644 --- a/pyecsca/sca/target/PCSC.py +++ b/pyecsca/sca/target/PCSC.py @@ -37,7 +37,7 @@ class PCSCTarget(ISO7816Target): # pragma: no cover return bytes(self.connection.getATR()) def select(self, aid: bytes) -> bool: - apdu = CommandAPDU(0x00, 0xa4, 0x04, 0x00, aid) + apdu = CommandAPDU(0x00, 0xA4, 0x04, 0x00, aid) resp = self.send_apdu(apdu) return resp.sw == ISO7816.SW_NO_ERROR diff --git a/pyecsca/sca/target/binary.py b/pyecsca/sca/target/binary.py index 3e2877b..d4c402e 100644 --- a/pyecsca/sca/target/binary.py +++ b/pyecsca/sca/target/binary.py @@ -13,11 +13,14 @@ from .serial import SerialTarget @public class BinaryTarget(SerialTarget): """A binary target that is runnable on the host and communicates using the stdin/stdout streams.""" + binary: List[str] process: Optional[Popen] = None debug_output: bool - def __init__(self, binary: Union[str, List[str]], debug_output: bool = False, **kwargs): + def __init__( + self, binary: Union[str, List[str]], debug_output: bool = False, **kwargs + ): super().__init__() if not isinstance(binary, (str, list)): raise TypeError @@ -27,8 +30,13 @@ class BinaryTarget(SerialTarget): self.debug_output = debug_output def connect(self): - self.process = Popen(self.binary, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - text=True, bufsize=1) + self.process = Popen( + self.binary, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + text=True, + bufsize=1, + ) def write(self, data: bytes) -> None: if self.process is None: diff --git a/pyecsca/sca/target/chipwhisperer.py b/pyecsca/sca/target/chipwhisperer.py index ac3fa42..4037f12 100644 --- a/pyecsca/sca/target/chipwhisperer.py +++ b/pyecsca/sca/target/chipwhisperer.py @@ -22,7 +22,9 @@ class ChipWhispererTarget(Flashable, SimpleSerialTarget): # pragma: no cover using ChipWhisperer-Lite/Pro. """ - def __init__(self, target: SimpleSerial, scope: ScopeTemplate, programmer, **kwargs): + def __init__( + self, target: SimpleSerial, scope: ScopeTemplate, programmer, **kwargs + ): super().__init__() self.target = target self.scope = scope diff --git a/pyecsca/sca/target/ectester.py b/pyecsca/sca/target/ectester.py index 6ab10be..f6635c3 100644 --- a/pyecsca/sca/target/ectester.py +++ b/pyecsca/sca/target/ectester.py @@ -49,6 +49,7 @@ class ShiftableFlag(IntFlag): # pragma: no cover @public class KeypairEnum(ShiftableFlag): # pragma: no cover """ECTester's KeyPair type.""" + KEYPAIR_LOCAL = 0x01 KEYPAIR_REMOTE = 0x02 KEYPAIR_BOTH = KEYPAIR_LOCAL | KEYPAIR_REMOTE @@ -57,12 +58,13 @@ class KeypairEnum(ShiftableFlag): # pragma: no cover @public class InstructionEnum(IntEnum): # pragma: no cover """ECTester's instruction (INS).""" - INS_ALLOCATE = 0x5a - INS_CLEAR = 0x5b - INS_SET = 0x5c - INS_TRANSFORM = 0x5d - INS_GENERATE = 0x5e - INS_EXPORT = 0x5f + + INS_ALLOCATE = 0x5A + INS_CLEAR = 0x5B + INS_SET = 0x5C + INS_TRANSFORM = 0x5D + INS_GENERATE = 0x5E + INS_EXPORT = 0x5F INS_ECDH = 0x70 INS_ECDH_DIRECT = 0x71 INS_ECDSA = 0x72 @@ -73,13 +75,14 @@ class InstructionEnum(IntEnum): # pragma: no cover INS_ALLOCATE_SIG = 0x77 INS_GET_INFO = 0x78 INS_SET_DRY_RUN_MODE = 0x79 - INS_BUFFER = 0x7a - INS_PERFORM = 0x7b + INS_BUFFER = 0x7A + INS_PERFORM = 0x7B @public class KeyBuildEnum(IntEnum): # pragma: no cover """ECTester's key builder type.""" + BUILD_KEYPAIR = 0x01 BUILD_KEYBUILDER = 0x02 @@ -87,7 +90,8 @@ class KeyBuildEnum(IntEnum): # pragma: no cover @public class ExportEnum(IntEnum): # pragma: no cover """ECTester's export boolean.""" - EXPORT_TRUE = 0xff + + EXPORT_TRUE = 0xFF EXPORT_FALSE = 0x00 @classmethod @@ -98,13 +102,15 @@ class ExportEnum(IntEnum): # pragma: no cover @public class RunModeEnum(IntEnum): # pragma: no cover """ECTester's run mode.""" - MODE_NORMAL = 0xaa - MODE_DRY_RUN = 0xbb + + MODE_NORMAL = 0xAA + MODE_DRY_RUN = 0xBB @public class KeyEnum(ShiftableFlag): # pragma: no cover """ECTester's key enum.""" + PUBLIC = 0x01 PRIVATE = 0x02 BOTH = PRIVATE | PUBLIC @@ -113,6 +119,7 @@ class KeyEnum(ShiftableFlag): # pragma: no cover @public class AppletBaseEnum(IntEnum): # pragma: no cover """ECTester's JavaCard applet base version.""" + BASE_221 = 0x0221 BASE_222 = 0x0222 @@ -120,6 +127,7 @@ class AppletBaseEnum(IntEnum): # pragma: no cover @public class KeyClassEnum(IntEnum): # pragma: no cover """JavaCard EC-based key class.""" + ALG_EC_F2M = 4 ALG_EC_FP = 5 @@ -127,6 +135,7 @@ class KeyClassEnum(IntEnum): # pragma: no cover @public class KeyAgreementEnum(IntEnum): # pragma: no cover """JavaCard `KeyAgreement` type values.""" + ALG_EC_SVDP_DH = 1 ALG_EC_SVDP_DH_KDF = 1 ALG_EC_SVDP_DHC = 2 @@ -140,6 +149,7 @@ class KeyAgreementEnum(IntEnum): # pragma: no cover @public class SignatureEnum(IntEnum): # pragma: no cover """JavaCard `Signature` type values.""" + ALG_ECDSA_SHA = 17 ALG_ECDSA_SHA_224 = 37 ALG_ECDSA_SHA_256 = 33 @@ -150,6 +160,7 @@ class SignatureEnum(IntEnum): # pragma: no cover @public class TransformationEnum(ShiftableFlag): # pragma: no cover """ECTester's point/value transformation types.""" + NONE = 0x00 FIXED = 0x01 FULLRANDOM = 0x02 @@ -167,6 +178,7 @@ class TransformationEnum(ShiftableFlag): # pragma: no cover @public class FormatEnum(IntEnum): # pragma: no cover """ECTester's point format types.""" + UNCOMPRESSED = 0 COMPRESSED = 1 HYBRID = 2 @@ -175,8 +187,9 @@ class FormatEnum(IntEnum): # pragma: no cover @public class CurveEnum(IntEnum): # pragma: no cover """ECTester's curve constants.""" + default = 0x00 - external = 0xff + external = 0xFF secp112r1 = 0x01 secp128r1 = 0x02 secp160r1 = 0x03 @@ -186,15 +199,16 @@ class CurveEnum(IntEnum): # pragma: no cover secp384r1 = 0x07 secp521r1 = 0x08 sect163r1 = 0x09 - sect233r1 = 0x0a - sect283r1 = 0x0b - sect409r1 = 0x0c - sect571r1 = 0x0d + sect233r1 = 0x0A + sect283r1 = 0x0B + sect409r1 = 0x0C + sect571r1 = 0x0D @public class ParameterEnum(ShiftableFlag): # pragma: no cover """ECTester's parameter ids.""" + NONE = 0x00 FP = 0x01 F2M = 0x02 @@ -214,11 +228,13 @@ class ParameterEnum(ShiftableFlag): # pragma: no cover @public class ChunkingException(Exception): # pragma: no cover """An exception that is raised if an error happened during the chunking process of a large APDU.""" + pass class Response(ABC): # pragma: no cover """An abstract base class of a response APDU.""" + resp: ResponseAPDU sws: List[int] params: List[bytes] @@ -233,7 +249,7 @@ class Response(ABC): # pragma: no cover offset = 0 for i in range(num_sw): if len(resp.data) >= offset + 2: - self.sws[i] = int.from_bytes(resp.data[offset:offset + 2], "big") + self.sws[i] = int.from_bytes(resp.data[offset : offset + 2], "big") offset += 2 if self.sws[i] != ISO7816.SW_NO_ERROR: self.success = False @@ -250,13 +266,13 @@ class Response(ABC): # pragma: no cover self.success = False self.error = True break - param_len = int.from_bytes(resp.data[offset:offset + 2], "big") + param_len = int.from_bytes(resp.data[offset : offset + 2], "big") offset += 2 if len(resp.data) < offset + param_len: self.success = False self.error = True break - self.params[i] = resp.data[offset:offset + param_len] + self.params[i] = resp.data[offset : offset + param_len] offset += param_len def __repr__(self): @@ -322,12 +338,18 @@ class GenerateResponse(Response): # pragma: no cover @public class ExportResponse(Response): # pragma: no cover """A response to the Export command, contains the exported parameters/values.""" + keypair: KeypairEnum key: KeyEnum parameters: ParameterEnum - def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum, key: KeyEnum, - params: ParameterEnum): + def __init__( + self, + resp: ResponseAPDU, + keypair: KeypairEnum, + key: KeyEnum, + params: ParameterEnum, + ): self.keypair = keypair self.key = key self.parameters = params @@ -344,7 +366,9 @@ class ExportResponse(Response): # pragma: no cover other = 0 other += 1 if key & KeyEnum.PUBLIC and params & ParameterEnum.W else 0 other += 1 if key & KeyEnum.PRIVATE and params & ParameterEnum.S else 0 - super().__init__(resp, exported, exported * keys * param_count + exported * other) + super().__init__( + resp, exported, exported * keys * param_count + exported * other + ) def get_index(self, keypair: KeypairEnum, param: ParameterEnum) -> Optional[int]: pair = KeypairEnum.KEYPAIR_LOCAL @@ -378,8 +402,10 @@ class ExportResponse(Response): # pragma: no cover return None def __repr__(self): - return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error}, " \ - f"keypair={self.keypair.name}, key={self.key.name}, params={self.parameters.name})" + return ( + f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error}, " + f"keypair={self.keypair.name}, key={self.key.name}, params={self.parameters.name})" + ) @public @@ -434,6 +460,7 @@ class RunModeResponse(Response): # pragma: no cover class InfoResponse(Response): # pragma: no cover """A response to the Info command, contains all information about the applet version/environment.""" + version: str base: AppletBaseEnum system_version: float @@ -447,33 +474,39 @@ class InfoResponse(Response): # pragma: no cover super().__init__(resp, 1, 0) offset = 2 - version_len = int.from_bytes(resp.data[offset:offset + 2], "big") + version_len = int.from_bytes(resp.data[offset : offset + 2], "big") offset += 2 - self.version = resp.data[offset:offset + version_len].decode() + self.version = resp.data[offset : offset + version_len].decode() offset += version_len - self.base = AppletBaseEnum(int.from_bytes(resp.data[offset:offset + 2], "big")) + self.base = AppletBaseEnum( + int.from_bytes(resp.data[offset : offset + 2], "big") + ) offset += 2 - system_version = int.from_bytes(resp.data[offset:offset + 2], "big") + system_version = int.from_bytes(resp.data[offset : offset + 2], "big") system_major = system_version >> 8 - system_minor = system_version & 0xff + system_minor = system_version & 0xFF minor_size = 1 if system_minor == 0 else ceil(log(system_minor, 10)) self.system_version = system_major + system_minor / (minor_size * 10) offset += 2 - self.object_deletion_supported = int.from_bytes(resp.data[offset:offset + 2], "big") == 1 + self.object_deletion_supported = ( + int.from_bytes(resp.data[offset : offset + 2], "big") == 1 + ) offset += 2 - self.buf_len = int.from_bytes(resp.data[offset:offset + 2], "big") + self.buf_len = int.from_bytes(resp.data[offset : offset + 2], "big") offset += 2 - self.ram1_len = int.from_bytes(resp.data[offset:offset + 2], "big") + self.ram1_len = int.from_bytes(resp.data[offset : offset + 2], "big") offset += 2 - self.ram2_len = int.from_bytes(resp.data[offset:offset + 2], "big") + self.ram2_len = int.from_bytes(resp.data[offset : offset + 2], "big") offset += 2 - self.apdu_len = int.from_bytes(resp.data[offset:offset + 2], "big") + self.apdu_len = int.from_bytes(resp.data[offset : offset + 2], "big") offset += 2 def __repr__(self): - return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, " \ - f"success={self.success}, error={self.error}, version={self.version}, base={self.base.name}, system_version={self.system_version}, " \ - f"object_deletion_supported={self.object_deletion_supported}, buf_len={self.buf_len}, ram1_len={self.ram1_len}, ram2_len={self.ram2_len}, apdu_len={self.apdu_len})" + return ( + f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, " + f"success={self.success}, error={self.error}, version={self.version}, base={self.base.name}, system_version={self.system_version}, " + f"object_deletion_supported={self.object_deletion_supported}, buf_len={self.buf_len}, ram1_len={self.ram1_len}, ram2_len={self.ram2_len}, apdu_len={self.apdu_len})" + ) @public @@ -482,7 +515,8 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover A smartcard target which communicates with the `ECTester <https://github.com/crocs-muni/ECTester>`_ applet on smartcards of the JavaCard platform using PCSC. """ - CLA_ECTESTER = 0xb0 + + CLA_ECTESTER = 0xB0 AID_PREFIX = bytes([0x45, 0x43, 0x54, 0x65, 0x73, 0x74, 0x65, 0x72]) AID_CURRENT_VERSION = bytes([0x30, 0x33, 0x33]) # Version v0.3.3 AID_SUFFIX_221 = bytes([0x62]) @@ -507,15 +541,19 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover chunk_length = 255 if chunk_start + chunk_length > len(data): chunk_length = len(data) - chunk_start - chunk = data[chunk_start: chunk_start + chunk_length] - chunk_apdu = CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_BUFFER, 0, 0, chunk) + chunk = data[chunk_start : chunk_start + chunk_length] + chunk_apdu = CommandAPDU( + self.CLA_ECTESTER, InstructionEnum.INS_BUFFER, 0, 0, chunk + ) resp = super().send_apdu(chunk_apdu) if resp.sw != 0x9000: raise ChunkingException() apdu = CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_PERFORM, 0, 0) resp = super().send_apdu(apdu) - if resp.sw & 0xff00 == ISO7816.SW_BYTES_REMAINING_00: - resp = super().send_apdu(CommandAPDU(0x00, 0xc0, 0x00, 0x00, None, resp.sw & 0xff)) + if resp.sw & 0xFF00 == ISO7816.SW_BYTES_REMAINING_00: + resp = super().send_apdu( + CommandAPDU(0x00, 0xC0, 0x00, 0x00, None, resp.sw & 0xFF) + ) return resp def select_applet(self, latest_version: bytes = AID_CURRENT_VERSION): @@ -548,7 +586,9 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover return True @staticmethod - def encode_parameters(params: ParameterEnum, obj: Union[DomainParameters, Point, int]) -> Mapping[ParameterEnum, bytes]: + def encode_parameters( + params: ParameterEnum, obj: Union[DomainParameters, Point, int] + ) -> Mapping[ParameterEnum, bytes]: """Encode values from `obj` into the byte parameters that the **ECTester** applet expects.""" def convert_int(obj: int) -> bytes: @@ -559,7 +599,9 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover return bytes(obj) result = {} - if isinstance(obj, DomainParameters) and isinstance(obj.curve.model, ShortWeierstrassModel): + if isinstance(obj, DomainParameters) and isinstance( + obj.curve.model, ShortWeierstrassModel + ): for param in params & ParameterEnum.DOMAIN_FP: if param == ParameterEnum.G: result[param] = convert_point(obj.generator.to_affine()) @@ -577,7 +619,9 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover for param in params & (ParameterEnum.G | ParameterEnum.W): result[param] = convert_point(obj) elif isinstance(obj, int): - for param in params & ((ParameterEnum.DOMAIN_FP ^ ParameterEnum.G) | ParameterEnum.S): + for param in params & ( + (ParameterEnum.DOMAIN_FP ^ ParameterEnum.G) | ParameterEnum.S + ): result[param] = convert_int(obj) else: raise TypeError @@ -591,8 +635,14 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ALLOCATE_KA, 0, 0, - bytes([ka_type]))) + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_ALLOCATE_KA, + 0, + 0, + bytes([ka_type]), + ) + ) return AllocateKaResponse(resp) def allocate_sig(self, sig_type: SignatureEnum) -> AllocateSigResponse: @@ -602,12 +652,24 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :param sig_type: Which Signature type to allocate. :return: The response. """ - resp = self.send_apdu(CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ALLOCATE_SIG, 0, 0, - bytes([sig_type]))) + resp = self.send_apdu( + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_ALLOCATE_SIG, + 0, + 0, + bytes([sig_type]), + ) + ) return AllocateSigResponse(resp) - def allocate(self, keypair: KeypairEnum, builder: KeyBuildEnum, key_length: int, - key_class: KeyClassEnum) -> AllocateResponse: + def allocate( + self, + keypair: KeypairEnum, + builder: KeyBuildEnum, + key_length: int, + key_class: KeyClassEnum, + ) -> AllocateResponse: """ Send the Allocate KeyPair command. @@ -618,8 +680,14 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ALLOCATE, keypair, builder, - key_length.to_bytes(2, "big") + bytes([key_class]))) + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_ALLOCATE, + keypair, + builder, + key_length.to_bytes(2, "big") + bytes([key_class]), + ) + ) return AllocateResponse(resp, keypair) def clear(self, keypair: KeypairEnum) -> ClearResponse: @@ -630,11 +698,17 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_CLEAR, keypair, 0, None)) + CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_CLEAR, keypair, 0, None) + ) return ClearResponse(resp, keypair) - def set(self, keypair: KeypairEnum, curve: CurveEnum, params: ParameterEnum, - values: Optional[Mapping[ParameterEnum, bytes]] = None) -> SetResponse: + def set( + self, + keypair: KeypairEnum, + curve: CurveEnum, + params: ParameterEnum, + values: Optional[Mapping[ParameterEnum, bytes]] = None, + ) -> SetResponse: """ Send the Set command. @@ -656,18 +730,31 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover break e <<= 1 resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_SET, keypair, curve, - payload)) + CommandAPDU( + self.CLA_ECTESTER, InstructionEnum.INS_SET, keypair, curve, payload + ) + ) elif values is not None: raise ValueError("Values should be specified only if curve is external.") else: resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_SET, keypair, curve, - params.to_bytes(2, "big"))) + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_SET, + keypair, + curve, + params.to_bytes(2, "big"), + ) + ) return SetResponse(resp, keypair) - def transform(self, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum, - transformation: TransformationEnum) -> TransformResponse: + def transform( + self, + keypair: KeypairEnum, + key: KeyEnum, + params: ParameterEnum, + transformation: TransformationEnum, + ) -> TransformResponse: """ Send the Transform command. @@ -678,8 +765,14 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_TRANSFORM, keypair, key, - params.to_bytes(2, "big") + transformation.to_bytes(2, "big"))) + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_TRANSFORM, + keypair, + key, + params.to_bytes(2, "big") + transformation.to_bytes(2, "big"), + ) + ) return TransformResponse(resp, keypair) def generate(self, keypair: KeypairEnum) -> GenerateResponse: @@ -690,10 +783,15 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_GENERATE, keypair, 0, None)) + CommandAPDU( + self.CLA_ECTESTER, InstructionEnum.INS_GENERATE, keypair, 0, None + ) + ) return GenerateResponse(resp, keypair) - def export(self, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum) -> ExportResponse: + def export( + self, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum + ) -> ExportResponse: """ Send the Export command. @@ -703,12 +801,24 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response, containing the exported parameters. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_EXPORT, keypair, key, - params.to_bytes(2, "big"))) + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_EXPORT, + keypair, + key, + params.to_bytes(2, "big"), + ) + ) return ExportResponse(resp, keypair, key, params) - def ecdh(self, pubkey: KeypairEnum, privkey: KeypairEnum, export: bool, - transformation: TransformationEnum, ka_type: KeyAgreementEnum) -> ECDHResponse: + def ecdh( + self, + pubkey: KeypairEnum, + privkey: KeypairEnum, + export: bool, + transformation: TransformationEnum, + ka_type: KeyAgreementEnum, + ) -> ECDHResponse: """ Send the ECDH command. @@ -720,13 +830,26 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDH, pubkey, privkey, - bytes([ExportEnum.from_bool(export)]) + transformation.to_bytes( - 2, "big") + bytes([ka_type]))) + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_ECDH, + pubkey, + privkey, + bytes([ExportEnum.from_bool(export)]) + + transformation.to_bytes(2, "big") + + bytes([ka_type]), + ) + ) return ECDHResponse(resp, export) - def ecdh_direct(self, privkey: KeypairEnum, export: bool, transformation: TransformationEnum, - ka_type: KeyAgreementEnum, pubkey: bytes) -> ECDHResponse: + def ecdh_direct( + self, + privkey: KeypairEnum, + export: bool, + transformation: TransformationEnum, + ka_type: KeyAgreementEnum, + pubkey: bytes, + ) -> ECDHResponse: """ Send the ECDH direct command. @@ -738,14 +861,22 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDH_DIRECT, privkey, - ExportEnum.from_bool(export), - transformation.to_bytes(2, "big") + bytes([ka_type]) + len( - pubkey).to_bytes(2, "big") + pubkey)) + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_ECDH_DIRECT, + privkey, + ExportEnum.from_bool(export), + transformation.to_bytes(2, "big") + + bytes([ka_type]) + + len(pubkey).to_bytes(2, "big") + + pubkey, + ) + ) return ECDHResponse(resp, export) - def ecdsa(self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum, - data: bytes) -> ECDSAResponse: + def ecdsa( + self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum, data: bytes + ) -> ECDSAResponse: """ Send the ECDSA command. @@ -755,13 +886,20 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :param data: The data to sign and verify. :return: The response. """ - resp = self.send_apdu(CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDSA, keypair, - ExportEnum.from_bool(export), - bytes([sig_type]) + len(data).to_bytes(2, "big") + data)) + resp = self.send_apdu( + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_ECDSA, + keypair, + ExportEnum.from_bool(export), + bytes([sig_type]) + len(data).to_bytes(2, "big") + data, + ) + ) return ECDSAResponse(resp, export) - def ecdsa_sign(self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum, - data: bytes) -> ECDSAResponse: + def ecdsa_sign( + self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum, data: bytes + ) -> ECDSAResponse: """ Send the ECDSA sign command. @@ -772,13 +910,19 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDSA_SIGN, keypair, - ExportEnum.from_bool(export), - bytes([sig_type]) + len(data).to_bytes(2, "big") + data)) + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_ECDSA_SIGN, + keypair, + ExportEnum.from_bool(export), + bytes([sig_type]) + len(data).to_bytes(2, "big") + data, + ) + ) return ECDSAResponse(resp, export) - def ecdsa_verify(self, keypair: KeypairEnum, sig_type: SignatureEnum, sig: bytes, - data: bytes) -> ECDSAResponse: + def ecdsa_verify( + self, keypair: KeypairEnum, sig_type: SignatureEnum, sig: bytes, data: bytes + ) -> ECDSAResponse: """ Send the ECDSA verify command. @@ -788,10 +932,15 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :param data: The data. :return: The response. """ - resp = self.send_apdu(CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDSA_VERIFY, - keypair, sig_type, - len(data).to_bytes(2, "big") + data + len(sig).to_bytes(2, - "big") + sig)) + resp = self.send_apdu( + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_ECDSA_VERIFY, + keypair, + sig_type, + len(data).to_bytes(2, "big") + data + len(sig).to_bytes(2, "big") + sig, + ) + ) return ECDSAResponse(resp, False) def cleanup(self) -> CleanupResponse: @@ -801,7 +950,8 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_CLEANUP, 0, 0, None)) + CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_CLEANUP, 0, 0, None) + ) return CleanupResponse(resp) def info(self) -> InfoResponse: @@ -811,7 +961,8 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_GET_INFO, 0, 0, None)) + CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_GET_INFO, 0, 0, None) + ) return InfoResponse(resp) def run_mode(self, run_mode: RunModeEnum) -> RunModeResponse: @@ -821,6 +972,12 @@ class ECTesterTarget(PCSCTarget): # pragma: no cover :return: The response. """ resp = self.send_apdu( - CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_SET_DRY_RUN_MODE, run_mode, 0, - None)) + CommandAPDU( + self.CLA_ECTESTER, + InstructionEnum.INS_SET_DRY_RUN_MODE, + run_mode, + 0, + None, + ) + ) return RunModeResponse(resp) diff --git a/pyecsca/sca/target/simpleserial.py b/pyecsca/sca/target/simpleserial.py index 03e5768..03cb9cf 100644 --- a/pyecsca/sca/target/simpleserial.py +++ b/pyecsca/sca/target/simpleserial.py @@ -13,6 +13,7 @@ from .serial import SerialTarget @public class SimpleSerialMessage(object): """A SimpleSerial message consisting of a starting character and a hexadecimal string.""" + char: str data: str @@ -60,7 +61,9 @@ class SimpleSerialTarget(SerialTarget): result[msg.char] = msg return result - def send_cmd(self, cmd: SimpleSerialMessage, timeout: int) -> Mapping[str, SimpleSerialMessage]: + def send_cmd( + self, cmd: SimpleSerialMessage, timeout: int + ) -> Mapping[str, SimpleSerialMessage]: """ Send a :py:class:`SimpleSerialMessage` and receive the response messages that the command produces, within a `timeout`. @@ -71,7 +74,7 @@ class SimpleSerialTarget(SerialTarget): """ data = bytes(cmd) for i in range(0, len(data), 64): - chunk = data[i:i + 64] + chunk = data[i : i + 64] sleep(0.010) self.write(chunk) self.write(b"\n") diff --git a/pyecsca/sca/trace/align.py b/pyecsca/sca/trace/align.py index 56be5b3..d97e358 100644 --- a/pyecsca/sca/trace/align.py +++ b/pyecsca/sca/trace/align.py @@ -11,8 +11,9 @@ from .process import normalize from .trace import Trace -def _align_reference(reference: Trace, *traces: Trace, - align_func: Callable[[Trace], Tuple[bool, int]]) -> Tuple[List[Trace], List[int]]: +def _align_reference( + reference: Trace, *traces: Trace, align_func: Callable[[Trace], Tuple[bool, int]] +) -> Tuple[List[Trace], List[int]]: result = [deepcopy(reference)] offsets = [0] for trace in traces: @@ -25,18 +26,23 @@ def _align_reference(reference: Trace, *traces: Trace, else: result_samples = np.zeros(len(trace.samples), dtype=trace.samples.dtype) if offset > 0: - result_samples[:length - offset] = trace.samples[offset:] + result_samples[: length - offset] = trace.samples[offset:] else: - result_samples[-offset:] = trace.samples[:length + offset] + result_samples[-offset:] = trace.samples[: length + offset] result.append(trace.with_samples(result_samples)) offsets.append(offset) return result, offsets @public -def align_correlation(reference: Trace, *traces: Trace, - reference_offset: int, reference_length: int, - max_offset: int, min_correlation: float = 0.5) -> Tuple[List[Trace], List[int]]: +def align_correlation( + reference: Trace, + *traces: Trace, + reference_offset: int, + reference_length: int, + max_offset: int, + min_correlation: float = 0.5 +) -> Tuple[List[Trace], List[int]]: """ Align `traces` to the reference `trace`. Using the cross-correlation of a part of the reference trace starting at `reference_offset` with `reference_length` and try to match it to a part of @@ -54,14 +60,19 @@ def align_correlation(reference: Trace, *traces: Trace, """ reference_centered = normalize(reference) reference_part = reference_centered.samples[ - reference_offset:reference_offset + reference_length] + reference_offset : reference_offset + reference_length + ] def align_func(trace): length = len(trace.samples) correlation_start = max(reference_offset - max_offset, 0) - correlation_end = min(reference_offset + reference_length + max_offset, length - 1) + correlation_end = min( + reference_offset + reference_length + max_offset, length - 1 + ) trace_part = trace.samples[correlation_start:correlation_end] - trace_part = (trace_part - np.mean(trace_part)) / (np.std(trace_part) * len(trace_part)) + trace_part = (trace_part - np.mean(trace_part)) / ( + np.std(trace_part) * len(trace_part) + ) correlation = np.correlate(trace_part, reference_part, "same") max_correlation_offset = correlation.argmax(axis=0) max_correlation = correlation[max_correlation_offset] @@ -76,8 +87,13 @@ def align_correlation(reference: Trace, *traces: Trace, @public -def align_peaks(reference: Trace, *traces: Trace, - reference_offset: int, reference_length: int, max_offset: int) -> Tuple[List[Trace], List[int]]: +def align_peaks( + reference: Trace, + *traces: Trace, + reference_offset: int, + reference_length: int, + max_offset: int +) -> Tuple[List[Trace], List[int]]: """ Align `traces` to the reference `trace` so that the maximum value within the reference trace window from `reference_offset` of `reference_length` aligns with the maximum @@ -90,14 +106,16 @@ def align_peaks(reference: Trace, *traces: Trace, :param max_offset: :return: """ - reference_part = reference.samples[reference_offset: reference_offset + reference_length] + reference_part = reference.samples[ + reference_offset : reference_offset + reference_length + ] reference_peak = np.argmax(reference_part) def align_func(trace): length = len(trace.samples) window_start = max(reference_offset - max_offset, 0) window_end = min(reference_offset + reference_length + max_offset, length - 1) - window = trace.samples[window_start: window_end] + window = trace.samples[window_start:window_end] window_peak = np.argmax(window) left_space = min(max_offset, reference_offset) return True, int(window_peak - reference_peak - left_space) @@ -106,9 +124,15 @@ def align_peaks(reference: Trace, *traces: Trace, @public -def align_offset(reference: Trace, *traces: Trace, - reference_offset: int, reference_length: int, max_offset: int, - dist_func: Callable[[np.ndarray, np.ndarray], float], max_dist: float = float("inf")) -> Tuple[List[Trace], List[int]]: +def align_offset( + reference: Trace, + *traces: Trace, + reference_offset: int, + reference_length: int, + max_offset: int, + dist_func: Callable[[np.ndarray, np.ndarray], float], + max_dist: float = float("inf") +) -> Tuple[List[Trace], List[int]]: """ Align `traces` to the reference `trace` so that the value of the `dist_func` is minimized between the reference trace window from `reference_offset` of `reference_length` and the trace @@ -122,7 +146,9 @@ def align_offset(reference: Trace, *traces: Trace, :param dist_func: :return: """ - reference_part = reference.samples[reference_offset: reference_offset + reference_length] + reference_part = reference.samples[ + reference_offset : reference_offset + reference_length + ] def align_func(trace): length = len(trace.samples) @@ -142,12 +168,18 @@ def align_offset(reference: Trace, *traces: Trace, return True, best_offset else: return False, 0 + return _align_reference(reference, *traces, align_func=align_func) @public -def align_sad(reference: Trace, *traces: Trace, - reference_offset: int, reference_length: int, max_offset: int) -> Tuple[List[Trace], List[int]]: +def align_sad( + reference: Trace, + *traces: Trace, + reference_offset: int, + reference_length: int, + max_offset: int +) -> Tuple[List[Trace], List[int]]: """ Align `traces` to the reference `trace` so that the Sum Of Absolute Differences between the reference trace window from `reference_offset` of `reference_length` and the trace being aligned @@ -160,17 +192,24 @@ def align_sad(reference: Trace, *traces: Trace, :param max_offset: :return: """ + def sad(reference_part, trace_part): return float(np.sum(np.abs(reference_part - trace_part))) - return align_offset(reference, *traces, - reference_offset=reference_offset, reference_length=reference_length, - max_offset=max_offset, dist_func=sad) + return align_offset( + reference, + *traces, + reference_offset=reference_offset, + reference_length=reference_length, + max_offset=max_offset, + dist_func=sad + ) @public -def align_dtw_scale(reference: Trace, *traces: Trace, radius: int = 1, - fast: bool = True) -> List[Trace]: +def align_dtw_scale( + reference: Trace, *traces: Trace, radius: int = 1, fast: bool = True +) -> List[Trace]: """ Align `traces` to the `reference` trace. Using fastdtw (Dynamic Time Warping) with scaling as per: @@ -206,7 +245,9 @@ def align_dtw_scale(reference: Trace, *traces: Trace, radius: int = 1, @public -def align_dtw(reference: Trace, *traces: Trace, radius: int = 1, fast: bool = True) -> List[Trace]: +def align_dtw( + reference: Trace, *traces: Trace, radius: int = 1, fast: bool = True +) -> List[Trace]: """ Align `traces` to the `reference` trace. Using fastdtw (Dynamic Time Warping) as per: @@ -228,9 +269,13 @@ def align_dtw(reference: Trace, *traces: Trace, radius: int = 1, fast: bool = Tr dist, path = fastdtw(reference_samples, trace.samples, radius=radius) else: dist, path = dtw(reference_samples, trace.samples) - result_samples = np.zeros(max((len(trace.samples), len(reference_samples))), dtype=trace.samples.dtype) - pairs = np.array(np.array(path, dtype=np.dtype("int,int")), - dtype=np.dtype([("x", "int"), ("y", "int")])) + result_samples = np.zeros( + max((len(trace.samples), len(reference_samples))), dtype=trace.samples.dtype + ) + pairs = np.array( + np.array(path, dtype=np.dtype("int,int")), + dtype=np.dtype([("x", "int"), ("y", "int")]), + ) result_samples[pairs["x"]] = trace.samples[pairs["y"]] del pairs del path diff --git a/pyecsca/sca/trace/combine.py b/pyecsca/sca/trace/combine.py index 8b80869..853b127 100644 --- a/pyecsca/sca/trace/combine.py +++ b/pyecsca/sca/trace/combine.py @@ -25,13 +25,15 @@ def average(*traces: Trace) -> Optional[CombinedTrace]: s = np.zeros(min_samples, dtype=np.float64) for t in traces: s = np.add(s, t.samples[:min_samples]) - avg = ((1 / len(traces)) * s) + avg = (1 / len(traces)) * s del s return CombinedTrace(avg) @public -def conditional_average(*traces: Trace, condition: Callable[[Trace], bool]) -> Optional[CombinedTrace]: +def conditional_average( + *traces: Trace, condition: Callable[[Trace], bool] +) -> Optional[CombinedTrace]: """ Average `traces` for which the `condition` is True, sample-wise. @@ -107,8 +109,10 @@ def average_and_variance(*traces) -> Optional[Tuple[CombinedTrace, CombinedTrace if not traces: return None if len(traces) == 1: - return (CombinedTrace(traces[0].samples.copy()), - CombinedTrace(np.zeros(len(traces[0]), dtype=np.float64))) + return ( + CombinedTrace(traces[0].samples.copy()), + CombinedTrace(np.zeros(len(traces[0]), dtype=np.float64)), + ) min_samples = min(map(len, traces)) s = np.zeros(min_samples, dtype=np.float64) for t in traces: diff --git a/pyecsca/sca/trace/edit.py b/pyecsca/sca/trace/edit.py index c0af078..5aec15f 100644 --- a/pyecsca/sca/trace/edit.py +++ b/pyecsca/sca/trace/edit.py @@ -39,8 +39,11 @@ def reverse(trace: Trace) -> Trace: @public -def pad(trace: Trace, lengths: Union[Tuple[int, int], int], - values: Union[Tuple[Any, Any], Any] = (0, 0)) -> Trace: +def pad( + trace: Trace, + lengths: Union[Tuple[int, int], int], + values: Union[Tuple[Any, Any], Any] = (0, 0), +) -> Trace: """ Pad the samples of the `trace` by `values` at the beginning and end. @@ -53,4 +56,6 @@ def pad(trace: Trace, lengths: Union[Tuple[int, int], int], lengths = (lengths, lengths) if not isinstance(values, tuple): values = (values, values) - return trace.with_samples(np.pad(trace.samples, lengths, "constant", constant_values=values)) + return trace.with_samples( + np.pad(trace.samples, lengths, "constant", constant_values=values) + ) diff --git a/pyecsca/sca/trace/filter.py b/pyecsca/sca/trace/filter.py index 720d311..2fbc284 100644 --- a/pyecsca/sca/trace/filter.py +++ b/pyecsca/sca/trace/filter.py @@ -8,13 +8,23 @@ from typing import Union, Tuple from .trace import Trace -def _filter_any(trace: Trace, sampling_frequency: int, - cutoff: Union[int, Tuple[int, int]], band_type: str) -> Trace: +def _filter_any( + trace: Trace, + sampling_frequency: int, + cutoff: Union[int, Tuple[int, int]], + band_type: str, +) -> Trace: nyq = 0.5 * sampling_frequency if not isinstance(cutoff, int): - b, a = butter(6, tuple(map(lambda x: x / nyq, cutoff)), btype=band_type, analog=False, output='ba') + b, a = butter( + 6, + tuple(map(lambda x: x / nyq, cutoff)), + btype=band_type, + analog=False, + output="ba", + ) else: - b, a = butter(6, cutoff / nyq, btype=band_type, analog=False, output='ba') + b, a = butter(6, cutoff / nyq, btype=band_type, analog=False, output="ba") return trace.with_samples(lfilter(b, a, trace.samples)) @@ -47,7 +57,9 @@ def filter_highpass(trace: Trace, sampling_frequency: int, cutoff: int) -> Trace @public -def filter_bandpass(trace: Trace, sampling_frequency: int, low: int, high: int) -> Trace: +def filter_bandpass( + trace: Trace, sampling_frequency: int, low: int, high: int +) -> Trace: """ Apply a bandpass digital filter (Butterworth) to `trace`, given `sampling_frequency`, with the passband from `low` to `high`. @@ -62,7 +74,9 @@ def filter_bandpass(trace: Trace, sampling_frequency: int, low: int, high: int) @public -def filter_bandstop(trace: Trace, sampling_frequency: int, low: int, high: int) -> Trace: +def filter_bandstop( + trace: Trace, sampling_frequency: int, low: int, high: int +) -> Trace: """ Apply a bandstop digital filter (Butterworth) to `trace`, given `sampling_frequency`, with the stopband from `low` to `high`. diff --git a/pyecsca/sca/trace/match.py b/pyecsca/sca/trace/match.py index 3728d13..50c86bc 100644 --- a/pyecsca/sca/trace/match.py +++ b/pyecsca/sca/trace/match.py @@ -43,7 +43,9 @@ def match_pattern(trace: Trace, pattern: Trace, threshold: float = 0.8) -> List[ @public -def match_part(trace: Trace, offset: int, length: int, threshold: float = 0.8) -> List[int]: +def match_part( + trace: Trace, offset: int, length: int, threshold: float = 0.8 +) -> List[int]: """ Match a part of a `trace` starting at `offset` of `length` to the `trace`. Returns indices where the pattern matches , e.g. those where correlation of the two traces has peaks larger than `threshold`. Uses the diff --git a/pyecsca/sca/trace/plot.py b/pyecsca/sca/trace/plot.py index b9843cc..6a8cec1 100644 --- a/pyecsca/sca/trace/plot.py +++ b/pyecsca/sca/trace/plot.py @@ -40,10 +40,12 @@ def plot_traces(*traces: Trace, **kwargs): # pragma: no cover ["orange", "darkorange"], ["plum", "deeppink"], ["peru", "chocolate"], - ["cyan", "darkcyan"] + ["cyan", "darkcyan"], ] dss = [] for i, trace in enumerate(traces): - line = hv.Curve((range(len(trace)), trace.samples), kdims="x", vdims="y", **kwargs) + line = hv.Curve( + (range(len(trace)), trace.samples), kdims="x", vdims="y", **kwargs + ) dss.append(datashade(line, normalization="log", cmap=_cmaps[i % len(_cmaps)])) return reduce(lambda x, y: x * y, dss) diff --git a/pyecsca/sca/trace/process.py b/pyecsca/sca/trace/process.py index 87b00ee..2499df4 100644 --- a/pyecsca/sca/trace/process.py +++ b/pyecsca/sca/trace/process.py @@ -61,8 +61,14 @@ def rolling_mean(trace: Trace, window: int) -> Trace: :param window: :return: """ - return trace.with_samples(cast(np.ndarray, np.mean(_rolling_window(trace.samples, window), -1).astype( - dtype=trace.samples.dtype, copy=False))) + return trace.with_samples( + cast( + np.ndarray, + np.mean(_rolling_window(trace.samples, window), -1).astype( + dtype=trace.samples.dtype, copy=False + ), + ) + ) @public @@ -101,7 +107,9 @@ def normalize(trace: Trace) -> Trace: :param trace: :return: """ - return trace.with_samples((trace.samples - np.mean(trace.samples)) / np.std(trace.samples)) + return trace.with_samples( + (trace.samples - np.mean(trace.samples)) / np.std(trace.samples) + ) @public @@ -112,5 +120,7 @@ def normalize_wl(trace: Trace) -> Trace: :param trace: :return: """ - return trace.with_samples((trace.samples - np.mean(trace.samples)) / ( - np.std(trace.samples) * len(trace.samples))) + return trace.with_samples( + (trace.samples - np.mean(trace.samples)) + / (np.std(trace.samples) * len(trace.samples)) + ) diff --git a/pyecsca/sca/trace/sampling.py b/pyecsca/sca/trace/sampling.py index b263adc..71dbdac 100644 --- a/pyecsca/sca/trace/sampling.py +++ b/pyecsca/sca/trace/sampling.py @@ -20,8 +20,15 @@ def downsample_average(trace: Trace, factor: int = 2) -> Trace: :param factor: :return: """ - resized = np.resize(trace.samples, len(trace.samples) - (len(trace.samples) % factor)) - result_samples = cast(np.ndarray, resized.reshape(-1, factor).mean(axis=1).astype(trace.samples.dtype, copy=False)) + resized = np.resize( + trace.samples, len(trace.samples) - (len(trace.samples) % factor) + ) + result_samples = cast( + np.ndarray, + resized.reshape(-1, factor) + .mean(axis=1) + .astype(trace.samples.dtype, copy=False), + ) return trace.with_samples(result_samples) @@ -49,8 +56,13 @@ def downsample_max(trace: Trace, factor: int = 2) -> Trace: :param factor: :return: """ - resized = np.resize(trace.samples, len(trace.samples) - (len(trace.samples) % factor)) - result_samples = cast(np.ndarray, resized.reshape(-1, factor).max(axis=1).astype(trace.samples.dtype, copy=False)) + resized = np.resize( + trace.samples, len(trace.samples) - (len(trace.samples) % factor) + ) + result_samples = cast( + np.ndarray, + resized.reshape(-1, factor).max(axis=1).astype(trace.samples.dtype, copy=False), + ) return trace.with_samples(result_samples) @@ -64,8 +76,13 @@ def downsample_min(trace: Trace, factor: int = 2) -> Trace: :param factor: :return: """ - resized = np.resize(trace.samples, len(trace.samples) - (len(trace.samples) % factor)) - result_samples = cast(np.ndarray, resized.reshape(-1, factor).min(axis=1).astype(trace.samples.dtype, copy=False)) + resized = np.resize( + trace.samples, len(trace.samples) - (len(trace.samples) % factor) + ) + result_samples = cast( + np.ndarray, + resized.reshape(-1, factor).min(axis=1).astype(trace.samples.dtype, copy=False), + ) return trace.with_samples(result_samples) diff --git a/pyecsca/sca/trace/test.py b/pyecsca/sca/trace/test.py index 8512a70..f90498a 100644 --- a/pyecsca/sca/trace/test.py +++ b/pyecsca/sca/trace/test.py @@ -12,8 +12,9 @@ from .combine import average_and_variance from .edit import trim -def _ttest_func(first_set: Sequence[Trace], second_set: Sequence[Trace], - equal_var: bool) -> Optional[CombinedTrace]: +def _ttest_func( + first_set: Sequence[Trace], second_set: Sequence[Trace], equal_var: bool +) -> Optional[CombinedTrace]: if not first_set or not second_set or len(first_set) == 0 or len(second_set) == 0: return None first_stack = np.stack([first.samples for first in first_set]) @@ -23,7 +24,12 @@ def _ttest_func(first_set: Sequence[Trace], second_set: Sequence[Trace], @public -def welch_ttest(first_set: Sequence[Trace], second_set: Sequence[Trace], dof: bool = False, p_value: bool = False) -> Optional[Tuple[CombinedTrace, ...]]: +def welch_ttest( + first_set: Sequence[Trace], + second_set: Sequence[Trace], + dof: bool = False, + p_value: bool = False, +) -> Optional[Tuple[CombinedTrace, ...]]: """ Perform the Welch's t-test sample wise on two sets of traces `first_set` and `second_set`. Useful for Test Vector Leakage Analysis (TVLA). @@ -51,8 +57,8 @@ def welch_ttest(first_set: Sequence[Trace], second_set: Sequence[Trace], dof: bo tval = (mean_0.samples - mean_1.samples) / np.sqrt(varn_0 + varn_1) result = [CombinedTrace(tval)] if dof or p_value: - top = (varn_0 + varn_1)**2 - bot = (varn_0**2 / (n0 - 1)) + (varn_1**2 / (n1 - 1)) + top = (varn_0 + varn_1) ** 2 + bot = (varn_0 ** 2 / (n0 - 1)) + (varn_1 ** 2 / (n1 - 1)) df = top / bot del top del bot @@ -66,8 +72,9 @@ def welch_ttest(first_set: Sequence[Trace], second_set: Sequence[Trace], dof: bo @public -def student_ttest(first_set: Sequence[Trace], second_set: Sequence[Trace]) -> Optional[ - CombinedTrace]: +def student_ttest( + first_set: Sequence[Trace], second_set: Sequence[Trace] +) -> Optional[CombinedTrace]: """ Perform the Students's t-test sample wise on two sets of traces `first_set` and `second_set`. Useful for Test Vector Leakage Analysis (TVLA). @@ -80,7 +87,9 @@ def student_ttest(first_set: Sequence[Trace], second_set: Sequence[Trace]) -> Op @public -def ks_test(first_set: Sequence[Trace], second_set: Sequence[Trace]) -> Optional[CombinedTrace]: +def ks_test( + first_set: Sequence[Trace], second_set: Sequence[Trace] +) -> Optional[CombinedTrace]: """ Perform the Kolmogorov-Smirnov two sample test on equality of distributions sample wise on two sets of traces `first_set` and `second_set`. diff --git a/pyecsca/sca/trace/trace.py b/pyecsca/sca/trace/trace.py index aef0837..96f1ec8 100644 --- a/pyecsca/sca/trace/trace.py +++ b/pyecsca/sca/trace/trace.py @@ -13,10 +13,13 @@ from public import public @public class Trace(object): """A trace, which has some samples and metadata.""" + meta: Mapping[str, Any] samples: ndarray - def __init__(self, samples: ndarray, meta: Mapping[str, Any] = None, trace_set: Any = None): + def __init__( + self, samples: ndarray, meta: Mapping[str, Any] = None, trace_set: Any = None + ): """ Construct a new trace. @@ -92,8 +95,11 @@ class Trace(object): return Trace(copy(self.samples), copy(self.meta), copy(self.trace_set)) def __deepcopy__(self, memodict={}): - return Trace(deepcopy(self.samples, memo=memodict), deepcopy(self.meta, memo=memodict), - deepcopy(self.trace_set, memo=memodict)) + return Trace( + deepcopy(self.samples, memo=memodict), + deepcopy(self.meta, memo=memodict), + deepcopy(self.trace_set, memo=memodict), + ) def __repr__(self): return f"Trace(samples={self.samples!r}, trace_set={self.trace_set!r})" @@ -103,8 +109,13 @@ class Trace(object): class CombinedTrace(Trace): """A trace that was combined from other traces, `parents`.""" - def __init__(self, samples: ndarray, meta: Mapping[str, Any] = None, trace_set: Any = None, - parents: Sequence[Trace] = None): + def __init__( + self, + samples: ndarray, + meta: Mapping[str, Any] = None, + trace_set: Any = None, + parents: Sequence[Trace] = None, + ): super().__init__(samples, meta, trace_set=trace_set) self.parents = None if parents is not None: diff --git a/pyecsca/sca/trace_set/base.py b/pyecsca/sca/trace_set/base.py index 6f4e4b0..097d666 100644 --- a/pyecsca/sca/trace_set/base.py +++ b/pyecsca/sca/trace_set/base.py @@ -12,6 +12,7 @@ from ..trace import Trace @public class TraceSet(object): """A set of traces with some metadata.""" + _traces: List[Trace] _keys: List @@ -46,6 +47,7 @@ class TraceSet(object): raise NotImplementedError def __repr__(self): - args = ", ".join(["{}={!r}".format(key, getattr(self, key)) for key in - self._keys]) + args = ", ".join( + ["{}={!r}".format(key, getattr(self, key)) for key in self._keys] + ) return "TraceSet({})".format(args) diff --git a/pyecsca/sca/trace_set/chipwhisperer.py b/pyecsca/sca/trace_set/chipwhisperer.py index ba78a85..a56e676 100644 --- a/pyecsca/sca/trace_set/chipwhisperer.py +++ b/pyecsca/sca/trace_set/chipwhisperer.py @@ -24,7 +24,9 @@ class ChipWhispererTraceSet(TraceSet): raise ValueError @classmethod - def inplace(cls, input: Union[str, Path, bytes, BinaryIO]) -> "ChipWhispererTraceSet": + def inplace( + cls, input: Union[str, Path, bytes, BinaryIO] + ) -> "ChipWhispererTraceSet": raise NotImplementedError def write(self, output: Union[str, Path, BinaryIO]): @@ -39,10 +41,12 @@ class ChipWhispererTraceSet(TraceSet): name = file_name[7:-4] data = ChipWhispererTraceSet.__read_data(path, name) traces = [] - for samples, key, textin, textout in zip_longest(data["traces"], data["keylist"], - data["textin"], data["textout"]): + for samples, key, textin, textout in zip_longest( + data["traces"], data["keylist"], data["textin"], data["textout"] + ): traces.append( - Trace(samples, {"key": key, "textin": textin, "textout": textout})) + Trace(samples, {"key": key, "textin": textin, "textout": textout}) + ) del data["traces"] del data["keylist"] del data["textin"] @@ -52,7 +56,13 @@ class ChipWhispererTraceSet(TraceSet): @classmethod def __read_data(cls, path, name): - types = {"keylist": None, "knownkey": None, "textin": None, "textout": None, "traces": None} + types = { + "keylist": None, + "knownkey": None, + "textin": None, + "textout": None, + "traces": None, + } for type in types.keys(): type_path = join(path, name + type + ".npy") if exists(type_path) and isfile(type_path): diff --git a/pyecsca/sca/trace_set/hdf5.py b/pyecsca/sca/trace_set/hdf5.py index 4b3b7b0..f1d34ef 100644 --- a/pyecsca/sca/trace_set/hdf5.py +++ b/pyecsca/sca/trace_set/hdf5.py @@ -22,6 +22,7 @@ from .. import Trace @public class HDF5Meta(MutableMapping): """Metadata mapping that is HDF5-compatible (items are picklable).""" + _dataset: h5py.AttributeManager def __init__(self, attrs: h5py.AttributeManager): @@ -55,12 +56,18 @@ class HDF5Meta(MutableMapping): @public class HDF5TraceSet(TraceSet): """A traceset based on the HDF5 (Hierarchical Data Format).""" + _file: Optional[h5py.File] _ordering: List[str] # _meta: Optional[HDF5Meta] - def __init__(self, *traces: Trace, _file: Optional[h5py.File] = None, - _ordering: Optional[List[str]] = None, **kwargs): + def __init__( + self, + *traces: Trace, + _file: Optional[h5py.File] = None, + _ordering: Optional[List[str]] = None, + **kwargs, + ): # self._meta = HDF5Meta(_file.attrs) if _file is not None else None self._file = _file if _ordering is None: @@ -76,7 +83,9 @@ class HDF5TraceSet(TraceSet): else: raise TypeError kwargs = dict(hdf5.attrs) - kwargs["_ordering"] = list(kwargs["_ordering"]) if "_ordering" in kwargs else list(hdf5.keys()) + kwargs["_ordering"] = ( + list(kwargs["_ordering"]) if "_ordering" in kwargs else list(hdf5.keys()) + ) traces = [] for k in kwargs["_ordering"]: meta = dict(HDF5Meta(hdf5[k].attrs)) @@ -94,7 +103,9 @@ class HDF5TraceSet(TraceSet): else: raise TypeError kwargs = dict(hdf5.attrs) - kwargs["_ordering"] = list(kwargs["_ordering"]) if "_ordering" in kwargs else list(hdf5.keys()) + kwargs["_ordering"] = ( + list(kwargs["_ordering"]) if "_ordering" in kwargs else list(hdf5.keys()) + ) traces = [] for k in kwargs["_ordering"]: meta = HDF5Meta(hdf5[k].attrs) @@ -182,5 +193,11 @@ class HDF5TraceSet(TraceSet): fname = self._file.filename else: status = "(closed)" - args = ", ".join([f"{key}={getattr(self, key)!r}" for key in self._keys if not key.startswith("_")]) + args = ", ".join( + [ + f"{key}={getattr(self, key)!r}" + for key in self._keys + if not key.startswith("_") + ] + ) return f"HDF5TraceSet('{fname}'{status}, {args})" diff --git a/pyecsca/sca/trace_set/inspector.py b/pyecsca/sca/trace_set/inspector.py index cc3f81f..83bbade 100644 --- a/pyecsca/sca/trace_set/inspector.py +++ b/pyecsca/sca/trace_set/inspector.py @@ -25,7 +25,7 @@ class SampleCoding(IntEnum): def dtype(self): char = "f" if self.value & 0x10 else "i" - return np.dtype("<{}{}".format(char, self.value & 0x0f)) + return np.dtype("<{}{}".format(char, self.value & 0x0F)) @public @@ -107,37 +107,44 @@ class InspectorTraceSet(TraceSet): _tag_parsers: dict = { 0x41: ("num_traces", 4, Parsers.read_int, Parsers.write_int), 0x42: ("num_samples", 4, Parsers.read_int, Parsers.write_int), - 0x43: ("sample_coding", 1, - lambda bytes: SampleCoding(Parsers.read_int(bytes)), - lambda coding, length: Parsers.write_int(coding.value, - length=length)), + 0x43: ( + "sample_coding", + 1, + lambda bytes: SampleCoding(Parsers.read_int(bytes)), + lambda coding, length: Parsers.write_int(coding.value, length=length), + ), 0x44: ("data_space", 2, Parsers.read_int, Parsers.write_int), 0x45: ("title_space", 1, Parsers.read_int, Parsers.write_int), 0x46: ("global_title", None, Parsers.read_str, Parsers.write_str), 0x47: ("description", None, Parsers.read_str, Parsers.write_str), 0x48: ("x_offset", None, Parsers.read_int, Parsers.write_int), 0x49: ("x_label", None, Parsers.read_str, Parsers.write_str), - 0x4a: ("y_label", None, Parsers.read_str, Parsers.write_str), - 0x4b: ("x_scale", 4, Parsers.read_float, Parsers.write_float), - 0x4c: ("y_scale", 4, Parsers.read_float, Parsers.write_float), - 0x4d: ("trace_offset", 4, Parsers.read_int, Parsers.write_int), - 0x4e: ("log_scale", 1, Parsers.read_int, Parsers.write_int), + 0x4A: ("y_label", None, Parsers.read_str, Parsers.write_str), + 0x4B: ("x_scale", 4, Parsers.read_float, Parsers.write_float), + 0x4C: ("y_scale", 4, Parsers.read_float, Parsers.write_float), + 0x4D: ("trace_offset", 4, Parsers.read_int, Parsers.write_int), + 0x4E: ("log_scale", 1, Parsers.read_int, Parsers.write_int), 0x55: ("scope_range", 4, Parsers.read_float, Parsers.write_float), 0x56: ("scope_coupling", 4, Parsers.read_int, Parsers.write_int), 0x57: ("scope_offset", 4, Parsers.read_float, Parsers.write_float), 0x58: ("scope_impedance", 4, Parsers.read_float, Parsers.write_float), 0x59: ("scope_id", None, Parsers.read_str, Parsers.write_str), - 0x5a: ("filter_type", 4, Parsers.read_int, Parsers.write_int), - 0x5b: ("filter_frequency", 4, Parsers.read_float, Parsers.write_float), - 0x5c: ("filter_range", 4, Parsers.read_float, Parsers.read_float), + 0x5A: ("filter_type", 4, Parsers.read_int, Parsers.write_int), + 0x5B: ("filter_frequency", 4, Parsers.read_float, Parsers.write_float), + 0x5C: ("filter_range", 4, Parsers.read_float, Parsers.read_float), 0x60: ("external_clock", 1, Parsers.read_bool, Parsers.write_bool), 0x61: ("external_clock_threshold", 4, Parsers.read_float, Parsers.write_float), 0x62: ("external_clock_multiplier", 4, Parsers.read_int, Parsers.write_int), 0x63: ("external_clock_phase_shift", 4, Parsers.read_int, Parsers.write_int), 0x64: ("external_clock_resampler_mask", 4, Parsers.read_int, Parsers.write_int), - 0x65: ("external_clock_resampler_enabled", 1, Parsers.read_bool, Parsers.write_bool), + 0x65: ( + "external_clock_resampler_enabled", + 1, + Parsers.read_bool, + Parsers.write_bool, + ), 0x66: ("external_clock_frequency", 4, Parsers.read_float, Parsers.write_float), - 0x67: ("external_clock_time_base", 4, Parsers.read_int, Parsers.write_int) + 0x67: ("external_clock_time_base", 4, Parsers.read_int, Parsers.write_int), } @classmethod @@ -171,28 +178,33 @@ class InspectorTraceSet(TraceSet): tag = ord(file.read(1)) length = ord(file.read(1)) if length & 0x80: - length = Parsers.read_int(file.read(length & 0x7f)) + length = Parsers.read_int(file.read(length & 0x7F)) value = file.read(length) if tag in InspectorTraceSet._tag_parsers: tag_name, tag_len, tag_reader, _ = InspectorTraceSet._tag_parsers[tag] if tag_len is None or length == tag_len: tags[tag_name] = tag_reader(value) - elif tag == 0x5f and length == 0: + elif tag == 0x5F and length == 0: break else: continue result = [] for _ in range(tags["num_traces"]): - title = None if "title_space" not in tags else Parsers.read_str( - file.read(tags["title_space"])) + title = ( + None + if "title_space" not in tags + else Parsers.read_str(file.read(tags["title_space"])) + ) data = None if "data_space" not in tags else file.read(tags["data_space"]) dtype = tags["sample_coding"].dtype() try: samples = np.fromfile(file, dtype, tags["num_samples"]) except UnsupportedOperation: samples = np.frombuffer( - file.read(dtype.itemsize * tags["num_samples"]), dtype, - tags["num_samples"]) + file.read(dtype.itemsize * tags["num_samples"]), + dtype, + tags["num_samples"], + ) result.append(Trace(samples, {"title": title, "data": data})) return result, tags @@ -222,12 +234,13 @@ class InspectorTraceSet(TraceSet): tag_byte = Parsers.write_int(tag, length=1) value_bytes = tag_writer(getattr(self, tag_name), tag_len) length = len(value_bytes) - if length <= 0x7f: + if length <= 0x7F: length_bytes = Parsers.write_int(length, length=1) else: - length_data = Parsers.write_int(length, length=(length.bit_length() + 7) // 8) - length_bytes = Parsers.write_int( - 0x80 | len(length_data)) + length_data + length_data = Parsers.write_int( + length, length=(length.bit_length() + 7) // 8 + ) + length_bytes = Parsers.write_int(0x80 | len(length_data)) + length_data file.write(tag_byte) file.write(length_bytes) file.write(value_bytes) @@ -238,7 +251,9 @@ class InspectorTraceSet(TraceSet): file.write(Parsers.write_str(trace.meta["title"])) if self.data_space != 0 and trace.meta["data"] is not None: file.write(trace.meta["data"]) - unscaled = InspectorTraceSet.__unscale(trace.samples, self.y_scale, self.sample_coding) + unscaled = InspectorTraceSet.__unscale( + trace.samples, self.y_scale, self.sample_coding + ) try: unscaled.tofile(file) except UnsupportedOperation: @@ -48,7 +48,7 @@ setup( "chipwhisperer": ["chipwhisperer"], "smartcard": ["pyscard"], "gmp": ["gmpy2"], - "dev": ["mypy", "flake8", "interrogate", "pyinstrument"], + "dev": ["mypy", "flake8", "interrogate", "pyinstrument", "black"], "test": ["nose2", "parameterized", "coverage"], "doc": ["sphinx", "sphinx-autodoc-typehints", "nbsphinx"] } diff --git a/test/ec/perf_formula.py b/test/ec/perf_formula.py index 9e651ae..2cc4513 100755 --- a/test/ec/perf_formula.py +++ b/test/ec/perf_formula.py @@ -9,9 +9,20 @@ from utils import Profiler @click.command() @click.option("-p", "--profiler", type=click.Choice(("py", "c")), default="py") -@click.option("-m", "--mod", type=click.Choice(("python", "gmp")), default="gmp" if has_gmp else "python") +@click.option( + "-m", + "--mod", + type=click.Choice(("python", "gmp")), + default="gmp" if has_gmp else "python", +) @click.option("-o", "--operations", type=click.INT, default=5000) -@click.option("-d", "--directory", type=click.Path(file_okay=False, dir_okay=True), default=None, envvar="DIR") +@click.option( + "-d", + "--directory", + type=click.Path(file_okay=False, dir_okay=True), + default=None, + envvar="DIR", +) def main(profiler, mod, operations, directory): with TemporaryConfig() as cfg: cfg.ec.mod_implementation = mod @@ -19,22 +30,36 @@ def main(profiler, mod, operations, directory): coords = p256.curve.coordinate_model add = coords.formulas["add-2016-rcb"] dbl = coords.formulas["dbl-2016-rcb"] - click.echo(f"Profiling {operations} {p256.curve.prime.bit_length()}-bit doubling formula executions...") + click.echo( + f"Profiling {operations} {p256.curve.prime.bit_length()}-bit doubling formula executions..." + ) one_point = p256.generator - with Profiler(profiler, directory, f"formula_dbl2016rcb_p256_{operations}_{mod}"): + with Profiler( + profiler, directory, f"formula_dbl2016rcb_p256_{operations}_{mod}" + ): for _ in range(operations): one_point = dbl(p256.curve.prime, one_point, **p256.curve.parameters)[0] - click.echo(f"Profiling {operations} {p256.curve.prime.bit_length()}-bit addition formula executions...") + click.echo( + f"Profiling {operations} {p256.curve.prime.bit_length()}-bit addition formula executions..." + ) other_point = p256.generator - with Profiler(profiler, directory, f"formula_add2016rcb_p256_{operations}_{mod}"): + with Profiler( + profiler, directory, f"formula_add2016rcb_p256_{operations}_{mod}" + ): for _ in range(operations): - one_point = add(p256.curve.prime, one_point, other_point, **p256.curve.parameters)[0] + one_point = add( + p256.curve.prime, one_point, other_point, **p256.curve.parameters + )[0] ed25519 = get_params("other", "Ed25519", "extended") ecoords = ed25519.curve.coordinate_model dblg = ecoords.formulas["mdbl-2008-hwcd"] - click.echo(f"Profiling {operations} {ed25519.curve.prime.bit_length()}-bit doubling formula executions (with assumption)...") + click.echo( + f"Profiling {operations} {ed25519.curve.prime.bit_length()}-bit doubling formula executions (with assumption)..." + ) eone_point = ed25519.generator - with Profiler(profiler, directory, f"formula_mdbl2008hwcd_ed25519_{operations}_{mod}"): + with Profiler( + profiler, directory, f"formula_mdbl2008hwcd_ed25519_{operations}_{mod}" + ): for _ in range(operations): dblg(ed25519.curve.prime, eone_point, **ed25519.curve.parameters) diff --git a/test/ec/perf_mod.py b/test/ec/perf_mod.py index 37cf41b..49fe9a7 100755 --- a/test/ec/perf_mod.py +++ b/test/ec/perf_mod.py @@ -8,20 +8,33 @@ from utils import Profiler @click.command() @click.option("-p", "--profiler", type=click.Choice(("py", "c")), default="py") -@click.option("-m", "--mod", type=click.Choice(("python", "gmp")), default="gmp" if has_gmp else "python") +@click.option( + "-m", + "--mod", + type=click.Choice(("python", "gmp")), + default="gmp" if has_gmp else "python", +) @click.option("-o", "--operations", type=click.INT, default=100000) -@click.option("-d", "--directory", type=click.Path(file_okay=False, dir_okay=True), default=None, envvar="DIR") +@click.option( + "-d", + "--directory", + type=click.Path(file_okay=False, dir_okay=True), + default=None, + envvar="DIR", +) def main(profiler, mod, operations, directory): with TemporaryConfig() as cfg: cfg.ec.mod_implementation = mod - n = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff + n = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF a = Mod(0x11111111111111111111111111111111, n) - b = Mod(0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, n) + b = Mod(0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, n) click.echo(f"Profiling {operations} {n.bit_length()}-bit modular inverse...") with Profiler(profiler, directory, f"mod_256b_inverse_{operations}_{mod}"): for _ in range(operations): a.inverse() - click.echo(f"Profiling {operations} {n.bit_length()}-bit modular square root...") + click.echo( + f"Profiling {operations} {n.bit_length()}-bit modular square root..." + ) with Profiler(profiler, directory, f"mod_256b_sqrt_{operations}_{mod}"): for _ in range(operations): a.sqrt() @@ -30,16 +43,20 @@ def main(profiler, mod, operations, directory): with Profiler(profiler, directory, f"mod_256b_multiply_{operations}_{mod}"): for _ in range(operations): c = c * b - click.echo(f"Profiling {operations} {n.bit_length()}-bit constant modular multiply...") + click.echo( + f"Profiling {operations} {n.bit_length()}-bit constant modular multiply..." + ) c = a - with Profiler(profiler, directory, f"mod_256b_constmultiply_{operations}_{mod}"): + with Profiler( + profiler, directory, f"mod_256b_constmultiply_{operations}_{mod}" + ): for _ in range(operations): c = c * 48006 click.echo(f"Profiling {operations} {n.bit_length()}-bit modular square...") c = a with Profiler(profiler, directory, f"mod_256b_square_{operations}_{mod}"): for _ in range(operations): - c = c**2 + c = c ** 2 click.echo(f"Profiling {operations} {n.bit_length()}-bit modular add...") c = a with Profiler(profiler, directory, f"mod_256b_add_{operations}_{mod}"): @@ -50,7 +67,9 @@ def main(profiler, mod, operations, directory): with Profiler(profiler, directory, f"mod_256b_subtract_{operations}_{mod}"): for _ in range(operations): c = c - b - click.echo(f"Profiling {operations} {n.bit_length()}-bit modular quadratic residue checks...") + click.echo( + f"Profiling {operations} {n.bit_length()}-bit modular quadratic residue checks..." + ) with Profiler(profiler, directory, f"mod_256b_isresidue_{operations}_{mod}"): for _ in range(operations): a.is_residue() diff --git a/test/ec/perf_mult.py b/test/ec/perf_mult.py index 2ec82b0..36f004b 100755 --- a/test/ec/perf_mult.py +++ b/test/ec/perf_mult.py @@ -10,9 +10,20 @@ from utils import Profiler @click.command() @click.option("-p", "--profiler", type=click.Choice(("py", "c")), default="py") -@click.option("-m", "--mod", type=click.Choice(("python", "gmp")), default="gmp" if has_gmp else "python") +@click.option( + "-m", + "--mod", + type=click.Choice(("python", "gmp")), + default="gmp" if has_gmp else "python", +) @click.option("-o", "--operations", type=click.INT, default=50) -@click.option("-d", "--directory", type=click.Path(file_okay=False, dir_okay=True), default=None, envvar="DIR") +@click.option( + "-d", + "--directory", + type=click.Path(file_okay=False, dir_okay=True), + default=None, + envvar="DIR", +) def main(profiler, mod, operations, directory): with TemporaryConfig() as cfg: cfg.ec.mod_implementation = mod @@ -21,12 +32,16 @@ def main(profiler, mod, operations, directory): add = coords.formulas["add-2016-rcb"] dbl = coords.formulas["dbl-2016-rcb"] mult = LTRMultiplier(add, dbl) - click.echo(f"Profiling {operations} {p256.curve.prime.bit_length()}-bit scalar multiplication executions...") + click.echo( + f"Profiling {operations} {p256.curve.prime.bit_length()}-bit scalar multiplication executions..." + ) one_point = p256.generator with Profiler(profiler, directory, f"mult_ltr_rcb_p256_{operations}_{mod}"): for _ in range(operations): mult.init(p256, one_point) - one_point = mult.multiply(0x71a55e0c1abb3a0e069419e0f837bc195f1b9545e69fc51e53c4d48d7fea3b1a) + one_point = mult.multiply( + 0x71A55E0C1ABB3A0E069419E0F837BC195F1B9545E69FC51E53C4D48D7FEA3B1A + ) # ed25519 = get_params("other", "Ed25519", "extended") # ecoords = ed25519.curve.coordinate_model # dblg = ecoords.formulas["mdbl-2008-hwcd"] diff --git a/test/ec/test_configuration.py b/test/ec/test_configuration.py index 9c8e361..7603321 100644 --- a/test/ec/test_configuration.py +++ b/test/ec/test_configuration.py @@ -1,14 +1,20 @@ from unittest import TestCase -from pyecsca.ec.configuration import (all_configurations, HashType, RandomMod, Multiplication, - Squaring, Reduction, Inversion) +from pyecsca.ec.configuration import ( + all_configurations, + HashType, + RandomMod, + Multiplication, + Squaring, + Reduction, + Inversion, +) from pyecsca.ec.model import ShortWeierstrassModel from pyecsca.ec.mult import LTRMultiplier from .utils import slow class ConfigurationTests(TestCase): - def base_independents(self): return { "hash_type": HashType.SHA1, @@ -16,7 +22,7 @@ class ConfigurationTests(TestCase): "mult": Multiplication.BASE, "sqr": Squaring.BASE, "red": Reduction.BASE, - "inv": Inversion.GCD + "inv": Inversion.GCD, } @slow @@ -29,15 +35,23 @@ class ConfigurationTests(TestCase): def test_weierstrass_projective(self): model = ShortWeierstrassModel() coords = model.coordinates["projective"] - configs = list(all_configurations(model=model, coords=coords, **self.base_independents())) + configs = list( + all_configurations(model=model, coords=coords, **self.base_independents()) + ) self.assertEqual(len(configs), 1960) def test_mult_class(self): model = ShortWeierstrassModel() coords = model.coordinates["projective"] scalarmult = LTRMultiplier - configs = list(all_configurations(model=model, coords=coords, scalarmult=scalarmult, - **self.base_independents())) + configs = list( + all_configurations( + model=model, + coords=coords, + scalarmult=scalarmult, + **self.base_independents() + ) + ) self.assertEqual(len(configs), 560) def test_one(self): @@ -50,16 +64,37 @@ class ConfigurationTests(TestCase): "scl": None, "always": True, "complete": False, - "short_circuit": True + "short_circuit": True, } - configs = list(all_configurations(model=model, coords=coords, scalarmult=scalarmult, - **self.base_independents())) + configs = list( + all_configurations( + model=model, + coords=coords, + scalarmult=scalarmult, + **self.base_independents() + ) + ) self.assertEqual(len(configs), 1) - scalarmult = LTRMultiplier(coords.formulas["add-1998-cmo"], coords.formulas["dbl-1998-cmo"], - None, True, False, True) - configs = list(all_configurations(model=model, coords=coords, scalarmult=scalarmult, - **self.base_independents())) + scalarmult = LTRMultiplier( + coords.formulas["add-1998-cmo"], + coords.formulas["dbl-1998-cmo"], + None, + True, + False, + True, + ) + configs = list( + all_configurations( + model=model, + coords=coords, + scalarmult=scalarmult, + **self.base_independents() + ) + ) self.assertEqual(len(configs), 1) - configs = list(all_configurations(model=model, scalarmult=scalarmult, - **self.base_independents())) + configs = list( + all_configurations( + model=model, scalarmult=scalarmult, **self.base_independents() + ) + ) self.assertEqual(len(configs), 1) diff --git a/test/ec/test_context.py b/test/ec/test_context.py index 603369d..04dbc7d 100644 --- a/test/ec/test_context.py +++ b/test/ec/test_context.py @@ -1,7 +1,15 @@ from unittest import TestCase -from pyecsca.ec.context import (local, DefaultContext, NullContext, getcontext, - setcontext, resetcontext, Tree, PathContext) +from pyecsca.ec.context import ( + local, + DefaultContext, + NullContext, + getcontext, + setcontext, + resetcontext, + Tree, + PathContext, +) from pyecsca.ec.key_generation import KeygenAction, KeyGeneration from pyecsca.ec.params import get_params from pyecsca.ec.mod import RandomModAction @@ -9,7 +17,6 @@ from pyecsca.ec.mult import LTRMultiplier, ScalarMultiplicationAction class TreeTests(TestCase): - def test_walk_by_key(self): tree = Tree() tree["a"] = Tree() @@ -46,14 +53,16 @@ class TreeTests(TestCase): class ContextTests(TestCase): - def setUp(self): self.secp128r1 = get_params("secg", "secp128r1", "projective") self.base = self.secp128r1.generator self.coords = self.secp128r1.curve.coordinate_model - self.mult = LTRMultiplier(self.coords.formulas["add-1998-cmo"], - self.coords.formulas["dbl-1998-cmo"], self.coords.formulas["z"], - always=True) + self.mult = LTRMultiplier( + self.coords.formulas["add-1998-cmo"], + self.coords.formulas["dbl-1998-cmo"], + self.coords.formulas["z"], + always=True, + ) self.mult.init(self.secp128r1, self.base) def test_null(self): diff --git a/test/ec/test_curve.py b/test/ec/test_curve.py index c358c68..345d3de 100644 --- a/test/ec/test_curve.py +++ b/test/ec/test_curve.py @@ -19,55 +19,89 @@ class CurveTests(TestCase): def test_init(self): with self.assertRaises(ValueError): - EllipticCurve(MontgomeryModel(), self.secp128r1.curve.coordinate_model, 1, - InfinityPoint(self.secp128r1.curve.coordinate_model), parameters={}) + EllipticCurve( + MontgomeryModel(), + self.secp128r1.curve.coordinate_model, + 1, + InfinityPoint(self.secp128r1.curve.coordinate_model), + parameters={}, + ) with self.assertRaises(ValueError): - EllipticCurve(self.secp128r1.curve.model, self.secp128r1.curve.coordinate_model, 15, - InfinityPoint(self.secp128r1.curve.coordinate_model), parameters={"c": 0}) + EllipticCurve( + self.secp128r1.curve.model, + self.secp128r1.curve.coordinate_model, + 15, + InfinityPoint(self.secp128r1.curve.coordinate_model), + parameters={"c": 0}, + ) with self.assertRaises(ValueError): - EllipticCurve(self.secp128r1.curve.model, self.secp128r1.curve.coordinate_model, 15, - InfinityPoint(self.secp128r1.curve.coordinate_model), - parameters={"a": Mod(1, 5), "b": Mod(2, 5)}) + EllipticCurve( + self.secp128r1.curve.model, + self.secp128r1.curve.coordinate_model, + 15, + InfinityPoint(self.secp128r1.curve.coordinate_model), + parameters={"a": Mod(1, 5), "b": Mod(2, 5)}, + ) def test_is_neutral(self): - self.assertTrue(self.secp128r1.curve.is_neutral(InfinityPoint(self.secp128r1.curve.coordinate_model))) + self.assertTrue( + self.secp128r1.curve.is_neutral( + InfinityPoint(self.secp128r1.curve.coordinate_model) + ) + ) def test_is_on_curve(self): self.assertTrue(self.secp128r1.curve.is_on_curve(self.secp128r1.curve.neutral)) - pt = Point(self.secp128r1.curve.coordinate_model, - X=Mod(0x161ff7528b899b2d0c28607ca52c5b86, self.secp128r1.curve.prime), - Y=Mod(0xcf5ac8395bafeb13c02da292dded7a83, self.secp128r1.curve.prime), - Z=Mod(1, self.secp128r1.curve.prime)) + pt = Point( + self.secp128r1.curve.coordinate_model, + X=Mod(0x161FF7528B899B2D0C28607CA52C5B86, self.secp128r1.curve.prime), + Y=Mod(0xCF5AC8395BAFEB13C02DA292DDED7A83, self.secp128r1.curve.prime), + Z=Mod(1, self.secp128r1.curve.prime), + ) self.assertTrue(self.secp128r1.curve.is_on_curve(pt)) self.assertTrue(self.secp128r1.curve.is_on_curve(pt.to_affine())) - other = Point(self.secp128r1.curve.coordinate_model, - X=Mod(0x161ff7528b899b2d0c28607ca52c5b86, self.secp128r1.curve.prime), - Y=Mod(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, self.secp128r1.curve.prime), - Z=Mod(1, self.secp128r1.curve.prime)) + other = Point( + self.secp128r1.curve.coordinate_model, + X=Mod(0x161FF7528B899B2D0C28607CA52C5B86, self.secp128r1.curve.prime), + Y=Mod(0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, self.secp128r1.curve.prime), + Z=Mod(1, self.secp128r1.curve.prime), + ) self.assertFalse(self.secp128r1.curve.is_on_curve(other)) self.assertFalse(self.secp128r1.curve.is_on_curve(self.curve25519.generator)) def test_affine_add(self): - pt = Point(AffineCoordinateModel(self.secp128r1.curve.model), - x=Mod(0xeb916224eda4fb356421773573297c15, self.secp128r1.curve.prime), - y=Mod(0xbcdaf32a2c08fd4271228fef35070848, self.secp128r1.curve.prime)) + pt = Point( + AffineCoordinateModel(self.secp128r1.curve.model), + x=Mod(0xEB916224EDA4FB356421773573297C15, self.secp128r1.curve.prime), + y=Mod(0xBCDAF32A2C08FD4271228FEF35070848, self.secp128r1.curve.prime), + ) self.assertIsNotNone(self.secp128r1.curve.affine_add(self.affine_base, pt)) added = self.secp128r1.curve.affine_add(self.affine_base, self.affine_base) doubled = self.secp128r1.curve.affine_double(self.affine_base) self.assertEqual(added, doubled) - self.assertEqual(self.secp128r1.curve.affine_add(self.secp128r1.curve.neutral, pt), pt) - self.assertEqual(self.secp128r1.curve.affine_add(pt, self.secp128r1.curve.neutral), pt) + self.assertEqual( + self.secp128r1.curve.affine_add(self.secp128r1.curve.neutral, pt), pt + ) + self.assertEqual( + self.secp128r1.curve.affine_add(pt, self.secp128r1.curve.neutral), pt + ) def test_affine_double(self): self.assertIsNotNone(self.secp128r1.curve.affine_double(self.affine_base)) - self.assertEqual(self.secp128r1.curve.affine_double(self.secp128r1.curve.neutral), self.secp128r1.curve.neutral) + self.assertEqual( + self.secp128r1.curve.affine_double(self.secp128r1.curve.neutral), + self.secp128r1.curve.neutral, + ) def test_affine_negate(self): self.assertIsNotNone(self.secp128r1.curve.affine_negate(self.affine_base)) - self.assertEqual(self.secp128r1.curve.affine_negate(self.secp128r1.curve.neutral), self.secp128r1.curve.neutral) + self.assertEqual( + self.secp128r1.curve.affine_negate(self.secp128r1.curve.neutral), + self.secp128r1.curve.neutral, + ) with self.assertRaises(ValueError): self.secp128r1.curve.affine_negate(self.base) with self.assertRaises(ValueError): @@ -79,8 +113,13 @@ class CurveTests(TestCase): expected = self.secp128r1.curve.affine_double(expected) expected = self.secp128r1.curve.affine_add(expected, self.affine_base) expected = self.secp128r1.curve.affine_double(expected) - self.assertEqual(self.secp128r1.curve.affine_multiply(self.affine_base, 10), expected) - self.assertEqual(self.secp128r1.curve.affine_multiply(self.secp128r1.curve.neutral, 10), self.secp128r1.curve.neutral) + self.assertEqual( + self.secp128r1.curve.affine_multiply(self.affine_base, 10), expected + ) + self.assertEqual( + self.secp128r1.curve.affine_multiply(self.secp128r1.curve.neutral, 10), + self.secp128r1.curve.neutral, + ) with self.assertRaises(ValueError): self.secp128r1.curve.affine_multiply(self.base, 10) with self.assertRaises(ValueError): @@ -129,7 +168,9 @@ class CurveTests(TestCase): with self.assertRaises(ValueError): affine_curve.decode_point(unhexlify("03161ff7528b899b2d0c28607ca52c5b")) with self.assertRaises(ValueError): - affine_curve.decode_point(unhexlify("04161ff7528b899b2d0c28607ca52c5b2c5b2c5b2c5b")) + affine_curve.decode_point( + unhexlify("04161ff7528b899b2d0c28607ca52c5b2c5b2c5b2c5b") + ) with self.assertRaises(ValueError): affine_curve.decode_point(unhexlify("7a161ff7528b899b2d0c28607ca52c5b86")) with self.assertRaises(ValueError): diff --git a/test/ec/test_formula.py b/test/ec/test_formula.py index ec585ad..ffae4c4 100644 --- a/test/ec/test_formula.py +++ b/test/ec/test_formula.py @@ -10,20 +10,25 @@ from pyecsca.ec.point import Point class FormulaTests(TestCase): - def setUp(self): self.secp128r1 = get_params("secg", "secp128r1", "projective") self.add = self.secp128r1.curve.coordinate_model.formulas["add-2007-bl"] self.dbl = self.secp128r1.curve.coordinate_model.formulas["dbl-2007-bl"] self.mdbl = self.secp128r1.curve.coordinate_model.formulas["mdbl-2007-bl"] self.jac_secp128r1 = get_params("secg", "secp128r1", "jacobian") - self.jac_dbl = self.jac_secp128r1.curve.coordinate_model.formulas["dbl-1998-hnm"] + self.jac_dbl = self.jac_secp128r1.curve.coordinate_model.formulas[ + "dbl-1998-hnm" + ] def test_wrong_call(self): with self.assertRaises(ValueError): self.add(self.secp128r1.curve.prime) with self.assertRaises(ValueError): - self.add(self.secp128r1.curve.prime, self.secp128r1.generator.to_affine(), self.secp128r1.generator.to_affine()) + self.add( + self.secp128r1.curve.prime, + self.secp128r1.generator.to_affine(), + self.secp128r1.generator.to_affine(), + ) def test_indices(self): self.assertEqual(self.add.input_index, 1) @@ -47,33 +52,56 @@ class FormulaTests(TestCase): self.assertEqual(self.add.num_addsubs, 10) def test_assumptions(self): - res = self.mdbl(self.secp128r1.curve.prime, self.secp128r1.generator, **self.secp128r1.curve.parameters) + res = self.mdbl( + self.secp128r1.curve.prime, + self.secp128r1.generator, + **self.secp128r1.curve.parameters + ) self.assertIsNotNone(res) - coords = {name: value * 5 for name, value in self.secp128r1.generator.coords.items()} + coords = { + name: value * 5 for name, value in self.secp128r1.generator.coords.items() + } other = Point(self.secp128r1.generator.coordinate_model, **coords) with self.assertRaises(UnsatisfiedAssumptionError): - self.mdbl(self.secp128r1.curve.prime, other, **self.secp128r1.curve.parameters) + self.mdbl( + self.secp128r1.curve.prime, other, **self.secp128r1.curve.parameters + ) with TemporaryConfig() as cfg: cfg.ec.unsatisfied_formula_assumption_action = "ignore" - pt = self.mdbl(self.secp128r1.curve.prime, other, **self.secp128r1.curve.parameters) + pt = self.mdbl( + self.secp128r1.curve.prime, other, **self.secp128r1.curve.parameters + ) self.assertIsNotNone(pt) def test_parameters(self): - res = self.jac_dbl(self.secp128r1.curve.prime, self.jac_secp128r1.generator, **self.jac_secp128r1.curve.parameters) + res = self.jac_dbl( + self.secp128r1.curve.prime, + self.jac_secp128r1.generator, + **self.jac_secp128r1.curve.parameters + ) self.assertIsNotNone(res) def test_symbolic(self): p = self.secp128r1.curve.prime k = FF(p) coords = self.secp128r1.curve.coordinate_model - sympy_params = {key: SymbolicMod(k(int(value)), p) for key, value in self.secp128r1.curve.parameters.items()} - symbolic_point = Point(coords, **{key: SymbolicMod(symbols(key), p) for key in coords.variables}) + sympy_params = { + key: SymbolicMod(k(int(value)), p) + for key, value in self.secp128r1.curve.parameters.items() + } + symbolic_point = Point( + coords, **{key: SymbolicMod(symbols(key), p) for key in coords.variables} + ) symbolic_double = self.dbl(p, symbolic_point, **sympy_params)[0] - generator_double = self.dbl(p, self.secp128r1.generator, **self.secp128r1.curve.parameters)[0] + generator_double = self.dbl( + p, self.secp128r1.generator, **self.secp128r1.curve.parameters + )[0] for outer_var in coords.variables: symbolic_val = getattr(symbolic_double, outer_var).x generator_val = getattr(generator_double, outer_var).x for inner_var in coords.variables: - symbolic_val = symbolic_val.subs(inner_var, k(getattr(self.secp128r1.generator, inner_var).x)) + symbolic_val = symbolic_val.subs( + inner_var, k(getattr(self.secp128r1.generator, inner_var).x) + ) self.assertEqual(Mod(int(symbolic_val), p), Mod(generator_val, p)) diff --git a/test/ec/test_key_agreement.py b/test/ec/test_key_agreement.py index cbdb1c8..240c174 100644 --- a/test/ec/test_key_agreement.py +++ b/test/ec/test_key_agreement.py @@ -3,33 +3,40 @@ from unittest import TestCase from parameterized import parameterized from pyecsca.ec.params import get_params -from pyecsca.ec.key_agreement import (ECDH_NONE, ECDH_SHA1, ECDH_SHA224, ECDH_SHA256, ECDH_SHA384, - ECDH_SHA512) +from pyecsca.ec.key_agreement import ( + ECDH_NONE, + ECDH_SHA1, + ECDH_SHA224, + ECDH_SHA256, + ECDH_SHA384, + ECDH_SHA512, +) from pyecsca.ec.mod import Mod from pyecsca.ec.mult import LTRMultiplier class KeyAgreementTests(TestCase): - def setUp(self): self.secp128r1 = get_params("secg", "secp128r1", "projective") self.add = self.secp128r1.curve.coordinate_model.formulas["add-2007-bl"] self.dbl = self.secp128r1.curve.coordinate_model.formulas["dbl-2007-bl"] self.mult = LTRMultiplier(self.add, self.dbl) - self.priv_a = Mod(0xdeadbeef, self.secp128r1.order) + self.priv_a = Mod(0xDEADBEEF, self.secp128r1.order) self.mult.init(self.secp128r1, self.secp128r1.generator) self.pub_a = self.mult.multiply(int(self.priv_a)) - self.priv_b = Mod(0xcafebabe, self.secp128r1.order) + self.priv_b = Mod(0xCAFEBABE, self.secp128r1.order) self.pub_b = self.mult.multiply(int(self.priv_b)) - @parameterized.expand([ - ("NONE", ECDH_NONE), - ("SHA1", ECDH_SHA1), - ("SHA224", ECDH_SHA224), - ("SHA256", ECDH_SHA256), - ("SHA384", ECDH_SHA384), - ("SHA512", ECDH_SHA512) - ]) + @parameterized.expand( + [ + ("NONE", ECDH_NONE), + ("SHA1", ECDH_SHA1), + ("SHA224", ECDH_SHA224), + ("SHA256", ECDH_SHA256), + ("SHA384", ECDH_SHA384), + ("SHA512", ECDH_SHA512), + ] + ) def test_all(self, name, algo): result_ab = algo(self.mult, self.secp128r1, self.pub_a, self.priv_b).perform() result_ba = algo(self.mult, self.secp128r1, self.pub_b, self.priv_a).perform() diff --git a/test/ec/test_key_generation.py b/test/ec/test_key_generation.py index f0d926c..7eb26f0 100644 --- a/test/ec/test_key_generation.py +++ b/test/ec/test_key_generation.py @@ -6,7 +6,6 @@ from pyecsca.ec.mult import LTRMultiplier class KeyGenerationTests(TestCase): - def setUp(self): self.secp128r1 = get_params("secg", "secp128r1", "projective") self.add = self.secp128r1.curve.coordinate_model.formulas["add-2007-bl"] diff --git a/test/ec/test_mod.py b/test/ec/test_mod.py index a21ef06..2c45447 100644 --- a/test/ec/test_mod.py +++ b/test/ec/test_mod.py @@ -1,13 +1,27 @@ from sympy import FF, symbols from unittest import TestCase -from pyecsca.ec.mod import Mod, gcd, extgcd, Undefined, miller_rabin, has_gmp, RawMod, SymbolicMod, jacobi -from pyecsca.ec.error import NonInvertibleError, NonResidueError, NonInvertibleWarning, NonResidueWarning +from pyecsca.ec.mod import ( + Mod, + gcd, + extgcd, + Undefined, + miller_rabin, + has_gmp, + RawMod, + SymbolicMod, + jacobi, +) +from pyecsca.ec.error import ( + NonInvertibleError, + NonResidueError, + NonInvertibleWarning, + NonResidueWarning, +) from pyecsca.misc.cfg import getconfig, TemporaryConfig class ModTests(TestCase): - def test_gcd(self): self.assertEqual(gcd(15, 20), 5) self.assertEqual(extgcd(15, 0), (1, 0, 15)) @@ -15,20 +29,33 @@ class ModTests(TestCase): def test_jacobi(self): self.assertEqual(jacobi(5, 1153486465415345646578465454655646543248656451), 1) - self.assertEqual(jacobi(564786456646845, 46874698564153465453246546545456849797895547657), -1) - self.assertEqual(jacobi(564786456646845, 46874698564153465453246546545456849797895), 0) + self.assertEqual( + jacobi(564786456646845, 46874698564153465453246546545456849797895547657), -1 + ) + self.assertEqual( + jacobi(564786456646845, 46874698564153465453246546545456849797895), 0 + ) def test_miller_rabin(self): self.assertTrue(miller_rabin(2)) self.assertTrue(miller_rabin(3)) self.assertTrue(miller_rabin(5)) self.assertFalse(miller_rabin(8)) - self.assertTrue(miller_rabin(0xe807561107ccf8fa82af74fd492543a918ca2e9c13750233a9)) - self.assertFalse(miller_rabin(0x6f6889deb08da211927370810f026eb4c17b17755f72ea005)) + self.assertTrue( + miller_rabin(0xE807561107CCF8FA82AF74FD492543A918CA2E9C13750233A9) + ) + self.assertFalse( + miller_rabin(0x6F6889DEB08DA211927370810F026EB4C17B17755F72EA005) + ) def test_inverse(self): - p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff - self.assertEqual(Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).inverse(), Mod(0x1cb2e5274bba085c4ca88eede75ae77949e7a410c80368376e97ab22eb590f9d, p)) + p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF + self.assertEqual( + Mod( + 0x702BDAFD3C1C837B23A1CB196ED7F9FADB333C5CFE4A462BE32ADCD67BFB6AC1, p + ).inverse(), + Mod(0x1CB2E5274BBA085C4CA88EEDE75AE77949E7A410C80368376E97AB22EB590F9D, p), + ) with self.assertRaises(NonInvertibleError): Mod(0, p).inverse() with self.assertRaises(NonInvertibleError): @@ -50,22 +77,42 @@ class ModTests(TestCase): self.assertTrue(Mod(1, 2).is_residue()) def test_sqrt(self): - p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff - self.assertIn(Mod(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc, p).sqrt(), (0x9add512515b70d9ec471151c1dec46625cd18b37bde7ca7fb2c8b31d7033599d, 0x6522aed9ea48f2623b8eeae3e213b99da32e74c9421835804d374ce28fcca662)) + p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF + self.assertIn( + Mod( + 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC, p + ).sqrt(), + ( + 0x9ADD512515B70D9EC471151C1DEC46625CD18B37BDE7CA7FB2C8B31D7033599D, + 0x6522AED9EA48F2623B8EEAE3E213B99DA32E74C9421835804D374CE28FCCA662, + ), + ) with self.assertRaises(NonResidueError): - Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt() + Mod( + 0x702BDAFD3C1C837B23A1CB196ED7F9FADB333C5CFE4A462BE32ADCD67BFB6AC1, p + ).sqrt() getconfig().ec.non_residue_action = "warning" with self.assertRaises(NonResidueWarning): - Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt() + Mod( + 0x702BDAFD3C1C837B23A1CB196ED7F9FADB333C5CFE4A462BE32ADCD67BFB6AC1, p + ).sqrt() getconfig().ec.non_residue_action = "ignore" - Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt() + Mod( + 0x702BDAFD3C1C837B23A1CB196ED7F9FADB333C5CFE4A462BE32ADCD67BFB6AC1, p + ).sqrt() with TemporaryConfig() as cfg: cfg.ec.non_residue_action = "warning" with self.assertRaises(NonResidueWarning): - Mod(0x702bdafd3c1c837b23a1cb196ed7f9fadb333c5cfe4a462be32adcd67bfb6ac1, p).sqrt() + Mod( + 0x702BDAFD3C1C837B23A1CB196ED7F9FADB333C5CFE4A462BE32ADCD67BFB6AC1, + p, + ).sqrt() self.assertEqual(Mod(0, p).sqrt(), Mod(0, p)) - q = 0x75d44fee9a71841ae8403c0c251fbad - self.assertIn(Mod(0x591e0db18cf1bd81a11b2985a821eb3, q).sqrt(), (0x113b41a1a2b73f636e73be3f9a3716e, 0x64990e4cf7ba44b779cc7dcc8ae8a3f)) + q = 0x75D44FEE9A71841AE8403C0C251FBAD + self.assertIn( + Mod(0x591E0DB18CF1BD81A11B2985A821EB3, q).sqrt(), + (0x113B41A1A2B73F636E73BE3F9A3716E, 0x64990E4CF7BA44B779CC7DCC8AE8A3F), + ) getconfig().ec.non_residue_action = "error" def test_eq(self): @@ -77,9 +124,9 @@ class ModTests(TestCase): def test_pow(self): a = Mod(5, 7) - self.assertEqual(a**(-1), a.inverse()) - self.assertEqual(a**0, Mod(1, 7)) - self.assertEqual(a**(-2), a.inverse()**2) + self.assertEqual(a ** (-1), a.inverse()) + self.assertEqual(a ** 0, Mod(1, 7)) + self.assertEqual(a ** (-2), a.inverse() ** 2) def test_wrong_mod(self): a = Mod(5, 7) @@ -91,7 +138,7 @@ class ModTests(TestCase): a = Mod(5, 7) c = Mod(4, 11) with self.assertRaises(TypeError): - a**c + a ** c def test_other(self): a = Mod(5, 7) @@ -116,7 +163,15 @@ class ModTests(TestCase): def test_undefined(self): u = Undefined() for k, meth in u.__class__.__dict__.items(): - if k in ("__module__", "__new__", "__init__", "__doc__", "__hash__", "__abstractmethods__", "_abc_impl"): + if k in ( + "__module__", + "__new__", + "__init__", + "__doc__", + "__hash__", + "__abstractmethods__", + "_abc_impl", + ): continue args = [5 for _ in range(meth.__code__.co_argcount - 1)] if k == "__repr__": diff --git a/test/ec/test_model.py b/test/ec/test_model.py index b9d4383..d1c03c3 100644 --- a/test/ec/test_model.py +++ b/test/ec/test_model.py @@ -1,11 +1,14 @@ from unittest import TestCase -from pyecsca.ec.model import (ShortWeierstrassModel, MontgomeryModel, EdwardsModel, - TwistedEdwardsModel) +from pyecsca.ec.model import ( + ShortWeierstrassModel, + MontgomeryModel, + EdwardsModel, + TwistedEdwardsModel, +) class CurveModelTests(TestCase): - def test_load(self): self.assertGreater(len(ShortWeierstrassModel().coordinates), 0) self.assertGreater(len(MontgomeryModel().coordinates), 0) diff --git a/test/ec/test_mult.py b/test/ec/test_mult.py index 19db2b2..5200520 100644 --- a/test/ec/test_mult.py +++ b/test/ec/test_mult.py @@ -3,16 +3,21 @@ from unittest import TestCase from parameterized import parameterized from pyecsca.ec.params import get_params -from pyecsca.ec.mult import (LTRMultiplier, RTLMultiplier, LadderMultiplier, BinaryNAFMultiplier, - WindowNAFMultiplier, SimpleLadderMultiplier, - DifferentialLadderMultiplier, - CoronMultiplier) +from pyecsca.ec.mult import ( + LTRMultiplier, + RTLMultiplier, + LadderMultiplier, + BinaryNAFMultiplier, + WindowNAFMultiplier, + SimpleLadderMultiplier, + DifferentialLadderMultiplier, + CoronMultiplier, +) from pyecsca.ec.point import InfinityPoint from .utils import cartesian class ScalarMultiplierTests(TestCase): - def setUp(self): self.secp128r1 = get_params("secg", "secp128r1", "projective") self.base = self.secp128r1.generator @@ -31,9 +36,13 @@ class ScalarMultiplierTests(TestCase): else: assert one.equals(other) - def do_basic_test(self, mult_class, params, base, add, dbl, scale, neg=None, **kwargs): - mult = mult_class(*self.get_formulas(params.curve.coordinate_model, add, dbl, neg, scale), - **kwargs) + def do_basic_test( + self, mult_class, params, base, add, dbl, scale, neg=None, **kwargs + ): + mult = mult_class( + *self.get_formulas(params.curve.coordinate_model, add, dbl, neg, scale), + **kwargs + ) mult.init(params, base) res = mult.multiply(314) other = mult.multiply(157) @@ -44,71 +53,120 @@ class ScalarMultiplierTests(TestCase): self.assertEqual(InfinityPoint(params.curve.coordinate_model), mult.multiply(0)) return res - @parameterized.expand([ - ("scaled", "add-1998-cmo", "dbl-1998-cmo", "z"), - ("complete", "add-2016-rcb", "dbl-2016-rcb", None), - ("none", "add-1998-cmo", "dbl-1998-cmo", None) - ]) + @parameterized.expand( + [ + ("scaled", "add-1998-cmo", "dbl-1998-cmo", "z"), + ("complete", "add-2016-rcb", "dbl-2016-rcb", None), + ("none", "add-1998-cmo", "dbl-1998-cmo", None), + ] + ) def test_rtl(self, name, add, dbl, scale): self.do_basic_test(RTLMultiplier, self.secp128r1, self.base, add, dbl, scale) - @parameterized.expand([ - ("scaled", "add-1998-cmo", "dbl-1998-cmo", "z"), - ("complete", "add-2016-rcb", "dbl-2016-rcb", None), - ("none", "add-1998-cmo", "dbl-1998-cmo", None) - ]) + @parameterized.expand( + [ + ("scaled", "add-1998-cmo", "dbl-1998-cmo", "z"), + ("complete", "add-2016-rcb", "dbl-2016-rcb", None), + ("none", "add-1998-cmo", "dbl-1998-cmo", None), + ] + ) def test_ltr(self, name, add, dbl, scale): - a = self.do_basic_test(LTRMultiplier, self.secp128r1, self.base, add, dbl, scale) - b = self.do_basic_test(LTRMultiplier, self.secp128r1, self.base, add, dbl, scale, - always=True) - c = self.do_basic_test(LTRMultiplier, self.secp128r1, self.base, add, dbl, scale, - complete=False) - d = self.do_basic_test(LTRMultiplier, self.secp128r1, self.base, add, dbl, scale, - always=True, - complete=False) + a = self.do_basic_test( + LTRMultiplier, self.secp128r1, self.base, add, dbl, scale + ) + b = self.do_basic_test( + LTRMultiplier, self.secp128r1, self.base, add, dbl, scale, always=True + ) + c = self.do_basic_test( + LTRMultiplier, self.secp128r1, self.base, add, dbl, scale, complete=False + ) + d = self.do_basic_test( + LTRMultiplier, + self.secp128r1, + self.base, + add, + dbl, + scale, + always=True, + complete=False, + ) self.assertPointEquality(a, b, scale) self.assertPointEquality(b, c, scale) self.assertPointEquality(c, d, scale) - @parameterized.expand([ - ("scaled", "add-1998-cmo", "dbl-1998-cmo", "z"), - ("complete", "add-2016-rcb", "dbl-2016-rcb", None), - ("none", "add-1998-cmo", "dbl-1998-cmo", None) - ]) + @parameterized.expand( + [ + ("scaled", "add-1998-cmo", "dbl-1998-cmo", "z"), + ("complete", "add-2016-rcb", "dbl-2016-rcb", None), + ("none", "add-1998-cmo", "dbl-1998-cmo", None), + ] + ) def test_coron(self, name, add, dbl, scale): self.do_basic_test(CoronMultiplier, self.secp128r1, self.base, add, dbl, scale) def test_ladder(self): - a = self.do_basic_test(LadderMultiplier, self.curve25519, self.base25519, "ladd-1987-m", - "dbl-1987-m", "scale") - b = self.do_basic_test(LadderMultiplier, self.curve25519, self.base25519, "ladd-1987-m", - "dbl-1987-m", "scale", complete=False) + a = self.do_basic_test( + LadderMultiplier, + self.curve25519, + self.base25519, + "ladd-1987-m", + "dbl-1987-m", + "scale", + ) + b = self.do_basic_test( + LadderMultiplier, + self.curve25519, + self.base25519, + "ladd-1987-m", + "dbl-1987-m", + "scale", + complete=False, + ) self.assertPointEquality(a, b, True) - @parameterized.expand([ - ("scaled", "add-1998-cmo", "dbl-1998-cmo", "z"), - ("complete", "add-2016-rcb", "dbl-2016-rcb", None), - ("none", "add-1998-cmo", "dbl-1998-cmo", None) - ]) + @parameterized.expand( + [ + ("scaled", "add-1998-cmo", "dbl-1998-cmo", "z"), + ("complete", "add-2016-rcb", "dbl-2016-rcb", None), + ("none", "add-1998-cmo", "dbl-1998-cmo", None), + ] + ) def test_simple_ladder(self, name, add, dbl, scale): - self.do_basic_test(SimpleLadderMultiplier, self.secp128r1, self.base, add, dbl, scale) + self.do_basic_test( + SimpleLadderMultiplier, self.secp128r1, self.base, add, dbl, scale + ) - @parameterized.expand([ - ("15", 15, True), - ("15", 15, False), - ("2355498743", 2355498743, True), - ("2355498743", 2355498743, False), - ("325385790209017329644351321912443757746", 325385790209017329644351321912443757746, True), - ("325385790209017329644351321912443757746", 325385790209017329644351321912443757746, False) - ]) + @parameterized.expand( + [ + ("15", 15, True), + ("15", 15, False), + ("2355498743", 2355498743, True), + ("2355498743", 2355498743, False), + ( + "325385790209017329644351321912443757746", + 325385790209017329644351321912443757746, + True, + ), + ( + "325385790209017329644351321912443757746", + 325385790209017329644351321912443757746, + False, + ), + ] + ) def test_ladder_differential(self, name, num, complete): - ladder = LadderMultiplier(self.coords25519.formulas["ladd-1987-m"], - self.coords25519.formulas["dbl-1987-m"], - self.coords25519.formulas["scale"], complete=complete) - differential = DifferentialLadderMultiplier(self.coords25519.formulas["dadd-1987-m"], - self.coords25519.formulas["dbl-1987-m"], - self.coords25519.formulas["scale"], - complete=complete) + ladder = LadderMultiplier( + self.coords25519.formulas["ladd-1987-m"], + self.coords25519.formulas["dbl-1987-m"], + self.coords25519.formulas["scale"], + complete=complete, + ) + differential = DifferentialLadderMultiplier( + self.coords25519.formulas["dadd-1987-m"], + self.coords25519.formulas["dbl-1987-m"], + self.coords25519.formulas["scale"], + complete=complete, + ) ladder.init(self.curve25519, self.base25519) res_ladder = ladder.multiply(num) differential.init(self.curve25519, self.base25519) @@ -116,22 +174,28 @@ class ScalarMultiplierTests(TestCase): self.assertEqual(res_ladder, res_differential) self.assertEqual(InfinityPoint(self.coords25519), differential.multiply(0)) - @parameterized.expand([ - ("scaled", "add-1998-cmo", "dbl-1998-cmo", "neg", "z"), - ("complete", "add-2016-rcb", "dbl-2016-rcb", "neg", None), - ("none", "add-1998-cmo", "dbl-1998-cmo", "neg", None) - ]) + @parameterized.expand( + [ + ("scaled", "add-1998-cmo", "dbl-1998-cmo", "neg", "z"), + ("complete", "add-2016-rcb", "dbl-2016-rcb", "neg", None), + ("none", "add-1998-cmo", "dbl-1998-cmo", "neg", None), + ] + ) def test_binary_naf(self, name, add, dbl, neg, scale): - self.do_basic_test(BinaryNAFMultiplier, self.secp128r1, self.base, add, dbl, scale, neg) + self.do_basic_test( + BinaryNAFMultiplier, self.secp128r1, self.base, add, dbl, scale, neg + ) - @parameterized.expand([ - ("scaled3", "add-1998-cmo", "dbl-1998-cmo", "neg", 3, "z"), - ("none3", "add-1998-cmo", "dbl-1998-cmo", "neg", 3, None), - ("complete3", "add-2016-rcb", "dbl-2016-rcb", "neg", 3, None), - ("scaled5", "add-1998-cmo", "dbl-1998-cmo", "neg", 5, "z"), - ("none5", "add-1998-cmo", "dbl-1998-cmo", "neg", 5, None), - ("complete5", "add-2016-rcb", "dbl-2016-rcb", "neg", 5, None), - ]) + @parameterized.expand( + [ + ("scaled3", "add-1998-cmo", "dbl-1998-cmo", "neg", 3, "z"), + ("none3", "add-1998-cmo", "dbl-1998-cmo", "neg", 3, None), + ("complete3", "add-2016-rcb", "dbl-2016-rcb", "neg", 3, None), + ("scaled5", "add-1998-cmo", "dbl-1998-cmo", "neg", 5, "z"), + ("none5", "add-1998-cmo", "dbl-1998-cmo", "neg", 5, None), + ("complete5", "add-2016-rcb", "dbl-2016-rcb", "neg", 5, None), + ] + ) def test_window_naf(self, name, add, dbl, neg, width, scale): formulas = self.get_formulas(self.coords, add, dbl, neg, scale) mult = WindowNAFMultiplier(*formulas[:3], width, *formulas[3:]) @@ -144,41 +208,59 @@ class ScalarMultiplierTests(TestCase): mult.init(self.secp128r1, self.base) self.assertEqual(InfinityPoint(self.coords), mult.multiply(0)) - mult = WindowNAFMultiplier(*formulas[:3], width, *formulas[3:], - precompute_negation=True) + mult = WindowNAFMultiplier( + *formulas[:3], width, *formulas[3:], precompute_negation=True + ) mult.init(self.secp128r1, self.base) res_precompute = mult.multiply(157 * 789) self.assertPointEquality(res_precompute, res, scale) - @parameterized.expand(cartesian([ - ("10", 10), - ("2355498743", 2355498743), - ("325385790209017329644351321912443757746", 325385790209017329644351321912443757746) - ], [ - ("add-1998-cmo", "dbl-1998-cmo"), - ("add-2016-rcb", "dbl-2016-rcb") - ])) + @parameterized.expand( + cartesian( + [ + ("10", 10), + ("2355498743", 2355498743), + ( + "325385790209017329644351321912443757746", + 325385790209017329644351321912443757746, + ), + ], + [("add-1998-cmo", "dbl-1998-cmo"), ("add-2016-rcb", "dbl-2016-rcb")], + ) + ) def test_basic_multipliers(self, name, num, add, dbl): - ltr = LTRMultiplier(self.coords.formulas[add], - self.coords.formulas[dbl], self.coords.formulas["z"]) + ltr = LTRMultiplier( + self.coords.formulas[add], + self.coords.formulas[dbl], + self.coords.formulas["z"], + ) with self.assertRaises(ValueError): ltr.multiply(1) ltr.init(self.secp128r1, self.base) res_ltr = ltr.multiply(num) - rtl = RTLMultiplier(self.coords.formulas[add], - self.coords.formulas["dbl-1998-cmo"], self.coords.formulas["z"]) + rtl = RTLMultiplier( + self.coords.formulas[add], + self.coords.formulas["dbl-1998-cmo"], + self.coords.formulas["z"], + ) with self.assertRaises(ValueError): rtl.multiply(1) rtl.init(self.secp128r1, self.base) res_rtl = rtl.multiply(num) self.assertEqual(res_ltr, res_rtl) - ltr_always = LTRMultiplier(self.coords.formulas[add], - self.coords.formulas[dbl], self.coords.formulas["z"], - always=True) - rtl_always = RTLMultiplier(self.coords.formulas[add], - self.coords.formulas[dbl], self.coords.formulas["z"], - always=True) + ltr_always = LTRMultiplier( + self.coords.formulas[add], + self.coords.formulas[dbl], + self.coords.formulas["z"], + always=True, + ) + rtl_always = RTLMultiplier( + self.coords.formulas[add], + self.coords.formulas[dbl], + self.coords.formulas["z"], + always=True, + ) ltr_always.init(self.secp128r1, self.base) rtl_always.init(self.secp128r1, self.base) res_ltr_always = ltr_always.multiply(num) @@ -186,36 +268,47 @@ class ScalarMultiplierTests(TestCase): self.assertEqual(res_ltr, res_ltr_always) self.assertEqual(res_rtl, res_rtl_always) - bnaf = BinaryNAFMultiplier(self.coords.formulas[add], - self.coords.formulas[dbl], - self.coords.formulas["neg"], self.coords.formulas["z"]) + bnaf = BinaryNAFMultiplier( + self.coords.formulas[add], + self.coords.formulas[dbl], + self.coords.formulas["neg"], + self.coords.formulas["z"], + ) with self.assertRaises(ValueError): bnaf.multiply(1) bnaf.init(self.secp128r1, self.base) res_bnaf = bnaf.multiply(num) self.assertEqual(res_bnaf, res_ltr) - wnaf = WindowNAFMultiplier(self.coords.formulas[add], - self.coords.formulas[dbl], - self.coords.formulas["neg"], 3, self.coords.formulas["z"]) + wnaf = WindowNAFMultiplier( + self.coords.formulas[add], + self.coords.formulas[dbl], + self.coords.formulas["neg"], + 3, + self.coords.formulas["z"], + ) with self.assertRaises(ValueError): wnaf.multiply(1) wnaf.init(self.secp128r1, self.base) res_wnaf = wnaf.multiply(num) self.assertEqual(res_wnaf, res_ltr) - ladder = SimpleLadderMultiplier(self.coords.formulas[add], - self.coords.formulas[dbl], - self.coords.formulas["z"]) + ladder = SimpleLadderMultiplier( + self.coords.formulas[add], + self.coords.formulas[dbl], + self.coords.formulas["z"], + ) with self.assertRaises(ValueError): ladder.multiply(1) ladder.init(self.secp128r1, self.base) res_ladder = ladder.multiply(num) self.assertEqual(res_ladder, res_ltr) - coron = CoronMultiplier(self.coords.formulas[add], - self.coords.formulas[dbl], - self.coords.formulas["z"]) + coron = CoronMultiplier( + self.coords.formulas[add], + self.coords.formulas[dbl], + self.coords.formulas["z"], + ) with self.assertRaises(ValueError): coron.multiply(1) coron.init(self.secp128r1, self.base) @@ -223,12 +316,17 @@ class ScalarMultiplierTests(TestCase): self.assertEqual(res_coron, res_ltr) def test_init_fail(self): - mult = DifferentialLadderMultiplier(self.coords25519.formulas["dadd-1987-m"], - self.coords25519.formulas["dbl-1987-m"], - self.coords25519.formulas["scale"]) + mult = DifferentialLadderMultiplier( + self.coords25519.formulas["dadd-1987-m"], + self.coords25519.formulas["dbl-1987-m"], + self.coords25519.formulas["scale"], + ) with self.assertRaises(ValueError): mult.init(self.secp128r1, self.base) with self.assertRaises(ValueError): - LadderMultiplier(self.coords25519.formulas["ladd-1987-m"], - scl=self.coords25519.formulas["scale"], complete=False) + LadderMultiplier( + self.coords25519.formulas["ladd-1987-m"], + scl=self.coords25519.formulas["scale"], + complete=False, + ) diff --git a/test/ec/test_naf.py b/test/ec/test_naf.py index c87c03f..bdc176a 100644 --- a/test/ec/test_naf.py +++ b/test/ec/test_naf.py @@ -4,7 +4,6 @@ from pyecsca.ec.naf import naf, wnaf class NafTests(TestCase): - def test_nafs(self): i = 0b1100110101001101011011 self.assertListEqual(naf(i), wnaf(i, 2)) diff --git a/test/ec/test_op.py b/test/ec/test_op.py index 019e0e8..c09914f 100644 --- a/test/ec/test_op.py +++ b/test/ec/test_op.py @@ -10,28 +10,31 @@ from pyecsca.ec.op import CodeOp, OpType class OpTests(TestCase): - - @parameterized.expand([ - ("add", "x = a+b", "x = a+b", OpType.Add), - ("sub", "x = a-b", "x = a-b", OpType.Sub), - ("mul", "y = a*b", "y = a*b", OpType.Mult), - ("div", "z = a/b", "z = a/b", OpType.Div), - ("inv", "z = 1/b", "z = 1/b", OpType.Inv), - ("pow", "b = a**d", "b = a^d", OpType.Pow), - ("sqr", "b = a**2", "b = a^2", OpType.Sqr), - ("id1", "b = 7", "b = 7", OpType.Id), - ("id2", "b = a", "b = a", OpType.Id), - ]) + @parameterized.expand( + [ + ("add", "x = a+b", "x = a+b", OpType.Add), + ("sub", "x = a-b", "x = a-b", OpType.Sub), + ("mul", "y = a*b", "y = a*b", OpType.Mult), + ("div", "z = a/b", "z = a/b", OpType.Div), + ("inv", "z = 1/b", "z = 1/b", OpType.Inv), + ("pow", "b = a**d", "b = a^d", OpType.Pow), + ("sqr", "b = a**2", "b = a^2", OpType.Sqr), + ("id1", "b = 7", "b = 7", OpType.Id), + ("id2", "b = a", "b = a", OpType.Id), + ] + ) def test_str(self, name, module, result, op_type): code = parse(module, mode="exec") op = CodeOp(code) self.assertEqual(str(op), result) self.assertEqual(op.operator, op_type) - @parameterized.expand([ - ("add", "x = a+b", {"a": Mod(5, 21), "b": Mod(7, 21)}, Mod(12, 21)), - ("sub", "x = a-b", {"a": Mod(7, 21), "b": Mod(5, 21)}, Mod(2, 21)) - ]) + @parameterized.expand( + [ + ("add", "x = a+b", {"a": Mod(5, 21), "b": Mod(7, 21)}, Mod(12, 21)), + ("sub", "x = a-b", {"a": Mod(7, 21), "b": Mod(5, 21)}, Mod(2, 21)), + ] + ) def test_call(self, name, module, locals, result): code = parse(module, mode="exec") op = CodeOp(code) @@ -40,7 +43,6 @@ class OpTests(TestCase): class OpResultTests(TestCase): - def test_str(self): for op, char in zip((ast.Add(), ast.Sub(), ast.Mult(), ast.Div()), "+-*/"): res = OpResult("X1", Mod(0, 5), op, Mod(2, 5), Mod(3, 5)) diff --git a/test/ec/test_params.py b/test/ec/test_params.py index eb70342..b2a57b6 100644 --- a/test/ec/test_params.py +++ b/test/ec/test_params.py @@ -21,15 +21,17 @@ class DomainParameterTests(TestCase): def test_str(self): self.assertEqual(str(self.secp128r1), "DomainParameters(secg/secp128r1)") - @parameterized.expand([ - ("secg/secp128r1", "projective"), - ("secg/secp256r1", "projective"), - ("secg/secp521r1", "projective"), - ("other/Curve25519", "xz"), - ("other/Ed25519", "projective"), - ("other/Ed448", "projective"), - ("other/E-222", "projective") - ]) + @parameterized.expand( + [ + ("secg/secp128r1", "projective"), + ("secg/secp256r1", "projective"), + ("secg/secp521r1", "projective"), + ("other/Curve25519", "xz"), + ("other/Ed25519", "projective"), + ("other/Ed448", "projective"), + ("other/E-222", "projective"), + ] + ) def test_get_params(self, name, coords): params = get_params(*name.split("/"), coords) try: @@ -37,10 +39,15 @@ class DomainParameterTests(TestCase): except NotImplementedError: pass - @parameterized.expand([ - ("anssi", "projective"), - ("brainpool", lambda name: "projective" if name.endswith("r1") else "jacobian") - ]) + @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) @@ -55,11 +62,13 @@ class DomainParameterTests(TestCase): category = load_category("test/data/curves.json", "yz") self.assertEqual(len(category), 1) - @parameterized.expand([ - ("no_category/some", "else"), - ("secg/no_curve", "else"), - ("secg/secp128r1", "some") - ]) + @parameterized.expand( + [ + ("no_category/some", "else"), + ("secg/no_curve", "else"), + ("secg/secp128r1", "some"), + ] + ) def test_unknown(self, name, coords): with self.assertRaises(ValueError): get_params(*name.split("/"), coords) diff --git a/test/ec/test_point.py b/test/ec/test_point.py index 9bff800..51907ec 100644 --- a/test/ec/test_point.py +++ b/test/ec/test_point.py @@ -19,10 +19,12 @@ class PointTests(TestCase): Point(self.coords) def test_to_affine(self): - pt = Point(self.coords, - X=Mod(0x161ff7528b899b2d0c28607ca52c5b86, self.secp128r1.curve.prime), - Y=Mod(0xcf5ac8395bafeb13c02da292dded7a83, self.secp128r1.curve.prime), - Z=Mod(1, self.secp128r1.curve.prime)) + pt = Point( + self.coords, + X=Mod(0x161FF7528B899B2D0C28607CA52C5B86, self.secp128r1.curve.prime), + Y=Mod(0xCF5AC8395BAFEB13C02DA292DDED7A83, self.secp128r1.curve.prime), + Z=Mod(1, self.secp128r1.curve.prime), + ) affine = pt.to_affine() self.assertIsInstance(affine.coordinate_model, AffineCoordinateModel) @@ -35,7 +37,11 @@ class PointTests(TestCase): self.assertIsInstance(affine, InfinityPoint) def test_to_model(self): - affine = Point(self.affine, x=Mod(0xabcd, self.secp128r1.curve.prime), y=Mod(0xef, self.secp128r1.curve.prime)) + affine = Point( + self.affine, + x=Mod(0xABCD, self.secp128r1.curve.prime), + y=Mod(0xEF, self.secp128r1.curve.prime), + ) projective_model = self.coords other = affine.to_model(projective_model, self.secp128r1.curve) @@ -53,26 +59,34 @@ class PointTests(TestCase): self.base.to_model(self.coords, self.secp128r1.curve) def test_to_from_affine(self): - pt = Point(self.coords, - X=Mod(0x161ff7528b899b2d0c28607ca52c5b86, self.secp128r1.curve.prime), - Y=Mod(0xcf5ac8395bafeb13c02da292dded7a83, self.secp128r1.curve.prime), - Z=Mod(1, self.secp128r1.curve.prime)) + pt = Point( + self.coords, + X=Mod(0x161FF7528B899B2D0C28607CA52C5B86, self.secp128r1.curve.prime), + Y=Mod(0xCF5AC8395BAFEB13C02DA292DDED7A83, self.secp128r1.curve.prime), + Z=Mod(1, self.secp128r1.curve.prime), + ) other = pt.to_affine().to_model(self.coords, self.secp128r1.curve) self.assertEqual(pt, other) def test_equals(self): - pt = Point(self.coords, - X=Mod(0x4, self.secp128r1.curve.prime), - Y=Mod(0x6, self.secp128r1.curve.prime), - Z=Mod(2, self.secp128r1.curve.prime)) - other = Point(self.coords, - X=Mod(0x2, self.secp128r1.curve.prime), - Y=Mod(0x3, self.secp128r1.curve.prime), - Z=Mod(1, self.secp128r1.curve.prime)) - third = Point(self.coords, - X=Mod(0x5, self.secp128r1.curve.prime), - Y=Mod(0x3, self.secp128r1.curve.prime), - Z=Mod(1, self.secp128r1.curve.prime)) + pt = Point( + self.coords, + X=Mod(0x4, self.secp128r1.curve.prime), + Y=Mod(0x6, self.secp128r1.curve.prime), + Z=Mod(2, self.secp128r1.curve.prime), + ) + other = Point( + self.coords, + X=Mod(0x2, self.secp128r1.curve.prime), + Y=Mod(0x3, self.secp128r1.curve.prime), + Z=Mod(1, self.secp128r1.curve.prime), + ) + third = Point( + self.coords, + X=Mod(0x5, self.secp128r1.curve.prime), + Y=Mod(0x3, self.secp128r1.curve.prime), + Z=Mod(1, self.secp128r1.curve.prime), + ) self.assertTrue(pt.equals(other)) self.assertNotEqual(pt, other) self.assertFalse(pt.equals(2)) @@ -94,17 +108,26 @@ class PointTests(TestCase): self.assertFalse(pt.equals_scaled(infty_one)) mont = MontgomeryModel() - different = Point(mont.coordinates["xz"], - X=Mod(0x64daccd2656420216545e5f65221eb, - 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa), - Z=Mod(1, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)) + different = Point( + mont.coordinates["xz"], + X=Mod( + 0x64DACCD2656420216545E5F65221EB, + 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, + ), + Z=Mod(1, 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA), + ) self.assertFalse(pt.equals(different)) self.assertNotEqual(pt, different) def test_bytes(self): - pt = Point(self.coords, - X=Mod(0x4, self.secp128r1.curve.prime), - Y=Mod(0x6, self.secp128r1.curve.prime), - Z=Mod(2, self.secp128r1.curve.prime)) - self.assertEqual(bytes(pt), b"\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02") + pt = Point( + self.coords, + X=Mod(0x4, self.secp128r1.curve.prime), + Y=Mod(0x6, self.secp128r1.curve.prime), + Z=Mod(2, self.secp128r1.curve.prime), + ) + self.assertEqual( + bytes(pt), + b"\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02", + ) self.assertEqual(bytes(InfinityPoint(self.coords)), b"\x00") diff --git a/test/ec/test_regress.py b/test/ec/test_regress.py index ef593fc..e02cc8e 100644 --- a/test/ec/test_regress.py +++ b/test/ec/test_regress.py @@ -10,7 +10,6 @@ from pyecsca.ec.point import Point class RegressionTests(TestCase): - def test_issue_7(self): secp128r1 = get_params("secg", "secp128r1", "projective") base = secp128r1.generator @@ -18,7 +17,9 @@ class RegressionTests(TestCase): add = coords.formulas["add-1998-cmo"] dbl = coords.formulas["dbl-1998-cmo"] scl = coords.formulas["z"] - mult = LTRMultiplier(add, dbl, scl, always=False, complete=False, short_circuit=True) + mult = LTRMultiplier( + add, dbl, scl, always=False, complete=False, short_circuit=True + ) mult.init(secp128r1, base) pt = mult.multiply(13613624287328732) self.assertIsInstance(pt.coords["X"], Mod) @@ -44,7 +45,9 @@ class RegressionTests(TestCase): coords = model.coordinates["xz"] p = 19 neutral = Point(coords, X=Mod(1, p), Z=Mod(0, p)) - curve = EllipticCurve(model, coords, p, neutral, {"a": Mod(8, p), "b": Mod(1, p)}) + curve = EllipticCurve( + model, coords, p, neutral, {"a": Mod(8, p), "b": Mod(1, p)} + ) base = Point(coords, X=Mod(12, p), Z=Mod(1, p)) formula = coords.formulas["dbl-1987-m-2"] res = formula(p, base, **curve.parameters)[0] @@ -60,13 +63,13 @@ class RegressionTests(TestCase): model = EdwardsModel() coords = model.coordinates["yz"] coords_sqr = model.coordinates["yzsquared"] - p = 0x1d + p = 0x1D c = Mod(1, p) - d = Mod(0x1c, p) + d = Mod(0x1C, p) r = d.sqrt() neutral = Point(coords, Y=c * r, Z=Mod(1, p)) curve = EllipticCurve(model, coords, p, neutral, {"c": c, "d": d, "r": r}) neutral_affine = Point(AffineCoordinateModel(model), x=Mod(0, p), y=c) self.assertEqual(neutral, neutral_affine.to_model(coords, curve)) - neutral_sqr = Point(coords_sqr, Y=c**2 * r, Z=Mod(1, p)) + neutral_sqr = Point(coords_sqr, Y=c ** 2 * r, Z=Mod(1, p)) self.assertEqual(neutral_sqr, neutral_affine.to_model(coords_sqr, curve)) diff --git a/test/ec/test_signature.py b/test/ec/test_signature.py index 8d4a439..f6ab302 100644 --- a/test/ec/test_signature.py +++ b/test/ec/test_signature.py @@ -5,29 +5,38 @@ from parameterized import parameterized from pyecsca.ec.params import get_params from pyecsca.ec.mod import Mod from pyecsca.ec.mult import LTRMultiplier -from pyecsca.ec.signature import (Signature, SignatureResult, ECDSA_NONE, ECDSA_SHA1, ECDSA_SHA224, - ECDSA_SHA256, ECDSA_SHA384, ECDSA_SHA512) +from pyecsca.ec.signature import ( + Signature, + SignatureResult, + ECDSA_NONE, + ECDSA_SHA1, + ECDSA_SHA224, + ECDSA_SHA256, + ECDSA_SHA384, + ECDSA_SHA512, +) class SignatureTests(TestCase): - def setUp(self): self.secp128r1 = get_params("secg", "secp128r1", "projective") self.add = self.secp128r1.curve.coordinate_model.formulas["add-2007-bl"] self.dbl = self.secp128r1.curve.coordinate_model.formulas["dbl-2007-bl"] self.mult = LTRMultiplier(self.add, self.dbl) - self.msg = 0xcafebabe.to_bytes(4, byteorder="big") - self.priv = Mod(0xdeadbeef, self.secp128r1.order) + self.msg = 0xCAFEBABE .to_bytes(4, byteorder="big") + self.priv = Mod(0xDEADBEEF, self.secp128r1.order) self.mult.init(self.secp128r1, self.secp128r1.generator) self.pub = self.mult.multiply(self.priv.x) - @parameterized.expand([ - ("SHA1", ECDSA_SHA1), - ("SHA224", ECDSA_SHA224), - ("SHA256", ECDSA_SHA256), - ("SHA384", ECDSA_SHA384), - ("SHA512", ECDSA_SHA512) - ]) + @parameterized.expand( + [ + ("SHA1", ECDSA_SHA1), + ("SHA224", ECDSA_SHA224), + ("SHA256", ECDSA_SHA256), + ("SHA384", ECDSA_SHA384), + ("SHA512", ECDSA_SHA512), + ] + ) def test_all(self, name, algo): signer = algo(self.mult, self.secp128r1, privkey=self.priv) self.assertTrue(signer.can_sign) @@ -36,13 +45,17 @@ class SignatureTests(TestCase): self.assertTrue(verifier.can_verify) self.assertTrue(verifier.verify_data(sig, self.msg)) - none = ECDSA_NONE(self.mult, self.secp128r1, add=self.add, pubkey=self.pub, privkey=self.priv) + none = ECDSA_NONE( + self.mult, self.secp128r1, add=self.add, pubkey=self.pub, privkey=self.priv + ) digest = signer.hash_algo(self.msg).digest() sig = none.sign_hash(digest) self.assertTrue(none.verify_hash(sig, digest)) def test_cannot(self): - ok = ECDSA_NONE(self.mult, self.secp128r1, add=self.add, pubkey=self.pub, privkey=self.priv) + ok = ECDSA_NONE( + self.mult, self.secp128r1, add=self.add, pubkey=self.pub, privkey=self.priv + ) data = b"aaaa" sig = ok.sign_data(data) @@ -60,23 +73,25 @@ class SignatureTests(TestCase): with self.assertRaises(ValueError): Signature(self.mult, self.secp128r1) - @parameterized.expand([ - ("SHA1", ECDSA_SHA1), - ("SHA224", ECDSA_SHA224), - ("SHA256", ECDSA_SHA256), - ("SHA384", ECDSA_SHA384), - ("SHA512", ECDSA_SHA512) - ]) + @parameterized.expand( + [ + ("SHA1", ECDSA_SHA1), + ("SHA224", ECDSA_SHA224), + ("SHA256", ECDSA_SHA256), + ("SHA384", ECDSA_SHA384), + ("SHA512", ECDSA_SHA512), + ] + ) def test_fixed_nonce(self, name, algo): signer = algo(self.mult, self.secp128r1, privkey=self.priv) - sig_one = signer.sign_data(self.msg, nonce=0xabcdef) - sig_other = signer.sign_data(self.msg, nonce=0xabcdef) + sig_one = signer.sign_data(self.msg, nonce=0xABCDEF) + sig_other = signer.sign_data(self.msg, nonce=0xABCDEF) verifier = algo(self.mult, self.secp128r1, add=self.add, pubkey=self.pub) self.assertTrue(verifier.verify_data(sig_one, self.msg)) self.assertTrue(verifier.verify_data(sig_other, self.msg)) self.assertEqual(sig_one, sig_other) def test_der(self): - sig = SignatureResult(0xaaaaa, 0xbbbbb) + sig = SignatureResult(0xAAAAA, 0xBBBBB) self.assertEqual(sig, SignatureResult.from_DER(sig.to_DER())) self.assertNotEqual(sig, "abc") diff --git a/test/ec/test_transformations.py b/test/ec/test_transformations.py index 951fbb8..b15f868 100644 --- a/test/ec/test_transformations.py +++ b/test/ec/test_transformations.py @@ -5,7 +5,6 @@ from pyecsca.ec.transformations import M2SW, M2TE, TE2M, SW2M, SW2TE class TransformationTests(TestCase): - def test_montgomery(self): curve25519 = get_params("other", "Curve25519", "affine") sw = M2SW(curve25519) diff --git a/test/ec/utils.py b/test/ec/utils.py index 6429ac9..84c568d 100644 --- a/test/ec/utils.py +++ b/test/ec/utils.py @@ -46,12 +46,21 @@ class Profiler(object): raise ValueError if self._output_directory is None or self._benchmark_name is None: return - git_commit = run(["git", "rev-parse", "--short", "HEAD"], stdout=PIPE, stderr=DEVNULL).stdout.strip().decode() - git_dirty = run(["git", "diff", "--quiet"], stdout=DEVNULL, stderr=DEVNULL).returncode != 0 + git_commit = ( + run(["git", "rev-parse", "--short", "HEAD"], stdout=PIPE, stderr=DEVNULL) + .stdout.strip() + .decode() + ) + git_dirty = ( + run(["git", "diff", "--quiet"], stdout=DEVNULL, stderr=DEVNULL).returncode + != 0 + ) version = git_commit + ("-dirty" if git_dirty else "") output_path = Path(self._output_directory) / (self._benchmark_name + ".csv") with output_path.open("a") as f: - f.write(f"{version},{'.'.join(map(str, sys.version_info[:3]))},{self.get_time()}\n") + f.write( + f"{version},{'.'.join(map(str, sys.version_info[:3]))},{self.get_time()}\n" + ) def output(self): if self._state != "out": diff --git a/test/sca/test_align.py b/test/sca/test_align.py index 2630b24..607b297 100644 --- a/test/sca/test_align.py +++ b/test/sca/test_align.py @@ -1,28 +1,49 @@ import numpy as np -from pyecsca.sca import align_correlation, align_peaks, align_sad, align_dtw_scale,\ - align_dtw, Trace, InspectorTraceSet +from pyecsca.sca import ( + align_correlation, + align_peaks, + align_sad, + align_dtw_scale, + align_dtw, + Trace, + InspectorTraceSet, +) from .utils import Plottable, slow class AlignTests(Plottable): - def test_align(self): - first_arr = np.array([10, 64, 120, 64, 10, 10, 10, 10, 10], dtype=np.dtype("i1")) + first_arr = np.array( + [10, 64, 120, 64, 10, 10, 10, 10, 10], dtype=np.dtype("i1") + ) second_arr = np.array([10, 10, 10, 10, 50, 80, 50, 20], dtype=np.dtype("i1")) third_arr = np.array([70, 30, 42, 35, 28, 21, 15, 10, 5], dtype=np.dtype("i1")) a = Trace(first_arr) b = Trace(second_arr) c = Trace(third_arr) - result, offsets = align_correlation(a, b, c, reference_offset=1, reference_length=3, max_offset=4, min_correlation=0.65) + result, offsets = align_correlation( + a, + b, + c, + reference_offset=1, + reference_length=3, + max_offset=4, + min_correlation=0.65, + ) self.assertIsNotNone(result) self.assertEqual(len(result), 2) np.testing.assert_equal(result[0].samples, first_arr) - np.testing.assert_equal(result[1].samples, np.array([10, 50, 80, 50, 20, 0, 0, 0], dtype=np.dtype("i1"))) + np.testing.assert_equal( + result[1].samples, + np.array([10, 50, 80, 50, 20, 0, 0, 0], dtype=np.dtype("i1")), + ) @slow def test_large_align(self): example = InspectorTraceSet.read("test/data/example.trs") - result, offsets = align_correlation(*example, reference_offset=100000, reference_length=20000, max_offset=15000) + result, offsets = align_correlation( + *example, reference_offset=100000, reference_length=20000, max_offset=15000 + ) self.assertIsNotNone(result) @slow @@ -32,25 +53,46 @@ class AlignTests(Plottable): self.assertIsNotNone(result) def test_peak_align(self): - first_arr = np.array([10, 64, 14, 120, 15, 30, 10, 15, 20, 15, 15, 10, 10], dtype=np.dtype("i1")) - second_arr = np.array([10, 10, 10, 10, 90, 40, 50, 20, 10, 17, 16, 10], dtype=np.dtype("i1")) + first_arr = np.array( + [10, 64, 14, 120, 15, 30, 10, 15, 20, 15, 15, 10, 10], dtype=np.dtype("i1") + ) + second_arr = np.array( + [10, 10, 10, 10, 90, 40, 50, 20, 10, 17, 16, 10], dtype=np.dtype("i1") + ) a = Trace(first_arr) b = Trace(second_arr) - result, offsets = align_peaks(a, b, reference_offset=2, reference_length=5, max_offset=3) + result, offsets = align_peaks( + a, b, reference_offset=2, reference_length=5, max_offset=3 + ) self.assertEqual(np.argmax(result[0].samples), np.argmax(result[1].samples)) def test_sad_align(self): - first_arr = np.array([10, 64, 14, 120, 15, 30, 10, 15, 20, 15, 15, 10, 10], dtype=np.dtype("i1")) - second_arr = np.array([10, 10, 90, 40, 50, 20, 10, 17, 16, 10, 10], dtype=np.dtype("i1")) + first_arr = np.array( + [10, 64, 14, 120, 15, 30, 10, 15, 20, 15, 15, 10, 10], dtype=np.dtype("i1") + ) + second_arr = np.array( + [10, 10, 90, 40, 50, 20, 10, 17, 16, 10, 10], dtype=np.dtype("i1") + ) a = Trace(first_arr) b = Trace(second_arr) - result, offsets = align_sad(a, b, reference_offset=2, reference_length=5, max_offset=3) + result, offsets = align_sad( + a, b, reference_offset=2, reference_length=5, max_offset=3 + ) self.assertEqual(len(result), 2) def test_dtw_align_scale(self): - first_arr = np.array([10, 64, 14, 120, 15, 30, 10, 15, 20, 15, 15, 10, 10, 8, 10, 12, 10, 13, 9], dtype=np.dtype("f2")) - second_arr = np.array([10, 10, 60, 40, 90, 20, 10, 17, 16, 10, 10, 10, 10, 10, 17, 12, 10], dtype=np.dtype("f2")) - third_arr = np.array([10, 30, 20, 21, 15, 8, 10, 37, 21, 77, 20, 28, 25, 10, 9, 10, 15, 9, 10], dtype=np.dtype("f2")) + first_arr = np.array( + [10, 64, 14, 120, 15, 30, 10, 15, 20, 15, 15, 10, 10, 8, 10, 12, 10, 13, 9], + dtype=np.dtype("f2"), + ) + second_arr = np.array( + [10, 10, 60, 40, 90, 20, 10, 17, 16, 10, 10, 10, 10, 10, 17, 12, 10], + dtype=np.dtype("f2"), + ) + third_arr = np.array( + [10, 30, 20, 21, 15, 8, 10, 37, 21, 77, 20, 28, 25, 10, 9, 10, 15, 9, 10], + dtype=np.dtype("f2"), + ) a = Trace(first_arr) b = Trace(second_arr) c = Trace(third_arr) @@ -62,14 +104,27 @@ class AlignTests(Plottable): result_other = align_dtw_scale(a, b, c, fast=False) - self.assertEqual(np.argmax(result_other[0].samples), np.argmax(result_other[1].samples)) - self.assertEqual(np.argmax(result_other[1].samples), np.argmax(result_other[2].samples)) + self.assertEqual( + np.argmax(result_other[0].samples), np.argmax(result_other[1].samples) + ) + self.assertEqual( + np.argmax(result_other[1].samples), np.argmax(result_other[2].samples) + ) self.plot(*result_other) def test_dtw_align(self): - first_arr = np.array([10, 64, 14, 120, 15, 30, 10, 15, 20, 15, 15, 10, 10, 8, 10, 12, 10, 13, 9], dtype=np.dtype("i1")) - second_arr = np.array([10, 10, 60, 40, 90, 20, 10, 17, 16, 10, 10, 10, 10, 10, 17, 12, 10], dtype=np.dtype("i1")) - third_arr = np.array([10, 30, 20, 21, 15, 8, 10, 47, 21, 77, 20, 28, 25, 10, 9, 10, 15, 9, 10], dtype=np.dtype("i1")) + first_arr = np.array( + [10, 64, 14, 120, 15, 30, 10, 15, 20, 15, 15, 10, 10, 8, 10, 12, 10, 13, 9], + dtype=np.dtype("i1"), + ) + second_arr = np.array( + [10, 10, 60, 40, 90, 20, 10, 17, 16, 10, 10, 10, 10, 10, 17, 12, 10], + dtype=np.dtype("i1"), + ) + third_arr = np.array( + [10, 30, 20, 21, 15, 8, 10, 47, 21, 77, 20, 28, 25, 10, 9, 10, 15, 9, 10], + dtype=np.dtype("i1"), + ) a = Trace(first_arr) b = Trace(second_arr) c = Trace(third_arr) @@ -81,6 +136,10 @@ class AlignTests(Plottable): result_other = align_dtw(a, b, c, fast=False) - self.assertEqual(np.argmax(result_other[0].samples), np.argmax(result_other[1].samples)) - self.assertEqual(np.argmax(result_other[1].samples), np.argmax(result_other[2].samples)) + self.assertEqual( + np.argmax(result_other[0].samples), np.argmax(result_other[1].samples) + ) + self.assertEqual( + np.argmax(result_other[1].samples), np.argmax(result_other[2].samples) + ) self.plot(*result_other) diff --git a/test/sca/test_combine.py b/test/sca/test_combine.py index 51f7f02..953b4bf 100644 --- a/test/sca/test_combine.py +++ b/test/sca/test_combine.py @@ -1,11 +1,20 @@ from unittest import TestCase import numpy as np -from pyecsca.sca import Trace, CombinedTrace, average, conditional_average, standard_deviation, variance, average_and_variance, add, subtract +from pyecsca.sca import ( + Trace, + CombinedTrace, + average, + conditional_average, + standard_deviation, + variance, + average_and_variance, + add, + subtract, +) class CombineTests(TestCase): - def setUp(self): self.a = Trace(np.array([20, 80], dtype=np.dtype("i1")), {"data": b"\xff"}) self.b = Trace(np.array([30, 42], dtype=np.dtype("i1")), {"data": b"\xff"}) @@ -21,8 +30,12 @@ class CombineTests(TestCase): self.assertEqual(result.samples[1], 61) def test_conditional_average(self): - result = conditional_average(self.a, self.b, self.c, - condition=lambda trace: trace.meta["data"] == b"\xff") + result = conditional_average( + self.a, + self.b, + self.c, + condition=lambda trace: trace.meta["data"] == b"\xff", + ) self.assertIsInstance(result, CombinedTrace) self.assertEqual(len(result.samples), 2) self.assertEqual(result.samples[0], 25) diff --git a/test/sca/test_edit.py b/test/sca/test_edit.py index f701d83..282e62e 100644 --- a/test/sca/test_edit.py +++ b/test/sca/test_edit.py @@ -6,18 +6,21 @@ from pyecsca.sca import Trace, trim, reverse, pad class EditTests(TestCase): - def setUp(self): self._trace = Trace(np.array([10, 20, 30, 40, 50], dtype=np.dtype("i1"))) def test_trim(self): result = trim(self._trace, 2) self.assertIsNotNone(result) - np.testing.assert_equal(result.samples, np.array([30, 40, 50], dtype=np.dtype("i1"))) + np.testing.assert_equal( + result.samples, np.array([30, 40, 50], dtype=np.dtype("i1")) + ) result = trim(self._trace, end=3) self.assertIsNotNone(result) - np.testing.assert_equal(result.samples, np.array([10, 20, 30], dtype=np.dtype("i1"))) + np.testing.assert_equal( + result.samples, np.array([10, 20, 30], dtype=np.dtype("i1")) + ) with self.assertRaises(ValueError): trim(self._trace, 5, 1) @@ -25,16 +28,21 @@ class EditTests(TestCase): def test_reverse(self): result = reverse(self._trace) self.assertIsNotNone(result) - np.testing.assert_equal(result.samples, - np.array([50, 40, 30, 20, 10], dtype=np.dtype("i1"))) + np.testing.assert_equal( + result.samples, np.array([50, 40, 30, 20, 10], dtype=np.dtype("i1")) + ) def test_pad(self): result = pad(self._trace, 2) self.assertIsNotNone(result) - np.testing.assert_equal(result.samples, - np.array([0, 0, 10, 20, 30, 40, 50, 0, 0], dtype=np.dtype("i1"))) + np.testing.assert_equal( + result.samples, + np.array([0, 0, 10, 20, 30, 40, 50, 0, 0], dtype=np.dtype("i1")), + ) result = pad(self._trace, (1, 3)) self.assertIsNotNone(result) - np.testing.assert_equal(result.samples, - np.array([0, 10, 20, 30, 40, 50, 0, 0, 0], dtype=np.dtype("i1"))) + np.testing.assert_equal( + result.samples, + np.array([0, 10, 20, 30, 40, 50, 0, 0, 0], dtype=np.dtype("i1")), + ) diff --git a/test/sca/test_filter.py b/test/sca/test_filter.py index 1c9e9ca..9d63ea3 100644 --- a/test/sca/test_filter.py +++ b/test/sca/test_filter.py @@ -1,16 +1,25 @@ from unittest import TestCase import numpy as np -from pyecsca.sca import Trace, filter_lowpass, filter_highpass, filter_bandpass, filter_bandstop +from pyecsca.sca import ( + Trace, + filter_lowpass, + filter_highpass, + filter_bandpass, + filter_bandstop, +) from .utils import Plottable class FilterTests(Plottable): - def setUp(self): self._trace = Trace( - np.array([5, 12, 15, 13, 15, 11, 7, 2, -4, -8, -10, -8, -13, -9, -11, -8, -5], - dtype=np.dtype("i1")), None) + np.array( + [5, 12, 15, 13, 15, 11, 7, 2, -4, -8, -10, -8, -13, -9, -11, -8, -5], + dtype=np.dtype("i1"), + ), + None, + ) def test_lowpass(self): result = filter_lowpass(self._trace, 100, 20) diff --git a/test/sca/test_match.py b/test/sca/test_match.py index cd0b780..9ba81a3 100644 --- a/test/sca/test_match.py +++ b/test/sca/test_match.py @@ -7,22 +7,68 @@ from .utils import Plottable class MatchingTests(Plottable): - def test_simple_match(self): - pattern = Trace(np.array([1, 15, 12, -10, 0, 13, 17, -1, 0], dtype=np.dtype("i1")), None) - base = Trace(np.array( + pattern = Trace( + np.array([1, 15, 12, -10, 0, 13, 17, -1, 0], dtype=np.dtype("i1")), None + ) + base = Trace( + np.array( [0, 1, 3, 1, 2, -2, -3, 1, 15, 12, -10, 0, 13, 17, -1, 0, 3, 1], - dtype=np.dtype("i1")), None) + dtype=np.dtype("i1"), + ), + None, + ) filtered = match_part(base, 7, 9) self.assertListEqual(filtered, [7]) self.plot(base=base, pattern=pad(pattern, (filtered[0], 0))) def test_multiple_match(self): - pattern = Trace(np.array([1, 15, 12, -10, 0, 13, 17, -1, 0], dtype=np.dtype("i1")), None) - base = Trace(np.array( - [0, 1, 3, 1, 2, -2, -3, 1, 18, 10, -5, 0, 13, 17, -1, 0, 3, 1, 2, 5, 13, 8, -8, 1, - 11, 15, 0, 1, 5, 2, 4], - dtype=np.dtype("i1")), None) + pattern = Trace( + np.array([1, 15, 12, -10, 0, 13, 17, -1, 0], dtype=np.dtype("i1")), None + ) + base = Trace( + np.array( + [ + 0, + 1, + 3, + 1, + 2, + -2, + -3, + 1, + 18, + 10, + -5, + 0, + 13, + 17, + -1, + 0, + 3, + 1, + 2, + 5, + 13, + 8, + -8, + 1, + 11, + 15, + 0, + 1, + 5, + 2, + 4, + ], + dtype=np.dtype("i1"), + ), + None, + ) filtered = match_pattern(base, pattern, 0.9) self.assertListEqual(filtered, [7, 19]) - self.plot(base=base, pattern1=pad(pattern, (filtered[0], 0)), pattern2=pad(pattern, (filtered[1], 0))) + self.plot( + base=base, + pattern1=pad(pattern, (filtered[0], 0)), + pattern2=pad(pattern, (filtered[1], 0)), + ) diff --git a/test/sca/test_plot.py b/test/sca/test_plot.py index 2cd6b11..7d2cec0 100644 --- a/test/sca/test_plot.py +++ b/test/sca/test_plot.py @@ -4,13 +4,17 @@ import numpy as np import holoviews as hv import matplotlib as mpl from pyecsca.sca.trace import Trace -from pyecsca.sca.trace.plot import (plot_trace, save_figure, save_figure_png, save_figure_svg, - plot_traces) +from pyecsca.sca.trace.plot import ( + plot_trace, + save_figure, + save_figure_png, + save_figure_svg, + plot_traces, +) from .utils import Plottable class PlotTests(Plottable): - def setUp(self) -> None: self.trace1 = Trace(np.array([6, 7, 3, -2, 5, 1], dtype=np.dtype("i1"))) self.trace2 = Trace(np.array([2, 3, 7, 0, -1, 0], dtype=np.dtype("i1"))) diff --git a/test/sca/test_process.py b/test/sca/test_process.py index 4525d01..fda8575 100644 --- a/test/sca/test_process.py +++ b/test/sca/test_process.py @@ -1,11 +1,20 @@ from unittest import TestCase import numpy as np -from pyecsca.sca import Trace, absolute, invert, threshold, rolling_mean, offset, recenter, normalize, normalize_wl +from pyecsca.sca import ( + Trace, + absolute, + invert, + threshold, + rolling_mean, + offset, + recenter, + normalize, + normalize_wl, +) class ProcessTests(TestCase): - def setUp(self): self._trace = Trace(np.array([30, -60, 145, 247], dtype=np.dtype("i2")), None) @@ -36,7 +45,9 @@ class ProcessTests(TestCase): def test_offset(self): result = offset(self._trace, 5) self.assertIsNotNone(result) - np.testing.assert_equal(result.samples, np.array([35, -55, 150, 252], dtype=np.dtype("i2"))) + np.testing.assert_equal( + result.samples, np.array([35, -55, 150, 252], dtype=np.dtype("i2")) + ) def test_recenter(self): self.assertIsNotNone(recenter(self._trace)) diff --git a/test/sca/test_rpa.py b/test/sca/test_rpa.py index 0c96e86..ac8392e 100644 --- a/test/sca/test_rpa.py +++ b/test/sca/test_rpa.py @@ -3,14 +3,18 @@ from unittest import TestCase from parameterized import parameterized from pyecsca.ec.context import local -from pyecsca.ec.mult import LTRMultiplier, BinaryNAFMultiplier, WindowNAFMultiplier, LadderMultiplier, \ - DifferentialLadderMultiplier +from pyecsca.ec.mult import ( + LTRMultiplier, + BinaryNAFMultiplier, + WindowNAFMultiplier, + LadderMultiplier, + DifferentialLadderMultiplier, +) from pyecsca.ec.params import get_params from pyecsca.sca.re.rpa import MultipleContext class MultipleContextTests(TestCase): - def setUp(self): self.secp128r1 = get_params("secg", "secp128r1", "projective") self.base = self.secp128r1.generator @@ -20,14 +24,26 @@ class MultipleContextTests(TestCase): self.neg = self.coords.formulas["neg"] self.scale = self.coords.formulas["z"] - @parameterized.expand([ - ("10", 10), - ("2355498743", 2355498743), - ("325385790209017329644351321912443757746", 325385790209017329644351321912443757746), - ("13613624287328732", 13613624287328732) - ]) + @parameterized.expand( + [ + ("10", 10), + ("2355498743", 2355498743), + ( + "325385790209017329644351321912443757746", + 325385790209017329644351321912443757746, + ), + ("13613624287328732", 13613624287328732), + ] + ) def test_basic(self, name, scalar): - mult = LTRMultiplier(self.add, self.dbl, self.scale, always=False, complete=False, short_circuit=True) + mult = LTRMultiplier( + self.add, + self.dbl, + self.scale, + always=False, + complete=False, + short_circuit=True, + ) with local(MultipleContext()) as ctx: mult.init(self.secp128r1, self.base) mult.multiply(scalar) diff --git a/test/sca/test_sampling.py b/test/sca/test_sampling.py index 6108c17..4a54b4d 100644 --- a/test/sca/test_sampling.py +++ b/test/sca/test_sampling.py @@ -1,12 +1,18 @@ from unittest import TestCase import numpy as np -from pyecsca.sca import Trace, downsample_average, downsample_pick, downsample_decimate, downsample_max, downsample_min +from pyecsca.sca import ( + Trace, + downsample_average, + downsample_pick, + downsample_decimate, + downsample_max, + downsample_min, +) from .utils import Plottable class SamplingTests(Plottable): - def setUp(self): self._trace = Trace(np.array([20, 40, 50, 50, 10], dtype=np.dtype("i1"))) @@ -27,29 +33,136 @@ class SamplingTests(Plottable): self.assertEqual(result.samples[1], 50) def test_downsample_max(self): - trace = Trace(np.array([20, 30, 55, 18, 15, 10, 35, 24, 21, 15, 10, 8, -10, -5, - -8, -12, -15, -18, -34, -21, -17, -10, -5, -12, -6, -2, - 4, 8, 21, 28], dtype=np.dtype("i1"))) + trace = Trace( + np.array( + [ + 20, + 30, + 55, + 18, + 15, + 10, + 35, + 24, + 21, + 15, + 10, + 8, + -10, + -5, + -8, + -12, + -15, + -18, + -34, + -21, + -17, + -10, + -5, + -12, + -6, + -2, + 4, + 8, + 21, + 28, + ], + dtype=np.dtype("i1"), + ) + ) result = downsample_max(trace, 2) self.assertIsNotNone(result) self.assertIsInstance(result, Trace) self.assertEqual(len(result.samples), 15) - self.assertEqual(list(result), [30, 55, 15, 35, 21, 10, -5, -8, -15, -21, -10, -5, -2, 8, 28]) + self.assertEqual( + list(result), [30, 55, 15, 35, 21, 10, -5, -8, -15, -21, -10, -5, -2, 8, 28] + ) def test_downsample_min(self): - trace = Trace(np.array([20, 30, 55, 18, 15, 10, 35, 24, 21, 15, 10, 8, -10, -5, - -8, -12, -15, -18, -34, -21, -17, -10, -5, -12, -6, -2, - 4, 8, 21, 28], dtype=np.dtype("i1"))) + trace = Trace( + np.array( + [ + 20, + 30, + 55, + 18, + 15, + 10, + 35, + 24, + 21, + 15, + 10, + 8, + -10, + -5, + -8, + -12, + -15, + -18, + -34, + -21, + -17, + -10, + -5, + -12, + -6, + -2, + 4, + 8, + 21, + 28, + ], + dtype=np.dtype("i1"), + ) + ) result = downsample_min(trace, 2) self.assertIsNotNone(result) self.assertIsInstance(result, Trace) self.assertEqual(len(result.samples), 15) - self.assertEqual(list(result), [20, 18, 10, 24, 15, 8, -10, -12, -18, -34, -17, -12, -6, 4, 21]) + self.assertEqual( + list(result), + [20, 18, 10, 24, 15, 8, -10, -12, -18, -34, -17, -12, -6, 4, 21], + ) def test_downsample_decimate(self): - trace = Trace(np.array([20, 30, 55, 18, 15, 10, 35, 24, 21, 15, 10, 8, -10, -5, - -8, -12, -15, -18, -34, -21, -17, -10, -5, -12, -6, -2, - 4, 8, 21, 28], dtype=np.dtype("i1"))) + trace = Trace( + np.array( + [ + 20, + 30, + 55, + 18, + 15, + 10, + 35, + 24, + 21, + 15, + 10, + 8, + -10, + -5, + -8, + -12, + -15, + -18, + -34, + -21, + -17, + -10, + -5, + -12, + -6, + -2, + 4, + 8, + 21, + 28, + ], + dtype=np.dtype("i1"), + ) + ) result = downsample_decimate(trace, 2) self.assertIsNotNone(result) self.assertIsInstance(result, Trace) diff --git a/test/sca/test_target.py b/test/sca/test_target.py index a0f4fec..4168152 100644 --- a/test/sca/test_target.py +++ b/test/sca/test_target.py @@ -12,10 +12,24 @@ from pyecsca.ec.mult import LTRMultiplier from pyecsca.ec.params import DomainParameters, get_params from pyecsca.ec.point import Point from pyecsca.ec.signature import SignatureResult, ECDSA_SHA1 -from pyecsca.sca.target import BinaryTarget, SimpleSerialTarget, SimpleSerialMessage, has_pyscard -from pyecsca.sca.target.ectester import (KeyAgreementEnum, SignatureEnum, KeypairEnum, KeyBuildEnum, - KeyClassEnum, CurveEnum, ParameterEnum, RunModeEnum, - KeyEnum, TransformationEnum) +from pyecsca.sca.target import ( + BinaryTarget, + SimpleSerialTarget, + SimpleSerialMessage, + has_pyscard, +) +from pyecsca.sca.target.ectester import ( + KeyAgreementEnum, + SignatureEnum, + KeypairEnum, + KeyBuildEnum, + KeyClassEnum, + CurveEnum, + ParameterEnum, + RunModeEnum, + KeyEnum, + TransformationEnum, +) if has_pyscard: from pyecsca.sca.target.ectester import ECTesterTarget @@ -28,7 +42,6 @@ class TestTarget(SimpleSerialTarget, BinaryTarget): class BinaryTargetTests(TestCase): - def test_basic_target(self): target_path = join(dirname(realpath(__file__)), "..", "data", "target.py") target = TestTarget(["python", target_path]) @@ -67,6 +80,7 @@ class ECTesterTargetTests(TestCase): if not has_pyscard: return from smartcard.System import readers + try: rs = readers() except BaseSCardException: @@ -95,35 +109,64 @@ class ECTesterTargetTests(TestCase): self.assertTrue(ka_resp.success) sig_resp = self.target.allocate_sig(SignatureEnum.ALG_ECDSA_SHA) self.assertTrue(sig_resp.success) - key_resp = self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) + key_resp = self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) self.assertTrue(key_resp.success) def test_set(self): - self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) - set_resp = self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, - ParameterEnum.DOMAIN_FP) + self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) + set_resp = self.target.set( + KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP + ) self.assertTrue(set_resp.success) def test_set_explicit(self): - self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) - values = ECTesterTarget.encode_parameters(ParameterEnum.DOMAIN_FP, self.secp256r1) - set_resp = self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.external, - ParameterEnum.DOMAIN_FP, values) + self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) + values = ECTesterTarget.encode_parameters( + ParameterEnum.DOMAIN_FP, self.secp256r1 + ) + set_resp = self.target.set( + KeypairEnum.KEYPAIR_LOCAL, + CurveEnum.external, + ParameterEnum.DOMAIN_FP, + values, + ) self.assertTrue(set_resp.success) def test_generate(self): - self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) - self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP) + self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) + self.target.set( + KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP + ) generate_resp = self.target.generate(KeypairEnum.KEYPAIR_LOCAL) self.assertTrue(generate_resp.success) def test_clear(self): - self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) + self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) clear_resp = self.target.clear(KeypairEnum.KEYPAIR_LOCAL) self.assertTrue(clear_resp.success) @@ -138,184 +181,312 @@ class ECTesterTargetTests(TestCase): def test_dry_run(self): dry_run_resp = self.target.run_mode(RunModeEnum.MODE_DRY_RUN) self.assertTrue(dry_run_resp.success) - allocate_resp = self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, - 256, - KeyClassEnum.ALG_EC_FP) + allocate_resp = self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) self.assertTrue(allocate_resp.success) dry_run_resp = self.target.run_mode(RunModeEnum.MODE_NORMAL) self.assertTrue(dry_run_resp.success) def test_export(self): - self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) - self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP) + self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) + self.target.set( + KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP + ) self.target.generate(KeypairEnum.KEYPAIR_LOCAL) - export_public_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC, - ParameterEnum.W) + export_public_resp = self.target.export( + KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC, ParameterEnum.W + ) self.assertTrue(export_public_resp.success) - pubkey_bytes = export_public_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W) + pubkey_bytes = export_public_resp.get_param( + KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W + ) pubkey = self.secp256r1.curve.decode_point(pubkey_bytes) - export_privkey_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE, - ParameterEnum.S) + export_privkey_resp = self.target.export( + KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE, ParameterEnum.S + ) self.assertTrue(export_privkey_resp.success) privkey = int.from_bytes( - export_privkey_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S), "big") - self.assertEqual(pubkey, - self.secp256r1.curve.affine_multiply(self.secp256r1.generator, privkey)) + export_privkey_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S), + "big", + ) + self.assertEqual( + pubkey, + self.secp256r1.curve.affine_multiply(self.secp256r1.generator, privkey), + ) def test_export_curve(self): - self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) - self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP) - export_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC, - ParameterEnum.DOMAIN_FP) + self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) + self.target.set( + KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP + ) + export_resp = self.target.export( + KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC, ParameterEnum.DOMAIN_FP + ) self.assertTrue(export_resp.success) def test_transform(self): - self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) - self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP) + self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) + self.target.set( + KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP + ) self.target.generate(KeypairEnum.KEYPAIR_LOCAL) - export_privkey_resp1 = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE, - ParameterEnum.S) + export_privkey_resp1 = self.target.export( + KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE, ParameterEnum.S + ) privkey = int.from_bytes( - export_privkey_resp1.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S), "big") - transform_resp = self.target.transform(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE, - ParameterEnum.S, TransformationEnum.INCREMENT) + export_privkey_resp1.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S), + "big", + ) + transform_resp = self.target.transform( + KeypairEnum.KEYPAIR_LOCAL, + KeyEnum.PRIVATE, + ParameterEnum.S, + TransformationEnum.INCREMENT, + ) self.assertTrue(transform_resp.success) - export_privkey_resp2 = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE, - ParameterEnum.S) + export_privkey_resp2 = self.target.export( + KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE, ParameterEnum.S + ) privkey_new = int.from_bytes( - export_privkey_resp2.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S), "big") + export_privkey_resp2.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S), + "big", + ) self.assertEqual(privkey + 1, privkey_new) def test_ecdh(self): self.target.allocate_ka(KeyAgreementEnum.ALG_EC_SVDP_DH) - self.target.allocate(KeypairEnum.KEYPAIR_BOTH, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) - self.target.set(KeypairEnum.KEYPAIR_BOTH, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP) + self.target.allocate( + KeypairEnum.KEYPAIR_BOTH, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) + self.target.set( + KeypairEnum.KEYPAIR_BOTH, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP + ) self.target.generate(KeypairEnum.KEYPAIR_BOTH) - ecdh_resp = self.target.ecdh(KeypairEnum.KEYPAIR_LOCAL, KeypairEnum.KEYPAIR_REMOTE, True, - TransformationEnum.NONE, KeyAgreementEnum.ALG_EC_SVDP_DH) + ecdh_resp = self.target.ecdh( + KeypairEnum.KEYPAIR_LOCAL, + KeypairEnum.KEYPAIR_REMOTE, + True, + TransformationEnum.NONE, + KeyAgreementEnum.ALG_EC_SVDP_DH, + ) self.assertTrue(ecdh_resp.success) - export_public_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC, - ParameterEnum.W) - pubkey_bytes = export_public_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W) + export_public_resp = self.target.export( + KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC, ParameterEnum.W + ) + pubkey_bytes = export_public_resp.get_param( + KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W + ) pubkey = self.secp256r1.curve.decode_point(pubkey_bytes) - export_privkey_resp = self.target.export(KeypairEnum.KEYPAIR_REMOTE, KeyEnum.PRIVATE, - ParameterEnum.S) - privkey = Mod(int.from_bytes( - export_privkey_resp.get_param(KeypairEnum.KEYPAIR_REMOTE, ParameterEnum.S), "big"), - self.secp256r1.curve.prime) - pubkey_projective = pubkey.to_model(self.secp256r1_projective.curve.coordinate_model, self.secp256r1.curve) + export_privkey_resp = self.target.export( + KeypairEnum.KEYPAIR_REMOTE, KeyEnum.PRIVATE, ParameterEnum.S + ) + privkey = Mod( + int.from_bytes( + export_privkey_resp.get_param( + KeypairEnum.KEYPAIR_REMOTE, ParameterEnum.S + ), + "big", + ), + self.secp256r1.curve.prime, + ) + pubkey_projective = pubkey.to_model( + self.secp256r1_projective.curve.coordinate_model, self.secp256r1.curve + ) mult = LTRMultiplier( - self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], - self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"]) + self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], + self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"], + ) ecdh = ECDH_SHA1(mult, self.secp256r1_projective, pubkey_projective, privkey) expected = ecdh.perform() self.assertEqual(ecdh_resp.secret, expected) def test_ecdh_raw(self): self.target.allocate_ka(KeyAgreementEnum.ALG_EC_SVDP_DH) - self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) - self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP) + self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) + self.target.set( + KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP + ) self.target.generate(KeypairEnum.KEYPAIR_LOCAL) mult = LTRMultiplier( - self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], - self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"]) + self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], + self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"], + ) keygen = KeyGeneration(copy(mult), self.secp256r1_projective) priv, pubkey_projective = keygen.generate() - ecdh_resp = self.target.ecdh_direct(KeypairEnum.KEYPAIR_LOCAL, True, - TransformationEnum.NONE, - KeyAgreementEnum.ALG_EC_SVDP_DH, - bytes(pubkey_projective.to_affine())) + ecdh_resp = self.target.ecdh_direct( + KeypairEnum.KEYPAIR_LOCAL, + True, + TransformationEnum.NONE, + KeyAgreementEnum.ALG_EC_SVDP_DH, + bytes(pubkey_projective.to_affine()), + ) self.assertTrue(ecdh_resp.success) - export_privkey_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE, - ParameterEnum.S) - privkey = Mod(int.from_bytes( - export_privkey_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S), "big"), - self.secp256r1.curve.prime) + export_privkey_resp = self.target.export( + KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE, ParameterEnum.S + ) + privkey = Mod( + int.from_bytes( + export_privkey_resp.get_param( + KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S + ), + "big", + ), + self.secp256r1.curve.prime, + ) - ecdh = ECDH_SHA1(copy(mult), self.secp256r1_projective, pubkey_projective, privkey) + ecdh = ECDH_SHA1( + copy(mult), self.secp256r1_projective, pubkey_projective, privkey + ) expected = ecdh.perform() self.assertEqual(ecdh_resp.secret, expected) def test_ecdsa(self): self.target.allocate_sig(SignatureEnum.ALG_ECDSA_SHA) - self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) - self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP) + self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) + self.target.set( + KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP + ) self.target.generate(KeypairEnum.KEYPAIR_LOCAL) data = "Some text over here.".encode() - ecdsa_resp = self.target.ecdsa(KeypairEnum.KEYPAIR_LOCAL, True, SignatureEnum.ALG_ECDSA_SHA, - data) + ecdsa_resp = self.target.ecdsa( + KeypairEnum.KEYPAIR_LOCAL, True, SignatureEnum.ALG_ECDSA_SHA, data + ) self.assertTrue(ecdsa_resp.success) - export_public_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC, - ParameterEnum.W) - pubkey_bytes = export_public_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W) + export_public_resp = self.target.export( + KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC, ParameterEnum.W + ) + pubkey_bytes = export_public_resp.get_param( + KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W + ) pubkey = self.secp256r1.curve.decode_point(pubkey_bytes) - pubkey_projective = pubkey.to_model(self.secp256r1_projective.curve.coordinate_model, self.secp256r1.curve) + pubkey_projective = pubkey.to_model( + self.secp256r1_projective.curve.coordinate_model, self.secp256r1.curve + ) sig = SignatureResult.from_DER(ecdsa_resp.signature) mult = LTRMultiplier( - self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], - self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"]) - ecdsa = ECDSA_SHA1(copy(mult), self.secp256r1_projective, - self.secp256r1_projective.curve.coordinate_model.formulas[ - "add-2016-rcb"], - pubkey_projective) + self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], + self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"], + ) + ecdsa = ECDSA_SHA1( + copy(mult), + self.secp256r1_projective, + self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], + pubkey_projective, + ) self.assertTrue(ecdsa.verify_data(sig, data)) def test_ecdsa_sign(self): self.target.allocate_sig(SignatureEnum.ALG_ECDSA_SHA) - self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) - self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP) + self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) + self.target.set( + KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP + ) self.target.generate(KeypairEnum.KEYPAIR_LOCAL) data = "Some text over here.".encode() - ecdsa_resp = self.target.ecdsa_sign(KeypairEnum.KEYPAIR_LOCAL, True, - SignatureEnum.ALG_ECDSA_SHA, data) + ecdsa_resp = self.target.ecdsa_sign( + KeypairEnum.KEYPAIR_LOCAL, True, SignatureEnum.ALG_ECDSA_SHA, data + ) self.assertTrue(ecdsa_resp.success) - export_public_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC, - ParameterEnum.W) - pubkey_bytes = export_public_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W) + export_public_resp = self.target.export( + KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC, ParameterEnum.W + ) + pubkey_bytes = export_public_resp.get_param( + KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W + ) pubkey = self.secp256r1.curve.decode_point(pubkey_bytes) - pubkey_projective = pubkey.to_model(self.secp256r1_projective.curve.coordinate_model, self.secp256r1.curve) + pubkey_projective = pubkey.to_model( + self.secp256r1_projective.curve.coordinate_model, self.secp256r1.curve + ) sig = SignatureResult.from_DER(ecdsa_resp.signature) mult = LTRMultiplier( - self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], - self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"]) - ecdsa = ECDSA_SHA1(copy(mult), self.secp256r1_projective, - self.secp256r1_projective.curve.coordinate_model.formulas[ - "add-2016-rcb"], - pubkey_projective) + self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], + self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"], + ) + ecdsa = ECDSA_SHA1( + copy(mult), + self.secp256r1_projective, + self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], + pubkey_projective, + ) self.assertTrue(ecdsa.verify_data(sig, data)) def test_ecdsa_verify(self): self.target.allocate_sig(SignatureEnum.ALG_ECDSA_SHA) - self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256, - KeyClassEnum.ALG_EC_FP) - self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP) + self.target.allocate( + KeypairEnum.KEYPAIR_LOCAL, + KeyBuildEnum.BUILD_KEYPAIR, + 256, + KeyClassEnum.ALG_EC_FP, + ) + self.target.set( + KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP + ) mult = LTRMultiplier( - self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], - self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"]) + self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], + self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"], + ) keygen = KeyGeneration(copy(mult), self.secp256r1_projective) priv, pubkey_projective = keygen.generate() - self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.external, ParameterEnum.W, - ECTesterTarget.encode_parameters(ParameterEnum.W, - pubkey_projective.to_affine())) - ecdsa = ECDSA_SHA1(copy(mult), self.secp256r1_projective, - self.secp256r1_projective.curve.coordinate_model.formulas[ - "add-2016-rcb"], - pubkey_projective, - priv) + self.target.set( + KeypairEnum.KEYPAIR_LOCAL, + CurveEnum.external, + ParameterEnum.W, + ECTesterTarget.encode_parameters( + ParameterEnum.W, pubkey_projective.to_affine() + ), + ) + ecdsa = ECDSA_SHA1( + copy(mult), + self.secp256r1_projective, + self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"], + pubkey_projective, + priv, + ) data = "Some text over here.".encode() sig = ecdsa.sign_data(data) - ecdsa_resp = self.target.ecdsa_verify(KeypairEnum.KEYPAIR_LOCAL, - SignatureEnum.ALG_ECDSA_SHA, sig.to_DER(), data) + ecdsa_resp = self.target.ecdsa_verify( + KeypairEnum.KEYPAIR_LOCAL, SignatureEnum.ALG_ECDSA_SHA, sig.to_DER(), data + ) self.assertTrue(ecdsa_resp.success) diff --git a/test/sca/test_test.py b/test/sca/test_test.py index 5a20f70..7b4f346 100644 --- a/test/sca/test_test.py +++ b/test/sca/test_test.py @@ -6,7 +6,6 @@ from pyecsca.sca import Trace, welch_ttest, student_ttest, ks_test class TTestTests(TestCase): - def setUp(self): self.a = Trace(np.array([20, 80], dtype=np.dtype("i1"))) self.b = Trace(np.array([30, 42], dtype=np.dtype("i1"))) @@ -15,9 +14,15 @@ class TTestTests(TestCase): def test_welch_ttest(self): self.assertIsNotNone(welch_ttest([self.a, self.b], [self.c, self.d])) - a = Trace(np.array([19.8, 20.4, 19.6, 17.8, 18.5, 18.9, 18.3, 18.9, 19.5, 22.0])) - b = Trace(np.array([28.2, 26.6, 20.1, 23.3, 25.2, 22.1, 17.7, 27.6, 20.6, 13.7])) - c = Trace(np.array([20.2, 21.6, 27.1, 13.3, 24.2, 20.1, 11.7, 25.6, 26.6, 21.4])) + a = Trace( + np.array([19.8, 20.4, 19.6, 17.8, 18.5, 18.9, 18.3, 18.9, 19.5, 22.0]) + ) + b = Trace( + np.array([28.2, 26.6, 20.1, 23.3, 25.2, 22.1, 17.7, 27.6, 20.6, 13.7]) + ) + c = Trace( + np.array([20.2, 21.6, 27.1, 13.3, 24.2, 20.1, 11.7, 25.6, 26.6, 21.4]) + ) result = welch_ttest([a, b], [b, c], dof=True, p_value=True) self.assertIsNotNone(result) @@ -28,7 +33,6 @@ class TTestTests(TestCase): class KolmogorovSmirnovTests(TestCase): - def test_ks_test(self): self.assertIsNone(ks_test([], [])) diff --git a/test/sca/test_trace.py b/test/sca/test_trace.py index 91b640d..93baa89 100644 --- a/test/sca/test_trace.py +++ b/test/sca/test_trace.py @@ -4,7 +4,6 @@ from pyecsca.sca import Trace class TraceTests(TestCase): - def test_basic(self): trace = Trace(np.array([10, 15, 24], dtype=np.dtype("i1"))) self.assertIsNotNone(trace) diff --git a/test/sca/test_traceset.py b/test/sca/test_traceset.py index 7adff41..515790e 100644 --- a/test/sca/test_traceset.py +++ b/test/sca/test_traceset.py @@ -6,17 +6,24 @@ from unittest import TestCase import numpy as np -from pyecsca.sca import (TraceSet, InspectorTraceSet, ChipWhispererTraceSet, PickleTraceSet, - HDF5TraceSet, Trace) +from pyecsca.sca import ( + TraceSet, + InspectorTraceSet, + ChipWhispererTraceSet, + PickleTraceSet, + HDF5TraceSet, + Trace, +) -EXAMPLE_TRACES = [Trace(np.array([20, 40, 50, 50, 10], dtype=np.dtype("i1")), {"something": 5}), - Trace(np.array([1, 2, 3, 4, 5], dtype=np.dtype("i1"))), - Trace(np.array([6, 7, 8, 9, 10], dtype=np.dtype("i1")))] +EXAMPLE_TRACES = [ + Trace(np.array([20, 40, 50, 50, 10], dtype=np.dtype("i1")), {"something": 5}), + Trace(np.array([1, 2, 3, 4, 5], dtype=np.dtype("i1"))), + Trace(np.array([6, 7, 8, 9, 10], dtype=np.dtype("i1"))), +] EXAMPLE_KWARGS = {"num_traces": 3, "thingy": "abc"} class TraceSetTests(TestCase): - def test_create(self): self.assertIsNotNone(TraceSet()) self.assertIsNotNone(InspectorTraceSet()) @@ -26,7 +33,6 @@ class TraceSetTests(TestCase): class InspectorTraceSetTests(TestCase): - def test_load_fname(self): result = InspectorTraceSet.read("test/data/example.trs") self.assertIsNotNone(result) @@ -55,7 +61,6 @@ class InspectorTraceSetTests(TestCase): class ChipWhispererTraceSetTests(TestCase): - def test_load_fname(self): result = ChipWhispererTraceSet.read("test/data/config_chipwhisperer_.cfg") self.assertIsNotNone(result) @@ -63,7 +68,6 @@ class ChipWhispererTraceSetTests(TestCase): class PickleTraceSetTests(TestCase): - def test_load_fname(self): result = PickleTraceSet.read("test/data/test.pickle") self.assertIsNotNone(result) @@ -82,7 +86,6 @@ class PickleTraceSetTests(TestCase): class HDF5TraceSetTests(TestCase): - def test_load_fname(self): result = HDF5TraceSet.read("test/data/test.h5") self.assertIsNotNone(result) @@ -97,8 +100,12 @@ class HDF5TraceSetTests(TestCase): shutil.copy("test/data/test.h5", path) trace_set = HDF5TraceSet.inplace(path) self.assertIsNotNone(trace_set) - test_trace = Trace(np.array([6, 7], dtype=np.dtype("i1")), meta={"thing": "ring"}) - other_trace = Trace(np.array([15, 7], dtype=np.dtype("i1")), meta={"a": "b"}) + test_trace = Trace( + np.array([6, 7], dtype=np.dtype("i1")), meta={"thing": "ring"} + ) + other_trace = Trace( + np.array([15, 7], dtype=np.dtype("i1")), meta={"a": "b"} + ) trace_set.append(test_trace) self.assertEqual(len(trace_set), 4) trace_set.append(other_trace) diff --git a/test/sca/utils.py b/test/sca/utils.py index 0a996cd..b00015b 100644 --- a/test/sca/utils.py +++ b/test/sca/utils.py @@ -24,7 +24,6 @@ cases: Dict[str, int] = {} class Plottable(TestCase): - def get_dir(self): if split(getcwd())[1] == "test": directory = "plots" |
