Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions electrum/gui/qml/components/controls/SeedTextArea.qml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ Pane {
property string indicatorText
property bool indicatorValid

// Whether to validate individual words against the wordlist.
// When true, invalid words are visually flagged (red underline area).
// Does not block input - user can still type and correct.
property bool validateWords: false

property var _suggestions: []
property var _invalidWordIndices: []

onTextChanged: {
if (seedtextarea.text != text)
Expand Down Expand Up @@ -63,6 +69,19 @@ Pane {
_suggestions = bitcoin.mnemonicsFor(seedtextarea.text.split(' ').pop())
// TODO: cursorPosition only on suggestion apply
cursorPosition = text.length

// per-word validation: flag words not in the wordlist
if (root.validateWords) {
var words = seedtextarea.text.split(' ')
// last word may be incomplete (still being typed), skip it
var invalid = []
for (var i = 0; i < words.length - 1; i++) {
if (words[i].length > 0 && !bitcoin.isWordInWordlist(words[i])) {
invalid.push(i)
}
}
_invalidWordIndices = invalid
}
}

Rectangle {
Expand All @@ -86,6 +105,19 @@ Pane {
}
}

Label {
id: invalidWordWarning
visible: root.validateWords && _invalidWordIndices.length > 0
text: _invalidWordIndices.length === 1
? qsTr('Word %1 is not in the word list').arg(_invalidWordIndices[0] + 1)
: qsTr('%1 words are not in the word list').arg(_invalidWordIndices.length)
color: Material.color(Material.Red)
font.pixelSize: constants.fontSizeSmall
Layout.fillWidth: true
wrapMode: Text.Wrap
leftPadding: constants.paddingXXSmall
}

Flickable {
Layout.preferredWidth: parent.width
Layout.minimumHeight: fontMetrics.lineSpacing + 2*constants.paddingXXSmall + 2*constants.paddingXSmall + 2
Expand Down
1 change: 1 addition & 0 deletions electrum/gui/qml/components/wizard/WCConfirmSeed.qml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ WizardComponent {
Layout.fillWidth: true
Layout.topMargin: constants.paddingSmall
placeholderText: qsTr('Enter your seed')
validateWords: true
onTextChanged: checkValid()
}
}
Expand Down
1 change: 1 addition & 0 deletions electrum/gui/qml/components/wizard/WCHaveSeed.qml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ WizardComponent {
Layout.topMargin: constants.paddingLarge

placeholderText: cosigner ? qsTr('Enter cosigner seed') : qsTr('Enter your seed')
validateWords: true

indicatorValid: root._seedValid
? root._seedType == 'bip39' && root._validationMessage
Expand Down
8 changes: 8 additions & 0 deletions electrum/gui/qml/qebitcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,11 @@ def mnemonicsFor(self, fragment):
if not self._words:
self._words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
return sorted(filter(lambda x: x.startswith(fragment), self._words))

@pyqtSlot(str, result=bool)
def isWordInWordlist(self, word):
if not word:
return True
if not self._words:
self._words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
return word in self._words