diff options
Diffstat (limited to 'src')
26 files changed, 680 insertions, 53 deletions
diff --git a/src/common/apply_permutation.h b/src/common/apply_permutation.h index 4fd952686..8684f14ce 100644 --- a/src/common/apply_permutation.h +++ b/src/common/apply_permutation.h @@ -30,6 +30,8 @@ // This algorithm is adapted from Raymond Chen's code: // https://blogs.msdn.microsoft.com/oldnewthing/20170109-00/?p=95145 +#pragma once + #include <vector> #include <functional> #include "misc_log_ex.h" diff --git a/src/common/util.cpp b/src/common/util.cpp index a4a435104..bcea70558 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -34,6 +34,8 @@ #include <gnu/libc-version.h> #endif +#include "unbound.h" + #include "include_base_utils.h" #include "file_io_utils.h" #include "wipeable_string.h" @@ -453,8 +455,7 @@ std::string get_nix_version_display_string() // namespace fs = boost::filesystem; // Windows < Vista: C:\Documents and Settings\Username\Application Data\CRYPTONOTE_NAME // Windows >= Vista: C:\Users\Username\AppData\Roaming\CRYPTONOTE_NAME - // Mac: ~/Library/Application Support/CRYPTONOTE_NAME - // Unix: ~/.CRYPTONOTE_NAME + // Unix & Mac: ~/.CRYPTONOTE_NAME std::string config_folder; #ifdef WIN32 @@ -466,15 +467,8 @@ std::string get_nix_version_display_string() pathRet = "/"; else pathRet = pszHome; -#ifdef MAC_OSX - // Mac - pathRet /= "Library/Application Support"; - config_folder = (pathRet + "/" + CRYPTONOTE_NAME); -#else - // Unix config_folder = (pathRet + "/." + CRYPTONOTE_NAME); #endif -#endif return config_folder; } @@ -522,6 +516,18 @@ std::string get_nix_version_display_string() return std::error_code(code, std::system_category()); } + static bool unbound_built_with_threads() + { + ub_ctx *ctx = ub_ctx_create(); + if (!ctx) return false; // cheat a bit, should not happen unless OOM + ub_ctx_zone_add(ctx, "monero", "unbound"); // this calls ub_ctx_finalize first, then errors out with UB_SYNTAX + // if no threads, bails out early with UB_NOERROR, otherwise fails with UB_AFTERFINAL id already finalized + bool with_threads = ub_ctx_async(ctx, 1) != 0; // UB_AFTERFINAL is not defined in public headers, check any error + ub_ctx_delete(ctx); + MINFO("libunbound was built " << (with_threads ? "with" : "without") << " threads"); + return with_threads; + } + bool sanitize_locale() { // boost::filesystem throws for "invalid" locales, such as en_US.UTF-8, or kjsdkfs, @@ -564,6 +570,9 @@ std::string get_nix_version_display_string() OPENSSL_init_ssl(0, NULL); #endif + if (!unbound_built_with_threads()) + MCLOG_RED(el::Level::Warning, "global", "libunbound was not built with threads enabled - crashes may occur"); + return true; } void set_strict_default_file_permissions(bool strict) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 4af987c3b..69288a2b7 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -2933,7 +2933,7 @@ bool Blockchain::check_fee(size_t blob_size, uint64_t fee) const needed_fee += (blob_size % 1024) ? 1 : 0; needed_fee *= fee_per_kb; - if (fee < needed_fee * 0.98) // keep a little buffer on acceptance + if (fee < needed_fee - needed_fee / 50) // keep a little 2% buffer on acceptance - no integer overflow { MERROR_VER("transaction fee is not enough: " << print_money(fee) << ", minimum fee: " << print_money(needed_fee)); return false; diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index fc2f4c343..a1682ef8c 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -271,7 +271,7 @@ namespace cryptonote { crypto::hash block_hash; uint64_t current_blockchain_height; - std::vector<size_t> missing_tx_indices; + std::vector<uint64_t> missing_tx_indices; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_VAL_POD_AS_BLOB(block_hash) diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 5d25d1058..700eceff3 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -273,7 +273,7 @@ namespace cryptonote { if (version < hshd.top_version) MCLOG_RED(el::Level::Warning, "global", context << " peer claims higher version that we think (" << - (unsigned)hshd.top_version << " for " << (hshd.current_height - 1) << "instead of " << (unsigned)version << + (unsigned)hshd.top_version << " for " << (hshd.current_height - 1) << " instead of " << (unsigned)version << ") - we may be forked from the network and a software upgrade may be needed"); return false; } @@ -876,6 +876,8 @@ namespace cryptonote } context.m_remote_blockchain_height = arg.current_blockchain_height; + if (context.m_remote_blockchain_height > m_core.get_target_blockchain_height()) + m_core.set_target_blockchain_height(context.m_remote_blockchain_height); std::vector<crypto::hash> block_hashes; block_hashes.reserve(arg.blocks.size()); diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index 49d3bc836..237105d06 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -102,7 +102,7 @@ target_link_libraries(daemon ${Boost_SYSTEM_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${ZMQ_LIB} - ${Readline_LIBRARY} + ${GNU_READLINE_LIBRARY} ${EXTRA_LIBRARIES}) set_property(TARGET daemon PROPERTY diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 6ac47fcb2..a47e74c71 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -269,7 +269,7 @@ int main(int argc, char const * argv[]) } else { - std::cerr << "Unknown command" << std::endl; + std::cerr << "Unknown command: " << command.front() << std::endl; return 1; } } diff --git a/src/gen_multisig/CMakeLists.txt b/src/gen_multisig/CMakeLists.txt index ff3c46862..8c534d213 100644 --- a/src/gen_multisig/CMakeLists.txt +++ b/src/gen_multisig/CMakeLists.txt @@ -43,8 +43,8 @@ target_link_libraries(gen_multisig ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} - ${Readline_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} + ${GNU_READLINE_LIBRARY} ${EXTRA_LIBRARIES}) add_dependencies(gen_multisig version) diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 269a9ba87..f8b96d7a4 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -1062,7 +1062,10 @@ namespace nodetool max_random_index = std::min<uint64_t>(local_peers_count -1, 20); random_index = get_random_index_with_fixed_probability(max_random_index); } else { - random_index = crypto::rand<size_t>() % m_peerlist.get_gray_peers_count(); + local_peers_count = m_peerlist.get_gray_peers_count(); + if (!local_peers_count) + return false; + random_index = crypto::rand<size_t>() % local_peers_count; } CHECK_AND_ASSERT_MES(random_index < local_peers_count, false, "random_starter_index < peers_local.size() failed!!"); diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 24ab08778..3c34a5637 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -52,6 +52,13 @@ namespace rct { return proof; } + bool verBulletproof(const Bulletproof &proof) + { + try { return bulletproof_VERIFY(proof); } + // we can get deep throws from ge_frombytes_vartime if input isn't valid + catch (...) { return false; } + } + //Borromean (c.f. gmax/andytoshi's paper) boroSig genBorromean(const key64 x, const key64 P1, const key64 P2, const bits indices) { key64 L[2], alpha; @@ -645,7 +652,7 @@ namespace rct { rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, amounts[i]); #ifdef DBG if (bulletproof) - CHECK_AND_ASSERT_THROW_MES(bulletproof_VERIFY(rv.p.bulletproofs[i]), "bulletproof_VERIFY failed on newly created proof"); + CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs[i]), "verBulletproof failed on newly created proof"); else CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof"); #endif @@ -725,7 +732,7 @@ namespace rct { rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, outamounts[i]); #ifdef DBG if (bulletproof) - CHECK_AND_ASSERT_THROW_MES(bulletproof_VERIFY(rv.p.bulletproofs[i]), "bulletproof_VERIFY failed on newly created proof"); + CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs[i]), "verBulletproof failed on newly created proof"); else CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof"); #endif @@ -817,7 +824,7 @@ namespace rct { for (size_t i = 0; i < rv.outPk.size(); i++) { tpool.submit(&waiter, [&, i] { if (rv.p.rangeSigs.empty()) - results[i] = bulletproof_VERIFY(rv.p.bulletproofs[i]); + results[i] = verBulletproof(rv.p.bulletproofs[i]); else results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); }); @@ -913,7 +920,7 @@ namespace rct { for (size_t i = 0; i < rv.outPk.size(); i++) { tpool.submit(&waiter, [&, i] { if (rv.p.rangeSigs.empty()) - results[i] = bulletproof_VERIFY(rv.p.bulletproofs[i]); + results[i] = verBulletproof(rv.p.bulletproofs[i]); else results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); }); diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index bf4371a4e..82d73728c 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -77,17 +77,25 @@ namespace cryptonote CHAIN_HTTP_TO_MAP2(connection_context); //forward http requests to uri map BEGIN_URI_MAP2() + MAP_URI_AUTO_JON2("/get_height", on_get_height, COMMAND_RPC_GET_HEIGHT) MAP_URI_AUTO_JON2("/getheight", on_get_height, COMMAND_RPC_GET_HEIGHT) + MAP_URI_AUTO_BIN2("/get_blocks.bin", on_get_blocks, COMMAND_RPC_GET_BLOCKS_FAST) MAP_URI_AUTO_BIN2("/getblocks.bin", on_get_blocks, COMMAND_RPC_GET_BLOCKS_FAST) + MAP_URI_AUTO_BIN2("/get_blocks_by_height.bin", on_get_blocks_by_height, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT) MAP_URI_AUTO_BIN2("/getblocks_by_height.bin", on_get_blocks_by_height, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT) + MAP_URI_AUTO_BIN2("/get_hashes.bin", on_get_hashes, COMMAND_RPC_GET_HASHES_FAST) MAP_URI_AUTO_BIN2("/gethashes.bin", on_get_hashes, COMMAND_RPC_GET_HASHES_FAST) MAP_URI_AUTO_BIN2("/get_o_indexes.bin", on_get_indexes, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES) + MAP_URI_AUTO_BIN2("/get_random_outs.bin", on_get_random_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS) MAP_URI_AUTO_BIN2("/getrandom_outs.bin", on_get_random_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS) MAP_URI_AUTO_BIN2("/get_outs.bin", on_get_outs_bin, COMMAND_RPC_GET_OUTPUTS_BIN) + MAP_URI_AUTO_BIN2("/get_random_rctouts.bin", on_get_random_rct_outs, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS) MAP_URI_AUTO_BIN2("/getrandom_rctouts.bin", on_get_random_rct_outs, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS) + MAP_URI_AUTO_JON2("/get_transactions", on_get_transactions, COMMAND_RPC_GET_TRANSACTIONS) MAP_URI_AUTO_JON2("/gettransactions", on_get_transactions, COMMAND_RPC_GET_TRANSACTIONS) MAP_URI_AUTO_JON2("/get_alt_blocks_hashes", on_get_alt_blocks_hashes, COMMAND_RPC_GET_ALT_BLOCKS_HASHES) MAP_URI_AUTO_JON2("/is_key_image_spent", on_is_key_image_spent, COMMAND_RPC_IS_KEY_IMAGE_SPENT) + MAP_URI_AUTO_JON2("/send_raw_transaction", on_send_raw_tx, COMMAND_RPC_SEND_RAW_TX) MAP_URI_AUTO_JON2("/sendrawtransaction", on_send_raw_tx, COMMAND_RPC_SEND_RAW_TX) MAP_URI_AUTO_JON2_IF("/start_mining", on_start_mining, COMMAND_RPC_START_MINING, !m_restricted) MAP_URI_AUTO_JON2_IF("/stop_mining", on_stop_mining, COMMAND_RPC_STOP_MINING, !m_restricted) @@ -101,6 +109,7 @@ namespace cryptonote MAP_URI_AUTO_JON2("/get_transaction_pool_hashes.bin", on_get_transaction_pool_hashes, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES) MAP_URI_AUTO_JON2("/get_transaction_pool_stats", on_get_transaction_pool_stats, COMMAND_RPC_GET_TRANSACTION_POOL_STATS) MAP_URI_AUTO_JON2_IF("/stop_daemon", on_stop_daemon, COMMAND_RPC_STOP_DAEMON, !m_restricted) + MAP_URI_AUTO_JON2("/get_info", on_get_info, COMMAND_RPC_GET_INFO) MAP_URI_AUTO_JON2("/getinfo", on_get_info, COMMAND_RPC_GET_INFO) MAP_URI_AUTO_JON2("/get_limit", on_get_limit, COMMAND_RPC_GET_LIMIT) MAP_URI_AUTO_JON2_IF("/set_limit", on_set_limit, COMMAND_RPC_SET_LIMIT, !m_restricted) @@ -110,14 +119,23 @@ namespace cryptonote MAP_URI_AUTO_JON2("/get_outs", on_get_outs, COMMAND_RPC_GET_OUTPUTS) MAP_URI_AUTO_JON2_IF("/update", on_update, COMMAND_RPC_UPDATE, !m_restricted) BEGIN_JSON_RPC_MAP("/json_rpc") + MAP_JON_RPC("get_block_count", on_getblockcount, COMMAND_RPC_GETBLOCKCOUNT) MAP_JON_RPC("getblockcount", on_getblockcount, COMMAND_RPC_GETBLOCKCOUNT) + MAP_JON_RPC_WE("on_get_block_hash", on_getblockhash, COMMAND_RPC_GETBLOCKHASH) MAP_JON_RPC_WE("on_getblockhash", on_getblockhash, COMMAND_RPC_GETBLOCKHASH) + MAP_JON_RPC_WE("get_block_template", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) MAP_JON_RPC_WE("getblocktemplate", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) + MAP_JON_RPC_WE("submit_block", on_submitblock, COMMAND_RPC_SUBMITBLOCK) MAP_JON_RPC_WE("submitblock", on_submitblock, COMMAND_RPC_SUBMITBLOCK) + MAP_JON_RPC_WE("get_last_block_header", on_get_last_block_header, COMMAND_RPC_GET_LAST_BLOCK_HEADER) MAP_JON_RPC_WE("getlastblockheader", on_get_last_block_header, COMMAND_RPC_GET_LAST_BLOCK_HEADER) + MAP_JON_RPC_WE("get_block_header_by_hash", on_get_block_header_by_hash, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH) MAP_JON_RPC_WE("getblockheaderbyhash", on_get_block_header_by_hash, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH) + MAP_JON_RPC_WE("get_block_header_by_height", on_get_block_header_by_height, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT) MAP_JON_RPC_WE("getblockheaderbyheight", on_get_block_header_by_height, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT) + MAP_JON_RPC_WE("get_block_headers_range", on_get_block_headers_range, COMMAND_RPC_GET_BLOCK_HEADERS_RANGE) MAP_JON_RPC_WE("getblockheadersrange", on_get_block_headers_range, COMMAND_RPC_GET_BLOCK_HEADERS_RANGE) + MAP_JON_RPC_WE("get_block", on_get_block, COMMAND_RPC_GET_BLOCK) MAP_JON_RPC_WE("getblock", on_get_block, COMMAND_RPC_GET_BLOCK) MAP_JON_RPC_WE_IF("get_connections", on_get_connections, COMMAND_RPC_GET_CONNECTIONS, !m_restricted) MAP_JON_RPC_WE("get_info", on_get_info_json, COMMAND_RPC_GET_INFO) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index ad0bff077..00b553bad 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1653,8 +1653,8 @@ namespace cryptonote struct response { std::string status; - uint64_t limit_up; - uint64_t limit_down; + int64_t limit_up; + int64_t limit_down; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) diff --git a/src/rpc/zmq_server.cpp b/src/rpc/zmq_server.cpp index afdff2328..6f06f4497 100644 --- a/src/rpc/zmq_server.cpp +++ b/src/rpc/zmq_server.cpp @@ -102,7 +102,7 @@ bool ZmqServer::addTCPSocket(std::string address, std::string port) rep_socket.reset(new zmq::socket_t(context, ZMQ_REP)); - rep_socket->setsockopt(ZMQ_RCVTIMEO, DEFAULT_RPC_RECV_TIMEOUT_MS); + rep_socket->setsockopt(ZMQ_RCVTIMEO, &DEFAULT_RPC_RECV_TIMEOUT_MS, sizeof(DEFAULT_RPC_RECV_TIMEOUT_MS)); std::string bind_address = addr_prefix + address + std::string(":") + port; rep_socket->bind(bind_address.c_str()); diff --git a/src/simplewallet/CMakeLists.txt b/src/simplewallet/CMakeLists.txt index beaacf0e9..f190ada8d 100644 --- a/src/simplewallet/CMakeLists.txt +++ b/src/simplewallet/CMakeLists.txt @@ -55,8 +55,8 @@ target_link_libraries(simplewallet ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} - ${Readline_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} + ${GNU_READLINE_LIBRARY} ${EXTRA_LIBRARIES}) set_property(TARGET simplewallet PROPERTY diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index a4d6f0c2d..b6b6da441 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -122,7 +122,7 @@ namespace const command_line::arg_descriptor<std::string> arg_electrum_seed = {"electrum-seed", sw::tr("Specify Electrum seed for wallet recovery/creation"), ""}; const command_line::arg_descriptor<bool> arg_restore_deterministic_wallet = {"restore-deterministic-wallet", sw::tr("Recover wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor<bool> arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false}; - const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Create non-deterministic view and spend keys"), false}; + const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Generate non-deterministic view and spend keys"), false}; const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", sw::tr("Enable commands which rely on a trusted daemon"), false}; const command_line::arg_descriptor<bool> arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false}; const command_line::arg_descriptor<uint64_t> arg_restore_height = {"restore-height", sw::tr("Restore from specific blockchain height"), 0}; @@ -1690,6 +1690,16 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::check_spend_proof, this, _1), tr("check_spend_proof <txid> <signature_file> [<message>]"), tr("Check a signature proving that the signer generated <txid>, optionally with a challenge string <message>.")); + m_cmd_binder.set_handler("get_reserve_proof", + boost::bind(&simple_wallet::get_reserve_proof, this, _1), + tr("get_reserve_proof (all|<amount>) [<message>]"), + tr("Generate a signature proving that you own at least this much, optionally with a challenge string <message>.\n" + "If 'all' is specified, you prove the entire sum of all of your existing accounts' balances.\n" + "Otherwise, you prove the reserve of the smallest possible amount above <amount> available in your current account.")); + m_cmd_binder.set_handler("check_reserve_proof", + boost::bind(&simple_wallet::check_reserve_proof, this, _1), + tr("check_reserve_proof <address> <signature_file> [<message>]"), + tr("Check a signature proving that the owner of <address> holds at least this much, optionally with a challenge string <message>.")); m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]"), @@ -1770,11 +1780,11 @@ simple_wallet::simple_wallet() tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets")); m_cmd_binder.set_handler("export_multisig_info", boost::bind(&simple_wallet::export_multisig, this, _1), - tr("export_multisig <filename>"), + tr("export_multisig_info <filename>"), tr("Export multisig info for other participants")); m_cmd_binder.set_handler("import_multisig_info", boost::bind(&simple_wallet::import_multisig, this, _1), - tr("import_multisig <filename> [<filename>...]"), + tr("import_multisig_info <filename> [<filename>...]"), tr("Import multisig info from other participants")); m_cmd_binder.set_handler("sign_multisig", boost::bind(&simple_wallet::sign_multisig, this, _1), @@ -1786,7 +1796,7 @@ simple_wallet::simple_wallet() tr("Submit a signed multisig transaction from a file")); m_cmd_binder.set_handler("export_raw_multisig_tx", boost::bind(&simple_wallet::export_raw_multisig, this, _1), - tr("export_raw_multisig <filename>"), + tr("export_raw_multisig_tx <filename>"), tr("Export a signed multisig transaction to a file")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), @@ -3832,7 +3842,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri else { if (nblocks[0].first > m_wallet->get_confirm_backlog_threshold()) - prompt << (boost::format(tr("There is currently a %u block backlog at that fee level. Is this okay? (Y/Yes/N/No)")) % nblocks[0].first).str(); + prompt << (boost::format(tr("There is currently a %u block backlog at that fee level. Is this okay? (Y/Yes/N/No): ")) % nblocks[0].first).str(); } } catch (const std::exception &e) @@ -4250,7 +4260,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a print_money(total_fee); } else { - prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % + prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) % print_money(total_sent) % print_money(total_fee); } @@ -4455,7 +4465,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) std::ostringstream prompt; if (!print_ring_members(ptx_vector, prompt)) return true; - prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % + prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) % print_money(total_sent) % print_money(total_fee); std::string accepted = input_line(prompt.str()); @@ -4547,8 +4557,6 @@ bool simple_wallet::donate(const std::vector<std::string> &args_) fail_msg_writer() << tr("usage: donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]"); return true; } - // Hardcode Monero's donation address (see #1447) - const std::string address_str = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; std::string amount_str; std::string payment_id_str; // get payment id and pop @@ -4564,11 +4572,11 @@ bool simple_wallet::donate(const std::vector<std::string> &args_) amount_str = local_args.back(); local_args.pop_back(); // push back address, amount, payment id - local_args.push_back(address_str); + local_args.push_back(MONERO_DONATION_ADDR); local_args.push_back(amount_str); if (!payment_id_str.empty()) local_args.push_back(payment_id_str); - message_writer() << tr("Donating ") << amount_str << " to The Monero Project (donate.getmonero.org/44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A)."; + message_writer() << tr("Donating ") << amount_str << " to The Monero Project (donate.getmonero.org/"<< MONERO_DONATION_ADDR <<")."; transfer_new(local_args); return true; } @@ -5137,6 +5145,110 @@ bool simple_wallet::check_spend_proof(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args) +{ + if(args.size() != 1 && args.size() != 2) { + fail_msg_writer() << tr("usage: get_reserve_proof (all|<amount>) [<message>]"); + return true; + } + + if (m_wallet->watch_only() || m_wallet->multisig()) + { + fail_msg_writer() << tr("The reserve proof can be generated only by a full wallet"); + return true; + } + + boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; + if (args[0] != "all") + { + account_minreserve = std::pair<uint32_t, uint64_t>(); + account_minreserve->first = m_current_subaddress_account; + if (!cryptonote::parse_amount(account_minreserve->second, args[0])) + { + fail_msg_writer() << tr("amount is wrong: ") << args[0]; + return true; + } + } + + if (!try_connect_to_daemon()) + { + fail_msg_writer() << tr("failed to connect to the daemon"); + return true; + } + + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + LOCK_IDLE_SCOPE(); + + try + { + const std::string sig_str = m_wallet->get_reserve_proof(account_minreserve, args.size() == 2 ? args[1] : ""); + const std::string filename = "monero_reserve_proof"; + if (epee::file_io_utils::save_string_to_file(filename, sig_str)) + success_msg_writer() << tr("signature file saved to: ") << filename; + else + fail_msg_writer() << tr("failed to save signature file"); + } + catch (const std::exception &e) + { + fail_msg_writer() << e.what(); + } + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_reserve_proof(const std::vector<std::string> &args) +{ + if(args.size() != 2 && args.size() != 3) { + fail_msg_writer() << tr("usage: check_reserve_proof <address> <signature_file> [<message>]"); + return true; + } + + if (!try_connect_to_daemon()) + { + fail_msg_writer() << tr("failed to connect to the daemon"); + return true; + } + + cryptonote::address_parse_info info; + if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[0], oa_prompter)) + { + fail_msg_writer() << tr("failed to parse address"); + return true; + } + if (info.is_subaddress) + { + fail_msg_writer() << tr("Address must not be a subaddress"); + return true; + } + + std::string sig_str; + if (!epee::file_io_utils::load_file_to_string(args[1], sig_str)) + { + fail_msg_writer() << tr("failed to load signature file"); + return true; + } + + LOCK_IDLE_SCOPE(); + + try + { + uint64_t total, spent; + if (m_wallet->check_reserve_proof(info.address, args.size() == 3 ? args[2] : "", sig_str, total, spent)) + { + success_msg_writer() << boost::format(tr("Good signature -- total: %s, spent: %s, unspent: %s")) % print_money(total) % print_money(spent) % print_money(total - spent); + } + else + { + fail_msg_writer() << tr("Bad signature"); + } + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + return true; +} +//---------------------------------------------------------------------------------------------------- static std::string get_human_readable_timestamp(uint64_t ts) { char buffer[64]; @@ -6636,7 +6748,8 @@ int main(int argc, char* argv[]) std::vector<std::string> command = command_line::get_arg(*vm, arg_command); if (!command.empty()) { - w.process_command(command); + if (!w.process_command(command)) + fail_msg_writer() << tr("Unknown command: ") << command.front(); w.stop(); w.deinit(); } diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 024077ca1..086076be0 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -49,6 +49,8 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.simplewallet" +// Hardcode Monero's donation address (see #1447) +constexpr const char MONERO_DONATION_ADDR[] = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; /*! * \namespace cryptonote @@ -170,6 +172,8 @@ namespace cryptonote bool check_tx_proof(const std::vector<std::string> &args); bool get_spend_proof(const std::vector<std::string> &args); bool check_spend_proof(const std::vector<std::string> &args); + bool get_reserve_proof(const std::vector<std::string> &args); + bool check_reserve_proof(const std::vector<std::string> &args); bool show_transfers(const std::vector<std::string> &args); bool unspent_outputs(const std::vector<std::string> &args); bool rescan_blockchain(const std::vector<std::string> &args); diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 2d664ba15..abecabbc3 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -126,6 +126,6 @@ if (BUILD_GUI_DEPS) endif() install(TARGETS wallet_merged ARCHIVE DESTINATION ${lib_folder}) - - add_subdirectory(api) endif() + +add_subdirectory(api) diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt index 26127b75c..60d6970bd 100644 --- a/src/wallet/api/CMakeLists.txt +++ b/src/wallet/api/CMakeLists.txt @@ -78,6 +78,8 @@ target_link_libraries(wallet_api PRIVATE ${EXTRA_LIBRARIES}) +set_property(TARGET wallet_api PROPERTY EXCLUDE_FROM_ALL TRUE) +set_property(TARGET obj_wallet_api PROPERTY EXCLUDE_FROM_ALL TRUE) if(IOS) set(lib_folder lib-${ARCH}) diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index fd0b65866..f96640d6e 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1576,6 +1576,55 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string } } +std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const { + try + { + m_status = Status_Ok; + boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; + if (!all) + { + account_minreserve = std::make_pair(account_index, amount); + } + return m_wallet->get_reserve_proof(account_minreserve, message); + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return ""; + } +} + +bool WalletImpl::checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const { + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse address"); + return false; + } + if (info.is_subaddress) + { + m_status = Status_Error; + m_errorString = tr("Address must not be a subaddress"); + return false; + } + + good = false; + try + { + m_status = Status_Ok; + good = m_wallet->check_reserve_proof(info.address, message, signature, total, spent); + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; + } +} + std::string WalletImpl::signMessage(const std::string &message) { return m_wallet->sign(message); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 01359ffc6..0b9fc851b 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -142,6 +142,8 @@ public: 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 getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const; + virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const; virtual std::string signMessage(const std::string &message); virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const; virtual void startRefresh(); diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index ab1a48d6e..acecbb9dd 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -706,6 +706,12 @@ struct Wallet 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 getReserveProof - Generates a proof that proves the reserve of unspent funds + * Parameters `account_index` and `amount` are ignored when `all` is true + */ + virtual std::string getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const = 0; + virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const = 0; /* * \brief signMessage - sign a message with the spend private key diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 2c0aa36ef..0c2b7e984 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -357,7 +357,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const bool deprecated_wallet = restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) || crypto::ElectrumWords::get_is_old_style_seed(field_seed)); THROW_WALLET_EXCEPTION_IF(deprecated_wallet, tools::error::wallet_internal_error, - tools::wallet2::tr("Cannot create deprecated wallets from JSON")); + tools::wallet2::tr("Cannot generate deprecated wallets from JSON")); wallet.reset(make_basic(vm, opts, password_prompter).release()); wallet->set_refresh_from_block_height(field_scan_from_height); @@ -544,8 +544,7 @@ crypto::hash8 get_short_payment_id(const tools::wallet2::pending_tx &ptx) { crypto::hash8 payment_id8 = null_hash8; std::vector<tx_extra_field> tx_extra_fields; - if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) - return payment_id8; + parse_tx_extra(ptx.tx.extra, tx_extra_fields); // ok if partially parsed cryptonote::tx_extra_nonce extra_nonce; if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { @@ -1460,14 +1459,12 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans entry.first->second.m_change = received; std::vector<tx_extra_field> tx_extra_fields; - if(parse_tx_extra(tx.extra, tx_extra_fields)) + parse_tx_extra(tx.extra, tx_extra_fields); // ok if partially parsed + tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { - tx_extra_nonce extra_nonce; - if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) - { - // we do not care about failure here - get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id); - } + // we do not care about failure here + get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id); } entry.first->second.m_subaddr_account = subaddr_account; entry.first->second.m_subaddr_indices = subaddr_indices; @@ -2983,6 +2980,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); + add_subaddress_account(tr("Primary account")); if (!wallet_.empty()) store(); @@ -4239,8 +4237,7 @@ std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts( crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const { std::vector<tx_extra_field> tx_extra_fields; - if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) - return crypto::null_hash; + parse_tx_extra(ptx.tx.extra, tx_extra_fields); // ok if partially parsed tx_extra_nonce extra_nonce; crypto::hash payment_id = null_hash; if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) @@ -7947,6 +7944,251 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account return false; } +std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t, uint64_t>> &account_minreserve, const std::string &message) +{ + THROW_WALLET_EXCEPTION_IF(m_watch_only || m_multisig, error::wallet_internal_error, "Reserve proof can only be generated by a full wallet"); + THROW_WALLET_EXCEPTION_IF(balance_all() == 0, error::wallet_internal_error, "Zero balance"); + THROW_WALLET_EXCEPTION_IF(account_minreserve && balance(account_minreserve->first) < account_minreserve->second, error::wallet_internal_error, + "Not enough balance in this account for the requested minimum reserve amount"); + + // determine which outputs to include in the proof + std::vector<size_t> selected_transfers; + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details &td = m_transfers[i]; + if (!td.m_spent && (!account_minreserve || account_minreserve->first == td.m_subaddr_index.major)) + selected_transfers.push_back(i); + } + + if (account_minreserve) + { + // minimize the number of outputs included in the proof, by only picking the N largest outputs that can cover the requested min reserve amount + std::sort(selected_transfers.begin(), selected_transfers.end(), [&](const size_t a, const size_t b) + { return m_transfers[a].amount() > m_transfers[b].amount(); }); + while (selected_transfers.size() >= 2 && m_transfers[selected_transfers[1]].amount() >= account_minreserve->second) + selected_transfers.erase(selected_transfers.begin()); + size_t sz = 0; + uint64_t total = 0; + while (total < account_minreserve->second) + { + total += m_transfers[selected_transfers[sz]].amount(); + ++sz; + } + selected_transfers.resize(sz); + } + + // compute signature prefix hash + std::string prefix_data = message; + prefix_data.append((const char*)&m_account.get_keys().m_account_address, sizeof(cryptonote::account_public_address)); + for (size_t i = 0; i < selected_transfers.size(); ++i) + { + prefix_data.append((const char*)&m_transfers[selected_transfers[i]].m_key_image, sizeof(crypto::key_image)); + } + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + // generate proof entries + std::vector<reserve_proof_entry> proofs(selected_transfers.size()); + std::unordered_set<cryptonote::subaddress_index> subaddr_indices = { {0,0} }; + for (size_t i = 0; i < selected_transfers.size(); ++i) + { + const transfer_details &td = m_transfers[selected_transfers[i]]; + reserve_proof_entry& proof = proofs[i]; + proof.txid = td.m_txid; + proof.index_in_tx = td.m_internal_output_index; + proof.key_image = td.m_key_image; + subaddr_indices.insert(td.m_subaddr_index); + + // get tx pub key + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == crypto::null_pkey, error::wallet_internal_error, "The tx public key isn't found"); + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + + // determine which tx pub key was used for deriving the output key + const crypto::public_key *tx_pub_key_used = &tx_pub_key; + for (int i = 0; i < 2; ++i) + { + proof.shared_secret = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(*tx_pub_key_used), rct::sk2rct(m_account.get_keys().m_view_secret_key))); + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), + error::wallet_internal_error, "Failed to generate key derivation"); + crypto::public_key subaddress_spendkey; + THROW_WALLET_EXCEPTION_IF(!derive_subaddress_public_key(td.get_public_key(), derivation, proof.index_in_tx, subaddress_spendkey), + error::wallet_internal_error, "Failed to derive subaddress public key"); + if (m_subaddresses.count(subaddress_spendkey) == 1) + break; + THROW_WALLET_EXCEPTION_IF(additional_tx_pub_keys.empty(), error::wallet_internal_error, + "Normal tx pub key doesn't derive the expected output, while the additional tx pub keys are empty"); + THROW_WALLET_EXCEPTION_IF(i == 1, error::wallet_internal_error, + "Neither normal tx pub key nor additional tx pub key derive the expected output key"); + tx_pub_key_used = &additional_tx_pub_keys[proof.index_in_tx]; + } + + // generate signature for shared secret + crypto::generate_tx_proof(prefix_hash, m_account.get_keys().m_account_address.m_view_public_key, *tx_pub_key_used, boost::none, proof.shared_secret, m_account.get_keys().m_view_secret_key, proof.shared_secret_sig); + + // derive ephemeral secret key + crypto::key_image ki; + cryptonote::keypair ephemeral; + const bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, td.get_public_key(), tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, ephemeral, ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(ephemeral.pub != td.get_public_key(), error::wallet_internal_error, "Derived public key doesn't agree with the stored one"); + + // generate signature for key image + const std::vector<const crypto::public_key*> pubs = { &ephemeral.pub }; + crypto::generate_ring_signature(prefix_hash, td.m_key_image, &pubs[0], 1, ephemeral.sec, 0, &proof.key_image_sig); + } + + // collect all subaddress spend keys that received those outputs and generate their signatures + std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; + for (const cryptonote::subaddress_index &index : subaddr_indices) + { + crypto::secret_key subaddr_spend_skey = m_account.get_keys().m_spend_secret_key; + if (!index.is_zero()) + { + crypto::secret_key m = cryptonote::get_subaddress_secret_key(m_account.get_keys().m_view_secret_key, index); + crypto::secret_key tmp = subaddr_spend_skey; + sc_add((unsigned char*)&subaddr_spend_skey, (unsigned char*)&m, (unsigned char*)&tmp); + } + crypto::public_key subaddr_spend_pkey; + secret_key_to_public_key(subaddr_spend_skey, subaddr_spend_pkey); + crypto::generate_signature(prefix_hash, subaddr_spend_pkey, subaddr_spend_skey, subaddr_spendkeys[subaddr_spend_pkey]); + } + + // serialize & encode + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << proofs << subaddr_spendkeys; + return "ReserveProofV1" + tools::base58::encode(oss.str()); +} + +bool wallet2::check_reserve_proof(const cryptonote::account_public_address &address, const std::string &message, const std::string &sig_str, uint64_t &total, uint64_t &spent) +{ + uint32_t rpc_version; + THROW_WALLET_EXCEPTION_IF(!check_connection(&rpc_version), error::wallet_internal_error, "Failed to connect to daemon: " + get_daemon_address()); + THROW_WALLET_EXCEPTION_IF(rpc_version < MAKE_CORE_RPC_VERSION(1, 0), error::wallet_internal_error, "Daemon RPC version is too old"); + + static constexpr char header[] = "ReserveProofV1"; + THROW_WALLET_EXCEPTION_IF(!boost::string_ref{sig_str}.starts_with(header), error::wallet_internal_error, + "Signature header check error"); + + std::string sig_decoded; + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(std::strlen(header)), sig_decoded), error::wallet_internal_error, + "Signature decoding error"); + + std::istringstream iss(sig_decoded); + boost::archive::portable_binary_iarchive ar(iss); + std::vector<reserve_proof_entry> proofs; + std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; + ar >> proofs >> subaddr_spendkeys; + + THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(address.m_spend_public_key) == 0, error::wallet_internal_error, + "The given address isn't found in the proof"); + + // compute signature prefix hash + std::string prefix_data = message; + prefix_data.append((const char*)&address, sizeof(cryptonote::account_public_address)); + for (size_t i = 0; i < proofs.size(); ++i) + { + prefix_data.append((const char*)&proofs[i].key_image, sizeof(crypto::key_image)); + } + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + // fetch txes from daemon + COMMAND_RPC_GET_TRANSACTIONS::request gettx_req; + COMMAND_RPC_GET_TRANSACTIONS::response gettx_res; + for (size_t i = 0; i < proofs.size(); ++i) + gettx_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(proofs[i].txid)); + m_daemon_rpc_mutex.lock(); + bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || gettx_res.txs.size() != proofs.size(), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + // check spent status + COMMAND_RPC_IS_KEY_IMAGE_SPENT::request kispent_req; + COMMAND_RPC_IS_KEY_IMAGE_SPENT::response kispent_res; + for (size_t i = 0; i < proofs.size(); ++i) + kispent_req.key_images.push_back(epee::string_tools::pod_to_hex(proofs[i].key_image)); + m_daemon_rpc_mutex.lock(); + ok = epee::net_utils::invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(), + error::wallet_internal_error, "Failed to get key image spent status from daemon"); + + total = spent = 0; + for (size_t i = 0; i < proofs.size(); ++i) + { + const reserve_proof_entry& proof = proofs[i]; + THROW_WALLET_EXCEPTION_IF(gettx_res.txs[i].in_pool, error::wallet_internal_error, "Tx is unconfirmed"); + + cryptonote::blobdata tx_data; + ok = string_tools::parse_hexstr_to_binbuff(gettx_res.txs[i].as_hex, 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 != proof.txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + + THROW_WALLET_EXCEPTION_IF(proof.index_in_tx >= tx.vout.size(), error::wallet_internal_error, "index_in_tx is out of bound"); + + const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[proof.index_in_tx].target)); + THROW_WALLET_EXCEPTION_IF(!out_key, error::wallet_internal_error, "Output key wasn't found") + + // get tx pub key + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == crypto::null_pkey, error::wallet_internal_error, "The tx public key isn't found"); + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + + // check singature for shared secret + ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, proof.shared_secret, proof.shared_secret_sig); + if (!ok && additional_tx_pub_keys.size() == tx.vout.size()) + ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[proof.index_in_tx], boost::none, proof.shared_secret, proof.shared_secret_sig); + if (!ok) + return false; + + // check signature for key image + const std::vector<const crypto::public_key*> pubs = { &out_key->key }; + ok = crypto::check_ring_signature(prefix_hash, proof.key_image, &pubs[0], 1, &proof.key_image_sig); + if (!ok) + return false; + + // check if the address really received the fund + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); + crypto::public_key subaddr_spendkey; + crypto::derive_subaddress_public_key(out_key->key, derivation, proof.index_in_tx, subaddr_spendkey); + THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(subaddr_spendkey) == 0, error::wallet_internal_error, + "The address doesn't seem to have received the fund"); + + // check amount + uint64_t amount = tx.vout[proof.index_in_tx].amount; + if (amount == 0) + { + // decode rct + crypto::secret_key shared_secret; + crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret)); + amount = rct::h2d(ecdh_info.amount); + } + total += amount; + if (kispent_res.spent_status[i]) + spent += amount; + } + + // check signatures for all subaddress spend keys + for (const auto &i : subaddr_spendkeys) + { + if (!crypto::check_signature(prefix_hash, i.first, i.second)) + return false; + } + return true; +} + std::string wallet2::get_wallet_file() const { return m_wallet_file; @@ -8637,11 +8879,8 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail // the hot wallet wouldn't have known about key images (except if we already exported them) cryptonote::keypair in_ephemeral; - std::vector<tx_extra_field> tx_extra_fields; THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i)); - THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(td.m_tx.extra, tx_extra_fields), error::wallet_internal_error, - "Transaction extra has unsupported format at index " + boost::lexical_cast<std::string>(i)); crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td); const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 2fbe05f89..04d789f18 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -433,6 +433,16 @@ namespace tools bool m_is_subaddress; }; + struct reserve_proof_entry + { + crypto::hash txid; + uint64_t index_in_tx; + crypto::public_key shared_secret; + crypto::key_image key_image; + crypto::signature shared_secret_sig; + crypto::signature key_image_sig; + }; + typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry; /*! @@ -836,6 +846,26 @@ namespace tools 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 Generates a proof that proves the reserve of unspent funds + * \param account_minreserve When specified, collect outputs only belonging to the given account and prove the smallest reserve above the given amount + * When unspecified, proves for all unspent outputs across all accounts + * \param message Arbitrary challenge message to be signed together + * \return Signature string + */ + std::string get_reserve_proof(const boost::optional<std::pair<uint32_t, uint64_t>> &account_minreserve, const std::string &message); + /*! + * \brief Verifies a proof of reserve + * \param address The signer's address + * \param message Challenge message used for signing + * \param sig_str Signature string + * \param total [OUT] the sum of funds included in the signature + * \param spent [OUT] the sum of spent funds included in the signature + * \return true if the signature verifies correctly + */ + bool check_reserve_proof(const cryptonote::account_public_address &address, const std::string &message, const std::string &sig_str, uint64_t &total, uint64_t &spent); + /*! * \brief GUI Address book get/store */ @@ -1119,6 +1149,7 @@ 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::reserve_proof_entry, 0) 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, 2) @@ -1402,6 +1433,17 @@ namespace boost } template <class Archive> + inline void serialize(Archive& a, tools::wallet2::reserve_proof_entry& x, const boost::serialization::version_type ver) + { + a & x.txid; + a & x.index_in_tx; + a & x.shared_secret; + a & x.key_image; + a & x.shared_secret_sig; + a & x.key_image_sig; + } + + template <class Archive> inline void serialize(Archive &a, tools::wallet2::unsigned_tx_set &x, const boost::serialization::version_type ver) { a & x.txes; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index fc2c43c04..3bb69f2be 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -258,6 +258,7 @@ namespace tools entry.note = m_wallet->get_tx_note(pd.m_tx_hash); entry.type = "in"; entry.subaddr_index = pd.m_subaddr_index; + entry.address = m_wallet->get_subaddress_as_str(pd.m_subaddr_index); } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd) @@ -283,6 +284,7 @@ namespace tools entry.type = "out"; entry.subaddr_index = { pd.m_subaddr_account, 0 }; + entry.address = m_wallet->get_subaddress_as_str({pd.m_subaddr_account, 0}); } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd) @@ -301,6 +303,7 @@ namespace tools entry.note = m_wallet->get_tx_note(txid); entry.type = is_failed ? "failed" : "pending"; entry.subaddr_index = { pd.m_subaddr_account, 0 }; + entry.address = m_wallet->get_subaddress_as_str({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::pool_payment_details &ppd) @@ -319,6 +322,7 @@ namespace tools entry.double_spend_seen = ppd.m_double_spend_seen; entry.type = "pool"; entry.subaddr_index = pd.m_subaddr_index; + entry.address = m_wallet->get_subaddress_as_str(pd.m_subaddr_index); } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er) @@ -1721,6 +1725,66 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; + if (!req.all) + { + if (req.account_index >= m_wallet->get_num_subaddress_accounts()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Account index is out of bound"; + return false; + } + account_minreserve = std::make_pair(req.account_index, req.amount); + } + + try + { + res.signature = m_wallet->get_reserve_proof(account_minreserve, 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_reserve_proof(const wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + 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; + } + if (info.is_subaddress) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Address must not be a subaddress"; + return false; + } + + try + { + res.good = m_wallet->check_reserve_proof(info.address, req.message, req.signature, res.total, res.spent); + } + 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) { if (!m_wallet) return not_open(er); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 88f2a85a4..a70d8626e 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -67,6 +67,8 @@ namespace tools BEGIN_URI_MAP2() BEGIN_JSON_RPC_MAP("/json_rpc") + MAP_JON_RPC_WE("get_balance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE) + MAP_JON_RPC_WE("get_address", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS) MAP_JON_RPC_WE("getbalance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE) MAP_JON_RPC_WE("getaddress", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS) MAP_JON_RPC_WE("create_address", on_create_address, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS) @@ -78,6 +80,7 @@ namespace tools MAP_JON_RPC_WE("tag_accounts", on_tag_accounts, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS) MAP_JON_RPC_WE("untag_accounts", on_untag_accounts, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS) MAP_JON_RPC_WE("set_account_tag_description", on_set_account_tag_description, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION) + MAP_JON_RPC_WE("get_height", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT) MAP_JON_RPC_WE("getheight", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT) MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT) @@ -104,6 +107,8 @@ namespace tools 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_reserve_proof", on_get_reserve_proof, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF) + MAP_JON_RPC_WE("check_reserve_proof", on_check_reserve_proof, wallet_rpc::COMMAND_RPC_CHECK_RESERVE_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) @@ -170,6 +175,8 @@ namespace tools 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_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er); + bool on_check_reserve_proof(const wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_RESERVE_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); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index d797af5c1..ac25e8e84 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1114,6 +1114,7 @@ namespace wallet_rpc std::string type; uint64_t unlock_time; cryptonote::subaddress_index subaddr_index; + std::string address; bool double_spend_seen; BEGIN_KV_SERIALIZE_MAP() @@ -1128,6 +1129,7 @@ namespace wallet_rpc KV_SERIALIZE(type); KV_SERIALIZE(unlock_time) KV_SERIALIZE(subaddr_index); + KV_SERIALIZE(address); KV_SERIALIZE(double_spend_seen) END_KV_SERIALIZE_MAP() }; @@ -1180,6 +1182,62 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_RESERVE_PROOF + { + struct request + { + bool all; + uint32_t account_index; // ignored when `all` is true + uint64_t amount; // ignored when `all` is true + std::string message; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(all) + KV_SERIALIZE(account_index) + KV_SERIALIZE(amount) + 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_RESERVE_PROOF + { + struct request + { + std::string address; + std::string message; + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(message) + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool good; + uint64_t total; + uint64_t spent; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(good) + KV_SERIALIZE(total) + KV_SERIALIZE(spent) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_TRANSFERS { struct request |