Merge pull request #4849

1ebcd7b9 MMS (Multisig Messaging System): Initial version (rbrunner7)
This commit is contained in:
Riccardo Spagni 2018-12-20 12:33:59 +02:00
commit c8fc06c7b6
No known key found for this signature in database
GPG Key ID: 55432DF31CCD4FCD
12 changed files with 3877 additions and 127 deletions

View File

@ -58,6 +58,7 @@
#include "include_base_utils.h" #include "include_base_utils.h"
#include "file_io_utils.h" #include "file_io_utils.h"
#include "wipeable_string.h" #include "wipeable_string.h"
#include "misc_os_dependent.h"
using namespace epee; using namespace epee;
#include "crypto/crypto.h" #include "crypto/crypto.h"
@ -1025,4 +1026,15 @@ std::string get_nix_version_display_string()
#endif #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);
}
} }

View File

@ -242,4 +242,6 @@ namespace tools
#endif #endif
void closefrom(int fd); void closefrom(int fd);
std::string get_human_readable_timestamp(uint64_t ts);
} }

File diff suppressed because it is too large Load Diff

View File

@ -154,7 +154,7 @@ namespace cryptonote
bool show_incoming_transfers(const std::vector<std::string> &args); bool show_incoming_transfers(const std::vector<std::string> &args);
bool show_payments(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 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 transfer(const std::vector<std::string> &args);
bool locked_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); 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 payment_id(const std::vector<std::string> &args);
bool print_fee_info(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(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(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 finalize_multisig(const std::vector<std::string> &args);
bool exchange_multisig_keys(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(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(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 accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs);
bool sign_multisig(const std::vector<std::string>& args); 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(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 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 print_ring(const std::vector<std::string>& args);
bool set_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); bool save_known_rings(const std::vector<std::string>& args);
@ -386,5 +394,40 @@ namespace cryptonote
bool m_auto_refresh_refreshing; bool m_auto_refresh_refreshing;
std::atomic<bool> m_in_manual_refresh; std::atomic<bool> m_in_manual_refresh;
uint32_t m_current_subaddress_account; 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);
}; };
} }

View File

@ -34,7 +34,10 @@ set(wallet_sources
wallet2.cpp wallet2.cpp
wallet_args.cpp wallet_args.cpp
ringdb.cpp ringdb.cpp
node_rpc_proxy.cpp) node_rpc_proxy.cpp
message_store.cpp
message_transporter.cpp
)
set(wallet_private_headers set(wallet_private_headers
wallet2.h wallet2.h
@ -44,7 +47,9 @@ set(wallet_private_headers
wallet_rpc_server_commands_defs.h wallet_rpc_server_commands_defs.h
wallet_rpc_server_error_codes.h wallet_rpc_server_error_codes.h
ringdb.h ringdb.h
node_rpc_proxy.h) node_rpc_proxy.h
message_store.h
message_transporter.h)
monero_private_headers(wallet monero_private_headers(wallet
${wallet_private_headers}) ${wallet_private_headers})

1445
src/wallet/message_store.cpp Normal file

File diff suppressed because it is too large Load Diff

420
src/wallet/message_store.h Normal file
View File

@ -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;
}
}
}

View File

@ -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>";
}
}

View File

@ -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);
};
}

View File

