From b46ed3a9e6407b2b6f345596e4add609340c147f Mon Sep 17 00:00:00 2001 From: nolash Date: Wed, 17 Mar 2021 15:34:51 +0100 Subject: [PATCH] Remove eth_keys, web3 dependencies --- config/config.ini | 2 +- crypto_dev_signer/__init__.py | 4 -- crypto_dev_signer/eth/signer/defaultsigner.py | 43 +++++++++++++------ crypto_dev_signer/keystore/__init__.py | 10 ++--- crypto_dev_signer/keystore/dict.py | 19 +++++--- crypto_dev_signer/keystore/interface.py | 28 ++++++++---- crypto_dev_signer/keystore/postgres.py | 17 ++++---- crypto_dev_signer/runnable/keyfile.py | 8 ++++ crypto_dev_signer/runnable/signer.py | 19 +++++--- requirements.txt | 9 ++-- setup.py | 2 +- test/test_helper.py | 28 ++++++------ test/test_keystore_dict.py | 2 +- test/test_keystore_reference.py | 2 +- test/test_sign.py | 4 +- 15 files changed, 122 insertions(+), 75 deletions(-) create mode 100644 crypto_dev_signer/runnable/keyfile.py diff --git a/config/config.ini b/config/config.ini index e81e470..779a2bf 100644 --- a/config/config.ini +++ b/config/config.ini @@ -1,3 +1,3 @@ [signer] secret = deadbeef -socket_path = /tmp/crypto-dev-signer/jsonrpc.ipc +socket_path = ipc:///tmp/crypto-dev-signer/jsonrpc.ipc diff --git a/crypto_dev_signer/__init__.py b/crypto_dev_signer/__init__.py index 5efe4c8..e69de29 100644 --- a/crypto_dev_signer/__init__.py +++ b/crypto_dev_signer/__init__.py @@ -1,4 +0,0 @@ -import crypto_dev_signer.eth.signer -import crypto_dev_signer.eth.web3ext -import crypto_dev_signer.eth.transaction -import crypto_dev_signer.common diff --git a/crypto_dev_signer/eth/signer/defaultsigner.py b/crypto_dev_signer/eth/signer/defaultsigner.py index 566dc05..5bdbeb0 100644 --- a/crypto_dev_signer/eth/signer/defaultsigner.py +++ b/crypto_dev_signer/eth/signer/defaultsigner.py @@ -1,11 +1,15 @@ +# standard imports import logging + +# external imports import sha3 +import coincurve +#from eth_keys import KeyAPI +#from eth_keys.backends import NativeECCBackend -from eth_keys import KeyAPI -from eth_keys.backends import NativeECCBackend - -keys = KeyAPI(NativeECCBackend) -logg = logging.getLogger(__name__) +#keys = KeyAPI(NativeECCBackend) +#logg = logging.getLogger(__name__) +logg = logging.getLogger() class Signer: @@ -31,9 +35,9 @@ class ReferenceSigner(Signer): s = tx.rlp_serialize() h = sha3.keccak_256() h.update(s) - g = h.digest() - k = keys.PrivateKey(self.keyGetter.get(tx.sender, password)) - z = keys.ecdsa_sign(message_hash=g, private_key=k) + message_to_sign = h.digest() + z = self.sign(tx.sender, message_to_sign, password) + vnum = int.from_bytes(tx.v, 'big') v = (vnum * 2) + 35 + z[64] byts = ((v.bit_length()-1)/8)+1 @@ -55,17 +59,30 @@ class ReferenceSigner(Signer): def signEthereumMessage(self, address, message, password=None): - #msg = b'\x19Ethereum Signed Message:\n{}{}'.format(len(message), message) - k = keys.PrivateKey(self.keyGetter.get(address, password)) + + #k = keys.PrivateKey(self.keyGetter.get(address, password)) #z = keys.ecdsa_sign(message_hash=g, private_key=k) - z = None if type(message).__name__ == 'str': logg.debug('signing message in "str" format: {}'.format(message)) - z = k.sign_msg(bytes.fromhex(message)) + #z = k.sign_msg(bytes.fromhex(message)) + message = bytes.fromhex(message) elif type(message).__name__ == 'bytes': logg.debug('signing message in "bytes" format: {}'.format(message.hex())) - z = k.sign_msg(message) + #z = k.sign_msg(message) else: + logg.debug('unhandled format {}'.format(type(message).__name__)) raise ValueError('message must be type str or bytes, received {}'.format(type(message).__name__)) + + ethereumed_message_header = b'\x19' + 'Ethereum Signed Message:\n{}'.format(len(message)).encode('utf-8') + h = sha3.keccak_256() + h.update(ethereumed_message_header + message) + message_to_sign = h.digest() + + z = self.sign(address, message_to_sign, password) return z + + def sign(self, address, message, password=None): + pk = coincurve.PrivateKey(secret=self.keyGetter.get(address, password)) + z = pk.sign(hasher=None, message=message) + return z diff --git a/crypto_dev_signer/keystore/__init__.py b/crypto_dev_signer/keystore/__init__.py index 15332dd..743986a 100644 --- a/crypto_dev_signer/keystore/__init__.py +++ b/crypto_dev_signer/keystore/__init__.py @@ -1,8 +1,8 @@ # third-party imports -from eth_keys import KeyAPI -from eth_keys.backends import NativeECCBackend +#from eth_keys import KeyAPI +#from eth_keys.backends import NativeECCBackend -keyapi = KeyAPI(NativeECCBackend) +#keyapi = KeyAPI(NativeECCBackend) -from .postgres import ReferenceKeystore -from .dict import DictKeystore +#from .postgres import ReferenceKeystore +#from .dict import DictKeystore diff --git a/crypto_dev_signer/keystore/dict.py b/crypto_dev_signer/keystore/dict.py index 59be2ad..49a5bcf 100644 --- a/crypto_dev_signer/keystore/dict.py +++ b/crypto_dev_signer/keystore/dict.py @@ -1,11 +1,14 @@ # standard imports import logging +# external imports +from hexathon import strip_0x + # local imports -from . import keyapi +#from . import keyapi from .interface import Keystore from crypto_dev_signer.error import UnknownAccountError -from crypto_dev_signer.common import strip_hex_prefix +from crypto_dev_signer.encoding import private_key_to_address logg = logging.getLogger() @@ -25,9 +28,13 @@ class DictKeystore(Keystore): raise UnknownAccountError(address) + def list(self): + return list(self.keys.keys()) + + def import_key(self, pk, password=None): - pubk = keyapi.private_key_to_public_key(pk) - address_hex = pubk.to_checksum_address() - address_hex_clean = strip_hex_prefix(address_hex) - self.keys[address_hex_clean] = pk.to_bytes() + address_hex = private_key_to_address(pk) + address_hex_clean = strip_0x(address_hex) + self.keys[address_hex_clean] = pk.secret + logg.debug('added key {}'.format(address_hex)) return address_hex diff --git a/crypto_dev_signer/keystore/interface.py b/crypto_dev_signer/keystore/interface.py index 6754ecf..17e73c2 100644 --- a/crypto_dev_signer/keystore/interface.py +++ b/crypto_dev_signer/keystore/interface.py @@ -1,12 +1,13 @@ # standard imports import os - -# third-party imports -import eth_keyfile +import json +import logging # local imports -from . import keyapi +from crypto_dev_signer.keystore import keyfile +import coincurve +logg = logging.getLogger(__name__) class Keystore: @@ -14,13 +15,17 @@ class Keystore: raise NotImplementedError + def list(self): + raise NotImplementedError + + def new(self, password=None): b = os.urandom(32) return self.import_raw_key(b, password) def import_raw_key(self, b, password=None): - pk = keyapi.PrivateKey(b) + pk = coincurve.PrivateKey(secret=b) return self.import_key(pk, password) @@ -30,9 +35,16 @@ class Keystore: def import_keystore_data(self, keystore_content, password=''): #private_key = w3.eth.account.decrypt(keystore_content, password) - private_key = eth_keyfile.decode_keyfile_json(keystore_content, password.encode('utf-8')) + if type(keystore_content).__name__ == 'str': + keystore_content = json.loads(keystore_content) + elif type(keystore_content).__name__ == 'bytes': + logg.debug('bytes {}'.format(keystore_content)) + keystore_content = json.loads(keystore_content.decode('utf-8')) + private_key = keyfile.from_dict(keystore_content, password.encode('utf-8')) return self.import_raw_key(private_key, password) def import_keystore_file(self, keystore_file, password=''): - keystore_content = eth_keyfile.load_keyfile(keystore_file) - return self.import_keystore_data(keystore_content, password) + private_key = keyfile.from_file(keystore_file) + #return self.import_keystore_data(keystore_content, password) + return self.import_raw_key(private_key, password) + #return kes diff --git a/crypto_dev_signer/keystore/postgres.py b/crypto_dev_signer/keystore/postgres.py index 10fc777..e080ef6 100644 --- a/crypto_dev_signer/keystore/postgres.py +++ b/crypto_dev_signer/keystore/postgres.py @@ -2,7 +2,7 @@ import logging import base64 -# third-party imports +# external imports from cryptography.fernet import Fernet #import psycopg2 #from psycopg2 import sql @@ -10,12 +10,13 @@ from cryptography.fernet import Fernet from sqlalchemy import create_engine, text from sqlalchemy.orm import sessionmaker import sha3 +from hexathon import strip_0x # local imports from .interface import Keystore -from crypto_dev_signer.common import strip_hex_prefix -from . import keyapi +#from . import keyapi from crypto_dev_signer.error import UnknownAccountError +from crypto_dev_signer.encoding import private_key_to_address logg = logging.getLogger(__file__) @@ -53,7 +54,7 @@ class ReferenceKeystore(Keystore): def get(self, address, password=None): - safe_address = strip_hex_prefix(address) + safe_address = strip_0x(address) s = text('SELECT key_ciphertext FROM ethereum WHERE wallet_address_hex = :a') r = self.db_session.execute(s, { 'a': safe_address, @@ -69,10 +70,10 @@ class ReferenceKeystore(Keystore): def import_key(self, pk, password=None): - pubk = keyapi.private_key_to_public_key(pk) - address_hex = pubk.to_checksum_address() - address_hex_clean = strip_hex_prefix(address_hex) - c = self._encrypt(pk.to_bytes(), password) + address_hex = private_key_to_address(pk) + address_hex_clean = strip_0x(address_hex) + + c = self._encrypt(pk.secret, password) s = text('INSERT INTO ethereum (wallet_address_hex, key_ciphertext) VALUES (:a, :c)') #%s, %s)') self.db_session.execute(s, { 'a': address_hex_clean, diff --git a/crypto_dev_signer/runnable/keyfile.py b/crypto_dev_signer/runnable/keyfile.py new file mode 100644 index 0000000..dedc62a --- /dev/null +++ b/crypto_dev_signer/runnable/keyfile.py @@ -0,0 +1,8 @@ +# standard imports +import logging +import sys + +# local imports +from crypto_dev_signer.keystore.keyfile import parse_file + +print(from_file(sys.argv[1]).hex()) diff --git a/crypto_dev_signer/runnable/signer.py b/crypto_dev_signer/runnable/signer.py index fa241d1..5bb0d29 100755 --- a/crypto_dev_signer/runnable/signer.py +++ b/crypto_dev_signer/runnable/signer.py @@ -1,4 +1,5 @@ # standard imports +import re import os import sys import stat @@ -6,6 +7,7 @@ import socket import json import logging import argparse +from urllib.parse import urlparse # third-party imports import confini @@ -51,9 +53,9 @@ logg.debug('config loaded from {}:\n{}'.format(config_dir, config)) if args.i: chainId = args.i if args.s: - socket_path = args.s + socket_url = urlparse(args.s) elif config.get('SIGNER_SOCKET_PATH'): - socket_path = config.get('SIGNER_SOCKET_PATH') + socket_url = urlparse(config.get('SIGNER_SOCKET_PATH')) # connect to database @@ -68,6 +70,8 @@ dsn = 'postgresql://{}:{}@{}:{}/{}'.format( logg.info('using dsn {}'.format(dsn)) logg.info('using socket {}'.format(socket_path)) +re_http = r'^http' +re_unix = r'^ipc' class MissingSecretError(BaseException): @@ -114,7 +118,7 @@ def personal_sign_transaction(p): # TODO: temporary workaround for platform, since personal_signTransaction is missing from web3.py def eth_signTransaction(tx): - return personal_sign_transaction([tx, '']) + return personal_sign_transaction([tx[0], '']) def eth_sign(p): @@ -249,13 +253,16 @@ def main(): arg = json.loads(sys.argv[1]) except: logg.info('no json rpc command detected, starting socket server') - socket_spec = socket_path.split(':') - if len(socket_spec) == 2: + scheme = 'ipc' + if socket_url.scheme != '': + scheme = socket_url.scheme + if re.match(re_http, socket_url.scheme): + socket_spec = socket_url.netloc.split(':') host = socket_spec[0] port = int(socket_spec[1]) start_server_tcp((host, port)) else: - start_server_unix(socket_path) + start_server_unix(socket_url.path) sys.exit(0) (rpc_id, response) = process_input(arg) diff --git a/requirements.txt b/requirements.txt index 0f2b2ff..a1006d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ -web3==5.12.2 psycopg2==2.8.6 -cryptography==3.2.1 -eth-keys==0.3.3 +#cryptography==3.2.1 pysha3==1.0.2 -rlp==2.0.1 +simple-rlp==0.1.2 json-rpc==1.13.0 confini~=0.3.6a1 sqlalchemy==1.3.20 +coincurve==15.0.0 +pycrypto==2.6.1 +hexathon==0.0.1a3 diff --git a/setup.py b/setup.py index 9851b8b..33462b8 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ f.close() setup( name="crypto-dev-signer", - version="0.4.13rc4", + version="0.4.13rc6", description="A signer and keystore daemon and library for cryptocurrency software development", author="Louis Holbrook", author_email="dev@holbrook.no", diff --git a/test/test_helper.py b/test/test_helper.py index 9495f88..51a4439 100644 --- a/test/test_helper.py +++ b/test/test_helper.py @@ -3,14 +3,12 @@ import unittest import logging import os -# third-party imports -import web3 - # local imports -from crypto_dev_signer.keystore import DictKeystore +from crypto_dev_signer.keystore.dict import DictKeystore from crypto_dev_signer.eth.signer import ReferenceSigner from crypto_dev_signer.helper import TxExecutor -from crypto_dev_signer.eth.helper import EthTxExecutor +#from crypto_dev_signer.eth.helper import EthTxExecutor +from crypto_dev_signer.encoding import to_checksum_address logging.basicConfig(level=logging.DEBUG) logg = logging.getLogger() @@ -42,7 +40,7 @@ class MockEthTxBackend: def builder_two(self, tx): tx['value'] = 10243 - tx['to'] = web3.Web3.toChecksumAddress('0x' + os.urandom(20).hex()) + tx['to'] = to_checksum_address('0x' + os.urandom(20).hex()) tx['data'] = '' if tx.get('feePrice') != None: tx['gasPrice'] = tx['feePrice'] @@ -78,15 +76,15 @@ class TestHelper(unittest.TestCase): executor.sign_and_send([backend.builder_two]) - def test_eth_helper(self): - backend = MockEthTxBackend() - w3 = web3.Web3(web3.Web3.HTTPProvider('http://localhost:8545')) - executor = EthTxExecutor(w3, self.address_hex, self.signer, 1337) - - tx_ish = {'from': self.address_hex} - #executor.sign_and_send([backend.builder, backend.builder_two]) - with self.assertRaises(ValueError): - executor.sign_and_send([backend.builder_two]) +# def test_eth_helper(self): +# backend = MockEthTxBackend() +# w3 = web3.Web3(web3.Web3.HTTPProvider('http://localhost:8545')) +# executor = EthTxExecutor(w3, self.address_hex, self.signer, 1337) +# +# tx_ish = {'from': self.address_hex} +# #executor.sign_and_send([backend.builder, backend.builder_two]) +# with self.assertRaises(ValueError): +# executor.sign_and_send([backend.builder_two]) if __name__ == '__main__': diff --git a/test/test_keystore_dict.py b/test/test_keystore_dict.py index 0a7d38c..44c9e7d 100644 --- a/test/test_keystore_dict.py +++ b/test/test_keystore_dict.py @@ -7,7 +7,7 @@ import base64 import os # local imports -from crypto_dev_signer.keystore import DictKeystore +from crypto_dev_signer.keystore.dict import DictKeystore from crypto_dev_signer.error import UnknownAccountError from crypto_dev_signer.eth.signer import ReferenceSigner diff --git a/test/test_keystore_reference.py b/test/test_keystore_reference.py index 53d6078..06b382c 100644 --- a/test/test_keystore_reference.py +++ b/test/test_keystore_reference.py @@ -12,7 +12,7 @@ from psycopg2 import sql from cryptography.fernet import Fernet, InvalidToken # local imports -from crypto_dev_signer.keystore import ReferenceKeystore +from crypto_dev_signer.keystore.postgres import ReferenceKeystore from crypto_dev_signer.error import UnknownAccountError logging.basicConfig(level=logging.DEBUG) diff --git a/test/test_sign.py b/test/test_sign.py index 3785a56..af60030 100644 --- a/test/test_sign.py +++ b/test/test_sign.py @@ -68,14 +68,14 @@ class TestSign(unittest.TestCase): t = EIP155Transaction(tx_ints, 0) self.assertRegex(t.__class__.__name__, "Transaction") s = t.serialize() - self.assertEqual('{}'.format(s), "{'nonce': '', 'gasPrice': '0x04a817c800', 'gas': '0x5208', 'to': '0x3535353535353535353535353535353535353535', 'value': '0x03e8', 'data': '0xdeadbeef', 'v': '0x01', 'r': '', 's': ''}") + self.assertEqual('{}'.format(s), "{'nonce': '0x00', 'gasPrice': '0x04a817c800', 'gas': '0x5208', 'to': '0x3535353535353535353535353535353535353535', 'value': '0x03e8', 'data': '0xdeadbeef', 'v': '0x01', 'r': '', 's': ''}") r = t.rlp_serialize() self.assertEqual(r.hex(), 'ea808504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef018080') t = EIP155Transaction(tx_hexs, 0) self.assertRegex(t.__class__.__name__, "Transaction") s = t.serialize() - self.assertEqual('{}'.format(s), "{'nonce': '', 'gasPrice': '0x04a817c800', 'gas': '0x5208', 'to': '0x3535353535353535353535353535353535353535', 'value': '0x03e8', 'data': '0xdeadbeef', 'v': '0x01', 'r': '', 's': ''}") + self.assertEqual('{}'.format(s), "{'nonce': '0x00', 'gasPrice': '0x04a817c800', 'gas': '0x5208', 'to': '0x3535353535353535353535353535353535353535', 'value': '0x03e8', 'data': '0xdeadbeef', 'v': '0x01', 'r': '', 's': ''}") r = t.rlp_serialize() self.assertEqual(r.hex(), 'ea808504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef018080')