WIP refactor eth to separate module, rename to funga

This commit is contained in:
nolash 2021-10-10 09:41:56 +02:00
parent 7f8a3628ce
commit cb974126cd
Signed by: lash
GPG Key ID: 21D2E7BB88C2A746
30 changed files with 324 additions and 65 deletions

View File

@ -1,11 +0,0 @@
def strip_hex_prefix(hx):
if hx[:2] == '0x':
return hx[2:]
return hx
def add_hex_prefix(hx):
if len(hx) < 2:
return hx
if hx[:2] != '0x':
return '0x' + hx
return hx

View File

@ -1,8 +0,0 @@
ethereum_recid_modifier = 35
def chain_id_to_v(chain_id, signature):
v = signature[64]
return (chain_id * 2) + ethereum_recid_modifier + v
def chainv_to_v(chain_id, v):
return v - ethereum_recid_modifier - (chain_id * 2)

View File

@ -1 +0,0 @@
from crypto_dev_signer.eth.signer.defaultsigner import ReferenceSigner, Signer

View File

@ -53,7 +53,7 @@ def is_checksum_address(address_hex):
hx = to_checksum(address_hex)
except ValueError:
return False
return hx == address_hex
return hx == strip_0x(address_hex)
def to_checksum_address(address_hex):
@ -65,7 +65,8 @@ def to_checksum_address(address_hex):
h.update(address_hex.encode('utf-8'))
z = h.digest()
checksum_address_hex = '0x'
#checksum_address_hex = '0x'
checksum_address_hex = ''
for (i, c) in enumerate(address_hex):
if c in '1234567890':
@ -79,3 +80,12 @@ def to_checksum_address(address_hex):
return checksum_address_hex
to_checksum = to_checksum_address
ethereum_recid_modifier = 35
def chain_id_to_v(chain_id, signature):
v = signature[64]
return (chain_id * 2) + ethereum_recid_modifier + v
def chainv_to_v(chain_id, v):
return v - ethereum_recid_modifier - (chain_id * 2)

View File

@ -0,0 +1 @@
from .tx import EthTxExecutor

58
funga/eth/helper/tx.py Normal file
View File

@ -0,0 +1,58 @@
# standard imports
import logging
# local imports
from crypto_dev_signer.helper import TxExecutor
from crypto_dev_signer.error import NetworkError
logg = logging.getLogger()
logging.getLogger('web3').setLevel(logging.CRITICAL)
logging.getLogger('urllib3').setLevel(logging.CRITICAL)
class EthTxExecutor(TxExecutor):
def __init__(self, w3, sender, signer, chain_id, verifier=None, block=False):
self.w3 = w3
nonce = self.w3.eth.getTransactionCount(sender, 'pending')
super(EthTxExecutor, self).__init__(sender, signer, self.translator, self.dispatcher, self.reporter, nonce, chain_id, self.fee_helper, self.fee_price_helper, verifier, block)
def fee_helper(self, tx):
estimate = self.w3.eth.estimateGas(tx)
if estimate < 21000:
estimate = 21000
logg.debug('estimate {} {}'.format(tx, estimate))
return estimate
def fee_price_helper(self):
return self.w3.eth.gasPrice
def dispatcher(self, tx):
error_object = None
try:
tx_hash = self.w3.eth.sendRawTransaction(tx)
except ValueError as e:
error_object = e.args[0]
logg.error('node could not intepret rlp {}'.format(tx))
if error_object != None:
raise NetworkError(error_object)
return tx_hash
def reporter(self, tx):
return self.w3.eth.getTransactionReceipt(tx)
def translator(self, tx):
if tx.get('feePrice') != None:
tx['gasPrice'] = tx['feePrice']
del tx['feePrice']
if tx.get('feeUnits') != None:
tx['gas'] = tx['feeUnits']
del tx['feeUnits']
return tx

View File

@ -9,14 +9,14 @@ from hexathon import (
# local imports
#from . import keyapi
from .interface import Keystore
from crypto_dev_signer.error import UnknownAccountError
from crypto_dev_signer.encoding import private_key_to_address
from funga.error import UnknownAccountError
from .interface import EthKeystore
from funga.eth.encoding import private_key_to_address
logg = logging.getLogger(__name__)
class DictKeystore(Keystore):
class DictKeystore(EthKeystore):
def __init__(self):
self.keys = {}

View File

@ -4,23 +4,28 @@ import json
import logging
# local imports
from crypto_dev_signer.keystore import keyfile
from crypto_dev_signer.encoding import private_key_from_bytes
from funga.keystore import Keystore
from funga.eth.keystore import keyfile
from funga.eth.encoding import private_key_from_bytes
logg = logging.getLogger(__name__)
class Keystore:
def get(self, address, password=None):
raise NotImplementedError
def native_keygen(self):
return os.urandom(32)
def list(self):
raise NotImplementedError
class EthKeystore(Keystore):
def __init__(self, private_key_generator=native_keygen):
super(Keystore, self).__init__(
private_key_generator=private_key_generator,
private_key_parser=private_key_from_bytes,
keystore_parser=keyfile.from_some,
)
def new(self, password=None):
b = os.urandom(32)
return self.import_raw_key(b, password)
@ -42,6 +47,7 @@ class Keystore:
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=''):
private_key = keyfile.from_file(keystore_file, password)
return self.import_raw_key(private_key)

