qml: implement initial wallet from master key
This commit is contained in:
@@ -47,8 +47,12 @@ Wizard {
|
||||
if (wizard_data['seed_type'] != 'bip39' && Daemon.singlePassword)
|
||||
page.last = true
|
||||
break
|
||||
// case 'masterkey'
|
||||
// case 'hardware'
|
||||
case 'masterkey':
|
||||
page = _loadNextComponent(components.havemasterkey, wizard_data)
|
||||
page.next.connect(function() {havemasterkeyDone()})
|
||||
if (Daemon.singlePassword)
|
||||
page.last = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +91,12 @@ Wizard {
|
||||
page.last = true
|
||||
}
|
||||
|
||||
function havemasterkeyDone(d) {
|
||||
console.log('have master key done')
|
||||
var page = _loadNextComponent(components.walletpassword, wizard_data)
|
||||
page.last = true
|
||||
}
|
||||
|
||||
Item {
|
||||
id: components
|
||||
property Component walletname: Component {
|
||||
@@ -117,6 +127,10 @@ Wizard {
|
||||
WCBIP39Refine {}
|
||||
}
|
||||
|
||||
property Component havemasterkey: Component {
|
||||
WCHaveMasterKey {}
|
||||
}
|
||||
|
||||
property Component walletpassword: Component {
|
||||
WCWalletPassword {}
|
||||
}
|
||||
|
||||
86
electrum/gui/qml/components/wizard/WCHaveMasterKey.qml
Normal file
86
electrum/gui/qml/components/wizard/WCHaveMasterKey.qml
Normal file
@@ -0,0 +1,86 @@
|
||||
import QtQuick 2.6
|
||||
import QtQuick.Layouts 1.0
|
||||
import QtQuick.Controls 2.1
|
||||
|
||||
import org.electrum 1.0
|
||||
|
||||
import "../controls"
|
||||
|
||||
WizardComponent {
|
||||
id: root
|
||||
|
||||
valid: false
|
||||
|
||||
onAccept: {
|
||||
wizard_data['master_key'] = masterkey_ta.text
|
||||
}
|
||||
|
||||
function verifyMasterKey(key) {
|
||||
return valid = bitcoin.verify_master_key(key)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
|
||||
Label { text: qsTr('Create keystore from a master key') }
|
||||
|
||||
RowLayout {
|
||||
TextArea {
|
||||
id: masterkey_ta
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 80
|
||||
focus: true
|
||||
wrapMode: TextEdit.WrapAnywhere
|
||||
onTextChanged: verifyMasterKey(text)
|
||||
}
|
||||
ColumnLayout {
|
||||
ToolButton {
|
||||
icon.source: '../../../icons/paste.png'
|
||||
icon.height: constants.iconSizeMedium
|
||||
icon.width: constants.iconSizeMedium
|
||||
onClicked: {
|
||||
if (verifyMasterKey(AppController.clipboardToText()))
|
||||
masterkey_ta.text = AppController.clipboardToText()
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
icon.source: '../../../icons/qrcode.png'
|
||||
icon.height: constants.iconSizeMedium
|
||||
icon.width: constants.iconSizeMedium
|
||||
scale: 1.2
|
||||
onClicked: {
|
||||
var scan = qrscan.createObject(root)
|
||||
scan.onFound.connect(function() {
|
||||
if (verifyMasterKey(scan.scanData))
|
||||
masterkey_ta.text = scan.scanData
|
||||
scan.destroy()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: qrscan
|
||||
QRScan {
|
||||
width: root.width
|
||||
height: root.height
|
||||
|
||||
ToolButton {
|
||||
icon.source: '../../../icons/closebutton.png'
|
||||
icon.height: constants.iconSizeMedium
|
||||
icon.width: constants.iconSizeMedium
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
onClicked: {
|
||||
parent.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bitcoin {
|
||||
id: bitcoin
|
||||
}
|
||||
}
|
||||
@@ -27,13 +27,13 @@ WizardComponent {
|
||||
text: qsTr('I already have a seed')
|
||||
}
|
||||
RadioButton {
|
||||
enabled: false
|
||||
ButtonGroup.group: keystoregroup
|
||||
property string keystoretype: 'masterkey'
|
||||
text: qsTr('Use a master key')
|
||||
}
|
||||
RadioButton {
|
||||
enabled: false
|
||||
visible: false
|
||||
ButtonGroup.group: keystoregroup
|
||||
property string keystoretype: 'hardware'
|
||||
text: qsTr('Use a hardware device')
|
||||
|
||||
@@ -3,8 +3,9 @@ import asyncio
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||
|
||||
from electrum import mnemonic
|
||||
from electrum.bip32 import is_bip32_derivation
|
||||
from electrum.keystore import bip39_is_checksum_valid
|
||||
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 parse_URI, create_bip21_uri, InvalidBitcoinURI, get_asyncio_loop
|
||||
@@ -46,6 +47,12 @@ class QEBitcoin(QObject):
|
||||
def validation_message(self):
|
||||
return self.validationMessage
|
||||
|
||||
@validation_message.setter
|
||||
def validation_message(self, msg):
|
||||
if self.validationMessage != msg:
|
||||
self.validationMessage = msg
|
||||
self.validationMessageChanged.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
@pyqtSlot(str)
|
||||
@pyqtSlot(str,str)
|
||||
@@ -68,16 +75,16 @@ class QEBitcoin(QObject):
|
||||
|
||||
seed_type = ''
|
||||
seed_valid = False
|
||||
validation_message = ''
|
||||
self.validationMessage = ''
|
||||
|
||||
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)
|
||||
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
|
||||
self.validationMessage = 'BIP39 (%s)' % status
|
||||
|
||||
if is_checksum:
|
||||
seed_type = 'bip39'
|
||||
@@ -87,9 +94,9 @@ class QEBitcoin(QObject):
|
||||
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)
|
||||
self.validationMessage = '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)
|
||||
self.validationMessage = 'SLIP39: %s' % str(e)
|
||||
seed_valid = False # for now
|
||||
|
||||
# cosigning seed
|
||||
@@ -100,16 +107,32 @@ class QEBitcoin(QObject):
|
||||
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))
|
||||
|
||||
@pyqtSlot(str, result=bool)
|
||||
@pyqtSlot(str, str, result=bool)
|
||||
def verify_master_key(self, key, wallet_type='standard'):
|
||||
self.validationMessage = ''
|
||||
if not keystore.is_master_key(key):
|
||||
self.validationMessage = _('Not a master key')
|
||||
return False
|
||||
|
||||
if wallet_type == 'standard':
|
||||
# validation message?
|
||||
k = keystore.from_master_key(key)
|
||||
has_xpub = isinstance(k, keystore.Xpub)
|
||||
assert has_xpub
|
||||
t1 = xpub_type(k.xpub)
|
||||
if t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']:
|
||||
self.validationMessage = '%s: %s' % (_('Wrong key type'), t1)
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
@pyqtSlot(str, result=bool)
|
||||
def verify_derivation_path(self, path):
|
||||
return is_bip32_derivation(path)
|
||||
|
||||
@@ -5,7 +5,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||
from electrum.logging import get_logger
|
||||
from electrum.storage import WalletStorage, StorageEncryptionVersion
|
||||
from electrum.wallet_db import WalletDB
|
||||
from electrum.bip32 import normalize_bip32_derivation
|
||||
from electrum.bip32 import normalize_bip32_derivation, xpub_type
|
||||
from electrum.util import InvalidPassword
|
||||
from electrum import keystore
|
||||
|
||||
@@ -178,6 +178,8 @@ class QEWalletDB(QObject):
|
||||
data = js_data.toVariant()
|
||||
self._logger.debug(str(data))
|
||||
|
||||
assert data['wallet_type'] == 'standard' # only standard wallets for now
|
||||
|
||||
if single_password_enabled and single_password:
|
||||
data['encrypt'] = True
|
||||
data['password'] = single_password
|
||||
@@ -188,17 +190,27 @@ class QEWalletDB(QObject):
|
||||
raise Exception('file already exists at path')
|
||||
storage = WalletStorage(path)
|
||||
|
||||
if data['seed_type'] in ['old', 'standard', 'segwit']: #2fa, 2fa-segwit
|
||||
self._logger.debug('creating keystore from electrum seed')
|
||||
k = keystore.from_seed(data['seed'], data['seed_extra_words'], data['wallet_type'] == 'multisig')
|
||||
elif data['seed_type'] == 'bip39':
|
||||
self._logger.debug('creating keystore from bip39 seed')
|
||||
root_seed = keystore.bip39_to_seed(data['seed'], data['seed_extra_words'])
|
||||
derivation = normalize_bip32_derivation(data['derivation_path'])
|
||||
script = data['script_type'] if data['script_type'] != 'p2pkh' else 'standard'
|
||||
k = keystore.from_bip43_rootseed(root_seed, derivation, xtype=script)
|
||||
if data['keystore_type'] in ['createseed', 'haveseed']:
|
||||
if data['seed_type'] in ['old', 'standard', 'segwit']: #2fa, 2fa-segwit
|
||||
self._logger.debug('creating keystore from electrum seed')
|
||||
k = keystore.from_seed(data['seed'], data['seed_extra_words'], data['wallet_type'] == 'multisig')
|
||||
elif data['seed_type'] == 'bip39':
|
||||
self._logger.debug('creating keystore from bip39 seed')
|
||||
root_seed = keystore.bip39_to_seed(data['seed'], data['seed_extra_words'])
|
||||
derivation = normalize_bip32_derivation(data['derivation_path'])
|
||||
script = data['script_type'] if data['script_type'] != 'p2pkh' else 'standard'
|
||||
k = keystore.from_bip43_rootseed(root_seed, derivation, xtype=script)
|
||||
else:
|
||||
raise Exception('unsupported/unknown seed_type %s' % data['seed_type'])
|
||||
elif data['keystore_type'] == 'masterkey':
|
||||
k = keystore.from_master_key(data['master_key'])
|
||||
has_xpub = isinstance(k, keystore.Xpub)
|
||||
assert has_xpub
|
||||
t1 = xpub_type(k.xpub)
|
||||
if t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']:
|
||||
raise Exception('wrong key type %s' % t1)
|
||||
else:
|
||||
raise Exception('unsupported/unknown seed_type %s' % data['seed_type'])
|
||||
raise Exception('unsupported/unknown keystore_type %s' % data['keystore_type'])
|
||||
|
||||
if data['encrypt']:
|
||||
if k.may_have_password():
|
||||
@@ -209,7 +221,8 @@ class QEWalletDB(QObject):
|
||||
db.set_keystore_encryption(bool(data['password']) and data['encrypt'])
|
||||
|
||||
db.put('wallet_type', data['wallet_type'])
|
||||
db.put('seed_type', data['seed_type'])
|
||||
if 'seed_type' in data:
|
||||
db.put('seed_type', data['seed_type'])
|
||||
db.put('keystore', k.dump())
|
||||
if k.can_have_deterministic_lightning_xprv():
|
||||
db.put('lightning_xprv', k.get_lightning_xprv(data['password'] if data['encrypt'] else None))
|
||||
@@ -223,5 +236,5 @@ class QEWalletDB(QObject):
|
||||
|
||||
self.createSuccess.emit()
|
||||
except Exception as e:
|
||||
self._logger.error(str(e))
|
||||
self._logger.error(repr(e))
|
||||
self.createError.emit(str(e))
|
||||
|
||||
Reference in New Issue
Block a user