From fcffffdd5d4758bf354ae84ac013296cb3103f04 Mon Sep 17 00:00:00 2001 From: thotbot Date: Sat, 3 Jul 2021 22:07:52 +0200 Subject: [PATCH] Offline transaction signing --- src/wallet/api/CMakeLists.txt | 4 + src/wallet/api/pending_transaction.cpp | 47 ++++++++++++ src/wallet/api/pending_transaction.h | 9 +++ src/wallet/api/pending_transaction_info.cpp | 47 ++++++++++++ src/wallet/api/pending_transaction_info.h | 37 ++++++++++ .../api/transaction_construction_info.cpp | 63 ++++++++++++++++ .../api/transaction_construction_info.h | 32 ++++++++ src/wallet/api/unsigned_transaction.cpp | 21 ++++++ src/wallet/api/unsigned_transaction.h | 5 +- src/wallet/api/wallet.cpp | 43 +++++++++++ src/wallet/api/wallet.h | 7 ++ src/wallet/api/wallet2_api.h | 73 ++++++++++++++++++- 12 files changed, 386 insertions(+), 2 deletions(-) create mode 100644 src/wallet/api/pending_transaction_info.cpp create mode 100644 src/wallet/api/pending_transaction_info.h create mode 100644 src/wallet/api/transaction_construction_info.cpp create mode 100644 src/wallet/api/transaction_construction_info.h diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt index af7948d8a..4245fac7e 100644 --- a/src/wallet/api/CMakeLists.txt +++ b/src/wallet/api/CMakeLists.txt @@ -35,6 +35,8 @@ set(wallet_api_sources wallet_manager.cpp transaction_info.cpp transaction_history.cpp + transaction_construction_info.cpp + pending_transaction_info.cpp pending_transaction.cpp utils.cpp address_book.cpp @@ -50,6 +52,8 @@ set(wallet_api_private_headers wallet_manager.h transaction_info.h transaction_history.h + transaction_construction_info.h + pending_transaction_info.h pending_transaction.h common_defines.h address_book.h diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 70a702796..25e544667 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -35,6 +35,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "common/base58.h" +#include "string_coding.h" #include #include @@ -263,4 +264,50 @@ std::vector PendingTransactionImpl::signersKeys() const { return keys; } +std::string PendingTransactionImpl::unsignedTxToBin() const { + return m_wallet.m_wallet->dump_tx_to_str(m_pending_tx); +} + +std::string PendingTransactionImpl::unsignedTxToBase64() const { + return epee::string_encoding::base64_encode(m_wallet.m_wallet->dump_tx_to_str(m_pending_tx)); +} + +std::string PendingTransactionImpl::signedTxToHex(int index) const { + auto index_ = static_cast(index); + if (index < 0 || index_ >= m_pending_tx.size()) { + return ""; + } + + return epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(m_pending_tx[index_].tx)); +} + +size_t PendingTransactionImpl::signedTxSize(int index) const { + auto index_ = static_cast(index); + if (index < 0 || index_ >= m_pending_tx.size()) { + return 0; + } + + return cryptonote::tx_to_blob(m_pending_tx[index_].tx).size(); +} + +PendingTransactionInfo * PendingTransactionImpl::transaction(int index) const { + if (index < 0) + return nullptr; + auto index_ = static_cast(index); + return index_ < m_pending_tx_info.size() ? m_pending_tx_info[index_] : nullptr; +} + +void PendingTransactionImpl::refresh() { + for (auto t : m_pending_tx_info) + delete t; + m_pending_tx_info.clear(); + + for (const auto& p : m_pending_tx) + m_pending_tx_info.push_back(new PendingTransactionInfoImpl(m_wallet, p)); +} + +std::vector PendingTransactionImpl::getAll() const { + return m_pending_tx_info; +} + } diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 0a9779c07..fa2cf6599 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -30,6 +30,7 @@ #include "wallet/api/wallet2_api.h" #include "wallet/wallet2.h" +#include "pending_transaction_info.h" #include #include @@ -53,6 +54,13 @@ public: uint64_t txCount() const override; std::vector subaddrAccount() const override; std::vector> subaddrIndices() const override; + std::string unsignedTxToBin() const override; + std::string unsignedTxToBase64() const override; + std::string signedTxToHex(int index) const override; + size_t signedTxSize(int index) const override; + void refresh() override; + std::vector getAll() const override; + PendingTransactionInfo * transaction(int index) const override; // TODO: continue with interface; std::string multisigSignData() override; @@ -66,6 +74,7 @@ private: int m_status; std::string m_errorString; std::vector m_pending_tx; + std::vector m_pending_tx_info; std::unordered_set m_signers; std::vector m_tx_device_aux; std::vector m_key_images; diff --git a/src/wallet/api/pending_transaction_info.cpp b/src/wallet/api/pending_transaction_info.cpp new file mode 100644 index 000000000..86499e006 --- /dev/null +++ b/src/wallet/api/pending_transaction_info.cpp @@ -0,0 +1,47 @@ +#include "pending_transaction_info.h" +#include "transaction_construction_info.h" + +using namespace std; + +namespace Monero { + + PendingTransactionInfo::~PendingTransactionInfo() = default; + + PendingTransactionInfoImpl::PendingTransactionInfoImpl(WalletImpl &wallet, const tools::wallet2::pending_tx & ptx) + : m_wallet(wallet) + , m_ptx(ptx) + , m_constructionData(new TransactionConstructionInfoImpl(wallet, ptx.construction_data)) + { + } + + PendingTransactionInfoImpl::~PendingTransactionInfoImpl() = default; + + uint64_t PendingTransactionInfoImpl::fee() const + { + return m_ptx.fee; + } + + uint64_t PendingTransactionInfoImpl::dust() const + { + return m_ptx.dust; + } + + bool PendingTransactionInfoImpl::dustAddedToFee() const + { + return m_ptx.dust_added_to_fee; + } + + std::string PendingTransactionInfoImpl::txKey() const + { + return epee::string_tools::pod_to_hex(m_ptx.tx_key); + } + + TransactionConstructionInfo * PendingTransactionInfoImpl::constructionData() const { + return m_constructionData; + } + +// TransactionConstructionInfo::Output TransactionConstructionInfoImpl::change() const { +// return Output( +// {m_ptx.change_dts.amount, m_ptx.change_dts.address(m_wallet.m_wallet->nettype(), crypto::hash())}); +// } +} \ No newline at end of file diff --git a/src/wallet/api/pending_transaction_info.h b/src/wallet/api/pending_transaction_info.h new file mode 100644 index 000000000..58ccc4f33 --- /dev/null +++ b/src/wallet/api/pending_transaction_info.h @@ -0,0 +1,37 @@ +#ifndef WOWLET_PENDING_TX_H +#define WOWLET_PENDING_TX_H + +#include "wallet/api/wallet2_api.h" +#include "wallet/wallet2.h" +#include "wallet.h" +#include + +namespace Monero { + +class PendingTransactionImpl; + +class PendingTransactionInfoImpl : public PendingTransactionInfo +{ +public: + PendingTransactionInfoImpl(WalletImpl &wallet, const tools::wallet2::pending_tx & ptx); + ~PendingTransactionInfoImpl() override; + + uint64_t fee() const override; + uint64_t dust() const override; + bool dustAddedToFee() const override; + std::string txKey() const override; + TransactionConstructionInfo *constructionData() const override; +// Output change() const override; + +private: + friend class WalletImpl; + WalletImpl &m_wallet; + tools::wallet2::pending_tx m_ptx; + TransactionConstructionInfo *m_constructionData; +}; + +} + + + +#endif //FEATHER_PENDING_TX_H \ No newline at end of file diff --git a/src/wallet/api/transaction_construction_info.cpp b/src/wallet/api/transaction_construction_info.cpp new file mode 100644 index 000000000..10cef2a57 --- /dev/null +++ b/src/wallet/api/transaction_construction_info.cpp @@ -0,0 +1,63 @@ +#include "transaction_construction_info.h" + +using namespace std; + +namespace Monero { + TransactionConstructionInfo::~TransactionConstructionInfo() = default; + + TransactionConstructionInfo::Input::Input(uint64_t _amount, const std::string &_pubkey) + : amount(_amount), pubkey(_pubkey) {} + + TransactionConstructionInfo::Output::Output(uint64_t _amount, const std::string &_address) + : amount(_amount), address(_address) {} + + TransactionConstructionInfoImpl::TransactionConstructionInfoImpl(WalletImpl &wallet, const tools::wallet2::tx_construction_data & txcd) + : m_wallet(wallet) + , m_txcd(txcd) {} + + TransactionConstructionInfoImpl::~TransactionConstructionInfoImpl() = default; + + uint64_t TransactionConstructionInfoImpl::unlockTime() const { + return m_txcd.unlock_time; + } + + std::set TransactionConstructionInfoImpl::subaddressIndices() const { + return m_txcd.subaddr_indices; + } + + std::vector TransactionConstructionInfoImpl::subaddresses() const { + std::vector s; + auto major = m_txcd.subaddr_account; + for (const auto &minor : m_txcd.subaddr_indices) { + s.push_back(m_wallet.m_wallet->get_subaddress_as_str({major, minor})); + } + return s; + } + + uint64_t TransactionConstructionInfoImpl::minMixinCount() const { + uint64_t min_mixin = -1; + for (const auto &source : m_txcd.sources) { + size_t mixin = source.outputs.size() - 1; + if (mixin < min_mixin) + min_mixin = mixin; + } + + return min_mixin; + } + + std::vector TransactionConstructionInfoImpl::inputs() const { + std::vector inputs; + for (const auto &i : m_txcd.sources) { + inputs.emplace_back(i.amount, epee::string_tools::pod_to_hex(i.real_out_tx_key)); + } + return inputs; + } + + std::vector TransactionConstructionInfoImpl::outputs() const { + std::vector outputs; + for (const auto &o : m_txcd.splitted_dsts) { + outputs.emplace_back(o.amount, o.address(m_wallet.m_wallet->nettype(), crypto::hash())); + } + return outputs; + } +} \ No newline at end of file diff --git a/src/wallet/api/transaction_construction_info.h b/src/wallet/api/transaction_construction_info.h new file mode 100644 index 000000000..48c20392a --- /dev/null +++ b/src/wallet/api/transaction_construction_info.h @@ -0,0 +1,32 @@ +#ifndef WOWLET_TRANSACTION_CONSTRUCTION_INFO_H +#define WOWLET_TRANSACTION_CONSTRUCTION_INFO_H + +#include "wallet/api/wallet2_api.h" +#include "wallet/wallet2.h" +#include "wallet.h" +#include + +namespace Monero { + +class TransactionConstructionInfoImpl : public TransactionConstructionInfo +{ +public: + TransactionConstructionInfoImpl(WalletImpl &wallet, const tools::wallet2::tx_construction_data & ptx); + ~TransactionConstructionInfoImpl() override; + + uint64_t unlockTime() const override; + std::set subaddressIndices() const override; + std::vector subaddresses() const override; + uint64_t minMixinCount() const override; + std::vector inputs() const override; + std::vector outputs() const override; + +private: + friend class WalletImpl; + WalletImpl &m_wallet; + tools::wallet2::tx_construction_data m_txcd; +}; + +} + +#endif //WOWLET_TRANSACTION_CONSTRUCTION_INFO_H \ No newline at end of file diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp index 6165a2240..8e76591fe 100644 --- a/src/wallet/api/unsigned_transaction.cpp +++ b/src/wallet/api/unsigned_transaction.cpp @@ -31,6 +31,7 @@ #include "unsigned_transaction.h" #include "wallet.h" #include "common_defines.h" +#include "transaction_construction_info.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/cryptonote_basic_impl.h" @@ -315,4 +316,24 @@ uint64_t UnsignedTransactionImpl::minMixinCount() const return min_mixin; } +TransactionConstructionInfo * UnsignedTransactionImpl::transaction(int index) const { + if (index < 0) + return nullptr; + auto index_ = static_cast(index); + return index_ < m_constructionInfo.size() ? m_constructionInfo[index_] : nullptr; +} + +void UnsignedTransactionImpl::refresh() { + for (auto t : m_constructionInfo) + delete t; + m_constructionInfo.clear(); + + for (const auto& p : m_unsigned_tx_set.txes) + m_constructionInfo.push_back(new TransactionConstructionInfoImpl(m_wallet, p)); +} + +std::vector UnsignedTransactionImpl::getAll() const { + return m_constructionInfo; +} + } // namespace diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h index 30065a7fa..2c89aaea2 100644 --- a/src/wallet/api/unsigned_transaction.h +++ b/src/wallet/api/unsigned_transaction.h @@ -55,6 +55,9 @@ public: bool sign(const std::string &signedFileName) override; std::string confirmationMessage() const override {return m_confirmationMessage;} uint64_t minMixinCount() const override; + void refresh() override; + std::vector getAll() const override; + TransactionConstructionInfo * transaction(int index) const override; private: // Callback function to check all loaded tx's and generate confirmationMessage @@ -67,7 +70,7 @@ private: std::string m_errorString; tools::wallet2::unsigned_tx_set m_unsigned_tx_set; std::string m_confirmationMessage; + std::vector m_constructionInfo; }; - } diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 652cf9051..adbbff2cb 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -38,6 +38,7 @@ #include "subaddress_account.h" #include "common_defines.h" #include "common/util.h" +#include "string_coding.h" #include "mnemonics/electrum-words.h" #include "mnemonics/english.h" @@ -1154,6 +1155,48 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file return transaction; } +UnsignedTransaction *WalletImpl::loadUnsignedTxFromStr(const std::string &unsigned_tx) { + clearStatus(); + + UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); + if (!m_wallet->parse_unsigned_tx_from_str(unsigned_tx, transaction->m_unsigned_tx_set)) { + setStatusError(tr("Failed to load unsigned transactions")); + transaction->m_status = UnsignedTransaction::Status::Status_Error; + transaction->m_errorString = errorString(); + + return transaction; + } + + // Check tx data and construct confirmation message + std::string extra_message; + if (!transaction->m_unsigned_tx_set.transfers.second.empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.second.size()).str(); + transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message); + setStatus(transaction->status(), transaction->errorString()); + + return transaction; +} + +UnsignedTransaction *WalletImpl::loadUnsignedTxFromBase64Str(const std::string &unsigned_tx_base64) { + clearStatus(); + + std::string decoded_tx = epee::string_encoding::base64_decode(unsigned_tx_base64); + + return this->loadUnsignedTxFromStr(decoded_tx); +} + +PendingTransaction *WalletImpl::loadSignedTx(const std::string &signed_filename) { + clearStatus(); + PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); + + if (!m_wallet->load_tx(signed_filename, transaction->m_pending_tx)) { + setStatusError(tr("Failed to load unsigned transactions")); + return transaction; + } + + return transaction; +} + bool WalletImpl::submitTransaction(const string &fileName) { clearStatus(); std::unique_ptr transaction(new PendingTransactionImpl(*this)); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index ec2d7e9b3..f813db83e 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -48,6 +48,8 @@ class AddressBookImpl; class SubaddressImpl; class SubaddressAccountImpl; struct Wallet2CallbackImpl; +class PendingTransactionInfoImpl; +class TransactionConstructionInfoImpl; class WalletImpl : public Wallet { @@ -165,6 +167,9 @@ public: virtual PendingTransaction * createSweepUnmixableTransaction() override; bool submitTransaction(const std::string &fileName) override; virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) override; + virtual UnsignedTransaction * loadUnsignedTxFromStr(const std::string &unsigned_tx) override; + virtual UnsignedTransaction * loadUnsignedTxFromBase64Str(const std::string &unsigned_tx) override; + virtual PendingTransaction * loadSignedTx(const std::string &signed_filename) override; bool exportKeyImages(const std::string &filename, bool all = false) override; bool importKeyImages(const std::string &filename) override; bool exportOutputs(const std::string &filename, bool all = false) override; @@ -248,6 +253,8 @@ private: friend class AddressBookImpl; friend class SubaddressImpl; friend class SubaddressAccountImpl; + friend class PendingTransactionInfoImpl; + friend class TransactionConstructionInfoImpl; std::unique_ptr m_wallet; mutable boost::mutex m_statusMutex; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 71991df0d..1c2791079 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -67,6 +67,47 @@ enum NetworkType : uint8_t { bool set; }; +/* + * @brief Transaction construction data + */ +struct TransactionConstructionInfo +{ + struct Input { + Input(uint64_t _amount, const std::string &_pubkey); + const uint64_t amount; + const std::string pubkey; + }; + + struct Output { + Output(uint64_t _amount, const std::string &_address); + const uint64_t amount; + const std::string address; + }; + + virtual ~TransactionConstructionInfo() = 0; + virtual uint64_t unlockTime() const = 0; + virtual std::set subaddressIndices() const = 0; + virtual std::vector subaddresses() const = 0; + virtual uint64_t minMixinCount() const = 0; + virtual std::vector inputs() const = 0; + virtual std::vector outputs() const = 0; +}; + + +/* +* @brief Detailed pending transaction information +*/ +struct PendingTransactionInfo +{ + virtual ~PendingTransactionInfo() = 0; + virtual uint64_t fee() const = 0; + virtual uint64_t dust() const = 0; + virtual bool dustAddedToFee() const = 0; + virtual std::string txKey() const = 0; + virtual TransactionConstructionInfo * constructionData() const = 0; +}; + + /** * @brief Transaction-like interface for sending money */ @@ -102,6 +143,13 @@ struct PendingTransaction virtual uint64_t txCount() const = 0; virtual std::vector subaddrAccount() const = 0; virtual std::vector> subaddrIndices() const = 0; + virtual std::string unsignedTxToBin() const = 0; + virtual std::string unsignedTxToBase64() const = 0; + virtual std::string signedTxToHex(int index) const = 0; + virtual size_t signedTxSize(int index) const = 0; + virtual PendingTransactionInfo * transaction(int index) const = 0; + virtual void refresh() = 0; + virtual std::vector getAll() const = 0; /** * @brief multisigSignData @@ -161,6 +209,9 @@ struct UnsignedTransaction * return - true on success */ virtual bool sign(const std::string &signedFileName) = 0; + virtual void refresh() = 0; + virtual std::vector getAll() const = 0; + virtual TransactionConstructionInfo * transaction(int index) const = 0; }; /** @@ -878,7 +929,27 @@ struct Wallet * after object returned */ virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; - + + /*! + * \brief loadUnsignedTx - creates transaction from unsigned tx string + * \return - UnsignedTransaction object. caller is responsible to check UnsignedTransaction::status() + * after object returned + */ + virtual UnsignedTransaction * loadUnsignedTxFromStr(const std::string &unsigned_tx) = 0; + + /*! + * \brief loadUnsignedTx - creates transaction from unsigned base64 encoded tx string + * \return - UnsignedTransaction object. caller is responsible to check UnsignedTransaction::status() + * after object returned + */ + virtual UnsignedTransaction * loadUnsignedTxFromBase64Str(const std::string &unsigned_tx_base64) = 0; + + /*! + * \brief loadSignedTx - creates transaction from signed tx file + * \return - PendingTransaction object. + */ + virtual PendingTransaction * loadSignedTx(const std::string &signed_filename) = 0; + /*! * \brief submitTransaction - submits transaction in signed tx file * \return - true on success