View File

@ -12,7 +12,7 @@ from Crypto.Util import Counter
import sha3
# local imports
from crypto_dev_signer.encoding import private_key_to_address
from funga.eth.encoding import private_key_to_address
logg = logging.getLogger(__name__)
@ -141,3 +141,18 @@ def from_file(filepath, passphrase=''):
f.close()
return from_dict(o, passphrase)
def from_some(v, passphrase=''):
if isinstance(v, bytes):
v = v.decode('utf-8')
if isinstance(v, str):
try:
return from_file(v, passphrase)
except Exception:
logg.debug('keyfile parse as file fail')
pass
v = json.loads(v)
return from_dict(v, passphrase)

View File

@ -0,0 +1 @@
from funga.eth.signer.defaultsigner import EIP155Signer

View File

@ -7,27 +7,16 @@ import coincurve
from hexathon import int_to_minbytes
# local imports
from crypto_dev_signer.eth.encoding import chain_id_to_v
from funga.signer import Signer
from funga.eth.encoding import chain_id_to_v
logg = logging.getLogger(__name__)
class Signer:
class EIP155Signer(Signer):
def __init__(self, keyGetter):
self.keyGetter = keyGetter
def sign_transaction(self, tx, password=None):
return NotImplementedError
class ReferenceSigner(Signer):
def __init__(self, keyGetter):
super(ReferenceSigner, self).__init__(keyGetter)
super(EIP155Signer, self).__init__(keyGetter)
def sign_transaction(self, tx, password=None):

View File

@ -0,0 +1,29 @@
import logging
import re
from web3 import Web3 as Web3super
from web3 import WebsocketProvider, HTTPProvider
from .middleware import PlatformMiddleware
re_websocket = re.compile('^wss?://')
re_http = re.compile('^https?://')
logg = logging.getLogger(__file__)
def create_middleware(ipcpath):
PlatformMiddleware.ipcaddr = ipcpath
return PlatformMiddleware
# overrides the original Web3 constructor
#def Web3(blockchain_provider='ws://localhost:8546', ipcpath=None):
def Web3(provider, ipcpath=None):
w3 = Web3super(provider)
if ipcpath != None:
logg.info('using signer middleware with ipc {}'.format(ipcpath))
w3.middleware_onion.add(create_middleware(ipcpath))
w3.eth.personal = w3.geth.personal
return w3

View File

@ -0,0 +1,116 @@
# standard imports
import logging
import re
import socket
import uuid
import json
logg = logging.getLogger(__file__)
def jsonrpc_request(method, params):
uu = uuid.uuid4()
return {
"jsonrpc": "2.0",
"id": str(uu),
"method": method,
"params": params,
}
class PlatformMiddleware:
# id for the request is not available, meaning we cannot easily short-circuit
# hack workaround
id_seq = -1
re_personal = re.compile('^personal_.*')
ipcaddr = None
def __init__(self, make_request, w3):
self.w3 = w3
self.make_request = make_request
if self.ipcaddr == None:
raise AttributeError('ipcaddr not set')
# TODO: understand what format input params come in
# single entry input gives a tuple on params, wtf...
# dict input comes as [{}] and fails if not passed on as an array
@staticmethod
def _translate_params(params):
#if params.__class__.__name__ == 'tuple':
# r = []
# for p in params:
# r.append(p)
# return r
if params.__class__.__name__ == 'list' and len(params) > 0:
return params[0]
return params
# TODO: DRY
def __call__(self, method, suspect_params):
self.id_seq += 1
logg.debug('in middleware method {} params {} ipcpath {}'.format(method, suspect_params, self.ipcaddr))
if self.re_personal.match(method) != None:
params = PlatformMiddleware._translate_params(suspect_params)
# multiple providers is removed in web3.py 5.12.0
# https://github.com/ethereum/web3.py/issues/1701
# thus we need a workaround to use the same web3 instance
s = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM, proto=0)
ipc_provider_workaround = s.connect(self.ipcaddr)
logg.info('redirecting method {} params {} original params {}'.format(method, params, suspect_params))
o = jsonrpc_request(method, params[0])
j = json.dumps(o)
logg.debug('send {}'.format(j))
s.send(j.encode('utf-8'))
r = s.recv(4096)
s.close()
logg.debug('got recv {}'.format(str(r)))
jr = json.loads(r)
jr['id'] = self.id_seq
#return str(json.dumps(jr))
return jr
elif method == 'eth_signTransaction':
params = PlatformMiddleware._translate_params(suspect_params)
s = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM, proto=0)
ipc_provider_workaround = s.connect(self.ipcaddr)
logg.info('redirecting method {} params {} original params {}'.format(method, params, suspect_params))
o = jsonrpc_request(method, params[0])
j = json.dumps(o)
logg.debug('send {}'.format(j))
s.send(j.encode('utf-8'))
r = s.recv(4096)
s.close()
logg.debug('got recv {}'.format(str(r)))
jr = json.loads(r)
jr['id'] = self.id_seq
#return str(json.dumps(jr))
return jr
elif method == 'eth_sign':
params = PlatformMiddleware._translate_params(suspect_params)
s = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM, proto=0)
ipc_provider_workaround = s.connect(self.ipcaddr)
logg.info('redirecting method {} params {} original params {}'.format(method, params, suspect_params))
o = jsonrpc_request(method, params)
j = json.dumps(o)
logg.debug('send {}'.format(j))
s.send(j.encode('utf-8'))
r = s.recv(4096)
s.close()
logg.debug('got recv {}'.format(str(r)))
jr = json.loads(r)
jr['id'] = self.id_seq
return jr
r = self.make_request(method, suspect_params)
return r

