commit 86c018033536c2608da78befd4cc69737908a573
parent cdd1c58c514b0d0f6227b479d7574fd29ec90fa3
Author: nolash <dev@holbrook.no>
Date: Sun, 18 Oct 2020 10:32:23 +0200
Upgrade confini
Diffstat:
8 files changed, 264 insertions(+), 249 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -2,3 +2,6 @@ __pycache__
*.pyc
venv
.venv
+build
+dist
+*.egg-info
diff --git a/CHANGELOG b/CHANGELOG
@@ -1,3 +1,5 @@
+* 0.2.6
+ - Upgrade confini
* 0.2.5
- Add default env override
- Do not create middleware if ipc no set
diff --git a/README.md b/README.md
@@ -1,9 +1,9 @@
-# CIC PLATFORM SIGNER
+# CRYPTO DEV SIGNER
This package is written because at the time no good solution seemed to exist for solving the following combined requirements and issues:
* A service has custody of its users' private keys.
-* The are a large number of private keys involved (tens of thousands minimum).
+* The are a large number of private keys involved (hundreds of thousands and up).
* Need to sign transactions conforming to EIP-155, with the ability to arbitrarily specify the "chain id".
* Do not want to store the keys inside an ethereum node, especially not the one connected to the network.
* Want to use the "standard" web3 JSON-RPC interface, so that the component can be easily replaced later.
@@ -14,20 +14,12 @@ This package is written because at the time no good solution seemed to exist for
### Scripts
-Two scripts are currently available:
-
-### `crypto-dev-daemon.py`
-
-An Unix socket IPC server implementing the following web3 json-rpc methods:
+When installed with pip/setuptools, this package provides a Unix socket IPC server as `crypto-dev-daemon` implementing the following web3 json-rpc methods:
* web3.eth.personal.newAccount
* web3.eth.personal.signTransaction
* web3.eth.signTransaction
-### `web3_middleware.py`
-
-Demonstrates use of the IPC server as middleware for handling calls to the web3 json-rpc methods provided by the daemon.
-
### Classes
The classes and packages provided are:
@@ -49,9 +41,9 @@ The classes and packages provided are:
## VERSION
-This software is in alpha state and very brittle.
+This software is in alpha state.
-Current version is 0.1.0
+Current version is 0.2.5
## LICENSE
diff --git a/crypto_dev_signer/runnable/signer.py b/crypto_dev_signer/runnable/signer.py
@@ -0,0 +1,228 @@
+# standard imports
+import os
+import sys
+import stat
+import socket
+import json
+import logging
+import argparse
+
+# third-party imports
+import confini
+from jsonrpc.exceptions import *
+
+# local imports
+from crypto_dev_signer.eth.signer import ReferenceSigner
+from crypto_dev_signer.eth.transaction import EIP155Transaction
+from crypto_dev_signer.keystore import ReferenceKeystore
+
+#logging.basicConfig(level=logging.DEBUG)
+logg = logging.getLogger()
+
+config_dir = os.path.join('/usr/local/etc/cic-eth')
+
+db = None
+signer = None
+chainId = 8995
+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')
+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))
+
+if args.i:
+ chainId = args.i
+if args.s:
+ socket_path = args.s
+elif config.get('SIGNER_SOCKET_PATH'):
+ socket_path = config.get('SIGNER_SOCKET_PATH')
+
+
+# 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'),
+ )
+
+logg.info('using dsn {}'.format(dsn))
+logg.info('using socket {}'.format(socket_path))
+
+
+class MissingSecretError(BaseException):
+
+ def __init__(self, message):
+ super(MissingSecretError, self).__init__(message)
+
+
+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], p[0]['nonce'], 8995)
+ z = signer.signTransaction(t, p[1])
+ raw_signed_tx = t.rlp_serialize()
+ o = {
+ 'raw': '0x' + raw_signed_tx.hex(),
+ 'tx': t.serialize(),
+ }
+ logg.debug('signed {}'.format(o))
+ 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():
+ 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)
+ 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 Exception as e:
+ logg.error('input error {}'.format(e))
+ csock.send(json.dumps(jsonrpc_error(None, JSONRPCParseError)).encode('utf-8'))
+ csock.close()
+ continue
+
+ try:
+ (rpc_id, r) = process_input(j)
+ r = jsonrpc_ok(rpc_id, r)
+ j = json.dumps(r).encode('utf-8')
+ csock.send(j)
+ except ValueError as e:
+ # TODO: handle cases to give better error context to caller
+ logg.error('process error {}'.format(e))
+ csock.send(json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8'))
+
+ 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')
+
+ secret = bytes.fromhex(secret_hex)
+ kw = {
+ 'symmetric_key': secret,
+ }
+ db = ReferenceKeystore(dsn, **kw)
+ signer = ReferenceSigner(db)
+
+
+#if __name__ == '__main__':
+def 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/crypto-dev-daemon b/scripts/crypto-dev-daemon
@@ -1,227 +0,0 @@
-# standard imports
-import os
-import sys
-import stat
-import socket
-import json
-import logging
-import argparse
-
-# third-party imports
-import confini
-from jsonrpc.exceptions import *
-
-# local imports
-from crypto_dev_signer.eth.signer import ReferenceSigner
-from crypto_dev_signer.eth.transaction import EIP155Transaction
-from crypto_dev_signer.keystore import ReferenceKeystore
-
-#logging.basicConfig(level=logging.DEBUG)
-logg = logging.getLogger()
-
-config_dir = os.path.join('/usr/local/etc/cic-eth')
-
-db = None
-signer = None
-chainId = 8995
-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')
-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))
-
-if args.i:
- chainId = args.i
-if args.s:
- socket_path = args.s
-elif config.get('SIGNER_SOCKET_PATH'):
- socket_path = config.get('SIGNER_SOCKET_PATH')
-
-
-# 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'),
- )
-
-logg.info('using dsn {}'.format(dsn))
-logg.info('using socket {}'.format(socket_path))
-
-
-class MissingSecretError(BaseException):
-
- def __init__(self, message):
- super(MissingSecretError, self).__init__(message)
-
-
-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], p[0]['nonce'], 8995)
- z = signer.signTransaction(t, p[1])
- raw_signed_tx = t.rlp_serialize()
- o = {
- 'raw': '0x' + raw_signed_tx.hex(),
- 'tx': t.serialize(),
- }
- logg.debug('signed {}'.format(o))
- 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():
- 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)
- 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 Exception as e:
- logg.error('input error {}'.format(e))
- csock.send(json.dumps(jsonrpc_error(None, JSONRPCParseError)).encode('utf-8'))
- csock.close()
- continue
-
- try:
- (rpc_id, r) = process_input(j)
- r = jsonrpc_ok(rpc_id, r)
- j = json.dumps(r).encode('utf-8')
- csock.send(j)
- except ValueError as e:
- # TODO: handle cases to give better error context to caller
- logg.error('process error {}'.format(e))
- csock.send(json.dumps(jsonrpc_error(j['id'], JSONRPCServerError)).encode('utf-8'))
-
- 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')
-
- secret = bytes.fromhex(secret_hex)
- kw = {
- 'symmetric_key': secret,
- }
- db = ReferenceKeystore(dsn, **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/crypto-dev-daemon.py b/scripts/crypto-dev-daemon.py
@@ -1 +0,0 @@
-crypto-dev-daemon
-\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
@@ -1,3 +1,11 @@
[metadata]
+classifiers =
+ Programming Language :: Python :: 3
+ Operating System :: OS Independent
+ Development Status :: 3 - Alpha
+ Intended Audience :: Developers
+ Topic :: Software Development :: Libraries
+ License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
license = GPLv3
-license_file = LICENSE.txt
+license_files =
+ LICENSE.txt
diff --git a/setup.py b/setup.py
@@ -1,8 +1,12 @@
from setuptools import setup
+f = open('README.md', 'r')
+long_description = f.read()
+f.close()
+
setup(
name="crypto-dev-signer",
- version="0.2.5",
+ version="0.2.6",
description="A signer and keystore daemon and library for cryptocurrency software development",
author="Louis Holbrook",
author_email="dev@holbrook.no",
@@ -11,6 +15,7 @@ setup(
'crypto_dev_signer.eth.web3ext',
'crypto_dev_signer.eth',
'crypto_dev_signer.keystore',
+ 'crypto_dev_signer.runnable',
'crypto_dev_signer',
],
install_requires=[
@@ -21,11 +26,17 @@ setup(
'pysha3',
'rlp',
'json-rpc',
- 'confini==0.2.1',
+ 'confini==0.2.3',
],
- scripts = [
- 'scripts/crypto-dev-daemon',
- ],
- data_files = [('', ['LICENSE.txt'])],
+ 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',
+ ],
+ },
url='https://gitlab.com/nolash/crypto-dev-signer',
)