URI: 
       tadd message signing/decryption for segwit addresses - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit e299df7b82f626f669098ccc0dbe041934d72d86
   DIR parent 0bc53d34d16b891af78b6ebf86722100789c0ac2
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Thu,  5 Oct 2017 14:44:44 +0200
       
       add message signing/decryption for segwit addresses
       
       Diffstat:
         M gui/qt/main_window.py               |       8 +++-----
         M lib/bitcoin.py                      |      10 ++++++----
         M lib/keystore.py                     |      17 +++++++----------
         M lib/wallet.py                       |      80 +++++++++++++++----------------
       
       4 files changed, 54 insertions(+), 61 deletions(-)
       ---
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -1898,7 +1898,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if not bitcoin.is_address(address):
                    self.show_message('Invalid Bitcoin address.')
                    return
       -        if not bitcoin.is_p2pkh(address):
       +        txin_type = self.wallet.get_txin_type(address)
       +        if txin_type not in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:
                    self.show_message('Cannot sign messages with this type of address.' + '\n\n' + self.msg_sign)
                    return
                if not self.wallet.is_mine(address):
       t@@ -1916,9 +1917,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if not bitcoin.is_address(address):
                    self.show_message('Invalid Bitcoin address.')
                    return
       -        if not bitcoin.is_p2pkh(address):
       -            self.show_message('Cannot verify messages with this type of address.' + '\n\n' + self.msg_sign)
       -            return
                try:
                    # This can throw on invalid base64
                    sig = base64.b64decode(str(signature.toPlainText()))
       t@@ -1932,7 +1930,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
            def sign_verify_message(self, address=''):
                d = WindowModalDialog(self, _('Sign/verify Message'))
       -        d.setMinimumSize(410, 290)
       +        d.setMinimumSize(610, 290)
        
                layout = QGridLayout(d)
        
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -536,8 +536,7 @@ def public_key_from_private_key(pk, compressed):
        def address_from_private_key(sec):
            txin_type, privkey, compressed = deserialize_privkey(sec)
            public_key = public_key_from_private_key(privkey, compressed)
       -    address = pubkey_to_address(txin_type, public_key)
       -    return address
       +    return pubkey_to_address(txin_type, public_key)
        
        def is_segwit_address(addr):
            witver, witprog = segwit_addr.decode(SEGWIT_HRP, addr)
       t@@ -607,8 +606,11 @@ def verify_message(address, sig, message):
                public_key, compressed = pubkey_from_signature(sig, h)
                # check public key using the address
                pubkey = point_to_ser(public_key.pubkey.point, compressed)
       -        addr = public_key_to_p2pkh(pubkey)
       -        if address != addr:
       +        for txin_type in ['p2pkh','p2wpkh','p2wpkh-p2sh']:
       +            addr = pubkey_to_address(txin_type, bh2u(pubkey))
       +            if address == addr:
       +                break
       +        else:
                    raise Exception("Bad signature")
                # check message
                public_key.verify_digest(sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
   DIR diff --git a/lib/keystore.py b/lib/keystore.py
       t@@ -87,14 +87,13 @@ class Software_KeyStore(KeyStore):
                return not self.is_watching_only()
        
            def sign_message(self, sequence, message, password):
       -        privkey = self.get_private_key(sequence, password)
       -        compressed = self.use_compressed_pubkeys
       +        privkey, compressed = self.get_private_key(sequence, password)
                key = regenerate_key(privkey)
                return key.sign_message(message, compressed)
        
            def decrypt_message(self, sequence, message, password):
       -        sec = self.get_private_key(sequence, password)
       -        ec = regenerate_key(sec)
       +        privkey, compressed = self.get_private_key(sequence, password)
       +        ec = regenerate_key(privkey)
                decrypted = ec.decrypt_message(message)
                return decrypted
        
       t@@ -106,7 +105,7 @@ class Software_KeyStore(KeyStore):
                # Add private keys
                keypairs = self.get_tx_derivations(tx)
                for k, v in keypairs.items():
       -            keypairs[k] = self.get_private_key(v, password)
       +            keypairs[k] = self.get_private_key(v, password)[0]
                # Sign
                if keypairs:
                    tx.sign(keypairs)
       t@@ -156,7 +155,7 @@ class Imported_KeyStore(Software_KeyStore):
                # this checks the password
                if pubkey != public_key_from_private_key(privkey, compressed):
                    raise InvalidPassword()
       -        return privkey
       +        return privkey, compressed
        
            def get_pubkey_derivation(self, x_pubkey):
                if x_pubkey[0:2] in ['02', '03', '04']:
       t@@ -279,7 +278,6 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
            def __init__(self, d):
                Xpub.__init__(self)
                Deterministic_KeyStore.__init__(self, d)
       -        self.use_compressed_pubkeys = True
                self.xpub = d.get('xpub')
                self.xprv = d.get('xprv')
        
       t@@ -331,7 +329,7 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
                xprv = self.get_master_private_key(password)
                _, _, _, _, c, k = deserialize_xprv(xprv)
                pk = bip32_private_key(sequence, k, c)
       -        return pk
       +        return pk, True
        
        
        
       t@@ -340,7 +338,6 @@ class Old_KeyStore(Deterministic_KeyStore):
            def __init__(self, d):
                Deterministic_KeyStore.__init__(self, d)
                self.mpk = d.get('mpk')
       -        self.use_compressed_pubkeys = False
        
            def get_hex_seed(self, password):
                return pw_decode(self.seed, password).encode('utf8')
       t@@ -421,7 +418,7 @@ class Old_KeyStore(Deterministic_KeyStore):
                for_change, n = sequence
                secexp = self.stretch_key(seed)
                pk = self.get_private_key_from_stretched_exponent(for_change, n, secexp)
       -        return pk
       +        return pk, False
        
            def check_seed(self, seed):
                secexp = self.stretch_key(seed)
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -273,8 +273,7 @@ class Abstract_Wallet(PrintError):
                if self.is_watching_only():
                    return []
                index = self.get_address_index(address)
       -        pk = self.keystore.get_private_key(index, password)
       -        compressed = self.keystore.use_compressed_pubkeys
       +        pk, compressed = self.keystore.get_private_key(index, password)
                if self.txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']:
                    pubkeys = self.get_public_keys(address)
                    redeem_script = self.pubkeys_to_redeem_script(pubkeys)
       t@@ -282,13 +281,6 @@ class Abstract_Wallet(PrintError):
                    redeem_script = None
                return bitcoin.serialize_privkey(pk, compressed, self.txin_type), redeem_script
        
       -    def get_public_key(self, address):
       -        if self.keystore.can_import():
       -            pubkey = self.get_address_index(address)
       -        else:
       -            sequence = self.get_address_index(address)
       -            pubkey = self.get_pubkey(*sequence)
       -        return pubkey
        
            def get_public_keys(self, address):
                sequence = self.get_address_index(address)
       t@@ -1336,6 +1328,15 @@ class Abstract_Wallet(PrintError):
            def has_password(self):
                return self.storage.get('use_encryption', False)
        
       +    def sign_message(self, address, message, password):
       +        index = self.get_address_index(address)
       +        return self.keystore.sign_message(index, message, password)
       +
       +    def decrypt_message(self, pubkey, message, password):
       +        addr = self.pubkeys_to_address(pubkey)
       +        index = self.get_address_index(addr)
       +        return self.keystore.decrypt_message(index, message, password)
       +
        
        class Imported_Wallet(Abstract_Wallet):
            # wallet made of imported addresses
       t@@ -1429,8 +1430,10 @@ class Imported_Wallet(Abstract_Wallet):
                self.storage.write()
        
            def get_address_index(self, address):
       -        if self.keystore.can_import():
       -            return self.addresses[address]['pubkey']
       +        return self.get_public_key(address)
       +
       +    def get_public_key(self, address):
       +        return self.addresses[address].get('pubkey')
        
            def import_private_key(self, sec, pw, redeem_script=None):
                try:
       t@@ -1461,7 +1464,11 @@ class Imported_Wallet(Abstract_Wallet):
                sec = pw_decode(self.keystore.keypairs[pubkey], password)
                return sec, redeem_script
        
       +    def get_txin_type(self, address):
       +        return self.addresses[address].get('type', 'address')
       +
            def add_input_sig_info(self, txin, address):
       +        txin['type'] = self.get_txin_type(address)
                if self.is_watching_only():
                    addrtype, hash160 = b58_address_to_hash160(address)
                    x_pubkey = 'fd' + bh2u(bytes([addrtype]) + hash160)
       t@@ -1469,8 +1476,6 @@ class Imported_Wallet(Abstract_Wallet):
                    txin['signatures'] = [None]
                    return
        
       -        txin_type = self.addresses[address]['type']
       -        txin['type'] = txin_type
                if txin_type in ['p2pkh', 'p2wkh', 'p2wkh-p2sh']:
                    pubkey = self.addresses[address]['pubkey']
                    txin['num_sig'] = 1
       t@@ -1484,7 +1489,10 @@ class Imported_Wallet(Abstract_Wallet):
                    txin['redeem_script'] = redeem_script
                    txin['signatures'] = [None] * num_keys
        
       -
       +    def pubkeys_to_address(self, pubkey):
       +        for addr, v in self.addresses.items():
       +            if v.get('pubkey') == pubkey:
       +                return addr
        
        class Deterministic_Wallet(Abstract_Wallet):
        
       t@@ -1604,10 +1612,21 @@ class Deterministic_Wallet(Abstract_Wallet):
            def get_fingerprint(self):
                return self.get_master_public_key()
        
       +    def get_txin_type(self, address):
       +        return self.txin_type
       +
        
       -class Simple_Wallet(Abstract_Wallet):
       +class Simple_Deterministic_Wallet(Deterministic_Wallet):
        
       -    """ Wallet with a single pubkey per address """
       +    """ Deterministic Wallet with a single pubkey per address """
       +
       +    def __init__(self, storage):
       +        Deterministic_Wallet.__init__(self, storage)
       +
       +    def get_public_key(self, address):
       +        sequence = self.get_address_index(address)
       +        pubkey = self.get_pubkey(*sequence)
       +        return pubkey
        
            def load_keystore(self):
                self.keystore = load_keystore(self.storage, 'keystore')
       t@@ -1631,30 +1650,12 @@ class Simple_Wallet(Abstract_Wallet):
                return [self.get_public_key(address)]
        
            def add_input_sig_info(self, txin, address):
       -        if not self.keystore.can_import():
       -            derivation = self.get_address_index(address)
       -            x_pubkey = self.keystore.get_xpubkey(*derivation)
       -        else:
       -            x_pubkey = self.get_public_key(address)
       +        derivation = self.get_address_index(address)
       +        x_pubkey = self.keystore.get_xpubkey(*derivation)
                txin['x_pubkeys'] = [x_pubkey]
                txin['signatures'] = [None]
                txin['num_sig'] = 1
        
       -    def sign_message(self, address, message, password):
       -        index = self.get_address_index(address)
       -        return self.keystore.sign_message(index, message, password)
       -
       -    def decrypt_message(self, pubkey, message, password):
       -        addr = self.pubkeys_to_address(pubkey)
       -        index = self.get_address_index(addr)
       -        return self.keystore.decrypt_message(index, message, password)
       -
       -
       -class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet):
       -
       -    def __init__(self, storage):
       -        Deterministic_Wallet.__init__(self, storage)
       -
            def get_master_public_key(self):
                return self.keystore.get_master_public_key()
        
       t@@ -1687,9 +1688,6 @@ class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet):
            def save_keystore(self):
                self.storage.put('keystore', self.keystore.dump())
        
       -    def can_delete_address(self):
       -        return self.keystore.can_import()
       -
            def delete_address(self, address):
                pubkey = self.get_public_key(address)
                self.keystore.delete_imported_key(pubkey)
       t@@ -1698,9 +1696,6 @@ class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet):
                self.save_addresses()
                self.storage.write()
        
       -    def can_import_privkey(self):
       -        return self.keystore.can_import()
       -
        
        
        
       t@@ -1710,6 +1705,7 @@ class Standard_Wallet(Simple_Deterministic_Wallet):
            def pubkeys_to_address(self, pubkey):
                return bitcoin.pubkey_to_address(self.txin_type, pubkey)
        
       +
        class Multisig_Wallet(Deterministic_Wallet):
            # generic m of n
            gap_limit = 20