tFix can_sign and cold storage - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit b1b15f510cc1496a765ac75cafdccef4d2af2c94 DIR parent abeb7818792b24aa029bc0572abfbea8cb8210c9 HTML Author: ThomasV <thomasv@electrum.org> Date: Thu, 1 Sep 2016 13:52:49 +0200 Fix can_sign and cold storage Diffstat: M lib/keystore.py | 87 ++++++++++++++++++++----------- M lib/wallet.py | 28 +++++++--------------------- 2 files changed, 64 insertions(+), 51 deletions(-) --- DIR diff --git a/lib/keystore.py b/lib/keystore.py t@@ -49,6 +49,32 @@ class KeyStore(PrintError): def can_import(self): return False + def get_tx_derivations(self, tx): + keypairs = {} + for txin in tx.inputs(): + num_sig = txin.get('num_sig') + if num_sig is None: + continue + x_signatures = txin['signatures'] + signatures = filter(None, x_signatures) + if len(signatures) == num_sig: + # input is complete + continue + for k, x_pubkey in enumerate(txin['x_pubkeys']): + if x_signatures[k] is not None: + # this pubkey already signed + continue + derivation = self.get_pubkey_derivation(x_pubkey) + if not derivation: + continue + keypairs[x_pubkey] = derivation + return keypairs + + def can_sign(self, tx): + if self.is_watching_only(): + return False + return bool(self.get_tx_derivations(tx)) + class Software_KeyStore(KeyStore): t@@ -70,32 +96,15 @@ class Software_KeyStore(KeyStore): decrypted = ec.decrypt_message(message) return decrypted - def get_keypairs_for_sig(self, tx, password): - keypairs = {} - for txin in tx.inputs(): - num_sig = txin.get('num_sig') - if num_sig is None: - continue - x_signatures = txin['signatures'] - signatures = filter(None, x_signatures) - if len(signatures) == num_sig: - # input is complete - continue - for k, x_pubkey in enumerate(txin['x_pubkeys']): - if x_signatures[k] is not None: - # this pubkey already signed - continue - derivation = txin['derivation'] - sec = self.get_private_key(derivation, password) - if sec: - keypairs[x_pubkey] = sec - return keypairs - def sign_transaction(self, tx, password): + if self.is_watching_only(): + return # Raise if password is not correct. self.check_password(password) # Add private keys - keypairs = self.get_keypairs_for_sig(tx, password) + keypairs = self.get_tx_derivations(tx) + for k, v in keypairs.items(): + keypairs[k] = self.get_private_key(v, password) # Sign if keypairs: tx.sign(keypairs) t@@ -157,13 +166,19 @@ class Imported_KeyStore(Software_KeyStore): def get_private_key(self, sequence, password): for_change, i = sequence assert for_change == 0 - pubkey = (self.change_pubkeys if for_change else self.receiving_pubkeys)[i] + pubkey = self.receiving_pubkeys[i] pk = pw_decode(self.keypairs[pubkey], password) # this checks the password if pubkey != public_key_from_private_key(pk): raise InvalidPassword() return pk + def get_pubkey_derivation(self, pubkey): + if pubkey not in self.receiving_keys: + return + i = self.receiving_keys.index(pubkey) + return (False, i) + def update_password(self, old_password, new_password): if old_password is not None: self.check_password(old_password) t@@ -255,6 +270,14 @@ class Xpub: assert len(s) == 2 return xkey, s + def get_pubkey_derivation(self, x_pubkey): + if x_pubkey[0:2] != 'ff': + return + xpub, derivation = self.parse_xpubkey(x_pubkey) + if self.xpub != xpub: + return + return derivation + class BIP32_KeyStore(Deterministic_KeyStore, Xpub): t@@ -301,7 +324,6 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub): def is_watching_only(self): return self.xprv is None - def get_mnemonic(self, password): return self.get_seed(password) t@@ -314,9 +336,6 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub): xprv, xpub = bip32_private_derivation(xprv, "m/", derivation) self.add_xprv(xprv) - def can_sign(self, xpub): - return xpub == self.xpub and self.xprv is not None - def get_private_key(self, sequence, password): xprv = self.get_master_private_key(password) _, _, _, c, k = deserialize_xkey(xprv) t@@ -324,6 +343,7 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub): return pk + class Old_KeyStore(Deterministic_KeyStore): def __init__(self, d): t@@ -430,8 +450,7 @@ class Old_KeyStore(Deterministic_KeyStore): def get_xpubkey(self, for_change, n): s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change, n))) - x_pubkey = 'fe' + self.mpk + s - return x_pubkey + return 'fe' + self.mpk + s @classmethod def parse_xpubkey(self, x_pubkey): t@@ -447,6 +466,14 @@ class Old_KeyStore(Deterministic_KeyStore): assert len(s) == 2 return mpk, s + def get_pubkey_derivation(self, x_pubkey): + if x_pubkey[0:2] != 'fe': + return + mpk, derivation = self.parse_xpubkey(x_pubkey) + if self.mpk != mpk: + return + return derivation + def update_password(self, old_password, new_password): if old_password is not None: self.check_password(old_password) t@@ -550,7 +577,7 @@ def xpubkey_to_address(x_pubkey): pubkey = BIP32_KeyStore.derive_pubkey_from_xpub(xpub, s[0], s[1]) elif x_pubkey[0:2] == 'fe': mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey) - pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk.decode('hex'), s[0], s[1]) + pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1]) elif x_pubkey[0:2] == 'fd': addrtype = ord(x_pubkey[2:4].decode('hex')) hash160 = x_pubkey[4:].decode('hex') DIR diff --git a/lib/wallet.py b/lib/wallet.py t@@ -981,28 +981,21 @@ class Abstract_Wallet(PrintError): address = txin['address'] if self.is_mine(address): self.add_input_sig_info(txin, address) - else: - txin['can_sign'] = False def can_sign(self, tx): - if self.is_watching_only(): - return False if tx.is_complete(): return False - ## add input info. (should be done already) - #for txin in tx.inputs(): - # self.add_input_info(txin) - can_sign = any([txin['can_sign'] for txin in tx.inputs()]) - return can_sign + for k in self.get_keystores(): + if k.can_sign(tx): + return True def get_input_tx(self, tx_hash): # First look up an input transaction in the wallet where it # will likely be. If co-signing a transaction it may not have # all the input txs, in which case we ask the network. tx = self.transactions.get(tx_hash) - if not tx: + if not tx and self.network: request = ('blockchain.transaction.get', [tx_hash]) - # FIXME: what if offline? tx = Transaction(self.network.synchronous_get(request)) return tx t@@ -1014,7 +1007,6 @@ class Abstract_Wallet(PrintError): for txin in tx.inputs(): tx_hash = txin['prevout_hash'] txin['prev_tx'] = self.get_input_tx(tx_hash) - # I should add the address index if it's an address of mine # add output info for hw wallets tx.output_info = [] t@@ -1024,12 +1016,9 @@ class Abstract_Wallet(PrintError): tx.output_info.append((change, address_index)) # sign - for keystore in self.get_keystores(): - if not keystore.is_watching_only(): - try: - keystore.sign_transaction(tx, password) - except: - print "keystore cannot sign", keystore + for k in self.get_keystores(): + k.sign_transaction(tx, password) + def get_unused_addresses(self): # fixme: use slots from expired requests t@@ -1270,7 +1259,6 @@ class P2PK_Wallet(Abstract_Wallet): txin['signatures'] = [None] txin['redeemPubkey'] = pubkey txin['num_sig'] = 1 - txin['can_sign'] = any([x is None for x in txin['signatures']]) def sign_message(self, address, message, password): sequence = self.get_address_index(address) t@@ -1534,8 +1522,6 @@ class Multisig_Wallet(Deterministic_Wallet): txin['signatures'] = [None] * len(pubkeys) txin['redeemScript'] = self.redeem_script(*derivation) txin['num_sig'] = self.m - txin['can_sign'] = any([x is None for x in txin['signatures']]) - wallet_types = ['standard', 'multisig', 'imported']