#!/usr/bin/env python3 from Cryptotools.Groups.point import Point from math import sqrt import numpy as np class Elliptic: """ This class generate a group for Elliptic Curve An Elliptic Curve is a algebraic group from the Group theory branch. An Elliptic Curve is a set of points from this equation (Weierstrass equations): $y2 = x3 + ax + b$ To generate points of $E(F_p)$, first, we need to generate all square modulos The, for all X, we increment it until $X < n$ and if exist a square modulos It's a point of the list $E(F_p)$ Attributes: n (Integer): It's the modulo a (Integer): b (Integer): squares (Dict): Dictionary which contain quadratic nonresidue. The key is the quadratic nonresidue and for each entry, we have a list of point for the quadratic nonresidue E (List): List of all Points order (Int): Order (length) of the group """ def __init__(self, n, a, b): self._n = n self._a = a self._b = b self._squares = dict() self._E = list() self._order = 0 def quadraticResidues(self): """ This function generate all quadratic modulo of n. A quadratic: if exist and satisfy $x^2 \equiv q mod n$, means it's a square modulo n and q is quadratic nonresidue modulo n https://en.wikipedia.org/wiki/Quadratic_residue For instance, n = 13, q = 9 For all x belongs to n for x in n: if x ** 2 % n == q: print(x, q) """ for q in range(self._n): x2 = pow(q, 2) % self._n if x2 not in self._squares: self._squares[x2] = list() self._squares[x2].append(q) def getQuadraticResidues(self) -> dict: """ This function return the dict contains all squares modulo of n Returns: Return a dictionary of squares modulo """ return self._squares def pointsE(self): """ This function generate all points for $E(F_p)$. Each entry in the list contain another list of two entries: x and y Returns: Return the list of points of E(F_p) """ self._E.append(Point(0, 0)) for x in range(self._n): y = (pow(x, 3) + (x * self._a) + self._b) % self._n # If not quadratic residue, no point in the curve # and x not produce a point in the curve if y in self._squares: for e in self._squares[y]: self._E.append(Point(x, e)) return self._E def additionTable(self): raise NotImplementedError def _slope(self): raise NotImplementedError def _curves(self): self._curves = dict() self._curves["weierstrass"] = weierstrass self._curves["curve25519"] = curve25519 self._curves["curve448"] = curve448 def weierstrass(self, x): raise NotImplementedError def curve448(self, x): raise NotImplementedError def curve25519(self, x): """ This function generate a curve based on the Montgomery's curve. Using that formula: y2 = x^3 + 486662\times x^2 + x """ y = pow(x, 3) + 486662 * pow(x, 2) + x if y > 0: return sqrt(y) else: return 0 def add(self, P, Q) -> Point: """ This function operathe addition operation on two points P and Q Args: P (Object): The first Point on the curve Q (Object): The second Point on the curve Returns: Return the Point object R """ ## Check if P or Q are infinity if (P.x, P.y) == (0, 0) and (Q.x, Q.y) == (0, 0): return Point(0, 0) elif (P.x, P.y) == (0, 0): return Point(Q.x, Q.y) elif (Q.x, Q.y) == (0, 0): return Point(P.x, P.y) # point doubling if P.x == Q.x: # Infinity if P.y != Q.y or Q.y == 0: return Point(0, 0) # Point doubling try: inv = pow(2 * P.y, -1, self._n); # It's working with the inverse modular, WHY ??? m = ((3 * pow(P.x, 2)) + self._a) * inv % self._n except ValueError: return Point(0, 0) else: try: inv = pow(Q.x - P.x, -1, self._n) m = ((Q.y - P.y) * inv) % self._n except ValueError: # May call this Exception: base is not invertible for the given modulus # I return an Infinity point until I fixed that return Point(0, 0) xr = int((pow(m, 2) - P.x - Q.x)) % self._n yr = int((m * (P.x - xr)) - P.y) % self._n return Point(xr, yr) def scalar(self, P, n) -> Point: """ This function compute a Scalar Multiplication of P, n time. This algorithm is also known as Double and Add. Args: P (point): the Point to multiplication n (Integer): multiplicate n time P Returns: Return the result of the Scalar multiplication """ binary = bin(n)[2:] binary = binary[::-1] # We need to reverse the binary nP = Point(0, 0) Rtmp = P for b in binary: if b == '1': nP = self.add(nP, Rtmp) Rtmp = self.add(Rtmp, Rtmp) # Double P return nP def pointExist(self, P) -> bool: """ This function determine if the Point P(x, y) exist in the Curve To identify if a point P (x, y) lies on the curve We need to compute y ** 2 mod n Then, we compute x ** 3 + ax + b mod n If both are equal, the point exist, otherwise not Args: P (Point): The point to check if exist in the curve Returns: Return True if lies on the curve otherwise it's False """ y2 = pow(P.y, 2) % self._n x3 = (pow(P.x, 3) + (self._a * P.x) + self._b) % self._n if y2 == x3: return True return False def findOrder(self) -> int: """ This function find the order of the Curve over Fp Returns: Return the order of the Curve """ l = list() l.append(Point(0, 0)) # It's the same of the function pointsE for x in range(self._n): r = (pow(x, 3) + (self._a * x) + self._b) % self._n if r in self._squares: for s in self._squares[r]: P = Point(x, s) l.append(P) self._order = len(l) return self._order @property def order(self) -> int: """ This function return the order of the Group """ return self._order @property def cofactor(self) -> int: """ This function return the cofactor. A cofactor describe the relation between the number of points and the group. It's based on the Lagrange's theorem. """ if self._order == 0: raise ValueError("You must generate the order of the group") return self._order / self._n