From 5e92e09044b718f5e4df092dc048ccf36031da60 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 16 Oct 2016 13:16:40 +0200 Subject: [PATCH] fix pubkey ordering in multisig wallets. fix #1975 --- gui/qt/main_window.py | 18 +++++------- lib/wallet.py | 24 ++++++++++------ plugins/trezor/plugin.py | 59 ++++++++++++++++++++++++++-------------- 3 files changed, 60 insertions(+), 41 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 387fd4c39..4b7915893 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -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) diff --git a/lib/wallet.py b/lib/wallet.py index cf7ab60e9..a058409ce 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -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(): @@ -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 @@ -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() @@ -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 = {} @@ -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(): @@ -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())) diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py index 3e7ab582a..2513d91cc 100644 --- a/plugins/trezor/plugin.py +++ b/plugins/trezor/plugin.py @@ -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