funga

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

commit e606384ed27f25a11ffed1c1fd97fcb5dc1c34f9
parent 91b94f5ddf6ef2610354026602fb7be9eb1ffdcf
Author: nolash <dev@holbrook.no>
Date:   Sun, 10 Oct 2021 09:55:15 +0200

Make signer test pass

Diffstat:
Mfunga/eth/keystore/interface.py | 13+++++--------
Dfunga/eth/keystore/reference.py | 109-------------------------------------------------------------------------------
Afunga/eth/keystore/sql.py | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dtests/test_helper.py | 91-------------------------------------------------------------------------------
Mtests/test_keystore_reference.py | 6+++---
5 files changed, 116 insertions(+), 211 deletions(-)

diff --git a/funga/eth/keystore/interface.py b/funga/eth/keystore/interface.py @@ -4,29 +4,26 @@ import json import logging # local imports -from funga.keystore import Keystore from funga.eth.keystore import keyfile from funga.eth.encoding import private_key_from_bytes +from funga.keystore import Keystore logg = logging.getLogger(__name__) -def native_keygen(self): +def native_keygen(*args, **kwargs): return os.urandom(32) class EthKeystore(Keystore): def __init__(self, private_key_generator=native_keygen): - super(Keystore, self).__init__( - private_key_generator=private_key_generator, - private_key_parser=private_key_from_bytes, - keystore_parser=keyfile.from_some, - ) + super(EthKeystore, self).__init__(private_key_generator, private_key_from_bytes, keyfile.from_some) def new(self, password=None): - return self.import_raw_key(b, password) + b = self.private_key_generator() + return self.import_raw_key(b, password=password) def import_raw_key(self, b, password=None): diff --git a/funga/eth/keystore/reference.py b/funga/eth/keystore/reference.py @@ -1,109 +0,0 @@ -# standard imports -import logging -import base64 - -# external imports -from cryptography.fernet import Fernet -#import psycopg2 -#from psycopg2 import sql -#from psycopg2.extensions import make_dsn -from sqlalchemy import create_engine, text -from sqlalchemy.orm import sessionmaker -import sha3 -from hexathon import ( - strip_0x, - add_0x, - ) - -# local imports -from .interface import Keystore -#from . import keyapi -from crypto_dev_signer.error import UnknownAccountError -from crypto_dev_signer.encoding import private_key_to_address - -logg = logging.getLogger(__name__) - - -def to_bytes(x): - return x.encode('utf-8') - - -class ReferenceKeystore(Keystore): - - schema = [ - """CREATE TABLE IF NOT EXISTS ethereum ( - id SERIAL NOT NULL PRIMARY KEY, - key_ciphertext VARCHAR(256) NOT NULL, - wallet_address_hex CHAR(40) NOT NULL - ); -""", - """CREATE UNIQUE INDEX IF NOT EXISTS ethereum_address_idx ON ethereum ( wallet_address_hex ); -""", - ] - - def __init__(self, dsn, **kwargs): - logg.debug('starting db session with dsn {}'.format(dsn)) - self.db_engine = create_engine(dsn) - self.db_session = sessionmaker(bind=self.db_engine)() - for s in self.schema: - self.db_session.execute(s) - self.db_session.commit() - self.symmetric_key = kwargs.get('symmetric_key') - - - def __del__(self): - logg.debug('closing db session') - self.db_session.close() - - - def get(self, address, password=None): - safe_address = strip_0x(address).lower() - s = text('SELECT key_ciphertext FROM ethereum WHERE wallet_address_hex = :a') - r = self.db_session.execute(s, { - 'a': safe_address, - }, - ) - try: - k = r.first()[0] - except TypeError: - self.db_session.rollback() - raise UnknownAccountError(safe_address) - self.db_session.commit() - a = self._decrypt(k, password) - return a - - - def import_key(self, pk, password=None): - address_hex = private_key_to_address(pk) - address_hex_clean = strip_0x(address_hex).lower() - - c = self._encrypt(pk.secret, password) - s = text('INSERT INTO ethereum (wallet_address_hex, key_ciphertext) VALUES (:a, :c)') #%s, %s)') - self.db_session.execute(s, { - 'a': address_hex_clean, - 'c': c.decode('utf-8'), - }, - ) - self.db_session.commit() - logg.info('added private key for address {}'.format(address_hex_clean)) - return add_0x(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')) diff --git a/funga/eth/keystore/sql.py b/funga/eth/keystore/sql.py @@ -0,0 +1,108 @@ +# standard imports +import logging +import base64 + +# external imports +from cryptography.fernet import Fernet +#import psycopg2 +from sqlalchemy import create_engine, text +from sqlalchemy.orm import sessionmaker +import sha3 +from hexathon import ( + strip_0x, + add_0x, + ) + +# local imports +from .interface import EthKeystore +#from . import keyapi +from funga.error import UnknownAccountError +from funga.eth.encoding import private_key_to_address + +logg = logging.getLogger(__name__) + + +def to_bytes(x): + return x.encode('utf-8') + + +class SQLKeystore(EthKeystore): + + schema = [ + """CREATE TABLE IF NOT EXISTS ethereum ( + id SERIAL NOT NULL PRIMARY KEY, + key_ciphertext VARCHAR(256) NOT NULL, + wallet_address_hex CHAR(40) NOT NULL + ); +""", + """CREATE UNIQUE INDEX IF NOT EXISTS ethereum_address_idx ON ethereum ( wallet_address_hex ); +""", + ] + + def __init__(self, dsn, **kwargs): + super(SQLKeystore, self).__init__() + logg.debug('starting db session with dsn {}'.format(dsn)) + self.db_engine = create_engine(dsn) + self.db_session = sessionmaker(bind=self.db_engine)() + for s in self.schema: + self.db_session.execute(s) + self.db_session.commit() + self.symmetric_key = kwargs.get('symmetric_key') + + + def __del__(self): + logg.debug('closing db session') + self.db_session.close() + + + def get(self, address, password=None): + safe_address = strip_0x(address).lower() + s = text('SELECT key_ciphertext FROM ethereum WHERE wallet_address_hex = :a') + r = self.db_session.execute(s, { + 'a': safe_address, + }, + ) + try: + k = r.first()[0] + except TypeError: + self.db_session.rollback() + raise UnknownAccountError(safe_address) + self.db_session.commit() + a = self._decrypt(k, password) + return a + + + def import_key(self, pk, password=None): + address_hex = private_key_to_address(pk) + address_hex_clean = strip_0x(address_hex).lower() + + c = self._encrypt(pk.secret, password) + s = text('INSERT INTO ethereum (wallet_address_hex, key_ciphertext) VALUES (:a, :c)') #%s, %s)') + self.db_session.execute(s, { + 'a': address_hex_clean, + 'c': c.decode('utf-8'), + }, + ) + self.db_session.commit() + logg.info('added private key for address {}'.format(address_hex_clean)) + return add_0x(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')) diff --git a/tests/test_helper.py b/tests/test_helper.py @@ -1,91 +0,0 @@ -# standard imports -import unittest -import logging -import os - -# local imports -from crypto_dev_signer.keystore.dict import DictKeystore -from crypto_dev_signer.eth.signer import ReferenceSigner -from crypto_dev_signer.helper import TxExecutor -#from crypto_dev_signer.eth.helper import EthTxExecutor -from crypto_dev_signer.encoding import to_checksum_address - -logging.basicConfig(level=logging.DEBUG) -logg = logging.getLogger() - -script_dir = os.path.realpath(os.path.dirname(__file__)) - - -class MockEthTxBackend: - - def dispatcher(self, tx): - logg.debug('sender {}'.format(tx)) - return os.urandom(32) - - def reporter(self, tx): - logg.debug('reporter {}'.format(tx)) - - def verifier(self, rcpt): - logg.debug('reporter {}'.format(rcpt)) - - def fee_price_helper(self): - return 21 - - def fee_helper(self, tx): - logg.debug('fee helper tx {}'.format(tx)) - return 2 - - def builder(self, tx): - return tx - - def builder_two(self, tx): - tx['value'] = 10243 - tx['to'] = to_checksum_address('0x' + os.urandom(20).hex()) - tx['data'] = '0x' - if tx.get('feePrice') != None: - tx['gasPrice'] = tx['feePrice'] - del tx['feePrice'] - if tx.get('feeUnits') != None: - tx['gas'] = tx['feeUnits'] - del tx['feeUnits'] - return tx - - -class TestHelper(unittest.TestCase): - - def setUp(self): - logg.debug('setup') - self.db = DictKeystore() - - keystore_filename = 'UTC--2021-01-08T18-37-01.187235289Z--00a329c0648769a73afac7f9381e08fb43dbea72' - keystore_filepath = os.path.join(script_dir, 'testdata', keystore_filename) - - self.address_hex = self.db.import_keystore_file(keystore_filepath, '') - self.signer = ReferenceSigner(self.db) - - - def tearDown(self): - pass - - - def test_helper(self): - backend = MockEthTxBackend() - executor = TxExecutor(self.address_hex, self.signer, backend.builder, backend.dispatcher, backend.reporter, 666, 13, backend.fee_helper, backend.fee_price_helper, backend.verifier) - - tx_ish = {'from': self.address_hex} - executor.sign_and_send([backend.builder_two]) - - -# def test_eth_helper(self): -# backend = MockEthTxBackend() -# w3 = web3.Web3(web3.Web3.HTTPProvider('http://localhost:8545')) -# executor = EthTxExecutor(w3, self.address_hex, self.signer, 1337) -# -# tx_ish = {'from': self.address_hex} -# #executor.sign_and_send([backend.builder, backend.builder_two]) -# with self.assertRaises(ValueError): -# executor.sign_and_send([backend.builder_two]) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_keystore_reference.py b/tests/test_keystore_reference.py @@ -12,8 +12,8 @@ from psycopg2 import sql from cryptography.fernet import Fernet, InvalidToken # local imports -from crypto_dev_signer.keystore.reference import ReferenceKeystore -from crypto_dev_signer.error import UnknownAccountError +from funga.eth.keystore.sql import SQLKeystore +from funga.error import UnknownAccountError logging.basicConfig(level=logging.DEBUG) logg = logging.getLogger() @@ -37,7 +37,7 @@ class TestDatabase(unittest.TestCase): kw = { 'symmetric_key': self.symkey, } - self.db = ReferenceKeystore('postgres+psycopg2://postgres@localhost:5432/signer_test', **kw) + self.db = SQLKeystore('postgres+psycopg2://postgres@localhost:5432/signer_test', **kw) self.address_hex = self.db.new('foo') #self.address_hex = add_0x(address_hex)