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. 49
      electrum/gui/qml/qebitcoin.py
  3. 9
      electrum/gui/qml/qewizard.py
  4. 5
      electrum/gui/qt/seed_dialog.py
  5. 13
      electrum/wizard.py

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

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

49
electrum/gui/qml/qebitcoin.py

@ -7,10 +7,9 @@ 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 get_asyncio_loop
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.bitcoin import is_address
@ -50,7 +49,7 @@ class QEBitcoin(QObject):
@pyqtSlot()
@pyqtSlot(str)
@pyqtSlot(str,str)
@pyqtSlot(str, str)
def generateSeed(self, seed_type='segwit', language='en'):
self._logger.debug('generating seed of type ' + str(seed_type))
@ -61,50 +60,6 @@ class QEBitcoin(QObject):
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)
def verifyMasterKey(self, key, wallet_type='standard'):
self.validationMessage = ''

9
electrum/gui/qml/qewizard.py

@ -107,6 +107,15 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
def isMatchingSeed(self, seed, 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)
def createStorage(self, js_data, single_password_enabled, single_password):
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':
from electrum.keystore import bip39_is_checksum_valid
is_checksum, is_wordlist = bip39_is_checksum_valid(s)
status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist'
label = 'BIP39' + ' (%s)'%status
label = ''
if bool(s):
label = ('' if is_checksum else _('BIP39 Checksum failed')) if is_wordlist else _('Unknown BIP39 wordlist')
elif self.seed_type == 'slip39':
self.slip39_mnemonics[self.slip39_mnemonic_index] = s
try:

13
electrum/wizard.py

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

Loading…
Cancel
Save