electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit b87c5d12fa09c6d0a53bb34c4d43a957e7c23a0e
parent b436042c89dc852790bc95287fae18bfe2158031
Author: ThomasV <thomasv@electrum.org>
Date:   Sun, 14 Aug 2016 11:30:38 +0200

 - fix sign/verify messages
 - fix hardware wallet tx_outputs

Diffstat:
Mgui/qt/installwizard.py | 10++++++++++
Mgui/qt/main_window.py | 3++-
Mlib/base_wizard.py | 1-
Mlib/bitcoin.py | 60+++++++++++++++++++++++++++++++++++-------------------------
Mlib/keystore.py | 32+++++++++++++++++++++++++-------
Mlib/wallet.py | 33++++++++++++++++-----------------
Mplugins/trezor/plugin.py | 9++++++---
Mplugins/trezor/qt_generic.py | 2+-
8 files changed, 95 insertions(+), 55 deletions(-)

diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py @@ -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."), diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py @@ -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) diff --git a/lib/base_wizard.py b/lib/base_wizard.py @@ -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 diff --git a/lib/bitcoin.py b/lib/bitcoin.py @@ -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)) @@ -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""" @@ -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 diff --git a/lib/keystore.py b/lib/keystore.py @@ -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 @@ -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 @@ -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)) diff --git a/lib/wallet.py b/lib/wallet.py @@ -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: @@ -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 @@ -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) @@ -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): diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py @@ -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 @@ -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 @@ -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: diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py @@ -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 @@ -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):