Compare commits
17 Commits
standard_o
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee50ef60b9 | ||
|
|
64119f8baa | ||
|
|
1f7cbcfa13 | ||
|
|
c4f1c8b5af | ||
|
|
3618687377 | ||
|
|
498d0064cd | ||
|
|
c9a58a7305 | ||
|
|
a386de5197 | ||
|
|
0a3bf90767 | ||
| 042f3da96e | |||
|
|
282edba6a0 | ||
|
|
09c24b370f | ||
|
|
05f2f09043 | ||
|
|
ea9816f198 | ||
|
|
11c5ff96a3 | ||
|
|
34198f7313 | ||
|
|
c8db052b42 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,4 +3,6 @@ build/
|
||||
dist/
|
||||
.github/
|
||||
pybtc.egg-info/
|
||||
|
||||
pyflo.egg-info/
|
||||
pyflo_lib.egg-info/
|
||||
*.pyc
|
||||
128
README.md
128
README.md
@ -1,8 +1,6 @@
|
||||
## Python bitcoin library modified for FLO
|
||||
|
||||
|
||||
### Installation
|
||||
|
||||
To install 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.
|
||||
|
||||
```
|
||||
>>> import pybtc
|
||||
>>> import pyflo
|
||||
|
||||
# ADDRESS GENERATION
|
||||
>>> a = pybtc.Address(address_type="P2PKH")
|
||||
>>> a = pyflo.Address(address_type="P2PKH")
|
||||
>>> a.address
|
||||
'FTP7LL7QjhgKfqYX1pis18bCqEpZaGSRzZ'
|
||||
>>> a.private_key.wif
|
||||
@ -34,13 +32,13 @@ Every message sent to the Blockchain is in hash format, and not in plain string.
|
||||
'033c30b269e2d5df229f3f0ce294b19c4f0a3a8d12280415ce41e7bd3784a619c4'
|
||||
|
||||
# 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'
|
||||
>>> pybtc.sha256(b'vivek'.hex()).hex()
|
||||
>>> pyflo.sha256(b'vivek'.hex()).hex()
|
||||
'a3da73976501812cd7b821a20bfb09af0a6a891e419cdfb761fb19a92c914242'
|
||||
>>> msg = b'vivek'.hex()
|
||||
>>> msg_hash_hex = sha256(msg).hex()
|
||||
>>> msg_hash_bytes = sha256(msg)
|
||||
>>> msg_hash_hex = pyflo.sha256(msg).hex()
|
||||
>>> msg_hash_bytes = pyflo.sha256(msg)
|
||||
>>> msg
|
||||
'766976656b'
|
||||
>>> msg_hash_hex
|
||||
@ -49,10 +47,120 @@ 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'
|
||||
|
||||
# SIGN AND VERIFY THE MESSAGE
|
||||
>>> sig_msg_hex = pybtc.sign_message(msg_hash_hex, a.private_key.wif)
|
||||
>>> pybtc.verify_signature(sig_msg_hex, a.public_key.hex, msg_hash_hex)
|
||||
>>> sig_msg_hex = pyflo.sign_message(msg_hash_hex, a.private_key.wif)
|
||||
>>> pyflo.verify_signature(sig_msg_hex, a.public_key.hex, msg_hash_hex)
|
||||
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
|
||||
|
||||
```
|
||||
|
||||
The **sign_message_standard_ops** function verifies with [RanchiMall standard operations](https://github.com/ranchimall/Standard_Operations).
|
||||
Things to note:
|
||||
The function now takes 2 parameters -
|
||||
|
||||
1. Message in string ( not hex-encoded string)
|
||||
2. Private key in wif (not bytes etc.)
|
||||
The hex parameter has been removed as we are always returning hex in standard ops
|
||||
|
||||
New libraries used - hashlib
|
||||
The elliptical curve folder, holds the code taken from starkbank ecdsa (MIT License)
|
||||
no other additional dependencies
|
||||
|
||||
The signature generated with **sign_message_standard_ops** cannot be verified using purely Pyflo. Users can use the following API to verify signatures
|
||||
|
||||
** Python **
|
||||
```
|
||||
import requests
|
||||
|
||||
url = 'https://flo-sign-validator.duckdns.org'
|
||||
myobj = {'floID': floID,
|
||||
'pubKey': pubKey,
|
||||
'message': message,
|
||||
'sign': sign}
|
||||
|
||||
x = requests.post(url, json = myobj)
|
||||
print(x.text)
|
||||
|
||||
```
|
||||
|
||||
** JavaScript **
|
||||
```
|
||||
fetch("https://flo-sign-validator.duckdns.org", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
floID: floID,
|
||||
pubKey: pubKey,
|
||||
message: message,
|
||||
sign: sign
|
||||
}),
|
||||
headers: {
|
||||
"Content-type": "application/json; charset=UTF-8",
|
||||
},
|
||||
})
|
||||
.then(function (response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
console.log(data);
|
||||
})
|
||||
.catch((error) => console.error("Error:", error));
|
||||
|
||||
```
|
||||
|
||||
** PHP **
|
||||
```
|
||||
function callAPI($method, $url, $data){
|
||||
$curl = curl_init();
|
||||
switch ($method){
|
||||
case "POST":
|
||||
curl_setopt($curl, CURLOPT_POST, 1);
|
||||
if ($data)
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
||||
break;
|
||||
case "PUT":
|
||||
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||
if ($data)
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
||||
break;
|
||||
default:
|
||||
if ($data)
|
||||
$url = sprintf("%s?%s", $url, http_build_query($data));
|
||||
}
|
||||
// OPTIONS:
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
|
||||
'APIKEY: 111111111111111111111',
|
||||
'Content-Type: application/json',
|
||||
));
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
|
||||
// EXECUTE:
|
||||
$result = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
return $result;
|
||||
}
|
||||
|
||||
$floID = $_POST['floID'];
|
||||
$pubKey = $_POST['floPubKey'];
|
||||
$message = $_POST['message'];
|
||||
$signDataWithFlo = $_POST['signDataWithFlo'];
|
||||
|
||||
|
||||
$data_array = array( "floID" => $floID, "pubKey" => $pubKey, "message" => $message, "sign" => $signDataWithFlo );
|
||||
$make_call = callAPI('POST', 'https://flo-sign-validator.duckdns.org', json_encode($data_array));
|
||||
$response = json_decode($make_call, true);
|
||||
|
||||
print_r($response);
|
||||
|
||||
```
|
||||
|
||||
|
||||
@ -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 *
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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 *
|
||||
@ -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,
|
||||
@ -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):
|
||||
@ -14,7 +14,7 @@ ECDSA_SEC256K1_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8
|
||||
|
||||
MAINNET_ADDRESS_BYTE_PREFIX = b'\x23'
|
||||
TESTNET_ADDRESS_BYTE_PREFIX = b'\x73'
|
||||
MAINNET_SCRIPT_ADDRESS_BYTE_PREFIX = b'\x05'
|
||||
MAINNET_SCRIPT_ADDRESS_BYTE_PREFIX = b'\x5e'
|
||||
TESTNET_SCRIPT_ADDRESS_BYTE_PREFIX = b'\xc4'
|
||||
MAINNET_SEGWIT_ADDRESS_BYTE_PREFIX = b'\x03\x03\x00\x02\x03'
|
||||
TESTNET_SEGWIT_ADDRESS_BYTE_PREFIX = b'\x03\x03\x00\x14\x02'
|
||||
@ -22,7 +22,8 @@ TESTNET_SEGWIT_ADDRESS_BYTE_PREFIX = b'\x03\x03\x00\x14\x02'
|
||||
MAINNET_ADDRESS_PREFIX = 'F'
|
||||
TESTNET_ADDRESS_PREFIX = 'o'
|
||||
TESTNET_ADDRESS_PREFIX_2 = 'o'
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX = '3'
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX = 'e'
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX_2 = 'f'
|
||||
TESTNET_SCRIPT_ADDRESS_PREFIX = '2'
|
||||
|
||||
MAINNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX = '5'
|
||||
@ -35,6 +36,7 @@ ADDRESS_PREFIX_LIST = (MAINNET_ADDRESS_PREFIX,
|
||||
TESTNET_ADDRESS_PREFIX,
|
||||
TESTNET_ADDRESS_PREFIX_2,
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX,
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX_2,
|
||||
TESTNET_SCRIPT_ADDRESS_PREFIX)
|
||||
|
||||
PRIVATE_KEY_PREFIX_LIST = (MAINNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX,
|
||||
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 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,
|
||||
@ -14,7 +14,7 @@ from pybtc.functions.encode import (encode_base58,
|
||||
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,
|
||||
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())
|
||||
|
||||
|
||||
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,
|
||||
P2WSH script hash is SHA256.
|
||||
@ -129,7 +129,8 @@ def address_type(address, num=False):
|
||||
:return: address type in string or numeric format.
|
||||
"""
|
||||
if address[0] in (TESTNET_SCRIPT_ADDRESS_PREFIX,
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX):
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX,
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX_2):
|
||||
t = 'P2SH'
|
||||
elif address[0] in (MAINNET_ADDRESS_PREFIX,
|
||||
TESTNET_ADDRESS_PREFIX,
|
||||
@ -155,7 +156,7 @@ def address_net_type(address):
|
||||
:param address: address in base58 or bech32 format.
|
||||
:return: address network type in string format or None.
|
||||
"""
|
||||
if address[0] in (MAINNET_SCRIPT_ADDRESS_PREFIX,
|
||||
if address[0] in (MAINNET_SCRIPT_ADDRESS_PREFIX, MAINNET_SCRIPT_ADDRESS_PREFIX_2,
|
||||
MAINNET_ADDRESS_PREFIX):
|
||||
return "mainnet"
|
||||
elif address[:2] == MAINNET_SEGWIT_ADDRESS_PREFIX:
|
||||
@ -178,7 +179,8 @@ def address_to_script(address, hex=False):
|
||||
:return: public key script in HEX or bytes string.
|
||||
"""
|
||||
if address[0] in (TESTNET_SCRIPT_ADDRESS_PREFIX,
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX):
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX,
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX_2):
|
||||
s = [OP_HASH160,
|
||||
b'\x14',
|
||||
address_to_hash(address, hex=False),
|
||||
@ -222,6 +224,7 @@ def is_address_valid(address, testnet=False):
|
||||
return False
|
||||
if address[0] in (MAINNET_ADDRESS_PREFIX,
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX,
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX_2,
|
||||
TESTNET_ADDRESS_PREFIX,
|
||||
TESTNET_ADDRESS_PREFIX_2,
|
||||
TESTNET_SCRIPT_ADDRESS_PREFIX):
|
||||
@ -232,7 +235,8 @@ def is_address_valid(address, testnet=False):
|
||||
return False
|
||||
else:
|
||||
if address[0] not in (MAINNET_ADDRESS_PREFIX,
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX):
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX,
|
||||
MAINNET_SCRIPT_ADDRESS_PREFIX_2):
|
||||
return False
|
||||
h = decode_base58(address)
|
||||
if len(h) != 25:
|
||||
@ -275,11 +279,11 @@ def is_address_valid(address, testnet=False):
|
||||
if checksum != checksum2:
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_witness_version(address):
|
||||
address = address.split("1")[1]
|
||||
h = rebase_32_to_5(address)
|
||||
return h[0]
|
||||
|
||||
|
||||
@ -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):
|
||||
@ -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):
|
||||
"""
|
||||
@ -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):
|
||||
"""
|
||||
@ -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"
|
||||
@ -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
|
||||
@ -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,14 @@ 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
|
||||
import requests, json
|
||||
|
||||
|
||||
def public_key_to_pubkey_script(key, hex=True):
|
||||
@ -161,9 +166,6 @@ def script_to_address(script, testnet=False):
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def decode_script(script, asm=False):
|
||||
"""
|
||||
Decode script to ASM format or to human readable OPCODES string.
|
||||
@ -401,10 +403,76 @@ def verify_signature(sig, pub_key, msg):
|
||||
return True if result else False
|
||||
|
||||
|
||||
def verify_signature_standard_ops(sig, pub_key, msg, floID):
|
||||
"""
|
||||
Verify signature for message and given public key
|
||||
|
||||
:param sig: signature in bytes or HEX encoded string.
|
||||
:param pub_key: public key in bytes or HEX encoded string.
|
||||
:param msg: message in bytes, string or HEX encoded string.
|
||||
:flo_id: FLO ID in HEX encoded string.
|
||||
:return: boolean.
|
||||
"""
|
||||
url = 'https://flo-sign-validator.duckdns.org'
|
||||
post_data = {
|
||||
'floID': floID,
|
||||
'pubKey': pub_key,
|
||||
'message': msg,
|
||||
'sign': sig
|
||||
}
|
||||
signature_verification = requests.post(url, json = post_data)
|
||||
signature_verification = json.loads(signature_verification.text)
|
||||
if signature_verification['success']:
|
||||
return True
|
||||
else:
|
||||
return 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):
|
||||
"""
|
||||
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 hex: (optional) If set to True return key in HEX format, by default is True.
|
||||
@ -447,6 +515,25 @@ def sign_message(msg, private_key, hex=True):
|
||||
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):
|
||||
if isinstance(signature, str):
|
||||
signature = bytes_from_hex(signature)
|
||||
@ -551,5 +638,4 @@ def is_valid_signature_encoding(sig):
|
||||
# interpreted as a negative number.
|
||||
if (len_s > 1) and (sig[len_r + 6] == 0x00) and (not sig[len_r + 7] & 0x80):
|
||||
return False
|
||||
return True
|
||||
|
||||
return True
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user