GUI cold signing

This commit is contained in:
Jaquee 2017-01-12 20:53:27 +01:00
parent fd983955b4
commit 15c79df378
No known key found for this signature in database
GPG Key ID: 384E52B09F45DC39
10 changed files with 454 additions and 35 deletions

View File

@ -40,6 +40,7 @@
#include "Wallet.h"
#include "QRCodeImageProvider.h"
#include "PendingTransaction.h"
#include "UnsignedTransaction.h"
#include "TranslationManager.h"
#include "TransactionInfo.h"
#include "TransactionHistory.h"
@ -72,6 +73,9 @@ int main(int argc, char *argv[])
qmlRegisterUncreatableType<PendingTransaction>("moneroComponents.PendingTransaction", 1, 0, "PendingTransaction",
"PendingTransaction can't be instantiated directly");
qmlRegisterUncreatableType<UnsignedTransaction>("moneroComponents.UnsignedTransaction", 1, 0, "UnsignedTransaction",
"UnsignedTransaction can't be instantiated directly");
qmlRegisterUncreatableType<WalletManager>("moneroComponents.WalletManager", 1, 0, "WalletManager",
"WalletManager can't be instantiated directly");

View File

@ -228,6 +228,8 @@ ApplicationWindow {
currentWallet = wallet
updateSyncing(false)
viewOnly = currentWallet.viewOnly;
// connect handlers
currentWallet.refreshed.connect(onWalletRefresh)
currentWallet.updated.connect(onWalletUpdate)
@ -293,7 +295,6 @@ ApplicationWindow {
// wallet opened successfully, subscribing for wallet updates
connectWallet(wallet)
}
@ -474,7 +475,7 @@ ApplicationWindow {
// called on "transfer"
function handlePayment(address, paymentId, amount, mixinCount, priority, description) {
function handlePayment(address, paymentId, amount, mixinCount, priority, description, createFile) {
console.log("Creating transaction: ")
console.log("\taddress: ", address,
", payment_id: ", paymentId,
@ -522,6 +523,24 @@ ApplicationWindow {
currentWallet.createTransactionAsync(address, paymentId, amountxmr, mixinCount, priority);
}
//Choose where to save transaction
FileDialog {
id: saveTxDialog
title: "Please choose a location"
folder: "file://" +moneroAccountsDir
selectExisting: false;
onAccepted: {
handleTransactionConfirmed()
}
onRejected: {
// do nothing
}
}
function handleSweepUnmixable() {
console.log("Creating transaction: ")
@ -562,7 +581,7 @@ ApplicationWindow {
}
// called after user confirms transaction
function handleTransactionConfirmed() {
function handleTransactionConfirmed(fileName) {
// grab transaction.txid before commit, since it clears it.
// we actually need to copy it, because QML will incredibly
// call the function multiple times when the variable is used
@ -573,6 +592,20 @@ ApplicationWindow {
for (var i = 0; i < txid_org.length; ++i)
txid[i] = txid_org[i]
// View only wallet - we save the tx
if(viewOnly && saveTxDialog.fileUrl){
// No file specified - abort
if(!saveTxDialog.fileUrl) {
currentWallet.disposeTransaction(transaction)
return;
}
var path = walletManager.urlToLocalPath(saveTxDialog.fileUrl)
// Store to file
transaction.setFilename(path);
}
if (!transaction.commit()) {
console.log("Error committing transaction: " + transaction.errorString);
informationPopup.title = qsTr("Error") + translationManager.emptyString
@ -585,7 +618,7 @@ ApplicationWindow {
txid_text += ", "
txid_text += txid[i]
}
informationPopup.text = qsTr("Money sent successfully: %1 transaction(s) ").arg(txid.length) + txid_text + translationManager.emptyString
informationPopup.text = (viewOnly)? qsTr("Transaction saved to file: %1").arg(path) : qsTr("Money sent successfully: %1 transaction(s) ").arg(txid.length) + txid_text + translationManager.emptyString
informationPopup.icon = StandardIcon.Information
if (transactionDescription.length > 0) {
for (var i = 0; i < txid.length; ++i)
@ -771,10 +804,31 @@ ApplicationWindow {
id: transactionConfirmationPopup
onAccepted: {
close();
handleTransactionConfirmed()
// Save transaction to file if view only wallet
if(viewOnly) {
saveTxDialog.open();
return;
} else
handleTransactionConfirmed()
}
}
StandardDialog {
id: confirmationDialog
property var onAcceptedCallback
property var onRejectedCallback
onAccepted: {
if (onAcceptedCallback)
onAcceptedCallback()
}
onRejected: {
if (onRejectedCallback)
onRejectedCallback();
}
}
//Open Wallet from file
FileDialog {
id: fileDialog

View File

@ -9,7 +9,7 @@ CONFIG += c++11
# cleaning "auto-generated" bitmonero directory on "make distclean"
QMAKE_DISTCLEAN += -r $$WALLET_ROOT
INCLUDEPATH += $$WALLET_ROOT/include \
INCLUDEPATH += $$WALLET_ROOT/include \
$$PWD/src/libwalletqt \
$$PWD/src/QR-Code-generator \
$$PWD/src \
@ -36,7 +36,8 @@ HEADERS += \
src/daemon/DaemonManager.h \
src/model/AddressBookModel.h \
src/libwalletqt/AddressBook.h \
src/zxcvbn-c/zxcvbn.h
src/zxcvbn-c/zxcvbn.h \
src/libwalletqt/UnsignedTransaction.h
SOURCES += main.cpp \
@ -59,7 +60,8 @@ SOURCES += main.cpp \
src/daemon/DaemonManager.cpp \
src/model/AddressBookModel.cpp \
src/libwalletqt/AddressBook.cpp \
src/zxcvbn-c/zxcvbn.c
src/zxcvbn-c/zxcvbn.c \
src/libwalletqt/UnsignedTransaction.cpp
lupdate_only {
SOURCES = *.qml \
@ -289,7 +291,8 @@ OTHER_FILES += \
$$TRANSLATIONS
DISTFILES += \
notes.txt
notes.txt \
monero/src/wallet/CMakeLists.txt
# windows application icon

View File

@ -92,7 +92,10 @@ Rectangle {
Item {
id: pageRoot
anchors.fill: parent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height:550
Label {
id: amountLabel
anchors.left: parent.left
@ -381,7 +384,7 @@ Rectangle {
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
enabled : pageRoot.checkInformation(amountLine.text, addressLine.text, paymentIdLine.text, appWindow.persistentSettings.testnet)
enabled : !appWindow.viewOnly && pageRoot.checkInformation(amountLine.text, addressLine.text, paymentIdLine.text, appWindow.persistentSettings.testnet)
onClicked: {
console.log("Transfer: paymentClicked")
var priority = priorityModel.get(priorityDropdown.currentIndex).priority
@ -395,25 +398,7 @@ Rectangle {
}
}
StandardButton {
id: sweepUnmixableButton
anchors.right: parent.right
anchors.top: descriptionLine.bottom
anchors.rightMargin: 17
anchors.topMargin: 17
width: 60*2
text: qsTr("SWEEP UNMIXABLE") + translationManager.emptyString
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
enabled : true
onClicked: {
console.log("Transfer: sweepUnmixableClicked")
root.sweepUnmixableClicked()
}
}
} // pageRoot
Rectangle {
id:desaturate
@ -422,7 +407,192 @@ Rectangle {
opacity: 0.1
visible: (pageRoot.enabled)? 0 : 1;
}
} // Rectangle
ColumnLayout {
anchors.top: pageRoot.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 17
spacing:10
enabled: !viewOnly || pageRoot.enabled
RowLayout {
Label {
id: manageWalletLabel
Layout.fillWidth: true
color: "#4A4949"
text: qsTr("Advanced") + translationManager.emptyString
fontSize: 16
Layout.topMargin: 20
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: "#DEDEDE"
}
RowLayout {
StandardButton {
id: sweepUnmixableButton
text: qsTr("SWEEP UNMIXABLE") + translationManager.emptyString
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
enabled : pageRoot.enabled
onClicked: {
console.log("Transfer: sweepUnmixableClicked")
root.sweepUnmixableClicked()
}
}
StandardButton {
id: saveTxButton
text: qsTr("create tx file") + translationManager.emptyString
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
visible: appWindow.viewOnly
enabled: pageRoot.checkInformation(amountLine.text, addressLine.text, paymentIdLine.text, appWindow.persistentSettings.testnet)
onClicked: {
console.log("Transfer: saveTx Clicked")
var priority = priorityModel.get(priorityDropdown.currentIndex).priority
console.log("priority: " + priority)
console.log("amount: " + amountLine.text)
addressLine.text = addressLine.text.trim()
paymentIdLine.text = paymentIdLine.text.trim()
root.paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, scaleValueToMixinCount(privacyLevelItem.fillLevel),
priority, descriptionLine.text)
}
}
StandardButton {
id: signTxButton
text: qsTr("sign tx file") + translationManager.emptyString
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
visible: !appWindow.viewOnly
onClicked: {
console.log("Transfer: sign tx clicked")
signTxDialog.open();
}
}
StandardButton {
id: submitTxButton
text: qsTr("submit tx file") + translationManager.emptyString
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
visible: appWindow.viewOnly
enabled: pageRoot.enabled
onClicked: {
console.log("Transfer: submit tx clicked")
submitTxDialog.open();
}
}
}
}
//SignTxDialog
FileDialog {
id: signTxDialog
title: "Please choose a file"
folder: "file://" +moneroAccountsDir
nameFilters: [ "Unsigned transfers (*)"]
onAccepted: {
var path = walletManager.urlToLocalPath(fileUrl);
// Load the unsigned tx from file
var transaction = currentWallet.loadTxFile(path);
if (transaction.status !== PendingTransaction.Status_Ok) {
console.error("Can't load unsigned transaction: ", transaction.errorString);
informationPopup.title = qsTr("Error") + translationManager.emptyString;
informationPopup.text = qsTr("Can't load unsigned transaction: ") + transaction.errorString
informationPopup.icon = StandardIcon.Critical
informationPopup.onCloseCallback = null
informationPopup.open();
// deleting transaction object, we don't want memleaks
transaction.destroy();
} else {
confirmationDialog.text = qsTr("\nNumber of transactions: ") + transaction.txCount
for (var i = 0; i < transaction.txCount; ++i) {
confirmationDialog.text += qsTr("\nTransaction #%1").arg(i+1)
+qsTr("\nRecipient: ") + transaction.recipientAddress[i]
+ (transaction.paymentId[i] == "" ? "" : qsTr("\n\payment ID: ") + transaction.paymentId[i])
+ qsTr("\nAmount: ") + walletManager.displayAmount(transaction.amount(i))
+ qsTr("\nFee: ") + walletManager.displayAmount(transaction.fee(i))
+ qsTr("\nMixin: ") + transaction.mixin(i)
// TODO: add descriptions to unsigned_tx_set?
// + (transactionDescription === "" ? "" : (qsTr("\n\nDescription: ") + transactionDescription))
+ translationManager.emptyString
if (i > 0) {
confirmationDialog.text += "\n\n"
}
}
console.log(transaction.confirmationMessage);
// Show confirmation dialog
confirmationDialog.title = qsTr("Confirmation") + translationManager.emptyString
confirmationDialog.icon = StandardIcon.Question
confirmationDialog.onAcceptedCallback = function() {
transaction.sign(path+"_signed");
transaction.destroy();
};
confirmationDialog.onRejectedCallback = transaction.destroy;
confirmationDialog.open()
}
}
onRejected: {
// File dialog closed
console.log("Canceled")
}
}
//SignTxDialog
FileDialog {
id: submitTxDialog
title: "Please choose a file"
folder: "file://" +moneroAccountsDir
nameFilters: [ "signed transfers (*)"]
onAccepted: {
if(!currentWallet.submitTxFile(walletManager.urlToLocalPath(fileUrl))){
informationPopup.title = qsTr("Error") + translationManager.emptyString;
informationPopup.text = qsTr("Can't submit transaction: ") + currentWallet.errorString
informationPopup.icon = StandardIcon.Critical
informationPopup.onCloseCallback = null
informationPopup.open();
} else {
informationPopup.title = qsTr("Information") + translationManager.emptyString
informationPopup.text = qsTr("Money sent successfully") + translationManager.emptyString
informationPopup.icon = StandardIcon.Information
informationPopup.onCloseCallback = null
informationPopup.open();
}
}
onRejected: {
console.log("Canceled")
}
}
Rectangle {
x: root.width/2 - width/2
@ -465,9 +635,10 @@ Rectangle {
}
if (currentWallet.viewOnly) {
statusText.text = qsTr("Wallet is view only.")
return;
// statusText.text = qsTr("Wallet is view only.")
//return;
}
pageRoot.enabled = false;
switch (currentWallet.connected) {
case Wallet.ConnectionStatus_Disconnected:

View File

@ -13,7 +13,10 @@ QString PendingTransaction::errorString() const
bool PendingTransaction::commit()
{
return m_pimpl->commit();
// Save transaction to file if fileName is set.
if(!m_fileName.isEmpty())
return m_pimpl->commit(m_fileName.toStdString());
return m_pimpl->commit(m_fileName.toStdString());
}
quint64 PendingTransaction::amount() const
@ -47,6 +50,11 @@ quint64 PendingTransaction::txCount() const
return m_pimpl->txCount();
}
void PendingTransaction::setFilename(const QString &fileName)
{
m_fileName = fileName;
}
PendingTransaction::PendingTransaction(Monero::PendingTransaction *pt, QObject *parent)
: QObject(parent), m_pimpl(pt)
{

View File

@ -44,6 +44,7 @@ public:
quint64 fee() const;
QStringList txid() const;
quint64 txCount() const;
Q_INVOKABLE void setFilename(const QString &fileName);
private:
explicit PendingTransaction(Monero::PendingTransaction * pt, QObject *parent = 0);
@ -51,6 +52,7 @@ private:
private:
friend class Wallet;
Monero::PendingTransaction * m_pimpl;
QString m_fileName;
};
#endif // PENDINGTRANSACTION_H

View File

@ -0,0 +1,89 @@
#include "UnsignedTransaction.h"
#include <QVector>
#include <QDebug>
UnsignedTransaction::Status UnsignedTransaction::status() const
{
return static_cast<Status>(m_pimpl->status());
}
QString UnsignedTransaction::errorString() const
{
return QString::fromStdString(m_pimpl->errorString());
}
quint64 UnsignedTransaction::amount(int index) const
{
std::vector<uint64_t> arr = m_pimpl->amount();
if(index > arr.size() - 1)
return 0;
return arr[index];
}
quint64 UnsignedTransaction::fee(int index) const
{
std::vector<uint64_t> arr = m_pimpl->fee();
if(index > arr.size() - 1)
return 0;
return arr[index];
}
quint64 UnsignedTransaction::mixin(int index) const
{
std::vector<uint64_t> arr = m_pimpl->mixin();
if(index > arr.size() - 1)
return 0;
return arr[index];
}
quint64 UnsignedTransaction::txCount() const
{
return m_pimpl->txCount();
}
quint64 UnsignedTransaction::minMixinCount() const
{
return m_pimpl->minMixinCount();
}
QString UnsignedTransaction::confirmationMessage() const
{
return QString::fromStdString(m_pimpl->confirmationMessage());
}
QStringList UnsignedTransaction::paymentId() const
{
QList<QString> list;
for (const auto &t: m_pimpl->paymentId())
list.append(QString::fromStdString(t));
return list;
}
QStringList UnsignedTransaction::recipientAddress() const
{
QList<QString> list;
for (const auto &t: m_pimpl->recipientAddress())
list.append(QString::fromStdString(t));
return list;
}
bool UnsignedTransaction::sign(const QString &fileName) const
{
return m_pimpl->sign(fileName.toStdString());
}
void UnsignedTransaction::setFilename(const QString &fileName)
{
m_fileName = fileName;
}
UnsignedTransaction::UnsignedTransaction(Monero::UnsignedTransaction *pt, QObject *parent)
: QObject(parent), m_pimpl(pt)
{
}
UnsignedTransaction::~UnsignedTransaction()
{
delete m_pimpl;
}

View File

@ -0,0 +1,58 @@
#ifndef UNSIGNEDTRANSACTION_H
#define UNSIGNEDTRANSACTION_H
#include <QObject>
#include <wallet/wallet2_api.h>
class UnsignedTransaction : public QObject
{
Q_OBJECT
Q_PROPERTY(Status status READ status)
Q_PROPERTY(QString errorString READ errorString)
// Q_PROPERTY(QList<qulonglong> amount READ amount)
// Q_PROPERTY(QList<qulonglong> fee READ fee)
Q_PROPERTY(quint64 txCount READ txCount)
Q_PROPERTY(QString confirmationMessage READ confirmationMessage)
Q_PROPERTY(QStringList recipientAddress READ recipientAddress)
Q_PROPERTY(QStringList paymentId READ paymentId)
Q_PROPERTY(quint64 minMixinCount READ minMixinCount)
public:
enum Status {
Status_Ok = Monero::UnsignedTransaction::Status_Ok,
Status_Error = Monero::UnsignedTransaction::Status_Error,
Status_Critical = Monero::UnsignedTransaction::Status_Critical
};
Q_ENUM(Status)
enum Priority {
Priority_Low = Monero::UnsignedTransaction::Priority_Low,
Priority_Medium = Monero::UnsignedTransaction::Priority_Medium,
Priority_High = Monero::UnsignedTransaction::Priority_High
};
Q_ENUM(Priority)
Status status() const;
QString errorString() const;
Q_INVOKABLE quint64 amount(int index) const;
Q_INVOKABLE quint64 fee(int index) const;
Q_INVOKABLE quint64 mixin(int index) const;
QStringList recipientAddress() const;
QStringList paymentId() const;
quint64 txCount() const;
QString confirmationMessage() const;
quint64 minMixinCount() const;
Q_INVOKABLE bool sign(const QString &fileName) const;
Q_INVOKABLE void setFilename(const QString &fileName);
private:
explicit UnsignedTransaction(Monero::UnsignedTransaction * pt, QObject *parent = 0);
~UnsignedTransaction();
private:
friend class Wallet;
Monero::UnsignedTransaction * m_pimpl;
QString m_fileName;
};
#endif // UNSIGNEDTRANSACTION_H

View File

@ -1,5 +1,6 @@
#include "Wallet.h"
#include "PendingTransaction.h"
#include "UnsignedTransaction.h"
#include "TransactionHistory.h"
#include "AddressBook.h"
#include "model/TransactionHistoryModel.h"
@ -211,7 +212,6 @@ quint64 Wallet::daemonBlockChainHeight() const
quint64 Wallet::daemonBlockChainTargetHeight() const
{
if (m_daemonBlockChainTargetHeight == 0
|| m_daemonBlockChainTargetHeightTime.elapsed() / 1000 > m_daemonBlockChainTargetHeightTtl) {
m_daemonBlockChainTargetHeight = m_walletImpl->daemonBlockChainTargetHeight();
@ -323,12 +323,31 @@ void Wallet::createSweepUnmixableTransactionAsync()
});
}
UnsignedTransaction * Wallet::loadTxFile(const QString &fileName)
{
qDebug() << "Trying to sign " << fileName;
Monero::UnsignedTransaction * ptImpl = m_walletImpl->loadUnsignedTx(fileName.toStdString());
UnsignedTransaction * result = new UnsignedTransaction(ptImpl, this);
return result;
}
bool Wallet::submitTxFile(const QString &fileName) const
{
qDebug() << "Trying to submit " << fileName;
return m_walletImpl->submitTransaction(fileName.toStdString());
}
void Wallet::disposeTransaction(PendingTransaction *t)
{
m_walletImpl->disposeTransaction(t->m_pimpl);
delete t;
}
void Wallet::disposeTransaction(UnsignedTransaction *t)
{
delete t;
}
TransactionHistory *Wallet::history() const
{
return m_history;

View File

@ -6,6 +6,7 @@
#include "wallet/wallet2_api.h" // we need to have an access to the Monero::Wallet::Status enum here;
#include "PendingTransaction.h" // we need to have an access to the PendingTransaction::Priority enum here;
#include "UnsignedTransaction.h"
namespace Monero {
class Wallet; // forward declaration
@ -162,9 +163,19 @@ public:
//! creates async sweep unmixable transaction
Q_INVOKABLE void createSweepUnmixableTransactionAsync();
//! Sign a transfer from file
Q_INVOKABLE UnsignedTransaction * loadTxFile(const QString &fileName);
//! Submit a transfer from file
Q_INVOKABLE bool submitTxFile(const QString &fileName) const;
//! deletes transaction and frees memory
Q_INVOKABLE void disposeTransaction(PendingTransaction * t);
//! deletes unsigned transaction and frees memory
Q_INVOKABLE void disposeTransaction(UnsignedTransaction * t);
//! returns transaction history
TransactionHistory * history() const;