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:
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):