From 16efeaec2df6cb1a992b6fac59e1684e0c320f00 Mon Sep 17 00:00:00 2001 From: gbucchino Date: Wed, 18 Feb 2026 12:32:05 +0100 Subject: [PATCH] Add Elliptic Curve Cryptography --- Cryptotools/Groups/elliptic.py | 240 ++++++++++++++++++++++ examples/elliptic/ecc.py | 33 ++++ examples/elliptic/ecc_generator.py | 65 ++++++ examples/elliptic/ecdlp.py | 307 +++++++++++++++++++++++++++++ 4 files changed, 645 insertions(+) create mode 100644 Cryptotools/Groups/elliptic.py create mode 100644 examples/elliptic/ecc.py create mode 100644 examples/elliptic/ecc_generator.py create mode 100644 examples/elliptic/ecdlp.py diff --git a/Cryptotools/Groups/elliptic.py b/Cryptotools/Groups/elliptic.py new file mode 100644 index 0000000..0ce6597 --- /dev/null +++ b/Cryptotools/Groups/elliptic.py @@ -0,0 +1,240 @@ +#!/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 + diff --git a/examples/elliptic/ecc.py b/examples/elliptic/ecc.py new file mode 100644 index 0000000..8c461c2 --- /dev/null +++ b/examples/elliptic/ecc.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +import matplotlib.pyplot as plt +from Cryptotools.Groups.elliptic import Point, Elliptic + + +a = 3 +b = 8 +n = 89 # Must be prime number + +E = Elliptic(n, a, b) +E.quadraticResidues() +Ep = E.pointsE() +#print(E.getQuadraticResidues()) + +# For the graphic +MAX=n + +fig, ax = plt.subplots() +ax.legend(fontsize=14) +ax.grid() + +# We generate x for the graphic +x = list() +x = [i for i in range(0, MAX)] + +# Drawing the point. +for p in Ep: + ax.plot(p.x, p.y, 'bo') + +# Draw a line for the symmetry +plt.axline([0, 45], [45, 45], linestyle="--", color="red") +plt.show() diff --git a/examples/elliptic/ecc_generator.py b/examples/elliptic/ecc_generator.py new file mode 100644 index 0000000..abc6bbd --- /dev/null +++ b/examples/elliptic/ecc_generator.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +import matplotlib.pyplot as plt +from Cryptotools.Groups.elliptic import Elliptic +from Cryptotools.Groups.point import Point + +# https://course.ece.cmu.edu/~ece733/lectures/21-intro-ecc.pdf + + +a = 3 +b = 7 +n = 53 # Must be prime number + +E = Elliptic(n, a, b) +E.quadraticResidues() +Ep = E.pointsE() +#print(E.getQuadraticResidues()) + +# Now, we can make the Addition Table +#for p in Ep: +# for q in Ep: +# nP = E.add(p, q) +# print(f"({p.x}, {p.y}), ({q.x}, {q.y}) {nP.x, nP.y}") +# print() +# +#print() + +# In cryptography, where is the public key and where is the private key on the elliptic curve ??? +# For the graphic +MAX=n + +fig, ax = plt.subplots() +ax.legend(fontsize=14) +ax.grid() + +# We generate x for the graphic +x = list() +x = [i for i in range(0, MAX)] + +# Drawing the point. +for p in Ep: + ax.plot(p.x, p.y, 'bo') + +# Find the order of the Curve +order = E.findOrder() +cofactor = E.cofactor +print(f"Order: {order}") +print(f"Cofactor: {cofactor}") + +# Lets make an example, here, G is the first element of all points (except the point at infinity) +G = Ep[1] + +for i in range(1, 15): + P = E.scalar(G, i) + + plt.plot(P.x, P.y, marker='o', color="red") + plt.annotate(f'P{i}', (P.x, P.y + 0.5)) + + # Check if the point exist in the curve + if E.pointExist(P): + print(f"The point P ({P.x}, {P.y}) lies on the Curve") + else: + print(f"The point P ({P.x}, {P.y}) doesn't lies on the Curve") + +plt.show() diff --git a/examples/elliptic/ecdlp.py b/examples/elliptic/ecdlp.py new file mode 100644 index 0000000..766236d --- /dev/null +++ b/examples/elliptic/ecdlp.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 + +import matplotlib.pyplot as plt +from math import sqrt +from Cryptotools.Groups.point import Point +import numpy as np + +# https://course.ece.cmu.edu/~ece733/lectures/21-intro-ecc.pdf + + + +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) + print(self._squares) + + 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 + + # Why quadratic residues ???? + # 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)) + # print(y, x, e) + # print(self._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 + # print(binary) + + for b in binary: + if b == '1': + nP = self.add(nP, Rtmp) + # print(b, nP.x, nP.y) + 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) % n + # print(y2) + x3 = (pow(P.x, 3) + (a * P.x) + b) % n + # print(x3) + 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 + +a = 3 +b = 7 +n = 97 + +E = Elliptic(n, a, b) +E.quadraticResidues() +Ep = E.pointsE() + +# In cryptography, where is the public key and where is the private key on the elliptic curve ??? + +# Need to generate public/private key +## - First, we need to generate the private key. +## This key must be a random value between d = {1, n - 1} +## - Second, for the public key Q, we need to compute Q = d * G +## - d a random value; Q = {x, y} +## +## For encrypting, + +G = Ep[1] +privkey = 48 +pubkey = E.scalar(G, privkey) +if not E.pointExist(pubkey): + raise ValueError("The public key is not in the Curve") + +print(f"Private key: {privkey}") +print(f"Public key: ({pubkey.x}, {pubkey.y})") + +from math import log + +# For testing +def test(): + g = 4 + k = 6 + print(g ** k) + r = 1 + for _ in range(k): + r *= g + print(r) + # DLP + print(log(r, g)) # = 6, we resolved the DLP + +#test() + +# We can brute-force until we found Q +# We know the poing G, because it's in the domain parameters and it's public +# Same for the public key, Q which is public +# And we know the prime number +# Need to find the private key, k + +# Here, we brute force until we match with the public key +for x in range(n): + Q = E.scalar(G, x) + if Q.x == pubkey.x and Q.y == pubkey.y: + print(f"We found the private key {privkey}") + break