URI: 
       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']