Pure functions docs
This commit is contained in:
parent
bf906e24e2
commit
4ecdadc0f4
@ -19,6 +19,7 @@ sys.path.insert(0, os.path.abspath('.'))
|
|||||||
sys.path.insert(0, os.path.abspath('..'))
|
sys.path.insert(0, os.path.abspath('..'))
|
||||||
sys.path.insert(0, os.path.abspath('../_static/'))
|
sys.path.insert(0, os.path.abspath('../_static/'))
|
||||||
sys.path.insert(0, os.path.abspath('../../_static/'))
|
sys.path.insert(0, os.path.abspath('../../_static/'))
|
||||||
|
sys.path.insert(0, os.path.abspath('../../pybtc/'))
|
||||||
sys.path.insert(0, os.path.abspath('./_static/'))
|
sys.path.insert(0, os.path.abspath('./_static/'))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
49
docs/source/functional.rst
Normal file
49
docs/source/functional.rst
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
==============
|
||||||
|
Pure functions
|
||||||
|
==============
|
||||||
|
|
||||||
|
Base function primitives implemeted in functional programming paradigm.
|
||||||
|
|
||||||
|
Key management
|
||||||
|
==============
|
||||||
|
|
||||||
|
Tools for private and public key managment
|
||||||
|
|
||||||
|
|
||||||
|
Private key
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. autofunction:: pybtc.create_private_key
|
||||||
|
.. autofunction:: pybtc.private_key_to_wif
|
||||||
|
.. autofunction:: pybtc.wif_to_private_key
|
||||||
|
.. autofunction:: pybtc.is_wif_valid
|
||||||
|
|
||||||
|
|
||||||
|
Public key
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. WARNING::
|
||||||
|
Using uncompressed public keys is
|
||||||
|
`deprecated <https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#restrictions-on-public-key-type>`_
|
||||||
|
in a new SEGWIT address format.
|
||||||
|
To avoid potential future funds loss, users MUST NOT use uncompressed keys
|
||||||
|
in version 0 witness programs. Use uncompressed keys only for backward
|
||||||
|
compatibilitylegacy in legacy address format (PUBKEY, P2PKH).
|
||||||
|
|
||||||
|
|
||||||
|
.. autofunction:: pybtc.private_to_public_key
|
||||||
|
.. autofunction:: pybtc.is_public_key_valid
|
||||||
|
|
||||||
|
|
||||||
|
Addresses
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. autofunction:: pybtc.hash_to_address
|
||||||
|
.. autofunction:: pybtc.address_to_hash
|
||||||
|
.. autofunction:: pybtc.public_key_to_address
|
||||||
|
.. autofunction:: pybtc.address_type
|
||||||
|
.. autofunction:: pybtc.address_to_script
|
||||||
|
.. autofunction:: pybtc.is_address_valid
|
||||||
|
|
||||||
|
|
||||||
@ -111,4 +111,4 @@ Table Of Contents
|
|||||||
:name: mastertoc
|
:name: mastertoc
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
tools.rst
|
functional.rst
|
||||||
@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
Tools
|
|
||||||
======
|
|
||||||
|
|
||||||
.. autofunction:: pybtc.tools.sign_message
|
|
||||||
386
pybtc/tools.py
386
pybtc/tools.py
@ -9,33 +9,53 @@ import math
|
|||||||
import io
|
import io
|
||||||
|
|
||||||
|
|
||||||
# Bitcoin keys
|
# Key management
|
||||||
#
|
|
||||||
def create_private_key(compressed=True, testnet=False, wif=True):
|
def create_private_key(compressed=True, testnet=False, wif=True, hex=False):
|
||||||
"""
|
"""
|
||||||
:return: 32 bytes private key
|
Create private key
|
||||||
|
|
||||||
|
:param compressed: (optional) Type of public key, by default set to compressed.
|
||||||
|
Using uncompressed public keys is deprecated in new SEGWIT addresses,
|
||||||
|
use this option only for backward compatibility.
|
||||||
|
:param testnet: (optional) flag for testnet network, by default is False.
|
||||||
|
:param wif: (optional) If set to True return key in WIF format, by default is True.
|
||||||
|
:param hex: (optional) If set to True return key in HEX format, by default is False.
|
||||||
|
:return: Private key in wif format (default), hex encoded byte string in case of hex flag or
|
||||||
|
raw bytes string in case wif and hex flags set to False.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
a = random.SystemRandom().randint(0, MAX_INT_PRIVATE_KEY)
|
a = random.SystemRandom().randint(0, MAX_INT_PRIVATE_KEY)
|
||||||
i = int((time.time() % 0.01)*100000)
|
i = int((time.time() % 0.01)*100000)
|
||||||
h = a.to_bytes(32, byteorder="big")
|
h = a.to_bytes(32, byteorder="big")
|
||||||
while True:
|
# more entropy from system timer and sha256 derivation
|
||||||
|
while i:
|
||||||
h = hashlib.sha256(h).digest()
|
h = hashlib.sha256(h).digest()
|
||||||
if i > 1:
|
i -= 1
|
||||||
i -= 1
|
if not i and int.from_bytes(h, byteorder="big") > MAX_INT_PRIVATE_KEY:
|
||||||
else:
|
i += 1
|
||||||
if int.from_bytes(h, byteorder="big") < MAX_INT_PRIVATE_KEY:
|
if wif:
|
||||||
break
|
|
||||||
if hex:
|
|
||||||
return private_key_to_wif(h)
|
return private_key_to_wif(h)
|
||||||
|
elif hex:
|
||||||
|
return hexlify(h).decode()
|
||||||
return h
|
return h
|
||||||
|
|
||||||
|
|
||||||
def private_key_to_wif(h, compressed=True, testnet=False):
|
def private_key_to_wif(h, compressed=True, testnet=False):
|
||||||
# uncompressed: 0x80 + [32-byte secret] + [4 bytes of Hash() of previous 33 bytes], base58 encoded
|
"""
|
||||||
# compressed: 0x80 + [32-byte secret] + 0x01 + [4 bytes of Hash() previous 34 bytes], base58 encoded
|
Encode private key in HEX or RAW bytes format to WIF format.
|
||||||
if type(h) == str:
|
|
||||||
|
:param h: private key 32 byte string or HEX encoded string.
|
||||||
|
:param compressed: (optional) flag of public key compressed format, by default set to True.
|
||||||
|
:param testnet: (optional) flag for testnet network, by default is False.
|
||||||
|
:return: Private key in WIF format
|
||||||
|
"""
|
||||||
|
# uncompressed: 0x80 + [32-byte secret] + [4 bytes of Hash() of previous 33 bytes], base58 encoded.
|
||||||
|
# compressed: 0x80 + [32-byte secret] + 0x01 + [4 bytes of Hash() previous 34 bytes], base58 encoded.
|
||||||
|
if isinstance(h, str):
|
||||||
h = unhexlify(h)
|
h = unhexlify(h)
|
||||||
assert len(h) == 32
|
if len(h) != 32 and isinstance(h, bytes):
|
||||||
|
raise TypeError("private key must be a 32 bytes or hex encoded string")
|
||||||
if testnet:
|
if testnet:
|
||||||
h = TESTNET_PRIVATE_KEY_BYTE_PREFIX + h
|
h = TESTNET_PRIVATE_KEY_BYTE_PREFIX + h
|
||||||
else:
|
else:
|
||||||
@ -46,8 +66,15 @@ def private_key_to_wif(h, compressed=True, testnet=False):
|
|||||||
return encode_base58(h)
|
return encode_base58(h)
|
||||||
|
|
||||||
|
|
||||||
def wif_to_private_key(h, hex=False):
|
def wif_to_private_key(h, hex=True):
|
||||||
assert is_wif_valid(h)
|
"""
|
||||||
|
Decode WIF private key to bytes string or HEX encoded string
|
||||||
|
|
||||||
|
:param hex: (optional) if set to True return key in HEX format, by default is True.
|
||||||
|
:return: Private key HEX encoded string or raw bytes string.
|
||||||
|
"""
|
||||||
|
if not is_wif_valid(h):
|
||||||
|
raise TypeError("invalid wif key")
|
||||||
h = decode_base58(h)
|
h = decode_base58(h)
|
||||||
if hex:
|
if hex:
|
||||||
return hexlify(h[1:33]).decode()
|
return hexlify(h[1:33]).decode()
|
||||||
@ -55,7 +82,14 @@ def wif_to_private_key(h, hex=False):
|
|||||||
|
|
||||||
|
|
||||||
def is_wif_valid(wif):
|
def is_wif_valid(wif):
|
||||||
assert type(wif) == str
|
"""
|
||||||
|
Check is private key in WIF format string is valid.
|
||||||
|
|
||||||
|
:param wif: private key in WIF format string.
|
||||||
|
:return: boolean
|
||||||
|
"""
|
||||||
|
if not isinstance(wif, str):
|
||||||
|
raise TypeError("invalid wif key")
|
||||||
if wif[0] not in PRIVATE_KEY_PREFIX_LIST:
|
if wif[0] not in PRIVATE_KEY_PREFIX_LIST:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
@ -75,10 +109,20 @@ def is_wif_valid(wif):
|
|||||||
|
|
||||||
|
|
||||||
def private_to_public_key(private_key, compressed=True, hex=True):
|
def private_to_public_key(private_key, compressed=True, hex=True):
|
||||||
if type(private_key)!= bytes:
|
"""
|
||||||
if type(private_key) == bytearray:
|
Get public key from private key using ECDSA secp256k1
|
||||||
|
|
||||||
|
:param private_key: private key in WIF, HEX or bytes.
|
||||||
|
:param compressed: (optional) flag of public key compressed format, by default set to True.
|
||||||
|
In case private_key in WIF format, this flag is set in accordance with
|
||||||
|
the key format specified in WIF string.
|
||||||
|
:param hex: (optional) if set to True return key in HEX format, by default is True.
|
||||||
|
:return: 33/65 bytes public key in HEX or bytes string
|
||||||
|
"""
|
||||||
|
if not isinstance(private_key, bytes):
|
||||||
|
if isinstance(private_key, bytearray):
|
||||||
private_key = bytes(private_key)
|
private_key = bytes(private_key)
|
||||||
elif type(private_key) == str:
|
elif isinstance(private_key, str):
|
||||||
if not is_wif_valid(private_key):
|
if not is_wif_valid(private_key):
|
||||||
private_key = unhexlify(private_key)
|
private_key = unhexlify(private_key)
|
||||||
else:
|
else:
|
||||||
@ -90,18 +134,26 @@ def private_to_public_key(private_key, compressed=True, hex=True):
|
|||||||
raise TypeError("private key must be a bytes or WIF or hex encoded string")
|
raise TypeError("private key must be a bytes or WIF or hex encoded string")
|
||||||
pubkey_ptr = ffi.new('secp256k1_pubkey *')
|
pubkey_ptr = ffi.new('secp256k1_pubkey *')
|
||||||
r = secp256k1.secp256k1_ec_pubkey_create(ECDSA_CONTEXT_ALL, pubkey_ptr, private_key)
|
r = secp256k1.secp256k1_ec_pubkey_create(ECDSA_CONTEXT_ALL, pubkey_ptr, private_key)
|
||||||
assert r == 1
|
if not r:
|
||||||
|
raise RuntimeError("secp256k1 error")
|
||||||
len_key = 33 if compressed else 65
|
len_key = 33 if compressed else 65
|
||||||
pubkey = ffi.new('char [%d]' % len_key)
|
pubkey = ffi.new('char [%d]' % len_key)
|
||||||
outlen = ffi.new('size_t *', len_key)
|
outlen = ffi.new('size_t *', len_key)
|
||||||
compflag = EC_COMPRESSED if compressed else EC_UNCOMPRESSED
|
compflag = EC_COMPRESSED if compressed else EC_UNCOMPRESSED
|
||||||
r = secp256k1.secp256k1_ec_pubkey_serialize(ECDSA_CONTEXT_VERIFY, pubkey, outlen, pubkey_ptr, compflag)
|
r = secp256k1.secp256k1_ec_pubkey_serialize(ECDSA_CONTEXT_VERIFY, pubkey, outlen, pubkey_ptr, compflag)
|
||||||
assert r == 1
|
|
||||||
pub = bytes(ffi.buffer(pubkey, len_key))
|
pub = bytes(ffi.buffer(pubkey, len_key))
|
||||||
|
if not r:
|
||||||
|
raise RuntimeError("secp256k1 error")
|
||||||
return hexlify(pub).decode() if hex else pub
|
return hexlify(pub).decode() if hex else pub
|
||||||
|
|
||||||
|
|
||||||
def is_valid_public_key(key):
|
def is_public_key_valid(key):
|
||||||
|
"""
|
||||||
|
Check public key is valid.
|
||||||
|
|
||||||
|
:param key: public key in HEX or bytes string format.
|
||||||
|
:return: boolean
|
||||||
|
"""
|
||||||
if isinstance(key, str):
|
if isinstance(key, str):
|
||||||
key = unhexlify(key)
|
key = unhexlify(key)
|
||||||
if len(key) < 33:
|
if len(key) < 33:
|
||||||
@ -114,18 +166,30 @@ def is_valid_public_key(key):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
#
|
# Addresses
|
||||||
# Bitcoin addresses
|
|
||||||
#
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
def hash_to_address(address_hash, testnet=False,
|
:param address_hash: public key hash or script hash in HEX or bytes string format.
|
||||||
script_hash=False, witness_version=0):
|
:param testnet: (optional) flag for testnet network, by default is False.
|
||||||
if type(address_hash) == str:
|
:param script_hash: (optional) flag for script hash (P2SH address), by default is False.
|
||||||
|
:param witness_version: (optional) witness program version, by default is 0, for legacy
|
||||||
|
address format use None.
|
||||||
|
:return: address in base58 or bech32 format.
|
||||||
|
"""
|
||||||
|
if isinstance(address_hash, str):
|
||||||
address_hash = unhexlify(address_hash)
|
address_hash = unhexlify(address_hash)
|
||||||
|
if not isinstance(address_hash, bytes):
|
||||||
|
raise TypeError("address hash must be HEX encoded string or bytes")
|
||||||
|
|
||||||
if not script_hash:
|
if not script_hash:
|
||||||
if witness_version is None:
|
if witness_version is None:
|
||||||
assert len(address_hash) == 20
|
if len(address_hash) != 20:
|
||||||
|
raise TypeError("address hash length incorrect")
|
||||||
if testnet:
|
if testnet:
|
||||||
prefix = TESTNET_ADDRESS_BYTE_PREFIX
|
prefix = TESTNET_ADDRESS_BYTE_PREFIX
|
||||||
else:
|
else:
|
||||||
@ -134,7 +198,9 @@ def hash_to_address(address_hash, testnet=False,
|
|||||||
address_hash += double_sha256(address_hash)[:4]
|
address_hash += double_sha256(address_hash)[:4]
|
||||||
return encode_base58(address_hash)
|
return encode_base58(address_hash)
|
||||||
else:
|
else:
|
||||||
assert len(address_hash) in (20,32)
|
if len(address_hash) not in (20, 32):
|
||||||
|
raise TypeError("address hash length incorrect")
|
||||||
|
|
||||||
if witness_version is None:
|
if witness_version is None:
|
||||||
if testnet:
|
if testnet:
|
||||||
prefix = TESTNET_SCRIPT_ADDRESS_BYTE_PREFIX
|
prefix = TESTNET_SCRIPT_ADDRESS_BYTE_PREFIX
|
||||||
@ -143,19 +209,59 @@ def hash_to_address(address_hash, testnet=False,
|
|||||||
address_hash = prefix + address_hash
|
address_hash = prefix + address_hash
|
||||||
address_hash += double_sha256(address_hash)[:4]
|
address_hash += double_sha256(address_hash)[:4]
|
||||||
return encode_base58(address_hash)
|
return encode_base58(address_hash)
|
||||||
|
|
||||||
if testnet:
|
if testnet:
|
||||||
prefix = TESTNET_SEGWIT_ADDRESS_BYTE_PREFIX
|
prefix = TESTNET_SEGWIT_ADDRESS_BYTE_PREFIX
|
||||||
hrp = TESTNET_SEGWIT_ADDRESS_PREFIX
|
hrp = TESTNET_SEGWIT_ADDRESS_PREFIX
|
||||||
else:
|
else:
|
||||||
prefix = MAINNET_SEGWIT_ADDRESS_BYTE_PREFIX
|
prefix = MAINNET_SEGWIT_ADDRESS_BYTE_PREFIX
|
||||||
hrp = MAINNET_SEGWIT_ADDRESS_PREFIX
|
hrp = MAINNET_SEGWIT_ADDRESS_PREFIX
|
||||||
|
|
||||||
address_hash = witness_version.to_bytes(1, "big") + rebase_8_to_5(address_hash)
|
address_hash = witness_version.to_bytes(1, "big") + rebase_8_to_5(address_hash)
|
||||||
checksum = bech32_polymod(prefix + address_hash + b"\x00" * 6)
|
checksum = bech32_polymod(prefix + address_hash + b"\x00" * 6)
|
||||||
checksum = rebase_8_to_5(checksum.to_bytes(5, "big"))[2:]
|
checksum = rebase_8_to_5(checksum.to_bytes(5, "big"))[2:]
|
||||||
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 address_to_hash(address, hex=False):
|
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.
|
||||||
|
|
||||||
|
:param pubkey: public key HEX or bytes string format.
|
||||||
|
:param testnet: (optional) flag for testnet network, by default is False.
|
||||||
|
:param p2sh_p2wpkh: (optional) flag for P2WPKH inside P2SH address, by default is False.
|
||||||
|
:param witness_version: (optional) witness program version, by default is 0, for legacy
|
||||||
|
address format use None.
|
||||||
|
:return: address in base58 or bech32 format.
|
||||||
|
"""
|
||||||
|
if isinstance(pubkey, str):
|
||||||
|
pubkey = unhexlify(pubkey)
|
||||||
|
if not isinstance(pubkey, bytes):
|
||||||
|
raise TypeError("public key invalid")
|
||||||
|
if p2sh_p2wpkh:
|
||||||
|
if len(pubkey) != 33:
|
||||||
|
raise TypeError("public key invalid")
|
||||||
|
h = hash160(b'\x00\x14' + hash160(pubkey))
|
||||||
|
witness_version = None
|
||||||
|
else:
|
||||||
|
if witness_version is not None:
|
||||||
|
if len(pubkey) != 33:
|
||||||
|
raise TypeError("public key invalid")
|
||||||
|
h = hash160(pubkey)
|
||||||
|
return hash_to_address(h, testnet=testnet,
|
||||||
|
script_hash=p2sh_p2wpkh,
|
||||||
|
witness_version=witness_version)
|
||||||
|
|
||||||
|
|
||||||
|
def address_to_hash(address, hex=True):
|
||||||
|
"""
|
||||||
|
Get address hash from base58 or bech32 address format.
|
||||||
|
|
||||||
|
:param address: address in base58 or bech32 format.
|
||||||
|
:param hex: (optional) If set to True return key in HEX format, by default is True.
|
||||||
|
:return: script in HEX or bytes string
|
||||||
|
"""
|
||||||
if address[0] in ADDRESS_PREFIX_LIST:
|
if address[0] in ADDRESS_PREFIX_LIST:
|
||||||
h = decode_base58(address)[1:-4]
|
h = decode_base58(address)[1:-4]
|
||||||
elif address[:2] in (MAINNET_SEGWIT_ADDRESS_PREFIX,
|
elif address[:2] in (MAINNET_SEGWIT_ADDRESS_PREFIX,
|
||||||
@ -164,19 +270,17 @@ def address_to_hash(address, hex=False):
|
|||||||
h = rebase_5_to_8(rebase_32_to_5(address)[1:-6], False)
|
h = rebase_5_to_8(rebase_32_to_5(address)[1:-6], False)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
if hex:
|
return h.hex() if hex else h
|
||||||
return h.hex()
|
|
||||||
else:
|
|
||||||
return h
|
|
||||||
|
|
||||||
|
|
||||||
def get_witness_version(address):
|
|
||||||
address = address.split("1")[1]
|
|
||||||
h = rebase_32_to_5(address)
|
|
||||||
return h[0]
|
|
||||||
|
|
||||||
|
|
||||||
def address_type(address, num=False):
|
def address_type(address, num=False):
|
||||||
|
"""
|
||||||
|
Get address type.
|
||||||
|
|
||||||
|
:param address: address in base58 or bech32 format.
|
||||||
|
:param num: (optional) If set to True return type in numeric format, by default is False.
|
||||||
|
:return: address type in string or numeric format.
|
||||||
|
"""
|
||||||
if address[0] in (TESTNET_SCRIPT_ADDRESS_PREFIX,
|
if address[0] in (TESTNET_SCRIPT_ADDRESS_PREFIX,
|
||||||
MAINNET_SCRIPT_ADDRESS_PREFIX):
|
MAINNET_SCRIPT_ADDRESS_PREFIX):
|
||||||
t = 'P2SH'
|
t = 'P2SH'
|
||||||
@ -198,6 +302,12 @@ def address_type(address, num=False):
|
|||||||
|
|
||||||
|
|
||||||
def address_net_type(address):
|
def address_net_type(address):
|
||||||
|
"""
|
||||||
|
Get address network type.
|
||||||
|
|
||||||
|
: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_ADDRESS_PREFIX):
|
MAINNET_ADDRESS_PREFIX):
|
||||||
return "mainnet"
|
return "mainnet"
|
||||||
@ -212,16 +322,14 @@ def address_net_type(address):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def script_to_hash(s, witness=False, hex=False):
|
|
||||||
if type(s) == str:
|
|
||||||
s = unhexlify(s)
|
|
||||||
if witness:
|
|
||||||
return sha256(s, hex)
|
|
||||||
else:
|
|
||||||
return hash160(s, hex)
|
|
||||||
|
|
||||||
|
|
||||||
def address_to_script(address, hex=False):
|
def address_to_script(address, hex=False):
|
||||||
|
"""
|
||||||
|
Get public key script from address.
|
||||||
|
|
||||||
|
:param address: address in base58 or bech32 format.
|
||||||
|
:param hex: (optional) If set to True return key in HEX format, by default is True.
|
||||||
|
:return: public key script in HEX or bytes string
|
||||||
|
"""
|
||||||
if address[0] in (TESTNET_SCRIPT_ADDRESS_PREFIX,
|
if address[0] in (TESTNET_SCRIPT_ADDRESS_PREFIX,
|
||||||
MAINNET_SCRIPT_ADDRESS_PREFIX):
|
MAINNET_SCRIPT_ADDRESS_PREFIX):
|
||||||
s = [BYTE_OPCODE["OP_HASH160"],
|
s = [BYTE_OPCODE["OP_HASH160"],
|
||||||
@ -244,7 +352,7 @@ def address_to_script(address, hex=False):
|
|||||||
bytes([len(h)]),
|
bytes([len(h)]),
|
||||||
h]
|
h]
|
||||||
else:
|
else:
|
||||||
assert False
|
raise TypeError("address invalid")
|
||||||
s = b''.join(s)
|
s = b''.join(s)
|
||||||
return hexlify(s).decode() if hex else s
|
return hexlify(s).decode() if hex else s
|
||||||
|
|
||||||
@ -254,27 +362,84 @@ def public_key_to_p2sh_p2wpkh_script(pubkey):
|
|||||||
return b'\x00\x14' + hash160(pubkey)
|
return b'\x00\x14' + hash160(pubkey)
|
||||||
|
|
||||||
|
|
||||||
def public_key_to_address(pubkey, testnet=False,
|
def is_address_valid(address, testnet=False):
|
||||||
p2sh_p2wpkh=False,
|
"""
|
||||||
witness_version=0):
|
Check is address valid.
|
||||||
if type(pubkey) == str:
|
|
||||||
pubkey = unhexlify(pubkey)
|
|
||||||
if p2sh_p2wpkh:
|
|
||||||
assert len(pubkey) == 33
|
|
||||||
h = hash160(b'\x00\x14' + hash160(pubkey))
|
|
||||||
witness_version = None
|
|
||||||
else:
|
|
||||||
if witness_version is not None:
|
|
||||||
assert len(pubkey) == 33
|
|
||||||
h = hash160(pubkey)
|
|
||||||
return hash_to_address(h, testnet=testnet,
|
|
||||||
script_hash=p2sh_p2wpkh,
|
|
||||||
witness_version=witness_version)
|
|
||||||
|
|
||||||
|
:param address: address in base58 or bech32 format.
|
||||||
|
:param testnet: (optional) flag for testnet network, by default is False.
|
||||||
|
:return: boolean
|
||||||
|
"""
|
||||||
|
if not address or type(address) != str:
|
||||||
|
return False
|
||||||
|
if address[0] in (MAINNET_ADDRESS_PREFIX,
|
||||||
|
MAINNET_SCRIPT_ADDRESS_PREFIX,
|
||||||
|
TESTNET_ADDRESS_PREFIX,
|
||||||
|
TESTNET_ADDRESS_PREFIX_2,
|
||||||
|
TESTNET_SCRIPT_ADDRESS_PREFIX):
|
||||||
|
if testnet:
|
||||||
|
if address[0] not in (TESTNET_ADDRESS_PREFIX,
|
||||||
|
TESTNET_ADDRESS_PREFIX_2,
|
||||||
|
TESTNET_SCRIPT_ADDRESS_PREFIX):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if address[0] not in (MAINNET_ADDRESS_PREFIX,
|
||||||
|
MAINNET_SCRIPT_ADDRESS_PREFIX):
|
||||||
|
return False
|
||||||
|
h = decode_base58(address)
|
||||||
|
if len(h) != 25:
|
||||||
|
return False
|
||||||
|
checksum = h[-4:]
|
||||||
|
if double_sha256(h[:-4])[:4] != checksum:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
elif address[:2].lower() in (TESTNET_SEGWIT_ADDRESS_PREFIX,
|
||||||
|
MAINNET_SEGWIT_ADDRESS_PREFIX):
|
||||||
|
if len(address) not in (42, 62):
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
prefix, payload = address.split('1')
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
upp = True if prefix[0].isupper() else False
|
||||||
|
for i in payload[1:]:
|
||||||
|
if upp:
|
||||||
|
if not i.isupper() or i not in base32charset_upcase:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if i.isupper() or i not in base32charset:
|
||||||
|
return False
|
||||||
|
payload = payload.lower()
|
||||||
|
prefix = prefix.lower()
|
||||||
|
if testnet:
|
||||||
|
if prefix != TESTNET_SEGWIT_ADDRESS_PREFIX:
|
||||||
|
return False
|
||||||
|
stripped_prefix = TESTNET_SEGWIT_ADDRESS_BYTE_PREFIX
|
||||||
|
else:
|
||||||
|
if prefix != MAINNET_SEGWIT_ADDRESS_PREFIX:
|
||||||
|
return False
|
||||||
|
stripped_prefix = MAINNET_SEGWIT_ADDRESS_BYTE_PREFIX
|
||||||
|
d = rebase_32_to_5(payload)
|
||||||
|
address_hash = d[:-6]
|
||||||
|
checksum = d[-6:]
|
||||||
|
checksum2 = bech32_polymod(stripped_prefix + address_hash + b"\x00" * 6)
|
||||||
|
checksum2 = rebase_8_to_5(checksum2.to_bytes(5, "big"))[2:]
|
||||||
|
if checksum != checksum2:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_witness_version(address):
|
||||||
|
address = address.split("1")[1]
|
||||||
|
h = rebase_32_to_5(address)
|
||||||
|
return h[0]
|
||||||
|
|
||||||
|
|
||||||
|
# Script
|
||||||
|
|
||||||
def parse_script(script, segwit=True):
|
def parse_script(script, segwit=True):
|
||||||
if not script:
|
if not script:
|
||||||
return {"nType": 7, "type": "NON_STANDARD", "reqSigs": 0, "script": b""}
|
return {"nType": 7, "type": "NON_STANDARD", "reqSigs": 0, "script": b""}
|
||||||
if type(script) == str:
|
if type(script) == str:
|
||||||
try:
|
try:
|
||||||
script = unhexlify(script)
|
script = unhexlify(script)
|
||||||
@ -288,12 +453,12 @@ def parse_script(script, segwit=True):
|
|||||||
if l == 34 and script[0] == 0:
|
if l == 34 and script[0] == 0:
|
||||||
return {"nType": 6, "type": "P2WSH", "reqSigs": None, "addressHash": script[2:]}
|
return {"nType": 6, "type": "P2WSH", "reqSigs": None, "addressHash": script[2:]}
|
||||||
if l == 25 and \
|
if l == 25 and \
|
||||||
script[:2] == b"\x76\xa9" and \
|
script[:2] == b"\x76\xa9" and \
|
||||||
script[-2:] == b"\x88\xac":
|
script[-2:] == b"\x88\xac":
|
||||||
return {"nType": 0, "type": "P2PKH", "reqSigs": 1, "addressHash": script[3:-2]}
|
return {"nType": 0, "type": "P2PKH", "reqSigs": 1, "addressHash": script[3:-2]}
|
||||||
if l == 23 and \
|
if l == 23 and \
|
||||||
script[0] == 169 and \
|
script[0] == 169 and \
|
||||||
script[-1] == 135:
|
script[-1] == 135:
|
||||||
return {"nType": 1, "type": "P2SH", "reqSigs": None, "addressHash": script[2:-1]}
|
return {"nType": 1, "type": "P2SH", "reqSigs": None, "addressHash": script[2:-1]}
|
||||||
if l == 67 and script[-1] == 172:
|
if l == 67 and script[-1] == 172:
|
||||||
return {"nType": 2, "type": "PUBKEY", "reqSigs": 1, "addressHash": hash160(script[1:-1])}
|
return {"nType": 2, "type": "PUBKEY", "reqSigs": 1, "addressHash": hash160(script[1:-1])}
|
||||||
@ -316,7 +481,7 @@ def parse_script(script, segwit=True):
|
|||||||
break
|
break
|
||||||
s += 1
|
s += 1
|
||||||
if c == script[-2] - 80:
|
if c == script[-2] - 80:
|
||||||
return {"nType": 4, "type": "MULTISIG", "reqSigs": script[0] - 80, "script": script}
|
return {"nType": 4, "type": "MULTISIG", "reqSigs": script[0] - 80, "script": script}
|
||||||
|
|
||||||
s, m, n, last, req_sigs = 0, 0, 0, 0, 0
|
s, m, n, last, req_sigs = 0, 0, 0, 0, 0
|
||||||
while l - s > 0:
|
while l - s > 0:
|
||||||
@ -364,7 +529,7 @@ def parse_script(script, segwit=True):
|
|||||||
if last:
|
if last:
|
||||||
last -= 1
|
last -= 1
|
||||||
s += 1
|
s += 1
|
||||||
return {"nType": 7, "type": "NON_STANDARD", "reqSigs": req_sigs, "script": script}
|
return {"nType": 7, "type": "NON_STANDARD", "reqSigs": req_sigs, "script": script}
|
||||||
|
|
||||||
|
|
||||||
def decode_script(script, asm=False):
|
def decode_script(script, asm=False):
|
||||||
@ -380,7 +545,7 @@ def decode_script(script, asm=False):
|
|||||||
while l - s > 0:
|
while l - s > 0:
|
||||||
if script[s] < 0x4c and script[s]:
|
if script[s] < 0x4c and script[s]:
|
||||||
if asm:
|
if asm:
|
||||||
result.append(hexlify(script[s+1:s+1 +script[s]]).decode())
|
result.append(hexlify(script[s + 1:s + 1 + script[s]]).decode())
|
||||||
else:
|
else:
|
||||||
result.append('[%s]' % script[s])
|
result.append('[%s]' % script[s])
|
||||||
s += script[s] + 1
|
s += script[s] + 1
|
||||||
@ -445,7 +610,7 @@ def delete_from_script(script, sub_script):
|
|||||||
k = s
|
k = s
|
||||||
else:
|
else:
|
||||||
t = stack.pop(0)
|
t = stack.pop(0)
|
||||||
result.append(script[k:k+t])
|
result.append(script[k:k + t])
|
||||||
k += t
|
k += t
|
||||||
if script[k:s][:ls] == sub_script:
|
if script[k:s][:ls] == sub_script:
|
||||||
if s - k > ls:
|
if s - k > ls:
|
||||||
@ -456,64 +621,13 @@ def delete_from_script(script, sub_script):
|
|||||||
return b''.join(result) if not s_hex else hexlify(b''.join(result)).decode()
|
return b''.join(result) if not s_hex else hexlify(b''.join(result)).decode()
|
||||||
|
|
||||||
|
|
||||||
def is_address_valid(address, testnet=False):
|
def script_to_hash(s, witness=False, hex=False):
|
||||||
if not address or type(address) != str:
|
if type(s) == str:
|
||||||
return False
|
s = unhexlify(s)
|
||||||
if address[0] in (MAINNET_ADDRESS_PREFIX,
|
if witness:
|
||||||
MAINNET_SCRIPT_ADDRESS_PREFIX,
|
return sha256(s, hex)
|
||||||
TESTNET_ADDRESS_PREFIX,
|
else:
|
||||||
TESTNET_ADDRESS_PREFIX_2,
|
return hash160(s, hex)
|
||||||
TESTNET_SCRIPT_ADDRESS_PREFIX):
|
|
||||||
if testnet:
|
|
||||||
if address[0] not in (TESTNET_ADDRESS_PREFIX,
|
|
||||||
TESTNET_ADDRESS_PREFIX_2,
|
|
||||||
TESTNET_SCRIPT_ADDRESS_PREFIX):
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if address[0] not in (MAINNET_ADDRESS_PREFIX,
|
|
||||||
MAINNET_SCRIPT_ADDRESS_PREFIX):
|
|
||||||
return False
|
|
||||||
h = decode_base58(address)
|
|
||||||
if len(h) != 25:
|
|
||||||
return False
|
|
||||||
checksum = h[-4:]
|
|
||||||
if double_sha256(h[:-4])[:4] != checksum:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
elif address[:2].lower() in (TESTNET_SEGWIT_ADDRESS_PREFIX,
|
|
||||||
MAINNET_SEGWIT_ADDRESS_PREFIX):
|
|
||||||
if len(address) not in (42, 62):
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
prefix, payload = address.split('1')
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
upp = True if prefix[0].isupper() else False
|
|
||||||
for i in payload[1:]:
|
|
||||||
if upp:
|
|
||||||
if not i.isupper() or i not in base32charset_upcase:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if i.isupper() or i not in base32charset:
|
|
||||||
return False
|
|
||||||
payload = payload.lower()
|
|
||||||
prefix = prefix.lower()
|
|
||||||
if testnet:
|
|
||||||
if prefix != TESTNET_SEGWIT_ADDRESS_PREFIX:
|
|
||||||
return False
|
|
||||||
stripped_prefix = TESTNET_SEGWIT_ADDRESS_BYTE_PREFIX
|
|
||||||
else:
|
|
||||||
if prefix != MAINNET_SEGWIT_ADDRESS_PREFIX:
|
|
||||||
return False
|
|
||||||
stripped_prefix = MAINNET_SEGWIT_ADDRESS_BYTE_PREFIX
|
|
||||||
d = rebase_32_to_5(payload)
|
|
||||||
address_hash = d[:-6]
|
|
||||||
checksum = d[-6:]
|
|
||||||
checksum2 = bech32_polymod(stripped_prefix + address_hash + b"\x00" * 6)
|
|
||||||
checksum2 = rebase_8_to_5(checksum2.to_bytes(5, "big"))[2:]
|
|
||||||
if checksum != checksum2:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user