URI: 
       tfix pubkey ordering in multisig wallets. fix #1975 - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 5e92e09044b718f5e4df092dc048ccf36031da60
   DIR parent 11ba5c441ab745ddec3f0e2176817bcabae0fa17
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Sun, 16 Oct 2016 13:16:40 +0200
       
       fix pubkey ordering in multisig wallets. fix #1975
       
       Diffstat:
         M gui/qt/main_window.py               |      18 +++++++-----------
         M lib/wallet.py                       |      24 +++++++++++++++---------
         M plugins/trezor/plugin.py            |      59 ++++++++++++++++++++-----------
       
       3 files changed, 60 insertions(+), 41 deletions(-)
       ---
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -1667,27 +1667,23 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
            def show_master_public_keys(self):
                dialog = WindowModalDialog(self, "Master Public Keys")
       -        mpk_dict = self.wallet.get_master_public_keys()
       +        mpk_list = self.wallet.get_master_public_keys()
                vbox = QVBoxLayout()
                mpk_text = ShowQRTextEdit()
                mpk_text.setMaximumHeight(100)
                mpk_text.addCopyButton(self.app)
       -        sorted_keys = sorted(mpk_dict.keys())
                def show_mpk(index):
       -            mpk_text.setText(mpk_dict[sorted_keys[index]])
       +            mpk_text.setText(mpk_list[index])
        
                # only show the combobox in case multiple accounts are available
       -        if len(mpk_dict) > 1:
       +        if len(mpk_list) > 1:
                    def label(key):
                        if isinstance(self.wallet, Multisig_Wallet):
       -                    is_mine = False#self.wallet.master_private_keys.has_key(key)
       -                    mine_text = [_("cosigner"), _("self")]
       -                    return "%s (%s)" % (key, mine_text[is_mine])
       -                return key
       -            labels = list(map(label, sorted_keys))
       +                    return _("cosigner") + ' ' + str(i+1)
       +                return ''
       +            labels = [ label(i) for i in range(len(mpk_list))]
                    on_click = lambda clayout: show_mpk(clayout.selected_index())
       -            labels_clayout = ChoicesLayout(_("Master Public Keys"), labels,
       -                                           on_click)
       +            labels_clayout = ChoicesLayout(_("Master Public Keys"), labels, on_click)
                    vbox.addLayout(labels_clayout.layout())
        
                show_mpk(0)
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -1063,11 +1063,17 @@ class Abstract_Wallet(PrintError):
                    txin['prev_tx'] = self.get_input_tx(tx_hash)
        
                # add output info for hw wallets
       -        tx.output_info = []
       -        for i, txout in enumerate(tx.outputs()):
       +        info = {}
       +        xpubs = self.get_master_public_keys()
       +        for txout in tx.outputs():
                    _type, addr, amount = txout
       -            change, address_index = self.get_address_index(addr) if self.is_change(addr) else (None, None)
       -            tx.output_info.append((change, address_index))
       +            if self.is_change(addr):
       +                index = self.get_address_index(addr)
       +                pubkeys = self.get_public_keys(addr)
       +                # sort xpubs using the order of pubkeys
       +                sorted_pubkeys, sorted_xpubs = zip(*sorted(zip(pubkeys, xpubs)))
       +                info[addr] = index, sorted_xpubs, self.m if isinstance(self, Multisig_Wallet) else None
       +        tx.output_info = info
        
                # sign
                for k in self.get_keystores():
       t@@ -1301,7 +1307,7 @@ class Imported_Wallet(Abstract_Wallet):
                return False
        
            def get_master_public_keys(self):
       -        return {}
       +        return []
        
            def is_beyond_limit(self, address, is_change):
                return False
       t@@ -1506,7 +1512,7 @@ class Deterministic_Wallet(Abstract_Wallet):
                return True
        
            def get_master_public_keys(self):
       -        return {'x':self.get_master_public_key()}
       +        return [self.get_master_public_key()]
        
            def get_fingerprint(self):
                return self.get_master_public_key()
       t@@ -1599,7 +1605,7 @@ class Multisig_Wallet(Deterministic_Wallet):
                return address
        
            def new_pubkeys(self, c, i):
       -        return [k.derive_pubkey(c, i) for k in self.keystores.values()]
       +        return [k.derive_pubkey(c, i) for k in self.get_keystores()]
        
            def load_keystore(self):
                self.keystores = {}
       t@@ -1616,7 +1622,7 @@ class Multisig_Wallet(Deterministic_Wallet):
                return self.keystores.get('x1/')
        
            def get_keystores(self):
       -        return self.keystores.values()
       +        return [self.keystores[i] for i in sorted(self.keystores.keys())]
        
            def update_password(self, old_pw, new_pw):
                for name, keystore in self.keystores.items():
       t@@ -1640,7 +1646,7 @@ class Multisig_Wallet(Deterministic_Wallet):
                return self.keystore.get_master_public_key()
        
            def get_master_public_keys(self):
       -        return dict(map(lambda x: (x[0], x[1].get_master_public_key()), self.keystores.items()))
       +        return [k.get_master_public_key() for k in self.get_keystores()]
        
            def get_fingerprint(self):
                return ''.join(sorted(self.get_master_public_keys()))
   DIR diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py
       t@@ -312,31 +312,48 @@ class TrezorCompatiblePlugin(HW_PluginBase):
            def tx_outputs(self, derivation, tx):
                outputs = []
                has_change = False
       -        for i, (_type, address, amount) in enumerate(tx.outputs()):
       -            txoutputtype = self.types.TxOutputType()
       -            txoutputtype.amount = amount
       -            change, index = tx.output_info[i]
       -            if _type == TYPE_SCRIPT:
       -                txoutputtype.script_type = self.types.PAYTOOPRETURN
       -                txoutputtype.op_return_data = address[2:]
       -            elif _type == TYPE_ADDRESS:
       +
       +        for _type, address, amount in tx.outputs():
       +            info = tx.output_info.get(address)
       +            if info is not None and not has_change:
       +                has_change = True # no more than one change address
                        addrtype, hash_160 = bc_address_to_hash_160(address)
       -                if addrtype == 0 and change is not None and not has_change:
       -                    address_path = "%s/%d/%d"%(derivation, change, index)
       -                    address_n = self.client_class.expand_path(address_path)
       -                    txoutputtype.address_n.extend(address_n)
       -                    # do not show more than one change address to device
       -                    has_change = True
       -                else:
       -                    txoutputtype.address = address
       +                index, xpubs, m = info
                        if addrtype == 0:
       -                    txoutputtype.script_type = self.types.PAYTOADDRESS
       +                    address_n = self.client_class.expand_path(derivation + "/%d/%d"%index)
       +                    txoutputtype = self.types.TxOutputType(
       +                        amount = amount,
       +                        script_type = self.types.PAYTOADDRESS,
       +                        address_n = address_n,
       +                    )
                        elif addrtype == 5:
       -                    txoutputtype.script_type = self.types.PAYTOSCRIPTHASH
       -                else:
       -                    raise BaseException('addrtype')
       +                    address_n = self.client_class.expand_path("/%d/%d"%index)
       +                    nodes = map(self.ckd_public.deserialize, xpubs)
       +                    pubkeys = [ self.types.HDNodePathType(node=node, address_n=address_n) for node in nodes]
       +                    multisig = self.types.MultisigRedeemScriptType(
       +                        pubkeys = pubkeys,
       +                        signatures = [b'', b'', b''],
       +                        m = m)
       +                    txoutputtype = self.types.TxOutputType(
       +                        multisig = multisig,
       +                        amount = amount,
       +                        script_type = self.types.PAYTOMULTISIG)
                    else:
       -                raise BaseException('addrtype')
       +                txoutputtype = self.types.TxOutputType()
       +                txoutputtype.amount = amount
       +                if _type == TYPE_SCRIPT:
       +                    txoutputtype.script_type = self.types.PAYTOOPRETURN
       +                    txoutputtype.op_return_data = address[2:]
       +                elif _type == TYPE_ADDRESS:
       +                    addrtype, hash_160 = bc_address_to_hash_160(address)
       +                    if addrtype == 0:
       +                        txoutputtype.script_type = self.types.PAYTOADDRESS
       +                    elif addrtype == 5:
       +                        txoutputtype.script_type = self.types.PAYTOSCRIPTHASH
       +                    else:
       +                        raise BaseException('addrtype')
       +                    txoutputtype.address = address
       +
                    outputs.append(txoutputtype)
        
                return outputs