Offline transaction signing

This commit is contained in:
thotbot 2021-07-03 22:07:52 +02:00 committed by wowario
parent 176ed83a39
commit c7c1558c1d
No known key found for this signature in database
GPG key ID: 793504B449C69220
12 changed files with 386 additions and 2 deletions

View file

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

View file

@ -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 <memory>
#include <vector>
@ -263,4 +264,50 @@ std::vector<std::string> 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<unsigned>(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<unsigned>(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<unsigned>(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<PendingTransactionInfo*> PendingTransactionImpl::getAll() const {
return m_pending_tx_info;
}
}

View file

@ -30,6 +30,7 @@
#include "wallet/api/wallet2_api.h"
#include "wallet/wallet2.h"
#include "pending_transaction_info.h"
#include <string>
#include <vector>
@ -53,6 +54,13 @@ public:
uint64_t txCount() const override;
std::vector<uint32_t> subaddrAccount() const override;
std::vector<std::set<uint32_t>> 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<PendingTransactionInfo*> 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<tools::wallet2::pending_tx> m_pending_tx;
std::vector<PendingTransactionInfo*> m_pending_tx_info;
std::unordered_set<crypto::public_key> m_signers;
std::vector<std::string> m_tx_device_aux;
std::vector<crypto::key_image> m_key_images;

View file

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

View file

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

View file

@ -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<std::uint32_t> TransactionConstructionInfoImpl::subaddressIndices() const {
return m_txcd.subaddr_indices;
}
std::vector<std::string> TransactionConstructionInfoImpl::subaddresses() const {
std::vector<std::string> 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<TransactionConstructionInfo::Input> TransactionConstructionInfoImpl::inputs() const {
std::vector<Input> 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<TransactionConstructionInfo::Output> TransactionConstructionInfoImpl::outputs() const {
std::vector<Output> 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;
}
}

View file

@ -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 <string>
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<std::uint32_t> subaddressIndices() const override;
std::vector<std::string> subaddresses() const override;
uint64_t minMixinCount() const override;
std::vector<Input> inputs() const override;
std::vector<Output> outputs() const override;
private:
friend class WalletImpl;
WalletImpl &m_wallet;
tools::wallet2::tx_construction_data m_txcd;
};
}
#endif //WOWLET_TRANSACTION_CONSTRUCTION_INFO_H

View file

@ -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<unsigned>(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<TransactionConstructionInfo*> UnsignedTransactionImpl::getAll() const {
return m_constructionInfo;
}
} // namespace

View file

@ -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<TransactionConstructionInfo*> 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<TransactionConstructionInfo*> m_constructionInfo;
};
}

View file

@ -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<PendingTransactionImpl> transaction(new PendingTransactionImpl(*this));

View file

@ -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<tools::wallet2> m_wallet;
mutable boost::mutex m_statusMutex;

View file

@ -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<std::uint32_t> subaddressIndices() const = 0;
virtual std::vector<std::string> subaddresses() const = 0;
virtual uint64_t minMixinCount() const = 0;
virtual std::vector<Input> inputs() const = 0;
virtual std::vector<Output> 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<uint32_t> subaddrAccount() const = 0;
virtual std::vector<std::set<uint32_t>> 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<PendingTransactionInfo*> 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<TransactionConstructionInfo*> 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