Source code for pyecsca.ec.point

"""Provides a :py:class:`.Point` class and a special :py:class:`.InfinityPoint` class for the point at infinity."""
from copy import copy
from typing import Mapping, TYPE_CHECKING

from public import public

from .context import ResultAction
from .coordinates import AffineCoordinateModel, CoordinateModel
from .mod import Mod, Undefined
from .op import CodeOp


if TYPE_CHECKING:
    from .curve import EllipticCurve


[docs] @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" ): 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})"
[docs] @public class Point: """A point with coordinates in a coordinate model.""" coordinate_model: CoordinateModel coords: Mapping[str, Mod] field: int def __init__(self, model: CoordinateModel, **coords: Mod): if not set(model.variables) == set(coords.keys()): raise ValueError( f"Wrong coordinate values for coordinate model, expected {model.variables} got {coords.keys()}." ) self.coordinate_model = model self.coords = coords field = None for value in self.coords.values(): if field is None: field = value.n else: if field != 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): # Do the magic such that point.X1 works! if "coords" in super().__getattribute__("__dict__"): coords = super().__getattribute__("coords") if name in coords: return coords[name] return super().__getattribute__(name)
[docs] 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: if isinstance(self.coordinate_model, AffineCoordinateModel): return action.exit(copy(self)) ops = [] for s in self.coordinate_model.satisfying: try: ops.append(CodeOp(s)) except Exception: pass result_variables = set(map(lambda x: x.result, ops)) if not result_variables.issuperset(affine_model.variables): raise NotImplementedError( f"Coordinate model does have affine mapping function ({result_variables})" ) result = {} locls = {**self.coords} for op in ops: try: locls[op.result] = op(**locls) except NameError as e: if op.result in affine_model.variables: raise e else: continue if op.result in affine_model.variables: result[op.result] = locls[op.result] return action.exit(Point(affine_model, **result))
[docs] def to_model( self, coordinate_model: CoordinateModel, curve: "EllipticCurve", randomized: bool = False, ) -> "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: if isinstance(coordinate_model, AffineCoordinateModel): return action.exit(Point(coordinate_model, **self.coords)) ops = [] for s in coordinate_model.tosystem: try: ops.append(CodeOp(s)) except Exception: pass locls = {**self.coords, **curve.parameters} for op in ops: try: locls[op.result] = op(**locls) except Exception: continue result = {} for var in coordinate_model.variables: if var in locls: result[var] = ( Mod(locls[var], curve.prime) if not isinstance(locls[var], Mod) else locls[var] ) else: raise NotImplementedError if randomized: lmbd = Mod.random(curve.prime) for var, value in result.items(): result[var] = value * (lmbd**coordinate_model.homogweights[var]) return action.exit(Point(coordinate_model, **result))
[docs] def equals_affine(self, other: "Point") -> bool: """Test whether this point is equal to :paramref:`~.equals_affine.other` irrespective of the coordinate model (in the affine sense).""" if not isinstance(other, Point) or isinstance(other, InfinityPoint): return False if self.coordinate_model.curve_model != other.coordinate_model.curve_model: return False return self.to_affine() == other.to_affine()
[docs] def equals_scaled(self, other: "Point") -> bool: """ Test whether this point is equal to :paramref:`~.equals_scaled.other` using the "z" scaling formula. The "z" scaling formula maps the projective class to a single representative. :param other: The point to compare :raises ValueError: If the "z" formula is not available for the coordinate system. :return: Whether the points are equal. """ if not isinstance(other, Point) or isinstance(other, InfinityPoint): return False if self.coordinate_model.curve_model != other.coordinate_model.curve_model: return False if "z" in self.coordinate_model.formulas: formula = self.coordinate_model.formulas["z"] self_mapped = formula(self.field, self) other_mapped = formula(self.field, other) return self_mapped == other_mapped else: raise ValueError("No scaling formula available.")
[docs] def equals(self, other: "Point") -> bool: """Test whether this point is equal to `other` irrespective of the coordinate model (in the affine sense).""" return self.equals_affine(other)
def __iter__(self): for k in sorted(self.coords.keys()): yield self.coords[k] def __len__(self): return len(self.coords) def __bytes__(self): res = b"\x04" for k in sorted(self.coords.keys()): res += bytes(self.coords[k]) return res def __eq__(self, other): if not isinstance(other, Point): return False if self.coordinate_model != other.coordinate_model: return False return self.coords == other.coords def __hash__(self): return hash( ( self.coordinate_model, tuple(self.coords.keys()), tuple(self.coords.values()), ) ) def __str__(self): args = ", ".join([f"{key}={val}" for key, val in self.coords.items()]) return f"[{args}]" def __repr__(self): return f"Point({str(self)} in {self.coordinate_model})"
[docs] @public class InfinityPoint(Point): """A point at infinity.""" def __init__(self, model: CoordinateModel): coords = {key: Undefined() for key in model.variables} super().__init__(model, **coords)
[docs] def to_affine(self) -> "InfinityPoint": return InfinityPoint(AffineCoordinateModel(self.coordinate_model.curve_model))
[docs] def to_model( self, coordinate_model: CoordinateModel, curve: "EllipticCurve", randomized: bool = False ) -> "InfinityPoint": return InfinityPoint(coordinate_model)
[docs] def equals_affine(self, other: "Point") -> bool: return self == other
[docs] def equals_scaled(self, other: "Point") -> bool: return self == other
[docs] def equals(self, other: "Point") -> bool: return self == other
def __iter__(self): yield from () def __len__(self): return 0 def __bytes__(self): return b"\x00" def __eq__(self, other): if type(other) is not InfinityPoint: return False else: return self.coordinate_model == other.coordinate_model def __hash__(self): return hash((self.coordinate_model, 0)) def __str__(self): return "Infinity" def __repr__(self): return f"InfinityPoint({self.coordinate_model})"