validate seeds for Electrum, BIP39, SLIP39 seeds and perform create wallet in from seed scenario
Currently only Electrum seeds are considered valid. For BIP39 additional dialog is needed. For SLIP39 multiple mnemonics need to be supported to generate a seed
This commit is contained in:
@@ -11,6 +11,8 @@ Wizard {
|
||||
|
||||
signal walletCreated
|
||||
|
||||
property alias path: walletdb.path
|
||||
|
||||
enter: null // disable transition
|
||||
|
||||
// State transition functions. These functions are called when the 'Next'
|
||||
|
||||
@@ -107,6 +107,8 @@ Pane {
|
||||
dialog.open()
|
||||
dialog.walletCreated.connect(function() {
|
||||
Daemon.availableWallets.reload()
|
||||
// and load the new wallet
|
||||
Daemon.load_wallet(dialog.path, dialog.wizard_data['password'])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,18 +123,18 @@ Item {
|
||||
|
||||
function setWarningText(numwords) {
|
||||
var t = [
|
||||
"<p>",
|
||||
qsTr("Please save these %1 words on paper (order is important). ").arg(numwords),
|
||||
qsTr("This seed will allow you to recover your wallet in case of computer failure."),
|
||||
"</p>",
|
||||
"<b>" + qsTr("WARNING") + ":</b>",
|
||||
"<ul>",
|
||||
"<li>" + qsTr("Never disclose your seed.") + "</li>",
|
||||
"<li>" + qsTr("Never type it on a website.") + "</li>",
|
||||
"<li>" + qsTr("Do not store it electronically.") + "</li>",
|
||||
"</ul>"
|
||||
'<p>',
|
||||
qsTr('Please save these %1 words on paper (order is important).').arg(numwords),
|
||||
qsTr('This seed will allow you to recover your wallet in case of computer failure.'),
|
||||
'</p>',
|
||||
'<b>' + qsTr('WARNING') + ':</b>',
|
||||
'<ul>',
|
||||
'<li>' + qsTr('Never disclose your seed.') + '</li>',
|
||||
'<li>' + qsTr('Never type it on a website.') + '</li>',
|
||||
'<li>' + qsTr('Do not store it electronically.') + '</li>',
|
||||
'</ul>'
|
||||
]
|
||||
warningtext.text = t.join("")
|
||||
warningtext.text = t.join(' ')
|
||||
}
|
||||
|
||||
Flickable {
|
||||
@@ -187,7 +187,7 @@ Item {
|
||||
id: bitcoin
|
||||
onGeneratedSeedChanged: {
|
||||
seedtext.text = generated_seed
|
||||
setWarningText(generated_seed.split(" ").length)
|
||||
setWarningText(generated_seed.split(' ').length)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,16 +195,16 @@ Item {
|
||||
|
||||
property Component haveseed: Component {
|
||||
WizardComponent {
|
||||
id: root
|
||||
valid: false
|
||||
|
||||
onAccept: {
|
||||
wizard_data['seed'] = seedtext.text
|
||||
wizard_data['seed_type'] = bitcoin.seed_type
|
||||
wizard_data['seed_extend'] = extendcb.checked
|
||||
wizard_data['seed_extra_words'] = extendcb.checked ? customwordstext.text : ''
|
||||
wizard_data['seed_bip39'] = bip39cb.checked
|
||||
}
|
||||
|
||||
function checkValid() {
|
||||
wizard_data['seed_bip39'] = seed_type.getTypeCode() == 'BIP39'
|
||||
wizard_data['seed_slip39'] = seed_type.getTypeCode() == 'SLIP39'
|
||||
}
|
||||
|
||||
function setSeedTypeHelpText() {
|
||||
@@ -230,6 +230,10 @@ Item {
|
||||
infotext.text = t[seed_type.currentText]
|
||||
}
|
||||
|
||||
function checkValid() {
|
||||
bitcoin.verify_seed(seedtext.text, seed_type.getTypeCode() == 'BIP39', seed_type.getTypeCode() == 'SLIP39')
|
||||
}
|
||||
|
||||
Flickable {
|
||||
anchors.fill: parent
|
||||
contentHeight: mainLayout.height
|
||||
@@ -243,11 +247,18 @@ Item {
|
||||
|
||||
Label {
|
||||
text: qsTr('Seed Type')
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
ComboBox {
|
||||
id: seed_type
|
||||
model: ['Electrum', 'BIP39', 'SLIP39']
|
||||
onActivated: setSeedTypeHelpText()
|
||||
onActivated: {
|
||||
setSeedTypeHelpText()
|
||||
checkValid()
|
||||
}
|
||||
function getTypeCode() {
|
||||
return currentText
|
||||
}
|
||||
}
|
||||
InfoTextArea {
|
||||
id: infotext
|
||||
@@ -263,9 +274,36 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
onTextChanged: {
|
||||
checkValid()
|
||||
validationTimer.restart()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: contentText
|
||||
color: 'green'
|
||||
border.color: Material.accentColor
|
||||
radius: 2
|
||||
}
|
||||
Label {
|
||||
id: contentText
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
leftPadding: text != '' ? 16 : 0
|
||||
rightPadding: text != '' ? 16 : 0
|
||||
font.bold: false
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
TextArea {
|
||||
id: validationtext
|
||||
visible: text != ''
|
||||
Layout.fillWidth: true
|
||||
readOnly: true
|
||||
wrapMode: TextInput.WordWrap
|
||||
background: Rectangle {
|
||||
color: 'transparent'
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: extendcb
|
||||
Layout.columnSpan: 2
|
||||
@@ -284,7 +322,18 @@ Item {
|
||||
|
||||
Bitcoin {
|
||||
id: bitcoin
|
||||
onSeedTypeChanged: contentText.text = bitcoin.seed_type
|
||||
onSeedValidChanged: root.valid = bitcoin.seed_valid
|
||||
onValidationMessageChanged: validationtext.text = bitcoin.validation_message
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: validationTimer
|
||||
interval: 500
|
||||
repeat: false
|
||||
onTriggered: checkValid()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
setSeedTypeHelpText()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import asyncio
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||
|
||||
from electrum.logging import get_logger
|
||||
from electrum.keystore import bip39_is_checksum_valid
|
||||
from electrum.slip39 import decode_mnemonic, Slip39Error
|
||||
from electrum import mnemonic
|
||||
|
||||
class QEBitcoin(QObject):
|
||||
@@ -18,10 +20,28 @@ class QEBitcoin(QObject):
|
||||
seedValidChanged = pyqtSignal()
|
||||
seedValid = False
|
||||
|
||||
seedTypeChanged = pyqtSignal()
|
||||
seedType = ''
|
||||
|
||||
validationMessageChanged = pyqtSignal()
|
||||
validationMessage = ''
|
||||
|
||||
@pyqtProperty('QString', notify=generatedSeedChanged)
|
||||
def generated_seed(self):
|
||||
return self.generatedSeed
|
||||
|
||||
@pyqtProperty(bool, notify=seedValidChanged)
|
||||
def seed_valid(self):
|
||||
return self.seedValid
|
||||
|
||||
@pyqtProperty('QString', notify=seedTypeChanged)
|
||||
def seed_type(self):
|
||||
return self.seedType
|
||||
|
||||
@pyqtProperty('QString', notify=validationMessageChanged)
|
||||
def validation_message(self):
|
||||
return self.validationMessage
|
||||
|
||||
@pyqtSlot()
|
||||
@pyqtSlot(str)
|
||||
@pyqtSlot(str,str)
|
||||
@@ -36,17 +56,55 @@ class QEBitcoin(QObject):
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio.run_coroutine_threadsafe(co_gen_seed(seed_type, language), loop)
|
||||
|
||||
@pyqtProperty(bool, notify=seedValidChanged)
|
||||
def seed_valid(self):
|
||||
return self.seedValid
|
||||
|
||||
@pyqtSlot(str)
|
||||
@pyqtSlot(str,str)
|
||||
@pyqtSlot(str,str,str)
|
||||
@pyqtSlot(str,str,str,str)
|
||||
def verify_seed(self, seed, bip39=False, seed_type='segwit', language='en'):
|
||||
self._logger.debug('verify seed of type ' + str(seed_type))
|
||||
#TODO
|
||||
#self._logger.debug('seed verified')
|
||||
#self.seedValidChanged.emit()
|
||||
@pyqtSlot(str,bool,bool)
|
||||
@pyqtSlot(str,bool,bool,str,str,str)
|
||||
def verify_seed(self, seed, bip39=False, slip39=False, wallet_type='standard', language='en'):
|
||||
self._logger.debug('bip39 ' + str(bip39))
|
||||
self._logger.debug('slip39 ' + str(slip39))
|
||||
|
||||
seed_type = ''
|
||||
seed_valid = False
|
||||
validation_message = ''
|
||||
|
||||
if not (bip39 or slip39):
|
||||
seed_type = mnemonic.seed_type(seed)
|
||||
if seed_type != '':
|
||||
seed_valid = True
|
||||
elif bip39:
|
||||
is_checksum, is_wordlist = 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
|
||||
seed_valid = False # for now
|
||||
|
||||
elif slip39: # TODO: incomplete impl, this code only validates a single share.
|
||||
try:
|
||||
share = decode_mnemonic(seed)
|
||||
seed_type = 'slip39'
|
||||
validation_message = 'SLIP39: share #%d in %dof%d scheme' % (share.group_index, share.group_threshold, share.group_count)
|
||||
except Slip39Error as e:
|
||||
validation_message = 'SLIP39: %s' % str(e)
|
||||
seed_valid = False # for now
|
||||
|
||||
# cosigning seed
|
||||
if wallet_type != 'standard' and seed_type not in ['standard', 'segwit']:
|
||||
seed_type = ''
|
||||
seed_valid = False
|
||||
|
||||
self.seedType = seed_type
|
||||
self.seedTypeChanged.emit()
|
||||
|
||||
if self.validationMessage != validation_message:
|
||||
self.validationMessage = validation_message
|
||||
self.validationMessageChanged.emit()
|
||||
|
||||
if self.seedValid != seed_valid:
|
||||
self.seedValid = seed_valid
|
||||
self.seedValidChanged.emit()
|
||||
|
||||
self._logger.debug('seed verified: ' + str(seed_valid))
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
|
||||
from electrum.util import register_callback, get_new_wallet_name
|
||||
from electrum.logging import get_logger
|
||||
from electrum.wallet import Wallet, Abstract_Wallet
|
||||
from electrum.storage import WalletStorage
|
||||
from electrum.storage import WalletStorage, StorageReadWriteError
|
||||
|
||||
from .qewallet import QEWallet
|
||||
|
||||
@@ -106,6 +106,9 @@ class QEDaemon(QObject):
|
||||
self._logger.debug('load wallet ' + str(self._path))
|
||||
try:
|
||||
storage = WalletStorage(self._path)
|
||||
if not storage.file_exists():
|
||||
self.couldNotOpenFile.emit()
|
||||
return
|
||||
except StorageReadWriteError as e:
|
||||
self.couldNotOpenFile.emit()
|
||||
return
|
||||
|
||||
@@ -59,8 +59,8 @@ class QEWalletDB(QObject):
|
||||
if wallet_path == self._path:
|
||||
return
|
||||
|
||||
self._logger.info('setting path: ' + wallet_path)
|
||||
self.reset()
|
||||
self._logger.warning('path: ' + wallet_path)
|
||||
self._path = wallet_path
|
||||
|
||||
self.load_storage()
|
||||
@@ -229,6 +229,10 @@ class QEWalletDB(QObject):
|
||||
db.load_plugins()
|
||||
db.write(storage)
|
||||
|
||||
# minimally populate self after create
|
||||
self._password = data['password']
|
||||
self.path = path
|
||||
|
||||
self.createSuccess.emit()
|
||||
except Exception as e:
|
||||
self._logger.error(str(e))
|
||||
|
||||
Reference in New Issue
Block a user