diff --git a/electrum/gui/qml/components/wizard/WCConfirmExt.qml b/electrum/gui/qml/components/wizard/WCConfirmExt.qml new file mode 100644 index 000000000..abcfc3130 --- /dev/null +++ b/electrum/gui/qml/components/wizard/WCConfirmExt.qml @@ -0,0 +1,72 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.Material + +import org.electrum 1.0 + +import "../controls" + +WizardComponent { + id: root + securePage: true + + valid: false + + property int cosigner: 0 + + function checkValid() { + valid = false + var input = customwordstext.text + if (input == '') { + return + } + + if (cosigner) { + // multisig cosigner + if (input != wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extra_words']) { + return + } + } else { + if (input != wizard_data['seed_extra_words']) { + return + } + } + valid = true + } + + Flickable { + anchors.fill: parent + contentHeight: mainLayout.height + clip: true + interactive: height < contentHeight + + ColumnLayout { + id: mainLayout + width: parent.width + spacing: constants.paddingLarge + + Label { + Layout.fillWidth: true + wrapMode: Text.Wrap + text: qsTr('Please enter your custom word(s) a second time:') + } + + TextField { + id: customwordstext + Layout.fillWidth: true + Layout.columnSpan: 2 + placeholderText: qsTr('Enter your custom word(s) here') + inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase + onTextChanged: checkValid() + } + } + } + + Component.onCompleted: { + if (wizard_data['wallet_type'] == 'multisig') { + if ('multisig_current_cosigner' in wizard_data) + cosigner = wizard_data['multisig_current_cosigner'] + } + } +} \ No newline at end of file diff --git a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml index a5f27d8e0..a2b058e95 100644 --- a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml +++ b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml @@ -14,8 +14,7 @@ WizardComponent { function checkValid() { var seedvalid = wizard.wiz.isMatchingSeed(wizard_data['seed'], confirm.text) - var customwordsvalid = customwordstext.text == wizard_data['seed_extra_words'] - valid = seedvalid && (wizard_data['seed_extend'] ? customwordsvalid : true) + valid = seedvalid } Flickable { @@ -46,19 +45,6 @@ WizardComponent { placeholderText: qsTr('Enter your seed') onTextChanged: checkValid() } - - TextField { - id: customwordstext - Layout.fillWidth: true - placeholderText: qsTr('Enter your custom word(s)') - inputMethodHints: Qt.ImhNoPredictiveText - - onTextChanged: checkValid() - } } } - - Component.onCompleted: { - customwordstext.visible = wizard_data['seed_extend'] - } } diff --git a/electrum/gui/qml/components/wizard/WCCreateSeed.qml b/electrum/gui/qml/components/wizard/WCCreateSeed.qml index da087706a..efa4df90a 100644 --- a/electrum/gui/qml/components/wizard/WCCreateSeed.qml +++ b/electrum/gui/qml/components/wizard/WCCreateSeed.qml @@ -9,13 +9,12 @@ import "../controls" WizardComponent { securePage: true - valid: seedtext.text != '' && extendcb.checked ? customwordstext.text != '' : true + valid: seedtext.text != '' function apply() { wizard_data['seed'] = seedtext.text wizard_data['seed_variant'] = 'electrum' // generated seed always electrum variant - wizard_data['seed_extend'] = extendcb.checked - wizard_data['seed_extra_words'] = extendcb.checked ? customwordstext.text : '' + wizard_data['seed_extend'] = true // true so we get forwarded to the passphrase page } function setWarningText(numwords) { @@ -70,20 +69,6 @@ WizardComponent { } } - ElCheckBox { - id: extendcb - Layout.fillWidth: true - text: qsTr('Extend seed with custom words') - } - - TextField { - id: customwordstext - visible: extendcb.checked - Layout.fillWidth: true - placeholderText: qsTr('Enter your custom word(s)') - inputMethodHints: Qt.ImhNoPredictiveText - } - Component.onCompleted : { setWarningText(12) } diff --git a/electrum/gui/qml/components/wizard/WCEnterExt.qml b/electrum/gui/qml/components/wizard/WCEnterExt.qml new file mode 100644 index 000000000..b804f09ab --- /dev/null +++ b/electrum/gui/qml/components/wizard/WCEnterExt.qml @@ -0,0 +1,119 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.Material + +import org.electrum 1.0 + +import "../controls" + +WizardComponent { + id: root + securePage: true + + valid: true + + property int cosigner: 0 + + function apply() { + var seed_extend = extendcb.checked + if (cosigner) { + wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extend'] = seed_extend + wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extra_words'] = seed_extend ? customwordstext.text : '' + } else { + wizard_data['seed_extend'] = seed_extend + wizard_data['seed_extra_words'] = seed_extend ? customwordstext.text : '' + } + } + + function checkValid() { + valid = false + validationtext.text = '' + + if (extendcb.checked && customwordstext.text == '') { + return + } else { + // passphrase is either disabled or filled with text + apply() + if (cosigner && wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_variant'] == 'electrum') { + // check if master keys are not duplicated after entering passphrase + if (wiz.hasDuplicateMasterKeys(wizard_data)) { + validationtext.text = qsTr('Error: duplicate master public key') + return + } + } + } + valid = true + } + + Flickable { + anchors.fill: parent + contentHeight: mainLayout.height + clip: true + interactive: height < contentHeight + + ColumnLayout { + id: mainLayout + width: parent.width + spacing: constants.paddingLarge + + InfoTextArea { + id: validationtext + Layout.fillWidth: true + Layout.columnSpan: 2 + visible: text + iconStyle: InfoTextArea.IconStyle.Error + } + + Label { + Layout.fillWidth: true + wrapMode: Text.Wrap + text: [ + qsTr('You may extend your seed with custom words.'), + qsTr('Your seed extension must be saved together with your seed.'), + qsTr('Note that this is NOT your encryption password.'), + '
', + qsTr('Do not enable it unless you know what it does!'), + ].join(' ') + } + + ElCheckBox { + id: extendcb + Layout.columnSpan: 2 + Layout.fillWidth: true + text: qsTr('Extend seed with custom words') + onCheckedChanged: checkValid() + } + + TextField { + id: customwordstext + enabled: extendcb.checked + Layout.fillWidth: true + Layout.columnSpan: 2 + placeholderText: qsTr('Enter your custom word(s)') + inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase + onTextChanged: startValidationTimer() + } + } + } + + function startValidationTimer() { + valid = false + validationTimer.restart() + } + + Timer { + id: validationTimer + interval: 250 + repeat: false + onTriggered: checkValid() + } + + Component.onCompleted: { + if (wizard_data['wallet_type'] == 'multisig') { + if ('multisig_current_cosigner' in wizard_data) + cosigner = wizard_data['multisig_current_cosigner'] + } + checkValid() + } +} diff --git a/electrum/gui/qml/components/wizard/WCHaveSeed.qml b/electrum/gui/qml/components/wizard/WCHaveSeed.qml index 4afc638a7..3be2a29d5 100644 --- a/electrum/gui/qml/components/wizard/WCHaveSeed.qml +++ b/electrum/gui/qml/components/wizard/WCHaveSeed.qml @@ -24,19 +24,16 @@ WizardComponent { property bool _seedValid function apply() { - var seed_extend = extendcb.checked && _canPassphrase if (cosigner) { wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed'] = seedtext.text wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_variant'] = seed_variant_cb.currentValue wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_type'] = _seedType - wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extend'] = seed_extend - wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extra_words'] = seed_extend ? customwordstext.text : '' + wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extend'] = _canPassphrase } else { wizard_data['seed'] = seedtext.text wizard_data['seed_variant'] = seed_variant_cb.currentValue wizard_data['seed_type'] = _seedType - wizard_data['seed_extend'] = seed_extend - wizard_data['seed_extra_words'] = seed_extend ? customwordstext.text : '' + wizard_data['seed_extend'] = _canPassphrase // determine script type from electrum seed type // (used to limit script type options for bip39 cosigners) @@ -52,22 +49,20 @@ WizardComponent { function setSeedTypeHelpText() { var t = { 'electrum': [ + // not shown as electrum is the default seed type anyways and the name is self-explanatory qsTr('Electrum seeds are the default seed type.'), qsTr('If you are restoring from a seed previously created by Electrum, choose this option') ].join(' '), 'bip39': [ qsTr('BIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'), - '

