diff options
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | pyecsca/ec/context.py | 173 | ||||
| -rw-r--r-- | pyecsca/ec/formula.py | 123 | ||||
| -rw-r--r-- | pyecsca/ec/key_agreement.py | 86 | ||||
| -rw-r--r-- | pyecsca/ec/key_generation.py | 44 | ||||
| -rw-r--r-- | pyecsca/ec/mod.py | 20 | ||||
| -rw-r--r-- | pyecsca/ec/mult.py | 301 | ||||
| -rw-r--r-- | pyecsca/ec/op.py | 11 | ||||
| -rw-r--r-- | pyecsca/ec/point.py | 88 | ||||
| -rw-r--r-- | pyecsca/ec/signature.py | 169 | ||||
| -rw-r--r-- | test/ec/test_context.py | 30 | ||||
| -rw-r--r-- | test/ec/test_formula.py | 35 | ||||
| -rw-r--r-- | test/ec/test_key_agreement.py | 12 | ||||
| -rw-r--r-- | test/ec/test_key_generation.py | 26 | ||||
| -rw-r--r-- | test/ec/test_op.py | 9 | ||||
| -rw-r--r-- | test/ec/test_signature.py | 13 |
16 files changed, 714 insertions, 430 deletions
@@ -1,5 +1,5 @@ -EC_TESTS = ec.test_context ec.test_curve ec.test_curves ec.test_params ec.test_key_agreement ec.test_mod ec.test_model \ -ec.test_mult ec.test_naf ec.test_op ec.test_point ec.test_signature +EC_TESTS = ec.test_context ec.test_curve ec.test_curves ec.test_formula ec.test_params ec.test_key_agreement ec.test_key_generation \ +ec.test_mod ec.test_model ec.test_mult ec.test_naf ec.test_op ec.test_point ec.test_signature SCA_TESTS = sca.test_align sca.test_combine sca.test_edit sca.test_filter sca.test_match sca.test_process \ sca.test_sampling sca.test_test sca.test_trace sca.test_traceset diff --git a/pyecsca/ec/context.py b/pyecsca/ec/context.py index 8f797a8..d6f56af 100644 --- a/pyecsca/ec/context.py +++ b/pyecsca/ec/context.py @@ -1,85 +1,45 @@ -import ast from abc import ABCMeta, abstractmethod +from collections import OrderedDict from contextvars import ContextVar, Token from copy import deepcopy -from typing import List, Tuple, Optional, Union, MutableMapping, Any, ContextManager +from typing import List, Optional, ContextManager, Any from public import public -from .formula import Formula -from .mod import Mod -from .op import CodeOp, OpType -from .point import Point - - -@public -class OpResult(object): - """A result of an operation.""" - parents: Tuple - op: OpType - name: str - value: Mod - - def __init__(self, name: str, value: Mod, op: OpType, *parents: Any): - self.parents = tuple(parents) - self.name = name - self.value = value - self.op = op - - def __str__(self): - return self.name - - def __repr__(self): - char = self.op.op_str - parents = char.join(str(parent) for parent in self.parents) - return f"{self.name} = {parents}" - @public class Action(object): - """An execution of some operations with inputs and outputs.""" - inputs: MutableMapping[str, Mod] - input_points: List[Point] - intermediates: MutableMapping[str, OpResult] - outputs: MutableMapping[str, OpResult] - output_points: List[Point] + """An Action.""" + inside: bool - def __init__(self, *points: Point, **inputs: Mod): - self.inputs = inputs - self.intermediates = {} - self.outputs = {} - self.input_points = list(points) - self.output_points = [] + def __init__(self): + self.inside = False - def add_operation(self, op: CodeOp, value: Mod): - parents: List[Union[Mod, OpResult]] = [] - for parent in {*op.variables, *op.parameters}: - if parent in self.intermediates: - parents.append(self.intermediates[parent]) - elif parent in self.inputs: - parents.append(self.inputs[parent]) - self.intermediates[op.result] = OpResult(op.result, value, op.operator, *parents) + def __enter__(self): + getcontext().enter_action(self) + self.inside = True + return self - def add_result(self, point: Point, **outputs: Mod): - for k in outputs: - self.outputs[k] = self.intermediates[k] - self.output_points.append(point) + def __exit__(self, exc_type, exc_val, exc_tb): + getcontext().exit_action(self) + self.inside = False - def __repr__(self): - return f"{self.__class__.__name__}({self.input_points}) = {self.output_points}" -@public -class FormulaAction(Action): - """An execution of a formula, on some input points and parameters, with some outputs.""" - formula: Formula - def __init__(self, formula: Formula, *points: Point, **inputs: Mod): - super().__init__(*points, **inputs) - self.formula = formula - def __repr__(self): - return f"{self.__class__.__name__}({self.formula}, {self.input_points}) = {self.output_points}" +class Tree(OrderedDict): + + def walk_get(self, path: List) -> Any: + if len(path) == 0: + return self + value = self[path[0]] + if isinstance(value, Tree): + return value.walk_get(path[1:]) + elif len(path) == 1: + return value + else: + raise ValueError @public @@ -87,92 +47,49 @@ class Context(object): __metaclass__ = ABCMeta @abstractmethod - def _log_formula(self, formula: Formula, *points: Point, **inputs: Mod): - ... - - @abstractmethod - def _log_operation(self, op: CodeOp, value: Mod): + def enter_action(self, action: Action): ... @abstractmethod - def _log_result(self, point: Point, **outputs: Mod): + def exit_action(self, action: Action): ... - def _execute(self, formula: Formula, *points: Point, **params: Mod) -> Tuple[Point, ...]: - if len(points) != formula.num_inputs: - raise ValueError(f"Wrong number of inputs for {formula}.") - coords = {} - for i, point in enumerate(points): - if point.coordinate_model != formula.coordinate_model: - raise ValueError(f"Wrong coordinate model of point {point}.") - for coord, value in point.coords.items(): - coords[coord + str(i + 1)] = value - locals = {**coords, **params} - self._log_formula(formula, *points, **locals) - for op in formula.code: - op_result = op(**locals) - self._log_operation(op, op_result) - locals[op.result] = op_result - result = [] - for i in range(formula.num_outputs): - ind = str(i + formula.output_index) - resulting = {} - full_resulting = {} - for variable in formula.coordinate_model.variables: - full_variable = variable + ind - resulting[variable] = locals[full_variable] - full_resulting[full_variable] = locals[full_variable] - point = Point(formula.coordinate_model, **resulting) - - self._log_result(point, **full_resulting) - result.append(point) - return tuple(result) - - def execute(self, formula: Formula, *points: Point, **params: Mod) -> Tuple[Point, ...]: - """ - Execute a formula. - - :param formula: Formula to execute. - :param points: Points to pass into the formula. - :param params: Parameters of the curve. - :return: The resulting point(s). - """ - return self._execute(formula, *points, **params) - def __str__(self): return self.__class__.__name__ @public class NullContext(Context): - """A context that does not trace any operations.""" - - def _log_formula(self, formula: Formula, *points: Point, **inputs: Mod): - pass + """A context that does not trace any actions.""" - def _log_operation(self, op: CodeOp, value: Mod): + def enter_action(self, action: Action): pass - def _log_result(self, point: Point, **outputs: Mod): + def exit_action(self, action: Action): pass @public class DefaultContext(Context): - """A context that traces executions of formulas.""" - actions: List[FormulaAction] + """A context that traces executions of actions.""" + actions: Tree + current: List[Action] - def __init__(self): - self.actions = [] + def enter_action(self, action: Action): + self.actions.walk_get(self.current)[action] = Tree() + self.current.append(action) - def _log_formula(self, formula: Formula, *points: Point, **inputs: Mod): - self.actions.append(FormulaAction(formula, *points, **inputs)) + def exit_action(self, action: Action): + if self.current[-1] != action: + raise ValueError + self.current.pop() - def _log_operation(self, op: CodeOp, value: Mod): - self.actions[-1].add_operation(op, value) + def __init__(self): + self.actions = Tree() + self.current = [] - def _log_result(self, point: Point, **outputs: Mod): - self.actions[-1].add_result(point, **outputs) + def __repr__(self): + return f"{self.__class__.__name__}({self.actions}, current={self.current})" _actual_context: ContextVar[Context] = ContextVar("operational_context", default=NullContext()) diff --git a/pyecsca/ec/formula.py b/pyecsca/ec/formula.py index aeed4a6..a3875ec 100644 --- a/pyecsca/ec/formula.py +++ b/pyecsca/ec/formula.py @@ -1,11 +1,74 @@ from ast import parse, Expression, Mult, Add, Sub, Pow, Div from itertools import product -from typing import List, Set, Any, ClassVar, MutableMapping +from typing import List, Set, Any, ClassVar, MutableMapping, Tuple, Union from pkg_resources import resource_stream from public import public -from .op import CodeOp +from .context import Action +from .mod import Mod +from .op import CodeOp, OpType + + +@public +class OpResult(object): + """A result of an operation.""" + parents: Tuple + op: OpType + name: str + value: Mod + + def __init__(self, name: str, value: Mod, op: OpType, *parents: Any): + self.parents = tuple(parents) + self.name = name + self.value = value + self.op = op + + def __str__(self): + return self.name + + def __repr__(self): + char = self.op.op_str + parents = char.join(str(parent) for parent in self.parents) + return f"{self.name} = {parents}" + + +@public +class FormulaAction(Action): + """An execution of a formula, on some input points and parameters, with some outputs.""" + formula: "Formula" + inputs: MutableMapping[str, Mod] + input_points: List[Any] + intermediates: MutableMapping[str, OpResult] + outputs: MutableMapping[str, OpResult] + output_points: List[Any] + + def __init__(self, formula: "Formula", *points: Any, + **inputs: Mod): + super().__init__() + self.formula = formula + self.inputs = inputs + self.intermediates = {} + self.outputs = {} + self.input_points = list(points) + self.output_points = [] + + def add_operation(self, op: CodeOp, value: Mod): + parents: List[Union[Mod, OpResult]] = [] + for parent in {*op.variables, *op.parameters}: + if parent in self.intermediates: + parents.append(self.intermediates[parent]) + elif parent in self.inputs: + parents.append(self.inputs[parent]) + self.intermediates[op.result] = OpResult(op.result, value, op.operator, *parents) + + def add_result(self, point: Any, **outputs: Mod): + for k in outputs: + self.outputs[k] = self.intermediates[k] + self.output_points.append(point) + + def __repr__(self): + return f"{self.__class__.__name__}({self.formula}, {self.input_points}) = {self.output_points}" class Formula(object): @@ -20,6 +83,44 @@ class Formula(object): num_inputs: ClassVar[int] num_outputs: ClassVar[int] + def __call__(self, *points: Any, **params: Mod) -> Tuple[Any, ...]: + """ + Execute a formula. + + :param points: Points to pass into the formula. + :param params: Parameters of the curve. + :return: The resulting point(s). + """ + from .point import Point + if len(points) != self.num_inputs: + raise ValueError(f"Wrong number of inputs for {self}.") + coords = {} + for i, point in enumerate(points): + if point.coordinate_model != self.coordinate_model: + raise ValueError(f"Wrong coordinate model of point {point}.") + for coord, value in point.coords.items(): + coords[coord + str(i + 1)] = value + locals = {**coords, **params} + with FormulaAction(self, *points, **locals) as action: + for op in self.code: + op_result = op(**locals) + action.add_operation(op, op_result) + locals[op.result] = op_result + result = [] + for i in range(self.num_outputs): + ind = str(i + self.output_index) + resulting = {} + full_resulting = {} + for variable in self.coordinate_model.variables: + full_variable = variable + ind + resulting[variable] = locals[full_variable] + full_resulting[full_variable] = locals[full_variable] + point = Point(self.coordinate_model, **resulting) + + action.add_result(point, **full_resulting) + result.append(point) + return tuple(result) + def __repr__(self): return f"{self.__class__.__name__}({self.name} for {self.coordinate_model})" @@ -51,22 +152,32 @@ class Formula(object): @property def num_multiplications(self) -> int: """Number of multiplications.""" - return len(list(filter(lambda op: isinstance(op.operator, Mult), self.code))) + return len(list(filter(lambda op: op.operator == OpType.Mult, self.code))) + + @property + def num_divisions(self) -> int: + """Number of divisions.""" + return len(list(filter(lambda op: op.operator == OpType.Div, self.code))) @property def num_inversions(self) -> int: """Number of inversions.""" - return len(list(filter(lambda op: isinstance(op.operator, Div), self.code))) + return len(list(filter(lambda op: op.operator == OpType.Inv, self.code))) + + @property + def num_powers(self) -> int: + """Number of powers.""" + return len(list(filter(lambda op: op.operator == OpType.Pow, self.code))) @property def num_squarings(self) -> int: """Number of squarings.""" - return len(list(filter(lambda op: isinstance(op.operator, Pow), self.code))) + return len(list(filter(lambda op: op.operator == OpType.Sqr, self.code))) @property def num_addsubs(self) -> int: """Number of additions and subtractions.""" - return len(list(filter(lambda op: isinstance(op.operator, (Add, Sub)), self.code))) + return len(list(filter(lambda op: op.operator == OpType.Add or op.operator == OpType.Sub, self.code))) class EFDFormula(Formula): diff --git a/pyecsca/ec/key_agreement.py b/pyecsca/ec/key_agreement.py index 3071ffb..6d6728f 100644 --- a/pyecsca/ec/key_agreement.py +++ b/pyecsca/ec/key_agreement.py @@ -3,28 +3,51 @@ from typing import Optional, Any from public import public -from .params import DomainParameters +from .context import Action +from .mod import Mod from .mult import ScalarMultiplier +from .params import DomainParameters from .point import Point @public +class ECDHAction(Action): + """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): + super().__init__() + self.params = params + self.hash_algo = hash_algo + self.privkey = privkey + self.pubkey = pubkey + + def __repr__(self): + return f"{self.__class__.__name__}({self.params}, {self.hash_algo}, {self.privkey}, {self.pubkey})" + + +@public class KeyAgreement(object): """An EC based key agreement primitive. (ECDH)""" mult: ScalarMultiplier - group: DomainParameters + params: DomainParameters pubkey: Point - privkey: int + privkey: Mod hash_algo: Optional[Any] - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, pubkey: Point, privkey: int, + def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, privkey: Mod, hash_algo: Optional[Any] = None): self.mult = mult - self.group = group + self.params = params self.pubkey = pubkey self.privkey = privkey self.hash_algo = hash_algo - self.mult.init(self.group, self.pubkey) + self.mult.init(self.params, self.pubkey) def perform_raw(self) -> Point: """ @@ -32,8 +55,8 @@ class KeyAgreement(object): :return: The shared point. """ - point = self.mult.multiply(self.privkey) - return point.to_affine() # TODO: This conversion should be somehow added to the context + point = self.mult.multiply(int(self.privkey)) + return point.to_affine() def perform(self) -> bytes: """ @@ -41,59 +64,66 @@ class KeyAgreement(object): :return: The shared secret. """ - affine_point = self.perform_raw() - x = int(affine_point.x) - p = self.group.curve.prime - n = (p.bit_length() + 7) // 8 - result = x.to_bytes(n, byteorder="big") - if self.hash_algo is not None: - result = self.hash_algo(result).digest() - return result + with ECDHAction(self.params, self.hash_algo, self.privkey, self.pubkey): + affine_point = self.perform_raw() + x = int(affine_point.x) + p = self.params.curve.prime + n = (p.bit_length() + 7) // 8 + result = x.to_bytes(n, byteorder="big") + if self.hash_algo is not None: + result = self.hash_algo(result).digest() + return result @public class ECDH_NONE(KeyAgreement): """Raw x-coordinate ECDH.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, pubkey: Point, privkey: int): - super().__init__(mult, group, pubkey, privkey) + def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, + privkey: Mod): + super().__init__(mult, params, pubkey, privkey) @public class ECDH_SHA1(KeyAgreement): """ECDH with SHA1 of x-coordinate.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, pubkey: Point, privkey: int): - super().__init__(mult, group, pubkey, privkey, hashlib.sha1) + def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, + privkey: Mod): + super().__init__(mult, params, pubkey, privkey, hashlib.sha1) @public class ECDH_SHA224(KeyAgreement): """ECDH with SHA224 of x-coordinate.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, pubkey: Point, privkey: int): - super().__init__(mult, group, pubkey, privkey, hashlib.sha224) + def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, + privkey: Mod): + super().__init__(mult, params, pubkey, privkey, hashlib.sha224) @public class ECDH_SHA256(KeyAgreement): """ECDH with SHA256 of x-coordinate.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, pubkey: Point, privkey: int): - super().__init__(mult, group, pubkey, privkey, hashlib.sha256) + def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, + privkey: Mod): + super().__init__(mult, params, pubkey, privkey, hashlib.sha256) @public class ECDH_SHA384(KeyAgreement): """ECDH with SHA384 of x-coordinate.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, pubkey: Point, privkey: int): - super().__init__(mult, group, pubkey, privkey, hashlib.sha384) + def __init__(self, mult: ScalarMultiplier, params: DomainParameters, pubkey: Point, + privkey: Mod): + super().__init__(mult, params, pubkey, privkey, hashlib.sha384) @public class ECDH_SHA512(KeyAgreement): """ECDH with SHA512 of x-coordinate.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, pubkey: Point, privkey: int): - super().__init__(mult, group, pubkey, privkey, hashlib.sha512) + 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 new file mode 100644 index 0000000..0813dbd --- /dev/null +++ b/pyecsca/ec/key_generation.py @@ -0,0 +1,44 @@ +from typing import Tuple + +from public import public + +from .context import Action +from .mod import Mod +from .mult import ScalarMultiplier +from .params import DomainParameters +from .point import Point + + +@public +class KeygenAction(Action): + """A key generation.""" + params: DomainParameters + + def __init__(self, params: DomainParameters): + super().__init__() + self.params = params + + def __repr__(self): + return f"{self.__class__.__name__}({self.params})" + + +@public +class KeyGeneration(object): + """Key generator.""" + mult: ScalarMultiplier + params: DomainParameters + affine: bool + + def __init__(self, mult: ScalarMultiplier, params: DomainParameters, affine: bool = False): + self.mult = mult + self.params = params + self.mult.init(self.params, self.params.generator) + self.affine = affine + + def generate(self) -> Tuple[Mod, Point]: + with KeygenAction(self.params): + privkey = Mod.random(self.params.order) + pubkey = self.mult.multiply(privkey.x) + if self.affine: + pubkey = pubkey.to_affine() + return privkey, pubkey diff --git a/pyecsca/ec/mod.py b/pyecsca/ec/mod.py index a142b71..2b16a07 100644 --- a/pyecsca/ec/mod.py +++ b/pyecsca/ec/mod.py @@ -1,7 +1,10 @@ +import secrets from functools import wraps from public import public +from .context import Action + @public def gcd(a, b): @@ -48,6 +51,18 @@ def check(func): return method +@public +class RandomModAction(Action): + """A random sampling from Z_n.""" + order: int + + def __init__(self, order: int): + super().__init__() + self.order = order + + def __repr__(self): + return f"{self.__class__.__name__}({self.order:x})" + @public class Mod(object): @@ -120,6 +135,11 @@ class Mod(object): q, r = divmod(self.x, divisor.x) return Mod(q, self.n), Mod(r, self.n) + @staticmethod + def random(n: int): + with RandomModAction(n): + return Mod(secrets.randbelow(n), n) + def __int__(self): return self.x diff --git a/pyecsca/ec/mult.py b/pyecsca/ec/mult.py index 4f937fa..4c5b6d9 100644 --- a/pyecsca/ec/mult.py +++ b/pyecsca/ec/mult.py @@ -1,16 +1,31 @@ from copy import copy -from typing import Mapping, Tuple, Optional, MutableMapping, Union, ClassVar, Set, Type +from typing import Mapping, Tuple, Optional, MutableMapping, ClassVar, Set, Type from public import public -from .context import getcontext +from .context import Action from .formula import (Formula, AdditionFormula, DoublingFormula, DifferentialAdditionFormula, ScalingFormula, LadderFormula, NegationFormula) -from .params import DomainParameters from .naf import naf, wnaf +from .params import DomainParameters from .point import Point +@public +class ScalarMultiplicationAction(Action): + """A scalar multiplication of a point on a curve by a scalar.""" + point: Point + scalar: int + + def __init__(self, point: Point, scalar: int): + super().__init__() + self.point = point + self.scalar = scalar + + def __repr__(self): + return f"{self.__class__.__name__}({self.point}, {self.scalar})" + + class ScalarMultiplier(object): """ A scalar multiplication algorithm. @@ -42,9 +57,7 @@ class ScalarMultiplier(object): return copy(other) if other == self._group.neutral: return copy(one) - return \ - getcontext().execute(self.formulas["add"], one, other, **self._group.curve.parameters)[ - 0] + return self.formulas["add"](one, other, **self._group.curve.parameters)[0] def _dbl(self, point: Point) -> Point: if "dbl" not in self.formulas: @@ -52,12 +65,12 @@ class ScalarMultiplier(object): if self.short_circuit: if point == self._group.neutral: return copy(point) - return getcontext().execute(self.formulas["dbl"], point, **self._group.curve.parameters)[0] + return self.formulas["dbl"](point, **self._group.curve.parameters)[0] def _scl(self, point: Point) -> Point: if "scl" not in self.formulas: raise NotImplementedError - return getcontext().execute(self.formulas["scl"], point, **self._group.curve.parameters)[0] + return self.formulas["scl"](point, **self._group.curve.parameters)[0] def _ladd(self, start: Point, to_dbl: Point, to_add: Point) -> Tuple[Point, ...]: if "ladd" not in self.formulas: @@ -67,8 +80,7 @@ class ScalarMultiplier(object): return to_dbl, to_add if to_add == self._group.neutral: return self._dbl(to_dbl), to_dbl - return getcontext().execute(self.formulas["ladd"], start, to_dbl, to_add, - **self._group.curve.parameters) + return self.formulas["ladd"](start, to_dbl, to_add, **self._group.curve.parameters) def _dadd(self, start: Point, one: Point, other: Point) -> Point: if "dadd" not in self.formulas: @@ -78,13 +90,12 @@ class ScalarMultiplier(object): return copy(other) if other == self._group.neutral: return copy(one) - return getcontext().execute(self.formulas["dadd"], start, one, other, - **self._group.curve.parameters)[0] + return self.formulas["dadd"](start, one, other, **self._group.curve.parameters)[0] def _neg(self, point: Point) -> Point: if "neg" not in self.formulas: raise NotImplementedError - return getcontext().execute(self.formulas["neg"], point, **self._group.curve.parameters)[0] + return self.formulas["neg"](point, **self._group.curve.parameters)[0] def init(self, group: DomainParameters, point: Point): """Initialize the scalar multiplier with a group and a point.""" @@ -122,25 +133,26 @@ class LTRMultiplier(ScalarMultiplier): def multiply(self, scalar: int) -> Point: if not self._initialized: raise ValueError("ScalaMultiplier not initialized.") - if scalar == 0: - return copy(self._group.neutral) - if self.complete: - q = self._point - r = copy(self._group.neutral) - top = self._group.order.bit_length() - 1 - else: - q = self._dbl(self._point) - r = copy(self._point) - top = scalar.bit_length() - 2 - for i in range(top, -1, -1): - r = self._dbl(r) - if scalar & (1 << i) != 0: - r = self._add(r, q) - elif self.always: - self._add(r, q) - if "scl" in self.formulas: - r = self._scl(r) - return r + with ScalarMultiplicationAction(self._point, scalar): + if scalar == 0: + return copy(self._group.neutral) + if self.complete: + q = self._point + r = copy(self._group.neutral) + top = self._group.order.bit_length() - 1 + else: + q = self._dbl(self._point) + r = copy(self._point) + top = scalar.bit_length() - 2 + for i in range(top, -1, -1): + r = self._dbl(r) + if scalar & (1 << i) != 0: + r = self._add(r, q) + elif self.always: + self._add(r, q) + if "scl" in self.formulas: + r = self._scl(r) + return r @public @@ -162,20 +174,21 @@ class RTLMultiplier(ScalarMultiplier): def multiply(self, scalar: int) -> Point: if not self._initialized: raise ValueError("ScalaMultiplier not initialized.") - if scalar == 0: - return copy(self._group.neutral) - q = self._point - r = copy(self._group.neutral) - while scalar > 0: - if scalar & 1 != 0: - r = self._add(r, q) - elif self.always: - self._add(r, q) - q = self._dbl(q) - scalar >>= 1 - if "scl" in self.formulas: - r = self._scl(r) - return r + with ScalarMultiplicationAction(self._point, scalar): + if scalar == 0: + return copy(self._group.neutral) + q = self._point + r = copy(self._group.neutral) + while scalar > 0: + if scalar & 1 != 0: + r = self._add(r, q) + elif self.always: + self._add(r, q) + q = self._dbl(q) + scalar >>= 1 + if "scl" in self.formulas: + r = self._scl(r) + return r class CoronMultiplier(ScalarMultiplier): @@ -196,18 +209,19 @@ class CoronMultiplier(ScalarMultiplier): def multiply(self, scalar: int) -> Point: if not self._initialized: raise ValueError("ScalaMultiplier not initialized.") - if scalar == 0: - return copy(self._group.neutral) - q = self._point - p0 = copy(q) - for i in range(scalar.bit_length() - 2, -1, -1): - p0 = self._dbl(p0) - p1 = self._add(p0, q) - if scalar & (1 << i) != 0: - p0 = p1 - if "scl" in self.formulas: - p0 = self._scl(p0) - return p0 + with ScalarMultiplicationAction(self._point, scalar): + if scalar == 0: + return copy(self._group.neutral) + q = self._point + p0 = copy(q) + for i in range(scalar.bit_length() - 2, -1, -1): + p0 = self._dbl(p0) + p1 = self._add(p0, q) + if scalar & (1 << i) != 0: + p0 = p1 + if "scl" in self.formulas: + p0 = self._scl(p0) + return p0 @public @@ -229,25 +243,26 @@ class LadderMultiplier(ScalarMultiplier): def multiply(self, scalar: int) -> Point: if not self._initialized: raise ValueError("ScalaMultiplier not initialized.") - if scalar == 0: - return copy(self._group.neutral) - q = self._point - if self.complete: - p0 = copy(self._group.neutral) - p1 = self._point - top = self._group.order.bit_length() - 1 - else: - p0 = copy(q) - p1 = self._dbl(q) - top = scalar.bit_length() - 2 - for i in range(top, -1, -1): - if scalar & (1 << i) == 0: - p0, p1 = self._ladd(q, p0, p1) + with ScalarMultiplicationAction(self._point, scalar): + if scalar == 0: + return copy(self._group.neutral) + q = self._point + if self.complete: + p0 = copy(self._group.neutral) + p1 = self._point + top = self._group.order.bit_length() - 1 else: - p1, p0 = self._ladd(q, p1, p0) - if "scl" in self.formulas: - p0 = self._scl(p0) - return p0 + p0 = copy(q) + p1 = self._dbl(q) + top = scalar.bit_length() - 2 + for i in range(top, -1, -1): + if scalar & (1 << i) == 0: + p0, p1 = self._ladd(q, p0, p1) + else: + p1, p0 = self._ladd(q, p1, p0) + if "scl" in self.formulas: + p0 = self._scl(p0) + return p0 @public @@ -267,24 +282,25 @@ class SimpleLadderMultiplier(ScalarMultiplier): def multiply(self, scalar: int) -> Point: if not self._initialized: raise ValueError("ScalaMultiplier not initialized.") - if scalar == 0: - return copy(self._group.neutral) - if self.complete: - top = self._group.order.bit_length() - 1 - else: - top = scalar.bit_length() - 1 - p0 = copy(self._group.neutral) - p1 = copy(self._point) - for i in range(top, -1, -1): - if scalar & (1 << i) == 0: - p1 = self._add(p0, p1) - p0 = self._dbl(p0) + with ScalarMultiplicationAction(self._point, scalar): + if scalar == 0: + return copy(self._group.neutral) + if self.complete: + top = self._group.order.bit_length() - 1 else: - p0 = self._add(p0, p1) - p1 = self._dbl(p1) - if "scl" in self.formulas: - p0 = self._scl(p0) - return p0 + top = scalar.bit_length() - 1 + p0 = copy(self._group.neutral) + p1 = copy(self._point) + for i in range(top, -1, -1): + if scalar & (1 << i) == 0: + p1 = self._add(p0, p1) + p0 = self._dbl(p0) + else: + p0 = self._add(p0, p1) + p1 = self._dbl(p1) + if "scl" in self.formulas: + p0 = self._scl(p0) + return p0 @public @@ -304,25 +320,26 @@ class DifferentialLadderMultiplier(ScalarMultiplier): def multiply(self, scalar: int) -> Point: if not self._initialized: raise ValueError("ScalaMultiplier not initialized.") - if scalar == 0: - return copy(self._group.neutral) - if self.complete: - top = self._group.order.bit_length() - 1 - else: - top = scalar.bit_length() - 1 - q = self._point - p0 = copy(self._group.neutral) - p1 = copy(q) - for i in range(top, -1, -1): - if scalar & (1 << i) == 0: - p1 = self._dadd(q, p0, p1) - p0 = self._dbl(p0) + with ScalarMultiplicationAction(self._point, scalar): + if scalar == 0: + return copy(self._group.neutral) + if self.complete: + top = self._group.order.bit_length() - 1 else: - p0 = self._dadd(q, p0, p1) - p1 = self._dbl(p1) - if "scl" in self.formulas: - p0 = self._scl(p0) - return p0 + top = scalar.bit_length() - 1 + q = self._point + p0 = copy(self._group.neutral) + p1 = copy(q) + for i in range(top, -1, -1): + if scalar & (1 << i) == 0: + p1 = self._dadd(q, p0, p1) + p0 = self._dbl(p0) + else: + p0 = self._dadd(q, p0, p1) + p1 = self._dbl(p1) + if "scl" in self.formulas: + p0 = self._scl(p0) + return p0 @public @@ -343,19 +360,20 @@ class BinaryNAFMultiplier(ScalarMultiplier): def multiply(self, scalar: int) -> Point: if not self._initialized: raise ValueError("ScalaMultiplier not initialized.") - if scalar == 0: - return copy(self._group.neutral) - bnaf = naf(scalar) - q = copy(self._group.neutral) - for val in bnaf: - q = self._dbl(q) - if val == 1: - q = self._add(q, self._point) - if val == -1: - q = self._add(q, self._point_neg) - if "scl" in self.formulas: - q = self._scl(q) - return q + with ScalarMultiplicationAction(self._point, scalar): + if scalar == 0: + return copy(self._group.neutral) + bnaf = naf(scalar) + q = copy(self._group.neutral) + for val in bnaf: + q = self._dbl(q) + if val == 1: + q = self._add(q, self._point) + if val == -1: + q = self._add(q, self._point_neg) + if "scl" in self.formulas: + q = self._scl(q) + return q @public @@ -390,20 +408,21 @@ class WindowNAFMultiplier(ScalarMultiplier): def multiply(self, scalar: int) -> Point: if not self._initialized: raise ValueError("ScalaMultiplier not initialized.") - if scalar == 0: - return copy(self._group.neutral) - naf = wnaf(scalar, self.width) - q = copy(self._group.neutral) - for val in naf: - q = self._dbl(q) - if val > 0: - q = self._add(q, self._points[val]) - elif val < 0: - if self.precompute_negation: - neg = self._points_neg[-val] - else: - neg = self._neg(self._points[-val]) - q = self._add(q, neg) - if "scl" in self.formulas: - q = self._scl(q) - return q + with ScalarMultiplicationAction(self._point, scalar): + if scalar == 0: + return copy(self._group.neutral) + naf = wnaf(scalar, self.width) + q = copy(self._group.neutral) + for val in naf: + q = self._dbl(q) + if val > 0: + q = self._add(q, self._points[val]) + elif val < 0: + if self.precompute_negation: + neg = self._points_neg[-val] + else: + neg = self._neg(self._points[-val]) + q = self._add(q, neg) + if "scl" in self.formulas: + q = self._scl(q) + return q diff --git a/pyecsca/ec/op.py b/pyecsca/ec/op.py index 3bf502e..0685572 100644 --- a/pyecsca/ec/op.py +++ b/pyecsca/ec/op.py @@ -6,6 +6,7 @@ from typing import FrozenSet, cast, Any, Optional from public import public +from .context import Action from .mod import Mod @@ -103,3 +104,13 @@ class CodeOp(object): loc = dict(kwargs) exec(self.compiled, {}, loc) return loc[self.result] + + +@public +class OperationAction(Action): + """An operation.""" + operation: CodeOp + + def __init__(self, operation: CodeOp): + super().__init__() + self.operation = operation diff --git a/pyecsca/ec/point.py b/pyecsca/ec/point.py index b5ba10b..2e41606 100644 --- a/pyecsca/ec/point.py +++ b/pyecsca/ec/point.py @@ -3,12 +3,30 @@ from typing import Mapping, Any from public import public -from .coordinates import CoordinateModel, AffineCoordinateModel +from .context import Action +from .coordinates import AffineCoordinateModel, CoordinateModel from .mod import Mod, Undefined from .op import CodeOp @public +class CoordinateMappingAction(Action): + """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"): + super().__init__() + self.model_from = model_from + self.model_to = model_to + self.point = point + + def __repr__(self): + return f"{self.__class__.__name__}(from={self.model_from}, to={self.model_to}, {self.point})" + + +@public class Point(object): """A point with coordinates in a coordinate model.""" coordinate_model: CoordinateModel @@ -29,44 +47,46 @@ class Point(object): def to_affine(self) -> "Point": """Convert this point into the affine coordinate model, if possible.""" - if isinstance(self.coordinate_model, AffineCoordinateModel): - return copy(self) - ops = set() - for s in self.coordinate_model.satisfying: - try: - ops.add(CodeOp(s)) - except Exception: - pass affine_model = AffineCoordinateModel(self.coordinate_model.curve_model) - result_variables = set(map(lambda x: x.result, ops)) - if not result_variables.issuperset(affine_model.variables): - raise NotImplementedError - result = {} - for op in ops: - if op.result not in affine_model.variables: - continue - result[op.result] = op(**self.coords) - return Point(affine_model, **result) + with CoordinateMappingAction(self.coordinate_model, affine_model, self): + if isinstance(self.coordinate_model, AffineCoordinateModel): + return copy(self) + ops = set() + for s in self.coordinate_model.satisfying: + try: + ops.add(CodeOp(s)) + except Exception: + pass + result_variables = set(map(lambda x: x.result, ops)) + if not result_variables.issuperset(affine_model.variables): + raise NotImplementedError + result = {} + for op in ops: + if op.result not in affine_model.variables: + continue + result[op.result] = op(**self.coords) + return Point(affine_model, **result) @staticmethod def from_affine(coordinate_model: CoordinateModel, affine_point: "Point") -> "Point": """Convert an affine point into a given coordinate model, if possible.""" - if not isinstance(affine_point.coordinate_model, AffineCoordinateModel): - raise ValueError - result = {} - n = affine_point.coords["x"].n - for var in coordinate_model.variables: # XXX: This just works for the stuff currently in EFD. - if var == "X": - result[var] = affine_point.coords["x"] - elif var == "Y": - result[var] = affine_point.coords["y"] - elif var.startswith("Z"): - result[var] = Mod(1, n) - elif var == "T": - result[var] = Mod(affine_point.coords["x"] * affine_point.coords["y"], n) - else: - raise NotImplementedError - return Point(coordinate_model, **result) + with CoordinateMappingAction(affine_point.coordinate_model, coordinate_model, affine_point): + if not isinstance(affine_point.coordinate_model, AffineCoordinateModel): + raise ValueError + result = {} + n = affine_point.coords["x"].n + for var in coordinate_model.variables: # XXX: This just works for the stuff currently in EFD. + if var == "X": + result[var] = affine_point.coords["x"] + elif var == "Y": + result[var] = affine_point.coords["y"] + elif var.startswith("Z"): + result[var] = Mod(1, n) + elif var == "T": + result[var] = Mod(affine_point.coords["x"] * affine_point.coords["y"], n) + else: + raise NotImplementedError + return Point(coordinate_model, **result) def equals(self, other: Any) -> bool: """Test whether this point is equal to `other` irrespective of the coordinate model (in the affine sense).""" diff --git a/pyecsca/ec/signature.py b/pyecsca/ec/signature.py index 90ed9fb..569367e 100644 --- a/pyecsca/ec/signature.py +++ b/pyecsca/ec/signature.py @@ -5,11 +5,11 @@ from typing import Optional, Any from asn1crypto.core import Sequence, SequenceOf, Integer from public import public -from .context import getcontext +from .context import Action from .formula import AdditionFormula -from .params import DomainParameters from .mod import Mod from .mult import ScalarMultiplier +from .params import DomainParameters from .point import Point @@ -20,7 +20,7 @@ class SignatureResult(object): s: int def __init__(self, r: int, s: int, data: Optional[bytes] = None, digest: Optional[bytes] = None, - nonce: Optional[int] = None, privkey: Optional[int] = None, + nonce: Optional[int] = None, privkey: Optional[Mod] = None, pubkey: Optional[Point] = None): self.r = r self.s = s @@ -51,17 +51,66 @@ 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): + super().__init__() + self.params = params + self.hash_algo = hash_algo + self.msg = msg + + def __repr__(self): + return f"{self.__class__.__name__}({self.params}, {self.hash_algo}, {self.msg})" + + +@public +class ECDSASignAction(ECDSAAction): + """An ECDSA signing.""" + privkey: Mod + + def __init__(self, params: DomainParameters, hash_algo: Optional[Any], msg: bytes, + privkey: Mod): + super().__init__(params, hash_algo, msg) + self.privkey = privkey + + def __repr__(self): + return f"{self.__class__.__name__}({self.params}, {self.hash_algo}, {self.msg}, {self.privkey})" + + +@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): + super().__init__(params, hash_algo, msg) + self.signature = signature + self.pubkey = pubkey + + def __repr__(self): + return f"{self.__class__.__name__}({self.params}, {self.hash_algo}, {self.msg}, {self.signature}, {self.pubkey})" + + +@public class Signature(object): """An EC based signature primitive. (ECDSA)""" mult: ScalarMultiplier - group: DomainParameters + params: DomainParameters add: Optional[AdditionFormula] pubkey: Optional[Point] - privkey: Optional[int] + privkey: Optional[Mod] hash_algo: Optional[Any] - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[int] = 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 @@ -71,7 +120,7 @@ class Signature(object): elif isinstance(mult.formulas["add"], AdditionFormula): add = mult.formulas["add"] self.mult = mult - self.group = group + self.params = params self.add = add self.pubkey = pubkey self.privkey = privkey @@ -89,19 +138,19 @@ class Signature(object): def _get_nonce(self, nonce: Optional[int]) -> Mod: if nonce is None: - return Mod(secrets.randbelow(self.group.order), self.group.order) + return Mod.random(self.params.order) else: - return Mod(nonce, self.group.order) + return Mod(nonce, self.params.order) def _do_sign(self, nonce: Mod, digest: bytes) -> SignatureResult: z = int.from_bytes(digest, byteorder="big") - if len(digest) * 8 > self.group.order.bit_length(): - z >>= len(digest) * 8 - self.group.order.bit_length() - self.mult.init(self.group, self.group.generator) + if len(digest) * 8 > self.params.order.bit_length(): + z >>= len(digest) * 8 - self.params.order.bit_length() + self.mult.init(self.params, self.params.generator) point = self.mult.multiply(int(nonce)) - affine_point = point.to_affine() # TODO: add to context - r = Mod(int(affine_point.x), self.group.order) - s = nonce.inverse() * (Mod(z, self.group.order) + r * self.privkey) + 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) @@ -116,29 +165,30 @@ class Signature(object): """Sign data.""" if not self.can_sign: raise RuntimeError("This instance cannot sign.") - k = self._get_nonce(nonce) - if self.hash_algo is None: - digest = data - else: - digest = self.hash_algo(data).digest() - return self._do_sign(k, digest) + with ECDSASignAction(self.params, self.hash_algo, data, self.privkey): + k = self._get_nonce(nonce) + if self.hash_algo is None: + digest = data + else: + digest = self.hash_algo(data).digest() + return self._do_sign(k, digest) def _do_verify(self, signature: SignatureResult, digest: bytes) -> bool: - if self.pubkey is None: + if self.pubkey is None or self.add is None: return False z = int.from_bytes(digest, byteorder="big") - if len(digest) * 8 > self.group.order.bit_length(): - z >>= len(digest) * 8 - self.group.order.bit_length() - c = Mod(signature.s, self.group.order).inverse() - u1 = Mod(z, self.group.order) * c - u2 = Mod(signature.r, self.group.order) * c - self.mult.init(self.group, self.group.generator) + if len(digest) * 8 > self.params.order.bit_length(): + z >>= len(digest) * 8 - self.params.order.bit_length() + c = Mod(signature.s, self.params.order).inverse() + u1 = Mod(z, self.params.order) * c + u2 = Mod(signature.r, self.params.order) * c + self.mult.init(self.params, self.params.generator) p1 = self.mult.multiply(int(u1)) - self.mult.init(self.group, self.pubkey) + self.mult.init(self.params, self.pubkey) p2 = self.mult.multiply(int(u2)) - p = getcontext().execute(self.add, p1, p2, **self.group.curve.parameters)[0] - affine = p.to_affine() # TODO: add to context - v = Mod(int(affine.x), self.group.order) + p = self.add(p1, p2, **self.params.curve.parameters)[0] + affine = p.to_affine() + v = Mod(int(affine.x), self.params.order) return signature.r == int(v) def verify_hash(self, signature: SignatureResult, digest: bytes) -> bool: @@ -151,62 +201,69 @@ class Signature(object): """Verify data.""" if not self.can_verify: raise RuntimeError("This instance cannot verify.") - if self.hash_algo is None: - digest = data - else: - digest = self.hash_algo(data).digest() - return self._do_verify(signature, digest) + with ECDSAVerifyAction(self.params, self.hash_algo, data, signature, self.pubkey): + if self.hash_algo is None: + digest = data + else: + digest = self.hash_algo(data).digest() + return self._do_verify(signature, digest) @public class ECDSA_NONE(Signature): """ECDSA with raw message input.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[int] = None): - super().__init__(mult, group, add, pubkey, privkey) + 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) @public class ECDSA_SHA1(Signature): """ECDSA with SHA1.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[int] = None): - super().__init__(mult, group, add, pubkey, privkey, hashlib.sha1) + 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) @public class ECDSA_SHA224(Signature): """ECDSA with SHA224.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[int] = None): - super().__init__(mult, group, add, pubkey, privkey, hashlib.sha224) + 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) @public class ECDSA_SHA256(Signature): """ECDSA with SHA256.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[int] = None): - super().__init__(mult, group, add, pubkey, privkey, hashlib.sha256) + 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) @public class ECDSA_SHA384(Signature): """ECDSA with SHA384.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[int] = None): - super().__init__(mult, group, add, pubkey, privkey, hashlib.sha384) + 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) @public class ECDSA_SHA512(Signature): """ECDSA with SHA512.""" - def __init__(self, mult: ScalarMultiplier, group: DomainParameters, add: Optional[AdditionFormula] = None, - pubkey: Optional[Point] = None, privkey: Optional[int] = None): - super().__init__(mult, group, add, pubkey, privkey, hashlib.sha512) + 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/test/ec/test_context.py b/test/ec/test_context.py index 6b9c526..3f46c9d 100644 --- a/test/ec/test_context.py +++ b/test/ec/test_context.py @@ -1,22 +1,9 @@ -import ast from unittest import TestCase -from pyecsca.ec.context import (local, DefaultContext, OpResult, NullContext, getcontext, - setcontext, - resetcontext) -from pyecsca.ec.coordinates import AffineCoordinateModel +from pyecsca.ec.context import (local, DefaultContext, NullContext, getcontext, + setcontext, resetcontext) from pyecsca.ec.curves import get_params -from pyecsca.ec.mod import Mod -from pyecsca.ec.mult import LTRMultiplier -from pyecsca.ec.point import Point - - -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)) - self.assertEqual(str(res), "X1") +from pyecsca.ec.mult import LTRMultiplier, ScalarMultiplicationAction class ContextTests(TestCase): @@ -40,17 +27,10 @@ class ContextTests(TestCase): with local(DefaultContext()) as ctx: self.mult.multiply(59) - self.assertEqual(len(ctx.actions), 10) + self.assertEqual(len(ctx.actions), 1) + self.assertIsInstance(next(iter(ctx.actions.keys())), ScalarMultiplicationAction) self.assertEqual(len(getcontext().actions), 0) - def test_execute(self): - with self.assertRaises(ValueError): - getcontext().execute(self.coords.formulas["z"], self.base, self.base) - with self.assertRaises(ValueError): - getcontext().execute(self.coords.formulas["z"], - Point(AffineCoordinateModel(self.secp128r1.curve.model), - x=Mod(1, 5), y=Mod(2, 5))) - def test_str(self): with local(DefaultContext()) as default: self.mult.multiply(59) diff --git a/test/ec/test_formula.py b/test/ec/test_formula.py new file mode 100644 index 0000000..cedbe1f --- /dev/null +++ b/test/ec/test_formula.py @@ -0,0 +1,35 @@ +from unittest import TestCase + +from pyecsca.ec.curves import get_params +from pyecsca.ec.key_generation import KeyGeneration +from pyecsca.ec.mult import LTRMultiplier + + +class FormulaTests(TestCase): + + def setUp(self): + self.secp128r1 = get_params("secg", "secp128r1", "projective") + self.add = self.secp128r1.curve.coordinate_model.formulas["add-2007-bl"] + + def test_wrong_call(self): + with self.assertRaises(ValueError): + self.add() + with self.assertRaises(ValueError): + self.add(self.secp128r1.generator.to_affine(), self.secp128r1.generator.to_affine()) + + def test_indices(self): + self.assertEqual(self.add.input_index, 1) + self.assertEqual(self.add.output_index, 3) + + def test_inputs_outputs(self): + self.assertEqual(self.add.inputs, {"X1", "Y1", "Z1", "X2", "Y2", "Z2"}) + self.assertEqual(self.add.outputs, {"X3", "Y3", "Z3"}) + + def test_num_ops(self): + self.assertEqual(self.add.num_operations, 33) + self.assertEqual(self.add.num_multiplications, 17) + self.assertEqual(self.add.num_divisions, 0) + self.assertEqual(self.add.num_inversions, 0) + self.assertEqual(self.add.num_powers, 0) + self.assertEqual(self.add.num_squarings, 6) + self.assertEqual(self.add.num_addsubs, 10)
\ No newline at end of file diff --git a/test/ec/test_key_agreement.py b/test/ec/test_key_agreement.py index b771863..adffbab 100644 --- a/test/ec/test_key_agreement.py +++ b/test/ec/test_key_agreement.py @@ -3,7 +3,9 @@ from unittest import TestCase from parameterized import parameterized from pyecsca.ec.curves import get_params -from pyecsca.ec.key_agreement import * +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 @@ -14,11 +16,11 @@ class KeyAgreementTests(TestCase): 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 = 0xdeadbeef + self.priv_a = Mod(0xdeadbeef, self.secp128r1.order) self.mult.init(self.secp128r1, self.secp128r1.generator) - self.pub_a = self.mult.multiply(self.priv_a) - self.priv_b = 0xcafebabe - self.pub_b = self.mult.multiply(self.priv_b) + self.pub_a = self.mult.multiply(int(self.priv_a)) + self.priv_b = Mod(0xcafebabe, self.secp128r1.order) + self.pub_b = self.mult.multiply(int(self.priv_b)) @parameterized.expand([ ("NONE", ECDH_NONE), diff --git a/test/ec/test_key_generation.py b/test/ec/test_key_generation.py new file mode 100644 index 0000000..59f3b23 --- /dev/null +++ b/test/ec/test_key_generation.py @@ -0,0 +1,26 @@ +from unittest import TestCase + +from pyecsca.ec.curves import get_params +from pyecsca.ec.key_generation import KeyGeneration +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"] + self.dbl = self.secp128r1.curve.coordinate_model.formulas["dbl-2007-bl"] + self.mult = LTRMultiplier(self.add, self.dbl) + + def test_basic(self): + generator = KeyGeneration(self.mult, self.secp128r1) + priv, pub = generator.generate() + self.assertIsNotNone(priv) + self.assertIsNotNone(pub) + self.assertTrue(self.secp128r1.curve.is_on_curve(pub)) + generator = KeyGeneration(self.mult, self.secp128r1, True) + priv, pub = generator.generate() + self.assertIsNotNone(priv) + self.assertIsNotNone(pub) + self.assertTrue(self.secp128r1.curve.is_on_curve(pub)) diff --git a/test/ec/test_op.py b/test/ec/test_op.py index a7f9cd0..9471148 100644 --- a/test/ec/test_op.py +++ b/test/ec/test_op.py @@ -1,8 +1,10 @@ +import ast from ast import parse from unittest import TestCase from parameterized import parameterized +from pyecsca.ec.formula import OpResult from pyecsca.ec.mod import Mod from pyecsca.ec.op import CodeOp, OpType @@ -35,3 +37,10 @@ class OpTests(TestCase): op = CodeOp(code) res = op(**locals) self.assertEqual(res, result) + +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)) + self.assertEqual(str(res), "X1")
\ No newline at end of file diff --git a/test/ec/test_signature.py b/test/ec/test_signature.py index 41e9df2..125c280 100644 --- a/test/ec/test_signature.py +++ b/test/ec/test_signature.py @@ -1,10 +1,13 @@ -from hashlib import sha1 from unittest import TestCase +from parameterized import parameterized + from pyecsca.ec.curves import get_params +from pyecsca.ec.mod import Mod from pyecsca.ec.mult import LTRMultiplier -from pyecsca.ec.signature import * -from parameterized import parameterized +from pyecsca.ec.signature import (Signature, SignatureResult, ECDSA_NONE, ECDSA_SHA1, ECDSA_SHA224, + ECDSA_SHA256, ECDSA_SHA384, ECDSA_SHA512) + class SignatureTests(TestCase): @@ -14,9 +17,9 @@ class SignatureTests(TestCase): 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 = 0xdeadbeef + self.priv = Mod(0xdeadbeef, self.secp128r1.order) self.mult.init(self.secp128r1, self.secp128r1.generator) - self.pub = self.mult.multiply(self.priv) + self.pub = self.mult.multiply(self.priv.x) @parameterized.expand([ ("SHA1", ECDSA_SHA1), |
