diff --git a/electrum/gui/qml/components/NewWalletWizard.qml b/electrum/gui/qml/components/NewWalletWizard.qml index 8a0c13c21..37365ba8c 100644 --- a/electrum/gui/qml/components/NewWalletWizard.qml +++ b/electrum/gui/qml/components/NewWalletWizard.qml @@ -11,6 +11,8 @@ Wizard { signal walletCreated + property alias path: walletdb.path + enter: null // disable transition // State transition functions. These functions are called when the 'Next' diff --git a/electrum/gui/qml/components/Wallets.qml b/electrum/gui/qml/components/Wallets.qml index 9877754e4..11c627ec0 100644 --- a/electrum/gui/qml/components/Wallets.qml +++ b/electrum/gui/qml/components/Wallets.qml @@ -107,6 +107,8 @@ Pane { dialog.open() dialog.walletCreated.connect(function() { Daemon.availableWallets.reload() + // and load the new wallet + Daemon.load_wallet(dialog.path, dialog.wizard_data['password']) }) } } diff --git a/electrum/gui/qml/components/WizardComponents.qml b/electrum/gui/qml/components/WizardComponents.qml index c8dd3df93..7110820ab 100644 --- a/electrum/gui/qml/components/WizardComponents.qml +++ b/electrum/gui/qml/components/WizardComponents.qml @@ -123,18 +123,18 @@ Item { function setWarningText(numwords) { var t = [ - "

", - qsTr("Please save these %1 words on paper (order is important). ").arg(numwords), - qsTr("This seed will allow you to recover your wallet in case of computer failure."), - "

", - "" + qsTr("WARNING") + ":", - "" + '

', + qsTr('Please save these %1 words on paper (order is important).').arg(numwords), + qsTr('This seed will allow you to recover your wallet in case of computer failure.'), + '

', + '' + qsTr('WARNING') + ':', + '' ] - warningtext.text = t.join("") + warningtext.text = t.join(' ') } Flickable { @@ -187,7 +187,7 @@ Item { id: bitcoin onGeneratedSeedChanged: { seedtext.text = generated_seed - setWarningText(generated_seed.split(" ").length) + setWarningText(generated_seed.split(' ').length) } } } @@ -195,16 +195,16 @@ Item { property Component haveseed: Component { WizardComponent { + id: root valid: false onAccept: { wizard_data['seed'] = seedtext.text + wizard_data['seed_type'] = bitcoin.seed_type wizard_data['seed_extend'] = extendcb.checked wizard_data['seed_extra_words'] = extendcb.checked ? customwordstext.text : '' - wizard_data['seed_bip39'] = bip39cb.checked - } - - function checkValid() { + wizard_data['seed_bip39'] = seed_type.getTypeCode() == 'BIP39' + wizard_data['seed_slip39'] = seed_type.getTypeCode() == 'SLIP39' } function setSeedTypeHelpText() { @@ -230,6 +230,10 @@ Item { infotext.text = t[seed_type.currentText] } + function checkValid() { + bitcoin.verify_seed(seedtext.text, seed_type.getTypeCode() == 'BIP39', seed_type.getTypeCode() == 'SLIP39') + } + Flickable { anchors.fill: parent contentHeight: mainLayout.height @@ -243,11 +247,18 @@ Item { Label { text: qsTr('Seed Type') + Layout.fillWidth: true } ComboBox { id: seed_type model: ['Electrum', 'BIP39', 'SLIP39'] - onActivated: setSeedTypeHelpText() + onActivated: { + setSeedTypeHelpText() + checkValid() + } + function getTypeCode() { + return currentText + } } InfoTextArea { id: infotext @@ -263,9 +274,36 @@ Item { Layout.fillWidth: true Layout.columnSpan: 2 onTextChanged: { - checkValid() + validationTimer.restart() + } + + Rectangle { + anchors.fill: contentText + color: 'green' + border.color: Material.accentColor + radius: 2 + } + Label { + id: contentText + anchors.right: parent.right + anchors.bottom: parent.bottom + leftPadding: text != '' ? 16 : 0 + rightPadding: text != '' ? 16 : 0 + font.bold: false + font.pixelSize: 13 } } + TextArea { + id: validationtext + visible: text != '' + Layout.fillWidth: true + readOnly: true + wrapMode: TextInput.WordWrap + background: Rectangle { + color: 'transparent' + } + } + CheckBox { id: extendcb Layout.columnSpan: 2 @@ -284,7 +322,18 @@ Item { Bitcoin { id: bitcoin + onSeedTypeChanged: contentText.text = bitcoin.seed_type + onSeedValidChanged: root.valid = bitcoin.seed_valid + onValidationMessageChanged: validationtext.text = bitcoin.validation_message } + + Timer { + id: validationTimer + interval: 500 + repeat: false + onTriggered: checkValid() + } + Component.onCompleted: { setSeedTypeHelpText() } diff --git a/electrum/gui/qml/qebitcoin.py b/electrum/gui/qml/qebitcoin.py index d4f602235..16ed23a05 100644 --- a/electrum/gui/qml/qebitcoin.py +++ b/electrum/gui/qml/qebitcoin.py @@ -3,6 +3,8 @@ import asyncio from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum.logging import get_logger +from electrum.keystore import bip39_is_checksum_valid +from electrum.slip39 import decode_mnemonic, Slip39Error from electrum import mnemonic class QEBitcoin(QObject): @@ -18,10 +20,28 @@ class QEBitcoin(QObject): seedValidChanged = pyqtSignal() seedValid = False + seedTypeChanged = pyqtSignal() + seedType = '' + + validationMessageChanged = pyqtSignal() + validationMessage = '' + @pyqtProperty('QString', notify=generatedSeedChanged) def generated_seed(self): return self.generatedSeed + @pyqtProperty(bool, notify=seedValidChanged) + def seed_valid(self): + return self.seedValid + + @pyqtProperty('QString', notify=seedTypeChanged) + def seed_type(self): + return self.seedType + + @pyqtProperty('QString', notify=validationMessageChanged) + def validation_message(self): + return self.validationMessage + @pyqtSlot() @pyqtSlot(str) @pyqtSlot(str,str) @@ -36,17 +56,55 @@ class QEBitcoin(QObject): loop = asyncio.get_event_loop() asyncio.run_coroutine_threadsafe(co_gen_seed(seed_type, language), loop) - @pyqtProperty(bool, notify=seedValidChanged) - def seed_valid(self): - return self.seedValid - @pyqtSlot(str) - @pyqtSlot(str,str) - @pyqtSlot(str,str,str) - @pyqtSlot(str,str,str,str) - def verify_seed(self, seed, bip39=False, seed_type='segwit', language='en'): - self._logger.debug('verify seed of type ' + str(seed_type)) - #TODO - #self._logger.debug('seed verified') - #self.seedValidChanged.emit() + @pyqtSlot(str,bool,bool) + @pyqtSlot(str,bool,bool,str,str,str) + def verify_seed(self, seed, bip39=False, slip39=False, wallet_type='standard', language='en'): + self._logger.debug('bip39 ' + str(bip39)) + self._logger.debug('slip39 ' + str(slip39)) + + seed_type = '' + seed_valid = False + validation_message = '' + + if not (bip39 or slip39): + seed_type = mnemonic.seed_type(seed) + if seed_type != '': + seed_valid = True + elif bip39: + is_checksum, is_wordlist = bip39_is_checksum_valid(seed) + status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist' + validation_message = 'BIP39 (%s)' % status + + if is_checksum: + seed_type = 'bip39' + seed_valid = True + seed_valid = False # for now + + elif slip39: # TODO: incomplete impl, this code only validates a single share. + try: + share = decode_mnemonic(seed) + seed_type = 'slip39' + validation_message = 'SLIP39: share #%d in %dof%d scheme' % (share.group_index, share.group_threshold, share.group_count) + except Slip39Error as e: + validation_message = 'SLIP39: %s' % str(e) + seed_valid = False # for now + + # cosigning seed + if wallet_type != 'standard' and seed_type not in ['standard', 'segwit']: + seed_type = '' + seed_valid = False + + self.seedType = seed_type + self.seedTypeChanged.emit() + + if self.validationMessage != validation_message: + self.validationMessage = validation_message + self.validationMessageChanged.emit() + + if self.seedValid != seed_valid: + self.seedValid = seed_valid + self.seedValidChanged.emit() + + self._logger.debug('seed verified: ' + str(seed_valid)) diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py index e98e7dd90..8feeab858 100644 --- a/electrum/gui/qml/qedaemon.py +++ b/electrum/gui/qml/qedaemon.py @@ -6,7 +6,7 @@ from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex from electrum.util import register_callback, get_new_wallet_name from electrum.logging import get_logger from electrum.wallet import Wallet, Abstract_Wallet -from electrum.storage import WalletStorage +from electrum.storage import WalletStorage, StorageReadWriteError from .qewallet import QEWallet @@ -106,6 +106,9 @@ class QEDaemon(QObject): self._logger.debug('load wallet ' + str(self._path)) try: storage = WalletStorage(self._path) + if not storage.file_exists(): + self.couldNotOpenFile.emit() + return except StorageReadWriteError as e: self.couldNotOpenFile.emit() return diff --git a/electrum/gui/qml/qewalletdb.py b/electrum/gui/qml/qewalletdb.py index fe0d3bb16..7a638860f 100644 --- a/electrum/gui/qml/qewalletdb.py +++ b/electrum/gui/qml/qewalletdb.py @@ -59,8 +59,8 @@ class QEWalletDB(QObject): if wallet_path == self._path: return + self._logger.info('setting path: ' + wallet_path) self.reset() - self._logger.warning('path: ' + wallet_path) self._path = wallet_path self.load_storage() @@ -229,6 +229,10 @@ class QEWalletDB(QObject): db.load_plugins() db.write(storage) + # minimally populate self after create + self._password = data['password'] + self.path = path + self.createSuccess.emit() except Exception as e: self._logger.error(str(e))