', - qsTr('However, we do not generate BIP39 seeds, because they do not meet our safety standard.'), - qsTr('BIP39 seeds do not include a version number, which compromises compatibility with future software.') + qsTr('BIP39 seeds do not include a version number, which compromises compatibility with future software.'), ].join(' '), 'slip39': [ qsTr('SLIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'), - '

', - qsTr('However, we do not generate SLIP39 seeds.') ].join(' ') } infotext.text = t[seed_variant_cb.currentValue] + infotext.visible = !cosigner && !is2fa && seed_variant_cb.currentValue != 'electrum' } function checkValid() { @@ -100,11 +95,6 @@ WizardComponent { } } - if (_canPassphrase && extendcb.checked && customwordstext.text == '') { - valid = false - return - } - valid = _seedValid } @@ -196,7 +186,6 @@ WizardComponent { InfoTextArea { id: infotext - visible: !cosigner && !is2fa Layout.fillWidth: true Layout.columnSpan: 2 Layout.bottomMargin: constants.paddingLarge @@ -221,26 +210,6 @@ WizardComponent { startValidationTimer() } } - - ElCheckBox { - id: extendcb - Layout.columnSpan: 2 - Layout.fillWidth: true - visible: _canPassphrase - text: qsTr('Extend seed with custom words') - onCheckedChanged: startValidationTimer() - } - - TextField { - id: customwordstext - visible: extendcb.checked && extendcb.visible - Layout.fillWidth: true - Layout.columnSpan: 2 - placeholderText: qsTr('Enter your custom word(s)') - inputMethodHints: Qt.ImhNoPredictiveText - - onTextChanged: startValidationTimer() - } } } diff --git a/electrum/gui/qml/qewizard.py b/electrum/gui/qml/qewizard.py index 0a9d51fa1..cd45e9e57 100644 --- a/electrum/gui/qml/qewizard.py +++ b/electrum/gui/qml/qewizard.py @@ -68,14 +68,18 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): 'wallet_type': {'gui': 'WCWalletType'}, 'keystore_type': {'gui': 'WCKeystoreType'}, 'create_seed': {'gui': 'WCCreateSeed'}, + 'create_ext': {'gui': 'WCEnterExt'}, 'confirm_seed': {'gui': 'WCConfirmSeed'}, + 'confirm_ext': {'gui': 'WCConfirmExt'}, 'have_seed': {'gui': 'WCHaveSeed'}, + 'have_ext': {'gui': 'WCEnterExt'}, 'script_and_derivation': {'gui': 'WCScriptAndDerivation'}, 'have_master_key': {'gui': 'WCHaveMasterKey'}, 'multisig': {'gui': 'WCMultisig'}, 'multisig_cosigner_keystore': {'gui': 'WCCosignerKeystore'}, 'multisig_cosigner_key': {'gui': 'WCHaveMasterKey'}, 'multisig_cosigner_seed': {'gui': 'WCHaveSeed'}, + 'multisig_cosigner_have_ext': {'gui': 'WCEnterExt'}, 'multisig_cosigner_script_and_derivation': {'gui': 'WCScriptAndDerivation'}, 'imported': {'gui': 'WCImport'}, 'wallet_password': {'gui': 'WCWalletPassword'} diff --git a/electrum/wizard.py b/electrum/wizard.py index 0eb094a59..9092e891f 100644 --- a/electrum/wizard.py +++ b/electrum/wizard.py @@ -333,7 +333,7 @@ class KeystoreWizard(AbstractWizard): def keystore_from_data(self, wallet_type: str, data: dict): if data['keystore_type'] in ['createseed', 'haveseed'] and 'seed' in data: - seed_extension = data['seed_extra_words'] if data['seed_extend'] else '' + seed_extension = data.get('seed_extra_words', '') if data['seed_variant'] == 'electrum': for_multisig = wallet_type in ['multisig'] return keystore.from_seed(data['seed'], passphrase=seed_extension, for_multisig=for_multisig)