URI: 
       t - fix sign/verify messages - fix hardware wallet tx_outputs - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit b87c5d12fa09c6d0a53bb34c4d43a957e7c23a0e
   DIR parent b436042c89dc852790bc95287fae18bfe2158031
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Sun, 14 Aug 2016 11:30:38 +0200
       
        - fix sign/verify messages
        - fix hardware wallet tx_outputs
       
       Diffstat:
         M gui/qt/installwizard.py             |      10 ++++++++++
         M gui/qt/main_window.py               |       3 ++-
         M lib/base_wizard.py                  |       1 -
         M lib/bitcoin.py                      |      60 ++++++++++++++++++-------------
         M lib/keystore.py                     |      32 ++++++++++++++++++++++++-------
         M lib/wallet.py                       |      33 +++++++++++++++----------------
         M plugins/trezor/plugin.py            |       9 ++++++---
         M plugins/trezor/qt_generic.py        |       2 +-
       
       8 files changed, 95 insertions(+), 55 deletions(-)
       ---
   DIR diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
       t@@ -372,6 +372,16 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                return action
        
            @wizard_dialog
       +    def input_dialog(self, title, message, run_next):
       +        line = QLineEdit()
       +        vbox = QVBoxLayout()
       +        vbox.addWidget(QLabel(message))
       +        vbox.addWidget(line)
       +        self.set_main_layout(vbox, title)
       +        action = line.text()
       +        return action
       +
       +    @wizard_dialog
            def show_xpub_dialog(self, xpub, run_next):
                msg = ' '.join([
                    _("Here is your master public key."),
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -1849,7 +1849,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
                pubkey_e = QLineEdit()
                if address:
       -            pubkey = self.wallet.get_public_keys(address)[0]
       +            sequence = self.wallet.get_address_index(address)
       +            pubkey = self.wallet.get_pubkey(*sequence)
                    pubkey_e.setText(pubkey)
                layout.addWidget(QLabel(_('Public key')), 2, 0)
                layout.addWidget(pubkey_e, 2, 1)
   DIR diff --git a/lib/base_wizard.py b/lib/base_wizard.py
       t@@ -176,7 +176,6 @@ class BaseWizard(object):
                from keystore import load_keystore
                keystore = load_keystore(self.storage, None)
                keystore.plugin.on_create_wallet(keystore, self)
       -        self.create_wallet(keystore, None)
        
            def on_hardware_seed(self):
                from keystore import load_keystore
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -410,9 +410,17 @@ def msg_magic(message):
            return "\x18Bitcoin Signed Message:\n" + encoded_varint + message
        
        
       -def verify_message(address, signature, message):
       +def verify_message(address, sig, message):
            try:
       -        EC_KEY.verify_message(address, signature, message)
       +        public_key, compressed = pubkey_from_signature(sig, message)
       +        # check public key using the address
       +        pubkey = point_to_ser(public_key.pubkey.point, compressed)
       +        addr = public_key_to_bc_address(pubkey)
       +        if address != addr:
       +            raise Exception("Bad signature")
       +        # check message
       +        h = Hash(msg_magic(message))
       +        public_key.verify_digest(sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
                return True
            except Exception as e:
                print_error("Verification error: {0}".format(e))
       t@@ -493,6 +501,22 @@ class MyVerifyingKey(ecdsa.VerifyingKey):
                return klass.from_public_point( Q, curve )
        
        
       +def pubkey_from_signature(sig, message):
       +    if len(sig) != 65:
       +        raise Exception("Wrong encoding")
       +    nV = ord(sig[0])
       +    if nV < 27 or nV >= 35:
       +        raise Exception("Bad encoding")
       +    if nV >= 31:
       +        compressed = True
       +        nV -= 4
       +    else:
       +        compressed = False
       +    recid = nV - 27
       +    h = Hash(msg_magic(message))
       +    return MyVerifyingKey.from_signature(sig[1:], recid, h, curve = SECP256k1), compressed
       +
       +
        class MySigningKey(ecdsa.SigningKey):
            """Enforce low S values in signatures"""
        
       t@@ -524,41 +548,27 @@ class EC_KEY(object):
                assert public_key.verify_digest(signature, msg_hash, sigdecode = ecdsa.util.sigdecode_string)
                return signature
        
       -    def sign_message(self, message, compressed, address):
       +    def sign_message(self, message, is_compressed):
                signature = self.sign(Hash(msg_magic(message)))
                for i in range(4):
       -            sig = chr(27 + i + (4 if compressed else 0)) + signature
       +            sig = chr(27 + i + (4 if is_compressed else 0)) + signature
                    try:
       -                self.verify_message(address, sig, message)
       +                self.verify_message(sig, message)
                        return sig
                    except Exception:
                        continue
                else:
                    raise Exception("error: cannot sign message")
        
       -    @classmethod
       -    def verify_message(self, address, sig, message):
       -        if len(sig) != 65:
       -            raise Exception("Wrong encoding")
       -        nV = ord(sig[0])
       -        if nV < 27 or nV >= 35:
       -            raise Exception("Bad encoding")
       -        if nV >= 31:
       -            compressed = True
       -            nV -= 4
       -        else:
       -            compressed = False
       -        recid = nV - 27
        
       -        h = Hash(msg_magic(message))
       -        public_key = MyVerifyingKey.from_signature(sig[1:], recid, h, curve = SECP256k1)
       +    def verify_message(self, sig, message):
       +        public_key, compressed = pubkey_from_signature(sig, message)
                # check public key
       -        public_key.verify_digest(sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
       -        pubkey = point_to_ser(public_key.pubkey.point, compressed)
       -        # check that we get the original signing address
       -        addr = public_key_to_bc_address(pubkey)
       -        if address != addr:
       +        if point_to_ser(public_key.pubkey.point, compressed) != point_to_ser(self.pubkey.point, compressed):
                    raise Exception("Bad signature")
       +        # check message
       +        h = Hash(msg_magic(message))
       +        public_key.verify_digest(sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
        
        
            # ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac
   DIR diff --git a/lib/keystore.py b/lib/keystore.py
       t@@ -62,6 +62,19 @@ class Software_KeyStore(KeyStore):
            def has_password(self):
                return self.use_encryption
        
       +    def sign_message(self, sequence, message, password):
       +        sec = self.get_private_key(sequence, password)
       +        key = regenerate_key(sec)
       +        compressed = is_compressed(sec)
       +        return key.sign_message(message, compressed)
       +
       +    def decrypt_message(self, sequence, message, password):
       +        sec = self.get_private_key(sequence, password)
       +        ec = regenerate_key(sec)
       +        decrypted = ec.decrypt_message(message)
       +        return decrypted
       +
       +
        
        class Imported_KeyStore(Software_KeyStore):
            # keystore for imported private keys
       t@@ -109,6 +122,11 @@ class Imported_KeyStore(Software_KeyStore):
            def delete_imported_key(self, key):
                self.keypairs.pop(key)
        
       +    def get_public_key(self, sequence):
       +        for_change, i = sequence
       +        pubkey = (self.change_pubkeys if for_change else self.receiving_pubkeys)[i]
       +        return pubkey
       +
            def get_private_key(self, sequence, password):
                for_change, i = sequence
                assert for_change == 0
       t@@ -296,14 +314,14 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
            def make_seed(self, lang=None):
                return Mnemonic(lang).make_seed()
        
       -    @classmethod
       -    def address_derivation(self, account_id, change, address_index):
       -        account_derivation = self.account_derivation(account_id)
       -        return "%s/%d/%d" % (account_derivation, change, address_index)
       +    #@classmethod
       +    #def address_derivation(self, account_id, change, address_index):
       +    #    account_derivation = self.account_derivation(account_id)
       +    #    return "%s/%d/%d" % (account_derivation, change, address_index)
        
       -    def address_id(self, address):
       -        acc_id, (change, address_index) = self.get_address_index(address)
       -        return self.address_derivation(acc_id, change, address_index)
       +    #def address_id(self, address):
       +    #    acc_id, (change, address_index) = self.get_address_index(address)
       +    #    return self.address_derivation(acc_id, change, address_index)
        
            def add_seed_and_xprv(self, seed, password, passphrase=''):
                xprv, xpub = bip32_root(self.mnemonic_to_seed(seed, passphrase))
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -276,22 +276,6 @@ class Abstract_Wallet(PrintError):
                sequence = self.get_address_index(address)
                return self.get_pubkeys(*sequence)
        
       -    def sign_message(self, address, message, password):
       -        keys = self.get_private_key(address, password)
       -        assert len(keys) == 1
       -        sec = keys[0]
       -        key = regenerate_key(sec)
       -        compressed = is_compressed(sec)
       -        return key.sign_message(message, compressed, address)
       -
       -    def decrypt_message(self, pubkey, message, password):
       -        address = public_key_to_bc_address(pubkey.decode('hex'))
       -        keys = self.get_private_key(address, password)
       -        secret = keys[0]
       -        ec = regenerate_key(secret)
       -        decrypted = ec.decrypt_message(message)
       -        return decrypted
       -
            def add_unverified_tx(self, tx_hash, tx_height):
                # tx will be verified only if height > 0
                if tx_hash not in self.verified_tx:
       t@@ -1036,7 +1020,7 @@ class Abstract_Wallet(PrintError):
                tx.output_info = []
                for i, txout in enumerate(tx.outputs()):
                    _type, addr, amount = txout
       -            change, address_index = self.get_address_index(addr) if self.is_change(addr) else None, None
       +            change, address_index = self.get_address_index(addr) if self.is_change(addr) else (None, None)
                    tx.output_info.append((change, address_index))
        
                # sign
       t@@ -1251,6 +1235,13 @@ class P2PK_Wallet(Abstract_Wallet):
                pubkey_list = self.change_pubkeys if c else self.receiving_pubkeys
                return pubkey_list[i]
        
       +    def get_pubkey_index(self, pubkey):
       +        if pubkey in self.receiving_pubkeys:
       +            return False, self.receiving_pubkeys.index(pubkey)
       +        if pubkey in self.change_pubkeys:
       +            return True, self.change_pubkeys.index(pubkey)
       +        raise BaseExeption('pubkey not found')
       +
            def add_input_sig_info(self, txin, address):
                txin['derivation'] = derivation = self.get_address_index(address)
                x_pubkey = self.keystore.get_xpubkey(*derivation)
       t@@ -1262,6 +1253,14 @@ class P2PK_Wallet(Abstract_Wallet):
                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)
       +        return self.keystore.sign_message(sequence, message, password)
       +
       +    def decrypt_message(self, pubkey, message, password):
       +        sequence = self.get_pubkey_index(pubkey)
       +        return self.keystore.decrypt_message(sequence, message, password)
       +
        
        class Deterministic_Wallet(Abstract_Wallet):
        
   DIR diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py
       t@@ -25,6 +25,9 @@ class TrezorCompatibleKeyStore(Hardware_KeyStore):
            root = "m/44'/0'"
            account_id = 0
        
       +    def load(self, storage, name):
       +        self.xpub = storage.get('master_public_keys', {}).get(name)
       +
            def get_derivation(self):
                return self.root + "/%d'"%self.account_id
        
       t@@ -46,9 +49,9 @@ class TrezorCompatibleKeyStore(Hardware_KeyStore):
                result = client.decrypt_message(address_n, nonce, message, msg_hmac)
                return result.message
        
       -    def sign_message(self, address, message, password):
       +    def sign_message(self, sequence, message, password):
                client = self.get_client()
       -        address_path = self.address_id(address)
       +        address_path = self.get_derivation() + "/%d/%d"%sequence
                address_n = client.expand_path(address_path)
                msg_sig = client.sign_message('Bitcoin', address_n, message)
                return msg_sig.signature
       t@@ -312,7 +315,7 @@ class TrezorCompatiblePlugin(HW_PluginBase):
                        txoutputtype.op_return_data = address[2:]
                    elif _type == TYPE_ADDRESS:
                        if change is not None:
       -                    address_path = "%s/%d/%d/"%(derivation, change, index)
       +                    address_path = "%s/%d/%d"%(derivation, change, index)
                            address_n = self.client_class.expand_path(address_path)
                            txoutputtype.address_n.extend(address_n)
                        else:
   DIR diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py
       t@@ -285,7 +285,6 @@ def qt_plugin_class(base_plugin_class):
                keystore.thread.add(partial(self.get_client, keystore))
        
            def on_create_wallet(self, keystore, wizard):
       -        #assert type(keystore) == self.keystore_class
                keystore.handler = self.create_handler(wizard)
                keystore.thread = TaskThread(wizard, wizard.on_error)
                # Setup device and create accounts in separate thread; wait until done
       t@@ -298,6 +297,7 @@ def qt_plugin_class(base_plugin_class):
                if exc_info:
                    wizard.on_error(exc_info)
                    raise UserCancelled
       +        wizard.create_wallet(keystore, None)
        
            @hook
            def receive_menu(self, menu, addrs, wallet):