aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/CMakeLists.txt19
-rw-r--r--src/wallet/api/wallet.cpp60
-rw-r--r--src/wallet/api/wallet.h4
-rw-r--r--src/wallet/api/wallet2_api.h17
-rw-r--r--src/wallet/api/wallet_manager.cpp2
-rw-r--r--src/wallet/message_store.cpp1445
-rw-r--r--src/wallet/message_store.h421
-rw-r--r--src/wallet/message_transporter.cpp317
-rw-r--r--src/wallet/message_transporter.h113
-rw-r--r--src/wallet/node_rpc_proxy.cpp1
-rw-r--r--src/wallet/ringdb.cpp1
-rw-r--r--src/wallet/wallet2.cpp1400
-rw-r--r--src/wallet/wallet2.h142
-rw-r--r--src/wallet/wallet_args.cpp8
-rw-r--r--src/wallet/wallet_errors.h42
-rw-r--r--src/wallet/wallet_rpc_server.cpp424
-rw-r--r--src/wallet/wallet_rpc_server.h4
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h114
-rw-r--r--src/wallet/wallet_rpc_server_error_codes.h1
19 files changed, 4166 insertions, 369 deletions
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt
index be10b9f62..2991f75c5 100644
--- a/src/wallet/CMakeLists.txt
+++ b/src/wallet/CMakeLists.txt
@@ -34,7 +34,10 @@ set(wallet_sources
wallet2.cpp
wallet_args.cpp
ringdb.cpp
- node_rpc_proxy.cpp)
+ node_rpc_proxy.cpp
+ message_store.cpp
+ message_transporter.cpp
+)
set(wallet_private_headers
wallet2.h
@@ -44,7 +47,9 @@ set(wallet_private_headers
wallet_rpc_server_commands_defs.h
wallet_rpc_server_error_codes.h
ringdb.h
- node_rpc_proxy.h)
+ node_rpc_proxy.h
+ message_store.h
+ message_transporter.h)
monero_private_headers(wallet
${wallet_private_headers})
@@ -57,6 +62,7 @@ target_link_libraries(wallet
common
cryptonote_core
mnemonics
+ device_trezor
${LMDB_LIBRARY}
${Boost_CHRONO_LIBRARY}
${Boost_SERIALIZATION_LIBRARY}
@@ -119,7 +125,8 @@ if (BUILD_GUI_DEPS)
ringct
ringct_basic
checkpoints
- version)
+ version
+ device_trezor)
foreach(lib ${libs_to_merge})
list(APPEND objlibs $<TARGET_OBJECTS:obj_${lib}>) # matches naming convention in src/CMakeLists.txt
@@ -132,6 +139,12 @@ if (BUILD_GUI_DEPS)
endif()
install(TARGETS wallet_merged
ARCHIVE DESTINATION ${lib_folder})
+
+ install(FILES ${TREZOR_DEP_LIBS}
+ DESTINATION ${lib_folder})
+ file(WRITE "trezor_link_flags.txt" ${TREZOR_DEP_LINKER})
+ install(FILES "trezor_link_flags.txt"
+ DESTINATION ${lib_folder})
endif()
add_subdirectory(api)
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 1b4370c36..935a8d51c 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -381,6 +381,7 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds)
, m_synchronized(false)
, m_rebuildWalletCache(false)
, m_is_connected(false)
+ , m_refreshShouldRescan(false)
{
m_wallet.reset(new tools::wallet2(static_cast<cryptonote::network_type>(nettype), kdf_rounds, true));
m_history.reset(new TransactionHistoryImpl(this));
@@ -506,7 +507,7 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas
auto key_images = m_wallet->export_key_images();
uint64_t spent = 0;
uint64_t unspent = 0;
- view_wallet->import_key_images(key_images,spent,unspent,false);
+ view_wallet->import_key_images(key_images.second, key_images.first, spent, unspent, false);
clearStatus();
} catch (const std::exception &e) {
LOG_ERROR("Error creating view only wallet: " << e.what());
@@ -941,6 +942,12 @@ uint64_t WalletImpl::approximateBlockChainHeight() const
{
return m_wallet->get_approximate_blockchain_height();
}
+
+uint64_t WalletImpl::estimateBlockChainHeight() const
+{
+ return m_wallet->estimate_blockchain_height();
+}
+
uint64_t WalletImpl::daemonBlockChainHeight() const
{
if(m_wallet->light_wallet()) {
@@ -1011,6 +1018,20 @@ void WalletImpl::refreshAsync()
m_refreshCV.notify_one();
}
+bool WalletImpl::rescanBlockchain()
+{
+ clearStatus();
+ m_refreshShouldRescan = true;
+ doRefresh();
+ return status() == Status_Ok;
+}
+
+void WalletImpl::rescanBlockchainAsync()
+{
+ m_refreshShouldRescan = true;
+ refreshAsync();
+}
+
void WalletImpl::setAutoRefreshInterval(int millis)
{
if (millis > MAX_REFRESH_INTERVAL_MILLIS) {
@@ -1036,8 +1057,8 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file
// Check tx data and construct confirmation message
std::string extra_message;
- if (!transaction->m_unsigned_tx_set.transfers.empty())
- extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.size()).str();
+ if (!transaction->m_unsigned_tx_set.transfers.second.empty())
+ extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.second.size()).str();
transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message);
setStatus(transaction->status(), transaction->errorString());
@@ -1134,7 +1155,7 @@ std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addre
}
catch (const std::exception &e)
{
- LOG_ERROR("Error getting subaddress label: ") << e.what();
+ LOG_ERROR("Error getting subaddress label: " << e.what());
setStatusError(string(tr("Failed to get subaddress label: ")) + e.what());
return "";
}
@@ -1147,7 +1168,7 @@ void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex
}
catch (const std::exception &e)
{
- LOG_ERROR("Error setting subaddress label: ") << e.what();
+ LOG_ERROR("Error setting subaddress label: " << e.what());
setStatusError(string(tr("Failed to set subaddress label: ")) + e.what());
}
}
@@ -1164,7 +1185,7 @@ string WalletImpl::getMultisigInfo() const {
clearStatus();
return m_wallet->get_multisig_info();
} catch (const exception& e) {
- LOG_ERROR("Error on generating multisig info: ") << e.what();
+ LOG_ERROR("Error on generating multisig info: " << e.what());
setStatusError(string(tr("Failed to get multisig info: ")) + e.what());
}
@@ -1181,7 +1202,7 @@ string WalletImpl::makeMultisig(const vector<string>& info, uint32_t threshold)
return m_wallet->make_multisig(epee::wipeable_string(m_password), info, threshold);
} catch (const exception& e) {
- LOG_ERROR("Error on making multisig wallet: ") << e.what();
+ LOG_ERROR("Error on making multisig wallet: " << e.what());
setStatusError(string(tr("Failed to make multisig: ")) + e.what());
}
@@ -1195,7 +1216,7 @@ std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &inf
return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info);
} catch (const exception& e) {
- LOG_ERROR("Error on exchanging multisig keys: ") << e.what();
+ LOG_ERROR("Error on exchanging multisig keys: " << e.what());
setStatusError(string(tr("Failed to make multisig: ")) + e.what());
}
@@ -1213,7 +1234,7 @@ bool WalletImpl::finalizeMultisig(const vector<string>& extraMultisigInfo) {
setStatusError(tr("Failed to finalize multisig wallet creation"));
} catch (const exception& e) {
- LOG_ERROR("Error on finalizing multisig wallet creation: ") << e.what();
+ LOG_ERROR("Error on finalizing multisig wallet creation: " << e.what());
setStatusError(string(tr("Failed to finalize multisig wallet creation: ")) + e.what());
}
@@ -1229,7 +1250,7 @@ bool WalletImpl::exportMultisigImages(string& images) {
images = epee::string_tools::buff_to_hex_nodelimer(blob);
return true;
} catch (const exception& e) {
- LOG_ERROR("Error on exporting multisig images: ") << e.what();
+ LOG_ERROR("Error on exporting multisig images: " << e.what());
setStatusError(string(tr("Failed to export multisig images: ")) + e.what());
}
@@ -1257,7 +1278,7 @@ size_t WalletImpl::importMultisigImages(const vector<string>& images) {
return m_wallet->import_multisig(blobs);
} catch (const exception& e) {
- LOG_ERROR("Error on importing multisig images: ") << e.what();
+ LOG_ERROR("Error on importing multisig images: " << e.what());
setStatusError(string(tr("Failed to import multisig images: ")) + e.what());
}
@@ -1271,7 +1292,7 @@ bool WalletImpl::hasMultisigPartialKeyImages() const {
return m_wallet->has_multisig_partial_key_images();
} catch (const exception& e) {
- LOG_ERROR("Error on checking for partial multisig key images: ") << e.what();
+ LOG_ERROR("Error on checking for partial multisig key images: " << e.what());
setStatusError(string(tr("Failed to check for partial multisig key images: ")) + e.what());
}
@@ -1299,7 +1320,7 @@ PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signDat
return ptx;
} catch (exception& e) {
- LOG_ERROR("Error on restoring multisig transaction: ") << e.what();
+ LOG_ERROR("Error on restoring multisig transaction: " << e.what());
setStatusError(string(tr("Failed to restore multisig transaction: ")) + e.what());
}
@@ -1386,9 +1407,11 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
if (amount) {
vector<cryptonote::tx_destination_entry> dsts;
cryptonote::tx_destination_entry de;
+ de.original = dst_addr;
de.addr = info.address;
de.amount = *amount;
de.is_subaddress = info.is_subaddress;
+ de.is_integrated = info.has_payment_id;
dsts.push_back(de);
transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */,
adjusted_priority,
@@ -1984,6 +2007,7 @@ void WalletImpl::refreshThreadFunc()
LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired...");
LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled);
LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << status());
+ LOG_PRINT_L3(__FUNCTION__ << ": m_refreshShouldRescan: " << m_refreshShouldRescan);
if (m_refreshEnabled) {
LOG_PRINT_L3(__FUNCTION__ << ": refreshing...");
doRefresh();
@@ -1994,12 +2018,16 @@ void WalletImpl::refreshThreadFunc()
void WalletImpl::doRefresh()
{
+ bool rescan = m_refreshShouldRescan.exchange(false);
// synchronizing async and sync refresh calls
boost::lock_guard<boost::mutex> guarg(m_refreshMutex2);
- try {
+ do try {
+ LOG_PRINT_L3(__FUNCTION__ << ": doRefresh, rescan = "<<rescan);
// Syncing daemon and refreshing wallet simultaneously is very resource intensive.
// Disable refresh if wallet is disconnected or daemon isn't synced.
if (m_wallet->light_wallet() || daemonSynced()) {
+ if(rescan)
+ m_wallet->rescan_blockchain(false);
m_wallet->refresh(trustedDaemon());
if (!m_synchronized) {
m_synchronized = true;
@@ -2016,7 +2044,9 @@ void WalletImpl::doRefresh()
}
} catch (const std::exception &e) {
setStatusError(e.what());
- }
+ break;
+ }while(!rescan && (rescan=m_refreshShouldRescan.exchange(false))); // repeat if not rescanned and rescan was requested
+
if (m_wallet2Callback->getListener()) {
m_wallet2Callback->getListener()->refreshed();
}
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
index 6d343888b..55240d64f 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -109,11 +109,14 @@ public:
uint64_t unlockedBalance(uint32_t accountIndex = 0) const override;
uint64_t blockChainHeight() const override;
uint64_t approximateBlockChainHeight() const override;
+ uint64_t estimateBlockChainHeight() const override;
uint64_t daemonBlockChainHeight() const override;
uint64_t daemonBlockChainTargetHeight() const override;
bool synchronized() const override;
bool refresh() override;
void refreshAsync() override;
+ bool rescanBlockchain() override;
+ void rescanBlockchainAsync() override;
void setAutoRefreshInterval(int millis) override;
int autoRefreshInterval() const override;
void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) override;
@@ -232,6 +235,7 @@ private:
std::atomic<bool> m_refreshEnabled;
std::atomic<bool> m_refreshThreadDone;
std::atomic<int> m_refreshIntervalMillis;
+ std::atomic<bool> m_refreshShouldRescan;
// synchronizing refresh loop;
boost::mutex m_refreshMutex;
diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h
index ec1a84877..5c301974f 100644
--- a/src/wallet/api/wallet2_api.h
+++ b/src/wallet/api/wallet2_api.h
@@ -575,6 +575,12 @@ struct Wallet
virtual uint64_t approximateBlockChainHeight() const = 0;
/**
+ * @brief estimateBlockChainHeight - returns estimate blockchain height. More accurate than approximateBlockChainHeight,
+ * uses daemon height and falls back to calculation from date/time
+ * @return
+ **/
+ virtual uint64_t estimateBlockChainHeight() const = 0;
+ /**
* @brief daemonBlockChainHeight - returns daemon blockchain height
* @return 0 - in case error communicating with the daemon.
* status() will return Status_Error and errorString() will return verbose error description
@@ -644,6 +650,17 @@ struct Wallet
virtual void refreshAsync() = 0;
/**
+ * @brief rescanBlockchain - rescans the wallet, updating transactions from daemon
+ * @return - true if refreshed successfully;
+ */
+ virtual bool rescanBlockchain() = 0;
+
+ /**
+ * @brief rescanBlockchainAsync - rescans wallet asynchronously, starting from genesys
+ */
+ virtual void rescanBlockchainAsync() = 0;
+
+ /**
* @brief setAutoRefreshInterval - setup interval for automatic refresh.
* @param seconds - interval in millis. if zero or less than zero - automatic refresh disabled;
*/
diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp
index 5b262f1b7..89fe01c0d 100644
--- a/src/wallet/api/wallet_manager.cpp
+++ b/src/wallet/api/wallet_manager.cpp
@@ -127,6 +127,8 @@ Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path,
WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds);
if(restoreHeight > 0){
wallet->setRefreshFromBlockHeight(restoreHeight);
+ } else {
+ wallet->setRefreshFromBlockHeight(wallet->estimateBlockChainHeight());
}
auto lookahead = tools::parse_subaddress_lookahead(subaddressLookahead);
if (lookahead)
diff --git a/src/wallet/message_store.cpp b/src/wallet/message_store.cpp
new file mode 100644
index 000000000..7381005c1
--- /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 " + std::to_string(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 " + std::to_string(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: " + std::to_string(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 " + std::to_string(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 " + std::to_string((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 " + std::to_string(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 " + std::to_string(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..637bd29a1
--- /dev/null
+++ b/src/wallet/message_store.h
@@ -0,0 +1,421 @@
+// 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));
+ me = false;
+ 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/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp
index 346c052b5..605531e59 100644
--- a/src/wallet/node_rpc_proxy.cpp
+++ b/src/wallet/node_rpc_proxy.cpp
@@ -28,7 +28,6 @@
#include "node_rpc_proxy.h"
#include "rpc/core_rpc_server_commands_defs.h"
-#include "common/json_util.h"
#include "storages/http_abstract_invoke.h"
using namespace epee;
diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp
index f562d6c06..b69022af4 100644
--- a/src/wallet/ringdb.cpp
+++ b/src/wallet/ringdb.cpp
@@ -30,6 +30,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/filesystem.hpp>
+#include "common/util.h"
#include "misc_log_ex.h"
#include "misc_language.h"
#include "wallet_errors.h"
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 6b3a8533e..7a7d0ad64 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -67,10 +67,14 @@ using namespace epee;
#include "common/json_util.h"
#include "memwipe.h"
#include "common/base58.h"
+#include "common/combinator.h"
#include "common/dns_utils.h"
#include "common/notify.h"
+#include "common/perf_timer.h"
#include "ringct/rctSigs.h"
#include "ringdb.h"
+#include "device/device_cold.hpp"
+#include "device_trezor/device_trezor.hpp"
extern "C"
{
@@ -110,11 +114,11 @@ using namespace cryptonote;
#define SUBADDRESS_LOOKAHEAD_MAJOR 50
#define SUBADDRESS_LOOKAHEAD_MINOR 200
-#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002"
+#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\003"
#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001"
-#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003"
+#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\004"
#define SEGREGATION_FORK_HEIGHT 99999999
#define TESTNET_SEGREGATION_FORK_HEIGHT 99999999
@@ -123,6 +127,8 @@ using namespace cryptonote;
#define FIRST_REFRESH_GRANULARITY 1024
+#define GAMMA_PICK_HALF_WINDOW 5
+
static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1";
static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1";
@@ -172,6 +178,51 @@ namespace
return public_keys;
}
+
+ bool keys_intersect(const std::unordered_set<crypto::public_key>& s1, const std::unordered_set<crypto::public_key>& s2)
+ {
+ if (s1.empty() || s2.empty())
+ return false;
+
+ for (const auto& e: s1)
+ {
+ if (s2.find(e) != s2.end())
+ return true;
+ }
+
+ return false;
+ }
+
+ void add_reason(std::string &reasons, const char *reason)
+ {
+ if (!reasons.empty())
+ reasons += ", ";
+ reasons += reason;
+ }
+
+ std::string get_text_reason(const cryptonote::COMMAND_RPC_SEND_RAW_TX::response &res)
+ {
+ std::string reason;
+ if (res.low_mixin)
+ add_reason(reason, "bad ring size");
+ if (res.double_spend)
+ add_reason(reason, "double spend");
+ if (res.invalid_input)
+ add_reason(reason, "invalid input");
+ if (res.invalid_output)
+ add_reason(reason, "invalid output");
+ if (res.too_big)
+ add_reason(reason, "too big");
+ if (res.overspend)
+ add_reason(reason, "overspend");
+ if (res.fee_too_low)
+ add_reason(reason, "fee too low");
+ if (res.not_rct)
+ add_reason(reason, "tx is not ringct");
+ if (res.not_relayed)
+ add_reason(reason, "tx was not relayed");
+ return reason;
+ }
}
namespace
@@ -202,10 +253,11 @@ struct options {
};
const command_line::arg_descriptor<uint64_t> kdf_rounds = {"kdf-rounds", tools::wallet2::tr("Number of rounds for the key derivation function"), 1};
const command_line::arg_descriptor<std::string> hw_device = {"hw-device", tools::wallet2::tr("HW device to use"), ""};
+ const command_line::arg_descriptor<std::string> hw_device_derivation_path = {"hw-device-deriv-path", tools::wallet2::tr("HW device wallet derivation path (e.g., SLIP-10)"), ""};
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;
@@ -217,6 +269,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)
@@ -254,6 +307,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
auto daemon_host = command_line::get_arg(vm, opts.daemon_host);
auto daemon_port = command_line::get_arg(vm, opts.daemon_port);
auto device_name = command_line::get_arg(vm, opts.hw_device);
+ auto device_derivation_path = command_line::get_arg(vm, opts.hw_device_derivation_path);
THROW_WALLET_EXCEPTION_IF(!daemon_address.empty() && !daemon_host.empty() && 0 != daemon_port,
tools::error::wallet_internal_error, tools::wallet2::tr("can't specify daemon host or port more than once"));
@@ -308,7 +362,9 @@ 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);
try
{
@@ -558,19 +614,6 @@ std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> generate_f
return {nullptr, tools::password_container{}};
}
-static void throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method)
-{
- // no error
- if (!status)
- return;
-
- // empty string -> not connection
- THROW_WALLET_EXCEPTION_IF(status->empty(), tools::error::no_connection_to_daemon, method);
-
- THROW_WALLET_EXCEPTION_IF(*status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, method);
- THROW_WALLET_EXCEPTION_IF(*status != CORE_RPC_STATUS_OK, tools::error::wallet_generic_rpc_error, method, *status);
-}
-
std::string strjoin(const std::vector<size_t> &V, const char *sep)
{
std::stringstream ss;
@@ -766,6 +809,48 @@ uint32_t get_subaddress_clamped_sum(uint32_t idx, uint32_t extra)
return idx + extra;
}
+static void setup_shim(hw::wallet_shim * shim, tools::wallet2 * wallet)
+{
+ shim->get_tx_pub_key_from_received_outs = boost::bind(&tools::wallet2::get_tx_pub_key_from_received_outs, wallet, _1);
+}
+
+bool get_pruned_tx(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry &entry, cryptonote::transaction &tx, crypto::hash &tx_hash)
+{
+ cryptonote::blobdata bd;
+
+ // easy case if we have the whole tx
+ if (!entry.as_hex.empty() || (!entry.prunable_as_hex.empty() && !entry.pruned_as_hex.empty()))
+ {
+ CHECK_AND_ASSERT_MES(epee::string_tools::parse_hexstr_to_binbuff(entry.as_hex.empty() ? entry.pruned_as_hex + entry.prunable_as_hex : entry.as_hex, bd), false, "Failed to parse tx data");
+ CHECK_AND_ASSERT_MES(cryptonote::parse_and_validate_tx_from_blob(bd, tx), false, "Invalid tx data");
+ tx_hash = cryptonote::get_transaction_hash(tx);
+ // if the hash was given, check it matches
+ CHECK_AND_ASSERT_MES(entry.tx_hash.empty() || epee::string_tools::pod_to_hex(tx_hash) == entry.tx_hash, false,
+ "Response claims a different hash than the data yields");
+ return true;
+ }
+ // case of a pruned tx with its prunable data hash
+ if (!entry.pruned_as_hex.empty() && !entry.prunable_hash.empty())
+ {
+ crypto::hash ph;
+ CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(entry.prunable_hash, ph), false, "Failed to parse prunable hash");
+ CHECK_AND_ASSERT_MES(epee::string_tools::parse_hexstr_to_binbuff(entry.pruned_as_hex, bd), false, "Failed to parse pruned data");
+ CHECK_AND_ASSERT_MES(parse_and_validate_tx_base_from_blob(bd, tx), false, "Invalid base tx data");
+ // only v2 txes can calculate their txid after pruned
+ if (bd[0] > 1)
+ {
+ tx_hash = cryptonote::get_pruned_transaction_hash(tx, ph);
+ }
+ else
+ {
+ // for v1, we trust the dameon
+ CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(entry.tx_hash, tx_hash), false, "Failed to parse tx hash");
+ }
+ return true;
+ }
+ return false;
+}
+
//-----------------------------------------------------------------
} //namespace
@@ -805,12 +890,36 @@ wallet_keys_unlocker::~wallet_keys_unlocker()
{
if (!locked)
return;
- w.encrypt_keys(key);
+ try { w.encrypt_keys(key); }
+ catch (...)
+ {
+ MERROR("Failed to re-encrypt wallet keys");
+ // do not propagate through dtor, we'd crash
+ }
+}
+
+void wallet_device_callback::on_button_request()
+{
+ if (wallet)
+ wallet->on_button_request();
+}
+
+void wallet_device_callback::on_pin_request(epee::wipeable_string & pin)
+{
+ if (wallet)
+ wallet->on_pin_request(pin);
+}
+
+void wallet_device_callback::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase)
+{
+ if (wallet)
+ wallet->on_passphrase_request(on_device, passphrase);
}
wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_multisig_rescan_info(NULL),
m_multisig_rescan_k(NULL),
+ m_upper_transaction_weight_limit(0),
m_run(true),
m_callback(0),
m_trusted_daemon(false),
@@ -840,10 +949,15 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_key_reuse_mitigation2(true),
m_segregation_height(0),
m_ignore_fractional_outputs(true),
+ m_track_uses(false),
m_is_initialized(false),
m_kdf_rounds(kdf_rounds),
is_old_file_format(false),
+ m_watch_only(false),
+ m_multisig(false),
+ m_multisig_threshold(0),
m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex),
+ m_account_public_address{crypto::null_pkey, crypto::null_pkey},
m_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR),
m_subaddress_lookahead_minor(SUBADDRESS_LOOKAHEAD_MINOR),
m_light_wallet(false),
@@ -852,12 +966,16 @@ 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(),
m_last_block_reward(0),
m_encrypt_keys_after_refresh(boost::none),
- m_unattended(unattended)
+ m_unattended(unattended),
+ m_devices_registered(false),
+ m_device_last_key_image_sync(0)
{
}
@@ -880,6 +998,11 @@ std::string wallet2::device_name_option(const boost::program_options::variables_
return command_line::get_arg(vm, options().hw_device);
}
+std::string wallet2::device_derivation_path_option(const boost::program_options::variables_map &vm)
+{
+ return command_line::get_arg(vm, options().hw_device_derivation_path);
+}
+
void wallet2::init_options(boost::program_options::options_description& desc_params)
{
const options opts{};
@@ -895,7 +1018,9 @@ 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);
}
@@ -915,7 +1040,7 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file(
return {nullptr, password_container{}};
}
auto wallet = make_basic(vm, unattended, opts, password_prompter);
- if (wallet)
+ if (wallet && !wallet_file.empty())
{
wallet->load(wallet_file, pwd->password());
}
@@ -1054,17 +1179,20 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl
bool wallet2::reconnect_device()
{
bool r = true;
- hw::device &hwdev = hw::get_device(m_device_name);
+ hw::device &hwdev = lookup_device(m_device_name);
hwdev.set_name(m_device_name);
+ hwdev.set_network_type(m_nettype);
+ hwdev.set_derivation_path(m_device_derivation_path);
+ hwdev.set_callback(get_device_callback());
r = hwdev.init();
if (!r){
- LOG_PRINT_L2("Could not init device");
+ MERROR("Could not init device");
return false;
}
r = hwdev.connect();
if (!r){
- LOG_PRINT_L2("Could not connect to the device");
+ MERROR("Could not connect to the device");
return false;
}
@@ -1281,6 +1409,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &
{
case rct::RCTTypeSimple:
case rct::RCTTypeBulletproof:
+ case rct::RCTTypeBulletproof2:
return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask, hwdev);
case rct::RCTTypeFull:
return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask, hwdev);
@@ -1296,7 +1425,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs)
+void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs, bool pool)
{
THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index");
@@ -1307,7 +1436,7 @@ void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::publi
CRITICAL_REGION_LOCAL(password_lock);
if (!m_encrypt_keys_after_refresh)
{
- boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password("output received");
+ boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? "output found in pool" : "output received");
THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming monero"));
THROW_WALLET_EXCEPTION_IF(!verify_password(*pwd), error::password_needed, tr("Invalid password: password is needed to compute key image for incoming monero"));
decrypt_keys(*pwd);
@@ -1341,14 +1470,12 @@ void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::publi
//----------------------------------------------------------------------------------------------------
void wallet2::cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const
{
- const cryptonote::account_keys& keys = m_account.get_keys();
-
if(!parse_tx_extra(tx.extra, tx_cache_data.tx_extra_fields))
{
// Extra may only be partially parsed, it's OK if tx_extra_fields contains public key
LOG_PRINT_L0("Transaction extra has unsupported format: " << txid);
- tx_cache_data.tx_extra_fields.clear();
- return;
+ if (tx_cache_data.tx_extra_fields.empty())
+ return;
}
// Don't try to extract tx public key if tx has no ouputs
@@ -1378,8 +1505,9 @@ void wallet2::cache_tx_data(const cryptonote::transaction& tx, const crypto::has
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data)
+void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache)
{
+ PERF_TIMER(process_new_transaction);
// In this function, tx (probably) only contains the base information
// (that is, the prunable stuff may or may not be included)
if (!miner_tx && !pool)
@@ -1512,7 +1640,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
if (tx_scan_info[i].received)
{
hwdev.conceal_derivation(tx_scan_info[i].received->derivation, tx_pub_key, additional_tx_pub_keys.data, derivation, additional_derivations);
- scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs);
+ scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs, pool);
}
}
}
@@ -1535,7 +1663,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
if (tx_scan_info[i].received)
{
hwdev.conceal_derivation(tx_scan_info[i].received->derivation, tx_pub_key, additional_tx_pub_keys.data, derivation, additional_derivations);
- scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs);
+ scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs, pool);
}
}
}
@@ -1551,7 +1679,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
boost::unique_lock<hw::device> hwdev_lock (hwdev);
hwdev.set_mode(hw::device::NONE);
hwdev.conceal_derivation(tx_scan_info[i].received->derivation, tx_pub_key, additional_tx_pub_keys.data, derivation, additional_derivations);
- scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs);
+ scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs, pool);
}
}
}
@@ -1591,6 +1719,25 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_txid = txid;
td.m_key_image = tx_scan_info[o].ki;
td.m_key_image_known = !m_watch_only && !m_multisig;
+ if (!td.m_key_image_known)
+ {
+ // we might have cold signed, and have a mapping to key images
+ std::unordered_map<crypto::public_key, crypto::key_image>::const_iterator i = m_cold_key_images.find(tx_scan_info[o].in_ephemeral.pub);
+ if (i != m_cold_key_images.end())
+ {
+ td.m_key_image = i->second;
+ td.m_key_image_known = true;
+ }
+ }
+ if (m_watch_only)
+ {
+ // for view wallets, that flag means "we want to request it"
+ td.m_key_image_request = true;
+ }
+ else
+ {
+ td.m_key_image_request = false;
+ }
td.m_key_image_partial = m_multisig;
td.m_amount = amount;
td.m_pk_index = pk_index - 1;
@@ -1612,9 +1759,11 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_rct = false;
}
set_unspent(m_transfers.size()-1);
- if (!m_multisig && !m_watch_only)
+ if (td.m_key_image_known)
m_key_images[td.m_key_image] = m_transfers.size()-1;
m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1;
+ if (output_tracker_cache)
+ (*output_tracker_cache)[std::make_pair(tx.vout[o].amount, td.m_global_output_index)] = m_transfers.size() - 1;
if (m_multisig)
{
THROW_WALLET_EXCEPTION_IF(!m_multisig_rescan_k && m_multisig_rescan_info,
@@ -1680,6 +1829,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_mask = rct::identity();
td.m_rct = false;
}
+ if (output_tracker_cache)
+ (*output_tracker_cache)[std::make_pair(tx.vout[o].amount, td.m_global_output_index)] = kit->second;
if (m_multisig)
{
THROW_WALLET_EXCEPTION_IF(!m_multisig_rescan_k && m_multisig_rescan_info,
@@ -1711,11 +1862,12 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
{
if(in.type() != typeid(cryptonote::txin_to_key))
continue;
- auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image);
+ const cryptonote::txin_to_key &in_to_key = boost::get<cryptonote::txin_to_key>(in);
+ auto it = m_key_images.find(in_to_key.k_image);
if(it != m_key_images.end())
{
transfer_details& td = m_transfers[it->second];
- uint64_t amount = boost::get<cryptonote::txin_to_key>(in).amount;
+ uint64_t amount = in_to_key.amount;
if (amount > 0)
{
if(amount != td.amount())
@@ -1746,6 +1898,34 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index);
}
}
+
+ if (!pool && m_track_uses)
+ {
+ PERF_TIMER(track_uses);
+ const uint64_t amount = in_to_key.amount;
+ std::vector<uint64_t> offsets = cryptonote::relative_output_offsets_to_absolute(in_to_key.key_offsets);
+ if (output_tracker_cache)
+ {
+ for (uint64_t offset: offsets)
+ {
+ const std::map<std::pair<uint64_t, uint64_t>, size_t>::const_iterator i = output_tracker_cache->find(std::make_pair(amount, offset));
+ if (i != output_tracker_cache->end())
+ {
+ size_t idx = i->second;
+ THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "Output tracker cache index out of range");
+ m_transfers[idx].m_uses.push_back(std::make_pair(height, txid));
+ }
+ }
+ }
+ else for (transfer_details &td: m_transfers)
+ {
+ if (amount != in_to_key.amount)
+ continue;
+ for (uint64_t offset: offsets)
+ if (offset == td.m_global_output_index)
+ td.m_uses.push_back(std::make_pair(height, txid));
+ }
+ }
}
uint64_t fee = miner_tx ? 0 : tx.version == 1 ? tx_money_spent_in_ins - get_outs_money_amount(tx) : tx.rct_signatures.txnFee;
@@ -1913,6 +2093,7 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans
entry.first->second.m_subaddr_indices = subaddr_indices;
}
+ entry.first->second.m_rings.clear();
for (const auto &in: tx.vin)
{
if (in.type() != typeid(cryptonote::txin_to_key))
@@ -1927,7 +2108,7 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans
add_rings(tx);
}
//----------------------------------------------------------------------------------------------------
-void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset)
+void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache)
{
THROW_WALLET_EXCEPTION_IF(bche.txs.size() + 1 != parsed_block.o_indices.indices.size(), error::wallet_internal_error,
"block transactions=" + std::to_string(bche.txs.size()) +
@@ -1940,7 +2121,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
{
TIME_MEASURE_START(miner_tx_handle_time);
if (m_refresh_type != RefreshNoCoinbase)
- process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, parsed_block.o_indices.indices[0].indices, height, b.timestamp, true, false, false, tx_cache_data[tx_cache_data_offset]);
+ process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, parsed_block.o_indices.indices[0].indices, height, b.timestamp, true, false, false, tx_cache_data[tx_cache_data_offset], output_tracker_cache);
++tx_cache_data_offset;
TIME_MEASURE_FINISH(miner_tx_handle_time);
@@ -1949,7 +2130,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
THROW_WALLET_EXCEPTION_IF(bche.txs.size() != parsed_block.txes.size(), error::wallet_internal_error, "Wrong amount of transactions for block");
for (size_t idx = 0; idx < b.tx_hashes.size(); ++idx)
{
- process_new_transaction(b.tx_hashes[idx], parsed_block.txes[idx], parsed_block.o_indices.indices[idx+1].indices, height, b.timestamp, false, false, false, tx_cache_data[tx_cache_data_offset++]);
+ process_new_transaction(b.tx_hashes[idx], parsed_block.txes[idx], parsed_block.o_indices.indices[idx+1].indices, height, b.timestamp, false, false, false, tx_cache_data[tx_cache_data_offset++], output_tracker_cache);
}
TIME_MEASURE_FINISH(txs_handle_time);
m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx);
@@ -2019,7 +2200,7 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height,
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblocks.bin");
THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblocks.bin");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, res.status);
+ THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, get_rpc_status(res.status));
THROW_WALLET_EXCEPTION_IF(res.blocks.size() != res.output_indices.size(), error::wallet_internal_error,
"mismatched blocks (" + boost::lexical_cast<std::string>(res.blocks.size()) + ") and output_indices (" +
boost::lexical_cast<std::string>(res.output_indices.size()) + ") sizes from daemon");
@@ -2041,13 +2222,13 @@ void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height,
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gethashes.bin");
THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gethashes.bin");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_hashes_error, res.status);
+ THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_hashes_error, get_rpc_status(res.status));
blocks_start_height = res.start_height;
hashes = std::move(res.m_block_ids);
}
//----------------------------------------------------------------------------------------------------
-void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<parsed_block> &parsed_blocks, uint64_t& blocks_added)
+void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<parsed_block> &parsed_blocks, uint64_t& blocks_added, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache)
{
size_t current_index = start_height;
blocks_added = 0;
@@ -2086,7 +2267,6 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
const cryptonote::account_keys &keys = m_account.get_keys();
auto gender = [&](wallet2::is_out_data &iod) {
- boost::unique_lock<hw::device> hwdev_lock(hwdev);
if (!hwdev.generate_key_derivation(iod.pkey, keys.m_view_secret_key, iod.derivation))
{
MWARNING("Failed to generate key derivation from tx pubkey, skipping");
@@ -2095,12 +2275,16 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
}
};
- for (auto &slot: tx_cache_data)
+ for (size_t i = 0; i < tx_cache_data.size(); ++i)
{
- for (auto &iod: slot.primary)
- tpool.submit(&waiter, [&gender, &iod]() { gender(iod); }, true);
- for (auto &iod: slot.additional)
- tpool.submit(&waiter, [&gender, &iod]() { gender(iod); }, true);
+ tpool.submit(&waiter, [&hwdev, &gender, &tx_cache_data, i]() {
+ auto &slot = tx_cache_data[i];
+ boost::unique_lock<hw::device> hwdev_lock(hwdev);
+ for (auto &iod: slot.primary)
+ gender(iod);
+ for (auto &iod: slot.additional)
+ gender(iod);
+ }, true);
}
waiter.wait(&tpool);
@@ -2132,7 +2316,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
{
THROW_WALLET_EXCEPTION_IF(txidx >= tx_cache_data.size(), error::wallet_internal_error, "txidx out of range");
const size_t n_vouts = m_refresh_type == RefreshType::RefreshOptimizeCoinbase ? 1 : parsed_blocks[i].block.miner_tx.vout.size();
- tpool.submit(&waiter, [&, i, txidx](){ geniod(parsed_blocks[i].block.miner_tx, n_vouts, txidx); }, true);
+ tpool.submit(&waiter, [&, i, n_vouts, txidx](){ geniod(parsed_blocks[i].block.miner_tx, n_vouts, txidx); }, true);
}
++txidx;
for (size_t j = 0; j < parsed_blocks[i].txes.size(); ++j)
@@ -2154,7 +2338,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
if(current_index >= m_blockchain.size())
{
- process_new_blockchain_entry(bl, blocks[i], parsed_blocks[i], bl_id, current_index, tx_cache_data, tx_cache_data_offset);
+ process_new_blockchain_entry(bl, blocks[i], parsed_blocks[i], bl_id, current_index, tx_cache_data, tx_cache_data_offset, output_tracker_cache);
++blocks_added;
}
else if(bl_id != m_blockchain[current_index])
@@ -2166,7 +2350,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
string_tools::pod_to_hex(m_blockchain[current_index]));
detach_blockchain(current_index);
- process_new_blockchain_entry(bl, blocks[i], parsed_blocks[i], bl_id, current_index, tx_cache_data, tx_cache_data_offset);
+ process_new_blockchain_entry(bl, blocks[i], parsed_blocks[i], bl_id, current_index, tx_cache_data, tx_cache_data_offset, output_tracker_cache);
}
else
{
@@ -2200,11 +2384,10 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks
THROW_WALLET_EXCEPTION_IF(prev_blocks.size() != prev_parsed_blocks.size(), error::wallet_internal_error, "size mismatch");
// prepend the last 3 blocks, should be enough to guard against a block or two's reorg
- std::vector<parsed_block>::const_reverse_iterator i = prev_parsed_blocks.rbegin();
- for (size_t n = 0; n < std::min((size_t)3, prev_parsed_blocks.size()); ++n)
+ auto s = std::next(prev_parsed_blocks.rbegin(), std::min((size_t)3, prev_parsed_blocks.size())).base();
+ for (; s != prev_parsed_blocks.end(); ++s)
{
- short_chain_history.push_front(i->hash);
- ++i;
+ short_chain_history.push_front(s->hash);
}
// pull the new blocks
@@ -2284,7 +2467,7 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe
//----------------------------------------------------------------------------------------------------
void wallet2::update_pool_state(bool refreshed)
{
- MDEBUG("update_pool_state start");
+ MTRACE("update_pool_state start");
auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() {
if (m_encrypt_keys_after_refresh)
@@ -2303,7 +2486,7 @@ void wallet2::update_pool_state(bool refreshed)
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_transaction_pool_hashes.bin");
THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_transaction_pool_hashes.bin");
THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error);
- MDEBUG("update_pool_state got pool");
+ MTRACE("update_pool_state got pool");
// remove any pending tx that's not in the pool
std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin();
@@ -2360,7 +2543,7 @@ void wallet2::update_pool_state(bool refreshed)
}
}
}
- MDEBUG("update_pool_state done first loop");
+ MTRACE("update_pool_state done first loop");
// remove pool txes to us that aren't in the pool anymore
// but only if we just refreshed, so that the tx can go in
@@ -2369,7 +2552,7 @@ void wallet2::update_pool_state(bool refreshed)
if (refreshed)
remove_obsolete_pool_txs(res.tx_hashes);
- MDEBUG("update_pool_state done second loop");
+ MTRACE("update_pool_state done second loop");
// gather txids of new pool txes to us
std::vector<std::pair<crypto::hash, bool>> txids;
@@ -2443,7 +2626,7 @@ void wallet2::update_pool_state(bool refreshed)
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first));
MDEBUG("asking for " << txids.size() << " transactions");
req.decode_as_json = false;
- req.prune = false;
+ req.prune = true;
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
@@ -2458,11 +2641,10 @@ void wallet2::update_pool_state(bool refreshed)
{
cryptonote::transaction tx;
cryptonote::blobdata bd;
- crypto::hash tx_hash, tx_prefix_hash;
- if (epee::string_tools::parse_hexstr_to_binbuff(tx_entry.as_hex, bd))
+ crypto::hash tx_hash;
+
+ if (get_pruned_tx(tx_entry, tx, tx_hash))
{
- if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash))
- {
const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(),
[tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; });
if (i != txids.end())
@@ -2479,11 +2661,6 @@ void wallet2::update_pool_state(bool refreshed)
{
MERROR("Got txid " << tx_hash << " which we did not ask for");
}
- }
- else
- {
- LOG_PRINT_L0("failed to validate transaction from daemon");
- }
}
else
{
@@ -2503,10 +2680,10 @@ void wallet2::update_pool_state(bool refreshed)
}
else
{
- LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << res.status);
+ LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << get_rpc_status(res.status));
}
}
- MDEBUG("update_pool_state end");
+ MTRACE("update_pool_state end");
}
//----------------------------------------------------------------------------------------------------
void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force)
@@ -2599,7 +2776,18 @@ bool wallet2::delete_address_book_row(std::size_t row_id) {
}
//----------------------------------------------------------------------------------------------------
-void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money)
+std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> wallet2::create_output_tracker_cache() const
+{
+ std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> cache{new std::map<std::pair<uint64_t, uint64_t>, size_t>()};
+ for (size_t i = 0; i < m_transfers.size(); ++i)
+ {
+ const transfer_details &td = m_transfers[i];
+ (*cache)[std::make_pair(td.is_rct() ? 0 : td.amount(), td.m_global_output_index)] = i;
+ }
+ return cache;
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool)
{
if(m_light_wallet) {
@@ -2645,6 +2833,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
std::vector<cryptonote::block_complete_entry> blocks;
std::vector<parsed_block> parsed_blocks;
bool refreshed = false;
+ std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> output_tracker_cache;
// pull the first set of blocks
get_short_chain_history(short_chain_history, (m_first_refresh_done || trusted_daemon) ? 1 : FIRST_REFRESH_GRANULARITY);
@@ -2679,13 +2868,16 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
bool first = true;
while(m_run.load(std::memory_order_relaxed))
{
+ uint64_t next_blocks_start_height;
+ std::vector<cryptonote::block_complete_entry> next_blocks;
+ std::vector<parsed_block> next_parsed_blocks;
+ bool error;
try
{
// pull the next set of blocks while we're processing the current one
- uint64_t next_blocks_start_height;
- std::vector<cryptonote::block_complete_entry> next_blocks;
- std::vector<parsed_block> next_parsed_blocks;
- bool error = false;
+ error = false;
+ next_blocks.clear();
+ next_parsed_blocks.clear();
added_blocks = 0;
if (!first && blocks.empty())
{
@@ -2698,7 +2890,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
{
try
{
- process_parsed_blocks(blocks_start_height, blocks, parsed_blocks, added_blocks);
+ process_parsed_blocks(blocks_start_height, blocks, parsed_blocks, added_blocks, output_tracker_cache.get());
}
catch (const tools::error::out_of_hashchain_bounds_error&)
{
@@ -2714,7 +2906,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
short_chain_history.clear();
get_short_chain_history(short_chain_history);
fast_refresh(stop_height, blocks_start_height, short_chain_history, true);
- THROW_WALLET_EXCEPTION_IF(m_blockchain.size() != stop_height, error::wallet_internal_error, "Unexpected hashchain size");
+ THROW_WALLET_EXCEPTION_IF((m_blockchain.size() == stop_height || (m_blockchain.size() == 1 && stop_height == 0) ? false : true), error::wallet_internal_error, "Unexpected hashchain size");
THROW_WALLET_EXCEPTION_IF(m_blockchain.offset() != 0, error::wallet_internal_error, "Unexpected hashchain offset");
for (const auto &h: tip)
m_blockchain.push_back(h);
@@ -2723,6 +2915,11 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
start_height = stop_height;
throw std::runtime_error(""); // loop again
}
+ catch (const std::exception &e)
+ {
+ MERROR("Error parsing blocks: " << e.what());
+ error = true;
+ }
blocks_fetched += added_blocks;
}
waiter.wait(&tpool);
@@ -2741,6 +2938,11 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
throw std::runtime_error("proxy exception in refresh thread");
}
+ // if we've got at least 10 blocks to refresh, assume we're starting
+ // a long refresh, and setup a tracking output cache if we need to
+ if (m_track_uses && !output_tracker_cache && next_blocks.size() >= 10)
+ output_tracker_cache = create_output_tracker_cache();
+
// switch to the new blocks from the daemon
blocks_start_height = next_blocks_start_height;
blocks = std::move(next_blocks);
@@ -2775,7 +2977,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
try
{
// If stop() is called we don't need to check pending transactions
- if(m_run.load(std::memory_order_relaxed))
+ if (check_pool && m_run.load(std::memory_order_relaxed))
update_pool_state(refreshed);
}
catch (...)
@@ -2835,10 +3037,11 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t>
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response res = AUTO_VAL_INIT(res);
req.amounts.push_back(0);
req.from_height = 0;
- req.cumulative = true;
+ req.cumulative = false;
req.binary = true;
+ req.compress = true;
m_daemon_rpc_mutex.lock();
- bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req, res, m_http_client, rpc_timeout);
+ bool r = net_utils::invoke_http_bin("/get_output_distribution.bin", req, res, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
if (!r)
{
@@ -2865,8 +3068,10 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t>
MWARNING("Failed to request output distribution: results are not for amount 0");
return false;
}
- start_height = res.distributions[0].start_height;
- distribution = std::move(res.distributions[0].distribution);
+ for (size_t i = 1; i < res.distributions[0].data.distribution.size(); ++i)
+ res.distributions[0].data.distribution[i] += res.distributions[0].data.distribution[i-1];
+ start_height = res.distributions[0].data.start_height;
+ distribution = std::move(res.distributions[0].data.distribution);
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -2938,6 +3143,7 @@ bool wallet2::deinit()
{
m_is_initialized=false;
unlock_keys_file();
+ m_account.deinit();
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -2959,6 +3165,7 @@ bool wallet2::clear()
m_subaddresses.clear();
m_subaddress_labels.clear();
m_multisig_rounds_passed = 0;
+ m_device_last_key_image_sync = 0;
return true;
}
@@ -3108,18 +3315,39 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
value2.SetInt(m_ignore_fractional_outputs ? 1 : 0);
json.AddMember("ignore_fractional_outputs", value2, json.GetAllocator());
+ value2.SetInt(m_track_uses ? 1 : 0);
+ json.AddMember("track_uses", value2, json.GetAllocator());
+
value2.SetUint(m_subaddress_lookahead_major);
json.AddMember("subaddress_lookahead_major", value2, json.GetAllocator());
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());
value.SetString(m_device_name.c_str(), m_device_name.size());
json.AddMember("device_name", value, json.GetAllocator());
+ 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);
@@ -3127,20 +3355,28 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
account_data = buffer.GetString();
// Encrypt the entire JSON object.
- crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
std::string cipher;
cipher.resize(account_data.size());
keys_file_data.iv = crypto::rand<crypto::chacha_iv>();
crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]);
keys_file_data.account_data = cipher;
- unlock_keys_file();
+ std::string tmp_file_name = keys_file_name + ".new";
std::string buf;
r = ::serialization::dump_binary(keys_file_data, buf);
- r = r && epee::file_io_utils::save_string_to_file(keys_file_name, buf); //and never touch wallet_keys_file again, only read
- CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << keys_file_name);
+ r = r && epee::file_io_utils::save_string_to_file(tmp_file_name, buf);
+ CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name);
+
+ unlock_keys_file();
+ std::error_code e = tools::replace_file(tmp_file_name, keys_file_name);
lock_keys_file();
+ if (e) {
+ boost::filesystem::remove(tmp_file_name);
+ LOG_ERROR("failed to update wallet keys file " << keys_file_name);
+ return false;
+ }
+
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -3228,9 +3464,12 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_key_reuse_mitigation2 = true;
m_segregation_height = 0;
m_ignore_fractional_outputs = true;
+ m_track_uses = false;
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;
encrypted_secret_keys = false;
}
@@ -3378,6 +3617,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_segregation_height = field_segregation_height;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_fractional_outputs, int, Int, false, true);
m_ignore_fractional_outputs = field_ignore_fractional_outputs;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false);
+ m_track_uses = field_track_uses;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_major, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MAJOR);
m_subaddress_lookahead_major = field_subaddress_lookahead_major;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_minor, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MINOR);
@@ -3398,6 +3639,38 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_device_name = m_key_device_type == hw::device::device_type::LEDGER ? "Ledger" : "default";
}
}
+
+ 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
{
@@ -3407,13 +3680,22 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
r = epee::serialization::load_t_from_binary(m_account, account_data);
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
- if (m_key_device_type == hw::device::device_type::LEDGER) {
+ if (m_key_device_type == hw::device::device_type::LEDGER || m_key_device_type == hw::device::device_type::TREZOR) {
LOG_PRINT_L0("Account on device. Initing device...");
- hw::device &hwdev = hw::get_device(m_device_name);
- hwdev.set_name(m_device_name);
- hwdev.init();
- hwdev.connect();
+ hw::device &hwdev = lookup_device(m_device_name);
+ THROW_WALLET_EXCEPTION_IF(!hwdev.set_name(m_device_name), error::wallet_internal_error, "Could not set device name " + m_device_name);
+ hwdev.set_network_type(m_nettype);
+ hwdev.set_derivation_path(m_device_derivation_path);
+ hwdev.set_callback(get_device_callback());
+ THROW_WALLET_EXCEPTION_IF(!hwdev.init(), error::wallet_internal_error, "Could not initialize the device " + m_device_name);
+ THROW_WALLET_EXCEPTION_IF(!hwdev.connect(), error::wallet_internal_error, "Could not connect to the device " + m_device_name);
m_account.set_device(hwdev);
+
+ account_public_address device_account_public_address;
+ THROW_WALLET_EXCEPTION_IF(!hwdev.get_public_address(device_account_public_address), error::wallet_internal_error, "Cannot get a device address");
+ THROW_WALLET_EXCEPTION_IF(device_account_public_address != m_account.get_keys().m_account_address, error::wallet_internal_error, "Device wallet does not match wallet address. "
+ "Device address: " + cryptonote::get_account_address_as_str(m_nettype, false, device_account_public_address) +
+ ", wallet address: " + m_account.get_public_address_str(m_nettype));
LOG_PRINT_L0("Device inited...");
} else if (key_on_device()) {
THROW_WALLET_EXCEPTION(error::wallet_internal_error, "hardware device not supported");
@@ -3444,7 +3726,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
const cryptonote::account_keys& keys = m_account.get_keys();
hw::device &hwdev = m_account.get_device();
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
- if(!m_watch_only && !m_multisig)
+ if(!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD)
r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
@@ -3468,7 +3750,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password)
{
// this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded).
unlock_keys_file();
- bool r = verify_password(m_keys_file, password, m_watch_only || m_multisig, m_account.get_device(), m_kdf_rounds);
+ bool r = verify_password(m_keys_file, password, m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig, m_account.get_device(), m_kdf_rounds);
lock_keys_file();
return r;
}
@@ -3715,6 +3997,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();
@@ -3752,6 +4038,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);
@@ -3840,6 +4127,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);
@@ -3880,6 +4168,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);
@@ -3908,8 +4197,11 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file);
}
- auto &hwdev = hw::get_device(device_name);
+ auto &hwdev = lookup_device(device_name);
hwdev.set_name(device_name);
+ hwdev.set_network_type(m_nettype);
+ hwdev.set_derivation_path(m_device_derivation_path);
+ hwdev.set_callback(get_device_callback());
m_account.create_from_device(hwdev);
m_key_device_type = m_account.get_device().get_type();
@@ -3918,6 +4210,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;
@@ -4030,6 +4323,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...");
@@ -4257,8 +4559,25 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
return make_multisig(password, secret_keys, public_keys, threshold);
}
-bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unordered_set<crypto::public_key> pkeys, std::vector<crypto::public_key> signers)
+bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers)
{
+ bool ready;
+ uint32_t threshold, total;
+ if (!multisig(&ready, &threshold, &total))
+ {
+ MERROR("This is not a multisig wallet");
+ return false;
+ }
+ if (ready)
+ {
+ MERROR("This multisig wallet is already finalized");
+ return false;
+ }
+ if (threshold + 1 != total)
+ {
+ MERROR("finalize_multisig should only be used for N-1/N wallets, use exchange_multisig_keys instead");
+ return false;
+ }
exchange_multisig_keys(password, pkeys, signers);
return true;
}
@@ -4463,8 +4782,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);
@@ -4518,7 +4837,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;
}
//----------------------------------------------------------------------------------------------------
@@ -4711,6 +5030,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()
@@ -4816,6 +5137,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
@@ -4845,6 +5167,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
@@ -4870,6 +5200,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
@@ -5039,7 +5377,7 @@ void wallet2::rescan_spent()
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "is_key_image_spent");
THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent");
- THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::is_key_image_spent_error, daemon_resp.status);
+ THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::is_key_image_spent_error, get_rpc_status(daemon_resp.status));
THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != n_outputs, error::wallet_internal_error,
"daemon returned wrong response for is_key_image_spent, wrong amounts count = " +
std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(n_outputs));
@@ -5071,11 +5409,31 @@ void wallet2::rescan_spent()
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::rescan_blockchain(bool refresh)
+void wallet2::rescan_blockchain(bool hard, bool refresh)
{
- clear();
-
- setup_new_blockchain();
+ if(hard)
+ {
+ clear();
+ setup_new_blockchain();
+ }
+ else
+ {
+ m_blockchain.clear();
+ m_transfers.clear();
+ m_key_images.clear();
+ m_pub_keys.clear();
+ m_unconfirmed_txs.clear();
+ m_payments.clear();
+ m_confirmed_txs.clear();
+ m_unconfirmed_payments.clear();
+ m_scanned_pool_txs[0].clear();
+ m_scanned_pool_txs[1].clear();
+
+ cryptonote::block b;
+ generate_genesis(b);
+ m_blockchain.push_back(get_block_hash(b));
+ m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx);
+ }
if (refresh)
this->refresh(false);
@@ -5352,7 +5710,7 @@ void wallet2::commit_tx(pending_tx& ptx)
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_raw_tx");
// MyMonero and OpenMonero use different status strings
- THROW_WALLET_EXCEPTION_IF(ores.status != "OK" && ores.status != "success" , error::tx_rejected, ptx.tx, ores.status, ores.error);
+ THROW_WALLET_EXCEPTION_IF(ores.status != "OK" && ores.status != "success" , error::tx_rejected, ptx.tx, get_rpc_status(ores.status), ores.error);
}
else
{
@@ -5366,7 +5724,7 @@ void wallet2::commit_tx(pending_tx& ptx)
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction");
THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction");
- THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status, daemon_send_resp.reason);
+ THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp.status), get_text_reason(daemon_send_resp));
// sanity checks
for (size_t idx: ptx.selected_transfers)
{
@@ -5442,7 +5800,7 @@ std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) c
txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device()));
}
- txs.transfers = m_transfers;
+ txs.transfers = export_outputs();
// save as binary
std::ostringstream oss;
boost::archive::portable_binary_oarchive ar(oss);
@@ -5563,15 +5921,16 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin
LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << sd.sources[0].outputs.size());
signed_txes.ptx.push_back(pending_tx());
tools::wallet2::pending_tx &ptx = signed_txes.ptx.back();
- rct::RangeProofType range_proof_type = rct::RangeProofBorromean;
+ rct::RCTConfig rct_config = { rct::RangeProofBorromean, 0 };
if (sd.use_bulletproofs)
{
- range_proof_type = rct::RangeProofPaddedBulletproof;
+ rct_config.range_proof_type = rct::RangeProofPaddedBulletproof;
+ rct_config.bp_version = use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1;
}
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
rct::multisig_out msout;
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, range_proof_type, m_multisig ? &msout : NULL);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, rct_config, m_multisig ? &msout : NULL);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
// we don't test tx size, because we don't know the current limit, due to not having a blockchain,
// and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway,
@@ -5610,6 +5969,65 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin
ptx.construction_data = sd;
txs.push_back(ptx);
+
+ // add tx keys only to ptx
+ txs.back().tx_key = tx_key;
+ txs.back().additional_tx_keys = additional_tx_keys;
+ }
+
+ // add key image mapping for these txes
+ const account_keys &keys = get_account().get_keys();
+ hw::device &hwdev = m_account.get_device();
+ for (size_t n = 0; n < exported_txs.txes.size(); ++n)
+ {
+ const cryptonote::transaction &tx = signed_txes.ptx[n].tx;
+
+ crypto::key_derivation derivation;
+ std::vector<crypto::key_derivation> additional_derivations;
+
+ // compute public keys from out secret keys
+ crypto::public_key tx_pub_key;
+ crypto::secret_key_to_public_key(txs[n].tx_key, tx_pub_key);
+ std::vector<crypto::public_key> additional_tx_pub_keys;
+ for (const crypto::secret_key &skey: txs[n].additional_tx_keys)
+ {
+ additional_tx_pub_keys.resize(additional_tx_pub_keys.size() + 1);
+ crypto::secret_key_to_public_key(skey, additional_tx_pub_keys.back());
+ }
+
+ // compute derivations
+ hwdev.set_mode(hw::device::TRANSACTION_PARSE);
+ if (!hwdev.generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation))
+ {
+ MWARNING("Failed to generate key derivation from tx pubkey in " << cryptonote::get_transaction_hash(tx) << ", skipping");
+ static_assert(sizeof(derivation) == sizeof(rct::key), "Mismatched sizes of key_derivation and rct::key");
+ memcpy(&derivation, rct::identity().bytes, sizeof(derivation));
+ }
+ for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
+ {
+ additional_derivations.push_back({});
+ if (!hwdev.generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()))
+ {
+ MWARNING("Failed to generate key derivation from additional tx pubkey in " << cryptonote::get_transaction_hash(tx) << ", skipping");
+ memcpy(&additional_derivations.back(), rct::identity().bytes, sizeof(crypto::key_derivation));
+ }
+ }
+
+ for (size_t i = 0; i < tx.vout.size(); ++i)
+ {
+ if (tx.vout[i].target.type() != typeid(cryptonote::txout_to_key))
+ continue;
+ const cryptonote::txout_to_key &out = boost::get<cryptonote::txout_to_key>(tx.vout[i].target);
+ // if this output is back to this wallet, we can calculate its key image already
+ if (!is_out_to_acc_precomp(m_subaddresses, out.key, derivation, additional_derivations, i, hwdev))
+ continue;
+ crypto::key_image ki;
+ cryptonote::keypair in_ephemeral;
+ if (generate_key_image_helper(keys, m_subaddresses, out.key, tx_pub_key, additional_tx_pub_keys, i, in_ephemeral, ki, hwdev))
+ signed_txes.tx_key_images[out.key] = ki;
+ else
+ MERROR("Failed to calculate key image");
+ }
}
// add key images
@@ -5771,22 +6189,12 @@ bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<too
}
// import key images
- if (signed_txs.key_images.size() > m_transfers.size())
- {
- LOG_PRINT_L1("More key images returned that we know outputs for");
- return false;
- }
- for (size_t i = 0; i < signed_txs.key_images.size(); ++i)
- {
- transfer_details &td = m_transfers[i];
- if (td.m_key_image_known && !td.m_key_image_partial && td.m_key_image != signed_txs.key_images[i])
- LOG_PRINT_L0("WARNING: imported key image differs from previously known key image at index " << i << ": trusting imported one");
- td.m_key_image = signed_txs.key_images[i];
- m_key_images[m_transfers[i].m_key_image] = i;
- td.m_key_image_known = true;
- td.m_key_image_partial = false;
- m_pub_keys[m_transfers[i].get_public_key()] = i;
- }
+ bool r = import_key_images(signed_txs.key_images);
+ if (!r) return false;
+
+ // remember key images for this tx, for when we get those txes from the blockchain
+ for (const auto &e: signed_txs.tx_key_images)
+ m_cold_key_images.insert(e);
ptx = signed_txs.ptx;
@@ -5984,15 +6392,13 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
cryptonote::transaction tx;
rct::multisig_out msout = ptx.multisig_sigs.front().msout;
auto sources = sd.sources;
- rct::RangeProofType range_proof_type = rct::RangeProofBorromean;
+ rct::RCTConfig rct_config = { rct::RangeProofBorromean, 0 };
if (sd.use_bulletproofs)
{
- range_proof_type = rct::RangeProofBulletproof;
- for (const rct::Bulletproof &proof: ptx.tx.rct_signatures.p.bulletproofs)
- if (proof.V.size() > 1)
- range_proof_type = rct::RangeProofPaddedBulletproof;
+ rct_config.range_proof_type = rct::RangeProofPaddedBulletproof;
+ rct_config.bp_version = use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1;
}
- bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, range_proof_type, &msout, false);
+ bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, rct_config, &msout, false);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(get_transaction_prefix_hash (tx) != get_transaction_prefix_hash(ptx.tx),
@@ -6005,7 +6411,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
for (auto &sig: ptx.multisig_sigs)
{
- if (sig.ignore != local_signer)
+ if (sig.ignore.find(local_signer) == sig.ignore.end())
{
ptx.tx.rct_signatures = sig.sigs;
@@ -6039,7 +6445,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
bool found = false;
for (const auto &sig: ptx.multisig_sigs)
{
- if (sig.ignore != local_signer && exported_txs.m_signers.find(sig.ignore) == exported_txs.m_signers.end())
+ if (sig.ignore.find(local_signer) == sig.ignore.end() && !keys_intersect(sig.ignore, exported_txs.m_signers))
{
THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final");
ptx.tx.rct_signatures = sig.sigs;
@@ -6270,7 +6676,7 @@ uint32_t wallet2::adjust_priority(uint32_t priority)
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblockheadersrange");
THROW_WALLET_EXCEPTION_IF(getbh_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblockheadersrange");
- THROW_WALLET_EXCEPTION_IF(getbh_res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, getbh_res.status);
+ THROW_WALLET_EXCEPTION_IF(getbh_res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, get_rpc_status(getbh_res.status));
if (getbh_res.headers.size() != N)
{
MERROR("Bad blockheaders size");
@@ -6336,6 +6742,19 @@ crypto::chacha_key wallet2::get_ringdb_key()
return *m_ringdb_key;
}
+void wallet2::register_devices(){
+ hw::trezor::register_all();
+}
+
+hw::device& wallet2::lookup_device(const std::string & device_descriptor){
+ if (!m_devices_registered){
+ m_devices_registered = true;
+ register_devices();
+ }
+
+ return hw::get_device(device_descriptor);
+}
+
bool wallet2::add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx)
{
if (!m_ringdb)
@@ -6429,11 +6848,12 @@ bool wallet2::find_and_save_rings(bool force)
MDEBUG("Found " << std::to_string(txs_hashes.size()) << " transactions");
// get those transactions from the daemon
+ auto it = txs_hashes.begin();
static const size_t SLICE_SIZE = 200;
for (size_t slice = 0; slice < txs_hashes.size(); slice += SLICE_SIZE)
{
req.decode_as_json = false;
- req.prune = false;
+ req.prune = true;
req.txs_hashes.clear();
size_t ntxes = slice + SLICE_SIZE > txs_hashes.size() ? txs_hashes.size() - slice : SLICE_SIZE;
for (size_t s = slice; s < slice + ntxes; ++s)
@@ -6452,19 +6872,15 @@ bool wallet2::find_and_save_rings(bool force)
MDEBUG("Scanning " << res.txs.size() << " transactions");
THROW_WALLET_EXCEPTION_IF(slice + res.txs.size() > txs_hashes.size(), error::wallet_internal_error, "Unexpected tx array size");
- auto it = req.txs_hashes.begin();
for (size_t i = 0; i < res.txs.size(); ++i, ++it)
{
const auto &tx_info = res.txs[i];
- THROW_WALLET_EXCEPTION_IF(tx_info.tx_hash != epee::string_tools::pod_to_hex(txs_hashes[slice + i]), error::wallet_internal_error, "Wrong txid received");
- THROW_WALLET_EXCEPTION_IF(tx_info.tx_hash != *it, error::wallet_internal_error, "Wrong txid received");
- cryptonote::blobdata bd;
- THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(tx_info.as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr");
- cryptonote::transaction tx;
- crypto::hash tx_hash, tx_prefix_hash;
- THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob");
- THROW_WALLET_EXCEPTION_IF(epee::string_tools::pod_to_hex(tx_hash) != tx_info.tx_hash, error::wallet_internal_error, "txid mismatch");
- THROW_WALLET_EXCEPTION_IF(!add_rings(get_ringdb_key(), tx), error::wallet_internal_error, "Failed to save ring");
+ cryptonote::transaction tx;
+ crypto::hash tx_hash;
+ THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(tx_info, tx, tx_hash), error::wallet_internal_error,
+ "Failed to get transaction from daemon");
+ THROW_WALLET_EXCEPTION_IF(!(tx_hash == *it), error::wallet_internal_error, "Wrong txid received");
+ THROW_WALLET_EXCEPTION_IF(!add_rings(get_ringdb_key(), tx), error::wallet_internal_error, "Failed to save ring");
}
}
@@ -6683,15 +7099,21 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
uint64_t rct_start_height;
std::vector<uint64_t> rct_offsets;
bool has_rct = false;
+ uint64_t max_rct_index = 0;
for (size_t idx: selected_transfers)
if (m_transfers[idx].is_rct())
- { has_rct = true; break; }
+ {
+ has_rct = true;
+ max_rct_index = std::max(max_rct_index, m_transfers[idx].m_global_output_index);
+ }
const bool has_rct_distribution = has_rct && get_rct_distribution(rct_start_height, rct_offsets);
if (has_rct_distribution)
{
// check we're clear enough of rct start, to avoid corner cases below
THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE,
error::get_output_distribution, "Not enough rct outputs");
+ THROW_WALLET_EXCEPTION_IF(rct_offsets.back() <= max_rct_index,
+ error::get_output_distribution, "Daemon reports suspicious number of rct outputs");
}
// get histogram for the amounts we need
@@ -6713,7 +7135,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected");
THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram");
- THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status);
+ THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, get_rpc_status(resp_t.status));
}
// if we want to segregate fake outs pre or post fork, get distribution
@@ -6736,7 +7158,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected");
THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_distribution");
- THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_output_distribution, resp_t.status);
+ THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_output_distribution, get_rpc_status(resp_t.status));
// check we got all data
for(size_t idx: selected_transfers)
@@ -6747,13 +7169,13 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
{
if (d.amount == amount)
{
- THROW_WALLET_EXCEPTION_IF(d.start_height > segregation_fork_height, error::get_output_distribution, "Distribution start_height too high");
- THROW_WALLET_EXCEPTION_IF(segregation_fork_height - d.start_height >= d.distribution.size(), error::get_output_distribution, "Distribution size too small");
- THROW_WALLET_EXCEPTION_IF(segregation_fork_height - RECENT_OUTPUT_BLOCKS - d.start_height >= d.distribution.size(), error::get_output_distribution, "Distribution size too small");
+ THROW_WALLET_EXCEPTION_IF(d.data.start_height > segregation_fork_height, error::get_output_distribution, "Distribution start_height too high");
+ THROW_WALLET_EXCEPTION_IF(segregation_fork_height - d.data.start_height >= d.data.distribution.size(), error::get_output_distribution, "Distribution size too small");
+ THROW_WALLET_EXCEPTION_IF(segregation_fork_height - RECENT_OUTPUT_BLOCKS - d.data.start_height >= d.data.distribution.size(), error::get_output_distribution, "Distribution size too small");
THROW_WALLET_EXCEPTION_IF(segregation_fork_height <= RECENT_OUTPUT_BLOCKS, error::wallet_internal_error, "Fork height too low");
- THROW_WALLET_EXCEPTION_IF(segregation_fork_height - RECENT_OUTPUT_BLOCKS < d.start_height, error::get_output_distribution, "Bad start height");
- uint64_t till_fork = d.distribution[segregation_fork_height - d.start_height];
- uint64_t recent = till_fork - d.distribution[segregation_fork_height - RECENT_OUTPUT_BLOCKS - d.start_height];
+ THROW_WALLET_EXCEPTION_IF(segregation_fork_height - RECENT_OUTPUT_BLOCKS < d.data.start_height, error::get_output_distribution, "Bad start height");
+ uint64_t till_fork = d.data.distribution[segregation_fork_height - d.data.start_height];
+ uint64_t recent = till_fork - d.data.distribution[segregation_fork_height - RECENT_OUTPUT_BLOCKS - d.data.start_height];
segregation_limit[amount] = std::make_pair(till_fork, recent);
found = true;
break;
@@ -6782,23 +7204,49 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
//static const double shape = m_testnet ? 17.02 : 17.28;
static const double scale = 1/1.61;
std::gamma_distribution<double> gamma(shape, scale);
+ THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, error::wallet_internal_error, "Bad offset calculation");
+ uint64_t last_usable_block = rct_offsets.size() - 1;
auto pick_gamma = [&]()
{
double x = gamma(engine);
x = exp(x);
uint64_t block_offset = x / DIFFICULTY_TARGET_V2; // this assumes constant target over the whole rct range
- if (block_offset >= rct_offsets.size() - 1)
+ if (block_offset > last_usable_block - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE)
return std::numeric_limits<uint64_t>::max(); // bad pick
- block_offset = rct_offsets.size() - 2 - block_offset;
- THROW_WALLET_EXCEPTION_IF(block_offset >= rct_offsets.size() - 1, error::wallet_internal_error, "Bad offset calculation");
- THROW_WALLET_EXCEPTION_IF(rct_offsets[block_offset + 1] < rct_offsets[block_offset],
+ block_offset = last_usable_block - block_offset;
+ THROW_WALLET_EXCEPTION_IF(block_offset > last_usable_block, error::wallet_internal_error, "Bad offset calculation");
+ THROW_WALLET_EXCEPTION_IF(block_offset > 0 && rct_offsets[block_offset] < rct_offsets[block_offset - 1],
error::get_output_distribution, "Decreasing offsets in rct distribution: " +
- std::to_string(block_offset) + ": " + std::to_string(rct_offsets[block_offset]) + ", " +
- std::to_string(block_offset + 1) + ": " + std::to_string(rct_offsets[block_offset + 1]));
- uint64_t n_rct = rct_offsets[block_offset + 1] - rct_offsets[block_offset];
+ std::to_string(block_offset - 1) + ": " + std::to_string(rct_offsets[block_offset - 1]) + ", " +
+ std::to_string(block_offset) + ": " + std::to_string(rct_offsets[block_offset]));
+ uint64_t first_block_offset = block_offset, last_block_offset = block_offset;
+ for (size_t half_window = 0; half_window <= GAMMA_PICK_HALF_WINDOW; ++half_window)
+ {
+ // end when we have a non empty block
+ uint64_t cum0 = first_block_offset > 0 ? rct_offsets[first_block_offset] - rct_offsets[first_block_offset - 1] : rct_offsets[0];
+ if (cum0 > 1)
+ break;
+ uint64_t cum1 = last_block_offset > 0 ? rct_offsets[last_block_offset] - rct_offsets[last_block_offset - 1] : rct_offsets[0];
+ if (cum1 > 1)
+ break;
+ if (first_block_offset == 0 && last_block_offset >= last_usable_block)
+ break;
+ // expand up to bounds
+ if (first_block_offset > 0)
+ --first_block_offset;
+ else
+ return std::numeric_limits<uint64_t>::max(); // bad pick
+ if (last_block_offset < last_usable_block - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE)
+ ++last_block_offset;
+ else
+ return std::numeric_limits<uint64_t>::max(); // bad pick
+ }
+ const uint64_t first_rct = first_block_offset == 0 ? 0 : rct_offsets[first_block_offset - 1];
+ const uint64_t n_rct = rct_offsets[last_block_offset] - first_rct;
if (n_rct == 0)
return rct_offsets[block_offset] ? rct_offsets[block_offset] - 1 : 0;
- return rct_offsets[block_offset] + crypto::rand<uint64_t>() % n_rct;
+ MDEBUG("Picking 1/" << n_rct << " in " << (last_block_offset - first_block_offset + 1) << " blocks centered around " << block_offset + rct_start_height);
+ return first_rct + crypto::rand<uint64_t>() % n_rct;
};
size_t num_selected_transfers = 0;
@@ -7103,12 +7551,13 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
}
// get the keys for those
+ req.get_txid = false;
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, daemon_resp, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin");
THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin");
- THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_outs_error, daemon_resp.status);
+ THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_outs_error, get_rpc_status(daemon_resp.status));
THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != req.outputs.size(), error::wallet_internal_error,
"daemon returned wrong response for get_outs.bin, wrong amounts count = " +
std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(req.outputs.size()));
@@ -7137,6 +7586,9 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
break;
}
}
+ bool use_histogram = amount != 0 || !has_rct_distribution;
+ if (!use_histogram)
+ num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE];
// make sure the real outputs we asked for are really included, along
// with the correct key and mask: this guards against an active attack
@@ -7229,6 +7681,20 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
outs.push_back(v);
}
}
+
+ // save those outs in the ringdb for reuse
+ for (size_t i = 0; i < selected_transfers.size(); ++i)
+ {
+ const size_t idx = selected_transfers[i];
+ THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "selected_transfers entry out of range");
+ const transfer_details &td = m_transfers[idx];
+ std::vector<uint64_t> ring;
+ ring.reserve(outs[i].size());
+ for (const auto &e: outs[i])
+ ring.push_back(std::get<0>(e));
+ if (!set_ring(td.m_key_image, ring, false))
+ MERROR("Failed to set ring for " << td.m_key_image);
+ }
}
template<typename T>
@@ -7344,7 +7810,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
std::vector<crypto::secret_key> additional_tx_keys;
rct::multisig_out msout;
LOG_PRINT_L2("constructing tx");
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, rct::RangeProofBulletproof, m_multisig ? &msout : NULL);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, {}, m_multisig ? &msout : NULL);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit);
@@ -7393,7 +7859,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
- uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, rct::RangeProofType range_proof_type)
+ uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config)
{
using namespace cryptonote;
// throw if attempting a transaction with no destinations
@@ -7417,30 +7883,56 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
// if this is a multisig wallet, create a list of multisig signers we can use
std::deque<crypto::public_key> multisig_signers;
size_t n_multisig_txes = 0;
+ std::vector<std::unordered_set<crypto::public_key>> ignore_sets;
if (m_multisig && !m_transfers.empty())
{
const crypto::public_key local_signer = get_multisig_signer_public_key();
size_t n_available_signers = 1;
+
+ // At this step we need to define set of participants available for signature,
+ // i.e. those of them who exchanged with multisig info's
for (const crypto::public_key &signer: m_multisig_signers)
{
if (signer == local_signer)
continue;
- multisig_signers.push_front(signer);
for (const auto &i: m_transfers[0].m_multisig_info)
{
if (i.m_signer == signer)
{
- multisig_signers.pop_front();
multisig_signers.push_back(signer);
++n_available_signers;
break;
}
}
}
- multisig_signers.push_back(local_signer);
+ // n_available_signers includes the transaction creator, but multisig_signers doesn't
MDEBUG("We can use " << n_available_signers << "/" << m_multisig_signers.size() << " other signers");
- THROW_WALLET_EXCEPTION_IF(n_available_signers+1 < m_multisig_threshold, error::multisig_import_needed);
- n_multisig_txes = n_available_signers == m_multisig_signers.size() ? m_multisig_threshold : 1;
+ THROW_WALLET_EXCEPTION_IF(n_available_signers < m_multisig_threshold, error::multisig_import_needed);
+ if (n_available_signers > m_multisig_threshold)
+ {
+ // If there more potential signers (those who exchanged with multisig info)
+ // than threshold needed some of them should be skipped since we don't know
+ // who will sign tx and who won't. Hence we don't contribute their LR pairs to the signature.
+
+ // We create as many transactions as many combinations of excluded signers may be.
+ // For example, if we have 2/4 wallet and wallets are: A, B, C and D. Let A be
+ // transaction creator, so we need just 1 signature from set of B, C, D.
+ // Using "excluding" logic here we have to exclude 2-of-3 wallets. Combinations go as follows:
+ // BC, BD, and CD. We save these sets to use later and counting the number of required txs.
+ tools::Combinator<crypto::public_key> c(std::vector<crypto::public_key>(multisig_signers.begin(), multisig_signers.end()));
+ auto ignore_combinations = c.combine(multisig_signers.size() + 1 - m_multisig_threshold);
+ for (const auto& combination: ignore_combinations)
+ {
+ ignore_sets.push_back(std::unordered_set<crypto::public_key>(combination.begin(), combination.end()));
+ }
+
+ n_multisig_txes = ignore_sets.size();
+ }
+ else
+ {
+ // If we have exact count of signers just to fit in threshold we don't exclude anyone and create 1 transaction
+ n_multisig_txes = 1;
+ }
MDEBUG("We will create " << n_multisig_txes << " txes");
}
@@ -7508,8 +8000,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
src.mask = td.m_mask;
if (m_multisig)
{
- crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front();
- src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore, used_L, used_L);
+ auto ignore_set = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front();
+ src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_set, used_L, used_L);
}
else
src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
@@ -7549,7 +8041,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
rct::multisig_out msout;
LOG_PRINT_L2("constructing tx");
auto sources_copy = sources;
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, range_proof_type, m_multisig ? &msout : NULL);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, rct_config, m_multisig ? &msout : NULL);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit);
@@ -7571,7 +8063,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
std::vector<tools::wallet2::multisig_sig> multisig_sigs;
if (m_multisig)
{
- crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front();
+ auto ignore = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front();
multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, std::unordered_set<crypto::public_key>(), msout});
if (m_multisig_threshold < m_multisig_signers.size())
@@ -7579,7 +8071,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
const crypto::hash prefix_hash = cryptonote::get_transaction_prefix_hash(tx);
// create the other versions, one for every other participant (the first one's already done above)
- for (size_t signer_index = 1; signer_index < n_multisig_txes; ++signer_index)
+ for (size_t ignore_index = 1; ignore_index < ignore_sets.size(); ++ignore_index)
{
std::unordered_set<rct::key> new_used_L;
size_t src_idx = 0;
@@ -7587,19 +8079,19 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
for(size_t idx: selected_transfers)
{
cryptonote::tx_source_entry& src = sources_copy[src_idx];
- src.multisig_kLRki = get_multisig_composite_kLRki(idx, multisig_signers[signer_index], used_L, new_used_L);
+ src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_sets[ignore_index], used_L, new_used_L);
++src_idx;
}
LOG_PRINT_L2("Creating supplementary multisig transaction");
cryptonote::transaction ms_tx;
auto sources_copy_copy = sources_copy;
- bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, range_proof_type, &msout, false);
+ bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, rct_config, &msout, false);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit);
THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_prefix_hash(ms_tx) != prefix_hash, error::wallet_internal_error, "Multisig txes do not share prefix");
- multisig_sigs.push_back({ms_tx.rct_signatures, multisig_signers[signer_index], new_used_L, std::unordered_set<crypto::public_key>(), msout});
+ multisig_sigs.push_back({ms_tx.rct_signatures, ignore_sets[ignore_index], new_used_L, std::unordered_set<crypto::public_key>(), msout});
ms_tx.rct_signatures = tx.rct_signatures;
THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_hash(ms_tx) != cryptonote::get_transaction_hash(tx), error::wallet_internal_error, "Multisig txes differ by more than the signatures");
@@ -7900,6 +8392,7 @@ void wallet2::light_wallet_get_unspent_outs()
td.m_key_image = unspent_key_image;
td.m_key_image_known = !m_watch_only && !m_multisig;
+ td.m_key_image_request = false;
td.m_key_image_partial = m_multisig;
td.m_amount = o.amount;
td.m_pk_index = 0;
@@ -8004,7 +8497,6 @@ void wallet2::light_wallet_get_address_txs()
// for balance calculation
uint64_t wallet_total_sent = 0;
- uint64_t wallet_total_unlocked_sent = 0;
// txs in pool
std::vector<crypto::hash> pool_txs;
@@ -8271,15 +8763,16 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
TX() : weight(0), needed_fee(0) {}
- void add(const account_public_address &addr, bool is_subaddress, uint64_t amount, unsigned int original_output_index, bool merge_destinations) {
+ void add(const cryptonote::tx_destination_entry &de, uint64_t amount, unsigned int original_output_index, bool merge_destinations) {
if (merge_destinations)
{
std::vector<cryptonote::tx_destination_entry>::iterator i;
- i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &addr, sizeof(addr)); });
+ i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &de.addr, sizeof(de.addr)); });
if (i == dsts.end())
{
- dsts.push_back(tx_destination_entry(0,addr,is_subaddress));
+ dsts.push_back(de);
i = dsts.end() - 1;
+ i->amount = 0;
}
i->amount += amount;
}
@@ -8288,8 +8781,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
THROW_WALLET_EXCEPTION_IF(original_output_index > dsts.size(), error::wallet_internal_error,
std::string("original_output_index too large: ") + std::to_string(original_output_index) + " > " + std::to_string(dsts.size()));
if (original_output_index == dsts.size())
- dsts.push_back(tx_destination_entry(0,addr,is_subaddress));
- THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &addr, sizeof(addr)), error::wallet_internal_error, "Mismatched destination address");
+ {
+ dsts.push_back(de);
+ dsts.back().amount = 0;
+ }
+ THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &de.addr, sizeof(de.addr)), error::wallet_internal_error, "Mismatched destination address");
dsts[original_output_index].amount += amount;
}
}
@@ -8301,7 +8797,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0);
const bool use_rct = use_fork_rules(4, 0);
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
- const rct::RangeProofType range_proof_type = bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean;
+ const rct::RCTConfig rct_config {
+ bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean,
+ bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0
+ };
const uint64_t base_fee = get_base_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
@@ -8336,7 +8835,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// early out if we know we can't make it anyway
// we could also check for being within FEE_PER_KB, but if the fee calculation
// ever changes, this might be missed, so let this go through
- const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof)) / 1024;
+ const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof));
uint64_t balance_subtotal = 0;
uint64_t unlocked_balance_subtotal = 0;
for (uint32_t index_minor : subaddr_indices)
@@ -8404,12 +8903,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
}
}
- // shuffle & sort output indices
+ // sort output indices
{
- std::random_device rd;
- std::mt19937 g(rd());
- std::shuffle(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), g);
- std::shuffle(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), g);
auto sort_predicate = [&unlocked_balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y)
{
return unlocked_balance_per_subaddr[x.first] > unlocked_balance_per_subaddr[y.first];
@@ -8561,7 +9056,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// we can fully pay that destination
LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(dsts[0].amount));
- tx.add(dsts[0].addr, dsts[0].is_subaddress, dsts[0].amount, original_output_index, m_merge_destinations);
+ tx.add(dsts[0], dsts[0].amount, original_output_index, m_merge_destinations);
available_amount -= dsts[0].amount;
dsts[0].amount = 0;
pop_index(dsts, 0);
@@ -8572,7 +9067,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// we can partially fill that destination
LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(available_amount) << "/" << print_money(dsts[0].amount));
- tx.add(dsts[0].addr, dsts[0].is_subaddress, available_amount, original_output_index, m_merge_destinations);
+ tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations);
dsts[0].amount -= available_amount;
available_amount = 0;
}
@@ -8601,7 +9096,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
cryptonote::transaction test_tx;
pending_tx test_ptx;
- needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_multiplier);
+ needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask);
uint64_t inputs = 0, outputs = needed_fee;
for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount();
@@ -8618,7 +9113,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
tx.selected_transfers.size() << " inputs");
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx, range_proof_type);
+ test_tx, test_ptx, rct_config);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -8661,7 +9156,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
while (needed_fee > test_ptx.fee) {
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx, range_proof_type);
+ test_tx, test_ptx, rct_config);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -8734,7 +9229,7 @@ skip_tx:
extra, /* const std::vector<uint8_t>& extra, */
test_tx, /* OUT cryptonote::transaction& tx, */
test_ptx, /* OUT cryptonote::transaction& tx, */
- range_proof_type);
+ rct_config);
} else {
transfer_selected(tx.dsts,
tx.selected_transfers,
@@ -8874,7 +9369,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE);
const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0);
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
- const rct::RangeProofType range_proof_type = bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean;
+ const rct::RCTConfig rct_config {
+ bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean,
+ bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0,
+ };
const uint64_t base_fee = get_base_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
const uint64_t fee_quantization_mask = get_fee_quantization_mask();
@@ -8950,7 +9448,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
tx.selected_transfers.size() << " outputs");
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx, range_proof_type);
+ test_tx, test_ptx, rct_config);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -8987,7 +9485,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
}
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx, range_proof_type);
+ test_tx, test_ptx, rct_config);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -9026,7 +9524,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
pending_tx test_ptx;
if (use_rct) {
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, unlock_time, tx.needed_fee, extra,
- test_tx, test_ptx, range_proof_type);
+ test_tx, test_ptx, rct_config);
} else {
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, unlock_time, tx.needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -9055,6 +9553,64 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
return ptx_vector;
}
//----------------------------------------------------------------------------------------------------
+void wallet2::cold_tx_aux_import(const std::vector<pending_tx> & ptx, const std::vector<std::string> & tx_device_aux)
+{
+ CHECK_AND_ASSERT_THROW_MES(ptx.size() == tx_device_aux.size(), "TX aux has invalid size");
+ for (size_t i = 0; i < ptx.size(); ++i){
+ crypto::hash txid;
+ txid = get_transaction_hash(ptx[i].tx);
+ set_tx_device_aux(txid, tx_device_aux[i]);
+ }
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::vector<std::string> & tx_device_aux)
+{
+ auto & hwdev = get_account().get_device();
+ if (!hwdev.has_tx_cold_sign()){
+ throw std::invalid_argument("Device does not support cold sign protocol");
+ }
+
+ unsigned_tx_set txs;
+ for (auto &tx: ptx_vector)
+ {
+ txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device()));
+ }
+ txs.transfers = std::make_pair(0, m_transfers);
+
+ auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev);
+ CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
+
+ hw::tx_aux_data aux_data;
+ hw::wallet_shim wallet_shim;
+ setup_shim(&wallet_shim, this);
+ aux_data.tx_recipients = dsts_info;
+ dev_cold->tx_sign(&wallet_shim, txs, exported_txs, aux_data);
+ tx_device_aux = aux_data.tx_device_aux;
+
+ MDEBUG("Signed tx data from hw: " << exported_txs.ptx.size() << " transactions");
+ for (auto &c_ptx: exported_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(c_ptx.tx));
+}
+//----------------------------------------------------------------------------------------------------
+uint64_t wallet2::cold_key_image_sync(uint64_t &spent, uint64_t &unspent) {
+ auto & hwdev = get_account().get_device();
+ CHECK_AND_ASSERT_THROW_MES(hwdev.has_ki_cold_sync(), "Device does not support cold ki sync protocol");
+
+ auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev);
+ CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
+
+ std::vector<std::pair<crypto::key_image, crypto::signature>> ski;
+ hw::wallet_shim wallet_shim;
+ setup_shim(&wallet_shim, this);
+
+ dev_cold->ki_sync(&wallet_shim, m_transfers, ski);
+
+ // Call COMMAND_RPC_IS_KEY_IMAGE_SPENT only if daemon is trusted.
+ uint64_t import_res = import_key_images(ski, 0, spent, unspent, is_trusted_daemon());
+ m_device_last_key_image_sync = time(NULL);
+
+ return import_res;
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const
{
boost::optional<std::string> result = m_node_rpc_proxy.get_earliest_height(version, earliest_height);
@@ -9263,7 +9819,7 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_
COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req);
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
- req.prune = false;
+ req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
bool r;
{
@@ -9276,11 +9832,10 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_
THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
"daemon returned wrong response for gettransactions, wrong txs count = " +
std::to_string(res.txs.size()) + ", expected 1");
- cryptonote::blobdata bd;
- THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr");
cryptonote::transaction tx;
- crypto::hash tx_hash, tx_prefix_hash;
- THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob");
+ crypto::hash tx_hash;
+ THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error,
+ "Failed to get transaction from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch");
std::vector<tx_extra_field> tx_extra_fields;
THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(tx.extra, tx_extra_fields), error::wallet_internal_error, "Transaction extra has unsupported format");
@@ -9314,7 +9869,7 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string
COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req);
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
- req.prune = false;
+ req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
bool r;
{
@@ -9327,12 +9882,10 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string
THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
"daemon returned wrong response for gettransactions, wrong txs count = " +
std::to_string(res.txs.size()) + ", expected 1");
- cryptonote::blobdata bd;
- THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr");
+
cryptonote::transaction tx;
- crypto::hash tx_hash, tx_prefix_hash;
- THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob");
- THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch");
+ crypto::hash tx_hash;
+ THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, "Failed to get tx from daemon");
std::vector<std::vector<crypto::signature>> signatures;
@@ -9434,7 +9987,7 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes
COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req);
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
- req.prune = false;
+ req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
bool r;
{
@@ -9447,12 +10000,10 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes
THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
"daemon returned wrong response for gettransactions, wrong txs count = " +
std::to_string(res.txs.size()) + ", expected 1");
- cryptonote::blobdata bd;
- THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr");
+
cryptonote::transaction tx;
- crypto::hash tx_hash, tx_prefix_hash;
- THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob");
- THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch");
+ crypto::hash tx_hash;
+ THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, "failed to get tx from daemon");
// check signature size
size_t num_sigs = 0;
@@ -9559,24 +10110,30 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de
COMMAND_RPC_GET_TRANSACTIONS::response res;
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
- req.prune = false;
+ req.prune = true;
m_daemon_rpc_mutex.lock();
bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
error::wallet_internal_error, "Failed to get transaction from daemon");
- cryptonote::blobdata tx_data;
+ cryptonote::transaction tx;
+ crypto::hash tx_hash;
if (res.txs.size() == 1)
- ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data);
+ {
+ ok = get_pruned_tx(res.txs.front(), tx, tx_hash);
+ THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
+ }
else
+ {
+ cryptonote::blobdata tx_data;
+ crypto::hash tx_prefix_hash;
ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data);
- THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
+ THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
+ THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash),
+ error::wallet_internal_error, "Failed to validate transaction from daemon");
+ }
- crypto::hash tx_hash, tx_prefix_hash;
- cryptonote::transaction tx;
- THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error,
- "Failed to validate transaction from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error,
"Failed to get the right transaction from daemon");
THROW_WALLET_EXCEPTION_IF(!additional_derivations.empty() && additional_derivations.size() != tx.vout.size(), error::wallet_internal_error,
@@ -9615,7 +10172,7 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de
crypto::secret_key scalar1;
hwdev.derivation_to_scalar(found_derivation, n, scalar1);
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
- hwdev.ecdhDecode(ecdh_info, rct::sk2rct(scalar1));
+ hwdev.ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2);
const rct::key C = tx.rct_signatures.outPk[n].mask;
rct::key Ctmp;
THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask");
@@ -9699,24 +10256,30 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac
COMMAND_RPC_GET_TRANSACTIONS::response res;
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
- req.prune = false;
+ req.prune = true;
m_daemon_rpc_mutex.lock();
bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
error::wallet_internal_error, "Failed to get transaction from daemon");
- cryptonote::blobdata tx_data;
+ cryptonote::transaction tx;
+ crypto::hash tx_hash;
if (res.txs.size() == 1)
- ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data);
+ {
+ ok = get_pruned_tx(res.txs.front(), tx, tx_hash);
+ THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
+ }
else
+ {
+ cryptonote::blobdata tx_data;
+ crypto::hash tx_prefix_hash;
ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data);
- THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
+ THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
+ THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash),
+ error::wallet_internal_error, "Failed to validate transaction from daemon");
+ }
- crypto::hash tx_hash, tx_prefix_hash;
- cryptonote::transaction tx;
- THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error,
- "Failed to validate transaction from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon");
crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
@@ -9811,24 +10374,30 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account
COMMAND_RPC_GET_TRANSACTIONS::response res;
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
- req.prune = false;
+ req.prune = true;
m_daemon_rpc_mutex.lock();
bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
error::wallet_internal_error, "Failed to get transaction from daemon");
- cryptonote::blobdata tx_data;
+ cryptonote::transaction tx;
+ crypto::hash tx_hash;
if (res.txs.size() == 1)
- ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data);
+ {
+ ok = get_pruned_tx(res.txs.front(), tx, tx_hash);
+ THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
+ }
else
+ {
+ cryptonote::blobdata tx_data;
+ crypto::hash tx_prefix_hash;
ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data);
- THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
+ THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
+ THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash),
+ error::wallet_internal_error, "Failed to validate transaction from daemon");
+ }
- crypto::hash tx_hash, tx_prefix_hash;
- cryptonote::transaction tx;
- THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error,
- "Failed to validate transaction from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon");
crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
@@ -10047,7 +10616,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
for (size_t i = 0; i < proofs.size(); ++i)
gettx_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(proofs[i].txid));
gettx_req.decode_as_json = false;
- gettx_req.prune = false;
+ gettx_req.prune = true;
m_daemon_rpc_mutex.lock();
bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client);
m_daemon_rpc_mutex.unlock();
@@ -10071,14 +10640,11 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
const reserve_proof_entry& proof = proofs[i];
THROW_WALLET_EXCEPTION_IF(gettx_res.txs[i].in_pool, error::wallet_internal_error, "Tx is unconfirmed");
- cryptonote::blobdata tx_data;
- ok = string_tools::parse_hexstr_to_binbuff(gettx_res.txs[i].as_hex, tx_data);
+ cryptonote::transaction tx;
+ crypto::hash tx_hash;
+ ok = get_pruned_tx(gettx_res.txs[i], tx, tx_hash);
THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
- crypto::hash tx_hash, tx_prefix_hash;
- cryptonote::transaction tx;
- THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error,
- "Failed to validate transaction from daemon");
THROW_WALLET_EXCEPTION_IF(tx_hash != proof.txid, error::wallet_internal_error, "Failed to get the right transaction from daemon");
THROW_WALLET_EXCEPTION_IF(proof.index_in_tx >= tx.vout.size(), error::wallet_internal_error, "index_in_tx is out of bound");
@@ -10120,7 +10686,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
crypto::secret_key shared_secret;
crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret);
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx];
- rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret));
+ rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2);
amount = rct::h2d(ecdh_info.amount);
}
total += amount;
@@ -10159,7 +10725,10 @@ uint64_t wallet2::get_daemon_blockchain_height(string &err) const
boost::optional<std::string> result = m_node_rpc_proxy.get_height(height);
if (result)
{
- err = *result;
+ if (m_trusted_daemon)
+ err = *result;
+ else
+ err = "daemon error";
return 0;
}
@@ -10174,7 +10743,10 @@ uint64_t wallet2::get_daemon_blockchain_target_height(string &err)
const auto result = m_node_rpc_proxy.get_target_height(target_height);
if (result && *result != CORE_RPC_STATUS_OK)
{
- err= *result;
+ if (m_trusted_daemon)
+ err = *result;
+ else
+ err = "daemon error";
return 0;
}
return target_height;
@@ -10211,6 +10783,19 @@ std::string wallet2::get_tx_note(const crypto::hash &txid) const
return i->second;
}
+void wallet2::set_tx_device_aux(const crypto::hash &txid, const std::string &aux)
+{
+ m_tx_device[txid] = aux;
+}
+
+std::string wallet2::get_tx_device_aux(const crypto::hash &txid) const
+{
+ std::unordered_map<crypto::hash, std::string>::const_iterator i = m_tx_device.find(txid);
+ if (i == m_tx_device.end())
+ return std::string();
+ return i->second;
+}
+
void wallet2::set_attribute(const std::string &key, const std::string &value)
{
m_attributes[key] = value;
@@ -10254,7 +10839,7 @@ const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& w
return m_account_tags;
}
-void wallet2::set_account_tag(const std::set<uint32_t> account_indices, const std::string& tag)
+void wallet2::set_account_tag(const std::set<uint32_t> &account_indices, const std::string& tag)
{
for (uint32_t account_index : account_indices)
{
@@ -10401,36 +10986,50 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
bool wallet2::export_key_images(const std::string &filename) const
{
- std::vector<std::pair<crypto::key_image, crypto::signature>> ski = export_key_images();
+ PERF_TIMER(export_key_images);
+ std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = export_key_images();
std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC));
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
+ const uint32_t offset = ski.first;
std::string data;
+ data.reserve(4 + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key));
+ data.resize(4);
+ data[0] = offset & 0xff;
+ data[1] = (offset >> 8) & 0xff;
+ data[2] = (offset >> 16) & 0xff;
+ data[3] = (offset >> 24) & 0xff;
data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
- for (const auto &i: ski)
+ for (const auto &i: ski.second)
{
data += std::string((const char *)&i.first, sizeof(crypto::key_image));
data += std::string((const char *)&i.second, sizeof(crypto::signature));
}
// encrypt data, keep magic plaintext
+ PERF_TIMER(export_key_images_encrypt);
std::string ciphertext = encrypt_with_view_secret_key(data);
return epee::file_io_utils::save_string_to_file(filename, magic + ciphertext);
}
//----------------------------------------------------------------------------------------------------
-std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key_images() const
+std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> wallet2::export_key_images(bool all) const
{
+ PERF_TIMER(export_key_images_raw);
std::vector<std::pair<crypto::key_image, crypto::signature>> ski;
- ski.reserve(m_transfers.size());
- for (size_t n = 0; n < m_transfers.size(); ++n)
+ size_t offset = 0;
+ if (!all)
{
- const transfer_details &td = m_transfers[n];
+ while (offset < m_transfers.size() && !m_transfers[offset].m_key_image_request)
+ ++offset;
+ }
- crypto::hash hash;
- crypto::cn_fast_hash(&td.m_key_image, sizeof(td.m_key_image), hash);
+ ski.reserve(m_transfers.size() - offset);
+ for (size_t n = offset; n < m_transfers.size(); ++n)
+ {
+ const transfer_details &td = m_transfers[n];
// get ephemeral public key
const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index];
@@ -10469,11 +11068,12 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key
ski.push_back(std::make_pair(td.m_key_image, signature));
}
- return ski;
+ return std::make_pair(offset, ski);
}
uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent)
{
+ PERF_TIMER(import_key_images_fsu);
std::string data;
bool r = epee::file_io_utils::load_file_to_string(filename, data);
@@ -10487,6 +11087,7 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent
try
{
+ PERF_TIMER(import_key_images_decrypt);
data = decrypt_with_view_secret_key(std::string(data, magiclen));
}
catch (const std::exception &e)
@@ -10494,15 +11095,17 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent
THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + filename + ": " + e.what());
}
- const size_t headerlen = 2 * sizeof(crypto::public_key);
+ const size_t headerlen = 4 + 2 * sizeof(crypto::public_key);
THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename);
- const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0];
- const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)];
+ const uint32_t offset = (uint8_t)data[0] | (((uint8_t)data[1]) << 8) | (((uint8_t)data[2]) << 16) | (((uint8_t)data[3]) << 24);
+ const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[4];
+ const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[4 + sizeof(crypto::public_key)];
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key)
{
THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + filename + " are for a different account");
}
+ THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs");
const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature);
THROW_WALLET_EXCEPTION_IF((data.size() - headerlen) % record_size,
@@ -10519,28 +11122,33 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent
ski.push_back(std::make_pair(key_image, signature));
}
- return import_key_images(ski, spent, unspent);
+ return import_key_images(ski, offset, spent, unspent);
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent, bool check_spent)
+uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent)
{
+ PERF_TIMER(import_key_images_lots);
COMMAND_RPC_IS_KEY_IMAGE_SPENT::request req = AUTO_VAL_INIT(req);
COMMAND_RPC_IS_KEY_IMAGE_SPENT::response daemon_resp = AUTO_VAL_INIT(daemon_resp);
- THROW_WALLET_EXCEPTION_IF(signed_key_images.size() > m_transfers.size(), error::wallet_internal_error,
+ THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs");
+ THROW_WALLET_EXCEPTION_IF(signed_key_images.size() > m_transfers.size() - offset, error::wallet_internal_error,
"The blockchain is out of date compared to the signed key images");
- if (signed_key_images.empty())
+ if (signed_key_images.empty() && offset == 0)
{
spent = 0;
unspent = 0;
return 0;
}
+ req.key_images.reserve(signed_key_images.size());
+
+ PERF_TIMER_START(import_key_images_A);
for (size_t n = 0; n < signed_key_images.size(); ++n)
{
- const transfer_details &td = m_transfers[n];
+ const transfer_details &td = m_transfers[n + offset];
const crypto::key_image &key_image = signed_key_images[n].first;
const crypto::signature &signature = signed_key_images[n].second;
@@ -10551,30 +11159,37 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
const cryptonote::txout_to_key &o = boost::get<cryptonote::txout_to_key>(out.target);
const crypto::public_key pkey = o.key;
- std::vector<const crypto::public_key*> pkeys;
- pkeys.push_back(&pkey);
- THROW_WALLET_EXCEPTION_IF(!(rct::scalarmultKey(rct::ki2rct(key_image), rct::curveOrder()) == rct::identity()),
- error::wallet_internal_error, "Key image out of validity domain: input " + boost::lexical_cast<std::string>(n) + "/"
- + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image));
-
- THROW_WALLET_EXCEPTION_IF(!crypto::check_ring_signature((const crypto::hash&)key_image, key_image, pkeys, &signature),
- error::wallet_internal_error, "Signature check failed: input " + boost::lexical_cast<std::string>(n) + "/"
- + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image)
- + ", signature " + epee::string_tools::pod_to_hex(signature) + ", pubkey " + epee::string_tools::pod_to_hex(*pkeys[0]));
+ if (!td.m_key_image_known || !(key_image == td.m_key_image))
+ {
+ std::vector<const crypto::public_key*> pkeys;
+ pkeys.push_back(&pkey);
+ THROW_WALLET_EXCEPTION_IF(!(rct::scalarmultKey(rct::ki2rct(key_image), rct::curveOrder()) == rct::identity()),
+ error::wallet_internal_error, "Key image out of validity domain: input " + boost::lexical_cast<std::string>(n + offset) + "/"
+ + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image));
+ THROW_WALLET_EXCEPTION_IF(!crypto::check_ring_signature((const crypto::hash&)key_image, key_image, pkeys, &signature),
+ error::signature_check_failed, boost::lexical_cast<std::string>(n + offset) + "/"
+ + boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image)
+ + ", signature " + epee::string_tools::pod_to_hex(signature) + ", pubkey " + epee::string_tools::pod_to_hex(*pkeys[0]));
+ }
req.key_images.push_back(epee::string_tools::pod_to_hex(key_image));
}
+ PERF_TIMER_STOP(import_key_images_A);
+ PERF_TIMER_START(import_key_images_B);
for (size_t n = 0; n < signed_key_images.size(); ++n)
{
- m_transfers[n].m_key_image = signed_key_images[n].first;
- m_key_images[m_transfers[n].m_key_image] = n;
- m_transfers[n].m_key_image_known = true;
- m_transfers[n].m_key_image_partial = false;
+ m_transfers[n + offset].m_key_image = signed_key_images[n].first;
+ m_key_images[m_transfers[n + offset].m_key_image] = n + offset;
+ m_transfers[n + offset].m_key_image_known = true;
+ m_transfers[n + offset].m_key_image_request = false;
+ m_transfers[n + offset].m_key_image_partial = false;
}
+ PERF_TIMER_STOP(import_key_images_B);
if(check_spent)
{
+ PERF_TIMER(import_key_images_RPC);
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
@@ -10586,7 +11201,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(signed_key_images.size()));
for (size_t n = 0; n < daemon_resp.spent_status.size(); ++n)
{
- transfer_details &td = m_transfers[n];
+ transfer_details &td = m_transfers[n + offset];
td.m_spent = daemon_resp.spent_status[n] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT;
}
}
@@ -10597,6 +11212,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
// was created by sweep_all, so we can't know the spent height and other detailed info.
std::unordered_map<crypto::key_image, crypto::hash> spent_key_images;
+ PERF_TIMER_START(import_key_images_C);
for (const transfer_details &td: m_transfers)
{
for (const cryptonote::txin_v& in : td.m_tx.vin)
@@ -10605,10 +11221,12 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
spent_key_images.insert(std::make_pair(boost::get<cryptonote::txin_to_key>(in).k_image, td.m_txid));
}
}
+ PERF_TIMER_STOP(import_key_images_C);
+ PERF_TIMER_START(import_key_images_D);
for(size_t i = 0; i < signed_key_images.size(); ++i)
{
- transfer_details &td = m_transfers[i];
+ const transfer_details &td = m_transfers[i + offset];
uint64_t amount = td.amount();
if (td.m_spent)
spent += amount;
@@ -10626,6 +11244,8 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
spent_txids.insert(skii->second);
}
}
+ PERF_TIMER_STOP(import_key_images_D);
+
MDEBUG("Total: " << print_money(spent) << " spent, " << print_money(unspent) << " unspent");
if (check_spent)
@@ -10634,9 +11254,13 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
COMMAND_RPC_GET_TRANSACTIONS::request gettxs_req;
COMMAND_RPC_GET_TRANSACTIONS::response gettxs_res;
gettxs_req.decode_as_json = false;
- gettxs_req.prune = false;
+ gettxs_req.prune = true;
+ gettxs_req.txs_hashes.reserve(spent_txids.size());
for (const crypto::hash& spent_txid : spent_txids)
gettxs_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(spent_txid));
+
+
+ PERF_TIMER_START(import_key_images_E);
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
@@ -10644,21 +11268,22 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
THROW_WALLET_EXCEPTION_IF(gettxs_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions");
THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error,
"daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size()));
+ PERF_TIMER_STOP(import_key_images_E);
// process each outgoing tx
+ PERF_TIMER_START(import_key_images_F);
auto spent_txid = spent_txids.begin();
hw::device &hwdev = m_account.get_device();
+ auto it = spent_txids.begin();
for (const COMMAND_RPC_GET_TRANSACTIONS::entry& e : gettxs_res.txs)
{
THROW_WALLET_EXCEPTION_IF(e.in_pool, error::wallet_internal_error, "spent tx isn't supposed to be in txpool");
- // parse tx
- cryptonote::blobdata bd;
- THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(e.as_hex, bd), error::wallet_internal_error, "parse_hexstr_to_binbuff failed");
cryptonote::transaction spent_tx;
- crypto::hash spnet_txid_parsed, spent_txid_prefix;
- THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, spent_tx, spnet_txid_parsed, spent_txid_prefix), error::wallet_internal_error, "parse_and_validate_tx_from_blob failed");
- THROW_WALLET_EXCEPTION_IF(*spent_txid != spnet_txid_parsed, error::wallet_internal_error, "parsed txid mismatch");
+ crypto::hash spnet_txid_parsed;
+ THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(e, spent_tx, spnet_txid_parsed), error::wallet_internal_error, "Failed to get tx from daemon");
+ THROW_WALLET_EXCEPTION_IF(!(spnet_txid_parsed == *it), error::wallet_internal_error, "parsed txid mismatch");
+ ++it;
// get received (change) amount
uint64_t tx_money_got_in_outs = 0;
@@ -10741,7 +11366,9 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
++spent_txid;
}
+ PERF_TIMER_STOP(import_key_images_F);
+ PERF_TIMER_START(import_key_images_G);
for (size_t n : swept_transfers)
{
const transfer_details& td = m_transfers[n];
@@ -10752,10 +11379,35 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
const crypto::hash &spent_txid = crypto::null_hash; // spent txid is unknown
m_confirmed_txs.insert(std::make_pair(spent_txid, pd));
}
+ PERF_TIMER_STOP(import_key_images_G);
}
return m_transfers[signed_key_images.size() - 1].m_block_height;
}
+
+bool wallet2::import_key_images(std::vector<crypto::key_image> key_images)
+{
+ if (key_images.size() > m_transfers.size())
+ {
+ LOG_PRINT_L1("More key images returned that we know outputs for");
+ return false;
+ }
+ for (size_t i = 0; i < key_images.size(); ++i)
+ {
+ transfer_details &td = m_transfers[i];
+ if (td.m_key_image_known && !td.m_key_image_partial && td.m_key_image != key_images[i])
+ LOG_PRINT_L0("WARNING: imported key image differs from previously known key image at index " << i << ": trusting imported one");
+ td.m_key_image = key_images[i];
+ m_key_images[m_transfers[i].m_key_image] = i;
+ td.m_key_image_known = true;
+ td.m_key_image_request = false;
+ td.m_key_image_partial = false;
+ m_pub_keys[m_transfers[i].get_public_key()] = i;
+ }
+
+ return true;
+}
+
wallet2::payment_container wallet2::export_payments() const
{
payment_container payments;
@@ -10814,50 +11466,87 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect
m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx);
}
//----------------------------------------------------------------------------------------------------
-std::vector<tools::wallet2::transfer_details> wallet2::export_outputs() const
+std::pair<size_t, std::vector<tools::wallet2::transfer_details>> wallet2::export_outputs() const
{
+ PERF_TIMER(export_outputs);
std::vector<tools::wallet2::transfer_details> outs;
- outs.reserve(m_transfers.size());
- for (size_t n = 0; n < m_transfers.size(); ++n)
+ size_t offset = 0;
+ while (offset < m_transfers.size() && (m_transfers[offset].m_key_image_known && !m_transfers[offset].m_key_image_request))
+ ++offset;
+
+ outs.reserve(m_transfers.size() - offset);
+ for (size_t n = offset; n < m_transfers.size(); ++n)
{
const transfer_details &td = m_transfers[n];
outs.push_back(td);
}
- return outs;
+ return std::make_pair(offset, outs);
}
//----------------------------------------------------------------------------------------------------
std::string wallet2::export_outputs_to_str() const
{
- std::vector<tools::wallet2::transfer_details> outs = export_outputs();
+ PERF_TIMER(export_outputs_to_str);
std::stringstream oss;
boost::archive::portable_binary_oarchive ar(oss);
- ar << outs;
+ const auto& outputs = export_outputs();
+ ar << outputs;
std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC));
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
std::string header;
header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
+ PERF_TIMER(export_outputs_encryption);
std::string ciphertext = encrypt_with_view_secret_key(header + oss.str());
return magic + ciphertext;
}
//----------------------------------------------------------------------------------------------------
-size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs)
+size_t wallet2::import_outputs(const std::pair<size_t, std::vector<tools::wallet2::transfer_details>> &outputs)
{
- m_transfers.clear();
- m_transfers.reserve(outputs.size());
- for (size_t i = 0; i < outputs.size(); ++i)
+ PERF_TIMER(import_outputs);
+
+ THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error,
+ "Imported outputs omit more outputs that we know of");
+
+ const size_t offset = outputs.first;
+ const size_t original_size = m_transfers.size();
+ m_transfers.resize(offset + outputs.second.size());
+ for (size_t i = 0; i < offset; ++i)
+ m_transfers[i].m_key_image_request = false;
+ for (size_t i = 0; i < outputs.second.size(); ++i)
{
- transfer_details td = outputs[i];
+ transfer_details td = outputs.second[i];
+
+ // skip those we've already imported, or which have different data
+ if (i + offset < original_size)
+ {
+ // compare the data used to create the key image below
+ const transfer_details &org_td = m_transfers[i + offset];
+ if (!org_td.m_key_image_known)
+ goto process;
+#define CMPF(f) if (!(td.f == org_td.f)) goto process
+ CMPF(m_txid);
+ CMPF(m_key_image);
+ CMPF(m_internal_output_index);
+#undef CMPF
+ if (!(get_transaction_prefix_hash(td.m_tx) == get_transaction_prefix_hash(org_td.m_tx)))
+ goto process;
+
+ // copy anyway, since the comparison does not include ancillary fields which may have changed
+ m_transfers[i + offset] = std::move(td);
+ continue;
+ }
+
+process:
// the hot wallet wouldn't have known about key images (except if we already exported them)
cryptonote::keypair in_ephemeral;
- THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i));
+ THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i + offset));
crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td);
const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
@@ -10868,13 +11557,14 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
expand_subaddresses(td.m_subaddr_index);
td.m_key_image_known = true;
+ td.m_key_image_request = true;
td.m_key_image_partial = false;
THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != out_key,
- error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i));
+ error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i + offset));
- m_key_images[td.m_key_image] = m_transfers.size();
- m_pub_keys[td.get_public_key()] = m_transfers.size();
- m_transfers.push_back(std::move(td));
+ m_key_images[td.m_key_image] = i + offset;
+ m_pub_keys[td.get_public_key()] = i + offset;
+ m_transfers[i + offset] = std::move(td);
}
return m_transfers.size();
@@ -10882,6 +11572,7 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
//----------------------------------------------------------------------------------------------------
size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
{
+ PERF_TIMER(import_outputs_from_str);
std::string data = outputs_st;
const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC);
if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen))
@@ -10891,6 +11582,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
try
{
+ PERF_TIMER(import_outputs_decrypt);
data = decrypt_with_view_secret_key(std::string(data, magiclen));
}
catch (const std::exception &e)
@@ -10917,7 +11609,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
std::string body(data, headerlen);
std::stringstream iss;
iss << body;
- std::vector<tools::wallet2::transfer_details> outputs;
+ std::pair<size_t, std::vector<tools::wallet2::transfer_details>> outputs;
try
{
boost::archive::portable_binary_iarchive ar(iss);
@@ -10996,7 +11688,7 @@ rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) con
return kLRki;
}
//----------------------------------------------------------------------------------------------------
-rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const
+rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const std::unordered_set<crypto::public_key> &ignore_set, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const
{
CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad transfer index");
@@ -11007,8 +11699,9 @@ rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const crypto
size_t n_signers_used = 1;
for (const auto &p: m_transfers[n].m_multisig_info)
{
- if (p.m_signer == ignore)
+ if (ignore_set.find(p.m_signer) != ignore_set.end())
continue;
+
for (const auto &lr: p.m_LR)
{
if (used_L.find(lr.m_L) != used_L.end())
@@ -11053,7 +11746,6 @@ cryptonote::blobdata wallet2::export_multisig()
for (size_t n = 0; n < m_transfers.size(); ++n)
{
transfer_details &td = m_transfers[n];
- const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
crypto::key_image ki;
td.m_multisig_k.clear();
info[n].m_LR.clear();
@@ -11067,7 +11759,10 @@ cryptonote::blobdata wallet2::export_multisig()
info[n].m_partial_key_images.push_back(ki);
}
- size_t nlr = m_multisig_threshold < m_multisig_signers.size() ? m_multisig_threshold - 1 : 1;
+ // Wallet tries to create as many transactions as many signers combinations. We calculate the maximum number here as follows:
+ // if we have 2/4 wallet with signers: A, B, C, D and A is a transaction creator it will need to pick up 1 signer from 3 wallets left.
+ // That means counting combinations for excluding 2-of-3 wallets (k = total signers count - threshold, n = total signers count - 1).
+ size_t nlr = tools::combinations_count(m_multisig_signers.size() - m_multisig_threshold, m_multisig_signers.size() - 1);
for (size_t m = 0; m < nlr; ++m)
{
td.m_multisig_k.push_back(rct::skGen());
@@ -11082,7 +11777,6 @@ cryptonote::blobdata wallet2::export_multisig()
boost::archive::portable_binary_oarchive ar(oss);
ar << info;
- std::string magic(MULTISIG_EXPORT_FILE_MAGIC, strlen(MULTISIG_EXPORT_FILE_MAGIC));
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
std::string header;
header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
@@ -11109,6 +11803,7 @@ void wallet2::update_multisig_rescan_info(const std::vector<std::vector<rct::key
m_key_images.erase(td.m_key_image);
td.m_key_image = get_multisig_composite_key_image(n);
td.m_key_image_known = true;
+ td.m_key_image_request = false;
td.m_key_image_partial = false;
td.m_multisig_k = multisig_k[n];
m_key_images[td.m_key_image] = n;
@@ -11491,7 +12186,7 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui
else if (res.status == CORE_RPC_STATUS_BUSY)
oss << "daemon is busy";
else
- oss << res.status;
+ oss << get_rpc_status(res.status);
throw std::runtime_error(oss.str());
}
cryptonote::block blk_min, blk_mid, blk_max;
@@ -11662,4 +12357,75 @@ uint64_t wallet2::get_segregation_fork_height() const
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){
+ m_device_callback.reset(new wallet_device_callback(this));
+ }
+ return m_device_callback.get();
+}//----------------------------------------------------------------------------------------------------
+void wallet2::on_button_request()
+{
+ if (0 != m_callback)
+ m_callback->on_button_request();
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::on_pin_request(epee::wipeable_string & pin)
+{
+ if (0 != m_callback)
+ m_callback->on_pin_request(pin);
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase)
+{
+ if (0 != m_callback)
+ m_callback->on_passphrase_request(on_device, passphrase);
+}
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::get_rpc_status(const std::string &s) const
+{
+ if (m_trusted_daemon)
+ return s;
+ return "<error>";
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method) const
+{
+ // no error
+ if (!status)
+ return;
+
+ MERROR("RPC error: " << method << ": status " << *status);
+
+ // empty string -> not connection
+ THROW_WALLET_EXCEPTION_IF(status->empty(), tools::error::no_connection_to_daemon, method);
+
+ THROW_WALLET_EXCEPTION_IF(*status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, method);
+ THROW_WALLET_EXCEPTION_IF(*status != CORE_RPC_STATUS_OK, tools::error::wallet_generic_rpc_error, method, m_trusted_daemon ? *status : "daemon error");
+}
+
}
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 680196f01..3b2dd6076 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -37,6 +37,7 @@
#include <boost/serialization/list.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/deque.hpp>
+#include <boost/thread/lock_guard.hpp>
#include <atomic>
#include "include_base_utils.h"
@@ -49,15 +50,18 @@
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_core/cryptonote_tx_utils.h"
#include "common/unordered_containers_boost_serialization.h"
+#include "common/util.h"
#include "crypto/chacha.h"
#include "crypto/hash.h"
#include "ringct/rctTypes.h"
#include "ringct/rctOps.h"
#include "checkpoints/checkpoints.h"
+#include "serialization/pair.h"
#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"
@@ -97,11 +101,26 @@ namespace tools
virtual void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
virtual void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
+ // Device callbacks
+ virtual void on_button_request() {}
+ virtual void on_pin_request(epee::wipeable_string & pin) {}
+ virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) {}
// Common callbacks
virtual void on_pool_tx_removed(const crypto::hash &txid) {}
virtual ~i_wallet2_callback() {}
};
+ class wallet_device_callback : public hw::i_device_callback
+ {
+ public:
+ wallet_device_callback(wallet2 * wallet): wallet(wallet) {};
+ void on_button_request() override;
+ void on_pin_request(epee::wipeable_string & pin) override;
+ void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) override;
+ private:
+ wallet2 * wallet;
+ };
+
struct tx_dust_policy
{
uint64_t dust_threshold;
@@ -153,6 +172,7 @@ namespace tools
{
friend class ::Serialization_portability_wallet_Test;
friend class wallet_keys_unlocker;
+ friend class wallet_device_callback;
public:
static constexpr const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30);
@@ -174,6 +194,7 @@ namespace tools
static bool has_testnet_option(const boost::program_options::variables_map& vm);
static bool has_stagenet_option(const boost::program_options::variables_map& vm);
static std::string device_name_option(const boost::program_options::variables_map& vm);
+ static std::string device_derivation_path_option(const boost::program_options::variables_map &vm);
static void init_options(boost::program_options::options_description& desc_params);
//! Uses stdin and stdout. Returns a wallet2 if no errors.
@@ -229,7 +250,7 @@ namespace tools
bool error;
boost::optional<cryptonote::subaddress_receive_info> received;
- tx_scan_info_t(): money_transfered(0), error(true) {}
+ tx_scan_info_t(): amount(0), money_transfered(0), error(true) {}
};
struct transfer_details
@@ -246,11 +267,13 @@ namespace tools
uint64_t m_amount;
bool m_rct;
bool m_key_image_known;
+ bool m_key_image_request; // view wallets: we want to request it; cold wallets: it was requested
size_t m_pk_index;
cryptonote::subaddress_index m_subaddr_index;
bool m_key_image_partial;
std::vector<rct::key> m_multisig_k;
std::vector<multisig_info> m_multisig_info; // one per other participant
+ std::vector<std::pair<uint64_t, crypto::hash>> m_uses;
bool is_rct() const { return m_rct; }
uint64_t amount() const { return m_amount; }
@@ -269,11 +292,13 @@ namespace tools
FIELD(m_amount)
FIELD(m_rct)
FIELD(m_key_image_known)
+ FIELD(m_key_image_request)
FIELD(m_pk_index)
FIELD(m_subaddr_index)
FIELD(m_key_image_partial)
FIELD(m_multisig_k)
FIELD(m_multisig_info)
+ FIELD(m_uses)
END_SERIALIZE()
};
@@ -371,7 +396,7 @@ namespace tools
struct multisig_sig
{
rct::rctSig sigs;
- crypto::public_key ignore;
+ std::unordered_set<crypto::public_key> ignore;
std::unordered_set<rct::key> used_L;
std::unordered_set<crypto::public_key> signing_keys;
rct::multisig_out msout;
@@ -416,13 +441,14 @@ namespace tools
struct unsigned_tx_set
{
std::vector<tx_construction_data> txes;
- wallet2::transfer_container transfers;
+ std::pair<size_t, wallet2::transfer_container> transfers;
};
struct signed_tx_set
{
std::vector<pending_tx> ptx;
std::vector<crypto::key_image> key_images;
+ std::unordered_map<crypto::public_key, crypto::key_image> tx_key_images;
};
struct multisig_tx_set
@@ -589,7 +615,7 @@ namespace tools
/*!
* \brief Finalizes creation of a multisig wallet
*/
- bool finalize_multisig(const epee::wipeable_string &password, std::unordered_set<crypto::public_key> pkeys, std::vector<crypto::public_key> signers);
+ bool finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers);
/*!
* Get a packaged multisig information string
*/
@@ -652,7 +678,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; }
@@ -707,7 +733,7 @@ namespace tools
bool is_deprecated() const;
void refresh(bool trusted_daemon);
void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched);
- void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money);
+ void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true);
bool refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& received_money, bool& ok);
void set_refresh_type(RefreshType refresh_type) { m_refresh_type = refresh_type; }
@@ -738,7 +764,7 @@ namespace tools
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx);
void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
- uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, rct::RangeProofType range_proof_type);
+ uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config);
void commit_tx(pending_tx& ptx_vector);
void commit_tx(std::vector<pending_tx>& ptx_vector);
@@ -764,6 +790,9 @@ namespace tools
std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices);
std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra);
std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra);
+ void cold_tx_aux_import(const std::vector<pending_tx>& ptx, const std::vector<std::string>& tx_device_aux);
+ void cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::vector<std::string> & tx_device_aux);
+ uint64_t cold_key_image_sync(uint64_t &spent, uint64_t &unspent);
bool load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func);
@@ -782,11 +811,12 @@ namespace tools
uint64_t get_blockchain_current_height() const { return m_light_wallet_blockchain_height ? m_light_wallet_blockchain_height : m_blockchain.size(); }
void rescan_spent();
- void rescan_blockchain(bool refresh = true);
+ void rescan_blockchain(bool hard, bool refresh = true);
bool is_transfer_unlocked(const transfer_details& td) const;
bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const;
uint64_t get_last_block_reward() const { return m_last_block_reward; }
+ uint64_t get_device_last_key_image_sync() const { return m_device_last_key_image_sync; }
template <class t_archive>
inline void serialize(t_archive &a, const unsigned int ver)
@@ -892,6 +922,15 @@ namespace tools
if(ver < 25)
return;
a & m_last_block_reward;
+ if(ver < 26)
+ return;
+ a & m_tx_device;
+ if(ver < 27)
+ return;
+ a & m_device_last_key_image_sync;
+ if(ver < 28)
+ return;
+ a & m_cold_key_images;
}
/*!
@@ -951,8 +990,12 @@ namespace tools
void ignore_fractional_outputs(bool value) { m_ignore_fractional_outputs = value; }
bool confirm_non_default_ring_size() const { return m_confirm_non_default_ring_size; }
void confirm_non_default_ring_size(bool always) { m_confirm_non_default_ring_size = always; }
+ bool track_uses() const { return m_track_uses; }
+ void track_uses(bool value) { m_track_uses = value; }
const std::string & device_name() const { return m_device_name; }
void device_name(const std::string & device_name) { m_device_name = device_name; }
+ const std::string & device_derivation_path() const { return m_device_derivation_path; }
+ void device_derivation_path(const std::string &device_derivation_path) { m_device_derivation_path = device_derivation_path; }
bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys);
@@ -1020,6 +1063,9 @@ namespace tools
void set_tx_note(const crypto::hash &txid, const std::string &note);
std::string get_tx_note(const crypto::hash &txid) const;
+ void set_tx_device_aux(const crypto::hash &txid, const std::string &aux);
+ std::string get_tx_device_aux(const crypto::hash &txid) const;
+
void set_description(const std::string &description);
std::string get_description() const;
@@ -1033,7 +1079,7 @@ namespace tools
* \param account_indices Indices of accounts.
* \param tag Tag's name. If empty, the accounts become untagged.
*/
- void set_account_tag(const std::set<uint32_t> account_indices, const std::string& tag);
+ void set_account_tag(const std::set<uint32_t> &account_indices, const std::string& tag);
/*!
* \brief Set the label of the given tag.
* \param tag Tag's name (which must be non-empty).
@@ -1061,9 +1107,9 @@ namespace tools
bool verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const;
// Import/Export wallet data
- std::vector<tools::wallet2::transfer_details> export_outputs() const;
+ std::pair<size_t, std::vector<tools::wallet2::transfer_details>> export_outputs() const;
std::string export_outputs_to_str() const;
- size_t import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs);
+ size_t import_outputs(const std::pair<size_t, std::vector<tools::wallet2::transfer_details>> &outputs);
size_t import_outputs_from_str(const std::string &outputs_st);
payment_container export_payments() const;
void import_payments(const payment_container &payments);
@@ -1071,9 +1117,11 @@ namespace tools
std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> export_blockchain() const;
void import_blockchain(const std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> &bc);
bool export_key_images(const std::string &filename) const;
- std::vector<std::pair<crypto::key_image, crypto::signature>> export_key_images() const;
- uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent, bool check_spent = true);
+ std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> export_key_images(bool all = false) const;
+ uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent = true);
uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent);
+ bool import_key_images(std::vector<crypto::key_image> key_images);
+ crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
void update_pool_state(bool refreshed = false);
void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes);
@@ -1179,6 +1227,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;
@@ -1187,6 +1240,8 @@ namespace tools
void set_tx_notify(const std::shared_ptr<tools::Notify> &notify) { m_tx_notify = notify; }
+ bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_height) const;
+
private:
/*!
* \brief Stores wallet information to wallet file.
@@ -1202,17 +1257,16 @@ namespace tools
* \param password Password of wallet file
*/
bool load_keys(const std::string& keys_file_name, const epee::wipeable_string& password);
- void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data);
- void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset);
+ void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
+ void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
void detach_blockchain(uint64_t height);
void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const;
- bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_height) const;
bool clear();
void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices);
void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes);
void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false);
void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error);
- void process_parsed_blocks(uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<parsed_block> &parsed_blocks, uint64_t& blocks_added);
+ void process_parsed_blocks(uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<parsed_block> &parsed_blocks, uint64_t& blocks_added, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers) const;
bool prepare_file_names(const std::string& file_path);
void process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height);
@@ -1236,13 +1290,12 @@ namespace tools
void set_unspent(size_t idx);
void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count);
bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const;
- crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const;
std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const;
- void scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs);
+ void scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs, bool pool);
void trim_hashchain();
crypto::key_image get_multisig_composite_key_image(size_t n) const;
- rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const;
+ rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const std::unordered_set<crypto::public_key> &ignore_set, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const;
rct::multisig_kLRki get_multisig_kLRki(size_t n, const rct::key &k) const;
rct::key get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const;
void update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n);
@@ -1253,6 +1306,9 @@ namespace tools
crypto::chacha_key get_ringdb_key();
void setup_keys(const epee::wipeable_string &password);
+ void register_devices();
+ hw::device& lookup_device(const std::string & device_descriptor);
+
bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution);
uint64_t get_segregation_fork_height() const;
@@ -1264,15 +1320,25 @@ namespace tools
std::unordered_set<crypto::public_key> &pkeys) const;
void cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const;
+ std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> create_output_tracker_cache() const;
void setup_new_blockchain();
void create_keys_file(const std::string &wallet_, bool watch_only, const epee::wipeable_string &password, bool create_address_file);
+ wallet_device_callback * get_device_callback();
+ void on_button_request();
+ void on_pin_request(epee::wipeable_string & pin);
+ void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase);
+
+ std::string get_rpc_status(const std::string &s) const;
+ void throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method) const;
+
cryptonote::account_base m_account;
boost::optional<epee::net_utils::http::login> m_daemon_login;
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;
@@ -1296,6 +1362,7 @@ namespace tools
uint64_t m_upper_transaction_weight_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value
const std::vector<std::vector<tools::wallet2::multisig_info>> *m_multisig_rescan_info;
const std::vector<std::vector<rct::key>> *m_multisig_rescan_k;
+ std::unordered_map<crypto::public_key, crypto::key_image> m_cold_key_images;
std::atomic<bool> m_run;
@@ -1341,11 +1408,17 @@ namespace tools
bool m_key_reuse_mitigation2;
uint64_t m_segregation_height;
bool m_ignore_fractional_outputs;
+ bool m_track_uses;
bool m_is_initialized;
NodeRPCProxy m_node_rpc_proxy;
std::unordered_set<crypto::hash> m_scanned_pool_txs[2];
size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor;
std::string m_device_name;
+ std::string m_device_derivation_path;
+ uint64_t m_device_last_key_image_sync;
+
+ // Aux transaction data from device
+ std::unordered_map<crypto::hash, std::string> m_tx_device;
// Light wallet
bool m_light_wallet; /* sends view key to daemon for scanning */
@@ -1368,17 +1441,24 @@ 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;
bool m_unattended;
+ bool m_devices_registered;
std::shared_ptr<tools::Notify> m_tx_notify;
+ std::unique_ptr<wallet_device_callback> m_device_callback;
};
}
-BOOST_CLASS_VERSION(tools::wallet2, 25)
-BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 9)
+BOOST_CLASS_VERSION(tools::wallet2, 28)
+BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 11)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1)
@@ -1389,7 +1469,7 @@ BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 6)
BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17)
BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0)
BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0)
-BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0)
+BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1)
BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 3)
BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3)
BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0)
@@ -1436,6 +1516,10 @@ namespace boost
x.m_multisig_k.clear();
x.m_multisig_info.clear();
}
+ if (ver < 10)
+ {
+ x.m_key_image_request = false;
+ }
}
template <class Archive>
@@ -1517,6 +1601,15 @@ namespace boost
a & x.m_multisig_info;
a & x.m_multisig_k;
a & x.m_key_image_partial;
+ if (ver < 10)
+ {
+ initialize_transfer_details(a, x, ver);
+ return;
+ }
+ a & x.m_key_image_request;
+ if (ver < 11)
+ return;
+ a & x.m_uses;
}
template <class Archive>
@@ -1713,6 +1806,9 @@ namespace boost
{
a & x.ptx;
a & x.key_images;
+ if (ver < 1)
+ return;
+ a & x.tx_key_images;
}
template <class Archive>
diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp
index 95a4e0ad6..b9d0a6a75 100644
--- a/src/wallet/wallet_args.cpp
+++ b/src/wallet/wallet_args.cpp
@@ -211,6 +211,14 @@ namespace wallet_args
Print(print) << boost::format(wallet_args::tr("Logging to %s")) % log_path;
+ const ssize_t lockable_memory = tools::get_lockable_memory();
+ if (lockable_memory >= 0 && lockable_memory < 256 * 4096) // 256 pages -> at least 256 secret keys and other such small/medium objects
+ Print(print) << tr("WARNING: You may not have a high enough lockable memory limit")
+#ifdef ELPP_OS_UNIX
+ << ", " << tr("see ulimit -l")
+#endif
+ ;
+
return {std::move(vm), should_terminate};
}
}
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index bc518d04a..35862bda1 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -72,6 +72,7 @@ namespace tools
// tx_parse_error
// get_tx_pool_error
// out_of_hashchain_bounds_error
+ // signature_check_failed
// transfer_error *
// get_outs_general_error
// not_enough_unlocked_money
@@ -218,6 +219,14 @@ namespace tools
}
};
//----------------------------------------------------------------------------------------------------
+ struct password_entry_failed : public wallet_runtime_error
+ {
+ explicit password_entry_failed(std::string&& loc, const std::string &msg = "Password entry failed")
+ : wallet_runtime_error(std::move(loc), msg)
+ {
+ }
+ };
+ //----------------------------------------------------------------------------------------------------
const char* const file_error_messages[] = {
"file already exists",
"file not found",
@@ -418,6 +427,14 @@ namespace tools
std::string to_string() const { return refresh_error::to_string(); }
};
//----------------------------------------------------------------------------------------------------
+ struct signature_check_failed : public wallet_logic_error
+ {
+ explicit signature_check_failed(std::string&& loc, const std::string& message)
+ : wallet_logic_error(std::move(loc), "Signature check failed " + message)
+ {
+ }
+ };
+ //----------------------------------------------------------------------------------------------------
struct transfer_error : public wallet_logic_error
{
protected:
@@ -804,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)
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 1b63d65b6..5dc6ac0e8 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -644,9 +644,11 @@ namespace tools
return false;
}
+ de.original = it->address;
de.addr = info.address;
de.is_subaddress = info.is_subaddress;
de.amount = it->amount;
+ de.is_integrated = info.has_payment_id;
dsts.push_back(de);
if (info.has_payment_id)
@@ -981,6 +983,12 @@ namespace tools
for (auto &ptx: ptxs)
{
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+ if (req.get_tx_keys)
+ {
+ res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
+ for (const crypto::secret_key& additional_tx_key : ptx.additional_tx_keys)
+ res.tx_key_list.back() += epee::string_tools::pod_to_hex(additional_tx_key);
+ }
}
if (req.export_raw)
@@ -994,6 +1002,171 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_describe_transfer(const wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ if (m_restricted)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DENIED;
+ er.message = "Command unavailable in restricted mode.";
+ return false;
+ }
+ if (m_wallet->key_on_device())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "command not supported by HW wallet";
+ return false;
+ }
+ if(m_wallet->watch_only())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
+ er.message = "command not supported by watch-only wallet";
+ return false;
+ }
+
+ tools::wallet2::unsigned_tx_set exported_txs;
+ try
+ {
+ cryptonote::blobdata blob;
+ if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
+ er.message = "Failed to parse hex.";
+ return false;
+ }
+ if(!m_wallet->parse_unsigned_tx_from_str(blob, exported_txs))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "cannot load unsigned_txset";
+ return false;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "failed to parse unsigned transfers: " + std::string(e.what());
+ return false;
+ }
+
+ std::vector<tools::wallet2::pending_tx> ptx;
+ try
+ {
+ // gather info to ask the user
+ std::unordered_map<cryptonote::account_public_address, std::pair<std::string, uint64_t>> dests;
+ int first_known_non_zero_change_index = -1;
+ for (size_t n = 0; n < exported_txs.txes.size(); ++n)
+ {
+ const tools::wallet2::tx_construction_data &cd = exported_txs.txes[n];
+ res.desc.push_back({0, 0, std::numeric_limits<uint32_t>::max(), 0, {}, "", 0, "", 0, 0, ""});
+ wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::transfer_description &desc = res.desc.back();
+
+ std::vector<cryptonote::tx_extra_field> tx_extra_fields;
+ bool has_encrypted_payment_id = false;
+ crypto::hash8 payment_id8 = crypto::null_hash8;
+ if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields))
+ {
+ cryptonote::tx_extra_nonce extra_nonce;
+ if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
+ {
+ crypto::hash payment_id;
+ if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
+ {
+ desc.payment_id = epee::string_tools::pod_to_hex(payment_id8);
+ has_encrypted_payment_id = true;
+ }
+ else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
+ {
+ desc.payment_id = epee::string_tools::pod_to_hex(payment_id);
+ }
+ }
+ }
+
+ for (size_t s = 0; s < cd.sources.size(); ++s)
+ {
+ desc.amount_in += cd.sources[s].amount;
+ size_t ring_size = cd.sources[s].outputs.size();
+ if (ring_size < desc.ring_size)
+ desc.ring_size = ring_size;
+ }
+ for (size_t d = 0; d < cd.splitted_dsts.size(); ++d)
+ {
+ const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d];
+ std::string address = cryptonote::get_account_address_as_str(m_wallet->nettype(), entry.is_subaddress, entry.addr);
+ if (has_encrypted_payment_id && !entry.is_subaddress)
+ address = cryptonote::get_account_integrated_address_as_str(m_wallet->nettype(), entry.addr, payment_id8);
+ auto i = dests.find(entry.addr);
+ if (i == dests.end())
+ dests.insert(std::make_pair(entry.addr, std::make_pair(address, entry.amount)));
+ else
+ i->second.second += entry.amount;
+ desc.amount_out += entry.amount;
+ }
+ if (cd.change_dts.amount > 0)
+ {
+ auto it = dests.find(cd.change_dts.addr);
+ if (it == dests.end())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "Claimed change does not go to a paid address";
+ return false;
+ }
+ if (it->second.second < cd.change_dts.amount)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "Claimed change is larger than payment to the change address";
+ return false;
+ }
+ if (cd.change_dts.amount > 0)
+ {
+ if (first_known_non_zero_change_index == -1)
+ first_known_non_zero_change_index = n;
+ const tools::wallet2::tx_construction_data &cdn = exported_txs.txes[first_known_non_zero_change_index];
+ if (memcmp(&cd.change_dts.addr, &cdn.change_dts.addr, sizeof(cd.change_dts.addr)))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "Change goes to more than one address";
+ return false;
+ }
+ }
+ desc.change_amount += cd.change_dts.amount;
+ it->second.second -= cd.change_dts.amount;
+ if (it->second.second == 0)
+ dests.erase(cd.change_dts.addr);
+ }
+
+ size_t n_dummy_outputs = 0;
+ for (auto i = dests.begin(); i != dests.end(); )
+ {
+ if (i->second.second > 0)
+ {
+ desc.recipients.push_back({i->second.first, i->second.second});
+ }
+ else
+ ++desc.dummy_outputs;
+ ++i;
+ }
+
+ if (desc.change_amount > 0)
+ {
+ const tools::wallet2::tx_construction_data &cd0 = exported_txs.txes[0];
+ desc.change_address = get_account_address_as_str(m_wallet->nettype(), cd0.subaddr_account > 0, cd0.change_dts.addr);
+ }
+
+ desc.fee = desc.amount_in - desc.amount_out;
+ desc.unlock_time = cd.unlock_time;
+ desc.extra = epee::to_hex::string({cd.extra.data(), cd.extra.size()});
+ }
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "failed to parse unsigned transfers";
+ return false;
+ }
+
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
@@ -1575,10 +1748,42 @@ namespace tools
if (req.key_type.compare("mnemonic") == 0)
{
epee::wipeable_string seed;
- if (!m_wallet->get_seed(seed))
+ bool ready;
+ if (m_wallet->multisig(&ready))
{
+ if (!ready)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+ er.message = "This wallet is multisig, but not yet finalized";
+ return false;
+ }
+ if (!m_wallet->get_multisig_seed(seed))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to get multisig seed.";
+ return false;
+ }
+ }
+ else
+ {
+ if (m_wallet->watch_only())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
+ er.message = "The wallet is watch-only. Cannot display seed.";
+ return false;
+ }
+ if (!m_wallet->is_deterministic())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC;
er.message = "The wallet is non-deterministic. Cannot display seed.";
return false;
+ }
+ if (!m_wallet->get_seed(seed))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to get seed.";
+ return false;
+ }
}
res.key = std::string(seed.data(), seed.size()); // send to the network, then wipe RAM :D
}
@@ -1613,7 +1818,7 @@ namespace tools
try
{
- m_wallet->rescan_blockchain();
+ m_wallet->rescan_blockchain(req.hard);
}
catch (const std::exception& e)
{
@@ -2292,12 +2497,13 @@ namespace tools
if (!m_wallet) return not_open(er);
try
{
- std::vector<std::pair<crypto::key_image, crypto::signature>> ski = m_wallet->export_key_images();
- res.signed_key_images.resize(ski.size());
- for (size_t n = 0; n < ski.size(); ++n)
+ std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = m_wallet->export_key_images(req.all);
+ res.offset = ski.first;
+ res.signed_key_images.resize(ski.second.size());
+ for (size_t n = 0; n < ski.second.size(); ++n)
{
- res.signed_key_images[n].key_image = epee::string_tools::pod_to_hex(ski[n].first);
- res.signed_key_images[n].signature = epee::string_tools::pod_to_hex(ski[n].second);
+ res.signed_key_images[n].key_image = epee::string_tools::pod_to_hex(ski.second[n].first);
+ res.signed_key_images[n].signature = epee::string_tools::pod_to_hex(ski.second[n].second);
}
}
@@ -2350,7 +2556,7 @@ namespace tools
ski[n].second = *reinterpret_cast<const crypto::signature*>(bd.data());
}
uint64_t spent = 0, unspent = 0;
- uint64_t height = m_wallet->import_key_images(ski, spent, unspent);
+ uint64_t height = m_wallet->import_key_images(ski, req.offset, spent, unspent);
res.spent = spent;
res.unspent = unspent;
res.height = height;
@@ -2687,7 +2893,8 @@ namespace tools
cryptonote::COMMAND_RPC_GET_HEIGHT::response hres;
hres.height = 0;
bool r = wal->invoke_http_json("/getheight", hreq, hres);
- wal->set_refresh_from_block_height(hres.height);
+ if (r)
+ wal->set_refresh_from_block_height(hres.height);
crypto::secret_key dummy_key;
try {
wal->generate(wallet_file, req.password, dummy_key, false, false);
@@ -2905,6 +3112,11 @@ namespace tools
er.code = WALLET_RPC_ERROR_CODE_ADDRESS_INDEX_OUT_OF_BOUNDS;
er.message = e.what();
}
+ catch (const error::signature_check_failed& e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_WRONG_SIGNATURE;
+ er.message = e.what();
+ }
catch (const std::exception& e)
{
er.code = default_error_code;
@@ -2917,6 +3129,200 @@ namespace tools
}
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_restore_deterministic_wallet(const wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET::request &req, wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET::response &res, epee::json_rpc::error &er)
+ {
+ if (m_wallet_dir.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NO_WALLET_DIR;
+ er.message = "No wallet dir configured";
+ return false;
+ }
+
+ // early check for mandatory fields
+ if (req.filename.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "field 'filename' is mandatory. Please provide a filename to save the restored wallet to.";
+ return false;
+ }
+ if (req.seed.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "field 'seed' is mandatory. Please provide a seed you want to restore from.";
+ return false;
+ }
+
+ namespace po = boost::program_options;
+ po::variables_map vm2;
+ const char *ptr = strchr(req.filename.c_str(), '/');
+ #ifdef _WIN32
+ if (!ptr)
+ ptr = strchr(req.filename.c_str(), '\\');
+ if (!ptr)
+ ptr = strchr(req.filename.c_str(), ':');
+ #endif
+ if (ptr)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Invalid filename";
+ return false;
+ }
+ std::string wallet_file = m_wallet_dir + "/" + req.filename;
+ // check if wallet file already exists
+ if (!wallet_file.empty())
+ {
+ try
+ {
+ boost::system::error_code ignored_ec;
+ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(wallet_file, ignored_ec), error::file_exists, wallet_file);
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Wallet already exists.";
+ return false;
+ }
+ }
+ crypto::secret_key recovery_key;
+ std::string old_language;
+
+ // check the given seed
+ {
+ if (!crypto::ElectrumWords::words_to_bytes(req.seed, recovery_key, old_language))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Electrum-style word list failed verification";
+ return false;
+ }
+ }
+
+ // process seed_offset if given
+ {
+ if (!req.seed_offset.empty())
+ {
+ recovery_key = cryptonote::decrypt_key(recovery_key, req.seed_offset);
+ }
+ }
+ {
+ po::options_description desc("dummy");
+ const command_line::arg_descriptor<std::string, true> arg_password = {"password", "password"};
+ const char *argv[4];
+ int argc = 3;
+ argv[0] = "wallet-rpc";
+ argv[1] = "--password";
+ argv[2] = req.password.c_str();
+ argv[3] = NULL;
+ vm2 = *m_vm;
+ command_line::add_arg(desc, arg_password);
+ po::store(po::parse_command_line(argc, argv, desc), vm2);
+ }
+
+ auto rc = tools::wallet2::make_new(vm2, true, nullptr);
+ std::unique_ptr<wallet2> wal;
+ wal = std::move(rc.first);
+ if (!wal)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to create wallet";
+ return false;
+ }
+
+ epee::wipeable_string password = rc.second.password();
+
+ bool was_deprecated_wallet = ((old_language == crypto::ElectrumWords::old_language_name) ||
+ crypto::ElectrumWords::get_is_old_style_seed(req.seed));
+
+ std::string mnemonic_language = old_language;
+ if (was_deprecated_wallet)
+ {
+ // The user had used an older version of the wallet with old style mnemonics.
+ res.was_deprecated = true;
+ }
+
+ if (old_language == crypto::ElectrumWords::old_language_name)
+ {
+ if (req.language.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Wallet was using the old seed language. You need to specify a new seed language.";
+ return false;
+ }
+ std::vector<std::string> language_list;
+ std::vector<std::string> language_list_en;
+ crypto::ElectrumWords::get_language_list(language_list);
+ crypto::ElectrumWords::get_language_list(language_list_en, true);
+ if (std::find(language_list.begin(), language_list.end(), req.language) == language_list.end() &&
+ std::find(language_list_en.begin(), language_list_en.end(), req.language) == language_list_en.end())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Wallet was using the old seed language, and the specified new seed language is invalid.";
+ return false;
+ }
+ mnemonic_language = req.language;
+ }
+
+ wal->set_seed_language(mnemonic_language);
+
+ crypto::secret_key recovery_val;
+ try
+ {
+ recovery_val = wal->generate(wallet_file, std::move(rc.second).password(), recovery_key, true, false, false);
+ MINFO("Wallet has been restored.\n");
+ }
+ catch (const std::exception &e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+
+ // // Convert the secret key back to seed
+ epee::wipeable_string electrum_words;
+ if (!crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, mnemonic_language))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to encode seed";
+ return false;
+ }
+ res.seed = electrum_words.data();
+
+ if (!wal)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to generate wallet";
+ return false;
+ }
+
+ // set blockheight if given
+ try
+ {
+ wal->set_refresh_from_block_height(req.restore_height);
+ wal->rewrite(wallet_file, password);
+ }
+ catch (const std::exception &e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+
+ if (m_wallet)
+ {
+ try
+ {
+ m_wallet->store();
+ }
+ catch (const std::exception &e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+ delete m_wallet;
+ }
+ m_wallet = wal.release();
+ res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype());
+ res.info = "Wallet has been restored successfully.";
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index 35ea11902..abbbe82c5 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -87,6 +87,7 @@ namespace tools
MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER)
MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT)
MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER)
+ MAP_JON_RPC_WE("describe_transfer", on_describe_transfer, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER)
MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER)
MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST)
MAP_JON_RPC_WE("sweep_unmixable", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST)
@@ -136,6 +137,7 @@ namespace tools
MAP_JON_RPC_WE("open_wallet", on_open_wallet, wallet_rpc::COMMAND_RPC_OPEN_WALLET)
MAP_JON_RPC_WE("close_wallet", on_close_wallet, wallet_rpc::COMMAND_RPC_CLOSE_WALLET)
MAP_JON_RPC_WE("change_wallet_password", on_change_wallet_password, wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD)
+ MAP_JON_RPC_WE("restore_deterministic_wallet", on_restore_deterministic_wallet, wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET)
MAP_JON_RPC_WE("is_multisig", on_is_multisig, wallet_rpc::COMMAND_RPC_IS_MULTISIG)
MAP_JON_RPC_WE("prepare_multisig", on_prepare_multisig, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG)
MAP_JON_RPC_WE("make_multisig", on_make_multisig, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG)
@@ -167,6 +169,7 @@ namespace tools
bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er);
bool on_sign_transfer(const wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::response& res, epee::json_rpc::error& er);
+ bool on_describe_transfer(const wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er);
bool on_sweep_all(const wallet_rpc::COMMAND_RPC_SWEEP_ALL::request& req, wallet_rpc::COMMAND_RPC_SWEEP_ALL::response& res, epee::json_rpc::error& er);
@@ -214,6 +217,7 @@ namespace tools
bool on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er);
bool on_close_wallet(const wallet_rpc::COMMAND_RPC_CLOSE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CLOSE_WALLET::response& res, epee::json_rpc::error& er);
bool on_change_wallet_password(const wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD::request& req, wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD::response& res, epee::json_rpc::error& er);
+ bool on_restore_deterministic_wallet(const wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET::request& req, wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET::response& res, epee::json_rpc::error& er);
bool on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er);
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 2377b69e3..f0c1a4e9d 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -47,7 +47,7 @@
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define WALLET_RPC_VERSION_MAJOR 1
-#define WALLET_RPC_VERSION_MINOR 4
+#define WALLET_RPC_VERSION_MINOR 7
#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR)
namespace tools
@@ -531,16 +531,79 @@ namespace wallet_rpc
};
};
+ struct COMMAND_RPC_DESCRIBE_TRANSFER
+ {
+ struct recipient
+ {
+ std::string address;
+ uint64_t amount;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(amount)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct transfer_description
+ {
+ uint64_t amount_in;
+ uint64_t amount_out;
+ uint32_t ring_size;
+ uint64_t unlock_time;
+ std::list<recipient> recipients;
+ std::string payment_id;
+ uint64_t change_amount;
+ std::string change_address;
+ uint64_t fee;
+ uint32_t dummy_outputs;
+ std::string extra;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount_in)
+ KV_SERIALIZE(amount_out)
+ KV_SERIALIZE(ring_size)
+ KV_SERIALIZE(unlock_time)
+ KV_SERIALIZE(recipients)
+ KV_SERIALIZE(payment_id)
+ KV_SERIALIZE(change_amount)
+ KV_SERIALIZE(change_address)
+ KV_SERIALIZE(fee)
+ KV_SERIALIZE(dummy_outputs)
+ KV_SERIALIZE(extra)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct request
+ {
+ std::string unsigned_txset;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(unsigned_txset)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::list<transfer_description> desc;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(desc)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
struct COMMAND_RPC_SIGN_TRANSFER
{
struct request
{
std::string unsigned_txset;
bool export_raw;
+ bool get_tx_keys;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(unsigned_txset)
KV_SERIALIZE_OPT(export_raw, false)
+ KV_SERIALIZE_OPT(get_tx_keys, false)
END_KV_SERIALIZE_MAP()
};
@@ -549,11 +612,13 @@ namespace wallet_rpc
std::string signed_txset;
std::list<std::string> tx_hash_list;
std::list<std::string> tx_raw_list;
+ std::list<std::string> tx_key_list;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(signed_txset)
KV_SERIALIZE(tx_hash_list)
KV_SERIALIZE(tx_raw_list)
+ KV_SERIALIZE(tx_key_list)
END_KV_SERIALIZE_MAP()
};
};
@@ -992,7 +1057,10 @@ namespace wallet_rpc
{
struct request
{
+ bool hard;
+
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(hard, false);
END_KV_SERIALIZE_MAP()
};
@@ -1497,7 +1565,10 @@ namespace wallet_rpc
{
struct request
{
+ bool all;
+
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(all, false);
END_KV_SERIALIZE_MAP()
};
@@ -1514,9 +1585,11 @@ namespace wallet_rpc
struct response
{
+ uint32_t offset;
std::vector<signed_key_image> signed_key_images;
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(offset);
KV_SERIALIZE(signed_key_images);
END_KV_SERIALIZE_MAP()
};
@@ -1537,9 +1610,11 @@ namespace wallet_rpc
struct request
{
+ uint32_t offset;
std::vector<signed_key_image> signed_key_images;
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(offset, (uint32_t)0);
KV_SERIALIZE(signed_key_images);
END_KV_SERIALIZE_MAP()
};
@@ -1859,6 +1934,43 @@ namespace wallet_rpc
};
};
+ struct COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET
+ {
+ struct request
+ {
+ uint64_t restore_height;
+ std::string filename;
+ std::string seed;
+ std::string seed_offset;
+ std::string password;
+ std::string language;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(restore_height, (uint64_t)0)
+ KV_SERIALIZE(filename)
+ KV_SERIALIZE(seed)
+ KV_SERIALIZE(seed_offset)
+ KV_SERIALIZE(password)
+ KV_SERIALIZE(language)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::string address;
+ std::string seed;
+ std::string info;
+ bool was_deprecated;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(seed)
+ KV_SERIALIZE(info)
+ KV_SERIALIZE(was_deprecated)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
struct COMMAND_RPC_IS_MULTISIG
{
struct request
diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h
index f127ae240..9b3a2847d 100644
--- a/src/wallet/wallet_rpc_server_error_codes.h
+++ b/src/wallet/wallet_rpc_server_error_codes.h
@@ -73,3 +73,4 @@
#define WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA -40
#define WALLET_RPC_ERROR_CODE_SIGNED_SUBMISSION -41
#define WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED -42
+#define WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC -43