Browse Source

qt, qml: allow BIP39 seeds which fail checksum or wordlist (fixes #8720)

removes verifySeed from qebitcoin as this code was 99% duplicate of wizard.validate_seed
master
Sander van Grieken 2 years ago
parent
commit
b87d091a6d
No known key found for this signature in database
GPG Key ID: 9BCF8209EA402EBA
  1. 49
      electrum/gui/qml/components/wizard/WCHaveSeed.qml
  2. 47
      electrum/gui/qml/qebitcoin.py
  3. 9
      electrum/gui/qml/qewizard.py
  4. 5
      electrum/gui/qt/seed_dialog.py
  5. 11
      electrum/wizard.py

49
electrum/gui/qml/components/wizard/WCHaveSeed.qml

@ -18,17 +18,20 @@ WizardComponent {
property int participants: 0 property int participants: 0
property string multisigMasterPubkey: wizard_data['multisig_master_pubkey'] property string multisigMasterPubkey: wizard_data['multisig_master_pubkey']
property string _seedType
property string _validationMessage
function apply() { function apply() {
if (cosigner) { if (cosigner) {
wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed'] = seedtext.text 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_variant'] = seed_variant_cb.currentValue
wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_type'] = bitcoin.seedType wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_type'] = _seedType
wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extend'] = extendcb.checked wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extend'] = extendcb.checked
wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extra_words'] = extendcb.checked ? customwordstext.text : '' wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extra_words'] = extendcb.checked ? customwordstext.text : ''
} else { } else {
wizard_data['seed'] = seedtext.text wizard_data['seed'] = seedtext.text
wizard_data['seed_variant'] = seed_variant_cb.currentValue wizard_data['seed_variant'] = seed_variant_cb.currentValue
wizard_data['seed_type'] = bitcoin.seedType wizard_data['seed_type'] = _seedType
wizard_data['seed_extend'] = extendcb.checked wizard_data['seed_extend'] = extendcb.checked
wizard_data['seed_extra_words'] = extendcb.checked ? customwordstext.text : '' wizard_data['seed_extra_words'] = extendcb.checked ? customwordstext.text : ''
@ -38,7 +41,7 @@ WizardComponent {
wizard_data['script_type'] = { wizard_data['script_type'] = {
'standard': 'p2sh', 'standard': 'p2sh',
'segwit': 'p2wsh' 'segwit': 'p2wsh'
}[bitcoin.seedType] }[_seedType]
} }
} }
} }
@ -66,14 +69,18 @@ WizardComponent {
function checkValid() { function checkValid() {
valid = false valid = false
validationtext.text = '' _validationMessage = ''
if (extendcb.checked && customwordstext.text == '') if (extendcb.checked && customwordstext.text == '')
return return
var validSeed = bitcoin.verifySeed(seedtext.text, seed_variant_cb.currentValue, wizard_data['wallet_type']) var verifyResult = wiz.verifySeed(seedtext.text, seed_variant_cb.currentValue, wizard_data['wallet_type'])
if (!cosigner || !validSeed) {
valid = validSeed _validationMessage = verifyResult.message
_seedType = verifyResult.type
if (!cosigner || !verifyResult.valid) {
valid = verifyResult.valid
return return
} else { } else {
// bip39 validate after derivation path is known // bip39 validate after derivation path is known
@ -196,21 +203,19 @@ WizardComponent {
placeholderText: cosigner ? qsTr('Enter cosigner seed') : qsTr('Enter your seed') placeholderText: cosigner ? qsTr('Enter cosigner seed') : qsTr('Enter your seed')
indicatorValid: root.valid indicatorValid: root.valid
? root._seedType == 'bip39' && root._validationMessage
? false
: root.valid
: root.valid
indicatorText: root.valid
? root._validationMessage
? root._validationMessage
: root._seedType
: ''
onTextChanged: { onTextChanged: {
startValidationTimer() startValidationTimer()
} }
} }
TextArea {
id: validationtext
visible: text
Layout.fillWidth: true
readOnly: true
wrapMode: TextInput.WordWrap
background: Rectangle {
color: 'transparent'
}
}
ElCheckBox { ElCheckBox {
id: extendcb id: extendcb
@ -231,14 +236,10 @@ WizardComponent {
} }
} }
Bitcoin {
id: bitcoin
onSeedTypeChanged: seedtext.indicatorText = bitcoin.seedType
}
function startValidationTimer() { function startValidationTimer() {
valid = false valid = false
seedtext.indicatorText = '' root._seedType = ''
root._validationMessage = ''
validationTimer.restart() validationTimer.restart()
} }

47
electrum/gui/qml/qebitcoin.py

@ -7,10 +7,9 @@ from electrum import keystore
from electrum.i18n import _ from electrum.i18n import _
from electrum.bip32 import is_bip32_derivation, xpub_type from electrum.bip32 import is_bip32_derivation, xpub_type
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.slip39 import decode_mnemonic, Slip39Error
from electrum.util import get_asyncio_loop from electrum.util import get_asyncio_loop
from electrum.transaction import tx_from_any from electrum.transaction import tx_from_any
from electrum.mnemonic import Mnemonic, is_any_2fa_seed_type from electrum.mnemonic import Mnemonic
from electrum.old_mnemonic import wordlist as old_wordlist from electrum.old_mnemonic import wordlist as old_wordlist
from electrum.bitcoin import is_address from electrum.bitcoin import is_address
@ -61,50 +60,6 @@ class QEBitcoin(QObject):
asyncio.run_coroutine_threadsafe(co_gen_seed(seed_type, language), get_asyncio_loop()) asyncio.run_coroutine_threadsafe(co_gen_seed(seed_type, language), get_asyncio_loop())
@pyqtSlot(str,str,str, result=bool)
def verifySeed(self, seed, seed_variant, wallet_type='standard'):
seed_type = ''
seed_valid = False
self.validationMessage = ''
if seed_variant == 'electrum':
seed_type = mnemonic.seed_type(seed)
if seed_type != '':
seed_valid = True
elif seed_variant == 'bip39':
is_checksum, is_wordlist = keystore.bip39_is_checksum_valid(seed)
status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist'
self.validationMessage = 'BIP39 (%s)' % status
if is_checksum:
seed_type = 'bip39'
seed_valid = True
elif seed_variant == 'slip39': # TODO: incomplete impl, this code only validates a single share.
try:
share = decode_mnemonic(seed)
seed_type = 'slip39'
self.validationMessage = 'SLIP39: share #%d in %dof%d scheme' % (share.group_index, share.group_threshold, share.group_count)
except Slip39Error as e:
self.validationMessage = 'SLIP39: %s' % str(e)
seed_valid = False # for now
else:
raise Exception(f'unknown seed variant {seed_variant}')
# check if seed matches wallet type
if wallet_type == '2fa' and not is_any_2fa_seed_type(seed_type):
seed_valid = False
elif wallet_type == 'standard' and seed_type not in ['old', 'standard', 'segwit', 'bip39']:
seed_valid = False
elif wallet_type == 'multisig' and seed_type not in ['standard', 'segwit', 'bip39']:
seed_valid = False
self._seed_type = seed_type
self.seedTypeChanged.emit()
self._logger.debug('seed verified: ' + str(seed_valid))
return seed_valid
@pyqtSlot(str, str, result=bool) @pyqtSlot(str, str, result=bool)
def verifyMasterKey(self, key, wallet_type='standard'): def verifyMasterKey(self, key, wallet_type='standard'):
self.validationMessage = '' self.validationMessage = ''

9
electrum/gui/qml/qewizard.py

@ -107,6 +107,15 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
def isMatchingSeed(self, seed, seed_again): def isMatchingSeed(self, seed, seed_again):
return mnemonic.is_matching_seed(seed=seed, seed_again=seed_again) return mnemonic.is_matching_seed(seed=seed, seed_again=seed_again)
@pyqtSlot(str, str, str, result='QVariantMap')
def verifySeed(self, seed, seed_variant, wallet_type='standard'):
seed_valid, seed_type, validation_message = self.validate_seed(seed, seed_variant, wallet_type)
return {
'valid': seed_valid,
'type': seed_type,
'message': validation_message
}
@pyqtSlot('QJSValue', bool, str) @pyqtSlot('QJSValue', bool, str)
def createStorage(self, js_data, single_password_enabled, single_password): def createStorage(self, js_data, single_password_enabled, single_password):
self._logger.info('Creating wallet from wizard data') self._logger.info('Creating wallet from wizard data')

5
electrum/gui/qt/seed_dialog.py

@ -267,8 +267,9 @@ class SeedLayout(QVBoxLayout):
if self.seed_type == 'bip39': if self.seed_type == 'bip39':
from electrum.keystore import bip39_is_checksum_valid from electrum.keystore import bip39_is_checksum_valid
is_checksum, is_wordlist = bip39_is_checksum_valid(s) is_checksum, is_wordlist = bip39_is_checksum_valid(s)
status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist' label = ''
label = 'BIP39' + ' (%s)'%status if bool(s):
label = ('' if is_checksum else _('BIP39 Checksum failed')) if is_wordlist else _('Unknown BIP39 wordlist')
elif self.seed_type == 'slip39': elif self.seed_type == 'slip39':
self.slip39_mnemonics[self.slip39_mnemonic_index] = s self.slip39_mnemonics[self.slip39_mnemonic_index] = s
try: try:

11
electrum/wizard.py

@ -478,12 +478,13 @@ class NewWalletWizard(AbstractWizard):
seed_valid = True seed_valid = True
elif seed_variant == 'bip39': elif seed_variant == 'bip39':
is_checksum, is_wordlist = keystore.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 = ('' if is_checksum else _('BIP39 checksum failed')) if is_wordlist else _('Unknown BIP39 wordlist')
validation_message = 'BIP39 (%s)' % status if not bool(seed):
validation_message = ''
if is_checksum:
seed_type = 'bip39' seed_type = 'bip39'
seed_valid = True # bip39 always valid, even if checksum failed, see #8720
# however, reject empty string
seed_valid = bool(seed)
elif seed_variant == 'slip39': elif seed_variant == 'slip39':
# seed shares should be already validated by wizard page, we have a combined encrypted seed # seed shares should be already validated by wizard page, we have a combined encrypted seed
if seed and isinstance(seed, EncryptedSeed): if seed and isinstance(seed, EncryptedSeed):

Loading…
Cancel
Save