aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/common/util.cpp12
-rw-r--r--src/common/util.h2
-rw-r--r--src/simplewallet/simplewallet.cpp1485
-rw-r--r--src/simplewallet/simplewallet.h45
-rw-r--r--src/wallet/CMakeLists.txt9
-rw-r--r--src/wallet/message_store.cpp1445
-rw-r--r--src/wallet/message_store.h420
-rw-r--r--src/wallet/message_transporter.cpp317
-rw-r--r--src/wallet/message_transporter.h113
-rw-r--r--src/wallet/wallet2.cpp117
-rw-r--r--src/wallet/wallet2.h14
-rw-r--r--src/wallet/wallet_errors.h25
12 files changed, 3877 insertions, 127 deletions
diff --git a/src/common/util.cpp b/src/common/util.cpp
index 58b0d8210..448f792ff 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"
@@ -1025,4 +1026,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/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index b729c4bb7..281702774 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,14 @@ 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;)
+
enum TransferType {
Transfer,
TransferLocked,
@@ -836,65 +839,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;
+ return false;
}
// parse threshold
@@ -902,14 +923,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 +945,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());
@@ -997,35 +1022,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;
+ return false;
}
try
@@ -1036,6 +1067,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 +1081,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 +1089,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;
+ 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 +1154,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;
+ 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 +1219,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 +1230,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 +1249,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;
+ 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 +1364,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;
+ 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 +1443,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;
@@ -2308,6 +2432,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);
@@ -2655,6 +2785,89 @@ simple_wallet::simple_wallet()
boost::bind(&simple_wallet::export_raw_multisig, this, _1),
tr("export_raw_multisig_tx <filename>"),
tr("Export a signed multisig transaction to a file"));
+ m_cmd_binder.set_handler("mms",
+ boost::bind(&simple_wallet::mms, this, _1),
+ tr("mms [<subcommand> [<subcommand_parameters>]]"),
+ 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("mms init <required_signers>/<authorized_signers> <own_label> <own_transport_address>"),
+ 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("mms info"),
+ tr("Display current MMS configuration"));
+ m_cmd_binder.set_handler("mms signer",
+ boost::bind(&simple_wallet::mms, this, _1),
+ tr("mms signer [<number> <label> [<transport_address> [<monero_address>]]]"),
+ 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("mms list"),
+ tr("List all messages"));
+ m_cmd_binder.set_handler("mms next",
+ boost::bind(&simple_wallet::mms, this, _1),
+ tr("mms next [sync]"),
+ 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("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("mms transfer <transfer_command_arguments>"),
+ 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("mms delete (<message_id> | all)"),
+ 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("mms send [<message_id>]"),
+ 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("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("mms export <message_id"),
+ 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("mms note [<label> <text>]"),
+ 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("mms show <message_id>"),
+ tr("Show detailed info about a single message"));
+ m_cmd_binder.set_handler("mms set",
+ boost::bind(&simple_wallet::mms, this, _1),
+ tr("mms set <option_name> [<option_value>]"),
+ 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("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("mms start_auto_config [<label> <label> ...]"),
+ 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("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("mms auto_config <auto_config_token>"),
+ 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>"),
@@ -4843,11 +5056,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 +5068,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 +5090,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 +5102,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 +5149,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 +5163,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 +5196,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 +5211,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 +5221,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 +5238,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 +5255,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 +5274,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 +5298,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 +5314,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 +5354,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 +5419,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 +5442,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 +5481,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 +5489,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 +5504,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 +5519,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 +5533,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_)
@@ -6799,19 +7029,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 +7313,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 +7377,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)
@@ -7363,6 +7580,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 +7618,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));
@@ -8336,7 +8577,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 +8627,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 +8652,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 +8683,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);
@@ -8602,3 +8843,1009 @@ 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();
+ 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 4e3fb1ae5..8425e4a03 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 &param)
+{
+ 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 &param)
+{
+ // 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 &param)
+{
+ 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 &param);
+ static void add_xml_rpc_base64_param(std::string &xml, const std::string &param);
+ static void add_xml_rpc_integer_param(std::string &xml, const int32_t &param);
+ 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)