commit
05f2f09043
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,4 +3,5 @@ build/
|
|||||||
dist/
|
dist/
|
||||||
.github/
|
.github/
|
||||||
pybtc.egg-info/
|
pybtc.egg-info/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
|||||||
33
README.md
33
README.md
@ -1,8 +1,6 @@
|
|||||||
## Python bitcoin library modified for FLO
|
## Python bitcoin library modified for FLO
|
||||||
|
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
To install pyflo,
|
To install pyflo,
|
||||||
|
|
||||||
$ git clone https://github.com/ranchimall/pyflo
|
$ git clone https://github.com/ranchimall/pyflo
|
||||||
@ -20,10 +18,10 @@ To install pyflo,
|
|||||||
Every message sent to the Blockchain is in hash format, and not in plain string. So we convert the message we are signing into a SHA256 hash before.
|
Every message sent to the Blockchain is in hash format, and not in plain string. So we convert the message we are signing into a SHA256 hash before.
|
||||||
|
|
||||||
```
|
```
|
||||||
>>> import pybtc
|
>>> import pyflo
|
||||||
|
|
||||||
# ADDRESS GENERATION
|
# ADDRESS GENERATION
|
||||||
>>> a = pybtc.Address(address_type="P2PKH")
|
>>> a = pyflo.Address(address_type="P2PKH")
|
||||||
>>> a.address
|
>>> a.address
|
||||||
'FTP7LL7QjhgKfqYX1pis18bCqEpZaGSRzZ'
|
'FTP7LL7QjhgKfqYX1pis18bCqEpZaGSRzZ'
|
||||||
>>> a.private_key.wif
|
>>> a.private_key.wif
|
||||||
@ -34,13 +32,13 @@ Every message sent to the Blockchain is in hash format, and not in plain string.
|
|||||||
'033c30b269e2d5df229f3f0ce294b19c4f0a3a8d12280415ce41e7bd3784a619c4'
|
'033c30b269e2d5df229f3f0ce294b19c4f0a3a8d12280415ce41e7bd3784a619c4'
|
||||||
|
|
||||||
# CONVERT MESSAGE INTO SHA-256 HASH
|
# CONVERT MESSAGE INTO SHA-256 HASH
|
||||||
>>> pybtc.sha256(b'vivek'.hex())
|
>>> pyflo.sha256(b'vivek'.hex())
|
||||||
b'\xa3\xdas\x97e\x01\x81,\xd7\xb8!\xa2\x0b\xfb\t\xaf\nj\x89\x1eA\x9c\xdf\xb7a\xfb\x19\xa9,\x91BB'
|
b'\xa3\xdas\x97e\x01\x81,\xd7\xb8!\xa2\x0b\xfb\t\xaf\nj\x89\x1eA\x9c\xdf\xb7a\xfb\x19\xa9,\x91BB'
|
||||||
>>> pybtc.sha256(b'vivek'.hex()).hex()
|
>>> pyflo.sha256(b'vivek'.hex()).hex()
|
||||||
'a3da73976501812cd7b821a20bfb09af0a6a891e419cdfb761fb19a92c914242'
|
'a3da73976501812cd7b821a20bfb09af0a6a891e419cdfb761fb19a92c914242'
|
||||||
>>> msg = b'vivek'.hex()
|
>>> msg = b'vivek'.hex()
|
||||||
>>> msg_hash_hex = sha256(msg).hex()
|
>>> msg_hash_hex = pyflo.sha256(msg).hex()
|
||||||
>>> msg_hash_bytes = sha256(msg)
|
>>> msg_hash_bytes = pyflo.sha256(msg)
|
||||||
>>> msg
|
>>> msg
|
||||||
'766976656b'
|
'766976656b'
|
||||||
>>> msg_hash_hex
|
>>> msg_hash_hex
|
||||||
@ -49,10 +47,19 @@ b'\xa3\xdas\x97e\x01\x81,\xd7\xb8!\xa2\x0b\xfb\t\xaf\nj\x89\x1eA\x9c\xdf\xb7a\xf
|
|||||||
b'\xa3\xdas\x97e\x01\x81,\xd7\xb8!\xa2\x0b\xfb\t\xaf\nj\x89\x1eA\x9c\xdf\xb7a\xfb\x19\xa9,\x91BB'
|
b'\xa3\xdas\x97e\x01\x81,\xd7\xb8!\xa2\x0b\xfb\t\xaf\nj\x89\x1eA\x9c\xdf\xb7a\xfb\x19\xa9,\x91BB'
|
||||||
|
|
||||||
# SIGN AND VERIFY THE MESSAGE
|
# SIGN AND VERIFY THE MESSAGE
|
||||||
>>> sig_msg_hex = pybtc.sign_message(msg_hash_hex, a.private_key.wif)
|
>>> sig_msg_hex = pyflo.sign_message(msg_hash_hex, a.private_key.wif)
|
||||||
>>> pybtc.verify_signature(sig_msg_hex, a.public_key.hex, msg_hash_hex)
|
>>> pyflo.verify_signature(sig_msg_hex, a.public_key.hex, msg_hash_hex)
|
||||||
True
|
True
|
||||||
|
|
||||||
|
# SIGN AND VERIFY MESSAGE IN STANDARD OPERATION
|
||||||
|
>> pyflo.sign_message_standard_ops('vivek', a.private_key.wif)
|
||||||
|
'3045022039747449a6fbac008d04d763a3a62f2261d1c5a35ee6a21a8354d8757d27593802210085a4d4b9886de6d06c3563c97160c8d70f492ce56f9e00dbcd7276004369402e'
|
||||||
|
>> sig_msg_hex = pyflo.sign_message_standard_ops('vivek', a.private_key.wif)
|
||||||
|
|
||||||
|
# To verify the above signature, run the following in the console of any Standard Ops app
|
||||||
|
>> sign_msg_hex = '3045022039747449a6fbac008d04d763a3a62f2261d1c5a35ee6a21a8354d8757d27593802210085a4d4b9886de6d06c3563c97160c8d70f492ce56f9e00dbcd7276004369402e'
|
||||||
|
>> a.public_key.hex = '033c30b269e2d5df229f3f0ce294b19c4f0a3a8d12280415ce41e7bd3784a619c4'
|
||||||
|
>> floCrypto.verifySign('vivek', sig_msg_hex, a.public_key.hex)
|
||||||
|
true
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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 *
|
|
||||||
10
pyflo/__init__.py
Normal file
10
pyflo/__init__.py
Normal file
@ -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 *
|
||||||
BIN
pyflo/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
pyflo/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/__pycache__/address.cpython-38.pyc
Normal file
BIN
pyflo/__pycache__/address.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/__pycache__/block.cpython-38.pyc
Normal file
BIN
pyflo/__pycache__/block.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/__pycache__/consensus.cpython-38.pyc
Normal file
BIN
pyflo/__pycache__/consensus.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/__pycache__/constants.cpython-38.pyc
Normal file
BIN
pyflo/__pycache__/constants.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/__pycache__/opcodes.cpython-38.pyc
Normal file
BIN
pyflo/__pycache__/opcodes.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/__pycache__/transaction.cpython-38.pyc
Normal file
BIN
pyflo/__pycache__/transaction.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/__pycache__/wallet.cpython-38.pyc
Normal file
BIN
pyflo/__pycache__/wallet.cpython-38.pyc
Normal file
Binary file not shown.
@ -1,10 +1,10 @@
|
|||||||
from pybtc.constants import *
|
from pyflo.constants import *
|
||||||
from pybtc.opcodes import *
|
from pyflo.opcodes import *
|
||||||
from pybtc.functions.tools import bytes_from_hex, int_to_var_int
|
from pyflo.functions.tools import bytes_from_hex, int_to_var_int
|
||||||
from pybtc.functions.script import op_push_data, decode_script
|
from pyflo.functions.script import op_push_data, decode_script
|
||||||
from pybtc.functions.hash import hash160, sha256
|
from pyflo.functions.hash import hash160, sha256
|
||||||
from pybtc.functions.address import hash_to_address, public_key_to_p2sh_p2wpkh_script
|
from pyflo.functions.address import hash_to_address, public_key_to_p2sh_p2wpkh_script
|
||||||
from pybtc.functions.key import (create_private_key,
|
from pyflo.functions.key import (create_private_key,
|
||||||
private_key_to_wif,
|
private_key_to_wif,
|
||||||
is_wif_valid,
|
is_wif_valid,
|
||||||
wif_to_private_key,
|
wif_to_private_key,
|
||||||
@ -1,9 +1,9 @@
|
|||||||
from struct import unpack, pack
|
from struct import unpack, pack
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pybtc.functions.block import bits_to_target, target_to_difficulty
|
from pyflo.functions.block import bits_to_target, target_to_difficulty
|
||||||
from pybtc.functions.hash import double_sha256
|
from pyflo.functions.hash import double_sha256
|
||||||
from pybtc.functions.tools import var_int_to_int, read_var_int, var_int_len, rh2s
|
from pyflo.functions.tools import var_int_to_int, read_var_int, var_int_len, rh2s
|
||||||
from pybtc.transaction import Transaction
|
from pyflo.transaction import Transaction
|
||||||
|
|
||||||
|
|
||||||
class Block(dict):
|
class Block(dict):
|
||||||
6
pyflo/ellipticcurve/__init__.py
Normal file
6
pyflo/ellipticcurve/__init__.py
Normal file
@ -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
|
||||||
BIN
pyflo/ellipticcurve/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/__pycache__/curve.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/__pycache__/curve.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/__pycache__/ecdsa.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/__pycache__/ecdsa.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/__pycache__/math.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/__pycache__/math.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/__pycache__/point.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/__pycache__/point.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/__pycache__/privateKey.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/__pycache__/privateKey.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/__pycache__/publicKey.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/__pycache__/publicKey.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/__pycache__/signature.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/__pycache__/signature.cpython-38.pyc
Normal file
Binary file not shown.
79
pyflo/ellipticcurve/curve.py
Normal file
79
pyflo/ellipticcurve/curve.py
Normal file
@ -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]
|
||||||
46
pyflo/ellipticcurve/ecdsa.py
Normal file
46
pyflo/ellipticcurve/ecdsa.py
Normal file
@ -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
|
||||||
177
pyflo/ellipticcurve/math.py
Normal file
177
pyflo/ellipticcurve/math.py
Normal file
@ -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
|
||||||
|
)
|
||||||
14
pyflo/ellipticcurve/point.py
Normal file
14
pyflo/ellipticcurve/point.py
Normal file
@ -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
|
||||||
72
pyflo/ellipticcurve/privateKey.py
Normal file
72
pyflo/ellipticcurve/privateKey.py
Normal file
@ -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-----
|
||||||
|
"""
|
||||||
88
pyflo/ellipticcurve/publicKey.py
Normal file
88
pyflo/ellipticcurve/publicKey.py
Normal file
@ -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-----
|
||||||
|
"""
|
||||||
48
pyflo/ellipticcurve/signature.py
Normal file
48
pyflo/ellipticcurve/signature.py
Normal file
@ -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)
|
||||||
0
pyflo/ellipticcurve/utils/__init__.py
Normal file
0
pyflo/ellipticcurve/utils/__init__.py
Normal file
BIN
pyflo/ellipticcurve/utils/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/utils/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/utils/__pycache__/binary.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/utils/__pycache__/binary.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
pyflo/ellipticcurve/utils/__pycache__/der.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/utils/__pycache__/der.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/utils/__pycache__/file.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/utils/__pycache__/file.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/utils/__pycache__/integer.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/utils/__pycache__/integer.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/utils/__pycache__/oid.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/utils/__pycache__/oid.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/ellipticcurve/utils/__pycache__/pem.cpython-38.pyc
Normal file
BIN
pyflo/ellipticcurve/utils/__pycache__/pem.cpython-38.pyc
Normal file
Binary file not shown.
37
pyflo/ellipticcurve/utils/binary.py
Normal file
37
pyflo/ellipticcurve/utils/binary.py
Normal file
@ -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))
|
||||||
40
pyflo/ellipticcurve/utils/compatibility.py
Normal file
40
pyflo/ellipticcurve/utils/compatibility.py
Normal file
@ -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)
|
||||||
159
pyflo/ellipticcurve/utils/der.py
Normal file
159
pyflo/ellipticcurve/utils/der.py
Normal file
@ -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),
|
||||||
|
}
|
||||||
9
pyflo/ellipticcurve/utils/file.py
Normal file
9
pyflo/ellipticcurve/utils/file.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class File:
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def read(cls, path, mode="r"):
|
||||||
|
with open(path, mode) as blob:
|
||||||
|
content = blob.read()
|
||||||
|
return content
|
||||||
16
pyflo/ellipticcurve/utils/integer.py
Normal file
16
pyflo/ellipticcurve/utils/integer.py
Normal file
@ -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)
|
||||||
35
pyflo/ellipticcurve/utils/oid.py
Normal file
35
pyflo/ellipticcurve/utils/oid.py
Normal file
@ -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"
|
||||||
14
pyflo/ellipticcurve/utils/pem.py
Normal file
14
pyflo/ellipticcurve/utils/pem.py
Normal file
@ -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))
|
||||||
BIN
pyflo/functions/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
pyflo/functions/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/functions/__pycache__/address.cpython-38.pyc
Normal file
BIN
pyflo/functions/__pycache__/address.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/functions/__pycache__/bip32.cpython-38.pyc
Normal file
BIN
pyflo/functions/__pycache__/bip32.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/functions/__pycache__/bip39_mnemonic.cpython-38.pyc
Normal file
BIN
pyflo/functions/__pycache__/bip39_mnemonic.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/functions/__pycache__/block.cpython-38.pyc
Normal file
BIN
pyflo/functions/__pycache__/block.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/functions/__pycache__/encode.cpython-38.pyc
Normal file
BIN
pyflo/functions/__pycache__/encode.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/functions/__pycache__/hash.cpython-38.pyc
Normal file
BIN
pyflo/functions/__pycache__/hash.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/functions/__pycache__/key.cpython-38.pyc
Normal file
BIN
pyflo/functions/__pycache__/key.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/functions/__pycache__/script.cpython-38.pyc
Normal file
BIN
pyflo/functions/__pycache__/script.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pyflo/functions/__pycache__/tools.cpython-38.pyc
Normal file
BIN
pyflo/functions/__pycache__/tools.cpython-38.pyc
Normal file
Binary file not shown.
@ -1,9 +1,9 @@
|
|||||||
from pybtc.opcodes import *
|
from pyflo.opcodes import *
|
||||||
from pybtc.constants import *
|
from pyflo.constants import *
|
||||||
|
|
||||||
from pybtc.functions.tools import bytes_from_hex
|
from pyflo.functions.tools import bytes_from_hex
|
||||||
from pybtc.functions.hash import double_sha256, hash160
|
from pyflo.functions.hash import double_sha256, hash160
|
||||||
from pybtc.functions.encode import (encode_base58,
|
from pyflo.functions.encode import (encode_base58,
|
||||||
rebase_8_to_5,
|
rebase_8_to_5,
|
||||||
bech32_polymod,
|
bech32_polymod,
|
||||||
rebase_5_to_32,
|
rebase_5_to_32,
|
||||||
@ -14,7 +14,7 @@ from pybtc.functions.encode import (encode_base58,
|
|||||||
base32charset_upcase)
|
base32charset_upcase)
|
||||||
|
|
||||||
|
|
||||||
def hash_to_address(address_hash, testnet=False, script_hash=False, witness_version=None):
|
def hash_to_address(address_hash, testnet=False, script_hash=False, witness_version=0):
|
||||||
"""
|
"""
|
||||||
Get address from public key/script hash. In case PUBKEY, P2PKH, P2PKH public key/script hash is SHA256+RIPEMD160,
|
Get address from public key/script hash. In case PUBKEY, P2PKH, P2PKH public key/script hash is SHA256+RIPEMD160,
|
||||||
P2WSH script hash is SHA256.
|
P2WSH script hash is SHA256.
|
||||||
@ -70,7 +70,7 @@ def hash_to_address(address_hash, testnet=False, script_hash=False, witness_vers
|
|||||||
return "%s1%s" % (hrp, rebase_5_to_32(address_hash + checksum).decode())
|
return "%s1%s" % (hrp, rebase_5_to_32(address_hash + checksum).decode())
|
||||||
|
|
||||||
|
|
||||||
def public_key_to_address(pubkey, testnet=False, p2sh_p2wpkh=False, witness_version=None):
|
def public_key_to_address(pubkey, testnet=False, p2sh_p2wpkh=False, witness_version=0):
|
||||||
"""
|
"""
|
||||||
Get address from public key/script hash. In case PUBKEY, P2PKH, P2PKH public key/script hash is SHA256+RIPEMD160,
|
Get address from public key/script hash. In case PUBKEY, P2PKH, P2PKH public key/script hash is SHA256+RIPEMD160,
|
||||||
P2WSH script hash is SHA256.
|
P2WSH script hash is SHA256.
|
||||||
@ -1,11 +1,11 @@
|
|||||||
from struct import pack
|
from struct import pack
|
||||||
from secp256k1 import ffi, lib
|
from secp256k1 import ffi, lib
|
||||||
from pybtc.functions.key import private_to_public_key, private_key_to_wif
|
from pyflo.functions.key import private_to_public_key, private_key_to_wif
|
||||||
from pybtc.functions.hash import hmac_sha512, double_sha256, hash160
|
from pyflo.functions.hash import hmac_sha512, double_sha256, hash160
|
||||||
from pybtc.functions.encode import (encode_base58,
|
from pyflo.functions.encode import (encode_base58,
|
||||||
decode_base58_with_checksum,
|
decode_base58_with_checksum,
|
||||||
encode_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):
|
def create_master_xprivate_key(seed, testnet=False, base58=True, hex=False):
|
||||||
@ -1,8 +1,8 @@
|
|||||||
from pybtc.constants import *
|
from pyflo.constants import *
|
||||||
import time
|
import time
|
||||||
import hashlib
|
import hashlib
|
||||||
from pybtc.functions.hash import sha256
|
from pyflo.functions.hash import sha256
|
||||||
from pybtc.functions.tools import int_from_bytes
|
from pyflo.functions.tools import int_from_bytes
|
||||||
|
|
||||||
def generate_entropy(strength=256, hex=True):
|
def generate_entropy(strength=256, hex=True):
|
||||||
"""
|
"""
|
||||||
@ -1,5 +1,5 @@
|
|||||||
from pybtc.functions.tools import s2rh, bytes_from_hex, int_from_bytes
|
from pyflo.functions.tools import s2rh, bytes_from_hex, int_from_bytes
|
||||||
from pybtc.functions.hash import double_sha256
|
from pyflo.functions.hash import double_sha256
|
||||||
|
|
||||||
def merkle_root(tx_hash_list, hex=True):
|
def merkle_root(tx_hash_list, hex=True):
|
||||||
"""
|
"""
|
||||||
@ -1,5 +1,5 @@
|
|||||||
from pybtc.functions.hash import double_sha256
|
from pyflo.functions.hash import double_sha256
|
||||||
from pybtc.functions.tools import bytes_from_hex
|
from pyflo.functions.tools import bytes_from_hex
|
||||||
|
|
||||||
b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||||
base32charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
base32charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||||
@ -2,9 +2,9 @@ from secp256k1 import ffi, lib
|
|||||||
secp256k1_ec_pubkey_create = lib.secp256k1_ec_pubkey_create
|
secp256k1_ec_pubkey_create = lib.secp256k1_ec_pubkey_create
|
||||||
secp256k1_ec_pubkey_serialize = lib.secp256k1_ec_pubkey_serialize
|
secp256k1_ec_pubkey_serialize = lib.secp256k1_ec_pubkey_serialize
|
||||||
|
|
||||||
from pybtc.constants import *
|
from pyflo.constants import *
|
||||||
from pybtc.functions.encode import encode_base58, decode_base58
|
from pyflo.functions.encode import encode_base58, decode_base58
|
||||||
from pybtc.functions.hash import double_sha256
|
from pyflo.functions.hash import double_sha256
|
||||||
from .bip39_mnemonic import generate_entropy
|
from .bip39_mnemonic import generate_entropy
|
||||||
|
|
||||||
bytes_from_hex = bytes.fromhex
|
bytes_from_hex = bytes.fromhex
|
||||||
@ -1,5 +1,9 @@
|
|||||||
from struct import unpack
|
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
|
from secp256k1 import ffi, lib
|
||||||
secp256k1_ecdsa_signature_parse_der = lib.secp256k1_ecdsa_signature_parse_der
|
secp256k1_ecdsa_signature_parse_der = lib.secp256k1_ecdsa_signature_parse_der
|
||||||
secp256k1_ec_pubkey_parse = lib.secp256k1_ec_pubkey_parse
|
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_ecdsa_recover = lib.secp256k1_ecdsa_recover
|
||||||
secp256k1_ec_pubkey_serialize = lib.secp256k1_ec_pubkey_serialize
|
secp256k1_ec_pubkey_serialize = lib.secp256k1_ec_pubkey_serialize
|
||||||
|
|
||||||
from pybtc.opcodes import *
|
from pyflo.opcodes import *
|
||||||
from pybtc.constants import *
|
from pyflo.constants import *
|
||||||
|
|
||||||
from pybtc.functions.tools import bytes_from_hex, int_to_bytes, get_stream
|
from pyflo.functions.tools import bytes_from_hex, int_to_bytes, get_stream
|
||||||
from pybtc.functions.hash import hash160, sha256
|
from pyflo.functions.hash import hash160, sha256
|
||||||
from pybtc.functions.address import hash_to_address
|
from pyflo.functions.address import hash_to_address
|
||||||
from pybtc.functions.key import is_wif_valid, wif_to_private_key
|
from pyflo.functions.key import is_wif_valid, wif_to_private_key
|
||||||
|
|
||||||
|
|
||||||
def public_key_to_pubkey_script(key, hex=True):
|
def public_key_to_pubkey_script(key, hex=True):
|
||||||
@ -400,11 +404,50 @@ def verify_signature(sig, pub_key, msg):
|
|||||||
result = secp256k1_ecdsa_verify(ECDSA_CONTEXT_VERIFY, raw_sig, msg, raw_pubkey)
|
result = secp256k1_ecdsa_verify(ECDSA_CONTEXT_VERIFY, raw_sig, msg, raw_pubkey)
|
||||||
return True if result else False
|
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
|
||||||
|
|
||||||
|
# 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, hex=True):
|
def sign_message(msg, private_key, hex=True):
|
||||||
"""
|
"""
|
||||||
Sign message
|
Sign message
|
||||||
|
|
||||||
:param msg: message to sign bytes or HEX encoded string.
|
:param msg: message to sign bytes or HEX encoded string.
|
||||||
:param private_key: private key (bytes, hex encoded string or WIF format)
|
:param private_key: private key (bytes, hex encoded string or WIF format)
|
||||||
:param hex: (optional) If set to True return key in HEX format, by default is True.
|
:param hex: (optional) If set to True return key in HEX format, by default is True.
|
||||||
@ -447,6 +490,25 @@ def sign_message(msg, private_key, hex=True):
|
|||||||
return signature.hex() if hex else signature
|
return signature.hex() if hex else signature
|
||||||
|
|
||||||
|
|
||||||
|
def sign_message_standard_ops(msg, private_key):
|
||||||
|
"""
|
||||||
|
Sign message
|
||||||
|
|
||||||
|
: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(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:]
|
||||||
|
|
||||||
|
|
||||||
def public_key_recovery(signature, messsage, rec_id, compressed=True, hex=True):
|
def public_key_recovery(signature, messsage, rec_id, compressed=True, hex=True):
|
||||||
if isinstance(signature, str):
|
if isinstance(signature, str):
|
||||||
signature = bytes_from_hex(signature)
|
signature = bytes_from_hex(signature)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user