diff --git a/.circleci/config.yml b/.circleci/config.yml index 87ced90054baac442d5a2910f53f79d5f2569443..12d1bb086f6a9471f8074c75f72d342309b5fd92 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,7 +34,7 @@ jobs: - run: name: install dependencies command: | - sudo python -m pip install --upgrade pip setuptools wheel mock pytest pytest-cov parameterized + sudo python -m pip install --upgrade pip setuptools wheel mock pytest pytest-cov parameterized cryptography cffi secp256k1 sudo python -m pip install -r requirements.txt sudo python -m pip install --upgrade pycodestyle pyflakes coverage tox codacy-coverage virtualenv diff --git a/README.rst b/README.rst index b35478e5c9a9d0a0f84153fb73971e6a7accec04..8a3db881cc116119a1f4d84850ce0bb996e2b57e 100644 --- a/README.rst +++ b/README.rst @@ -35,8 +35,9 @@ Current build status .. image:: https://codecov.io/gh/holgern/beem/branch/master/graph/badge.svg :target: https://codecov.io/gh/holgern/beem -.. image:: https://coveralls.io/repos/github/holgern/beem/badge.svg?branch=master - :target: https://coveralls.io/github/holgern/beem?branch=master +.. image:: https://coveralls.io/repos/github/holgern/beem/badge.svg +:target: https://coveralls.io/github/holgern/beem + .. image:: https://api.codacy.com/project/badge/Grade/e5476faf97df4c658697b8e7a7efebd7 :target: https://www.codacy.com/app/holgern/beem?utm_source=github.com&utm_medium=referral&utm_content=holgern/beem&utm_campaign=Badge_Grade @@ -78,6 +79,12 @@ For Termux on Android, please install the following packages: .. code:: bash pkg install clang openssl-dev python-dev + +Signing and Verify can be fasten (200 %) by installing cryptography: + +.. code:: bash + + pip install -U cryptography Install beem by pip:: @@ -108,10 +115,19 @@ Once the conda-forge channel has been enabled, beem can be installed with: conda install beem +Signing and Verify can be fasten (200 %) by installing cryptography: + +.. code:: bash + + conda install cryptography + + CLI tool bundled ---------------- I started to work on a CLI tool: +.. code:: bash + beempy Documentation @@ -120,6 +136,14 @@ Documentation is available at http://beem.readthedocs.io/en/latest/ Changelog ========= +0.19.17 +------- +* GOLOS chain added +* Huge speed improvements for all sign/verify operaions (around 200%) +* Sign/verify speed can now be improved by installing cryptography +* benchmark added +* Example for speed comparison with steem-python added + 0.19.16 ------- * rename wallet.purge() and wallet.purgeWallet() to wallet.wipe() diff --git a/beem/account.py b/beem/account.py index b7dbf95d26865f4de99e8b7a057c668c4d6f5177..3188f30bf91dadf8b70c6bafd6fa18ae9e798981 100644 --- a/beem/account.py +++ b/beem/account.py @@ -214,10 +214,9 @@ class Account(BlockchainObject): def get_steem_power(self, onlyOwnSP=False): """ Returns the account steem power """ - if onlyOwnSP: - vests = (self["vesting_shares"]) - else: - vests = (self["vesting_shares"]) - (self["delegated_vesting_shares"]) + (self["received_vesting_shares"]) + vests = (self["vesting_shares"]) + if not onlyOwnSP and "delegated_vesting_shares" in self and "received_vesting_shares" in self: + vests = vests - (self["delegated_vesting_shares"]) + (self["received_vesting_shares"]) return self.steem.vests_to_sp(vests) def get_voting_value_SBD(self, voting_weight=100, voting_power=None, steem_power=None): @@ -392,27 +391,40 @@ class Account(BlockchainObject): """ List balances of an account. This call returns instances of :class:`steem.amount.Amount`. """ - available_amount = [self["balance"], self["sbd_balance"], self["vesting_shares"]] + amount_list = ["balance", "sbd_balance", "vesting_shares"] + available_amount = [] + for amount in amount_list: + if amount in self: + available_amount.append(self[amount]) return available_amount @property def saving_balances(self): - savings_amount = [self["savings_balance"], self["savings_sbd_balance"]] + savings_amount = [] + amount_list = ["savings_balance", "savings_sbd_balance"] + for amount in amount_list: + if amount in self: + savings_amount.append(self[amount]) return savings_amount @property def reward_balances(self): - rewards_amount = [self["reward_steem_balance"], self["reward_sbd_balance"], self["reward_vesting_balance"]] + amount_list = ["reward_steem_balance", "reward_sbd_balance", "reward_vesting_balance"] + rewards_amount = [] + for amount in amount_list: + if amount in self: + rewards_amount.append(self[amount]) return rewards_amount @property def total_balances(self): + symbols = [self.available_balances[0]["symbol"], self.available_balances[1]["symbol"], self.available_balances[2]["symbol"]] return [ - self.get_balance(self.available_balances, "STEEM") + self.get_balance(self.saving_balances, "STEEM") + - self.get_balance(self.reward_balances, "STEEM"), - self.get_balance(self.available_balances, "SBD") + self.get_balance(self.saving_balances, "SBD") + - self.get_balance(self.reward_balances, "SBD"), - self.get_balance(self.available_balances, "VESTS") + self.get_balance(self.reward_balances, "VESTS"), + self.get_balance(self.available_balances, symbols[0]) + self.get_balance(self.saving_balances, symbols[0]) + + self.get_balance(self.reward_balances, symbols[0]), + self.get_balance(self.available_balances, symbols[1]) + self.get_balance(self.saving_balances, symbols[1]) + + self.get_balance(self.reward_balances, symbols[1]), + self.get_balance(self.available_balances, symbols[2]) + self.get_balance(self.reward_balances, symbols[2]), ] @property diff --git a/beem/blockchain.py b/beem/blockchain.py index 45eabdffc93b6a7c84d676716db1c4074b58be7c..13ab6bc8e108b03193cfc5c8c222dea0980adcc2 100644 --- a/beem/blockchain.py +++ b/beem/blockchain.py @@ -31,10 +31,11 @@ class Blockchain(object): """ This class allows to access the blockchain and read data from it - :param beem.steem.Steem steem_instance: Steem - instance + :param beem.steem.Steem steem_instance: Steem instance :param str mode: (default) Irreversible block (``irreversible``) or - actual head block (``head``) + actual head block (``head``) + :param int max_block_wait_repetition: (default) 3 maximum wait time for next block + is max_block_wait_repetition * block_interval This class let's you deal with blockchain related data and methods. Read blockchain related data: @@ -63,6 +64,7 @@ class Blockchain(object): self, steem_instance=None, mode="irreversible", + max_block_wait_repetition=None, data_refresh_time_seconds=900, ): self.steem = steem_instance or shared_steem_instance() @@ -73,6 +75,13 @@ class Blockchain(object): self.mode = "head_block_number" else: raise ValueError("invalid value for 'mode'!") + if max_block_wait_repetition: + self.max_block_wait_repetition = max_block_wait_repetition + else: + self.max_block_wait_repetition = 3 + + def is_irreversible_mode(self): + return self.mode == 'last_irreversible_block_num' def get_current_block_num(self): """ This call returns the current block number @@ -163,7 +172,7 @@ class Blockchain(object): confirmed by 2/3 of all block producers and is thus irreversible) """ # Let's find out how often blocks are generated! - block_interval = self.steem.get_block_interval() + self.block_interval = self.steem.get_block_interval() if not start: start = self.get_current_block_num() @@ -234,7 +243,7 @@ class Blockchain(object): # Blocks from start until head block for blocknum in range(start, head_block + 1): # Get full block - block = Block(blocknum, steem_instance=self.steem) + block = self.wait_for_and_get_block(blocknum) yield block # Set new start start = head_block + 1 @@ -244,7 +253,35 @@ class Blockchain(object): return # Sleep for one block - time.sleep(block_interval) + time.sleep(self.block_interval) + + def wait_for_and_get_block(self, block_number, blocks_waiting_for=None): + """ Get the desired block from the chain, if the current head block is smaller (for both head and irreversible) + then we wait, but a maxmimum of blocks_waiting_for * max_block_wait_repetition time before failure. + :param int block_number: desired block number + :param int blocks_waiting_for: (default) difference between block_number and current head + how many blocks we are willing to wait, positive int + """ + if not blocks_waiting_for: + blocks_waiting_for = max(1, block_number - self.get_current_block_num()) + + repetition = 0 + # can't return the block before the chain has reached it (support future block_num) + while self.get_current_block_num() < block_number: + repetition += 1 + time.sleep(self.block_interval) + if repetition > blocks_waiting_for * self.max_block_wait_repetition: + raise Exception("Wait time for new block exceeded, aborting") + # block has to be returned properly + block = Block(block_number, steem_instance=self.steem) + repetition = 0 + while not block: + repetition += 1 + time.sleep(self.block_interval) + if repetition > self.max_block_wait_repetition: + raise Exception("Wait time for new block exceeded, aborting") + block = Block(block_number, steem_instance=self.steem) + return block def ops(self, start=None, stop=None, **kwargs): """ Yields all operations (including virtual operations) starting from diff --git a/beem/instance.py b/beem/instance.py index a9ed61e139525342ffc7781afdb66d06cfbd9e20..6cb1e139dde1725aed21b68f26c73fb2ffa78bf9 100644 --- a/beem/instance.py +++ b/beem/instance.py @@ -9,6 +9,7 @@ import beem as stm class SharedInstance(object): instance = None + config = {} def shared_steem_instance(): @@ -28,7 +29,7 @@ def shared_steem_instance(): """ if not SharedInstance.instance: clear_cache() - SharedInstance.instance = stm.Steem() + SharedInstance.instance = stm.Steem(**SharedInstance.config) return SharedInstance.instance @@ -47,3 +48,13 @@ def clear_cache(): """ from .blockchainobject import BlockchainObject BlockchainObject.clear_cache() + + +def set_shared_config(config): + """ This allows to set a config that will be used when calling + ``shared_steem_instance`` and allows to define the configuration + without requiring to actually create an instance + """ + if not isinstance(config, dict): + raise AssertionError() + SharedInstance.config = config diff --git a/beem/steem.py b/beem/steem.py index 67a6b3b8f09751c57830762036604c4d8d20aade..527543321985c936b1ddde3927d1e8232c76d8f6 100644 --- a/beem/steem.py +++ b/beem/steem.py @@ -240,13 +240,15 @@ class Steem(object): if use_stored_data: self.refresh_data() return self.data['dynamic_global_properties'] - else: - if self.rpc is None: - return None + if self.rpc is None: + return None + try: if self.rpc.get_use_appbase(): return self.rpc.get_dynamic_global_properties(api="database") else: return self.rpc.get_dynamic_global_properties() + except: + return None def get_reserve_ratio(self, use_stored_data=True): """ This call returns the *dynamic global properties* @@ -256,9 +258,10 @@ class Steem(object): if use_stored_data: self.refresh_data() return self.data['reserve_ratio'] - else: - if self.rpc is None: - return None + + if self.rpc is None: + return None + try: if self.rpc.get_use_appbase(): return self.rpc.get_reserve_ratio(api="witness") else: @@ -266,6 +269,8 @@ class Steem(object): return {'id': 0, 'average_block_size': props['average_block_size'], 'current_reserve_ratio': props['current_reserve_ratio'], 'max_virtual_bandwidth': props['max_virtual_bandwidth']} + except: + return None def get_feed_history(self, use_stored_data=True): """ Returns the feed_history @@ -275,13 +280,15 @@ class Steem(object): if use_stored_data: self.refresh_data() return self.data['feed_history'] - else: + try: if self.rpc is None: return None if self.rpc.get_use_appbase(): return self.rpc.get_feed_history(api="database") else: return self.rpc.get_feed_history() + except: + return None def get_reward_funds(self, use_stored_data=True): """ Get details for a reward fund. @@ -291,9 +298,10 @@ class Steem(object): if use_stored_data: self.refresh_data() return self.data['reward_funds'] - else: - if self.rpc is None: - return None + + if self.rpc is None: + return None + try: if self.rpc.get_use_appbase(): funds = self.rpc.get_reward_funds(api="database")['funds'] if len(funds) > 0: @@ -301,6 +309,8 @@ class Steem(object): return funds else: return self.rpc.get_reward_fund("post") + except: + return None def get_current_median_history(self, use_stored_data=True): """ Returns the current median price @@ -310,13 +320,15 @@ class Steem(object): if use_stored_data: self.refresh_data() return self.data['get_feed_history']['current_median_history'] - else: - if self.rpc is None: - return None + if self.rpc is None: + return None + try: if self.rpc.get_use_appbase(): return self.rpc.get_feed_history(api="database")['current_median_history'] else: return self.rpc.get_current_median_history_price()['current_median_history'] + except: + return None def get_hardfork_properties(self, use_stored_data=True): """ Returns Hardfork and live_time of the hardfork @@ -326,13 +338,15 @@ class Steem(object): if use_stored_data: self.refresh_data() return self.data['hardfork_properties'] - else: - if self.rpc is None: - return None + if self.rpc is None: + return None + try: if self.rpc.get_use_appbase(): return self.rpc.get_hardfork_properties(api="database") else: return self.rpc.get_next_scheduled_hardfork() + except: + return None def get_network(self, use_stored_data=True): """ Identify the network @@ -345,10 +359,13 @@ class Steem(object): if use_stored_data: self.refresh_data() return self.data['network'] - else: - if self.rpc is None: - return None + + if self.rpc is None: + return None + try: return self.rpc.get_network() + except: + return None def get_median_price(self): """ Returns the current median history price as Price @@ -472,13 +489,16 @@ class Steem(object): if use_stored_data: self.refresh_data() return self.data['witness_schedule'] - else: - if self.rpc is None: - return None + + if self.rpc is None: + return None + try: if self.rpc.get_use_appbase(): return self.rpc.get_witness_schedule(api="database") else: return self.rpc.get_witness_schedule() + except: + return None def get_config(self, use_stored_data=True): """ Returns internal chain configuration. @@ -486,13 +506,15 @@ class Steem(object): if use_stored_data: self.refresh_data() return self.data['config'] - else: - if self.rpc is None: - return None + if self.rpc is None: + return None + try: if self.rpc.get_use_appbase(): return self.rpc.get_config(api="database") else: return self.rpc.get_config() + except: + return None @property def chain_params(self): diff --git a/beem/version.py b/beem/version.py index 3f796c97ad965a606788822ba16299728f2993f2..005fb8f7c8fa9159dabca8c5e496b2062ee8a278 100644 --- a/beem/version.py +++ b/beem/version.py @@ -1,2 +1,2 @@ """THIS FILE IS GENERATED FROM beem SETUP.PY.""" -version = '0.19.16' +version = '0.19.17' diff --git a/beemapi/version.py b/beemapi/version.py index 3f796c97ad965a606788822ba16299728f2993f2..005fb8f7c8fa9159dabca8c5e496b2062ee8a278 100644 --- a/beemapi/version.py +++ b/beemapi/version.py @@ -1,2 +1,2 @@ """THIS FILE IS GENERATED FROM beem SETUP.PY.""" -version = '0.19.16' +version = '0.19.17' diff --git a/beembase/chains.py b/beembase/chains.py index 9c39646e64743cca195989f95f6cb812c8481b52..8bd21a12f9a3eb062a9e90c5e7124bb459e1aedd 100644 --- a/beembase/chains.py +++ b/beembase/chains.py @@ -39,9 +39,19 @@ known_chains = { "min_version": '0.0.0', "prefix": "TST", "chain_assets": [ - {"asset": "TBD", "symbol": "SBD", "precision": 3, "id": 0}, - {"asset": "TESTS", "symbol": "STEEM", "precision": 3, "id": 1}, + {"asset": "SBD", "symbol": "TBD", "precision": 3, "id": 0}, + {"asset": "STEEM", "symbol": "TESTS", "precision": 3, "id": 1}, {"asset": "VESTS", "symbol": "VESTS", "precision": 6, "id": 2} ], }, + "GOLOS": { + "chain_id": "782a3039b478c839e4cb0c941ff4eaeb7df40bdd68bd441afd444b9da763de12", + "min_version": '0.0.0', + "prefix": "GLS", + "chain_assets": [ + {"asset": "SBD", "symbol": "GBG", "precision": 3, "id": 0}, + {"asset": "STEEM", "symbol": "GOLOS", "precision": 3, "id": 1}, + {"asset": "VESTS", "symbol": "GESTS", "precision": 6, "id": 2} + ], + }, } diff --git a/beembase/version.py b/beembase/version.py index 3f796c97ad965a606788822ba16299728f2993f2..005fb8f7c8fa9159dabca8c5e496b2062ee8a278 100644 --- a/beembase/version.py +++ b/beembase/version.py @@ -1,2 +1,2 @@ """THIS FILE IS GENERATED FROM beem SETUP.PY.""" -version = '0.19.16' +version = '0.19.17' diff --git a/beemgrapheneapi/version.py b/beemgrapheneapi/version.py index 3f796c97ad965a606788822ba16299728f2993f2..005fb8f7c8fa9159dabca8c5e496b2062ee8a278 100644 --- a/beemgrapheneapi/version.py +++ b/beemgrapheneapi/version.py @@ -1,2 +1,2 @@ """THIS FILE IS GENERATED FROM beem SETUP.PY.""" -version = '0.19.16' +version = '0.19.17' diff --git a/beemgraphenebase/account.py b/beemgraphenebase/account.py index 9e825df74515a06d5a804a7f38a46b6a5fc12019..5127f847a6c78c09927d1aebe701e83a390b2373 100644 --- a/beemgraphenebase/account.py +++ b/beemgraphenebase/account.py @@ -11,14 +11,14 @@ import hashlib import sys import re import os - +import ecdsa +import ctypes from binascii import hexlify, unhexlify + from .base58 import ripemd160, Base58 from .dictionary import words as BrainKeyDictionary from .py23 import py23_bytes -import ecdsa - class PasswordKey(object): """ This class derives a private key given the account name, the @@ -166,6 +166,18 @@ class Address(object): addressbin = ripemd160(hexlify(hashlib.sha256(pkbin).digest())) return Base58(hexlify(addressbin).decode('ascii')) + def derive256address_with_version(self, version=56): + """ Derive address using ``RIPEMD160(SHA256(x))`` + and adding version + checksum + """ + pkbin = unhexlify(repr(self._pubkey)) + addressbin = ripemd160(hexlify(hashlib.sha256(pkbin).digest())) + addr = py23_bytes(ctypes.c_uint8(version & 0xFF)) + addressbin + check = hashlib.sha256(addr).digest() + check = hashlib.sha256(check).digest() + buffer = addr + check[0:4] + return Base58(hexlify(ripemd160(hexlify(buffer))).decode('ascii')) + def derivesha512address(self): """ Derive address using ``RIPEMD160(SHA512(x))`` """ pkbin = unhexlify(repr(self._pubkey)) diff --git a/beemgraphenebase/ecdsasig.py b/beemgraphenebase/ecdsasig.py index 43eb952ae7e0bc5529b6371ce2de28ce77b72e7c..c9446dd17c81d75813608d543cfb3660a5f2d377 100644 --- a/beemgraphenebase/ecdsasig.py +++ b/beemgraphenebase/ecdsasig.py @@ -9,20 +9,32 @@ import sys import time import ecdsa import hashlib +from binascii import hexlify, unhexlify import struct import logging -from .account import PrivateKey +from .account import PrivateKey, PublicKey from .py23 import py23_bytes, bytes_types log = logging.getLogger(__name__) -try: - import secp256k1 - USE_SECP256K1 = True - log.debug("Loaded secp256k1 binding.") -except Exception: - USE_SECP256K1 = False - log.debug("To speed up transactions signing install \n" - " pip install secp256k1") +SECP256K1_MODULE = None +GMPY2_MODULE = False +if not SECP256K1_MODULE: + try: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.hazmat.primitives.asymmetric.utils \ + import decode_dss_signature, encode_dss_signature + from cryptography.exceptions import InvalidSignature + SECP256K1_MODULE = "cryptography" + except ImportError: + try: + import secp256k1 + SECP256K1_MODULE = "secp256k1" + except ImportError: + SECP256K1_MODULE = "ecdsa" + +log.debug("Using SECP256K1 module: %s" % SECP256K1_MODULE) def _is_canonical(sig): @@ -34,15 +46,23 @@ def _is_canonical(sig): def compressedPubkey(pk): - order = pk.curve.generator.order() - p = pk.pubkey.point - x_str = ecdsa.util.number_to_string(p.x(), order) - return py23_bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str + if SECP256K1_MODULE == "cryptography" and not isinstance(pk, ecdsa.keys.VerifyingKey): + order = ecdsa.SECP256k1.order + x = pk.public_numbers().x + y = pk.public_numbers().y + else: + order = pk.curve.generator.order() + p = pk.pubkey.point + x = p.x() + y = p.y() + x_str = ecdsa.util.number_to_string(x, order) + return py23_bytes(chr(2 + (y & 1)), 'ascii') + x_str -def recover_public_key(digest, signature, i): +def recover_public_key(digest, signature, i, message=None): """ Recover the public key from the the signature """ + # See http: //www.secg.org/download/aid-780/sec1-v2.pdf section 4.1.6 primarily curve = ecdsa.SECP256k1.curve G = ecdsa.SECP256k1.generator @@ -62,26 +82,49 @@ def recover_public_key(digest, signature, i): e = ecdsa.util.string_to_number(digest) # 1.6 Compute Q = r^-1(sR - eG) Q = ecdsa.numbertheory.inverse_mod(r, order) * (s * R + (-e % order) * G) - # Not strictly necessary, but let's verify the message for paranoia's sake. - if not ecdsa.VerifyingKey.from_public_point(Q, curve=ecdsa.SECP256k1).verify_digest(signature, digest, sigdecode=ecdsa.util.sigdecode_string): - return None - return ecdsa.VerifyingKey.from_public_point(Q, curve=ecdsa.SECP256k1) + + if SECP256K1_MODULE == "cryptography" and message is not None: + if not isinstance(message, bytes_types): + message = py23_bytes(message, "utf-8") + sigder = encode_dss_signature(r, s) + public_key = ec.EllipticCurvePublicNumbers(Q._Point__x, Q._Point__y, ec.SECP256K1()).public_key(default_backend()) + public_key.verify(sigder, message, ec.ECDSA(hashes.SHA256())) + return public_key + else: + # Not strictly necessary, but let's verify the message for paranoia's sake. + if not ecdsa.VerifyingKey.from_public_point(Q, curve=ecdsa.SECP256k1).verify_digest(signature, digest, sigdecode=ecdsa.util.sigdecode_string): + return None + return ecdsa.VerifyingKey.from_public_point(Q, curve=ecdsa.SECP256k1) def recoverPubkeyParameter(message, digest, signature, pubkey): """ Use to derive a number that allows to easily recover the public key from the signature """ + if not isinstance(message, bytes_types): + message = py23_bytes(message, "utf-8") for i in range(0, 4): - if USE_SECP256K1: + if SECP256K1_MODULE == "secp256k1": sig = pubkey.ecdsa_recoverable_deserialize(signature, i) p = secp256k1.PublicKey(pubkey.ecdsa_recover(message, sig)) if p.serialize() == pubkey.serialize(): return i + elif SECP256K1_MODULE == "cryptography" and not isinstance(pubkey, PublicKey): + p = recover_public_key(digest, signature, i, message) + p_comp = hexlify(compressedPubkey(p)) + pubkey_comp = hexlify(compressedPubkey(pubkey)) + if (p_comp == pubkey_comp): + return i else: p = recover_public_key(digest, signature, i) - if (p.to_string() == pubkey.to_string() or - compressedPubkey(p) == pubkey.to_string()): + p_comp = hexlify(compressedPubkey(p)) + p_string = hexlify(p.to_string()) + if isinstance(pubkey, PublicKey): + pubkey_string = py23_bytes(repr(pubkey), 'latin') + else: + pubkey_string = hexlify(pubkey.to_string()) + if (p_string == pubkey_string or + p_comp == pubkey_string): return i return None @@ -96,9 +139,9 @@ def sign_message(message, wif, hashfn=hashlib.sha256): message = py23_bytes(message, "utf-8") digest = hashfn(message).digest() - p = py23_bytes(PrivateKey(wif)) - - if USE_SECP256K1: + priv_key = PrivateKey(wif) + if SECP256K1_MODULE == "secp256k1": + p = py23_bytes(priv_key) ndata = secp256k1.ffi.new("const int *ndata") ndata[0] = 0 while True: @@ -120,8 +163,36 @@ def sign_message(message, wif, hashfn=hashlib.sha256): i += 4 # compressed i += 27 # compact break + elif SECP256K1_MODULE == "cryptography": + cnt = 0 + private_key = ec.derive_private_key(int(repr(priv_key), 16), ec.SECP256K1(), default_backend()) + public_key = private_key.public_key() + while True: + cnt += 1 + if not cnt % 20: + log.info("Still searching for a canonical signature. Tried %d times already!" % cnt) + order = ecdsa.SECP256k1.order + signer = private_key.signer(ec.ECDSA(hashes.SHA256())) + signer.update(message) + sigder = signer.finalize() + r, s = decode_dss_signature(sigder) + signature = ecdsa.util.sigencode_string(r, s, order) + # Make sure signature is canonical! + # + sigder = bytearray(sigder) + lenR = sigder[3] + lenS = sigder[5 + lenR] + if lenR is 32 and lenS is 32: + # Derive the recovery parameter + # + i = recoverPubkeyParameter( + message, digest, signature, public_key) + i += 4 # compressed + i += 27 # compact + break else: cnt = 0 + p = py23_bytes(priv_key) sk = ecdsa.SigningKey.from_string(p, curve=ecdsa.SECP256k1) while 1: cnt += 1 @@ -186,7 +257,7 @@ def verify_message(message, signature, hashfn=hashlib.sha256): sig = signature[1:] recoverParameter = bytearray(signature)[0] - 4 - 27 # recover parameter only - if USE_SECP256K1: + if SECP256K1_MODULE == "secp256k1": ALL_FLAGS = secp256k1.lib.SECP256K1_CONTEXT_VERIFY | secp256k1.lib.SECP256K1_CONTEXT_SIGN # Placeholder pub = secp256k1.PublicKey(flags=ALL_FLAGS) @@ -199,6 +270,13 @@ def verify_message(message, signature, hashfn=hashlib.sha256): # Verify verifyPub.ecdsa_verify(message, normalSig) phex = verifyPub.serialize(compressed=True) + elif SECP256K1_MODULE == "cryptography": + p = recover_public_key(digest, sig, recoverParameter, message) + order = ecdsa.SECP256k1.order + r, s = ecdsa.util.sigdecode_string(sig, order) + sigder = encode_dss_signature(r, s) + p.verify(sigder, message, ec.ECDSA(hashes.SHA256())) + phex = compressedPubkey(p) else: p = recover_public_key(digest, sig, recoverParameter) # Will throw an exception of not valid diff --git a/beemgraphenebase/version.py b/beemgraphenebase/version.py index 3f796c97ad965a606788822ba16299728f2993f2..005fb8f7c8fa9159dabca8c5e496b2062ee8a278 100644 --- a/beemgraphenebase/version.py +++ b/beemgraphenebase/version.py @@ -1,2 +1,2 @@ """THIS FILE IS GENERATED FROM beem SETUP.PY.""" -version = '0.19.16' +version = '0.19.17' diff --git a/benchmarks/README.rst b/benchmarks/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..25bc75c201f37f5e82a0c21f2f4eb85fb918b2ff --- /dev/null +++ b/benchmarks/README.rst @@ -0,0 +1,46 @@ +.. -*- rst -*- + +=============== +beem benchmarks +=============== + +Benchmarking beem with Airspeed Velocity. + + +Usage +----- + +Airspeed Velocity manages building and Python virtualenvs (or conda +environments) by itself, unless told otherwise. + +First navigate to the benchmarks subfolder of the repository. + + cd benchmarks + +To run all benchmarks once against the current build of beem:: + + asv run --python=same --quick + +To run all benchmarks more than once against the current build of beem:: + + asv run --python=same + +The following notation (tag followed by ^!) can be used to run only on a +specific tag or commit. (In this case, a python version for the virtualenv +must be provided) + + asv run --python=3.6 --quick v0.19.16^! + +To record the results use: + + asv publish + +And to see the results via a web broweser, run: + + asv preview + +More on how to use ``asv`` can be found in `ASV documentation`_ +Command-line help is available as usual via ``asv --help`` and +``asv run --help``. + +.. _ASV documentation: https://asv.readthedocs.io/ diff --git a/benchmarks/asv.conf.json b/benchmarks/asv.conf.json new file mode 100644 index 0000000000000000000000000000000000000000..07a9ffd6172d18ca2da624c1dbc3ebc5433736f1 --- /dev/null +++ b/benchmarks/asv.conf.json @@ -0,0 +1,84 @@ +{ + // The version of the config file format. Do not change, unless + // you know what you are doing. + "version": 1, + + // The name of the project being benchmarked + "project": "beem", + + // The project's homepage + "project_url": "https://github.com/holgern/beem", + + // The URL or local path of the source code repository for the + // project being benchmarked + //"repo": "https://github.com/holgern/beem.git", + "repo": "..", + + // List of branches to benchmark. If not provided, defaults to "master" + // (for git) or "tip" (for mercurial). + "branches": ["master"], // for git + + // The DVCS being used. If not set, it will be automatically + // determined from "repo" by looking at the protocol in the URL + // (if remote), or by looking for special directories, such as + // ".git" (if local). + "dvcs": "git", + + // The tool to use to create environments. May be "conda", + // "virtualenv" or other value depending on the plugins in use. + // If missing or the empty string, the tool will be automatically + // determined by looking for tools on the PATH environment + // variable. + "environment_type": "conda", + + + // the base URL to show a commit for the project. + "show_commit_url": "http://github.com/holgern/beem/commit/", + + // The Pythons you'd like to test against. If not provided, defaults + // to the current version of Python used to run `asv`. + // "pythons": ["2.7", "3.4"], + + // The matrix of dependencies to test. Each key is the name of a + // package (in PyPI) and the values are version numbers. An empty + // list indicates to just test against the default (latest) + // version. + "matrix": { + "future": [], + "ecdsa": [], + "requests": [], + "websocket-client": [], + "appdirs": [], + "Events": [], + "pylibscrypt": [], + "pycryptodomex": [], + "pytz": [], + "Click": [], + "prettytable": [], + }, + + // The directory (relative to the current directory) that benchmarks are + // stored in. If not provided, defaults to "benchmarks" + "benchmark_dir": "benchmarks", + + // The directory (relative to the current directory) to cache the Python + // environments in. If not provided, defaults to "env" + // "env_dir": "env", + + + // The directory (relative to the current directory) that raw benchmark + // results are stored in. If not provided, defaults to "results". + "results_dir": "results", + + // The directory (relative to the current directory) that the html tree + // should be written to. If not provided, defaults to "html". + "html_dir": "html", + + // The number of characters to retain in the commit hashes. + // "hash_length": 8, + + // `asv` will cache wheels of the recent builds in each + // environment, making them faster to install next time. This is + // number of builds to keep, per environment. + "wheel_cache_size": 2 +} diff --git a/benchmarks/benchmarks/__init__.py b/benchmarks/benchmarks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/benchmarks/benchmarks/bench_account.py b/benchmarks/benchmarks/bench_account.py new file mode 100644 index 0000000000000000000000000000000000000000..549180bfbc7daab830a1f425ac1e4eb34cce5602 --- /dev/null +++ b/benchmarks/benchmarks/bench_account.py @@ -0,0 +1,55 @@ +# This Python file uses the following encoding: utf-8 +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +from builtins import str +from beemgraphenebase.base58 import Base58 +from beemgraphenebase.account import BrainKey, Address, PublicKey, PrivateKey, PasswordKey + + +class Benchmark(object): + goal_time = 1 + + +class Account(Benchmark): + def setup(self): + self.b = BrainKey("COLORER BICORN KASBEKE FAERIE LOCHIA GOMUTI SOVKHOZ Y GERMAL AUNTIE PERFUMY TIME FEATURE GANGAN CELEMIN MATZO") + + def time_B85hexgetb58_btc(self): + format(Base58("02b52e04a0acfe611a4b6963462aca94b6ae02b24e321eda86507661901adb49"), "WIF") + + def time_B85hexgetb58(self): + format(Base58("02b52e04a0acfe611a4b6963462aca94b6ae02b24e321eda86507661901adb49"), "BTS") + + def time_Address(self): + format(Address("BTSFN9r6VYzBK8EKtMewfNbfiGCr56pHDBFi", prefix="BTS"), "BTS") + + def time_PubKey(self): + format(PublicKey("BTS6UtYWWs3rkZGV8JA86qrgkG6tyFksgECefKE1MiH4HkLD8PFGL", prefix="BTS").address, "BTS") + + def time_btsprivkey(self): + format(PrivateKey("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd").address, "BTS") + + def time_btcprivkey(self): + format(PrivateKey("5HvVz6XMx84aC5KaaBbwYrRLvWE46cH6zVnv4827SBPLorg76oq").uncompressed.address, "BTC") + + def time_PublicKey(self): + str(PublicKey("BTS6UtYWWs3rkZGV8JA86qrgkG6tyFksgECefKE1MiH4HkLD8PFGL", prefix="BTS")) + + def time_Privatekey(self): + str(PrivateKey("5HvVz6XMx84aC5KaaBbwYrRLvWE46cH6zVnv4827SBPLorg76oq")) + + def time_BrainKey(self): + str(BrainKey("COLORER BICORN KASBEKE FAERIE LOCHIA GOMUTI SOVKHOZ Y GERMAL AUNTIE PERFUMY TIME FEATURE GANGAN CELEMIN MATZO").get_private()) + + def time_BrainKey_normalize(self): + BrainKey("COLORER BICORN KASBEKE FAERIE LOCHIA GOMUTI SOVKHOZ Y GERMAL AUNTIE PERFUMY TIME FEATURE GANGAN CELEMIN MATZO").get_brainkey() + + def time_BrainKey_sequences(self): + p = self.b.next_sequence().get_private() + + def time_PasswordKey(self): + pwd = "Aang7foN3oz1Ungai2qua5toh3map8ladei1eem2ohsh2shuo8aeji9Thoseo7ah" + format(PasswordKey("xeroc", pwd, "posting").get_public(), "STM") + \ No newline at end of file diff --git a/benchmarks/benchmarks/bench_ecdsa.py b/benchmarks/benchmarks/bench_ecdsa.py new file mode 100644 index 0000000000000000000000000000000000000000..ee6f4385c023baca040d4a60c61ecc03a41c0ffe --- /dev/null +++ b/benchmarks/benchmarks/bench_ecdsa.py @@ -0,0 +1,120 @@ +# This Python file uses the following encoding: utf-8 +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +import hashlib +import ecdsa +from binascii import hexlify, unhexlify +from beemgraphenebase.account import PrivateKey, PublicKey, Address +import beemgraphenebase.ecdsasig as ecda +from beemgraphenebase.py23 import py23_bytes + + +class Benchmark(object): + goal_time = 10 + + +class ECDSA(Benchmark): + def setup(self): + ecda.SECP256K1_MODULE = "ecdsa" + + def time_sign(self): + wif = "5J4KCbg1G3my9b9hCaQXnHSm6vrwW9xQTJS6ZciW2Kek7cCkCEk" + message = '576b2c99564392ed50e36c80654224953fdf8b5259528a1a4342c19be2da9b133c44429ac2be4d5dd588ec28e97015c34db80b7e8d8915e023c2501acd3eafe0' + signature = ecda.sign_message(message, wif) + message = 'foo' + signature = ecda.sign_message(message, wif) + message = 'This is a short Message' + signature = ecda.sign_message(message, wif) + message = '1234567890' + signature = ecda.sign_message(message, wif) + + + def time_verify(self): + message = '576b2c99564392ed50e36c80654224953fdf8b5259528a1a4342c19be2da9b133c44429ac2be4d5dd588ec28e97015c34db80b7e8d8915e023c2501acd3eafe0' + signature = b' S\xef\x14x\x06\xeb\xba\xc5\xf9\x0e\xac\x02pL\xbeLO;\x1d"$\xd7\xfc\x07\xfb\x9c\x08\xc5b^\x1e\xec\x19\xb1y\x11\np\xec(\xc9\xf3\xfd\x1f~\xe3\x99\xe8\xc98]\xd3\x951m${\x82\x0f[(\xa9\x90#' + pubkey = ecda.verify_message(message, signature) + signature = b' W\x83\xe5w\x8f\x07\x19EV\xba\x9d\x90\x9f\xfd \x81&\x0f\xa1L\xa00zK0\x08\xf78/\x9d\x0c\x06JFx[*Z\xfe\xd1F\x8d\x9f \x19\xad\xd9\xc9\xbf\xd3\x1br\xdd\x8e\x8ei\xf8\xd2\xf40\xad\xc6\x9c\xe5' + message = 'foo' + pubkey = ecda.verify_message(message, signature) + signature = b'\x1f9\xb6_\x85\xbdr7\\\xb2N\xfb~\x82\xb7E\x80\xf1M\xa4EP=\x8elJ\x1d[t\xab%v~a\xb7\xdbS\x86;~N\xd2!\xf1k=\xb6tMm-\xf1\xd9\xfc\xf3`\xbf\xd5)\x1b\xb3N\x92u/' + message = 'This is a short Message' + pubkey = ecda.verify_message(message, signature) + message = '1234567890' + signature = b' 7\x82\xe2\xad\xdc\xdb]~\xd6\xa8J\xdc\xa5\xf4\x13<i\xb9\xc0\xdcEc\x10\xd0)t\xc7^\xecw\x05 U\x91\x0f\xa2\xce\x04\xa1\xdb\xb0\nQ\xbd\xafP`\\\x8bb\x99\xcf\xe0;\x01*\xe9D]\xad\xd9l\x1f\x05' + pubkey = ecda.verify_message(message, signature) + + +class Cryptography(Benchmark): + def setup(self): + try: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.hazmat.primitives.asymmetric.utils \ + import decode_dss_signature, encode_dss_signature + from cryptography.exceptions import InvalidSignature + ecda.SECP256K1_MODULE = "cryptography" + except ImportError: + raise NotImplementedError("cryptography not available") + + def time_sign(self): + wif = "5J4KCbg1G3my9b9hCaQXnHSm6vrwW9xQTJS6ZciW2Kek7cCkCEk" + message = '576b2c99564392ed50e36c80654224953fdf8b5259528a1a4342c19be2da9b133c44429ac2be4d5dd588ec28e97015c34db80b7e8d8915e023c2501acd3eafe0' + signature = ecda.sign_message(message, wif) + message = 'foo' + signature = ecda.sign_message(message, wif) + message = 'This is a short Message' + signature = ecda.sign_message(message, wif) + message = '1234567890' + signature = ecda.sign_message(message, wif) + + def time_verify(self): + message = '576b2c99564392ed50e36c80654224953fdf8b5259528a1a4342c19be2da9b133c44429ac2be4d5dd588ec28e97015c34db80b7e8d8915e023c2501acd3eafe0' + signature = b' S\xef\x14x\x06\xeb\xba\xc5\xf9\x0e\xac\x02pL\xbeLO;\x1d"$\xd7\xfc\x07\xfb\x9c\x08\xc5b^\x1e\xec\x19\xb1y\x11\np\xec(\xc9\xf3\xfd\x1f~\xe3\x99\xe8\xc98]\xd3\x951m${\x82\x0f[(\xa9\x90#' + pubkey = ecda.verify_message(message, signature) + signature = b' W\x83\xe5w\x8f\x07\x19EV\xba\x9d\x90\x9f\xfd \x81&\x0f\xa1L\xa00zK0\x08\xf78/\x9d\x0c\x06JFx[*Z\xfe\xd1F\x8d\x9f \x19\xad\xd9\xc9\xbf\xd3\x1br\xdd\x8e\x8ei\xf8\xd2\xf40\xad\xc6\x9c\xe5' + message = 'foo' + pubkey = ecda.verify_message(message, signature) + signature = b'\x1f9\xb6_\x85\xbdr7\\\xb2N\xfb~\x82\xb7E\x80\xf1M\xa4EP=\x8elJ\x1d[t\xab%v~a\xb7\xdbS\x86;~N\xd2!\xf1k=\xb6tMm-\xf1\xd9\xfc\xf3`\xbf\xd5)\x1b\xb3N\x92u/' + message = 'This is a short Message' + pubkey = ecda.verify_message(message, signature) + message = '1234567890' + signature = b' 7\x82\xe2\xad\xdc\xdb]~\xd6\xa8J\xdc\xa5\xf4\x13<i\xb9\xc0\xdcEc\x10\xd0)t\xc7^\xecw\x05 U\x91\x0f\xa2\xce\x04\xa1\xdb\xb0\nQ\xbd\xafP`\\\x8bb\x99\xcf\xe0;\x01*\xe9D]\xad\xd9l\x1f\x05' + pubkey = ecda.verify_message(message, signature) + + +class Secp256k1(Benchmark): + def setup(self): + try: + import secp256k1 + ecda.SECP256K1_MODULE = "secp256k1" + except ImportError: + raise NotImplementedError("secp256k1 not available") + + def time_sign(self): + wif = "5J4KCbg1G3my9b9hCaQXnHSm6vrwW9xQTJS6ZciW2Kek7cCkCEk" + message = '576b2c99564392ed50e36c80654224953fdf8b5259528a1a4342c19be2da9b133c44429ac2be4d5dd588ec28e97015c34db80b7e8d8915e023c2501acd3eafe0' + signature = ecda.sign_message(message, wif) + message = 'foo' + signature = ecda.sign_message(message, wif) + message = 'This is a short Message' + signature = ecda.sign_message(message, wif) + message = '1234567890' + signature = ecda.sign_message(message, wif) + + def time_verify(self): + message = '576b2c99564392ed50e36c80654224953fdf8b5259528a1a4342c19be2da9b133c44429ac2be4d5dd588ec28e97015c34db80b7e8d8915e023c2501acd3eafe0' + signature = b' S\xef\x14x\x06\xeb\xba\xc5\xf9\x0e\xac\x02pL\xbeLO;\x1d"$\xd7\xfc\x07\xfb\x9c\x08\xc5b^\x1e\xec\x19\xb1y\x11\np\xec(\xc9\xf3\xfd\x1f~\xe3\x99\xe8\xc98]\xd3\x951m${\x82\x0f[(\xa9\x90#' + pubkey = ecda.verify_message(message, signature) + signature = b' W\x83\xe5w\x8f\x07\x19EV\xba\x9d\x90\x9f\xfd \x81&\x0f\xa1L\xa00zK0\x08\xf78/\x9d\x0c\x06JFx[*Z\xfe\xd1F\x8d\x9f \x19\xad\xd9\xc9\xbf\xd3\x1br\xdd\x8e\x8ei\xf8\xd2\xf40\xad\xc6\x9c\xe5' + message = 'foo' + pubkey = ecda.verify_message(message, signature) + signature = b'\x1f9\xb6_\x85\xbdr7\\\xb2N\xfb~\x82\xb7E\x80\xf1M\xa4EP=\x8elJ\x1d[t\xab%v~a\xb7\xdbS\x86;~N\xd2!\xf1k=\xb6tMm-\xf1\xd9\xfc\xf3`\xbf\xd5)\x1b\xb3N\x92u/' + message = 'This is a short Message' + pubkey = ecda.verify_message(message, signature) + message = '1234567890' + signature = b' 7\x82\xe2\xad\xdc\xdb]~\xd6\xa8J\xdc\xa5\xf4\x13<i\xb9\xc0\xdcEc\x10\xd0)t\xc7^\xecw\x05 U\x91\x0f\xa2\xce\x04\xa1\xdb\xb0\nQ\xbd\xafP`\\\x8bb\x99\xcf\xe0;\x01*\xe9D]\xad\xd9l\x1f\x05' + pubkey = ecda.verify_message(message, signature) + diff --git a/benchmarks/benchmarks/bench_transaction.py b/benchmarks/benchmarks/bench_transaction.py new file mode 100644 index 0000000000000000000000000000000000000000..4eb2aaff6dae10b4a7b1191ad9999a2be17f3f58 --- /dev/null +++ b/benchmarks/benchmarks/bench_transaction.py @@ -0,0 +1,421 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +from builtins import bytes +from builtins import chr +from builtins import range +from builtins import super +import random +from pprint import pprint +from binascii import hexlify +from collections import OrderedDict + +from beembase import ( + transactions, + memo, + operations, + objects +) +from beembase.objects import Operation +from beembase.signedtransactions import Signed_Transaction +from beemgraphenebase.account import PrivateKey +from beemgraphenebase import account +from beembase.operationids import getOperationNameForId +from beemgraphenebase.py23 import py23_bytes, bytes_types +from beem.amount import Amount +from beem.asset import Asset +from beem.steem import Steem + +class Benchmark(object): + goal_time = 2 + + +class Transaction(Benchmark): + def setup(self): + self.prefix = u"STEEM" + self.default_prefix = u"STM" + self.wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" + self.ref_block_num = 34294 + self.ref_block_prefix = 3707022213 + self.expiration = "2016-04-06T08:29:27" + self.stm = Steem( + offline=True + ) + + def doit(self, printWire=False, ops=None): + if ops is None: + ops = [Operation(self.op)] + tx = Signed_Transaction(ref_block_num=self.ref_block_num, + ref_block_prefix=self.ref_block_prefix, + expiration=self.expiration, + operations=ops) + tx = tx.sign([self.wif], chain=self.prefix) + tx.verify([PrivateKey(self.wif, prefix=u"STM").pubkey], self.prefix) + txWire = hexlify(py23_bytes(tx)).decode("ascii") + + def time_emptyOp(self): + self.doit(ops=[]) + + def time_transfer(self): + self.op = operations.Transfer(**{ + "from": "foo", + "to": "baar", + "amount": Amount("111.110 STEEM", steem_instance=self.stm), + "memo": "Fooo", + "prefix": self.default_prefix + }) + self.doit() + + def time_create_account(self): + self.op = operations.Account_create( + **{ + 'creator': + 'xeroc', + 'fee': + '10.000 STEEM', + 'json_metadata': + '', + 'memo_key': + 'STM6zLNtyFVToBsBZDsgMhgjpwysYVbsQD6YhP3kRkQhANUB4w7Qp', + 'new_account_name': + 'fsafaasf', + 'owner': { + 'account_auths': [], + 'key_auths': [[ + 'STM5jYVokmZHdEpwo5oCG3ES2Ca4VYz' + 'y6tM8pWWkGdgVnwo2mFLFq', 1 + ], [ + 'STM6zLNtyFVToBsBZDsgMhgjpwysYVb' + 'sQD6YhP3kRkQhANUB4w7Qp', 1 + ]], + 'weight_threshold': + 1 + }, + 'active': { + 'account_auths': [], + 'key_auths': [[ + 'STM6pbVDAjRFiw6fkiKYCrkz7PFeL7' + 'XNAfefrsREwg8MKpJ9VYV9x', 1 + ], [ + 'STM6zLNtyFVToBsBZDsgMhgjpwysYV' + 'bsQD6YhP3kRkQhANUB4w7Qp', 1 + ]], + 'weight_threshold': + 1 + }, + 'posting': { + 'account_auths': [], + 'key_auths': [[ + 'STM8CemMDjdUWSV5wKotEimhK6c4d' + 'Y7p2PdzC2qM1HpAP8aLtZfE7', 1 + ], [ + 'STM6zLNtyFVToBsBZDsgMhgjpwys' + 'YVbsQD6YhP3kRkQhANUB4w7Qp', 1 + ], [ + 'STM6pbVDAjRFiw6fkiKYCrkz7PFeL' + '7XNAfefrsREwg8MKpJ9VYV9x', 1 + ]], + 'weight_threshold': + 1 + }, + "prefix": self.default_prefix + }) + + self.doit() + + def time_Transfer_to_vesting(self): + self.op = operations.Transfer_to_vesting(**{ + "from": "foo", + "to": "baar", + "amount": "111.110 STEEM", + "prefix": self.default_prefix + }) + + self.doit() + + def time_withdraw_vesting(self): + self.op = operations.Withdraw_vesting(**{ + "account": "foo", + "vesting_shares": "100 VESTS", + "prefix": self.default_prefix + }) + + self.doit() + + def time_Comment(self): + self.op = operations.Comment( + **{ + "parent_author": "foobara", + "parent_permlink": "foobarb", + "author": "foobarc", + "permlink": "foobard", + "title": "foobare", + "body": "foobarf", + "json_metadata": { + "foo": "bar" + }, + "prefix": self.default_prefix + }) + + self.doit() + + def time_Vote(self): + self.op = operations.Vote( + **{ + "voter": "foobara", + "author": "foobarc", + "permlink": "foobard", + "weight": 1000, + "prefix": self.default_prefix + }) + + self.doit() + + def time_Transfer_to_savings(self): + self.op = operations.Transfer_to_savings( + **{ + "from": "testuser", + "to": "testuser", + "amount": "1.000 STEEM", + "memo": "testmemo", + "prefix": self.default_prefix + }) + + self.doit() + + def time_Transfer_from_savings(self): + self.op = operations.Transfer_from_savings( + **{ + "from": "testuser", + "request_id": 9001, + "to": "testser", + "amount": "100.000 SBD", + "memo": "memohere", + "prefix": self.default_prefix + }) + + self.doit() + + def time_Cancel_transfer_from_savings(self): + self.op = operations.Cancel_transfer_from_savings(**{ + "from": "tesuser", + "request_id": 9001, + "prefix": self.default_prefix + }) + + + self.doit() + + def time_order_create(self): + self.op = operations.Limit_order_create( + **{ + "owner": "", + "orderid": 0, + "amount_to_sell": "0.000 STEEM", + "min_to_receive": "0.000 STEEM", + "fill_or_kill": False, + "expiration": "2016-12-31T23:59:59", + "prefix": self.default_prefix + }) + + self.doit() + + def time_account_update(self): + self.op = operations.Account_update( + **{ + "account": + "streemian", + "posting": { + "weight_threshold": + 1, + "account_auths": [["xeroc", 1], ["fabian", 1]], + "key_auths": [[ + "STM6KChDK2sns9MwugxkoRvPEnyju" + "TxHN5upGsZ1EtanCffqBVVX3", 1 + ], [ + "STM7sw22HqsXbz7D2CmJfmMwt9ri" + "mtk518dRzsR1f8Cgw52dQR1pR", 1 + ]] + }, + "owner": { + "weight_threshold": + 1, + "account_auths": [], + "key_auths": [[ + "STM7sw22HqsXbz7D2CmJfmMwt9r" + "imtk518dRzsR1f8Cgw52dQR1pR", 1 + ], [ + "STM6KChDK2sns9MwugxkoRvPEn" + "yjuTxHN5upGsZ1EtanCffqBVVX3", 1 + ]] + }, + "active": { + "weight_threshold": + 2, + "account_auths": [], + "key_auths": [[ + "STM6KChDK2sns9MwugxkoRvPEnyju" + "TxHN5upGsZ1EtanCffqBVVX3", 1 + ], [ + "STM7sw22HqsXbz7D2CmJfmMwt9ri" + "mtk518dRzsR1f8Cgw52dQR1pR", 1 + ]] + }, + "memo_key": + "STM728uLvStTeAkYJsQefks3FX8yfmpFHp8wXw3RY3kwey2JGDooR", + "json_metadata": + "", + "prefix": self.default_prefix + }) + + self.doit() + + def time_order_cancel(self): + self.op = operations.Limit_order_cancel(**{ + "owner": "", + "orderid": 2141244, + "prefix": self.default_prefix + }) + + self.doit() + + def time_set_route(self): + self.op = operations.Set_withdraw_vesting_route( + **{ + "from_account": "xeroc", + "to_account": "xeroc", + "percent": 1000, + "auto_vest": False, + "prefix": self.default_prefix + }) + + self.doit() + + def time_convert(self): + self.op = operations.Convert(**{ + "owner": "xeroc", + "requestid": 2342343235, + "amount": "100.000 SBD", + "prefix": self.default_prefix + }) + + self.doit() + + def time_utf8tests(self): + self.op = operations.Comment( + **{ + "parent_author": "", + "parent_permlink": "", + "author": "a", + "permlink": "a", + "title": "-", + "body": "".join([chr(i) for i in range(0, 2048)]), + "json_metadata": {}, + "prefix": self.default_prefix + }) + + self.doit() + + def time_feed_publish(self): + self.op = operations.Feed_publish( + **{ + "publisher": "xeroc", + "exchange_rate": { + "base": "1.000 SBD", + "quote": "4.123 STEEM" + }, + "prefix": self.default_prefix + }) + + self.doit() + + def time_delete_comment(self): + self.op = operations.Delete_comment( + **{ + "author": "turbot", + "permlink": "testpost", + "prefix": self.default_prefix + }) + + self.doit() + + def time_witness_update(self): + self.op = operations.Witness_update( + **{ + "owner": + "xeroc", + "url": + "foooobar", + "block_signing_key": + "STM6zLNtyFVToBsBZDsgMhgjpwysYVbsQD6YhP3kRkQhANUB4w7Qp", + "props": { + "account_creation_fee": "10.000 STEEM", + "maximum_block_size": 1111111, + "sbd_interest_rate": 1000 + }, + "fee": + "10.000 STEEM", + "prefix": self.default_prefix + }) + + self.doit() + + def time_witness_vote(self): + self.op = operations.Account_witness_vote(**{ + "account": "xeroc", + "witness": "chainsquad", + "approve": True, + "prefix": self.default_prefix + }) + + self.doit() + + def time_custom_json(self): + self.op = operations.Custom_json( + **{ + "json": [ + "reblog", + OrderedDict( + [ # need an ordered dict to keep order for the test + ("account", "xeroc"), ("author", "chainsquad"), ( + "permlink", "streemian-com-to-open-its-doors-" + "and-offer-a-20-discount") + ]) + ], + "required_auths": [], + "required_posting_auths": ["xeroc"], + "id": + "follow", + "prefix": self.default_prefix + }) + + self.doit() + + def time_comment_options(self): + self.op = operations.Comment_options( + **{ + "author": + "xeroc", + "permlink": + "piston", + "max_accepted_payout": + "1000000.000 SBD", + "percent_steem_dollars": + 10000, + "allow_votes": + True, + "allow_curation_rewards": + True, + "beneficiaries": [{ + "weight": 2000, + "account": "good-karma" + }, { + "weight": 5000, + "account": "null" + }], + "prefix": self.default_prefix + }) + + self.doit() + diff --git a/examples/compare_transactions_speed_with_steem.py b/examples/compare_transactions_speed_with_steem.py new file mode 100644 index 0000000000000000000000000000000000000000..56069635cd6fd7dc49c3b55df153ca48d8b50694 --- /dev/null +++ b/examples/compare_transactions_speed_with_steem.py @@ -0,0 +1,128 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +from builtins import bytes +from builtins import chr +from builtins import range +from builtins import super +import random +from pprint import pprint +from binascii import hexlify +from collections import OrderedDict + +from beembase import ( + transactions, + memo, + operations, + objects +) +from beembase.objects import Operation +from beembase.signedtransactions import Signed_Transaction +from beemgraphenebase.account import PrivateKey +from beemgraphenebase import account +from beembase.operationids import getOperationNameForId +from beemgraphenebase.py23 import py23_bytes, bytes_types +from beem.amount import Amount +from beem.asset import Asset +from beem.steem import Steem +import time + +from steem import Steem as steemSteem +from steembase.account import PrivateKey as steemPrivateKey +from steembase.transactions import SignedTransaction as steemSignedTransaction +from steembase import operations as steemOperations +from timeit import default_timer as timer + + +class BeemTest(object): + + def setup(self): + self.prefix = u"STEEM" + self.default_prefix = u"STM" + self.wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" + self.ref_block_num = 34294 + self.ref_block_prefix = 3707022213 + self.expiration = "2016-04-06T08:29:27" + self.stm = Steem(offline=True) + + def doit(self, printWire=False, ops=None): + ops = [Operation(ops)] + tx = Signed_Transaction(ref_block_num=self.ref_block_num, + ref_block_prefix=self.ref_block_prefix, + expiration=self.expiration, + operations=ops) + start = timer() + tx = tx.sign([self.wif], chain=self.prefix) + end1 = timer() + tx.verify([PrivateKey(self.wif, prefix=u"STM").pubkey], self.prefix) + end2 = timer() + return end2 - end1, end1 - start + + +class SteemTest(object): + + def setup(self): + self.prefix = u"STEEM" + self.default_prefix = u"STM" + self.wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" + self.ref_block_num = 34294 + self.ref_block_prefix = 3707022213 + self.expiration = "2016-04-06T08:29:27" + + def doit(self, printWire=False, ops=None): + ops = [steemOperations.Operation(ops)] + tx = steemSignedTransaction(ref_block_num=self.ref_block_num, + ref_block_prefix=self.ref_block_prefix, + expiration=self.expiration, + operations=ops) + start = timer() + tx = tx.sign([self.wif], chain=self.prefix) + end1 = timer() + tx.verify([steemPrivateKey(self.wif, prefix=u"STM").pubkey], self.prefix) + end2 = timer() + return end2 - end1, end1 - start + + +if __name__ == "__main__": + steem_test = SteemTest() + beem_test = BeemTest() + steem_test.setup() + beem_test.setup() + steem_times = [] + beem_times = [] + loops = 50 + for i in range(0, loops): + print(i) + opSteem = steemOperations.Transfer(**{ + "from": "foo", + "to": "baar", + "amount": "111.110 STEEM", + "memo": "Fooo" + }) + opBeem = operations.Transfer(**{ + "from": "foo", + "to": "baar", + "amount": Amount("111.110 STEEM", steem_instance=Steem(offline=True)), + "memo": "Fooo" + }) + + t_s, t_v = steem_test.doit(ops=opSteem) + steem_times.append([t_s, t_v]) + + t_s, t_v = beem_test.doit(ops=opBeem) + beem_times.append([t_s, t_v]) + + steem_dt = [0, 0] + beem_dt = [0, 0] + for i in range(0, loops): + steem_dt[0] += steem_times[i][0] + steem_dt[1] += steem_times[i][1] + beem_dt[0] += beem_times[i][0] + beem_dt[1] += beem_times[i][1] + print("steem vs beem:\n") + print("steem: sign: %.2f s, verification %.2f s" % (steem_dt[0] / loops, steem_dt[1] / loops)) + print("beem: sign: %.2f s, verification %.2f s" % (beem_dt[0] / loops, beem_dt[1] / loops)) + print("------------------------------------") + print("beem is %.2f %% (sign) and %.2f %% (verify) faster than steem" % + (steem_dt[0] / beem_dt[0] * 100, steem_dt[1] / beem_dt[1] * 100)) diff --git a/setup.py b/setup.py index 489c711b16f125341e0d7f0e9aaaee6a5d82d6d2..899b046ba00d742bd747310fe7194f91cd1e0cb5 100755 --- a/setup.py +++ b/setup.py @@ -16,9 +16,9 @@ except LookupError: ascii = codecs.lookup('ascii') codecs.register(lambda name, enc=ascii: {True: enc}.get(name == 'mbcs')) -VERSION = '0.19.16' +VERSION = '0.19.17' -tests_require = ['mock >= 2.0.0', 'pytest', 'pytest-mock', 'parameterized'] +tests_require = ['mock >= 2.0.0', 'pytest', 'pytest-mock', 'parameterized', 'secp256k1'] requires = [ "future", @@ -27,7 +27,7 @@ requires = [ "websocket-client", "appdirs", "Events", - "scrypt", + "pylibscrypt", "pycryptodomex", "pytz", "Click", diff --git a/tests/beembase/test_memo.py b/tests/beembase/test_memo.py index 39f763e71c447aced87a4d19118e0defdef6b8eb..bc86bab7b95f872052b58a39c34b5c23baf0ad55 100644 --- a/tests/beembase/test_memo.py +++ b/tests/beembase/test_memo.py @@ -7,9 +7,11 @@ from builtins import bytes from builtins import chr from builtins import range import unittest +import hashlib +from binascii import hexlify, unhexlify import os from pprint import pprint -from beemgraphenebase.account import BrainKey, Address, PublicKey, PrivateKey +from beemgraphenebase.account import BrainKey, Address, PublicKey, PrivateKey, PasswordKey from beembase.memo import ( get_shared_secret, _pad, @@ -110,6 +112,21 @@ class Testcases(unittest.TestCase): memo["plain"], prefix="GPH") self.assertEqual(memo["message"], enc) + def test_encrypt_decrypt(self): + base58 = u'#HU6pdQ4Hh8cFrDVooekRPVZu4BdrhAe9RxrWrei2CwfAApAPdM4PT5mSV9cV3tTuWKotYQF6suyM4JHFBZz4pcwyezPzuZ2na7uwhRcLqFotsqxWRBpaXkNks2QCnYLS8' + text = u'#爱' + nonce = u'1462976530069648' + wif = str(PasswordKey("", "", role="", prefix="STM").get_private_key()) + private_key = PrivateKey(wif=wif, prefix="STM") + public_key = private_key.pubkey + cypertext = encode_memo(private_key, + public_key, + nonce, + text, prefix="STM") + self.assertEqual(cypertext, base58) + plaintext = decode_memo(private_key, cypertext) + self.assertEqual(plaintext, text) + def test_shared_secret(self): for s in test_shared_secrets: priv = PrivateKey(s[0]) diff --git a/tests/beembase/test_transactions.py b/tests/beembase/test_transactions.py index 356c44f404d7c15afecaafa050c2e4bc1b0718af..f9b166832cfd41cf907657e305c682edf704ffb8 100644 --- a/tests/beembase/test_transactions.py +++ b/tests/beembase/test_transactions.py @@ -5,13 +5,19 @@ from __future__ import unicode_literals from builtins import bytes from builtins import chr from builtins import range +from builtins import super +import random +import unittest +from pprint import pprint +from binascii import hexlify +from collections import OrderedDict + from beembase import ( transactions, memo, operations, objects ) -from collections import OrderedDict from beembase.objects import Operation from beembase.signedtransactions import Signed_Transaction from beemgraphenebase.account import PrivateKey @@ -20,10 +26,8 @@ from beembase.operationids import getOperationNameForId from beemgraphenebase.py23 import py23_bytes, bytes_types from beem.amount import Amount from beem.asset import Asset -import random -import unittest -from pprint import pprint -from binascii import hexlify +from beem.steem import Steem + TEST_AGAINST_CLI_WALLET = False @@ -36,6 +40,12 @@ expiration = "2016-04-06T08:29:27" class Testcases(unittest.TestCase): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.stm = Steem( + offline=True + ) def doit(self, printWire=False, ops=None): if ops is None: @@ -72,7 +82,7 @@ class Testcases(unittest.TestCase): self.op = operations.Transfer(**{ "from": "foo", "to": "baar", - "amount": Amount("111.110 STEEM"), + "amount": Amount("111.110 STEEM", steem_instance=self.stm), "memo": "Fooo", "prefix": default_prefix }) diff --git a/tests/beemgraphene/test_ecdsa.py b/tests/beemgraphene/test_ecdsa.py index bc0cad0a9b8f6dcc5dc796ace83309e4079dd277..a1b4b55fc3893ecfdea1fa6e214380220685f818 100644 --- a/tests/beemgraphene/test_ecdsa.py +++ b/tests/beemgraphene/test_ecdsa.py @@ -4,11 +4,14 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals import pytest +from parameterized import parameterized import unittest -from beemgraphenebase.ecdsasig import ( - sign_message, - verify_message -) +import hashlib +import ecdsa +from binascii import hexlify, unhexlify +from beemgraphenebase.account import PrivateKey, PublicKey, Address +import beemgraphenebase.ecdsasig as ecda +from beemgraphenebase.py23 import py23_bytes wif = "5J4KCbg1G3my9b9hCaQXnHSm6vrwW9xQTJS6ZciW2Kek7cCkCEk" @@ -18,10 +21,99 @@ class Testcases(unittest.TestCase): # Ignore warning: # https://www.reddit.com/r/joinmarket/comments/5crhfh/userwarning_implicit_cast_from_char_to_a/ - @pytest.mark.filterwarnings() - def test_sign_message(self): - signature = sign_message("Foobar", wif) - self.assertTrue(verify_message("Foobar", signature)) + # @pytest.mark.filterwarnings() + @parameterized.expand([ + ("cryptography"), + ("secp256k1"), + ("ecdsa") + ]) + def test_sign_message(self, module): + if module == "cryptography": + try: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.hazmat.primitives.asymmetric.utils \ + import decode_dss_signature, encode_dss_signature + from cryptography.exceptions import InvalidSignature + ecda.SECP256K1_MODULE = "cryptography" + except ImportError: + return + elif module == "secp256k1": + try: + import secp256k1 + ecda.SECP256K1_MODULE = "secp256k1" + except ImportError: + return + else: + ecda.SECP256K1_MODULE = module + pub_key = py23_bytes(repr(PrivateKey(wif).pubkey), "latin") + signature = ecda.sign_message("Foobar", wif) + pub_key_sig = ecda.verify_message("Foobar", signature) + self.assertEqual(hexlify(pub_key_sig), pub_key) + + @parameterized.expand([ + ("cryptography"), + ("secp256k1"), + ]) + def test_sign_message_cross(self, module): + if module == "cryptography": + try: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.hazmat.primitives.asymmetric.utils \ + import decode_dss_signature, encode_dss_signature + from cryptography.exceptions import InvalidSignature + ecda.SECP256K1_MODULE = "cryptography" + except ImportError: + return + elif module == "secp256k1": + try: + import secp256k1 + ecda.SECP256K1_MODULE = "secp256k1" + except ImportError: + return + pub_key = py23_bytes(repr(PrivateKey(wif).pubkey), "latin") + signature = ecda.sign_message("Foobar", wif) + ecda.SECP256K1_MODULE = "ecdsa" + pub_key_sig = ecda.verify_message("Foobar", signature) + self.assertEqual(hexlify(pub_key_sig), pub_key) + signature = ecda.sign_message("Foobar", wif) + ecda.SECP256K1_MODULE = module + pub_key_sig = ecda.verify_message("Foobar", signature) + self.assertEqual(hexlify(pub_key_sig), pub_key) + + @parameterized.expand([ + ("cryptography"), + ("secp256k1"), + ("ecdsa"), + ]) + def test_wrong_signature(self, module): + if module == "cryptography": + try: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.hazmat.primitives.asymmetric.utils \ + import decode_dss_signature, encode_dss_signature + from cryptography.exceptions import InvalidSignature + ecda.SECP256K1_MODULE = "cryptography" + except ImportError: + return + elif module == "secp256k1": + try: + import secp256k1 + ecda.SECP256K1_MODULE = "secp256k1" + except ImportError: + return + pub_key = py23_bytes(repr(PrivateKey(wif).pubkey), "latin") + ecda.SECP256K1_MODULE = module + signature = ecda.sign_message("Foobar", wif) + pub_key_sig = ecda.verify_message("Foobar", signature) + self.assertEqual(hexlify(pub_key_sig), pub_key) + pub_key_sig2 = ecda.verify_message("Foobar2", signature) + self.assertTrue(hexlify(pub_key_sig2) != pub_key) if __name__ == '__main__': diff --git a/tests/beemgraphene/test_key_format.py b/tests/beemgraphene/test_key_format.py new file mode 100644 index 0000000000000000000000000000000000000000..810a5ccc61d6b72b785b9361db30d2ede57fcb46 --- /dev/null +++ b/tests/beemgraphene/test_key_format.py @@ -0,0 +1,71 @@ +# This Python file uses the following encoding: utf-8 +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +import unittest +from beemgraphenebase.account import PrivateKey, PublicKey, Address +from beemgraphenebase.bip38 import encrypt, decrypt + + +key = { + "public_key": "STM7jDPoMwyjVH5obFmqzFNp4Ffp7G2nvC7FKFkrMBpo7Sy4uq5Mj", + "private_key": "20991828d456b389d0768ed7fb69bf26b9bb87208dd699ef49f10481c20d3e18", + "private_key_WIF_format": "5J4eFhjREJA7hKG6KcvHofHMXyGQZCDpQE463PAaKo9xXY6UDPq", + "bts_address": "STM8DvGQqzbgCR5FHiNsFf8kotEXr8VKD3mR", + "pts_address": "Po3mqkgMzBL4F1VXJArwQxeWf3fWEpxUf3", + "encrypted_private_key": "5e1ae410919c450dce1c476ae3ed3e5fe779ad248081d85b3dcf2888e698744d0a4b60efb7e854453bec3f6883bcbd1d", + "blockchain_address": "4f3a560442a05e4fbb257e8dc5859b736306bace", + "Uncompressed_BTC": "STMLAFmEtM8as1mbmjVcj5dphLdPguXquimn", + "Compressed_BTC": "STMANNTSEaUviJgWLzJBersPmyFZBY4jJETY", + "Uncompressed_PTS": "STMEgj7RM6FBwSoccGaESJLC3Mi18785bM3T", + "Compressed_PTS": "STMD5rYtofD6D4UHJH6mo953P5wpBfMhdMEi", +} + + +class Testcases(unittest.TestCase): + def test_public_from_private(self): + private_key = PrivateKey(key["private_key"]) + public_key = private_key.pubkey + self.assertEqual(key["public_key"], str(public_key)) + + def test_short_address(self): + public_key = PublicKey(key["public_key"]) + self.assertEqual(key["bts_address"], str(public_key.address)) + + def test_blockchain_address(self): + public_key = PublicKey(key["public_key"]) + self.assertEqual(key["blockchain_address"], repr(public_key.address)) + + def test_import_export(self): + public_key = PublicKey(key["public_key"]) + self.assertEqual(key["public_key"], str(public_key)) + + def test_to_wif(self): + private_key = PrivateKey(key["private_key"]) + self.assertEqual(key["private_key_WIF_format"], str(private_key)) + + def test_calc_pub_key(self): + private_key = PrivateKey(key["private_key"]) + public_key = private_key.pubkey + self.assertEqual(key["bts_address"], str(public_key.address)) + + def test_btc_uncompressed(self): + public_key = PublicKey(key["public_key"]) + address = Address(address=None, pubkey=public_key.unCompressed()) + self.assertEqual(key["Uncompressed_BTC"], format(address.derive256address_with_version(0), "STM")) + + def test_btc_compressed(self): + public_key = PublicKey(key["public_key"]) + address = Address(address=None, pubkey=repr(public_key)) + self.assertEqual(key["Compressed_BTC"], format(address.derive256address_with_version(0), "STM")) + + def test_pts_uncompressed(self): + public_key = PublicKey(key["public_key"]) + address = Address(address=None, pubkey=public_key.unCompressed()) + self.assertEqual(key["Uncompressed_PTS"], format(address.derive256address_with_version(56), "STM")) + + def test_pts_compressed(self): + public_key = PublicKey(key["public_key"]) + address = Address(address=None, pubkey=repr(public_key)) + self.assertEqual(key["Compressed_PTS"], format(address.derive256address_with_version(56), "STM")) diff --git a/tox.ini b/tox.ini index cfe93926665216bf6ee090cfe962957438098a2b..9b1cb7a57e2e64cf8e7099ce2e758915340213e6 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,8 @@ deps = pytest pytest-mock parameterized + cryptography + secp256k1 commands = pytest @@ -17,6 +19,8 @@ deps = pytest pytest-mock parameterized + cryptography + secp256k1 coverage commands = coverage run --parallel-mode -m pytest {posargs}