From 2eed4aa1ef79287cdfe66ec50eadc33281fcbabb Mon Sep 17 00:00:00 2001 From: 4tochka Date: Wed, 20 Jun 2018 15:22:24 +0400 Subject: [PATCH] fix --- .gitmodules | 3 + README.md | 2 +- docs/source/address.rst | 30 ++ docs/source/classes.rst | 13 + docs/source/conf.py | 9 +- docs/source/contributing.rst | 36 +++ docs/source/examples.rst | 118 ++++++++ docs/source/functional.rst | 82 +++++- docs/source/index.rst | 53 ++-- docs/source/installation.rst | 39 +++ docs/source/transaction.rst | 11 + docs/src/alabaster | 1 + docs/src/pip-delete-this-directory.txt | 5 + pybtc/__init__.py | 1 + pybtc/address.py | 164 ++++++++--- pybtc/constants.py | 28 +- pybtc/opcodes.py | 150 +++++++++- pybtc/tools.py | 364 +++++++++++++++++++------ pybtc/transaction.py | 10 +- setup.py | 19 +- tests/test/address_class.py | 9 +- 21 files changed, 939 insertions(+), 208 deletions(-) create mode 100644 .gitmodules create mode 160000 docs/src/alabaster create mode 100644 docs/src/pip-delete-this-directory.txt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..278c27d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/src/alabaster"] + path = docs/src/alabaster + url = https://github.com/bitprophet/alabaster.git diff --git a/README.md b/README.md index 4f036d9..0addfc0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ - + ## Python bitcoin library diff --git a/docs/source/address.rst b/docs/source/address.rst index e69de29..fc02023 100644 --- a/docs/source/address.rst +++ b/docs/source/address.rst @@ -0,0 +1,30 @@ +========= +Addresses +========= + +Collection of base classes that implement the work with Bitcoin addresses and address keys. +Supports addresses types PUBKEY, P2PKH, P2SH, P2SH-PWPKH, P2WPKH, P2WSH. + +| +| + +.. autoclass:: pybtc.PrivateKey + :members: + :inherited-members: + +| +| + +.. autoclass:: pybtc.PublicKey + :members: + :inherited-members: + +| +| + +.. autoclass:: pybtc.Address + :members: + :inherited-members: + + + diff --git a/docs/source/classes.rst b/docs/source/classes.rst index e69de29..4555cae 100644 --- a/docs/source/classes.rst +++ b/docs/source/classes.rst @@ -0,0 +1,13 @@ +========= +Reference +========= + +.. toctree:: + :name: mastertoc + :maxdepth: 2 + + address.rst + transaction.rst + block.rst + + diff --git a/docs/source/conf.py b/docs/source/conf.py index 5a89d26..c68f7fc 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,6 +14,9 @@ # import os import sys +from sphinx.ext.autodoc import ( + ClassLevelDocumenter, InstanceAttributeDocumenter) + sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath('..')) @@ -28,10 +31,14 @@ sys.path.insert(0, os.path.abspath('../..')) # -- Project information ----------------------------------------------------- project = 'pybtc' -copyright = '2018, Aleksey Karpov' +copyright = '2015-2018, bitaps.com' author = 'Aleksey Karpov' +def iad_add_directive_header(self, sig): + ClassLevelDocumenter.add_directive_header(self, sig) + +InstanceAttributeDocumenter.add_directive_header = iad_add_directive_header # The short X.Y version version = '' diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index e69de29..a7b801b 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -0,0 +1,36 @@ +.. _pybtc-contributing: + +============ +Contributing +============ + +Instructions for contributors +----------------------------- + + +In order to make a clone of the GitHub repo: open the link and press the +"Fork" button on the upper-right menu of the web page. + + +Workflow is pretty straightforward: + + 1. Clone the GitHub + + 2. Make a change + + 3. Make sure all tests passed + + 4. Add a record intp file into ``change.log``. + + 5. Commit changes to own aiohttp clone + + 6. Make pull request from github page for your clone against master branch + + +Tests coverage +-------------- + +We are trying hard to have good test coverage; please don't make it worse. +All tests located in ``tests/`` folder. + + diff --git a/docs/source/examples.rst b/docs/source/examples.rst index e69de29..3dc5ae3 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -0,0 +1,118 @@ +======== +Examples +======== + + +Create address +-------------- + +This is example of usage Address class. The address class implements the work with addresses controlled by a private key. +Supports the ability to create P2WPKH, P2PKH, PUBKEY address types and P2SH_P2WPKH as exception for SEGWIT adoption. +It is recommended to use native SEGWIT address type - P2WPKH, which reduces costs of miner fee and expand block capacity. +To create an address, you need to create a class object. Buy default, +will be created P2WPKH address for mainnet. + + + +.. code-block:: bash + + >>> import pybtc + >>> a = pybtc.Address() + >>> a.address + 'bc1q6cxx5t8xkruz3s5khx7923xvsx5ry4c6p74m5s' + >>> a.private_key.wif + 'L5XKGA2xEHcinWEpmyiABS1bqQux8Av5dGVqcpRtVJC3ZCR5sXUe' + >>> + >>> # create P2PKH legacy format + >>> pybtc.Address(address_type="P2PKH").address + '1ChpKurzFhdCULKaNHCc3Ra9KfxM2LRguw' + >>> + >>> # create testnet address + >>> pybtc.Address(address_type="P2PKH", testnet=True).address + 'mpR4hDfu269yxgZtPVYSD21gtpvdxpTmH6' + >>> + >>> # create P2SH_P2WPKH SEGWIT adoption address + >>> pybtc.Address(address_type="P2SH_P2WPKH").address + '3Bqeq3XqL6azMK3BxNyr8vXgXUtoG63J4T' + >>> + + +Get address from key +-------------------- + +In case you already have private or public key you can object from your key. + +.. code-block:: bash + + >>> a = pybtc.Address('L5XKGA2xEHcinWEpmyiABS1bqQux8Av5dGVqcpRtVJC3ZCR5sXUe') + >>> a.address + 'bc1q6cxx5t8xkruz3s5khx7923xvsx5ry4c6p74m5s' + >>> a.public_key.hex + '03b8b44876e1f45be7e42953ea47026c39cc45341344d3ab32701b93de696107af' + >>> + >>> # get address from public key + >>> pub = pybtc.PublicKey('03b8b44876e1f45be7e42953ea47026c39cc45341344d3ab32701b93de696107af') + >>> + >>> pybtc.Address(pub).address + 'bc1q6cxx5t8xkruz3s5khx7923xvsx5ry4c6p74m5s' + >>> + +Pure functions for address +-------------------------- + +Create private key + +.. code-block:: bash + + >>> import pybtc + >>> pybtc.create_private_key() + 'KyvZYvdzWD4JSPFt4wXwjG53as227zT2qiWbMTicZEUSjiwvbEqi' + >>> + >>> pybtc.create_private_key(compressed=False) + '5Jw8DY1uBrd35xup6eD6KLEFa4AJFbX381HWuHvPGirJto9ZTnr' + >>> + >>> pybtc.is_wif_valid('5Jw8DY1uBrd35xup6eD6KLEFa4AJFbX381HWuHvPGirJto9ZTnr') + True + >>> pybtc.is_wif_valid('5Jw8DY1uBrd35xup6eD6KLEFa4AJFbX381**********Jto9ZTnr') + False + >>> + +Get public key from private key + +.. code-block:: bash + + >>> import pybtc + >>> pybtc.private_to_public_key('5Jw8DY1uBrd35xup6eD6KLEFa4AJFbX381HWuHvPGirJto9ZTnr') + '0479f17a94410afd4f27588a192bacada53add0741765092dc0f8e2a29ea1bcd276dbc1ef74c3e0172d9db8047f2a0a5dc2e8e51a13f7f0cc072de906b765e0f7f' + >>> + >>> pybtc.public_key_to_address('0479f17a94410afd4f27588a192bacada53add0741765092dc0f8e2a29ea1bcd276dbc1ef74c3e0172d9db8047f2a0a5dc2e8e51a13f7f0cc072de906b765e0f7f') + >>> + >>> # this is uncompressed public key, so we can't create witness address + >>> # we have to set witness_version to None to get non segwit address + >>> pub = pybtc.private_to_public_key('5Jw8DY1uBrd35xup6eD6KLEFa4AJFbX381HWuHvPGirJto9ZTnr') + >>> pybtc.public_key_to_address(pub, witness_version=None) + '17mXwxxZRmj1nJJzDszZbW9URSAradEuAt' + >>> + +Tools + +.. code-block:: bash + + >>> pybtc.is_address_valid('17mXwxxZRmj1nJJzDszZbW9URSAradEuAt') + True + >>> pybtc.address_type('17mXwxxZRmj1nJJzDszZbW9URSAradEuAt') + 'P2PKH' + >>> pybtc.address_net_type('17mXwxxZRmj1nJJzDszZbW9URSAradEuAt') + 'mainnet' + >>> + + +Create script address +--------------------- + + + + + + + diff --git a/docs/source/functional.rst b/docs/source/functional.rst index 2446767..f0d3686 100644 --- a/docs/source/functional.rst +++ b/docs/source/functional.rst @@ -1,18 +1,14 @@ -============== -Pure functions -============== +======================== +Pure functions reference +======================== -Base function primitives implemeted in functional programming paradigm. - -Key management -============== - -Tools for private and public key managment +Base function primitives implemented in functional programming paradigm. -Private key ------------ + +Private keys +============ .. autofunction:: pybtc.create_private_key .. autofunction:: pybtc.private_key_to_wif @@ -20,8 +16,8 @@ Private key .. autofunction:: pybtc.is_wif_valid -Public key ----------- +Public keys +=========== .. WARNING:: Using uncompressed public keys is @@ -47,6 +43,66 @@ Addresses .. autofunction:: pybtc.is_address_valid +Script +====== + +.. autofunction:: pybtc.decode_script +.. autofunction:: pybtc.parse_script +.. autofunction:: pybtc.delete_from_script +.. autofunction:: pybtc.script_to_hash + + Signatures ========== +.. autofunction:: pybtc.verify_signature +.. autofunction:: pybtc.sign_message +.. autofunction:: pybtc.is_valid_signature_encoding + + +Hash encoding +============= + +.. autofunction:: pybtc.rh2s +.. autofunction:: pybtc.s2rh +.. autofunction:: pybtc.reverse_hash + + +Merkle root +=========== + +.. autofunction:: pybtc.merkle_root +.. autofunction:: pybtc.merkle_branches +.. autofunction:: pybtc.merkleroot_from_branches + + +Difficulty +========== + +.. autofunction:: pybtc.bits_to_target +.. autofunction:: pybtc.target_to_difficulty +.. autofunction:: pybtc.bits_to_difficulty +.. autofunction:: pybtc.difficulty_to_target + + +Tools +===== + +.. autofunction:: pybtc.bytes_needed +.. autofunction:: pybtc.int_to_bytes +.. autofunction:: pybtc.bytes_to_int +.. autofunction:: pybtc.int_to_var_int +.. autofunction:: pybtc.var_int_to_int +.. autofunction:: pybtc.var_int_len +.. autofunction:: pybtc.get_var_int_len +.. autofunction:: pybtc.read_var_int +.. autofunction:: pybtc.read_var_list +.. autofunction:: pybtc.int_to_c_int +.. autofunction:: pybtc.c_int_to_int +.. autofunction:: pybtc.c_int_len + + + + + + diff --git a/docs/source/index.rst b/docs/source/index.rst index ee67d62..8ac9e21 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -28,39 +28,36 @@ Key Features .. _aiohttp-installation: -Library Installation -==================== +Quick library Installation +========================== .. code-block:: bash - $ pip install secp256k1 $ pip install pybtc Getting Started =============== -Client example:: - - import pybtc - import asyncio - -Server example:: +Usage example:: import pybtc + a = pybtc.Address() + print(a.address) + print(a.private_key.wif()) -What's new in pybtc 2? -======================== +What's new in pybtc 2.0 ? +========================= - -Tutorial -======== - +- Mnemonic code generation (BIP39) +- Hierarchical Deterministic Wallets (BIP32) +- Wallet class implemented acording BIP44 +- Imporved transaction deserialization perfomance @@ -81,21 +78,16 @@ Dependencies - *secp256k1* -Communication channels -====================== - - - -Contributing -============ - - - Authors and License =================== -The ``pybtc`` package is written mostly by Aleksey Karpov. +The ``pybtc`` package was initially written by `Aleksey Karpov `_ and development continues with contributors. + +Recent contributors: + +- `Aleksey Karpov `_ +- `Aleksey Karybkin `_ It's *GPL-3.0* licensed and freely available. @@ -111,4 +103,11 @@ Table Of Contents :name: mastertoc :maxdepth: 2 - functional.rst \ No newline at end of file + installation.rst + examples.rst + classes.rst + functional.rst + contributing.rst + + + diff --git a/docs/source/installation.rst b/docs/source/installation.rst index e69de29..d5c3d9d 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -0,0 +1,39 @@ +============ +Installation +============ + +This part of the documentation covers the installation of pybtc library. The first step to using any software package is getting it properly installed. + +Get from pip package +-------------------- + +To install pybtc, simply run this simple command in your terminal of choice: + +.. code-block:: bash + + $ pip install pybtc + + +If you don’t have pip installed, this Python pip `installation guide `_ can guide you through the process. + + +Get the Source Code +------------------- + +You can clone the public repository: + +.. code-block:: bash + + $ git clone git://github.com/bitaps-com/pybtc + +Once you have a copy of the source, you can embed it in your own Python package, or install it into your site-packages easily: + +.. code-block:: bash + + $ cd pybtc + $ python3 setup.py install + + + + + diff --git a/docs/source/transaction.rst b/docs/source/transaction.rst index e69de29..ac2360c 100644 --- a/docs/source/transaction.rst +++ b/docs/source/transaction.rst @@ -0,0 +1,11 @@ +============ +Transactions +============ + +The class for creating transaction. + + +.. autoclass:: pybtc.Transaction + :members: + + diff --git a/docs/src/alabaster b/docs/src/alabaster new file mode 160000 index 0000000..609374c --- /dev/null +++ b/docs/src/alabaster @@ -0,0 +1 @@ +Subproject commit 609374c5434eb960eab1ae25ea50b007fbc6c2ae diff --git a/docs/src/pip-delete-this-directory.txt b/docs/src/pip-delete-this-directory.txt new file mode 100644 index 0000000..c8883ea --- /dev/null +++ b/docs/src/pip-delete-this-directory.txt @@ -0,0 +1,5 @@ +This file is placed here by pip to indicate the source was put +here by pip. + +Once this package is successfully installed this source code will be +deleted (unless you remove this file). diff --git a/pybtc/__init__.py b/pybtc/__init__.py index feaa99a..bc2e576 100644 --- a/pybtc/__init__.py +++ b/pybtc/__init__.py @@ -2,6 +2,7 @@ from .tools import * from .opcodes import * from .consensus import * from .transaction import * +from .block import * from .address import * version = "2.0.1" diff --git a/pybtc/address.py b/pybtc/address.py index 132a82b..b67b2c1 100644 --- a/pybtc/address.py +++ b/pybtc/address.py @@ -2,11 +2,32 @@ from .tools import * class PrivateKey(): + """ + The class for creating private key object. + + :param key: (optional) private key in HEX, bytes string or WIF format. In case no key specified + new random private key will be created. + :param compressed: (optional) if set to True private key corresponding compressed public key, + by default set to True. Recommended use only compressed public key. + :param testnet: (optional) if set to True mean that this private key for testnet Bitcoin network. + + """ def __init__(self, key=None, compressed=True, testnet=False): + if key is None: + + #: flag for compressed type of corresponding public key (boolean) self.compressed = compressed + #: flag for testnet network private key (boolean) self.testnet = testnet - self.raw_key = create_private_key(wif=False) + + #: private key in bytes (bytes) + self.key = create_private_key(wif=False) + #: private key in HEX (string) + self.hex = hexlify(self.key).decode() + #: private key in WIF format (string) + self.wif = private_key_to_wif(self.key, compressed, testnet) + else: if isinstance(key, str): try: @@ -15,13 +36,17 @@ class PrivateKey(): pass if isinstance(key, bytes): if len(key) != 32: - raise TypeError("private key invalid") - self.raw_key = key + raise TypeError("private key invalid length") + self.key = key self.compressed = compressed self.testnet = testnet + self.hex = hexlify(self.key).decode() + self.wif = private_key_to_wif(self.key, compressed, testnet) return assert isinstance(key, str) - self.raw_key = wif_to_private_key(key, hex=False) + self.key = wif_to_private_key(key, hex=False) + self.hex = hexlify(self.key).decode() + self.wif = private_key_to_wif(self.key, compressed, testnet) if key[0] in (MAINNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX, TESTNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX): self.compressed = False @@ -33,57 +58,101 @@ class PrivateKey(): else: self.testnet = False - def hex(self): - return hexlify(self.raw_key).decode() - - def wif(self, compressed=None, testnet=None): - if compressed is None: - compressed = self.compressed - if testnet is None: - testnet = self.testnet - return private_key_to_wif(self.raw_key, compressed, testnet) - def __str__(self): - return self.wif() + return self.wif class PublicKey(): - def __init__(self, key=None): + """ + The class for public key object. + + :param key: one of this types allowed: + + - private key is instance of ``PrivateKey`` class + - private key HEX encoded string + - private key 32 bytes string + - private key in WIF format + - public key in HEX encoded string + - public key [33/65] bytes string + + In case no key specified with HEX or bytes string you have to provide flag for testnet + and compressed key. WIF format and ``PrivateKey`` instance already contain this flags. + For HEX or bytes public key only testnet flag has the meaning, comressed flag is determined + according to the length of key. + + :param compressed: (optional) if set to True private key corresponding compressed public key, + by default set to True. Recommended use only compressed public key. + :param testnet: (optional) if set to True mean that this private key for testnet Bitcoin network. + + """ + def __init__(self, key, compressed=True, testnet=False): if isinstance(key, str): try: key = unhexlify(key) except: - pass + if is_wif_valid(key): + key = PrivateKey(key) + + if isinstance(key, bytes): + if len(key) == 32: + key = PrivateKey(key, compressed=compressed, testnet=testnet) + elif is_public_key_valid(key): + public_key = key + self.testnet = testnet + self.compressed = True if len(key) == 33 else False + else: + raise TypeError("key invalid") + if isinstance(key, PrivateKey): - key = private_to_public_key(key.raw_key, + #: flag for testnet network private key (boolean) + self.testnet = key.testnet + #: flag for compressed type of corresponding public key (boolean) + self.compressed = key.compressed + public_key = private_to_public_key(key.key, compressed=key.compressed, hex=False) - if not isinstance(key, bytes): - raise TypeError("public key invalid") - if len(key) != 33 and len(key) != 65: - raise TypeError("public key invalid") - if len(key) == 33: - self.compressed = True - else: - self.compressed = False - self.raw_key = key - - def hex(self): - return hexlify(self.raw_key).decode() + #: public key in bytes (bytes) + self.key = public_key + #: public key in HEX (string) + self.hex = hexlify(self.key).decode() def __str__(self): - return self.hex() + return self.hex class Address(): + """ + The class for Address object. + + :param key: (optional) one of this types allowed: + + - private key WIF format + - instance of ``PrivateKey`` + - private key HEX encoded string + - instance of ``PublicKey`` + + In case no key specified new Address will be created with random keys. + :param address_type: (optional) P2PKH, PUBKEY, P2WPKH, P2SH_P2WPKH, by default P2WPKH. + :param compressed: (optional) if set to True private key corresponding compressed public key, + by default set to True. Recommended use only compressed public key. + :param testnet: (optional) if set to True mean that this private key for testnet Bitcoin network. + + In case instanse is created from WIF private key, ``PrivateKey`` or ``PublicKey`` compressed and testnet flags + already contain in initial key parameter and will be ignored. + """ def __init__(self, key=None, address_type="P2WPKH", testnet=False, compressed=True): if key is None: + #: instance of ``PrivateKey`` class self.private_key = PrivateKey(testnet=testnet, compressed=compressed) + #: instance of ``PublicKey`` class self.public_key = PublicKey(self.private_key) + #: flag for testnet network address (boolean) self.testnet = testnet - elif isinstance(key, PrivateKey): + if isinstance(key, str) or isinstance(key, bytes): + key = PrivateKey(key, testnet=testnet, compressed=compressed) + if isinstance(key, PrivateKey): self.private_key = key self.testnet = key.testnet compressed = key.compressed @@ -97,21 +166,31 @@ class Address(): if not compressed: if address_type not in ("P2PKH", "PUBKEY", "P2SH"): raise TypeError("compressed public key invalid") + #: flag for testnet network address (boolean) self.type = address_type if address_type in ("P2WPKH"): + #: version of witness program for SEGWIT address (string) self.witness_version = 0 else: self.witness_version = None self.compressed = compressed if address_type == "P2SH_P2WPKH": + #: flag for script hash address (boolean) self.script_hash = True - self.redeem_script_raw = public_key_to_p2sh_p2wpkh_script(self.public_key.raw_key) - self.redeem_script = hexlify(self.redeem_script_raw).decode() - self.hash = hash160(self.redeem_script_raw) + #: redeeem script, only for P2SH_P2WPKH (bytes) + self.redeem_script = public_key_to_p2sh_p2wpkh_script(self.public_key.key) + #: redeeem script HEX, only for P2SH_P2WPKH (string) + self.redeem_script_hex = hexlify(self.redeem_script).decode() + #: address hash + self.hash = hash160(self.redeem_script) + self.witness_version = None else: self.script_hash = False - self.hash = hash160(self.public_key.raw_key) + self.hash = hash160(self.public_key.key) + #: address hash HEX (string) + self.hash_hex = hexlify(self.hash).decode() + #: address in base58 or bech32 encoding (string) self.address = hash_to_address(self.hash, script_hash=self.script_hash, witness_version=self.witness_version, @@ -128,12 +207,13 @@ class ScriptAddress(): self.testnet = testnet if isinstance(script, str): script = unhexlify(script) - self.script_raw = script - self.script = hexlify(self.script_raw).decode() - self.hash = hash160(self.script_raw) - self.script_opcodes = decode_script(self.script_raw) - self.script_opcodes_asm = decode_script(self.script_raw, 1) + self.script = script + self.script_hex = hexlify(self.script).decode() + self.hash = hash160(self.script) + self.script_opcodes = decode_script(self.script) + self.script_opcodes_asm = decode_script(self.script, 1) self.address = hash_to_address(self.hash, script_hash=True, witness_version=self.witness_version, - testnet=self.testnet) \ No newline at end of file + testnet=self.testnet) + diff --git a/pybtc/constants.py b/pybtc/constants.py index 7001e04..7f47d23 100644 --- a/pybtc/constants.py +++ b/pybtc/constants.py @@ -2,11 +2,11 @@ from secp256k1 import lib as secp256k1 import random MAX_AMOUNT = 2100000000000000 -SIGHASH_ALL = 0x00000001 -SIGHASH_NONE = 0x00000002 -SIGHASH_SINGLE = 0x00000003 -SIGHASH_ANYONECANPAY = 0x00000080 -MAX_INT_PRIVATE_KEY = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 +SIGHASH_ALL = 0x00000001 +SIGHASH_NONE = 0x00000002 +SIGHASH_SINGLE = 0x00000003 +SIGHASH_ANYONECANPAY = 0x00000080 +MAX_INT_PRIVATE_KEY = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 MAINNET_ADDRESS_BYTE_PREFIX = b'\x00' TESTNET_ADDRESS_BYTE_PREFIX = b'\x6f' @@ -62,16 +62,16 @@ ECDSA_CONTEXT_SIGN = secp256k1.secp256k1_context_create(FLAG_SIGN) ECDSA_CONTEXT_VERIFY = secp256k1.secp256k1_context_create(FLAG_VERIFY) ECDSA_CONTEXT_ALL = secp256k1.secp256k1_context_create(ALL_FLAGS) secp256k1.secp256k1_context_randomize(ECDSA_CONTEXT_SIGN, - random.SystemRandom().randint(0,MAX_INT_PRIVATE_KEY).to_bytes(32,byteorder="big")) + random.SystemRandom().randint(0, MAX_INT_PRIVATE_KEY).to_bytes(32,byteorder="big")) -SCRIPT_TYPES = { "P2PKH": 0, - "P2SH" : 1, - "PUBKEY": 2, - "NULL_DATA": 3, - "MULTISIG": 4, - "P2WPKH": 5, - "P2WSH": 6, - "NON_STANDART": 7 +SCRIPT_TYPES = {"P2PKH": 0, + "P2SH": 1, + "PUBKEY": 2, + "NULL_DATA": 3, + "MULTISIG": 4, + "P2WPKH": 5, + "P2WSH": 6, + "NON_STANDART": 7 } diff --git a/pybtc/opcodes.py b/pybtc/opcodes.py index ad416ee..93d9162 100644 --- a/pybtc/opcodes.py +++ b/pybtc/opcodes.py @@ -150,6 +150,150 @@ OPCODE["OP_PUBKEY"] = 0xfe OPCODE["OP_INVALIDOPCODE"] = 0xff -RAW_OPCODE = dict ( (OPCODE[i], i) for i in OPCODE ) -BYTE_OPCODE = dict ((i,bytes([OPCODE[i]])) for i in OPCODE ) -HEX_OPCODE = dict ((i,hexlify(bytes([OPCODE[i]])).decode()) for i in OPCODE ) +RAW_OPCODE = dict((OPCODE[i], i) for i in OPCODE) +BYTE_OPCODE = dict((i, bytes([OPCODE[i]])) for i in OPCODE) +HEX_OPCODE = dict((i, hexlify(bytes([OPCODE[i]])).decode()) for i in OPCODE) + +OP_FALSE = BYTE_OPCODE["OP_FALSE"] +OP_0 = BYTE_OPCODE["OP_0"] +OP_PUSHDATA1 = BYTE_OPCODE["OP_PUSHDATA1"] +OP_PUSHDATA2 = BYTE_OPCODE["OP_PUSHDATA2"] +OP_PUSHDATA4 = BYTE_OPCODE["OP_PUSHDATA4"] +OP_1NEGATE = BYTE_OPCODE["OP_1NEGATE"] +OP_RESERVED = BYTE_OPCODE["OP_RESERVED"] +OP_1 = BYTE_OPCODE["OP_1"] +OP_TRUE = BYTE_OPCODE["OP_TRUE"] +OP_2 = BYTE_OPCODE["OP_2"] +OP_3 = BYTE_OPCODE["OP_3"] +OP_4 = BYTE_OPCODE["OP_4"] +OP_5 = BYTE_OPCODE["OP_5"] +OP_6 = BYTE_OPCODE["OP_6"] +OP_7 = BYTE_OPCODE["OP_7"] +OP_8 = BYTE_OPCODE["OP_8"] +OP_9 = BYTE_OPCODE["OP_9"] +OP_10 = BYTE_OPCODE["OP_10"] +OP_11 = BYTE_OPCODE["OP_11"] +OP_12 = BYTE_OPCODE["OP_12"] +OP_13 = BYTE_OPCODE["OP_13"] +OP_14 = BYTE_OPCODE["OP_14"] +OP_15 = BYTE_OPCODE["OP_15"] +OP_16 = BYTE_OPCODE["OP_16"] + +# control + +OP_NOP = BYTE_OPCODE["OP_NOP"] +OP_VER = BYTE_OPCODE["OP_VER"] +OP_IF = BYTE_OPCODE["OP_IF"] +OP_NOTIF = BYTE_OPCODE["OP_NOTIF"] +OP_VERIF = BYTE_OPCODE["OP_VERIF"] +OP_ELSE = BYTE_OPCODE["OP_ELSE"] +OP_ENDIF = BYTE_OPCODE["OP_ENDIF"] +OP_VERIFY = BYTE_OPCODE["OP_VERIFY"] +OP_RETURN = BYTE_OPCODE["OP_RETURN"] + +# stack + +OP_TOALTSTACK = BYTE_OPCODE["OP_TOALTSTACK"] +OP_FROMALTSTACK = BYTE_OPCODE["OP_FROMALTSTACK"] +OP_2DROP = BYTE_OPCODE["OP_2DROP"] +OP_2DUP = BYTE_OPCODE["OP_2DUP"] +OP_3DUP = BYTE_OPCODE["OP_3DUP"] +OP_2OVER = BYTE_OPCODE["OP_2OVER"] +OP_2ROT = BYTE_OPCODE["OP_2ROT"] +OP_2SWAP = BYTE_OPCODE["OP_2SWAP"] +OP_IFDUP = BYTE_OPCODE["OP_IFDUP"] +OP_DEPTH = BYTE_OPCODE["OP_DEPTH"] +OP_DROP = BYTE_OPCODE["OP_DROP"] +OP_DUP = BYTE_OPCODE["OP_DUP"] +OP_NIP = BYTE_OPCODE["OP_NIP"] +OP_OVER = BYTE_OPCODE["OP_OVER"] +OP_PICK = BYTE_OPCODE["OP_PICK"] +OP_ROLL = BYTE_OPCODE["OP_ROLL"] +OP_ROT = BYTE_OPCODE["OP_ROT"] +OP_SWAP = BYTE_OPCODE["OP_SWAP"] +OP_TUCK = BYTE_OPCODE["OP_TUCK"] + +# splice + +OP_CAT = BYTE_OPCODE["OP_CAT"] +OP_SUBSTR = BYTE_OPCODE["OP_SUBSTR"] +OP_LEFT = BYTE_OPCODE["OP_LEFT"] +OP_RIGHT = BYTE_OPCODE["OP_RIGHT"] +OP_SIZE = BYTE_OPCODE["OP_SIZE"] + +# bit operations + +OP_INVERT = BYTE_OPCODE["OP_INVERT"] +OP_AND = BYTE_OPCODE["OP_AND"] +OP_OR = BYTE_OPCODE["OP_OR"] +OP_XOR = BYTE_OPCODE["OP_XOR"] +OP_EQUAL = BYTE_OPCODE["OP_EQUAL"] +OP_EQUALVERIFY = BYTE_OPCODE["OP_EQUALVERIFY"] +OP_RESERVED1 = BYTE_OPCODE["OP_RESERVED1"] +OP_RESERVED2 = BYTE_OPCODE["OP_RESERVED2"] + +# math + +OP_1ADD = BYTE_OPCODE["OP_1ADD"] +OP_1SUB = BYTE_OPCODE["OP_1SUB"] +OP_2MUL = BYTE_OPCODE["OP_2MUL"] +OP_2DIV = BYTE_OPCODE["OP_2DIV"] +OP_NEGATE = BYTE_OPCODE["OP_NEGATE"] +OP_ABS = BYTE_OPCODE["OP_ABS"] +OP_NOT = BYTE_OPCODE["OP_NOT"] +OP_0NOTEQUAL = BYTE_OPCODE["OP_0NOTEQUAL"] + +OP_ADD = BYTE_OPCODE["OP_ADD"] +OP_SUB = BYTE_OPCODE["OP_SUB"] +OP_MUL = BYTE_OPCODE["OP_MUL"] +OP_DIV = BYTE_OPCODE["OP_DIV"] +OP_MOD = BYTE_OPCODE["OP_MOD"] +OP_LSHIFT = BYTE_OPCODE["OP_LSHIFT"] +OP_RSHIFT = BYTE_OPCODE["OP_RSHIFT"] + +OP_BOOLAND = BYTE_OPCODE["OP_BOOLAND"] +OP_BOOLOR = BYTE_OPCODE["OP_BOOLOR"] +OP_NUMEQUAL = BYTE_OPCODE["OP_NUMEQUAL"] +OP_NUMEQUALVERIFY = BYTE_OPCODE["OP_NUMEQUALVERIFY"] +OP_NUMNOTEQUAL = BYTE_OPCODE["OP_NUMNOTEQUAL"] +OP_LESSTHAN = BYTE_OPCODE["OP_LESSTHAN"] +OP_GREATERTHAN = BYTE_OPCODE["OP_GREATERTHAN"] +OP_LESSTHANOREQUAL = BYTE_OPCODE["OP_LESSTHANOREQUAL"] +OP_GREATERTHANOREQUAL = BYTE_OPCODE["OP_GREATERTHANOREQUAL"] +OP_MIN = BYTE_OPCODE["OP_MIN"] +OP_MAX = BYTE_OPCODE["OP_MAX"] +OP_WITHIN = BYTE_OPCODE["OP_WITHIN"] + +# crypto + +OP_RIPEMD160 = BYTE_OPCODE["OP_RIPEMD160"] +OP_SHA1 = BYTE_OPCODE["OP_SHA1"] +OP_SHA256 = BYTE_OPCODE["OP_SHA256"] +OP_HASH160 = BYTE_OPCODE["OP_HASH160"] +OP_HASH256 = BYTE_OPCODE["OP_HASH256"] +OP_CODESEPARATOR = BYTE_OPCODE["OP_CODESEPARATOR"] +OP_CHECKSIG = BYTE_OPCODE["OP_CHECKSIG"] +OP_CHECKSIGVERIFY = BYTE_OPCODE["OP_CHECKSIGVERIFY"] +OP_CHECKMULTISIG = BYTE_OPCODE["OP_CHECKMULTISIG"] +OP_CHECKMULTISIGVERIFY = BYTE_OPCODE["OP_CHECKMULTISIGVERIFY"] + +# expansion + +OP_NOP1 = BYTE_OPCODE["OP_NOP1"] +OP_CHECKLOCKTIMEVERIFY = BYTE_OPCODE["OP_CHECKLOCKTIMEVERIFY"] +OP_CHECKSEQUENCEVERIFY = BYTE_OPCODE["OP_CHECKSEQUENCEVERIFY"] +OP_NOP4 = BYTE_OPCODE["OP_NOP4"] +OP_NOP5 = BYTE_OPCODE["OP_NOP5"] +OP_NOP6 = BYTE_OPCODE["OP_NOP6"] +OP_NOP7 = BYTE_OPCODE["OP_NOP7"] +OP_NOP8 = BYTE_OPCODE["OP_NOP8"] +OP_NOP9 = BYTE_OPCODE["OP_NOP9"] +OP_NOP10 = BYTE_OPCODE["OP_NOP10"] + +# template matching params + +OP_SMALLINTEGER = BYTE_OPCODE["OP_SMALLINTEGER"] +OP_PUBKEYS = BYTE_OPCODE["OP_PUBKEYS"] +OP_PUBKEYHASH = BYTE_OPCODE["OP_PUBKEYHASH"] +OP_PUBKEY = BYTE_OPCODE["OP_PUBKEY"] +OP_INVALIDOPCODE = BYTE_OPCODE["OP_INVALIDOPCODE"] \ No newline at end of file diff --git a/pybtc/tools.py b/pybtc/tools.py index 918d209..a3808a3 100644 --- a/pybtc/tools.py +++ b/pybtc/tools.py @@ -35,7 +35,7 @@ def create_private_key(compressed=True, testnet=False, wif=True, hex=False): if not i and int.from_bytes(h, byteorder="big") > MAX_INT_PRIVATE_KEY: i += 1 if wif: - return private_key_to_wif(h) + return private_key_to_wif(h, compressed=compressed, testnet=testnet) elif hex: return hexlify(h).decode() return h @@ -48,7 +48,7 @@ def private_key_to_wif(h, compressed=True, testnet=False): :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 + :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. @@ -86,7 +86,7 @@ def is_wif_valid(wif): Check is private key in WIF format string is valid. :param wif: private key in WIF format string. - :return: boolean + :return: boolean. """ if not isinstance(wif, str): raise TypeError("invalid wif key") @@ -117,7 +117,7 @@ def private_to_public_key(private_key, compressed=True, hex=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 + :return: 33/65 bytes public key in HEX or bytes string. """ if not isinstance(private_key, bytes): if isinstance(private_key, bytearray): @@ -152,7 +152,7 @@ def is_public_key_valid(key): Check public key is valid. :param key: public key in HEX or bytes string format. - :return: boolean + :return: boolean. """ if isinstance(key, str): key = unhexlify(key) @@ -260,7 +260,7 @@ def address_to_hash(address, hex=True): :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 + :return: script in HEX or bytes string. """ if address[0] in ADDRESS_PREFIX_LIST: h = decode_base58(address)[1:-4] @@ -328,7 +328,7 @@ def address_to_script(address, hex=False): :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 + :return: public key script in HEX or bytes string. """ if address[0] in (TESTNET_SCRIPT_ADDRESS_PREFIX, MAINNET_SCRIPT_ADDRESS_PREFIX): @@ -358,8 +358,9 @@ def address_to_script(address, hex=False): def public_key_to_p2sh_p2wpkh_script(pubkey): - assert len(pubkey) == 33 - return b'\x00\x14' + hash160(pubkey) + if len(pubkey) != 33: + raise TypeError("public key len invalid") + return b'\x00\x14%s' % hash160(pubkey) def is_address_valid(address, testnet=False): @@ -368,7 +369,7 @@ def is_address_valid(address, testnet=False): :param address: address in base58 or bech32 format. :param testnet: (optional) flag for testnet network, by default is False. - :return: boolean + :return: boolean. """ if not address or type(address) != str: return False @@ -438,6 +439,20 @@ def get_witness_version(address): # Script def parse_script(script, segwit=True): + """ + Parse script and return script type, script address and required signatures count. + + :param script: script in bytes string or HEX encoded string format. + :param segwit: (optional) If set to True recognize P2WPKH and P2WSH sripts, by default set to True. + + :return: dictionary: + + - nType - numeric script type + - type - script type + - addressHash - address hash in case address recognized + - script - script if no address recognized + - reqSigs - required signatures count + """ if not script: return {"nType": 7, "type": "NON_STANDARD", "reqSigs": 0, "script": b""} if type(script) == str: @@ -533,12 +548,20 @@ def parse_script(script, segwit=True): def decode_script(script, asm=False): - if type(script) == str: + """ + Decode script to ASM format or to human readable OPCODES string. + + :param script: script in bytes string or HEX encoded string format. + :param asm: (optional) If set to True decode to ASM fromat, by default set to False. + :return: script in ASM format string or OPCODES string. + """ + if isinstance(script, str): try: script = unhexlify(script) except: pass - assert type(script) == bytes + if not isinstance(script, bytes): + raise TypeError("script invalid") l = len(script) s = 0 result = [] @@ -562,22 +585,33 @@ def decode_script(script, asm=False): def delete_from_script(script, sub_script): + """ + Decode OPCODE or subscript from script. + + :param script: traget script in bytes or HEX encoded string. + :param sub_script: sub_script which is necessary to remove from target script in bytes or HEX encoded string. + :return: script in bytes or HEX encoded string corresponding to the format of target script. + """ if not sub_script: return script s_hex = False - if type(script) == str: + if isinstance(script, str): try: script = unhexlify(script) s_hex = True except: pass - assert type(script) == bytes - if type(sub_script) == str: + if isinstance(sub_script, str): try: sub_script = unhexlify(sub_script) except: pass - assert type(sub_script) == bytes + + if not isinstance(script, bytes): + raise TypeError("script invalid") + if not isinstance(sub_script, bytes): + raise TypeError("sub_script invalid") + l = len(script) ls = len(sub_script) s = 0 @@ -621,41 +655,57 @@ def delete_from_script(script, sub_script): return b''.join(result) if not s_hex else hexlify(b''.join(result)).decode() -def script_to_hash(s, witness=False, hex=False): - if type(s) == str: - s = unhexlify(s) +def script_to_hash(script, witness=False, hex=True): + """ + Encode script to hash HASH160 or SHA256 in dependency of the witness. + + :param script: script in bytes or HEX encoded string. + :param witness: (optional) If set to True return SHA256 hash for P2WSH, by default is False. + :param hex: (optional) If set to True return key in HEX format, by default is True. + :param sub_script: sub_script which is necessary to remove from target script in bytes or HEX encoded string. + :return: script in bytes or HEX encoded string corresponding to the format of target script. + """ + if isinstance(script, str): + s = unhexlify(script) if witness: - return sha256(s, hex) + return sha256(script, hex) else: - return hash160(s, hex) + return hash160(script, hex) -# -# ECDSA -# +# Signatures def verify_signature(sig, pub_key, msg): - if type(sig) != bytes: - if type(sig) == bytearray: + """ + 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 or HEX encoded string. + :return: boolean. + """ + if not isinstance(sig, bytes): + if isinstance(sig, bytearray): sig = bytes(sig) - elif type(sig) == str: + elif isinstance(sig, str): sig = unhexlify(sig) else: raise TypeError("signature must be a bytes or hex encoded string") - if type(pub_key) != bytes: - if type(pub_key) == bytearray: + if not isinstance(pub_key, bytes): + if isinstance(pub_key, bytearray): pub_key = bytes(pub_key) - elif type(pub_key) == str: + elif isinstance(pub_key, str): pub_key = unhexlify(pub_key) else: raise TypeError("public key must be a bytes or hex encoded string") - if type(msg) != bytes: - if type(msg) == bytearray: + if not isinstance(msg, bytes): + if isinstance(msg, bytearray): msg = bytes(msg) - elif type(msg) == str: + elif isinstance(msg, str): msg = unhexlify(msg) else: raise TypeError("message must be a bytes or hex encoded string") + raw_sig = ffi.new('secp256k1_ecdsa_signature *') raw_pubkey = ffi.new('secp256k1_pubkey *') if not secp256k1.secp256k1_ecdsa_signature_parse_der(ECDSA_CONTEXT_VERIFY, raw_sig, sig, len(sig)): @@ -666,45 +716,59 @@ def verify_signature(sig, pub_key, msg): return True if result else False -def sign_message(msg, private_key, hex=False): +def sign_message(msg, private_key, hex=True): """ Sign message - :param msg: message to sign - :param private_key: private key (bytes, hex encoded string) - :param hex: - :return: DER encoded sinature + :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. + :return: DER encoded signature in bytes or HEX encoded string. """ - if type(msg) != bytes: - if type(msg) == bytearray: - msg = bytes(msg) - - elif type(msg) == str: + if isinstance(msg, bytearray): + msg = bytes(msg) + if isinstance(msg, str): + try: msg = unhexlify(msg) - else: + except: + pass + if not isinstance(msg, bytes): raise TypeError("message must be a bytes or hex encoded string") - if type(private_key) != bytes: - if type(private_key) == bytearray: - private_key = bytes(private_key) - elif type(private_key) == str: + + if isinstance(private_key, bytearray): + private_key = bytes(private_key) + if isinstance(private_key, str): + try: private_key = unhexlify(private_key) - else: - raise TypeError("private key must be a bytes or hex encoded string") + except: + if is_wif_valid(private_key): + private_key = wif_to_private_key(private_key, hex=False) + if not isinstance(private_key, bytes): + raise TypeError("private key must be a bytes, hex encoded string or in WIF format") + raw_sig = ffi.new('secp256k1_ecdsa_signature *') signed = secp256k1.secp256k1_ecdsa_sign(ECDSA_CONTEXT_SIGN, raw_sig, msg, private_key, ffi.NULL, ffi.NULL) - assert signed == 1 + if not signed: + raise RuntimeError("secp256k1 error") len_sig = 74 output = ffi.new('unsigned char[%d]' % len_sig) outputlen = ffi.new('size_t *', len_sig) res = secp256k1.secp256k1_ecdsa_signature_serialize_der(ECDSA_CONTEXT_SIGN, output, outputlen, raw_sig) - assert res == 1 + if not res: + raise RuntimeError("secp256k1 error") signature = bytes(ffi.buffer(output, outputlen[0])) return hexlify(signature).decode() if hex else signature def is_valid_signature_encoding(sig): + """ + Check is valid signature encoded in DER format + + :param sig: signature in bytes or HEX encoded string. + :return: boolean. + """ # Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash] # * total-length: 1-byte length descriptor of everything that follows, # excluding the sighash byte. @@ -766,17 +830,25 @@ def is_valid_signature_encoding(sig): return True -# -# Transaction encoding -# +# Hash encoding -def rh2s(tthash): - # raw hash to string - return hexlify(tthash[::-1]).decode() +def rh2s(raw_hash): + """ + Encode raw transaction hash to HEX string with bytes order change + + :param raw_hash: transaction hash in bytes string. + :return: HEX encoded string. + """ + return hexlify(raw_hash[::-1]).decode() def s2rh(hash_string): - # string to raw hash + """ + Decode HEX transaction hash to bytes with byte order change + + :param raw_hash: transaction hash in bytes string. + :return: bytes string. + """ return unhexlify(hash_string)[::-1] @@ -785,16 +857,27 @@ def s2rh_step4(hash_string): return reverse_hash(h) -def reverse_hash(h): - return struct.pack('>IIIIIIII', *struct.unpack('>IIIIIIII', h)[::-1])[::-1] +def reverse_hash(raw_hash): + """ + Reverse hash order -# -# -# + :param raw_hash: bytes string. + :return: bytes string. + """ + return struct.pack('>IIIIIIII', *struct.unpack('>IIIIIIII', raw_hash)[::-1])[::-1] -def merkle_root(tx_hash_list): - tx_hash_list = list(tx_hash_list) +# Merkle root + +def merkle_root(tx_hash_list, hex=True): + """ + Calculate merkle root from transaction hash list + + :param tx_hash_list: list of transaction hashes in bytes or HEX encoded string. + :param hex: (optional) If set to True return result in HEX format, by default is True. + :return: merkle root in bytes or HEX encoded string corresponding hex flag. + """ + tx_hash_list = [h if isinstance(h, bytes) else s2rh(h) for h in tx_hash_list] if len(tx_hash_list) == 1: return tx_hash_list[0] while True: @@ -809,11 +892,18 @@ def merkle_root(tx_hash_list): if len(new_hash_list) > 1: tx_hash_list = new_hash_list else: - return new_hash_list[0] + return new_hash_list[0] if not hex else hexlify(new_hash_list[0]).decode() -def merkle_branches(tx_hash_list): - tx_hash_list = list(tx_hash_list) +def merkle_branches(tx_hash_list, hex=True): + """ + Calculate merkle branches for coinbase transacton + + :param tx_hash_list: list of transaction hashes in bytes or HEX encoded string. + :param hex: (optional) If set to True return result in HEX format, by default is True. + :return: list of merkle branches in bytes or HEX encoded string corresponding hex flag. + """ + tx_hash_list = [h if isinstance(h, bytes) else s2rh(h) for h in tx_hash_list] branches = [] if len(tx_hash_list) == 1: return [] @@ -833,19 +923,36 @@ def merkle_branches(tx_hash_list): else: if new_hash_list: branches.append(new_hash_list.pop(0)) - return branches + return branches if not hex else [hexlify(h).decode() for h in branches] -def merkleroot_from_branches(merkle_branches, coinbase_hash_bin): - merkle_root = coinbase_hash_bin +def merkleroot_from_branches(merkle_branches, coinbase_hash, hex=True): + """ + Calculate merkle root from merkle branches and coinbase transacton hash + + :param merkle_branches: list merkle branches in bytes or HEX encoded string. + :param coinbase_hash: list coinbase transaction hash in bytes or HEX encoded string. + :param hex: (optional) If set to True return result in HEX format, by default is True. + :return: merkle root in bytes or HEX encoded string corresponding hex flag. + """ + merkle_root = coinbase_hash if not isinstance(coinbase_hash, str) else unhexlify(coinbase_hash) for h in merkle_branches: if type(h) == str: h = unhexlify(h) merkle_root = double_sha256(merkle_root + h) - return merkle_root + return merkle_root if not hex else hexlify(merkle_root).decode() + + +# Difficulty def bits_to_target(bits): + """ + Calculate target from bits + + :param bits: HEX string, bytes string or integer representation of bits. + :return: integer. + """ if type(bits) == str: bits = unhexlify(bits) if type(bits) == bytes: @@ -857,48 +964,97 @@ def bits_to_target(bits): def target_to_difficulty(target): + """ + Calculate difficulty from target + + :param target: integer. + :return: float. + """ return 0x00000000FFFF0000000000000000000000000000000000000000000000000000 / target def bits_to_difficulty(bits): + """ + Calculate difficulty from bits + + :param bits: HEX string, bytes string or integer representation of bits. + :return: integer. + """ return target_to_difficulty(bits_to_target(bits)) def difficulty_to_target(difficulty): + """ + Calculate target from difficulty + + :param target: integer. + :return: float. + """ return int(0x00000000FFFF0000000000000000000000000000000000000000000000000000 / difficulty) -# -# -# +# Tools + def bytes_needed(n): + """ + Calculate bytes needed to convert integer to bytes. + + :param n: integer. + :return: integer. + """ if n == 0: return 1 return math.ceil(n.bit_length()/8) def int_to_bytes(i, byteorder='big'): + """ + Convert integer to bytes. + + :param n: integer. + :param byteorder: (optional) byte order 'big' or 'little', by default 'big'. + :return: bytes. + """ return i.to_bytes(bytes_needed(i), byteorder=byteorder, signed=False) def bytes_to_int(i, byteorder='big'): + """ + Convert bytes to integer. + + :param i: bytes. + :param byteorder: (optional) byte order 'big' or 'little', by default 'big'. + :return: integer. + """ return int.from_bytes(i, byteorder=byteorder, signed=False) # variable integer def int_to_var_int(i): + """ + Convert integer to variable integer + + :param i: integer. + :return: bytes. + """ if i < 0xfd: return struct.pack('