199 lines
5.6 KiB
Python
199 lines
5.6 KiB
Python
# built-in dependencies
|
||
from typing import Tuple
|
||
from abc import ABC, abstractmethod
|
||
|
||
# project dependencies
|
||
from lightecc.commons.errors import PointNotOnCurve
|
||
from lightecc.commons.logger import Logger
|
||
|
||
logger = Logger(module="lightecc/interfaces/elliptic_curve.py")
|
||
|
||
# Signature for elliptic curve
|
||
|
||
|
||
class EllipticCurve(ABC):
|
||
# point at infinity or neutral / identity element
|
||
O: Tuple[int, int]
|
||
|
||
# base point G
|
||
G: Tuple[int, int]
|
||
|
||
# modulo (prime p or polynomial fx)
|
||
modulo: int
|
||
|
||
# order of the curve
|
||
n: int
|
||
|
||
# coefficients
|
||
a: int
|
||
b: int = 0 # for weierstrass & koblitz form
|
||
d: int = 0 # for edwards form
|
||
|
||
@abstractmethod
|
||
def add_points(
|
||
self,
|
||
P: Tuple[int, int],
|
||
Q: Tuple[int, int],
|
||
) -> Tuple[int, int]:
|
||
pass
|
||
|
||
@abstractmethod
|
||
def double_point(self, P: Tuple[int, int]) -> Tuple[int, int]:
|
||
pass
|
||
|
||
@abstractmethod
|
||
def is_on_curve(self, P: Tuple[int, int]) -> bool:
|
||
pass
|
||
|
||
@abstractmethod
|
||
def negative_point(self, P: Tuple[int, int]) -> Tuple[int, int]:
|
||
pass
|
||
|
||
def double_and_add(self, G: Tuple[int, int], k: int) -> Tuple[int, int]:
|
||
"""
|
||
Perform scalar multiplication over elliptic curve
|
||
Args:
|
||
G (Tuple[int, int]): a point on an elliptic curve
|
||
k (int): scalar value
|
||
Returns
|
||
kxG (Tuple[int, int]): a point on an elliptic curve
|
||
"""
|
||
target_point = G
|
||
|
||
if self.n is not None and k >= self.n:
|
||
k = k % self.n
|
||
if k == 0:
|
||
return self.O
|
||
if k < 0:
|
||
return self.negative_point(self.double_and_add(G, abs(k)))
|
||
|
||
k_binary = bin(k)[2:]
|
||
|
||
for i in range(1, len(k_binary)):
|
||
current_bit = k_binary[i : i + 1]
|
||
|
||
# doubling - always
|
||
target_point = self.double_point(target_point)
|
||
|
||
if current_bit == "1":
|
||
target_point = self.add_points(target_point, G)
|
||
|
||
assert (
|
||
self.is_on_curve(target_point) is True
|
||
), f"{target_point} is not on the curve!"
|
||
|
||
return target_point
|
||
|
||
|
||
class EllipticCurvePoint:
|
||
"""
|
||
Define a point on an elliptic curve
|
||
"""
|
||
|
||
def __init__(self, x: int, y: int, curve: EllipticCurve):
|
||
self.x = x
|
||
self.y = y
|
||
self.curve = curve
|
||
|
||
if not self.curve.is_on_curve((x, y)):
|
||
raise PointNotOnCurve(f"({x}, {y}) is not on the curve!")
|
||
|
||
def get_point(self) -> Tuple[int, int]:
|
||
return (self.x, self.y)
|
||
|
||
def __repr__(self):
|
||
|
||
if (
|
||
self.x == self.curve.O[0]
|
||
and self.y == self.curve.O[1]
|
||
and self.x == float("inf")
|
||
and self.y == float("inf")
|
||
):
|
||
# because Edwards has neutral / identity element instead of point at infinity
|
||
return "\U0001d4aa" # unicode for "𝒪" (circle O)
|
||
return f"({self.x}, {self.y})"
|
||
|
||
def __str__(self):
|
||
return self.__repr__()
|
||
|
||
def __add__(self, other: "EllipticCurvePoint") -> "EllipticCurvePoint":
|
||
"""
|
||
Calculate P + Q for two given points P and Q
|
||
"""
|
||
if not isinstance(other, EllipticCurvePoint):
|
||
raise ValueError("Addition is only defined for 2 points")
|
||
|
||
if self.curve != other.curve:
|
||
raise ValueError("Points are not on the same curve")
|
||
|
||
x, y = self.curve.add_points((self.x, self.y), (other.x, other.y))
|
||
return EllipticCurvePoint(x, y, self.curve)
|
||
|
||
def __sub__(self, other: "EllipticCurvePoint") -> "EllipticCurvePoint":
|
||
"""
|
||
Calculate P - Q for two given points P and Q
|
||
"""
|
||
return self.__add__(other=other.__neg__())
|
||
|
||
def __mul__(self, k: int) -> "EllipticCurvePoint":
|
||
"""
|
||
Calculate k*P for a given k and P
|
||
"""
|
||
if not isinstance(k, int):
|
||
raise ValueError("Multiplication is only defined for an integer")
|
||
|
||
x, y = self.curve.double_and_add((self.x, self.y), k)
|
||
return EllipticCurvePoint(x, y, self.curve)
|
||
|
||
def __rmul__(self, k: int) -> "EllipticCurvePoint":
|
||
"""
|
||
Calculate k*P for a given k and P
|
||
"""
|
||
if not isinstance(k, int):
|
||
raise ValueError("Multiplication is only defined for an integer")
|
||
return self.__mul__(k)
|
||
|
||
def __neg__(self) -> "EllipticCurvePoint":
|
||
"""
|
||
Calculate -P for a given P
|
||
"""
|
||
x, y = self.curve.negative_point((self.x, self.y))
|
||
return EllipticCurvePoint(x, y, self.curve)
|
||
|
||
def __eq__(self, other: "EllipticCurvePoint") -> bool:
|
||
"""
|
||
Check if two points are equal
|
||
"""
|
||
return self.x == other.x and self.y == other.y and self.curve == other.curve
|
||
|
||
def __truediv__(self, other: "EllipticCurvePoint") -> int:
|
||
"""
|
||
Resolve ECDLP - this is a hard problem!
|
||
"""
|
||
# TODO: you may consider to use baby-step-giant-step instead of brute force
|
||
|
||
logger.debug(f"Find k from ({self.x}, {self.y}) = k x ({other.x}, {other.y})")
|
||
|
||
ox, oy = self.curve.O
|
||
if self.x == other.x and self.y == other.y:
|
||
return 1
|
||
if self.x == ox and self.y == oy:
|
||
return self.curve.n
|
||
|
||
# base point
|
||
gx, gy = other.get_point()
|
||
|
||
k = 2
|
||
while True:
|
||
kG = self.curve.double_and_add((gx, gy), k)
|
||
|
||
if kG[0] == self.x and kG[1] == self.y:
|
||
return k
|
||
|
||
k = k + 1
|
||
|
||
if k > self.curve.n:
|
||
raise ValueError(
|
||
f"Cannot restore scalar from ({self.x}, {self.y}) = k x ({other.x}, {other.y})"
|
||
)
|