funga/crypto_dev_signer/runnable/signer.py

278 lines
7.8 KiB
Python
Raw Normal View History

2020-10-17 02:44:25 +02:00
# standard imports
2021-03-17 15:34:51 +01:00
import re
2020-10-17 02:44:25 +02:00
import os
import sys
import stat
2020-08-08 11:33:15 +02:00
import socket
import json
import logging
2020-10-17 02:44:25 +02:00
import argparse
2021-03-17 15:34:51 +01:00
from urllib.parse import urlparse
2020-08-08 11:33:15 +02:00
# external imports
2020-10-17 02:44:25 +02:00
import confini
2020-08-08 11:33:15 +02:00
from jsonrpc.exceptions import *
from hexathon import add_0x
2020-08-08 11:33:15 +02:00
2020-10-17 02:44:25 +02:00
# local imports
2020-08-08 11:33:15 +02:00
from crypto_dev_signer.eth.signer import ReferenceSigner
from crypto_dev_signer.eth.transaction import EIP155Transaction
2021-03-25 19:14:55 +01:00
from crypto_dev_signer.keystore.reference import ReferenceKeystore
2020-12-25 09:21:09 +01:00
from crypto_dev_signer.error import UnknownAccountError
2020-08-08 11:33:15 +02:00
2021-01-25 18:22:06 +01:00
logging.basicConfig(level=logging.WARNING)
2020-08-08 11:33:15 +02:00
logg = logging.getLogger()
2021-01-25 18:22:06 +01:00
config_dir = '.'
2020-10-17 02:44:25 +02:00
2020-08-08 11:33:15 +02:00
db = None
signer = None
2021-02-28 08:39:15 +01:00
session = None
2020-08-08 11:33:15 +02:00
chainId = 8995
2020-10-17 02:44:25 +02:00
socket_path = '/run/crypto-dev-signer/jsonrpc.ipc'
argparser = argparse.ArgumentParser()
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
2020-10-17 02:44:25 +02:00
argparser.add_argument('-i', type=int, help='default chain id for EIP155')
argparser.add_argument('-s', type=str, help='socket path')
argparser.add_argument('-v', action='store_true', help='be verbose')
argparser.add_argument('-vv', action='store_true', help='be more verbose')
args = argparser.parse_args()
if args.vv:
logging.getLogger().setLevel(logging.DEBUG)
elif args.v:
logging.getLogger().setLevel(logging.INFO)
config = confini.Config(args.c, args.env_prefix)
config.process()
config.censor('PASSWORD', 'DATABASE')
config.censor('SECRET', 'SIGNER')
logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
2020-10-17 02:44:25 +02:00
if args.i:
chainId = args.i
if args.s:
2021-03-17 15:34:51 +01:00
socket_url = urlparse(args.s)
2020-10-17 02:44:25 +02:00
elif config.get('SIGNER_SOCKET_PATH'):
2021-03-17 15:34:51 +01:00
socket_url = urlparse(config.get('SIGNER_SOCKET_PATH'))
2020-08-08 11:33:15 +02:00
# connect to database
dsn = 'postgresql://{}:{}@{}:{}/{}'.format(
config.get('DATABASE_USER'),
config.get('DATABASE_PASSWORD'),
config.get('DATABASE_HOST'),
config.get('DATABASE_PORT'),
config.get('DATABASE_NAME'),
)
2020-08-08 11:33:15 +02:00
logg.info('using dsn {}'.format(dsn))
logg.info('using socket {}'.format(socket_path))
2021-03-17 15:34:51 +01:00
re_http = r'^http'
re_unix = r'^ipc'
2020-08-08 12:27:05 +02:00
class MissingSecretError(BaseException):
def __init__(self, message):
super(MissingSecretError, self).__init__(message)
2020-08-08 11:33:15 +02:00
def personal_new_account(p):
password = p
if p.__class__.__name__ != 'str':
if p.__class__.__name__ != 'list':
e = JSONRPCInvalidParams()
e.data = 'parameter must be list containing one string'
raise ValueError(e)
logg.error('foo {}'.format(p))
if len(p) != 1:
e = JSONRPCInvalidParams()
e.data = 'parameter must be list containing one string'
raise ValueError(e)
if p[0].__class__.__name__ != 'str':
e = JSONRPCInvalidParams()
e.data = 'parameter must be list containing one string'
raise ValueError(e)
password = p[0]
r = db.new(password)
return add_0x(r)
2020-08-08 11:33:15 +02:00
# TODO: move to translation module ("personal" rpc namespace is node-specific)
2021-03-27 15:32:05 +01:00
def personal_signTransaction(p):
2020-10-26 09:02:29 +01:00
logg.debug('got {} to sign'.format(p[0]))
2021-01-14 11:11:25 +01:00
#t = EIP155Transaction(p[0], p[0]['nonce'], 8995)
t = EIP155Transaction(p[0], p[0]['nonce'], p[0]['chainId'])
2021-03-27 15:32:05 +01:00
# z = signer.sign_transaction(t, p[1])
# raw_signed_tx = t.rlp_serialize()
raw_signed_tx = signer.sign_transaction_to_rlp(t, p[1])
2020-08-08 11:33:15 +02:00
o = {
'raw': '0x' + raw_signed_tx.hex(),
'tx': t.serialize(),
}
logg.debug('signed {}'.format(o))
2020-08-08 11:33:15 +02:00
return o
2021-03-27 15:32:05 +01:00
def eth_signTransaction(tx):
o = personal_signTransaction([tx[0], ''])
return o['raw']
2020-08-08 11:33:15 +02:00
2020-12-25 12:00:58 +01:00
def eth_sign(p):
logg.debug('got message {} to sign'.format(p[1]))
message_type = type(p[1]).__name__
if message_type != 'str':
raise ValueError('invalid message format, must be {}, not {}'.format(message_type))
z = signer.sign_ethereum_message(p[0], p[1][2:])
return str(z)
2020-12-22 14:50:00 +01:00
2020-08-08 11:33:15 +02:00
methods = {
'personal_newAccount': personal_new_account,
2021-03-27 15:32:05 +01:00
'personal_signTransaction': personal_signTransaction,
'eth_signTransaction': eth_signTransaction,
2020-12-19 08:47:21 +01:00
'eth_sign': eth_sign,
2020-08-08 11:33:15 +02:00
}
def jsonrpc_error(rpc_id, err):
return {
'json-rpc': '2.0',
'id': rpc_id,
'error': {
'code': err.CODE,
'message': err.MESSAGE,
},
}
def jsonrpc_ok(rpc_id, response):
return {
'json-rpc': '2.0',
'id': rpc_id,
'result': response,
}
def is_valid_json(j):
if j.get('id') == 'None':
raise ValueError('id missing')
return True
def process_input(j):
rpc_id = j['id']
m = j['method']
p = j['params']
return (rpc_id, methods[m](p))
def start_server_tcp(spec):
2021-01-25 18:22:06 +01:00
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
s.bind(spec)
logg.debug('created tcp socket {}'.format(spec))
start_server(s)
def start_server_unix(socket_path):
2020-10-17 02:44:25 +02:00
socket_dir = os.path.dirname(socket_path)
try:
fi = os.stat(socket_dir)
if not stat.S_ISDIR:
RuntimeError('socket path {} is not a directory'.format(socket_dir))
except FileNotFoundError:
os.mkdir(socket_dir)
2020-08-08 11:33:15 +02:00
try:
2020-10-17 02:44:25 +02:00
os.unlink(socket_path)
2020-08-08 11:33:15 +02:00
except FileNotFoundError:
pass
s = socket.socket(family = socket.AF_UNIX, type = socket.SOCK_STREAM)
2020-10-17 02:44:25 +02:00
s.bind(socket_path)
2021-01-25 18:22:06 +01:00
logg.debug('created unix ipc socket {}'.format(socket_path))
start_server(s)
2021-01-25 18:22:06 +01:00
def start_server(s):
2020-08-08 11:33:15 +02:00
s.listen(10)
2021-01-25 18:22:06 +01:00
logg.debug('server started')
2020-08-08 11:33:15 +02:00
while True:
(csock, caddr) = s.accept()
d = csock.recv(4096)
j = None
try:
j = json.loads(d)
is_valid_json(j)
logg.debug('{}'.format(d.decode('utf-8')))
except Exception as e:
logg.exception('input error {}'.format(e))
2020-08-08 11:33:15 +02:00
csock.send(json.dumps(jsonrpc_error(None, JSONRPCParseError)).encode('utf-8'))
csock.close()
continue
try:
(rpc_id, r) = process_input(j)
2020-10-17 02:44:25 +02:00
r = jsonrpc_ok(rpc_id, r)
j = json.dumps(r).encode('utf-8')
csock.send(j)
except ValueError as e:
2020-08-08 11:33:15 +02:00
# TODO: handle cases to give better error context to caller
logg.exception('process error {}'.format(e))
2020-08-08 11:33:15 +02:00
csock.send(json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8'))
2020-12-25 09:21:09 +01:00
except UnknownAccountError as e:
logg.exception('process unknown account error {}'.format(e))
2020-12-25 09:21:09 +01:00
csock.send(json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8'))
2020-08-08 11:33:15 +02:00
csock.close()
s.close()
2020-10-17 02:44:25 +02:00
os.unlink(socket_path)
2020-08-08 11:33:15 +02:00
def init():
global db, signer
2020-10-17 02:44:25 +02:00
secret_hex = config.get('SIGNER_SECRET')
if secret_hex == None:
raise MissingSecretError('please provide a valid hex value for the SIGNER_SECRET configuration variable')
2020-08-08 12:27:05 +02:00
2020-08-08 11:33:15 +02:00
secret = bytes.fromhex(secret_hex)
kw = {
'symmetric_key': secret,
}
db = ReferenceKeystore(dsn, **kw)
2020-08-08 11:33:15 +02:00
signer = ReferenceSigner(db)
2020-10-18 10:32:23 +02:00
def main():
2020-08-08 11:33:15 +02:00
init()
arg = None
try:
arg = json.loads(sys.argv[1])
except:
logg.info('no json rpc command detected, starting socket server')
2021-03-17 15:34:51 +01:00
scheme = 'ipc'
if socket_url.scheme != '':
scheme = socket_url.scheme
if re.match(re_http, socket_url.scheme):
socket_spec = socket_url.netloc.split(':')
2021-01-25 18:22:06 +01:00
host = socket_spec[0]
port = int(socket_spec[1])
start_server_tcp((host, port))
2021-01-25 18:22:06 +01:00
else:
2021-03-17 15:34:51 +01:00
start_server_unix(socket_url.path)
2020-08-08 11:33:15 +02:00
sys.exit(0)
(rpc_id, response) = process_input(arg)
r = jsonrpc_ok(rpc_id, response)
sys.stdout.write(json.dumps(r))
2020-10-26 09:02:29 +01:00
if __name__ == '__main__':
main()