funga

Signer and keystore daemon and library for cryptocurrency software development
Log | Files | Refs | README | LICENSE

commit 51f076ba2aa51643df6efd4f19692dcc75f398c0
parent 472e2f04fc106523b24b65f99aa572cc6df453fa
Author: nolash <dev@holbrook.no>
Date:   Sat,  8 Aug 2020 10:45:37 +0200

Add setup.py, rename server script

Diffstat:
Ascripts/crypto-dev-daemon | 2++
Ascripts/crypto-dev-daemon.py | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dscripts/server.py | 157-------------------------------------------------------------------------------
Asetup.py | 14++++++++++++++
Msrc/keystore/postgres.py | 8+++-----
Msrc/transaction.py | 4++++
Msrc/web3ext/middleware.py | 1+
7 files changed, 182 insertions(+), 162 deletions(-)

diff --git a/scripts/crypto-dev-daemon b/scripts/crypto-dev-daemon @@ -0,0 +1 @@ +scripts/crypto-dev-daemon.py +\ No newline at end of file diff --git a/scripts/crypto-dev-daemon.py b/scripts/crypto-dev-daemon.py @@ -0,0 +1,158 @@ +#!/usr/bin/python3 + +import socket +import json +import logging +import sys +import os + +from jsonrpc.exceptions import * + +from signer import ReferenceSigner +from keystore import ReferenceKeystore +from transaction import EIP155Transaction + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + +db = None +signer = None +chainId = 8995 + + +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 r + + +def personal_sign_transaction(p): + t = EIP155Transaction(p[0], 0, 8995) + z = signer.signTransaction(t, p[1]) + raw_signed_tx = t.rlp_serialize() + o = { + 'raw': '0x' + raw_signed_tx.hex(), + 'tx': t.serialize(), + } + return o + + +# TODO: temporary workaround for platform, since personal_signTransaction is missing from web3.py +def eth_signTransaction(tx): + return personal_sign_transaction([tx, '']) + + +methods = { + 'personal_newAccount': personal_new_account, + 'personal_signTransaction': personal_sign_transaction, + 'eth_signTransaction': eth_signTransaction, + } + + +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(): + try: + os.unlink('/tmp/foo.ipc') + except FileNotFoundError: + pass + s = socket.socket(family = socket.AF_UNIX, type = socket.SOCK_STREAM) + s.bind('/tmp/foo.ipc') + s.listen(10) + 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: + csock.send(json.dumps(jsonrpc_error(None, JSONRPCParseError)).encode('utf-8')) + csock.close() + continue + + try: + (rpc_id, r) = process_input(j) + csock.send(json.dumps(jsonrpc_ok(rpc_id, r)).encode('utf-8')) + except: + # TODO: handle cases to give better error context to caller + csock.send(json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8')) + + csock.close() + s.close() + + os.unlink('/tmp/foo.ipc') + + +def init(): + global db, signer + secret_hex = os.environ.get('SIGNER_SECRET') + secret = bytes.fromhex(secret_hex) + kw = { + 'symmetric_key': secret, + } + db = ReferenceKeystore('cic_signer', **kw) + signer = ReferenceSigner(db) + + +if __name__ == '__main__': + init() + arg = None + try: + arg = json.loads(sys.argv[1]) + except: + logg.info('no json rpc command detected, starting socket server') + start_server() + sys.exit(0) + + (rpc_id, response) = process_input(arg) + r = jsonrpc_ok(rpc_id, response) + sys.stdout.write(json.dumps(r)) diff --git a/scripts/server.py b/scripts/server.py @@ -1,157 +0,0 @@ -import socket -import json -import logging -import sys -import os - -from jsonrpc.exceptions import * - -from signer import ReferenceSigner -from keystore import ReferenceKeystore -from transaction import EIP155Transaction - -logging.basicConfig(level=logging.DEBUG) -logg = logging.getLogger() - -db = None -signer = None -chainId = 8995 - - -def personal_new_account(p): - if p.__class__.__name__ != 'list': - e = JSONRPCInvalidParams() - e.data = 'parameter must be list containing one string' - raise ValueError(e) - 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) - - r = db.new(p[0]) - - return r - - -def personal_sign_transaction(p): - t = EIP155Transaction(p[0], 0, 8995) - z = signer.signTransaction(t, p[1]) - raw_signed_tx = t.rlp_serialize() - o = { - 'raw': '0x' + raw_signed_tx.hex(), - 'tx': t.serialize(), - } - return o - - -# TODO: temporary workaround for platform, since personal_signTransaction is missing from web3.py -def eth_signTransaction(tx): - password = tx['password'] - del tx['password'] - tx_signed = personal_sign_transaction([tx, password]) - return tx_signed - - -methods = { - 'personal_newAccount': personal_new_account, - 'personal_signTransaction': personal_sign_transaction, - 'eth_signTransaction': eth_signTransaction, - } - - -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(): - try: - os.unlink('/tmp/foo.ipc') - except FileNotFoundError: - pass - s = socket.socket(family = socket.AF_UNIX, type = socket.SOCK_STREAM) - s.bind('/tmp/foo.ipc') - s.listen(10) - 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: - csock.send(json.dumps(jsonrpc_error(None, JSONRPCParseError)).encode('utf-8')) - csock.close() - continue - - try: - (rpc_id, r) = process_input(j) - csock.send(json.dumps(jsonrpc_ok(rpc_id, r)).encode('utf-8')) - except: - # TODO: handle cases to give better error context to caller - csock.send(json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8')) - - csock.close() - s.close() - - os.unlink('/tmp/foo.ipc') - - -def init(): - global db, signer - secret_hex = os.environ.get('SIGNER_SECRET') - secret = bytes.fromhex(secret_hex) - kw = { - 'symmetric_key': secret, - } - db = ReferenceKeystore('cic_signer', **kw) - signer = ReferenceSigner(db) - - -if __name__ == '__main__': - init() - arg = None - try: - arg = json.loads(sys.argv[1]) - except: - logg.info('no json rpc command detected, starting socket server') - start_server() - sys.exit(0) - - (rpc_id, response) = process_input(arg) - r = jsonrpc_ok(rpc_id, response) - sys.stdout.write(json.dumps(r)) diff --git a/setup.py b/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup + +setup( + name="crypto-dev-signer", + version="0.1.0", + description="A signer and keystore daemon and library for cryptocurrency software development", + author="Louis Holbrook", + author_email="dev@holbrook.no", + packages=['crypto-dev-signer'], + install_requires=['web3', 'psycopg2', 'cryptography', 'eth-keys', 'pysha3', 'rlp'], + scripts = [ + 'scripts/crypto-dev-daemon.py', + ], + ) diff --git a/src/keystore/postgres.py b/src/keystore/postgres.py @@ -1,7 +1,9 @@ +# standard imports import logging import base64 import os +# third-party imports from cryptography.fernet import Fernet import psycopg2 from psycopg2 import sql @@ -9,6 +11,7 @@ from eth_keys import KeyAPI from eth_keys.backends import NativeECCBackend import sha3 +# local imports from common import strip_hex_prefix from keystore.interface import Keystore @@ -18,15 +21,10 @@ logging.basicConfig(level=logging.DEBUG) logg = logging.getLogger(__file__) - def to_bytes(x): return x.encode('utf-8') - - - - class ReferenceKeystore(Keystore): schema = [ diff --git a/src/transaction.py b/src/transaction.py @@ -1,12 +1,16 @@ +# standard imports import logging import binascii +# third-party imports from rlp import encode as rlp_encode +# local imports from common import strip_hex_prefix, add_hex_prefix logg = logging.getLogger(__name__) + class Transaction: def rlp_serialize(self): diff --git a/src/web3ext/middleware.py b/src/web3ext/middleware.py @@ -1,3 +1,4 @@ +# standard imports import logging import re import socket