diff --git a/pybtc/__init__.py b/pybtc/__init__.py deleted file mode 100644 index a8e288c..0000000 --- a/pybtc/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from pybtc.constants import * -from pybtc.opcodes import * -from pybtc.consensus import * -from pybtc.functions import * - - -from .transaction import * -from .block import * -from .address import * -from .wallet import * diff --git a/pyflo/__init__.py b/pyflo/__init__.py new file mode 100644 index 0000000..a88e7b1 --- /dev/null +++ b/pyflo/__init__.py @@ -0,0 +1,10 @@ +from pyflo.constants import * +from pyflo.opcodes import * +from pyflo.consensus import * +from pyflo.functions import * + + +from .transaction import * +from .block import * +from .address import * +from .wallet import * diff --git a/pybtc/__pycache__/__init__.cpython-36.pyc b/pyflo/__pycache__/__init__.cpython-36.pyc similarity index 100% rename from pybtc/__pycache__/__init__.cpython-36.pyc rename to pyflo/__pycache__/__init__.cpython-36.pyc diff --git a/pyflo/__pycache__/__init__.cpython-38.pyc b/pyflo/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..76df823 Binary files /dev/null and b/pyflo/__pycache__/__init__.cpython-38.pyc differ diff --git a/pybtc/__pycache__/address.cpython-36.pyc b/pyflo/__pycache__/address.cpython-36.pyc similarity index 100% rename from pybtc/__pycache__/address.cpython-36.pyc rename to pyflo/__pycache__/address.cpython-36.pyc diff --git a/pyflo/__pycache__/address.cpython-38.pyc b/pyflo/__pycache__/address.cpython-38.pyc new file mode 100644 index 0000000..9e8cc81 Binary files /dev/null and b/pyflo/__pycache__/address.cpython-38.pyc differ diff --git a/pybtc/__pycache__/block.cpython-36.pyc b/pyflo/__pycache__/block.cpython-36.pyc similarity index 100% rename from pybtc/__pycache__/block.cpython-36.pyc rename to pyflo/__pycache__/block.cpython-36.pyc diff --git a/pyflo/__pycache__/block.cpython-38.pyc b/pyflo/__pycache__/block.cpython-38.pyc new file mode 100644 index 0000000..0568787 Binary files /dev/null and b/pyflo/__pycache__/block.cpython-38.pyc differ diff --git a/pybtc/__pycache__/consensus.cpython-36.pyc b/pyflo/__pycache__/consensus.cpython-36.pyc similarity index 100% rename from pybtc/__pycache__/consensus.cpython-36.pyc rename to pyflo/__pycache__/consensus.cpython-36.pyc diff --git a/pyflo/__pycache__/consensus.cpython-38.pyc b/pyflo/__pycache__/consensus.cpython-38.pyc new file mode 100644 index 0000000..27d0852 Binary files /dev/null and b/pyflo/__pycache__/consensus.cpython-38.pyc differ diff --git a/pybtc/__pycache__/constants.cpython-36.pyc b/pyflo/__pycache__/constants.cpython-36.pyc similarity index 100% rename from pybtc/__pycache__/constants.cpython-36.pyc rename to pyflo/__pycache__/constants.cpython-36.pyc diff --git a/pyflo/__pycache__/constants.cpython-38.pyc b/pyflo/__pycache__/constants.cpython-38.pyc new file mode 100644 index 0000000..4450685 Binary files /dev/null and b/pyflo/__pycache__/constants.cpython-38.pyc differ diff --git a/pybtc/__pycache__/opcodes.cpython-36.pyc b/pyflo/__pycache__/opcodes.cpython-36.pyc similarity index 100% rename from pybtc/__pycache__/opcodes.cpython-36.pyc rename to pyflo/__pycache__/opcodes.cpython-36.pyc diff --git a/pyflo/__pycache__/opcodes.cpython-38.pyc b/pyflo/__pycache__/opcodes.cpython-38.pyc new file mode 100644 index 0000000..85d6052 Binary files /dev/null and b/pyflo/__pycache__/opcodes.cpython-38.pyc differ diff --git a/pybtc/__pycache__/transaction.cpython-36.pyc b/pyflo/__pycache__/transaction.cpython-36.pyc similarity index 100% rename from pybtc/__pycache__/transaction.cpython-36.pyc rename to pyflo/__pycache__/transaction.cpython-36.pyc diff --git a/pyflo/__pycache__/transaction.cpython-38.pyc b/pyflo/__pycache__/transaction.cpython-38.pyc new file mode 100644 index 0000000..221fa73 Binary files /dev/null and b/pyflo/__pycache__/transaction.cpython-38.pyc differ diff --git a/pybtc/__pycache__/wallet.cpython-36.pyc b/pyflo/__pycache__/wallet.cpython-36.pyc similarity index 100% rename from pybtc/__pycache__/wallet.cpython-36.pyc rename to pyflo/__pycache__/wallet.cpython-36.pyc diff --git a/pyflo/__pycache__/wallet.cpython-38.pyc b/pyflo/__pycache__/wallet.cpython-38.pyc new file mode 100644 index 0000000..2649863 Binary files /dev/null and b/pyflo/__pycache__/wallet.cpython-38.pyc differ diff --git a/pybtc/__to_remove_blockchain__.py b/pyflo/__to_remove_blockchain__.py similarity index 100% rename from pybtc/__to_remove_blockchain__.py rename to pyflo/__to_remove_blockchain__.py diff --git a/pybtc/address.py b/pyflo/address.py similarity index 97% rename from pybtc/address.py rename to pyflo/address.py index 3267e81..b9eba76 100644 --- a/pybtc/address.py +++ b/pyflo/address.py @@ -1,10 +1,10 @@ -from pybtc.constants import * -from pybtc.opcodes import * -from pybtc.functions.tools import bytes_from_hex, int_to_var_int -from pybtc.functions.script import op_push_data, decode_script -from pybtc.functions.hash import hash160, sha256 -from pybtc.functions.address import hash_to_address, public_key_to_p2sh_p2wpkh_script -from pybtc.functions.key import (create_private_key, +from pyflo.constants import * +from pyflo.opcodes import * +from pyflo.functions.tools import bytes_from_hex, int_to_var_int +from pyflo.functions.script import op_push_data, decode_script +from pyflo.functions.hash import hash160, sha256 +from pyflo.functions.address import hash_to_address, public_key_to_p2sh_p2wpkh_script +from pyflo.functions.key import (create_private_key, private_key_to_wif, is_wif_valid, wif_to_private_key, diff --git a/pybtc/bip39_word_list/chinese_simplified.txt b/pyflo/bip39_word_list/chinese_simplified.txt similarity index 100% rename from pybtc/bip39_word_list/chinese_simplified.txt rename to pyflo/bip39_word_list/chinese_simplified.txt diff --git a/pybtc/bip39_word_list/chinese_traditional.txt b/pyflo/bip39_word_list/chinese_traditional.txt similarity index 100% rename from pybtc/bip39_word_list/chinese_traditional.txt rename to pyflo/bip39_word_list/chinese_traditional.txt diff --git a/pybtc/bip39_word_list/english.txt b/pyflo/bip39_word_list/english.txt similarity index 100% rename from pybtc/bip39_word_list/english.txt rename to pyflo/bip39_word_list/english.txt diff --git a/pybtc/bip39_word_list/french.txt b/pyflo/bip39_word_list/french.txt similarity index 100% rename from pybtc/bip39_word_list/french.txt rename to pyflo/bip39_word_list/french.txt diff --git a/pybtc/bip39_word_list/italian.txt b/pyflo/bip39_word_list/italian.txt similarity index 100% rename from pybtc/bip39_word_list/italian.txt rename to pyflo/bip39_word_list/italian.txt diff --git a/pybtc/bip39_word_list/japanese.txt b/pyflo/bip39_word_list/japanese.txt similarity index 100% rename from pybtc/bip39_word_list/japanese.txt rename to pyflo/bip39_word_list/japanese.txt diff --git a/pybtc/bip39_word_list/korean.txt b/pyflo/bip39_word_list/korean.txt similarity index 100% rename from pybtc/bip39_word_list/korean.txt rename to pyflo/bip39_word_list/korean.txt diff --git a/pybtc/bip39_word_list/spanish.txt b/pyflo/bip39_word_list/spanish.txt similarity index 100% rename from pybtc/bip39_word_list/spanish.txt rename to pyflo/bip39_word_list/spanish.txt diff --git a/pybtc/block.py b/pyflo/block.py similarity index 94% rename from pybtc/block.py rename to pyflo/block.py index 1f1be47..dd8dd2a 100644 --- a/pybtc/block.py +++ b/pyflo/block.py @@ -1,9 +1,9 @@ from struct import unpack, pack from io import BytesIO -from pybtc.functions.block import bits_to_target, target_to_difficulty -from pybtc.functions.hash import double_sha256 -from pybtc.functions.tools import var_int_to_int, read_var_int, var_int_len, rh2s -from pybtc.transaction import Transaction +from pyflo.functions.block import bits_to_target, target_to_difficulty +from pyflo.functions.hash import double_sha256 +from pyflo.functions.tools import var_int_to_int, read_var_int, var_int_len, rh2s +from pyflo.transaction import Transaction class Block(dict): diff --git a/pybtc/consensus.py b/pyflo/consensus.py similarity index 100% rename from pybtc/consensus.py rename to pyflo/consensus.py diff --git a/pybtc/constants.py b/pyflo/constants.py similarity index 100% rename from pybtc/constants.py rename to pyflo/constants.py diff --git a/pyflo/ellipticcurve/__init__.py b/pyflo/ellipticcurve/__init__.py new file mode 100644 index 0000000..5f4727f --- /dev/null +++ b/pyflo/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/pyflo/ellipticcurve/__pycache__/__init__.cpython-38.pyc b/pyflo/ellipticcurve/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..6b79d38 Binary files /dev/null and b/pyflo/ellipticcurve/__pycache__/__init__.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/__pycache__/curve.cpython-38.pyc b/pyflo/ellipticcurve/__pycache__/curve.cpython-38.pyc new file mode 100644 index 0000000..1ce4ba4 Binary files /dev/null and b/pyflo/ellipticcurve/__pycache__/curve.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/__pycache__/ecdsa.cpython-38.pyc b/pyflo/ellipticcurve/__pycache__/ecdsa.cpython-38.pyc new file mode 100644 index 0000000..5506e08 Binary files /dev/null and b/pyflo/ellipticcurve/__pycache__/ecdsa.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/__pycache__/math.cpython-38.pyc b/pyflo/ellipticcurve/__pycache__/math.cpython-38.pyc new file mode 100644 index 0000000..1b4fa4a Binary files /dev/null and b/pyflo/ellipticcurve/__pycache__/math.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/__pycache__/point.cpython-38.pyc b/pyflo/ellipticcurve/__pycache__/point.cpython-38.pyc new file mode 100644 index 0000000..c19b031 Binary files /dev/null and b/pyflo/ellipticcurve/__pycache__/point.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/__pycache__/privateKey.cpython-38.pyc b/pyflo/ellipticcurve/__pycache__/privateKey.cpython-38.pyc new file mode 100644 index 0000000..f0c0070 Binary files /dev/null and b/pyflo/ellipticcurve/__pycache__/privateKey.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/__pycache__/publicKey.cpython-38.pyc b/pyflo/ellipticcurve/__pycache__/publicKey.cpython-38.pyc new file mode 100644 index 0000000..96e3a8a Binary files /dev/null and b/pyflo/ellipticcurve/__pycache__/publicKey.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/__pycache__/signature.cpython-38.pyc b/pyflo/ellipticcurve/__pycache__/signature.cpython-38.pyc new file mode 100644 index 0000000..0daf63a Binary files /dev/null and b/pyflo/ellipticcurve/__pycache__/signature.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/curve.py b/pyflo/ellipticcurve/curve.py new file mode 100644 index 0000000..ef7f4f6 --- /dev/null +++ b/pyflo/ellipticcurve/curve.py @@ -0,0 +1,79 @@ +# +# Elliptic Curve Equation +# +# y^2 = x^3 + A*x + B (mod P) +# +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 + + +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 + +supportedCurves = [ + secp256k1, + prime256v1, +] + +_curvesByOid = {tuple(curve.oid): curve for curve in supportedCurves} + + +def getCurveByOid(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 supportedCurves]), + )) + return _curvesByOid[oid] diff --git a/pyflo/ellipticcurve/ecdsa.py b/pyflo/ellipticcurve/ecdsa.py new file mode 100644 index 0000000..ea809e4 --- /dev/null +++ b/pyflo/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/pyflo/ellipticcurve/math.py b/pyflo/ellipticcurve/math.py new file mode 100644 index 0000000..0ef78d1 --- /dev/null +++ b/pyflo/ellipticcurve/math.py @@ -0,0 +1,177 @@ +from .point import Point + + +class Math: + + @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/pyflo/ellipticcurve/point.py b/pyflo/ellipticcurve/point.py new file mode 100644 index 0000000..b960a3a --- /dev/null +++ b/pyflo/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/pyflo/ellipticcurve/privateKey.py b/pyflo/ellipticcurve/privateKey.py new file mode 100644 index 0000000..9a41625 --- /dev/null +++ b/pyflo/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, getCurveByOid +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 = getCurveByOid(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/pyflo/ellipticcurve/publicKey.py b/pyflo/ellipticcurve/publicKey.py new file mode 100644 index 0000000..cebdd5e --- /dev/null +++ b/pyflo/ellipticcurve/publicKey.py @@ -0,0 +1,88 @@ +from .math import Math +from .point import Point +from .curve import secp256k1, getCurveByOid +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 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 = getCurveByOid(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 + + +_ecdsaPublicKeyOid = (1, 2, 840, 10045, 2, 1) + + +_pemTemplate = """ +-----BEGIN PUBLIC KEY----- +{content} +-----END PUBLIC KEY----- +""" diff --git a/pyflo/ellipticcurve/signature.py b/pyflo/ellipticcurve/signature.py new file mode 100644 index 0000000..3084f8f --- /dev/null +++ b/pyflo/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/pyflo/ellipticcurve/utils/__init__.py b/pyflo/ellipticcurve/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyflo/ellipticcurve/utils/__pycache__/__init__.cpython-38.pyc b/pyflo/ellipticcurve/utils/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..b881de1 Binary files /dev/null and b/pyflo/ellipticcurve/utils/__pycache__/__init__.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/utils/__pycache__/binary.cpython-38.pyc b/pyflo/ellipticcurve/utils/__pycache__/binary.cpython-38.pyc new file mode 100644 index 0000000..f869c0f Binary files /dev/null and b/pyflo/ellipticcurve/utils/__pycache__/binary.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/utils/__pycache__/compatibility.cpython-38.pyc b/pyflo/ellipticcurve/utils/__pycache__/compatibility.cpython-38.pyc new file mode 100644 index 0000000..d49e07f Binary files /dev/null and b/pyflo/ellipticcurve/utils/__pycache__/compatibility.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/utils/__pycache__/der.cpython-38.pyc b/pyflo/ellipticcurve/utils/__pycache__/der.cpython-38.pyc new file mode 100644 index 0000000..6910c9e Binary files /dev/null and b/pyflo/ellipticcurve/utils/__pycache__/der.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/utils/__pycache__/file.cpython-38.pyc b/pyflo/ellipticcurve/utils/__pycache__/file.cpython-38.pyc new file mode 100644 index 0000000..f2790ea Binary files /dev/null and b/pyflo/ellipticcurve/utils/__pycache__/file.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/utils/__pycache__/integer.cpython-38.pyc b/pyflo/ellipticcurve/utils/__pycache__/integer.cpython-38.pyc new file mode 100644 index 0000000..a760eab Binary files /dev/null and b/pyflo/ellipticcurve/utils/__pycache__/integer.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/utils/__pycache__/oid.cpython-38.pyc b/pyflo/ellipticcurve/utils/__pycache__/oid.cpython-38.pyc new file mode 100644 index 0000000..0339956 Binary files /dev/null and b/pyflo/ellipticcurve/utils/__pycache__/oid.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/utils/__pycache__/pem.cpython-38.pyc b/pyflo/ellipticcurve/utils/__pycache__/pem.cpython-38.pyc new file mode 100644 index 0000000..43b8465 Binary files /dev/null and b/pyflo/ellipticcurve/utils/__pycache__/pem.cpython-38.pyc differ diff --git a/pyflo/ellipticcurve/utils/binary.py b/pyflo/ellipticcurve/utils/binary.py new file mode 100644 index 0000000..348887f --- /dev/null +++ b/pyflo/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/pyflo/ellipticcurve/utils/compatibility.py b/pyflo/ellipticcurve/utils/compatibility.py new file mode 100644 index 0000000..3b22dd3 --- /dev/null +++ b/pyflo/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/pyflo/ellipticcurve/utils/der.py b/pyflo/ellipticcurve/utils/der.py new file mode 100644 index 0000000..84546ae --- /dev/null +++ b/pyflo/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/pyflo/ellipticcurve/utils/file.py b/pyflo/ellipticcurve/utils/file.py new file mode 100644 index 0000000..c7b1df7 --- /dev/null +++ b/pyflo/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/pyflo/ellipticcurve/utils/integer.py b/pyflo/ellipticcurve/utils/integer.py new file mode 100644 index 0000000..180f200 --- /dev/null +++ b/pyflo/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/pyflo/ellipticcurve/utils/oid.py b/pyflo/ellipticcurve/utils/oid.py new file mode 100644 index 0000000..dbfebe7 --- /dev/null +++ b/pyflo/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/pyflo/ellipticcurve/utils/pem.py b/pyflo/ellipticcurve/utils/pem.py new file mode 100644 index 0000000..1e58b40 --- /dev/null +++ b/pyflo/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)) diff --git a/pybtc/functions/__init__.py b/pyflo/functions/__init__.py similarity index 100% rename from pybtc/functions/__init__.py rename to pyflo/functions/__init__.py diff --git a/pybtc/functions/__pycache__/__init__.cpython-36.pyc b/pyflo/functions/__pycache__/__init__.cpython-36.pyc similarity index 100% rename from pybtc/functions/__pycache__/__init__.cpython-36.pyc rename to pyflo/functions/__pycache__/__init__.cpython-36.pyc diff --git a/pyflo/functions/__pycache__/__init__.cpython-38.pyc b/pyflo/functions/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..e7bb9fc Binary files /dev/null and b/pyflo/functions/__pycache__/__init__.cpython-38.pyc differ diff --git a/pybtc/functions/__pycache__/address.cpython-36.pyc b/pyflo/functions/__pycache__/address.cpython-36.pyc similarity index 100% rename from pybtc/functions/__pycache__/address.cpython-36.pyc rename to pyflo/functions/__pycache__/address.cpython-36.pyc diff --git a/pyflo/functions/__pycache__/address.cpython-38.pyc b/pyflo/functions/__pycache__/address.cpython-38.pyc new file mode 100644 index 0000000..e3f19bf Binary files /dev/null and b/pyflo/functions/__pycache__/address.cpython-38.pyc differ diff --git a/pybtc/functions/__pycache__/bip32.cpython-36.pyc b/pyflo/functions/__pycache__/bip32.cpython-36.pyc similarity index 100% rename from pybtc/functions/__pycache__/bip32.cpython-36.pyc rename to pyflo/functions/__pycache__/bip32.cpython-36.pyc diff --git a/pyflo/functions/__pycache__/bip32.cpython-38.pyc b/pyflo/functions/__pycache__/bip32.cpython-38.pyc new file mode 100644 index 0000000..089013a Binary files /dev/null and b/pyflo/functions/__pycache__/bip32.cpython-38.pyc differ diff --git a/pybtc/functions/__pycache__/bip39_mnemonic.cpython-36.pyc b/pyflo/functions/__pycache__/bip39_mnemonic.cpython-36.pyc similarity index 100% rename from pybtc/functions/__pycache__/bip39_mnemonic.cpython-36.pyc rename to pyflo/functions/__pycache__/bip39_mnemonic.cpython-36.pyc diff --git a/pyflo/functions/__pycache__/bip39_mnemonic.cpython-38.pyc b/pyflo/functions/__pycache__/bip39_mnemonic.cpython-38.pyc new file mode 100644 index 0000000..e25b0e1 Binary files /dev/null and b/pyflo/functions/__pycache__/bip39_mnemonic.cpython-38.pyc differ diff --git a/pybtc/functions/__pycache__/block.cpython-36.pyc b/pyflo/functions/__pycache__/block.cpython-36.pyc similarity index 100% rename from pybtc/functions/__pycache__/block.cpython-36.pyc rename to pyflo/functions/__pycache__/block.cpython-36.pyc diff --git a/pyflo/functions/__pycache__/block.cpython-38.pyc b/pyflo/functions/__pycache__/block.cpython-38.pyc new file mode 100644 index 0000000..1fe871b Binary files /dev/null and b/pyflo/functions/__pycache__/block.cpython-38.pyc differ diff --git a/pybtc/functions/__pycache__/encode.cpython-36.pyc b/pyflo/functions/__pycache__/encode.cpython-36.pyc similarity index 100% rename from pybtc/functions/__pycache__/encode.cpython-36.pyc rename to pyflo/functions/__pycache__/encode.cpython-36.pyc diff --git a/pyflo/functions/__pycache__/encode.cpython-38.pyc b/pyflo/functions/__pycache__/encode.cpython-38.pyc new file mode 100644 index 0000000..3bec164 Binary files /dev/null and b/pyflo/functions/__pycache__/encode.cpython-38.pyc differ diff --git a/pybtc/functions/__pycache__/hash.cpython-36.pyc b/pyflo/functions/__pycache__/hash.cpython-36.pyc similarity index 100% rename from pybtc/functions/__pycache__/hash.cpython-36.pyc rename to pyflo/functions/__pycache__/hash.cpython-36.pyc diff --git a/pyflo/functions/__pycache__/hash.cpython-38.pyc b/pyflo/functions/__pycache__/hash.cpython-38.pyc new file mode 100644 index 0000000..b0ba94b Binary files /dev/null and b/pyflo/functions/__pycache__/hash.cpython-38.pyc differ diff --git a/pybtc/functions/__pycache__/key.cpython-36.pyc b/pyflo/functions/__pycache__/key.cpython-36.pyc similarity index 100% rename from pybtc/functions/__pycache__/key.cpython-36.pyc rename to pyflo/functions/__pycache__/key.cpython-36.pyc diff --git a/pyflo/functions/__pycache__/key.cpython-38.pyc b/pyflo/functions/__pycache__/key.cpython-38.pyc new file mode 100644 index 0000000..35b29ff Binary files /dev/null and b/pyflo/functions/__pycache__/key.cpython-38.pyc differ diff --git a/pybtc/functions/__pycache__/script.cpython-36.pyc b/pyflo/functions/__pycache__/script.cpython-36.pyc similarity index 100% rename from pybtc/functions/__pycache__/script.cpython-36.pyc rename to pyflo/functions/__pycache__/script.cpython-36.pyc diff --git a/pyflo/functions/__pycache__/script.cpython-38.pyc b/pyflo/functions/__pycache__/script.cpython-38.pyc new file mode 100644 index 0000000..02458d3 Binary files /dev/null and b/pyflo/functions/__pycache__/script.cpython-38.pyc differ diff --git a/pybtc/functions/__pycache__/tools.cpython-36.pyc b/pyflo/functions/__pycache__/tools.cpython-36.pyc similarity index 100% rename from pybtc/functions/__pycache__/tools.cpython-36.pyc rename to pyflo/functions/__pycache__/tools.cpython-36.pyc diff --git a/pyflo/functions/__pycache__/tools.cpython-38.pyc b/pyflo/functions/__pycache__/tools.cpython-38.pyc new file mode 100644 index 0000000..d65f9c8 Binary files /dev/null and b/pyflo/functions/__pycache__/tools.cpython-38.pyc differ diff --git a/pybtc/functions/address.py b/pyflo/functions/address.py similarity index 98% rename from pybtc/functions/address.py rename to pyflo/functions/address.py index fb489ff..708f263 100644 --- a/pybtc/functions/address.py +++ b/pyflo/functions/address.py @@ -1,9 +1,9 @@ -from pybtc.opcodes import * -from pybtc.constants import * +from pyflo.opcodes import * +from pyflo.constants import * -from pybtc.functions.tools import bytes_from_hex -from pybtc.functions.hash import double_sha256, hash160 -from pybtc.functions.encode import (encode_base58, +from pyflo.functions.tools import bytes_from_hex +from pyflo.functions.hash import double_sha256, hash160 +from pyflo.functions.encode import (encode_base58, rebase_8_to_5, bech32_polymod, rebase_5_to_32, diff --git a/pybtc/functions/bip32.py b/pyflo/functions/bip32.py similarity index 97% rename from pybtc/functions/bip32.py rename to pyflo/functions/bip32.py index 624902a..484093c 100644 --- a/pybtc/functions/bip32.py +++ b/pyflo/functions/bip32.py @@ -1,11 +1,11 @@ from struct import pack from secp256k1 import ffi, lib -from pybtc.functions.key import private_to_public_key, private_key_to_wif -from pybtc.functions.hash import hmac_sha512, double_sha256, hash160 -from pybtc.functions.encode import (encode_base58, +from pyflo.functions.key import private_to_public_key, private_key_to_wif +from pyflo.functions.hash import hmac_sha512, double_sha256, hash160 +from pyflo.functions.encode import (encode_base58, decode_base58_with_checksum, encode_base58_with_checksum) -from pybtc.constants import * +from pyflo.constants import * def create_master_xprivate_key(seed, testnet=False, base58=True, hex=False): diff --git a/pybtc/functions/bip39_mnemonic.py b/pyflo/functions/bip39_mnemonic.py similarity index 98% rename from pybtc/functions/bip39_mnemonic.py rename to pyflo/functions/bip39_mnemonic.py index 43ad391..df046b5 100644 --- a/pybtc/functions/bip39_mnemonic.py +++ b/pyflo/functions/bip39_mnemonic.py @@ -1,8 +1,8 @@ -from pybtc.constants import * +from pyflo.constants import * import time import hashlib -from pybtc.functions.hash import sha256 -from pybtc.functions.tools import int_from_bytes +from pyflo.functions.hash import sha256 +from pyflo.functions.tools import int_from_bytes def generate_entropy(strength=256, hex=True): """ diff --git a/pybtc/functions/block.py b/pyflo/functions/block.py similarity index 97% rename from pybtc/functions/block.py rename to pyflo/functions/block.py index 1cf84a2..d6c1f4b 100644 --- a/pybtc/functions/block.py +++ b/pyflo/functions/block.py @@ -1,5 +1,5 @@ -from pybtc.functions.tools import s2rh, bytes_from_hex, int_from_bytes -from pybtc.functions.hash import double_sha256 +from pyflo.functions.tools import s2rh, bytes_from_hex, int_from_bytes +from pyflo.functions.hash import double_sha256 def merkle_root(tx_hash_list, hex=True): """ diff --git a/pybtc/functions/encode.py b/pyflo/functions/encode.py similarity index 97% rename from pybtc/functions/encode.py rename to pyflo/functions/encode.py index d26f3a5..6c55083 100644 --- a/pybtc/functions/encode.py +++ b/pyflo/functions/encode.py @@ -1,5 +1,5 @@ -from pybtc.functions.hash import double_sha256 -from pybtc.functions.tools import bytes_from_hex +from pyflo.functions.hash import double_sha256 +from pyflo.functions.tools import bytes_from_hex b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' base32charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" diff --git a/pybtc/functions/hash.py b/pyflo/functions/hash.py similarity index 100% rename from pybtc/functions/hash.py rename to pyflo/functions/hash.py diff --git a/pybtc/functions/key.py b/pyflo/functions/key.py similarity index 97% rename from pybtc/functions/key.py rename to pyflo/functions/key.py index b00e81f..d44503b 100644 --- a/pybtc/functions/key.py +++ b/pyflo/functions/key.py @@ -2,9 +2,9 @@ from secp256k1 import ffi, lib secp256k1_ec_pubkey_create = lib.secp256k1_ec_pubkey_create secp256k1_ec_pubkey_serialize = lib.secp256k1_ec_pubkey_serialize -from pybtc.constants import * -from pybtc.functions.encode import encode_base58, decode_base58 -from pybtc.functions.hash import double_sha256 +from pyflo.constants import * +from pyflo.functions.encode import encode_base58, decode_base58 +from pyflo.functions.hash import double_sha256 from .bip39_mnemonic import generate_entropy bytes_from_hex = bytes.fromhex diff --git a/pybtc/functions/script.py b/pyflo/functions/script.py similarity index 89% rename from pybtc/functions/script.py rename to pyflo/functions/script.py index e73c6f6..8392953 100644 --- a/pybtc/functions/script.py +++ b/pyflo/functions/script.py @@ -1,5 +1,9 @@ from struct import unpack - +import hashlib +from pyflo.ellipticcurve.privateKey import PrivateKey +from pyflo.ellipticcurve.signature import Signature +from pyflo.ellipticcurve.math import Math +from pyflo.ellipticcurve.utils.integer import RandomInteger from secp256k1 import ffi, lib secp256k1_ecdsa_signature_parse_der = lib.secp256k1_ecdsa_signature_parse_der secp256k1_ec_pubkey_parse = lib.secp256k1_ec_pubkey_parse @@ -11,13 +15,13 @@ secp256k1_ecdsa_recoverable_signature_parse_compact = lib.secp256k1_ecdsa_recove secp256k1_ecdsa_recover = lib.secp256k1_ecdsa_recover secp256k1_ec_pubkey_serialize = lib.secp256k1_ec_pubkey_serialize -from pybtc.opcodes import * -from pybtc.constants import * +from pyflo.opcodes import * +from pyflo.constants import * -from pybtc.functions.tools import bytes_from_hex, int_to_bytes, get_stream -from pybtc.functions.hash import hash160, sha256 -from pybtc.functions.address import hash_to_address -from pybtc.functions.key import is_wif_valid, wif_to_private_key +from pyflo.functions.tools import bytes_from_hex, int_to_bytes, get_stream +from pyflo.functions.hash import hash160, sha256 +from pyflo.functions.address import hash_to_address +from pyflo.functions.key import is_wif_valid, wif_to_private_key def public_key_to_pubkey_script(key, hex=True): @@ -400,51 +404,64 @@ def verify_signature(sig, pub_key, msg): result = secp256k1_ecdsa_verify(ECDSA_CONTEXT_VERIFY, raw_sig, msg, raw_pubkey) return True if result else False +def to_base(n, base): + if base == 10: + return n + result = 0 + counter = 0 + while n: + r = n % base + n //= base + result += r * 10 ** counter + counter += 1 + return result -def sign_message(msg, private_key, hex=True): +# https://raw.githubusercontent.com/starkbank/ecdsa-python/master/ellipticcurve/ecdsa.py +def modSign(message, privateKey, hashfunc=sha256): + # byteMessage = toBytes(message).digest() + # byteMessage = hashfunc(toBytes(message)).digest() + + byteMessage = hashlib.sha256(message.encode("utf-8")).hexdigest() + # byteMessage = '0x' + byteMessage + + # byteMessage = script_to_hash("686579", True) + numberMessage = int(byteMessage, base=16) + numberMessage = to_base(numberMessage, 16) + 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) + +def sign_message(msg, private_key): """ Sign message - :param msg: message to sign bytes or HEX encoded string. - :param private_key: private key (bytes, hex encoded string or WIF format) + :param msg: message to sign in string. + :param private_key: private key (WIF format) :param hex: (optional) If set to True return key in HEX format, by default is True. :return: DER encoded signature in bytes or HEX encoded string. """ - if isinstance(msg, bytearray): - msg = bytes(msg) - if isinstance(msg, str): - try: - msg = bytes_from_hex(msg) - except: - pass - if not isinstance(msg, bytes): - raise TypeError("message must be a bytes or hex encoded string") + if(is_wif_valid(private_key)): + hexKey = wif_to_private_key(private_key) + else: + raise TypeError("Invalid Private_Key, must be in WIF format") + pk = PrivateKey.fromString(hexKey) + signature = modSign(msg, pk) + toDer = int.from_bytes(signature.toDer(), 'big') + return hex(toDer)[2:] - if isinstance(private_key, bytearray): - private_key = bytes(private_key) - if isinstance(private_key, str): - try: - private_key = bytes_from_hex(private_key) - except: - if is_wif_valid(private_key): - private_key = wif_to_private_key(private_key, hex=False) - if not isinstance(private_key, bytes): - raise TypeError("private key must be a bytes, hex encoded string or in WIF format") - - raw_sig = ffi.new('secp256k1_ecdsa_signature *') - signed = secp256k1_ecdsa_sign(ECDSA_CONTEXT_SIGN, raw_sig, msg, - private_key, ffi.NULL, ffi.NULL) - if not signed: - raise RuntimeError("secp256k1 error") - len_sig = 74 - output = ffi.new('unsigned char[%d]' % len_sig) - outputlen = ffi.new('size_t *', len_sig) - res = secp256k1_ecdsa_signature_serialize_der(ECDSA_CONTEXT_SIGN, - output, outputlen, raw_sig) - if not res: - raise RuntimeError("secp256k1 error") - signature = bytes(ffi.buffer(output, outputlen[0])) - return signature.hex() if hex else signature def public_key_recovery(signature, messsage, rec_id, compressed=True, hex=True): diff --git a/pybtc/functions/tools.py b/pyflo/functions/tools.py similarity index 100% rename from pybtc/functions/tools.py rename to pyflo/functions/tools.py diff --git a/pybtc/opcodes.py b/pyflo/opcodes.py similarity index 100% rename from pybtc/opcodes.py rename to pyflo/opcodes.py diff --git a/pybtc/test/__init__.py b/pyflo/test/__init__.py similarity index 100% rename from pybtc/test/__init__.py rename to pyflo/test/__init__.py diff --git a/pybtc/test/address_class.py b/pyflo/test/address_class.py similarity index 100% rename from pybtc/test/address_class.py rename to pyflo/test/address_class.py diff --git a/pybtc/test/address_functions.py b/pyflo/test/address_functions.py similarity index 100% rename from pybtc/test/address_functions.py rename to pyflo/test/address_functions.py diff --git a/pybtc/test/block.py b/pyflo/test/block.py similarity index 100% rename from pybtc/test/block.py rename to pyflo/test/block.py diff --git a/pybtc/test/create_transaction.py b/pyflo/test/create_transaction.py similarity index 100% rename from pybtc/test/create_transaction.py rename to pyflo/test/create_transaction.py diff --git a/pybtc/test/ecdsa.py b/pyflo/test/ecdsa.py similarity index 100% rename from pybtc/test/ecdsa.py rename to pyflo/test/ecdsa.py diff --git a/pybtc/test/hash_functions.py b/pyflo/test/hash_functions.py similarity index 100% rename from pybtc/test/hash_functions.py rename to pyflo/test/hash_functions.py diff --git a/pybtc/test/integer.py b/pyflo/test/integer.py similarity index 100% rename from pybtc/test/integer.py rename to pyflo/test/integer.py diff --git a/pybtc/test/mnemonic.py b/pyflo/test/mnemonic.py similarity index 100% rename from pybtc/test/mnemonic.py rename to pyflo/test/mnemonic.py diff --git a/pybtc/test/raw_block.txt b/pyflo/test/raw_block.txt similarity index 100% rename from pybtc/test/raw_block.txt rename to pyflo/test/raw_block.txt diff --git a/pybtc/test/script_deserialize.py b/pyflo/test/script_deserialize.py similarity index 100% rename from pybtc/test/script_deserialize.py rename to pyflo/test/script_deserialize.py diff --git a/pybtc/test/script_functions.py b/pyflo/test/script_functions.py similarity index 100% rename from pybtc/test/script_functions.py rename to pyflo/test/script_functions.py diff --git a/pybtc/test/sighash.py b/pyflo/test/sighash.py similarity index 100% rename from pybtc/test/sighash.py rename to pyflo/test/sighash.py diff --git a/pybtc/test/transaction_constructor.py b/pyflo/test/transaction_constructor.py similarity index 100% rename from pybtc/test/transaction_constructor.py rename to pyflo/test/transaction_constructor.py diff --git a/pybtc/test/transaction_deserialize.py b/pyflo/test/transaction_deserialize.py similarity index 100% rename from pybtc/test/transaction_deserialize.py rename to pyflo/test/transaction_deserialize.py diff --git a/pybtc/transaction.py b/pyflo/transaction.py similarity index 99% rename from pybtc/transaction.py rename to pyflo/transaction.py index 85e437b..c632590 100644 --- a/pybtc/transaction.py +++ b/pyflo/transaction.py @@ -2,21 +2,21 @@ import json from struct import unpack, pack from math import ceil from io import BytesIO -from pybtc.constants import * -from pybtc.opcodes import * -from pybtc.functions.tools import (int_to_var_int, +from pyflo.constants import * +from pyflo.opcodes import * +from pyflo.functions.tools import (int_to_var_int, read_var_int, var_int_to_int, rh2s, s2rh, bytes_from_hex, get_stream) -from pybtc.functions.script import op_push_data, decode_script, parse_script, sign_message -from pybtc.functions.script import get_multisig_public_keys, read_opcode, is_valid_signature_encoding -from pybtc.functions.script import public_key_recovery, delete_from_script -from pybtc.functions.hash import hash160, sha256, double_sha256 -from pybtc.functions.address import hash_to_address, address_net_type, address_to_script -from pybtc.address import PrivateKey, Address, ScriptAddress, PublicKey +from pyflo.functions.script import op_push_data, decode_script, parse_script, sign_message +from pyflo.functions.script import get_multisig_public_keys, read_opcode, is_valid_signature_encoding +from pyflo.functions.script import public_key_recovery, delete_from_script +from pyflo.functions.hash import hash160, sha256, double_sha256 +from pyflo.functions.address import hash_to_address, address_net_type, address_to_script +from pyflo.address import PrivateKey, Address, ScriptAddress, PublicKey diff --git a/pybtc/wallet.py b/pyflo/wallet.py similarity index 100% rename from pybtc/wallet.py rename to pyflo/wallet.py diff --git a/tanishktests.py b/tanishktests.py new file mode 100644 index 0000000..7fec64c --- /dev/null +++ b/tanishktests.py @@ -0,0 +1,20 @@ +import pyflo + +# pk = pybtc.create_private_key() +# print("pk = " + pk) +# pk = "RCJ9Q6kH5ywdRhchVKrxPUFiJE7cGMKFEB8n9zd4VgdNVYzHNedz" + + +# a = pybtc.sign_message_tanishk("hey", pk, hex=True) +# print(a) +# b = pybtc.verify_signature(a, public, "6865790D0A") +# print(b) +# pybtc.test_function() +msg = "hey" +pk = "RCJ9Q6kH5ywdRhchVKrxPUFiJE7cGMKFEB8n9zd4VgdNVYzHNedz" +public = pyflo.private_to_public_key(pk) +print("public = " + public) +sig = '30460221008b30bdc5039264abb40b686b1bdb9db0900e0b6c50dea793c0c1b1bde654119a022100b51e25bc73c274a5f3cd8bcdb11143921a2b1595c3b62f1cffcbe3bc5e876b65' +sig = pyflo.sign_message(msg, pk) +print(sig) +