about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/ellipticcurve
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/ellipticcurve
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/ellipticcurve')
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/__init__.py6
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/curve.py90
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/ecdsa.py46
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/math.py181
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/point.py14
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/privateKey.py72
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/publicKey.py107
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/signature.py48
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/utils/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/utils/binary.py37
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/utils/compatibility.py40
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/utils/der.py159
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/utils/file.py9
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/utils/integer.py16
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/utils/oid.py35
-rw-r--r--.venv/lib/python3.12/site-packages/ellipticcurve/utils/pem.py14
16 files changed, 874 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/__init__.py b/.venv/lib/python3.12/site-packages/ellipticcurve/__init__.py
new file mode 100644
index 00000000..5f4727f4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/__init__.py
@@ -0,0 +1,6 @@
+from .utils.compatibility import *
+from .privateKey import PrivateKey
+from .publicKey import PublicKey
+from .signature import Signature
+from .utils.file import File
+from .ecdsa import Ecdsa
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/curve.py b/.venv/lib/python3.12/site-packages/ellipticcurve/curve.py
new file mode 100644
index 00000000..df3e119e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/curve.py
@@ -0,0 +1,90 @@
+#
+# Elliptic Curve Equation
+#
+# y^2 = x^3 + A*x + B (mod P)
+#
+from .math import Math
+from .point import Point
+
+
+class CurveFp:
+
+    def __init__(self, A, B, P, N, Gx, Gy, name, oid, nistName=None):
+        self.A = A
+        self.B = B
+        self.P = P
+        self.N = N
+        self.G = Point(Gx, Gy)
+        self.name = name
+        self.nistName = nistName
+        self.oid = oid  # ASN.1 Object Identifier
+
+    def contains(self, p):
+        """
+        Verify if the point `p` is on the curve
+
+        :param p: Point p = Point(x, y)
+        :return: boolean
+        """
+        if not 0 <= p.x <= self.P - 1:
+            return False
+        if not 0 <= p.y <= self.P - 1:
+            return False
+        if (p.y**2 - (p.x**3 + self.A * p.x + self.B)) % self.P != 0:
+            return False
+        return True
+
+    def length(self):
+        return (1 + len("%x" % self.N)) // 2
+
+    def y(self, x, isEven):
+        ySquared = (pow(x, 3, self.P) + self.A * x + self.B) % self.P
+        y = Math.modularSquareRoot(ySquared, self.P)
+        if isEven != (y % 2 == 0):
+            y = self.P - y
+        return y
+
+
+_curvesByOid = {tuple(curve.oid): curve for curve in []}
+
+
+def add(curve):
+    _curvesByOid[tuple(curve.oid)] = curve
+
+
+def getByOid(oid):
+    if oid not in _curvesByOid:
+        raise Exception("Unknown curve with oid {oid}; The following are registered: {names}".format(
+            oid=".".join([str(number) for number in oid]),
+            names=", ".join([curve.name for curve in _curvesByOid.values()]),
+        ))
+    return _curvesByOid[oid]
+
+
+secp256k1 = CurveFp(
+    name="secp256k1",
+    A=0x0000000000000000000000000000000000000000000000000000000000000000,
+    B=0x0000000000000000000000000000000000000000000000000000000000000007,
+    P=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,
+    N=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,
+    Gx=0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
+    Gy=0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8,
+    oid=[1, 3, 132, 0, 10]
+)
+
+prime256v1 = CurveFp(
+    name="prime256v1",
+    nistName="P-256",
+    A=0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc,
+    B=0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b,
+    P=0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff,
+    N=0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551,
+    Gx=0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
+    Gy=0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5,
+    oid=[1, 2, 840, 10045, 3, 1, 7],
+)
+
+p256 = prime256v1
+
+add(secp256k1)
+add(prime256v1)
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/ecdsa.py b/.venv/lib/python3.12/site-packages/ellipticcurve/ecdsa.py
new file mode 100644
index 00000000..ea809e4f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/ecdsa.py
@@ -0,0 +1,46 @@
+from hashlib import sha256
+from .signature import Signature
+from .math import Math
+from .utils.integer import RandomInteger
+from .utils.binary import numberFromByteString
+from .utils.compatibility import *
+
+
+class Ecdsa:
+
+    @classmethod
+    def sign(cls, message, privateKey, hashfunc=sha256):
+        byteMessage = hashfunc(toBytes(message)).digest()
+        numberMessage = numberFromByteString(byteMessage)
+        curve = privateKey.curve
+
+        r, s, randSignPoint = 0, 0, None
+        while r == 0 or s == 0:
+            randNum = RandomInteger.between(1, curve.N - 1)
+            randSignPoint = Math.multiply(curve.G, n=randNum, A=curve.A, P=curve.P, N=curve.N)
+            r = randSignPoint.x % curve.N
+            s = ((numberMessage + r * privateKey.secret) * (Math.inv(randNum, curve.N))) % curve.N
+        recoveryId = randSignPoint.y & 1
+        if randSignPoint.y > curve.N:
+            recoveryId += 2
+
+        return Signature(r=r, s=s, recoveryId=recoveryId)
+
+    @classmethod
+    def verify(cls, message, signature, publicKey, hashfunc=sha256):
+        byteMessage = hashfunc(toBytes(message)).digest()
+        numberMessage = numberFromByteString(byteMessage)
+        curve = publicKey.curve
+        r = signature.r
+        s = signature.s
+        if not 1 <= r <= curve.N - 1:
+            return False
+        if not 1 <= s <= curve.N - 1:
+            return False
+        inv = Math.inv(s, curve.N)
+        u1 = Math.multiply(curve.G, n=(numberMessage * inv) % curve.N, N=curve.N, A=curve.A, P=curve.P)
+        u2 = Math.multiply(publicKey.point, n=(r * inv) % curve.N, N=curve.N, A=curve.A, P=curve.P)
+        v = Math.add(u1, u2, A=curve.A, P=curve.P)
+        if v.isAtInfinity():
+            return False
+        return v.x % curve.N == r
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/math.py b/.venv/lib/python3.12/site-packages/ellipticcurve/math.py
new file mode 100644
index 00000000..981ab4e7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/math.py
@@ -0,0 +1,181 @@
+from .point import Point
+
+
+class Math:
+
+    @classmethod
+    def modularSquareRoot(cls, value, prime):
+        return pow(value, (prime + 1) // 4, prime)
+
+    @classmethod
+    def multiply(cls, p, n, N, A, P):
+        """
+        Fast way to multily point and scalar in elliptic curves
+
+        :param p: First Point to mutiply
+        :param n: Scalar to mutiply
+        :param N: Order of the elliptic curve
+        :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
+        :param A: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
+        :return: Point that represents the sum of First and Second Point
+        """
+        return cls._fromJacobian(
+            cls._jacobianMultiply(cls._toJacobian(p), n, N, A, P), P
+        )
+
+    @classmethod
+    def add(cls, p, q, A, P):
+        """
+        Fast way to add two points in elliptic curves
+
+        :param p: First Point you want to add
+        :param q: Second Point you want to add
+        :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
+        :param A: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
+        :return: Point that represents the sum of First and Second Point
+        """
+        return cls._fromJacobian(
+            cls._jacobianAdd(cls._toJacobian(p), cls._toJacobian(q), A, P), P,
+        )
+
+    @classmethod
+    def inv(cls, x, n):
+        """
+        Extended Euclidean Algorithm. It's the 'division' in elliptic curves
+
+        :param x: Divisor
+        :param n: Mod for division
+        :return: Value representing the division
+        """
+        if x == 0:
+            return 0
+
+        lm = 1
+        hm = 0
+        low = x % n
+        high = n
+
+        while low > 1:
+            r = high // low
+            nm = hm - lm * r
+            nw = high - low * r
+            high = low
+            hm = lm
+            low = nw
+            lm = nm
+
+        return lm % n
+
+    @classmethod
+    def _toJacobian(cls, p):
+        """
+        Convert point to Jacobian coordinates
+
+        :param p: First Point you want to add
+        :return: Point in Jacobian coordinates
+        """
+        return Point(p.x, p.y, 1)
+
+    @classmethod
+    def _fromJacobian(cls, p, P):
+        """
+        Convert point back from Jacobian coordinates
+
+        :param p: First Point you want to add
+        :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
+        :return: Point in default coordinates
+        """
+        z = cls.inv(p.z, P)
+        x = (p.x * z ** 2) % P
+        y = (p.y * z ** 3) % P
+
+        return Point(x, y, 0)
+
+    @classmethod
+    def _jacobianDouble(cls, p, A, P):
+        """
+        Double a point in elliptic curves
+
+        :param p: Point you want to double
+        :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
+        :param A: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
+        :return: Point that represents the sum of First and Second Point
+        """
+        if p.y == 0:
+            return Point(0, 0, 0)
+
+        ysq = (p.y ** 2) % P
+        S = (4 * p.x * ysq) % P
+        M = (3 * p.x ** 2 + A * p.z ** 4) % P
+        nx = (M**2 - 2 * S) % P
+        ny = (M * (S - nx) - 8 * ysq ** 2) % P
+        nz = (2 * p.y * p.z) % P
+
+        return Point(nx, ny, nz)
+
+    @classmethod
+    def _jacobianAdd(cls, p, q, A, P):
+        """
+        Add two points in elliptic curves
+
+        :param p: First Point you want to add
+        :param q: Second Point you want to add
+        :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
+        :param A: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
+        :return: Point that represents the sum of First and Second Point
+        """
+        if p.y == 0:
+            return q
+        if q.y == 0:
+            return p
+
+        U1 = (p.x * q.z ** 2) % P
+        U2 = (q.x * p.z ** 2) % P
+        S1 = (p.y * q.z ** 3) % P
+        S2 = (q.y * p.z ** 3) % P
+
+        if U1 == U2:
+            if S1 != S2:
+                return Point(0, 0, 1)
+            return cls._jacobianDouble(p, A, P)
+
+        H = U2 - U1
+        R = S2 - S1
+        H2 = (H * H) % P
+        H3 = (H * H2) % P
+        U1H2 = (U1 * H2) % P
+        nx = (R ** 2 - H3 - 2 * U1H2) % P
+        ny = (R * (U1H2 - nx) - S1 * H3) % P
+        nz = (H * p.z * q.z) % P
+
+        return Point(nx, ny, nz)
+
+    @classmethod
+    def _jacobianMultiply(cls, p, n, N, A, P):
+        """
+        Multily point and scalar in elliptic curves
+
+        :param p: First Point to mutiply
+        :param n: Scalar to mutiply
+        :param N: Order of the elliptic curve
+        :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
+        :param A: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
+        :return: Point that represents the sum of First and Second Point
+        """
+        if p.y == 0 or n == 0:
+            return Point(0, 0, 1)
+
+        if n == 1:
+            return p
+
+        if n < 0 or n >= N:
+            return cls._jacobianMultiply(p, n % N, N, A, P)
+
+        if (n % 2) == 0:
+            return cls._jacobianDouble(
+                cls._jacobianMultiply(p, n // 2, N, A, P), A, P
+            )
+
+        return cls._jacobianAdd(
+            cls._jacobianDouble(cls._jacobianMultiply(p, n // 2, N, A, P), A, P), p, A, P
+        )
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/point.py b/.venv/lib/python3.12/site-packages/ellipticcurve/point.py
new file mode 100644
index 00000000..b960a3a6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/point.py
@@ -0,0 +1,14 @@
+
+
+class Point:
+
+    def __init__(self, x=0, y=0, z=0):
+        self.x = x
+        self.y = y
+        self.z = z
+
+    def __str__(self):
+        return "({x}, {y}, {z})".format(x=self.x, y=self.y, z=self.z)
+
+    def isAtInfinity(self):
+        return self.y == 0
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/privateKey.py b/.venv/lib/python3.12/site-packages/ellipticcurve/privateKey.py
new file mode 100644
index 00000000..df6fb4d9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/privateKey.py
@@ -0,0 +1,72 @@
+from .math import Math
+from .utils.integer import RandomInteger
+from .utils.pem import getPemContent, createPem
+from .utils.binary import hexFromByteString, byteStringFromHex, intFromHex, base64FromByteString, byteStringFromBase64
+from .utils.der import hexFromInt, parse, encodeConstructed, DerFieldType, encodePrimitive
+from .curve import secp256k1, getByOid
+from .publicKey import PublicKey
+
+
+class PrivateKey:
+
+    def __init__(self, curve=secp256k1, secret=None):
+        self.curve = curve
+        self.secret = secret or RandomInteger.between(1, curve.N - 1)
+
+    def publicKey(self):
+        curve = self.curve
+        publicPoint = Math.multiply(
+            p=curve.G,
+            n=self.secret,
+            N=curve.N,
+            A=curve.A,
+            P=curve.P,
+        )
+        return PublicKey(point=publicPoint, curve=curve)
+
+    def toString(self):
+        return hexFromInt(self.secret)
+
+    def toDer(self):
+        publicKeyString = self.publicKey().toString(encoded=True)
+        hexadecimal = encodeConstructed(
+            encodePrimitive(DerFieldType.integer, 1),
+            encodePrimitive(DerFieldType.octetString, hexFromInt(self.secret)),
+            encodePrimitive(DerFieldType.oidContainer, encodePrimitive(DerFieldType.object, self.curve.oid)),
+            encodePrimitive(DerFieldType.publicKeyPointContainer, encodePrimitive(DerFieldType.bitString, publicKeyString))
+        )
+        return byteStringFromHex(hexadecimal)
+
+    def toPem(self):
+        der = self.toDer()
+        return createPem(content=base64FromByteString(der), template=_pemTemplate)
+
+    @classmethod
+    def fromPem(cls, string):
+        privateKeyPem = getPemContent(pem=string, template=_pemTemplate)
+        return cls.fromDer(byteStringFromBase64(privateKeyPem))
+
+    @classmethod
+    def fromDer(cls, string):
+        hexadecimal = hexFromByteString(string)
+        privateKeyFlag, secretHex, curveData, publicKeyString = parse(hexadecimal)[0]
+        if privateKeyFlag != 1:
+            raise Exception("Private keys should start with a '1' flag, but a '{flag}' was found instead".format(
+                flag=privateKeyFlag
+            ))
+        curve = getByOid(curveData[0])
+        privateKey = cls.fromString(string=secretHex, curve=curve)
+        if privateKey.publicKey().toString(encoded=True) != publicKeyString[0]:
+            raise Exception("The public key described inside the private key file doesn't match the actual public key of the pair")
+        return privateKey
+
+    @classmethod
+    def fromString(cls, string, curve=secp256k1):
+        return PrivateKey(secret=intFromHex(string), curve=curve)
+
+
+_pemTemplate = """
+-----BEGIN EC PRIVATE KEY-----
+{content}
+-----END EC PRIVATE KEY-----
+"""
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/publicKey.py b/.venv/lib/python3.12/site-packages/ellipticcurve/publicKey.py
new file mode 100644
index 00000000..3ebb5938
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/publicKey.py
@@ -0,0 +1,107 @@
+from .math import Math
+from .point import Point
+from .curve import secp256k1, getByOid
+from .utils.pem import getPemContent, createPem
+from .utils.der import hexFromInt, parse, DerFieldType, encodeConstructed, encodePrimitive
+from .utils.binary import hexFromByteString, byteStringFromHex, intFromHex, base64FromByteString, byteStringFromBase64
+
+
+class PublicKey:
+
+    def __init__(self, point, curve):
+        self.point = point
+        self.curve = curve
+
+    def toString(self, encoded=False):
+        baseLength = 2 * self.curve.length()
+        xHex = hexFromInt(self.point.x).zfill(baseLength)
+        yHex = hexFromInt(self.point.y).zfill(baseLength)
+        string = xHex + yHex
+        if encoded:
+            return "0004" + string
+        return string
+
+    def toCompressed(self):
+        baseLength = 2 * self.curve.length()
+        parityTag = _evenTag if self.point.y % 2 == 0 else _oddTag
+        xHex = hexFromInt(self.point.x).zfill(baseLength)
+        return parityTag + xHex
+
+    def toDer(self):
+        hexadecimal = encodeConstructed(
+            encodeConstructed(
+                encodePrimitive(DerFieldType.object, _ecdsaPublicKeyOid),
+                encodePrimitive(DerFieldType.object, self.curve.oid),
+            ),
+            encodePrimitive(DerFieldType.bitString, self.toString(encoded=True)),
+        )
+        return byteStringFromHex(hexadecimal)
+
+    def toPem(self):
+        der = self.toDer()
+        return createPem(content=base64FromByteString(der), template=_pemTemplate)
+
+    @classmethod
+    def fromPem(cls, string):
+        publicKeyPem = getPemContent(pem=string, template=_pemTemplate)
+        return cls.fromDer(byteStringFromBase64(publicKeyPem))
+
+    @classmethod
+    def fromDer(cls, string):
+        hexadecimal = hexFromByteString(string)
+        curveData, pointString = parse(hexadecimal)[0]
+        publicKeyOid, curveOid = curveData
+        if publicKeyOid != _ecdsaPublicKeyOid:
+            raise Exception("The Public Key Object Identifier (OID) should be {ecdsaPublicKeyOid}, but {actualOid} was found instead".format(
+                ecdsaPublicKeyOid=_ecdsaPublicKeyOid,
+                actualOid=publicKeyOid,
+            ))
+        curve = getByOid(curveOid)
+        return cls.fromString(string=pointString, curve=curve)
+
+    @classmethod
+    def fromString(cls, string, curve=secp256k1, validatePoint=True):
+        baseLength = 2 * curve.length()
+        if len(string) > 2 * baseLength and string[:4] == "0004":
+            string = string[4:]
+
+        xs = string[:baseLength]
+        ys = string[baseLength:]
+
+        p = Point(
+            x=intFromHex(xs),
+            y=intFromHex(ys),
+        )
+        publicKey = PublicKey(point=p, curve=curve)
+        if not validatePoint:
+            return publicKey
+        if p.isAtInfinity():
+            raise Exception("Public Key point is at infinity")
+        if not curve.contains(p):
+            raise Exception("Point ({x},{y}) is not valid for curve {name}".format(x=p.x, y=p.y, name=curve.name))
+        if not Math.multiply(p=p, n=curve.N, N=curve.N, A=curve.A, P=curve.P).isAtInfinity():
+            raise Exception("Point ({x},{y}) * {name}.N is not at infinity".format(x=p.x, y=p.y, name=curve.name))
+        return publicKey
+        
+    @classmethod
+    def fromCompressed(cls, string, curve=secp256k1):
+        parityTag, xHex = string[:2], string[2:]
+        if parityTag not in [_evenTag, _oddTag]:
+            raise Exception("Compressed string should start with 02 or 03")
+        x = intFromHex(xHex)
+        y = curve.y(x, isEven=parityTag == _evenTag)
+        return cls(point=Point(x, y), curve=curve)
+
+
+_evenTag = "02"
+_oddTag = "03"
+
+
+_ecdsaPublicKeyOid = (1, 2, 840, 10045, 2, 1)
+
+
+_pemTemplate = """
+-----BEGIN PUBLIC KEY-----
+{content}
+-----END PUBLIC KEY-----
+"""
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/signature.py b/.venv/lib/python3.12/site-packages/ellipticcurve/signature.py
new file mode 100644
index 00000000..3084f8f8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/signature.py
@@ -0,0 +1,48 @@
+from .utils.compatibility import *
+from .utils.der import parse, encodeConstructed, encodePrimitive, DerFieldType
+from .utils.binary import hexFromByteString, byteStringFromHex, base64FromByteString, byteStringFromBase64
+
+
+class Signature:
+
+    def __init__(self, r, s, recoveryId=None):
+        self.r = r
+        self.s = s
+        self.recoveryId = recoveryId
+
+    def toDer(self, withRecoveryId=False):
+        hexadecimal = self._toString()
+        encodedSequence = byteStringFromHex(hexadecimal)
+        if not withRecoveryId:
+            return encodedSequence
+        return toBytes(chr(27 + self.recoveryId)) + encodedSequence
+
+    def toBase64(self, withRecoveryId=False):
+        return base64FromByteString(self.toDer(withRecoveryId))
+
+    @classmethod
+    def fromDer(cls, string, recoveryByte=False):
+        recoveryId = None
+        if recoveryByte:
+            recoveryId = string[0] if isinstance(string[0], intTypes) else ord(string[0])
+            recoveryId -= 27
+            string = string[1:]
+
+        hexadecimal = hexFromByteString(string)
+        return cls._fromString(string=hexadecimal, recoveryId=recoveryId)
+
+    @classmethod
+    def fromBase64(cls, string, recoveryByte=False):
+        der = byteStringFromBase64(string)
+        return cls.fromDer(der, recoveryByte)
+
+    def _toString(self):
+        return encodeConstructed(
+            encodePrimitive(DerFieldType.integer, self.r),
+            encodePrimitive(DerFieldType.integer, self.s),
+        )
+
+    @classmethod
+    def _fromString(cls, string, recoveryId=None):
+        r, s = parse(string)[0]
+        return Signature(r=r, s=s, recoveryId=recoveryId)
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/utils/__init__.py b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/utils/binary.py b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/binary.py
new file mode 100644
index 00000000..348887f0
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/binary.py
@@ -0,0 +1,37 @@
+from base64 import b64encode, b64decode
+from .compatibility import safeHexFromBinary, safeBinaryFromHex, toString
+
+
+def hexFromInt(number):
+    hexadecimal = "{0:x}".format(number)
+    if len(hexadecimal) % 2 == 1:
+        hexadecimal = "0" + hexadecimal
+    return hexadecimal
+
+
+def intFromHex(hexadecimal):
+    return int(hexadecimal, 16)
+
+
+def hexFromByteString(byteString):
+    return safeHexFromBinary(byteString)
+
+
+def byteStringFromHex(hexadecimal):
+    return safeBinaryFromHex(hexadecimal)
+
+
+def numberFromByteString(byteString):
+    return intFromHex(hexFromByteString(byteString))
+
+
+def base64FromByteString(byteString):
+    return toString(b64encode(byteString))
+
+
+def byteStringFromBase64(base64String):
+    return b64decode(base64String)
+
+
+def bitsFromHex(hexadecimal):
+    return format(intFromHex(hexadecimal), 'b').zfill(4 * len(hexadecimal))
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/utils/compatibility.py b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/compatibility.py
new file mode 100644
index 00000000..3b22dd3c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/compatibility.py
@@ -0,0 +1,40 @@
+from sys import version_info as pyVersion
+from binascii import hexlify, unhexlify
+
+
+if pyVersion.major == 3:
+    # py3 constants and conversion functions
+
+    stringTypes = (str,)
+    intTypes = (int, float)
+
+    def toString(string, encoding="utf-8"):
+        return string.decode(encoding)
+
+    def toBytes(string, encoding="utf-8"):
+        return string.encode(encoding)
+
+    def safeBinaryFromHex(hexadecimal):
+        if len(hexadecimal) % 2 == 1:
+            hexadecimal = "0" + hexadecimal
+        return unhexlify(hexadecimal)
+
+    def safeHexFromBinary(byteString):
+        return toString(hexlify(byteString))
+else:
+    # py2 constants and conversion functions
+
+    stringTypes = (str, unicode)
+    intTypes = (int, float, long)
+
+    def toString(string, encoding="utf-8"):
+        return string
+
+    def toBytes(string, encoding="utf-8"):
+        return string
+
+    def safeBinaryFromHex(hexadecimal):
+        return unhexlify(hexadecimal)
+
+    def safeHexFromBinary(byteString):
+        return hexlify(byteString)
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/utils/der.py b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/der.py
new file mode 100644
index 00000000..84546aea
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/der.py
@@ -0,0 +1,159 @@
+from datetime import datetime
+from .oid import oidToHex, oidFromHex
+from .binary import hexFromInt, intFromHex, byteStringFromHex, bitsFromHex
+
+
+class DerFieldType:
+
+    integer = "integer"
+    bitString = "bitString"
+    octetString = "octetString"
+    null = "null"
+    object = "object"
+    printableString = "printableString"
+    utcTime = "utcTime"
+    sequence = "sequence"
+    set = "set"
+    oidContainer = "oidContainer"
+    publicKeyPointContainer = "publicKeyPointContainer"
+
+
+_hexTagToType = {
+    "02": DerFieldType.integer,
+    "03": DerFieldType.bitString,
+    "04": DerFieldType.octetString,
+    "05": DerFieldType.null,
+    "06": DerFieldType.object,
+    "13": DerFieldType.printableString,
+    "17": DerFieldType.utcTime,
+    "30": DerFieldType.sequence,
+    "31": DerFieldType.set,
+    "a0": DerFieldType.oidContainer,
+    "a1": DerFieldType.publicKeyPointContainer,
+}
+_typeToHexTag = {v: k for k, v in _hexTagToType.items()}
+
+
+def encodeConstructed(*encodedValues):
+    return encodePrimitive(DerFieldType.sequence, "".join(encodedValues))
+
+
+def encodePrimitive(tagType, value):
+    if tagType == DerFieldType.integer:
+        value = _encodeInteger(value)
+    if tagType == DerFieldType.object:
+        value = oidToHex(value)
+    return "{tag}{size}{value}".format(tag=_typeToHexTag[tagType], size=_generateLengthBytes(value), value=value)
+
+
+def parse(hexadecimal):
+    if not hexadecimal:
+        return []
+    typeByte, hexadecimal = hexadecimal[:2], hexadecimal[2:]
+    length, lengthBytes = _readLengthBytes(hexadecimal)
+    content, hexadecimal = hexadecimal[lengthBytes: lengthBytes + length], hexadecimal[lengthBytes + length:]
+    if len(content) < length:
+        raise Exception("missing bytes in DER parse")
+
+    tagData = _getTagData(typeByte)
+    if tagData["isConstructed"]:
+        content = parse(content)
+
+    valueParser = {
+        DerFieldType.null: _parseNull,
+        DerFieldType.object: _parseOid,
+        DerFieldType.utcTime: _parseTime,
+        DerFieldType.integer: _parseInteger,
+        DerFieldType.printableString: _parseString,
+    }.get(tagData["type"], _parseAny)
+    return [valueParser(content)] + parse(hexadecimal)
+
+
+def _parseAny(hexadecimal):
+    return hexadecimal
+
+
+def _parseOid(hexadecimal):
+    return tuple(oidFromHex(hexadecimal))
+
+
+def _parseTime(hexadecimal):
+    string = _parseString(hexadecimal)
+    return datetime.strptime(string, "%y%m%d%H%M%SZ")
+
+
+def _parseString(hexadecimal):
+    return byteStringFromHex(hexadecimal).decode()
+
+
+def _parseNull(_content):
+    return None
+
+
+def _parseInteger(hexadecimal):
+    integer = intFromHex(hexadecimal)
+    bits = bitsFromHex(hexadecimal[0])
+    if bits[0] == "0":  # negative numbers are encoded using two's complement
+        return integer
+    bitCount = 4 * len(hexadecimal)
+    return integer - (2 ** bitCount)
+
+
+def _encodeInteger(number):
+    hexadecimal = hexFromInt(abs(number))
+    if number < 0:
+        bitCount = 4 * len(hexadecimal)
+        twosComplement = (2 ** bitCount) + number
+        return hexFromInt(twosComplement)
+    bits = bitsFromHex(hexadecimal[0])
+    if bits[0] == "1":  # if first bit was left as 1, number would be parsed as a negative integer with two's complement
+        hexadecimal = "00" + hexadecimal
+    return hexadecimal
+
+
+def _readLengthBytes(hexadecimal):
+    lengthBytes = 2
+    lengthIndicator = intFromHex(hexadecimal[0:lengthBytes])
+    isShortForm = lengthIndicator < 128  # checks if first bit of byte is 1 (a.k.a. short-form)
+    if isShortForm:
+        length = lengthIndicator * 2
+        return length, lengthBytes
+
+    lengthLength = lengthIndicator - 128  # nullifies first bit of byte (only used as long-form flag)
+    if lengthLength == 0:
+        raise Exception("indefinite length encoding located in DER")
+    lengthBytes += 2 * lengthLength
+    length = intFromHex(hexadecimal[2:lengthBytes]) * 2
+    return length, lengthBytes
+
+
+def _generateLengthBytes(hexadecimal):
+    size = len(hexadecimal) // 2
+    length = hexFromInt(size)
+    if size < 128:  # checks if first bit of byte should be 0 (a.k.a. short-form flag)
+        return length.zfill(2)
+    lengthLength = 128 + len(length) // 2  # +128 sets the first bit of the byte as 1 (a.k.a. long-form flag)
+    return hexFromInt(lengthLength) + length
+
+
+def _getTagData(tag):
+    bits = bitsFromHex(tag)
+    bit8, bit7, bit6 = bits[:3]
+
+    tagClass = {
+        "0": {
+            "0": "universal",
+            "1": "application",
+        },
+        "1": {
+            "0": "context-specific",
+            "1": "private",
+        },
+    }[bit8][bit7]
+    isConstructed = bit6 == "1"
+
+    return {
+        "class": tagClass,
+        "isConstructed": isConstructed,
+        "type": _hexTagToType.get(tag),
+    }
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/utils/file.py b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/file.py
new file mode 100644
index 00000000..c7b1df73
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/file.py
@@ -0,0 +1,9 @@
+
+
+class File:
+
+    @classmethod
+    def read(cls, path, mode="r"):
+        with open(path, mode) as blob:
+            content = blob.read()
+        return content
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/utils/integer.py b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/integer.py
new file mode 100644
index 00000000..180f200c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/integer.py
@@ -0,0 +1,16 @@
+from random import SystemRandom
+
+
+class RandomInteger:
+
+    @classmethod
+    def between(cls, min, max):
+        """
+        Return integer x in the range: min <= x <= max
+
+        :param min: minimum value of the integer
+        :param max: maximum value of the integer
+        :return:
+        """
+
+        return SystemRandom().randrange(min, max + 1)
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/utils/oid.py b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/oid.py
new file mode 100644
index 00000000..dbfebe79
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/oid.py
@@ -0,0 +1,35 @@
+from .binary import intFromHex, hexFromInt
+
+
+def oidFromHex(hexadecimal):
+    firstByte, remainingBytes = hexadecimal[:2], hexadecimal[2:]
+    firstByteInt = intFromHex(firstByte)
+    oid = [firstByteInt // 40, firstByteInt % 40]
+    oidInt = 0
+    while len(remainingBytes) > 0:
+        byte, remainingBytes = remainingBytes[0:2], remainingBytes[2:]
+        byteInt = intFromHex(byte)
+        if byteInt >= 128:
+            oidInt = (128 * oidInt) + (byteInt - 128)
+            continue
+        oidInt = (128 * oidInt) + byteInt
+        oid.append(oidInt)
+        oidInt = 0
+    return oid
+
+
+def oidToHex(oid):
+    hexadecimal = hexFromInt(40 * oid[0] + oid[1])
+    for number in oid[2:]:
+        hexadecimal += _oidNumberToHex(number)
+    return hexadecimal
+
+
+def _oidNumberToHex(number):
+    hexadecimal = ""
+    endDelta = 0
+    while number > 0:
+        hexadecimal = hexFromInt((number % 128) + endDelta) + hexadecimal
+        number //= 128
+        endDelta = 128
+    return hexadecimal or "00"
diff --git a/.venv/lib/python3.12/site-packages/ellipticcurve/utils/pem.py b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/pem.py
new file mode 100644
index 00000000..1e58b409
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/ellipticcurve/utils/pem.py
@@ -0,0 +1,14 @@
+from re import search
+
+
+def getPemContent(pem, template):
+    pattern = template.format(content="(.*)")
+    return search("".join(pattern.splitlines()), "".join(pem.splitlines())).group(1)
+
+
+def createPem(content, template):
+    lines = [
+        content[start:start + 64]
+        for start in range(0, len(content), 64)
+    ]
+    return template.format(content="\n".join(lines))