From 6ea3a16cc84d9bce178ea4b7bf7bac2b82a221ba Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 4 Oct 2022 12:00:50 +0200 Subject: [PATCH] add wallet type imported addresses/private keys to wizard --- electrum/gui/qml/components/Wallets.qml | 7 +- .../gui/qml/components/wizard/WCImport.qml | 102 ++++++++++++++++++ .../qml/components/wizard/WCWalletType.qml | 3 +- electrum/gui/qml/qeaddresslistmodel.py | 4 +- electrum/gui/qml/qebitcoin.py | 9 ++ electrum/gui/qml/qewallet.py | 5 + electrum/gui/qml/qewizard.py | 1 + electrum/gui/wizard.py | 42 ++++++-- 8 files changed, 160 insertions(+), 13 deletions(-) create mode 100644 electrum/gui/qml/components/wizard/WCImport.qml diff --git a/electrum/gui/qml/components/Wallets.qml b/electrum/gui/qml/components/Wallets.qml index 0a2100ada..583c3422d 100644 --- a/electrum/gui/qml/components/Wallets.qml +++ b/electrum/gui/qml/components/Wallets.qml @@ -153,9 +153,14 @@ Pane { Label { text: 'has Seed'; color: Material.accentColor } Label { text: Daemon.currentWallet.hasSeed } - Label { Layout.columnSpan:4; text: qsTr('Master Public Key'); color: Material.accentColor } + Label { + visible: Daemon.currentWallet.masterPubkey + Layout.columnSpan:4; text: qsTr('Master Public Key'); color: Material.accentColor + } TextHighlightPane { + visible: Daemon.currentWallet.masterPubkey + Layout.columnSpan: 4 Layout.fillWidth: true padding: 0 diff --git a/electrum/gui/qml/components/wizard/WCImport.qml b/electrum/gui/qml/components/wizard/WCImport.qml new file mode 100644 index 000000000..7f479f9ee --- /dev/null +++ b/electrum/gui/qml/components/wizard/WCImport.qml @@ -0,0 +1,102 @@ +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: { + if (bitcoin.isAddressList(import_ta.text)) { + wizard_data['address_list'] = import_ta.text + } else if (bitcoin.isPrivateKeyList(import_ta.text)) { + wizard_data['private_key_list'] = import_ta.text + } + } + + function verify(text) { + return bitcoin.isAddressList(text) || bitcoin.isPrivateKeyList(text) + } + + ColumnLayout { + width: parent.width + + Label { text: qsTr('Import Bitcoin Addresses') } + + InfoTextArea { + text: qsTr('Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys.') + } + + RowLayout { + TextArea { + id: import_ta + Layout.fillWidth: true + Layout.minimumHeight: 80 + focus: true + wrapMode: TextEdit.WrapAnywhere + onTextChanged: valid = verify(text) + } + ColumnLayout { + Layout.alignment: Qt.AlignTop + ToolButton { + icon.source: '../../../icons/paste.png' + icon.height: constants.iconSizeMedium + icon.width: constants.iconSizeMedium + onClicked: { + if (verify(AppController.clipboardToText())) { + if (import_ta.text != '') + import_ta.text = import_ta.text + '\n' + import_ta.text = import_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 (verify(scan.scanData)) { + if (import_ta.text != '') + import_ta.text = import_ta.text + ',\n' + import_ta.text = import_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/WCWalletType.qml b/electrum/gui/qml/components/wizard/WCWalletType.qml index 8a55a0a29..1c0ee0c7a 100644 --- a/electrum/gui/qml/components/wizard/WCWalletType.qml +++ b/electrum/gui/qml/components/wizard/WCWalletType.qml @@ -38,9 +38,8 @@ WizardComponent { text: qsTr('Multi-signature wallet') } RadioButton { - enabled: false ButtonGroup.group: wallettypegroup - property string wallettype: 'import' + property string wallettype: 'imported' text: qsTr('Import Bitcoin addresses or private keys') } } diff --git a/electrum/gui/qml/qeaddresslistmodel.py b/electrum/gui/qml/qeaddresslistmodel.py index c82802fbc..1a8881bad 100644 --- a/electrum/gui/qml/qeaddresslistmodel.py +++ b/electrum/gui/qml/qeaddresslistmodel.py @@ -65,7 +65,7 @@ class QEAddressListModel(QAbstractListModel): def init_model(self): r_addresses = self.wallet.get_receiving_addresses() c_addresses = self.wallet.get_change_addresses() - n_addresses = len(r_addresses) + len(c_addresses) + n_addresses = len(r_addresses) + len(c_addresses) if self.wallet.use_change else 0 def insert_row(atype, alist, address, iaddr): item = self.addr_to_model(address) @@ -80,7 +80,7 @@ class QEAddressListModel(QAbstractListModel): insert_row('receive', self.receive_addresses, address, i) i = i + 1 i = 0 - for address in c_addresses: + for address in c_addresses if self.wallet.use_change else []: insert_row('change', self.change_addresses, address, i) i = i + 1 self.endInsertRows() diff --git a/electrum/gui/qml/qebitcoin.py b/electrum/gui/qml/qebitcoin.py index d7a63b429..4d37d1206 100644 --- a/electrum/gui/qml/qebitcoin.py +++ b/electrum/gui/qml/qebitcoin.py @@ -164,3 +164,12 @@ class QEBitcoin(QObject): return True except: return False + + @pyqtSlot(str, result=bool) + def isAddressList(self, csv: str): + return keystore.is_address_list(csv) + + @pyqtSlot(str, result=bool) + def isPrivateKeyList(self, csv: str): + return keystore.is_private_key_list(csv) + diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py index 83aa4054a..3549c2c9e 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -319,6 +319,8 @@ class QEWallet(AuthMixin, QObject, QtEventListener): @pyqtProperty(str, notify=dataChanged) def txinType(self): + if self.wallet.wallet_type == 'imported': + return self.wallet.txin_type return self.wallet.get_txin_type(self.wallet.dummy_address()) @pyqtProperty(bool, notify=dataChanged) @@ -342,6 +344,9 @@ class QEWallet(AuthMixin, QObject, QtEventListener): keystores = self.wallet.get_keystores() if len(keystores) > 1: self._logger.debug('multiple keystores not supported yet') + if len(keystores) == 0: + self._logger.debug('no keystore') + return '' return keystores[0].get_derivation_prefix() @pyqtProperty(str, notify=dataChanged) diff --git a/electrum/gui/qml/qewizard.py b/electrum/gui/qml/qewizard.py index f9749d3d3..0cb5b8a43 100644 --- a/electrum/gui/qml/qewizard.py +++ b/electrum/gui/qml/qewizard.py @@ -59,6 +59,7 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): 'have_seed': { 'gui': 'WCHaveSeed' }, 'bip39_refine': { 'gui': 'WCBIP39Refine' }, 'have_master_key': { 'gui': 'WCHaveMasterKey' }, + 'imported': { 'gui': 'WCImport' }, 'wallet_password': { 'gui': 'WCWalletPassword' } }) diff --git a/electrum/gui/wizard.py b/electrum/gui/wizard.py index 01dbdfbdb..869a94ef3 100644 --- a/electrum/gui/wizard.py +++ b/electrum/gui/wizard.py @@ -8,6 +8,7 @@ from electrum.storage import WalletStorage, StorageEncryptionVersion from electrum.wallet_db import WalletDB from electrum.bip32 import normalize_bip32_derivation, xpub_type from electrum import keystore +from electrum import bitcoin class WizardViewState(NamedTuple): view: str @@ -162,6 +163,10 @@ class NewWalletWizard(AbstractWizard): 'next': 'wallet_password', 'last': self.last_if_single_password }, + 'imported': { + 'next': 'wallet_password', + 'last': self.last_if_single_password + }, 'wallet_password': { 'last': True } @@ -180,10 +185,12 @@ class NewWalletWizard(AbstractWizard): return self.last_if_single_password(view, wizard_data) and not wizard_data['seed_type'] == 'bip39' def on_wallet_type(self, wizard_data): - if wizard_data['wallet_type'] == '2fa': - return 'trustedcoin_start' - - return 'keystore_type' + t = wizard_data['wallet_type'] + return { + 'standard': 'keystore_type', + '2fa': 'trustedcoin_start', + 'imported': 'imported' + }.get(t) def on_keystore_type(self, wizard_data): t = wizard_data['keystore_type'] @@ -205,13 +212,28 @@ class NewWalletWizard(AbstractWizard): def create_storage(self, path, data): # only standard and 2fa wallets for now - assert data['wallet_type'] in ['standard', '2fa'] + assert data['wallet_type'] in ['standard', '2fa', 'imported'] if os.path.exists(path): raise Exception('file already exists at path') storage = WalletStorage(path) - if data['keystore_type'] in ['createseed', 'haveseed']: + k = None + if not 'keystore_type' in data: + assert data['wallet_type'] == 'imported' + addresses = {} + if 'private_key_list' in data: + k = keystore.Imported_KeyStore({}) + keys = keystore.get_private_keys(data['private_key_list']) + for pk in keys: + assert bitcoin.is_private_key(pk) + txin_type, pubkey = k.import_privkey(pk, None) + addr = bitcoin.pubkey_to_address(txin_type, pubkey) + addresses[addr] = {'type': txin_type, 'pubkey': pubkey} + elif 'address_list' in data: + for addr in data['address_list'].split(): + addresses[addr] = {} + elif 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') @@ -237,7 +259,7 @@ class NewWalletWizard(AbstractWizard): raise Exception('unsupported/unknown keystore_type %s' % data['keystore_type']) if data['encrypt']: - if k.may_have_password(): + if k and k.may_have_password(): k.update_password(None, data['password']) storage.set_password(data['password'], enc_version=StorageEncryptionVersion.USER_PASSWORD) @@ -255,8 +277,12 @@ class NewWalletWizard(AbstractWizard): db.put('x2/', data['x2/']) db.put('x3/', data['x3/']) db.put('use_trustedcoin', True) + elif data['wallet_type'] == 'imported': + if k: + db.put('keystore', k.dump()) + db.put('addresses', addresses) - if k.can_have_deterministic_lightning_xprv(): + if k and k.can_have_deterministic_lightning_xprv(): db.put('lightning_xprv', k.get_lightning_xprv(data['password'] if data['encrypt'] else None)) db.load_plugins()