From ea5f3cad6f85229ec0d57475e40e3005cb60e5fa Mon Sep 17 00:00:00 2001
From: Holger Nahrstaedt <holger@nahrstaedt.de>
Date: Thu, 15 Mar 2018 22:55:31 +0100
Subject: [PATCH] ecdsa improved with cryptography

Account
* Improved some function, so that account works limited on GOLOS
Blockchain
* sync changes from python-bitshares
Instance
* sync changes from python-bitshares
Steem
* Improved some functions so that steem works on GOLOS
Chains
* Added GOLOS
graphenebase/Account
* derive256address_with_version added
ecsdasig
* Huge speed improvement (200%) by using cryptography
benchmark added
* benchmark for beemgraphenebase/account funcions
* benchmark for ecdsa
* benchmark for sign/verification of all transaction
Examples
* compare sign/verificaiton speed with steem
Unit tests
* use memo unittests from steem-js
* add key_format unittests from steem-js
* Improved the ecdsa tests
---
 .circleci/config.yml                          |   2 +-
 README.rst                                    |  28 +-
 beem/account.py                               |  36 +-
 beem/blockchain.py                            |  49 +-
 beem/instance.py                              |  13 +-
 beem/steem.py                                 |  72 +--
 beem/version.py                               |   2 +-
 beemapi/version.py                            |   2 +-
 beembase/chains.py                            |  14 +-
 beembase/version.py                           |   2 +-
 beemgrapheneapi/version.py                    |   2 +-
 beemgraphenebase/account.py                   |  18 +-
 beemgraphenebase/ecdsasig.py                  | 128 ++++--
 beemgraphenebase/version.py                   |   2 +-
 benchmarks/README.rst                         |  46 ++
 benchmarks/asv.conf.json                      |  84 ++++
 benchmarks/benchmarks/__init__.py             |   0
 benchmarks/benchmarks/bench_account.py        |  55 +++
 benchmarks/benchmarks/bench_ecdsa.py          | 120 +++++
 benchmarks/benchmarks/bench_transaction.py    | 421 ++++++++++++++++++
 .../compare_transactions_speed_with_steem.py  | 128 ++++++
 setup.py                                      |   6 +-
 tests/beembase/test_memo.py                   |  19 +-
 tests/beembase/test_transactions.py           |  22 +-
 tests/beemgraphene/test_ecdsa.py              | 108 ++++-
 tests/beemgraphene/test_key_format.py         |  71 +++
 tox.ini                                       |   4 +
 27 files changed, 1354 insertions(+), 100 deletions(-)
 create mode 100644 benchmarks/README.rst
 create mode 100644 benchmarks/asv.conf.json
 create mode 100644 benchmarks/benchmarks/__init__.py
 create mode 100644 benchmarks/benchmarks/bench_account.py
 create mode 100644 benchmarks/benchmarks/bench_ecdsa.py
 create mode 100644 benchmarks/benchmarks/bench_transaction.py
 create mode 100644 examples/compare_transactions_speed_with_steem.py
 create mode 100644 tests/beemgraphene/test_key_format.py

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 87ced900..12d1bb08 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 b35478e5..8a3db881 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&amp;utm_medium=referral&amp;utm_content=holgern/beem&amp;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 b7dbf95d..3188f30b 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 45eabdff..13ab6bc8 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 a9ed61e1..6cb1e139 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 67a6b3b8..52754332 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 3f796c97..005fb8f7 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 3f796c97..005fb8f7 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 9c39646e..8bd21a12 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 3f796c97..005fb8f7 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 3f796c97..005fb8f7 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 9e825df7..5127f847 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 43eb952a..c9446dd1 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 3f796c97..005fb8f7 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 00000000..25bc75c2
--- /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 00000000..07a9ffd6
--- /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 00000000..e69de29b
diff --git a/benchmarks/benchmarks/bench_account.py b/benchmarks/benchmarks/bench_account.py
new file mode 100644
index 00000000..549180bf
--- /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 00000000..ee6f4385
--- /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 00000000..4eb2aaff
--- /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 00000000..56069635
--- /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 489c711b..899b046b 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 39f763e7..bc86bab7 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 356c44f4..f9b16683 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 bc0cad0a..a1b4b55f 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 00000000..810a5ccc
--- /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 cfe93926..9b1cb7a5 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}
-- 
GitLab