Permalink
Browse files

add multisig transaction, commit transaction, sign multisig transacti…

…on and get multisig data
  • Loading branch information...
miltonf committed Jan 5, 2019
1 parent 0299713 commit e4d8ee393d30d3f4667495c3f20503750641259a
@@ -34,6 +34,7 @@

#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "common/base58.h"

#include <memory>
#include <vector>
@@ -102,6 +103,11 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite)
}
// Commit tx
else {
auto multisigState = m_wallet.multisig();
if (multisigState.isMultisig && m_signers.size() < multisigState.threshold) {
throw runtime_error("Not enough signers to send multisig transaction");
}

m_wallet.pauseRefresh();
while (!m_pending_tx.empty()) {
auto & ptx = m_pending_tx.back();
@@ -188,6 +194,53 @@ std::vector<std::set<uint32_t>> PendingTransactionImpl::subaddrIndices() const
return result;
}

std::string PendingTransactionImpl::multisigSignData() {
try {
if (!m_wallet.multisig().isMultisig) {
throw std::runtime_error("wallet is not multisig");
}

auto cipher = m_wallet.m_wallet->save_multisig_tx(m_pending_tx);
return epee::string_tools::buff_to_hex_nodelimer(cipher);
} catch (const std::exception& e) {
m_status = Status_Error;
m_errorString = std::string(tr("Couldn't multisig sign data: ")) + e.what();
}

return std::string();
}

void PendingTransactionImpl::signMultisigTx() {
try {
std::vector<crypto::hash> ignore;

tools::wallet2::multisig_tx_set txSet;
txSet.m_ptx = m_pending_tx;
txSet.m_signers = m_signers;

if (!m_wallet.m_wallet->sign_multisig_tx(txSet, ignore)) {
throw std::runtime_error("couldn't sign multisig transaction");
}

std::swap(m_pending_tx, txSet.m_ptx);
std::swap(m_signers, txSet.m_signers);
} catch (const std::exception& e) {
m_status = Status_Error;
m_errorString = std::string(tr("Couldn't sign multisig transaction: ")) + e.what();
}
}

std::vector<std::string> PendingTransactionImpl::signersKeys() const {
std::vector<std::string> keys;
keys.reserve(m_signers.size());

for (const auto& signer: m_signers) {
keys.emplace_back(tools::base58::encode(cryptonote::t_serializable_object_to_blob(signer)));
}

return keys;
}

}

namespace Bitmonero = Monero;
@@ -55,13 +55,18 @@ class PendingTransactionImpl : public PendingTransaction
std::vector<std::set<uint32_t>> subaddrIndices() const;
// TODO: continue with interface;

std::string multisigSignData();
void signMultisigTx();
std::vector<std::string> signersKeys() const;

private:
friend class WalletImpl;
WalletImpl &m_wallet;

int m_status;
std::string m_errorString;
std::vector<tools::wallet2::pending_tx> m_pending_tx;
std::unordered_set<crypto::public_key> m_signers;
};


@@ -79,6 +79,36 @@ namespace {
dir /= ".shared-ringdb";
return dir.string();
}

void checkMultisigWalletReady(const tools::wallet2* wallet) {
if (!wallet) {
throw runtime_error("Wallet is not initialized yet");
}

bool ready;
if (!wallet->multisig(&ready)) {
throw runtime_error("Wallet is not multisig");
}

if (!ready) {
throw runtime_error("Multisig wallet is not finalized yet");
}
}

void checkMultisigWalletNotReady(const tools::wallet2* wallet) {
if (!wallet) {
throw runtime_error("Wallet is not initialized yet");
}

bool ready;
if (!wallet->multisig(&ready)) {
throw runtime_error("Wallet is not multisig");
}

if (ready) {
throw runtime_error("Multisig wallet is already finalized");
}
}
}

struct Wallet2CallbackImpl : public tools::i_wallet2_callback
@@ -1070,6 +1100,132 @@ void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex
}
}

