[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,
|
||||
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 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)
|
||||
{
|
||||
auto old_name = m_account.name;
|
||||
auto oldName = m_account.name;
|
||||
auto oldMemoKey = memoKey();
|
||||
|
||||
m_account = obj;
|
||||
if (old_name != m_account.name)
|
||||
if (oldName != m_account.name)
|
||||
Q_EMIT nameChanged();
|
||||
if (oldMemoKey != memoKey())
|
||||
Q_EMIT memoKeyChanged();
|
||||
|
||||
if (!m_loaded) {
|
||||
m_loaded = true;
|
||||
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()
|
||||
{
|
||||
auto count = [](QQmlListProperty<Balance>* list) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class Account : public GrapheneObject {
|
|||
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
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)
|
||||
|
||||
account_object m_account;
|
||||
|
|
@ -36,6 +37,7 @@ public:
|
|||
void setAccountObject(const account_object& obj);
|
||||
|
||||
QString name()const { return QString::fromStdString(m_account.name); }
|
||||
QString memoKey()const;
|
||||
QQmlListProperty<Balance> balances();
|
||||
|
||||
void setBalances(QList<Balance*> balances) {
|
||||
|
|
@ -60,5 +62,6 @@ public:
|
|||
Q_SIGNALS:
|
||||
void nameChanged();
|
||||
void balancesChanged();
|
||||
void memoKeyChanged();
|
||||
void loaded();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#include "Operations.hpp"
|
||||
#include "Wallet.hpp"
|
||||
|
||||
#include <graphene/utilities/key_conversion.hpp>
|
||||
|
||||
#include <fc/smart_ref_impl.hpp>
|
||||
|
||||
|
|
@ -31,6 +34,38 @@ QString TransferOperation::memo() const {
|
|||
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) {
|
||||
if (memo == this->memo())
|
||||
return;
|
||||
|
|
@ -41,3 +76,12 @@ void TransferOperation::setMemo(QString memo) {
|
|||
m_op.memo->set_message({}, {}, memo.toStdString());
|
||||
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; }
|
||||
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
|
||||
/// performed elsewhere.
|
||||
/// performed by calling encryptMemo()
|
||||
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; }
|
||||
graphene::chain::transfer_operation& operation() { return m_op; }
|
||||
|
||||
|
|
@ -101,8 +105,9 @@ public Q_SLOTS:
|
|||
Q_EMIT amountTypeChanged();
|
||||
}
|
||||
/// 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 encryptMemo(Wallet* wallet, ChainDataModel* model);
|
||||
|
||||
Q_SIGNALS:
|
||||
void feeChanged();
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ public:
|
|||
return m_transaction.operations.size();
|
||||
}
|
||||
|
||||
graphene::chain::signed_transaction& internalTransaction() {
|
||||
return m_transaction;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void setStatus(Status status)
|
||||
{
|
||||
|
|
@ -51,5 +55,5 @@ private:
|
|||
Q_PROPERTY(QQmlListProperty<OperationBase> operations READ operations NOTIFY operationsChanged)
|
||||
|
||||
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 <graphene/utilities/key_conversion.hpp>
|
||||
#include <graphene/chain/protocol/fee_schedule.hpp>
|
||||
|
||||
#include <fc/crypto/aes.hpp>
|
||||
#include <fc/io/json.hpp>
|
||||
|
||||
|
|
@ -348,6 +352,16 @@ QList<QPair<QString,QString>> Wallet::getAllPublicKeys(bool only_if_private)cons
|
|||
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)
|
||||
{
|
||||
if( !isOpen() ) return QString::null;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ using graphene::chain::digest_type;
|
|||
using graphene::chain::signature_type;
|
||||
using fc::optional;
|
||||
|
||||
class Transaction;
|
||||
|
||||
QString toQString(public_key_type k);
|
||||
|
||||
struct key_data
|
||||
|
|
@ -125,6 +127,7 @@ class Wallet : public QObject
|
|||
/**
|
||||
* @pre !isLocked()
|
||||
*/
|
||||
Q_INVOKABLE void sign(Transaction* transaction) const;
|
||||
vector<signature_type> signDigest(const digest_type& d,
|
||||
const set<public_key_type>& keys)const;
|
||||
|
||||
|
|
|
|||
|
|
@ -62,4 +62,37 @@ FormBase {
|
|||
Loader {
|
||||
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 {
|
||||
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
|
||||
onClicked: completed([operation()])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ ApplicationWindow {
|
|||
// TODO: make back into a preview and confirm dialog
|
||||
var back = Qt.createComponent("TransactionConfirmationForm.qml")
|
||||
formBox.showForm(Qt.createComponent("FormFlipper.qml"), {frontComponent: front, backComponent: back},
|
||||
function() {
|
||||
console.log("Closed form")
|
||||
function(arg) {
|
||||
console.log("Closed form: " + JSON.stringify(arg))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue