diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cryptonote_core/tx_pool.cpp | 8 | ||||
-rw-r--r-- | src/p2p/net_node.inl | 8 | ||||
-rw-r--r-- | src/ringct/rctSigs.cpp | 13 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.cpp | 21 | ||||
-rw-r--r-- | src/serialization/list.h | 58 | ||||
-rw-r--r-- | src/serialization/serialization.h | 7 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 55 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.h | 1 | ||||
-rw-r--r-- | src/wallet/ringdb.cpp | 39 | ||||
-rw-r--r-- | src/wallet/ringdb.h | 2 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 133 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 15 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 33 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 3 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_error_codes.h | 1 |
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 |