MultisigState WalletImpl::multisig() const {
MultisigState state;
state.isMultisig = m_wallet->multisig(&state.isReady, &state.threshold, &state.total);

return state;
}

string WalletImpl::getMultisigInfo() const {
try {
clearStatus();
return m_wallet->get_multisig_info();
} catch (const exception& e) {
LOG_ERROR("Error on generating multisig info: ") << e.what();
setStatusError(string(tr("Failed to get multisig info: ")) + e.what());
}

return string();
}

string WalletImpl::makeMultisig(const vector<string>& info, uint32_t threshold) {
try {
clearStatus();

if (m_wallet->multisig()) {
throw runtime_error("Wallet is already multisig");
}

return m_wallet->make_multisig(epee::wipeable_string(m_password), info, threshold);
} catch (const exception& e) {
LOG_ERROR("Error on making multisig wallet: ") << e.what();
setStatusError(string(tr("Failed to make multisig: ")) + e.what());
}

return string();
}

bool WalletImpl::finalizeMultisig(const vector<string>& extraMultisigInfo) {
try {
clearStatus();
checkMultisigWalletNotReady(m_wallet);

if (m_wallet->finalize_multisig(epee::wipeable_string(m_password), extraMultisigInfo)) {
return true;
}

setStatusError(tr("Failed to finalize multisig wallet creation"));
} catch (const exception& e) {
LOG_ERROR("Error on finalizing multisig wallet creation: ") << e.what();
setStatusError(string(tr("Failed to finalize multisig wallet creation: ")) + e.what());
}

return false;
}

bool WalletImpl::exportMultisigImages(string& images) {
try {
clearStatus();
checkMultisigWalletReady(m_wallet);

auto blob = m_wallet->export_multisig();
images = epee::string_tools::buff_to_hex_nodelimer(blob);
return true;
} catch (const exception& e) {
LOG_ERROR("Error on exporting multisig images: ") << e.what();
setStatusError(string(tr("Failed to export multisig images: ")) + e.what());
}

return false;
}

size_t WalletImpl::importMultisigImages(const vector<string>& images) {
try {
clearStatus();
checkMultisigWalletReady(m_wallet);

std::vector<std::string> blobs;
blobs.reserve(images.size());

for (const auto& image: images) {
std::string blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(image, blob)) {
LOG_ERROR("Failed to parse imported multisig images");
setStatusError(tr("Failed to parse imported multisig images"));
return 0;
}

blobs.emplace_back(std::move(blob));
}

return m_wallet->import_multisig(blobs);
} catch (const exception& e) {
LOG_ERROR("Error on importing multisig images: ") << e.what();
setStatusError(string(tr("Failed to import multisig images: ")) + e.what());
}

return 0;
}

PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signData) {
try {
clearStatus();
checkMultisigWalletReady(m_wallet);

string binary;
if (!epee::string_tools::parse_hexstr_to_binbuff(signData, binary)) {
throw runtime_error("Failed to deserialize multisig transaction");
}

tools::wallet2::multisig_tx_set txSet;
if (!m_wallet->load_multisig_tx(binary, txSet, {})) {
throw runtime_error("couldn't parse multisig transaction data");
}

auto ptx = new PendingTransactionImpl(*this);
ptx->m_pending_tx = txSet.m_ptx;
ptx->m_signers = txSet.m_signers;

return ptx;
} catch (exception& e) {
LOG_ERROR("Error on restoring multisig transaction: ") << e.what();
setStatusError(string(tr("Failed to restore multisig transaction: ")) + e.what());
}

return nullptr;
}

// TODO:
// 1 - properly handle payment id (add another menthod with explicit 'payment_id' param)
// 2 - check / design how "Transaction" can be single interface
@@ -1107,6 +1263,8 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
break;
}



std::vector<uint8_t> extra;
// if dst_addr is not an integrated address, parse payment_id
if (!info.has_payment_id && !payment_id.empty()) {
@@ -1167,6 +1325,9 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
extra, subaddr_account, subaddr_indices, m_trustedDaemon);
}

