Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
c5d045a498
@ -33,6 +33,7 @@ class InstallWizard(QDialog):
|
|||||||
self.network = network
|
self.network = network
|
||||||
self.storage = storage
|
self.storage = storage
|
||||||
self.setMinimumSize(575, 400)
|
self.setMinimumSize(575, 400)
|
||||||
|
self.setMaximumSize(575, 400)
|
||||||
self.setWindowTitle('Electrum')
|
self.setWindowTitle('Electrum')
|
||||||
self.connect(self, QtCore.SIGNAL('accept'), self.accept)
|
self.connect(self, QtCore.SIGNAL('accept'), self.accept)
|
||||||
|
|
||||||
@ -313,16 +314,19 @@ class InstallWizard(QDialog):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def question(self, msg, icon=None):
|
def question(self, msg, yes_label=_('OK'), no_label=_('Cancel'), icon=None):
|
||||||
vbox = QVBoxLayout()
|
vbox = QVBoxLayout()
|
||||||
self.set_layout(vbox)
|
self.set_layout(vbox)
|
||||||
if icon:
|
if icon:
|
||||||
logo = QLabel()
|
logo = QLabel()
|
||||||
logo.setPixmap(icon)
|
logo.setPixmap(icon)
|
||||||
vbox.addWidget(logo)
|
vbox.addWidget(logo)
|
||||||
vbox.addWidget(QLabel(msg))
|
|
||||||
|
label = QLabel(msg)
|
||||||
|
label.setWordWrap(True)
|
||||||
|
vbox.addWidget(label)
|
||||||
vbox.addStretch(1)
|
vbox.addStretch(1)
|
||||||
vbox.addLayout(ok_cancel_buttons(self, _('OK')))
|
vbox.addLayout(ok_cancel_buttons(self, yes_label, no_label))
|
||||||
if not self.exec_():
|
if not self.exec_():
|
||||||
return None
|
return None
|
||||||
return True
|
return True
|
||||||
@ -343,29 +347,6 @@ class InstallWizard(QDialog):
|
|||||||
return run_password_dialog(self, None, self)[2]
|
return run_password_dialog(self, None, self)[2]
|
||||||
|
|
||||||
|
|
||||||
def create_cold_seed(self, wallet):
|
|
||||||
from electrum.bitcoin import mnemonic_to_seed, bip32_root
|
|
||||||
msg = _('You are about to generate the cold storage seed of your wallet.') + '\n' \
|
|
||||||
+ _('For safety, you should do this on an offline computer.')
|
|
||||||
icon = QPixmap( ':icons/cold_seed.png').scaledToWidth(56)
|
|
||||||
if not self.question(msg, icon):
|
|
||||||
return
|
|
||||||
|
|
||||||
cold_seed = wallet.make_seed()
|
|
||||||
if not self.show_seed(cold_seed, 'cold'):
|
|
||||||
return
|
|
||||||
if not self.verify_seed(cold_seed, 'cold'):
|
|
||||||
return
|
|
||||||
|
|
||||||
hex_seed = mnemonic_to_seed(cold_seed,'').encode('hex')
|
|
||||||
xpriv, xpub = bip32_root(hex_seed)
|
|
||||||
wallet.add_master_public_key('cold/', xpub)
|
|
||||||
|
|
||||||
msg = _('Your master public key was saved in your wallet file.') + '\n'\
|
|
||||||
+ _('Your cold seed must be stored on paper; it is not in the wallet file.')+ '\n\n' \
|
|
||||||
+ _('This program is about to close itself.') + '\n'\
|
|
||||||
+ _('You will need to reopen your wallet on an online computer, in order to complete the creation of your wallet')
|
|
||||||
self.show_message(msg)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -429,14 +410,13 @@ class InstallWizard(QDialog):
|
|||||||
return
|
return
|
||||||
self.waiting_dialog(wallet.synchronize)
|
self.waiting_dialog(wallet.synchronize)
|
||||||
|
|
||||||
elif action == 'create_cold_seed':
|
|
||||||
self.create_cold_seed(wallet)
|
|
||||||
return
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
r = run_hook('install_wizard_action', self, wallet, action)
|
f = run_hook('get_wizard_action', self, wallet, action)
|
||||||
if not r:
|
if not f:
|
||||||
raise BaseException('unknown wizard action', action)
|
raise BaseException('unknown wizard action', action)
|
||||||
|
r = f(wallet, self)
|
||||||
|
if not r:
|
||||||
|
return
|
||||||
|
|
||||||
# next action
|
# next action
|
||||||
action = wallet.get_action()
|
action = wallet.get_action()
|
||||||
@ -558,6 +538,7 @@ class InstallWizard(QDialog):
|
|||||||
wallet.create_main_account(password)
|
wallet.create_main_account(password)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
self.storage.put('wallet_type', t)
|
||||||
wallet = run_hook('installwizard_restore', self, self.storage)
|
wallet = run_hook('installwizard_restore', self, self.storage)
|
||||||
if not wallet:
|
if not wallet:
|
||||||
return
|
return
|
||||||
|
|||||||
@ -1988,6 +1988,7 @@ class ElectrumWindow(QMainWindow):
|
|||||||
decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
|
decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
|
||||||
message_e.setText(decrypted)
|
message_e.setText(decrypted)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
traceback.print_exc(file=sys.stdout)
|
||||||
self.show_message(str(e))
|
self.show_message(str(e))
|
||||||
|
|
||||||
|
|
||||||
@ -1998,6 +1999,7 @@ class ElectrumWindow(QMainWindow):
|
|||||||
encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
|
encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
|
||||||
encrypted_e.setText(encrypted)
|
encrypted_e.setText(encrypted)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
traceback.print_exc(file=sys.stdout)
|
||||||
self.show_message(str(e))
|
self.show_message(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -94,10 +94,10 @@ def close_button(dialog, label=_("Close") ):
|
|||||||
b.setDefault(True)
|
b.setDefault(True)
|
||||||
return hbox
|
return hbox
|
||||||
|
|
||||||
def ok_cancel_buttons2(dialog, ok_label=_("OK") ):
|
def ok_cancel_buttons2(dialog, ok_label=_("OK"), cancel_label=_('Cancel')):
|
||||||
hbox = QHBoxLayout()
|
hbox = QHBoxLayout()
|
||||||
hbox.addStretch(1)
|
hbox.addStretch(1)
|
||||||
b = QPushButton(_("Cancel"))
|
b = QPushButton(cancel_label)
|
||||||
hbox.addWidget(b)
|
hbox.addWidget(b)
|
||||||
b.clicked.connect(dialog.reject)
|
b.clicked.connect(dialog.reject)
|
||||||
b = QPushButton(ok_label)
|
b = QPushButton(ok_label)
|
||||||
@ -106,8 +106,8 @@ def ok_cancel_buttons2(dialog, ok_label=_("OK") ):
|
|||||||
b.setDefault(True)
|
b.setDefault(True)
|
||||||
return hbox, b
|
return hbox, b
|
||||||
|
|
||||||
def ok_cancel_buttons(dialog, ok_label=_("OK") ):
|
def ok_cancel_buttons(dialog, ok_label=_("OK"), cancel_label=_('Cancel')):
|
||||||
hbox, b = ok_cancel_buttons2(dialog, ok_label)
|
hbox, b = ok_cancel_buttons2(dialog, ok_label, cancel_label)
|
||||||
return hbox
|
return hbox
|
||||||
|
|
||||||
def line_dialog(parent, title, label, ok_label, default=None):
|
def line_dialog(parent, title, label, ok_label, default=None):
|
||||||
|
|||||||
@ -25,5 +25,6 @@
|
|||||||
<file>icons/network.png</file>
|
<file>icons/network.png</file>
|
||||||
<file>icons/dark_background.png</file>
|
<file>icons/dark_background.png</file>
|
||||||
<file>icons/qrcode.png</file>
|
<file>icons/qrcode.png</file>
|
||||||
|
<file>icons/trustedcoin.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
BIN
icons/trustedcoin.png
Normal file
BIN
icons/trustedcoin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
@ -706,10 +706,9 @@ def xpub_from_xprv(xprv, testnet=False):
|
|||||||
return EncodeBase58Check(xpub)
|
return EncodeBase58Check(xpub)
|
||||||
|
|
||||||
|
|
||||||
def bip32_root(mnemonic_seed, testnet=False):
|
def bip32_root(seed, testnet=False):
|
||||||
import hmac
|
import hmac
|
||||||
header_pub, header_priv = _get_headers(testnet)
|
header_pub, header_priv = _get_headers(testnet)
|
||||||
seed = mnemonic_to_seed(mnemonic_seed,'')
|
|
||||||
I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest()
|
I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest()
|
||||||
master_k = I[0:32]
|
master_k = I[0:32]
|
||||||
master_c = I[32:]
|
master_c = I[32:]
|
||||||
|
|||||||
@ -320,7 +320,7 @@ class Commands:
|
|||||||
|
|
||||||
label, is_default_label = self.wallet.get_label(tx_hash)
|
label, is_default_label = self.wallet.get_label(tx_hash)
|
||||||
|
|
||||||
out.append({'txid':tx_hash, 'date':"%16s"%time_str, 'label':label, 'value':format_satoshis(value)})
|
out.append({'txid':tx_hash, 'date':"%16s"%time_str, 'label':label, 'value':format_satoshis(value), 'confirmations':conf})
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def setlabel(self, key, label):
|
def setlabel(self, key, label):
|
||||||
|
|||||||
@ -50,6 +50,7 @@ def run_hook(name, *args):
|
|||||||
except Exception:
|
except Exception:
|
||||||
print_error("Plugin error")
|
print_error("Plugin error")
|
||||||
traceback.print_exc(file=sys.stdout)
|
traceback.print_exc(file=sys.stdout)
|
||||||
|
r = False
|
||||||
|
|
||||||
if r:
|
if r:
|
||||||
results.append(r)
|
results.append(r)
|
||||||
|
|||||||
@ -68,7 +68,7 @@ class Test_bitcoin(unittest.TestCase):
|
|||||||
assert xprv == "tprv8jTo9vZtZTSiTo6BBDjeQDgLEaipWPsrhYsQpZYoqqJNPKbCyDewkHJZhkoSHiWYCUf1Gm4TFzQxcG4D6s1J9Hsn4whDK7QYyHHokJeUuac"
|
assert xprv == "tprv8jTo9vZtZTSiTo6BBDjeQDgLEaipWPsrhYsQpZYoqqJNPKbCyDewkHJZhkoSHiWYCUf1Gm4TFzQxcG4D6s1J9Hsn4whDK7QYyHHokJeUuac"
|
||||||
|
|
||||||
def _do_test_bip32(self, seed, sequence, testnet):
|
def _do_test_bip32(self, seed, sequence, testnet):
|
||||||
xprv, xpub = bip32_root(seed, testnet)
|
xprv, xpub = bip32_root(seed.decode('hex'), testnet)
|
||||||
assert sequence[0:2] == "m/"
|
assert sequence[0:2] == "m/"
|
||||||
path = 'm'
|
path = 'm'
|
||||||
sequence = sequence[2:]
|
sequence = sequence[2:]
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import tempfile
|
|||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from lib.wallet import WalletStorage, NewWallet
|
from lib.wallet import WalletStorage, NewWallet
|
||||||
@ -97,20 +98,15 @@ class TestWalletStorage(WalletTestCase):
|
|||||||
contents = ""
|
contents = ""
|
||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
contents = f.read()
|
contents = f.read()
|
||||||
self.assertEqual(repr(some_dict), contents)
|
self.assertEqual(some_dict, json.loads(contents))
|
||||||
|
|
||||||
|
|
||||||
class TestNewWallet(WalletTestCase):
|
class TestNewWallet(WalletTestCase):
|
||||||
|
|
||||||
seed_text = "The seed will sprout and grow up tall."
|
seed_text = "travel nowhere air position hill peace suffer parent beautiful rise blood power home crumble teach"
|
||||||
password = "secret"
|
password = "secret"
|
||||||
|
|
||||||
master_xpub = "xpub661MyMwAqRbcGEop5Rnp68oX1ikeFNVMtx1utwXZGRKMmeXVxwBM5UzkwU9nGB1EofZekLDRfi1w5F9P7Vac3PEuWdWHr2gHLW8vp5YyKJ1"
|
|
||||||
master_xpriv = "xprv9s21ZrQH143K3kjLyQFoizrnTgv9qumWXj6K6Z7wi5nNtrCMRPs6XggH6Bbgz9CUgPJnZnV74yUdRSr8qWVELr9QQTgU5aNL33ViMyD9nhs"
|
|
||||||
|
|
||||||
first_account_name = "account1"
|
first_account_name = "account1"
|
||||||
first_account_first_address = "1Jv9pLCJ4Sqr7aDYLGX5QhET4ps5qRcB9V"
|
|
||||||
first_account_second_address = "14n9EsZsgTTc4eC4TxeP1ccP8bXgwxPMmL"
|
|
||||||
|
|
||||||
import_private_key = "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW"
|
import_private_key = "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW"
|
||||||
import_key_address = "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma"
|
import_key_address = "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma"
|
||||||
@ -131,7 +127,8 @@ class TestNewWallet(WalletTestCase):
|
|||||||
# in setUp()
|
# in setUp()
|
||||||
new_dir = tempfile.mkdtemp()
|
new_dir = tempfile.mkdtemp()
|
||||||
config = FakeConfig(new_dir)
|
config = FakeConfig(new_dir)
|
||||||
wallet = NewWallet(config)
|
storage = WalletStorage(config)
|
||||||
|
wallet = NewWallet(storage)
|
||||||
self.assertTrue(wallet.is_watching_only())
|
self.assertTrue(wallet.is_watching_only())
|
||||||
shutil.rmtree(new_dir) # Don't leave useless stuff in /tmp
|
shutil.rmtree(new_dir) # Don't leave useless stuff in /tmp
|
||||||
|
|
||||||
@ -142,22 +139,6 @@ class TestNewWallet(WalletTestCase):
|
|||||||
self.assertEqual(self.wallet.get_seed(self.password), self.seed_text)
|
self.assertEqual(self.wallet.get_seed(self.password), self.seed_text)
|
||||||
self.assertEqual(0, len(self.wallet.addresses()))
|
self.assertEqual(0, len(self.wallet.addresses()))
|
||||||
|
|
||||||
def test_add_account(self):
|
|
||||||
self.wallet.create_account(self.first_account_name, self.password)
|
|
||||||
self.assertEqual(1, len(self.wallet.addresses()))
|
|
||||||
self.assertIn(self.first_account_first_address,
|
|
||||||
self.wallet.addresses())
|
|
||||||
|
|
||||||
def test_add_account_add_address(self):
|
|
||||||
self.wallet.create_account(self.first_account_name, self.password)
|
|
||||||
self.wallet.synchronizer = FakeSynchronizer()
|
|
||||||
|
|
||||||
self.wallet.create_new_address()
|
|
||||||
self.assertEqual(2, len(self.wallet.addresses()))
|
|
||||||
self.assertIn(self.first_account_first_address,
|
|
||||||
self.wallet.addresses())
|
|
||||||
self.assertIn(self.first_account_second_address,
|
|
||||||
self.wallet.addresses())
|
|
||||||
|
|
||||||
def test_key_import(self):
|
def test_key_import(self):
|
||||||
# Wallets have no imported keys by default.
|
# Wallets have no imported keys by default.
|
||||||
|
|||||||
@ -1385,7 +1385,7 @@ class BIP39_Wallet(BIP32_Wallet):
|
|||||||
|
|
||||||
def create_master_keys(self, password):
|
def create_master_keys(self, password):
|
||||||
seed = self.get_seed(password)
|
seed = self.get_seed(password)
|
||||||
xprv, xpub = bip32_root(seed)
|
xprv, xpub = bip32_root(mnemonic_to_seed(seed,''))
|
||||||
xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
|
xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
|
||||||
self.add_master_public_key(self.root_name, xpub)
|
self.add_master_public_key(self.root_name, xpub)
|
||||||
self.add_master_private_key(self.root_name, xprv, password)
|
self.add_master_private_key(self.root_name, xprv, password)
|
||||||
@ -1459,11 +1459,17 @@ class Wallet_2of2(BIP39_Wallet):
|
|||||||
|
|
||||||
def add_cosigner_seed(self, seed, name, password):
|
def add_cosigner_seed(self, seed, name, password):
|
||||||
# we don't store the seed, only the master xpriv
|
# we don't store the seed, only the master xpriv
|
||||||
xprv, xpub = bip32_root(seed)
|
xprv, xpub = bip32_root(mnemonic_to_seed(seed,''))
|
||||||
xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
|
xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
|
||||||
self.add_master_public_key(name, xpub)
|
self.add_master_public_key(name, xpub)
|
||||||
self.add_master_private_key(name, xprv, password)
|
self.add_master_private_key(name, xprv, password)
|
||||||
|
|
||||||
|
def add_cosigner_xpub(self, seed, name):
|
||||||
|
# store only master xpub
|
||||||
|
xprv, xpub = bip32_root(mnemonic_to_seed(seed,''))
|
||||||
|
xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
|
||||||
|
self.add_master_public_key(name, xpub)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Wallet_2of3(Wallet_2of2):
|
class Wallet_2of3(Wallet_2of2):
|
||||||
|
|||||||
@ -45,42 +45,36 @@ class Listener(threading.Thread):
|
|||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.daemon = True
|
self.daemon = True
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.key = None
|
self.keyname = None
|
||||||
|
self.keyhash = None
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.message = None
|
self.message = None
|
||||||
self.delete = False
|
|
||||||
|
def set_key(self, keyname, keyhash):
|
||||||
def set_key(self, key):
|
self.keyname = keyname
|
||||||
self.key = key
|
self.keyhash = keyhash
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.delete = True
|
server.delete(self.keyhash)
|
||||||
|
self.message = None
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
while self.is_running:
|
while self.is_running:
|
||||||
|
if not self.keyhash:
|
||||||
if not self.key:
|
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not self.message:
|
if not self.message:
|
||||||
try:
|
try:
|
||||||
self.message = server.get(self.key)
|
self.message = server.get(self.keyhash)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
util.print_error("cannot contact cosigner pool")
|
util.print_error("cannot contact cosigner pool")
|
||||||
time.sleep(30)
|
time.sleep(30)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.message:
|
if self.message:
|
||||||
self.parent.win.emit(SIGNAL("cosigner:receive"))
|
self.parent.win.emit(SIGNAL("cosigner:receive"))
|
||||||
else:
|
# poll every 30 seconds
|
||||||
if self.delete:
|
|
||||||
# save it to disk
|
|
||||||
server.delete(self.key)
|
|
||||||
self.message = None
|
|
||||||
self.delete = False
|
|
||||||
|
|
||||||
time.sleep(30)
|
time.sleep(30)
|
||||||
|
|
||||||
|
|
||||||
@ -109,21 +103,25 @@ class Plugin(BasePlugin):
|
|||||||
self.load_wallet(self.win.wallet)
|
self.load_wallet(self.win.wallet)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def is_available(self):
|
||||||
|
if self.wallet is None:
|
||||||
|
return True
|
||||||
|
return self.wallet.wallet_type in ['2of2', '2of3']
|
||||||
|
|
||||||
def load_wallet(self, wallet):
|
def load_wallet(self, wallet):
|
||||||
self.wallet = wallet
|
self.wallet = wallet
|
||||||
|
if not self.is_available():
|
||||||
|
return
|
||||||
mpk = self.wallet.get_master_public_keys()
|
mpk = self.wallet.get_master_public_keys()
|
||||||
|
self.cosigner_list = []
|
||||||
self.cold = mpk.get('x2')
|
for key, xpub in mpk.items():
|
||||||
if self.cold:
|
keyname = key + '/' # fixme
|
||||||
self.cold_K = bitcoin.deserialize_xkey(self.cold)[-1].encode('hex')
|
K = bitcoin.deserialize_xkey(xpub)[-1].encode('hex')
|
||||||
self.cold_hash = bitcoin.Hash(self.cold_K).encode('hex')
|
_hash = bitcoin.Hash(K).encode('hex')
|
||||||
|
if self.wallet.master_private_keys.get(keyname):
|
||||||
self.hot = mpk.get('x1')
|
self.listener.set_key(keyname, _hash)
|
||||||
if self.hot:
|
else:
|
||||||
self.hot_K = bitcoin.deserialize_xkey(self.hot)[-1].encode('hex')
|
self.cosigner_list.append((xpub, K, _hash))
|
||||||
self.hot_hash = bitcoin.Hash(self.hot_K).encode('hex')
|
|
||||||
self.listener.set_key(self.hot_hash)
|
|
||||||
|
|
||||||
|
|
||||||
def transaction_dialog(self, d):
|
def transaction_dialog(self, d):
|
||||||
self.send_button = b = QPushButton(_("Send to cosigner"))
|
self.send_button = b = QPushButton(_("Send to cosigner"))
|
||||||
@ -131,18 +129,18 @@ class Plugin(BasePlugin):
|
|||||||
d.buttons.insertWidget(2, b)
|
d.buttons.insertWidget(2, b)
|
||||||
self.transaction_dialog_update(d)
|
self.transaction_dialog_update(d)
|
||||||
|
|
||||||
|
|
||||||
def transaction_dialog_update(self, d):
|
def transaction_dialog_update(self, d):
|
||||||
if d.tx.is_complete():
|
if d.tx.is_complete():
|
||||||
self.send_button.hide()
|
self.send_button.hide()
|
||||||
return
|
return
|
||||||
if self.cosigner_can_sign(d.tx):
|
for xpub, K, _hash in self.cosigner_list:
|
||||||
self.send_button.show()
|
if self.cosigner_can_sign(d.tx, xpub):
|
||||||
|
self.send_button.show()
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
self.send_button.hide()
|
self.send_button.hide()
|
||||||
|
|
||||||
|
def cosigner_can_sign(self, tx, cosigner_xpub):
|
||||||
def cosigner_can_sign(self, tx):
|
|
||||||
from electrum.transaction import x_to_xpub
|
from electrum.transaction import x_to_xpub
|
||||||
xpub_set = set([])
|
xpub_set = set([])
|
||||||
for txin in tx.inputs:
|
for txin in tx.inputs:
|
||||||
@ -151,24 +149,21 @@ class Plugin(BasePlugin):
|
|||||||
if xpub:
|
if xpub:
|
||||||
xpub_set.add(xpub)
|
xpub_set.add(xpub)
|
||||||
|
|
||||||
return self.cold in xpub_set
|
return cosigner_xpub in xpub_set
|
||||||
|
|
||||||
|
|
||||||
def do_send(self, tx):
|
def do_send(self, tx):
|
||||||
if not self.cosigner_can_sign(tx):
|
for xpub, K, _hash in self.cosigner_list:
|
||||||
return
|
if not self.cosigner_can_sign(tx, xpub):
|
||||||
|
continue
|
||||||
message = bitcoin.encrypt_message(tx.raw, self.cold_K)
|
message = bitcoin.encrypt_message(tx.raw, K)
|
||||||
|
try:
|
||||||
try:
|
server.put(_hash, message)
|
||||||
server.put(self.cold_hash, message)
|
except Exception as e:
|
||||||
self.win.show_message("Your transaction was sent to the cosigning pool.\nOpen your cosigner wallet to retrieve it.")
|
self.win.show_message(str(e))
|
||||||
except Exception as e:
|
return
|
||||||
self.win.show_message(str(e))
|
self.win.show_message("Your transaction was sent to the cosigning pool.\nOpen your cosigner wallet to retrieve it.")
|
||||||
|
|
||||||
|
|
||||||
def on_receive(self):
|
def on_receive(self):
|
||||||
|
|
||||||
if self.wallet.use_encryption:
|
if self.wallet.use_encryption:
|
||||||
password = self.win.password_dialog('An encrypted transaction was retrieved from cosigning pool.\nPlease enter your password to decrypt it.')
|
password = self.win.password_dialog('An encrypted transaction was retrieved from cosigning pool.\nPlease enter your password to decrypt it.')
|
||||||
if not password:
|
if not password:
|
||||||
@ -177,11 +172,12 @@ class Plugin(BasePlugin):
|
|||||||
password = None
|
password = None
|
||||||
|
|
||||||
message = self.listener.message
|
message = self.listener.message
|
||||||
xpriv = self.wallet.get_master_private_key('x1/', password)
|
key = self.listener.keyname
|
||||||
if not xpriv:
|
xprv = self.wallet.get_master_private_key(key, password)
|
||||||
|
if not xprv:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
k = bitcoin.deserialize_xkey(xpriv)[-1].encode('hex')
|
k = bitcoin.deserialize_xkey(xprv)[-1].encode('hex')
|
||||||
EC = bitcoin.EC_KEY(k.decode('hex'))
|
EC = bitcoin.EC_KEY(k.decode('hex'))
|
||||||
message = EC.decrypt_message(message)
|
message = EC.decrypt_message(message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -190,7 +186,6 @@ class Plugin(BasePlugin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.listener.clear()
|
self.listener.clear()
|
||||||
|
|
||||||
tx = transaction.Transaction.deserialize(message)
|
tx = transaction.Transaction.deserialize(message)
|
||||||
self.win.show_transaction(tx)
|
self.win.show_transaction(tx)
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL
|
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
|
||||||
import PyQt4.QtCore as QtCore
|
import PyQt4.QtCore as QtCore
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from struct import pack
|
from struct import pack
|
||||||
@ -7,7 +7,7 @@ from time import sleep
|
|||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
|
|
||||||
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
|
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
|
||||||
from electrum_gui.qt.util import ok_cancel_buttons
|
from electrum_gui.qt.util import ok_cancel_buttons, EnterButton
|
||||||
from electrum.account import BIP32_Account
|
from electrum.account import BIP32_Account
|
||||||
from electrum.bitcoin import EncodeBase58Check, public_key_to_bc_address, bc_address_to_hash_160
|
from electrum.bitcoin import EncodeBase58Check, public_key_to_bc_address, bc_address_to_hash_160
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
@ -43,6 +43,7 @@ class Plugin(BasePlugin):
|
|||||||
def __init__(self, gui, name):
|
def __init__(self, gui, name):
|
||||||
BasePlugin.__init__(self, gui, name)
|
BasePlugin.__init__(self, gui, name)
|
||||||
self._is_available = self._init()
|
self._is_available = self._init()
|
||||||
|
self._requires_settings = True
|
||||||
self.wallet = None
|
self.wallet = None
|
||||||
|
|
||||||
def _init(self):
|
def _init(self):
|
||||||
@ -55,6 +56,9 @@ class Plugin(BasePlugin):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def requires_settings(self):
|
||||||
|
return self._requires_settings
|
||||||
|
|
||||||
def set_enabled(self, enabled):
|
def set_enabled(self, enabled):
|
||||||
self.wallet.storage.put('use_' + self.name, enabled)
|
self.wallet.storage.put('use_' + self.name, enabled)
|
||||||
|
|
||||||
@ -77,6 +81,8 @@ class Plugin(BasePlugin):
|
|||||||
wallet_types.append(('trezor', _("Trezor wallet"), TrezorWallet))
|
wallet_types.append(('trezor', _("Trezor wallet"), TrezorWallet))
|
||||||
|
|
||||||
def installwizard_restore(self, wizard, storage):
|
def installwizard_restore(self, wizard, storage):
|
||||||
|
if storage.get('wallet_type') != 'trezor':
|
||||||
|
return
|
||||||
wallet = TrezorWallet(storage)
|
wallet = TrezorWallet(storage)
|
||||||
try:
|
try:
|
||||||
wallet.create_main_account(None)
|
wallet.create_main_account(None)
|
||||||
@ -91,6 +97,41 @@ class Plugin(BasePlugin):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
tx.error = str(e)
|
tx.error = str(e)
|
||||||
|
|
||||||
|
def settings_widget(self, window):
|
||||||
|
return EnterButton(_('Settings'), self.settings_dialog)
|
||||||
|
|
||||||
|
def settings_dialog(self):
|
||||||
|
get_label = lambda: self.wallet.get_client().features.label
|
||||||
|
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
|
||||||
|
|
||||||
|
d = QDialog()
|
||||||
|
layout = QGridLayout(d)
|
||||||
|
layout.addWidget(QLabel("Trezor Options"),0,0)
|
||||||
|
layout.addWidget(QLabel("ID:"),1,0)
|
||||||
|
layout.addWidget(QLabel(" %s" % self.wallet.get_client().get_device_id()),1,1)
|
||||||
|
|
||||||
|
def modify_label():
|
||||||
|
response = QInputDialog().getText(None, "Set New Trezor Label", "New Trezor Label: (upon submission confirm on Trezor)")
|
||||||
|
if not response[1]:
|
||||||
|
return
|
||||||
|
new_label = str(response[0])
|
||||||
|
twd.start("Please confirm label change on Trezor")
|
||||||
|
status = self.wallet.get_client().apply_settings(label=new_label)
|
||||||
|
twd.stop()
|
||||||
|
update_label()
|
||||||
|
|
||||||
|
current_label_label = QLabel()
|
||||||
|
update_label()
|
||||||
|
change_label_button = QPushButton("Modify")
|
||||||
|
change_label_button.clicked.connect(modify_label)
|
||||||
|
layout.addWidget(current_label_label,3,0)
|
||||||
|
layout.addWidget(change_label_button,3,1)
|
||||||
|
|
||||||
|
if d.exec_():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class TrezorWallet(NewWallet):
|
class TrezorWallet(NewWallet):
|
||||||
wallet_type = 'trezor'
|
wallet_type = 'trezor'
|
||||||
@ -138,7 +179,7 @@ class TrezorWallet(NewWallet):
|
|||||||
|
|
||||||
def address_id(self, address):
|
def address_id(self, address):
|
||||||
account_id, (change, address_index) = self.get_address_index(address)
|
account_id, (change, address_index) = self.get_address_index(address)
|
||||||
return "%s/%d/%d" % (account_id, change, address_index)
|
return "44'/0'/%s'/%d/%d" % (account_id, change, address_index)
|
||||||
|
|
||||||
def create_main_account(self, password):
|
def create_main_account(self, password):
|
||||||
self.create_account('Main account', None) #name, empty password
|
self.create_account('Main account', None) #name, empty password
|
||||||
@ -167,12 +208,9 @@ class TrezorWallet(NewWallet):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def decrypt_message(self, pubkey, message, password):
|
def decrypt_message(self, pubkey, message, password):
|
||||||
try:
|
address = public_key_to_bc_address(pubkey.decode('hex'))
|
||||||
address = public_key_to_bc_address(pubkey.decode('hex'))
|
address_path = self.address_id(address)
|
||||||
address_path = self.address_id(address)
|
address_n = self.get_client().expand_path(address_path)
|
||||||
address_n = self.get_client().expand_path(address_path)
|
|
||||||
except Exception, e:
|
|
||||||
raise e
|
|
||||||
try:
|
try:
|
||||||
decrypted_msg = self.get_client().decrypt_message(address_n, b64decode(message))
|
decrypted_msg = self.get_client().decrypt_message(address_n, b64decode(message))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
@ -182,6 +220,8 @@ class TrezorWallet(NewWallet):
|
|||||||
return str(decrypted_msg)
|
return str(decrypted_msg)
|
||||||
|
|
||||||
def sign_message(self, address, message, password):
|
def sign_message(self, address, message, password):
|
||||||
|
if not self.check_proper_device():
|
||||||
|
give_error('Wrong device or password')
|
||||||
try:
|
try:
|
||||||
address_path = self.address_id(address)
|
address_path = self.address_id(address)
|
||||||
address_n = self.get_client().expand_path(address_path)
|
address_n = self.get_client().expand_path(address_path)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user