[GUI] Add transaction signing

TODO: encrypt the memo (some work to facilitate this is done in this
commit)
This commit is contained in:
Nathan Hourt 2015-07-29 17:56:37 -04:00
parent 391cb5e627
commit 82ea3c1edd
11 changed files with 127 additions and 9 deletions

View file

@ -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());

View file

@ -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) {

View file

@ -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();
};

View file

@ -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();
}

View file

@ -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();

View file

@ -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;
};

View file

@ -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;

View file

@ -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;

View file

@ -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)
}
}
}
}
}

View file

@ -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()])
}

View file

@ -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))
})
}
}