if (multisig().isMultisig) {
transaction->m_signers = m_wallet->make_multisig_tx_set(transaction->m_pending_tx).m_signers;
}
} catch (const tools::error::daemon_busy&) {
// TODO: make it translatable with "tr"?
setStatusError(tr("daemon is busy. Please try again later."));
@@ -128,6 +128,14 @@ class WalletImpl : public Wallet
std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const;
void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label);

MultisigState multisig() const override;
std::string getMultisigInfo() const override;
std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override;
bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) override;
bool exportMultisigImages(std::string& images) override;
size_t importMultisigImages(const std::vector<std::string>& images) override;
PendingTransaction* restoreMultisigTransaction(const std::string& signData) override;

PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id,
optional<uint64_t> amount, uint32_t mixin_count,
PendingTransaction::Priority priority = PendingTransaction::Priority_Low,
@@ -100,6 +100,30 @@ 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;

/**
* @brief multisigSignData
* @return encoded multisig transaction with signers' keys.
* Transfer this data to another wallet participant to sign it.
* Assumed use case is:
* 1. Initiator:
* auto data = pendingTransaction->multisigSignData();
* 2. Signer1:
* pendingTransaction = wallet->restoreMultisigTransaction(data);
* pendingTransaction->signMultisigTx();
* auto signed = pendingTransaction->multisigSignData();
* 3. Signer2:
* pendingTransaction = wallet->restoreMultisigTransaction(signed);
* pendingTransaction->signMultisigTx();
* pendingTransaction->commit();
*/
virtual std::string multisigSignData() = 0;
virtual void signMultisigTx() = 0;
/**
* @brief signersKeys
* @return vector of base58-encoded signers' public keys
*/
virtual std::vector<std::string> signersKeys() const = 0;
};

/**
@@ -291,6 +315,15 @@ struct SubaddressAccount
virtual void refresh() = 0;
};

struct MultisigState {
MultisigState() : isMultisig(false), isReady(false), threshold(0), total(0) {}

bool isMultisig;
bool isReady;
uint32_t threshold;
uint32_t total;
};

struct WalletListener
{
virtual ~WalletListener() = 0;
@@ -636,6 +669,48 @@ struct Wallet
*/
virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0;

/**
* @brief multisig - returns current state of multisig wallet creation process
* @return MultisigState struct
*/
virtual MultisigState multisig() const = 0;
/**
* @brief getMultisigInfo
* @return serialized and signed multisig info string
*/
virtual std::string getMultisigInfo() const = 0;
/**
* @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets
* @param info - vector of multisig infos from other participants obtained with getMulitisInfo call
* @param threshold - number of required signers to make valid transaction. Must be equal to number of participants (N) or N - 1
* @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info
*/
virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0;
/**
* @brief finalizeMultisig - finalizes N - 1 / N multisig wallets creation
* @param extraMultisigInfo - wallet participants' extra multisig info obtained with makeMultisig call
* @return true if success
*/
virtual bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) = 0;
/**
* @brief exportMultisigImages - exports transfers' key images
* @param images - output paramter for hex encoded array of images
* @return true if success
*/
virtual bool exportMultisigImages(std::string& images) = 0;
/**
* @brief importMultisigImages - imports other participants' multisig images
* @param images - array of hex encoded arrays of images obtained with exportMultisigImages
* @return number of imported images
*/
virtual size_t importMultisigImages(const std::vector<std::string>& images) = 0;

/**
* @brief restoreMultisigTransaction creates PendingTransaction from signData
* @param signData encrypted unsigned transaction. Obtained with PendingTransaction::multisigSignData
* @return PendingTransaction
*/
virtual PendingTransaction* restoreMultisigTransaction(const std::string& signData) = 0;
/*!
* \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored
* \param dst_addr destination address as string
Oops, something went wrong.

0 comments on commit e4d8ee3

Please sign in to comment.