diff --git a/electrum/gui/qml/components/NewWalletWizard.qml b/electrum/gui/qml/components/NewWalletWizard.qml index 24578c258..61808faf4 100644 --- a/electrum/gui/qml/components/NewWalletWizard.qml +++ b/electrum/gui/qml/components/NewWalletWizard.qml @@ -47,8 +47,12 @@ Wizard { if (wizard_data['seed_type'] != 'bip39' && Daemon.singlePassword) page.last = true break -// case 'masterkey' -// case 'hardware' + case 'masterkey': + page = _loadNextComponent(components.havemasterkey, wizard_data) + page.next.connect(function() {havemasterkeyDone()}) + if (Daemon.singlePassword) + page.last = true + break } } @@ -87,6 +91,12 @@ Wizard { page.last = true } + function havemasterkeyDone(d) { + console.log('have master key done') + var page = _loadNextComponent(components.walletpassword, wizard_data) + page.last = true + } + Item { id: components property Component walletname: Component { @@ -117,6 +127,10 @@ Wizard { WCBIP39Refine {} } + property Component havemasterkey: Component { + WCHaveMasterKey {} + } + property Component walletpassword: Component { WCWalletPassword {} } diff --git a/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml b/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml new file mode 100644 index 000000000..904432fde --- /dev/null +++ b/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml @@ -0,0 +1,86 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 2.1 + +import org.electrum 1.0 + +import "../controls" + +WizardComponent { + id: root + + valid: false + + onAccept: { + wizard_data['master_key'] = masterkey_ta.text + } + + function verifyMasterKey(key) { + return valid = bitcoin.verify_master_key(key) + } + + ColumnLayout { + width: parent.width + + Label { text: qsTr('Create keystore from a master key') } + + RowLayout { + TextArea { + id: masterkey_ta + Layout.fillWidth: true + Layout.minimumHeight: 80 + focus: true + wrapMode: TextEdit.WrapAnywhere + onTextChanged: verifyMasterKey(text) + } + ColumnLayout { + ToolButton { + icon.source: '../../../icons/paste.png' + icon.height: constants.iconSizeMedium + icon.width: constants.iconSizeMedium + onClicked: { + if (verifyMasterKey(AppController.clipboardToText())) + masterkey_ta.text = AppController.clipboardToText() + } + } + ToolButton { + icon.source: '../../../icons/qrcode.png' + icon.height: constants.iconSizeMedium + icon.width: constants.iconSizeMedium + scale: 1.2 + onClicked: { + var scan = qrscan.createObject(root) + scan.onFound.connect(function() { + if (verifyMasterKey(scan.scanData)) + masterkey_ta.text = scan.scanData + scan.destroy() + }) + } + } + } + } + } + + Component { + id: qrscan + QRScan { + width: root.width + height: root.height + + ToolButton { + icon.source: '../../../icons/closebutton.png' + icon.height: constants.iconSizeMedium + icon.width: constants.iconSizeMedium + anchors.right: parent.right + anchors.top: parent.top + onClicked: { + parent.destroy() + } + } + } + } + + Bitcoin { + id: bitcoin + } +} diff --git a/electrum/gui/qml/components/wizard/WCKeystoreType.qml b/electrum/gui/qml/components/wizard/WCKeystoreType.qml index 2e180a640..e8837fe9d 100644 --- a/electrum/gui/qml/components/wizard/WCKeystoreType.qml +++ b/electrum/gui/qml/components/wizard/WCKeystoreType.qml @@ -27,13 +27,13 @@ WizardComponent { text: qsTr('I already have a seed') } RadioButton { - enabled: false ButtonGroup.group: keystoregroup property string keystoretype: 'masterkey' text: qsTr('Use a master key') } RadioButton { enabled: false + visible: false ButtonGroup.group: keystoregroup property string keystoretype: 'hardware' text: qsTr('Use a hardware device') diff --git a/electrum/gui/qml/qebitcoin.py b/electrum/gui/qml/qebitcoin.py index a63aa45f4..91b7cf611 100644 --- a/electrum/gui/qml/qebitcoin.py +++ b/electrum/gui/qml/qebitcoin.py @@ -3,8 +3,9 @@ import asyncio from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum import mnemonic -from electrum.bip32 import is_bip32_derivation -from electrum.keystore import bip39_is_checksum_valid +from electrum import keystore +from electrum.i18n import _ +from electrum.bip32 import is_bip32_derivation, xpub_type from electrum.logging import get_logger from electrum.slip39 import decode_mnemonic, Slip39Error from electrum.util import parse_URI, create_bip21_uri, InvalidBitcoinURI, get_asyncio_loop @@ -46,6 +47,12 @@ class QEBitcoin(QObject): def validation_message(self): return self.validationMessage + @validation_message.setter + def validation_message(self, msg): + if self.validationMessage != msg: + self.validationMessage = msg + self.validationMessageChanged.emit() + @pyqtSlot() @pyqtSlot(str) @pyqtSlot(str,str) @@ -68,16 +75,16 @@ class QEBitcoin(QObject): seed_type = '' seed_valid = False - validation_message = '' + self.validationMessage = '' 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) + is_checksum, is_wordlist = keystore.bip39_is_checksum_valid(seed) status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist' - validation_message = 'BIP39 (%s)' % status + self.validationMessage = 'BIP39 (%s)' % status if is_checksum: seed_type = 'bip39' @@ -87,9 +94,9 @@ class QEBitcoin(QObject): 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) + self.validationMessage = '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) + self.validationMessage = 'SLIP39: %s' % str(e) seed_valid = False # for now # cosigning seed @@ -100,16 +107,32 @@ class QEBitcoin(QObject): 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)) + @pyqtSlot(str, result=bool) + @pyqtSlot(str, str, result=bool) + def verify_master_key(self, key, wallet_type='standard'): + self.validationMessage = '' + if not keystore.is_master_key(key): + self.validationMessage = _('Not a master key') + return False + + if wallet_type == 'standard': + # validation message? + k = keystore.from_master_key(key) + has_xpub = isinstance(k, keystore.Xpub) + assert has_xpub + t1 = xpub_type(k.xpub) + if t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']: + self.validationMessage = '%s: %s' % (_('Wrong key type'), t1) + return False + return True + return False + @pyqtSlot(str, result=bool) def verify_derivation_path(self, path): return is_bip32_derivation(path) diff --git a/electrum/gui/qml/qewalletdb.py b/electrum/gui/qml/qewalletdb.py index 852864ee4..33a736117 100644 --- a/electrum/gui/qml/qewalletdb.py +++ b/electrum/gui/qml/qewalletdb.py @@ -5,7 +5,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum.logging import get_logger from electrum.storage import WalletStorage, StorageEncryptionVersion from electrum.wallet_db import WalletDB -from electrum.bip32 import normalize_bip32_derivation +from electrum.bip32 import normalize_bip32_derivation, xpub_type from electrum.util import InvalidPassword from electrum import keystore @@ -178,6 +178,8 @@ class QEWalletDB(QObject): data = js_data.toVariant() self._logger.debug(str(data)) + assert data['wallet_type'] == 'standard' # only standard wallets for now + if single_password_enabled and single_password: data['encrypt'] = True data['password'] = single_password @@ -188,17 +190,27 @@ class QEWalletDB(QObject): raise Exception('file already exists at path') storage = WalletStorage(path) - if data['seed_type'] in ['old', 'standard', 'segwit']: #2fa, 2fa-segwit - self._logger.debug('creating keystore from electrum seed') - k = keystore.from_seed(data['seed'], data['seed_extra_words'], data['wallet_type'] == 'multisig') - elif data['seed_type'] == 'bip39': - self._logger.debug('creating keystore from bip39 seed') - root_seed = keystore.bip39_to_seed(data['seed'], data['seed_extra_words']) - derivation = normalize_bip32_derivation(data['derivation_path']) - script = data['script_type'] if data['script_type'] != 'p2pkh' else 'standard' - k = keystore.from_bip43_rootseed(root_seed, derivation, xtype=script) + if data['keystore_type'] in ['createseed', 'haveseed']: + if data['seed_type'] in ['old', 'standard', 'segwit']: #2fa, 2fa-segwit + self._logger.debug('creating keystore from electrum seed') + k = keystore.from_seed(data['seed'], data['seed_extra_words'], data['wallet_type'] == 'multisig') + elif data['seed_type'] == 'bip39': + self._logger.debug('creating keystore from bip39 seed') + root_seed = keystore.bip39_to_seed(data['seed'], data['seed_extra_words']) + derivation = normalize_bip32_derivation(data['derivation_path']) + script = data['script_type'] if data['script_type'] != 'p2pkh' else 'standard' + k = keystore.from_bip43_rootseed(root_seed, derivation, xtype=script) + else: + raise Exception('unsupported/unknown seed_type %s' % data['seed_type']) + elif data['keystore_type'] == 'masterkey': + k = keystore.from_master_key(data['master_key']) + has_xpub = isinstance(k, keystore.Xpub) + assert has_xpub + t1 = xpub_type(k.xpub) + if t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']: + raise Exception('wrong key type %s' % t1) else: - raise Exception('unsupported/unknown seed_type %s' % data['seed_type']) + raise Exception('unsupported/unknown keystore_type %s' % data['keystore_type']) if data['encrypt']: if k.may_have_password(): @@ -209,7 +221,8 @@ class QEWalletDB(QObject): db.set_keystore_encryption(bool(data['password']) and data['encrypt']) db.put('wallet_type', data['wallet_type']) - db.put('seed_type', data['seed_type']) + if 'seed_type' in data: + db.put('seed_type', data['seed_type']) db.put('keystore', k.dump()) if k.can_have_deterministic_lightning_xprv(): db.put('lightning_xprv', k.get_lightning_xprv(data['password'] if data['encrypt'] else None)) @@ -223,5 +236,5 @@ class QEWalletDB(QObject): self.createSuccess.emit() except Exception as e: - self._logger.error(str(e)) + self._logger.error(repr(e)) self.createError.emit(str(e))