Remove eth_keys, web3 dependencies

This commit is contained in:
nolash 2021-03-17 15:34:51 +01:00
parent 3fb5745f98
commit b46ed3a9e6
Signed by: lash
GPG Key ID: 21D2E7BB88C2A746
15 changed files with 122 additions and 75 deletions

View File

@ -1,3 +1,3 @@
[signer] [signer]
secret = deadbeef secret = deadbeef
socket_path = /tmp/crypto-dev-signer/jsonrpc.ipc socket_path = ipc:///tmp/crypto-dev-signer/jsonrpc.ipc

View File

@ -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

View File

@ -1,11 +1,15 @@
# standard imports
import logging import logging
# external imports
import sha3 import sha3
import coincurve
#from eth_keys import KeyAPI
#from eth_keys.backends import NativeECCBackend
from eth_keys import KeyAPI #keys = KeyAPI(NativeECCBackend)
from eth_keys.backends import NativeECCBackend #logg = logging.getLogger(__name__)
logg = logging.getLogger()
keys = KeyAPI(NativeECCBackend)
logg = logging.getLogger(__name__)
class Signer: class Signer:
@ -31,9 +35,9 @@ class ReferenceSigner(Signer):
s = tx.rlp_serialize() s = tx.rlp_serialize()
h = sha3.keccak_256() h = sha3.keccak_256()
h.update(s) h.update(s)
g = h.digest() message_to_sign = h.digest()
k = keys.PrivateKey(self.keyGetter.get(tx.sender, password)) z = self.sign(tx.sender, message_to_sign, password)
z = keys.ecdsa_sign(message_hash=g, private_key=k)
vnum = int.from_bytes(tx.v, 'big') vnum = int.from_bytes(tx.v, 'big')
v = (vnum * 2) + 35 + z[64] v = (vnum * 2) + 35 + z[64]
byts = ((v.bit_length()-1)/8)+1 byts = ((v.bit_length()-1)/8)+1
@ -55,17 +59,30 @@ class ReferenceSigner(Signer):
def signEthereumMessage(self, address, message, password=None): 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 = keys.ecdsa_sign(message_hash=g, private_key=k)
z = None
if type(message).__name__ == 'str': if type(message).__name__ == 'str':
logg.debug('signing message in "str" format: {}'.format(message)) 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': elif type(message).__name__ == 'bytes':
logg.debug('signing message in "bytes" format: {}'.format(message.hex())) logg.debug('signing message in "bytes" format: {}'.format(message.hex()))
z = k.sign_msg(message) #z = k.sign_msg(message)
else: else:
logg.debug('unhandled format {}'.format(type(message).__name__))
raise ValueError('message must be type str or bytes, received {}'.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 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

View File

@ -1,8 +1,8 @@
# third-party imports # third-party imports
from eth_keys import KeyAPI #from eth_keys import KeyAPI
from eth_keys.backends import NativeECCBackend #from eth_keys.backends import NativeECCBackend
keyapi = KeyAPI(NativeECCBackend) #keyapi = KeyAPI(NativeECCBackend)
from .postgres import ReferenceKeystore #from .postgres import ReferenceKeystore
from .dict import DictKeystore #from .dict import DictKeystore

View File

@ -1,11 +1,14 @@
# standard imports # standard imports
import logging import logging
# external imports
from hexathon import strip_0x
# local imports # local imports
from . import keyapi #from . import keyapi
from .interface import Keystore from .interface import Keystore
from crypto_dev_signer.error import UnknownAccountError 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() logg = logging.getLogger()
@ -25,9 +28,13 @@ class DictKeystore(Keystore):
raise UnknownAccountError(address) raise UnknownAccountError(address)
def list(self):
return list(self.keys.keys())
def import_key(self, pk, password=None): def import_key(self, pk, password=None):
pubk = keyapi.private_key_to_public_key(pk) address_hex = private_key_to_address(pk)
address_hex = pubk.to_checksum_address() address_hex_clean = strip_0x(address_hex)
address_hex_clean = strip_hex_prefix(address_hex) self.keys[address_hex_clean] = pk.secret
self.keys[address_hex_clean] = pk.to_bytes() logg.debug('added key {}'.format(address_hex))
return address_hex return address_hex

View File

@ -1,12 +1,13 @@
# standard imports # standard imports
import os import os
import json
# third-party imports import logging
import eth_keyfile
# local imports # local imports
from . import keyapi from crypto_dev_signer.keystore import keyfile
import coincurve
logg = logging.getLogger(__name__)
class Keystore: class Keystore:
@ -14,13 +15,17 @@ class Keystore:
raise NotImplementedError raise NotImplementedError
def list(self):
raise NotImplementedError
def new(self, password=None): def new(self, password=None):
b = os.urandom(32) b = os.urandom(32)
return self.import_raw_key(b, password) return self.import_raw_key(b, password)
def import_raw_key(self, b, password=None): def import_raw_key(self, b, password=None):
pk = keyapi.PrivateKey(b) pk = coincurve.PrivateKey(secret=b)
return self.import_key(pk, password) return self.import_key(pk, password)
@ -30,9 +35,16 @@ class Keystore:
def import_keystore_data(self, keystore_content, password=''): def import_keystore_data(self, keystore_content, password=''):
#private_key = w3.eth.account.decrypt(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) return self.import_raw_key(private_key, password)
def import_keystore_file(self, keystore_file, password=''): def import_keystore_file(self, keystore_file, password=''):
keystore_content = eth_keyfile.load_keyfile(keystore_file) private_key = keyfile.from_file(keystore_file)
return self.import_keystore_data(keystore_content, password) #return self.import_keystore_data(keystore_content, password)
return self.import_raw_key(private_key, password)
#return kes

View File

@ -2,7 +2,7 @@
import logging import logging
import base64 import base64
# third-party imports # external imports
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
#import psycopg2 #import psycopg2
#from psycopg2 import sql #from psycopg2 import sql
@ -10,12 +10,13 @@ from cryptography.fernet import Fernet
from sqlalchemy import create_engine, text from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
import sha3 import sha3
from hexathon import strip_0x
# local imports # local imports
from .interface import Keystore 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.error import UnknownAccountError
from crypto_dev_signer.encoding import private_key_to_address
logg = logging.getLogger(__file__) logg = logging.getLogger(__file__)
@ -53,7 +54,7 @@ class ReferenceKeystore(Keystore):
def get(self, address, password=None): 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') s = text('SELECT key_ciphertext FROM ethereum WHERE wallet_address_hex = :a')
r = self.db_session.execute(s, { r = self.db_session.execute(s, {
'a': safe_address, 'a': safe_address,
@ -69,10 +70,10 @@ class ReferenceKeystore(Keystore):
def import_key(self, pk, password=None): def import_key(self, pk, password=None):
pubk = keyapi.private_key_to_public_key(pk) address_hex = private_key_to_address(pk)
address_hex = pubk.to_checksum_address() address_hex_clean = strip_0x(address_hex)
address_hex_clean = strip_hex_prefix(address_hex)
c = self._encrypt(pk.to_bytes(), password) c = self._encrypt(pk.secret, password)
s = text('INSERT INTO ethereum (wallet_address_hex, key_ciphertext) VALUES (:a, :c)') #%s, %s)') s = text('INSERT INTO ethereum (wallet_address_hex, key_ciphertext) VALUES (:a, :c)') #%s, %s)')
self.db_session.execute(s, { self.db_session.execute(s, {
'a': address_hex_clean, 'a': address_hex_clean,

View File

@ -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())

View File

@ -1,4 +1,5 @@
# standard imports # standard imports
import re
import os import os
import sys import sys
import stat import stat
@ -6,6 +7,7 @@ import socket
import json import json
import logging import logging
import argparse import argparse
from urllib.parse import urlparse
# third-party imports # third-party imports
import confini import confini
@ -51,9 +53,9 @@ logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
if args.i: if args.i:
chainId = args.i chainId = args.i
if args.s: if args.s:
socket_path = args.s socket_url = urlparse(args.s)
elif config.get('SIGNER_SOCKET_PATH'): elif config.get('SIGNER_SOCKET_PATH'):
socket_path = config.get('SIGNER_SOCKET_PATH') socket_url = urlparse(config.get('SIGNER_SOCKET_PATH'))
# connect to database # connect to database
@ -68,6 +70,8 @@ dsn = 'postgresql://{}:{}@{}:{}/{}'.format(
logg.info('using dsn {}'.format(dsn)) logg.info('using dsn {}'.format(dsn))
logg.info('using socket {}'.format(socket_path)) logg.info('using socket {}'.format(socket_path))
re_http = r'^http'
re_unix = r'^ipc'
class MissingSecretError(BaseException): 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 # TODO: temporary workaround for platform, since personal_signTransaction is missing from web3.py
def eth_signTransaction(tx): def eth_signTransaction(tx):
return personal_sign_transaction([tx, '']) return personal_sign_transaction([tx[0], ''])
def eth_sign(p): def eth_sign(p):
@ -249,13 +253,16 @@ def main():
arg = json.loads(sys.argv[1]) arg = json.loads(sys.argv[1])
except: except:
logg.info('no json rpc command detected, starting socket server') logg.info('no json rpc command detected, starting socket server')
socket_spec = socket_path.split(':') scheme = 'ipc'
if len(socket_spec) == 2: 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] host = socket_spec[0]
port = int(socket_spec[1]) port = int(socket_spec[1])
start_server_tcp((host, port)) start_server_tcp((host, port))
else: else:
start_server_unix(socket_path) start_server_unix(socket_url.path)
sys.exit(0) sys.exit(0)
(rpc_id, response) = process_input(arg) (rpc_id, response) = process_input(arg)

View File

@ -1,9 +1,10 @@
web3==5.12.2
psycopg2==2.8.6 psycopg2==2.8.6
cryptography==3.2.1 #cryptography==3.2.1
eth-keys==0.3.3
pysha3==1.0.2 pysha3==1.0.2
rlp==2.0.1 simple-rlp==0.1.2
json-rpc==1.13.0 json-rpc==1.13.0
confini~=0.3.6a1 confini~=0.3.6a1
sqlalchemy==1.3.20 sqlalchemy==1.3.20
coincurve==15.0.0
pycrypto==2.6.1
hexathon==0.0.1a3

View File

@ -24,7 +24,7 @@ f.close()
setup( setup(
name="crypto-dev-signer", name="crypto-dev-signer",
version="0.4.13rc4", version="0.4.13rc6",
description="A signer and keystore daemon and library for cryptocurrency software development", description="A signer and keystore daemon and library for cryptocurrency software development",
author="Louis Holbrook", author="Louis Holbrook",
author_email="dev@holbrook.no", author_email="dev@holbrook.no",

View File

@ -3,14 +3,12 @@ import unittest
import logging import logging
import os import os
# third-party imports
import web3
# local imports # 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.eth.signer import ReferenceSigner
from crypto_dev_signer.helper import TxExecutor 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) logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger() logg = logging.getLogger()
@ -42,7 +40,7 @@ class MockEthTxBackend:
def builder_two(self, tx): def builder_two(self, tx):
tx['value'] = 10243 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'] = '' tx['data'] = ''
if tx.get('feePrice') != None: if tx.get('feePrice') != None:
tx['gasPrice'] = tx['feePrice'] tx['gasPrice'] = tx['feePrice']
@ -78,15 +76,15 @@ class TestHelper(unittest.TestCase):
executor.sign_and_send([backend.builder_two]) executor.sign_and_send([backend.builder_two])
def test_eth_helper(self): # def test_eth_helper(self):
backend = MockEthTxBackend() # backend = MockEthTxBackend()
w3 = web3.Web3(web3.Web3.HTTPProvider('http://localhost:8545')) # w3 = web3.Web3(web3.Web3.HTTPProvider('http://localhost:8545'))
executor = EthTxExecutor(w3, self.address_hex, self.signer, 1337) # executor = EthTxExecutor(w3, self.address_hex, self.signer, 1337)
#
tx_ish = {'from': self.address_hex} # tx_ish = {'from': self.address_hex}
#executor.sign_and_send([backend.builder, backend.builder_two]) # #executor.sign_and_send([backend.builder, backend.builder_two])
with self.assertRaises(ValueError): # with self.assertRaises(ValueError):
executor.sign_and_send([backend.builder_two]) # executor.sign_and_send([backend.builder_two])
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -7,7 +7,7 @@ import base64
import os import os
# local imports # 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.error import UnknownAccountError
from crypto_dev_signer.eth.signer import ReferenceSigner from crypto_dev_signer.eth.signer import ReferenceSigner

View File

@ -12,7 +12,7 @@ from psycopg2 import sql
from cryptography.fernet import Fernet, InvalidToken from cryptography.fernet import Fernet, InvalidToken
# local imports # local imports
from crypto_dev_signer.keystore import ReferenceKeystore from crypto_dev_signer.keystore.postgres import ReferenceKeystore
from crypto_dev_signer.error import UnknownAccountError from crypto_dev_signer.error import UnknownAccountError
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)

View File

@ -68,14 +68,14 @@ class TestSign(unittest.TestCase):
t = EIP155Transaction(tx_ints, 0) t = EIP155Transaction(tx_ints, 0)
self.assertRegex(t.__class__.__name__, "Transaction") self.assertRegex(t.__class__.__name__, "Transaction")
s = t.serialize() 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() r = t.rlp_serialize()
self.assertEqual(r.hex(), 'ea808504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef018080') self.assertEqual(r.hex(), 'ea808504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef018080')
t = EIP155Transaction(tx_hexs, 0) t = EIP155Transaction(tx_hexs, 0)
self.assertRegex(t.__class__.__name__, "Transaction") self.assertRegex(t.__class__.__name__, "Transaction")
s = t.serialize() 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() r = t.rlp_serialize()
self.assertEqual(r.hex(), 'ea808504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef018080') self.assertEqual(r.hex(), 'ea808504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef018080')