diff --git a/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml b/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml index 1407dacbd..bce7b3997 100644 --- a/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml +++ b/electrum/gui/qml/components/wizard/WCHaveMasterKey.qml @@ -26,8 +26,10 @@ WizardComponent { valid = false validationtext.text = '' - if (!bitcoin.verifyMasterKey(key.trim(), wizard_data['wallet_type'])) + if (!bitcoin.verifyMasterKey(key.trim(), wizard_data['wallet_type'])) { + validationtext.text = qsTr('Error: invalid master key') return false + } if (cosigner) { apply() @@ -57,6 +59,7 @@ WizardComponent { id: masterkey_ta Layout.fillWidth: true Layout.minimumHeight: 80 + font.family: FixedFont focus: true wrapMode: TextEdit.WrapAnywhere onTextChanged: verifyMasterKey(text) @@ -67,8 +70,7 @@ WizardComponent { icon.height: constants.iconSizeMedium icon.width: constants.iconSizeMedium onClicked: { - if (verifyMasterKey(AppController.clipboardToText())) - masterkey_ta.text = AppController.clipboardToText() + masterkey_ta.text = AppController.clipboardToText() } } ToolButton { @@ -79,8 +81,7 @@ WizardComponent { onClicked: { var scan = qrscan.createObject(root) scan.onFound.connect(function() { - if (verifyMasterKey(scan.scanData)) - masterkey_ta.text = scan.scanData + masterkey_ta.text = scan.scanData scan.destroy() }) } diff --git a/electrum/gui/qml/components/wizard/WCShowMasterPubkey.qml b/electrum/gui/qml/components/wizard/WCShowMasterPubkey.qml index 2477700f3..67af8e86d 100644 --- a/electrum/gui/qml/components/wizard/WCShowMasterPubkey.qml +++ b/electrum/gui/qml/components/wizard/WCShowMasterPubkey.qml @@ -53,10 +53,12 @@ WizardComponent { } Component.onCompleted: { - if (wizard_data['seed_variant'] == 'electrum') { - masterPubkey = bitcoin.getMultisigMasterPubkey(wizard_data['seed_variant'], wizard_data['seed'], wizard_data['seed_extra_words']) + if ('master_key' in wizard_data) { + masterPubkey = bitcoin.getMultisigMasterPubkeyFromKey(wizard_data['master_key']) + } else if (wizard_data['seed_variant'] == 'electrum') { + masterPubkey = bitcoin.getMultisigMasterPubkeyFromSeed(wizard_data['seed_variant'], wizard_data['seed'], wizard_data['seed_extra_words']) } else { - masterPubkey = bitcoin.getMultisigMasterPubkey(wizard_data['seed_variant'], wizard_data['seed'], wizard_data['seed_extra_words'], wizard_data['derivation_path']) + masterPubkey = bitcoin.getMultisigMasterPubkeyFromSeed(wizard_data['seed_variant'], wizard_data['seed'], wizard_data['seed_extra_words'], wizard_data['derivation_path']) } } } diff --git a/electrum/gui/qml/qebitcoin.py b/electrum/gui/qml/qebitcoin.py index 4e0f69739..081a4c7a1 100644 --- a/electrum/gui/qml/qebitcoin.py +++ b/electrum/gui/qml/qebitcoin.py @@ -126,7 +126,6 @@ class QEBitcoin(QObject): if t1 not in ['standard', 'p2wsh', 'p2wsh-p2sh']: self.validationMessage = '%s: %s' % (_('Wrong key type'), t1) return False - # TODO: check against other cosigner xpubs return True raise Exception(f'Unsupported wallet type: {wallet_type}') @@ -167,9 +166,13 @@ class QEBitcoin(QObject): def isPrivateKeyList(self, csv: str): return keystore.is_private_key_list(csv) + @pyqtSlot(str, result=str) + def getMultisigMasterPubkeyFromKey(self, key): + return keystore.from_master_key(key).get_master_public_key() + @pyqtSlot(str, str, str, result=str) @pyqtSlot(str, str, str, str, result=str) - def getMultisigMasterPubkey(self, seed_variant, seed, seed_extra_words, derivation_path = None): + def getMultisigMasterPubkeyFromSeed(self, seed_variant, seed, seed_extra_words, derivation_path = None): if seed_variant == 'electrum': k = keystore.from_seed(seed, seed_extra_words, True) elif seed_variant == 'bip39': diff --git a/electrum/wizard.py b/electrum/wizard.py index 46f11d553..f237b9acc 100644 --- a/electrum/wizard.py +++ b/electrum/wizard.py @@ -256,16 +256,17 @@ class NewWalletWizard(AbstractWizard): # if last cosigner uses bip39 seed, we still need derivation path current_cosigner_data = wizard_data['multisig_cosigner_data'][str(wizard_data['multisig_current_cosigner'])] - if current_cosigner_data['seed_type'] == 'bip39' and 'derivation_path' not in current_cosigner_data: + if 'seed_type' in current_cosigner_data and current_cosigner_data['seed_type'] == 'bip39' and 'derivation_path' not in current_cosigner_data: return False return True def has_duplicate_keys(self, wizard_data): xpubs = [] - xpubs.append(self.xpub_from_data(wizard_data)) + xpubs.append(self.keystore_from_data(wizard_data).get_master_public_key()) for cosigner in wizard_data['multisig_cosigner_data']: - xpubs.append(self.xpub_from_data(wizard_data['multisig_cosigner_data'][cosigner])) + data = wizard_data['multisig_cosigner_data'][cosigner] + xpubs.append(self.keystore_from_data(data).get_master_public_key()) while len(xpubs): xpub = xpubs.pop() @@ -274,21 +275,18 @@ class NewWalletWizard(AbstractWizard): return False - def xpub_from_data(self, data): + def keystore_from_data(self, data): if 'seed' in data: if data['seed_variant'] == 'electrum': - k = keystore.from_seed(data['seed'], data['seed_extra_words'], True) + return keystore.from_seed(data['seed'], data['seed_extra_words'], True) elif data['seed_variant'] == 'bip39': root_seed = keystore.bip39_to_seed(data['seed'], data['seed_extra_words']) derivation = normalize_bip32_derivation(data['derivation_path']) - k = keystore.from_bip43_rootseed(root_seed, derivation, xtype='p2wsh') + return keystore.from_bip43_rootseed(root_seed, derivation, xtype='p2wsh') else: raise Exception('Unsupported seed variant %s' % data['seed_variant']) - - return k.get_master_public_key() elif 'master_key' in data: - k = keystore.from_master_key(data['master_key']) - return k.get_master_public_key() + return keystore.from_master_key(data['master_key']) else: raise Exception('no seed or master_key in data') @@ -297,13 +295,13 @@ class NewWalletWizard(AbstractWizard): # override def create_storage(self, path, data): - # only standard, 2fa and imported wallets for now - assert data['wallet_type'] in ['standard', '2fa', 'imported'] + assert data['wallet_type'] in ['standard', '2fa', 'imported', 'multisig'] if os.path.exists(path): raise Exception('file already exists at path') storage = WalletStorage(path) + # TODO: refactor using self.keystore_from_data k = None if 'keystore_type' not in data: assert data['wallet_type'] == 'imported' @@ -339,8 +337,12 @@ class NewWalletWizard(AbstractWizard): 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) + if data['wallet_type'] == 'multisig': + if t1 not in ['standard', 'p2wsh', 'p2wsh-p2sh']: + raise Exception('wrong key type %s' % t1) + else: + if t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']: + raise Exception('wrong key type %s' % t1) else: raise Exception('unsupported/unknown keystore_type %s' % data['keystore_type']) @@ -369,6 +371,14 @@ class NewWalletWizard(AbstractWizard): db.put('x2/', data['x2/']) db.put('x3/', data['x3/']) db.put('use_trustedcoin', True) + elif data['wallet_type'] == 'multisig': + db.put('wallet_type', '%dof%d' % (data['multisig_signatures'],data['multisig_participants'])) + db.put('x1/', k.dump()) + for cosigner in data['multisig_cosigner_data']: + cosigner_keystore = self.keystore_from_data(data['multisig_cosigner_data'][cosigner]) + if data['encrypt'] and cosigner_keystore.may_have_password(): + cosigner_keystore.update_password(None, data['password']) + db.put(f'x{cosigner}/', cosigner_keystore.dump()) elif data['wallet_type'] == 'imported': if k: db.put('keystore', k.dump())