sql.py (3659B)
1 # standard imports 2 import logging 3 import base64 4 5 # external imports 6 from cryptography.fernet import Fernet 7 #import psycopg2 8 from sqlalchemy import create_engine, text 9 from sqlalchemy.orm import sessionmaker 10 import sha3 11 from hexathon import ( 12 strip_0x, 13 add_0x, 14 ) 15 16 # local imports 17 from .interface import EthKeystore 18 #from . import keyapi 19 from funga.error import UnknownAccountError 20 from funga.eth.encoding import private_key_to_address 21 22 logg = logging.getLogger(__name__) 23 24 25 def to_bytes(x): 26 return x.encode('utf-8') 27 28 29 class SQLKeystore(EthKeystore): 30 31 schema = [ 32 """CREATE TABLE IF NOT EXISTS ethereum ( 33 id SERIAL NOT NULL PRIMARY KEY, 34 key_ciphertext VARCHAR(256) NOT NULL, 35 wallet_address_hex CHAR(40) NOT NULL 36 ); 37 """, 38 """CREATE UNIQUE INDEX IF NOT EXISTS ethereum_address_idx ON ethereum ( wallet_address_hex ); 39 """, 40 ] 41 42 def __init__(self, dsn, **kwargs): 43 super(SQLKeystore, self).__init__() 44 logg.debug('starting db session with dsn {}'.format(dsn)) 45 self.db_engine = create_engine(dsn) 46 self.db_session = sessionmaker(bind=self.db_engine)() 47 for s in self.schema: 48 self.db_session.execute(s) 49 self.db_session.commit() 50 self.symmetric_key = kwargs.get('symmetric_key') 51 self.__rs = None 52 self.__rs_crsr = 0 53 54 55 def __del__(self): 56 logg.debug('closing db session') 57 self.db_session.close() 58 59 60 def get(self, address, password=None): 61 safe_address = strip_0x(address).lower() 62 s = text('SELECT key_ciphertext FROM ethereum WHERE wallet_address_hex = :a') 63 r = self.db_session.execute(s, { 64 'a': safe_address, 65 }, 66 ) 67 try: 68 k = r.first()[0] 69 except TypeError: 70 self.db_session.rollback() 71 raise UnknownAccountError(safe_address) 72 self.db_session.commit() 73 a = self._decrypt(k, password) 74 return a 75 76 77 def list(self): 78 s = text('SELECT wallet_address_hex FROM ethereum') 79 self.__rs = self.db_session.execute(s) 80 addresses = [] 81 for r in self.__rs: 82 addresses.append(r) 83 return addresses 84 85 86 def import_key(self, pk, password=None): 87 address_hex = private_key_to_address(pk) 88 address_hex_clean = strip_0x(address_hex).lower() 89 90 c = self._encrypt(pk.secret, password) 91 s = text('INSERT INTO ethereum (wallet_address_hex, key_ciphertext) VALUES (:a, :c)') #%s, %s)') 92 self.db_session.execute(s, { 93 'a': address_hex_clean, 94 'c': c.decode('utf-8'), 95 }, 96 ) 97 self.db_session.commit() 98 logg.info('added private key for address {}'.format(address_hex_clean)) 99 return add_0x(address_hex) 100 101 102 def _encrypt(self, private_key, password): 103 f = self._generate_encryption_engine(password) 104 return f.encrypt(private_key) 105 106 107 def _generate_encryption_engine(self, password): 108 h = sha3.keccak_256() 109 h.update(self.symmetric_key) 110 if password != None: 111 password_bytes = to_bytes(password) 112 h.update(password_bytes) 113 g = h.digest() 114 return Fernet(base64.b64encode(g)) 115 116 117 def _decrypt(self, c, password): 118 f = self._generate_encryption_engine(password) 119 return f.decrypt(c.encode('utf-8'))