@ -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" , "" }; 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; keys_file = file_path;
wallet_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 {//provided wallet file name
keys_file += ".keys"; keys_file += ".keys";
} }
mms_file = file_path + ".mms";
} }
uint64_t calculate_fee(uint64_t fee_per_kb, size_t bytes, uint64_t fee_multiplier) 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); 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); boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir);
wallet->set_ring_database(ringdb_path.string()); wallet->set_ring_database(ringdb_path.string());
wallet->get_message_store().set_options(vm);
wallet->device_name(device_name); wallet->device_name(device_name);
wallet->device_derivation_path(device_derivation_path); 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_connected(false),
m_light_wallet_balance(0), m_light_wallet_balance(0),
m_light_wallet_unlocked_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_key_device_type(hw::device::device_type::SOFTWARE),
m_ring_history_saved(false), m_ring_history_saved(false),
m_ringdb(), 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.stagenet);
command_line::add_arg(desc_params, opts.shared_ringdb_dir); command_line::add_arg(desc_params, opts.shared_ringdb_dir);
command_line::add_arg(desc_params, opts.kdf_rounds); 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);
command_line::add_arg(desc_params, opts.hw_device_derivation_path); command_line::add_arg(desc_params, opts.hw_device_derivation_path);
command_line::add_arg(desc_params, opts.tx_notify); 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); value2.SetUint(m_subaddress_lookahead_minor);
json.AddMember("subaddress_lookahead_minor", value2, json.GetAllocator()); 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); value2.SetUint(1);
json.AddMember("encrypted_secret_keys", value2, json.GetAllocator()); 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()); value.SetString(m_device_derivation_path.c_str(), m_device_derivation_path.size());
json.AddMember("device_derivation_path", value, json.GetAllocator()); 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 // Serialize the JSON object
rapidjson::StringBuffer buffer; rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(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_ignore_fractional_outputs = true;
m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR;
m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR;
m_original_keys_available = false;
m_device_name = ""; m_device_name = "";
m_device_derivation_path = ""; m_device_derivation_path = "";
m_key_device_type = hw::device::device_type::SOFTWARE; 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()); 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; 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 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; m_key_device_type = hw::device::device_type::SOFTWARE;
setup_keys(password); 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); create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file);
setup_new_blockchain(); setup_new_blockchain();
@ -3846,6 +3900,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip
m_multisig = false; m_multisig = false;
m_multisig_threshold = 0; m_multisig_threshold = 0;
m_multisig_signers.clear(); m_multisig_signers.clear();
m_original_keys_available = false;
m_key_device_type = hw::device::device_type::SOFTWARE; m_key_device_type = hw::device::device_type::SOFTWARE;
setup_keys(password); setup_keys(password);
@ -3934,6 +3989,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
m_multisig = false; m_multisig = false;
m_multisig_threshold = 0; m_multisig_threshold = 0;
m_multisig_signers.clear(); m_multisig_signers.clear();
m_original_keys_available = false;
m_key_device_type = hw::device::device_type::SOFTWARE; m_key_device_type = hw::device::device_type::SOFTWARE;
setup_keys(password); setup_keys(password);
@ -3974,6 +4030,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
m_multisig = false; m_multisig = false;
m_multisig_threshold = 0; m_multisig_threshold = 0;
m_multisig_signers.clear(); m_multisig_signers.clear();
m_original_keys_available = false;
m_key_device_type = hw::device::device_type::SOFTWARE; m_key_device_type = hw::device::device_type::SOFTWARE;
setup_keys(password); setup_keys(password);
@ -4015,6 +4072,7 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
m_multisig = false; m_multisig = false;
m_multisig_threshold = 0; m_multisig_threshold = 0;
m_multisig_signers.clear(); m_multisig_signers.clear();
m_original_keys_available = false;
setup_keys(password); setup_keys(password);
m_device_name = device_name; m_device_name = device_name;
@ -4127,6 +4185,15 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
m_multisig_derivations = derivations; 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(); clear();
MINFO("Creating view key..."); 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) void wallet2::wallet_exists(const std::string& file_path, bool& keys_file_exists, bool& wallet_file_exists)
{ {
std::string keys_file, wallet_file; std::string keys_file, wallet_file, mms_file;
do_prepare_file_names(file_path, keys_file, wallet_file); do_prepare_file_names(file_path, keys_file, wallet_file, mms_file);
boost::system::error_code ignore; boost::system::error_code ignore;
keys_file_exists = boost::filesystem::exists(keys_file, 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) 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; 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"); 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() 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_file = m_wallet_file;
const std::string old_keys_file = m_keys_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_address_file = m_wallet_file + ".address.txt";
const std::string old_mms_file = m_mms_file;
// save keys to the new 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 // 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) { if (!r) {
LOG_ERROR("error removing file: " << old_address_file); 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 { } else {
// save to new file // save to new file
#ifdef WIN32 #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); 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); 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 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); 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() wallet_device_callback * wallet2::get_device_callback()
{ {
if (!m_device_callback){ if (!m_device_callback){

View File

@ -61,6 +61,7 @@
#include "wallet_errors.h" #include "wallet_errors.h"
#include "common/password.h" #include "common/password.h"
#include "node_rpc_proxy.h" #include "node_rpc_proxy.h"
#include "message_store.h"
#undef MONERO_DEFAULT_LOG_CATEGORY #undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" #define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2"
@ -674,7 +675,7 @@ namespace tools
bool init(std::string daemon_address = "http://localhost:8080", 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); 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; } i_wallet2_callback* callback() const { return m_callback; }
void callback(i_wallet2_callback* callback) { m_callback = 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 unblackball_output(const std::pair<uint64_t, uint64_t> &output);
bool is_output_blackballed(const std::pair<uint64_t, uint64_t> &output) const; 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 lock_keys_file();
bool unlock_keys_file(); bool unlock_keys_file();
bool is_keys_file_locked() const; bool is_keys_file_locked() const;
@ -1320,6 +1326,7 @@ namespace tools
std::string m_daemon_address; std::string m_daemon_address;
std::string m_wallet_file; std::string m_wallet_file;
std::string m_keys_file; std::string m_keys_file;
std::string m_mms_file;
epee::net_utils::http::http_simple_client m_http_client; epee::net_utils::http::http_simple_client m_http_client;
hashchain m_blockchain; hashchain m_blockchain;
std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs;
@ -1420,6 +1427,11 @@ namespace tools
uint64_t m_last_block_reward; uint64_t m_last_block_reward;
std::unique_ptr<tools::file_locker> m_keys_file_locker; 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; crypto::chacha_key m_cache_key;
boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh; boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh;

View File

@ -821,6 +821,31 @@ namespace tools
std::string m_wallet_file; 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) #if !defined(_MSC_VER)