aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cryptonote_core/tx_pool.cpp8
-rw-r--r--src/p2p/net_node.inl8
-rw-r--r--src/ringct/rctSigs.cpp13
-rw-r--r--src/rpc/core_rpc_server.cpp21
-rw-r--r--src/serialization/list.h58
-rw-r--r--src/serialization/serialization.h7
-rw-r--r--src/simplewallet/simplewallet.cpp55
-rw-r--r--src/simplewallet/simplewallet.h1
-rw-r--r--src/wallet/ringdb.cpp39
-rw-r--r--src/wallet/ringdb.h2
-rw-r--r--src/wallet/wallet2.cpp133
-rw-r--r--src/wallet/wallet2.h15
-rw-r--r--src/wallet/wallet_rpc_server.cpp33
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h3
-rw-r--r--src/wallet/wallet_rpc_server_error_codes.h1
15 files changed, 274 insertions, 123 deletions
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index c27261860..a68da0e62 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -97,9 +97,9 @@ namespace cryptonote
constexpr const std::chrono::seconds forward_delay_average{CRYPTONOTE_FORWARD_DELAY_AVERAGE};
// a kind of increasing backoff within min/max bounds
- uint64_t get_relay_delay(time_t now, time_t received)
+ uint64_t get_relay_delay(time_t last_relay, time_t received)
{
- time_t d = (now - received + MIN_RELAY_TIME) / MIN_RELAY_TIME * MIN_RELAY_TIME;
+ time_t d = (last_relay - received + MIN_RELAY_TIME) / MIN_RELAY_TIME * MIN_RELAY_TIME;
if (d > MAX_RELAY_TIME)
d = MAX_RELAY_TIME;
return d;
@@ -779,7 +779,7 @@ namespace cryptonote
case relay_method::local:
case relay_method::fluff:
case relay_method::block:
- if (now - meta.last_relayed_time <= get_relay_delay(now, meta.receive_time))
+ if (now - meta.last_relayed_time <= get_relay_delay(meta.last_relayed_time, meta.receive_time))
return true; // continue to next tx
break;
}
@@ -812,7 +812,7 @@ namespace cryptonote
function is only called every ~2 minutes, so this resetting should be
unnecessary, but is primarily a precaution against potential changes
to the callback routines. */
- elem.second.last_relayed_time = now + get_relay_delay(now, elem.second.receive_time);
+ elem.second.last_relayed_time = now + get_relay_delay(elem.second.last_relayed_time, elem.second.receive_time);
m_blockchain.update_txpool_tx(elem.first, elem.second);
}
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 872c1a633..f33ce977d 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -2464,8 +2464,12 @@ namespace nodetool
const epee::net_utils::zone zone_type = context.m_remote_address.get_zone();
network_zone& zone = m_network_zones.at(zone_type);
+ //will add self to peerlist if in same zone as outgoing later in this function
+ const bool outgoing_to_same_zone = !context.m_is_income && zone.m_our_address.get_zone() == zone_type;
+ const uint32_t max_peerlist_size = P2P_DEFAULT_PEERS_IN_HANDSHAKE - (outgoing_to_same_zone ? 1 : 0);
+
std::vector<peerlist_entry> local_peerlist_new;
- zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, P2P_DEFAULT_PEERS_IN_HANDSHAKE);
+ zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, max_peerlist_size);
//only include out peers we did not already send
rsp.local_peerlist_new.reserve(local_peerlist_new.size());
@@ -2485,7 +2489,7 @@ namespace nodetool
etc., because someone could give faulty addresses over Tor/I2P to get the
real peer with that identity banned/blacklisted. */
- if(!context.m_is_income && zone.m_our_address.get_zone() == zone_type)
+ if(outgoing_to_same_zone)
rsp.local_peerlist_new.push_back(peerlist_entry{zone.m_our_address, zone.m_config.m_peer_id, std::time(nullptr)});
LOG_DEBUG_CC(context, "COMMAND_TIMED_SYNC");
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index 3e85f60ce..bd67778ec 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -79,6 +79,7 @@ namespace
return rct::Bulletproof{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I), I, I, I};
}
+
rct::BulletproofPlus make_dummy_bulletproof_plus(const std::vector<uint64_t> &outamounts, rct::keyV &C, rct::keyV &masks)
{
const size_t n_outs = outamounts.size();
@@ -109,6 +110,13 @@ namespace
return rct::BulletproofPlus{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I)};
}
+
+ rct::clsag make_dummy_clsag(size_t ring_size)
+ {
+ const rct::key I = rct::identity();
+ const size_t n_scalars = ring_size;
+ return rct::clsag{rct::keyV(n_scalars, I), I, I, I};
+ }
}
namespace rct {
@@ -1323,7 +1331,10 @@ namespace rct {
{
if (is_rct_clsag(rv.type))
{
- rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, msout ? &msout->mu_p[i] : NULL, index[i], hwdev);
+ if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE)
+ rv.p.CLSAGs[i] = make_dummy_clsag(rv.mixRing[i].size());
+ else
+ rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, msout ? &msout->mu_p[i] : NULL, index[i], hwdev);
}
else
{
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 869040657..0fe28465f 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -350,12 +350,23 @@ namespace cryptonote
bool store_ssl_key = !restricted && rpc_config->ssl_options && rpc_config->ssl_options.auth.certificate_path.empty();
const auto ssl_base_path = (boost::filesystem::path{data_dir} / "rpc_ssl").string();
- if (store_ssl_key && boost::filesystem::exists(ssl_base_path + ".crt"))
+ const bool ssl_cert_file_exists = boost::filesystem::exists(ssl_base_path + ".crt");
+ const bool ssl_pkey_file_exists = boost::filesystem::exists(ssl_base_path + ".key");
+ if (store_ssl_key)
{
- // load key from previous run, password prompted by OpenSSL
- store_ssl_key = false;
- rpc_config->ssl_options.auth =
- epee::net_utils::ssl_authentication_t{ssl_base_path + ".key", ssl_base_path + ".crt"};
+ // .key files are often given different read permissions as their corresponding .crt files.
+ // Consequently, sometimes the .key file wont't get copied, while the .crt file will.
+ if (ssl_cert_file_exists != ssl_pkey_file_exists)
+ {
+ MFATAL("Certificate (.crt) and private key (.key) files must both exist or both not exist at path: " << ssl_base_path);
+ return false;
+ }
+ else if (ssl_cert_file_exists) { // and ssl_pkey_file_exists
+ // load key from previous run, password prompted by OpenSSL
+ store_ssl_key = false;
+ rpc_config->ssl_options.auth =
+ epee::net_utils::ssl_authentication_t{ssl_base_path + ".key", ssl_base_path + ".crt"};
+ }
}
auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); };
diff --git a/src/serialization/list.h b/src/serialization/list.h
deleted file mode 100644
index 16ee1b034..000000000
--- a/src/serialization/list.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2014-2022, 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.
-//
-// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
-
-#pragma once
-
-#include <list>
-
-template <template <bool> class Archive, class T>
-bool do_serialize(Archive<false> &ar, std::list<T> &v);
-template <template <bool> class Archive, class T>
-bool do_serialize(Archive<true> &ar, std::list<T> &v);
-
-namespace serialization
-{
- namespace detail
- {
- template <typename T>
- void do_add(std::list<T> &c, T &&e)
- {
- c.emplace_back(std::forward<T>(e));
- }
- }
-}
-
-#include "serialization.h"
-
-template <template <bool> class Archive, class T>
-bool do_serialize(Archive<false> &ar, std::list<T> &v) { return do_serialize_container(ar, v); }
-template <template <bool> class Archive, class T>
-bool do_serialize(Archive<true> &ar, std::list<T> &v) { return do_serialize_container(ar, v); }
-
diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h
index 590b84428..381d29cfc 100644
--- a/src/serialization/serialization.h
+++ b/src/serialization/serialization.h
@@ -132,13 +132,6 @@ inline bool do_serialize(Archive &ar, bool &v)
return true;
}
-// Never used in the code base
-// #ifndef __GNUC__
-// #ifndef constexpr
-// #define constexpr
-// #endif
-// #endif
-
/* the following add a trait to a set and define the serialization DSL*/
/*! \macro BLOB_SERIALIZER
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index d3e40ab74..05d10b734 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -142,6 +142,19 @@ typedef cryptonote::simple_wallet sw;
#define MIN_PAYMENT_RATE 0.01f // per hash
#define MAX_MNEW_ADDRESSES 1000
+#define CHECK_MULTISIG_ENABLED() \
+ do \
+ { \
+ if (!m_wallet->is_multisig_enabled()) \
+ { \
+ fail_msg_writer() << tr("Multisig is disabled."); \
+ fail_msg_writer() << tr("Multisig is an experimental feature and may have bugs. Things that could go wrong include: funds sent to a multisig wallet can't be spent at all, can only be spent with the participation of a malicious group member, or can be stolen by a malicious group member."); \
+ fail_msg_writer() << tr("You can enable it with:"); \
+ fail_msg_writer() << tr(" set enable-multisig-experimental 1"); \
+ return false; \
+ } \
+ } while(0)
+
enum TransferType {
Transfer,
TransferLocked,
@@ -986,12 +999,14 @@ bool simple_wallet::print_fee_info(const std::vector<std::string> &args/* = std:
bool simple_wallet::prepare_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
prepare_multisig_main(args, false);
return true;
}
bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
if (m_wallet->key_on_device())
{
fail_msg_writer() << tr("command not supported by HW wallet");
@@ -1031,12 +1046,14 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args,
bool simple_wallet::make_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
make_multisig_main(args, false);
return true;
}
bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
if (m_wallet->key_on_device())
{
fail_msg_writer() << tr("command not supported by HW wallet");
@@ -1121,11 +1138,13 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo
bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
exchange_multisig_keys_main(args, false);
return true;
}
bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms) {
+ CHECK_MULTISIG_ENABLED();
bool ready;
if (m_wallet->key_on_device())
{
@@ -1189,12 +1208,14 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &
bool simple_wallet::export_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
export_multisig_main(args, false);
return true;
}
bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
bool ready;
if (m_wallet->key_on_device())
{
@@ -1254,12 +1275,14 @@ bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, b
bool simple_wallet::import_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
import_multisig_main(args, false);
return true;
}
bool simple_wallet::import_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
bool ready;
uint32_t threshold, total;
if (m_wallet->key_on_device())
@@ -1349,12 +1372,14 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs)
bool simple_wallet::sign_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
sign_multisig_main(args, false);
return true;
}
bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
bool ready;
if (m_wallet->key_on_device())
{
@@ -1464,12 +1489,14 @@ bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, boo
bool simple_wallet::submit_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
submit_multisig_main(args, false);
return true;
}
bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
bool ready;
uint32_t threshold;
if (m_wallet->key_on_device())
@@ -1551,6 +1578,7 @@ bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, b
bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
bool ready;
uint32_t threshold;
if (m_wallet->key_on_device())
@@ -3074,6 +3102,25 @@ bool simple_wallet::set_load_deprecated_formats(const std::vector<std::string> &
return true;
}
+bool simple_wallet::set_enable_multisig(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ if (args.size() < 2)
+ {
+ fail_msg_writer() << tr("Value not specified");
+ return true;
+ }
+
+ const auto pwd_container = get_and_verify_password();
+ if (pwd_container)
+ {
+ parse_bool_and_use(args[1], [&](bool r) {
+ m_wallet->enable_multisig(r);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ });
+ }
+ return true;
+}
+
bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if(args.empty())
@@ -3391,6 +3438,8 @@ simple_wallet::simple_wallet()
" The RPC payment credits balance to target (0 for default).\n "
"show-wallet-name-when-locked <1|0>\n "
" Set this if you would like to display the wallet name when locked.\n "
+ "enable-multisig-experimental <1|0>\n "
+ " Set this to allow multisig commands. Multisig may currently be exploitable if parties do not trust each other.\n "
"inactivity-lock-timeout <unsigned int>\n "
" How many seconds to wait before locking the wallet (0 to disable)."));
m_cmd_binder.set_handler("encrypted_seed",
@@ -3806,6 +3855,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
success_msg_writer() << "auto-mine-for-rpc-payment-threshold = " << m_wallet->auto_mine_for_rpc_payment_threshold();
success_msg_writer() << "credits-target = " << m_wallet->credits_target();
success_msg_writer() << "load-deprecated-formats = " << m_wallet->load_deprecated_formats();
+ success_msg_writer() << "enable-multisig-experimental = " << m_wallet->is_multisig_enabled();
return true;
}
else
@@ -3872,6 +3922,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
CHECK_SIMPLE_VARIABLE("persistent-rpc-client-id", set_persistent_rpc_client_id, tr("0 or 1"));
CHECK_SIMPLE_VARIABLE("auto-mine-for-rpc-payment-threshold", set_auto_mine_for_rpc_payment_threshold, tr("floating point >= 0"));
CHECK_SIMPLE_VARIABLE("credits-target", set_credits_target, tr("unsigned integer"));
+ CHECK_SIMPLE_VARIABLE("enable-multisig-experimental", set_enable_multisig, tr("0 or 1"));
}
fail_msg_writer() << tr("set: unrecognized argument(s)");
return true;
@@ -6980,6 +7031,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
// actually commit the transactions
if (m_wallet->multisig())
{
+ CHECK_MULTISIG_ENABLED();
bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx");
if (!r)
{
@@ -7284,6 +7336,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co
// actually commit the transactions
if (m_wallet->multisig())
{
+ CHECK_MULTISIG_ENABLED();
bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx");
if (!r)
{
@@ -7518,6 +7571,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
// actually commit the transactions
if (m_wallet->multisig())
{
+ CHECK_MULTISIG_ENABLED();
bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx");
if (!r)
{
@@ -11549,6 +11603,7 @@ void simple_wallet::mms_auto_config(const std::vector<std::string> &args)
bool simple_wallet::mms(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
try
{
m_wallet->get_multisig_wallet_state();
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 4c005c53a..6c4ddd4e7 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -153,6 +153,7 @@ namespace cryptonote
bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>());
bool set_export_format(const std::vector<std::string> &args = std::vector<std::string>());
bool set_load_deprecated_formats(const std::vector<std::string> &args = std::vector<std::string>());
+ bool set_enable_multisig(const std::vector<std::string> &args = std::vector<std::string>());
bool set_persistent_rpc_client_id(const std::vector<std::string> &args = std::vector<std::string>());
bool set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args = std::vector<std::string>());
bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>());
diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp
index bfbbbaeb7..7e4f12f5b 100644
--- a/src/wallet/ringdb.cpp
+++ b/src/wallet/ringdb.cpp
@@ -344,12 +344,15 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote
return remove_rings(chacha_key, key_images);
}
-bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs)
+bool ringdb::get_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &all_outs)
{
MDB_txn *txn;
int dbr;
bool tx_active = false;
+ all_outs.clear();
+ all_outs.reserve(key_images.size());
+
dbr = resize_env(env, filename.c_str(), 0);
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_begin(env, NULL, 0, &txn);
@@ -357,6 +360,10 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im
epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);});
tx_active = true;
+ for (size_t i = 0; i < key_images.size(); ++i)
+ {
+ const crypto::key_image &key_image = key_images[i];
+
MDB_val key, data;
std::string key_ciphertext = encrypt(key_image, chacha_key, 0);
key.mv_data = (void*)key_ciphertext.data();
@@ -367,6 +374,7 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im
return false;
THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size");
+ std::vector<uint64_t> outs;
bool try_v0 = false;
std::string data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key, 1);
try { outs = decompress_ring(data_plaintext, V1TAG); if (outs.empty()) try_v0 = true; }
@@ -380,6 +388,9 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im
MDEBUG("Relative: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " "));
outs = cryptonote::relative_output_offsets_to_absolute(outs);
MDEBUG("Absolute: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " "));
+ all_outs.push_back(std::move(outs));
+
+ }
dbr = mdb_txn_commit(txn);
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn getting ring from database: " + std::string(mdb_strerror(dbr)));
@@ -387,20 +398,33 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im
return true;
}
-bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative)
+bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs)
+{
+ std::vector<std::vector<uint64_t>> all_outs;
+ if (!get_rings(chacha_key, std::vector<crypto::key_image>(1, key_image), all_outs))
+ return false;
+ outs = std::move(all_outs.front());
+ return true;
+}
+
+bool ringdb::set_rings(const crypto::chacha_key &chacha_key, const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative)
{
MDB_txn *txn;
int dbr;
bool tx_active = false;
- dbr = resize_env(env, filename.c_str(), outs.size() * 64);
+ size_t n_outs = 0;
+ for (const auto &e: rings)
+ n_outs += e.second.size();
+ dbr = resize_env(env, filename.c_str(), n_outs * 64);
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_begin(env, NULL, 0, &txn);
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);});
tx_active = true;
- store_relative_ring(txn, dbi_rings, key_image, relative ? outs : cryptonote::absolute_output_offsets_to_relative(outs), chacha_key);
+ for (const auto &e: rings)
+ store_relative_ring(txn, dbi_rings, e.first, relative ? e.second : cryptonote::absolute_output_offsets_to_relative(e.second), chacha_key);
dbr = mdb_txn_commit(txn);
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn setting ring to database: " + std::string(mdb_strerror(dbr)));
@@ -408,6 +432,13 @@ bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_im
return true;
}
+bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative)
+{
+ std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> rings;
+ rings.push_back(std::make_pair(key_image, outs));
+ return set_rings(chacha_key, rings, relative);
+}
+
bool ringdb::blackball_worker(const std::vector<std::pair<uint64_t, uint64_t>> &outputs, int op)
{
MDB_txn *txn;
diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h
index e9941bf94..bdecdba37 100644
--- a/src/wallet/ringdb.h
+++ b/src/wallet/ringdb.h
@@ -49,7 +49,9 @@ namespace tools
bool remove_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images);
bool remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx);
bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs);
+ bool get_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &all_outs);
bool set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative);
+ bool set_rings(const crypto::chacha_key &chacha_key, const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative);
bool blackball(const std::pair<uint64_t, uint64_t> &output);
bool blackball(const std::vector<std::pair<uint64_t, uint64_t>> &outputs);
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index f4a5a5855..5292cfecb 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -1216,7 +1216,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
m_rpc_version(0),
m_export_format(ExportFormat::Binary),
m_load_deprecated_formats(false),
- m_credits_target(0)
+ m_credits_target(0),
+ m_enable_multisig(false)
{
set_rpc_client_secret_key(rct::rct2sk(rct::skGen()));
}
@@ -4051,6 +4052,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee:
value2.SetUint64(m_credits_target);
json.AddMember("credits_target", value2, json.GetAllocator());
+ value2.SetInt(m_enable_multisig ? 1 : 0);
+ json.AddMember("enable_multisig", value2, json.GetAllocator());
+
// Serialize the JSON object
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
@@ -4199,6 +4203,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
m_persistent_rpc_client_id = false;
m_auto_mine_for_rpc_payment_threshold = -1.0f;
m_credits_target = 0;
+ m_enable_multisig = false;
}
else if(json.IsObject())
{
@@ -4431,6 +4436,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
m_auto_mine_for_rpc_payment_threshold = field_auto_mine_for_rpc_payment;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, credits_target, uint64_t, Uint64, false, 0);
m_credits_target = field_credits_target;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false);
+ m_enable_multisig = field_enable_multisig;
}
else
{
@@ -7606,6 +7613,14 @@ bool wallet2::get_ring(const crypto::chacha_key &key, const crypto::key_image &k
catch (const std::exception &e) { return false; }
}
+bool wallet2::get_rings(const crypto::chacha_key &key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &outs)
+{
+ if (!m_ringdb)
+ return false;
+ try { return m_ringdb->get_rings(key, key_images, outs); }
+ catch (const std::exception &e) { return false; }
+}
+
bool wallet2::get_rings(const crypto::hash &txid, std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &outs)
{
for (auto i: m_confirmed_txs)
@@ -7644,6 +7659,15 @@ bool wallet2::set_ring(const crypto::key_image &key_image, const std::vector<uin
catch (const std::exception &e) { return false; }
}
+bool wallet2::set_rings(const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative)
+{
+ if (!m_ringdb)
+ return false;
+
+ try { return m_ringdb->set_rings(get_ringdb_key(), rings, relative); }
+ catch (const std::exception &e) { return false; }
+}
+
bool wallet2::unset_ring(const std::vector<crypto::key_image> &key_images)
{
if (!m_ringdb)
@@ -7818,7 +7842,7 @@ bool wallet2::is_keys_file_locked() const
return m_keys_file_locker->locked();
}
-bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const
+bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set<crypto::public_key> &valid_public_keys_cache) const
{
if (!unlocked) // don't add locked outs
return false;
@@ -7829,16 +7853,18 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out
if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates
return false;
// check the keys are valid
- if (!rct::isInMainSubgroup(rct::pk2rct(output_public_key)))
+ if (valid_public_keys_cache.find(output_public_key) == valid_public_keys_cache.end() && !rct::isInMainSubgroup(rct::pk2rct(output_public_key)))
{
MWARNING("Key " << output_public_key << " at index " << global_index << " is not in the main subgroup");
return false;
}
- if (!rct::isInMainSubgroup(mask))
+ valid_public_keys_cache.insert(output_public_key);
+ if (valid_public_keys_cache.find(rct::rct2pk(mask)) == valid_public_keys_cache.end() && !rct::isInMainSubgroup(mask))
{
MWARNING("Commitment " << mask << " at index " << global_index << " is not in the main subgroup");
return false;
}
+ valid_public_keys_cache.insert(rct::rct2pk(mask));
// if (is_output_blackballed(output_public_key)) // don't add blackballed outputs
// return false;
outs.back().push_back(item);
@@ -7871,7 +7897,6 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs");
THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs received from light wallet node. Error: " + ores.Error);
- size_t n_outs = 0; for (const auto &e: ores.amount_outs) n_outs += e.outputs.size();
}
// Check if we got enough outputs for each amount
@@ -7882,6 +7907,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_
MDEBUG("selected transfers size: " << selected_transfers.size());
+ std::unordered_set<crypto::public_key> valid_public_keys_cache;
for(size_t idx: selected_transfers)
{
// Create new index
@@ -7933,7 +7959,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_
if(!light_wallet_parse_rct_str(ores.amount_outs[amount_key].outputs[i].rct, tx_public_key, 0, mask, rct_commit, false))
rct_commit = rct::zeroCommit(td.amount());
- if (tx_add_fake_output(outs, global_index, tx_public_key, rct_commit, td.m_global_output_index, true)) {
+ if (tx_add_fake_output(outs, global_index, tx_public_key, rct_commit, td.m_global_output_index, true, valid_public_keys_cache)) {
MDEBUG("added fake output " << ores.amount_outs[amount_key].outputs[i].public_key);
MDEBUG("index " << global_index);
}
@@ -7970,12 +7996,12 @@ std::pair<std::set<uint64_t>, size_t> outs_unique(const std::vector<std::vector<
return std::make_pair(std::move(unique), total);
}
-void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct)
+void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct, std::unordered_set<crypto::public_key> &valid_public_keys_cache)
{
std::vector<uint64_t> rct_offsets;
for (size_t attempts = 3; attempts > 0; --attempts)
{
- get_outs(outs, selected_transfers, fake_outputs_count, rct_offsets);
+ get_outs(outs, selected_transfers, fake_outputs_count, rct_offsets, valid_public_keys_cache);
if (!rct)
return;
@@ -7997,7 +8023,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
THROW_WALLET_EXCEPTION(error::wallet_internal_error, tr("Transaction sanity check failed"));
}
-void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets)
+void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets, std::unordered_set<crypto::public_key> &valid_public_keys_cache)
{
LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count);
outs.clear();
@@ -8041,6 +8067,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t);
// request histogram for all outputs, except 0 if we have the rct distribution
+ req_t.amounts.reserve(selected_transfers.size());
for(size_t idx: selected_transfers)
if (!m_transfers[idx].is_rct() || !has_rct_distribution)
req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount());
@@ -8068,6 +8095,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
{
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response resp_t = AUTO_VAL_INIT(resp_t);
+ req_t.amounts.reserve(req_t.amounts.size() + selected_transfers.size());
for(size_t idx: selected_transfers)
req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount());
std::sort(req_t.amounts.begin(), req_t.amounts.end());
@@ -8114,6 +8142,25 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
}
}
+ std::vector<crypto::key_image> ring_key_images;
+ ring_key_images.reserve(selected_transfers.size());
+ std::unordered_map<crypto::key_image, std::vector<uint64_t>> existing_rings;
+ for(size_t idx: selected_transfers)
+ {
+ const transfer_details &td = m_transfers[idx];
+ if (td.m_key_image_known && !td.m_key_image_partial)
+ ring_key_images.push_back(td.m_key_image);
+ }
+ if (!ring_key_images.empty())
+ {
+ std::vector<std::vector<uint64_t>> all_outs;
+ if (get_rings(get_ringdb_key(), ring_key_images, all_outs))
+ {
+ for (size_t i = 0; i < ring_key_images.size(); ++i)
+ existing_rings[ring_key_images[i]] = std::move(all_outs[i]);
+ }
+ }
+
// we ask for more, to have spares if some outputs are still locked
size_t base_requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1);
LOG_PRINT_L2("base_requested_outputs_count: " << base_requested_outputs_count);
@@ -8127,6 +8174,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
gamma.reset(new gamma_picker(rct_offsets));
size_t num_selected_transfers = 0;
+ req.outputs.reserve(selected_transfers.size() * (base_requested_outputs_count + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW));
+ daemon_resp.outs.reserve(selected_transfers.size() * (base_requested_outputs_count + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW));
for(size_t idx: selected_transfers)
{
++num_selected_transfers;
@@ -8236,9 +8285,12 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// if we have a known ring, use it
if (td.m_key_image_known && !td.m_key_image_partial)
{
- std::vector<uint64_t> ring;
- if (get_ring(get_ringdb_key(), td.m_key_image, ring))
+
+ const auto it = existing_rings.find(td.m_key_image);
+ const bool has_ring = it != existing_rings.end();
+ if (has_ring)
{
+ const std::vector<uint64_t> &ring = it->second;
MINFO("This output has a known ring, reusing (size " << ring.size() << ")");
THROW_WALLET_EXCEPTION_IF(ring.size() > fake_outputs_count + 1, error::wallet_internal_error,
"An output in this transaction was previously spent on another chain with ring size " +
@@ -8438,7 +8490,9 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
COMMAND_RPC_GET_OUTPUTS_BIN::request chunk_req = AUTO_VAL_INIT(chunk_req);
COMMAND_RPC_GET_OUTPUTS_BIN::response chunk_daemon_resp = AUTO_VAL_INIT(chunk_daemon_resp);
chunk_req.get_txid = false;
- for (size_t i = 0; i < std::min<size_t>(req.outputs.size() - offset, chunk_size); ++i)
+ const size_t this_chunk_size = std::min<size_t>(req.outputs.size() - offset, chunk_size);
+ chunk_req.outputs.reserve(this_chunk_size);
+ for (size_t i = 0; i < this_chunk_size; ++i)
chunk_req.outputs.push_back(req.outputs[offset + i]);
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
@@ -8508,9 +8562,10 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// then pick outs from an existing ring, if any
if (td.m_key_image_known && !td.m_key_image_partial)
{
- std::vector<uint64_t> ring;
- if (get_ring(get_ringdb_key(), td.m_key_image, ring))
+ const auto it = existing_rings.find(td.m_key_image);
+ if (it != existing_rings.end())
{
+ const std::vector<uint64_t> &ring = it->second;
for (uint64_t out: ring)
{
if (out < num_outs)
@@ -8524,7 +8579,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
if (req.outputs[i].index == out)
{
LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key << " (from existing ring)");
- tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked);
+ tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked, valid_public_keys_cache);
found = true;
break;
}
@@ -8549,7 +8604,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
{
size_t i = base + order[o];
LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key);
- tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked);
+ tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked, valid_public_keys_cache);
}
if (outs.back().size() < fake_outputs_count + 1)
{
@@ -8577,6 +8632,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
}
// save those outs in the ringdb for reuse
+ std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> rings;
+ rings.reserve(selected_transfers.size());
for (size_t i = 0; i < selected_transfers.size(); ++i)
{
const size_t idx = selected_transfers[i];
@@ -8586,14 +8643,15 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
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);
+ rings.push_back(std::make_pair(td.m_key_image, std::move(ring)));
}
+ if (!set_rings(rings, false))
+ MERROR("Failed to set rings");
}
template<typename T>
void wallet2::transfer_selected(const 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,
+ std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache,
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,
bool use_view_tags)
{
@@ -8631,7 +8689,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts");
if (outs.empty())
- get_outs(outs, selected_transfers, fake_outputs_count, false); // may throw
+ get_outs(outs, selected_transfers, fake_outputs_count, false, valid_public_keys_cache); // may throw
//prepare inputs
LOG_PRINT_L2("preparing outputs");
@@ -8755,7 +8813,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,
+ std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, bool use_view_tags)
{
using namespace cryptonote;
@@ -8849,7 +8907,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts");
if (outs.empty())
- get_outs(outs, selected_transfers, fake_outputs_count, all_rct); // may throw
+ get_outs(outs, selected_transfers, fake_outputs_count, all_rct, valid_public_keys_cache); // may throw
//prepare inputs
LOG_PRINT_L2("preparing outputs");
@@ -9674,7 +9732,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_transfers_indices_per_subaddr;
std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_dust_indices_per_subaddr;
uint64_t needed_money;
- uint64_t accumulated_fee, accumulated_outputs, accumulated_change;
+ uint64_t accumulated_fee, accumulated_change;
struct TX {
std::vector<size_t> selected_transfers;
std::vector<cryptonote::tx_destination_entry> dsts;
@@ -9735,6 +9793,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
bulletproof_plus ? 4 : 3
};
const bool use_view_tags = use_fork_rules(get_view_tag_fork(), 0);
+ std::unordered_set<crypto::public_key> valid_public_keys_cache;
const uint64_t base_fee = get_base_fee(priority);
const uint64_t fee_quantization_mask = get_fee_quantization_mask();
@@ -9865,7 +9924,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// start with an empty tx
txes.push_back(TX());
accumulated_fee = 0;
- accumulated_outputs = 0;
accumulated_change = 0;
adding_fee = false;
needed_fee = 0;
@@ -9878,8 +9936,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// the destination, and one for change.
LOG_PRINT_L2("checking preferred");
std::vector<size_t> preferred_inputs;
- uint64_t rct_outs_needed = 2 * (fake_outs_count + 1);
- rct_outs_needed += 100; // some fudge factor since we don't know how many are locked
if (use_rct)
{
// this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
@@ -9984,7 +10040,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// add this output to the list to spend
tx.selected_transfers.push_back(idx);
uint64_t available_amount = td.amount();
- accumulated_outputs += available_amount;
// clear any fake outs we'd already gathered, since we'll need a new set
outs.clear();
@@ -10112,10 +10167,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " outputs and " <<
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,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
else
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
@@ -10137,10 +10192,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee));
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,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
else
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
@@ -10206,6 +10261,7 @@ skip_tx:
tx.selected_transfers, /* const std::list<size_t> selected_transfers */
fake_outs_count, /* CONST size_t fake_outputs_count, */
tx.outs, /* MOD std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, */
+ valid_public_keys_cache,
unlock_time, /* CONST uint64_t unlock_time, */
tx.needed_fee, /* CONST uint64_t fee, */
extra, /* const std::vector<uint8_t>& extra, */
@@ -10218,6 +10274,7 @@ skip_tx:
tx.selected_transfers,
fake_outs_count,
tx.outs,
+ valid_public_keys_cache,
unlock_time,
tx.needed_fee,
extra,
@@ -10335,6 +10392,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!");
const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring;
const uint64_t fractional_threshold = (base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024);
+ std::unordered_set<crypto::public_key> valid_public_keys_cache;
THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account, false) == 0, error::wallet_internal_error, "No unlocked balance in the specified account");
@@ -10416,6 +10474,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
hw::device &hwdev = m_account.get_device();
boost::unique_lock<hw::device> hwdev_lock (hwdev);
hw::reset_mode rst(hwdev);
+ std::unordered_set<crypto::public_key> valid_public_keys_cache;
uint64_t accumulated_fee, accumulated_outputs, accumulated_change;
struct TX {
@@ -10518,10 +10577,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " <<
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,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
else
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
@@ -10555,10 +10614,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
dt.amount = dt_amount + dt_residue;
}
if (use_rct)
- transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
else
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
@@ -10594,10 +10653,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
cryptonote::transaction test_tx;
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,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, unlock_time, tx.needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
} else {
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, unlock_time, tx.needed_fee, extra,
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, unlock_time, tx.needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
}
auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 660e6a14b..e051946ad 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -1001,10 +1001,10 @@ private:
uint64_t unlocked_balance_all(bool strict, uint64_t *blocks_to_unlock = NULL, uint64_t *time_to_unlock = NULL);
template<typename T>
void transfer_selected(const 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,
+ std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache,
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, const bool use_view_tags);
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,
+ std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, const bool use_view_tags);
void commit_tx(pending_tx& ptx_vector);
@@ -1297,6 +1297,8 @@ private:
void set_rpc_client_secret_key(const crypto::secret_key &key) { m_rpc_client_secret_key = key; m_node_rpc_proxy.set_client_secret_key(key); }
uint64_t credits_target() const { return m_credits_target; }
void credits_target(uint64_t threshold) { m_credits_target = threshold; }
+ bool is_multisig_enabled() const { return m_enable_multisig; }
+ void enable_multisig(bool enable) { m_enable_multisig = enable; }
bool get_tx_key_cached(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, const boost::optional<cryptonote::account_public_address> &single_destination_subaddress = boost::none);
@@ -1558,7 +1560,9 @@ private:
const std::string get_ring_database() const { return m_ring_database; }
bool get_ring(const crypto::key_image &key_image, std::vector<uint64_t> &outs);
bool get_rings(const crypto::hash &txid, std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &outs);
+ bool get_rings(const crypto::chacha_key &key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &outs);
bool set_ring(const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative);
+ bool set_rings(const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative);
bool unset_ring(const std::vector<crypto::key_image> &key_images);
bool unset_ring(const crypto::hash &txid);
bool find_and_save_rings(bool force = true);
@@ -1665,9 +1669,9 @@ private:
void set_unspent(size_t idx);
bool is_spent(const transfer_details &td, bool strict = true) const;
bool is_spent(size_t idx, bool strict = true) const;
- void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct);
- void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets);
- 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;
+ void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct, std::unordered_set<crypto::public_key> &valid_public_keys_cache);
+ void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets, std::unordered_set<crypto::public_key> &valid_public_keys_cache);
+ 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, std::unordered_set<crypto::public_key> &valid_public_keys_cache) 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, bool miner_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);
@@ -1811,6 +1815,7 @@ private:
crypto::secret_key m_rpc_client_secret_key;
rpc_payment_state_t m_rpc_payment_state;
uint64_t m_credits_target;
+ bool m_enable_multisig;
// Aux transaction data from device
serializable_unordered_map<crypto::hash, std::string> m_tx_device;
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 57baf428f..7ec5fc7a1 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -61,6 +61,17 @@ using namespace epee;
#define DEFAULT_AUTO_REFRESH_PERIOD 20 // seconds
+#define CHECK_MULTISIG_ENABLED() \
+ do \
+ { \
+ if (m_wallet->multisig() && !m_wallet->is_multisig_enabled()) \
+ { \
+ er.code = WALLET_RPC_ERROR_CODE_DISABLED; \
+ er.message = "This wallet is multisig, and multisig is disabled. Multisig is an experimental feature and may have bugs. Things that could go wrong include: funds sent to a multisig wallet can't be spent at all, can only be spent with the participation of a malicious group member, or can be stolen by a malicious group member. You can enable it by running this once in monero-wallet-cli: set enable-multisig-experimental 1"; \
+ return false; \
+ } \
+ } while(0)
+
namespace
{
const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"};
@@ -1057,6 +1068,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
// validate the transfer requested and populate dsts & extra
if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er))
{
@@ -1109,6 +1122,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
// validate the transfer requested and populate dsts & extra; RPC_TRANSFER::request and RPC_TRANSFER_SPLIT::request are identical types.
if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er))
{
@@ -1163,6 +1178,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob))
{
@@ -1511,6 +1528,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
try
{
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions();
@@ -1539,6 +1558,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
// validate the transfer requested and populate dsts & extra
std::list<wallet_rpc::transfer_destination> destination;
destination.push_back(wallet_rpc::transfer_destination());
@@ -1604,6 +1625,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
// validate the transfer requested and populate dsts & extra
std::list<wallet_rpc::transfer_destination> destination;
destination.push_back(wallet_rpc::transfer_destination());
@@ -3933,6 +3956,9 @@ namespace tools
er.message = "This wallet is already multisig";
return false;
}
+ if (req.enable_multisig_experimental)
+ m_wallet->enable_multisig(true);
+ CHECK_MULTISIG_ENABLED();
if (m_wallet->watch_only())
{
er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
@@ -3959,6 +3985,7 @@ namespace tools
er.message = "This wallet is already multisig";
return false;
}
+ CHECK_MULTISIG_ENABLED();
if (m_wallet->watch_only())
{
er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
@@ -4003,6 +4030,7 @@ namespace tools
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
+ CHECK_MULTISIG_ENABLED();
cryptonote::blobdata info;
try
@@ -4044,6 +4072,7 @@ namespace tools
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
+ CHECK_MULTISIG_ENABLED();
if (req.info.size() < threshold - 1)
{
@@ -4096,6 +4125,7 @@ namespace tools
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
+ CHECK_MULTISIG_ENABLED();
return false;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -4123,6 +4153,7 @@ namespace tools
er.message = "This wallet is multisig, and already finalized";
return false;
}
+ CHECK_MULTISIG_ENABLED();
if (req.multisig_info.size() + 1 < total)
{
@@ -4172,6 +4203,7 @@ namespace tools
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
+ CHECK_MULTISIG_ENABLED();
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob))
@@ -4241,6 +4273,7 @@ namespace tools
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
+ CHECK_MULTISIG_ENABLED();
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob))
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index fe53e293f..ecfc8e673 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -2416,7 +2416,10 @@ namespace wallet_rpc
{
struct request_t
{
+ bool enable_multisig_experimental;
+
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(enable_multisig_experimental, false)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h
index 914939573..734229380 100644
--- a/src/wallet/wallet_rpc_server_error_codes.h
+++ b/src/wallet/wallet_rpc_server_error_codes.h
@@ -78,3 +78,4 @@
#define WALLET_RPC_ERROR_CODE_ATTRIBUTE_NOT_FOUND -45
#define WALLET_RPC_ERROR_CODE_ZERO_AMOUNT -46
#define WALLET_RPC_ERROR_CODE_INVALID_SIGNATURE_TYPE -47
+#define WALLET_RPC_ERROR_CODE_DISABLED -48