electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit afe39330dc2dda2c82294bff904a625cff7fb300
parent 428bc539b30d167e9eec77944a695b02319fc814
Author: ThomasV <thomasv@electrum.org>
Date:   Sun, 21 Aug 2016 11:58:15 +0200

update wallet format again, for keystore

Diffstat:
Mlib/base_wizard.py | 16+++++++---------
Mlib/keystore.py | 165++++++++++++++++++++++++++++++++++---------------------------------------------
Mlib/plugins.py | 8++++----
Mlib/storage.py | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mlib/wallet.py | 42+++++++++++++++++-------------------------
Mplugins/trezor/plugin.py | 5+----
Mplugins/trezor/qt_generic.py | 17+++++++++++------
7 files changed, 167 insertions(+), 153 deletions(-)

diff --git a/lib/base_wizard.py b/lib/base_wizard.py @@ -148,7 +148,6 @@ class BaseWizard(object): self.restore_keys_dialog(title=title, message=message, run_next=self.on_restore, is_valid=v) def choose_hw(self): - self.storage.put('key_type', 'hardware') hw_wallet_types, choices = self.plugins.hardware_wallets('create') choices = zip(hw_wallet_types, choices) title = _('Hardware Keystore') @@ -162,7 +161,7 @@ class BaseWizard(object): self.choice_dialog(title=title, message=msg, choices=choices, run_next=self.on_hardware) def on_hardware(self, hw_type): - self.storage.put('hardware_type', hw_type) + self.hw_type = hw_type title = _('Hardware wallet') + ' [%s]' % hw_type message = _('Do you have a device, or do you want to restore a wallet using an existing seed?') choices = [ @@ -178,10 +177,9 @@ class BaseWizard(object): def on_hardware_account_id(self, account_id): from keystore import load_keystore, bip44_derivation derivation = bip44_derivation(int(account_id)) - self.storage.put('derivation', derivation) - name = self.storage.get('hardware_type') - plugin = self.plugins.get_plugin(name) - plugin.on_create_wallet(self.storage, self) + plugin = self.plugins.get_plugin(self.hw_type) + k = plugin.create_keystore(self.hw_type, derivation, self) + self.create_wallet(k, None) def on_hardware_seed(self): self.storage.put('key_type', 'hw_seed') @@ -209,13 +207,13 @@ class BaseWizard(object): derivation = "m/44'/0'/%d'"%account_id self.storage.put('account_id', account_id) k.add_xprv_from_seed(bip32_seed, derivation, password) - k.save(self.storage, 'x/') + self.storage.put('keystore', k.dump()) self.wallet = Standard_Wallet(self.storage) self.run('create_addresses') def create_wallet(self, k, password): if self.wallet_type == 'standard': - k.save(self.storage, 'x/') + self.storage.put('keystore', k.dump()) self.wallet = Standard_Wallet(self.storage) self.run('create_addresses') elif self.wallet_type == 'multisig': @@ -232,7 +230,7 @@ class BaseWizard(object): d = self.storage.get('master_public_keys', {}) if keystore.xpub in d.values(): raise BaseException('duplicate key') - keystore.save(self.storage, 'x%d/'%(i+1)) + self.storage.put('x%d/'%(i+1), keystore.dump()) def add_cosigners(self, password, i): self.add_cosigner_dialog(run_next=lambda x: self.on_cosigner(x, password, i), index=i, is_valid=keystore.is_xpub) diff --git a/lib/keystore.py b/lib/keystore.py @@ -43,9 +43,6 @@ class KeyStore(PrintError): def has_seed(self): return False - def has_password(self): - return False - def is_watching_only(self): return False @@ -57,10 +54,6 @@ class Software_KeyStore(KeyStore): def __init__(self): KeyStore.__init__(self) - self.use_encryption = False - - def has_password(self): - return self.use_encryption def sign_message(self, sequence, message, password): sec = self.get_private_key(sequence, password) @@ -79,9 +72,11 @@ class Software_KeyStore(KeyStore): class Imported_KeyStore(Software_KeyStore): # keystore for imported private keys - def __init__(self): + def __init__(self, d): Software_KeyStore.__init__(self) - self.keypairs = {} + self.keypairs = d.get('keypairs', {}) + self.receiving_pubkeys = self.keypairs.keys() + self.change_pubkeys = [] def is_deterministic(self): return False @@ -92,16 +87,11 @@ class Imported_KeyStore(Software_KeyStore): def get_master_public_key(self): return None - def load(self, storage, name): - self.keypairs = storage.get('keypairs', {}) - self.use_encryption = storage.get('use_encryption', False) - self.receiving_pubkeys = self.keypairs.keys() - self.change_pubkeys = [] - - def save(self, storage, root_name): - storage.put('key_type', 'imported') - storage.put('keypairs', self.keypairs) - storage.put('use_encryption', self.use_encryption) + def dump(self): + return { + 'type': 'imported', + 'keypairs': self.keypairs, + } def can_import(self): return True @@ -148,25 +138,21 @@ class Imported_KeyStore(Software_KeyStore): b = pw_decode(v, old_password) c = pw_encode(b, new_password) self.keypairs[k] = b - self.use_encryption = (new_password is not None) class Deterministic_KeyStore(Software_KeyStore): - def __init__(self): + def __init__(self, d): Software_KeyStore.__init__(self) - self.seed = '' + self.seed = d.get('seed', '') def is_deterministic(self): return True - def load(self, storage, name): - self.seed = storage.get('seed', '') - self.use_encryption = storage.get('use_encryption', False) - - def save(self, storage, name): - storage.put('seed', self.seed) - storage.put('use_encryption', self.use_encryption) + def dump(self): + return { + 'seed': self.seed, + } def has_seed(self): return self.seed != '' @@ -180,7 +166,6 @@ class Deterministic_KeyStore(Software_KeyStore): self.seed_version, self.seed = self.format_seed(seed) if password: self.seed = pw_encode(self.seed, password) - self.use_encryption = (password is not None) def get_seed(self, password): return pw_decode(self.seed, password).encode('utf8') @@ -235,27 +220,21 @@ class Xpub: class BIP32_KeyStore(Deterministic_KeyStore, Xpub): - def __init__(self): + def __init__(self, d): Xpub.__init__(self) - Deterministic_KeyStore.__init__(self) - self.xprv = None + Deterministic_KeyStore.__init__(self, d) + self.xpub = d.get('xpub') + self.xprv = d.get('xprv') def format_seed(self, seed): return NEW_SEED_VERSION, ' '.join(seed.split()) - def load(self, storage, name): - Deterministic_KeyStore.load(self, storage, name) - self.xpub = storage.get('master_public_keys', {}).get(name) - self.xprv = storage.get('master_private_keys', {}).get(name) - - def save(self, storage, name): - Deterministic_KeyStore.save(self, storage, name) - d = storage.get('master_public_keys', {}) - d[name] = self.xpub - storage.put('master_public_keys', d) - d = storage.get('master_private_keys', {}) - d[name] = self.xprv - storage.put('master_private_keys', d) + def dump(self): + d = Deterministic_KeyStore.dump(self) + d['type'] = 'bip32' + d['xpub'] = self.xpub + d['xprv'] = self.xprv + return d def add_master_private_key(self, xprv, password): self.xprv = pw_encode(xprv, password) @@ -279,7 +258,6 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub): if self.xprv is not None: b = pw_decode(self.xprv, old_password) self.xprv = pw_encode(b, new_password) - self.use_encryption = (new_password is not None) def is_watching_only(self): return self.xprv is None @@ -340,18 +318,14 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub): class Old_KeyStore(Deterministic_KeyStore): - def __init__(self): - Deterministic_KeyStore.__init__(self) - self.mpk = None - - def load(self, storage, name): - Deterministic_KeyStore.load(self, storage, name) - self.mpk = storage.get('master_public_key').decode('hex') + def __init__(self, d): + Deterministic_KeyStore.__init__(self, d) + self.mpk = d.get('mpk').decode('hex') - def save(self, storage, name): - Deterministic_KeyStore.save(self, storage, name) - storage.put('wallet_type', 'old') - storage.put('master_public_key', self.mpk.encode('hex')) + def dump(self): + d = Deterministic_KeyStore.dump(self) + d['mpk'] = self.mpk.encode('hex') + return d def add_seed(self, seed, password): Deterministic_KeyStore.add_seed(self, seed, password) @@ -473,7 +447,6 @@ class Old_KeyStore(Deterministic_KeyStore): if self.has_seed(): decoded = self.get_seed(old_password) self.seed = pw_encode(decoded, new_password) - self.use_encryption = (new_password is not None) class Hardware_KeyStore(KeyStore, Xpub): @@ -485,24 +458,26 @@ class Hardware_KeyStore(KeyStore, Xpub): #restore_wallet_class = BIP32_RD_Wallet max_change_outputs = 1 - def __init__(self): + def __init__(self, d): Xpub.__init__(self) KeyStore.__init__(self) # Errors and other user interaction is done through the wallet's # handler. The handler is per-window and preserved across # device reconnects + self.xpub = d.get('xpub') + self.derivation = d.get('derivation') self.handler = None def is_deterministic(self): return True - def load(self, storage, name): - self.xpub = storage.get('master_public_keys', {}).get(name) - - def save(self, storage, name): - d = storage.get('master_public_keys', {}) - d[name] = self.xpub - storage.put('master_public_keys', d) + def dump(self): + return { + 'type': 'hardware', + 'hw_type': self.hw_type, + 'xpub': self.xpub, + 'derivation':self.derivation, + } def unpaired(self): '''A device paired with the wallet was diconnected. This can be @@ -572,37 +547,37 @@ def xpubkey_to_address(x_pubkey): return pubkey, address -keystores = [] +hw_keystores = {} + +def register_keystore(hw_type, constructor): + hw_keystores[hw_type] = constructor + +def hardware_keystore(hw_type, d): + if hw_type in hw_keystores: + constructor = hw_keystores[hw_type] + return constructor(d) + raise BaseException('unknown hardware type', hw_type) def load_keystore(storage, name): - w = storage.get('wallet_type') - t = storage.get('key_type', 'seed') - seed_version = storage.get_seed_version() - if seed_version == OLD_SEED_VERSION or w == 'old': - k = Old_KeyStore() + w = storage.get('wallet_type', 'standard') + d = storage.get(name, {}) + t = d.get('type') + if not t: + raise BaseException('wallet format requires update') + if t == 'old': + k = Old_KeyStore(d) elif t == 'imported': - k = Imported_KeyStore() - elif name and name not in [ 'x/', 'x1/' ]: - k = BIP32_KeyStore() - elif t in ['seed', 'hw_seed']: - k = BIP32_KeyStore() + k = Imported_KeyStore(d) + elif t == 'bip32': + k = BIP32_KeyStore(d) elif t == 'hardware': - hw_type = storage.get('hardware_type') - for cat, _type, constructor in keystores: - if cat == 'hardware' and _type == hw_type: - k = constructor() - break - else: - raise BaseException('unknown hardware type') + hw_type = d.get('hw_type') + k = hardware_keystore(hw_type, d) else: raise BaseException('unknown wallet type', t) - k.load(storage, name) return k -def register_keystore(category, type, constructor): - keystores.append((category, type, constructor)) - def is_old_mpk(mpk): try: @@ -649,35 +624,35 @@ def bip44_derivation(account_id): def from_seed(seed, password): if is_old_seed(seed): - keystore = Old_KeyStore() + keystore = Old_KeyStore({}) keystore.add_seed(seed, password) elif is_new_seed(seed): - keystore = BIP32_KeyStore() + keystore = BIP32_KeyStore({}) keystore.add_seed(seed, password) bip32_seed = Mnemonic.mnemonic_to_seed(seed, '') keystore.add_xprv_from_seed(bip32_seed, "m/", password) return keystore def from_private_key_list(text, password): - keystore = Imported_KeyStore() + keystore = Imported_KeyStore({}) for x in text.split(): keystore.import_key(x, None) keystore.update_password(None, password) return keystore def from_old_mpk(mpk): - keystore = Old_KeyStore() + keystore = Old_KeyStore({}) keystore.add_master_public_key(mpk) return keystore def from_xpub(xpub): - keystore = BIP32_KeyStore() + keystore = BIP32_KeyStore({}) keystore.add_master_public_key(xpub) return keystore def from_xprv(xprv, password): xpub = bitcoin.xpub_from_xprv(xprv) - keystore = BIP32_KeyStore() + keystore = BIP32_KeyStore({}) keystore.add_master_private_key(xprv, password) keystore.add_master_public_key(xpub) return keystore diff --git a/lib/plugins.py b/lib/plugins.py @@ -164,12 +164,12 @@ class Plugins(DaemonThread): def register_keystore(self, name, gui_good, details): from keystore import register_keystore - def dynamic_constructor(): - return self.get_plugin(name).keystore_class() + def dynamic_constructor(d): + return self.get_plugin(name).keystore_class(d) if details[0] == 'hardware': self.hw_wallets[name] = (gui_good, details) - self.print_error("registering keystore %s: %s" %(name, details)) - register_keystore(details[0], details[1], dynamic_constructor) + self.print_error("registering hardware %s: %s" %(name, details)) + register_keystore(details[1], dynamic_constructor) def get_plugin(self, name): if not name in self.plugins: diff --git a/lib/storage.py b/lib/storage.py @@ -37,6 +37,17 @@ from i18n import _ from util import NotEnoughFunds, PrintError, profiler from plugins import run_hook, plugin_loaders from keystore import bip44_derivation +from version import OLD_SEED_VERSION + + +def multisig_type(wallet_type): + '''If wallet_type is mofn multi-sig, return [m, n], + otherwise return None.''' + match = re.match('(\d+)of(\d+)', wallet_type) + if match: + match = [int(x) for x in match.group(1, 2)] + return match + class WalletStorage(PrintError): @@ -198,8 +209,9 @@ class WalletStorage(PrintError): def requires_upgrade(self): r = False - r |= self.convert_wallet_type(True) - r |= self.convert_imported(True) + if self.file_exists: + r |= self.convert_wallet_type(True) + r |= self.convert_imported(True) return r def upgrade(self): @@ -209,17 +221,52 @@ class WalletStorage(PrintError): def convert_wallet_type(self, is_test): assert not self.requires_split() - wallet_type = self.get('wallet_type') - if wallet_type not in ['trezor', 'keepkey']: + d = self.get('keystore', {}) + t = d.get('type') + if t: return False if is_test: return True - self.put('wallet_type', 'standard') - self.put('key_type', 'hardware') - self.put('hardware_type', wallet_type) - xpub = self.get('master_public_keys')["x/0'"] - self.put('master_public_keys', {'x/': xpub}) - self.put('derivation', bip44_derivation(0)) + wallet_type = self.get('wallet_type') + seed_version = self.get_seed_version() + if seed_version == OLD_SEED_VERSION or wallet_type == 'old': + seed = self.get('seed') + d = { + 'type': 'old', + 'seed': seed + } + self.put('wallet_type', 'standard') + self.put('keystore', d) + + elif wallet_type == 'standard': + xpub = self.get('master_public_keys')["x/"] + xprv = self.get('master_private_keys')["x/"] + d = { + 'type': 'bip32', + 'xpub': xpub, + 'xprv': xprv, + 'seed': self.get('seed', '') + } + self.put('wallet_type', 'standard') + self.put('keystore', d) + + elif wallet_type in ['trezor', 'keepkey']: + xpub = self.get('master_public_keys')["x/0'"] + d = { + 'type': 'hardware', + 'hardware_type': wallet_type, + 'xpub': xpub, + 'derivation': bip44_derivation(0), + } + self.put('wallet_type', 'standard') + self.put('keystore', d) + + elif multisig_type(wallet_type): + raise BaseException('not implemented') + + elif wallet_type in ['2fa']: + raise BaseException('not implemented') + def convert_imported(self, test): # '/x' is the internal ID for imported accounts diff --git a/lib/wallet.py b/lib/wallet.py @@ -50,6 +50,7 @@ from util import NotEnoughFunds, PrintError, profiler from bitcoin import * from version import * from keystore import load_keystore +from storage import multisig_type from transaction import Transaction from plugins import run_hook @@ -1164,6 +1165,9 @@ class Abstract_Wallet(PrintError): if self.synchronizer: self.synchronizer.add(address) + def has_password(self): + return self.storage.get('use_encryption', False) + class Imported_Wallet(Abstract_Wallet): # wallet made of imported addresses @@ -1242,7 +1246,7 @@ class P2PK_Wallet(Abstract_Wallet): return public_key_to_bc_address(pubkey.decode('hex')) def load_keystore(self): - self.keystore = load_keystore(self.storage, self.root_name) + self.keystore = load_keystore(self.storage, 'keystore') def get_pubkey(self, c, i): pubkey_list = self.change_pubkeys if c else self.receiving_pubkeys @@ -1402,7 +1406,6 @@ class Deterministic_Wallet(Abstract_Wallet): class Standard_Wallet(Deterministic_Wallet, P2PK_Wallet): - root_name = 'x/' wallet_type = 'standard' def __init__(self, storage): @@ -1426,22 +1429,23 @@ class Standard_Wallet(Deterministic_Wallet, P2PK_Wallet): def can_change_password(self): return self.keystore.can_change_password() - def has_password(self): - return self.keystore.has_password() - def check_password(self, password): self.keystore.check_password(password) def update_password(self, old_pw, new_pw): self.keystore.update_password(old_pw, new_pw) - self.keystore.save(self.storage, self.root_name) + self.save_keystore() + self.storage.put('use_encryption', (new_pw is not None)) + + def save_keystore(self): + self.storage.put('keystore', self.keystore.dump()) def can_import_privkey(self): return self.keystore.can_import() def import_key(self, pk, pw): pubkey = self.keystore.import_key(pk, pw) - self.keystore.save(self.storage, self.root_name) + self.save_keystore() self.receiving_pubkeys.append(pubkey) self.save_pubkeys() addr = self.pubkeys_to_address(pubkey) @@ -1452,12 +1456,11 @@ class Standard_Wallet(Deterministic_Wallet, P2PK_Wallet): class Multisig_Wallet(Deterministic_Wallet): # generic m of n - root_name = "x1/" gap_limit = 20 def __init__(self, storage): self.wallet_type = storage.get('wallet_type') - self.m, self.n = Wallet.multisig_type(self.wallet_type) + self.m, self.n = multisig_type(self.wallet_type) Deterministic_Wallet.__init__(self, storage) def get_pubkeys(self, c, i): @@ -1481,10 +1484,10 @@ class Multisig_Wallet(Deterministic_Wallet): for i in range(self.n): name = 'x%d/'%(i+1) self.keystores[name] = load_keystore(self.storage, name) - self.keystore = self.keystores[self.root_name] + self.keystore = self.keystores['x1/'] def get_keystore(self): - return self.keystores.get(self.root_name) + return self.keystores.get('x1/') def get_keystores(self): return self.keystores.values() @@ -1492,7 +1495,8 @@ class Multisig_Wallet(Deterministic_Wallet): def update_password(self, old_pw, new_pw): for name, keystore in self.keystores.items(): keystore.update_password(old_pw, new_pw) - keystore.save(self.storage, name) + self.storage.put(name, keystore.dump()) + self.storage.put('use_encryption', (new_pw is not None)) def has_seed(self): return self.keystore.has_seed() @@ -1500,9 +1504,6 @@ class Multisig_Wallet(Deterministic_Wallet): def can_change_password(self): return self.keystore.can_change_password() - def has_password(self): - return self.keystore.has_password() - def is_watching_only(self): return not any([not k.is_watching_only() for k in self.get_keystores()]) @@ -1568,18 +1569,9 @@ class Wallet(object): @staticmethod def wallet_class(wallet_type): - if Wallet.multisig_type(wallet_type): + if multisig_type(wallet_type): return Multisig_Wallet if wallet_type in wallet_constructors: return wallet_constructors[wallet_type] raise RuntimeError("Unknown wallet type: " + wallet_type) - @staticmethod - def multisig_type(wallet_type): - '''If wallet_type is mofn multi-sig, return [m, n], - otherwise return None.''' - match = re.match('(\d+)of(\d+)', wallet_type) - if match: - match = [int(x) for x in match.group(1, 2)] - return match - diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py @@ -20,10 +20,7 @@ from ..hw_wallet import HW_PluginBase TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY = range(0, 4) class TrezorCompatibleKeyStore(Hardware_KeyStore): - - def load(self, storage, name): - self.xpub = storage.get('master_public_keys', {}).get(name) - self.derivation = storage.get('derivation') + hw_type = 'trezor' def get_derivation(self): return self.derivation diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py @@ -284,14 +284,13 @@ def qt_plugin_class(base_plugin_class): # Trigger a pairing keystore.thread.add(partial(self.get_client, keystore)) - def on_create_wallet(self, storage, wizard): - from electrum.keystore import load_keystore + def create_keystore(self, hw_type, derivation, wizard): + from electrum.keystore import hardware_keystore handler = self.create_handler(wizard) thread = TaskThread(wizard, wizard.on_error) # Setup device and create accounts in separate thread; wait until done loop = QEventLoop() exc_info = [] - derivation = storage.get('derivation') self.setup_device(derivation, thread, handler, on_done=loop.quit, on_error=lambda info: exc_info.extend(info)) loop.exec_() @@ -299,9 +298,15 @@ def qt_plugin_class(base_plugin_class): if exc_info: wizard.on_error(exc_info) raise UserCancelled - storage.put('master_public_keys', {'/x':self.xpub}) - keystore = load_keystore(storage, '/x') # this calls the dynamic constructor - wizard.create_wallet(keystore, None) + # create keystore + d = { + 'xpub': self.xpub, + 'type': 'hardware', + 'hw_type': hw_type, + 'derivation': derivation + } + k = hardware_keystore(hw_type, d) + return k @hook def receive_menu(self, menu, addrs, wallet):