WIP refactor eth to separate module, rename to funga
This commit is contained in:
parent
7f8a3628ce
commit
cb974126cd
@ -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
|
@ -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)
|
@ -1 +0,0 @@
|
||||
from crypto_dev_signer.eth.signer.defaultsigner import ReferenceSigner, Signer
|
@ -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)
|
1
funga/eth/helper/__init__.py
Normal file
1
funga/eth/helper/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .tx import EthTxExecutor
|
58
funga/eth/helper/tx.py
Normal file
58
funga/eth/helper/tx.py
Normal 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
|
@ -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 = {}
|
@ -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)
|
@ -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)
|
1
funga/eth/signer/__init__.py
Normal file
1
funga/eth/signer/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from funga.eth.signer.defaultsigner import EIP155Signer
|
@ -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):
|
29
funga/eth/web3ext/__init__.py
Normal file
29
funga/eth/web3ext/__init__.py
Normal 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
|
116
funga/eth/web3ext/middleware.py
Normal file
116
funga/eth/web3ext/middleware.py
Normal 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
46
funga/keystore.py
Normal 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
8
funga/signer.py
Normal file
@ -0,0 +1,8 @@
|
||||
class Signer:
|
||||
|
||||
def __init__(self, keyGetter):
|
||||
self.keyGetter = keyGetter
|
||||
|
||||
|
||||
def sign_transaction(self, tx, password=None):
|
||||
return NotImplementedError
|
22
setup.py
22
setup.py
@ -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',
|
||||
)
|
||||
|
@ -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)))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user