diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/CMakeLists.txt | 63 | ||||
-rw-r--r-- | src/wallet/api/transaction_history.cpp | 6 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 186 | ||||
-rw-r--r-- | src/wallet/api/wallet.h | 5 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.cpp | 153 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.h | 1 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 1022 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 140 | ||||
-rw-r--r-- | src/wallet/wallet2_api.h | 20 | ||||
-rw-r--r-- | src/wallet/wallet_args.cpp | 33 | ||||
-rw-r--r-- | src/wallet/wallet_args.h | 1 | ||||
-rw-r--r-- | src/wallet/wallet_errors.h | 6 | ||||
-rw-r--r--[-rwxr-xr-x] | src/wallet/wallet_rpc_server.cpp | 452 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.h | 19 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 252 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_error_codes.h | 4 |
16 files changed, 1872 insertions, 491 deletions
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 24399790c..e5c79a447 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -79,7 +79,6 @@ target_link_libraries(wallet common cryptonote_core mnemonics - p2p ${Boost_CHRONO_LIBRARY} ${Boost_SERIALIZATION_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} @@ -89,42 +88,40 @@ target_link_libraries(wallet PRIVATE ${EXTRA_LIBRARIES}) -if (NOT BUILD_GUI_DEPS) - set(wallet_rpc_sources - wallet_rpc_server.cpp) +set(wallet_rpc_sources + wallet_rpc_server.cpp) - set(wallet_rpc_headers) +set(wallet_rpc_headers) - set(wallet_rpc_private_headers - wallet_rpc_server.h) +set(wallet_rpc_private_headers + wallet_rpc_server.h) - monero_private_headers(wallet_rpc_server - ${wallet_rpc_private_headers}) - monero_add_executable(wallet_rpc_server - ${wallet_rpc_sources} - ${wallet_rpc_headers} - ${wallet_rpc_private_headers}) +monero_private_headers(wallet_rpc_server + ${wallet_rpc_private_headers}) +monero_add_executable(wallet_rpc_server + ${wallet_rpc_sources} + ${wallet_rpc_headers} + ${wallet_rpc_private_headers}) - target_link_libraries(wallet_rpc_server - PRIVATE - wallet - epee - rpc - cryptonote_core - cncrypto - common - version - ${Boost_CHRONO_LIBRARY} - ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_FILESYSTEM_LIBRARY} - ${Boost_THREAD_LIBRARY} - ${CMAKE_THREAD_LIBS_INIT} - ${EXTRA_LIBRARIES}) - set_property(TARGET wallet_rpc_server - PROPERTY - OUTPUT_NAME "monero-wallet-rpc") - install(TARGETS wallet_rpc_server DESTINATION bin) -endif() +target_link_libraries(wallet_rpc_server + PRIVATE + wallet + epee + rpc + cryptonote_core + cncrypto + common + version + ${Boost_CHRONO_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +set_property(TARGET wallet_rpc_server + PROPERTY + OUTPUT_NAME "monero-wallet-rpc") +install(TARGETS wallet_rpc_server DESTINATION bin) # build and install libwallet_merged only if we building for GUI diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 59eca3dd7..8a8243047 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -217,10 +217,10 @@ void TransactionHistoryImpl::refresh() // unconfirmed payments (tx pool) - std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> upayments; + std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> upayments; m_wallet->m_wallet->get_unconfirmed_payments(upayments); - for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { - const tools::wallet2::payment_details &pd = i->second; + for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { + const tools::wallet2::payment_details &pd = i->second.m_pd; std::string payment_id = string_tools::pod_to_hex(i->first); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 8e747d16b..fd0b65866 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1385,24 +1385,194 @@ std::string WalletImpl::getUserNote(const std::string &txid) const return m_wallet->get_tx_note(htxid); } -std::string WalletImpl::getTxKey(const std::string &txid) const +std::string WalletImpl::getTxKey(const std::string &txid_str) const { - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - return ""; + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return ""; } - const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - if (m_wallet->get_tx_key(htxid, tx_key, additional_tx_keys)) + if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { - return epee::string_tools::pod_to_hex(tx_key); + m_status = Status_Ok; + std::ostringstream oss; + oss << epee::string_tools::pod_to_hex(tx_key); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); + return oss.str(); } else { - return ""; + m_status = Status_Error; + m_errorString = tr("no tx keys found for this txid"); + return ""; + } +} + +bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, const std::string &address_str, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return false; + } + + crypto::secret_key tx_key; + 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"); + return false; + } + tx_key_str = tx_key_str.substr(64); + while (!tx_key_str.empty()) + { + 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"); + return false; + } + tx_key_str = tx_key_str.substr(64); + } + + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address_str)) + { + m_status = Status_Error; + m_errorString = 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; + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; + } +} + +std::string WalletImpl::getTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message) 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"); + return ""; + } + + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address_str)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse address"); + return ""; + } + + try + { + m_status = Status_Ok; + 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(); + return ""; + } +} + +bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return false; + } + + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address_str)) + { + m_status = Status_Error; + m_errorString = 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; + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; + } +} + +std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::string &message) 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"); + return ""; + } + + try + { + m_status = Status_Ok; + return m_wallet->get_spend_proof(txid, message); + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return ""; + } +} + +bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string &message, const std::string &signature, bool &good) const { + good = false; + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return false; + } + + try + { + m_status = Status_Ok; + good = m_wallet->check_spend_proof(txid, message, signature); + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; } } diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 051eda3ba..8ceabc843 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -137,6 +137,11 @@ public: virtual bool setUserNote(const std::string &txid, const std::string ¬e); virtual std::string getUserNote(const std::string &txid) const; virtual std::string getTxKey(const std::string &txid) const; + virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); + virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message) const; + virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations); + virtual std::string getSpendProof(const std::string &txid, const std::string &message) const; + virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) 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 void startRefresh(); diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index a64766c84..ee69ec028 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -189,155 +189,6 @@ bool WalletManagerImpl::connected(uint32_t *version) const return true; } -bool WalletManagerImpl::checkPayment(const std::string &address_text, const std::string &txid_text, const std::string &txkey_text, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const -{ - error = ""; - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(txid_text, txid_data) || txid_data.size() != sizeof(crypto::hash)) - { - error = tr("failed to parse txid"); - return false; - } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); - - if (txkey_text.size() < 64 || txkey_text.size() % 64) - { - error = tr("failed to parse tx key"); - return false; - } - crypto::secret_key tx_key; - cryptonote::blobdata tx_key_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(txkey_text, tx_key_data) || tx_key_data.size() != sizeof(crypto::hash)) - { - error = tr("failed to parse tx key"); - return false; - } - tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); - - bool testnet = address_text[0] != '4'; - cryptonote::address_parse_info info; - if(!cryptonote::get_account_address_from_str(info, testnet, address_text)) - { - error = tr("failed to parse address"); - return false; - } - - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!connect_and_invoke(m_daemonAddress, "/gettransactions", req, res) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) - { - error = tr("failed to get transaction from daemon"); - return false; - } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) - { - error = tr("failed to parse transaction from daemon"); - return false; - } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) - { - error = tr("failed to validate transaction from daemon"); - return false; - } - if (tx_hash != txid) - { - error = tr("failed to get the right transaction from daemon"); - return false; - } - - crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation)) - { - error = tr("failed to generate key derivation from supplied parameters"); - return false; - } - - received = 0; - try { - for (size_t n = 0; n < tx.vout.size(); ++n) - { - if (typeid(cryptonote::txout_to_key) != tx.vout[n].target.type()) - continue; - const cryptonote::txout_to_key tx_out_to_key = boost::get<cryptonote::txout_to_key>(tx.vout[n].target); - crypto::public_key pubkey; - derive_public_key(derivation, n, info.address.m_spend_public_key, pubkey); - if (pubkey == tx_out_to_key.key) - { - uint64_t amount; - if (tx.version == 1) - { - amount = tx.vout[n].amount; - } - else - { - try - { - rct::key Ctmp; - //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); - crypto::key_derivation derivation; - bool r = crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation); - if (!r) - { - LOG_ERROR("Failed to generate key derivation to decode rct output " << n); - amount = 0; - } - else - { - crypto::secret_key scalar1; - crypto::derivation_to_scalar(derivation, n, scalar1); - rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); - rct::key C = tx.rct_signatures.outPk[n].mask; - rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); - if (rct::equalKeys(C, Ctmp)) - amount = rct::h2d(ecdh_info.amount); - else - amount = 0; - } - } - catch (...) { amount = 0; } - } - received += amount; - } - } - } - catch(const std::exception &e) - { - LOG_ERROR("error: " << e.what()); - error = std::string(tr("error: ")) + e.what(); - return false; - } - - if (received > 0) - { - LOG_PRINT_L1(get_account_address_as_str(testnet, info.is_subaddress, info.address) << " " << tr("received") << " " << cryptonote::print_money(received) << " " << tr("in txid") << " " << txid); - } - else - { - LOG_PRINT_L1(get_account_address_as_str(testnet, info.is_subaddress, info.address) << " " << tr("received nothing in txid") << " " << txid); - } - if (res.txs.front().in_pool) - { - height = 0; - } - else - { - height = res.txs.front().block_height; - } - - return true; -} - uint64_t WalletManagerImpl::blockchainHeight() const { cryptonote::COMMAND_RPC_GET_INFO::request ireq; @@ -434,12 +285,14 @@ std::string WalletManagerImpl::resolveOpenAlias(const std::string &address, bool return addresses.front(); } -std::tuple<bool, std::string, std::string, std::string, std::string> WalletManager::checkUpdates(const std::string &software, const std::string &subdir) +std::tuple<bool, std::string, std::string, std::string, std::string> WalletManager::checkUpdates(const std::string &software, std::string subdir) { #ifdef BUILD_TAG static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); #else static const char buildtag[] = "source"; + // Override the subdir string when built from source + subdir = "source"; #endif std::string version, hash; diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 8455f0f16..5772eda05 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -55,7 +55,6 @@ public: std::string errorString() const; void setDaemonAddress(const std::string &address); bool connected(uint32_t *version = NULL) const; - bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const; uint64_t blockchainHeight() const; uint64_t blockchainTargetHeight() const; uint64_t networkDifficulty() const; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index e9af93c05..c995c4333 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -59,7 +59,6 @@ using namespace epee; #include "rapidjson/stringbuffer.h" #include "common/json_util.h" #include "common/base58.h" -#include "common/scoped_message_writer.h" #include "ringct/rctSigs.h" extern "C" @@ -67,6 +66,8 @@ extern "C" #include "crypto/keccak.h" #include "crypto/crypto-ops.h" } +using namespace std; +using namespace crypto; using namespace cryptonote; #undef MONERO_DEFAULT_LOG_CATEGORY @@ -135,7 +136,7 @@ uint64_t calculate_fee(uint64_t fee_per_kb, const cryptonote::blobdata &blob, ui return calculate_fee(fee_per_kb, blob.size(), fee_multiplier); } -std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, const options& opts) +std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const bool testnet = command_line::get_arg(vm, opts.testnet); const bool restricted = command_line::get_arg(vm, opts.restricted); @@ -144,17 +145,16 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl auto daemon_host = command_line::get_arg(vm, opts.daemon_host); auto daemon_port = command_line::get_arg(vm, opts.daemon_port); - if (!daemon_address.empty() && !daemon_host.empty() && 0 != daemon_port) - { - tools::fail_msg_writer() << tools::wallet2::tr("can't specify daemon host or port more than once"); - return nullptr; - } + THROW_WALLET_EXCEPTION_IF(!daemon_address.empty() && !daemon_host.empty() && 0 != daemon_port, + tools::error::wallet_internal_error, tools::wallet2::tr("can't specify daemon host or port more than once")); boost::optional<epee::net_utils::http::login> login{}; if (command_line::has_arg(vm, opts.daemon_login)) { auto parsed = tools::login::parse( - command_line::get_arg(vm, opts.daemon_login), false, "Daemon client password" + command_line::get_arg(vm, opts.daemon_login), false, [password_prompter](bool verify) { + return password_prompter("Daemon client password", verify); + } ); if (!parsed) return nullptr; @@ -178,12 +178,11 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl return wallet; } -boost::optional<tools::password_container> get_password(const boost::program_options::variables_map& vm, const options& opts, const bool verify) +boost::optional<tools::password_container> get_password(const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char*, bool)> &password_prompter, const bool verify) { if (command_line::has_arg(vm, opts.password) && command_line::has_arg(vm, opts.password_file)) { - tools::fail_msg_writer() << tools::wallet2::tr("can't specify more than one of --password and --password-file"); - return boost::none; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("can't specify more than one of --password and --password-file")); } if (command_line::has_arg(vm, opts.password)) @@ -196,21 +195,19 @@ boost::optional<tools::password_container> get_password(const boost::program_opt std::string password; bool r = epee::file_io_utils::load_file_to_string(command_line::get_arg(vm, opts.password_file), password); - if (!r) - { - tools::fail_msg_writer() << tools::wallet2::tr("the password file specified could not be read"); - return boost::none; - } + THROW_WALLET_EXCEPTION_IF(!r, tools::error::wallet_internal_error, tools::wallet2::tr("the password file specified could not be read")); // Remove line breaks the user might have inserted boost::trim_right_if(password, boost::is_any_of("\r\n")); return {tools::password_container{std::move(password)}}; } - return tools::wallet2::password_prompt(verify); + THROW_WALLET_EXCEPTION_IF(!password_prompter, tools::error::wallet_internal_error, tools::wallet2::tr("no password specified; use --prompt-for-password to prompt for a password")); + + return password_prompter(verify ? tr("Enter new wallet password") : tr("Wallet password"), verify); } -std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, const options& opts) +std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const bool testnet = command_line::get_arg(vm, opts.testnet); @@ -221,22 +218,20 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const auto do_generate = [&]() -> bool { std::string buf; if (!epee::file_io_utils::load_file_to_string(json_file, buf)) { - tools::fail_msg_writer() << tools::wallet2::tr("Failed to load file ") << json_file; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, std::string(tools::wallet2::tr("Failed to load file ")) + json_file); return false; } rapidjson::Document json; if (json.Parse(buf.c_str()).HasParseError()) { - tools::fail_msg_writer() << tools::wallet2::tr("Failed to parse JSON"); + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("Failed to parse JSON")); return false; } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, version, unsigned, Uint, true, 0); const int current_version = 1; - if (field_version > current_version) { - tools::fail_msg_writer() << boost::format(tools::wallet2::tr("Version %u too new, we can only grok up to %u")) % field_version % current_version; - return false; - } + THROW_WALLET_EXCEPTION_IF(field_version > current_version, tools::error::wallet_internal_error, + ((boost::format(tools::wallet2::tr("Version %u too new, we can only grok up to %u")) % field_version % current_version)).str()); GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, filename, std::string, String, true, std::string()); @@ -252,14 +247,12 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, cryptonote::blobdata viewkey_data; if(!epee::string_tools::parse_hexstr_to_binbuff(field_viewkey, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to parse view key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to parse view key secret key")); } viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); crypto::public_key pkey; if (!crypto::secret_key_to_public_key(viewkey, pkey)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify view key secret key")); } } @@ -270,14 +263,12 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, cryptonote::blobdata spendkey_data; if(!epee::string_tools::parse_hexstr_to_binbuff(field_spendkey, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to parse spend key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to parse spend key secret key")); } spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data()); crypto::public_key pkey; if (!crypto::secret_key_to_public_key(spendkey, pkey)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify spend key secret key")); } } @@ -289,8 +280,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, { if (!crypto::ElectrumWords::words_to_bytes(field_seed, recovery_key, old_language)) { - tools::fail_msg_writer() << tools::wallet2::tr("Electrum-style word list failed verification"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("Electrum-style word list failed verification")); } restore_deterministic_wallet = true; @@ -305,15 +295,13 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string()); // compatibility checks - if (!field_seed_found && !field_viewkey_found) + if (!field_seed_found && !field_viewkey_found && !field_spendkey_found) { - tools::fail_msg_writer() << tools::wallet2::tr("At least one of Electrum-style word list and private view key must be specified"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("At least one of Electrum-style word list and private view key and private spend key must be specified")); } if (field_seed_found && (field_viewkey_found || field_spendkey_found)) { - tools::fail_msg_writer() << tools::wallet2::tr("Both Electrum-style word list and private key(s) specified"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("Both Electrum-style word list and private key(s) specified")); } // if an address was given, we check keys against it, and deduce the spend @@ -323,43 +311,36 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, cryptonote::address_parse_info info; if(!get_account_address_from_str(info, testnet, field_address)) { - tools::fail_msg_writer() << tools::wallet2::tr("invalid address"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("invalid address")); } if (field_viewkey_found) { crypto::public_key pkey; if (!crypto::secret_key_to_public_key(viewkey, pkey)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify view key secret key")); } if (info.address.m_view_public_key != pkey) { - tools::fail_msg_writer() << tools::wallet2::tr("view key does not match standard address"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("view key does not match standard address")); } } if (field_spendkey_found) { crypto::public_key pkey; if (!crypto::secret_key_to_public_key(spendkey, pkey)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify spend key secret key")); } if (info.address.m_spend_public_key != pkey) { - tools::fail_msg_writer() << tools::wallet2::tr("spend key does not match standard address"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("spend key does not match standard address")); } } } const bool deprecated_wallet = restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) || crypto::ElectrumWords::get_is_old_style_seed(field_seed)); - if (deprecated_wallet) { - tools::fail_msg_writer() << tools::wallet2::tr("Cannot create deprecated wallets from JSON"); - return false; - } + THROW_WALLET_EXCEPTION_IF(deprecated_wallet, tools::error::wallet_internal_error, + tools::wallet2::tr("Cannot create deprecated wallets from JSON")); - wallet.reset(make_basic(vm, opts).release()); + wallet.reset(make_basic(vm, opts, password_prompter).release()); wallet->set_refresh_from_block_height(field_scan_from_height); try @@ -368,12 +349,15 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, { wallet->generate(field_filename, field_password, recovery_key, recover, false); } + else if (field_viewkey.empty() && !field_spendkey.empty()) + { + wallet->generate(field_filename, field_password, spendkey, recover, false); + } else { cryptonote::account_public_address address; if (!crypto::secret_key_to_public_key(viewkey, address.m_view_public_key)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify view key secret key")); } if (field_spendkey.empty()) @@ -385,18 +369,20 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, cryptonote::address_parse_info info; if(!get_account_address_from_str(info, testnet, field_address)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to parse address: ") << field_address; - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, std::string(tools::wallet2::tr("failed to parse address: ")) + field_address); } address.m_spend_public_key = info.address.m_spend_public_key; } + else + { + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("Address must be specified in order to create watch-only wallet")); + } wallet->generate(field_filename, field_password, address, viewkey); } else { if (!crypto::secret_key_to_public_key(spendkey, address.m_spend_public_key)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify spend key secret key")); } wallet->generate(field_filename, field_password, address, spendkey, viewkey); } @@ -404,8 +390,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, } catch (const std::exception& e) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to generate new wallet: ") << e.what(); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, std::string(tools::wallet2::tr("failed to generate new wallet: ")) + e.what()); } return true; }; @@ -444,6 +429,34 @@ std::string strjoin(const std::vector<size_t> &V, const char *sep) return ss.str(); } +static void emplace_or_replace(std::unordered_multimap<crypto::hash, tools::wallet2::pool_payment_details> &container, + const crypto::hash &key, const tools::wallet2::pool_payment_details &pd) +{ + auto range = container.equal_range(key); + for (auto i = range.first; i != range.second; ++i) + { + if (i->second.m_pd.m_tx_hash == pd.m_pd.m_tx_hash) + { + i->second = pd; + return; + } + } + container.emplace(key, pd); +} + +void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_t N) +{ + std::list<crypto::hash>::iterator right; + // drop early N off, skipping the genesis block + if (short_chain_history.size() > N) { + right = short_chain_history.end(); + std::advance(right,-1); + std::list<crypto::hash>::iterator left = right; + std::advance(left, -N); + short_chain_history.erase(left, right); + } +} + } //namespace namespace tools @@ -472,34 +485,22 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.restricted); } -boost::optional<password_container> wallet2::password_prompt(const bool new_password) -{ - auto pwd_container = tools::password_container::prompt( - new_password, (new_password ? tr("Enter new wallet password") : tr("Wallet password")) - ); - if (!pwd_container) - { - tools::fail_msg_writer() << tr("failed to read wallet password"); - } - return pwd_container; -} - -std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file) +std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; - return generate_from_json(json_file, vm, opts); + return generate_from_json(json_file, vm, opts, password_prompter); } std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( - const boost::program_options::variables_map& vm, const std::string& wallet_file) + const boost::program_options::variables_map& vm, const std::string& wallet_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; - auto pwd = get_password(vm, opts, false); + auto pwd = get_password(vm, opts, password_prompter, false); if (!pwd) { return {nullptr, password_container{}}; } - auto wallet = make_basic(vm, opts); + auto wallet = make_basic(vm, opts, password_prompter); if (wallet) { wallet->load(wallet_file, pwd->password()); @@ -507,21 +508,21 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( return {std::move(wallet), std::move(*pwd)}; } -std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const boost::program_options::variables_map& vm) +std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter) { const options opts{}; - auto pwd = get_password(vm, opts, true); + auto pwd = get_password(vm, opts, password_prompter, true); if (!pwd) { return {nullptr, password_container{}}; } - return {make_basic(vm, opts), std::move(*pwd)}; + return {make_basic(vm, opts, password_prompter), std::move(*pwd)}; } -std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::variables_map& vm) +std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; - return make_basic(vm, opts); + return make_basic(vm, opts, password_prompter); } //---------------------------------------------------------------------------------------------------- @@ -793,7 +794,7 @@ void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote ++num_vouts_received; } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool) +void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen) { // In this function, tx (probably) only contains the base information // (that is, the prunable stuff may or may not be included) @@ -831,7 +832,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote int num_vouts_received = 0; tx_pub_key = pub_key_field.pub_key; - bool r = true; tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter; const cryptonote::account_keys& keys = m_account.get_keys(); @@ -854,46 +854,33 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote else if (miner_tx && m_refresh_type == RefreshOptimizeCoinbase) { check_acc_out_precomp(tx.vout[0], derivation, additional_derivations, 0, tx_scan_info[0]); - if (tx_scan_info[0].error) - { - r = false; - } - else + THROW_WALLET_EXCEPTION_IF(tx_scan_info[0].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); + + // this assumes that the miner tx pays a single address + if (tx_scan_info[0].received) { - // this assumes that the miner tx pays a single address - if (tx_scan_info[0].received) + // process the other outs from that tx + // the first one was already checked + for (size_t i = 1; i < tx.vout.size(); ++i) { - scan_output(keys, tx, tx_pub_key, 0, tx_scan_info[0], num_vouts_received, tx_money_got_in_outs, outs); - - // process the other outs from that tx - // the first one was already checked - for (size_t i = 1; i < tx.vout.size(); ++i) - { - tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, - std::ref(tx_scan_info[i]))); - } - waiter.wait(); + tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, + std::ref(tx_scan_info[i]))); + } + waiter.wait(); - for (size_t i = 1; i < tx.vout.size(); ++i) + // then scan all outputs from 0 + for (size_t i = 0; i < tx.vout.size(); ++i) + { + THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); + if (tx_scan_info[i].received) { - if (tx_scan_info[i].error) - { - r = false; - break; - } - if (tx_scan_info[i].received) - { - scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); - } + scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } } } else if (tx.vout.size() > 1 && tools::threadpool::getInstance().get_max_concurrency() > 1) { - tools::threadpool& tpool = tools::threadpool::getInstance(); - tools::threadpool::waiter waiter; - for (size_t i = 0; i < tx.vout.size(); ++i) { tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, @@ -902,11 +889,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote waiter.wait(); for (size_t i = 0; i < tx.vout.size(); ++i) { - if (tx_scan_info[i].error) - { - r = false; - break; - } + THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if (tx_scan_info[i].received) { scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); @@ -918,18 +901,13 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote for (size_t i = 0; i < tx.vout.size(); ++i) { check_acc_out_precomp(tx.vout[i], derivation, additional_derivations, i, tx_scan_info[i]); - if (tx_scan_info[i].error) - { - r = false; - break; - } + THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if (tx_scan_info[i].received) { scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } } - THROW_WALLET_EXCEPTION_IF(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if(!outs.empty() && num_vouts_received > 0) { @@ -1163,7 +1141,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote payment.m_timestamp = ts; payment.m_subaddr_index = i.first; if (pool) { - m_unconfirmed_payments.emplace(payment_id, payment); + emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, double_spend_seen}); if (0 != m_callback) m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount, payment.m_subaddr_index); } @@ -1241,7 +1219,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height) { TIME_MEASURE_START(miner_tx_handle_time); - process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false); + process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false, false); TIME_MEASURE_FINISH(miner_tx_handle_time); TIME_MEASURE_START(txs_handle_time); @@ -1252,7 +1230,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry cryptonote::transaction tx; bool r = parse_and_validate_tx_base_from_blob(txblob, tx); THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob); - process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false); + process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false, false); ++idx; } TIME_MEASURE_FINISH(txs_handle_time); @@ -1497,6 +1475,8 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei try { + drop_from_short_history(short_chain_history, 3); + // prepend the last 3 blocks, should be enough to guard against a block or two's reorg cryptonote::block bl; std::list<cryptonote::block_complete_entry>::const_reverse_iterator i = prev_blocks.rbegin(); @@ -1520,10 +1500,10 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes) { // remove pool txes to us that aren't in the pool anymore - std::unordered_multimap<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin(); + std::unordered_multimap<crypto::hash, wallet2::pool_payment_details>::iterator uit = m_unconfirmed_payments.begin(); while (uit != m_unconfirmed_payments.end()) { - const crypto::hash &txid = uit->second.m_tx_hash; + const crypto::hash &txid = uit->second.m_pd.m_tx_hash; bool found = false; for (const auto &it2: tx_hashes) { @@ -1626,23 +1606,27 @@ void wallet2::update_pool_state(bool refreshed) MDEBUG("update_pool_state done second loop"); // gather txids of new pool txes to us - std::vector<crypto::hash> txids; + std::vector<std::pair<crypto::hash, bool>> txids; for (const auto &txid: res.tx_hashes) { - if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end()) - { - LOG_PRINT_L2("Already seen " << txid << ", skipped"); - continue; - } bool txid_found_in_up = false; for (const auto &up: m_unconfirmed_payments) { - if (up.second.m_tx_hash == txid) + if (up.second.m_pd.m_tx_hash == txid) { txid_found_in_up = true; break; } } + if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end()) + { + // if it's for us, we want to keep track of whether we saw a double spend, so don't bail out + if (!txid_found_in_up) + { + LOG_PRINT_L2("Already seen " << txid << ", and not for us, skipped"); + continue; + } + } if (!txid_found_in_up) { LOG_PRINT_L1("Found new pool tx: " << txid); @@ -1670,7 +1654,7 @@ void wallet2::update_pool_state(bool refreshed) if (!found) { // not one of those we sent ourselves - txids.push_back(txid); + txids.push_back({txid, false}); } else { @@ -1680,6 +1664,7 @@ void wallet2::update_pool_state(bool refreshed) else { LOG_PRINT_L1("Already saw that one, it's for us"); + txids.push_back({txid, true}); } } @@ -1688,8 +1673,8 @@ void wallet2::update_pool_state(bool refreshed) { cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - for (const auto &txid: txids) - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + for (const auto &p: txids) + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first)); MDEBUG("asking for " << txids.size() << " transactions"); req.decode_as_json = false; m_daemon_rpc_mutex.lock(); @@ -1711,10 +1696,11 @@ void wallet2::update_pool_state(bool refreshed) { if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash)) { - const std::vector<crypto::hash>::const_iterator i = std::find(txids.begin(), txids.end(), tx_hash); + const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(), + [tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; }); if (i != txids.end()) { - process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true); + process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true, tx_entry.double_spend_seen); m_scanned_pool_txs[0].insert(tx_hash); if (m_scanned_pool_txs[0].size() > 5000) { @@ -1759,24 +1745,29 @@ void wallet2::update_pool_state(bool refreshed) void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history) { std::list<crypto::hash> hashes; - size_t current_index = m_blockchain.size(); + const uint64_t checkpoint_height = m_checkpoints.get_max_height(); + if (stop_height > checkpoint_height && m_blockchain.size()-1 < checkpoint_height) + { + // we will drop all these, so don't bother getting them + uint64_t missing_blocks = m_checkpoints.get_max_height() - m_blockchain.size(); + while (missing_blocks-- > 0) + m_blockchain.push_back(crypto::null_hash); // maybe a bit suboptimal, but deque won't do huge reallocs like vector + m_blockchain.push_back(m_checkpoints.get_points().at(checkpoint_height)); + m_local_bc_height = m_blockchain.size(); + short_chain_history.clear(); + get_short_chain_history(short_chain_history); + } + + size_t current_index = m_blockchain.size(); while(m_run.load(std::memory_order_relaxed) && current_index < stop_height) { pull_hashes(0, blocks_start_height, short_chain_history, hashes); if (hashes.size() <= 3) return; if (hashes.size() + current_index < stop_height) { - std::list<crypto::hash>::iterator right; - // drop early 3 off, skipping the genesis block - if (short_chain_history.size() > 3) { - right = short_chain_history.end(); - std::advance(right,-1); - std::list<crypto::hash>::iterator left = right; - std::advance(left, -3); - short_chain_history.erase(left, right); - } - right = hashes.end(); + drop_from_short_history(short_chain_history, 3); + std::list<crypto::hash>::iterator right = hashes.end(); // prepend 3 more for (int i = 0; i<3; i++) { right--; @@ -3073,11 +3064,11 @@ void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wall } } //---------------------------------------------------------------------------------------------------- -void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const +void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const { for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) { - if ((!subaddr_account || *subaddr_account == i->second.m_subaddr_index.major) && - (subaddr_indices.empty() || subaddr_indices.count(i->second.m_subaddr_index.minor) == 1)) + if ((!subaddr_account || *subaddr_account == i->second.m_pd.m_subaddr_index.major) && + (subaddr_indices.empty() || subaddr_indices.count(i->second.m_pd.m_subaddr_index.minor) == 1)) unconfirmed_payments.push_back(*i); } } @@ -3272,7 +3263,7 @@ float wallet2::get_output_relatedness(const transfer_details &td0, const transfe return 0.0f; } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers, bool smallest) const +size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::vector<size_t>& selected_transfers, bool smallest) const { std::vector<size_t> candidates; float best_relatedness = 1.0f; @@ -3280,7 +3271,7 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve { const transfer_details &candidate = transfers[unused_indices[n]]; float relatedness = 0.0f; - for (std::list<size_t>::const_iterator i = selected_transfers.begin(); i != selected_transfers.end(); ++i) + for (std::vector<size_t>::const_iterator i = selected_transfers.begin(); i != selected_transfers.end(); ++i) { float r = get_output_relatedness(candidate, transfers[*i]); if (r > relatedness) @@ -3321,7 +3312,7 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve return pop_index (unused_indices, candidates[idx]); } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers, bool smallest) const +size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::vector<size_t>& selected_transfers, bool smallest) const { return pop_best_value_from(m_transfers, unused_indices, selected_transfers, smallest); } @@ -3330,9 +3321,10 @@ size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::l // returns: // direct return: amount of money found // modified reference: selected_transfers, a list of iterators/indices of input sources -uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon) +uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers, bool trusted_daemon) { uint64_t found_money = 0; + selected_transfers.reserve(unused_transfers_indices.size()); while (found_money < needed_money && !unused_transfers_indices.empty()) { size_t idx = pop_best_value(unused_transfers_indices, selected_transfers); @@ -3950,6 +3942,19 @@ int wallet2::get_fee_algorithm() return 1; return 0; } +//------------------------------------------------------------------------------------------------------------------------------ +uint64_t wallet2::adjust_mixin(uint64_t mixin) +{ + if (mixin < 4 && use_fork_rules(6, 10)) { + MWARNING("Requested ring size " << (mixin + 1) << " too low for hard fork 6, using 5"); + mixin = 4; + } + else if (mixin < 2 && use_fork_rules(2, 10)) { + MWARNING("Requested ring size " << (mixin + 1) << " too low for hard fork 2, using 3"); + mixin = 2; + } + return mixin; +} //---------------------------------------------------------------------------------------------------- // separated the call(s) to wallet2::transfer into their own function // @@ -4073,7 +4078,7 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out return true; } -void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) { +void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count) { MDEBUG("LIGHTWALLET - Getting random outs"); @@ -4177,7 +4182,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ } } -void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) +void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count) { LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); outs.clear(); @@ -4412,7 +4417,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } template<typename T> -void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) { @@ -4566,7 +4571,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent LOG_PRINT_L2("transfer_selected done"); } -void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx) { @@ -5133,7 +5138,7 @@ void wallet2::light_wallet_get_address_txs() payments_txs.push_back(p.second.m_tx_hash); std::vector<crypto::hash> unconfirmed_payments_txs; for(const auto &up: m_unconfirmed_payments) - unconfirmed_payments_txs.push_back(up.second.m_tx_hash); + unconfirmed_payments_txs.push_back(up.second.m_pd.m_tx_hash); // for balance calculation uint64_t wallet_total_sent = 0; @@ -5199,7 +5204,11 @@ void wallet2::light_wallet_get_address_txs() if (t.mempool) { if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) { pool_txs.push_back(tx_hash); - m_unconfirmed_payments.emplace(tx_hash, payment); + // assume false as we don't get that info from the light wallet server + crypto::hash payment_id; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::hex_to_pod(t.payment_id, payment_id), + error::wallet_internal_error, "Failed to parse payment id"); + emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, false}); if (0 != m_callback) { m_callback->on_lw_unconfirmed_money_received(t.height, payment.m_tx_hash, payment.m_amount); } @@ -5329,11 +5338,27 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, crypto::key_image calculated_key_image; cryptonote::keypair in_ephemeral; - // Subaddresses aren't supported in mymonero/openmonero yet. Using empty values. - const std::vector<crypto::public_key> additional_tx_pub_keys; - const crypto::public_key pkey = crypto::null_pkey; - - cryptonote::generate_key_image_helper(get_account().get_keys(), m_subaddresses, pkey, tx_public_key, additional_tx_pub_keys, out_index, in_ephemeral, calculated_key_image); + // Subaddresses aren't supported in mymonero/openmonero yet. Roll out the original scheme: + // compute D = a*R + // compute P = Hs(D || i)*G + B + // compute x = Hs(D || i) + b (and check if P==x*G) + // compute I = x*Hp(P) + const account_keys& ack = get_account().get_keys(); + crypto::key_derivation derivation; + bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + + r = crypto::derive_public_key(derivation, out_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); + CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key (" << derivation << ", " << out_index << ", " << ack.m_account_address.m_spend_public_key << ")"); + + crypto::derive_secret_key(derivation, out_index, ack.m_spend_secret_key, in_ephemeral.sec); + crypto::public_key out_pkey_test; + r = crypto::secret_key_to_public_key(in_ephemeral.sec, out_pkey_test); + CHECK_AND_ASSERT_MES(r, false, "failed to secret_key_to_public_key(" << in_ephemeral.sec << ")"); + CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_pkey_test, false, "derived secret key doesn't match derived public key"); + + crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, calculated_key_image); + index_keyimage_map.emplace(out_index, calculated_key_image); m_key_image_cache.emplace(tx_public_key, index_keyimage_map); return key_image == calculated_key_image; @@ -5365,7 +5390,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp uint64_t needed_money; uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { - std::list<size_t> selected_transfers; + std::vector<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; @@ -5420,9 +5445,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // throw if attempting a transaction with no money THROW_WALLET_EXCEPTION_IF(needed_money == 0, error::zero_destination); - std::map<uint32_t, uint64_t> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); + std::map<uint32_t, uint64_t> unlocked_balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); + std::map<uint32_t, uint64_t> balance_per_subaddr = balance_per_subaddress(subaddr_account); - if (subaddr_indices.empty()) // "index=<N1>[,<N2>,...]" wasn't specified -> use all the indices with non-zero unlocked bakance + if (subaddr_indices.empty()) // "index=<N1>[,<N2>,...]" wasn't specified -> use all the indices with non-zero unlocked balance { for (const auto& i : balance_per_subaddr) subaddr_indices.insert(i.first); @@ -5432,10 +5458,17 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // we could also check for being within FEE_PER_KB, but if the fee calculation // ever changes, this might be missed, so let this go through uint64_t balance_subtotal = 0; + uint64_t unlocked_balance_subtotal = 0; for (uint32_t index_minor : subaddr_indices) + { balance_subtotal += balance_per_subaddr[index_minor]; + unlocked_balance_subtotal += unlocked_balance_per_subaddr[index_minor]; + } THROW_WALLET_EXCEPTION_IF(needed_money > balance_subtotal, error::not_enough_money, balance_subtotal, needed_money, 0); + // first check overall balance is enough, then unlocked one, so we throw distinct exceptions + THROW_WALLET_EXCEPTION_IF(needed_money > unlocked_balance_subtotal, error::not_enough_unlocked_money, + unlocked_balance_subtotal, needed_money, 0); for (uint32_t i : subaddr_indices) LOG_PRINT_L2("Candidate subaddress index for spending: " << i); @@ -5479,24 +5512,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } } - // early out if we know we can't make it anyway - // we could also check for being within FEE_PER_KB, but if the fee calculation - // ever changes, this might be missed, so let this go through - // first check overall balance is enough, then unlocked one, so we throw distinct exceptions - THROW_WALLET_EXCEPTION_IF(needed_money > balance(subaddr_account), error::not_enough_money, - unlocked_balance(subaddr_account), needed_money, 0); - THROW_WALLET_EXCEPTION_IF(needed_money > unlocked_balance(subaddr_account), error::not_enough_unlocked_money, - unlocked_balance(subaddr_account), needed_money, 0); - // shuffle & sort output indices { std::random_device rd; std::mt19937 g(rd()); std::shuffle(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), g); std::shuffle(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), g); - auto sort_predicate = [&balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y) + auto sort_predicate = [&unlocked_balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y) { - return balance_per_subaddr[x.first] > balance_per_subaddr[y.first]; + return unlocked_balance_per_subaddr[x.first] > unlocked_balance_per_subaddr[y.first]; }; std::sort(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), sort_predicate); std::sort(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), sort_predicate); @@ -5807,7 +5831,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below std::vector<size_t> unused_dust_indices; const bool use_rct = use_fork_rules(4, 0); - THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account) == 0, error::wallet_internal_error, "No unclocked balance in the entire wallet"); + THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account) == 0, error::wallet_internal_error, "No unlocked balance in the entire wallet"); std::map<uint32_t, uint64_t> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); @@ -5844,11 +5868,32 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); } +std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypto::key_image &ki, 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, bool trusted_daemon) +{ + std::vector<size_t> unused_transfers_indices; + std::vector<size_t> unused_dust_indices; + const bool use_rct = use_fork_rules(4, 0); + // find output with the given key image + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; + if (td.m_key_image_known && td.m_key_image == ki && !td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) + { + if (td.is_rct() || is_valid_decomposed_amount(td.amount())) + unused_transfers_indices.push_back(i); + else + unused_dust_indices.push_back(i); + break; + } + } + return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); +} + std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon) { uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { - std::list<size_t> selected_transfers; + std::vector<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; @@ -6168,6 +6213,576 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s additional_tx_keys = j->second; return true; } +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string &message) +{ + THROW_WALLET_EXCEPTION_IF(m_watch_only, error::wallet_internal_error, + "get_spend_proof requires spend secret key and is not available for a watch-only wallet"); + + // fetch tx from daemon + COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr"); + cryptonote::transaction tx; + crypto::hash tx_hash, tx_prefix_hash; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch"); + + std::vector<std::vector<crypto::signature>> signatures; + + // get signature prefix hash + std::string sig_prefix_data((const char*)&txid, sizeof(crypto::hash)); + sig_prefix_data += message; + crypto::hash sig_prefix_hash; + crypto::cn_fast_hash(sig_prefix_data.data(), sig_prefix_data.size(), sig_prefix_hash); + + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + + // check if the key image belongs to us + const auto found = m_key_images.find(in_key->k_image); + if(found == m_key_images.end()) + { + THROW_WALLET_EXCEPTION_IF(i > 0, error::wallet_internal_error, "subset of key images belong to us, very weird!"); + THROW_WALLET_EXCEPTION_IF(true, error::wallet_internal_error, "This tx wasn't generated by this wallet!"); + } + + // derive the real output keypair + const transfer_details& in_td = m_transfers[found->second]; + const txout_to_key* const in_tx_out_pkey = boost::get<txout_to_key>(std::addressof(in_td.m_tx.vout[in_td.m_internal_output_index].target)); + THROW_WALLET_EXCEPTION_IF(in_tx_out_pkey == nullptr, error::wallet_internal_error, "Output is not txout_to_key"); + const crypto::public_key in_tx_pub_key = get_tx_pub_key_from_extra(in_td.m_tx, in_td.m_pk_index); + const std::vector<crypto::public_key> in_additionakl_tx_pub_keys = get_additional_tx_pub_keys_from_extra(in_td.m_tx); + keypair in_ephemeral; + crypto::key_image in_img; + THROW_WALLET_EXCEPTION_IF(!generate_key_image_helper(m_account.get_keys(), m_subaddresses, in_tx_out_pkey->key, in_tx_pub_key, in_additionakl_tx_pub_keys, in_td.m_internal_output_index, in_ephemeral, in_img), + error::wallet_internal_error, "failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(in_key->k_image != in_img, error::wallet_internal_error, "key image mismatch"); + + // get output pubkeys in the ring + const std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key->key_offsets); + const size_t ring_size = in_key->key_offsets.size(); + THROW_WALLET_EXCEPTION_IF(absolute_offsets.size() != ring_size, error::wallet_internal_error, "absolute offsets size is wrong"); + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + req.outputs.resize(ring_size); + for (size_t j = 0; j < ring_size; ++j) + { + req.outputs[j].amount = in_key->amount; + req.outputs[j].index = absolute_offsets[j]; + } + COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != ring_size, error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(ring_size)); + + // copy pubkey pointers + std::vector<const crypto::public_key *> p_output_keys; + for (const COMMAND_RPC_GET_OUTPUTS_BIN::outkey &out : res.outs) + p_output_keys.push_back(&out.key); + + // figure out real output index and secret key + size_t sec_index = -1; + for (size_t j = 0; j < ring_size; ++j) + { + if (res.outs[j].key == in_ephemeral.pub) + { + sec_index = j; + break; + } + } + THROW_WALLET_EXCEPTION_IF(sec_index >= ring_size, error::wallet_internal_error, "secret index not found"); + + // generate ring sig for this input + signatures.push_back(std::vector<crypto::signature>()); + std::vector<crypto::signature>& sigs = signatures.back(); + sigs.resize(in_key->key_offsets.size()); + crypto::generate_ring_signature(sig_prefix_hash, in_key->k_image, p_output_keys, in_ephemeral.sec, sec_index, sigs.data()); + } + + std::string sig_str = "SpendProofV1"; + for (const std::vector<crypto::signature>& ring_sig : signatures) + for (const crypto::signature& sig : ring_sig) + sig_str += tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))); + return sig_str; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str) +{ + const std::string header = "SpendProofV1"; + const size_t header_len = header.size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error, + "Signature header check error"); + + // fetch tx from daemon + COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr"); + cryptonote::transaction tx; + crypto::hash tx_hash, tx_prefix_hash; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch"); + + // check signature size + size_t num_sigs = 0; + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key != nullptr) + num_sigs += in_key->key_offsets.size(); + } + 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"); + + // decode base58 + signatures.clear(); + size_t offset = header_len; + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + signatures.resize(signatures.size() + 1); + signatures.back().resize(in_key->key_offsets.size()); + for (size_t j = 0; j < in_key->key_offsets.size(); ++j) + { + std::string sig_decoded; + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset, sig_len), sig_decoded), error::wallet_internal_error, "Signature decoding error"); + THROW_WALLET_EXCEPTION_IF(sizeof(crypto::signature) != sig_decoded.size(), error::wallet_internal_error, "Signature decoding error"); + memcpy(&signatures.back()[j], sig_decoded.data(), sizeof(crypto::signature)); + offset += sig_len; + } + } + + // get signature prefix hash + std::string sig_prefix_data((const char*)&txid, sizeof(crypto::hash)); + sig_prefix_data += message; + crypto::hash sig_prefix_hash; + crypto::cn_fast_hash(sig_prefix_data.data(), sig_prefix_data.size(), sig_prefix_hash); + + std::vector<std::vector<crypto::signature>>::const_iterator sig_iter = signatures.cbegin(); + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + + // get output pubkeys in the ring + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + const std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key->key_offsets); + req.outputs.resize(absolute_offsets.size()); + for (size_t j = 0; j < absolute_offsets.size(); ++j) + { + req.outputs[j].amount = in_key->amount; + req.outputs[j].index = absolute_offsets[j]; + } + COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != req.outputs.size(), error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(req.outputs.size())); + + // copy pointers + std::vector<const crypto::public_key *> p_output_keys; + for (const COMMAND_RPC_GET_OUTPUTS_BIN::outkey &out : res.outs) + p_output_keys.push_back(&out.key); + + // check this ring + if (!crypto::check_ring_signature(sig_prefix_hash, in_key->k_image, p_output_keys, sig_iter->data())) + return false; + ++sig_iter; + } + THROW_WALLET_EXCEPTION_IF(sig_iter != signatures.cend(), error::wallet_internal_error, "Signature iterator didn't reach the end"); + return true; +} +//---------------------------------------------------------------------------------------------------- + +void wallet2::check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation), error::wallet_internal_error, + "Failed to generate key derivation from supplied parameters"); + + std::vector<crypto::key_derivation> additional_derivations; + additional_derivations.resize(additional_tx_keys.size()); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(address.m_view_public_key, additional_tx_keys[i], additional_derivations[i]), error::wallet_internal_error, + "Failed to generate key derivation from supplied parameters"); + + check_tx_key_helper(txid, derivation, additional_derivations, address, received, in_pool, confirmations); +} + +void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + m_daemon_rpc_mutex.lock(); + bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + cryptonote::blobdata tx_data; + if (res.txs.size() == 1) + ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); + + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, + "Failed to validate transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, + "Failed to get the right transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(!additional_derivations.empty() && additional_derivations.size() != tx.vout.size(), error::wallet_internal_error, + "The size of additional derivations is wrong"); + + received = 0; + for (size_t n = 0; n < tx.vout.size(); ++n) + { + const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[n].target)); + if (!out_key) + continue; + + crypto::public_key derived_out_key; + derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key); + bool found = out_key->key == derived_out_key; + crypto::key_derivation found_derivation = derivation; + if (!found && !additional_derivations.empty()) + { + derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key); + found = out_key->key == derived_out_key; + found_derivation = additional_derivations[n]; + } + + if (found) + { + uint64_t amount; + if (tx.version == 1 || tx.rct_signatures.type == rct::RCTTypeNull) + { + amount = tx.vout[n].amount; + } + else + { + crypto::secret_key scalar1; + crypto::derivation_to_scalar(found_derivation, n, scalar1); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); + const rct::key C = tx.rct_signatures.outPk[n].mask; + rct::key Ctmp; + rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); + if (rct::equalKeys(C, Ctmp)) + amount = rct::h2d(ecdh_info.amount); + else + amount = 0; + } + received += amount; + } + } + + in_pool = res.txs.front().in_pool; + confirmations = (uint64_t)-1; + if (!in_pool) + { + std::string err; + uint64_t bc_height = get_daemon_blockchain_height(err); + if (err.empty()) + confirmations = bc_height - (res.txs.front().block_height + 1); + } +} + +std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message) +{ + // determine if the address is found in the subaddress hash table (i.e. whether the proof is outbound or inbound) + const bool is_out = m_subaddresses.count(address.m_spend_public_key) == 0; + + std::string prefix_data((const char*)&txid, sizeof(crypto::hash)); + prefix_data += message; + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + std::vector<crypto::public_key> shared_secret; + std::vector<crypto::signature> sig; + std::string sig_str; + if (is_out) + { + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + THROW_WALLET_EXCEPTION_IF(!get_tx_key(txid, tx_key, additional_tx_keys), error::wallet_internal_error, "Tx secret key wasn't found in the wallet file."); + + const size_t num_sigs = 1 + additional_tx_keys.size(); + shared_secret.resize(num_sigs); + sig.resize(num_sigs); + + shared_secret[0] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); + crypto::public_key tx_pub_key; + if (is_subaddress) + { + tx_pub_key = rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_spend_public_key), rct::sk2rct(tx_key))); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, sig[0]); + } + else + { + crypto::secret_key_to_public_key(tx_key, tx_pub_key); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], tx_key, sig[0]); + } + for (size_t i = 1; i < num_sigs; ++i) + { + shared_secret[i] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(additional_tx_keys[i - 1]))); + if (is_subaddress) + { + tx_pub_key = rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_spend_public_key), rct::sk2rct(additional_tx_keys[i - 1]))); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], sig[i]); + } + else + { + crypto::secret_key_to_public_key(additional_tx_keys[i - 1], tx_pub_key); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]); + } + } + sig_str = std::string("OutProofV1"); + } + else + { + // fetch tx pubkey from the daemon + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + m_daemon_rpc_mutex.lock(); + bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + cryptonote::blobdata tx_data; + if (res.txs.size() == 1) + ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); + + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, + "Failed to validate transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found"); + + std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + const size_t num_sigs = 1 + additional_tx_pub_keys.size(); + shared_secret.resize(num_sigs); + sig.resize(num_sigs); + + const crypto::secret_key& a = m_account.get_keys().m_view_secret_key; + shared_secret[0] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(tx_pub_key), rct::sk2rct(a))); + if (is_subaddress) + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], a, sig[0]); + } + else + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], a, sig[0]); + } + for (size_t i = 1; i < num_sigs; ++i) + { + shared_secret[i] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(additional_tx_pub_keys[i - 1]), rct::sk2rct(a))); + if (is_subaddress) + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], address.m_spend_public_key, shared_secret[i], a, sig[i]); + } + else + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], boost::none, shared_secret[i], a, sig[i]); + } + } + sig_str = std::string("InProofV1"); + } + const size_t num_sigs = shared_secret.size(); + + // check if this address actually received any funds + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); + std::vector<crypto::key_derivation> additional_derivations(num_sigs - 1); + for (size_t i = 1; i < num_sigs; ++i) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation"); + uint64_t received; + bool in_pool; + uint64_t confirmations; + check_tx_key_helper(txid, derivation, additional_derivations, address, received, in_pool, confirmations); + THROW_WALLET_EXCEPTION_IF(!received, error::wallet_internal_error, tr("No funds received in this tx.")); + + // concatenate all signature strings + for (size_t i = 0; i < num_sigs; ++i) + sig_str += + tools::base58::encode(std::string((const char *)&shared_secret[i], sizeof(crypto::public_key))) + + tools::base58::encode(std::string((const char *)&sig[i], sizeof(crypto::signature))); + return sig_str; +} + +bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + const bool is_out = sig_str.substr(0, 3) == "Out"; + const std::string header = is_out ? "OutProofV1" : "InProofV1"; + const size_t header_len = header.size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error, + "Signature header check error"); + + // decode base58 + std::vector<crypto::public_key> shared_secret(1); + std::vector<crypto::signature> sig(1); + const size_t pk_len = tools::base58::encode(std::string((const char *)&shared_secret[0], sizeof(crypto::public_key))).size(); + const size_t sig_len = tools::base58::encode(std::string((const char *)&sig[0], sizeof(crypto::signature))).size(); + const size_t num_sigs = (sig_str.size() - header_len) / (pk_len + sig_len); + THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * (pk_len + sig_len), error::wallet_internal_error, + "Wrong signature size"); + shared_secret.resize(num_sigs); + sig.resize(num_sigs); + for (size_t i = 0; i < num_sigs; ++i) + { + std::string pk_decoded; + std::string sig_decoded; + const size_t offset = header_len + i * (pk_len + sig_len); + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset, pk_len), pk_decoded), error::wallet_internal_error, + "Signature decoding error"); + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset + pk_len, sig_len), sig_decoded), error::wallet_internal_error, + "Signature decoding error"); + THROW_WALLET_EXCEPTION_IF(sizeof(crypto::public_key) != pk_decoded.size() || sizeof(crypto::signature) != sig_decoded.size(), error::wallet_internal_error, + "Signature decoding error"); + memcpy(&shared_secret[i], pk_decoded.data(), sizeof(crypto::public_key)); + memcpy(&sig[i], sig_decoded.data(), sizeof(crypto::signature)); + } + + // fetch tx pubkey from the daemon + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + m_daemon_rpc_mutex.lock(); + bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + cryptonote::blobdata tx_data; + if (res.txs.size() == 1) + ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); + + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, + "Failed to validate transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found"); + + std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(additional_tx_pub_keys.size() + 1 != num_sigs, error::wallet_internal_error, "Signature size mismatch with additional tx pubkeys"); + + std::string prefix_data((const char*)&txid, sizeof(crypto::hash)); + prefix_data += message; + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + // check signature + std::vector<int> good_signature(num_sigs, 0); + if (is_out) + { + good_signature[0] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0]) : + crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0]); + + for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) + { + good_signature[i + 1] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) : + crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1]); + } + } + else + { + good_signature[0] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], sig[0]) : + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], sig[0]); + + for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) + { + good_signature[i + 1] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) : + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], boost::none, shared_secret[i + 1], sig[i + 1]); + } + } + + if (std::any_of(good_signature.begin(), good_signature.end(), [](int i) { return i > 0; })) + { + // obtain key derivation by multiplying scalar 1 to the shared secret + crypto::key_derivation derivation; + if (good_signature[0]) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); + + std::vector<crypto::key_derivation> additional_derivations(num_sigs - 1); + for (size_t i = 1; i < num_sigs; ++i) + if (good_signature[i]) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation"); + + check_tx_key_helper(txid, derivation, additional_derivations, address, received, in_pool, confirmations); + return true; + } + return false; +} std::string wallet2::get_wallet_file() const { @@ -6450,16 +7065,12 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent std::string data; bool r = epee::file_io_utils::load_file_to_string(filename, data); - if (!r) - { - fail_msg_writer() << tr("failed to read file ") << filename; - return 0; - } + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, std::string(tr("failed to read file ")) + filename); + const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen)) { - fail_msg_writer() << "Bad key image export file magic in " << filename; - return 0; + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic in ") + filename); } try @@ -6468,31 +7079,22 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent } catch (const std::exception &e) { - fail_msg_writer() << "Failed to decrypt " << filename << ": " << e.what(); - return 0; + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + filename + ": " + e.what()); } const size_t headerlen = 2 * sizeof(crypto::public_key); - if (data.size() < headerlen) - { - fail_msg_writer() << "Bad data size from file " << filename; - return 0; - } + THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename); 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) { - fail_msg_writer() << "Key images from " << filename << " are for a different account"; - return 0; + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + filename + " are for a different account"); } const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature); - if ((data.size() - headerlen) % record_size) - { - fail_msg_writer() << "Bad data size from file " << filename; - return 0; - } + THROW_WALLET_EXCEPTION_IF((data.size() - headerlen) % record_size, + error::wallet_internal_error, std::string("Bad data size from file ") + filename); size_t nki = (data.size() - headerlen) / record_size; std::vector<std::pair<crypto::key_image, crypto::signature>> ski; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f1e12a700..ce0c67fc3 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -147,7 +147,7 @@ namespace tools }; private: - wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false),m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} + wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false),m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} public: static const char* tr(const char* str); @@ -155,25 +155,22 @@ namespace tools static bool has_testnet_option(const boost::program_options::variables_map& vm); static void init_options(boost::program_options::options_description& desc_params); - //! \return Password retrieved from prompt. Logs error on failure. - static boost::optional<password_container> password_prompt(const bool new_password); - //! Uses stdin and stdout. Returns a wallet2 if no errors. - static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file); + static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Uses stdin and stdout. Returns a wallet2 and password for `wallet_file` if no errors. static std::pair<std::unique_ptr<wallet2>, password_container> - make_from_file(const boost::program_options::variables_map& vm, const std::string& wallet_file); + make_from_file(const boost::program_options::variables_map& vm, const std::string& wallet_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Uses stdin and stdout. Returns a wallet2 and password for wallet with no file if no errors. - static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm); + static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Just parses variables. - static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm); + static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); static bool verify_password(const std::string& keys_file_name, const std::string& password, bool watch_only); - wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {} + wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {} struct tx_scan_info_t { @@ -244,6 +241,12 @@ namespace tools bool m_incoming; }; + struct pool_payment_details + { + payment_details m_pd; + bool m_double_spend_seen; + }; + struct unconfirmed_transfer_details { cryptonote::transaction_prefix m_tx; @@ -282,13 +285,26 @@ namespace tools std::vector<cryptonote::tx_source_entry> sources; cryptonote::tx_destination_entry change_dts; std::vector<cryptonote::tx_destination_entry> splitted_dsts; // split, includes change - std::list<size_t> selected_transfers; + std::vector<size_t> selected_transfers; std::vector<uint8_t> extra; uint64_t unlock_time; bool use_rct; std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change uint32_t subaddr_account; // subaddress account of your wallet to be used in this transfer std::set<uint32_t> subaddr_indices; // set of address indices used as inputs in this transfer + + BEGIN_SERIALIZE_OBJECT() + FIELD(sources) + FIELD(change_dts) + FIELD(splitted_dsts) + FIELD(selected_transfers) + FIELD(extra) + FIELD(unlock_time) + FIELD(use_rct) + FIELD(dests) + FIELD(subaddr_account) + FIELD(subaddr_indices) + END_SERIALIZE() }; typedef std::vector<transfer_details> transfer_container; @@ -303,13 +319,27 @@ namespace tools uint64_t dust, fee; bool dust_added_to_fee; cryptonote::tx_destination_entry change_dts; - std::list<size_t> selected_transfers; + std::vector<size_t> selected_transfers; std::string key_images; crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; std::vector<cryptonote::tx_destination_entry> dests; tx_construction_data construction_data; + + BEGIN_SERIALIZE_OBJECT() + FIELD(tx) + FIELD(dust) + FIELD(fee) + FIELD(dust_added_to_fee) + FIELD(change_dts) + FIELD(selected_transfers) + FIELD(key_images) + FIELD(tx_key) + FIELD(additional_tx_keys) + FIELD(dests) + FIELD(construction_data) + END_SERIALIZE() }; // The term "Unsigned tx" is not really a tx since it's not signed yet. @@ -501,10 +531,10 @@ namespace tools void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, bool trusted_daemon); void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon); template<typename T> - void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, + void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); - void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, + void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); @@ -521,6 +551,7 @@ namespace tools 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); + std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, 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, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, 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<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon); bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000); @@ -530,7 +561,7 @@ namespace tools void get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; - void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; + void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; uint64_t get_blockchain_current_height() const { return m_local_bc_height; } void rescan_spent(); @@ -585,7 +616,7 @@ namespace tools std::unordered_map<crypto::hash, payment_details> m; a & m; for (std::unordered_map<crypto::hash, payment_details>::const_iterator i = m.begin(); i != m.end(); ++i) - m_unconfirmed_payments.insert(*i); + m_unconfirmed_payments.insert(std::make_pair(i->first, pool_payment_details{i->second, false})); } if(ver < 14) return; @@ -607,7 +638,14 @@ namespace tools a & m_address_book; if(ver < 17) return; - a & m_unconfirmed_payments; + if (ver < 22) + { + // we're loading an old version, where m_unconfirmed_payments payload was payment_details + std::unordered_multimap<crypto::hash, payment_details> m; + a & m; + for (const auto &i: m) + m_unconfirmed_payments.insert(std::make_pair(i.first, pool_payment_details{i.second, false})); + } if(ver < 18) return; a & m_scanned_pool_txs[0]; @@ -621,6 +659,9 @@ namespace tools if(ver < 21) return; a & m_attributes; + if(ver < 22) + return; + a & m_unconfirmed_payments; } /*! @@ -670,7 +711,13 @@ namespace tools uint32_t get_confirm_backlog_threshold() const { return m_confirm_backlog_threshold; }; bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; + void check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); + void check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); + std::string get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message); + bool check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations); + std::string get_spend_proof(const crypto::hash &txid, const std::string &message); + bool check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str); /*! * \brief GUI Address book get/store */ @@ -700,8 +747,8 @@ namespace tools std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon); std::vector<size_t> select_available_mixable_outputs(bool trusted_daemon); - size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers, bool smallest = false) const; - size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers, bool smallest = false) const; + size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::vector<size_t>& selected_transfers, bool smallest = false) const; + size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::vector<size_t>& selected_transfers, bool smallest = false) const; void set_tx_note(const crypto::hash &txid, const std::string ¬e); std::string get_tx_note(const crypto::hash &txid) const; @@ -744,6 +791,7 @@ namespace tools uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1); uint64_t get_per_kb_fee(); + uint64_t adjust_mixin(uint64_t mixin); // Light wallet specific functions // fetch unspent outs from lw node and store in m_transfers @@ -757,7 +805,7 @@ namespace tools // Send an import request to lw node. returns info about import fee, address and payment_id bool light_wallet_import_wallet_request(cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response); // get random outputs from light wallet server - void light_wallet_get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count); + void light_wallet_get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count); // Parse rct string bool light_wallet_parse_rct_str(const std::string& rct_string, const crypto::public_key& tx_pub_key, uint64_t internal_output_index, rct::key& decrypted_mask, rct::key& rct_commit, bool decrypt) const; // check if key image is ours @@ -797,7 +845,7 @@ namespace tools * \param password Password of wallet file */ bool load_keys(const std::string& keys_file_name, const std::string& password); - void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool); + void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen); void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices); void detach_blockchain(uint64_t height); void get_short_chain_history(std::list<crypto::hash>& ids) const; @@ -808,7 +856,7 @@ namespace tools void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history); void pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, bool &error); void process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, const std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t& blocks_added); - uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon); + uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers, bool trusted_daemon); bool prepare_file_names(const std::string& file_path); void process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height); void process_outgoing(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices); @@ -827,7 +875,7 @@ namespace tools std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const; void set_spent(size_t idx, uint64_t height); void set_unspent(size_t idx); - void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count); + void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count); bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; bool wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki); crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; @@ -846,7 +894,7 @@ namespace tools std::atomic<uint64_t> m_local_bc_height; //temporary workaround std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs; - std::unordered_multimap<crypto::hash, payment_details> m_unconfirmed_payments; + std::unordered_multimap<crypto::hash, pool_payment_details> m_unconfirmed_payments; std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys; cryptonote::checkpoints m_checkpoints; std::unordered_map<crypto::hash, std::vector<crypto::secret_key>> m_additional_tx_keys; @@ -908,16 +956,17 @@ namespace tools std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> > m_key_image_cache; }; } -BOOST_CLASS_VERSION(tools::wallet2, 21) +BOOST_CLASS_VERSION(tools::wallet2, 22) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 8) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2) +BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 5) BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17) BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0) -BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 1) -BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 1) +BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 2) +BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 2) namespace boost { @@ -1137,7 +1186,14 @@ namespace boost } a & x.m_subaddr_index; } - + + template <class Archive> + inline void serialize(Archive& a, tools::wallet2::pool_payment_details& x, const boost::serialization::version_type ver) + { + a & x.m_pd; + a & x.m_double_spend_seen; + } + template <class Archive> inline void serialize(Archive& a, tools::wallet2::address_book_row& x, const boost::serialization::version_type ver) { @@ -1172,7 +1228,16 @@ namespace boost a & x.sources; a & x.change_dts; a & x.splitted_dsts; - a & x.selected_transfers; + if (ver < 2) + { + // load list to vector + std::list<size_t> selected_transfers; + a & selected_transfers; + x.selected_transfers.clear(); + x.selected_transfers.reserve(selected_transfers.size()); + for (size_t t: selected_transfers) + x.selected_transfers.push_back(t); + } a & x.extra; a & x.unlock_time; a & x.use_rct; @@ -1184,6 +1249,9 @@ namespace boost } a & x.subaddr_account; a & x.subaddr_indices; + if (ver < 2) + return; + a & x.selected_transfers; } template <class Archive> @@ -1194,7 +1262,16 @@ namespace boost a & x.fee; a & x.dust_added_to_fee; a & x.change_dts; - a & x.selected_transfers; + if (ver < 2) + { + // load list to vector + std::list<size_t> selected_transfers; + a & selected_transfers; + x.selected_transfers.clear(); + x.selected_transfers.reserve(selected_transfers.size()); + for (size_t t: selected_transfers) + x.selected_transfers.push_back(t); + } a & x.key_images; a & x.tx_key; a & x.dests; @@ -1202,6 +1279,9 @@ namespace boost if (ver < 1) return; a & x.additional_tx_keys; + if (ver < 2) + return; + a & x.selected_transfers; } } } @@ -1291,7 +1371,7 @@ namespace tools // randomly select inputs for transaction // throw if requested send amount is greater than (unlocked) amount available to send - std::list<size_t> selected_transfers; + std::vector<size_t> selected_transfers; uint64_t found_money = select_transfers(needed_money, unused_transfers_indices, selected_transfers, trusted_daemon); THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_unlocked_money, found_money, needed_money - fee, fee); diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index a8c150ca7..b1f8369a3 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -700,6 +700,11 @@ struct Wallet */ virtual std::string getUserNote(const std::string &txid) const = 0; virtual std::string getTxKey(const std::string &txid) const = 0; + virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; + virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message) const = 0; + virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; + virtual std::string getSpendProof(const std::string &txid, const std::string &message) const = 0; + virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const = 0; /* * \brief signMessage - sign a message with the spend private key @@ -819,19 +824,6 @@ struct WalletManager */ virtual std::vector<std::string> findWallets(const std::string &path) = 0; - /*! - * \brief checkPayment - checks a payment was made using a txkey - * \param address - the address the payment was sent to - * \param txid - the transaction id for that payment - * \param txkey - the transaction's secret key - * \param daemon_address - the address (host and port) to the daemon to request transaction data - * \param received - if succesful, will hold the amount of monero received - * \param height - if succesful, will hold the height of the transaction (0 if only in the pool) - * \param error - if unsuccesful, will hold an error string with more information about the error - * \return - true is succesful, false otherwise - */ - virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0; - //! returns verbose error string regarding last error; virtual std::string errorString() const = 0; @@ -869,7 +861,7 @@ struct WalletManager virtual std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const = 0; //! checks for an update and returns version, hash and url - static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir); + static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, std::string subdir); }; diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index df01ec238..cc6bb1de2 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -30,7 +30,6 @@ #include <boost/filesystem/path.hpp> #include <boost/format.hpp> #include "common/i18n.h" -#include "common/scoped_message_writer.h" #include "common/util.h" #include "misc_log_ex.h" #include "string_tools.h" @@ -50,6 +49,20 @@ #define DEFAULT_MAX_CONCURRENCY 0 #endif +namespace +{ + class Print + { + public: + Print(const std::function<void(const std::string&, bool)> &p, bool em = false): print(p), emphasis(em) {} + ~Print() { print(ss.str(), emphasis); } + template<typename T> std::ostream &operator<<(const T &t) { ss << t; return ss; } + private: + const std::function<void(const std::string&, bool)> &print; + std::stringstream ss; + bool emphasis; + }; +} namespace wallet_args { @@ -73,6 +86,7 @@ namespace wallet_args const char* const usage, boost::program_options::options_description desc_params, const boost::program_options::positional_options_description& positional_options, + const std::function<void(const std::string&, bool)> &print, const char *default_log_name, bool log_to_console) @@ -118,16 +132,16 @@ namespace wallet_args if (command_line::get_arg(vm, command_line::arg_help)) { - tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL; - tools::msg_writer() << wallet_args::tr("This is the command line monero wallet. It needs to connect to a monero\n" + Print(print) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL; + Print(print) << wallet_args::tr("This is the command line monero wallet. It needs to connect to a monero\n" "daemon to work correctly.") << ENDL; - tools::msg_writer() << wallet_args::tr("Usage:") << ENDL << " " << usage; - tools::msg_writer() << desc_all; + Print(print) << wallet_args::tr("Usage:") << ENDL << " " << usage; + Print(print) << desc_all; return false; } else if (command_line::get_arg(vm, command_line::arg_version)) { - tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; + Print(print) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; return false; } @@ -142,7 +156,7 @@ namespace wallet_args } else { - tools::fail_msg_writer() << wallet_args::tr("Can't find config file ") << config; + MERROR(wallet_args::tr("Can't find config file ") << config); return false; } } @@ -167,14 +181,15 @@ namespace wallet_args if(command_line::has_arg(vm, arg_max_concurrency)) tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency)); - tools::scoped_message_writer(epee::console_color_white, true) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; + Print(print) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; if (!command_line::is_arg_defaulted(vm, arg_log_level)) MINFO("Setting log level = " << command_line::get_arg(vm, arg_log_level)); else MINFO("Setting log levels = " << getenv("MONERO_LOGS")); MINFO(wallet_args::tr("Logging to: ") << log_path); - tools::scoped_message_writer(epee::console_color_white, true) << boost::format(wallet_args::tr("Logging to %s")) % log_path; + + Print(print) << boost::format(wallet_args::tr("Logging to %s")) % log_path; return {std::move(vm)}; } diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index cf23ffded..8974098ad 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -50,5 +50,6 @@ namespace wallet_args const char* const usage, boost::program_options::options_description desc_params, const boost::program_options::positional_options_description& positional_options, + const std::function<void(const std::string&, bool)> &print, const char *default_log_name, bool log_to_console = false); } diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 9d66f125e..41eb77451 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -765,6 +765,12 @@ namespace tools #define STRINGIZE_DETAIL(x) #x #define STRINGIZE(x) STRINGIZE_DETAIL(x) +#define THROW_WALLET_EXCEPTION(err_type, ...) \ + do { \ + LOG_ERROR("THROW EXCEPTION: " << #err_type); \ + tools::error::throw_wallet_ex<err_type>(std::string(__FILE__ ":" STRINGIZE(__LINE__)), ## __VA_ARGS__); \ + } while(0) + #define THROW_WALLET_EXCEPTION_IF(cond, err_type, ...) \ if (cond) \ { \ diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 9e6a97bdc..fe7f51617 100755..100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -30,6 +30,7 @@ #include <boost/format.hpp> #include <boost/asio/ip/address.hpp> #include <boost/filesystem/operations.hpp> +#include <boost/algorithm/string.hpp> #include <cstdint> #include "include_base_utils.h" using namespace epee; @@ -58,8 +59,19 @@ namespace const command_line::arg_descriptor<bool> arg_disable_rpc_login = {"disable-rpc-login", "Disable HTTP authentication for RPC connections served by this process"}; const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", "Enable commands which rely on a trusted daemon", false}; const command_line::arg_descriptor<std::string> arg_wallet_dir = {"wallet-dir", "Directory for newly created wallets"}; + const command_line::arg_descriptor<bool> arg_prompt_for_password = {"prompt-for-password", "Prompts for password when not provided", false}; constexpr const char default_rpc_username[] = "monero"; + + boost::optional<tools::password_container> password_prompter(const char *prompt, bool verify) + { + auto pwd_container = tools::password_container::prompt(verify, prompt); + if (!pwd_container) + { + MERROR("failed to read wallet password"); + } + return pwd_container; + } } namespace tools @@ -131,7 +143,7 @@ namespace tools walvars = m_wallet; else { - tmpwal = tools::wallet2::make_dummy(*m_vm); + tmpwal = tools::wallet2::make_dummy(*m_vm, password_prompter); walvars = tmpwal.get(); } boost::optional<epee::net_utils::http::login> http_login{}; @@ -227,19 +239,6 @@ namespace tools return false; } //------------------------------------------------------------------------------------------------------------------------------ - uint64_t wallet_rpc_server::adjust_mixin(uint64_t mixin) - { - if (mixin < 4 && m_wallet->use_fork_rules(6, 10)) { - MWARNING("Requested ring size " << (mixin + 1) << " too low for hard fork 6, using 5"); - mixin = 4; - } - else if (mixin < 2 && m_wallet->use_fork_rules(2, 10)) { - MWARNING("Requested ring size " << (mixin + 1) << " too low for hard fork 2, using 3"); - mixin = 2; - } - return mixin; - } - //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd) { entry.txid = string_tools::pod_to_hex(pd.m_tx_hash); @@ -299,8 +298,9 @@ namespace tools entry.subaddr_index = { pd.m_subaddr_account, 0 }; } //------------------------------------------------------------------------------------------------------------------------------ - void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd) + void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &ppd) { + const tools::wallet2::payment_details &pd = ppd.m_pd; entry.txid = string_tools::pod_to_hex(pd.m_tx_hash); entry.payment_id = string_tools::pod_to_hex(payment_id); if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) @@ -311,6 +311,7 @@ namespace tools entry.unlock_time = pd.m_unlock_time; entry.fee = 0; // TODO entry.note = m_wallet->get_tx_note(pd.m_tx_hash); + entry.double_spend_seen = ppd.m_double_spend_seen; entry.type = "pool"; entry.subaddr_index = pd.m_subaddr_index; } @@ -605,7 +606,7 @@ namespace tools try { - uint64_t mixin = adjust_mixin(req.mixin); + uint64_t mixin = m_wallet->adjust_mixin(req.mixin); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); // reject proposed transactions if there are more than one. see on_transfer_split below. @@ -624,6 +625,8 @@ namespace tools if (req.get_tx_key) { res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key); + for (const crypto::secret_key& additional_tx_key : ptx_vector.back().additional_tx_keys) + res.tx_key += epee::string_tools::pod_to_hex(additional_tx_key); } res.fee = ptx_vector.back().fee; @@ -633,6 +636,13 @@ namespace tools tx_to_blob(ptx_vector.back().tx, blob); res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); } + if (req.get_tx_metadata) + { + std::ostringstream oss; + binary_archive<true> ar(oss); + ::serialization::serialize(ar, ptx_vector.back()); + res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } return true; } catch (const std::exception& e) @@ -665,7 +675,7 @@ namespace tools try { - uint64_t mixin = adjust_mixin(req.mixin); + uint64_t mixin = m_wallet->adjust_mixin(req.mixin); uint64_t ptx_amount; std::vector<wallet2::pending_tx> ptx_vector; LOG_PRINT_L2("on_transfer_split calling create_transactions_2"); @@ -680,12 +690,14 @@ namespace tools } // populate response with tx hashes - for (auto & ptx : ptx_vector) + for (const auto & ptx : ptx_vector) { res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); + for (const crypto::secret_key& additional_tx_key : ptx.additional_tx_keys) + res.tx_key_list.back() += epee::string_tools::pod_to_hex(additional_tx_key); } // Compute amount leaving wallet in tx. By convention dests does not include change outputs ptx_amount = 0; @@ -701,6 +713,13 @@ namespace tools tx_to_blob(ptx.tx, blob); res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); } + if (req.get_tx_metadata) + { + std::ostringstream oss; + binary_archive<true> ar(oss); + ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } return true; @@ -731,7 +750,7 @@ namespace tools m_wallet->commit_tx(ptx_vector); // populate response with tx hashes - for (auto & ptx : ptx_vector) + for (const auto & ptx : ptx_vector) { res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) @@ -745,6 +764,13 @@ namespace tools tx_to_blob(ptx.tx, blob); res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); } + if (req.get_tx_metadata) + { + std::ostringstream oss; + binary_archive<true> ar(oss); + ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } return true; @@ -782,14 +808,14 @@ namespace tools try { - uint64_t mixin = adjust_mixin(req.mixin); + uint64_t mixin = m_wallet->adjust_mixin(req.mixin); 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, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); if (!req.do_not_relay) m_wallet->commit_tx(ptx_vector); // populate response with tx hashes - for (auto & ptx : ptx_vector) + for (const auto & ptx : ptx_vector) { res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) @@ -802,6 +828,13 @@ namespace tools tx_to_blob(ptx.tx, blob); res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); } + if (req.get_tx_metadata) + { + std::ostringstream oss; + binary_archive<true> ar(oss); + ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } return true; @@ -813,6 +846,149 @@ namespace tools } return true; } +//------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::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) + { + std::vector<cryptonote::tx_destination_entry> dsts; + std::vector<uint8_t> extra; + + 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; + } + + // validate the transfer requested and populate dsts & extra + std::list<wallet_rpc::transfer_destination> destination; + destination.push_back(wallet_rpc::transfer_destination()); + destination.back().amount = 0; + destination.back().address = req.address; + if (!validate_transfer(destination, req.payment_id, dsts, extra, er)) + { + return false; + } + + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(req.key_image, ki)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "failed to parse key image"; + return false; + } + + try + { + uint64_t mixin = m_wallet->adjust_mixin(req.mixin); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon); + + if (ptx_vector.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "No outputs found"; + return false; + } + if (ptx_vector.size() > 1) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Multiple transactions are created, which is not supposed to happen"; + return false; + } + if (ptx_vector[0].selected_transfers.size() > 1) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "The transaction uses multiple inputs, which is not supposed to happen"; + return false; + } + + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); + + // populate response with tx hashes + const wallet2::pending_tx &ptx = ptx_vector[0]; + res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); + if (req.get_tx_key) + { + res.tx_key = epee::string_tools::pod_to_hex(ptx.tx_key); + } + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + binary_archive<true> ar(oss); + ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); + res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } + + return true; + } + catch (const tools::error::daemon_busy& e) + { + er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; + er.message = e.what(); + return false; + } + catch (const std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + er.message = e.what(); + return false; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_relay_tx(const wallet_rpc::COMMAND_RPC_RELAY_TX::request& req, wallet_rpc::COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.hex, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + std::stringstream ss; + ss << blob; + binary_archive<false> ba(ss); + + tools::wallet2::pending_tx ptx; + bool r = ::serialization::serialize(ba, ptx); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_TX_METADATA; + er.message = "Failed to parse tx metadata."; + return false; + } + + try + { + m_wallet->commit_tx(ptx); + } + catch(const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + er.message = "Failed to commit tx."; + return false; + } + + res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); + + return true; + } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er) { @@ -1069,6 +1245,7 @@ namespace tools rpc_transfers.tx_hash = epee::string_tools::pod_to_hex(td.m_txid); rpc_transfers.tx_size = txBlob.size(); rpc_transfers.subaddr_index = td.m_subaddr_index.minor; + rpc_transfers.key_image = req.verbose && td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : ""; res.transfers.push_back(rpc_transfers); } } @@ -1301,6 +1478,208 @@ namespace tools res.value = m_wallet->get_attribute(req.key); return true; } + bool wallet_rpc_server::on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + if (!m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) + { + er.code = WALLET_RPC_ERROR_CODE_NO_TXKEY; + er.message = "No tx secret key is stored for this tx"; + return false; + } + + std::ostringstream oss; + oss << epee::string_tools::pod_to_hex(tx_key); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); + res.tx_key = oss.str(); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_check_tx_key(const wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + std::string tx_key_str = req.tx_key; + crypto::secret_key tx_key; + if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), tx_key)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY; + er.message = "Tx key has invalid format"; + return false; + } + tx_key_str = tx_key_str.substr(64); + std::vector<crypto::secret_key> additional_tx_keys; + while (!tx_key_str.empty()) + { + 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())) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY; + er.message = "Tx key has invalid format"; + return false; + } + tx_key_str = tx_key_str.substr(64); + } + + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->testnet(), req.address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + + try + { + m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, res.received, res.in_pool, res.confirmations); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->testnet(), req.address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + + try + { + res.signature = m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, req.message); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_check_tx_proof(const wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->testnet(), req.address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + + try + { + uint64_t received; + bool in_pool; + uint64_t confirmations; + res.good = m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, req.message, req.signature, res.received, res.in_pool, res.confirmations); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_spend_proof(const wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + try + { + res.signature = m_wallet->get_spend_proof(txid, req.message); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_check_spend_proof(const wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + try + { + res.good = m_wallet->check_spend_proof(txid, req.message, req.signature); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er) { @@ -1357,9 +1736,9 @@ namespace tools { m_wallet->update_pool_state(); - std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; + std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> payments; m_wallet->get_unconfirmed_payments(payments, req.account_index, req.subaddr_indices); - for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { res.pool.push_back(wallet_rpc::transfer_entry()); fill_transfer_entry(res.pool.back(), i->first, i->second); } @@ -1430,10 +1809,10 @@ namespace tools m_wallet->update_pool_state(); - std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> pool_payments; + std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> pool_payments; m_wallet->get_unconfirmed_payments(pool_payments); - for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) { - if (i->second.m_tx_hash == txid) + for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) { + if (i->second.m_pd.m_tx_hash == txid) { fill_transfer_entry(res.transfer, i->first, i->second); return true; @@ -1809,7 +2188,7 @@ namespace tools command_line::add_arg(desc, arg_password); po::store(po::parse_command_line(argc, argv, desc), vm2); } - std::unique_ptr<tools::wallet2> wal = tools::wallet2::make_new(vm2).first; + std::unique_ptr<tools::wallet2> wal = tools::wallet2::make_new(vm2, nullptr).first; if (!wal) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -1883,7 +2262,7 @@ namespace tools } std::unique_ptr<tools::wallet2> wal = nullptr; try { - wal = tools::wallet2::make_from_file(vm2, wallet_file).first; + wal = tools::wallet2::make_from_file(vm2, wallet_file, nullptr).first; } catch (const std::exception& e) { @@ -1975,13 +2354,14 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_wallet_file); command_line::add_arg(desc_params, arg_from_json); 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( argc, argv, "monero-wallet-rpc [--wallet-file=<file>|--generate-from-json=<file>|--wallet-dir=<directory>] [--rpc-bind-port=<port>]", desc_params, po::positional_options_description(), + [](const std::string &s, bool emphasis){ epee::set_console_color(emphasis ? epee::console_color_white : epee::console_color_default, true); std::cout << s << std::endl; if (emphasis) epee::reset_console_color(); }, "monero-wallet-rpc.log", true ); @@ -1996,6 +2376,8 @@ int main(int argc, char** argv) { const auto wallet_file = command_line::get_arg(*vm, arg_wallet_file); const auto from_json = command_line::get_arg(*vm, arg_from_json); const auto wallet_dir = command_line::get_arg(*vm, arg_wallet_dir); + const auto prompt_for_password = command_line::get_arg(*vm, arg_prompt_for_password); + const auto password_prompt = prompt_for_password ? password_prompter : nullptr; if(!wallet_file.empty() && !from_json.empty()) { @@ -2018,11 +2400,19 @@ int main(int argc, char** argv) { LOG_PRINT_L0(tools::wallet_rpc_server::tr("Loading wallet...")); if(!wallet_file.empty()) { - wal = tools::wallet2::make_from_file(*vm, wallet_file).first; + wal = tools::wallet2::make_from_file(*vm, wallet_file, password_prompt).first; } else { - wal = tools::wallet2::make_from_json(*vm, from_json); + try + { + wal = tools::wallet2::make_from_json(*vm, from_json, password_prompt); + } + catch (const std::exception &e) + { + MERROR("Error creating wallet: " << e.what()); + return 1; + } } if (!wal) { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index b38726cb7..9455c4769 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -79,6 +79,8 @@ namespace tools MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT) MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST) MAP_JON_RPC_WE("sweep_all", on_sweep_all, wallet_rpc::COMMAND_RPC_SWEEP_ALL) + MAP_JON_RPC_WE("sweep_single", on_sweep_single, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE) + MAP_JON_RPC_WE("relay_tx", on_relay_tx, wallet_rpc::COMMAND_RPC_RELAY_TX) MAP_JON_RPC_WE("store", on_store, wallet_rpc::COMMAND_RPC_STORE) MAP_JON_RPC_WE("get_payments", on_get_payments, wallet_rpc::COMMAND_RPC_GET_PAYMENTS) MAP_JON_RPC_WE("get_bulk_payments", on_get_bulk_payments, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS) @@ -92,6 +94,12 @@ namespace tools MAP_JON_RPC_WE("get_tx_notes", on_get_tx_notes, wallet_rpc::COMMAND_RPC_GET_TX_NOTES) MAP_JON_RPC_WE("set_attribute", on_set_attribute, wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE) MAP_JON_RPC_WE("get_attribute", on_get_attribute, wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE) + MAP_JON_RPC_WE("get_tx_key", on_get_tx_key, wallet_rpc::COMMAND_RPC_GET_TX_KEY) + MAP_JON_RPC_WE("check_tx_key", on_check_tx_key, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY) + MAP_JON_RPC_WE("get_tx_proof", on_get_tx_proof, wallet_rpc::COMMAND_RPC_GET_TX_PROOF) + MAP_JON_RPC_WE("check_tx_proof", on_check_tx_proof, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF) + MAP_JON_RPC_WE("get_spend_proof", on_get_spend_proof, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF) + MAP_JON_RPC_WE("check_spend_proof", on_check_spend_proof, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF) MAP_JON_RPC_WE("get_transfers", on_get_transfers, wallet_rpc::COMMAND_RPC_GET_TRANSFERS) 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) @@ -126,6 +134,8 @@ namespace tools 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_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); + bool on_relay_tx(const wallet_rpc::COMMAND_RPC_RELAY_TX::request& req, wallet_rpc::COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& er); bool on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er); bool on_split_integrated_address(const wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er); bool on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er); @@ -138,6 +148,12 @@ namespace tools bool on_get_tx_notes(const wallet_rpc::COMMAND_RPC_GET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_GET_TX_NOTES::response& res, epee::json_rpc::error& er); bool on_set_attribute(const wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE::request& req, wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE::response& res, epee::json_rpc::error& er); bool on_get_attribute(const wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE::request& req, wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE::response& res, epee::json_rpc::error& er); + bool on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er); + bool on_check_tx_key(const wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::response& res, epee::json_rpc::error& er); + bool on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er); + bool on_check_tx_proof(const wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::response& res, epee::json_rpc::error& er); + bool on_get_spend_proof(const wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::response& res, epee::json_rpc::error& er); + bool on_check_spend_proof(const wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::response& res, epee::json_rpc::error& er); bool on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er); 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); @@ -163,9 +179,8 @@ namespace tools void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd); void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd); void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd); - void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd); + void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &pd); bool not_open(epee::json_rpc::error& er); - uint64_t adjust_mixin(uint64_t mixin); void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code); wallet2 *m_wallet; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index ffc2e2d49..e084d9e6d 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -291,6 +291,7 @@ namespace wallet_rpc bool get_tx_key; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -303,6 +304,7 @@ namespace wallet_rpc KV_SERIALIZE(get_tx_key) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -313,6 +315,7 @@ namespace wallet_rpc std::list<std::string> amount_keys; uint64_t fee; std::string tx_blob; + std::string tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -320,6 +323,7 @@ namespace wallet_rpc KV_SERIALIZE(amount_keys) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) + KV_SERIALIZE(tx_metadata) END_KV_SERIALIZE_MAP() }; }; @@ -338,6 +342,7 @@ namespace wallet_rpc bool get_tx_keys; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -350,6 +355,7 @@ namespace wallet_rpc KV_SERIALIZE(get_tx_keys) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -369,6 +375,7 @@ namespace wallet_rpc std::list<uint64_t> amount_list; std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; + std::list<std::string> tx_metadata_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -376,6 +383,7 @@ namespace wallet_rpc KV_SERIALIZE(amount_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) + KV_SERIALIZE(tx_metadata_list) END_KV_SERIALIZE_MAP() }; }; @@ -387,11 +395,13 @@ namespace wallet_rpc bool get_tx_keys; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(get_tx_keys) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -410,12 +420,14 @@ namespace wallet_rpc std::list<std::string> tx_key_list; std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; + std::list<std::string> tx_metadata_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) + KV_SERIALIZE(tx_metadata_list) END_KV_SERIALIZE_MAP() }; }; @@ -435,6 +447,7 @@ namespace wallet_rpc uint64_t below_amount; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) @@ -448,6 +461,7 @@ namespace wallet_rpc KV_SERIALIZE(below_amount) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -466,12 +480,88 @@ namespace wallet_rpc std::list<std::string> tx_key_list; std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; + std::list<std::string> tx_metadata_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) + KV_SERIALIZE(tx_metadata_list) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SWEEP_SINGLE + { + struct request + { + std::string address; + uint32_t priority; + uint64_t mixin; + uint64_t unlock_time; + std::string payment_id; + bool get_tx_key; + std::string key_image; + bool do_not_relay; + bool get_tx_hex; + bool get_tx_metadata; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(priority) + KV_SERIALIZE(mixin) + KV_SERIALIZE(unlock_time) + KV_SERIALIZE(payment_id) + KV_SERIALIZE(get_tx_key) + KV_SERIALIZE(key_image) + KV_SERIALIZE_OPT(do_not_relay, false) + KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_hash; + std::string tx_key; + uint64_t fee; + std::string tx_blob; + std::string tx_metadata; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_hash) + KV_SERIALIZE(tx_key) + KV_SERIALIZE(fee) + KV_SERIALIZE(tx_blob) + KV_SERIALIZE(tx_metadata) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_RELAY_TX + { + struct request + { + std::string hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(hex) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_hash; + std::string tx_key; + uint64_t fee; + std::string tx_blob; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_hash) + KV_SERIALIZE(tx_key) + KV_SERIALIZE(fee) + KV_SERIALIZE(tx_blob) END_KV_SERIALIZE_MAP() }; }; @@ -562,6 +652,7 @@ namespace wallet_rpc std::string tx_hash; uint64_t tx_size; uint32_t subaddr_index; + std::string key_image; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(amount) @@ -570,6 +661,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_size) KV_SERIALIZE(subaddr_index) + KV_SERIALIZE(key_image) END_KV_SERIALIZE_MAP() }; @@ -580,11 +672,13 @@ namespace wallet_rpc std::string transfer_type; uint32_t account_index; std::set<uint32_t> subaddr_indices; + bool verbose; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(transfer_type) KV_SERIALIZE(account_index) KV_SERIALIZE(subaddr_indices) + KV_SERIALIZE(verbose) END_KV_SERIALIZE_MAP() }; @@ -781,6 +875,114 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_TX_KEY + { + struct request + { + std::string txid; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_key; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_key) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CHECK_TX_KEY + { + struct request + { + std::string txid; + std::string tx_key; + std::string address; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(tx_key) + KV_SERIALIZE(address) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t received; + bool in_pool; + uint64_t confirmations; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(received) + KV_SERIALIZE(in_pool) + KV_SERIALIZE(confirmations) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_GET_TX_PROOF + { + struct request + { + std::string txid; + std::string address; + std::string message; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(address) + KV_SERIALIZE(message) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CHECK_TX_PROOF + { + struct request + { + std::string txid; + std::string address; + std::string message; + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(address) + KV_SERIALIZE(message) + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool good; + uint64_t received; + bool in_pool; + uint64_t confirmations; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(good) + KV_SERIALIZE(received) + KV_SERIALIZE(in_pool) + KV_SERIALIZE(confirmations) + END_KV_SERIALIZE_MAP() + }; + }; + struct transfer_entry { std::string txid; @@ -794,6 +996,7 @@ namespace wallet_rpc std::string type; uint64_t unlock_time; cryptonote::subaddress_index subaddr_index; + bool double_spend_seen; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(txid); @@ -807,9 +1010,58 @@ namespace wallet_rpc KV_SERIALIZE(type); KV_SERIALIZE(unlock_time) KV_SERIALIZE(subaddr_index); + KV_SERIALIZE(double_spend_seen) END_KV_SERIALIZE_MAP() }; + struct COMMAND_RPC_GET_SPEND_PROOF + { + struct request + { + std::string txid; + std::string message; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(message) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CHECK_SPEND_PROOF + { + struct request + { + std::string txid; + std::string message; + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(message) + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool good; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(good) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_TRANSFERS { struct request diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index e74e9110b..c3f3e20d1 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -54,3 +54,7 @@ #define WALLET_RPC_ERROR_CODE_WALLET_ALREADY_EXISTS -21 #define WALLET_RPC_ERROR_CODE_INVALID_PASSWORD -22 #define WALLET_RPC_ERROR_CODE_NO_WALLET_DIR -23 +#define WALLET_RPC_ERROR_CODE_NO_TXKEY -24 +#define WALLET_RPC_ERROR_CODE_WRONG_KEY -25 +#define WALLET_RPC_ERROR_CODE_BAD_HEX -26 +#define WALLET_RPC_ERROR_CODE_BAD_TX_METADATA -27 |