Fix setup.py to include all modules
This commit is contained in:
4
crypto_dev_signer/__init__.py
Normal file
4
crypto_dev_signer/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
import crypto_dev_signer.eth.signer
|
||||
import crypto_dev_signer.eth.web3ext
|
||||
import crypto_dev_signer.eth.transaction
|
||||
import crypto_dev_signer.common
|
||||
11
crypto_dev_signer/common.py
Normal file
11
crypto_dev_signer/common.py
Normal file
@@ -0,0 +1,11 @@
|
||||
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
|
||||
0
crypto_dev_signer/eth/__init__.py
Normal file
0
crypto_dev_signer/eth/__init__.py
Normal file
1
crypto_dev_signer/eth/signer/__init__.py
Normal file
1
crypto_dev_signer/eth/signer/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from crypto_dev_signer.eth.signer.defaultsigner import ReferenceSigner, Signer
|
||||
40
crypto_dev_signer/eth/signer/defaultsigner.py
Normal file
40
crypto_dev_signer/eth/signer/defaultsigner.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import logging
|
||||
import sha3
|
||||
|
||||
from eth_keys import KeyAPI
|
||||
from eth_keys.backends import NativeECCBackend
|
||||
|
||||
keys = KeyAPI(NativeECCBackend)
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Signer:
|
||||
|
||||
|
||||
def __init__(self, keyGetter):
|
||||
self.keyGetter = keyGetter
|
||||
|
||||
|
||||
def signTransaction(self, tx, password=None):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
||||
class ReferenceSigner(Signer):
|
||||
|
||||
|
||||
def __init__(self, keyGetter):
|
||||
super(ReferenceSigner, self).__init__(keyGetter)
|
||||
|
||||
|
||||
def signTransaction(self, tx, password=None):
|
||||
s = tx.rlp_serialize()
|
||||
h = sha3.keccak_256()
|
||||
h.update(s)
|
||||
g = h.digest()
|
||||
k = keys.PrivateKey(self.keyGetter.get(tx.sender, password))
|
||||
z = keys.ecdsa_sign(message_hash=g, private_key=k)
|
||||
tx.v = (tx.v * 2) + 35 + z[64]
|
||||
tx.r = z[:32]
|
||||
tx.s = z[32:64]
|
||||
return z
|
||||
68
crypto_dev_signer/eth/transaction.py
Normal file
68
crypto_dev_signer/eth/transaction.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import binascii
|
||||
|
||||
# third-party imports
|
||||
from rlp import encode as rlp_encode
|
||||
|
||||
# local imports
|
||||
from crypto_dev_signer.common import strip_hex_prefix, add_hex_prefix
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Transaction:
|
||||
|
||||
def rlp_serialize(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def serialize(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class EIP155Transaction:
|
||||
|
||||
def __init__(self, tx, nonce, chainId=1):
|
||||
|
||||
to = binascii.unhexlify(strip_hex_prefix(tx['to']))
|
||||
data = binascii.unhexlify(strip_hex_prefix(tx['data']))
|
||||
|
||||
self.nonce = nonce
|
||||
self.gas_price = int(tx['gasPrice'])
|
||||
self.start_gas = int(tx['gas'])
|
||||
self.to = to
|
||||
self.value = int(tx['value'])
|
||||
self.data = data
|
||||
self.v = chainId
|
||||
self.r = b''
|
||||
self.s = b''
|
||||
self.sender = strip_hex_prefix(tx['from'])
|
||||
|
||||
|
||||
def rlp_serialize(self):
|
||||
b = self.nonce.to_bytes(8, byteorder='little')
|
||||
s = [
|
||||
self.nonce,
|
||||
self.gas_price,
|
||||
self.start_gas,
|
||||
self.to,
|
||||
self.value,
|
||||
self.data,
|
||||
self.v,
|
||||
self.r,
|
||||
self.s,
|
||||
]
|
||||
return rlp_encode(s)
|
||||
|
||||
def serialize(self):
|
||||
return {
|
||||
'nonce': add_hex_prefix(hex(self.nonce)),
|
||||
'gasPrice': add_hex_prefix(hex(self.gas_price)),
|
||||
'gas': add_hex_prefix(hex(self.start_gas)),
|
||||
'to': add_hex_prefix(self.to.hex()),
|
||||
'value': add_hex_prefix(hex(self.value)),
|
||||
'data': add_hex_prefix(self.data.hex()),
|
||||
'v': add_hex_prefix(hex(self.v)),
|
||||
'r': add_hex_prefix(self.r.hex()),
|
||||
's': add_hex_prefix(self.s.hex()),
|
||||
}
|
||||
29
crypto_dev_signer/eth/web3ext/__init__.py
Normal file
29
crypto_dev_signer/eth/web3ext/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
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?://')
|
||||
|
||||
|
||||
#def create_middleware(ipcaddr='/var/run/cic-platform/cic.ipc'):
|
||||
def create_middleware(ipcaddr='/tmp/foo.ipc'):
|
||||
PlatformMiddleware.ipcaddr = ipcaddr
|
||||
return PlatformMiddleware
|
||||
|
||||
|
||||
# overrides the original Web3 constructor
|
||||
def Web3(blockchain_provider='ws://localhost:8546', ipcaddr=None):
|
||||
provider = None
|
||||
if re.match(re_websocket, blockchain_provider) != None:
|
||||
provider = WebsocketProvider(blockchain_provider)
|
||||
elif re.match(re_http, blockchain_provider) != None:
|
||||
provider = HTTPProvider(blockchain_provider)
|
||||
|
||||
w3 = Web3super(provider)
|
||||
|
||||
w3.middleware_onion.add(create_middleware())
|
||||
w3.eth.personal = w3.geth.personal
|
||||
return w3
|
||||
96
crypto_dev_signer/eth/web3ext/middleware.py
Normal file
96
crypto_dev_signer/eth/web3ext/middleware.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# 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 = '/tmp/foo.ipc'
|
||||
|
||||
|
||||
def __init__(self, make_request, w3):
|
||||
self.w3 = w3
|
||||
self.make_request = make_request
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
def __call__(self, method, suspect_params):
|
||||
|
||||
self.id_seq += 1
|
||||
logg.debug('in middleware method {} params {}'.format(method, suspect_params))
|
||||
|
||||
if self.re_personal.match(method) != None:
|
||||
params = PlatformMiddleware._translate_params(suspect_params)
|
||||
# multiple providers is broken in web3.py 5.12.0
|
||||
# https://github.com/ethereum/web3.py/issues/1701
|
||||
# hack workaround
|
||||
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 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)
|
||||
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
|
||||
|
||||
|
||||
r = self.make_request(method, suspect_params)
|
||||
return r
|
||||
1
crypto_dev_signer/keystore/__init__.py
Normal file
1
crypto_dev_signer/keystore/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .postgres import ReferenceKeystore
|
||||
8
crypto_dev_signer/keystore/interface.py
Normal file
8
crypto_dev_signer/keystore/interface.py
Normal file
@@ -0,0 +1,8 @@
|
||||
class Keystore:
|
||||
|
||||
def get(self, address, password=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def new(self, password=None):
|
||||
raise NotImplementedError
|
||||
|
||||
95
crypto_dev_signer/keystore/postgres.py
Normal file
95
crypto_dev_signer/keystore/postgres.py
Normal file
@@ -0,0 +1,95 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import base64
|
||||
import os
|
||||
|
||||
# third-party imports
|
||||
from cryptography.fernet import Fernet
|
||||
import psycopg2
|
||||
from psycopg2 import sql
|
||||
from eth_keys import KeyAPI
|
||||
from eth_keys.backends import NativeECCBackend
|
||||
import sha3
|
||||
|
||||
# local imports
|
||||
from crypto_dev_signer.common import strip_hex_prefix
|
||||
from .interface import Keystore
|
||||
|
||||
keyapi = KeyAPI(NativeECCBackend)
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
|
||||
def to_bytes(x):
|
||||
return x.encode('utf-8')
|
||||
|
||||
|
||||
class ReferenceKeystore(Keystore):
|
||||
|
||||
schema = [
|
||||
"""CREATE TABLE ethereum (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
key_ciphertext VARCHAR(256) NOT NULL,
|
||||
wallet_address_hex CHAR(40) NOT NULL
|
||||
);
|
||||
""",
|
||||
"""CREATE UNIQUE INDEX ethereum_address_idx ON ethereum ( wallet_address_hex );
|
||||
""",
|
||||
]
|
||||
|
||||
def __init__(self, dbname, **kwargs):
|
||||
self.conn = psycopg2.connect('dbname=' + dbname)
|
||||
self.cur = self.conn.cursor()
|
||||
self.symmetric_key = kwargs.get('symmetric_key')
|
||||
|
||||
|
||||
def get(self, address, password=None):
|
||||
safe_address = strip_hex_prefix(address)
|
||||
s = sql.SQL('SELECT key_ciphertext FROM ethereum WHERE wallet_address_hex = %s')
|
||||
self.cur.execute(s, [ safe_address ] )
|
||||
k = self.cur.fetchone()[0]
|
||||
return self._decrypt(k, password)
|
||||
|
||||
|
||||
def new(self, password=None):
|
||||
b = os.urandom(32)
|
||||
pk = keyapi.PrivateKey(b)
|
||||
|
||||
pubk = keyapi.private_key_to_public_key(pk)
|
||||
address_hex = pubk.to_checksum_address()
|
||||
address_hex_clean = strip_hex_prefix(address_hex)
|
||||
|
||||
logg.debug('address {}'.format(address_hex_clean))
|
||||
c = self._encrypt(pk.to_bytes(), password)
|
||||
s = sql.SQL('INSERT INTO ethereum (wallet_address_hex, key_ciphertext) VALUES (%s, %s)')
|
||||
self.cur.execute(s, [ address_hex_clean, c.decode('utf-8') ])
|
||||
self.conn.commit()
|
||||
return address_hex
|
||||
|
||||
|
||||
def _encrypt(self, private_key, password):
|
||||
f = self._generate_encryption_engine(password)
|
||||
return f.encrypt(private_key)
|
||||
|
||||
|
||||
def _generate_encryption_engine(self, password):
|
||||
h = sha3.keccak_256()
|
||||
h.update(self.symmetric_key)
|
||||
if password != None:
|
||||
password_bytes = to_bytes(password)
|
||||
h.update(password_bytes)
|
||||
g = h.digest()
|
||||
return Fernet(base64.b64encode(g))
|
||||
|
||||
|
||||
def _decrypt(self, c, password):
|
||||
f = self._generate_encryption_engine(password)
|
||||
return f.decrypt(c.encode('utf-8'))
|
||||
|
||||
|
||||
def __del__(self):
|
||||
logg.debug('closing database')
|
||||
self.conn.commit()
|
||||
self.cur.close()
|
||||
self.conn.close()
|
||||
Reference in New Issue
Block a user