[GUI] Add transaction signing
TODO: encrypt the memo (some work to facilitate this is done in this commit)
This commit is contained in:
parent
391cb5e627
commit
82ea3c1edd
11 changed files with 127 additions and 9 deletions
|
|
@ -33,7 +33,7 @@ void memo_data::set_message(const fc::ecc::private_key& priv, const fc::ecc::pub
|
||||||
string memo_data::get_message(const fc::ecc::private_key& priv,
|
string memo_data::get_message(const fc::ecc::private_key& priv,
|
||||||
const fc::ecc::public_key& pub)const
|
const fc::ecc::public_key& pub)const
|
||||||
{
|
{
|
||||||
if( from != public_key_type() )
|
if( from != public_key_type() )
|
||||||
{
|
{
|
||||||
auto secret = priv.get_shared_secret(pub);
|
auto secret = priv.get_shared_secret(pub);
|
||||||
auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str());
|
auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str());
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,15 @@
|
||||||
|
|
||||||
void Account::setAccountObject(const graphene::chain::account_object& obj)
|
void Account::setAccountObject(const graphene::chain::account_object& obj)
|
||||||
{
|
{
|
||||||
auto old_name = m_account.name;
|
auto oldName = m_account.name;
|
||||||
|
auto oldMemoKey = memoKey();
|
||||||
|
|
||||||
m_account = obj;
|
m_account = obj;
|
||||||
if (old_name != m_account.name)
|
if (oldName != m_account.name)
|
||||||
Q_EMIT nameChanged();
|
Q_EMIT nameChanged();
|
||||||
|
if (oldMemoKey != memoKey())
|
||||||
|
Q_EMIT memoKeyChanged();
|
||||||
|
|
||||||
if (!m_loaded) {
|
if (!m_loaded) {
|
||||||
m_loaded = true;
|
m_loaded = true;
|
||||||
Q_EMIT loaded();
|
Q_EMIT loaded();
|
||||||
|
|
@ -17,6 +22,11 @@ void Account::setAccountObject(const graphene::chain::account_object& obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Account::memoKey() const
|
||||||
|
{
|
||||||
|
return toQString(m_account.options.memo_key);
|
||||||
|
}
|
||||||
|
|
||||||
QQmlListProperty<Balance> Account::balances()
|
QQmlListProperty<Balance> Account::balances()
|
||||||
{
|
{
|
||||||
auto count = [](QQmlListProperty<Balance>* list) {
|
auto count = [](QQmlListProperty<Balance>* list) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ class Account : public GrapheneObject {
|
||||||
|
|
||||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||||
Q_PROPERTY(QQmlListProperty<Balance> balances READ balances NOTIFY balancesChanged)
|
Q_PROPERTY(QQmlListProperty<Balance> balances READ balances NOTIFY balancesChanged)
|
||||||
|
Q_PROPERTY(QString memoKey READ memoKey NOTIFY memoKeyChanged)
|
||||||
Q_PROPERTY(bool isLoaded MEMBER m_loaded NOTIFY loaded)
|
Q_PROPERTY(bool isLoaded MEMBER m_loaded NOTIFY loaded)
|
||||||
|
|
||||||
account_object m_account;
|
account_object m_account;
|
||||||
|
|
@ -36,6 +37,7 @@ public:
|
||||||
void setAccountObject(const account_object& obj);
|
void setAccountObject(const account_object& obj);
|
||||||
|
|
||||||
QString name()const { return QString::fromStdString(m_account.name); }
|
QString name()const { return QString::fromStdString(m_account.name); }
|
||||||
|
QString memoKey()const;
|
||||||
QQmlListProperty<Balance> balances();
|
QQmlListProperty<Balance> balances();
|
||||||
|
|
||||||
void setBalances(QList<Balance*> balances) {
|
void setBalances(QList<Balance*> balances) {
|
||||||
|
|
@ -60,5 +62,6 @@ public:
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
void balancesChanged();
|
void balancesChanged();
|
||||||
|
void memoKeyChanged();
|
||||||
void loaded();
|
void loaded();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
#include "Operations.hpp"
|
#include "Operations.hpp"
|
||||||
|
#include "Wallet.hpp"
|
||||||
|
|
||||||
|
#include <graphene/utilities/key_conversion.hpp>
|
||||||
|
|
||||||
#include <fc/smart_ref_impl.hpp>
|
#include <fc/smart_ref_impl.hpp>
|
||||||
|
|
||||||
|
|
@ -31,6 +34,38 @@ QString TransferOperation::memo() const {
|
||||||
return memo;
|
return memo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TransferOperation::canEncryptMemo(Wallet* wallet, ChainDataModel* model) const
|
||||||
|
{
|
||||||
|
if (!m_op.memo) return false;
|
||||||
|
auto pub = model->getAccount(sender())->memoKey();
|
||||||
|
if (!wallet->hasPrivateKey(pub)) return false;
|
||||||
|
return graphene::utilities::wif_to_key(wallet->getPrivateKey(pub).toStdString()).valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransferOperation::canDecryptMemo(Wallet* wallet, ChainDataModel* model) const
|
||||||
|
{
|
||||||
|
if (!m_op.memo) return false;
|
||||||
|
auto pub = model->getAccount(receiver())->memoKey();
|
||||||
|
if (!wallet->hasPrivateKey(pub)) return false;
|
||||||
|
return graphene::utilities::wif_to_key(wallet->getPrivateKey(pub).toStdString()).valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TransferOperation::decryptedMemo(Wallet* wallet, ChainDataModel* model) const
|
||||||
|
{
|
||||||
|
fc::ecc::private_key privateKey;
|
||||||
|
fc::ecc::public_key publicKey;
|
||||||
|
|
||||||
|
if (canEncryptMemo(wallet, model)) {
|
||||||
|
privateKey = *graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(sender())->memoKey()).toStdString());
|
||||||
|
publicKey = m_op.memo->to;
|
||||||
|
} else if (canDecryptMemo(wallet, model)) {
|
||||||
|
privateKey = *graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(receiver())->memoKey()).toStdString());
|
||||||
|
publicKey = m_op.memo->from;
|
||||||
|
} else return QString::null;
|
||||||
|
|
||||||
|
return QString::fromStdString(m_op.memo->get_message(privateKey, publicKey));
|
||||||
|
}
|
||||||
|
|
||||||
void TransferOperation::setMemo(QString memo) {
|
void TransferOperation::setMemo(QString memo) {
|
||||||
if (memo == this->memo())
|
if (memo == this->memo())
|
||||||
return;
|
return;
|
||||||
|
|
@ -41,3 +76,12 @@ void TransferOperation::setMemo(QString memo) {
|
||||||
m_op.memo->set_message({}, {}, memo.toStdString());
|
m_op.memo->set_message({}, {}, memo.toStdString());
|
||||||
Q_EMIT memoChanged();
|
Q_EMIT memoChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TransferOperation::encryptMemo(Wallet* wallet, ChainDataModel* model)
|
||||||
|
{
|
||||||
|
if (!canEncryptMemo(wallet, model)) return;
|
||||||
|
auto privateKey = graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(sender())->memoKey()).toStdString());
|
||||||
|
if (!privateKey) return;
|
||||||
|
m_op.memo->set_message(*privateKey, public_key_type(model->getAccount(receiver())->memoKey().toStdString()), memo().toStdString());
|
||||||
|
Q_EMIT memoChanged();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,13 @@ public:
|
||||||
qint64 amount() const { return m_op.amount.amount.value; }
|
qint64 amount() const { return m_op.amount.amount.value; }
|
||||||
ObjectId amountType() const { return m_op.amount.asset_id.instance.value; }
|
ObjectId amountType() const { return m_op.amount.asset_id.instance.value; }
|
||||||
/// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
|
/// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
|
||||||
/// performed elsewhere.
|
/// performed by calling encryptMemo()
|
||||||
QString memo() const;
|
QString memo() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE bool canEncryptMemo(Wallet* wallet, ChainDataModel* model) const;
|
||||||
|
Q_INVOKABLE bool canDecryptMemo(Wallet* wallet, ChainDataModel* model) const;
|
||||||
|
Q_INVOKABLE QString decryptedMemo(Wallet* wallet, ChainDataModel* model) const;
|
||||||
|
|
||||||
const graphene::chain::transfer_operation& operation() const { return m_op; }
|
const graphene::chain::transfer_operation& operation() const { return m_op; }
|
||||||
graphene::chain::transfer_operation& operation() { return m_op; }
|
graphene::chain::transfer_operation& operation() { return m_op; }
|
||||||
|
|
||||||
|
|
@ -101,8 +105,9 @@ public Q_SLOTS:
|
||||||
Q_EMIT amountTypeChanged();
|
Q_EMIT amountTypeChanged();
|
||||||
}
|
}
|
||||||
/// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
|
/// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
|
||||||
/// performed elsewhere.
|
/// performed by calling encryptMemo()
|
||||||
void setMemo(QString memo);
|
void setMemo(QString memo);
|
||||||
|
void encryptMemo(Wallet* wallet, ChainDataModel* model);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void feeChanged();
|
void feeChanged();
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ public:
|
||||||
return m_transaction.operations.size();
|
return m_transaction.operations.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
graphene::chain::signed_transaction& internalTransaction() {
|
||||||
|
return m_transaction;
|
||||||
|
}
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setStatus(Status status)
|
void setStatus(Status status)
|
||||||
{
|
{
|
||||||
|
|
@ -51,5 +55,5 @@ private:
|
||||||
Q_PROPERTY(QQmlListProperty<OperationBase> operations READ operations NOTIFY operationsChanged)
|
Q_PROPERTY(QQmlListProperty<OperationBase> operations READ operations NOTIFY operationsChanged)
|
||||||
|
|
||||||
Status m_status = Unbroadcasted;
|
Status m_status = Unbroadcasted;
|
||||||
graphene::chain::transaction m_transaction;
|
graphene::chain::signed_transaction m_transaction;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
|
#include "Transaction.hpp"
|
||||||
#include "Wallet.hpp"
|
#include "Wallet.hpp"
|
||||||
|
|
||||||
#include <graphene/utilities/key_conversion.hpp>
|
#include <graphene/utilities/key_conversion.hpp>
|
||||||
|
#include <graphene/chain/protocol/fee_schedule.hpp>
|
||||||
|
|
||||||
#include <fc/crypto/aes.hpp>
|
#include <fc/crypto/aes.hpp>
|
||||||
#include <fc/io/json.hpp>
|
#include <fc/io/json.hpp>
|
||||||
|
|
||||||
|
|
@ -348,6 +352,16 @@ QList<QPair<QString,QString>> Wallet::getAllPublicKeys(bool only_if_private)cons
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Wallet::sign(Transaction* transaction) const
|
||||||
|
{
|
||||||
|
if (transaction == nullptr) return;
|
||||||
|
|
||||||
|
auto& trx = transaction->internalTransaction();
|
||||||
|
flat_set<public_key_type> pubKeys = getAvailablePrivateKeys();
|
||||||
|
trx.signatures = signDigest(trx.digest(), set<public_key_type>(pubKeys.begin(), pubKeys.end()));
|
||||||
|
idump((trx));
|
||||||
|
}
|
||||||
|
|
||||||
QString Wallet::getPublicKey(QString label)
|
QString Wallet::getPublicKey(QString label)
|
||||||
{
|
{
|
||||||
if( !isOpen() ) return QString::null;
|
if( !isOpen() ) return QString::null;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ using graphene::chain::digest_type;
|
||||||
using graphene::chain::signature_type;
|
using graphene::chain::signature_type;
|
||||||
using fc::optional;
|
using fc::optional;
|
||||||
|
|
||||||
|
class Transaction;
|
||||||
|
|
||||||
QString toQString(public_key_type k);
|
QString toQString(public_key_type k);
|
||||||
|
|
||||||
struct key_data
|
struct key_data
|
||||||
|
|
@ -125,6 +127,7 @@ class Wallet : public QObject
|
||||||
/**
|
/**
|
||||||
* @pre !isLocked()
|
* @pre !isLocked()
|
||||||
*/
|
*/
|
||||||
|
Q_INVOKABLE void sign(Transaction* transaction) const;
|
||||||
vector<signature_type> signDigest(const digest_type& d,
|
vector<signature_type> signDigest(const digest_type& d,
|
||||||
const set<public_key_type>& keys)const;
|
const set<public_key_type>& keys)const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,4 +62,37 @@ FormBase {
|
||||||
Loader {
|
Loader {
|
||||||
sourceComponent: trx && Array.prototype.slice.call(trx.operations).length > 0? transactionDelegate : undefined
|
sourceComponent: trx && Array.prototype.slice.call(trx.operations).length > 0? transactionDelegate : undefined
|
||||||
}
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
Button {
|
||||||
|
text: qsTr("Cancel")
|
||||||
|
onClicked: {
|
||||||
|
canceled({})
|
||||||
|
trx = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
id: passwordField
|
||||||
|
Layout.preferredWidth: app.wallet.isLocked? Scaling.cm(4) : 0
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
placeholderText: qsTr("Wallet password")
|
||||||
|
visible: width > 0
|
||||||
|
onAccepted: finishButton.clicked()
|
||||||
|
|
||||||
|
Behavior on Layout.preferredWidth { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
id: finishButton
|
||||||
|
text: app.wallet.isLocked? qsTr("Unlock") : qsTr("Finish")
|
||||||
|
onClicked: {
|
||||||
|
if (app.wallet.isLocked)
|
||||||
|
app.wallet.unlock(passwordField.text)
|
||||||
|
else {
|
||||||
|
app.wallet.sign(trx)
|
||||||
|
completed(trx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,9 @@ FormBase {
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
id: transferButton
|
id: transferButton
|
||||||
text: qsTr("Transfer")
|
text: !senderAccount ||
|
||||||
|
!senderAccount.isLoaded ||
|
||||||
|
senderPicker.accountControlLevel >= 1? qsTr("Transfer") : qsTr("Propose")
|
||||||
enabled: senderPicker.account && recipientPicker.account && senderPicker.account !== recipientPicker.account && amountField.value
|
enabled: senderPicker.account && recipientPicker.account && senderPicker.account !== recipientPicker.account && amountField.value
|
||||||
onClicked: completed([operation()])
|
onClicked: completed([operation()])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,8 @@ ApplicationWindow {
|
||||||
// TODO: make back into a preview and confirm dialog
|
// TODO: make back into a preview and confirm dialog
|
||||||
var back = Qt.createComponent("TransactionConfirmationForm.qml")
|
var back = Qt.createComponent("TransactionConfirmationForm.qml")
|
||||||
formBox.showForm(Qt.createComponent("FormFlipper.qml"), {frontComponent: front, backComponent: back},
|
formBox.showForm(Qt.createComponent("FormFlipper.qml"), {frontComponent: front, backComponent: back},
|
||||||
function() {
|
function(arg) {
|
||||||
console.log("Closed form")
|
console.log("Closed form: " + JSON.stringify(arg))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue