1
mirror of https://github.com/monero-project/monero-gui synced 2024-12-22 03:15:52 +01:00

Add support for creating hardware wallet

This commit is contained in:
stoffu 2018-06-04 17:05:29 +09:00
parent c3f41ae003
commit 9bb6a25f31
No known key found for this signature in database
GPG Key ID: 41DAB8343A9EC012
11 changed files with 282 additions and 8 deletions

BIN
images/createWalletFromDevice.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -222,6 +222,9 @@ ApplicationWindow {
if (typeof wizard.m_wallet !== 'undefined') {
console.log("using wizard wallet")
//Set restoreHeight
if (persistentSettings.restore_height == 0 && persistentSettings.is_recovering_from_device && walletManager.localDaemonSynced()) {
persistentSettings.restore_height = walletManager.blockchainHeight() - 1;
}
if(persistentSettings.restore_height > 0){
// We store restore height in own variable for performance reasons.
restoreHeight = persistentSettings.restore_height
@ -331,7 +334,9 @@ ApplicationWindow {
currentDaemonAddress = localDaemonAddress
console.log("initializing with daemon address: ", currentDaemonAddress)
currentWallet.initAsync(currentDaemonAddress, 0, persistentSettings.is_recovering, persistentSettings.restore_height);
currentWallet.initAsync(currentDaemonAddress, 0, persistentSettings.is_recovering, persistentSettings.is_recovering_from_device, persistentSettings.restore_height);
// save wallet keys in case wallet settings have been changed in the init
currentWallet.setPassword(walletPassword);
}
function walletPath() {
@ -1014,6 +1019,7 @@ ApplicationWindow {
property string payment_id
property int restore_height : 0
property bool is_recovering : false
property bool is_recovering_from_device : false
property bool customDecorations : true
property string daemonFlags
property int logLevel: 0

View File

@ -85,9 +85,11 @@
<file>images/prevPage.png</file>
<file>wizard/WizardOptions.qml</file>
<file>images/createWallet.png</file>
<file>images/createWalletFromDevice.png</file>
<file>images/openAccount.png</file>
<file>images/recoverWallet.png</file>
<file>wizard/WizardCreateWallet.qml</file>
<file>wizard/WizardCreateWalletFromDevice.qml</file>
<file>images/greyTriangle.png</file>
<file>images/copyToClipboard.png</file>
<file>wizard/WizardPassword.qml</file>

View File

@ -172,12 +172,18 @@ bool Wallet::store(const QString &path)
return m_walletImpl->store(path.toStdString());
}
bool Wallet::init(const QString &daemonAddress, quint64 upperTransactionLimit, bool isRecovering, quint64 restoreHeight)
bool Wallet::init(const QString &daemonAddress, quint64 upperTransactionLimit, bool isRecovering, bool isRecoveringFromDevice, quint64 restoreHeight)
{
qDebug() << "init non async";
if (isRecovering){
qDebug() << "RESTORING";
m_walletImpl->setRecoveringFromSeed(true);
}
if (isRecoveringFromDevice){
qDebug() << "RESTORING FROM DEVICE";
m_walletImpl->setRecoveringFromDevice(true);
}
if (isRecovering || isRecoveringFromDevice) {
m_walletImpl->setRefreshFromBlockHeight(restoreHeight);
}
m_walletImpl->init(daemonAddress.toStdString(), upperTransactionLimit, m_daemonUsername.toStdString(), m_daemonPassword.toStdString());
@ -191,7 +197,7 @@ void Wallet::setDaemonLogin(const QString &daemonUsername, const QString &daemon
m_daemonPassword = daemonPassword;
}
void Wallet::initAsync(const QString &daemonAddress, quint64 upperTransactionLimit, bool isRecovering, quint64 restoreHeight)
void Wallet::initAsync(const QString &daemonAddress, quint64 upperTransactionLimit, bool isRecovering, bool isRecoveringFromDevice, quint64 restoreHeight)
{
qDebug() << "initAsync: " + daemonAddress;
// Change status to disconnected if connected
@ -201,7 +207,7 @@ void Wallet::initAsync(const QString &daemonAddress, quint64 upperTransactionLim
}
QFuture<bool> future = QtConcurrent::run(this, &Wallet::init,
daemonAddress, upperTransactionLimit, isRecovering, restoreHeight);
daemonAddress, upperTransactionLimit, isRecovering, isRecoveringFromDevice, restoreHeight);
QFutureWatcher<bool> * watcher = new QFutureWatcher<bool>();
connect(watcher, &QFutureWatcher<bool>::finished,

View File

@ -111,10 +111,10 @@ public:
Q_INVOKABLE bool store(const QString &path = "");
//! initializes wallet
Q_INVOKABLE bool init(const QString &daemonAddress, quint64 upperTransactionLimit = 0, bool isRecovering = false, quint64 restoreHeight = 0);
Q_INVOKABLE bool init(const QString &daemonAddress, quint64 upperTransactionLimit = 0, bool isRecovering = false, bool isRecoveringFromDevice = false, quint64 restoreHeight = 0);
//! initializes wallet asynchronously
Q_INVOKABLE void initAsync(const QString &daemonAddress, quint64 upperTransactionLimit = 0, bool isRecovering = false, quint64 restoreHeight = 0);
Q_INVOKABLE void initAsync(const QString &daemonAddress, quint64 upperTransactionLimit = 0, bool isRecovering = false, bool isRecoveringFromDevice = false, quint64 restoreHeight = 0);
// Set daemon rpc user/pass
Q_INVOKABLE void setDaemonLogin(const QString &daemonUsername = "", const QString &daemonPassword = "");

View File

@ -104,6 +104,20 @@ Wallet *WalletManager::createWalletFromKeys(const QString &path, const QString &
return m_currentWallet;
}
Wallet *WalletManager::createWalletFromDevice(const QString &path, const QString &password, NetworkType::Type nettype,
const QString &deviceName, quint64 restoreHeight, const QString &subaddressLookahead)
{
QMutexLocker locker(&m_mutex);
if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet;
delete m_currentWallet;
m_currentWallet = NULL;
}
Monero::Wallet * w = m_pimpl->createWalletFromDevice(path.toStdString(), password.toStdString(), static_cast<Monero::NetworkType>(nettype),
deviceName.toStdString(), restoreHeight, subaddressLookahead.toStdString());
m_currentWallet = new Wallet(w);
return m_currentWallet;
}
QString WalletManager::closeWallet()
{

View File

@ -62,6 +62,12 @@ public:
const QString &spendkey = "",
quint64 restoreHeight = 0);
Q_INVOKABLE Wallet * createWalletFromDevice(const QString &path,
const QString &password,
NetworkType::Type nettype,
const QString &deviceName,
quint64 restoreHeight = 0,
const QString &subaddressLookahead = "");
/*!
* \brief closeWallet - closes current open wallet and frees memory
* \return wallet address

View File

@ -0,0 +1,123 @@
// Copyright (c) 2014-2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.2
import moneroComponents.WalletManager 1.0
import moneroComponents.Wallet 1.0
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.2
import 'utils.js' as Utils
ColumnLayout {
opacity: 0
visible: false
Behavior on opacity {
NumberAnimation { duration: 100; easing.type: Easing.InQuad }
}
onOpacityChanged: visible = opacity !== 0
function onWizardRestarted() {
// reset account name field
uiItem.accountNameText = defaultAccountName
}
//! function called each time we display this page
function onPageOpened(settingsOblect) {
uiItem.checkNextButton()
uiItem.deviceNameDropdown.update()
}
function onPageClosed(settingsObject) {
settingsObject['account_name'] = uiItem.accountNameText
settingsObject['wallet_path'] = uiItem.walletPath
var restoreHeight = parseInt(uiItem.restoreHeight);
settingsObject['restore_height'] = isNaN(restoreHeight)? 0 : restoreHeight;
settingsObject['subaddress_lookahead'] = uiItem.subaddressLookahead;
settingsObject['deviceName'] = uiItem.deviceName;
var walletFullPath = wizard.createWalletPath(uiItem.walletPath,uiItem.accountNameText);
if (!wizard.walletPathValid(walletFullPath)) {
return false;
}
return createWalletFromDevice(settingsObject)
}
//! function called each time we hide this page
//
function createWalletFromDevice(settingsObject) {
// TODO: create wallet in temporary filename and a) move it to the path specified by user after the final
// page submitted or b) delete it when program closed before reaching final page
// Always delete the wallet object before creating new - we could be stepping back from recovering wallet
if (typeof m_wallet !== 'undefined') {
walletManager.closeWallet()
console.log("deleting wallet")
}
var tmp_wallet_filename = oshelper.temporaryFilename();
console.log("Creating temporary wallet", tmp_wallet_filename)
var nettype = appWindow.persistentSettings.nettype;
var restoreHeight = settingsObject.restore_height;
var subaddressLookahead = settingsObject.subaddress_lookahead;
var deviceName = settingsObject.deviceName;
var wallet = walletManager.createWalletFromDevice(tmp_wallet_filename, "", nettype, deviceName, restoreHeight, subaddressLookahead);
var success = wallet.status === Wallet.Status_Ok;
if (success) {
m_wallet = wallet;
settingsObject['is_recovering_from_device'] = true;
settingsObject['tmp_wallet_filename'] = tmp_wallet_filename
} else {
console.log(wallet.errorString)
walletErrorDialog.text = wallet.errorString;
walletErrorDialog.open();
walletManager.closeWallet();
}
return success;
}
WizardManageWalletUI {
id: uiItem
titleText: qsTr("Create a new wallet from hardware device") + translationManager.emptyString
wordsTextItem.clipboardButtonVisible: false
wordsTextItem.tipTextVisible: false
restoreHeightVisible:true
recoverMode: false
recoverFromDevice: true
}
Component.onCompleted: {
parent.wizardRestarted.connect(onWizardRestarted)
}
}

View File

@ -54,6 +54,7 @@ ColumnLayout {
"create_wallet" : [welcomePage, optionsPage, createWalletPage, passwordPage, daemonSettingsPage, finishPage ],
"recovery_wallet" : [welcomePage, optionsPage, recoveryWalletPage, passwordPage, daemonSettingsPage, finishPage ],
"create_view_only_wallet" : [ createViewOnlyWalletPage, passwordPage ],
"create_wallet_from_device" : [welcomePage, optionsPage, createWalletFromDevicePage, passwordPage, daemonSettingsPage, finishPage ],
}
property string currentPath: "create_wallet"
@ -162,6 +163,16 @@ ColumnLayout {
rootItem.state = "wizard";
}
function openCreateWalletFromDevicePage() {
wizardRestarted();
print ("show create wallet from device page");
currentPath = "create_wallet_from_device"
pages = paths[currentPath]
wizard.nextButton.visible = true
// goto next page
switchPage(true);
}
function createWalletPath(folder_path,account_name){
// Remove trailing slash - (default on windows and mac)
@ -233,6 +244,7 @@ ColumnLayout {
appWindow.persistentSettings.auto_donations_amount = false //settings.auto_donations_amount
appWindow.persistentSettings.restore_height = (isNaN(settings.restore_height))? 0 : settings.restore_height
appWindow.persistentSettings.is_recovering = (settings.is_recovering === undefined)? false : settings.is_recovering
appWindow.persistentSettings.is_recovering_from_device = (settings.is_recovering_from_device === undefined)? false : settings.is_recovering_from_device
}
// reading settings from persistent storage
@ -263,6 +275,7 @@ ColumnLayout {
onCreateWalletClicked: wizard.openCreateWalletPage()
onRecoveryWalletClicked: wizard.openRecoveryWalletPage()
onOpenWalletClicked: wizard.openOpenWalletPage();
onCreateWalletFromDeviceClicked: wizard.openCreateWalletFromDevicePage()
}
WizardCreateWallet {
@ -283,6 +296,12 @@ ColumnLayout {
Layout.topMargin: wizardTopMargin
}
WizardCreateWalletFromDevice {
id: createWalletFromDevicePage
Layout.bottomMargin: wizardBottomMargin
Layout.topMargin: wizardTopMargin
}
WizardPassword {
id: passwordPage
Layout.bottomMargin: wizardBottomMargin

View File

@ -44,6 +44,7 @@ ColumnLayout {
property alias wordsTextItem : memoTextItem
property alias restoreHeight : restoreHeightItem.text
property alias restoreHeightVisible: restoreHeightItem.visible
property alias subaddressLookahead : subaddressLookaheadItem.text
property alias walletName : accountName.text
property alias progressDotsModel : progressDots.model
property alias recoverFromKeysAddress: addressLine.text;
@ -53,6 +54,10 @@ ColumnLayout {
property bool recoverMode: false
// Recover form seed or keys
property bool recoverFromSeedMode: true
// Recover form hardware device
property bool recoverFromDevice: false
property var deviceName: deviceNameModel.get(deviceNameDropdown.currentIndex).column2
property alias deviceNameDropdown: deviceNameDropdown
property int rowSpacing: 10
function checkFields(){
@ -215,7 +220,7 @@ ColumnLayout {
// Recover from seed
RowLayout {
id: recoverFromSeed
visible: !recoverMode || ( recoverMode && recoverFromSeedMode)
visible: !recoverFromDevice && (!recoverMode || ( recoverMode && recoverFromSeedMode))
WizardMemoTextInput {
id : memoTextItem
Layout.fillWidth: true
@ -303,9 +308,56 @@ ColumnLayout {
fontBold: false
}
}
// Subaddress lookahead
RowLayout {
visible: recoverFromDevice
LineEdit {
id: subaddressLookaheadItem
Layout.fillWidth: true
Layout.maximumWidth: 600 * scaleRatio
Layout.minimumWidth: 200 * scaleRatio
placeholderFontBold: true
placeholderFontFamily: "Arial"
placeholderColor: Style.legacy_placeholderFontColor
placeholderText: qsTr("Subaddress lookahead (optional): <major>:<minor>") + translationManager.emptyString
placeholderOpacity: 1.0
borderColor: Qt.rgba(0, 0, 0, 0.15)
backgroundColor: "white"
fontColor: "black"
fontBold: false
}
}
// Device name
ColumnLayout {
visible: recoverFromDevice
Label {
Layout.topMargin: 20 * scaleRatio
fontFamily: "Arial"
fontColor: "#555555"
fontSize: 14 * scaleRatio
text: qsTr("Device name") + translationManager.emptyString
}
ListModel {
id: deviceNameModel
ListElement { column1: qsTr("Ledger") ; column2: "Ledger"; }
// ListElement { column1: qsTr("Trezor") ; column2: "Trezor"; }
}
StandardDropdown {
id: deviceNameDropdown
dataModel: deviceNameModel
Layout.fillWidth: true
Layout.topMargin: 6
colorHeaderBackground: "black"
releasedColor: "#363636"
pressedColor: "#202020"
}
}
// Wallet store location
ColumnLayout {
z: deviceNameDropdown.z - 1
Label {
Layout.fillWidth: true
Layout.topMargin: 20 * scaleRatio

View File

@ -37,9 +37,10 @@ ColumnLayout {
signal createWalletClicked()
signal recoveryWalletClicked()
signal openWalletClicked()
signal createWalletFromDeviceClicked()
opacity: 0
visible: false
property int buttonSize: (isMobile) ? 80 * scaleRatio : 190 * scaleRatio
property int buttonSize: (isMobile) ? 80 * scaleRatio : 140 * scaleRatio
property int buttonImageSize: (isMobile) ? buttonSize - 10 * scaleRatio : buttonSize - 30 * scaleRatio
function onPageClosed() {
@ -227,6 +228,51 @@ ColumnLayout {
wrapMode: Text.WordWrap
}
}
GridLayout {
Layout.fillHeight: true
Layout.fillWidth: true
flow: !isMobile ? GridLayout.TopToBottom : GridLayout.LeftToRight
rowSpacing: 20 * scaleRatio
columnSpacing: 10 * scaleRatio
Rectangle {
Layout.preferredHeight: page.buttonSize
Layout.preferredWidth: page.buttonSize
radius: page.buttonSize
color: createWalletFromDeviceArea.containsMouse ? "#DBDBDB" : "#FFFFFF"
Image {
width: page.buttonImageSize
height: page.buttonImageSize
fillMode: Image.PreserveAspectFit
horizontalAlignment: Image.AlignRight
verticalAlignment: Image.AlignTop
anchors.centerIn: parent
source: "qrc:///images/createWalletFromDevice.png"
}
MouseArea {
id: createWalletFromDeviceArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
page.createWalletFromDeviceClicked()
}
}
}
Text {
Layout.preferredWidth: page.buttonSize
font.family: "Arial"
font.pixelSize: 16 * scaleRatio
color: "#4A4949"
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
text: qsTr("Create a new wallet from hardware device") + translationManager.emptyString
}
}
}
ColumnLayout {