46
funga/keystore.py Normal file
View File

@ -0,0 +1,46 @@
# standard imports
import os
import json
import logging
logg = logging.getLogger(__name__)
class Keystore:
def __init__(self, private_key_generator, private_key_parser, keystore_parser):
self.private_key_generator = private_key_generator
self.private_key_parser = private_key_parser
self.keystore_parser = keystore_parser
def get(self, address, password=None):
raise NotImplementedError
def list(self):
raise NotImplementedError
def new(self, password=None):
self.private_key_generator(password=password)
def import_raw_key(self, b, password=None):
pk = self.private_key_parser(b)
return self.import_key(pk, password)
def import_key(self, pk, password=None):
raise NotImplementedError
def import_keystore_data(self, keystore_content, password=''):
private_key = self.keystore_parser(keystore_content, password.encode('utf-8'))
return self.import_raw_key(private_key, password=password)
def import_keystore_file(self, keystore_file, password=''):
private_key = self.keystore_parser(keystore_file, password)
return self.import_raw_key(private_key, password=password)

8
funga/signer.py Normal file
View File

@ -0,0 +1,8 @@
class Signer:
def __init__(self, keyGetter):
self.keyGetter = keyGetter
def sign_transaction(self, tx, password=None):
return NotImplementedError

View File

@ -32,18 +32,18 @@ while True:
f.close()
setup(
name="crypto-dev-signer",
version="0.4.15a7",
name="funga",
version="0.5.1a1",
description="A signer and keystore daemon and library for cryptocurrency software development",
author="Louis Holbrook",
author_email="dev@holbrook.no",
packages=[
'crypto_dev_signer.eth.signer',
'crypto_dev_signer.eth',
'crypto_dev_signer.cli',
'crypto_dev_signer.keystore',
'crypto_dev_signer.runnable',
'crypto_dev_signer',
'funga.eth.signer',
'funga.eth',
'funga.cli',
'funga.keystore',
'funga.runnable',
'funga',
],
install_requires=requirements,
extras_require={
@ -54,9 +54,9 @@ setup(
long_description_content_type='text/markdown',
entry_points = {
'console_scripts': [
'crypto-dev-daemon=crypto_dev_signer.runnable.signer:main',
'eth-keyfile=crypto_dev_signer.runnable.keyfile:main',
'crypto-dev-daemon=funga.runnable.signer:main',
'eth-keyfile=funga.runnable.keyfile:main',
],
},
url='https://gitlab.com/chaintool/crypto-dev-signer',
url='https://gitlab.com/chaintool/funga',
)

View File

@ -13,9 +13,9 @@ from hexathon import (
)
# local imports
from crypto_dev_signer.keystore.dict import DictKeystore
from crypto_dev_signer.error import UnknownAccountError
from crypto_dev_signer.eth.signer import ReferenceSigner
from funga.error import UnknownAccountError
from funga.eth.keystore.dict import DictKeystore
from funga.eth.signer import EIP155Signer
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
@ -53,7 +53,7 @@ class TestDict(unittest.TestCase):
def test_sign_message(self):
s = ReferenceSigner(self.db)
s = EIP155Signer(self.db)
z = s.sign_ethereum_message(strip_0x(self.address_hex), b'foo')
logg.debug('zzz {}'.format(str(z)))