From 787d92c4eec149389b42051578df12e2429c5eb6 Mon Sep 17 00:00:00 2001 From: nolash Date: Mon, 6 Sep 2021 15:48:06 +0200 Subject: [PATCH 1/5] Add http server --- MANIFEST.in | 1 + run_tests.sh | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 MANIFEST.in create mode 100644 run_tests.sh diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..ea97a42 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include *requirements* diff --git a/run_tests.sh b/run_tests.sh new file mode 100644 index 0000000..9ac346f --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e +set -x +#export PYTHONPATH=${PYTHONPATH:.} +for f in `ls tests/*.py`; do + python $f +done +set +x +set +e From 6dda9fa764871b5559217370f4cb542c841b83f6 Mon Sep 17 00:00:00 2001 From: nolash Date: Mon, 6 Sep 2021 21:34:09 +0200 Subject: [PATCH 2/5] Factor out components of cli daemon runnable to cli module --- crypto_dev_signer/cli/__init__.py | 0 crypto_dev_signer/cli/cmd.py | 0 crypto_dev_signer/cli/handle.py | 122 ++++++++++++ crypto_dev_signer/cli/http.py | 85 ++++++++ crypto_dev_signer/cli/jsonrpc.py | 30 +++ crypto_dev_signer/cli/socket.py | 58 ++++++ crypto_dev_signer/error.py | 14 ++ crypto_dev_signer/runnable/signer.py | 281 +-------------------------- 8 files changed, 319 insertions(+), 271 deletions(-) create mode 100644 crypto_dev_signer/cli/__init__.py create mode 100644 crypto_dev_signer/cli/cmd.py create mode 100644 crypto_dev_signer/cli/handle.py create mode 100644 crypto_dev_signer/cli/http.py create mode 100644 crypto_dev_signer/cli/jsonrpc.py create mode 100644 crypto_dev_signer/cli/socket.py diff --git a/crypto_dev_signer/cli/__init__.py b/crypto_dev_signer/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/crypto_dev_signer/cli/cmd.py b/crypto_dev_signer/cli/cmd.py new file mode 100644 index 0000000..e69de29 diff --git a/crypto_dev_signer/cli/handle.py b/crypto_dev_signer/cli/handle.py new file mode 100644 index 0000000..f029617 --- /dev/null +++ b/crypto_dev_signer/cli/handle.py @@ -0,0 +1,122 @@ +# standard imports +import json +import logging + +# external imports +from jsonrpc.exceptions import ( + JSONRPCServerError, + JSONRPCParseError, + JSONRPCInvalidParams, + ) + +# local imports +from crypto_dev_signer.eth.transaction import EIP155Transaction +from crypto_dev_signer.error import ( + UnknownAccountError, + SignerError, + ) +from crypto_dev_signer.cli.jsonrpc import jsonrpc_ok +from .jsonrpc import ( + jsonrpc_error, + is_valid_json, + ) + +logg = logging.getLogger(__name__) + +#methods = { +# 'personal_newAccount': personal_new_account, +# 'personal_signTransaction': personal_signTransaction, +# 'eth_signTransaction': eth_signTransaction, +# 'eth_sign': eth_sign, +# } + + +class SignRequestHandler: + + keystore = None + signer = None + + def process_input(self, j): + rpc_id = j['id'] + m = j['method'] + p = j['params'] + return (rpc_id, getattr(self, m)(p)) #methods[m](p)) + + + def handle_jsonrpc(self, d): + 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)) + j = json.dumps(jsonrpc_error(None, JSONRPCParseError)).encode('utf-8') + raise SignerError(j) + + try: + (rpc_id, r) = self.process_input(j) + r = jsonrpc_ok(rpc_id, r) + j = json.dumps(r).encode('utf-8') + except ValueError as e: + # TODO: handle cases to give better error context to caller + logg.exception('process error {}'.format(e)) + j = json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8') + raise SignerError(j) + except UnknownAccountError as e: + logg.exception('process unknown account error {}'.format(e)) + j = json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8') + raise SignerError(j) + + return j + + + def personal_newAccount(self, 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 = self.keystore.new(password) + + return add_0x(r) + + + # TODO: move to translation module ("personal" rpc namespace is node-specific) + def personal_signTransaction(self, p): + logg.debug('got {} to sign'.format(p[0])) + t = EIP155Transaction(p[0], p[0]['nonce'], p[0]['chainId']) + raw_signed_tx = self.signer.sign_transaction_to_rlp(t, p[1]) + o = { + 'raw': '0x' + raw_signed_tx.hex(), + 'tx': t.serialize(), + } + logg.debug('signed {}'.format(o)) + return o + + + def eth_signTransaction(self, tx): + o = personal_signTransaction([tx[0], '']) + return o['raw'] + + + def eth_sign(self, 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 = self.signer.sign_ethereum_message(p[0], p[1][2:]) + return str(z) + diff --git a/crypto_dev_signer/cli/http.py b/crypto_dev_signer/cli/http.py new file mode 100644 index 0000000..1ae2d72 --- /dev/null +++ b/crypto_dev_signer/cli/http.py @@ -0,0 +1,85 @@ +# standard imports +import logging + +# external imports +from http.server import ( + HTTPServer, + BaseHTTPRequestHandler, + ) + +# local imports +from .handle import SignRequestHandler +from crypto_dev_signer.error import SignerError + +logg = logging.getLogger(__name__) + + +def start_server_http(spec): + httpd = HTTPServer(spec, HTTPSignRequestHandler) + logg.debug('starting http server {}'.format(spec)) + httpd.serve_forever() + + +class HTTPSignRequestHandler(SignRequestHandler, BaseHTTPRequestHandler): + + def do_POST(self): + if self.headers.get('Content-Type') != 'application/json': + self.send_response(400, 'me read json only') + self.end_headers() + return + + try: + if 'application/json' not in self.headers.get('Accept').split(','): + self.send_response(400, 'me json only speak') + self.end_headers() + return + except AttributeError: + pass + + l = self.headers.get('Content-Length') + try: + l = int(l) + except ValueError: + self.send_response(400, 'content length must be integer') + self.end_headers() + return + if l > 4096: + self.send_response(400, 'too much information') + self.end_headers() + return + if l < 0: + self.send_response(400, 'you are too negative') + self.end_headers() + return + + b = b'' + c = 0 + while c < l: + d = self.rfile.read(l-c) + if d == None: + break + b += d + c += len(d) + if c > 4096: + self.send_response(413, 'i should slap you around for lying about your size') + self.end_headers() + return + + try: + r = self.handle_jsonrpc(d) + except SignerError as e: + r = e.to_jsonrpc() + + l = len(r) + self.send_response(200, 'You are the Keymaster') + self.send_header('Content-Length', str(l)) + self.send_header('Cache-Control', 'no-cache') + self.send_header('Content-Type', 'application/json') + self.end_headers() + + c = 0 + while c < l: + n = self.wfile.write(r[c:]) + c += n + + diff --git a/crypto_dev_signer/cli/jsonrpc.py b/crypto_dev_signer/cli/jsonrpc.py new file mode 100644 index 0000000..434c170 --- /dev/null +++ b/crypto_dev_signer/cli/jsonrpc.py @@ -0,0 +1,30 @@ +# local imports +from crypto_dev_signer.error import UnknownAccountError + + +def jsonrpc_error(rpc_id, err): + return { + 'jsonrpc': '2.0', + 'id': rpc_id, + 'error': { + 'code': err.CODE, + 'message': err.MESSAGE, + }, + } + + +def jsonrpc_ok(rpc_id, response): + return { + 'jsonrpc': '2.0', + 'id': rpc_id, + 'result': response, + } + + +def is_valid_json(j): + if j.get('id') == 'None': + raise ValueError('id missing') + return True + + + diff --git a/crypto_dev_signer/cli/socket.py b/crypto_dev_signer/cli/socket.py new file mode 100644 index 0000000..2331d51 --- /dev/null +++ b/crypto_dev_signer/cli/socket.py @@ -0,0 +1,58 @@ +# standard imports +import os +import logging +import socket +import stat + +# local imports +from crypto_dev_signer.error import SignerError +from .handle import SignRequestHandler + +logg = logging.getLogger(__name__) + + +def start_server_socket(s): + s.listen(10) + logg.debug('server started') + handler = SignRequestHandler() + while True: + (csock, caddr) = s.accept() + d = csock.recv(4096) + + r = None + try: + r = handler.handle_jsonrpc(d) + except SignerError as e: + r = e.to_jsonrpc() + + csock.send(r) + csock.close() + s.close() + + os.unlink(socket_path) + + +def start_server_tcp(spec): + s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) + s.bind(spec) + logg.debug('created tcp socket {}'.format(spec)) + start_server_socket(s) + + +def start_server_unix(socket_path): + 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) + + try: + os.unlink(socket_path) + except FileNotFoundError: + pass + s = socket.socket(family = socket.AF_UNIX, type = socket.SOCK_STREAM) + s.bind(socket_path) + logg.debug('created unix ipc socket {}'.format(socket_path)) + start_server_socket(s) diff --git a/crypto_dev_signer/error.py b/crypto_dev_signer/error.py index 9022a3d..2724ee6 100644 --- a/crypto_dev_signer/error.py +++ b/crypto_dev_signer/error.py @@ -8,3 +8,17 @@ class TransactionRevertError(Exception): class NetworkError(Exception): pass + + +class SignerError(Exception): + + def __init__(self, s): + super(SignerError, self).__init__(s) + self.jsonrpc_error = s + + + def to_jsonrpc(self): + return self.jsonrpc_error + + + diff --git a/crypto_dev_signer/runnable/signer.py b/crypto_dev_signer/runnable/signer.py index 54c55b1..6eac26c 100755 --- a/crypto_dev_signer/runnable/signer.py +++ b/crypto_dev_signer/runnable/signer.py @@ -2,17 +2,10 @@ import re import os import sys -import stat -import socket import json import logging import argparse from urllib.parse import urlparse -from http.server import ( - HTTPServer, - BaseHTTPRequestHandler, - ) - # external imports import confini @@ -20,10 +13,10 @@ from jsonrpc.exceptions import * from hexathon import add_0x # local imports +from crypto_dev_signer.cli.cmd import * from crypto_dev_signer.eth.signer import ReferenceSigner -from crypto_dev_signer.eth.transaction import EIP155Transaction from crypto_dev_signer.keystore.reference import ReferenceKeystore -from crypto_dev_signer.error import UnknownAccountError +from crypto_dev_signer.cli.handle import SignRequestHandler logging.basicConfig(level=logging.WARNING) logg = logging.getLogger() @@ -80,266 +73,12 @@ re_http = r'^http' re_tcp = r'^tcp' re_unix = r'^ipc' -class MissingSecretError(BaseException): - - def __init__(self, message): - super(MissingSecretError, self).__init__(message) +class MissingSecretError(Exception): + pass -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] +def main(): - r = db.new(password) - - return add_0x(r) - - -# TODO: move to translation module ("personal" rpc namespace is node-specific) -def personal_signTransaction(p): - logg.debug('got {} to sign'.format(p[0])) - #t = EIP155Transaction(p[0], p[0]['nonce'], 8995) - t = EIP155Transaction(p[0], p[0]['nonce'], p[0]['chainId']) - # z = signer.sign_transaction(t, p[1]) - # raw_signed_tx = t.rlp_serialize() - raw_signed_tx = signer.sign_transaction_to_rlp(t, p[1]) - o = { - 'raw': '0x' + raw_signed_tx.hex(), - 'tx': t.serialize(), - } - logg.debug('signed {}'.format(o)) - return o - - -def eth_signTransaction(tx): - o = personal_signTransaction([tx[0], '']) - return o['raw'] - - -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) - - -methods = { - 'personal_newAccount': personal_new_account, - 'personal_signTransaction': personal_signTransaction, - 'eth_signTransaction': eth_signTransaction, - 'eth_sign': eth_sign, - } - - -def jsonrpc_error(rpc_id, err): - return { - 'jsonrpc': '2.0', - 'id': rpc_id, - 'error': { - 'code': err.CODE, - 'message': err.MESSAGE, - }, - } - - -def jsonrpc_ok(rpc_id, response): - return { - 'jsonrpc': '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): - s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) - s.bind(spec) - logg.debug('created tcp socket {}'.format(spec)) - start_server_socket(s) - - -def start_server_unix(socket_path): - 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) - - try: - os.unlink(socket_path) - except FileNotFoundError: - pass - s = socket.socket(family = socket.AF_UNIX, type = socket.SOCK_STREAM) - s.bind(socket_path) - logg.debug('created unix ipc socket {}'.format(socket_path)) - start_server_socket(s) - - -def start_server_http(spec): - httpd = HTTPServer(spec, HTTPSignRequestHandler) - logg.debug('starting http server {}'.format(spec)) - httpd.serve_forever() - - -class SignerError(Exception): - - - def __init__(self, s): - super(SignerError, self).__init__(s) - self.jsonrpc_error = s - - - def to_jsonrpc(self): - return self.jsonrpc_error - - -class SignRequestHandler: - - def handle_jsonrpc(self, d): - 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)) - j = json.dumps(jsonrpc_error(None, JSONRPCParseError)).encode('utf-8') - raise SignerError(j) - - try: - (rpc_id, r) = process_input(j) - r = jsonrpc_ok(rpc_id, r) - j = json.dumps(r).encode('utf-8') - except ValueError as e: - # TODO: handle cases to give better error context to caller - logg.exception('process error {}'.format(e)) - j = json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8') - raise SignerError(j) - except UnknownAccountError as e: - logg.exception('process unknown account error {}'.format(e)) - j = json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8') - raise SignerError(j) - - return j - - -class HTTPSignRequestHandler(BaseHTTPRequestHandler, SignRequestHandler): - - def do_POST(self): - if self.headers.get('Content-Type') != 'application/json': - self.send_response(400, 'me read json only') - self.end_headers() - return - - try: - if 'application/json' not in self.headers.get('Accept').split(','): - self.send_response(400, 'me json only speak') - self.end_headers() - return - except AttributeError: - pass - - l = self.headers.get('Content-Length') - try: - l = int(l) - except ValueError: - self.send_response(400, 'content length must be integer') - self.end_headers() - return - if l > 4096: - self.send_response(400, 'too much information') - self.end_headers() - return - if l < 0: - self.send_response(400, 'you are too negative') - self.end_headers() - return - - b = b'' - c = 0 - while c < l: - d = self.rfile.read(l-c) - if d == None: - break - b += d - c += len(d) - if c > 4096: - self.send_response(413, 'i should slap you around for lying about your size') - self.end_headers() - return - - try: - r = self.handle_jsonrpc(d) - except SignerError as e: - r = e.to_jsonrpc() - - #b = json.dumps(r).encode('utf-8') - l = len(r) - self.send_response(200, 'You are the Keymaster') - self.send_header('Content-Length', str(l)) - self.send_header('Cache-Control', 'no-cache') - self.send_header('Content-Type', 'application/json') - self.end_headers() - - c = 0 - while c < l: - n = self.wfile.write(r[c:]) - c += n - - -def start_server_socket(s): - s.listen(10) - logg.debug('server started') - handler = SignRequestHandler() - while True: - (csock, caddr) = s.accept() - d = csock.recv(4096) - - r = None - try: - r = handler.handle_jsonrpc(d) - except SignerError as e: - r = e.to_jsonrpc() - - csock.send(r) - csock.close() - s.close() - - os.unlink(socket_path) - - -def init(): - global db, signer secret_hex = config.get('SIGNER_SECRET') if secret_hex == None: raise MissingSecretError('please provide a valid hex value for the SIGNER_SECRET configuration variable') @@ -348,12 +87,9 @@ def init(): kw = { 'symmetric_key': secret, } - db = ReferenceKeystore(dsn, **kw) - signer = ReferenceSigner(db) + SignRequestHandler.keystore = ReferenceKeystore(dsn, **kw) + SignRequestHandler.signer = ReferenceSigner(SignRequestHandler.keystore) - -def main(): - init() arg = None try: arg = json.loads(sys.argv[1]) @@ -363,16 +99,19 @@ def main(): if socket_url.scheme != '': scheme = socket_url.scheme if re.match(re_tcp, socket_url.scheme): + from crypto_dev_signer.cli.socket import start_server_tcp socket_spec = socket_url.netloc.split(':') host = socket_spec[0] port = int(socket_spec[1]) start_server_tcp((host, port)) elif re.match(re_http, socket_url.scheme): + from crypto_dev_signer.cli.http import start_server_http socket_spec = socket_url.netloc.split(':') host = socket_spec[0] port = int(socket_spec[1]) start_server_http((host, port)) else: + from crypto_dev_signer.cli.socket import start_server_unix start_server_unix(socket_url.path) sys.exit(0) From d7853acc7d89cc6b51f6a76043766f723be59b05 Mon Sep 17 00:00:00 2001 From: nolash Date: Mon, 6 Sep 2021 21:39:49 +0200 Subject: [PATCH 3/5] Remove empty file --- crypto_dev_signer/cli/cmd.py | 0 crypto_dev_signer/cli/handle.py | 9 +-------- 2 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 crypto_dev_signer/cli/cmd.py diff --git a/crypto_dev_signer/cli/cmd.py b/crypto_dev_signer/cli/cmd.py deleted file mode 100644 index e69de29..0000000 diff --git a/crypto_dev_signer/cli/handle.py b/crypto_dev_signer/cli/handle.py index f029617..d576729 100644 --- a/crypto_dev_signer/cli/handle.py +++ b/crypto_dev_signer/cli/handle.py @@ -23,13 +23,6 @@ from .jsonrpc import ( logg = logging.getLogger(__name__) -#methods = { -# 'personal_newAccount': personal_new_account, -# 'personal_signTransaction': personal_signTransaction, -# 'eth_signTransaction': eth_signTransaction, -# 'eth_sign': eth_sign, -# } - class SignRequestHandler: @@ -40,7 +33,7 @@ class SignRequestHandler: rpc_id = j['id'] m = j['method'] p = j['params'] - return (rpc_id, getattr(self, m)(p)) #methods[m](p)) + return (rpc_id, getattr(self, m)(p)) def handle_jsonrpc(self, d): From 9876efe377a8289abd7160d71b5f9f1f0ee4c0be Mon Sep 17 00:00:00 2001 From: nolash Date: Tue, 7 Sep 2021 08:56:28 +0200 Subject: [PATCH 4/5] Add socket test missing warning --- TODO | 6 ++ crypto_dev_signer/cli/handle.py | 6 +- crypto_dev_signer/cli/socket.py | 25 +++++--- crypto_dev_signer/runnable/signer.py | 2 - setup.py | 2 +- tests/test_cli.py | 88 ++++++++++++++++++++++++++++ tests/test_keystore_dict.py | 1 - tests/test_socket.py | 15 +++++ 8 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 TODO create mode 100644 tests/test_cli.py create mode 100644 tests/test_socket.py diff --git a/TODO b/TODO new file mode 100644 index 0000000..a3a6b9f --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +Missing tests for: + +- crypto_dev_signer/cli/http.py +- crypto_dev_signer/cli/socket.py + +tests/test_keystore_reference.py is dependent on postgres, should use sqlite for tests so that it can run independently diff --git a/crypto_dev_signer/cli/handle.py b/crypto_dev_signer/cli/handle.py index d576729..a58f290 100644 --- a/crypto_dev_signer/cli/handle.py +++ b/crypto_dev_signer/cli/handle.py @@ -8,6 +8,7 @@ from jsonrpc.exceptions import ( JSONRPCParseError, JSONRPCInvalidParams, ) +from hexathon import add_0x # local imports from crypto_dev_signer.eth.transaction import EIP155Transaction @@ -96,12 +97,11 @@ class SignRequestHandler: 'raw': '0x' + raw_signed_tx.hex(), 'tx': t.serialize(), } - logg.debug('signed {}'.format(o)) return o def eth_signTransaction(self, tx): - o = personal_signTransaction([tx[0], '']) + o = self.personal_signTransaction([tx[0], '']) return o['raw'] @@ -111,5 +111,5 @@ class SignRequestHandler: if message_type != 'str': raise ValueError('invalid message format, must be {}, not {}'.format(message_type)) z = self.signer.sign_ethereum_message(p[0], p[1][2:]) - return str(z) + return add_0x(z.hex()) diff --git a/crypto_dev_signer/cli/socket.py b/crypto_dev_signer/cli/socket.py index 2331d51..abf43c0 100644 --- a/crypto_dev_signer/cli/socket.py +++ b/crypto_dev_signer/cli/socket.py @@ -11,24 +11,33 @@ from .handle import SignRequestHandler logg = logging.getLogger(__name__) -def start_server_socket(s): - s.listen(10) - logg.debug('server started') - handler = SignRequestHandler() - while True: - (csock, caddr) = s.accept() +class SocketHandler: + + def __init__(self): + self.handler = SignRequestHandler() + + + def process(self, csock): d = csock.recv(4096) r = None try: - r = handler.handle_jsonrpc(d) + r = self.handler.handle_jsonrpc(d) except SignerError as e: r = e.to_jsonrpc() csock.send(r) + + +def start_server_socket(s): + s.listen(10) + logg.debug('server started') + handler = SocketHandler() + while True: + (csock, caddr) = s.accept() + handler.process(csock) csock.close() s.close() - os.unlink(socket_path) diff --git a/crypto_dev_signer/runnable/signer.py b/crypto_dev_signer/runnable/signer.py index 6eac26c..86918c5 100755 --- a/crypto_dev_signer/runnable/signer.py +++ b/crypto_dev_signer/runnable/signer.py @@ -10,10 +10,8 @@ from urllib.parse import urlparse # external imports import confini from jsonrpc.exceptions import * -from hexathon import add_0x # local imports -from crypto_dev_signer.cli.cmd import * from crypto_dev_signer.eth.signer import ReferenceSigner from crypto_dev_signer.keystore.reference import ReferenceKeystore from crypto_dev_signer.cli.handle import SignRequestHandler diff --git a/setup.py b/setup.py index 84b4f58..e5729bb 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ f.close() setup( name="crypto-dev-signer", - version="0.4.15a2", + version="0.4.15a3", description="A signer and keystore daemon and library for cryptocurrency software development", author="Louis Holbrook", author_email="dev@holbrook.no", diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..752e9e8 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,88 @@ +# standard imports +import unittest +import logging +import os + +# external imports +from hexathon import strip_0x + +# local imports +from crypto_dev_signer.eth.signer import ReferenceSigner +from crypto_dev_signer.keystore.dict import DictKeystore +from crypto_dev_signer.cli.handle import SignRequestHandler +from crypto_dev_signer.eth.transaction import EIP155Transaction + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + +script_dir = os.path.dirname(os.path.realpath(__file__)) +data_dir = os.path.join(script_dir, 'testdata') + +class TestCli(unittest.TestCase): + + def setUp(self): + #pk = bytes.fromhex('5087503f0a9cc35b38665955eb830c63f778453dd11b8fa5bd04bc41fd2cc6d6') + #pk_getter = pkGetter(pk) + self.keystore = DictKeystore() + SignRequestHandler.keystore = self.keystore + self.signer = ReferenceSigner(self.keystore) + SignRequestHandler.signer = self.signer + self.handler = SignRequestHandler() + + + def test_new_account(self): + q = { + 'id': 0, + 'method': 'personal_newAccount', + 'params': [''], + } + (rpc_id, result) = self.handler.process_input(q) + self.assertTrue(self.keystore.get(result)) + + + def test_sign_tx(self): + keystore_file = os.path.join(data_dir, 'UTC--2021-01-08T18-37-01.187235289Z--00a329c0648769a73afac7f9381e08fb43dbea72') + sender = self.keystore.import_keystore_file(keystore_file) + tx_hexs = { + 'nonce': '0x', + 'from': sender, + 'gasPrice': "0x04a817c800", + 'gas': "0x5208", + 'to': '0x3535353535353535353535353535353535353535', + 'value': "0x03e8", + 'data': "0xdeadbeef", + 'chainId': 8995, + } + tx = EIP155Transaction(tx_hexs, 42, 8995) + tx_s = tx.serialize() + + # TODO: move to serialization wrapper for tests + tx_s['chainId'] = tx_s['v'] + tx_s['from'] = sender + + # eth_signTransaction wraps personal_signTransaction, so here we test both already + q = { + 'id': 0, + 'method': 'eth_signTransaction', + 'params': [tx_s], + } + (rpc_id, result) = self.handler.process_input(q) + logg.debug('result {}'.format(result)) + + self.assertEqual(strip_0x(result), 'f86c2a8504a817c8008252089435353535353535353535353535353535353535358203e884deadbeef82466aa0b7c1bbf52f736ada30fe253c7484176f44d6fd097a9720dc85ae5bbc7f060e54a07afee2563b0cf6d00333df51cc62b0d13c63108b2bce54ce2ad24e26ce7b4f25') + + def test_sign_msg(self): + keystore_file = os.path.join(data_dir, 'UTC--2021-01-08T18-37-01.187235289Z--00a329c0648769a73afac7f9381e08fb43dbea72') + sender = self.keystore.import_keystore_file(keystore_file) + q = { + 'id': 0, + 'method': 'eth_sign', + 'params': [sender, '0xdeadbeef'], + } + (rpc_id, result) = self.handler.process_input(q) + logg.debug('result msg {}'.format(result)) + self.assertEqual(strip_0x(result), '50320dda75190a121b7b5979de66edadafd02bdfbe4f6d49552e79c01410d2464aae35e385c0e5b61663ff7b44ef65fa0ac7ad8a57472cf405db399b9dba3e1600') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_keystore_dict.py b/tests/test_keystore_dict.py index 06dcedf..6e216b7 100644 --- a/tests/test_keystore_dict.py +++ b/tests/test_keystore_dict.py @@ -29,7 +29,6 @@ class TestDict(unittest.TestCase): db = None def setUp(self): - logg.debug('setup') self.db = DictKeystore() keystore_filepath = os.path.join(script_dir, 'testdata', 'UTC--2021-01-08T18-37-01.187235289Z--00a329c0648769a73afac7f9381e08fb43dbea72') diff --git a/tests/test_socket.py b/tests/test_socket.py new file mode 100644 index 0000000..cb99d66 --- /dev/null +++ b/tests/test_socket.py @@ -0,0 +1,15 @@ +# standard imports +import unittest +import logging + +logg = logging.getLogger(__name__) + + +class SocketTest(unittest.TestCase): + + def test_placeholder_warning(self): + logg.warning('socket tests are missing! :/') + + +if __name__ == '__main__': + unittest.main() From 71dafa447643ab50870f9c3c3d4b9688fb5c4a45 Mon Sep 17 00:00:00 2001 From: nolash Date: Wed, 15 Sep 2021 17:35:33 +0200 Subject: [PATCH 5/5] Clean out commented lines in setup --- setup.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index e5729bb..81e9b88 100644 --- a/setup.py +++ b/setup.py @@ -33,18 +33,16 @@ f.close() setup( name="crypto-dev-signer", - version="0.4.15a3", + version="0.4.15a4", 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.web3ext', - #'crypto_dev_signer.eth.helper', 'crypto_dev_signer.eth', + 'crypto_dev_signer.cli', 'crypto_dev_signer.keystore', 'crypto_dev_signer.runnable', - #'crypto_dev_signer.helper', 'crypto_dev_signer', ], install_requires=requirements, @@ -54,9 +52,6 @@ setup( tests_require=test_requirements, long_description=long_description, long_description_content_type='text/markdown', - #scripts = [ - # 'scripts/crypto-dev-daemon', - # ], entry_points = { 'console_scripts': [ 'crypto-dev-daemon=crypto_dev_signer.runnable.signer:main',