diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/common/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/common/compat/glibc_compat.cpp | 98 | ||||
-rw-r--r-- | src/common/util.cpp | 44 | ||||
-rw-r--r-- | src/common/util.h | 2 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.cpp | 25 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.h | 5 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 1821 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.h | 45 | ||||
-rw-r--r-- | src/wallet/CMakeLists.txt | 9 | ||||
-rw-r--r-- | src/wallet/message_store.cpp | 1445 | ||||
-rw-r--r-- | src/wallet/message_store.h | 420 | ||||
-rw-r--r-- | src/wallet/message_transporter.cpp | 317 | ||||
-rw-r--r-- | src/wallet/message_transporter.h | 113 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 117 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 14 | ||||
-rw-r--r-- | src/wallet/wallet_errors.h | 25 |
17 files changed, 4237 insertions, 272 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6ee7effdd..a0b62da77 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,11 +34,6 @@ if (WIN32 OR STATIC) add_definitions(-DMINIUPNP_STATICLIB) endif () -# warnings are cleared only for GCC on Linux -if (NOT (MINGW OR APPLE OR FREEBSD OR OPENBSD OR DRAGONFLY)) - add_compile_options("${WARNINGS_AS_ERRORS_FLAG}") # applies only to targets that follow -endif() - function (monero_private_headers group) source_group("${group}\\Private" FILES diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 5da23c944..3b1eb6d23 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -50,6 +50,10 @@ if (STACK_TRACE) list(APPEND common_sources stack_trace.cpp) endif() +if (BACKCOMPAT) + list(APPEND common_sources compat/glibc_compat.cpp) +endif() + set(common_headers) set(common_private_headers diff --git a/src/common/compat/glibc_compat.cpp b/src/common/compat/glibc_compat.cpp new file mode 100644 index 000000000..bf567987d --- /dev/null +++ b/src/common/compat/glibc_compat.cpp @@ -0,0 +1,98 @@ +#include <cstddef> +#include <cstdint> +#include <strings.h> +#include <string.h> +#include <glob.h> +#include <unistd.h> +#include <fnmatch.h> + +#if defined(HAVE_SYS_SELECT_H) +#include <sys/select.h> +#endif + +// Prior to GLIBC_2.14, memcpy was aliased to memmove. +extern "C" void* memmove(void* a, const void* b, size_t c); +//extern "C" void* memset(void* a, int b, long unsigned int c); +extern "C" void* memcpy(void* a, const void* b, size_t c) +{ + return memmove(a, b, c); +} + +extern "C" void __chk_fail(void) __attribute__((__noreturn__)); + +#if defined(__i386__) || defined(__arm__) + +extern "C" int64_t __udivmoddi4(uint64_t u, uint64_t v, uint64_t* rp); + +extern "C" int64_t __wrap___divmoddi4(int64_t u, int64_t v, int64_t* rp) +{ + int32_t c1 = 0, c2 = 0; + int64_t uu = u, vv = v; + int64_t w; + int64_t r; + + if (uu < 0) { + c1 = ~c1, c2 = ~c2, uu = -uu; + } + if (vv < 0) { + c1 = ~c1, vv = -vv; + } + + w = __udivmoddi4(uu, vv, (uint64_t*)&r); + if (c1) + w = -w; + if (c2) + r = -r; + + *rp = r; + return w; +} +#endif + +/* glibc-internal users use __explicit_bzero_chk, and explicit_bzero + redirects to that. */ +#undef explicit_bzero +/* Set LEN bytes of S to 0. The compiler will not delete a call to + this function, even if S is dead after the call. */ +void +explicit_bzero (void *s, size_t len) +{ + memset (s, '\0', len); + /* Compiler barrier. */ + asm volatile ("" ::: "memory"); +} + +// Redefine explicit_bzero_chk +void +__explicit_bzero_chk (void *dst, size_t len, size_t dstlen) +{ + /* Inline __memset_chk to avoid a PLT reference to __memset_chk. */ + if (__glibc_unlikely (dstlen < len)) + __chk_fail (); + memset (dst, '\0', len); + /* Compiler barrier. */ + asm volatile ("" ::: "memory"); +} +/* libc-internal references use the hidden + __explicit_bzero_chk_internal symbol. This is necessary if + __explicit_bzero_chk is implemented as an IFUNC because some + targets do not support hidden references to IFUNC symbols. */ +#define strong_alias (__explicit_bzero_chk, __explicit_bzero_chk_internal) + +#undef glob +extern "C" int glob_old(const char * pattern, int flags, int (*errfunc) (const char *epath, int eerrno), glob_t *pglob); +#ifdef __i386__ +__asm__(".symver glob_old,glob@GLIBC_2.1"); +#elif defined(__amd64__) +__asm__(".symver glob_old,glob@GLIBC_2.2.5"); +#elif defined(__arm__) +__asm(".symver glob_old,glob@GLIBC_2.4"); +#elif defined(__aarch64__) +__asm__(".symver glob_old,glob@GLIBC_2.17"); +#endif + +extern "C" int __wrap_glob(const char * pattern, int flags, int (*errfunc) (const char *epath, int eerrno), glob_t *pglob) +{ + return glob_old(pattern, flags, errfunc, pglob); +} + diff --git a/src/common/util.cpp b/src/common/util.cpp index 58b0d8210..28745eea4 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -58,6 +58,7 @@ #include "include_base_utils.h" #include "file_io_utils.h" #include "wipeable_string.h" +#include "misc_os_dependent.h" using namespace epee; #include "crypto/crypto.h" @@ -81,6 +82,32 @@ using namespace epee; #include <boost/asio.hpp> #include <openssl/sha.h> +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "util" + +namespace +{ + +#ifndef _WIN32 +static int flock_exnb(int fd) +{ + struct flock fl; + int ret; + + memset(&fl, 0, sizeof(fl)); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + ret = fcntl(fd, F_SETLK, &fl); + if (ret < 0) + MERROR("Error locking fd " << fd << ": " << errno << " (" << strerror(errno) << ")"); + return ret; +} +#endif + +} + namespace tools { std::function<void(int)> signal_handler::m_handler; @@ -181,7 +208,7 @@ namespace tools struct stat wstats = {}; if (fstat(fdw, std::addressof(wstats)) == 0 && rstats.st_dev == wstats.st_dev && rstats.st_ino == wstats.st_ino && - flock(fdw, (LOCK_EX | LOCK_NB)) == 0 && ftruncate(fdw, 0) == 0) + flock_exnb(fdw) == 0 && ftruncate(fdw, 0) == 0) { std::FILE* file = fdopen(fdw, "w"); if (file) return {file, std::move(name)}; @@ -234,10 +261,10 @@ namespace tools MERROR("Failed to open " << filename << ": " << std::error_code(GetLastError(), std::system_category())); } #else - m_fd = open(filename.c_str(), O_RDONLY | O_CREAT | O_CLOEXEC, 0666); + m_fd = open(filename.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666); if (m_fd != -1) { - if (flock(m_fd, LOCK_EX | LOCK_NB) == -1) + if (flock_exnb(m_fd) == -1) { MERROR("Failed to lock " << filename << ": " << std::strerror(errno)); close(m_fd); @@ -1025,4 +1052,15 @@ std::string get_nix_version_display_string() #endif } + std::string get_human_readable_timestamp(uint64_t ts) + { + char buffer[64]; + if (ts < 1234567890) + return "<unknown>"; + time_t tt = ts; + struct tm tm; + misc_utils::get_gmt_time(tt, tm); + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); + return std::string(buffer); + } } diff --git a/src/common/util.h b/src/common/util.h index 1c5c5f4e7..d5aca15d1 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -242,4 +242,6 @@ namespace tools #endif void closefrom(int fd); + + std::string get_human_readable_timestamp(uint64_t ts); } diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index c6a3c6180..bbac20eaa 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -171,6 +171,11 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool) : LOG_PRINT_L3("Blockchain::" << __func__); } //------------------------------------------------------------------ +Blockchain::~Blockchain() +{ + deinit(); +} +//------------------------------------------------------------------ bool Blockchain::have_tx(const crypto::hash &id) const { LOG_PRINT_L3("Blockchain::" << __func__); @@ -550,15 +555,13 @@ bool Blockchain::deinit() // as this should be called if handling a SIGSEGV, need to check // if m_db is a NULL pointer (and thus may have caused the illegal // memory operation), otherwise we may cause a loop. - if (m_db == NULL) - { - throw DB_ERROR("The db pointer is null in Blockchain, the blockchain may be corrupt!"); - } - try { - m_db->close(); - MTRACE("Local blockchain read/write activity stopped successfully"); + if (m_db) + { + m_db->close(); + MTRACE("Local blockchain read/write activity stopped successfully"); + } } catch (const std::exception& e) { @@ -1269,7 +1272,9 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m uint64_t already_generated_coins; uint64_t pool_cookie; - CRITICAL_REGION_BEGIN(m_blockchain_lock); + m_tx_pool.lock(); + const auto unlock_guard = epee::misc_utils::create_scope_leave_handler([&]() { m_tx_pool.unlock(); }); + CRITICAL_REGION_LOCAL(m_blockchain_lock); height = m_db->height(); if (m_btc_valid) { // The pool cookie is atomic. The lack of locking is OK, as if it changes @@ -1305,8 +1310,6 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m median_weight = m_current_block_cumul_weight_limit / 2; already_generated_coins = m_db->get_block_already_generated_coins(height - 1); - CRITICAL_REGION_END(); - size_t txs_weight; uint64_t fee; if (!m_tx_pool.fill_block_template(b, median_weight, already_generated_coins, txs_weight, fee, expected_reward, m_hardfork->get_current_version())) @@ -1317,7 +1320,6 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) size_t real_txs_weight = 0; uint64_t real_fee = 0; - CRITICAL_REGION_BEGIN(m_tx_pool.m_transactions_lock); for(crypto::hash &cur_hash: b.tx_hashes) { auto cur_res = m_tx_pool.m_transactions.find(cur_hash); @@ -1361,7 +1363,6 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m { LOG_ERROR("Creating block template: error: wrongly calculated fee"); } - CRITICAL_REGION_END(); MDEBUG("Creating block template: height " << height << ", median weight " << median_weight << ", already generated coins " << already_generated_coins << diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 877828f81..67bccc6c6 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -121,6 +121,11 @@ namespace cryptonote Blockchain(tx_memory_pool& tx_pool); /** + * @brief Blockchain destructor + */ + ~Blockchain(); + + /** * @brief Initialize the Blockchain state * * @param db a pointer to the backing store to use for the blockchain diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index b729c4bb7..a8cc81869 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -65,6 +65,7 @@ #include "wallet/wallet_args.h" #include "version.h" #include <stdexcept> +#include "wallet/message_store.h" #ifdef WIN32 #include <boost/locale.hpp> @@ -102,12 +103,16 @@ typedef cryptonote::simple_wallet sw; m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ }) -#define SCOPED_WALLET_UNLOCK() \ +#define SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(code) \ LOCK_IDLE_SCOPE(); \ boost::optional<tools::password_container> pwd_container = boost::none; \ - if (m_wallet->ask_password() && !(pwd_container = get_and_verify_password())) { return true; } \ + if (m_wallet->ask_password() && !(pwd_container = get_and_verify_password())) { code; } \ tools::wallet_keys_unlocker unlocker(*m_wallet, pwd_container); +#define SCOPED_WALLET_UNLOCK() SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return true;) + +#define PRINT_USAGE(usage_help) fail_msg_writer() << boost::format(tr("usage: %s")) % usage_help; + enum TransferType { Transfer, TransferLocked, @@ -138,6 +143,93 @@ namespace const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""}; + const char* USAGE_START_MINING("start_mining [<number_of_threads>] [bg_mining] [ignore_battery]"); + const char* USAGE_SET_DAEMON("set_daemon <host>[:<port>] [trusted|untrusted]"); + const char* USAGE_SHOW_BALANCE("balance [detail]"); + const char* USAGE_INCOMING_TRANSFERS("incoming_transfers [available|unavailable] [verbose] [index=<N1>[,<N2>[,...]]]"); + const char* USAGE_PAYMENTS("payments <PID_1> [<PID_2> ... <PID_N>]"); + const char* USAGE_PAYMENT_ID("payment_id"); + const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]"); + const char* USAGE_LOCKED_TRANSFER("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <addr> <amount>) <lockblocks> [<payment_id>]"); + const char* USAGE_LOCKED_SWEEP_ALL("locked_sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <lockblocks> [<payment_id>]"); + const char* USAGE_SWEEP_ALL("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id>]"); + const char* USAGE_SWEEP_BELOW("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"); + const char* USAGE_SWEEP_SINGLE("sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id>]"); + const char* USAGE_DONATE("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]"); + const char* USAGE_SIGN_TRANSFER("sign_transfer [export_raw]"); + const char* USAGE_SET_LOG("set_log <level>|{+,-,}<categories>"); + const char* USAGE_ACCOUNT("account\n" + " account new <label text with white spaces allowed>\n" + " account switch <index> \n" + " account label <index> <label text with white spaces allowed>\n" + " account tag <tag_name> <account_index_1> [<account_index_2> ...]\n" + " account untag <account_index_1> [<account_index_2> ...]\n" + " account tag_description <tag_name> <description>"); + const char* USAGE_ADDRESS("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed>]"); + const char* USAGE_INTEGRATED_ADDRESS("integrated_address [<payment_id> | <address>]"); + const char* USAGE_ADDRESS_BOOK("address_book [(add ((<address> [pid <id>])|<integrated address>) [<description possibly with whitespaces>])|(delete <index>)]"); + const char* USAGE_SET_VARIABLE("set <option> [<value>]"); + const char* USAGE_GET_TX_KEY("get_tx_key <txid>"); + const char* USAGE_SET_TX_KEY("set_tx_key <txid> <tx_key>"); + const char* USAGE_CHECK_TX_KEY("check_tx_key <txid> <txkey> <address>"); + const char* USAGE_GET_TX_PROOF("get_tx_proof <txid> <address> [<message>]"); + const char* USAGE_CHECK_TX_PROOF("check_tx_proof <txid> <address> <signature_file> [<message>]"); + const char* USAGE_GET_SPEND_PROOF("get_spend_proof <txid> [<message>]"); + const char* USAGE_CHECK_SPEND_PROOF("check_spend_proof <txid> <signature_file> [<message>]"); + const char* USAGE_GET_RESERVE_PROOF("get_reserve_proof (all|<amount>) [<message>]"); + const char* USAGE_CHECK_RESERVE_PROOF("check_reserve_proof <address> <signature_file> [<message>]"); + const char* USAGE_SHOW_TRANSFERS("show_transfers [in|out|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]"); + const char* USAGE_UNSPENT_OUTPUTS("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]"); + const char* USAGE_RESCAN_BC("rescan_bc [hard]"); + const char* USAGE_SET_TX_NOTE("set_tx_note <txid> [free text note]"); + const char* USAGE_GET_TX_NOTE("get_tx_note <txid>"); + const char* USAGE_GET_DESCRIPTION("get_description"); + const char* USAGE_SET_DESCRIPTION("set_description [free text note]"); + const char* USAGE_SIGN("sign <filename>"); + const char* USAGE_VERIFY("verify <filename> <address> <signature>"); + const char* USAGE_EXPORT_KEY_IMAGES("export_key_images <filename>"); + const char* USAGE_IMPORT_KEY_IMAGES("import_key_images <filename>"); + const char* USAGE_HW_KEY_IMAGES_SYNC("hw_key_images_sync"); + const char* USAGE_HW_RECONNECT("hw_reconnect"); + const char* USAGE_EXPORT_OUTPUTS("export_outputs <filename>"); + const char* USAGE_IMPORT_OUTPUTS("import_outputs <filename>"); + const char* USAGE_SHOW_TRANSFER("show_transfer <txid>"); + const char* USAGE_MAKE_MULTISIG("make_multisig <threshold> <string1> [<string>...]"); + const char* USAGE_FINALIZE_MULTISIG("finalize_multisig <string> [<string>...]"); + const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys <string> [<string>...]"); + const char* USAGE_EXPORT_MULTISIG_INFO("export_multisig_info <filename>"); + const char* USAGE_IMPORT_MULTISIG_INFO("import_multisig_info <filename> [<filename>...]"); + const char* USAGE_SIGN_MULTISIG("sign_multisig <filename>"); + const char* USAGE_SUBMIT_MULTISIG("submit_multisig <filename>"); + const char* USAGE_EXPORT_RAW_MULTISIG_TX("export_raw_multisig_tx <filename>"); + const char* USAGE_MMS("mms [<subcommand> [<subcommand_parameters>]]"); + const char* USAGE_MMS_INIT("mms init <required_signers>/<authorized_signers> <own_label> <own_transport_address>"); + const char* USAGE_MMS_INFO("mms info"); + const char* USAGE_MMS_SIGNER("mms signer [<number> <label> [<transport_address> [<monero_address>]]]"); + const char* USAGE_MMS_LIST("mms list"); + const char* USAGE_MMS_NEXT("mms next [sync]"); + const char* USAGE_MMS_SYNC("mms sync"); + const char* USAGE_MMS_TRANSFER("mms transfer <transfer_command_arguments>"); + const char* USAGE_MMS_DELETE("mms delete (<message_id> | all)"); + const char* USAGE_MMS_SEND("mms send [<message_id>]"); + const char* USAGE_MMS_RECEIVE("mms receive"); + const char* USAGE_MMS_EXPORT("mms export <message_id>"); + const char* USAGE_MMS_NOTE("mms note [<label> <text>]"); + const char* USAGE_MMS_SHOW("mms show <message_id>"); + const char* USAGE_MMS_SET("mms set <option_name> [<option_value>]"); + const char* USAGE_MMS_SEND_SIGNER_CONFIG("mms send_signer_config"); + const char* USAGE_MMS_START_AUTO_CONFIG("mms start_auto_config [<label> <label> ...]"); + const char* USAGE_MMS_STOP_AUTO_CONFIG("mms stop_auto_config"); + const char* USAGE_MMS_AUTO_CONFIG("mms auto_config <auto_config_token>"); + const char* USAGE_PRINT_RING("print_ring <key_image> | <txid>"); + const char* USAGE_SET_RING("set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )"); + const char* USAGE_SAVE_KNOWN_RINGS("save_known_rings"); + const char* USAGE_MARK_OUTPUT_SPENT("mark_output_spent <amount>/<offset> | <filename> [add]"); + const char* USAGE_MARK_OUTPUT_UNSPENT("mark_output_unspent <amount>/<offset>"); + const char* USAGE_IS_OUTPUT_SPENT("is_output_spent <amount>/<offset>"); + const char* USAGE_VERSION("version"); + const char* USAGE_HELP("help [<command>]"); + std::string input_line(const std::string& prompt) { #ifdef HAVE_READLINE @@ -771,7 +863,7 @@ bool simple_wallet::payment_id(const std::vector<std::string> &args/* = std::vec crypto::hash payment_id; if (args.size() > 0) { - fail_msg_writer() << tr("usage: payment_id"); + PRINT_USAGE(USAGE_PAYMENT_ID); return true; } payment_id = crypto::rand<crypto::hash>(); @@ -836,65 +928,83 @@ bool simple_wallet::print_fee_info(const std::vector<std::string> &args/* = std: bool simple_wallet::prepare_multisig(const std::vector<std::string> &args) { + prepare_multisig_main(args, false); + return true; +} + +bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (m_wallet->multisig()) { fail_msg_writer() << tr("This wallet is already multisig"); - return true; + return false; } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); - return true; + return false; } if(m_wallet->get_num_transfer_details()) { fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); - return true; + return false; } - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); std::string multisig_info = m_wallet->get_multisig_info(); success_msg_writer() << multisig_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig <threshold> <info1> [<info2>...] with others' multisig info"); success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "); + + if (called_by_mms) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::key_set, multisig_info); + } + return true; } bool simple_wallet::make_multisig(const std::vector<std::string> &args) { + make_multisig_main(args, false); + return true; +} + +bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (m_wallet->multisig()) { fail_msg_writer() << tr("This wallet is already multisig"); - return true; + return false; } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); - return true; + return false; } if(m_wallet->get_num_transfer_details()) { fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); - return true; + return false; } if (args.size() < 2) { - fail_msg_writer() << tr("usage: make_multisig <threshold> <multisiginfo1> [<multisiginfo2>...]"); - return true; + PRINT_USAGE(USAGE_MAKE_MULTISIG); + return false; } // parse threshold @@ -902,14 +1012,14 @@ bool simple_wallet::make_multisig(const std::vector<std::string> &args) if (!string_tools::get_xtype_from_string(threshold, args[0])) { fail_msg_writer() << tr("Invalid threshold"); - return true; + return false; } const auto orig_pwd_container = get_and_verify_password(); if(orig_pwd_container == boost::none) { fail_msg_writer() << tr("Your original password was incorrect."); - return true; + return false; } LOCK_IDLE_SCOPE(); @@ -924,20 +1034,24 @@ bool simple_wallet::make_multisig(const std::vector<std::string> &args) success_msg_writer() << tr("Another step is needed"); success_msg_writer() << multisig_extra_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use exchange_multisig_keys <info1> [<info2>...] with others' multisig info"); + if (called_by_mms) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::additional_key_set, multisig_extra_info); + } return true; } } catch (const std::exception &e) { fail_msg_writer() << tr("Error creating multisig: ") << e.what(); - return true; + return false; } uint32_t total; if (!m_wallet->multisig(NULL, &threshold, &total)) { fail_msg_writer() << tr("Error creating multisig: new wallet is not multisig"); - return true; + return false; } success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ") << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); @@ -976,7 +1090,7 @@ bool simple_wallet::finalize_multisig(const std::vector<std::string> &args) if (args.size() < 2) { - fail_msg_writer() << tr("usage: finalize_multisig <multisiginfo1> [<multisiginfo2>...]"); + PRINT_USAGE(USAGE_FINALIZE_MULTISIG); return true; } @@ -997,35 +1111,41 @@ bool simple_wallet::finalize_multisig(const std::vector<std::string> &args) return true; } -bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) { +bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) +{ + exchange_multisig_keys_main(args, false); + return true; +} + +bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms) { bool ready; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); - return true; + return false; } if (ready) { fail_msg_writer() << tr("This wallet is already finalized"); - return true; + return false; } const auto orig_pwd_container = get_and_verify_password(); if(orig_pwd_container == boost::none) { fail_msg_writer() << tr("Your original password was incorrect."); - return true; + return false; } if (args.size() < 2) { - fail_msg_writer() << tr("usage: exchange_multisig_keys <multisiginfo1> [<multisiginfo2>...]"); - return true; + PRINT_USAGE(USAGE_EXCHANGE_MULTISIG_KEYS); + return false; } try @@ -1036,6 +1156,10 @@ bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) message_writer() << tr("Another step is needed"); message_writer() << multisig_extra_info; message_writer() << tr("Send this multisig info to all other participants, then use exchange_multisig_keys <info1> [<info2>...] with others' multisig info"); + if (called_by_mms) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::additional_key_set, multisig_extra_info); + } return true; } else { uint32_t threshold, total; @@ -1046,7 +1170,7 @@ bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) catch (const std::exception &e) { fail_msg_writer() << tr("Failed to perform multisig keys exchange: ") << e.what(); - return true; + return false; } return true; @@ -1054,50 +1178,63 @@ bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) bool simple_wallet::export_multisig(const std::vector<std::string> &args) { + export_multisig_main(args, false); + return true; +} + +bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ bool ready; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); - return true; + return false; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); - return true; + return false; } if (args.size() != 1) { - fail_msg_writer() << tr("usage: export_multisig_info <filename>"); - return true; + PRINT_USAGE(USAGE_EXPORT_MULTISIG_INFO); + return false; } const std::string filename = args[0]; - if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) + if (!called_by_mms && m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) return true; - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); try { cryptonote::blobdata ciphertext = m_wallet->export_multisig(); - bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); - if (!r) + if (called_by_mms) { - fail_msg_writer() << tr("failed to save file ") << filename; - return true; + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::multisig_sync_data, ciphertext); + } + else + { + bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); + if (!r) + { + fail_msg_writer() << tr("failed to save file ") << filename; + return false; + } } } catch (const std::exception &e) { LOG_ERROR("Error exporting multisig info: " << e.what()); fail_msg_writer() << tr("Error exporting multisig info: ") << e.what(); - return true; + return false; } success_msg_writer() << tr("Multisig info exported to ") << filename; @@ -1106,44 +1243,57 @@ bool simple_wallet::export_multisig(const std::vector<std::string> &args) bool simple_wallet::import_multisig(const std::vector<std::string> &args) { + import_multisig_main(args, false); + return true; +} + +bool simple_wallet::import_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ bool ready; uint32_t threshold, total; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (!m_wallet->multisig(&ready, &threshold, &total)) { fail_msg_writer() << tr("This wallet is not multisig"); - return true; + return false; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); - return true; + return false; } if (args.size() < threshold - 1) { - fail_msg_writer() << tr("usage: import_multisig_info <filename1> [<filename2>...] - one for each other participant"); - return true; + PRINT_USAGE(USAGE_IMPORT_MULTISIG_INFO); + return false; } std::vector<cryptonote::blobdata> info; for (size_t n = 0; n < args.size(); ++n) { - const std::string filename = args[n]; - std::string data; - bool r = epee::file_io_utils::load_file_to_string(filename, data); - if (!r) + if (called_by_mms) { - fail_msg_writer() << tr("failed to read file ") << filename; - return true; + info.push_back(args[n]); + } + else + { + const std::string &filename = args[n]; + 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 false; + } + info.push_back(std::move(data)); } - info.push_back(std::move(data)); } - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); // all read and parsed, actually import try @@ -1158,7 +1308,7 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args) catch (const std::exception &e) { fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); - return true; + return false; } if (m_wallet->is_trusted_daemon()) { @@ -1169,11 +1319,13 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args) catch (const std::exception &e) { message_writer() << tr("Failed to update spent status after importing multisig info: ") << e.what(); + return false; } } else { message_writer() << tr("Untrusted daemon, spent status may be incorrect. Use a trusted daemon and run \"rescan_spent\""); + return false; } return true; } @@ -1186,51 +1338,93 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) bool simple_wallet::sign_multisig(const std::vector<std::string> &args) { + sign_multisig_main(args, false); + return true; +} + +bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ bool ready; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if(!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This is not a multisig wallet"); - return true; + return false; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); - return true; + return false; } if (args.size() != 1) { - fail_msg_writer() << tr("usage: sign_multisig <filename>"); - return true; + PRINT_USAGE(USAGE_SIGN_MULTISIG); + return false; } - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); std::string filename = args[0]; std::vector<crypto::hash> txids; uint32_t signers = 0; try { - bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); - if (!r) + if (called_by_mms) { - fail_msg_writer() << tr("Failed to sign multisig transaction"); - return true; + tools::wallet2::multisig_tx_set exported_txs; + std::string ciphertext; + bool r = m_wallet->load_multisig_tx(args[0], exported_txs, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); + if (r) + { + r = m_wallet->sign_multisig_tx(exported_txs, txids); + } + if (r) + { + ciphertext = m_wallet->save_multisig_tx(exported_txs); + if (ciphertext.empty()) + { + r = false; + } + } + if (r) + { + mms::message_type message_type = mms::message_type::fully_signed_tx; + if (txids.empty()) + { + message_type = mms::message_type::partially_signed_tx; + } + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), message_type, ciphertext); + filename = "MMS"; // for the messages below + } + else + { + fail_msg_writer() << tr("Failed to sign multisig transaction"); + return false; + } + } + else + { + bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to sign multisig transaction"); + return false; + } } } catch (const tools::error::multisig_export_needed& e) { fail_msg_writer() << tr("Multisig error: ") << e.what(); - return true; + return false; } catch (const std::exception &e) { fail_msg_writer() << tr("Failed to sign multisig transaction: ") << e.what(); - return true; + return false; } if (txids.empty()) @@ -1259,49 +1453,67 @@ bool simple_wallet::sign_multisig(const std::vector<std::string> &args) bool simple_wallet::submit_multisig(const std::vector<std::string> &args) { + submit_multisig_main(args, false); + return true; +} + +bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, bool called_by_mms) +{ bool ready; uint32_t threshold; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); - return true; + return false; } if (!m_wallet->multisig(&ready, &threshold)) { fail_msg_writer() << tr("This is not a multisig wallet"); - return true; + return false; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); - return true; + return false; } if (args.size() != 1) { - fail_msg_writer() << tr("usage: submit_multisig <filename>"); - return true; + PRINT_USAGE(USAGE_SUBMIT_MULTISIG); + return false; } if (!try_connect_to_daemon()) - return true; + return false; - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); std::string filename = args[0]; try { tools::wallet2::multisig_tx_set txs; - bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); - if (!r) + if (called_by_mms) { - fail_msg_writer() << tr("Failed to load multisig transaction from file"); - return true; + bool r = m_wallet->load_multisig_tx(args[0], txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from MMS"); + return false; + } + } + else + { + bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from file"); + return false; + } } if (txs.m_signers.size() < threshold) { fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); - return true; + return false; } // actually commit the transactions @@ -1320,6 +1532,7 @@ bool simple_wallet::submit_multisig(const std::vector<std::string> &args) { LOG_ERROR("unknown error"); fail_msg_writer() << tr("unknown error"); + return false; } return true; @@ -1346,7 +1559,7 @@ bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args) } if (args.size() != 1) { - fail_msg_writer() << tr("usage: export_raw_multisig <filename>"); + PRINT_USAGE(USAGE_EXPORT_RAW_MULTISIG_TX); return true; } @@ -1409,7 +1622,7 @@ bool simple_wallet::print_ring(const std::vector<std::string> &args) crypto::hash txid; if (args.size() != 1) { - fail_msg_writer() << tr("usage: print_ring <key_image> | <txid>"); + PRINT_USAGE(USAGE_PRINT_RING); return true; } @@ -1566,7 +1779,7 @@ bool simple_wallet::set_ring(const std::vector<std::string> &args) if (args.size() < 3) { - fail_msg_writer() << tr("usage: set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )"); + PRINT_USAGE(USAGE_SET_RING); return true; } @@ -1641,7 +1854,7 @@ bool simple_wallet::blackball(const std::vector<std::string> &args) uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets; if (args.size() == 0) { - fail_msg_writer() << tr("usage: mark_output_spent <amount>/<offset> | <filename> [add]"); + PRINT_USAGE(USAGE_MARK_OUTPUT_SPENT); return true; } @@ -1730,7 +1943,7 @@ bool simple_wallet::unblackball(const std::vector<std::string> &args) std::pair<uint64_t, uint64_t> output; if (args.size() != 1) { - fail_msg_writer() << tr("usage: mark_output_unspent <amount>/<offset>"); + PRINT_USAGE(USAGE_MARK_OUTPUT_UNSPENT); return true; } @@ -1757,7 +1970,7 @@ bool simple_wallet::blackballed(const std::vector<std::string> &args) std::pair<uint64_t, uint64_t> output; if (args.size() != 1) { - fail_msg_writer() << tr("usage: is_output_spent <amount>/<offset>"); + PRINT_USAGE(USAGE_IS_OUTPUT_SPENT); return true; } @@ -2308,6 +2521,12 @@ bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<st { success_msg_writer() << get_commands_str(); } + else if ((args.size() == 2) && (args.front() == "mms")) + { + // Little hack to be able to do "help mms <subcommand>" + std::vector<std::string> mms_args(1, args.front() + " " + args.back()); + success_msg_writer() << get_command_usage(mms_args); + } else { success_msg_writer() << get_command_usage(args); @@ -2326,14 +2545,14 @@ simple_wallet::simple_wallet() { m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), - tr("start_mining [<number_of_threads>] [bg_mining] [ignore_battery]"), + tr(USAGE_START_MINING), tr("Start mining in the daemon (bg_mining and ignore_battery are optional booleans).")); m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), tr("Stop mining in the daemon.")); m_cmd_binder.set_handler("set_daemon", boost::bind(&simple_wallet::set_daemon, this, _1), - tr("set_daemon <host>[:<port>] [trusted|untrusted]"), + tr(USAGE_SET_DAEMON), tr("Set another daemon to connect to.")); m_cmd_binder.set_handler("save_bc", boost::bind(&simple_wallet::save_bc, this, _1), @@ -2343,70 +2562,64 @@ simple_wallet::simple_wallet() tr("Synchronize the transactions and balance.")); m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, _1), - tr("balance [detail]"), + tr(USAGE_SHOW_BALANCE), tr("Show the wallet's balance of the currently selected account.")); m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), - tr("incoming_transfers [available|unavailable] [verbose] [index=<N1>[,<N2>[,...]]]"), + tr(USAGE_INCOMING_TRANSFERS), tr("Show the incoming transfers, all or filtered by availability and address index.\n\n" "Output format:\n" "Amount, Spent(\"T\"|\"F\"), \"locked\"|\"unlocked\", RingCT, Global Index, Transaction Hash, Address Index, [Public Key, Key Image] ")); m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), - tr("payments <PID_1> [<PID_2> ... <PID_N>]"), + tr(USAGE_PAYMENTS), tr("Show the payments for the given payment IDs.")); m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show the blockchain height.")); m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), - tr("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]"), + tr(USAGE_TRANSFER), tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); m_cmd_binder.set_handler("locked_transfer", boost::bind(&simple_wallet::locked_transfer, this, _1), - tr("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <addr> <amount>) <lockblocks> [<payment_id>]"), + tr(USAGE_LOCKED_TRANSFER), tr("Transfer <amount> to <address> and lock it for <lockblocks> (max. 1000000). If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); m_cmd_binder.set_handler("locked_sweep_all", boost::bind(&simple_wallet::locked_sweep_all, this, _1), - tr("locked_sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <lockblocks> [<payment_id>]"), + tr(USAGE_LOCKED_SWEEP_ALL), tr("Send all unlocked balance to an address and lock it for <lockblocks> (max. 1000000). If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. <priority> is the priority of the sweep. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability.")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with ring_size 1")); m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), - tr("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id>]"), + tr(USAGE_SWEEP_ALL), tr("Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. If the parameter \"outputs=<N>\" is specified and N > 0, wallet splits the transaction into N even outputs.")); m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), - tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"), + tr(USAGE_SWEEP_BELOW), tr("Send all unlocked outputs below the threshold to an address.")); m_cmd_binder.set_handler("sweep_single", boost::bind(&simple_wallet::sweep_single, this, _1), - tr("sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id>]"), + tr(USAGE_SWEEP_SINGLE), tr("Send a single output of the given key image to an address without change.")); m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), - tr("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]"), + tr(USAGE_DONATE), tr("Donate <amount> to the development team (donate.getmonero.org).")); m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), - tr("sign_transfer [export_raw]"), + tr(USAGE_SIGN_TRANSFER), tr("Sign a transaction from a file. If the parameter \"export_raw\" is specified, transaction raw hex data suitable for the daemon RPC /sendrawtransaction is exported.")); m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), tr("Submit a signed transaction from a file.")); m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), - tr("set_log <level>|{+,-,}<categories>"), + tr(USAGE_SET_LOG), tr("Change the current log detail (level must be <0-4>).")); m_cmd_binder.set_handler("account", boost::bind(&simple_wallet::account, this, _1), - tr("account\n" - " account new <label text with white spaces allowed>\n" - " account switch <index> \n" - " account label <index> <label text with white spaces allowed>\n" - " account tag <tag_name> <account_index_1> [<account_index_2> ...]\n" - " account untag <account_index_1> [<account_index_2> ...]\n" - " account tag_description <tag_name> <description>"), + tr(USAGE_ACCOUNT), tr("If no arguments are specified, the wallet shows all the existing accounts along with their balances.\n" "If the \"new\" argument is specified, the wallet creates a new account with its label initialized by the provided label text (which can be empty).\n" "If the \"switch\" argument is specified, the wallet switches to the account specified by <index>.\n" @@ -2416,15 +2629,15 @@ simple_wallet::simple_wallet() "If the \"tag_description\" argument is specified, the tag <tag_name> is assigned an arbitrary text <description>.")); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), - tr("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed>]"), + tr(USAGE_ADDRESS), tr("If no arguments are specified or <index> is specified, the wallet shows the default or specified address. If \"all\" is specified, the wallet shows all the existing addresses in the currently selected account. If \"new \" is specified, the wallet creates a new address with the provided label text (which can be empty). If \"label\" is specified, the wallet sets the label of the address specified by <index> to the provided label text.")); m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), - tr("integrated_address [<payment_id> | <address>]"), + tr(USAGE_INTEGRATED_ADDRESS), tr("Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); m_cmd_binder.set_handler("address_book", boost::bind(&simple_wallet::address_book, this, _1), - tr("address_book [(add ((<address> [pid <id>])|<integrated address>) [<description possibly with whitespaces>])|(delete <index>)]"), + tr(USAGE_ADDRESS_BOOK), tr("Print all entries in the address book, optionally adding/deleting an entry to/from it.")); m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), @@ -2443,7 +2656,7 @@ simple_wallet::simple_wallet() tr("Display the Electrum-style mnemonic seed")); m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), - tr("set <option> [<value>]"), + tr(USAGE_SET_VARIABLE), tr("Available options:\n " "seed language\n " " Set the wallet's seed language.\n " @@ -2496,45 +2709,45 @@ simple_wallet::simple_wallet() tr("Rescan the blockchain for spent outputs.")); m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), - tr("get_tx_key <txid>"), + tr(USAGE_GET_TX_KEY), tr("Get the transaction key (r) for a given <txid>.")); m_cmd_binder.set_handler("set_tx_key", boost::bind(&simple_wallet::set_tx_key, this, _1), - tr("set_tx_key <txid> <tx_key>"), + tr(USAGE_SET_TX_KEY), tr("Set the transaction key (r) for a given <txid> in case the tx was made by some other device or 3rd party wallet.")); m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), - tr("check_tx_key <txid> <txkey> <address>"), + tr(USAGE_CHECK_TX_KEY), tr("Check the amount going to <address> in <txid>.")); m_cmd_binder.set_handler("get_tx_proof", boost::bind(&simple_wallet::get_tx_proof, this, _1), - tr("get_tx_proof <txid> <address> [<message>]"), + tr(USAGE_GET_TX_PROOF), tr("Generate a signature proving funds sent to <address> in <txid>, optionally with a challenge string <message>, using either the transaction secret key (when <address> is not your wallet's address) or the view secret key (otherwise), which does not disclose the secret key.")); m_cmd_binder.set_handler("check_tx_proof", boost::bind(&simple_wallet::check_tx_proof, this, _1), - tr("check_tx_proof <txid> <address> <signature_file> [<message>]"), + tr(USAGE_CHECK_TX_PROOF), tr("Check the proof for funds going to <address> in <txid> with the challenge string <message> if any.")); m_cmd_binder.set_handler("get_spend_proof", boost::bind(&simple_wallet::get_spend_proof, this, _1), - tr("get_spend_proof <txid> [<message>]"), + tr(USAGE_GET_SPEND_PROOF), tr("Generate a signature proving that you generated <txid> using the spend secret key, optionally with a challenge string <message>.")); m_cmd_binder.set_handler("check_spend_proof", boost::bind(&simple_wallet::check_spend_proof, this, _1), - tr("check_spend_proof <txid> <signature_file> [<message>]"), + tr(USAGE_CHECK_SPEND_PROOF), 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(USAGE_GET_RESERVE_PROOF), 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(USAGE_CHECK_RESERVE_PROOF), 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|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]"), + tr(USAGE_SHOW_TRANSFERS), // Seemingly broken formatting to compensate for the backslash before the quotes. tr("Show the incoming/outgoing transfers within an optional height range.\n\n" "Output format:\n" @@ -2550,26 +2763,27 @@ simple_wallet::simple_wallet() tr("Export to CSV the incoming/outgoing transfers within an optional height range.")); m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), - tr("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]"), + tr(USAGE_UNSPENT_OUTPUTS), tr("Show the unspent outputs of a specified address within an optional amount range.")); m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), - tr("rescan_bc [hard]"), + tr(USAGE_RESCAN_BC), tr("Rescan the blockchain from scratch, losing any information which can not be recovered from the blockchain itself.")); m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), - tr("set_tx_note <txid> [free text note]"), + tr(USAGE_SET_TX_NOTE), tr("Set an arbitrary string note for a <txid>.")); m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), - tr("get_tx_note <txid>"), + tr(USAGE_GET_TX_NOTE), tr("Get a string note for a txid.")); m_cmd_binder.set_handler("set_description", boost::bind(&simple_wallet::set_description, this, _1), - tr("set_description [free text note]"), + tr(USAGE_SET_DESCRIPTION), tr("Set an arbitrary description for the wallet.")); m_cmd_binder.set_handler("get_description", boost::bind(&simple_wallet::get_description, this, _1), + tr(USAGE_GET_DESCRIPTION), tr("Get the description of the wallet.")); m_cmd_binder.set_handler("status", boost::bind(&simple_wallet::status, this, _1), @@ -2579,45 +2793,46 @@ simple_wallet::simple_wallet() tr("Show the wallet's information.")); m_cmd_binder.set_handler("sign", boost::bind(&simple_wallet::sign, this, _1), - tr("sign <file>"), + tr(USAGE_SIGN), tr("Sign the contents of a file.")); m_cmd_binder.set_handler("verify", boost::bind(&simple_wallet::verify, this, _1), - tr("verify <filename> <address> <signature>"), + tr(USAGE_VERIFY), tr("Verify a signature on the contents of a file.")); m_cmd_binder.set_handler("export_key_images", boost::bind(&simple_wallet::export_key_images, this, _1), - tr("export_key_images <file>"), - tr("Export a signed set of key images to a <file>.")); + tr(USAGE_EXPORT_KEY_IMAGES), + tr("Export a signed set of key images to a <filename>.")); m_cmd_binder.set_handler("import_key_images", boost::bind(&simple_wallet::import_key_images, this, _1), - tr("import_key_images <file>"), + tr(USAGE_IMPORT_KEY_IMAGES), tr("Import a signed key images list and verify their spent status.")); m_cmd_binder.set_handler("hw_key_images_sync", boost::bind(&simple_wallet::hw_key_images_sync, this, _1), - tr("hw_key_images_sync"), + tr(USAGE_HW_KEY_IMAGES_SYNC), tr("Synchronizes key images with the hw wallet.")); m_cmd_binder.set_handler("hw_reconnect", boost::bind(&simple_wallet::hw_reconnect, this, _1), - tr("hw_reconnect"), + tr(USAGE_HW_RECONNECT), tr("Attempts to reconnect HW wallet.")); m_cmd_binder.set_handler("export_outputs", boost::bind(&simple_wallet::export_outputs, this, _1), - tr("export_outputs <file>"), + tr(USAGE_EXPORT_OUTPUTS), tr("Export a set of outputs owned by this wallet.")); m_cmd_binder.set_handler("import_outputs", boost::bind(&simple_wallet::import_outputs, this, _1), - tr("import_outputs <file>"), + tr(USAGE_IMPORT_OUTPUTS), tr("Import a set of outputs owned by this wallet.")); m_cmd_binder.set_handler("show_transfer", boost::bind(&simple_wallet::show_transfer, this, _1), - tr("show_transfer <txid>"), + tr(USAGE_SHOW_TRANSFER), tr("Show information about a transfer to/from this address.")); m_cmd_binder.set_handler("password", boost::bind(&simple_wallet::change_password, this, _1), tr("Change the wallet's password.")); m_cmd_binder.set_handler("payment_id", boost::bind(&simple_wallet::payment_id, this, _1), + tr(USAGE_PAYMENT_ID), tr("Generate a new random full size payment id. These will be unencrypted on the blockchain, see integrated_address for encrypted short payment ids.")); m_cmd_binder.set_handler("fee", boost::bind(&simple_wallet::print_fee_info, this, _1), @@ -2625,69 +2840,152 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("prepare_multisig", boost::bind(&simple_wallet::prepare_multisig, this, _1), tr("Export data needed to create a multisig wallet")); m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1), - tr("make_multisig <threshold> <string1> [<string>...]"), + tr(USAGE_MAKE_MULTISIG), tr("Turn this wallet into a multisig wallet")); m_cmd_binder.set_handler("finalize_multisig", boost::bind(&simple_wallet::finalize_multisig, this, _1), - tr("finalize_multisig <string> [<string>...]"), + tr(USAGE_FINALIZE_MULTISIG), tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets")); m_cmd_binder.set_handler("exchange_multisig_keys", boost::bind(&simple_wallet::exchange_multisig_keys, this, _1), - tr("exchange_multisig_keys <string> [<string>...]"), + tr(USAGE_EXCHANGE_MULTISIG_KEYS), tr("Performs extra multisig keys exchange rounds. Needed for arbitrary M/N multisig wallets")); m_cmd_binder.set_handler("export_multisig_info", boost::bind(&simple_wallet::export_multisig, this, _1), - tr("export_multisig_info <filename>"), + tr(USAGE_EXPORT_MULTISIG_INFO), 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_info <filename> [<filename>...]"), + tr(USAGE_IMPORT_MULTISIG_INFO), tr("Import multisig info from other participants")); m_cmd_binder.set_handler("sign_multisig", boost::bind(&simple_wallet::sign_multisig, this, _1), - tr("sign_multisig <filename>"), + tr(USAGE_SIGN_MULTISIG), tr("Sign a multisig transaction from a file")); m_cmd_binder.set_handler("submit_multisig", boost::bind(&simple_wallet::submit_multisig, this, _1), - tr("submit_multisig <filename>"), + tr(USAGE_SUBMIT_MULTISIG), 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_tx <filename>"), + tr(USAGE_EXPORT_RAW_MULTISIG_TX), tr("Export a signed multisig transaction to a file")); + m_cmd_binder.set_handler("mms", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS), + tr("Interface with the MMS (Multisig Messaging System)\n" + "<subcommand> is one of:\n" + " init, info, signer, list, next, sync, transfer, delete, send, receive, export, note, show, set, help\n" + " send_signer_config, start_auto_config, stop_auto_config, auto_config\n" + "Get help about a subcommand with: help mms <subcommand>, or mms help <subcommand>")); + m_cmd_binder.set_handler("mms init", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_INIT), + tr("Initialize and configure the MMS for M/N = number of required signers/number of authorized signers multisig")); + m_cmd_binder.set_handler("mms info", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_INFO), + tr("Display current MMS configuration")); + m_cmd_binder.set_handler("mms signer", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SIGNER), + tr("Set or modify authorized signer info (single-word label, transport address, Monero address), or list all signers")); + m_cmd_binder.set_handler("mms list", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_LIST), + tr("List all messages")); + m_cmd_binder.set_handler("mms next", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_NEXT), + tr("Evaluate the next possible multisig-related action(s) according to wallet state, and execute or offer for choice\n" + "By using 'sync' processing of waiting messages with multisig sync info can be forced regardless of wallet state")); + m_cmd_binder.set_handler("mms sync", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SYNC), + tr("Force generation of multisig sync info regardless of wallet state, to recover from special situations like \"stale data\" errors")); + m_cmd_binder.set_handler("mms transfer", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_TRANSFER), + tr("Initiate transfer with MMS support; arguments identical to normal 'transfer' command arguments, for info see there")); + m_cmd_binder.set_handler("mms delete", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_DELETE), + tr("Delete a single message by giving its id, or delete all messages by using 'all'")); + m_cmd_binder.set_handler("mms send", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SEND), + tr("Send a single message by giving its id, or send all waiting messages")); + m_cmd_binder.set_handler("mms receive", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_RECEIVE), + tr("Check right away for new messages to receive")); + m_cmd_binder.set_handler("mms export", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_EXPORT), + tr("Write the content of a message to a file \"mms_message_content\"")); + m_cmd_binder.set_handler("mms note", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_NOTE), + tr("Send a one-line message to an authorized signer, identified by its label, or show any waiting unread notes")); + m_cmd_binder.set_handler("mms show", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SHOW), + tr("Show detailed info about a single message")); + m_cmd_binder.set_handler("mms set", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SET), + tr("Available options:\n " + "auto-send <1|0>\n " + " Whether to automatically send newly generated messages right away.\n ")); + m_cmd_binder.set_handler("mms send_message_config", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_SEND_SIGNER_CONFIG), + tr("Send completed signer config to all other authorized signers")); + m_cmd_binder.set_handler("mms start_auto_config", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_START_AUTO_CONFIG), + tr("Start auto-config at the auto-config manager's wallet by issuing auto-config tokens and optionally set others' labels")); + m_cmd_binder.set_handler("mms stop_auto_config", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_STOP_AUTO_CONFIG), + tr("Delete any auto-config tokens and abort a auto-config process")); + m_cmd_binder.set_handler("mms auto_config", + boost::bind(&simple_wallet::mms, this, _1), + tr(USAGE_MMS_AUTO_CONFIG), + tr("Start auto-config by using the token received from the auto-config manager")); m_cmd_binder.set_handler("print_ring", boost::bind(&simple_wallet::print_ring, this, _1), - tr("print_ring <key_image> | <txid>"), + tr(USAGE_PRINT_RING), tr("Print the ring(s) used to spend a given key image or transaction (if the ring size is > 1)\n\n" "Output format:\n" "Key Image, \"absolute\", list of rings")); m_cmd_binder.set_handler("set_ring", boost::bind(&simple_wallet::set_ring, this, _1), - tr("set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )"), + tr(USAGE_SET_RING), tr("Set the ring used for a given key image, so it can be reused in a fork")); m_cmd_binder.set_handler("save_known_rings", boost::bind(&simple_wallet::save_known_rings, this, _1), - tr("save_known_rings"), + tr(USAGE_SAVE_KNOWN_RINGS), tr("Save known rings to the shared rings database")); m_cmd_binder.set_handler("mark_output_spent", boost::bind(&simple_wallet::blackball, this, _1), - tr("mark_output_spent <amount>/<offset> | <filename> [add]"), + tr(USAGE_MARK_OUTPUT_SPENT), tr("Mark output(s) as spent so they never get selected as fake outputs in a ring")); m_cmd_binder.set_handler("mark_output_unspent", boost::bind(&simple_wallet::unblackball, this, _1), - tr("mark_output_unspent <amount>/<offset>"), + tr(USAGE_MARK_OUTPUT_UNSPENT), tr("Marks an output as unspent so it may get selected as a fake output in a ring")); m_cmd_binder.set_handler("is_output_spent", boost::bind(&simple_wallet::blackballed, this, _1), - tr("is_output_spent <amount>/<offset>"), + tr(USAGE_IS_OUTPUT_SPENT), tr("Checks whether an output is marked as spent")); m_cmd_binder.set_handler("version", boost::bind(&simple_wallet::version, this, _1), - tr("version"), + tr(USAGE_VERSION), tr("Returns version information")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), - tr("help [<command>]"), + tr(USAGE_HELP), tr("Show the help section or the documentation about a <command>.")); } //---------------------------------------------------------------------------------------------------- @@ -2801,7 +3099,7 @@ bool simple_wallet::set_log(const std::vector<std::string> &args) { if(args.size() > 1) { - fail_msg_writer() << tr("usage: set_log <log_level_number_0-4> | <categories>"); + PRINT_USAGE(USAGE_SET_LOG); return true; } if(!args.empty()) @@ -2811,7 +3109,7 @@ bool simple_wallet::set_log(const std::vector<std::string> &args) { if(4 < level) { - fail_msg_writer() << tr("wrong number range, use: set_log <log_level_number_0-4> | <categories>"); + fail_msg_writer() << boost::format(tr("wrong number range, use: %s")) % USAGE_SET_LOG; return true; } mlog_set_log_level(level); @@ -4107,7 +4405,7 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args) if (!ok) { - fail_msg_writer() << tr("invalid arguments. Please use start_mining [<number_of_threads>] [do_bg_mining] [ignore_battery]"); + PRINT_USAGE(USAGE_START_MINING); return true; } @@ -4149,7 +4447,7 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args) if (args.size() < 1) { - fail_msg_writer() << tr("missing daemon URL argument"); + PRINT_USAGE(USAGE_SET_DAEMON); return true; } @@ -4494,7 +4792,7 @@ bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::v { if (args.size() > 1 || (args.size() == 1 && args[0] != "detail")) { - fail_msg_writer() << tr("usage: balance [detail]"); + PRINT_USAGE(USAGE_SHOW_BALANCE); return true; } LOCK_IDLE_SCOPE(); @@ -4506,7 +4804,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args { if (args.size() > 3) { - fail_msg_writer() << tr("usage: incoming_transfers [available|unavailable] [verbose] [index=<N1>[,<N2>[,...]]]"); + PRINT_USAGE(USAGE_INCOMING_TRANSFERS); return true; } auto local_args = args; @@ -4548,7 +4846,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args if (local_args.size() > 0) { - fail_msg_writer() << tr("usage: incoming_transfers [available|unavailable] [verbose] [index=<N1>[,<N2>[,...]]]"); + PRINT_USAGE(USAGE_INCOMING_TRANSFERS); return true; } @@ -4613,7 +4911,7 @@ bool simple_wallet::show_payments(const std::vector<std::string> &args) { if(args.empty()) { - fail_msg_writer() << tr("expected at least one payment ID"); + PRINT_USAGE(USAGE_PAYMENTS); return true; } @@ -4843,11 +5141,11 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_) +bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_, bool called_by_mms) { // "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]" if (!try_connect_to_daemon()) - return true; + return false; std::vector<std::string> local_args = args_; @@ -4855,7 +5153,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=") { if (!parse_subaddress_indices(local_args[0], subaddr_indices)) - return true; + return false; local_args.erase(local_args.begin()); } @@ -4877,7 +5175,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri else if (ring_size == 0) { fail_msg_writer() << tr("Ring size must not be 0"); - return true; + return false; } else { @@ -4889,19 +5187,19 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (adjusted_fake_outs_count > fake_outs_count) { fail_msg_writer() << (boost::format(tr("ring size %u is too small, minimum is %u")) % (fake_outs_count+1) % (adjusted_fake_outs_count+1)).str(); - return true; + return false; } if (adjusted_fake_outs_count < fake_outs_count) { fail_msg_writer() << (boost::format(tr("ring size %u is too large, maximum is %u")) % (fake_outs_count+1) % (adjusted_fake_outs_count+1)).str(); - return true; + return false; } const size_t min_args = (transfer_type == TransferLocked) ? 2 : 1; if(local_args.size() < min_args) { fail_msg_writer() << tr("wrong number of arguments"); - return true; + return false; } std::vector<uint8_t> extra; @@ -4936,7 +5234,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if(!r) { fail_msg_writer() << tr("payment id failed to encode"); - return true; + return false; } } @@ -4950,12 +5248,12 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri catch (const std::exception &e) { fail_msg_writer() << tr("bad locked_blocks parameter:") << " " << local_args.back(); - return true; + return false; } if (locked_blocks > 1000000) { fail_msg_writer() << tr("Locked blocks too high, max 1000000 (Ëœ4 yrs)"); - return true; + return false; } local_args.pop_back(); } @@ -4983,7 +5281,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (!tools::wallet2::parse_short_payment_id(payment_id_uri, info.payment_id)) { fail_msg_writer() << tr("failed to parse short payment ID from URI"); - return true; + return false; } info.has_payment_id = true; } @@ -4998,7 +5296,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri { fail_msg_writer() << tr("amount is wrong: ") << local_args[i] << ' ' << local_args[i + 1] << ", " << tr("expected number from 0 to ") << print_money(std::numeric_limits<uint64_t>::max()); - return true; + return false; } i += 2; } @@ -5008,13 +5306,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri fail_msg_writer() << tr("Invalid last argument: ") << local_args.back() << ": " << error; else fail_msg_writer() << tr("Invalid last argument: ") << local_args.back(); - return true; + return false; } if (!r) { fail_msg_writer() << tr("failed to parse address"); - return true; + return false; } de.addr = info.address; de.is_subaddress = info.is_subaddress; @@ -5025,7 +5323,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (payment_id_seen) { fail_msg_writer() << tr("a single transaction cannot use more than one payment id"); - return true; + return false; } crypto::hash payment_id; @@ -5042,13 +5340,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri else { fail_msg_writer() << tr("failed to parse payment id, though it was detected"); - return true; + return false; } bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce); if(!r) { fail_msg_writer() << tr("failed to set up payment id, though it was decoded correctly"); - return true; + return false; } payment_id_seen = true; } @@ -5061,16 +5359,16 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri { std::string accepted = input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): ")); if (std::cin.eof()) - return true; + return false; if (!command_line::is_yes(accepted)) { fail_msg_writer() << tr("transaction cancelled."); - return true; + return false; } } - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); try { @@ -5085,7 +5383,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (!err.empty()) { fail_msg_writer() << tr("failed to get blockchain height: ") << err; - return true; + return false; } unlock_block = bc_height + locked_blocks; ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); @@ -5101,7 +5399,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (ptx_vector.empty()) { fail_msg_writer() << tr("No outputs found, or daemon is not ready"); - return true; + return false; } // if we need to check for backlog, check the worst case tx @@ -5141,12 +5439,12 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri { std::string accepted = input_line(prompt_str); if (std::cin.eof()) - return true; + return false; if (!command_line::is_yes(accepted)) { fail_msg_writer() << tr("transaction cancelled."); - return true; + return false; } } } @@ -5206,7 +5504,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (m_wallet->print_ring_members()) { if (!print_ring_members(ptx_vector, prompt)) - return true; + return false; } bool default_ring_size = true; for (const auto &ptx: ptx_vector) @@ -5229,22 +5527,32 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri std::string accepted = input_line(prompt.str()); if (std::cin.eof()) - return true; + return false; if (!command_line::is_yes(accepted)) { fail_msg_writer() << tr("transaction cancelled."); - return true; + return false; } } // actually commit the transactions - if (m_wallet->multisig()) + if (m_wallet->multisig() && called_by_mms) + { + std::string ciphertext = m_wallet->save_multisig_tx(ptx_vector); + if (!ciphertext.empty()) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::partially_signed_tx, ciphertext); + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to MMS"); + } + } + else if (m_wallet->multisig()) { bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); if (!r) { fail_msg_writer() << tr("Failed to write transaction(s) to file"); + return false; } else { @@ -5258,7 +5566,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri tools::wallet2::signed_tx_set signed_tx; if (!cold_sign_tx(ptx_vector, signed_tx, dsts_info, [&](const tools::wallet2::signed_tx_set &tx){ return accept_loaded_tx(tx); })){ fail_msg_writer() << tr("Failed to cold sign transaction with HW wallet"); - return true; + return false; } commit_or_save(signed_tx.ptx, m_do_not_relay); @@ -5266,11 +5574,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri catch (const std::exception& e) { handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); + return false; } catch (...) { LOG_ERROR("Unknown error"); fail_msg_writer() << tr("unknown error"); + return false; } } else if (m_wallet->watch_only()) @@ -5279,6 +5589,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (!r) { fail_msg_writer() << tr("Failed to write transaction(s) to file"); + return false; } else { @@ -5293,11 +5604,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri catch (const std::exception &e) { handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); + return false; } catch (...) { LOG_ERROR("unknown error"); fail_msg_writer() << tr("unknown error"); + return false; } return true; @@ -5305,12 +5618,14 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer(const std::vector<std::string> &args_) { - return transfer_main(Transfer, args_); + transfer_main(Transfer, args_, false); + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::locked_transfer(const std::vector<std::string> &args_) { - return transfer_main(TransferLocked, args_); + transfer_main(TransferLocked, args_, false); + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_) @@ -5429,7 +5744,14 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st { auto print_usage = [below]() { - fail_msg_writer() << boost::format(tr("usage: %s [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id>]")) % (below ? "sweep_below" : "sweep_all"); + if (below) + { + PRINT_USAGE(USAGE_SWEEP_BELOW); + } + else + { + PRINT_USAGE(USAGE_SWEEP_ALL); + } }; if (args_.size() == 0) { @@ -5847,7 +6169,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) if (local_args.size() != 2) { - fail_msg_writer() << tr("usage: sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id>]"); + PRINT_USAGE(USAGE_SWEEP_SINGLE); return true; } @@ -6011,7 +6333,7 @@ bool simple_wallet::donate(const std::vector<std::string> &args_) std::vector<std::string> local_args = args_; if(local_args.empty() || local_args.size() > 5) { - fail_msg_writer() << tr("usage: donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]"); + PRINT_USAGE(USAGE_DONATE); return true; } std::string amount_str; @@ -6224,7 +6546,7 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) } if (args_.size() > 1 || (args_.size() == 1 && args_[0] != "export_raw")) { - fail_msg_writer() << tr("usage: sign_transfer [export_raw]"); + PRINT_USAGE(USAGE_SIGN_TRANSFER); return true; } @@ -6314,7 +6636,7 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) return true; } if(local_args.size() != 1) { - fail_msg_writer() << tr("usage: get_tx_key <txid>"); + PRINT_USAGE(USAGE_GET_TX_KEY); return true; } @@ -6350,7 +6672,7 @@ bool simple_wallet::set_tx_key(const std::vector<std::string> &args_) std::vector<std::string> local_args = args_; if(local_args.size() != 2) { - fail_msg_writer() << tr("usage: set_tx_key <txid> <tx_key>"); + PRINT_USAGE(USAGE_SET_TX_KEY); return true; } @@ -6412,7 +6734,7 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) } if (args.size() != 2 && args.size() != 3) { - fail_msg_writer() << tr("usage: get_tx_proof <txid> <address> [<message>]"); + PRINT_USAGE(USAGE_GET_TX_PROOF); return true; } @@ -6453,7 +6775,7 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) std::vector<std::string> local_args = args_; if(local_args.size() != 3) { - fail_msg_writer() << tr("usage: check_tx_key <txid> <txkey> <address>"); + PRINT_USAGE(USAGE_CHECK_TX_KEY); return true; } @@ -6539,7 +6861,7 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) { if(args.size() != 3 && args.size() != 4) { - fail_msg_writer() << tr("usage: check_tx_proof <txid> <address> <signature_file> [<message>]"); + PRINT_USAGE(USAGE_CHECK_TX_PROOF); return true; } @@ -6622,7 +6944,7 @@ bool simple_wallet::get_spend_proof(const std::vector<std::string> &args) return true; } if(args.size() != 1 && args.size() != 2) { - fail_msg_writer() << tr("usage: get_spend_proof <txid> [<message>]"); + PRINT_USAGE(USAGE_GET_SPEND_PROOF); return true; } @@ -6663,7 +6985,7 @@ bool simple_wallet::get_spend_proof(const std::vector<std::string> &args) bool simple_wallet::check_spend_proof(const std::vector<std::string> &args) { if(args.size() != 2 && args.size() != 3) { - fail_msg_writer() << tr("usage: check_spend_proof <txid> <signature_file> [<message>]"); + PRINT_USAGE(USAGE_CHECK_SPEND_PROOF); return true; } @@ -6706,7 +7028,7 @@ bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args) return true; } if(args.size() != 1 && args.size() != 2) { - fail_msg_writer() << tr("usage: get_reserve_proof (all|<amount>) [<message>]"); + PRINT_USAGE(USAGE_GET_RESERVE_PROOF); return true; } @@ -6752,7 +7074,7 @@ bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args) 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>]"); + PRINT_USAGE(USAGE_CHECK_RESERVE_PROOF); return true; } @@ -6799,19 +7121,6 @@ bool simple_wallet::check_reserve_proof(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- -static std::string get_human_readable_timestamp(uint64_t ts) -{ - char buffer[64]; - if (ts < 1234567890) - return "<unknown>"; - time_t tt = ts; - struct tm tm; - epee::misc_utils::get_gmt_time(tt, tm); - uint64_t now = time(NULL); - strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); - return std::string(buffer); -} -//---------------------------------------------------------------------------------------------------- static std::string get_human_readable_timespan(std::chrono::seconds seconds) { uint64_t ts = seconds.count(); @@ -7096,7 +7405,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) % transfer.block % transfer.direction % transfer.unlocked - % get_human_readable_timestamp(transfer.timestamp) + % tools::get_human_readable_timestamp(transfer.timestamp) % print_money(transfer.amount) % string_tools::pod_to_hex(transfer.hash) % transfer.payment_id @@ -7160,7 +7469,7 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_) % transfer.block % transfer.direction % transfer.unlocked - % get_human_readable_timestamp(transfer.timestamp) + % tools::get_human_readable_timestamp(transfer.timestamp) % print_money(transfer.amount) % print_money(running_balance) % string_tools::pod_to_hex(transfer.hash) @@ -7202,7 +7511,7 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) { if(args_.size() > 3) { - fail_msg_writer() << tr("usage: unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]"); + PRINT_USAGE(USAGE_UNSPENT_OUTPUTS); return true; } auto local_args = args_; @@ -7343,7 +7652,7 @@ bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_) { if (args_[0] != "hard") { - fail_msg_writer() << tr("usage: rescan_bc [hard]"); + PRINT_USAGE(USAGE_RESCAN_BC); return true; } hard = true; @@ -7363,6 +7672,22 @@ bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_) return refresh_main(0, hard ? ResetHard : ResetSoft, true); } //---------------------------------------------------------------------------------------------------- +void simple_wallet::check_for_messages() +{ + try + { + std::vector<mms::message> new_messages; + bool new_message = get_message_store().check_for_messages(get_multisig_wallet_state(), new_messages); + if (new_message) + { + message_writer(console_color_magenta, true) << tr("MMS received new message"); + list_mms_messages(new_messages); + m_cmd_binder.print_prompt(); + } + } + catch(...) {} +} +//---------------------------------------------------------------------------------------------------- void simple_wallet::wallet_idle_thread() { while (true) @@ -7385,6 +7710,14 @@ void simple_wallet::wallet_idle_thread() m_auto_refresh_refreshing = false; } + // Check for new MMS messages; + // For simplicity auto message check is ALSO controlled by "m_auto_refresh_enabled" and has no + // separate thread either; thread syncing is tricky enough with only this one idle thread here + if (m_auto_refresh_enabled && get_message_store().get_active()) + { + check_for_messages(); + } + if (!m_idle_run.load(std::memory_order_relaxed)) break; m_idle_cond.wait_for(lock, boost::chrono::seconds(90)); @@ -7564,14 +7897,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector } else { - fail_msg_writer() << tr("usage:\n" - " account\n" - " account new <label text with white spaces allowed>\n" - " account switch <index>\n" - " account label <index> <label text with white spaces allowed>\n" - " account tag <tag_name> <account_index_1> [<account_index_2> ...]\n" - " account untag <account_index_1> [<account_index_2> ...]\n" - " account tag_description <tag_name> <description>"); + PRINT_USAGE(USAGE_ACCOUNT); } return true; } @@ -7726,7 +8052,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std:: } else { - fail_msg_writer() << tr("usage: address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> ]"); + PRINT_USAGE(USAGE_ADDRESS); } return true; @@ -7737,7 +8063,7 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg crypto::hash8 payment_id; if (args.size() > 1) { - fail_msg_writer() << tr("usage: integrated_address [payment ID]"); + PRINT_USAGE(USAGE_INTEGRATED_ADDRESS); return true; } if (args.size() == 0) @@ -7789,7 +8115,7 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v } else if (args.size() == 1 || (args[0] != "add" && args[0] != "delete")) { - fail_msg_writer() << tr("usage: address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)]"); + PRINT_USAGE(USAGE_ADDRESS_BOOK); return true; } else if (args[0] == "add") @@ -7864,7 +8190,7 @@ bool simple_wallet::set_tx_note(const std::vector<std::string> &args) { if (args.size() == 0) { - fail_msg_writer() << tr("usage: set_tx_note [txid] free text note"); + PRINT_USAGE(USAGE_SET_TX_NOTE); return true; } @@ -7892,7 +8218,7 @@ bool simple_wallet::get_tx_note(const std::vector<std::string> &args) { if (args.size() != 1) { - fail_msg_writer() << tr("usage: get_tx_note [txid]"); + PRINT_USAGE(USAGE_GET_TX_NOTE); return true; } @@ -7933,7 +8259,7 @@ bool simple_wallet::get_description(const std::vector<std::string> &args) { if (args.size() != 0) { - fail_msg_writer() << tr("usage: get_description"); + PRINT_USAGE(USAGE_GET_DESCRIPTION); return true; } @@ -8006,7 +8332,7 @@ bool simple_wallet::sign(const std::vector<std::string> &args) } if (args.size() != 1) { - fail_msg_writer() << tr("usage: sign <filename>"); + PRINT_USAGE(USAGE_SIGN); return true; } if (m_wallet->watch_only()) @@ -8040,7 +8366,7 @@ bool simple_wallet::verify(const std::vector<std::string> &args) { if (args.size() != 3) { - fail_msg_writer() << tr("usage: verify <filename> <address> <signature>"); + PRINT_USAGE(USAGE_VERIFY); return true; } std::string filename = args[0]; @@ -8083,7 +8409,7 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args) } if (args.size() != 1) { - fail_msg_writer() << tr("usage: export_key_images <filename>"); + PRINT_USAGE(USAGE_EXPORT_KEY_IMAGES); return true; } if (m_wallet->watch_only()) @@ -8132,7 +8458,7 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args) if (args.size() != 1) { - fail_msg_writer() << tr("usage: import_key_images <filename>"); + PRINT_USAGE(USAGE_IMPORT_KEY_IMAGES); return true; } std::string filename = args[0]; @@ -8239,7 +8565,7 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args) } if (args.size() != 1) { - fail_msg_writer() << tr("usage: export_outputs <filename>"); + PRINT_USAGE(USAGE_EXPORT_OUTPUTS); return true; } @@ -8279,7 +8605,7 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args) } if (args.size() != 1) { - fail_msg_writer() << tr("usage: import_outputs <filename>"); + PRINT_USAGE(USAGE_IMPORT_OUTPUTS); return true; } std::string filename = args[0]; @@ -8311,7 +8637,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) { if (args.size() != 1) { - fail_msg_writer() << tr("usage: show_transfer <txid>"); + PRINT_USAGE(USAGE_SHOW_TRANSFER); return true; } @@ -8336,7 +8662,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) success_msg_writer() << "Incoming transaction found"; success_msg_writer() << "txid: " << txid; success_msg_writer() << "Height: " << pd.m_block_height; - success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Timestamp: " << tools::get_human_readable_timestamp(pd.m_timestamp); success_msg_writer() << "Amount: " << print_money(pd.m_amount); success_msg_writer() << "Payment ID: " << payment_id; if (pd.m_unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) @@ -8386,7 +8712,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) success_msg_writer() << "Outgoing transaction found"; success_msg_writer() << "txid: " << txid; success_msg_writer() << "Height: " << pd.m_block_height; - success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Timestamp: " << tools::get_human_readable_timestamp(pd.m_timestamp); success_msg_writer() << "Amount: " << print_money(pd.m_amount_in - change - fee); success_msg_writer() << "Payment ID: " << payment_id; success_msg_writer() << "Change: " << print_money(change); @@ -8411,7 +8737,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) payment_id = payment_id.substr(0,16); success_msg_writer() << "Unconfirmed incoming transaction found in the txpool"; success_msg_writer() << "txid: " << txid; - success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Timestamp: " << tools::get_human_readable_timestamp(pd.m_timestamp); success_msg_writer() << "Amount: " << print_money(pd.m_amount); success_msg_writer() << "Payment ID: " << payment_id; success_msg_writer() << "Address index: " << pd.m_subaddr_index.minor; @@ -8442,7 +8768,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) success_msg_writer() << (is_failed ? "Failed" : "Pending") << " outgoing transaction found"; success_msg_writer() << "txid: " << txid; - success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Timestamp: " << tools::get_human_readable_timestamp(pd.m_timestamp); success_msg_writer() << "Amount: " << print_money(amount - pd.m_change - fee); success_msg_writer() << "Payment ID: " << payment_id; success_msg_writer() << "Change: " << print_money(pd.m_change); @@ -8543,7 +8869,7 @@ int main(int argc, char* argv[]) bool should_terminate = false; std::tie(vm, should_terminate) = wallet_args::main( argc, argv, - "monero-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [<COMMAND>]", + "monero-wallet-cli [--wallet-file=<filename>|--generate-new-wallet=<filename>] [<COMMAND>]", sw::tr("This is the command line monero wallet. It needs to connect to a monero\ndaemon to work correctly.\nWARNING: Do not reuse your Monero keys on another fork, UNLESS this fork has key reuse mitigations built in. Doing so will harm your privacy."), desc_params, positional_options, @@ -8602,3 +8928,1010 @@ int main(int argc, char* argv[]) return 0; CATCH_ENTRY_L0("main", 1); } + +// MMS --------------------------------------------------------------------------------------------------- + +// Access to the message store, or more exactly to the list of the messages that can be changed +// by the idle thread, is guarded by the same mutex-based mechanism as access to the wallet +// as a whole and thus e.g. uses the "LOCK_IDLE_SCOPE" macro. This is a little over-cautious, but +// simple and safe. Care has to be taken however where MMS methods call other simplewallet methods +// that use "LOCK_IDLE_SCOPE" as this cannot be nested! + +// Methods for commands like "export_multisig_info" usually read data from file(s) or write data +// to files. The MMS calls now those methods as well, to produce data for messages and to process data +// from messages. As it would be quite inconvenient for the MMS to write data for such methods to files +// first or get data out of result files after the call, those methods detect a call from the MMS and +// expect data as arguments instead of files and give back data by calling 'process_wallet_created_data'. + +bool simple_wallet::user_confirms(const std::string &question) +{ + std::string answer = input_line(question + tr(" (Y/Yes/N/No): ")); + return !std::cin.eof() && command_line::is_yes(answer); +} + +bool simple_wallet::get_number_from_arg(const std::string &arg, uint32_t &number, const uint32_t lower_bound, const uint32_t upper_bound) +{ + bool valid = false; + try + { + number = boost::lexical_cast<uint32_t>(arg); + valid = (number >= lower_bound) && (number <= upper_bound); + } + catch(const boost::bad_lexical_cast &) + { + } + return valid; +} + +bool simple_wallet::choose_mms_processing(const std::vector<mms::processing_data> &data_list, uint32_t &choice) +{ + size_t choices = data_list.size(); + if (choices == 1) + { + choice = 0; + return true; + } + mms::message_store& ms = m_wallet->get_message_store(); + message_writer() << tr("Choose processing:"); + std::string text; + for (size_t i = 0; i < choices; ++i) + { + const mms::processing_data &data = data_list[i]; + text = std::to_string(i+1) + ": "; + switch (data.processing) + { + case mms::message_processing::sign_tx: + text += tr("Sign tx"); + break; + case mms::message_processing::send_tx: + { + mms::message m; + ms.get_message_by_id(data.message_ids[0], m); + if (m.type == mms::message_type::fully_signed_tx) + { + text += tr("Send the tx for submission to "); + } + else + { + text += tr("Send the tx for signing to "); + } + mms::authorized_signer signer = ms.get_signer(data.receiving_signer_index); + text += ms.signer_to_string(signer, 50); + break; + } + case mms::message_processing::submit_tx: + text += tr("Submit tx"); + break; + default: + text += tr("unknown"); + break; + } + message_writer() << text; + } + + std::string line = input_line(tr("Choice: ")); + if (std::cin.eof() || line.empty()) + { + return false; + } + bool choice_ok = get_number_from_arg(line, choice, 1, choices); + if (choice_ok) + { + choice--; + } + else + { + fail_msg_writer() << tr("Wrong choice"); + } + return choice_ok; +} + +void simple_wallet::list_mms_messages(const std::vector<mms::message> &messages) +{ + message_writer() << boost::format("%4s %-4s %-30s %-21s %7s %3s %-15s %-40s") % tr("Id") % tr("I/O") % tr("Authorized Signer") + % tr("Message Type") % tr("Height") % tr("R") % tr("Message State") % tr("Since"); + mms::message_store& ms = m_wallet->get_message_store(); + uint64_t now = (uint64_t)time(NULL); + for (size_t i = 0; i < messages.size(); ++i) + { + const mms::message &m = messages[i]; + const mms::authorized_signer &signer = ms.get_signer(m.signer_index); + bool highlight = (m.state == mms::message_state::ready_to_send) || (m.state == mms::message_state::waiting); + message_writer(m.direction == mms::message_direction::out ? console_color_green : console_color_magenta, highlight) << + boost::format("%4s %-4s %-30s %-21s %7s %3s %-15s %-40s") % + m.id % + ms.message_direction_to_string(m.direction) % + ms.signer_to_string(signer, 30) % + ms.message_type_to_string(m.type) % + m.wallet_height % + m.round % + ms.message_state_to_string(m.state) % + (tools::get_human_readable_timestamp(m.modified) + ", " + get_human_readable_timespan(std::chrono::seconds(now - m.modified)) + tr(" ago")); + } +} + +void simple_wallet::list_signers(const std::vector<mms::authorized_signer> &signers) +{ + message_writer() << boost::format("%2s %-20s %-s") % tr("#") % tr("Label") % tr("Transport Address"); + message_writer() << boost::format("%2s %-20s %-s") % "" % tr("Auto-Config Token") % tr("Monero Address"); + for (size_t i = 0; i < signers.size(); ++i) + { + const mms::authorized_signer &signer = signers[i]; + std::string label = signer.label.empty() ? tr("<not set>") : signer.label; + std::string monero_address; + if (signer.monero_address_known) + { + monero_address = get_account_address_as_str(m_wallet->nettype(), false, signer.monero_address); + } + else + { + monero_address = tr("<not set>"); + } + std::string transport_address = signer.transport_address.empty() ? tr("<not set>") : signer.transport_address; + message_writer() << boost::format("%2s %-20s %-s") % (i + 1) % label % transport_address; + message_writer() << boost::format("%2s %-20s %-s") % "" % signer.auto_config_token % monero_address; + message_writer() << ""; + } +} + +void simple_wallet::add_signer_config_messages() +{ + mms::message_store& ms = m_wallet->get_message_store(); + std::string signer_config; + ms.get_signer_config(signer_config); + + const std::vector<mms::authorized_signer> signers = ms.get_all_signers(); + mms::multisig_wallet_state state = get_multisig_wallet_state(); + uint32_t num_authorized_signers = ms.get_num_authorized_signers(); + for (uint32_t i = 1 /* without me */; i < num_authorized_signers; ++i) + { + ms.add_message(state, i, mms::message_type::signer_config, mms::message_direction::out, signer_config); + } +} + +void simple_wallet::show_message(const mms::message &m) +{ + mms::message_store& ms = m_wallet->get_message_store(); + const mms::authorized_signer &signer = ms.get_signer(m.signer_index); + bool display_content; + std::string sanitized_text; + switch (m.type) + { + case mms::message_type::key_set: + case mms::message_type::additional_key_set: + case mms::message_type::note: + display_content = true; + ms.get_sanitized_message_text(m, sanitized_text); + break; + default: + display_content = false; + } + uint64_t now = (uint64_t)time(NULL); + message_writer() << ""; + message_writer() << tr("Message ") << m.id; + message_writer() << tr("In/out: ") << ms.message_direction_to_string(m.direction); + message_writer() << tr("Type: ") << ms.message_type_to_string(m.type); + message_writer() << tr("State: ") << boost::format(tr("%s since %s, %s ago")) % + ms.message_state_to_string(m.state) % tools::get_human_readable_timestamp(m.modified) % get_human_readable_timespan(std::chrono::seconds(now - m.modified)); + if (m.sent == 0) + { + message_writer() << tr("Sent: Never"); + } + else + { + message_writer() << boost::format(tr("Sent: %s, %s ago")) % + tools::get_human_readable_timestamp(m.sent) % get_human_readable_timespan(std::chrono::seconds(now - m.sent)); + } + message_writer() << tr("Authorized signer: ") << ms.signer_to_string(signer, 100); + message_writer() << tr("Content size: ") << m.content.length() << tr(" bytes"); + message_writer() << tr("Content: ") << (display_content ? sanitized_text : tr("(binary data)")); + + if (m.type == mms::message_type::note) + { + // Showing a note and read its text is "processing" it: Set the state accordingly + // which will also delete it from Bitmessage as a side effect + // (Without this little "twist" it would never change the state, and never get deleted) + ms.set_message_processed_or_sent(m.id); + } +} + +void simple_wallet::ask_send_all_ready_messages() +{ + mms::message_store& ms = m_wallet->get_message_store(); + std::vector<mms::message> ready_messages; + const std::vector<mms::message> &messages = ms.get_all_messages(); + for (size_t i = 0; i < messages.size(); ++i) + { + const mms::message &m = messages[i]; + if (m.state == mms::message_state::ready_to_send) + { + ready_messages.push_back(m); + } + } + if (ready_messages.size() != 0) + { + list_mms_messages(ready_messages); + bool send = ms.get_auto_send(); + if (!send) + { + send = user_confirms(tr("Send these messages now?")); + } + if (send) + { + mms::multisig_wallet_state state = get_multisig_wallet_state(); + for (size_t i = 0; i < ready_messages.size(); ++i) + { + ms.send_message(state, ready_messages[i].id); + ms.set_message_processed_or_sent(ready_messages[i].id); + } + success_msg_writer() << tr("Queued for sending."); + } + } +} + +bool simple_wallet::get_message_from_arg(const std::string &arg, mms::message &m) +{ + mms::message_store& ms = m_wallet->get_message_store(); + bool valid_id = false; + uint32_t id; + try + { + id = (uint32_t)boost::lexical_cast<uint32_t>(arg); + valid_id = ms.get_message_by_id(id, m); + } + catch (const boost::bad_lexical_cast &) + { + } + if (!valid_id) + { + fail_msg_writer() << tr("Invalid message id"); + } + return valid_id; +} + +void simple_wallet::mms_init(const std::vector<std::string> &args) +{ + if (args.size() != 3) + { + fail_msg_writer() << tr("usage: mms init <required_signers>/<authorized_signers> <own_label> <own_transport_address>"); + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + if (ms.get_active()) + { + if (!user_confirms(tr("The MMS is already initialized. Re-initialize by deleting all signer info and messages?"))) + { + return; + } + } + uint32_t num_required_signers; + uint32_t num_authorized_signers; + const std::string &mn = args[0]; + std::vector<std::string> numbers; + boost::split(numbers, mn, boost::is_any_of("/")); + bool mn_ok = (numbers.size() == 2) + && get_number_from_arg(numbers[1], num_authorized_signers, 2, 100) + && get_number_from_arg(numbers[0], num_required_signers, 2, num_authorized_signers); + if (!mn_ok) + { + fail_msg_writer() << tr("Error in the number of required signers and/or authorized signers"); + return; + } + LOCK_IDLE_SCOPE(); + ms.init(get_multisig_wallet_state(), args[1], args[2], num_authorized_signers, num_required_signers); +} + +void simple_wallet::mms_info(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + if (ms.get_active()) + { + message_writer() << boost::format("The MMS is active for %s/%s multisig.") + % ms.get_num_required_signers() % ms.get_num_authorized_signers(); + } + else + { + message_writer() << tr("The MMS is not active."); + } +} + +void simple_wallet::mms_signer(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + const std::vector<mms::authorized_signer> &signers = ms.get_all_signers(); + if (args.size() == 0) + { + // Without further parameters list all defined signers + list_signers(signers); + return; + } + + uint32_t index; + bool index_valid = get_number_from_arg(args[0], index, 1, ms.get_num_authorized_signers()); + if (index_valid) + { + index--; + } + else + { + fail_msg_writer() << tr("Invalid signer number ") + args[0]; + return; + } + if ((args.size() < 2) || (args.size() > 4)) + { + fail_msg_writer() << tr("mms signer [<number> <label> [<transport_address> [<monero_address>]]]"); + return; + } + + boost::optional<string> label = args[1]; + boost::optional<string> transport_address; + if (args.size() >= 3) + { + transport_address = args[2]; + } + boost::optional<cryptonote::account_public_address> monero_address; + LOCK_IDLE_SCOPE(); + mms::multisig_wallet_state state = get_multisig_wallet_state(); + if (args.size() == 4) + { + cryptonote::address_parse_info info; + bool ok = cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[3], oa_prompter); + if (!ok) + { + fail_msg_writer() << tr("Invalid Monero address"); + return; + } + monero_address = info.address; + const std::vector<mms::message> &messages = ms.get_all_messages(); + if ((messages.size() > 0) || state.multisig) + { + fail_msg_writer() << tr("Wallet state does not allow changing Monero addresses anymore"); + return; + } + } + ms.set_signer(state, index, label, transport_address, monero_address); +} + +void simple_wallet::mms_list(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + if (args.size() != 0) + { + fail_msg_writer() << tr("Usage: mms list"); + return; + } + LOCK_IDLE_SCOPE(); + const std::vector<mms::message> &messages = ms.get_all_messages(); + list_mms_messages(messages); +} + +void simple_wallet::mms_next(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + if ((args.size() > 1) || ((args.size() == 1) && (args[0] != "sync"))) + { + fail_msg_writer() << tr("Usage: mms next [sync]"); + return; + } + bool avail = false; + std::vector<mms::processing_data> data_list; + bool force_sync = false; + uint32_t choice = 0; + { + LOCK_IDLE_SCOPE(); + if ((args.size() == 1) && (args[0] == "sync")) + { + // Force the MMS to process any waiting sync info although on its own it would just ignore + // those messages because no need to process them can be seen + force_sync = true; + } + string wait_reason; + { + avail = ms.get_processable_messages(get_multisig_wallet_state(), force_sync, data_list, wait_reason); + } + if (avail) + { + avail = choose_mms_processing(data_list, choice); + } + else if (!wait_reason.empty()) + { + message_writer() << tr("No next step: ") << wait_reason; + } + } + if (avail) + { + mms::processing_data data = data_list[choice]; + bool command_successful = false; + switch(data.processing) + { + case mms::message_processing::prepare_multisig: + message_writer() << tr("prepare_multisig"); + command_successful = prepare_multisig_main(std::vector<std::string>(), true); + break; + + case mms::message_processing::make_multisig: + { + message_writer() << tr("make_multisig"); + size_t number_of_key_sets = data.message_ids.size(); + std::vector<std::string> sig_args(number_of_key_sets + 1); + sig_args[0] = std::to_string(ms.get_num_required_signers()); + for (size_t i = 0; i < number_of_key_sets; ++i) + { + mms::message m = ms.get_message_by_id(data.message_ids[i]); + sig_args[i+1] = m.content; + } + command_successful = make_multisig_main(sig_args, true); + break; + } + + case mms::message_processing::exchange_multisig_keys: + { + message_writer() << tr("exchange_multisig_keys"); + size_t number_of_key_sets = data.message_ids.size(); + // Other than "make_multisig" only the key sets as parameters, no num_required_signers + std::vector<std::string> sig_args(number_of_key_sets); + for (size_t i = 0; i < number_of_key_sets; ++i) + { + mms::message m = ms.get_message_by_id(data.message_ids[i]); + sig_args[i] = m.content; + } + command_successful = exchange_multisig_keys_main(sig_args, true); + break; + } + + case mms::message_processing::create_sync_data: + { + message_writer() << tr("export_multisig_info"); + std::vector<std::string> export_args; + export_args.push_back("MMS"); // dummy filename + command_successful = export_multisig_main(export_args, true); + break; + } + + case mms::message_processing::process_sync_data: + { + message_writer() << tr("import_multisig_info"); + std::vector<std::string> import_args; + for (size_t i = 0; i < data.message_ids.size(); ++i) + { + mms::message m = ms.get_message_by_id(data.message_ids[i]); + import_args.push_back(m.content); + } + command_successful = import_multisig_main(import_args, true); + break; + } + + case mms::message_processing::sign_tx: + { + message_writer() << tr("sign_multisig"); + std::vector<std::string> sign_args; + mms::message m = ms.get_message_by_id(data.message_ids[0]); + sign_args.push_back(m.content); + command_successful = sign_multisig_main(sign_args, true); + break; + } + + case mms::message_processing::submit_tx: + { + message_writer() << tr("submit_multisig"); + std::vector<std::string> submit_args; + mms::message m = ms.get_message_by_id(data.message_ids[0]); + submit_args.push_back(m.content); + command_successful = submit_multisig_main(submit_args, true); + break; + } + + case mms::message_processing::send_tx: + { + message_writer() << tr("Send tx"); + mms::message m = ms.get_message_by_id(data.message_ids[0]); + LOCK_IDLE_SCOPE(); + ms.add_message(get_multisig_wallet_state(), data.receiving_signer_index, m.type, mms::message_direction::out, + m.content); + command_successful = true; + break; + } + + case mms::message_processing::process_signer_config: + { + message_writer() << tr("Process signer config"); + LOCK_IDLE_SCOPE(); + mms::message m = ms.get_message_by_id(data.message_ids[0]); + mms::authorized_signer me = ms.get_signer(0); + mms::multisig_wallet_state state = get_multisig_wallet_state(); + if (!me.auto_config_running) + { + // If no auto-config is running, the config sent may be unsolicited or problematic + // so show what arrived and ask for confirmation before taking it in + std::vector<mms::authorized_signer> signers; + ms.unpack_signer_config(state, m.content, signers); + list_signers(signers); + if (!user_confirms(tr("Replace current signer config with the one displayed above?"))) + { + break; + } + } + ms.process_signer_config(state, m.content); + ms.stop_auto_config(); + list_signers(ms.get_all_signers()); + command_successful = true; + break; + } + + case mms::message_processing::process_auto_config_data: + { + message_writer() << tr("Process auto config data"); + LOCK_IDLE_SCOPE(); + for (size_t i = 0; i < data.message_ids.size(); ++i) + { + ms.process_auto_config_data_message(data.message_ids[i]); + } + ms.stop_auto_config(); + list_signers(ms.get_all_signers()); + add_signer_config_messages(); + command_successful = true; + break; + } + + default: + message_writer() << tr("Nothing ready to process"); + break; + } + + if (command_successful) + { + { + LOCK_IDLE_SCOPE(); + ms.set_messages_processed(data); + ask_send_all_ready_messages(); + } + } + } +} + +void simple_wallet::mms_sync(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + if (args.size() != 0) + { + fail_msg_writer() << tr("Usage: mms sync"); + return; + } + // Force the start of a new sync round, for exceptional cases where something went wrong + // Can e.g. solve the problem "This signature was made with stale data" after trying to + // create 2 transactions in a row somehow + // Code is identical to the code for 'message_processing::create_sync_data' + message_writer() << tr("export_multisig_info"); + std::vector<std::string> export_args; + export_args.push_back("MMS"); // dummy filename + export_multisig_main(export_args, true); + ask_send_all_ready_messages(); +} + +void simple_wallet::mms_transfer(const std::vector<std::string> &args) +{ + // It's too complicated to check any arguments here, just let 'transfer_main' do the whole job + transfer_main(Transfer, args, true); +} + +void simple_wallet::mms_delete(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("Usage: mms delete (<message_id> | all)"); + return; + } + LOCK_IDLE_SCOPE(); + mms::message_store& ms = m_wallet->get_message_store(); + if (args[0] == "all") + { + if (user_confirms(tr("Delete all messages?"))) + { + ms.delete_all_messages(); + } + } + else + { + mms::message m; + bool valid_id = get_message_from_arg(args[0], m); + if (valid_id) + { + // If only a single message and not all delete even if unsent / unprocessed + ms.delete_message(m.id); + } + } +} + +void simple_wallet::mms_send(const std::vector<std::string> &args) +{ + if (args.size() == 0) + { + ask_send_all_ready_messages(); + return; + } + else if (args.size() != 1) + { + fail_msg_writer() << tr("Usage: mms send [<message_id>]"); + return; + } + LOCK_IDLE_SCOPE(); + mms::message_store& ms = m_wallet->get_message_store(); + mms::message m; + bool valid_id = get_message_from_arg(args[0], m); + if (valid_id) + { + ms.send_message(get_multisig_wallet_state(), m.id); + } +} + +void simple_wallet::mms_receive(const std::vector<std::string> &args) +{ + if (args.size() != 0) + { + fail_msg_writer() << tr("Usage: mms receive"); + return; + } + std::vector<mms::message> new_messages; + LOCK_IDLE_SCOPE(); + mms::message_store& ms = m_wallet->get_message_store(); + bool avail = ms.check_for_messages(get_multisig_wallet_state(), new_messages); + if (avail) + { + list_mms_messages(new_messages); + } +} + +void simple_wallet::mms_export(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("Usage: mms export <message_id>"); + return; + } + LOCK_IDLE_SCOPE(); + mms::message_store& ms = m_wallet->get_message_store(); + mms::message m; + bool valid_id = get_message_from_arg(args[0], m); + if (valid_id) + { + const std::string filename = "mms_message_content"; + if (epee::file_io_utils::save_string_to_file(filename, m.content)) + { + success_msg_writer() << tr("Message content saved to: ") << filename; + } + else + { + fail_msg_writer() << tr("Failed to to save message content"); + } + } +} + +void simple_wallet::mms_note(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + if (args.size() == 0) + { + LOCK_IDLE_SCOPE(); + const std::vector<mms::message> &messages = ms.get_all_messages(); + for (size_t i = 0; i < messages.size(); ++i) + { + const mms::message &m = messages[i]; + if ((m.type == mms::message_type::note) && (m.state == mms::message_state::waiting)) + { + show_message(m); + } + } + return; + } + if (args.size() < 2) + { + fail_msg_writer() << tr("Usage: mms note [<label> <text>]"); + return; + } + uint32_t signer_index; + bool found = ms.get_signer_index_by_label(args[0], signer_index); + if (!found) + { + fail_msg_writer() << tr("No signer found with label ") << args[0]; + return; + } + std::string note = ""; + for (size_t n = 1; n < args.size(); ++n) + { + if (n > 1) + { + note += " "; + } + note += args[n]; + } + LOCK_IDLE_SCOPE(); + ms.add_message(get_multisig_wallet_state(), signer_index, mms::message_type::note, + mms::message_direction::out, note); + ask_send_all_ready_messages(); +} + +void simple_wallet::mms_show(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("Usage: mms show <message_id>"); + return; + } + LOCK_IDLE_SCOPE(); + mms::message_store& ms = m_wallet->get_message_store(); + mms::message m; + bool valid_id = get_message_from_arg(args[0], m); + if (valid_id) + { + show_message(m); + } +} + +void simple_wallet::mms_set(const std::vector<std::string> &args) +{ + bool set = args.size() == 2; + bool query = args.size() == 1; + if (!set && !query) + { + fail_msg_writer() << tr("Usage: mms set <option_name> [<option_value>]"); + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + LOCK_IDLE_SCOPE(); + if (args[0] == "auto-send") + { + if (set) + { + bool result; + bool ok = parse_bool(args[1], result); + if (ok) + { + ms.set_auto_send(result); + } + else + { + fail_msg_writer() << tr("Wrong option value"); + } + } + else + { + message_writer() << (ms.get_auto_send() ? tr("Auto-send is on") : tr("Auto-send is off")); + } + } + else + { + fail_msg_writer() << tr("Unknown option"); + } +} + +void simple_wallet::mms_help(const std::vector<std::string> &args) +{ + if (args.size() > 1) + { + fail_msg_writer() << tr("Usage: mms help [<subcommand>]"); + return; + } + std::vector<std::string> help_args; + help_args.push_back("mms"); + if (args.size() == 1) + { + help_args.push_back(args[0]); + } + help(help_args); +} + +void simple_wallet::mms_send_signer_config(const std::vector<std::string> &args) +{ + if (args.size() != 0) + { + fail_msg_writer() << tr("Usage: mms send_signer_config"); + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + if (!ms.signer_config_complete()) + { + fail_msg_writer() << tr("Signer config not yet complete"); + return; + } + LOCK_IDLE_SCOPE(); + add_signer_config_messages(); + ask_send_all_ready_messages(); +} + +void simple_wallet::mms_start_auto_config(const std::vector<std::string> &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + uint32_t other_signers = ms.get_num_authorized_signers() - 1; + size_t args_size = args.size(); + if ((args_size != 0) && (args_size != other_signers)) + { + fail_msg_writer() << tr("Usage: mms start_auto_config [<label> <label> ...]"); + return; + } + if ((args_size == 0) && !ms.signer_labels_complete()) + { + fail_msg_writer() << tr("There are signers without a label set. Complete labels before auto-config or specify them as parameters here."); + return; + } + mms::authorized_signer me = ms.get_signer(0); + if (me.auto_config_running) + { + if (!user_confirms(tr("Auto-config is already running. Cancel and restart?"))) + { + return; + } + } + LOCK_IDLE_SCOPE(); + mms::multisig_wallet_state state = get_multisig_wallet_state(); + if (args_size != 0) + { + // Set (or overwrite) all the labels except "me" from the arguments + for (uint32_t i = 1; i < (other_signers + 1); ++i) + { + ms.set_signer(state, i, args[i - 1], boost::none, boost::none); + } + } + ms.start_auto_config(state); + // List the signers to show the generated auto-config tokens + list_signers(ms.get_all_signers()); +} + +void simple_wallet::mms_stop_auto_config(const std::vector<std::string> &args) +{ + if (args.size() != 0) + { + fail_msg_writer() << tr("Usage: mms stop_auto_config"); + return; + } + if (!user_confirms(tr("Delete any auto-config tokens and stop auto-config?"))) + { + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + LOCK_IDLE_SCOPE(); + ms.stop_auto_config(); +} + +void simple_wallet::mms_auto_config(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("Usage: mms auto_config <auto_config_token>"); + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + std::string adjusted_token; + if (!ms.check_auto_config_token(args[0], adjusted_token)) + { + fail_msg_writer() << tr("Invalid auto-config token"); + return; + } + mms::authorized_signer me = ms.get_signer(0); + if (me.auto_config_running) + { + if (!user_confirms(tr("Auto-config already running. Cancel and restart?"))) + { + return; + } + } + LOCK_IDLE_SCOPE(); + ms.add_auto_config_data_message(get_multisig_wallet_state(), adjusted_token); + ask_send_all_ready_messages(); +} + +bool simple_wallet::mms(const std::vector<std::string> &args) +{ + try + { + mms::message_store& ms = m_wallet->get_message_store(); + if (args.size() == 0) + { + mms_info(args); + return true; + } + + const std::string &sub_command = args[0]; + std::vector<std::string> mms_args = args; + mms_args.erase(mms_args.begin()); + + if (sub_command == "init") + { + mms_init(mms_args); + return true; + } + if (!ms.get_active()) + { + fail_msg_writer() << tr("The MMS is not active. Activate using the \"mms init\" command"); + return true; + } + else if (sub_command == "info") + { + mms_info(mms_args); + } + else if (sub_command == "signer") + { + mms_signer(mms_args); + } + else if (sub_command == "list") + { + mms_list(mms_args); + } + else if (sub_command == "next") + { + mms_next(mms_args); + } + else if (sub_command == "sync") + { + mms_sync(mms_args); + } + else if (sub_command == "transfer") + { + mms_transfer(mms_args); + } + else if (sub_command == "delete") + { + mms_delete(mms_args); + } + else if (sub_command == "send") + { + mms_send(mms_args); + } + else if (sub_command == "receive") + { + mms_receive(mms_args); + } + else if (sub_command == "export") + { + mms_export(mms_args); + } + else if (sub_command == "note") + { + mms_note(mms_args); + } + else if (sub_command == "show") + { + mms_show(mms_args); + } + else if (sub_command == "set") + { + mms_set(mms_args); + } + else if (sub_command == "help") + { + mms_help(mms_args); + } + else if (sub_command == "send_signer_config") + { + mms_send_signer_config(mms_args); + } + else if (sub_command == "start_auto_config") + { + mms_start_auto_config(mms_args); + } + else if (sub_command == "stop_auto_config") + { + mms_stop_auto_config(mms_args); + } + else if (sub_command == "auto_config") + { + mms_auto_config(mms_args); + } + else + { + fail_msg_writer() << tr("Invalid MMS subcommand"); + } + } + catch (const tools::error::no_connection_to_daemon &e) + { + fail_msg_writer() << tr("Error in MMS command: ") << e.what() << " " << e.request(); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Error in MMS command: ") << e.what(); + PRINT_USAGE(USAGE_MMS); + return true; + } + return true; +} +// End MMS ------------------------------------------------------------------------------------------------ + diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 665879cac..5010e3adc 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -154,7 +154,7 @@ namespace cryptonote bool show_incoming_transfers(const std::vector<std::string> &args); bool show_payments(const std::vector<std::string> &args); bool show_blockchain_height(const std::vector<std::string> &args); - bool transfer_main(int transfer_type, const std::vector<std::string> &args); + bool transfer_main(int transfer_type, const std::vector<std::string> &args, bool called_by_mms); bool transfer(const std::vector<std::string> &args); bool locked_transfer(const std::vector<std::string> &args); bool locked_sweep_all(const std::vector<std::string> &args); @@ -214,15 +214,23 @@ namespace cryptonote bool payment_id(const std::vector<std::string> &args); bool print_fee_info(const std::vector<std::string> &args); bool prepare_multisig(const std::vector<std::string>& args); + bool prepare_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool make_multisig(const std::vector<std::string>& args); + bool make_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool finalize_multisig(const std::vector<std::string> &args); bool exchange_multisig_keys(const std::vector<std::string> &args); + bool exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms); bool export_multisig(const std::vector<std::string>& args); + bool export_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool import_multisig(const std::vector<std::string>& args); + bool import_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs); bool sign_multisig(const std::vector<std::string>& args); + bool sign_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool submit_multisig(const std::vector<std::string>& args); + bool submit_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool export_raw_multisig(const std::vector<std::string>& args); + bool mms(const std::vector<std::string>& args); bool print_ring(const std::vector<std::string>& args); bool set_ring(const std::vector<std::string>& args); bool save_known_rings(const std::vector<std::string>& args); @@ -386,5 +394,40 @@ namespace cryptonote bool m_auto_refresh_refreshing; std::atomic<bool> m_in_manual_refresh; uint32_t m_current_subaddress_account; + + // MMS + mms::message_store& get_message_store() const { return m_wallet->get_message_store(); }; + mms::multisig_wallet_state get_multisig_wallet_state() const { return m_wallet->get_multisig_wallet_state(); }; + bool mms_active() const { return get_message_store().get_active(); }; + bool choose_mms_processing(const std::vector<mms::processing_data> &data_list, uint32_t &choice); + void list_mms_messages(const std::vector<mms::message> &messages); + void list_signers(const std::vector<mms::authorized_signer> &signers); + void add_signer_config_messages(); + void show_message(const mms::message &m); + void ask_send_all_ready_messages(); + void check_for_messages(); + bool user_confirms(const std::string &question); + bool get_message_from_arg(const std::string &arg, mms::message &m); + bool get_number_from_arg(const std::string &arg, uint32_t &number, const uint32_t lower_bound, const uint32_t upper_bound); + + void mms_init(const std::vector<std::string> &args); + void mms_info(const std::vector<std::string> &args); + void mms_signer(const std::vector<std::string> &args); + void mms_list(const std::vector<std::string> &args); + void mms_next(const std::vector<std::string> &args); + void mms_sync(const std::vector<std::string> &args); + void mms_transfer(const std::vector<std::string> &args); + void mms_delete(const std::vector<std::string> &args); + void mms_send(const std::vector<std::string> &args); + void mms_receive(const std::vector<std::string> &args); + void mms_export(const std::vector<std::string> &args); + void mms_note(const std::vector<std::string> &args); + void mms_show(const std::vector<std::string> &args); + void mms_set(const std::vector<std::string> &args); + void mms_help(const std::vector<std::string> &args); + void mms_send_signer_config(const std::vector<std::string> &args); + void mms_start_auto_config(const std::vector<std::string> &args); + void mms_stop_auto_config(const std::vector<std::string> &args); + void mms_auto_config(const std::vector<std::string> &args); }; } diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 64e84020e..2991f75c5 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -34,7 +34,10 @@ set(wallet_sources wallet2.cpp wallet_args.cpp ringdb.cpp - node_rpc_proxy.cpp) + node_rpc_proxy.cpp + message_store.cpp + message_transporter.cpp +) set(wallet_private_headers wallet2.h @@ -44,7 +47,9 @@ set(wallet_private_headers wallet_rpc_server_commands_defs.h wallet_rpc_server_error_codes.h ringdb.h - node_rpc_proxy.h) + node_rpc_proxy.h + message_store.h + message_transporter.h) monero_private_headers(wallet ${wallet_private_headers}) diff --git a/src/wallet/message_store.cpp b/src/wallet/message_store.cpp new file mode 100644 index 000000000..ce6f4f52b --- /dev/null +++ b/src/wallet/message_store.cpp @@ -0,0 +1,1445 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "message_store.h" +#include <boost/archive/portable_binary_oarchive.hpp> +#include <boost/archive/portable_binary_iarchive.hpp> +#include <boost/format.hpp> +#include <boost/algorithm/string.hpp> +#include <fstream> +#include <sstream> +#include "file_io_utils.h" +#include "storages/http_abstract_invoke.h" +#include "wallet_errors.h" +#include "serialization/binary_utils.h" +#include "common/base58.h" +#include "common/util.h" +#include "string_tools.h" + + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.mms" + +namespace mms +{ + +message_store::message_store() +{ + m_active = false; + m_auto_send = false; + m_next_message_id = 1; + m_num_authorized_signers = 0; + m_num_required_signers = 0; + m_nettype = cryptonote::network_type::UNDEFINED; + m_run = true; +} + +namespace +{ + // MMS options handling mirrors what "wallet2" is doing for its options, on-demand init and all + // It's not very clean to initialize Bitmessage-specific options here, but going one level further + // down still into "message_transporter" for that is a little bit too much + struct options + { + const command_line::arg_descriptor<std::string> bitmessage_address = {"bitmessage-address", mms::message_store::tr("Use PyBitmessage instance at URL <arg>"), "http://localhost:8442/"}; + const command_line::arg_descriptor<std::string> bitmessage_login = {"bitmessage-login", mms::message_store::tr("Specify <arg> as username:password for PyBitmessage API"), "username:password"}; + }; +} + +void message_store::init_options(boost::program_options::options_description& desc_params) +{ + const options opts{}; + command_line::add_arg(desc_params, opts.bitmessage_address); + command_line::add_arg(desc_params, opts.bitmessage_login); +} + +void message_store::init(const multisig_wallet_state &state, const std::string &own_label, + const std::string &own_transport_address, uint32_t num_authorized_signers, uint32_t num_required_signers) +{ + m_num_authorized_signers = num_authorized_signers; + m_num_required_signers = num_required_signers; + m_signers.clear(); + m_messages.clear(); + m_next_message_id = 1; + + // The vector "m_signers" gets here once the required number of elements, one for each authorized signer, + // and is never changed again. The rest of the code relies on "size(m_signers) == m_num_authorized_signers" + // without further checks. + authorized_signer signer; + for (uint32_t i = 0; i < m_num_authorized_signers; ++i) + { + signer.me = signer.index == 0; // Strict convention: The very first signer is fixed as / must be "me" + m_signers.push_back(signer); + signer.index++; + } + + set_signer(state, 0, own_label, own_transport_address, state.address); + + m_nettype = state.nettype; + set_active(true); + m_filename = state.mms_file; + save(state); +} + +void message_store::set_options(const boost::program_options::variables_map& vm) +{ + const options opts{}; + std::string bitmessage_address = command_line::get_arg(vm, opts.bitmessage_address); + epee::wipeable_string bitmessage_login = command_line::get_arg(vm, opts.bitmessage_login); + set_options(bitmessage_address, bitmessage_login); +} + +void message_store::set_options(const std::string &bitmessage_address, const epee::wipeable_string &bitmessage_login) +{ + m_transporter.set_options(bitmessage_address, bitmessage_login); +} + +void message_store::set_signer(const multisig_wallet_state &state, + uint32_t index, + const boost::optional<std::string> &label, + const boost::optional<std::string> &transport_address, + const boost::optional<cryptonote::account_public_address> monero_address) +{ + THROW_WALLET_EXCEPTION_IF(index >= m_num_authorized_signers, tools::error::wallet_internal_error, "Invalid signer index " + index); + authorized_signer &m = m_signers[index]; + if (label) + { + m.label = label.get(); + } + if (transport_address) + { + m.transport_address = transport_address.get(); + } + if (monero_address) + { + m.monero_address_known = true; + m.monero_address = monero_address.get(); + } + // Save to minimize the chance to loose that info (at least while in beta) + save(state); +} + +const authorized_signer &message_store::get_signer(uint32_t index) const +{ + THROW_WALLET_EXCEPTION_IF(index >= m_num_authorized_signers, tools::error::wallet_internal_error, "Invalid signer index " + index); + return m_signers[index]; +} + +bool message_store::signer_config_complete() const +{ + for (uint32_t i = 0; i < m_num_authorized_signers; ++i) + { + const authorized_signer &m = m_signers[i]; + if (m.label.empty() || m.transport_address.empty() || !m.monero_address_known) + { + return false; + } + } + return true; +} + +// Check if all signers have a label set (as it's a requirement for starting auto-config +// by the "manager") +bool message_store::signer_labels_complete() const +{ + for (uint32_t i = 0; i < m_num_authorized_signers; ++i) + { + const authorized_signer &m = m_signers[i]; + if (m.label.empty()) + { + return false; + } + } + return true; +} + +void message_store::get_signer_config(std::string &signer_config) +{ + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << m_signers; + signer_config = oss.str(); +} + +void message_store::unpack_signer_config(const multisig_wallet_state &state, const std::string &signer_config, + std::vector<authorized_signer> &signers) +{ + try + { + std::stringstream iss; + iss << signer_config; + boost::archive::portable_binary_iarchive ar(iss); + ar >> signers; + } + catch (...) + { + THROW_WALLET_EXCEPTION_IF(true, tools::error::wallet_internal_error, "Invalid structure of signer config"); + } + uint32_t num_signers = (uint32_t)signers.size(); + THROW_WALLET_EXCEPTION_IF(num_signers != m_num_authorized_signers, tools::error::wallet_internal_error, "Wrong number of signers in config: " + num_signers); +} + +void message_store::process_signer_config(const multisig_wallet_state &state, const std::string &signer_config) +{ + // The signers in "signer_config" and the resident wallet signers are matched not by label, but + // by Monero address, and ALL labels will be set from "signer_config", even the "me" label. + // In the auto-config process as implemented now the auto-config manager is responsible for defining + // the labels, and right at the end of the process ALL wallets use the SAME labels. The idea behind this + // is preventing problems like duplicate labels and confusion (Bob choosing a label "IamAliceHonest"). + // (Of course signers are free to re-define any labels they don't like AFTER auto-config.) + // + // Usually this method will be called with only the "me" signer defined in the wallet, and may + // produce unexpected behaviour if that wallet contains additional signers that have nothing to do with + // those arriving in "signer_config". + std::vector<authorized_signer> signers; + unpack_signer_config(state, signer_config, signers); + + uint32_t new_index = 1; + for (uint32_t i = 0; i < m_num_authorized_signers; ++i) + { + const authorized_signer &m = signers[i]; + uint32_t index; + uint32_t take_index; + bool found = get_signer_index_by_monero_address(m.monero_address, index); + if (found) + { + // Redefine existing (probably "me", under usual circumstances) + take_index = index; + } + else + { + // Add new; neglect that we may erroneously overwrite already defined signers + // (but protect "me") + take_index = new_index; + if ((new_index + 1) < m_num_authorized_signers) + { + new_index++; + } + } + authorized_signer &modify = m_signers[take_index]; + modify.label = m.label; // ALWAYS set label, see comments above + if (!modify.me) + { + modify.transport_address = m.transport_address; + modify.monero_address_known = m.monero_address_known; + if (m.monero_address_known) + { + modify.monero_address = m.monero_address; + } + } + } + save(state); +} + +void message_store::start_auto_config(const multisig_wallet_state &state) +{ + for (uint32_t i = 0; i < m_num_authorized_signers; ++i) + { + authorized_signer &m = m_signers[i]; + if (!m.me) + { + setup_signer_for_auto_config(i, create_auto_config_token(), true); + } + m.auto_config_running = true; + } + save(state); +} + +// Check auto-config token string and convert to standardized form; +// Try to make it as foolproof as possible, with built-in tolerance to make up for +// errors in transmission that still leave the token recognizable. +bool message_store::check_auto_config_token(const std::string &raw_token, + std::string &adjusted_token) const +{ + std::string prefix(AUTO_CONFIG_TOKEN_PREFIX); + uint32_t num_hex_digits = (AUTO_CONFIG_TOKEN_BYTES + 1) * 2; + uint32_t full_length = num_hex_digits + prefix.length(); + uint32_t raw_length = raw_token.length(); + std::string hex_digits; + + if (raw_length == full_length) + { + // Prefix must be there; accept it in any casing + std::string raw_prefix(raw_token.substr(0, 3)); + boost::algorithm::to_lower(raw_prefix); + if (raw_prefix != prefix) + { + return false; + } + hex_digits = raw_token.substr(3); + } + else if (raw_length == num_hex_digits) + { + // Accept the token without the prefix if it's otherwise ok + hex_digits = raw_token; + } + else + { + return false; + } + + // Convert to strict lowercase and correct any common misspellings + boost::algorithm::to_lower(hex_digits); + std::replace(hex_digits.begin(), hex_digits.end(), 'o', '0'); + std::replace(hex_digits.begin(), hex_digits.end(), 'i', '1'); + std::replace(hex_digits.begin(), hex_digits.end(), 'l', '1'); + + // Now it must be correct hex with correct checksum, no further tolerance possible + std::string token_bytes; + if (!epee::string_tools::parse_hexstr_to_binbuff(hex_digits, token_bytes)) + { + return false; + } + const crypto::hash &hash = crypto::cn_fast_hash(token_bytes.data(), token_bytes.size() - 1); + if (token_bytes[AUTO_CONFIG_TOKEN_BYTES] != hash.data[0]) + { + return false; + } + adjusted_token = prefix + hex_digits; + return true; +} + +// Create a new auto-config token with prefix, random 8-hex digits plus 2 checksum digits +std::string message_store::create_auto_config_token() +{ + unsigned char random[AUTO_CONFIG_TOKEN_BYTES]; + crypto::rand(AUTO_CONFIG_TOKEN_BYTES, random); + std::string token_bytes; + token_bytes.append((char *)random, AUTO_CONFIG_TOKEN_BYTES); + + // Add a checksum because technically ANY four bytes are a valid token, and without a checksum we would send + // auto-config messages "to nowhere" after the slightest typo without knowing it + const crypto::hash &hash = crypto::cn_fast_hash(token_bytes.data(), token_bytes.size()); + token_bytes += hash.data[0]; + std::string prefix(AUTO_CONFIG_TOKEN_PREFIX); + return prefix + epee::string_tools::buff_to_hex_nodelimer(token_bytes); +} + +// Add a message for sending "me" address data to the auto-config transport address +// that can be derived from the token and activate auto-config +size_t message_store::add_auto_config_data_message(const multisig_wallet_state &state, + const std::string &auto_config_token) +{ + authorized_signer &me = m_signers[0]; + me.auto_config_token = auto_config_token; + setup_signer_for_auto_config(0, auto_config_token, false); + me.auto_config_running = true; + + auto_config_data data; + data.label = me.label; + data.transport_address = me.transport_address; + data.monero_address = me.monero_address; + + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << data; + + return add_message(state, 0, message_type::auto_config_data, message_direction::out, oss.str()); +} + +// Process a single message with auto-config data, destined for "message.signer_index" +void message_store::process_auto_config_data_message(uint32_t id) +{ + // "auto_config_data" contains the label that the auto-config data sender uses for "me", but that's + // more for completeness' sake, and right now it's not used. In general, the auto-config manager + // decides/defines the labels, and right after completing auto-config ALL wallets use the SAME labels. + + const message &m = get_message_ref_by_id(id); + + auto_config_data data; + try + { + std::stringstream iss; + iss << m.content; + boost::archive::portable_binary_iarchive ar(iss); + ar >> data; + } + catch (...) + { + THROW_WALLET_EXCEPTION_IF(true, tools::error::wallet_internal_error, "Invalid structure of auto config data"); + } + + authorized_signer &signer = m_signers[m.signer_index]; + // "signer.label" does NOT change, see comment above + signer.transport_address = data.transport_address; + signer.monero_address_known = true; + signer.monero_address = data.monero_address; + signer.auto_config_running = false; +} + +void message_store::stop_auto_config() +{ + for (uint32_t i = 0; i < m_num_authorized_signers; ++i) + { + authorized_signer &m = m_signers[i]; + if (!m.me && !m.auto_config_transport_address.empty()) + { + // Try to delete those "unused API" addresses in PyBitmessage, especially since + // it seems it's not possible to delete them interactively, only to "disable" them + m_transporter.delete_transport_address(m.auto_config_transport_address); + } + m.auto_config_token.clear(); + m.auto_config_public_key = crypto::null_pkey; + m.auto_config_secret_key = crypto::null_skey; + m.auto_config_transport_address.clear(); + m.auto_config_running = false; + } +} + +void message_store::setup_signer_for_auto_config(uint32_t index, const std::string token, bool receiving) +{ + // It may be a little strange to hash the textual hex digits of the auto config token into + // 32 bytes and turn that into a Monero public/secret key pair, instead of doing something + // much less complicated like directly using the underlying random 40 bits as key for a + // symmetric cipher, but everything is there already for encrypting and decrypting messages + // with such key pairs, and furthermore it would be trivial to use tokens with a different + // number of bytes. + // + // In the wallet of the auto-config manager each signer except "me" gets set its own + // auto-config parameters. In the wallet of somebody using the token to send auto-config + // data the auto-config parameters are stored in the "me" signer and taken from there + // to send that data. + THROW_WALLET_EXCEPTION_IF(index >= m_num_authorized_signers, tools::error::wallet_internal_error, "Invalid signer index " + index); + authorized_signer &m = m_signers[index]; + m.auto_config_token = token; + crypto::hash_to_scalar(token.data(), token.size(), m.auto_config_secret_key); + crypto::secret_key_to_public_key(m.auto_config_secret_key, m.auto_config_public_key); + if (receiving) + { + m.auto_config_transport_address = m_transporter.derive_and_receive_transport_address(m.auto_config_token); + } + else + { + m.auto_config_transport_address = m_transporter.derive_transport_address(m.auto_config_token); + } +} + +bool message_store::get_signer_index_by_monero_address(const cryptonote::account_public_address &monero_address, uint32_t &index) const +{ + for (uint32_t i = 0; i < m_num_authorized_signers; ++i) + { + const authorized_signer &m = m_signers[i]; + if (m.monero_address == monero_address) + { + index = m.index; + return true; + } + } + MWARNING("No authorized signer with Monero address " << account_address_to_string(monero_address)); + return false; +} + +bool message_store::get_signer_index_by_label(const std::string label, uint32_t &index) const +{ + for (uint32_t i = 0; i < m_num_authorized_signers; ++i) + { + const authorized_signer &m = m_signers[i]; + if (m.label == label) + { + index = m.index; + return true; + } + } + MWARNING("No authorized signer with label " << label); + return false; +} + +void message_store::process_wallet_created_data(const multisig_wallet_state &state, message_type type, const std::string &content) +{ + switch(type) + { + case message_type::key_set: + // Result of a "prepare_multisig" command in the wallet + // Send the key set to all other signers + case message_type::additional_key_set: + // Result of a "make_multisig" command or a "exchange_multisig_keys" in the wallet in case of M/N multisig + // Send the additional key set to all other signers + case message_type::multisig_sync_data: + // Result of a "export_multisig_info" command in the wallet + // Send the sync data to all other signers + for (uint32_t i = 1; i < m_num_authorized_signers; ++i) + { + add_message(state, i, type, message_direction::out, content); + } + break; + + case message_type::partially_signed_tx: + // Result of a "transfer" command in the wallet, or a "sign_multisig" command + // that did not yet result in the minimum number of signatures required + // Create a message "from me to me" as a container for the tx data + if (m_num_required_signers == 1) + { + // Probably rare, but possible: The 1 signature is already enough, correct the type + // Easier to correct here than asking all callers to detect this rare special case + type = message_type::fully_signed_tx; + } + add_message(state, 0, type, message_direction::in, content); + break; + + case message_type::fully_signed_tx: + add_message(state, 0, type, message_direction::in, content); + break; + + default: + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, "Illegal message type " + (uint32_t)type); + break; + } +} + +size_t message_store::add_message(const multisig_wallet_state &state, + uint32_t signer_index, message_type type, message_direction direction, + const std::string &content) +{ + message m; + m.id = m_next_message_id++; + m.type = type; + m.direction = direction; + m.content = content; + m.created = (uint64_t)time(NULL); + m.modified = m.created; + m.sent = 0; + m.signer_index = signer_index; + if (direction == message_direction::out) + { + m.state = message_state::ready_to_send; + } + else + { + m.state = message_state::waiting; + }; + m.wallet_height = (uint32_t)state.num_transfer_details; + if (m.type == message_type::additional_key_set) + { + m.round = state.multisig_rounds_passed; + } + else + { + m.round = 0; + } + m.signature_count = 0; // Future expansion for signature counting when signing txs + m.hash = crypto::null_hash; + m_messages.push_back(m); + + // Save for every new message right away (at least while in beta) + save(state); + + MINFO(boost::format("Added %s message %s for signer %s of type %s") + % message_direction_to_string(direction) % m.id % signer_index % message_type_to_string(type)); + return m_messages.size() - 1; +} + +// Get the index of the message with id "id", return false if not found +bool message_store::get_message_index_by_id(uint32_t id, size_t &index) const +{ + for (size_t i = 0; i < m_messages.size(); ++i) + { + if (m_messages[i].id == id) + { + index = i; + return true; + } + } + MWARNING("No message found with an id of " << id); + return false; +} + +// Get the index of the message with id "id" that must exist +size_t message_store::get_message_index_by_id(uint32_t id) const +{ + size_t index; + bool found = get_message_index_by_id(id, index); + THROW_WALLET_EXCEPTION_IF(!found, tools::error::wallet_internal_error, "Invalid message id " + id); + return index; +} + +// Get the modifiable message with id "id" that must exist; private/internal use! +message& message_store::get_message_ref_by_id(uint32_t id) +{ + return m_messages[get_message_index_by_id(id)]; +} + +// Get the message with id "id", return false if not found +// This version of the method allows to check whether id is valid without triggering an error +bool message_store::get_message_by_id(uint32_t id, message &m) const +{ + size_t index; + bool found = get_message_index_by_id(id, index); + if (found) + { + m = m_messages[index]; + } + return found; +} + +// Get the message with id "id" that must exist +message message_store::get_message_by_id(uint32_t id) const +{ + message m; + bool found = get_message_by_id(id, m); + THROW_WALLET_EXCEPTION_IF(!found, tools::error::wallet_internal_error, "Invalid message id " + id); + return m; +} + +bool message_store::any_message_of_type(message_type type, message_direction direction) const +{ + for (size_t i = 0; i < m_messages.size(); ++i) + { + if ((m_messages[i].type == type) && (m_messages[i].direction == direction)) + { + return true; + } + } + return false; +} + +bool message_store::any_message_with_hash(const crypto::hash &hash) const +{ + for (size_t i = 0; i < m_messages.size(); ++i) + { + if (m_messages[i].hash == hash) + { + return true; + } + } + return false; +} + +// Count the ids in the vector that are set i.e. not 0, while ignoring index 0 +// Mostly used to check whether we have a message for each authorized signer except me, +// with the signer index used as index into 'ids'; the element at index 0, for me, +// is ignored, to make constant subtractions of 1 for indices when filling the +// vector unnecessary +size_t message_store::get_other_signers_id_count(const std::vector<uint32_t> &ids) const +{ + size_t count = 0; + for (size_t i = 1 /* and not 0 */; i < ids.size(); ++i) + { + if (ids[i] != 0) + { + count++; + } + } + return count; +} + +// Is in every element of vector 'ids' (except at index 0) a message id i.e. not 0? +bool message_store::message_ids_complete(const std::vector<uint32_t> &ids) const +{ + return get_other_signers_id_count(ids) == (ids.size() - 1); +} + +void message_store::delete_message(uint32_t id) +{ + delete_transport_message(id); + size_t index = get_message_index_by_id(id); + m_messages.erase(m_messages.begin() + index); +} + +void message_store::delete_all_messages() +{ + for (size_t i = 0; i < m_messages.size(); ++i) + { + delete_transport_message(m_messages[i].id); + } + m_messages.clear(); +} + +// Make a message text, which is "attacker controlled data", reasonably safe to display +// This is mostly geared towards the safe display of notes sent by "mms note" with a "mms show" command +void message_store::get_sanitized_message_text(const message &m, std::string &sanitized_text) const +{ + sanitized_text.clear(); + + // Restrict the size to fend of DOS-style attacks with heaps of data + size_t length = std::min(m.content.length(), (size_t)1000); + + for (size_t i = 0; i < length; ++i) + { + char c = m.content[i]; + if ((int)c < 32) + { + // Strip out any controls, especially ESC for getting rid of potentially dangerous + // ANSI escape sequences that a console window might interpret + c = ' '; + } + else if ((c == '<') || (c == '>')) + { + // Make XML or HTML impossible that e.g. might contain scripts that Qt might execute + // when displayed in the GUI wallet + c = ' '; + } + sanitized_text += c; + } +} + +void message_store::write_to_file(const multisig_wallet_state &state, const std::string &filename) +{ + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << *this; + std::string buf = oss.str(); + + crypto::chacha_key key; + crypto::generate_chacha_key(&state.view_secret_key, sizeof(crypto::secret_key), key, 1); + + file_data write_file_data = boost::value_initialized<file_data>(); + write_file_data.magic_string = "MMS"; + write_file_data.file_version = 0; + write_file_data.iv = crypto::rand<crypto::chacha_iv>(); + std::string encrypted_data; + encrypted_data.resize(buf.size()); + crypto::chacha20(buf.data(), buf.size(), key, write_file_data.iv, &encrypted_data[0]); + write_file_data.encrypted_data = encrypted_data; + + std::stringstream file_oss; + boost::archive::portable_binary_oarchive file_ar(file_oss); + file_ar << write_file_data; + + bool success = epee::file_io_utils::save_string_to_file(filename, file_oss.str()); + THROW_WALLET_EXCEPTION_IF(!success, tools::error::file_save_error, filename); +} + +void message_store::read_from_file(const multisig_wallet_state &state, const std::string &filename) +{ + boost::system::error_code ignored_ec; + bool file_exists = boost::filesystem::exists(filename, ignored_ec); + if (!file_exists) + { + // Simply do nothing if the file is not there; allows e.g. easy recovery + // from problems with the MMS by deleting the file + MERROR("No message store file found: " << filename); + return; + } + + std::string buf; + bool success = epee::file_io_utils::load_file_to_string(filename, buf); + THROW_WALLET_EXCEPTION_IF(!success, tools::error::file_read_error, filename); + + file_data read_file_data; + try + { + std::stringstream iss; + iss << buf; + boost::archive::portable_binary_iarchive ar(iss); + ar >> read_file_data; + } + catch (const std::exception &e) + { + MERROR("MMS file " << filename << " has bad structure <iv,encrypted_data>: " << e.what()); + THROW_WALLET_EXCEPTION_IF(true, tools::error::file_read_error, filename); + } + + crypto::chacha_key key; + crypto::generate_chacha_key(&state.view_secret_key, sizeof(crypto::secret_key), key, 1); + std::string decrypted_data; + decrypted_data.resize(read_file_data.encrypted_data.size()); + crypto::chacha20(read_file_data.encrypted_data.data(), read_file_data.encrypted_data.size(), key, read_file_data.iv, &decrypted_data[0]); + + try + { + std::stringstream iss; + iss << decrypted_data; + boost::archive::portable_binary_iarchive ar(iss); + ar >> *this; + } + catch (const std::exception &e) + { + MERROR("MMS file " << filename << " has bad structure: " << e.what()); + THROW_WALLET_EXCEPTION_IF(true, tools::error::file_read_error, filename); + } + + m_filename = filename; +} + +// Save to the same file this message store was loaded from +// Called after changes deemed "important", to make it less probable to lose messages in case of +// a crash; a better and long-term solution would of course be to use LMDB ... +void message_store::save(const multisig_wallet_state &state) +{ + if (!m_filename.empty()) + { + write_to_file(state, m_filename); + } +} + +bool message_store::get_processable_messages(const multisig_wallet_state &state, + bool force_sync, std::vector<processing_data> &data_list, std::string &wait_reason) +{ + uint32_t wallet_height = (uint32_t)state.num_transfer_details; + data_list.clear(); + wait_reason.clear(); + // In all scans over all messages looking for complete sets (1 message for each signer), + // if there are duplicates, the OLDEST of them is taken. This may not play a role with + // any of the current message types, but may with future ones, and it's probably a good + // idea to have a clear and somewhat defensive strategy. + + std::vector<uint32_t> auto_config_messages(m_num_authorized_signers, 0); + bool any_auto_config = false; + + for (size_t i = 0; i < m_messages.size(); ++i) + { + message &m = m_messages[i]; + if ((m.type == message_type::auto_config_data) && (m.state == message_state::waiting)) + { + if (auto_config_messages[m.signer_index] == 0) + { + auto_config_messages[m.signer_index] = m.id; + any_auto_config = true; + } + // else duplicate auto config data, ignore + } + } + + if (any_auto_config) + { + bool auto_config_complete = message_ids_complete(auto_config_messages); + if (auto_config_complete) + { + processing_data data; + data.processing = message_processing::process_auto_config_data; + data.message_ids = auto_config_messages; + data.message_ids.erase(data.message_ids.begin()); + data_list.push_back(data); + return true; + } + else + { + wait_reason = tr("Auto-config cannot proceed because auto config data from other signers is not complete"); + return false; + // With ANY auto config data present but not complete refuse to check for any + // other processing. Manually delete those messages to abort such an auto config + // phase if needed. + } + } + + // Any signer config that arrived will be processed right away, regardless of other things that may wait + for (size_t i = 0; i < m_messages.size(); ++i) + { + message &m = m_messages[i]; + if ((m.type == message_type::signer_config) && (m.state == message_state::waiting)) + { + processing_data data; + data.processing = message_processing::process_signer_config; + data.message_ids.push_back(m.id); + data_list.push_back(data); + return true; + } + } + + // ALL of the following processings depend on the signer info being complete + if (!signer_config_complete()) + { + wait_reason = tr("The signer config is not complete."); + return false; + } + + if (!state.multisig) + { + + if (!any_message_of_type(message_type::key_set, message_direction::out)) + { + // With the own key set not yet ready we must do "prepare_multisig" first; + // Key sets from other signers may be here already, but if we process them now + // the wallet will go multisig too early: we can't produce our own key set any more! + processing_data data; + data.processing = message_processing::prepare_multisig; + data_list.push_back(data); + return true; + } + + // Ids of key set messages per signer index, to check completeness + // Naturally, does not care about the order of the messages and is trivial to secure against + // key sets that were received more than once + // With full M/N multisig now possible consider only key sets of the right round, i.e. + // with not yet multisig the only possible round 0 + std::vector<uint32_t> key_set_messages(m_num_authorized_signers, 0); + + for (size_t i = 0; i < m_messages.size(); ++i) + { + message &m = m_messages[i]; + if ((m.type == message_type::key_set) && (m.state == message_state::waiting) + && (m.round == 0)) + { + if (key_set_messages[m.signer_index] == 0) + { + key_set_messages[m.signer_index] = m.id; + } + // else duplicate key set, ignore + } + } + + bool key_sets_complete = message_ids_complete(key_set_messages); + if (key_sets_complete) + { + // Nothing else can be ready to process earlier than this, ignore everything else and give back + processing_data data; + data.processing = message_processing::make_multisig; + data.message_ids = key_set_messages; + data.message_ids.erase(data.message_ids.begin()); + data_list.push_back(data); + return true; + } + else + { + wait_reason = tr("Wallet can't go multisig because key sets from other signers are missing or not complete."); + return false; + } + } + + if (state.multisig && !state.multisig_is_ready) + { + // In the case of M/N multisig the call 'wallet2::multisig' returns already true + // after "make_multisig" but with calls to "exchange_multisig_keys" still needed, and + // sets the parameter 'ready' to false to document this particular "in-between" state. + // So what may be possible here, with all necessary messages present, is a call to + // "exchange_multisig_keys". + // Consider only messages belonging to the next round to do, which has the number + // "state.multisig_rounds_passed". + std::vector<uint32_t> additional_key_set_messages(m_num_authorized_signers, 0); + + for (size_t i = 0; i < m_messages.size(); ++i) + { + message &m = m_messages[i]; + if ((m.type == message_type::additional_key_set) && (m.state == message_state::waiting) + && (m.round == state.multisig_rounds_passed)) + { + if (additional_key_set_messages[m.signer_index] == 0) + { + additional_key_set_messages[m.signer_index] = m.id; + } + // else duplicate key set, ignore + } + } + + bool key_sets_complete = message_ids_complete(additional_key_set_messages); + if (key_sets_complete) + { + processing_data data; + data.processing = message_processing::exchange_multisig_keys; + data.message_ids = additional_key_set_messages; + data.message_ids.erase(data.message_ids.begin()); + data_list.push_back(data); + return true; + } + else + { + wait_reason = tr("Wallet can't start another key exchange round because key sets from other signers are missing or not complete."); + return false; + } + } + + // Properly exchanging multisig sync data is easiest and most transparent + // for the user if a wallet sends its own data first and processes any received + // sync data afterwards so that's the order that the MMS enforces here. + // (Technically, it seems to work also the other way round.) + // + // To check whether a NEW round of syncing is necessary the MMS works with a + // "wallet state": new state means new syncing needed. + // + // The MMS monitors the "wallet state" by recording "wallet heights" as + // numbers of transfers present in a wallet at the time of message creation. While + // not watertight, this quite simple scheme should already suffice to trigger + // and orchestrate a sensible exchange of sync data. + if (state.has_multisig_partial_key_images || force_sync) + { + // Sync is necessary and not yet completed: Processing of transactions + // will only be possible again once properly synced + // Check first whether we generated already OUR sync info; take note of + // any processable sync info from other signers on the way in case we need it + bool own_sync_data_created = false; + std::vector<uint32_t> sync_messages(m_num_authorized_signers, 0); + for (size_t i = 0; i < m_messages.size(); ++i) + { + message &m = m_messages[i]; + if ((m.type == message_type::multisig_sync_data) && (force_sync || (m.wallet_height == wallet_height))) + // It's data for the same "round" of syncing, on the same "wallet height", therefore relevant + // With "force_sync" take ANY waiting sync data, maybe it will work out + { + if (m.direction == message_direction::out) + { + own_sync_data_created = true; + // Ignore whether sent already or not, and assume as complete if several other signers there + } + else if ((m.direction == message_direction::in) && (m.state == message_state::waiting)) + { + if (sync_messages[m.signer_index] == 0) + { + sync_messages[m.signer_index] = m.id; + } + // else duplicate sync message, ignore + } + } + } + if (!own_sync_data_created) + { + // As explained above, creating sync data BEFORE processing such data from + // other signers reliably works, so insist on that here + processing_data data; + data.processing = message_processing::create_sync_data; + data_list.push_back(data); + return true; + } + uint32_t id_count = (uint32_t)get_other_signers_id_count(sync_messages); + // Do we have sync data from ALL other signers? + bool all_sync_data = id_count == (m_num_authorized_signers - 1); + // Do we have just ENOUGH sync data to have a minimal viable sync set? + // In cases like 2/3 multisig we don't need messages from ALL other signers, only + // from enough of them i.e. num_required_signers minus 1 messages + bool enough_sync_data = id_count >= (m_num_required_signers - 1); + bool sync = false; + wait_reason = tr("Syncing not done because multisig sync data from other signers are missing or not complete."); + if (all_sync_data) + { + sync = true; + } + else if (enough_sync_data) + { + if (force_sync) + { + sync = true; + } + else + { + // Don't sync, but give a hint how this minimal set COULD be synced if really wanted + wait_reason += (boost::format("\nUse \"mms next sync\" if you want to sync with just %s out of %s authorized signers and transact just with them") + % (m_num_required_signers - 1) % (m_num_authorized_signers - 1)).str(); + } + } + if (sync) + { + processing_data data; + data.processing = message_processing::process_sync_data; + for (size_t i = 0; i < sync_messages.size(); ++i) + { + uint32_t id = sync_messages[i]; + if (id != 0) + { + data.message_ids.push_back(id); + } + } + data_list.push_back(data); + return true; + } + else + { + // We can't proceed to any transactions until we have synced; "wait_reason" already set above + return false; + } + } + + bool waiting_found = false; + bool note_found = false; + bool sync_data_found = false; + for (size_t i = 0; i < m_messages.size(); ++i) + { + message &m = m_messages[i]; + if (m.state == message_state::waiting) + { + waiting_found = true; + switch (m.type) + { + case message_type::fully_signed_tx: + { + // We can either submit it ourselves, or send it to any other signer for submission + processing_data data; + data.processing = message_processing::submit_tx; + data.message_ids.push_back(m.id); + data_list.push_back(data); + + data.processing = message_processing::send_tx; + for (uint32_t j = 1; j < m_num_authorized_signers; ++j) + { + data.receiving_signer_index = j; + data_list.push_back(data); + } + return true; + } + + case message_type::partially_signed_tx: + { + if (m.signer_index == 0) + { + // We started this ourselves, or signed it but with still signatures missing: + // We can send it to any other signer for signing / further signing + // In principle it does not make sense to send it back to somebody who + // already signed, but the MMS does not / not yet keep track of that, + // because that would be somewhat complicated. + processing_data data; + data.processing = message_processing::send_tx; + data.message_ids.push_back(m.id); + for (uint32_t j = 1; j < m_num_authorized_signers; ++j) + { + data.receiving_signer_index = j; + data_list.push_back(data); + } + return true; + } + else + { + // Somebody else sent this to us: We can sign it + // It would be possible to just pass it on, but that's not directly supported here + processing_data data; + data.processing = message_processing::sign_tx; + data.message_ids.push_back(m.id); + data_list.push_back(data); + return true; + } + } + + case message_type::note: + note_found = true; + break; + + case message_type::multisig_sync_data: + sync_data_found = true; + break; + + default: + break; + } + } + } + if (waiting_found) + { + wait_reason = tr("There are waiting messages, but nothing is ready to process under normal circumstances"); + if (sync_data_found) + { + wait_reason += tr("\nUse \"mms next sync\" if you want to force processing of the waiting sync data"); + } + if (note_found) + { + wait_reason += tr("\nUse \"mms note\" to display the waiting notes"); + } + } + else + { + wait_reason = tr("There are no messages waiting to be processed."); + } + + return false; +} + +void message_store::set_messages_processed(const processing_data &data) +{ + for (size_t i = 0; i < data.message_ids.size(); ++i) + { + set_message_processed_or_sent(data.message_ids[i]); + } +} + +void message_store::set_message_processed_or_sent(uint32_t id) +{ + message &m = get_message_ref_by_id(id); + if (m.state == message_state::waiting) + { + // So far a fairly cautious and conservative strategy: Only delete from Bitmessage + // when fully processed (and e.g. not already after reception and writing into + // the message store file) + delete_transport_message(id); + m.state = message_state::processed; + } + else if (m.state == message_state::ready_to_send) + { + m.state = message_state::sent; + } + m.modified = (uint64_t)time(NULL); +} + +void message_store::encrypt(crypto::public_key public_key, const std::string &plaintext, + std::string &ciphertext, crypto::public_key &encryption_public_key, crypto::chacha_iv &iv) +{ + crypto::secret_key encryption_secret_key; + crypto::generate_keys(encryption_public_key, encryption_secret_key); + + crypto::key_derivation derivation; + bool success = crypto::generate_key_derivation(public_key, encryption_secret_key, derivation); + THROW_WALLET_EXCEPTION_IF(!success, tools::error::wallet_internal_error, "Failed to generate key derivation for message encryption"); + + crypto::chacha_key chacha_key; + crypto::generate_chacha_key(&derivation, sizeof(derivation), chacha_key, 1); + iv = crypto::rand<crypto::chacha_iv>(); + ciphertext.resize(plaintext.size()); + crypto::chacha20(plaintext.data(), plaintext.size(), chacha_key, iv, &ciphertext[0]); +} + +void message_store::decrypt(const std::string &ciphertext, const crypto::public_key &encryption_public_key, const crypto::chacha_iv &iv, + const crypto::secret_key &view_secret_key, std::string &plaintext) +{ + crypto::key_derivation derivation; + bool success = crypto::generate_key_derivation(encryption_public_key, view_secret_key, derivation); + THROW_WALLET_EXCEPTION_IF(!success, tools::error::wallet_internal_error, "Failed to generate key derivation for message decryption"); + crypto::chacha_key chacha_key; + crypto::generate_chacha_key(&derivation, sizeof(derivation), chacha_key, 1); + plaintext.resize(ciphertext.size()); + crypto::chacha20(ciphertext.data(), ciphertext.size(), chacha_key, iv, &plaintext[0]); +} + +void message_store::send_message(const multisig_wallet_state &state, uint32_t id) +{ + message &m = get_message_ref_by_id(id); + const authorized_signer &me = m_signers[0]; + const authorized_signer &receiver = m_signers[m.signer_index]; + transport_message dm; + crypto::public_key public_key; + + dm.timestamp = (uint64_t)time(NULL); + dm.subject = "MMS V0 " + tools::get_human_readable_timestamp(dm.timestamp); + dm.source_transport_address = me.transport_address; + dm.source_monero_address = me.monero_address; + if (m.type == message_type::auto_config_data) + { + // Encrypt with the public key derived from the auto-config token, and send to the + // transport address likewise derived from that token + public_key = me.auto_config_public_key; + dm.destination_transport_address = me.auto_config_transport_address; + // The destination Monero address is not yet known + memset(&dm.destination_monero_address, 0, sizeof(cryptonote::account_public_address)); + } + else + { + // Encrypt with the receiver's view public key + public_key = receiver.monero_address.m_view_public_key; + const authorized_signer &receiver = m_signers[m.signer_index]; + dm.destination_monero_address = receiver.monero_address; + dm.destination_transport_address = receiver.transport_address; + } + encrypt(public_key, m.content, dm.content, dm.encryption_public_key, dm.iv); + dm.type = (uint32_t)m.type; + dm.hash = crypto::cn_fast_hash(dm.content.data(), dm.content.size()); + dm.round = m.round; + + crypto::generate_signature(dm.hash, me.monero_address.m_view_public_key, state.view_secret_key, dm.signature); + + m_transporter.send_message(dm); + + m.state=message_state::sent; + m.sent= (uint64_t)time(NULL); +} + +bool message_store::check_for_messages(const multisig_wallet_state &state, std::vector<message> &messages) +{ + m_run.store(true, std::memory_order_relaxed); + const authorized_signer &me = m_signers[0]; + std::vector<std::string> destinations; + destinations.push_back(me.transport_address); + for (uint32_t i = 1; i < m_num_authorized_signers; ++i) + { + const authorized_signer &m = m_signers[i]; + if (m.auto_config_running) + { + destinations.push_back(m.auto_config_transport_address); + } + } + std::vector<transport_message> transport_messages; + bool r = m_transporter.receive_messages(destinations, transport_messages); + if (!m_run.load(std::memory_order_relaxed)) + { + // Stop was called, don't waste time processing the messages + // (but once started processing them, don't react to stop request anymore, avoid receiving them "partially)" + return false; + } + + bool new_messages = false; + for (size_t i = 0; i < transport_messages.size(); ++i) + { + transport_message &rm = transport_messages[i]; + if (any_message_with_hash(rm.hash)) + { + // Already seen, do not take again + } + else + { + uint32_t sender_index; + bool take = false; + message_type type = static_cast<message_type>(rm.type); + crypto::secret_key decrypt_key = state.view_secret_key; + if (type == message_type::auto_config_data) + { + // Find out which signer sent it by checking which auto config transport address + // the message was sent to + for (uint32_t i = 1; i < m_num_authorized_signers; ++i) + { + const authorized_signer &m = m_signers[i]; + if (m.auto_config_transport_address == rm.destination_transport_address) + { + take = true; + sender_index = i; + decrypt_key = m.auto_config_secret_key; + break; + } + } + } + else if (type == message_type::signer_config) + { + // Typically we can't check yet whether we know the sender, so take from any + // and pretend it's from "me" because we might have nothing else yet + take = true; + sender_index = 0; + } + else + { + // Only accept from senders that are known as signer here, otherwise just ignore + take = get_signer_index_by_monero_address(rm.source_monero_address, sender_index); + } + if (take && (type != message_type::auto_config_data)) + { + // If the destination address is known, check it as well; this additional filter + // allows using the same transport address for multiple signers + take = rm.destination_monero_address == me.monero_address; + } + if (take) + { + crypto::hash actual_hash = crypto::cn_fast_hash(rm.content.data(), rm.content.size()); + THROW_WALLET_EXCEPTION_IF(actual_hash != rm.hash, tools::error::wallet_internal_error, "Message hash mismatch"); + + bool signature_valid = crypto::check_signature(actual_hash, rm.source_monero_address.m_view_public_key, rm.signature); + THROW_WALLET_EXCEPTION_IF(!signature_valid, tools::error::wallet_internal_error, "Message signature not valid"); + + std::string plaintext; + decrypt(rm.content, rm.encryption_public_key, rm.iv, decrypt_key, plaintext); + size_t index = add_message(state, sender_index, (message_type)rm.type, message_direction::in, plaintext); + message &m = m_messages[index]; + m.hash = rm.hash; + m.transport_id = rm.transport_id; + m.sent = rm.timestamp; + m.round = rm.round; + m.signature_count = rm.signature_count; + messages.push_back(m); + new_messages = true; + } + } + } + return new_messages; +} + +void message_store::delete_transport_message(uint32_t id) +{ + const message &m = get_message_by_id(id); + if (!m.transport_id.empty()) + { + m_transporter.delete_message(m.transport_id); + } +} + +std::string message_store::account_address_to_string(const cryptonote::account_public_address &account_address) const +{ + return get_account_address_as_str(m_nettype, false, account_address); +} + +const char* message_store::message_type_to_string(message_type type) +{ + switch (type) + { + case message_type::key_set: + return tr("key set"); + case message_type::additional_key_set: + return tr("additional key set"); + case message_type::multisig_sync_data: + return tr("multisig sync data"); + case message_type::partially_signed_tx: + return tr("partially signed tx"); + case message_type::fully_signed_tx: + return tr("fully signed tx"); + case message_type::note: + return tr("note"); + case message_type::signer_config: + return tr("signer config"); + case message_type::auto_config_data: + return tr("auto-config data"); + default: + return tr("unknown message type"); + } +} + +const char* message_store::message_direction_to_string(message_direction direction) +{ + switch (direction) + { + case message_direction::in: + return tr("in"); + case message_direction::out: + return tr("out"); + default: + return tr("unknown message direction"); + } +} + +const char* message_store::message_state_to_string(message_state state) +{ + switch (state) + { + case message_state::ready_to_send: + return tr("ready to send"); + case message_state::sent: + return tr("sent"); + case message_state::waiting: + return tr("waiting"); + case message_state::processed: + return tr("processed"); + case message_state::cancelled: + return tr("cancelled"); + default: + return tr("unknown message state"); + } +} + +// Convert a signer to string suitable for a column in a list, with 'max_width' +// Format: label: transport_address +std::string message_store::signer_to_string(const authorized_signer &signer, uint32_t max_width) +{ + std::string s = ""; + s.reserve(max_width); + uint32_t avail = max_width; + uint32_t label_len = signer.label.length(); + if (label_len > avail) + { + s.append(signer.label.substr(0, avail - 2)); + s.append(".."); + return s; + } + s.append(signer.label); + avail -= label_len; + uint32_t transport_addr_len = signer.transport_address.length(); + if ((transport_addr_len > 0) && (avail > 10)) + { + s.append(": "); + avail -= 2; + if (transport_addr_len <= avail) + { + s.append(signer.transport_address); + } + else + { + s.append(signer.transport_address.substr(0, avail-2)); + s.append(".."); + } + } + return s; +} + +} diff --git a/src/wallet/message_store.h b/src/wallet/message_store.h new file mode 100644 index 000000000..7d26f7889 --- /dev/null +++ b/src/wallet/message_store.h @@ -0,0 +1,420 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <cstdlib> +#include <string> +#include <vector> +#include "crypto/hash.h" +#include <boost/serialization/vector.hpp> +#include <boost/program_options/variables_map.hpp> +#include <boost/program_options/options_description.hpp> +#include <boost/optional/optional.hpp> +#include "serialization/serialization.h" +#include "cryptonote_basic/cryptonote_boost_serialization.h" +#include "cryptonote_basic/account_boost_serialization.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "common/i18n.h" +#include "common/command_line.h" +#include "wipeable_string.h" +#include "message_transporter.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.mms" +#define AUTO_CONFIG_TOKEN_BYTES 4 +#define AUTO_CONFIG_TOKEN_PREFIX "mms" + +namespace mms +{ + enum class message_type + { + key_set, + additional_key_set, + multisig_sync_data, + partially_signed_tx, + fully_signed_tx, + note, + signer_config, + auto_config_data + }; + + enum class message_direction + { + in, + out + }; + + enum class message_state + { + ready_to_send, + sent, + + waiting, + processed, + + cancelled + }; + + enum class message_processing + { + prepare_multisig, + make_multisig, + exchange_multisig_keys, + create_sync_data, + process_sync_data, + sign_tx, + send_tx, + submit_tx, + process_signer_config, + process_auto_config_data + }; + + struct message + { + uint32_t id; + message_type type; + message_direction direction; + std::string content; + uint64_t created; + uint64_t modified; + uint64_t sent; + uint32_t signer_index; + crypto::hash hash; + message_state state; + uint32_t wallet_height; + uint32_t round; + uint32_t signature_count; + std::string transport_id; + }; + // "wallet_height" (for lack of a short name that would describe what it is about) + // is the number of transfers present in the wallet at the time of message + // construction; used to coordinate generation of sync info (which depends + // on the content of the wallet at time of generation) + + struct authorized_signer + { + std::string label; + std::string transport_address; + bool monero_address_known; + cryptonote::account_public_address monero_address; + bool me; + uint32_t index; + std::string auto_config_token; + crypto::public_key auto_config_public_key; + crypto::secret_key auto_config_secret_key; + std::string auto_config_transport_address; + bool auto_config_running; + + authorized_signer() + { + monero_address_known = false; + memset(&monero_address, 0, sizeof(cryptonote::account_public_address)); + index = 0; + auto_config_public_key = crypto::null_pkey; + auto_config_secret_key = crypto::null_skey; + auto_config_running = false; + }; + }; + + struct processing_data + { + message_processing processing; + std::vector<uint32_t> message_ids; + uint32_t receiving_signer_index = 0; + }; + + struct file_transport_message + { + cryptonote::account_public_address sender_address; + crypto::chacha_iv iv; + crypto::public_key encryption_public_key; + message internal_message; + }; + + struct auto_config_data + { + std::string label; + std::string transport_address; + cryptonote::account_public_address monero_address; + }; + + // Overal .mms file structure, with the "message_store" object serialized to and + // encrypted in "encrypted_data" + struct file_data + { + std::string magic_string; + uint32_t file_version; + crypto::chacha_iv iv; + std::string encrypted_data; + }; + + // The following struct provides info about the current state of a "wallet2" object + // at the time of a "message_store" method call that those methods need. See on the + // one hand a first parameter of this type for several of those methods, and on the + // other hand the method "wallet2::get_multisig_wallet_state" which clients like the + // CLI wallet can use to get that info. + // + // Note that in the case of a wallet that is already multisig "address" is NOT the + // multisig address, but the "original" wallet address at creation time. Likewise + // "view_secret_key" is the original view secret key then. + // + // This struct definition is here and not in "wallet2.h" to avoid circular imports. + struct multisig_wallet_state + { + cryptonote::account_public_address address; + cryptonote::network_type nettype; + crypto::secret_key view_secret_key; + bool multisig; + bool multisig_is_ready; + bool has_multisig_partial_key_images; + uint32_t multisig_rounds_passed; + size_t num_transfer_details; + std::string mms_file; + }; + + class message_store + { + public: + message_store(); + // Initialize and start to use the MMS, set the first signer, this wallet itself + // Filename, if not null and not empty, is used to create the ".mms" file + // reset it if already used, with deletion of all signers and messages + void init(const multisig_wallet_state &state, const std::string &own_label, + const std::string &own_transport_address, uint32_t num_authorized_signers, uint32_t num_required_signers); + void set_active(bool active) { m_active = active; }; + void set_auto_send(bool auto_send) { m_auto_send = auto_send; }; + void set_options(const boost::program_options::variables_map& vm); + void set_options(const std::string &bitmessage_address, const epee::wipeable_string &bitmessage_login); + bool get_active() const { return m_active; }; + bool get_auto_send() const { return m_auto_send; }; + uint32_t get_num_required_signers() const { return m_num_required_signers; }; + uint32_t get_num_authorized_signers() const { return m_num_authorized_signers; }; + + void set_signer(const multisig_wallet_state &state, + uint32_t index, + const boost::optional<std::string> &label, + const boost::optional<std::string> &transport_address, + const boost::optional<cryptonote::account_public_address> monero_address); + + const authorized_signer &get_signer(uint32_t index) const; + bool get_signer_index_by_monero_address(const cryptonote::account_public_address &monero_address, uint32_t &index) const; + bool get_signer_index_by_label(const std::string label, uint32_t &index) const; + const std::vector<authorized_signer> &get_all_signers() const { return m_signers; }; + bool signer_config_complete() const; + bool signer_labels_complete() const; + void get_signer_config(std::string &signer_config); + void unpack_signer_config(const multisig_wallet_state &state, const std::string &signer_config, + std::vector<authorized_signer> &signers); + void process_signer_config(const multisig_wallet_state &state, const std::string &signer_config); + + void start_auto_config(const multisig_wallet_state &state); + bool check_auto_config_token(const std::string &raw_token, + std::string &adjusted_token) const; + size_t add_auto_config_data_message(const multisig_wallet_state &state, + const std::string &auto_config_token); + void process_auto_config_data_message(uint32_t id); + void stop_auto_config(); + + // Process data just created by "me" i.e. the own local wallet, e.g. as the result of a "prepare_multisig" command + // Creates the resulting messages to the right signers + void process_wallet_created_data(const multisig_wallet_state &state, message_type type, const std::string &content); + + // Go through all the messages, look at the "ready to process" ones, and check whether any single one + // or any group of them can be processed, because they are processable as single messages (like a tx + // that is fully signed and thus ready for submit to the net) or because they form a complete group + // (e.g. key sets from all authorized signers to make the wallet multisig). If there are multiple + // candidates, e.g. in 2/3 multisig sending to one OR the other signer to sign, there will be more + // than 1 element in 'data' for the user to choose. If nothing is ready "false" is returned. + // The method mostly ignores the order in which the messages were received because messages may be delayed + // (e.g. sync data from a signer arrives AFTER a transaction to submit) or because message time stamps + // may be wrong so it's not possible to order them reliably. + // Messages also may be ready by themselves but the wallet not yet ready for them (e.g. sync data already + // arriving when the wallet is not yet multisig because key sets were delayed or were lost altogether.) + // If nothing is ready 'wait_reason' may contain further info about the reason why. + bool get_processable_messages(const multisig_wallet_state &state, + bool force_sync, + std::vector<processing_data> &data_list, + std::string &wait_reason); + void set_messages_processed(const processing_data &data); + + size_t add_message(const multisig_wallet_state &state, + uint32_t signer_index, message_type type, message_direction direction, + const std::string &content); + const std::vector<message> &get_all_messages() const { return m_messages; }; + bool get_message_by_id(uint32_t id, message &m) const; + message get_message_by_id(uint32_t id) const; + void set_message_processed_or_sent(uint32_t id); + void delete_message(uint32_t id); + void delete_all_messages(); + void get_sanitized_message_text(const message &m, std::string &sanitized_text) const; + + void send_message(const multisig_wallet_state &state, uint32_t id); + bool check_for_messages(const multisig_wallet_state &state, std::vector<message> &messages); + void stop() { m_run.store(false, std::memory_order_relaxed); m_transporter.stop(); } + + void write_to_file(const multisig_wallet_state &state, const std::string &filename); + void read_from_file(const multisig_wallet_state &state, const std::string &filename); + + template <class t_archive> + inline void serialize(t_archive &a, const unsigned int ver) + { + a & m_active; + a & m_num_authorized_signers; + a & m_nettype; + a & m_num_required_signers; + a & m_signers; + a & m_messages; + a & m_next_message_id; + a & m_auto_send; + } + + static const char* message_type_to_string(message_type type); + static const char* message_direction_to_string(message_direction direction); + static const char* message_state_to_string(message_state state); + std::string signer_to_string(const authorized_signer &signer, uint32_t max_width); + + static const char *tr(const char *str) { return i18n_translate(str, "tools::mms"); } + static void init_options(boost::program_options::options_description& desc_params); + + private: + bool m_active; + uint32_t m_num_authorized_signers; + uint32_t m_num_required_signers; + bool m_auto_send; + cryptonote::network_type m_nettype; + std::vector<authorized_signer> m_signers; + std::vector<message> m_messages; + uint32_t m_next_message_id; + std::string m_filename; + message_transporter m_transporter; + std::atomic<bool> m_run; + + bool get_message_index_by_id(uint32_t id, size_t &index) const; + size_t get_message_index_by_id(uint32_t id) const; + message& get_message_ref_by_id(uint32_t id); + bool any_message_of_type(message_type type, message_direction direction) const; + bool any_message_with_hash(const crypto::hash &hash) const; + size_t get_other_signers_id_count(const std::vector<uint32_t> &ids) const; + bool message_ids_complete(const std::vector<uint32_t> &ids) const; + void encrypt(crypto::public_key public_key, const std::string &plaintext, + std::string &ciphertext, crypto::public_key &encryption_public_key, crypto::chacha_iv &iv); + void decrypt(const std::string &ciphertext, const crypto::public_key &encryption_public_key, const crypto::chacha_iv &iv, + const crypto::secret_key &view_secret_key, std::string &plaintext); + std::string create_auto_config_token(); + void setup_signer_for_auto_config(uint32_t index, const std::string token, bool receiving); + void delete_transport_message(uint32_t id); + std::string account_address_to_string(const cryptonote::account_public_address &account_address) const; + void save(const multisig_wallet_state &state); + }; +} + +BOOST_CLASS_VERSION(mms::file_data, 0) +BOOST_CLASS_VERSION(mms::message_store, 0) +BOOST_CLASS_VERSION(mms::message, 0) +BOOST_CLASS_VERSION(mms::file_transport_message, 0) +BOOST_CLASS_VERSION(mms::authorized_signer, 1) +BOOST_CLASS_VERSION(mms::auto_config_data, 0) + +namespace boost +{ + namespace serialization + { + template <class Archive> + inline void serialize(Archive &a, mms::file_data &x, const boost::serialization::version_type ver) + { + a & x.magic_string; + a & x.file_version; + a & x.iv; + a & x.encrypted_data; + } + + template <class Archive> + inline void serialize(Archive &a, mms::message &x, const boost::serialization::version_type ver) + { + a & x.id; + a & x.type; + a & x.direction; + a & x.content; + a & x.created; + a & x.modified; + a & x.sent; + a & x.signer_index; + a & x.hash; + a & x.state; + a & x.wallet_height; + a & x.round; + a & x.signature_count; + a & x.transport_id; + } + + template <class Archive> + inline void serialize(Archive &a, mms::authorized_signer &x, const boost::serialization::version_type ver) + { + a & x.label; + a & x.transport_address; + a & x.monero_address_known; + a & x.monero_address; + a & x.me; + a & x.index; + if (ver < 1) + { + return; + } + a & x.auto_config_token; + a & x.auto_config_public_key; + a & x.auto_config_secret_key; + a & x.auto_config_transport_address; + a & x.auto_config_running; + } + + template <class Archive> + inline void serialize(Archive &a, mms::auto_config_data &x, const boost::serialization::version_type ver) + { + a & x.label; + a & x.transport_address; + a & x.monero_address; + } + + template <class Archive> + inline void serialize(Archive &a, mms::file_transport_message &x, const boost::serialization::version_type ver) + { + a & x.sender_address; + a & x.iv; + a & x.encryption_public_key; + a & x.internal_message; + } + + template <class Archive> + inline void serialize(Archive &a, crypto::chacha_iv &x, const boost::serialization::version_type ver) + { + a & x.data; + } + + } +} diff --git a/src/wallet/message_transporter.cpp b/src/wallet/message_transporter.cpp new file mode 100644 index 000000000..eafd13d3b --- /dev/null +++ b/src/wallet/message_transporter.cpp @@ -0,0 +1,317 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "message_transporter.h" +#include "string_coding.h" +#include <boost/format.hpp> +#include "wallet_errors.h" +#include "net/http_client.h" +#include "net/net_parse_helpers.h" +#include <algorithm> + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.mms" +#define PYBITMESSAGE_DEFAULT_API_PORT 8442 + +namespace mms +{ + +namespace bitmessage_rpc +{ + + struct message_info + { + uint32_t encodingType; + std::string toAddress; + uint32_t read; + std::string msgid; + std::string message; + std::string fromAddress; + std::string receivedTime; + std::string subject; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(encodingType) + KV_SERIALIZE(toAddress) + KV_SERIALIZE(read) + KV_SERIALIZE(msgid) + KV_SERIALIZE(message); + KV_SERIALIZE(fromAddress) + KV_SERIALIZE(receivedTime) + KV_SERIALIZE(subject) + END_KV_SERIALIZE_MAP() + }; + + struct inbox_messages_response + { + std::vector<message_info> inboxMessages; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(inboxMessages) + END_KV_SERIALIZE_MAP() + }; + +} + +message_transporter::message_transporter() +{ + m_run = true; +} + +void message_transporter::set_options(const std::string &bitmessage_address, const epee::wipeable_string &bitmessage_login) +{ + m_bitmessage_url = bitmessage_address; + epee::net_utils::http::url_content address_parts{}; + epee::net_utils::parse_url(m_bitmessage_url, address_parts); + if (address_parts.port == 0) + { + address_parts.port = PYBITMESSAGE_DEFAULT_API_PORT; + } + m_bitmessage_login = bitmessage_login; + + m_http_client.set_server(address_parts.host, std::to_string(address_parts.port), boost::none); +} + +bool message_transporter::receive_messages(const std::vector<std::string> &destination_transport_addresses, + std::vector<transport_message> &messages) +{ + // The message body of the Bitmessage message is basically the transport message, as JSON (and nothing more). + // Weeding out other, non-MMS messages is done in a simple way: If it deserializes without error, it's an MMS message + // That JSON is Base64-encoded by the MMS because the Monero epee JSON serializer does not escape anything and happily + // includes even 0 (NUL) in strings, which might confuse Bitmessage or at least display confusingly in the client. + // There is yet another Base64-encoding of course as part of the Bitmessage API for the message body parameter + // The Bitmessage API call "getAllInboxMessages" gives back a JSON array with all the messages (despite using + // XML-RPC for the calls, and not JSON-RPC ...) + m_run.store(true, std::memory_order_relaxed); + std::string request; + start_xml_rpc_cmd(request, "getAllInboxMessages"); + end_xml_rpc_cmd(request); + std::string answer; + post_request(request, answer); + + std::string json = get_str_between_tags(answer, "<string>", "</string>"); + bitmessage_rpc::inbox_messages_response bitmessage_res; + epee::serialization::load_t_from_json(bitmessage_res, json); + size_t size = bitmessage_res.inboxMessages.size(); + messages.clear(); + + for (size_t i = 0; i < size; ++i) + { + if (!m_run.load(std::memory_order_relaxed)) + { + // Stop was called, don't waste time processing any more messages + return false; + } + const bitmessage_rpc::message_info &message_info = bitmessage_res.inboxMessages[i]; + if (std::find(destination_transport_addresses.begin(), destination_transport_addresses.end(), message_info.toAddress) != destination_transport_addresses.end()) + { + transport_message message; + bool is_mms_message = false; + try + { + // First Base64-decoding: The message body is Base64 in the Bitmessage API + std::string message_body = epee::string_encoding::base64_decode(message_info.message); + // Second Base64-decoding: The MMS uses Base64 to hide non-textual data in its JSON from Bitmessage + json = epee::string_encoding::base64_decode(message_body); + epee::serialization::load_t_from_json(message, json); + is_mms_message = true; + } + catch(const std::exception& e) + { + } + if (is_mms_message) + { + message.transport_id = message_info.msgid; + messages.push_back(message); + } + } + } + + return true; +} + +bool message_transporter::send_message(const transport_message &message) +{ + // <toAddress> <fromAddress> <subject> <message> [encodingType [TTL]] + std::string request; + start_xml_rpc_cmd(request, "sendMessage"); + add_xml_rpc_string_param(request, message.destination_transport_address); + add_xml_rpc_string_param(request, message.source_transport_address); + add_xml_rpc_base64_param(request, message.subject); + std::string json = epee::serialization::store_t_to_json(message); + std::string message_body = epee::string_encoding::base64_encode(json); // See comment in "receive_message" about reason for (double-)Base64 encoding + add_xml_rpc_base64_param(request, message_body); + add_xml_rpc_integer_param(request, 2); + end_xml_rpc_cmd(request); + std::string answer; + post_request(request, answer); + return true; +} + +bool message_transporter::delete_message(const std::string &transport_id) +{ + std::string request; + start_xml_rpc_cmd(request, "trashMessage"); + add_xml_rpc_string_param(request, transport_id); + end_xml_rpc_cmd(request); + std::string answer; + post_request(request, answer); + return true; +} + +// Deterministically derive a transport / Bitmessage address from 'seed' (the 10-hex-digits +// auto-config token will be used), but do not set it up for receiving in PyBitmessage as +// well, because it's possible the address will only ever be used to SEND auto-config data +std::string message_transporter::derive_transport_address(const std::string &seed) +{ + std::string request; + start_xml_rpc_cmd(request, "getDeterministicAddress"); + add_xml_rpc_base64_param(request, seed); + add_xml_rpc_integer_param(request, 4); // addressVersionNumber + add_xml_rpc_integer_param(request, 1); // streamNumber + end_xml_rpc_cmd(request); + std::string answer; + post_request(request, answer); + std::string address = get_str_between_tags(answer, "<string>", "</string>"); + return address; +} + +// Derive a transport address and configure it for receiving in PyBitmessage, typically +// for receiving auto-config messages by the wallet of the auto-config organizer +std::string message_transporter::derive_and_receive_transport_address(const std::string &seed) +{ + // We need to call both "get_deterministic_address" AND "createDeterministicAddresses" + // because we won't get back the address from the latter call if it exists already + std::string address = derive_transport_address(seed); + + std::string request; + start_xml_rpc_cmd(request, "createDeterministicAddresses"); + add_xml_rpc_base64_param(request, seed); + add_xml_rpc_integer_param(request, 1); // numberOfAddresses + add_xml_rpc_integer_param(request, 4); // addressVersionNumber + end_xml_rpc_cmd(request); + std::string answer; + post_request(request, answer); + + return address; +} + +bool message_transporter::delete_transport_address(const std::string &transport_address) +{ + std::string request; + start_xml_rpc_cmd(request, "deleteAddress"); + add_xml_rpc_string_param(request, transport_address); + end_xml_rpc_cmd(request); + std::string answer; + return post_request(request, answer); +} + +bool message_transporter::post_request(const std::string &request, std::string &answer) +{ + // Somehow things do not work out if one tries to connect "m_http_client" to Bitmessage + // and keep it connected over the course of several calls. But with a new connection per + // call and disconnecting after the call there is no problem (despite perhaps a small + // slowdown) + epee::net_utils::http::fields_list additional_params; + + // Basic access authentication according to RFC 7617 (which the epee HTTP classes do not seem to support?) + // "m_bitmessage_login" just contains what is needed here, "user:password" + std::string auth_string = epee::string_encoding::base64_encode((const unsigned char*)m_bitmessage_login.data(), m_bitmessage_login.size()); + auth_string.insert(0, "Basic "); + additional_params.push_back(std::make_pair("Authorization", auth_string)); + + additional_params.push_back(std::make_pair("Content-Type", "application/xml; charset=utf-8")); + const epee::net_utils::http::http_response_info* response = NULL; + std::chrono::milliseconds timeout = std::chrono::seconds(15); + bool r = m_http_client.invoke("/", "POST", request, timeout, std::addressof(response), std::move(additional_params)); + if (r) + { + answer = response->m_body; + } + else + { + LOG_ERROR("POST request to Bitmessage failed: " << request.substr(0, 300)); + THROW_WALLET_EXCEPTION(tools::error::no_connection_to_bitmessage, m_bitmessage_url); + } + m_http_client.disconnect(); // see comment above + std::string string_value = get_str_between_tags(answer, "<string>", "</string>"); + if ((string_value.find("API Error") == 0) || (string_value.find("RPC ") == 0)) + { + THROW_WALLET_EXCEPTION(tools::error::bitmessage_api_error, string_value); + } + + return r; +} + +// Pick some string between two delimiters +// When parsing the XML returned by PyBitmessage, don't bother to fully parse it but as a little hack rely on the +// fact that e.g. a single string returned will be, however deeply nested in "<params><param><value>...", delivered +// between the very first "<string>" and "</string>" tags to be found in the XML +std::string message_transporter::get_str_between_tags(const std::string &s, const std::string &start_delim, const std::string &stop_delim) +{ + size_t first_delim_pos = s.find(start_delim); + if (first_delim_pos != std::string::npos) + { + size_t end_pos_of_first_delim = first_delim_pos + start_delim.length(); + size_t last_delim_pos = s.find(stop_delim); + if (last_delim_pos != std::string::npos) + { + return s.substr(end_pos_of_first_delim, last_delim_pos - end_pos_of_first_delim); + } + } + return std::string(); +} + +void message_transporter::start_xml_rpc_cmd(std::string &xml, const std::string &method_name) +{ + xml = (boost::format("<?xml version=\"1.0\"?><methodCall><methodName>%s</methodName><params>") % method_name).str(); +} + +void message_transporter::add_xml_rpc_string_param(std::string &xml, const std::string ¶m) +{ + xml += (boost::format("<param><value><string>%s</string></value></param>") % param).str(); +} + +void message_transporter::add_xml_rpc_base64_param(std::string &xml, const std::string ¶m) +{ + // Bitmessage expects some arguments Base64-encoded, but it wants them as parameters of type "string", not "base64" that is also part of XML-RPC + std::string encoded_param = epee::string_encoding::base64_encode(param); + xml += (boost::format("<param><value><string>%s</string></value></param>") % encoded_param).str(); +} + +void message_transporter::add_xml_rpc_integer_param(std::string &xml, const int32_t ¶m) +{ + xml += (boost::format("<param><value><int>%i</int></value></param>") % param).str(); +} + +void message_transporter::end_xml_rpc_cmd(std::string &xml) +{ + xml += "</params></methodCall>"; +} + +} diff --git a/src/wallet/message_transporter.h b/src/wallet/message_transporter.h new file mode 100644 index 000000000..8291311ce --- /dev/null +++ b/src/wallet/message_transporter.h @@ -0,0 +1,113 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once +#include "serialization/keyvalue_serialization.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/cryptonote_boost_serialization.h" +#include "cryptonote_basic/account_boost_serialization.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "net/http_server_impl_base.h" +#include "net/http_client.h" +#include "common/util.h" +#include "wipeable_string.h" +#include "serialization/keyvalue_serialization.h" +#include <vector> + +namespace mms +{ + +struct transport_message +{ + cryptonote::account_public_address source_monero_address; + std::string source_transport_address; + cryptonote::account_public_address destination_monero_address; + std::string destination_transport_address; + crypto::chacha_iv iv; + crypto::public_key encryption_public_key; + uint64_t timestamp; + uint32_t type; + std::string subject; + std::string content; + crypto::hash hash; + crypto::signature signature; + uint32_t round; + uint32_t signature_count; + std::string transport_id; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(source_monero_address) + KV_SERIALIZE(source_transport_address) + KV_SERIALIZE(destination_monero_address) + KV_SERIALIZE(destination_transport_address) + KV_SERIALIZE_VAL_POD_AS_BLOB(iv) + KV_SERIALIZE_VAL_POD_AS_BLOB(encryption_public_key) + KV_SERIALIZE(timestamp) + KV_SERIALIZE(type) + KV_SERIALIZE(subject) + KV_SERIALIZE(content) + KV_SERIALIZE_VAL_POD_AS_BLOB(hash) + KV_SERIALIZE_VAL_POD_AS_BLOB(signature) + KV_SERIALIZE(round) + KV_SERIALIZE(signature_count) + KV_SERIALIZE(transport_id) + END_KV_SERIALIZE_MAP() +}; + +class message_transporter +{ +public: + message_transporter(); + void set_options(const std::string &bitmessage_address, const epee::wipeable_string &bitmessage_login); + bool send_message(const transport_message &message); + bool receive_messages(const std::vector<std::string> &destination_transport_addresses, + std::vector<transport_message> &messages); + bool delete_message(const std::string &transport_id); + void stop() { m_run.store(false, std::memory_order_relaxed); } + std::string derive_transport_address(const std::string &seed); + std::string derive_and_receive_transport_address(const std::string &seed); + bool delete_transport_address(const std::string &transport_address); + +private: + epee::net_utils::http::http_simple_client m_http_client; + std::string m_bitmessage_url; + epee::wipeable_string m_bitmessage_login; + std::atomic<bool> m_run; + + bool post_request(const std::string &request, std::string &answer); + static std::string get_str_between_tags(const std::string &s, const std::string &start_delim, const std::string &stop_delim); + + static void start_xml_rpc_cmd(std::string &xml, const std::string &method_name); + static void add_xml_rpc_string_param(std::string &xml, const std::string ¶m); + static void add_xml_rpc_base64_param(std::string &xml, const std::string ¶m); + static void add_xml_rpc_integer_param(std::string &xml, const int32_t ¶m); + static void end_xml_rpc_cmd(std::string &xml); + +}; + +} diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 097278961..0d2faca54 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -226,7 +226,7 @@ struct options { const command_line::arg_descriptor<std::string> tx_notify = { "tx-notify" , "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" , "" }; }; -void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file) +void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file, std::string &mms_file) { keys_file = file_path; wallet_file = file_path; @@ -238,6 +238,7 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file, {//provided wallet file name keys_file += ".keys"; } + mms_file = file_path + ".mms"; } uint64_t calculate_fee(uint64_t fee_per_kb, size_t bytes, uint64_t fee_multiplier) @@ -330,6 +331,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl wallet->init(std::move(daemon_address), std::move(login), 0, false, *trusted_daemon); boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); wallet->set_ring_database(ringdb_path.string()); + wallet->get_message_store().set_options(vm); wallet->device_name(device_name); wallet->device_derivation_path(device_derivation_path); @@ -907,6 +909,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0), + m_original_keys_available(false), + m_message_store(), m_key_device_type(hw::device::device_type::SOFTWARE), m_ring_history_saved(false), m_ringdb(), @@ -956,6 +960,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.stagenet); command_line::add_arg(desc_params, opts.shared_ringdb_dir); command_line::add_arg(desc_params, opts.kdf_rounds); + mms::message_store::init_options(desc_params); command_line::add_arg(desc_params, opts.hw_device); command_line::add_arg(desc_params, opts.hw_device_derivation_path); command_line::add_arg(desc_params, opts.tx_notify); @@ -3184,6 +3189,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetUint(m_subaddress_lookahead_minor); json.AddMember("subaddress_lookahead_minor", value2, json.GetAllocator()); + value2.SetInt(m_original_keys_available ? 1 : 0); + json.AddMember("original_keys_available", value2, json.GetAllocator()); + value2.SetUint(1); json.AddMember("encrypted_secret_keys", value2, json.GetAllocator()); @@ -3193,6 +3201,18 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value.SetString(m_device_derivation_path.c_str(), m_device_derivation_path.size()); json.AddMember("device_derivation_path", value, json.GetAllocator()); + std::string original_address; + std::string original_view_secret_key; + if (m_original_keys_available) + { + original_address = get_account_address_as_str(m_nettype, false, m_original_address); + value.SetString(original_address.c_str(), original_address.length()); + json.AddMember("original_address", value, json.GetAllocator()); + original_view_secret_key = epee::string_tools::pod_to_hex(m_original_view_secret_key); + value.SetString(original_view_secret_key.c_str(), original_view_secret_key.length()); + json.AddMember("original_view_secret_key", value, json.GetAllocator()); + } + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); @@ -3311,6 +3331,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_ignore_fractional_outputs = true; m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; + m_original_keys_available = false; m_device_name = ""; m_device_derivation_path = ""; m_key_device_type = hw::device::device_type::SOFTWARE; @@ -3483,6 +3504,35 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_derivation_path, std::string, String, false, std::string()); m_device_derivation_path = field_device_derivation_path; + + if (json.HasMember("original_keys_available")) + { + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, original_keys_available, int, Int, false, false); + m_original_keys_available = field_original_keys_available; + if (m_original_keys_available) + { + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, original_address, std::string, String, true, std::string()); + address_parse_info info; + bool ok = get_account_address_from_str(info, m_nettype, field_original_address); + if (!ok) + { + LOG_ERROR("Failed to parse original_address from JSON"); + return false; + } + m_original_address = info.address; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, original_view_secret_key, std::string, String, true, std::string()); + ok = epee::string_tools::hex_to_pod(field_original_view_secret_key, m_original_view_secret_key); + if (!ok) + { + LOG_ERROR("Failed to parse original_view_secret_key from JSON"); + return false; + } + } + } + else + { + m_original_keys_available = false; + } } else { @@ -3809,6 +3859,10 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); + // Not possible to restore a multisig wallet that is able to activate the MMS + // (because the original keys are not (yet) part of the restore info) + m_original_keys_available = false; + create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); setup_new_blockchain(); @@ -3846,6 +3900,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); + m_original_keys_available = false; m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); @@ -3934,6 +3989,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); + m_original_keys_available = false; m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); @@ -3974,6 +4030,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); + m_original_keys_available = false; m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); @@ -4015,6 +4072,7 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); + m_original_keys_available = false; setup_keys(password); m_device_name = device_name; @@ -4127,6 +4185,15 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, m_multisig_derivations = derivations; } } + + if (!m_original_keys_available) + { + // Save the original i.e. non-multisig keys so the MMS can continue to use them to encrypt and decrypt messages + // (making a wallet multisig overwrites those keys, see account_base::make_multisig) + m_original_address = m_account.get_keys().m_account_address; + m_original_view_secret_key = m_account.get_keys().m_view_secret_key; + m_original_keys_available = true; + } clear(); MINFO("Creating view key..."); @@ -4560,8 +4627,8 @@ void wallet2::write_watch_only_wallet(const std::string& wallet_name, const epee //---------------------------------------------------------------------------------------------------- void wallet2::wallet_exists(const std::string& file_path, bool& keys_file_exists, bool& wallet_file_exists) { - std::string keys_file, wallet_file; - do_prepare_file_names(file_path, keys_file, wallet_file); + std::string keys_file, wallet_file, mms_file; + do_prepare_file_names(file_path, keys_file, wallet_file, mms_file); boost::system::error_code ignore; keys_file_exists = boost::filesystem::exists(keys_file, ignore); @@ -4615,7 +4682,7 @@ bool wallet2::parse_payment_id(const std::string& payment_id_str, crypto::hash& //---------------------------------------------------------------------------------------------------- bool wallet2::prepare_file_names(const std::string& file_path) { - do_prepare_file_names(file_path, m_keys_file, m_wallet_file); + do_prepare_file_names(file_path, m_keys_file, m_wallet_file, m_mms_file); return true; } //---------------------------------------------------------------------------------------------------- @@ -4808,6 +4875,8 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass { MERROR("Failed to save rings, will try again next time"); } + + m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file); } //---------------------------------------------------------------------------------------------------- void wallet2::trim_hashchain() @@ -4913,6 +4982,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas const std::string old_file = m_wallet_file; const std::string old_keys_file = m_keys_file; const std::string old_address_file = m_wallet_file + ".address.txt"; + const std::string old_mms_file = m_mms_file; // save keys to the new file // if we here, main wallet file is saved and we only need to save keys and address files @@ -4942,6 +5012,14 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas if (!r) { LOG_ERROR("error removing file: " << old_address_file); } + // remove old message store file + if (boost::filesystem::exists(old_mms_file)) + { + r = boost::filesystem::remove(old_mms_file); + if (!r) { + LOG_ERROR("error removing file: " << old_mms_file); + } + } } else { // save to new file #ifdef WIN32 @@ -4967,6 +5045,14 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas std::error_code e = tools::replace_file(new_file, m_wallet_file); THROW_WALLET_EXCEPTION_IF(e, error::file_save_error, m_wallet_file, e); } + + if (m_message_store.get_active()) + { + // While the "m_message_store" object of course always exist, a file for the message + // store should only exist if the MMS is really active + m_message_store.write_to_file(get_multisig_wallet_state(), m_mms_file); + } + } //---------------------------------------------------------------------------------------------------- uint64_t wallet2::balance(uint32_t index_major) const @@ -12030,6 +12116,29 @@ void wallet2::generate_genesis(cryptonote::block& b) const { cryptonote::generate_genesis_block(b, get_config(m_nettype).GENESIS_TX, get_config(m_nettype).GENESIS_NONCE); } //---------------------------------------------------------------------------------------------------- +mms::multisig_wallet_state wallet2::get_multisig_wallet_state() const +{ + mms::multisig_wallet_state state; + state.nettype = m_nettype; + state.multisig = multisig(&state.multisig_is_ready); + state.has_multisig_partial_key_images = has_multisig_partial_key_images(); + state.multisig_rounds_passed = m_multisig_rounds_passed; + state.num_transfer_details = m_transfers.size(); + if (state.multisig) + { + THROW_WALLET_EXCEPTION_IF(!m_original_keys_available, error::wallet_internal_error, "MMS use not possible because own original Monero address not available"); + state.address = m_original_address; + state.view_secret_key = m_original_view_secret_key; + } + else + { + state.address = m_account.get_keys().m_account_address; + state.view_secret_key = m_account.get_keys().m_view_secret_key; + } + state.mms_file=m_mms_file; + return state; +} +//---------------------------------------------------------------------------------------------------- wallet_device_callback * wallet2::get_device_callback() { if (!m_device_callback){ diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 91c68bf3c..ace01a235 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -61,6 +61,7 @@ #include "wallet_errors.h" #include "common/password.h" #include "node_rpc_proxy.h" +#include "message_store.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" @@ -674,7 +675,7 @@ namespace tools bool init(std::string daemon_address = "http://localhost:8080", boost::optional<epee::net_utils::http::login> daemon_login = boost::none, uint64_t upper_transaction_weight_limit = 0, bool ssl = false, bool trusted_daemon = false); - void stop() { m_run.store(false, std::memory_order_relaxed); } + void stop() { m_run.store(false, std::memory_order_relaxed); m_message_store.stop(); } i_wallet2_callback* callback() const { return m_callback; } void callback(i_wallet2_callback* callback) { m_callback = callback; } @@ -1218,6 +1219,11 @@ namespace tools bool unblackball_output(const std::pair<uint64_t, uint64_t> &output); bool is_output_blackballed(const std::pair<uint64_t, uint64_t> &output) const; + // MMS ------------------------------------------------------------------------------------------------- + mms::message_store& get_message_store() { return m_message_store; }; + const mms::message_store& get_message_store() const { return m_message_store; }; + mms::multisig_wallet_state get_multisig_wallet_state() const; + bool lock_keys_file(); bool unlock_keys_file(); bool is_keys_file_locked() const; @@ -1320,6 +1326,7 @@ namespace tools std::string m_daemon_address; std::string m_wallet_file; std::string m_keys_file; + std::string m_mms_file; epee::net_utils::http::http_simple_client m_http_client; hashchain m_blockchain; std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; @@ -1420,6 +1427,11 @@ namespace tools uint64_t m_last_block_reward; std::unique_ptr<tools::file_locker> m_keys_file_locker; + + mms::message_store m_message_store; + bool m_original_keys_available; + cryptonote::account_public_address m_original_address; + crypto::secret_key m_original_view_secret_key; crypto::chacha_key m_cache_key; boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh; diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index e2caee5d2..35862bda1 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -821,6 +821,31 @@ namespace tools std::string m_wallet_file; }; //---------------------------------------------------------------------------------------------------- + struct mms_error : public wallet_logic_error + { + protected: + explicit mms_error(std::string&& loc, const std::string& message) + : wallet_logic_error(std::move(loc), message) + { + } + }; + //---------------------------------------------------------------------------------------------------- + struct no_connection_to_bitmessage : public mms_error + { + explicit no_connection_to_bitmessage(std::string&& loc, const std::string& address) + : mms_error(std::move(loc), "no connection to PyBitmessage at address " + address) + { + } + }; + //---------------------------------------------------------------------------------------------------- + struct bitmessage_api_error : public mms_error + { + explicit bitmessage_api_error(std::string&& loc, const std::string& error_string) + : mms_error(std::move(loc), "PyBitmessage returned " + error_string) + { + } + }; + //---------------------------------------------------------------------------------------------------- #if !defined(_MSC_VER) |