diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/api/pending_transaction.cpp | 53 | ||||
-rw-r--r-- | src/wallet/api/pending_transaction.h | 5 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 685 | ||||
-rw-r--r-- | src/wallet/api/wallet.h | 18 | ||||
-rw-r--r-- | src/wallet/api/wallet2_api.h | 102 | ||||
-rw-r--r-- | src/wallet/ringdb.cpp | 18 | ||||
-rw-r--r-- | src/wallet/ringdb.h | 1 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 296 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 53 | ||||
-rw-r--r-- | src/wallet/wallet_args.cpp | 16 | ||||
-rw-r--r-- | src/wallet/wallet_args.h | 7 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 238 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.h | 12 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 126 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_error_codes.h | 4 |
15 files changed, 1275 insertions, 359 deletions
diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index ff4619f0f..8d200220d 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -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; diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index d0bd66eb5..4f963c134 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -55,6 +55,10 @@ public: 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; @@ -62,6 +66,7 @@ private: int m_status; std::string m_errorString; std::vector<tools::wallet2::pending_tx> m_pending_tx; + std::unordered_set<crypto::public_key> m_signers; }; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 367011eaa..7ade42e11 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -69,14 +69,48 @@ namespace { // Connection timeout 30 sec static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30; - std::string get_default_ringdb_path() + std::string get_default_ringdb_path(cryptonote::network_type nettype) { boost::filesystem::path dir = tools::get_default_data_dir(); // remove .bitmonero, replace with .shared-ringdb dir = dir.remove_filename(); dir /= ".shared-ringdb"; + if (nettype == cryptonote::TESTNET) + dir /= "testnet"; + else if (nettype == cryptonote::STAGENET) + dir /= "stagenet"; 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 @@ -395,9 +429,9 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co // add logic to error out if new wallet requested but named wallet file exists if (keys_file_exists || wallet_file_exists) { - m_errorString = "attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."; - LOG_ERROR(m_errorString); - m_status = Status_Critical; + std::string error = "attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."; + LOG_ERROR(error); + setStatusCritical(error); return false; } // TODO: validate language @@ -406,11 +440,10 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co try { recovery_val = m_wallet->generate(path, password, secret_key, false, false); m_password = password; - m_status = Status_Ok; + clearStatus(); } catch (const std::exception &e) { LOG_ERROR("Error creating wallet: " << e.what()); - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); return false; } @@ -434,9 +467,9 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas // add logic to error out if new wallet requested but named wallet file exists if (keys_file_exists || wallet_file_exists) { - m_errorString = "attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting."; - LOG_ERROR(m_errorString); - m_status = Status_Error; + std::string error = "attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting."; + LOG_ERROR(error); + setStatusError(error); return false; } // TODO: validate language @@ -472,11 +505,10 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas uint64_t spent = 0; uint64_t unspent = 0; view_wallet->import_key_images(key_images,spent,unspent,false); - m_status = Status_Ok; + clearStatus(); } catch (const std::exception &e) { LOG_ERROR("Error creating view only wallet: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } // Store wallet @@ -503,8 +535,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, cryptonote::address_parse_info info; if(!get_account_address_from_str(info, m_wallet->nettype(), address_string)) { - m_errorString = tr("failed to parse address"); - m_status = Status_Error; + setStatusError(tr("failed to parse address")); return false; } @@ -515,8 +546,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, cryptonote::blobdata spendkey_data; if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key)) { - m_errorString = tr("failed to parse secret spend key"); - m_status = Status_Error; + setStatusError(tr("failed to parse secret spend key")); return false; } has_spendkey = true; @@ -524,61 +554,68 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, } // parse view secret key + bool has_viewkey = true; + crypto::secret_key viewkey; if (viewkey_string.empty()) { - m_errorString = tr("No view key supplied, cancelled"); - m_status = Status_Error; - return false; + if(has_spendkey) { + has_viewkey = false; + } + else { + setStatusError(tr("Neither view key nor spend key supplied, cancelled")); + return false; + } } - cryptonote::blobdata viewkey_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) - { - m_errorString = tr("failed to parse secret view key"); - m_status = Status_Error; - return false; + if(has_viewkey) { + cryptonote::blobdata viewkey_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) + { + setStatusError(tr("failed to parse secret view key")); + return false; + } + viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); } - crypto::secret_key viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); - // check the spend and view keys match the given address crypto::public_key pkey; if(has_spendkey) { if (!crypto::secret_key_to_public_key(spendkey, pkey)) { - m_errorString = tr("failed to verify secret spend key"); - m_status = Status_Error; + setStatusError(tr("failed to verify secret spend key")); return false; } if (info.address.m_spend_public_key != pkey) { - m_errorString = tr("spend key does not match address"); - m_status = Status_Error; + setStatusError(tr("spend key does not match address")); return false; } } - if (!crypto::secret_key_to_public_key(viewkey, pkey)) { - m_errorString = tr("failed to verify secret view key"); - m_status = Status_Error; - return false; - } - if (info.address.m_view_public_key != pkey) { - m_errorString = tr("view key does not match address"); - m_status = Status_Error; - return false; + if(has_viewkey) { + if (!crypto::secret_key_to_public_key(viewkey, pkey)) { + setStatusError(tr("failed to verify secret view key")); + return false; + } + if (info.address.m_view_public_key != pkey) { + setStatusError(tr("view key does not match address")); + return false; + } } try { - if (has_spendkey) { + if (has_spendkey && has_viewkey) { m_wallet->generate(path, password, info.address, spendkey, viewkey); - setSeedLanguage(language); - LOG_PRINT_L1("Generated new wallet from keys with seed language: " + language); + LOG_PRINT_L1("Generated new wallet from spend key and view key"); } - else { + if(!has_spendkey && has_viewkey) { m_wallet->generate(path, password, info.address, viewkey); LOG_PRINT_L1("Generated new view only wallet from keys"); } + if(has_spendkey && !has_viewkey) { + m_wallet->generate(path, password, spendkey, true, false, false); + setSeedLanguage(language); + LOG_PRINT_L1("Generated deterministic wallet from spend key with seed language: " + language); + } } catch (const std::exception& e) { - m_errorString = string(tr("failed to generate new wallet: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("failed to generate new wallet: ")) + e.what()); return false; } return true; @@ -599,16 +636,15 @@ bool WalletImpl::open(const std::string &path, const std::string &password) // Rebuilding wallet cache, using refresh height from .keys file m_rebuildWalletCache = true; } - m_wallet->set_ring_database(get_default_ringdb_path()); + m_wallet->set_ring_database(get_default_ringdb_path(m_wallet->nettype())); m_wallet->load(path, password); m_password = password; } catch (const std::exception &e) { LOG_ERROR("Error opening wallet: " << e.what()); - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } bool WalletImpl::recover(const std::string &path, const std::string &seed) @@ -621,9 +657,8 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c clearStatus(); m_errorString.clear(); if (seed.empty()) { - m_errorString = "Electrum seed is empty"; - LOG_ERROR(m_errorString); - m_status = Status_Error; + LOG_ERROR("Electrum seed is empty"); + setStatusError(tr("Electrum seed is empty")); return false; } @@ -631,8 +666,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c crypto::secret_key recovery_key; std::string old_language; if (!crypto::ElectrumWords::words_to_bytes(seed, recovery_key, old_language)) { - m_errorString = "Electrum-style word list failed verification"; - m_status = Status_Error; + setStatusError(tr("Electrum-style word list failed verification")); return false; } @@ -644,10 +678,9 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c m_wallet->generate(path, password, recovery_key, true, false); } catch (const std::exception &e) { - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } bool WalletImpl::close(bool store) @@ -671,8 +704,7 @@ bool WalletImpl::close(bool store) result = true; clearStatus(); } catch (const std::exception &e) { - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); LOG_ERROR("Error closing wallet: " << e.what()); } return result; @@ -698,14 +730,22 @@ void WalletImpl::setSeedLanguage(const std::string &arg) int WalletImpl::status() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); return m_status; } std::string WalletImpl::errorString() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); return m_errorString; } +void WalletImpl::statusWithErrorString(int& status, std::string& errorString) const { + boost::lock_guard<boost::mutex> l(m_statusMutex); + status = m_status; + errorString = m_errorString; +} + bool WalletImpl::setPassword(const std::string &password) { clearStatus(); @@ -713,10 +753,9 @@ bool WalletImpl::setPassword(const std::string &password) m_wallet->rewrite(m_wallet->get_wallet_file(), password); m_password = password; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } std::string WalletImpl::address(uint32_t accountIndex, uint32_t addressIndex) const @@ -753,6 +792,16 @@ std::string WalletImpl::publicSpendKey() const return epee::string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key); } +std::string WalletImpl::publicMultisigSignerKey() const +{ + try { + crypto::public_key signer = m_wallet->get_multisig_signer_public_key(); + return epee::string_tools::pod_to_hex(signer); + } catch (const std::exception&) { + return ""; + } +} + std::string WalletImpl::path() const { return m_wallet->path(); @@ -769,11 +818,11 @@ bool WalletImpl::store(const std::string &path) } } catch (const std::exception &e) { LOG_ERROR("Error saving wallet: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); + return false; } - return m_status == Status_Ok; + return true; } string WalletImpl::filename() const @@ -806,8 +855,7 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_ { cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response response; if(!m_wallet->light_wallet_import_wallet_request(response)){ - m_errorString = tr("Failed to send import wallet request"); - m_status = Status_Error; + setStatusError(tr("Failed to send import wallet request")); return false; } fee = response.import_fee; @@ -820,8 +868,7 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_ catch (const std::exception &e) { LOG_ERROR("Error sending import wallet request: " << e.what()); - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); return false; } return true; @@ -870,12 +917,9 @@ uint64_t WalletImpl::daemonBlockChainHeight() const if (!err.empty()) { LOG_ERROR(__FUNCTION__ << ": " << err); result = 0; - m_errorString = err; - m_status = Status_Error; - + setStatusError(err); } else { - m_status = Status_Ok; - m_errorString = ""; + clearStatus(); } return result; } @@ -892,12 +936,9 @@ uint64_t WalletImpl::daemonBlockChainTargetHeight() const if (!err.empty()) { LOG_ERROR(__FUNCTION__ << ": " << err); result = 0; - m_errorString = err; - m_status = Status_Error; - + setStatusError(err); } else { - m_status = Status_Ok; - m_errorString = ""; + clearStatus(); } // Target height can be 0 when daemon is synced. Use blockchain height instead. if(result == 0) @@ -921,8 +962,10 @@ bool WalletImpl::synchronized() const bool WalletImpl::refresh() { clearStatus(); + //TODO: make doRefresh return bool to know whether the error occured during refresh or not + //otherwise one may try, say, to send transaction, transfer fails and this method returns false doRefresh(); - return m_status == Status_Ok; + return status() == Status_Ok; } void WalletImpl::refreshAsync() @@ -952,8 +995,7 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file clearStatus(); UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ - m_errorString = tr("Failed to load unsigned transactions"); - m_status = Status_Error; + setStatusError(tr("Failed to load unsigned transactions")); } // Check tx data and construct confirmation message @@ -961,8 +1003,7 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file if (!transaction->m_unsigned_tx_set.transfers.empty()) extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.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); - m_status = transaction->status(); - m_errorString = transaction->errorString(); + setStatus(transaction->status(), transaction->errorString()); return transaction; } @@ -973,14 +1014,12 @@ bool WalletImpl::submitTransaction(const string &fileName) { bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx); if (!r) { - m_errorString = tr("Failed to load transaction from file"); - m_status = Status_Ok; + setStatus(Status_Ok, tr("Failed to load transaction from file")); return false; } if(!transaction->commit()) { - m_errorString = transaction->m_errorString; - m_status = Status_Error; + setStatusError(transaction->m_errorString); return false; } @@ -991,8 +1030,7 @@ bool WalletImpl::exportKeyImages(const string &filename) { if (m_wallet->watch_only()) { - m_errorString = tr("Wallet is view only"); - m_status = Status_Error; + setStatusError(tr("Wallet is view only")); return false; } @@ -1000,16 +1038,14 @@ bool WalletImpl::exportKeyImages(const string &filename) { if (!m_wallet->export_key_images(filename)) { - m_errorString = tr("failed to save file ") + filename; - m_status = Status_Error; + setStatusError(tr("failed to save file ") + filename); return false; } } catch (const std::exception &e) { LOG_ERROR("Error exporting key images: " << e.what()); - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); return false; } return true; @@ -1018,8 +1054,7 @@ bool WalletImpl::exportKeyImages(const string &filename) bool WalletImpl::importKeyImages(const string &filename) { if (!trustedDaemon()) { - m_status = Status_Error; - m_errorString = tr("Key images can only be imported with a trusted daemon"); + setStatusError(tr("Key images can only be imported with a trusted daemon")); return false; } try @@ -1032,8 +1067,7 @@ bool WalletImpl::importKeyImages(const string &filename) catch (const std::exception &e) { LOG_ERROR("Error exporting key images: " << e.what()); - m_errorString = string(tr("Failed to import key images: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to import key images: ")) + e.what()); return false; } @@ -1065,8 +1099,7 @@ std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addre catch (const std::exception &e) { LOG_ERROR("Error getting subaddress label: ") << e.what(); - m_errorString = string(tr("Failed to get subaddress label: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to get subaddress label: ")) + e.what()); return ""; } } @@ -1079,9 +1112,134 @@ void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex catch (const std::exception &e) { LOG_ERROR("Error setting subaddress label: ") << e.what(); - m_errorString = string(tr("Failed to set subaddress label: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to set subaddress label: ")) + e.what()); + } +} + +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: @@ -1117,8 +1275,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const do { if(!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), dst_addr)) { // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 - m_status = Status_Error; - m_errorString = "Invalid destination address"; + setStatusError(tr("Invalid destination address")); break; } @@ -1143,8 +1300,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const } if (!r) { - m_status = Status_Error; - m_errorString = tr("payment id has invalid format, expected 16 or 64 character hex string: ") + payment_id; + setStatusError(tr("payment id has invalid format, expected 16 or 64 character hex string: ") + payment_id); break; } } @@ -1153,8 +1309,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id); bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce); if (!r) { - m_status = Status_Error; - m_errorString = tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(info.payment_id); + setStatusError(tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(info.payment_id)); break; } } @@ -1185,40 +1340,33 @@ 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"? - m_errorString = tr("daemon is busy. Please try again later."); - m_status = Status_Error; + setStatusError(tr("daemon is busy. Please try again later.")); } catch (const tools::error::no_connection_to_daemon&) { - m_errorString = tr("no connection to daemon. Please make sure daemon is running."); - m_status = Status_Error; + setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); } catch (const tools::error::wallet_rpc_error& e) { - m_errorString = tr("RPC error: ") + e.to_string(); - m_status = Status_Error; + setStatusError(tr("RPC error: ") + e.to_string()); } catch (const tools::error::get_random_outs_error &e) { - m_errorString = (boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str(); - m_status = Status_Error; - + setStatusError((boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str()); } catch (const tools::error::not_enough_unlocked_money& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_money& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::tx_not_possible& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % @@ -1226,8 +1374,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_outs_to_mix& e) { std::ostringstream writer; writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; @@ -1235,42 +1382,31 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; } writer << "\n" << tr("Please sweep unmixable outputs."); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_not_constructed&) { - m_errorString = tr("transaction was not constructed"); - m_status = Status_Error; + setStatusError(tr("transaction was not constructed")); } catch (const tools::error::tx_rejected& e) { std::ostringstream writer; writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_sum_overflow& e) { - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); } catch (const tools::error::zero_destination&) { - m_errorString = tr("one of destinations is zero"); - m_status = Status_Error; + setStatusError(tr("one of destinations is zero")); } catch (const tools::error::tx_too_big& e) { - m_errorString = tr("failed to find a suitable way to split transactions"); - m_status = Status_Error; + setStatusError(tr("failed to find a suitable way to split transactions")); } catch (const tools::error::transfer_error& e) { - m_errorString = string(tr("unknown transfer error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unknown transfer error: ")) + e.what()); } catch (const tools::error::wallet_internal_error& e) { - m_errorString = string(tr("internal error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("internal error: ")) + e.what()); } catch (const std::exception& e) { - m_errorString = string(tr("unexpected error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unexpected error: ")) + e.what()); } catch (...) { - m_errorString = tr("unknown error"); - m_status = Status_Error; + setStatusError(tr("unknown error")); } } while (false); - transaction->m_status = m_status; - transaction->m_errorString = m_errorString; + statusWithErrorString(transaction->m_status, transaction->m_errorString); // Resume refresh thread startRefresh(); return transaction; @@ -1291,38 +1427,31 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? - m_errorString = tr("daemon is busy. Please try again later."); - m_status = Status_Error; + setStatusError(tr("daemon is busy. Please try again later.")); } catch (const tools::error::no_connection_to_daemon&) { - m_errorString = tr("no connection to daemon. Please make sure daemon is running."); - m_status = Status_Error; + setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); } catch (const tools::error::wallet_rpc_error& e) { - m_errorString = tr("RPC error: ") + e.to_string(); - m_status = Status_Error; + setStatusError(tr("RPC error: ") + e.to_string()); } catch (const tools::error::get_random_outs_error&) { - m_errorString = tr("failed to get random outputs to mix"); - m_status = Status_Error; - + setStatusError(tr("failed to get random outputs to mix")); } catch (const tools::error::not_enough_unlocked_money& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_money& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::tx_not_possible& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % @@ -1330,50 +1459,38 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_outs_to_mix& e) { std::ostringstream writer; writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; for (const std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) { writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; } - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_not_constructed&) { - m_errorString = tr("transaction was not constructed"); - m_status = Status_Error; + setStatusError(tr("transaction was not constructed")); } catch (const tools::error::tx_rejected& e) { std::ostringstream writer; writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_sum_overflow& e) { - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); } catch (const tools::error::zero_destination&) { - m_errorString = tr("one of destinations is zero"); - m_status = Status_Error; + setStatusError(tr("one of destinations is zero")); } catch (const tools::error::tx_too_big& e) { - m_errorString = tr("failed to find a suitable way to split transactions"); - m_status = Status_Error; + setStatusError(tr("failed to find a suitable way to split transactions")); } catch (const tools::error::transfer_error& e) { - m_errorString = string(tr("unknown transfer error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unknown transfer error: ")) + e.what()); } catch (const tools::error::wallet_internal_error& e) { - m_errorString = string(tr("internal error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("internal error: ")) + e.what()); } catch (const std::exception& e) { - m_errorString = string(tr("unexpected error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unexpected error: ")) + e.what()); } catch (...) { - m_errorString = tr("unknown error"); - m_status = Status_Error; + setStatusError(tr("unknown error")); } } while (false); - transaction->m_status = m_status; - transaction->m_errorString = m_errorString; + statusWithErrorString(transaction->m_status, transaction->m_errorString); return transaction; } @@ -1444,8 +1561,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } @@ -1453,7 +1569,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const std::vector<crypto::secret_key> additional_tx_keys; if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { - m_status = Status_Ok; + clearStatus(); std::ostringstream oss; oss << epee::string_tools::pod_to_hex(tx_key); for (size_t i = 0; i < additional_tx_keys.size(); ++i) @@ -1462,8 +1578,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const } else { - m_status = Status_Error; - m_errorString = tr("no tx keys found for this txid"); + setStatusError(tr("no tx keys found for this txid")); return ""; } } @@ -1473,8 +1588,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } @@ -1482,8 +1596,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, std::vector<crypto::secret_key> additional_tx_keys; if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), tx_key)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse tx key"); + setStatusError(tr("Failed to parse tx key")); return false; } tx_key_str = tx_key_str.substr(64); @@ -1492,8 +1605,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, additional_tx_keys.resize(additional_tx_keys.size() + 1); if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), additional_tx_keys.back())) { - m_status = Status_Error; - m_errorString = tr("Failed to parse tx key"); + setStatusError(tr("Failed to parse tx key")); return false; } tx_key_str = tx_key_str.substr(64); @@ -1502,21 +1614,19 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } try { m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, received, in_pool, confirmations); - m_status = Status_Ok; + clearStatus(); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1526,28 +1636,25 @@ std::string WalletImpl::getTxProof(const std::string &txid_str, const std::strin crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return ""; } try { - m_status = Status_Ok; + clearStatus(); return m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, message); } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1557,29 +1664,26 @@ bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &ad crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } try { good = m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, message, signature, received, in_pool, confirmations); - m_status = Status_Ok; + clearStatus(); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1588,20 +1692,18 @@ std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::st crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } try { - m_status = Status_Ok; + clearStatus(); return m_wallet->get_spend_proof(txid, message); } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1611,21 +1713,19 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } try { - m_status = Status_Ok; + clearStatus(); good = m_wallet->check_spend_proof(txid, message, signature); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1633,7 +1733,7 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const { try { - m_status = Status_Ok; + clearStatus(); boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; if (!all) { @@ -1643,8 +1743,7 @@ std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64 } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1653,28 +1752,25 @@ bool WalletImpl::checkReserveProof(const std::string &address, const std::string cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } if (info.is_subaddress) { - m_status = Status_Error; - m_errorString = tr("Address must not be a subaddress"); + setStatusError(tr("Address must not be a subaddress")); return false; } good = false; try { - m_status = Status_Ok; + clearStatus(); good = m_wallet->check_reserve_proof(info.address, message, signature, total, spent); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1694,13 +1790,57 @@ bool WalletImpl::verifySignedMessage(const std::string &message, const std::stri return m_wallet->verify(message, info.address, signature); } +std::string WalletImpl::signMultisigParticipant(const std::string &message) const +{ + clearStatus(); + + bool ready = false; + if (!m_wallet->multisig(&ready) || !ready) { + m_status = Status_Error; + m_errorString = tr("The wallet must be in multisig ready state"); + return {}; + } + + try { + return m_wallet->sign_multisig_participant(message); + } catch (const std::exception& e) { + m_status = Status_Error; + m_errorString = e.what(); + } + + return {}; +} + +bool WalletImpl::verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const +{ + clearStatus(); + + cryptonote::blobdata pkeyData; + if(!epee::string_tools::parse_hexstr_to_binbuff(publicKey, pkeyData) || pkeyData.size() != sizeof(crypto::public_key)) + { + m_status = Status_Error; + m_errorString = tr("Given string is not a key"); + return false; + } + + try { + crypto::public_key pkey = *reinterpret_cast<const crypto::public_key*>(pkeyData.data()); + return m_wallet->verify_with_public_key(message, pkey, signature); + } catch (const std::exception& e) { + m_status = Status_Error; + m_errorString = e.what(); + } + + return false; +} + bool WalletImpl::connectToDaemon() { bool result = m_wallet->check_connection(NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); - m_status = result ? Status_Ok : Status_Error; if (!result) { - m_errorString = "Error connecting to daemon at " + m_wallet->get_daemon_address(); + setStatusError("Error connecting to daemon at " + m_wallet->get_daemon_address()); } else { + clearStatus(); // start refreshing here } return result; @@ -1735,10 +1875,28 @@ bool WalletImpl::watchOnly() const void WalletImpl::clearStatus() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); m_status = Status_Ok; m_errorString.clear(); } +void WalletImpl::setStatusError(const std::string& message) const +{ + setStatus(Status_Error, message); +} + +void WalletImpl::setStatusCritical(const std::string& message) const +{ + setStatus(Status_Critical, message); +} + +void WalletImpl::setStatus(int status, const std::string& message) const +{ + boost::lock_guard<boost::mutex> l(m_statusMutex); + m_status = status; + m_errorString = message; +} + void WalletImpl::refreshThreadFunc() { LOG_PRINT_L3(__FUNCTION__ << ": starting refresh thread"); @@ -1752,7 +1910,7 @@ void WalletImpl::refreshThreadFunc() // if auto refresh enabled, we wait for the "m_refreshIntervalSeconds" interval. // if not - we wait forever if (m_refreshIntervalMillis > 0) { - boost::posix_time::milliseconds wait_for_ms(m_refreshIntervalMillis); + boost::posix_time::milliseconds wait_for_ms(m_refreshIntervalMillis.load()); m_refreshCV.timed_wait(lock, wait_for_ms); } else { m_refreshCV.wait(lock); @@ -1760,7 +1918,7 @@ void WalletImpl::refreshThreadFunc() LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired..."); LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled); - LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << m_status); + LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << status()); if (m_refreshEnabled) { LOG_PRINT_L3(__FUNCTION__ << ": refreshing..."); doRefresh(); @@ -1792,8 +1950,7 @@ void WalletImpl::doRefresh() LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced"); } } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); } if (m_wallet2Callback->getListener()) { m_wallet2Callback->getListener()->refreshed(); @@ -1882,16 +2039,14 @@ bool WalletImpl::rescanSpent() { clearStatus(); if (!trustedDaemon()) { - m_status = Status_Error; - m_errorString = tr("Rescan spent can only be used with a trusted daemon"); + setStatusError(tr("Rescan spent can only be used with a trusted daemon")); return false; } try { m_wallet->rescan_spent(); } catch (const std::exception &e) { LOG_ERROR(__FUNCTION__ << " error: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } return true; @@ -1917,8 +2072,7 @@ bool WalletImpl::blackballOutputs(const std::vector<std::string> &pubkeys, bool crypto::public_key pkey; if (!epee::string_tools::hex_to_pod(str, pkey)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse output public key"); + setStatusError(tr("Failed to parse output public key")); return false; } raw_pubkeys.push_back(pkey); @@ -1926,8 +2080,7 @@ bool WalletImpl::blackballOutputs(const std::vector<std::string> &pubkeys, bool bool ret = m_wallet->set_blackballed_outputs(raw_pubkeys, add); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to set blackballed outputs"); + setStatusError(tr("Failed to set blackballed outputs")); return false; } return true; @@ -1938,15 +2091,13 @@ bool WalletImpl::unblackballOutput(const std::string &pubkey) crypto::public_key raw_pubkey; if (!epee::string_tools::hex_to_pod(pubkey, raw_pubkey)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse output public key"); + setStatusError(tr("Failed to parse output public key")); return false; } bool ret = m_wallet->unblackball_output(raw_pubkey); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to unblackball output"); + setStatusError(tr("Failed to unblackball output")); return false; } return true; @@ -1957,15 +2108,13 @@ bool WalletImpl::getRing(const std::string &key_image, std::vector<uint64_t> &ri crypto::key_image raw_key_image; if (!epee::string_tools::hex_to_pod(key_image, raw_key_image)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse key image"); + setStatusError(tr("Failed to parse key image")); return false; } bool ret = m_wallet->get_ring(raw_key_image, ring); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to get ring"); + setStatusError(tr("Failed to get ring")); return false; } return true; @@ -1976,16 +2125,14 @@ bool WalletImpl::getRings(const std::string &txid, std::vector<std::pair<std::st crypto::hash raw_txid; if (!epee::string_tools::hex_to_pod(txid, raw_txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> raw_rings; bool ret = m_wallet->get_rings(raw_txid, raw_rings); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to get rings"); + setStatusError(tr("Failed to get rings")); return false; } for (const auto &r: raw_rings) @@ -2000,15 +2147,13 @@ bool WalletImpl::setRing(const std::string &key_image, const std::vector<uint64_ crypto::key_image raw_key_image; if (!epee::string_tools::hex_to_pod(key_image, raw_key_image)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse key image"); + setStatusError(tr("Failed to parse key image")); return false; } bool ret = m_wallet->set_ring(raw_key_image, ring, relative); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to set ring"); + setStatusError(tr("Failed to set ring")); return false; } return true; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 4929c9673..813ca4b30 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -83,6 +83,7 @@ public: // void setListener(Listener *) {} int status() const; std::string errorString() const; + void statusWithErrorString(int& status, std::string& errorString) const override; bool setPassword(const std::string &password); std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const; std::string integratedAddress(const std::string &payment_id) const; @@ -90,6 +91,7 @@ public: std::string publicViewKey() const; std::string secretSpendKey() const; std::string publicSpendKey() const; + std::string publicMultisigSignerKey() const; std::string path() const; bool store(const std::string &path); std::string filename() const; @@ -126,6 +128,14 @@ public: 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, @@ -157,6 +167,8 @@ public: virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const; virtual std::string signMessage(const std::string &message); virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const; + virtual std::string signMultisigParticipant(const std::string &message) const; + virtual bool verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const; virtual void startRefresh(); virtual void pauseRefresh(); virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error); @@ -174,6 +186,9 @@ public: private: void clearStatus() const; + void setStatusError(const std::string& message) const; + void setStatusCritical(const std::string& message) const; + void setStatus(int status, const std::string& message) const; void refreshThreadFunc(); void doRefresh(); bool daemonSynced() const; @@ -191,7 +206,8 @@ private: friend class SubaddressAccountImpl; tools::wallet2 * m_wallet; - mutable std::atomic<int> m_status; + mutable boost::mutex m_statusMutex; + mutable int m_status; mutable std::string m_errorString; std::string m_password; TransactionHistoryImpl * m_history; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 4fbc7298a..5b99bd975 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -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; @@ -358,9 +391,11 @@ struct Wallet virtual std::string getSeedLanguage() const = 0; virtual void setSeedLanguage(const std::string &arg) = 0; //! returns wallet status (Status_Ok | Status_Error) - virtual int status() const = 0; + virtual int status() const = 0; //deprecated: use safe alternative statusWithErrorString //! in case error status, returns error string - virtual std::string errorString() const = 0; + virtual std::string errorString() const = 0; //deprecated: use safe alternative statusWithErrorString + //! returns both error and error string atomically. suggested to use in instead of status() and errorString() + virtual void statusWithErrorString(int& status, std::string& errorString) const = 0; virtual bool setPassword(const std::string &password) = 0; virtual std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const = 0; std::string mainAddress() const { return address(0, 0); } @@ -409,6 +444,12 @@ struct Wallet virtual std::string publicSpendKey() const = 0; /*! + * \brief publicMultisigSignerKey - returns public signer key + * \return - public multisignature signer key or empty string if wallet is not multisig + */ + virtual std::string publicMultisigSignerKey() const = 0; + + /*! * \brief store - stores wallet to file. * \param path - main filename to store wallet to. additionally stores address file and keys file. * to store to the same file - just pass empty string; @@ -629,6 +670,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 @@ -748,6 +831,21 @@ struct Wallet */ virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; + /*! + * \brief signMultisigParticipant signs given message with the multisig public signer key + * \param message message to sign + * \return signature in case of success. Sets status to Error and return empty string in case of error + */ + virtual std::string signMultisigParticipant(const std::string &message) const = 0; + /*! + * \brief verifyMessageWithPublicKey verifies that message was signed with the given public key + * \param message message + * \param publicKey hex encoded public key + * \param signature signature of the message + * \return true if the signature is correct. false and sets error state in case of error + */ + virtual bool verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const = 0; + virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0; virtual std::string getDefaultDataDir() const = 0; diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index 44992520f..3f2634c8b 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -190,7 +190,8 @@ namespace tools { ringdb::ringdb(std::string filename, const std::string &genesis): - filename(filename) + filename(filename), + env(NULL) { MDB_txn *txn; bool tx_active = false; @@ -227,9 +228,18 @@ ringdb::ringdb(std::string filename, const std::string &genesis): ringdb::~ringdb() { - mdb_dbi_close(env, dbi_rings); - mdb_dbi_close(env, dbi_blackballs); - mdb_env_close(env); + close(); +} + +void ringdb::close() +{ + if (env) + { + mdb_dbi_close(env, dbi_rings); + mdb_dbi_close(env, dbi_blackballs); + mdb_env_close(env); + env = NULL; + } } bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 2bd1ac149..6b4bce124 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -41,6 +41,7 @@ namespace tools { public: ringdb(std::string filename, const std::string &genesis); + void close(); ~ringdb(); bool add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 18136ec27..bb0953689 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -110,11 +110,15 @@ using namespace cryptonote; #define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" +#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" + #define SEGREGATION_FORK_HEIGHT 1546000 #define TESTNET_SEGREGATION_FORK_HEIGHT 1000000 #define STAGENET_SEGREGATION_FORK_HEIGHT 1000000 #define SEGREGATION_FORK_VICINITY 1500 /* blocks */ +static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; + namespace { @@ -141,13 +145,15 @@ struct options { const command_line::arg_descriptor<bool> testnet = {"testnet", tools::wallet2::tr("For testnet. Daemon must also be launched with --testnet flag"), false}; const command_line::arg_descriptor<bool> stagenet = {"stagenet", tools::wallet2::tr("For stagenet. Daemon must also be launched with --stagenet flag"), false}; const command_line::arg_descriptor<bool> restricted = {"restricted-rpc", tools::wallet2::tr("Restricts to view-only commands"), false}; - const command_line::arg_descriptor<std::string, false, true> shared_ringdb_dir = { + const command_line::arg_descriptor<std::string, false, true, 2> shared_ringdb_dir = { "shared-ringdb-dir", tools::wallet2::tr("Set shared ring database path"), get_default_ringdb_path(), - testnet, - [](bool testnet, bool defaulted, std::string val)->std::string { - if (testnet) + {{ &testnet, &stagenet }}, + [](std::array<bool, 2> testnet_stagenet, bool defaulted, std::string val)->std::string { + if (testnet_stagenet[0]) return (boost::filesystem::path(val) / "testnet").string(); + else if (testnet_stagenet[1]) + return (boost::filesystem::path(val) / "stagenet").string(); return val; } }; @@ -2883,6 +2889,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) const * \param keys_file_name Keys file to verify password for * \param password Password to verify * \param no_spend_key If set = only verify view keys, otherwise also spend keys + * \param hwdev The hardware device to use * \return true if password is correct * * for verification only @@ -2933,9 +2940,10 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip /*! * \brief Generates a wallet or restores one. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param multisig_data The multisig restore info and keys + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param multisig_data The multisig restore info and keys + * \param create_address_file Whether to create an address file */ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const std::string& multisig_data, bool create_address_file) @@ -3028,12 +3036,13 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& /*! * \brief Generates a wallet or restores one. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param recovery_param If it is a restore, the recovery key - * \param recover Whether it is a restore - * \param two_random Whether it is a non-deterministic wallet - * \return The secret key of the generated wallet + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param recovery_param If it is a restore, the recovery key + * \param recover Whether it is a restore + * \param two_random Whether it is a non-deterministic wallet + * \param create_address_file Whether to create an address file + * \return The secret key of the generated wallet */ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const crypto::secret_key& recovery_param, bool recover, bool two_random, bool create_address_file) @@ -3129,9 +3138,11 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip /*! * \brief Creates a watch only wallet from a public address and a view secret key. -* \param wallet_ Name of wallet file -* \param password Password of wallet file -* \param viewkey view secret key +* \param wallet_ Name of wallet file +* \param password Password of wallet file +* \param account_public_address The account's public address +* \param viewkey view secret key +* \param create_address_file Whether to create an address file */ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, @@ -3178,10 +3189,12 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& /*! * \brief Creates a wallet from a public address and a spend/view secret key pair. -* \param wallet_ Name of wallet file -* \param password Password of wallet file -* \param spendkey spend secret key -* \param viewkey view secret key +* \param wallet_ Name of wallet file +* \param password Password of wallet file +* \param account_public_address The account's public address +* \param spendkey spend secret key +* \param viewkey view secret key +* \param create_address_file Whether to create an address file */ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, @@ -3611,6 +3624,14 @@ bool wallet2::has_multisig_partial_key_images() const return false; } +bool wallet2::has_unknown_key_images() const +{ + for (const auto &td: m_transfers) + if (!td.m_key_image_known) + return true; + return false; +} + /*! * \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there) * \param wallet_name Name of wallet file (should exist) @@ -3628,8 +3649,9 @@ void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_strin } /*! * \brief Writes to a file named based on the normal wallet (doesn't generate key, assumes it's already there) - * \param wallet_name Base name of wallet file - * \param password Password for wallet file + * \param wallet_name Base name of wallet file + * \param password Password for wallet file + * \param new_keys_filename [OUT] Name of new keys file */ void wallet2::write_watch_only_wallet(const std::string& wallet_name, const epee::wipeable_string& password, std::string &new_keys_filename) { @@ -4653,6 +4675,15 @@ void wallet2::commit_tx(std::vector<pending_tx>& ptx_vector) bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) const { LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions"); + std::string ciphertext = dump_tx_to_str(ptx_vector); + if (ciphertext.empty()) + return false; + return epee::file_io_utils::save_string_to_file(filename, ciphertext); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) const +{ + LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions"); unsigned_tx_set txs; for (auto &tx: ptx_vector) { @@ -4672,11 +4703,11 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri } catch (...) { - return false; + return std::string(); } LOG_PRINT_L2("Saving unsigned tx data: " << oss.str()); std::string ciphertext = encrypt_with_view_secret_key(oss.str()); - return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + ciphertext); + return std::string(UNSIGNED_TX_PREFIX) + ciphertext; } //---------------------------------------------------------------------------------------------------- bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) const @@ -4694,10 +4725,17 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx LOG_PRINT_L0("Failed to load from " << unsigned_filename); return false; } + + return parse_unsigned_tx_from_str(s, exported_txs); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const +{ + std::string s = unsigned_tx_st; const size_t magiclen = strlen(UNSIGNED_TX_PREFIX) - 1; if (strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen)) { - LOG_PRINT_L0("Bad magic from " << unsigned_filename); + LOG_PRINT_L0("Bad magic from unsigned tx"); return false; } s = s.substr(magiclen); @@ -4713,7 +4751,7 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx } catch (...) { - LOG_PRINT_L0("Failed to parse data from " << unsigned_filename); + LOG_PRINT_L0("Failed to parse data from unsigned tx"); return false; } } @@ -4730,19 +4768,19 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx } catch (...) { - LOG_PRINT_L0("Failed to parse data from " << unsigned_filename); + LOG_PRINT_L0("Failed to parse data from unsigned tx"); return false; } } catch (const std::exception &e) { - LOG_PRINT_L0("Failed to decrypt " << unsigned_filename << ": " << e.what()); + LOG_PRINT_L0("Failed to decrypt unsigned tx: " << e.what()); return false; } } else { - LOG_PRINT_L0("Unsupported version in " << unsigned_filename); + LOG_PRINT_L0("Unsupported version in unsigned tx"); return false; } LOG_PRINT_L1("Loaded tx unsigned data from binary: " << exported_txs.txes.size() << " transactions"); @@ -4763,14 +4801,12 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s } return sign_tx(exported_txs, signed_filename, txs, export_raw); } - //---------------------------------------------------------------------------------------------------- -bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, bool export_raw) +bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &txs, signed_tx_set &signed_txes) { import_outputs(exported_txs.transfers); // sign the transactions - signed_tx_set signed_txes; for (size_t n = 0; n < exported_txs.txes.size(); ++n) { tools::wallet2::tx_construction_data &sd = exported_txs.txes[n]; @@ -4832,19 +4868,20 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f signed_txes.key_images[i] = m_transfers[i].m_key_image; } - // save as binary - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << signed_txes; - } - catch(...) + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, bool export_raw) +{ + // sign the transactions + signed_tx_set signed_txes; + std::string ciphertext = sign_tx_dump_to_str(exported_txs, txs, signed_txes); + if (ciphertext.empty()) { + LOG_PRINT_L0("Failed to sign unsigned_tx_set"); return false; } - LOG_PRINT_L3("Saving signed tx data (with encryption): " << oss.str()); - std::string ciphertext = encrypt_with_view_secret_key(oss.str()); + if (!epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + ciphertext)) { LOG_PRINT_L0("Failed to save file to " << signed_filename); @@ -4867,6 +4904,32 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f return true; } //---------------------------------------------------------------------------------------------------- +std::string wallet2::sign_tx_dump_to_str(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &ptx, signed_tx_set &signed_txes) +{ + // sign the transactions + bool r = sign_tx(exported_txs, ptx, signed_txes); + if (!r) + { + LOG_PRINT_L0("Failed to sign unsigned_tx_set"); + return std::string(); + } + + // save as binary + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << signed_txes; + } + catch(...) + { + return std::string(); + } + LOG_PRINT_L3("Saving signed tx data (with encryption): " << oss.str()); + std::string ciphertext = encrypt_with_view_secret_key(oss.str()); + return std::string(SIGNED_TX_PREFIX) + ciphertext; +} +//---------------------------------------------------------------------------------------------------- bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func) { std::string s; @@ -4884,10 +4947,20 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal LOG_PRINT_L0("Failed to load from " << signed_filename); return false; } + + return parse_tx_from_str(s, ptx, accept_func); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func) +{ + std::string s = signed_tx_st; + boost::system::error_code errcode; + signed_tx_set signed_txs; + const size_t magiclen = strlen(SIGNED_TX_PREFIX) - 1; if (strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen)) { - LOG_PRINT_L0("Bad magic from " << signed_filename); + LOG_PRINT_L0("Bad magic from signed transaction"); return false; } s = s.substr(magiclen); @@ -4903,7 +4976,7 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal } catch (...) { - LOG_PRINT_L0("Failed to parse data from " << signed_filename); + LOG_PRINT_L0("Failed to parse data from signed transaction"); return false; } } @@ -4920,23 +4993,23 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal } catch (...) { - LOG_PRINT_L0("Failed to parse decrypted data from " << signed_filename); + LOG_PRINT_L0("Failed to parse decrypted data from signed transaction"); return false; } } catch (const std::exception &e) { - LOG_PRINT_L0("Failed to decrypt " << signed_filename << ": " << e.what()); + LOG_PRINT_L0("Failed to decrypt signed transaction: " << e.what()); return false; } } else { - LOG_PRINT_L0("Unsupported version in " << signed_filename); + LOG_PRINT_L0("Unsupported version in signed transaction"); return false; } LOG_PRINT_L0("Loaded signed tx data from binary: " << signed_txs.ptx.size() << " transactions"); - for (auto &ptx: signed_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx)); + for (auto &c_ptx: signed_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(c_ptx.tx)); if (accept_func && !accept_func(signed_txs)) { @@ -5013,7 +5086,7 @@ bool wallet2::save_multisig_tx(const multisig_tx_set &txs, const std::string &fi return epee::file_io_utils::save_string_to_file(filename, ciphertext); } //---------------------------------------------------------------------------------------------------- -std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) +wallet2::multisig_tx_set wallet2::make_multisig_tx_set(const std::vector<pending_tx>& ptx_vector) const { multisig_tx_set txs; txs.m_ptx = ptx_vector; @@ -5025,8 +5098,12 @@ std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) } txs.m_signers.insert(get_multisig_signer_public_key()); + return txs; +} - return save_multisig_tx(txs); +std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) +{ + return save_multisig_tx(make_multisig_tx_set(ptx_vector)); } //---------------------------------------------------------------------------------------------------- bool wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) @@ -5658,7 +5735,7 @@ bool wallet2::find_and_save_rings(bool force) for (size_t slice = 0; slice < txs_hashes.size(); slice += SLICE_SIZE) { req.decode_as_json = false; - req.prune = true; + req.prune = false; req.txs_hashes.clear(); size_t ntxes = slice + SLICE_SIZE > txs_hashes.size() ? txs_hashes.size() - slice : SLICE_SIZE; for (size_t s = slice; s < slice + ntxes; ++s) @@ -8414,8 +8491,9 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes } std::vector<std::vector<crypto::signature>> signatures = { std::vector<crypto::signature>(1) }; const size_t sig_len = tools::base58::encode(std::string((const char *)&signatures[0][0], sizeof(crypto::signature))).size(); - THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * sig_len, - error::wallet_internal_error, "incorrect signature size"); + if( sig_str.size() != header_len + num_sigs * sig_len ) { + return false; + } // decode base58 signatures.clear(); @@ -9268,6 +9346,40 @@ bool wallet2::verify(const std::string &data, const cryptonote::account_public_a memcpy(&s, decoded.data(), sizeof(s)); return crypto::check_signature(hash, address.m_spend_public_key, s); } + +std::string wallet2::sign_multisig_participant(const std::string& data) const +{ + CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); + + crypto::hash hash; + crypto::cn_fast_hash(data.data(), data.size(), hash); + const cryptonote::account_keys &keys = m_account.get_keys(); + crypto::signature signature; + crypto::generate_signature(hash, get_multisig_signer_public_key(), keys.m_spend_secret_key, signature); + return MULTISIG_SIGNATURE_MAGIC + tools::base58::encode(std::string((const char *)&signature, sizeof(signature))); +} + +bool wallet2::verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const +{ + if (signature.size() < MULTISIG_SIGNATURE_MAGIC.size() || signature.substr(0, MULTISIG_SIGNATURE_MAGIC.size()) != MULTISIG_SIGNATURE_MAGIC) { + MERROR("Signature header check error"); + return false; + } + crypto::hash hash; + crypto::cn_fast_hash(data.data(), data.size(), hash); + std::string decoded; + if (!tools::base58::decode(signature.substr(MULTISIG_SIGNATURE_MAGIC.size()), decoded)) { + MERROR("Signature decoding error"); + return false; + } + crypto::signature s; + if (sizeof(s) != decoded.size()) { + MERROR("Signature decoding error"); + return false; + } + memcpy(&s, decoded.data(), sizeof(s)); + return crypto::check_signature(hash, public_key, s); +} //---------------------------------------------------------------------------------------------------- crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const { @@ -9764,6 +9876,23 @@ std::vector<tools::wallet2::transfer_details> wallet2::export_outputs() const return outs; } //---------------------------------------------------------------------------------------------------- +std::string wallet2::export_outputs_to_str() const +{ + std::vector<tools::wallet2::transfer_details> outs = export_outputs(); + + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << outs; + + std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + std::string header; + header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + std::string ciphertext = encrypt_with_view_secret_key(header + oss.str()); + return magic + ciphertext; +} +//---------------------------------------------------------------------------------------------------- size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs) { m_transfers.clear(); @@ -9796,6 +9925,67 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail return m_transfers.size(); } //---------------------------------------------------------------------------------------------------- +size_t wallet2::import_outputs_from_str(const std::string &outputs_st) +{ + std::string data = outputs_st; + const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC); + if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen)) + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad magic from outputs")); + } + + try + { + data = decrypt_with_view_secret_key(std::string(data, magiclen)); + } + catch (const std::exception &e) + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt outputs: ") + e.what()); + } + + const size_t headerlen = 2 * sizeof(crypto::public_key); + if (data.size() < headerlen) + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad data size for outputs")); + } + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Outputs from are for a different account")); + } + + size_t imported_outputs = 0; + try + { + std::string body(data, headerlen); + std::stringstream iss; + iss << body; + std::vector<tools::wallet2::transfer_details> outputs; + try + { + boost::archive::portable_binary_iarchive ar(iss); + ar >> outputs; + } + catch (...) + { + iss.str(""); + iss << body; + boost::archive::binary_iarchive ar(iss); + ar >> outputs; + } + + imported_outputs = import_outputs(outputs); + } + catch (const std::exception &e) + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs") + e.what()); + } + + return imported_outputs; +} +//---------------------------------------------------------------------------------------------------- crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const { crypto::public_key pkey; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 69b63876a..a10cef561 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -477,21 +477,23 @@ namespace tools bool two_random = false, bool create_address_file = false); /*! * \brief Creates a wallet from a public address and a spend/view secret key pair. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param viewkey view secret key - * \param spendkey spend secret key - * \param create_address_file Whether to create an address file + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param account_public_address The account's public address + * \param spendkey spend secret key + * \param viewkey view secret key + * \param create_address_file Whether to create an address file */ void generate(const std::string& wallet, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey, bool create_address_file = false); /*! * \brief Creates a watch only wallet from a public address and a view secret key. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param viewkey view secret key - * \param create_address_file Whether to create an address file + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param account_public_address The account's public address + * \param viewkey view secret key + * \param create_address_file Whether to create an address file */ void generate(const std::string& wallet, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, @@ -561,9 +563,9 @@ namespace tools void load(const std::string& wallet, const epee::wipeable_string& password); void store(); /*! - * \brief store_to - stores wallet to another file(s), deleting old ones - * \param path - path to the wallet file (keys and address filenames will be generated based on this filename) - * \param password - password to protect new wallet (TODO: probably better save the password in the wallet object?) + * \brief store_to Stores wallet to another file(s), deleting old ones + * \param path Path to the wallet file (keys and address filenames will be generated based on this filename) + * \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?) */ void store_to(const std::string &path, const epee::wipeable_string &password); @@ -653,6 +655,7 @@ namespace tools bool watch_only() const { return m_watch_only; } bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; bool has_multisig_partial_key_images() const; + bool has_unknown_key_images() const; bool get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; bool key_on_device() const { return m_key_on_device; } @@ -682,17 +685,23 @@ namespace tools void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) const; + std::string dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) const; std::string save_multisig_tx(multisig_tx_set txs); bool save_multisig_tx(const multisig_tx_set &txs, const std::string &filename); std::string save_multisig_tx(const std::vector<pending_tx>& ptx_vector); bool save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename); + multisig_tx_set make_multisig_tx_set(const std::vector<pending_tx>& ptx_vector) const; // load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL, bool export_raw = false); // sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI bool sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, bool export_raw = false); + bool sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &ptx, signed_tx_set &signed_txs); + std::string sign_tx_dump_to_str(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &ptx, signed_tx_set &signed_txes); // load unsigned_tx_set from file. bool load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) const; + bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const; bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL); + bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func); std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); // pass subaddr_indices by value on purpose std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); @@ -959,16 +968,34 @@ namespace tools /*! * \brief Set the label of the given tag. * \param tag Tag's name (which must be non-empty). - * \param label Tag's description. + * \param description Tag's description. */ void set_account_tag_description(const std::string& tag, const std::string& description); std::string sign(const std::string &data) const; bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; + /*! + * \brief sign_multisig_participant signs given message with the multisig public signer key + * \param data message to sign + * \throws if wallet is not multisig + * \return signature + */ + std::string sign_multisig_participant(const std::string& data) const; + /*! + * \brief verify_with_public_key verifies message was signed with given public key + * \param data message + * \param public_key public key to check signature + * \param signature signature of the message + * \return true if the signature is correct + */ + bool verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const; + // Import/Export wallet data std::vector<tools::wallet2::transfer_details> export_outputs() const; + std::string export_outputs_to_str() const; size_t import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs); + size_t import_outputs_from_str(const std::string &outputs_st); payment_container export_payments() const; void import_payments(const payment_container &payments); void import_payments_out(const std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>> &confirmed_payments); diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index a6ff63dd3..6311e7700 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -82,7 +82,7 @@ namespace wallet_args return i18n_translate(str, "wallet_args"); } - boost::optional<boost::program_options::variables_map> main( + std::pair<boost::optional<boost::program_options::variables_map>, bool> main( int argc, char** argv, const char* const usage, const char* const notice, @@ -127,6 +127,7 @@ namespace wallet_args po::options_description desc_all; desc_all.add(desc_general).add(desc_params); po::variables_map vm; + bool should_terminate = false; bool r = command_line::handle_error_helper(desc_all, [&]() { auto parser = po::command_line_parser(argc, argv).options(desc_all).positional(positional_options); @@ -139,12 +140,14 @@ namespace wallet_args "daemon to work correctly.") << ENDL; Print(print) << wallet_args::tr("Usage:") << ENDL << " " << usage; Print(print) << desc_all; - return false; + should_terminate = true; + return true; } else if (command_line::get_arg(vm, command_line::arg_version)) { Print(print) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; - return false; + should_terminate = true; + return true; } if(command_line::has_arg(vm, arg_config_file)) @@ -167,7 +170,10 @@ namespace wallet_args return true; }); if (!r) - return boost::none; + return {boost::none, true}; + + if (should_terminate) + return {std::move(vm), should_terminate}; std::string log_path; if (!command_line::is_arg_defaulted(vm, arg_log_file)) @@ -196,6 +202,6 @@ namespace wallet_args Print(print) << boost::format(wallet_args::tr("Logging to %s")) % log_path; - return {std::move(vm)}; + return {std::move(vm), should_terminate}; } } diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index af6685845..a1f251144 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -44,8 +44,11 @@ namespace wallet_args concurrency. Log file and concurrency arguments are handled, along with basic global init for the wallet process. - \return The list of parsed options, iff there are no errors.*/ - boost::optional<boost::program_options::variables_map> main( + \return + pair.first: The list of parsed options, iff there are no errors. + pair.second: Should the execution terminate succesfully without actually launching the application + */ + std::pair<boost::optional<boost::program_options::variables_map>, bool> main( int argc, char** argv, const char* const usage, const char* const notice, diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index a9d211532..7f7d33642 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -712,7 +712,7 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ template<typename Ts, typename Tu> bool wallet_rpc_server::fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, - bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, bool do_not_relay, + bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er) { for (const auto & ptx : ptx_vector) @@ -741,7 +741,16 @@ namespace tools } else { - if (!do_not_relay) + if (m_wallet->watch_only()){ + unsigned_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->dump_tx_to_str(ptx_vector)); + if (unsigned_txset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save unsigned tx set after creation"; + return false; + } + } + else if (!do_not_relay) m_wallet->commit_tx(ptx_vector); // populate response with tx hashes @@ -811,7 +820,7 @@ namespace tools return false; } - return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er); } catch (const std::exception& e) @@ -858,7 +867,7 @@ namespace tools std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) @@ -869,6 +878,141 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_sign_transfer(const wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->key_on_device()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "command not supported by HW wallet"; + return false; + } + if(m_wallet->watch_only()) + { + er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; + er.message = "command not supported by watch-only wallet"; + return false; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + tools::wallet2::unsigned_tx_set exported_txs; + if(!m_wallet->parse_unsigned_tx_from_str(blob, exported_txs)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA; + er.message = "cannot load unsigned_txset"; + return false; + } + + std::vector<tools::wallet2::pending_tx> ptxs; + try + { + tools::wallet2::signed_tx_set signed_txs; + std::string ciphertext = m_wallet->sign_tx_dump_to_str(exported_txs, ptxs, signed_txs); + if (ciphertext.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED; + er.message = "Failed to sign unsigned tx"; + return false; + } + + res.signed_txset = epee::string_tools::buff_to_hex_nodelimer(ciphertext); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED; + er.message = std::string("Failed to sign unsigned tx: ") + e.what(); + return false; + } + + for (auto &ptx: ptxs) + { + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + } + + if (req.export_raw) + { + for (auto &ptx: ptxs) + { + res.tx_raw_list.push_back(epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(ptx.tx))); + } + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->key_on_device()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "command not supported by HW wallet"; + return false; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + std::vector<tools::wallet2::pending_tx> ptx_vector; + try + { + bool r = m_wallet->parse_tx_from_str(blob, ptx_vector, NULL); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA; + er.message = "Failed to parse signed tx data."; + return false; + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA; + er.message = std::string("Failed to parse signed tx: ") + e.what(); + return false; + } + + try + { + for (auto &ptx: ptx_vector) + { + m_wallet->commit_tx(ptx); + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_SIGNED_SUBMISSION; + er.message = std::string("Failed to submit signed tx: ") + e.what(); + return false; + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); @@ -883,7 +1027,7 @@ namespace tools { std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) @@ -931,7 +1075,7 @@ namespace tools uint32_t priority = m_wallet->adjust_priority(req.priority); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) @@ -1007,7 +1151,7 @@ namespace tools return false; } - return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er); } catch (const std::exception& e) @@ -1974,6 +2118,72 @@ namespace tools return false; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_export_outputs(const wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->key_on_device()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "command not supported by HW wallet"; + return false; + } + + try + { + res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str()); + } + catch (const std::exception &e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_import_outputs(const wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->key_on_device()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "command not supported by HW wallet"; + return false; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.outputs_data_hex, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + try + { + res.num_imported = m_wallet->import_outputs_from_str(blob); + } + catch (const std::exception &e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); @@ -2876,6 +3086,12 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er) + { + res.version = WALLET_RPC_VERSION; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ } int main(int argc, char** argv) { @@ -2895,7 +3111,9 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_wallet_dir); command_line::add_arg(desc_params, arg_prompt_for_password); - const auto vm = wallet_args::main( + boost::optional<po::variables_map> vm; + bool should_terminate = false; + std::tie(vm, should_terminate) = wallet_args::main( argc, argv, "monero-wallet-rpc [--wallet-file=<file>|--generate-from-json=<file>|--wallet-dir=<directory>] [--rpc-bind-port=<port>]", tools::wallet_rpc_server::tr("This is the RPC monero wallet. It needs to connect to a monero\ndaemon to work correctly."), @@ -2909,6 +3127,10 @@ int main(int argc, char** argv) { { return 1; } + if (should_terminate) + { + return 0; + } std::unique_ptr<tools::wallet2> wal; try diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 2ec53cc80..9cb67c593 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -84,6 +84,8 @@ namespace tools MAP_JON_RPC_WE("getheight", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT) MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT) + MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER) + MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER) MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST) MAP_JON_RPC_WE("sweep_unmixable", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST) MAP_JON_RPC_WE("sweep_all", on_sweep_all, wallet_rpc::COMMAND_RPC_SWEEP_ALL) @@ -114,6 +116,8 @@ namespace tools MAP_JON_RPC_WE("get_transfer_by_txid", on_get_transfer_by_txid, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID) MAP_JON_RPC_WE("sign", on_sign, wallet_rpc::COMMAND_RPC_SIGN) MAP_JON_RPC_WE("verify", on_verify, wallet_rpc::COMMAND_RPC_VERIFY) + MAP_JON_RPC_WE("export_outputs", on_export_outputs, wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS) + MAP_JON_RPC_WE("import_outputs", on_import_outputs, wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS) MAP_JON_RPC_WE("export_key_images", on_export_key_images, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES) MAP_JON_RPC_WE("import_key_images", on_import_key_images, wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES) MAP_JON_RPC_WE("make_uri", on_make_uri, wallet_rpc::COMMAND_RPC_MAKE_URI) @@ -135,6 +139,7 @@ namespace tools MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG) MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG) MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG) + MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION) END_JSON_RPC_MAP() END_URI_MAP2() @@ -154,6 +159,8 @@ namespace tools bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er); bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er); bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er); + bool on_sign_transfer(const wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::response& res, epee::json_rpc::error& er); + bool on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er); bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er); bool on_sweep_all(const wallet_rpc::COMMAND_RPC_SWEEP_ALL::request& req, wallet_rpc::COMMAND_RPC_SWEEP_ALL::response& res, epee::json_rpc::error& er); bool on_sweep_single(const wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::request& req, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::response& res, epee::json_rpc::error& er); @@ -182,6 +189,8 @@ namespace tools bool on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er); bool on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er); bool on_verify(const wallet_rpc::COMMAND_RPC_VERIFY::request& req, wallet_rpc::COMMAND_RPC_VERIFY::response& res, epee::json_rpc::error& er); + bool on_export_outputs(const wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::response& res, epee::json_rpc::error& er); + bool on_import_outputs(const wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::response& res, epee::json_rpc::error& er); bool on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er); bool on_import_key_images(const wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er); bool on_make_uri(const wallet_rpc::COMMAND_RPC_MAKE_URI::request& req, wallet_rpc::COMMAND_RPC_MAKE_URI::response& res, epee::json_rpc::error& er); @@ -203,6 +212,7 @@ namespace tools bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er); bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er); bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er); //json rpc v2 bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er); @@ -217,7 +227,7 @@ namespace tools template<typename Ts, typename Tu> bool fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, - bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, bool do_not_relay, + bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er); wallet2 *m_wallet; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index a0f43c9b9..96e135f01 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -39,6 +39,17 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" +// When making *any* change here, bump minor +// If the change is incompatible, then bump major and set minor to 0 +// This ensures WALLET_RPC_VERSION always increases, that every change +// has its own version, and that clients can just test major to see +// whether they can talk to a given wallet without having to know in +// advance which version they will stop working with +// Don't go over 32767 for any of these +#define WALLET_RPC_VERSION_MAJOR 1 +#define WALLET_RPC_VERSION_MINOR 0 +#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) +#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools { namespace wallet_rpc @@ -419,6 +430,7 @@ namespace wallet_rpc std::string tx_blob; std::string tx_metadata; std::string multisig_txset; + std::string unsigned_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -429,6 +441,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) + KV_SERIALIZE(unsigned_txset) END_KV_SERIALIZE_MAP() }; }; @@ -484,6 +497,7 @@ namespace wallet_rpc std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; std::string multisig_txset; + std::string unsigned_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -493,6 +507,55 @@ namespace wallet_rpc KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) + KV_SERIALIZE(unsigned_txset) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SIGN_TRANSFER + { + struct request + { + std::string unsigned_txset; + bool export_raw; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE_OPT(export_raw, false) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string signed_txset; + std::list<std::string> tx_hash_list; + std::list<std::string> tx_raw_list; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(signed_txset) + KV_SERIALIZE(tx_hash_list) + KV_SERIALIZE(tx_raw_list) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SUBMIT_TRANSFER + { + struct request + { + std::string tx_data_hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_data_hex) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::list<std::string> tx_hash_list; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_hash_list) END_KV_SERIALIZE_MAP() }; }; @@ -532,6 +595,7 @@ namespace wallet_rpc std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; std::string multisig_txset; + std::string unsigned_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -541,6 +605,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) + KV_SERIALIZE(unsigned_txset) END_KV_SERIALIZE_MAP() }; }; @@ -598,6 +663,7 @@ namespace wallet_rpc std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; std::string multisig_txset; + std::string unsigned_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -607,6 +673,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) + KV_SERIALIZE(unsigned_txset) END_KV_SERIALIZE_MAP() }; }; @@ -651,6 +718,7 @@ namespace wallet_rpc std::string tx_blob; std::string tx_metadata; std::string multisig_txset; + std::string unsigned_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -660,6 +728,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) + KV_SERIALIZE(unsigned_txset) END_KV_SERIALIZE_MAP() }; }; @@ -1364,6 +1433,45 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_EXPORT_OUTPUTS + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string outputs_data_hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(outputs_data_hex); + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_IMPORT_OUTPUTS + { + struct request + { + std::string outputs_data_hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(outputs_data_hex); + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t num_imported; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(num_imported); + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_EXPORT_KEY_IMAGES { struct request @@ -1848,5 +1956,23 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_VERSION + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint32_t version; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(version) + END_KV_SERIALIZE_MAP() + }; + }; + } } diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index d47467940..f127ae240 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -69,3 +69,7 @@ #define WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION -36 #define WALLET_RPC_ERROR_CODE_NOT_ENOUGH_UNLOCKED_MONEY -37 #define WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION -38 +#define WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA -39 +#define WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA -40 +#define WALLET_RPC_ERROR_CODE_SIGNED_SUBMISSION -41 +#define WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED -42 |