diff options
38 files changed, 2336 insertions, 490 deletions
diff --git a/.github/workflows/depends.yml b/.github/workflows/depends.yml index f20bf949f..32df7dc42 100644 --- a/.github/workflows/depends.yml +++ b/.github/workflows/depends.yml @@ -17,7 +17,7 @@ env: ccache --set-config=compression=true jobs: - build-macos: + build-cross: runs-on: ubuntu-18.04 env: CCACHE_TEMPDIR: /tmp/.ccache-temp @@ -581,7 +581,7 @@ You can also cross-compile static binaries on Linux for Windows and macOS with t * ```make depends target=aarch64-linux-android``` for 64bit android binaries -The required packages are the names for each toolchain on apt. Depending on your distro, they may have different names. +The required packages are the names for each toolchain on apt. Depending on your distro, they may have different names. The `depends` system has been tested on Ubuntu 18.04 and 20.04. Using `depends` might also be easier to compile Monero on Windows than using MSYS. Activate Windows Subsystem for Linux (WSL) with a distro (for example Ubuntu), install the apt build-essentials and follow the `depends` steps as depicted above. diff --git a/contrib/depends/packages/unbound.mk b/contrib/depends/packages/unbound.mk index a85c47e4e..9336524f3 100644 --- a/contrib/depends/packages/unbound.mk +++ b/contrib/depends/packages/unbound.mk @@ -4,6 +4,8 @@ $(package)_download_path=https://www.nlnetlabs.nl/downloads/$(package)/ $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=a480dc6c8937447b98d161fe911ffc76cfaffa2da18788781314e81339f1126f $(package)_dependencies=openssl expat ldns +$(package)_patches=disable-glibc-reallocarray.patch + define $(package)_set_vars $(package)_config_opts=--disable-shared --enable-static --without-pyunbound --prefix=$(host_prefix) --with-libexpat=$(host_prefix) --with-ssl=$(host_prefix) --with-libevent=no --without-pythonmodule --disable-flto --with-pthreads --with-libunbound-only @@ -12,8 +14,13 @@ define $(package)_set_vars $(package)_build_opts_mingw32=LDFLAGS="$($(package)_ldflags) -lpthread" endef +define $(package)_preprocess_cmds + patch -p1 < $($(package)_patch_dir)/disable-glibc-reallocarray.patch &&\ + autoconf +endef + define $(package)_config_cmds - $($(package)_autoconf) + $($(package)_autoconf) ac_cv_func_getentropy=no endef define $(package)_build_cmds diff --git a/contrib/depends/patches/unbound/disable-glibc-reallocarray.patch b/contrib/depends/patches/unbound/disable-glibc-reallocarray.patch new file mode 100644 index 000000000..d66a821ad --- /dev/null +++ b/contrib/depends/patches/unbound/disable-glibc-reallocarray.patch @@ -0,0 +1,14 @@ +diff --git a/configure.ac b/configure.ac +index 5c7da197..e2b25288 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1702,6 +1702,9 @@ AC_LINK_IFELSE([AC_LANG_SOURCE(AC_INCLUDES_DEFAULT + #ifndef _OPENBSD_SOURCE + #define _OPENBSD_SOURCE 1 + #endif ++#ifdef __linux__ ++# error reallocarray() is currently disabled on Linux to support glibc < 2.26 ++#endif + #include <stdlib.h> + int main(void) { + void* p = reallocarray(NULL, 10, 100); diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index f101f10c5..388013f96 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1042,7 +1042,7 @@ namespace cryptonote crypto::public_key subaddress_spendkey; if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) { - hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey); + CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key"); auto found = subaddresses.find(subaddress_spendkey); if (found != subaddresses.end()) return subaddress_receive_info{ found->second, derivation }; @@ -1054,7 +1054,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations"); if (out_can_be_to_acc(view_tag_opt, additional_derivations[output_index], output_index)) { - hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey); + CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key"); auto found = subaddresses.find(subaddress_spendkey); if (found != subaddresses.end()) return subaddress_receive_info{ found->second, additional_derivations[output_index] }; diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index f2a8e9b79..2ec194ef8 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -239,12 +239,15 @@ namespace config const unsigned char HASH_KEY_MEMORY = 'k'; const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const unsigned char HASH_KEY_MULTISIG_KEY_AGGREGATION[] = "Multisig_key_agg"; + const unsigned char HASH_KEY_CLSAG_ROUND_MULTISIG[] = "CLSAG_round_ms_merge_factor"; const unsigned char HASH_KEY_TXPROOF_V2[] = "TXPROOF_V2"; const unsigned char HASH_KEY_CLSAG_ROUND[] = "CLSAG_round"; const unsigned char HASH_KEY_CLSAG_AGG_0[] = "CLSAG_agg_0"; const unsigned char HASH_KEY_CLSAG_AGG_1[] = "CLSAG_agg_1"; const char HASH_KEY_MESSAGE_SIGNING[] = "MoneroMessageSignature"; const unsigned char HASH_KEY_MM_SLOT = 'm'; + const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS_SEED[] = "multisig_tx_privkeys_seed"; + const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS[] = "multisig_tx_privkeys"; // Multisig const uint32_t MULTISIG_MAX_SIGNERS{16}; diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index e272b94f0..69411e379 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -49,7 +49,6 @@ target_link_libraries(cryptonote_core common cncrypto blockchain_db - multisig ringct device hardforks diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 1d2024a05..472026217 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -203,7 +203,7 @@ namespace cryptonote return addr.m_view_public_key; } //--------------------------------------------------------------- - bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool shuffle_outs, bool use_view_tags) + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool shuffle_outs, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); @@ -216,10 +216,6 @@ namespace cryptonote std::vector<rct::key> amount_keys; tx.set_null(); amount_keys.clear(); - if (msout) - { - msout->c.clear(); - } tx.version = rct ? 2 : 1; tx.unlock_time = unlock_time; @@ -333,8 +329,8 @@ namespace cryptonote return false; } - //check that derivated key is equal with real output key (if non multisig) - if(!msout && !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) + //check that derivated key is equal with real output key + if(!(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) { LOG_ERROR("derived public key mismatch with output public key at index " << idx << ", real out " << src_entr.real_output << "! "<< ENDL << "derived_key:" << string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:" @@ -347,7 +343,7 @@ namespace cryptonote //put key image into tx input txin_to_key input_to_key; input_to_key.amount = src_entr.amount; - input_to_key.k_image = msout ? rct::rct2ki(src_entr.multisig_kLRki.ki) : img; + input_to_key.k_image = img; //fill outputs array and use relative offsets for(const tx_source_entry::output_entry& out_entry: src_entr.outputs) @@ -529,7 +525,6 @@ namespace cryptonote rct::keyV destinations; std::vector<uint64_t> inamounts, outamounts; std::vector<unsigned int> index; - std::vector<rct::multisig_kLRki> kLRki; for (size_t i = 0; i < sources.size(); ++i) { rct::ctkey ctkey; @@ -543,10 +538,6 @@ namespace cryptonote memwipe(&ctkey, sizeof(rct::ctkey)); // inPk: (public key, commitment) // will be done when filling in mixRing - if (msout) - { - kLRki.push_back(sources[i].multisig_kLRki); - } } for (size_t i = 0; i < tx.vout.size(); ++i) { @@ -598,9 +589,9 @@ namespace cryptonote get_transaction_prefix_hash(tx, tx_prefix_hash, hwdev); rct::ctkeyV outSk; if (use_simple_rct) - tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, rct_config, hwdev); + tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, index, outSk, rct_config, hwdev); else - tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, msout ? &kLRki[0] : NULL, msout, sources[0].real_output, outSk, rct_config, hwdev); // same index assumption + tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, sources[0].real_output, outSk, rct_config, hwdev); // same index assumption memwipe(inSk.data(), inSk.size() * sizeof(rct::ctkey)); CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout"); @@ -613,7 +604,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool use_view_tags) + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); hwdev.open_tx(tx_key); @@ -634,7 +625,7 @@ namespace cryptonote } bool shuffle_outs = true; - bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, msout, shuffle_outs, use_view_tags); + bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, shuffle_outs, use_view_tags); hwdev.close_tx(); return r; } catch(...) { @@ -650,7 +641,7 @@ namespace cryptonote crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; - return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}, NULL, false); + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}); } //--------------------------------------------------------------- bool generate_genesis_block( diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index f4ffb98ff..12d6b8ce5 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -119,8 +119,8 @@ namespace cryptonote //--------------------------------------------------------------- crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr); bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool shuffle_outs = true, bool use_view_tags = false); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool use_view_tags = false); + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool shuffle_outs = true, bool use_view_tags = false); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool use_view_tags = false); bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key, const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index, const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 51e65dfa5..aa73e998c 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -694,7 +694,8 @@ namespace hw { log_hexbuffer("derive_subaddress_public_key: [[IN]] pub ", pub_x.data, 32); log_hexbuffer("derive_subaddress_public_key: [[IN]] derivation", derivation_x.data, 32); log_message ("derive_subaddress_public_key: [[IN]] index ", std::to_string((int)output_index_x)); - this->controle_device->derive_subaddress_public_key(pub_x, derivation_x,output_index_x,derived_pub_x); + if (!this->controle_device->derive_subaddress_public_key(pub_x, derivation_x,output_index_x,derived_pub_x)) + return false; log_hexbuffer("derive_subaddress_public_key: [[OUT]] derived_pub", derived_pub_x.data, 32); #endif @@ -702,7 +703,8 @@ namespace hw { //If we are in TRANSACTION_PARSE, the given derivation has been retrieved uncrypted (wihtout the help //of the device), so continue that way. MDEBUG( "derive_subaddress_public_key : PARSE mode with known viewkey"); - crypto::derive_subaddress_public_key(pub, derivation, output_index,derived_pub); + if (!crypto::derive_subaddress_public_key(pub, derivation, output_index,derived_pub)) + return false; } else { AUTO_LOCK_CMD(); int offset = set_command_header_noopt(INS_DERIVE_SUBADDRESS_PUBLIC_KEY); @@ -1052,7 +1054,8 @@ namespace hw { crypto::key_derivation derivation_x; log_hexbuffer("generate_key_derivation: [[IN]] pub ", pub_x.data, 32); log_hexbuffer("generate_key_derivation: [[IN]] sec ", sec_x.data, 32); - this->controle_device->generate_key_derivation(pub_x, sec_x, derivation_x); + if (!this->controle_device->generate_key_derivation(pub_x, sec_x, derivation_x)) + return false; log_hexbuffer("generate_key_derivation: [[OUT]] derivation", derivation_x.data, 32); #endif @@ -1207,7 +1210,8 @@ namespace hw { log_hexbuffer("derive_public_key: [[IN]] derivation ", derivation_x.data, 32); log_message ("derive_public_key: [[IN]] output_index", std::to_string(output_index_x)); log_hexbuffer("derive_public_key: [[IN]] pub ", pub_x.data, 32); - this->controle_device->derive_public_key(derivation_x, output_index_x, pub_x, derived_pub_x); + if (!this->controle_device->derive_public_key(derivation_x, output_index_x, pub_x, derived_pub_x)) + return false; log_hexbuffer("derive_public_key: [[OUT]] derived_pub ", derived_pub_x.data, 32); #endif diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 0dd0cf5f0..336ef6519 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -71,8 +71,8 @@ const hardfork_t mainnet_hard_forks[] = { { 13, 2210000, 0, 1598180817 }, { 14, 2210720, 0, 1598180818 }, - { 15, 8000000, 0, 1608223241 }, // temp so tests test with these consensus rules - { 16, 8000001, 0, 1608223242 }, // temp so tests test with these consensus rules + { 15, 2688888, 0, 1656629117 }, + { 16, 2689608, 0, 1656629118 }, }; const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]); const uint64_t mainnet_hard_fork_version_1_till = 1009826; @@ -122,5 +122,7 @@ const hardfork_t stagenet_hard_forks[] = { { 12, 454721, 0, 1571419280 }, { 13, 675405, 0, 1598180817 }, { 14, 676125, 0, 1598180818 }, + { 15, 1151000, 0, 1656629117 }, + { 16, 1151720, 0, 1656629118 }, }; const size_t num_stagenet_hard_forks = sizeof(stagenet_hard_forks) / sizeof(stagenet_hard_forks[0]); diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt index 294a1721f..61e658a39 100644 --- a/src/multisig/CMakeLists.txt +++ b/src/multisig/CMakeLists.txt @@ -30,7 +30,9 @@ set(multisig_sources multisig.cpp multisig_account.cpp multisig_account_kex_impl.cpp - multisig_kex_msg.cpp) + multisig_clsag_context.cpp + multisig_kex_msg.cpp + multisig_tx_builder_ringct.cpp) set(multisig_headers) @@ -48,6 +50,7 @@ target_link_libraries(multisig PUBLIC ringct cryptonote_basic + cryptonote_core common cncrypto PRIVATE diff --git a/src/multisig/multisig_clsag_context.cpp b/src/multisig/multisig_clsag_context.cpp new file mode 100644 index 000000000..e3417b896 --- /dev/null +++ b/src/multisig/multisig_clsag_context.cpp @@ -0,0 +1,257 @@ +// Copyright (c) 2021, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "multisig_clsag_context.h" + +#include "int-util.h" + +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "ringct/rctOps.h" +#include "ringct/rctTypes.h" + +#include <cstring> +#include <string> +#include <vector> + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig { + +namespace signing { +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +template<std::size_t N> +static rct::key string_to_key(const unsigned char (&str)[N]) { + rct::key tmp{}; + static_assert(sizeof(tmp.bytes) >= N, ""); + std::memcpy(tmp.bytes, str, N); + return tmp; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void encode_int_to_key_le(const unsigned int i, rct::key &k_out) +{ + static_assert(sizeof(unsigned int) <= sizeof(std::uint64_t), "unsigned int max too large"); + static_assert(sizeof(std::uint64_t) <= sizeof(rct::key), ""); + std::uint64_t temp_i{SWAP64LE(i)}; + std::memcpy(k_out.bytes, &temp_i, sizeof(temp_i)); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +bool CLSAG_context_t::init( + const rct::keyV& P, + const rct::keyV& C_nonzero, + const rct::key& C_offset, + const rct::key& message, + const rct::key& I, + const rct::key& D, + const unsigned int l, + const rct::keyV& s, + const std::size_t num_alpha_components +) +{ + initialized = false; + + n = P.size(); + if (n <= 0) + return false; + if (C_nonzero.size() != n) + return false; + if (s.size() != n) + return false; + if (l >= n) + return false; + + c_params.clear(); + c_params.reserve(n * 2 + 5); + b_params.clear(); + b_params.reserve(n * 3 + 2 * num_alpha_components + 7); + + c_params.push_back(string_to_key(config::HASH_KEY_CLSAG_ROUND)); + b_params.push_back(string_to_key(config::HASH_KEY_CLSAG_ROUND_MULTISIG)); + c_params.insert(c_params.end(), P.begin(), P.end()); + b_params.insert(b_params.end(), P.begin(), P.end()); + c_params.insert(c_params.end(), C_nonzero.begin(), C_nonzero.end()); + b_params.insert(b_params.end(), C_nonzero.begin(), C_nonzero.end()); + c_params.emplace_back(C_offset); + b_params.emplace_back(C_offset); + c_params.emplace_back(message); + b_params.emplace_back(message); + c_params_L_offset = c_params.size(); + b_params_L_offset = b_params.size(); + c_params.resize(c_params.size() + 1); //this is where L will be inserted later + b_params.resize(b_params.size() + num_alpha_components); //multisig aggregate public nonces for L will be inserted here later + c_params_R_offset = c_params.size(); + b_params_R_offset = b_params.size(); + c_params.resize(c_params.size() + 1); //this is where R will be inserted later + b_params.resize(b_params.size() + num_alpha_components); //multisig aggregate public nonces for R will be inserted here later + b_params.emplace_back(I); + b_params.emplace_back(D); + b_params.insert(b_params.end(), s.begin(), s.begin() + l); //fake responses before 'l' + b_params.insert(b_params.end(), s.begin() + l + 1, s.end()); //fake responses after 'l' + b_params.emplace_back(); + encode_int_to_key_le(l, b_params.back()); //real signing index 'l' + b_params.emplace_back(); + encode_int_to_key_le(num_alpha_components, b_params.back()); //number of parallel nonces + b_params.emplace_back(); + encode_int_to_key_le(n, b_params.back()); //number of ring members + + rct::keyV mu_P_params; + rct::keyV mu_C_params; + mu_P_params.reserve(n * 2 + 4); + mu_C_params.reserve(n * 2 + 4); + + mu_P_params.push_back(string_to_key(config::HASH_KEY_CLSAG_AGG_0)); + mu_C_params.push_back(string_to_key(config::HASH_KEY_CLSAG_AGG_1)); + mu_P_params.insert(mu_P_params.end(), P.begin(), P.end()); + mu_C_params.insert(mu_C_params.end(), P.begin(), P.end()); + mu_P_params.insert(mu_P_params.end(), C_nonzero.begin(), C_nonzero.end()); + mu_C_params.insert(mu_C_params.end(), C_nonzero.begin(), C_nonzero.end()); + mu_P_params.emplace_back(I); + mu_C_params.emplace_back(I); + mu_P_params.emplace_back(scalarmultKey(D, rct::INV_EIGHT)); + mu_C_params.emplace_back(mu_P_params.back()); + mu_P_params.emplace_back(C_offset); + mu_C_params.emplace_back(C_offset); + mu_P = hash_to_scalar(mu_P_params); + mu_C = hash_to_scalar(mu_C_params); + + rct::geDsmp I_precomp; + rct::geDsmp D_precomp; + rct::precomp(I_precomp.k, I); + rct::precomp(D_precomp.k, D); + rct::key wH_l; + rct::addKeys3(wH_l, mu_P, I_precomp.k, mu_C, D_precomp.k); + rct::precomp(wH_l_precomp.k, wH_l); + W_precomp.resize(n); + H_precomp.resize(n); + for (std::size_t i = 0; i < n; ++i) { + rct::geDsmp P_precomp; + rct::geDsmp C_precomp; + rct::key C; + rct::subKeys(C, C_nonzero[i], C_offset); + rct::precomp(P_precomp.k, P[i]); + rct::precomp(C_precomp.k, C); + rct::key W; + rct::addKeys3(W, mu_P, P_precomp.k, mu_C, C_precomp.k); + rct::precomp(W_precomp[i].k, W); + ge_p3 Hi_p3; + rct::hash_to_p3(Hi_p3, P[i]); + ge_dsm_precomp(H_precomp[i].k, &Hi_p3); + } + rct::precomp(G_precomp.k, rct::G); + this->l = l; + this->s = s; + this->num_alpha_components = num_alpha_components; + + initialized = true; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool CLSAG_context_t::combine_alpha_and_compute_challenge( + const rct::keyV& total_alpha_G, + const rct::keyV& total_alpha_H, + const rct::keyV& alpha, + rct::key& alpha_combined, + rct::key& c_0, + rct::key& c +) +{ + if (not initialized) + return false; + + if (num_alpha_components != total_alpha_G.size()) + return false; + if (num_alpha_components != total_alpha_H.size()) + return false; + if (num_alpha_components != alpha.size()) + return false; + + // insert aggregate public nonces for L and R components + for (std::size_t i = 0; i < num_alpha_components; ++i) { + b_params[b_params_L_offset + i] = total_alpha_G[i]; + b_params[b_params_R_offset + i] = total_alpha_H[i]; + } + + // musig2-style combination factor 'b' + const rct::key b = rct::hash_to_scalar(b_params); + + // 1) store combined public nonces in the 'L' and 'R' slots for computing the initial challenge + // - L = sum_i(b^i total_alpha_G[i]) + // - R = sum_i(b^i total_alpha_H[i]) + // 2) compute the local signer's combined private nonce + // - alpha_combined = sum_i(b^i * alpha[i]) + rct::key& L_l = c_params[c_params_L_offset]; + rct::key& R_l = c_params[c_params_R_offset]; + rct::key b_i = rct::identity(); + L_l = rct::identity(); + R_l = rct::identity(); + alpha_combined = rct::zero(); + for (std::size_t i = 0; i < num_alpha_components; ++i) { + rct::addKeys(L_l, L_l, rct::scalarmultKey(total_alpha_G[i], b_i)); + rct::addKeys(R_l, R_l, rct::scalarmultKey(total_alpha_H[i], b_i)); + sc_muladd(alpha_combined.bytes, alpha[i].bytes, b_i.bytes, alpha_combined.bytes); + sc_mul(b_i.bytes, b_i.bytes, b.bytes); + } + + // compute initial challenge from real spend components + c = rct::hash_to_scalar(c_params); + + // 1) c_0: find the CLSAG's challenge for index '0', which will be stored in the proof + // note: in the CLSAG implementation in ringct/rctSigs, c_0 is denoted 'c1' (a notation error) + // 2) c: find the final challenge for the multisig signers to respond to + for (std::size_t i = (l + 1) % n; i != l; i = (i + 1) % n) { + if (i == 0) + c_0 = c; + rct::addKeys3(c_params[c_params_L_offset], s[i], G_precomp.k, c, W_precomp[i].k); + rct::addKeys3(c_params[c_params_R_offset], s[i], H_precomp[i].k, c, wH_l_precomp.k); + c = rct::hash_to_scalar(c_params); + } + if (l == 0) + c_0 = c; + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool CLSAG_context_t::get_mu( + rct::key& mu_P, + rct::key& mu_C +) const +{ + if (not initialized) + return false; + mu_P = this->mu_P; + mu_C = this->mu_C; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +} //namespace signing + +} //namespace multisig diff --git a/src/multisig/multisig_clsag_context.h b/src/multisig/multisig_clsag_context.h new file mode 100644 index 000000000..5017e8688 --- /dev/null +++ b/src/multisig/multisig_clsag_context.h @@ -0,0 +1,137 @@ +// Copyright (c) 2021, 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. + +//// +// References +// - CLSAG (base signature scheme): https://eprint.iacr.org/2019/654 +// - MuSig2 (style for multisig signing): https://eprint.iacr.org/2020/1261 +/// + + +#pragma once + +#include "ringct/rctTypes.h" + +#include <vector> + + +namespace multisig { + +namespace signing { + +class CLSAG_context_t final { +private: + // is the CLSAG context initialized? + bool initialized; + // challenge components: c = H(domain-separator, {P}, {C}, C_offset, message, L, R) + rct::keyV c_params; + // indices in c_params where L and R will be + std::size_t c_params_L_offset; + std::size_t c_params_R_offset; + // musig2-style nonce combination factor components for multisig signing + // b = H(domain-separator, {P}, {C}, C_offset, message, {L_combined_alphas}, {R_combined_alphas}, I, D, {s_non_l}, l, k, n) + // - {P} = ring of one-time addresses + // - {C} = ring of amount commitments (1:1 with one-time addresses) + // - C_offset = pseudo-output commitment to offset all amount commitments with + // - message = message the CLSAG will sign + // - {L_combined_alphas} = set of summed-together public nonces from all multisig signers for this CLSAG's L component + // - {R_combined_alphas} = set of summed-together public nonces from all multisig signers for this CLSAG's R component + // - I = key image for one-time address at {P}[l] + // - D = auxiliary key image for the offsetted amount commitment '{C}[l] - C_offset' + // - {s_non_l} = fake responses for this proof + // - l = real signing index in {P} and '{C} - C_offset' + // - k = number of parallel nonces that each participant provides + // - n = number of ring members + rct::keyV b_params; + // indices in b_params where L and R 'alpha' components will be + std::size_t b_params_L_offset; + std::size_t b_params_R_offset; + // CLSAG 'concise' coefficients for {P} and '{C} - C_offset' + // mu_x = H(domain-separator, {P}, {C}, I, (1/8)*D, C_offset) + // - note: 'D' is stored in the form '(1/8)*D' in transaction data + rct::key mu_P; + rct::key mu_C; + // ring size + std::size_t n; + // aggregate key image: mu_P*I + mu_C*D + rct::geDsmp wH_l_precomp; + // aggregate ring members: mu_P*P_i + mu_C*(C_i - C_offset) + std::vector<rct::geDsmp> W_precomp; + // key image component base keys: H_p(P_i) + std::vector<rct::geDsmp> H_precomp; + // cache for later: generator 'G' in 'precomp' representation + rct::geDsmp G_precomp; + // real signing index in this CLSAG + std::size_t l; + // signature responses + rct::keyV s; + // number of signing nonces expected per signer + std::size_t num_alpha_components; +public: + CLSAG_context_t() : initialized{false} {} + + // prepare CLSAG challenge context + bool init( + const rct::keyV& P, + const rct::keyV& C_nonzero, + const rct::key& C_offset, + const rct::key& message, + const rct::key& I, + const rct::key& D, + const unsigned int l, + const rct::keyV& s, + const std::size_t num_alpha_components + ); + + // get the local signer's combined musig2-style private nonce and compute the CLSAG challenge + bool combine_alpha_and_compute_challenge( + // set of summed-together musig2-style public nonces from all multisig signers for this CLSAG's L component + const rct::keyV& total_alpha_G, + // set of summed-together musig2-style public nonces from all multisig signers for this CLSAG's R component + const rct::keyV& total_alpha_H, + // local signer's private musig2-style nonces + const rct::keyV& alpha, + // local signer's final private nonce, using musig2-style combination with factor 'b' + // alpha_combined = sum_i(b^i * alpha[i]) + rct::key& alpha_combined, + // CLSAG challenge to store in the proof + rct::key& c_0, + // final CLSAG challenge to respond to (need this to make multisig partial signatures) + rct::key& c + ); + + // getter for CLSAG 'concise' coefficients + bool get_mu( + rct::key& mu_P, + rct::key& mu_C + ) const; +}; + +} //namespace signing + +} //namespace multisig diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp new file mode 100644 index 000000000..e5c9ac483 --- /dev/null +++ b/src/multisig/multisig_tx_builder_ringct.cpp @@ -0,0 +1,1059 @@ +// Copyright (c) 2021, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "multisig_tx_builder_ringct.h" + +#include "int-util.h" +#include "memwipe.h" + +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_config.h" +#include "cryptonote_core/cryptonote_tx_utils.h" +#include "device/device.hpp" +#include "multisig_clsag_context.h" +#include "ringct/bulletproofs.h" +#include "ringct/bulletproofs_plus.h" +#include "ringct/rctSigs.h" + +#include <boost/multiprecision/cpp_int.hpp> + +#include <algorithm> +#include <cstring> +#include <limits> +#include <set> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig { + +namespace signing { +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +bool view_tag_required(const int bp_version) +{ + // view tags were introduced at the same time as BP+, so they are needed after BP+ (v4 and later) + if (bp_version <= 3) + return false; + else + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void sort_sources( + std::vector<cryptonote::tx_source_entry>& sources +) +{ + std::sort(sources.begin(), sources.end(), [](const auto& lhs, const auto& rhs){ + const rct::key& ki0 = lhs.multisig_kLRki.ki; + const rct::key& ki1 = rhs.multisig_kLRki.ki; + return memcmp(&ki0, &ki1, sizeof(rct::key)) > 0; + }); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool compute_keys_for_sources( + const cryptonote::account_keys& account_keys, + const std::vector<cryptonote::tx_source_entry>& sources, + const std::uint32_t subaddr_account, + const std::set<std::uint32_t>& subaddr_minor_indices, + rct::keyV& input_secret_keys +) +{ + const std::size_t num_sources = sources.size(); + hw::device& hwdev = account_keys.get_device(); + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + for (const std::uint32_t minor_index: subaddr_minor_indices) { + subaddresses[hwdev.get_subaddress_spend_public_key( + account_keys, + {subaddr_account, minor_index} + )] = {subaddr_account, minor_index}; + } + input_secret_keys.resize(num_sources); + for (std::size_t i = 0; i < num_sources; ++i) { + const auto& src = sources[i]; + crypto::key_image tmp_key_image; + cryptonote::keypair tmp_keys; + if (src.real_output >= src.outputs.size()) + return false; + if (not cryptonote::generate_key_image_helper( + account_keys, + subaddresses, + rct::rct2pk(src.outputs[src.real_output].second.dest), + src.real_out_tx_key, + src.real_out_additional_tx_keys, + src.real_output_in_tx_index, + tmp_keys, + tmp_key_image, + hwdev + )) { + return false; + } + input_secret_keys[i] = rct::sk2rct(tmp_keys.sec); + } + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void shuffle_destinations( + std::vector<cryptonote::tx_destination_entry>& destinations +) +{ + std::shuffle(destinations.begin(), destinations.end(), crypto::random_device{}); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_extra( + const cryptonote::account_keys& account_keys, + const std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const crypto::secret_key& tx_secret_key, + const crypto::public_key& tx_public_key, + const std::vector<crypto::public_key>& tx_aux_public_keys, + const std::vector<std::uint8_t>& extra, + cryptonote::transaction& tx +) +{ + hw::device &hwdev = account_keys.get_device(); + tx.extra = extra; + // if we have a stealth payment id, find it and encrypt it with the tx key now + std::vector<cryptonote::tx_extra_field> tx_extra_fields; + if (cryptonote::parse_tx_extra(tx.extra, tx_extra_fields)) + { + bool add_dummy_payment_id = true; + cryptonote::tx_extra_nonce extra_nonce; + if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash payment_id = crypto::null_hash; + crypto::hash8 payment_id8 = crypto::null_hash8; + if (cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + LOG_PRINT_L2("Encrypting payment id " << payment_id8); + crypto::public_key view_key_pub = cryptonote::get_destination_view_key_pub(destinations, change.addr); + if (view_key_pub == crypto::null_pkey) + { + // valid combinations: + // - 1 output with encrypted payment ID, dummy change output (0 amount) + // - 0 outputs, 1 change output with encrypted payment ID + // - 1 output with encrypted payment ID, 1 change output + LOG_ERROR("Destinations have to have exactly one output to support encrypted payment ids"); + return false; + } + + if (!hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_secret_key)) + { + LOG_ERROR("Failed to encrypt payment id"); + return false; + } + + std::string extra_nonce_updated; + cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce_updated, payment_id8); + cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_nonce)); + if (!cryptonote::add_extra_nonce_to_tx_extra(tx.extra, extra_nonce_updated)) + { + LOG_ERROR("Failed to add encrypted payment id to tx extra"); + return false; + } + LOG_PRINT_L1("Encrypted payment ID: " << payment_id8); + add_dummy_payment_id = false; + } + else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + add_dummy_payment_id = false; + } + } + + // we don't add one if we've got more than the usual 1 destination plus change + if (destinations.size() > 2) + add_dummy_payment_id = false; + + if (add_dummy_payment_id) + { + // if we have neither long nor short payment id, add a dummy short one, + // this should end up being the vast majority of txes as time goes on + std::string extra_nonce_updated; + crypto::hash8 payment_id8 = crypto::null_hash8; + crypto::public_key view_key_pub = cryptonote::get_destination_view_key_pub(destinations, change.addr); + if (view_key_pub == crypto::null_pkey) + { + LOG_ERROR("Failed to get key to encrypt dummy payment id with"); + } + else + { + hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_secret_key); + cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce_updated, payment_id8); + if (!cryptonote::add_extra_nonce_to_tx_extra(tx.extra, extra_nonce_updated)) + { + LOG_ERROR("Failed to add dummy encrypted payment id to tx extra"); + // continue anyway + } + } + } + } + else + { + MWARNING("Failed to parse tx extra"); + tx_extra_fields.clear(); + } + + cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_pub_key)); + cryptonote::add_tx_pub_key_to_extra(tx.extra, tx_public_key); + cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_additional_pub_keys)); + LOG_PRINT_L2("tx pubkey: " << tx_public_key); + if (tx_aux_public_keys.size()) + { + LOG_PRINT_L2("additional tx pubkeys: "); + for (size_t i = 0; i < tx_aux_public_keys.size(); ++i) + LOG_PRINT_L2(tx_aux_public_keys[i]); + cryptonote::add_additional_tx_pub_keys_to_extra(tx.extra, tx_aux_public_keys); + } + if (not cryptonote::sort_tx_extra(tx.extra, tx.extra)) + return false; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void make_tx_secret_key_seed(const crypto::secret_key& tx_secret_key_entropy, + const std::vector<cryptonote::tx_source_entry>& sources, + crypto::secret_key& tx_secret_key_seed) +{ + // seed = H(H("domain separator"), entropy, {KI}) + static const std::string domain_separator{config::HASH_KEY_MULTISIG_TX_PRIVKEYS_SEED}; + + rct::keyV hash_context; + hash_context.reserve(2 + sources.size()); + auto hash_context_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(hash_context.data(), hash_context.size()); + }); + hash_context.emplace_back(); + rct::cn_fast_hash(hash_context.back(), domain_separator.data(), domain_separator.size()); //domain sep + hash_context.emplace_back(rct::sk2rct(tx_secret_key_entropy)); //entropy + + for (const cryptonote::tx_source_entry& source : sources) + hash_context.emplace_back(source.multisig_kLRki.ki); //{KI} + + // set the seed + tx_secret_key_seed = rct::rct2sk(rct::cn_fast_hash(hash_context)); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void make_tx_secret_keys(const crypto::secret_key& tx_secret_key_seed, + const std::size_t num_tx_keys, + std::vector<crypto::secret_key>& tx_secret_keys) +{ + // make tx secret keys as a hash chain of the seed + // h1 = H_n(seed || H("domain separator")) + // h2 = H_n(seed || h1) + // h3 = H_n(seed || h2) + // ... + static const std::string domain_separator{config::HASH_KEY_MULTISIG_TX_PRIVKEYS}; + + rct::keyV hash_context; + hash_context.resize(2); + auto hash_context_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(hash_context.data(), hash_context.size()); + }); + hash_context[0] = rct::sk2rct(tx_secret_key_seed); + rct::cn_fast_hash(hash_context[1], domain_separator.data(), domain_separator.size()); + + tx_secret_keys.clear(); + tx_secret_keys.resize(num_tx_keys); + + for (crypto::secret_key& tx_secret_key : tx_secret_keys) + { + // advance the hash chain + hash_context[1] = rct::hash_to_scalar(hash_context); + + // set this key + tx_secret_key = rct::rct2sk(hash_context[1]); + } +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool collect_tx_secret_keys(const std::vector<crypto::secret_key>& tx_secret_keys, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys) +{ + if (tx_secret_keys.size() == 0) + return false; + + tx_secret_key = tx_secret_keys[0]; + tx_aux_secret_keys.clear(); + tx_aux_secret_keys.reserve(tx_secret_keys.size() - 1); + for (std::size_t tx_key_index{1}; tx_key_index < tx_secret_keys.size(); ++tx_key_index) + tx_aux_secret_keys.emplace_back(tx_secret_keys[tx_key_index]); + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool compute_keys_for_destinations( + const cryptonote::account_keys& account_keys, + const std::uint32_t subaddr_account, + const std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const std::vector<std::uint8_t>& extra, + const bool use_view_tags, + const bool reconstruction, + const crypto::secret_key& tx_secret_key_seed, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys, + rct::keyV& output_public_keys, + rct::keyV& output_amount_secret_keys, + std::vector<crypto::view_tag>& view_tags, + cryptonote::transaction& unsigned_tx +) +{ + hw::device &hwdev = account_keys.get_device(); + + // check non-zero change amount case + if (change.amount > 0) + { + // the change output must be directed to the local account + if (change.addr != hwdev.get_subaddress(account_keys, {subaddr_account})) + return false; + + // expect the change destination to be in the destination set + if (std::find_if(destinations.begin(), destinations.end(), + [&change](const auto &destination) -> bool + { + return destination.addr == change.addr; + }) == destinations.end()) + return false; + } + + // collect non-change recipients into normal/subaddress buckets + std::unordered_set<cryptonote::account_public_address> unique_subbaddr_recipients; + std::unordered_set<cryptonote::account_public_address> unique_std_recipients; + for(const auto& dst_entr: destinations) { + if (dst_entr.addr == change.addr) + continue; + if (dst_entr.is_subaddress) + unique_subbaddr_recipients.insert(dst_entr.addr); + else + unique_std_recipients.insert(dst_entr.addr); + } + + // figure out how many tx secret keys are needed + // - tx aux keys: add if there are > 1 non-change recipients, with at least one to a subaddress + const std::size_t num_destinations = destinations.size(); + const bool need_tx_aux_keys = unique_subbaddr_recipients.size() + bool(unique_std_recipients.size()) > 1; + + const std::size_t num_tx_keys = 1 + (need_tx_aux_keys ? num_destinations : 0); + + // make tx secret keys + std::vector<crypto::secret_key> all_tx_secret_keys; + make_tx_secret_keys(tx_secret_key_seed, num_tx_keys, all_tx_secret_keys); + + // split up tx secret keys + crypto::secret_key tx_secret_key_temp; + std::vector<crypto::secret_key> tx_aux_secret_keys_temp; + if (not collect_tx_secret_keys(all_tx_secret_keys, tx_secret_key_temp, tx_aux_secret_keys_temp)) + return false; + + if (reconstruction) + { + // when reconstructing, the tx secret keys should be reproducible from input seed + if (!(tx_secret_key == tx_secret_key_temp)) + return false; + if (!(tx_aux_secret_keys == tx_aux_secret_keys_temp)) + return false; + } + else + { + tx_secret_key = tx_secret_key_temp; + tx_aux_secret_keys = std::move(tx_aux_secret_keys_temp); + } + + // tx pub key: R + crypto::public_key tx_public_key; + if (unique_std_recipients.empty() && unique_subbaddr_recipients.size() == 1) { + // if there is exactly 1 non-change recipient, and it's to a subaddress, then the tx pubkey = r*Ksi_nonchange_recipient + tx_public_key = rct::rct2pk( + hwdev.scalarmultKey( + rct::pk2rct(unique_subbaddr_recipients.begin()->m_spend_public_key), + rct::sk2rct(tx_secret_key) + )); + } + else { + // otherwise, the tx pub key = r*G + // - if there are > 1 non-change recipients, with at least one to a subaddress, then the tx pubkey is not used + // (additional tx keys will be used instead) + // - if all non-change recipients are to normal addresses, then the tx pubkey will be used by all recipients + // (including change recipient, even if change is to a subaddress) + tx_public_key = rct::rct2pk(hwdev.scalarmultBase(rct::sk2rct(tx_secret_key))); + } + + // additional tx pubkeys: R_t + output_public_keys.resize(num_destinations); + view_tags.resize(num_destinations); + std::vector<crypto::public_key> tx_aux_public_keys; + crypto::public_key temp_output_public_key; + + for (std::size_t i = 0; i < num_destinations; ++i) { + if (not hwdev.generate_output_ephemeral_keys( + unsigned_tx.version, + account_keys, + tx_public_key, + tx_secret_key, + destinations[i], + change.addr, + i, + need_tx_aux_keys, + tx_aux_secret_keys, + tx_aux_public_keys, + output_amount_secret_keys, + temp_output_public_key, + use_view_tags, + view_tags[i] //unused variable if use_view_tags is not set + )) { + return false; + } + output_public_keys[i] = rct::pk2rct(temp_output_public_key); + } + + if (num_destinations != output_amount_secret_keys.size()) + return false; + + CHECK_AND_ASSERT_MES( + tx_aux_public_keys.size() == tx_aux_secret_keys.size(), + false, + "Internal error creating additional public keys" + ); + + if (not set_tx_extra(account_keys, destinations, change, tx_secret_key, tx_public_key, tx_aux_public_keys, extra, unsigned_tx)) + return false; + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void set_tx_inputs( + const std::vector<cryptonote::tx_source_entry>& sources, + cryptonote::transaction& unsigned_tx +) +{ + const std::size_t num_sources = sources.size(); + unsigned_tx.vin.resize(num_sources); + for (std::size_t i = 0; i < num_sources; ++i) { + std::vector<std::uint64_t> offsets; + offsets.reserve(sources[i].outputs.size()); + for (const auto& e: sources[i].outputs) + offsets.emplace_back(e.first); + unsigned_tx.vin[i] = cryptonote::txin_to_key{ + .amount = 0, + .key_offsets = cryptonote::absolute_output_offsets_to_relative(offsets), + .k_image = rct::rct2ki(sources[i].multisig_kLRki.ki), + }; + } +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool onetime_addresses_are_unique(const rct::keyV& output_public_keys) +{ + for (auto addr_it = output_public_keys.begin(); addr_it != output_public_keys.end(); ++addr_it) + { + if (std::find(output_public_keys.begin(), addr_it, *addr_it) != addr_it) + return false; + } + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_outputs(const rct::keyV& output_public_keys, cryptonote::transaction& unsigned_tx) +{ + // sanity check: all onetime addresses should be unique + if (not onetime_addresses_are_unique(output_public_keys)) + return false; + + // set the tx outputs + const std::size_t num_destinations = output_public_keys.size(); + unsigned_tx.vout.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) + cryptonote::set_tx_out(0, rct::rct2pk(output_public_keys[i]), false, crypto::view_tag{}, unsigned_tx.vout[i]); + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_outputs_with_view_tags( + const rct::keyV& output_public_keys, + const std::vector<crypto::view_tag>& view_tags, + cryptonote::transaction& unsigned_tx +) +{ + // sanity check: all onetime addresses should be unique + if (not onetime_addresses_are_unique(output_public_keys)) + return false; + + // set the tx outputs (with view tags) + const std::size_t num_destinations = output_public_keys.size(); + CHECK_AND_ASSERT_MES(view_tags.size() == num_destinations, false, + "multisig signing protocol: internal error, view tag size mismatch."); + unsigned_tx.vout.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) + cryptonote::set_tx_out(0, rct::rct2pk(output_public_keys[i]), true, view_tags[i], unsigned_tx.vout[i]); + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void make_new_range_proofs(const int bp_version, + const std::vector<std::uint64_t>& output_amounts, + const rct::keyV& output_amount_masks, + rct::rctSigPrunable& sigs) +{ + sigs.bulletproofs.clear(); + sigs.bulletproofs_plus.clear(); + + if (bp_version == 3) + sigs.bulletproofs.push_back(rct::bulletproof_PROVE(output_amounts, output_amount_masks)); + else if (bp_version == 4) + sigs.bulletproofs_plus.push_back(rct::bulletproof_plus_PROVE(output_amounts, output_amount_masks)); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool try_reconstruct_range_proofs(const int bp_version, + const rct::rctSigPrunable& original_sigs, + const std::size_t num_destinations, + const rct::ctkeyV& output_public_keys, + rct::rctSigPrunable& reconstructed_sigs) +{ + auto try_reconstruct_range_proofs = + [&](const auto &original_range_proofs, auto &new_range_proofs) -> bool + { + if (original_range_proofs.size() != 1) + return false; + + new_range_proofs = original_range_proofs; + new_range_proofs[0].V.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) + new_range_proofs[0].V[i] = rct::scalarmultKey(output_public_keys[i].mask, rct::INV_EIGHT); + + return true; + }; + + if (bp_version == 3) + { + if (not try_reconstruct_range_proofs(original_sigs.bulletproofs, reconstructed_sigs.bulletproofs)) + return false; + return rct::bulletproof_VERIFY(reconstructed_sigs.bulletproofs); + } + else if (bp_version == 4) + { + if (not try_reconstruct_range_proofs(original_sigs.bulletproofs_plus, reconstructed_sigs.bulletproofs_plus)) + return false; + return rct::bulletproof_plus_VERIFY(reconstructed_sigs.bulletproofs_plus); + } + + return false; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_rct_signatures( + const std::uint64_t fee, + const std::vector<cryptonote::tx_source_entry>& sources, + const std::vector<cryptonote::tx_destination_entry>& destinations, + const rct::keyV& input_secret_keys, + const rct::keyV& output_public_keys, + const rct::keyV& output_amount_secret_keys, + const rct::RCTConfig& rct_config, + const bool reconstruction, + cryptonote::transaction& unsigned_tx, + std::vector<CLSAG_context_t>& CLSAG_contexts, + rct::keyV& cached_w +) +{ + if (rct_config.bp_version != 3 && + rct_config.bp_version != 4) + return false; + if (rct_config.range_proof_type != rct::RangeProofPaddedBulletproof) + return false; + + const std::size_t num_destinations = destinations.size(); + const std::size_t num_sources = sources.size(); + + // rct_signatures component of tx + rct::rctSig rv{}; + + // set misc. fields + if (rct_config.bp_version == 3) + rv.type = rct::RCTTypeCLSAG; + else if (rct_config.bp_version == 4) + rv.type = rct::RCTTypeBulletproofPlus; + else + return false; + rv.txnFee = fee; + rv.message = rct::hash2rct(cryptonote::get_transaction_prefix_hash(unsigned_tx)); + + // define outputs + std::vector<std::uint64_t> output_amounts(num_destinations); + rct::keyV output_amount_masks(num_destinations); + rv.ecdhInfo.resize(num_destinations); + rv.outPk.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) { + rv.outPk[i].dest = output_public_keys[i]; + output_amounts[i] = destinations[i].amount; + output_amount_masks[i] = genCommitmentMask(output_amount_secret_keys[i]); + rv.ecdhInfo[i].amount = rct::d2h(output_amounts[i]); + rct::addKeys2( + rv.outPk[i].mask, + output_amount_masks[i], + rv.ecdhInfo[i].amount, + rct::H + ); + rct::ecdhEncode(rv.ecdhInfo[i], output_amount_secret_keys[i], true); + } + + // output range proofs + if (not reconstruction) { + make_new_range_proofs(rct_config.bp_version, output_amounts, output_amount_masks, rv.p); + } + else { + if (not try_reconstruct_range_proofs(rct_config.bp_version, + unsigned_tx.rct_signatures.p, + num_destinations, + rv.outPk, + rv.p)) + return false; + } + + // prepare rings for input CLSAGs + rv.mixRing.resize(num_sources); + for (std::size_t i = 0; i < num_sources; ++i) { + const std::size_t ring_size = sources[i].outputs.size(); + rv.mixRing[i].resize(ring_size); + for (std::size_t j = 0; j < ring_size; ++j) { + rv.mixRing[i][j].dest = sources[i].outputs[j].second.dest; + rv.mixRing[i][j].mask = sources[i].outputs[j].second.mask; + } + } + + // make pseudo-output commitments + rct::keyV a; //pseudo-output commitment blinding factors + auto a_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(a.data()), a.size() * sizeof(rct::key)); + }); + if (not reconstruction) { + a.resize(num_sources); + rv.p.pseudoOuts.resize(num_sources); + a[num_sources - 1] = rct::zero(); + for (std::size_t i = 0; i < num_destinations; ++i) { + sc_add( + a[num_sources - 1].bytes, + a[num_sources - 1].bytes, + output_amount_masks[i].bytes + ); + } + for (std::size_t i = 0; i < num_sources - 1; ++i) { + rct::skGen(a[i]); + sc_sub( + a[num_sources - 1].bytes, + a[num_sources - 1].bytes, + a[i].bytes + ); + rct::genC(rv.p.pseudoOuts[i], a[i], sources[i].amount); + } + rct::genC( + rv.p.pseudoOuts[num_sources - 1], + a[num_sources - 1], + sources[num_sources - 1].amount + ); + } + // check balance if reconstructing the tx + else { + rv.p.pseudoOuts = unsigned_tx.rct_signatures.p.pseudoOuts; + if (num_sources != rv.p.pseudoOuts.size()) + return false; + rct::key balance_accumulator = rct::scalarmultH(rct::d2h(fee)); + for (const auto& e: rv.outPk) + rct::addKeys(balance_accumulator, balance_accumulator, e.mask); + for (const auto& pseudoOut: rv.p.pseudoOuts) + rct::subKeys(balance_accumulator, balance_accumulator, pseudoOut); + if (not (balance_accumulator == rct::identity())) + return false; + } + + // prepare input CLSAGs for signing + const rct::key message = get_pre_mlsag_hash(rv, hw::get_device("default")); + + rv.p.CLSAGs.resize(num_sources); + if (reconstruction) { + if (num_sources != unsigned_tx.rct_signatures.p.CLSAGs.size()) + return false; + } + + CLSAG_contexts.resize(num_sources); + if (not reconstruction) + cached_w.resize(num_sources); + + for (std::size_t i = 0; i < num_sources; ++i) { + const std::size_t ring_size = rv.mixRing[i].size(); + const rct::key& I = sources[i].multisig_kLRki.ki; + const std::size_t l = sources[i].real_output; + if (l >= ring_size) + return false; + rct::keyV& s = rv.p.CLSAGs[i].s; + const rct::key& C_offset = rv.p.pseudoOuts[i]; + rct::keyV P(ring_size); + rct::keyV C_nonzero(ring_size); + + if (not reconstruction) { + s.resize(ring_size); + for (std::size_t j = 0; j < ring_size; ++j) { + if (j != l) + s[j] = rct::skGen(); //make fake responses + } + } + else { + if (ring_size != unsigned_tx.rct_signatures.p.CLSAGs[i].s.size()) + return false; + s = unsigned_tx.rct_signatures.p.CLSAGs[i].s; + } + + for (std::size_t j = 0; j < ring_size; ++j) { + P[j] = rv.mixRing[i][j].dest; + C_nonzero[j] = rv.mixRing[i][j].mask; + } + + rct::key D; + rct::key z; + auto z_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&z), sizeof(rct::key)); + }); + if (not reconstruction) { + sc_sub(z.bytes, sources[i].mask.bytes, a[i].bytes); //commitment to zero privkey + ge_p3 H_p3; + rct::hash_to_p3(H_p3, rv.mixRing[i][l].dest); + rct::key H_l; + ge_p3_tobytes(H_l.bytes, &H_p3); + D = rct::scalarmultKey(H_l, z); //auxilliary key image (for commitment to zero) + rv.p.CLSAGs[i].D = rct::scalarmultKey(D, rct::INV_EIGHT); + rv.p.CLSAGs[i].I = I; + } + else { + rv.p.CLSAGs[i].D = unsigned_tx.rct_signatures.p.CLSAGs[i].D; + rv.p.CLSAGs[i].I = I; + D = rct::scalarmultKey(rv.p.CLSAGs[i].D, rct::EIGHT); + } + + if (not CLSAG_contexts[i].init(P, C_nonzero, C_offset, message, I, D, l, s, kAlphaComponents)) + return false; + + if (not reconstruction) { + rct::key mu_P; + rct::key mu_C; + if (not CLSAG_contexts[i].get_mu(mu_P, mu_C)) + return false; + sc_mul(cached_w[i].bytes, mu_P.bytes, input_secret_keys[i].bytes); + sc_muladd(cached_w[i].bytes, mu_C.bytes, z.bytes, cached_w[i].bytes); + } + } + unsigned_tx.rct_signatures = std::move(rv); + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool compute_tx_fee( + const std::vector<cryptonote::tx_source_entry>& sources, + const std::vector<cryptonote::tx_destination_entry>& destinations, + std::uint64_t& fee +) +{ + boost::multiprecision::uint128_t in_amount = 0; + for (const auto& src: sources) + in_amount += src.amount; + + boost::multiprecision::uint128_t out_amount = 0; + for (const auto& dst: destinations) + out_amount += dst.amount; + + if (out_amount > in_amount) + return false; + + if (in_amount - out_amount > std::numeric_limits<std::uint64_t>::max()) + return false; + + fee = static_cast<std::uint64_t>(in_amount - out_amount); + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +tx_builder_ringct_t::tx_builder_ringct_t(): initialized(false) {} +//---------------------------------------------------------------------------------------------------------------------- +tx_builder_ringct_t::~tx_builder_ringct_t() +{ + memwipe(static_cast<rct::key *>(cached_w.data()), cached_w.size() * sizeof(rct::key)); +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::init( + const cryptonote::account_keys& account_keys, + const std::vector<std::uint8_t>& extra, + const std::uint64_t unlock_time, + const std::uint32_t subaddr_account, + const std::set<std::uint32_t>& subaddr_minor_indices, + std::vector<cryptonote::tx_source_entry>& sources, + std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const rct::RCTConfig& rct_config, + const bool use_rct, + const bool reconstruction, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys, + crypto::secret_key& tx_secret_key_entropy, + cryptonote::transaction& unsigned_tx +) +{ + initialized = false; + this->reconstruction = reconstruction; + if (not use_rct) + return false; + if (sources.empty()) + return false; + + if (not reconstruction) + unsigned_tx.set_null(); + + std::uint64_t fee; + if (not compute_tx_fee(sources, destinations, fee)) + return false; + + // decide if view tags are needed + const bool use_view_tags{view_tag_required(rct_config.bp_version)}; + + // misc. fields + unsigned_tx.version = 2; //rct = 2 + unsigned_tx.unlock_time = unlock_time; + + // sort inputs + sort_sources(sources); + + // prepare tx secret key seed (must be AFTER sorting sources) + // - deriving the seed from sources plus entropy ensures uniqueness for every new tx attempt + // - the goal is that two multisig txs added to the chain will never have outputs with the same onetime addresses, + // which would burn funds (embedding the inputs' key images guarantees this) + // - it is acceptable if two tx attempts use the same input set and entropy (only a malicious tx proposer will do + // that, but all it can accomplish is leaking information about the recipients - which a malicious proposer can + // easily do outside the signing ritual anyway) + if (not reconstruction) + tx_secret_key_entropy = rct::rct2sk(rct::skGen()); + + // expect not null (note: wallet serialization code may set this to null if handling an old partial tx) + if (tx_secret_key_entropy == crypto::null_skey) + return false; + + crypto::secret_key tx_secret_key_seed; + make_tx_secret_key_seed(tx_secret_key_entropy, sources, tx_secret_key_seed); + + // get secret keys for signing input CLSAGs (multisig: or for the initial partial signature) + rct::keyV input_secret_keys; + auto input_secret_keys_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(input_secret_keys.data()), input_secret_keys.size() * sizeof(rct::key)); + }); + if (not compute_keys_for_sources(account_keys, sources, subaddr_account, subaddr_minor_indices, input_secret_keys)) + return false; + + // randomize output order + if (not reconstruction) + shuffle_destinations(destinations); + + // prepare outputs + rct::keyV output_public_keys; + rct::keyV output_amount_secret_keys; + std::vector<crypto::view_tag> view_tags; + auto output_amount_secret_keys_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(output_amount_secret_keys.data()), output_amount_secret_keys.size() * sizeof(rct::key)); + }); + if (not compute_keys_for_destinations(account_keys, + subaddr_account, + destinations, + change, + extra, + use_view_tags, + reconstruction, + tx_secret_key_seed, + tx_secret_key, + tx_aux_secret_keys, + output_public_keys, + output_amount_secret_keys, + view_tags, + unsigned_tx)) + return false; + + // add inputs to tx + set_tx_inputs(sources, unsigned_tx); + + // add output one-time addresses to tx + bool set_tx_outputs_result{false}; + if (use_view_tags) + set_tx_outputs_result = set_tx_outputs_with_view_tags(output_public_keys, view_tags, unsigned_tx); + else + set_tx_outputs_result = set_tx_outputs(output_public_keys, unsigned_tx); + + if (not set_tx_outputs_result) + return false; + + // prepare input signatures + if (not set_tx_rct_signatures(fee, sources, destinations, input_secret_keys, output_public_keys, output_amount_secret_keys, + rct_config, reconstruction, unsigned_tx, CLSAG_contexts, cached_w)) + return false; + + initialized = true; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::first_partial_sign( + const std::size_t source, + const rct::keyV& total_alpha_G, + const rct::keyV& total_alpha_H, + const rct::keyV& alpha, + rct::key& c_0, + rct::key& s +) +{ + if (not initialized or reconstruction) + return false; + const std::size_t num_sources = CLSAG_contexts.size(); + if (source >= num_sources) + return false; + rct::key c; + rct::key alpha_combined; + auto alpha_combined_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&alpha_combined), sizeof(rct::key)); + }); + if (not CLSAG_contexts[source].combine_alpha_and_compute_challenge( + total_alpha_G, + total_alpha_H, + alpha, + alpha_combined, + c_0, + c + )) { + return false; + } + + // initial partial response: + // s = alpha_combined_local - challenge*[mu_P*(local keys and sender-receiver secret and subaddress material) + + // mu_C*(commitment-to-zero secret)] + sc_mulsub(s.bytes, c.bytes, cached_w[source].bytes, alpha_combined.bytes); + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::next_partial_sign( + const rct::keyM& total_alpha_G, + const rct::keyM& total_alpha_H, + const rct::keyM& alpha, + const rct::key& x, + rct::keyV& c_0, + rct::keyV& s +) +{ + if (not initialized or not reconstruction) + return false; + const std::size_t num_sources = CLSAG_contexts.size(); + if (num_sources != total_alpha_G.size()) + return false; + if (num_sources != total_alpha_H.size()) + return false; + if (num_sources != alpha.size()) + return false; + if (num_sources != c_0.size()) + return false; + if (num_sources != s.size()) + return false; + for (std::size_t i = 0; i < num_sources; ++i) { + rct::key c; + rct::key alpha_combined; + auto alpha_combined_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&alpha_combined), sizeof(rct::key)); + }); + if (not CLSAG_contexts[i].combine_alpha_and_compute_challenge( + total_alpha_G[i], + total_alpha_H[i], + alpha[i], + alpha_combined, + c_0[i], + c + )) { + return false; + } + rct::key mu_P; + rct::key mu_C; + if (not CLSAG_contexts[i].get_mu(mu_P, mu_C)) + return false; + rct::key w; + auto w_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&w), sizeof(rct::key)); + }); + sc_mul(w.bytes, mu_P.bytes, x.bytes); + + // include local signer's response: + // s += alpha_combined_local - challenge*[mu_P*(local keys)] + sc_add(s[i].bytes, s[i].bytes, alpha_combined.bytes); + sc_mulsub(s[i].bytes, c.bytes, w.bytes, s[i].bytes); + } + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::finalize_tx( + const std::vector<cryptonote::tx_source_entry>& sources, + const rct::keyV& c_0, + const rct::keyV& s, + cryptonote::transaction& unsigned_tx +) +{ + // checks + const std::size_t num_sources = sources.size(); + if (num_sources != unsigned_tx.rct_signatures.p.CLSAGs.size()) + return false; + if (num_sources != c_0.size()) + return false; + if (num_sources != s.size()) + return false; + + // finalize tx signatures + for (std::size_t i = 0; i < num_sources; ++i) { + const std::size_t ring_size = unsigned_tx.rct_signatures.p.CLSAGs[i].s.size(); + if (sources[i].real_output >= ring_size) + return false; + unsigned_tx.rct_signatures.p.CLSAGs[i].s[sources[i].real_output] = s[i]; + unsigned_tx.rct_signatures.p.CLSAGs[i].c1 = c_0[i]; + } + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +} //namespace signing + +} //namespace multisig diff --git a/src/multisig/multisig_tx_builder_ringct.h b/src/multisig/multisig_tx_builder_ringct.h new file mode 100644 index 000000000..853934659 --- /dev/null +++ b/src/multisig/multisig_tx_builder_ringct.h @@ -0,0 +1,120 @@ +// Copyright (c) 2021, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#pragma once + +#include "ringct/rctTypes.h" + +#include <set> +#include <vector> + +namespace cryptonote { + +class transaction; +struct tx_source_entry; +struct tx_destination_entry; +struct account_keys; + +} + +namespace multisig { + +namespace signing { + +class CLSAG_context_t; + +// number of parallel signing nonces to use per signer (2 nonces as in musig2 and FROST) +constexpr std::size_t kAlphaComponents = 2; + +class tx_builder_ringct_t final { +private: + // the tx builder has been initialized + bool initialized; + // the tx builder is 'reconstructing' a tx that has already been created using this object + bool reconstruction; + // cached: mu_P*(local keys and sender-receiver secret and subaddress material) + mu_C*(commitment-to-zero secret) + // - these are only used for the initial building of a tx (not reconstructions) + rct::keyV cached_w; + // contexts for making CLSAG challenges with multisig nonces + std::vector<CLSAG_context_t> CLSAG_contexts; +public: + tx_builder_ringct_t(); + ~tx_builder_ringct_t(); + + // prepare an unsigned transaction (and get tx privkeys for outputs) + bool init( + const cryptonote::account_keys& account_keys, + const std::vector<std::uint8_t>& extra, + const std::uint64_t unlock_time, + const std::uint32_t subaddr_account, + const std::set<std::uint32_t>& subaddr_minor_indices, + std::vector<cryptonote::tx_source_entry>& sources, + std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const rct::RCTConfig& rct_config, + const bool use_rct, + const bool reconstruction, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys, + crypto::secret_key& tx_secret_key_entropy, + cryptonote::transaction& unsigned_tx + ); + + // get the first partial signature for the specified input ('source') + bool first_partial_sign( + const std::size_t source, + const rct::keyV& total_alpha_G, + const rct::keyV& total_alpha_H, + const rct::keyV& alpha, + rct::key& c_0, + rct::key& s + ); + + // get intermediate partial signatures for all the inputs + bool next_partial_sign( + const rct::keyM& total_alpha_G, + const rct::keyM& total_alpha_H, + const rct::keyM& alpha, + const rct::key& x, + rct::keyV& c_0, + rct::keyV& s + ); + + // finalize an unsigned transaction (add challenges and real responses to incomplete CLSAG signatures) + static bool finalize_tx( + const std::vector<cryptonote::tx_source_entry>& sources, + const rct::keyV& c_0, + const rct::keyV& s, + cryptonote::transaction& unsigned_tx + ); +}; + +} //namespace signing + +} //namespace multisig diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index bd67778ec..21040317c 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -238,14 +238,12 @@ namespace rct { // P[l] == p*G // C[l] == z*G // C[i] == C_nonzero[i] - C_offset (for hashing purposes) for all i - clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout, hw::device &hwdev) { + clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, hw::device &hwdev) { clsag sig; size_t n = P.size(); // ring size CHECK_AND_ASSERT_THROW_MES(n == C.size(), "Signing and commitment key vector sizes must match!"); CHECK_AND_ASSERT_THROW_MES(n == C_nonzero.size(), "Signing and commitment key vector sizes must match!"); CHECK_AND_ASSERT_THROW_MES(l < n, "Signing index out of range!"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); - CHECK_AND_ASSERT_THROW_MES((mscout && mspout) || !kLRki, "Multisig pointers are not all present"); // Key images ge_p3 H_p3; @@ -260,16 +258,7 @@ namespace rct { key aG; key aH; - // Multisig - if (kLRki) - { - sig.I = kLRki->ki; - scalarmultKey(D,H,z); - } - else - { - hwdev.clsag_prepare(p,z,sig.I,D,H,a,aG,aH); - } + hwdev.clsag_prepare(p,z,sig.I,D,H,a,aG,aH); geDsmp I_precomp; geDsmp D_precomp; @@ -317,18 +306,9 @@ namespace rct { c_to_hash[2*n+1] = C_offset; c_to_hash[2*n+2] = message; - // Multisig data is present - if (kLRki) - { - a = kLRki->k; - c_to_hash[2*n+3] = kLRki->L; - c_to_hash[2*n+4] = kLRki->R; - } - else - { - c_to_hash[2*n+3] = aG; - c_to_hash[2*n+4] = aH; - } + c_to_hash[2*n+3] = aG; + c_to_hash[2*n+4] = aH; + hwdev.clsag_hash(c_to_hash,c); size_t i; @@ -380,16 +360,11 @@ namespace rct { hwdev.clsag_sign(c,a,p,z,mu_P,mu_C,sig.s[l]); memwipe(&a, sizeof(key)); - if (mscout) - *mscout = c; - if (mspout) - *mspout = mu_P; - return sig; } clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l) { - return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, NULL, NULL, NULL, hw::get_device("default")); + return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, hw::get_device("default")); } // MLSAG signatures @@ -397,7 +372,7 @@ namespace rct { // This generalization allows for some dimensions not to require linkability; // this is used in practice for commitment data within signatures // Note that using more than one linkable dimension is not recommended. - mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev) { + mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows, hw::device &hwdev) { mgSig rv; size_t cols = pk.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!"); @@ -409,8 +384,6 @@ namespace rct { } CHECK_AND_ASSERT_THROW_MES(xx.size() == rows, "Bad xx size"); CHECK_AND_ASSERT_THROW_MES(dsRows <= rows, "Bad dsRows size"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); - CHECK_AND_ASSERT_THROW_MES(!kLRki || dsRows == 1, "Multisig requires exactly 1 dsRows"); size_t i = 0, j = 0, ii = 0; key c, c_old, L, R, Hi; @@ -428,20 +401,11 @@ namespace rct { DP("here1"); for (i = 0; i < dsRows; i++) { toHash[3 * i + 1] = pk[index][i]; - if (kLRki) { - // multisig - alpha[i] = kLRki->k; - toHash[3 * i + 2] = kLRki->L; - toHash[3 * i + 3] = kLRki->R; - rv.II[i] = kLRki->ki; - } - else { - hash_to_p3(Hi_p3, pk[index][i]); - ge_p3_tobytes(Hi.bytes, &Hi_p3); - hwdev.mlsag_prepare(Hi, xx[i], alpha[i] , aG[i] , aHP[i] , rv.II[i]); - toHash[3 * i + 2] = aG[i]; - toHash[3 * i + 3] = aHP[i]; - } + hash_to_p3(Hi_p3, pk[index][i]); + ge_p3_tobytes(Hi.bytes, &Hi_p3); + hwdev.mlsag_prepare(Hi, xx[i], alpha[i] , aG[i] , aHP[i] , rv.II[i]); + toHash[3 * i + 2] = aG[i]; + toHash[3 * i + 3] = aHP[i]; precomp(Ip[i].k, rv.II[i]); } size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper) @@ -485,8 +449,6 @@ namespace rct { } } hwdev.mlsag_sign(c, xx, alpha, rows, dsRows, rv.ss[index]); - if (mscout) - *mscout = c; return rv; } @@ -722,7 +684,7 @@ namespace rct { // this shows that sum inputs = sum outputs //Ver: // verifies the above sig is created corretly - mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, const key &txnFeeKey, hw::device &hwdev) { + mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, unsigned int index, const key &txnFeeKey, hw::device &hwdev) { //setup vars size_t cols = pubs.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); @@ -733,7 +695,6 @@ namespace rct { } CHECK_AND_ASSERT_THROW_MES(inSk.size() == rows, "Bad inSk size"); CHECK_AND_ASSERT_THROW_MES(outSk.size() == outPk.size(), "Bad outSk/outPk size"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); keyV sk(rows + 1); keyV tmp(rows + 1); @@ -766,7 +727,7 @@ namespace rct { for (size_t j = 0; j < outPk.size(); j++) { sc_sub(sk[rows].bytes, sk[rows].bytes, outSk[j].mask.bytes); //subtract output masks in last row.. } - mgSig result = MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev); + mgSig result = MLSAG_Gen(message, M, sk, index, rows, hwdev); memwipe(sk.data(), sk.size() * sizeof(key)); return result; } @@ -779,12 +740,11 @@ namespace rct { // inSk is x, a_in corresponding to signing index // a_out, Cout is for the output commitment // index is the signing index.. - mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, const multisig_kLRki *kLRki, key *mscout, unsigned int index, hw::device &hwdev) { + mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index, hw::device &hwdev) { //setup vars size_t rows = 1; size_t cols = pubs.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); keyV tmp(rows + 1); keyV sk(rows + 1); size_t i; @@ -796,17 +756,16 @@ namespace rct { M[i][0] = pubs[i].dest; subKeys(M[i][1], pubs[i].mask, Cout); } - mgSig result = MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev); + mgSig result = MLSAG_Gen(message, M, sk, index, rows, hwdev); memwipe(sk.data(), sk.size() * sizeof(key)); return result; } - clsag proveRctCLSAGSimple(const key &message, const ctkeyV &pubs, const ctkey &inSk, const key &a, const key &Cout, const multisig_kLRki *kLRki, key *mscout, key *mspout, unsigned int index, hw::device &hwdev) { + clsag proveRctCLSAGSimple(const key &message, const ctkeyV &pubs, const ctkey &inSk, const key &a, const key &Cout, unsigned int index, hw::device &hwdev) { //setup vars size_t rows = 1; size_t cols = pubs.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); keyV tmp(rows + 1); keyV sk(rows + 1); keyM M(cols, tmp); @@ -826,7 +785,7 @@ namespace rct { sk[0] = copy(inSk.dest); sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes); - clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, kLRki, mscout, mspout, hwdev); + clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, hwdev); memwipe(sk.data(), sk.size() * sizeof(key)); return result; } @@ -1084,14 +1043,13 @@ namespace rct { // must know the destination private key to find the correct amount, else will return a random number // Note: For txn fees, the last index in the amounts vector should contain that // Thus the amounts vector will be "one" longer than the destinations vectort - rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations"); CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations"); CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing"); for (size_t n = 0; n < mixRing.size(); ++n) { CHECK_AND_ASSERT_THROW_MES(mixRing[n].size() == inSk.size(), "Bad mixRing size"); } - CHECK_AND_ASSERT_THROW_MES((kLRki && msout) || (!kLRki && !msout), "Only one of kLRki/msout is present"); CHECK_AND_ASSERT_THROW_MES(inSk.size() < 2, "genRct is not suitable for 2+ rings"); rctSig rv; @@ -1130,23 +1088,21 @@ namespace rct { key txnFeeKey = scalarmultH(d2h(rv.txnFee)); rv.mixRing = mixRing; - if (msout) - msout->c.resize(1); - rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv, hwdev), rv.mixRing, inSk, outSk, rv.outPk, kLRki, msout ? &msout->c[0] : NULL, index, txnFeeKey,hwdev)); + rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv, hwdev), rv.mixRing, inSk, outSk, rv.outPk, index, txnFeeKey,hwdev)); return rv; } - rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin, const RCTConfig &rct_config, hw::device &hwdev) { + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin, const RCTConfig &rct_config, hw::device &hwdev) { unsigned int index; ctkeyM mixRing; ctkeyV outSk; tie(mixRing, index) = populateFromBlockchain(inPk, mixin); - return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, kLRki, msout, index, outSk, rct_config, hwdev); + return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, index, outSk, rct_config, hwdev); } //RCT simple //for post-rct only - rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { + rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { const bool bulletproof_or_plus = rct_config.range_proof_type > RangeProofBorromean; CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); @@ -1157,10 +1113,6 @@ namespace rct { for (size_t n = 0; n < mixRing.size(); ++n) { CHECK_AND_ASSERT_THROW_MES(index[n] < mixRing[n].size(), "Bad index into mixRing"); } - CHECK_AND_ASSERT_THROW_MES((kLRki && msout) || (!kLRki && !msout), "Only one of kLRki/msout is present"); - if (kLRki && msout) { - CHECK_AND_ASSERT_THROW_MES(kLRki->size() == inamounts.size(), "Mismatched kLRki/inamounts sizes"); - } rctSig rv; if (bulletproof_or_plus) @@ -1322,11 +1274,7 @@ namespace rct { DP(pseudoOuts[i]); key full_message = get_pre_mlsag_hash(rv,hwdev); - if (msout) - { - msout->c.resize(inamounts.size()); - msout->mu_p.resize(is_rct_clsag(rv.type) ? inamounts.size() : 0); - } + for (i = 0 ; i < inamounts.size(); i++) { if (is_rct_clsag(rv.type)) @@ -1334,17 +1282,17 @@ namespace rct { 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); + rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], index[i], hwdev); } else { - rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, index[i], hwdev); + rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], index[i], hwdev); } } return rv; } - rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev) { + rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev) { std::vector<unsigned int> index; index.resize(inPk.size()); ctkeyM mixRing; @@ -1354,7 +1302,7 @@ namespace rct { mixRing[i].resize(mixin+1); index[i] = populateFromBlockchainSimple(mixRing[i], inPk[i], mixin); } - return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, kLRki, msout, index, outSk, rct_config, hwdev); + return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, index, outSk, rct_config, hwdev); } //RingCT protocol @@ -1700,60 +1648,4 @@ namespace rct { key mask; return decodeRctSimple(rv, sk, i, mask, hwdev); } - - bool signMultisigMLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2, - false, "unsupported rct type"); - CHECK_AND_ASSERT_MES(!is_rct_clsag(rv.type), false, "CLSAG signature type in MLSAG signature function"); - CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); - CHECK_AND_ASSERT_MES(k.size() == rv.p.MGs.size(), false, "Mismatched k/MGs size"); - CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); - CHECK_AND_ASSERT_MES(rv.p.CLSAGs.empty(), false, "CLSAGs not empty for MLSAGs"); - if (rv.type == RCTTypeFull) - { - CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "MGs not a single element"); - } - for (size_t n = 0; n < indices.size(); ++n) { - CHECK_AND_ASSERT_MES(indices[n] < rv.p.MGs[n].ss.size(), false, "Index out of range"); - CHECK_AND_ASSERT_MES(!rv.p.MGs[n].ss[indices[n]].empty(), false, "empty ss line"); - } - - // MLSAG: each player contributes a share to the secret-index ss: k - cc*secret_key_share - // cc: msout.c[n], secret_key_share: secret_key - for (size_t n = 0; n < indices.size(); ++n) { - rct::key diff; - sc_mulsub(diff.bytes, msout.c[n].bytes, secret_key.bytes, k[n].bytes); - sc_add(rv.p.MGs[n].ss[indices[n]][0].bytes, rv.p.MGs[n].ss[indices[n]][0].bytes, diff.bytes); - } - return true; - } - - bool signMultisigCLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - CHECK_AND_ASSERT_MES(is_rct_clsag(rv.type), false, "unsupported rct type"); - CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); - CHECK_AND_ASSERT_MES(k.size() == rv.p.CLSAGs.size(), false, "Mismatched k/CLSAGs size"); - CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); - CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs not empty for CLSAGs"); - CHECK_AND_ASSERT_MES(msout.c.size() == msout.mu_p.size(), false, "Bad mu_p size"); - for (size_t n = 0; n < indices.size(); ++n) { - CHECK_AND_ASSERT_MES(indices[n] < rv.p.CLSAGs[n].s.size(), false, "Index out of range"); - } - - // CLSAG: each player contributes a share to the secret-index ss: k - cc*mu_p*secret_key_share - // cc: msout.c[n], mu_p, msout.mu_p[n], secret_key_share: secret_key - for (size_t n = 0; n < indices.size(); ++n) { - rct::key diff, sk; - sc_mul(sk.bytes, msout.mu_p[n].bytes, secret_key.bytes); - sc_mulsub(diff.bytes, msout.c[n].bytes, sk.bytes, k[n].bytes); - sc_add(rv.p.CLSAGs[n].s[indices[n]].bytes, rv.p.CLSAGs[n].s[indices[n]].bytes, diff.bytes); - } - return true; - } - - bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - if (is_rct_clsag(rv.type)) - return signMultisigCLSAG(rv, indices, k, msout, secret_key); - else - return signMultisigMLSAG(rv, indices, k, msout, secret_key); - } } diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index a0346b34e..17cfd77b9 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -74,12 +74,12 @@ namespace rct { // Gen creates a signature which proves that for some column in the keymatrix "pk" // the signer knows a secret key for each row in that column // Ver verifies that the MG sig was created correctly - mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev); + mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows, hw::device &hwdev); bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows); - clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout, hw::device &hwdev); + clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, hw::device &hwdev); clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l); - clsag proveRctCLSAGSimple(const key &, const ctkeyV &, const ctkey &, const key &, const key &, const multisig_kLRki *, key *, key *, unsigned int, hw::device &); + clsag proveRctCLSAGSimple(const key &, const ctkeyV &, const ctkey &, const key &, const key &, unsigned int, hw::device &); bool verRctCLSAGSimple(const key &, const clsag &, const ctkeyV &, const key &); //proveRange and verRange @@ -100,8 +100,8 @@ namespace rct { // this shows that sum inputs = sum outputs //Ver: // verifies the above sig is created corretly - mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, const key &txnFee, const key &message, hw::device &hwdev); - mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, const multisig_kLRki *kLRki, key *mscout, unsigned int index, hw::device &hwdev); + mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, unsigned int index, const key &txnFee, const key &message, hw::device &hwdev); + mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index, hw::device &hwdev); bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, const key &txnFee, const key &message); bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C); @@ -123,10 +123,10 @@ namespace rct { //decodeRct: (c.f. https://eprint.iacr.org/2015/1098 section 5.1.1) // uses the attached ecdh info to find the amounts represented by each output commitment // must know the destination private key to find the correct amount, else will return a random number - rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); - rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin, const RCTConfig &rct_config, hw::device &hwdev); - rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev); - rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); bool verRct(const rctSig & rv, bool semantics); static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); } bool verRctSemanticsSimple(const rctSig & rv); @@ -138,7 +138,6 @@ namespace rct { xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask, hw::device &hwdev); xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, hw::device &hwdev); key get_pre_mlsag_hash(const rctSig &rv, hw::device &hwdev); - bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key); } #endif /* RCTSIGS_H */ diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index f6e313089..a8f4e5a07 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -5673,14 +5673,18 @@ void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block m_refresh_progress_reporter.update(height, false); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) +void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) { if (m_locked) return; + std::stringstream burn; + if (burnt != 0) { + burn << " (" << print_money(amount) << " yet " << print_money(burnt) << " was burnt)"; + } message_writer(console_color_green, false) << "\r" << tr("Height ") << height << ", " << tr("txid ") << txid << ", " << - print_money(amount) << ", " << + print_money(amount - burnt) << burn.str() << ", " << tr("idx ") << subaddr_index; const uint64_t warn_height = m_wallet->nettype() == TESTNET ? 1000000 : m_wallet->nettype() == STAGENET ? 50000 : 1650000; @@ -6960,18 +6964,33 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer(const std::vector<std::string> &args_) { + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_TRANSFER); + return true; + } transfer_main(Transfer, args_, false); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::locked_transfer(const std::vector<std::string> &args_) { + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_LOCKED_TRANSFER); + return true; + } transfer_main(TransferLocked, args_, false); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_) { + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_LOCKED_SWEEP_ALL); + return true; + } sweep_main(m_current_subaddress_account, 0, true, args_); return true; } @@ -7670,6 +7689,7 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_) if (args_.size() < 1) { fail_msg_writer() << tr("missing threshold amount"); + PRINT_USAGE(USAGE_SWEEP_BELOW); return true; } if (!cryptonote::parse_amount(below, args_[0])) diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 6c4ddd4e7..6a9fa149d 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -346,7 +346,7 @@ namespace cryptonote //----------------- i_wallet2_callback --------------------- virtual void on_new_block(uint64_t height, const cryptonote::block& block); - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time); + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time); virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index); virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index); virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 260caa551..1ee2e20b6 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -154,18 +154,20 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) { std::string tx_hash = epee::string_tools::pod_to_hex(txid); LOG_PRINT_L3(__FUNCTION__ << ": money received. height: " << height << ", tx: " << tx_hash - << ", amount: " << print_money(amount) + << ", amount: " << print_money(amount - burnt) + << ", burnt: " << print_money(burnt) + << ", raw_output_value: " << print_money(amount) << ", idx: " << subaddr_index); // do not signal on received tx if wallet is not syncronized completely if (m_listener && m_wallet->synchronized()) { - m_listener->moneyReceived(tx_hash, amount); + m_listener->moneyReceived(tx_hash, amount - burnt); m_listener->updated(); } } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 0b2a6c0f5..195763949 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -62,6 +62,7 @@ using namespace epee; #include "multisig/multisig.h" #include "multisig/multisig_account.h" #include "multisig/multisig_kex_msg.h" +#include "multisig/multisig_tx_builder_ringct.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" @@ -2208,7 +2209,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); + m_callback->on_money_received(height, txid, tx, td.m_amount, 0, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); } total_received_1 += amount; notify = true; @@ -2242,7 +2243,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote tx_money_got_in_outs[tx_scan_info[o].received->index] -= m_transfers[kit->second].amount(); uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount; - uint64_t extra_amount = amount - m_transfers[kit->second].amount(); + uint64_t burnt = m_transfers[kit->second].amount(); + uint64_t extra_amount = amount - burnt; if (!pool) { transfer_details &td = m_transfers[kit->second]; @@ -2285,7 +2287,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); + m_callback->on_money_received(height, txid, tx, td.m_amount, burnt, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); } total_received_1 += extra_amount; notify = true; @@ -3159,14 +3161,18 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, } } - // get those txes - if (!txids.empty()) + // get_transaction_pool_hashes.bin may return more transactions than we're allowed to request in restricted mode + const size_t SLICE_SIZE = 100; // RESTRICTED_TRANSACTIONS_COUNT as defined in rpc/core_rpc_server.cpp + for (size_t offset = 0; offset < txids.size(); offset += SLICE_SIZE) { cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - for (const auto &p: txids) - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first)); - MDEBUG("asking for " << txids.size() << " transactions"); + + const size_t n_txids = std::min<size_t>(SLICE_SIZE, txids.size() - offset); + for (size_t n = offset; n < (offset + n_txids); ++n) { + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids.at(n).first)); + } + MDEBUG("asking for " << req.txs_hashes.size() << " transactions"); req.decode_as_json = false; req.prune = true; @@ -3183,7 +3189,7 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, MDEBUG("Got " << r << " and " << res.status); if (r && res.status == CORE_RPC_STATUS_OK) { - if (res.txs.size() == txids.size()) + if (res.txs.size() == req.txs_hashes.size()) { for (const auto &tx_entry: res.txs) { @@ -3219,7 +3225,7 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, } else { - LOG_PRINT_L0("Expected " << txids.size() << " tx(es), got " << res.txs.size()); + LOG_PRINT_L0("Expected " << n_txids << " out of " << txids.size() << " tx(es), got " << res.txs.size()); } } else @@ -3622,32 +3628,7 @@ bool wallet2::refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& rece //---------------------------------------------------------------------------------------------------- bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution) { - uint32_t rpc_version; - boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_version(rpc_version); - // no error - if (!!result) - { - // empty string -> not connection - THROW_WALLET_EXCEPTION_IF(result->empty(), tools::error::no_connection_to_daemon, "getversion"); - THROW_WALLET_EXCEPTION_IF(*result == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getversion"); - if (*result != CORE_RPC_STATUS_OK) - { - MDEBUG("Cannot determine daemon RPC version, not requesting rct distribution"); - return false; - } - } - else - { - if (rpc_version >= MAKE_CORE_RPC_VERSION(1, 19)) - { - MDEBUG("Daemon is recent enough, requesting rct distribution"); - } - else - { - MDEBUG("Daemon is too old, not requesting rct distribution"); - return false; - } - } + MDEBUG("Requesting rct distribution"); cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response res = AUTO_VAL_INIT(res); @@ -5070,7 +5051,6 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, m_multisig_rounds_passed = 1; // derivations stored (should be empty in last round) - // TODO: make use of the origins map for aggregation-style signing (instead of round-robin) m_multisig_derivations.clear(); m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size()); @@ -5127,7 +5107,6 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor expanded_msgs.emplace_back(msg); // reconstruct multisig account - crypto::public_key dummy; multisig::multisig_keyset_map_memsafe_t kex_origins_map; for (const auto &derivation : m_multisig_derivations) @@ -5163,7 +5142,6 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor "Failed to update multisig wallet account due to bad keys"); // derivations stored (should be empty in last round) - // TODO: make use of the origins map for aggregation-style signing (instead of round-robin) m_multisig_derivations.clear(); m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size()); @@ -6642,8 +6620,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin rct::RCTConfig rct_config = sd.rct_config; crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - rct::multisig_out msout; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, rct_config, m_multisig ? &msout : NULL, sd.use_view_tags); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, rct_config, sd.use_view_tags); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype); // we don't test tx size, because we don't know the current limit, due to not having a blockchain, // and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway, @@ -7151,77 +7128,114 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto txids.clear(); - // sign the transactions + // The 'exported_txs' contains a set of different transactions for the multisig group to try to sign. Each of those + // transactions has a set of 'signing attempts' corresponding to all the possible signing groups within the multisig. + // - Here, we will partially sign as many of those signing attempts as possible, for each proposed transaction. for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) { tools::wallet2::pending_tx &ptx = exported_txs.m_ptx[n]; THROW_WALLET_EXCEPTION_IF(ptx.multisig_sigs.empty(), error::wallet_internal_error, "No signatures found in multisig tx"); - tools::wallet2::tx_construction_data &sd = ptx.construction_data; - LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, mixin " << (sd.sources[0].outputs.size()-1) << + const tools::wallet2::tx_construction_data &sd = ptx.construction_data; + LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << (sd.sources[0].outputs.size()) << ", signed by " << exported_txs.m_signers.size() << "/" << m_multisig_threshold); - cryptonote::transaction tx; - rct::multisig_out msout = ptx.multisig_sigs.front().msout; - auto sources = sd.sources; - rct::RCTConfig rct_config = sd.rct_config; - bool shuffle_outs = false; - bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, rct_config, &msout, shuffle_outs, sd.use_view_tags); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype); - THROW_WALLET_EXCEPTION_IF(get_transaction_prefix_hash (tx) != get_transaction_prefix_hash(ptx.tx), - error::wallet_internal_error, "Transaction prefix does not match data"); - - // Tests passed, sign - std::vector<unsigned int> indices; - for (const auto &source: sources) - indices.push_back(source.real_output); + // reconstruct the partially-signed transaction attempt to verify we are signing something that at least looks like a transaction + // note: the caller should further verify that the tx details are acceptable (inputs/outputs/memos/tx type) + multisig::signing::tx_builder_ringct_t multisig_tx_builder; + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.init( + m_account.get_keys(), + ptx.construction_data.extra, + ptx.construction_data.unlock_time, + ptx.construction_data.subaddr_account, + ptx.construction_data.subaddr_indices, + ptx.construction_data.sources, + ptx.construction_data.splitted_dsts, + ptx.construction_data.change_dts, + ptx.construction_data.rct_config, + ptx.construction_data.use_rct, + true, //true = we are reconstructing the tx (it was first constructed by the tx proposer) + ptx.tx_key, + ptx.additional_tx_keys, + ptx.multisig_tx_key_entropy, + ptx.tx + ), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::init" + ); + // go through each signing attempt for this transaction (each signing attempt corresponds to some subgroup of signers + // of size 'threshold') for (auto &sig: ptx.multisig_sigs) { + // skip this partial tx if it's intended for a subgroup of signers that doesn't include the local signer + // note: this check can only weed out signers who provided multisig_infos to the multisig tx proposer's + // (initial author's) last call to import_multisig() before making this tx proposal; all other signers + // will encounter a 'need to export multisig' wallet error in get_multisig_k() below + // note2: the 'need to export multisig' wallet error can also appear if a bad/buggy tx proposer adds duplicate + // 'used_L' to the set of tx attempts, or if two different tx proposals use the same 'used_L' values and the + // local signer calls this function on both of them if (sig.ignore.find(local_signer) == sig.ignore.end()) { - ptx.tx.rct_signatures = sig.sigs; - - rct::keyV k; + rct::keyM local_nonces_k(sd.selected_transfers.size(), rct::keyV(multisig::signing::kAlphaComponents)); rct::key skey = rct::zero(); - auto wiper = epee::misc_utils::create_scope_leave_handler([&](){ memwipe(k.data(), k.size() * sizeof(k[0])); memwipe(&skey, sizeof(skey)); }); - - for (size_t idx: sd.selected_transfers) - k.push_back(get_multisig_k(idx, sig.used_L)); + auto wiper = epee::misc_utils::create_scope_leave_handler([&]{ + for (auto& e: local_nonces_k) + memwipe(e.data(), e.size() * sizeof(rct::key)); + memwipe(&skey, sizeof(rct::key)); + }); + + // get local signer's nonces for this transaction attempt's inputs + // note: whoever created 'exported_txs' has full power to match proposed tx inputs (selected_transfers) + // with the public nonces of the multisig signers who call this function (via 'used_L' as identifiers), however + // the local signer will only use a given nonce exactly once (even if a used_L is repeated) + for (std::size_t i = 0; i < local_nonces_k.size(); ++i) { + for (std::size_t j = 0; j < multisig::signing::kAlphaComponents; ++j) { + get_multisig_k(sd.selected_transfers[i], sig.used_L, local_nonces_k[i][j]); + } + } - for (const auto &msk: get_account().get_multisig_keys()) + // round-robin signing: sign with all local multisig key shares that other signers have not signed with yet + for (const auto &multisig_skey: get_account().get_multisig_keys()) { - crypto::public_key pmsk = get_multisig_signing_public_key(msk); + crypto::public_key multisig_pkey = get_multisig_signing_public_key(multisig_skey); - if (sig.signing_keys.find(pmsk) == sig.signing_keys.end()) + if (sig.signing_keys.find(multisig_pkey) == sig.signing_keys.end()) { - sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); - sig.signing_keys.insert(pmsk); + sc_add(skey.bytes, skey.bytes, rct::sk2rct(multisig_skey).bytes); + sig.signing_keys.insert(multisig_pkey); } } - THROW_WALLET_EXCEPTION_IF(!rct::signMultisig(ptx.tx.rct_signatures, indices, k, sig.msout, skey), - error::wallet_internal_error, "Failed signing, transaction likely malformed"); - sig.sigs = ptx.tx.rct_signatures; + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.next_partial_sign(sig.total_alpha_G, sig.total_alpha_H, local_nonces_k, skey, sig.c_0, sig.s), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::next_partial_sign" + ); } } const bool is_last = exported_txs.m_signers.size() + 1 >= m_multisig_threshold; if (is_last) { - // when the last signature on a multisig tx is made, we select the right - // signature to plug into the final tx + // if there are signatures from enough signers (assuming the local signer signed 1+ tx attempts), find the tx + // attempt with a full set of signatures so this tx can be finalized bool found = false; for (const auto &sig: ptx.multisig_sigs) { if (sig.ignore.find(local_signer) == sig.ignore.end() && !keys_intersect(sig.ignore, exported_txs.m_signers)) { THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final"); - ptx.tx.rct_signatures = sig.sigs; + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.finalize_tx(ptx.construction_data.sources, sig.c_0, sig.s, ptx.tx), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::finalize_tx" + ); found = true; } } THROW_WALLET_EXCEPTION_IF(!found, error::wallet_internal_error, - "Final signed transaction not found: this transaction was likely made without our export data, so we cannot sign it"); + "Unable to finalize the transaction: the ignore sets for these tx attempts seem to be malformed."); const crypto::hash txid = get_transaction_hash(ptx.tx); if (store_tx_info()) { @@ -7232,7 +7246,8 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto } } - // txes generated, get rid of used k values + // signatures generated, get rid of any unused k values (must do export_multisig() to make more tx attempts with the + // inputs in the transactions worked on here) for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) for (size_t idx: exported_txs.m_ptx[n].construction_data.selected_transfers) memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); @@ -8049,8 +8064,13 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> has_rct = true; max_rct_index = std::max(max_rct_index, m_transfers[idx].m_global_output_index); } - const bool has_rct_distribution = has_rct && (!rct_offsets.empty() || get_rct_distribution(rct_start_height, rct_offsets)); - if (has_rct_distribution) + + if (has_rct && rct_offsets.empty()) { + THROW_WALLET_EXCEPTION_IF(!get_rct_distribution(rct_start_height, rct_offsets), + error::get_output_distribution, "Could not obtain output distribution."); + } + + if (has_rct) { // check we're clear enough of rct start, to avoid corner cases below THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, @@ -8062,11 +8082,11 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // get histogram for the amounts we need 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 + // request histogram for all pre-rct outputs 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()); + if (!m_transfers[idx].is_rct()) + req_t.amounts.push_back(m_transfers[idx].amount()); if (!req_t.amounts.empty()) { std::sort(req_t.amounts.begin(), req_t.amounts.end()); @@ -8166,7 +8186,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> COMMAND_RPC_GET_OUTPUTS_BIN::response daemon_resp = AUTO_VAL_INIT(daemon_resp); std::unique_ptr<gamma_picker> gamma; - if (has_rct_distribution) + if (has_rct) gamma.reset(new gamma_picker(rct_offsets)); size_t num_selected_transfers = 0; @@ -8181,7 +8201,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // request more for rct in base recent (locked) coinbases are picked, since they're locked for longer size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); size_t start = req.outputs.size(); - bool use_histogram = amount != 0 || !has_rct_distribution; + bool use_histogram = amount != 0; const bool output_is_pre_fork = td.m_block_height < segregation_fork_height; uint64_t num_outs = 0, num_recent_outs = 0; @@ -8368,7 +8388,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> uint64_t i; const char *type = ""; - if (amount == 0 && has_rct_distribution) + if (amount == 0) { THROW_WALLET_EXCEPTION_IF(!gamma, error::wallet_internal_error, "No gamma picker"); // gamma distribution @@ -8530,7 +8550,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> break; } } - bool use_histogram = amount != 0 || !has_rct_distribution; + bool use_histogram = amount != 0; if (!use_histogram) num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE]; @@ -8758,9 +8778,8 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - rct::multisig_out msout; LOG_PRINT_L2("constructing tx"); - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, {}, m_multisig ? &msout : NULL, use_view_tags); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, {}, use_view_tags); LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); @@ -8842,6 +8861,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry // At this step we need to define set of participants available for signature, // i.e. those of them who exchanged with multisig info's + // note: The oldest unspent owned output's multisig info (in m_transfers) will contain the most recent result of + // 'import_multisig()', which means only 'fresh' multisig infos (public nonces) will be used to make tx attempts. + // - If a signer's info was missing from the latest call to 'import_multisig()', then they won't be able to participate! + // - If a newly-acquired output doesn't have enouch nonces from multisig infos, then it can't be spent! for (const crypto::public_key &signer: m_multisig_signers) { if (signer == local_signer) @@ -8909,7 +8932,6 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry LOG_PRINT_L2("preparing outputs"); size_t i = 0, out_index = 0; std::vector<cryptonote::tx_source_entry> sources; - std::unordered_set<rct::key> used_L; for(size_t idx: selected_transfers) { sources.resize(sources.size()+1); @@ -8952,10 +8974,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry src.real_output_in_tx_index = td.m_internal_output_index; src.mask = td.m_mask; if (m_multisig) - { - auto ignore_set = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front(); - src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_set, used_L, used_L); - } + // note: multisig_kLRki is a legacy struct, currently only used as a key image shuttle into the multisig tx builder + src.multisig_kLRki = {.k = {}, .L = {}, .R = {}, .ki = rct::ki2rct(td.m_key_image)}; else src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()}); detail::print_source_entry(src); @@ -8992,12 +9012,43 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - rct::multisig_out msout; + crypto::secret_key multisig_tx_key_entropy; LOG_PRINT_L2("constructing tx"); auto sources_copy = sources; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, rct_config, m_multisig ? &msout : NULL, use_view_tags); - LOG_PRINT_L2("constructed tx, r="<<r); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype); + multisig::signing::tx_builder_ringct_t multisig_tx_builder; + if (m_multisig) { + // prepare the core part of a multisig tx (many tx attempts for different signer groups can be spun off this core piece) + std::set<std::uint32_t> subaddr_minor_indices; + for (size_t idx: selected_transfers) { + subaddr_minor_indices.insert(m_transfers[idx].m_subaddr_index.minor); + } + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.init(m_account.get_keys(), + extra, + unlock_time, + subaddr_account, + subaddr_minor_indices, + sources, + splitted_dsts, + change_dts, + rct_config, + true, + false, + tx_key, + additional_tx_keys, + multisig_tx_key_entropy, + tx + ), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::init" + ); + } + else { + // make a normal tx + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, rct_config, use_view_tags); + LOG_PRINT_L2("constructed tx, r="<<r); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype); + } THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); // work out the permutation done on sources @@ -9015,42 +9066,77 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry THROW_WALLET_EXCEPTION_IF(ins_order.size() != sources.size(), error::wallet_internal_error, "Failed to work out sources permutation"); std::vector<tools::wallet2::multisig_sig> multisig_sigs; - if (m_multisig) - { - auto ignore = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front(); - multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, std::unordered_set<crypto::public_key>(), msout}); - - if (m_multisig_threshold < m_multisig_signers.size()) - { - const crypto::hash prefix_hash = cryptonote::get_transaction_prefix_hash(tx); - - // create the other versions, one for every other participant (the first one's already done above) - for (size_t ignore_index = 1; ignore_index < ignore_sets.size(); ++ignore_index) - { - std::unordered_set<rct::key> new_used_L; - size_t src_idx = 0; - THROW_WALLET_EXCEPTION_IF(selected_transfers.size() != sources.size(), error::wallet_internal_error, "mismatched selected_transfers and sources sixes"); - for(size_t idx: selected_transfers) - { - cryptonote::tx_source_entry& src = sources_copy[src_idx]; - src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_sets[ignore_index], used_L, new_used_L); - ++src_idx; + if (m_multisig) { + if (ignore_sets.empty()) + ignore_sets.emplace_back(); + const std::size_t num_multisig_attempts = ignore_sets.size(); + multisig_sigs.resize(num_multisig_attempts); + std::unordered_set<rct::key> all_used_L; + std::unordered_set<crypto::public_key> signing_keys; + for (const crypto::secret_key &multisig_skey: get_account().get_multisig_keys()) + signing_keys.insert(get_multisig_signing_public_key(multisig_skey)); + const std::size_t num_sources = sources.size(); + const std::size_t num_alpha_components = multisig::signing::kAlphaComponents; + + // initiate a multisig tx attempt for each unique set of signers that + // a) includes the local signer + // b) includes other signers who most recently sent the local signer LR public nonces via 'export_multisig() -> import_multisig()' + for (std::size_t i = 0; i < num_multisig_attempts; ++i) { + multisig_sig& sig = multisig_sigs[i]; + sig.total_alpha_G.resize(num_sources, rct::keyV(num_alpha_components)); + sig.total_alpha_H.resize(num_sources, rct::keyV(num_alpha_components)); + sig.s.resize(num_sources); + sig.c_0.resize(num_sources); + + // for each tx input, get public musig2-style nonces from + // a) temporary local-generated private nonces (used to make the local partial signatures on each tx attempt) + // b) other signers' public nonces, sent to the local signer via 'export_multisig() -> import_multisig()' + // - WARNING: If two multisig players initiate multisig tx attempts separately, but spend the same funds (and hence rely on the same LR public nonces), + // then if two signers partially sign different tx attempt sets, then all attempts that require both signers will become garbage, + // because LR nonces can only be used for one tx attempt. + for (std::size_t j = 0; j < num_sources; ++j) { + rct::keyV alpha(num_alpha_components); + auto alpha_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(alpha.data()), alpha.size() * sizeof(rct::key)); + }); + for (std::size_t m = 0; m < num_alpha_components; ++m) { + const rct::multisig_kLRki kLRki = get_multisig_composite_kLRki( + selected_transfers[ins_order[j]], + ignore_sets[i], + all_used_L, //collect all public L nonces used by this tx proposal (set of tx attempts) to avoid duplicates + sig.used_L //record the public L nonces used by this tx input to this tx attempt, for coordination with other signers + ); + alpha[m] = kLRki.k; + sig.total_alpha_G[j][m] = kLRki.L; + sig.total_alpha_H[j][m] = kLRki.R; } - LOG_PRINT_L2("Creating supplementary multisig transaction"); - cryptonote::transaction ms_tx; - auto sources_copy_copy = sources_copy; - bool shuffle_outs = false; - bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, rct_config, &msout, shuffle_outs, use_view_tags); - LOG_PRINT_L2("constructed tx, r="<<r); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); - THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); - THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_prefix_hash(ms_tx) != prefix_hash, error::wallet_internal_error, "Multisig txes do not share prefix"); - multisig_sigs.push_back({ms_tx.rct_signatures, ignore_sets[ignore_index], new_used_L, std::unordered_set<crypto::public_key>(), msout}); - - ms_tx.rct_signatures = tx.rct_signatures; - THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_hash(ms_tx) != cryptonote::get_transaction_hash(tx), error::wallet_internal_error, "Multisig txes differ by more than the signatures"); - } + // local signer: initial partial signature on this tx input for this tx attempt + // note: sign here with sender-receiver secret component, subaddress component, and ALL of the local signer's multisig key shares + // (this ultimately occurs deep in generate_key_image_helper_precomp()) + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.first_partial_sign(j, sig.total_alpha_G[j], sig.total_alpha_H[j], alpha, sig.c_0[j], sig.s[j]), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::first_partial_sign" + ); + } + + // note: record the ignore set so when other signers go to add their signatures (sign_multisig_tx()), they + // can skip this tx attempt if they aren't supposed to sign it; this only works for signers who provided + // multisig_infos to the last 'import_multisig()' call by the local signer, all 'other signers' will encounter + // a 'need to export multisig_info' wallet error if they try to sign this partial tx, which means if they want to sign a tx + // they need to export_multisig() -> send to the local signer -> local signer calls import_multisig() with fresh + // multisig_infos from all signers -> local signer makes completely new tx attempts (or a different signer makes tx attempts) + sig.ignore = ignore_sets[i]; + sig.signing_keys = signing_keys; //the local signer signed with ALL of their multisig key shares, record their pubkeys for reference by other signers + } + if (m_multisig_threshold <= 1) { + // local signer: finish signing the tx inputs if we are the only signer (ignore all but the first 'attempt') + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.finalize_tx(sources, multisig_sigs[0].c_0, multisig_sigs[0].s, tx), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::finalize_tx" + ); } } @@ -9077,6 +9163,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.additional_tx_keys = additional_tx_keys; ptx.dests = dsts; ptx.multisig_sigs = multisig_sigs; + ptx.multisig_tx_key_entropy = multisig_tx_key_entropy; ptx.construction_data.sources = sources_copy; ptx.construction_data.change_dts = change_dts; ptx.construction_data.splitted_dsts = splitted_dsts; @@ -12090,7 +12177,8 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr crypto::key_derivation derivation; THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); crypto::public_key subaddr_spendkey; - crypto::derive_subaddress_public_key(output_public_key, derivation, proof.index_in_tx, subaddr_spendkey); + THROW_WALLET_EXCEPTION_IF(!crypto::derive_subaddress_public_key(output_public_key, derivation, proof.index_in_tx, subaddr_spendkey), + error::wallet_internal_error, "Failed to derive subaddress public key"); THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(subaddr_spendkey) == 0, error::wallet_internal_error, "The address doesn't seem to have received the fund"); @@ -13336,19 +13424,26 @@ crypto::public_key wallet2::get_multisig_signing_public_key(size_t idx) const return get_multisig_signing_public_key(get_account().get_multisig_keys()[idx]); } //---------------------------------------------------------------------------------------------------- -rct::key wallet2::get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const +void wallet2::get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L, rct::key &nonce) { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "idx out of range"); - for (const auto &k: m_transfers[idx].m_multisig_k) + for (auto &k: m_transfers[idx].m_multisig_k) { + if (k == rct::zero()) + continue; + + // decide whether or not to return a nonce just based on if its pubkey 'L = k*G' is attached to the transfer 'idx' rct::key L; rct::scalarmultBase(L, k); if (used_L.find(L) != used_L.end()) - return k; + { + nonce = k; + memwipe(static_cast<rct::key *>(&k), sizeof(rct::key)); //CRITICAL: a nonce may only be used once! + return; + } } THROW_WALLET_EXCEPTION(tools::error::multisig_export_needed); - return rct::zero(); } //---------------------------------------------------------------------------------------------------- rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) const @@ -13414,15 +13509,23 @@ cryptonote::blobdata wallet2::export_multisig() const crypto::public_key signer = get_multisig_signer_public_key(); + // For each transfer (output owned by the multisig wallet): + // 1) Record the output's partial key image (from the local signer), so other signers can assemble the output's full key image. + // 2) Prepare enough signing nonces for one signing attempt with each possible combination of 'threshold' signers + // from the multisig group (only groups that include the local signer). + // - Calling this function will reset any nonces recorded by the previous call to this function. Doing so will + // invalidate any in-progress signing attempts that rely on the previous output of this function. info.resize(m_transfers.size()); for (size_t n = 0; n < m_transfers.size(); ++n) { transfer_details &td = m_transfers[n]; crypto::key_image ki; - memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0])); + if (td.m_multisig_k.size()) + memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0])); info[n].m_LR.clear(); info[n].m_partial_key_images.clear(); + // record the partial key images for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) { // we want to export the partial key image, not the full one, so we can't use td.m_key_image @@ -13435,6 +13538,15 @@ cryptonote::blobdata wallet2::export_multisig() // if we have 2/4 wallet with signers: A, B, C, D and A is a transaction creator it will need to pick up 1 signer from 3 wallets left. // That means counting combinations for excluding 2-of-3 wallets (k = total signers count - threshold, n = total signers count - 1). size_t nlr = tools::combinations_count(m_multisig_signers.size() - m_multisig_threshold, m_multisig_signers.size() - 1); + + // 'td.m_multisig_k' is an expansion of [{alpha_0, alpha_1, ...}, {alpha_0, alpha_1, ...}, {alpha_0, alpha_1, ...}], + // - A '{alpha_0, alpha_1, ...}' tuple contains a set of 'kAlphaComponents' nonces, which can be used for one + // signing attempt. Each output will gain 'nlr' tuples, so that every signing group can make one signing attempt. + // - All tuples are always cleared after 1+ of them is used to sign a tx attempt (in sign_multisig_tx()), so + // in practice, a call to this function only allows _one_ multisig signing cycle for each output (which can + // include signing attempts for multiple signer groups). + nlr *= multisig::signing::kAlphaComponents; + for (size_t m = 0; m < nlr; ++m) { td.m_multisig_k.push_back(rct::skGen()); @@ -14022,43 +14134,6 @@ uint64_t wallet2::get_segregation_fork_height() const if (m_segregation_height > 0) return m_segregation_height; - if (m_use_dns && !m_offline) - { - // All four MoneroPulse domains have DNSSEC on and valid - static const std::vector<std::string> dns_urls = { - "segheights.moneropulse.org", - "segheights.moneropulse.net", - "segheights.moneropulse.co", - "segheights.moneropulse.se" - }; - - const uint64_t current_height = get_blockchain_current_height(); - uint64_t best_diff = std::numeric_limits<uint64_t>::max(), best_height = 0; - std::vector<std::string> records; - if (tools::dns_utils::load_txt_records_from_dns(records, dns_urls)) - { - for (const auto& record : records) - { - std::vector<std::string> fields; - boost::split(fields, record, boost::is_any_of(":")); - if (fields.size() != 2) - continue; - uint64_t height; - if (!string_tools::get_xtype_from_string(height, fields[1])) - continue; - - MINFO("Found segregation height via DNS: " << fields[0] << " fork height at " << height); - uint64_t diff = height > current_height ? height - current_height : current_height - height; - if (diff < best_diff) - { - best_diff = diff; - best_height = height; - } - } - if (best_height) - return best_height; - } - } return SEGREGATION_FORK_HEIGHT; } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index e051946ad..16e898ad8 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -137,7 +137,7 @@ private: public: // Full wallet callbacks virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) {} + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) {} virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {} @@ -349,6 +349,8 @@ private: uint64_t amount() const { return m_amount; } const crypto::public_key get_public_key() const { crypto::public_key output_public_key; + THROW_WALLET_EXCEPTION_IF(m_tx.vout.size() <= m_internal_output_index, + error::wallet_internal_error, "Too few outputs, outputs may be corrupted"); THROW_WALLET_EXCEPTION_IF(!get_output_public_key(m_tx.vout[m_internal_output_index], output_public_key), error::wallet_internal_error, "Unable to get output public key from output"); return output_public_key; @@ -492,6 +494,7 @@ private: struct confirmed_transfer_details { + cryptonote::transaction_prefix m_tx; uint64_t m_amount_in; uint64_t m_amount_out; uint64_t m_change; @@ -506,10 +509,12 @@ private: confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(crypto::null_hash), m_timestamp(0), m_unlock_time(0), m_subaddr_account((uint32_t)-1) {} confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height): - m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices), m_rings(utd.m_rings) {} + m_tx(utd.m_tx), m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices), m_rings(utd.m_rings) {} BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) + VERSION_FIELD(1) + if (version >= 1) + FIELD(m_tx) VARINT_FIELD(m_amount_in) VARINT_FIELD(m_amount_out) VARINT_FIELD(m_change) @@ -594,13 +599,24 @@ private: std::unordered_set<crypto::public_key> signing_keys; rct::multisig_out msout; + rct::keyM total_alpha_G; + rct::keyM total_alpha_H; + rct::keyV c_0; + rct::keyV s; + BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) + VERSION_FIELD(1) + if (version < 1) + return false; FIELD(sigs) FIELD(ignore) FIELD(used_L) FIELD(signing_keys) FIELD(msout) + FIELD(total_alpha_G) + FIELD(total_alpha_H) + FIELD(c_0) + FIELD(s) END_SERIALIZE() }; @@ -619,10 +635,12 @@ private: std::vector<crypto::secret_key> additional_tx_keys; std::vector<cryptonote::tx_destination_entry> dests; std::vector<multisig_sig> multisig_sigs; + crypto::secret_key multisig_tx_key_entropy; tx_construction_data construction_data; BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(1) FIELD(tx) FIELD(dust) FIELD(fee) @@ -635,6 +653,12 @@ private: FIELD(dests) FIELD(construction_data) FIELD(multisig_sigs) + if (version < 1) + { + multisig_tx_key_entropy = crypto::null_skey; + return true; + } + FIELD(multisig_tx_key_entropy) END_SERIALIZE() }; @@ -1679,7 +1703,7 @@ private: crypto::key_image get_multisig_composite_key_image(size_t n) const; rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const std::unordered_set<crypto::public_key> &ignore_set, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const; rct::multisig_kLRki get_multisig_kLRki(size_t n, const rct::key &k) const; - rct::key get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const; + void get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L, rct::key &nonce); void update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n); bool add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx); bool add_rings(const cryptonote::transaction_prefix &tx); @@ -1878,7 +1902,7 @@ BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4) BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) -BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0) +BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 1) namespace boost { @@ -2316,6 +2340,12 @@ namespace boost a & x.used_L; a & x.signing_keys; a & x.msout; + if (ver < 1) + return; + a & x.total_alpha_G; + a & x.total_alpha_H; + a & x.c_0; + a & x.s; } template <class Archive> diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 61195c7b0..144e87bc2 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -1076,7 +1076,7 @@ bool construct_tx_rct(const cryptonote::account_keys& sender_account_keys, std:: std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; rct::RCTConfig rct_config = {range_proof_type, bp_version}; - return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, nullptr); + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config); } transaction construct_tx_with_fee(std::vector<test_event_entry>& events, const block& blk_head, diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp index 3db3d4059..28b44d293 100644 --- a/tests/core_tests/multisig.cpp +++ b/tests/core_tests/multisig.cpp @@ -28,6 +28,11 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include "ringct/rctSigs.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "multisig/multisig.h" +#include "multisig/multisig_tx_builder_ringct.h" +#include "common/apply_permutation.h" #include "chaingen.h" #include "multisig.h" @@ -113,7 +118,7 @@ static bool make_multisig_accounts(std::vector<cryptonote::account_base> &accoun bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry>& events, size_t inputs, size_t mixin, uint64_t amount_paid, bool valid, - size_t threshold, size_t total, size_t creator, std::vector<size_t> signers, + size_t threshold, size_t total, size_t creator, std::vector<size_t> other_signers, const std::function<void(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations)> &pre_tx, const std::function<void(transaction &tx)> &post_tx) const { @@ -122,30 +127,18 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry CHECK_AND_ASSERT_MES(total >= 2, false, "Bad scheme"); CHECK_AND_ASSERT_MES(threshold <= total, false, "Bad scheme"); -#ifdef NO_MULTISIG - CHECK_AND_ASSERT_MES(total <= 5, false, "Unsupported scheme"); -#endif CHECK_AND_ASSERT_MES(inputs >= 1 && inputs <= 8, false, "Inputs should between 1 and 8"); // given as 1 based for clarity --creator; - for (size_t &signer: signers) + for (size_t &signer: other_signers) --signer; CHECK_AND_ASSERT_MES(creator < total, false, "invalid creator"); - for (size_t signer: signers) + for (size_t signer: other_signers) CHECK_AND_ASSERT_MES(signer < total, false, "invalid signer"); -#ifdef NO_MULTISIG - GENERATE_ACCOUNT(acc0); - GENERATE_ACCOUNT(acc1); - GENERATE_ACCOUNT(acc2); - GENERATE_ACCOUNT(acc3); - GENERATE_ACCOUNT(acc4); - account_base miner_account[5] = {acc0, acc1, acc2, acc3, acc4}; -#else GENERATE_MULTISIG_ACCOUNT(miner_account, threshold, total); -#endif MAKE_GENESIS_BLOCK(events, blk_0, miner_account[creator], ts_start); @@ -193,14 +186,13 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry { tx_pub_key[n] = get_tx_pub_key_from_extra(blocks[n].miner_tx); MDEBUG("tx_pub_key: " << tx_pub_key); - output_pub_key[n] = boost::get<txout_to_key>(blocks[n].miner_tx.vout[0].target).key; + cryptonote::get_output_public_key(blocks[n].miner_tx.vout[0], output_pub_key[n]); MDEBUG("output_pub_key: " << output_pub_key); } std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_account[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; -#ifndef NO_MULTISIG // create k/L/R/ki for that output we're going to spend std::vector<std::vector<std::vector<crypto::secret_key>>> account_k(total); std::vector<std::vector<std::vector<crypto::public_key>>> account_L(total); @@ -213,6 +205,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry false, "Mismatched spend public keys"); size_t nlr = threshold < total ? threshold - 1 : 1; + nlr *= multisig::signing::kAlphaComponents; account_k[msidx].resize(inputs); account_L[msidx].resize(inputs); account_R[msidx].resize(inputs); @@ -226,9 +219,9 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry account_k[msidx][tdidx].push_back(rct::rct2sk(rct::skGen())); multisig::generate_multisig_LR(output_pub_key[tdidx], account_k[msidx][tdidx][n], account_L[msidx][tdidx][n], account_R[msidx][tdidx][n]); } - size_t numki = miner_account[msidx].get_multisig_keys().size(); - account_ki[msidx][tdidx].resize(numki); - for (size_t kiidx = 0; kiidx < numki; ++kiidx) + size_t num_account_partial_ki = miner_account[msidx].get_multisig_keys().size(); + account_ki[msidx][tdidx].resize(num_account_partial_ki); + for (size_t kiidx = 0; kiidx < num_account_partial_ki; ++kiidx) { r = multisig::generate_multisig_key_image(miner_account[msidx].get_keys(), kiidx, output_pub_key[tdidx], account_ki[msidx][tdidx][kiidx]); CHECK_AND_ASSERT_MES(r, false, "Failed to generate multisig export key image"); @@ -248,7 +241,6 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry MDEBUG("ki: " << ki); } } -#endif // create kLRki std::vector<rct::multisig_kLRki> kLRkis; @@ -257,34 +249,6 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry { kLRkis.push_back(rct::multisig_kLRki()); rct::multisig_kLRki &kLRki = kLRkis.back(); -#ifdef NO_MULTISIG - kLRki = {rct::zero(), rct::zero(), rct::zero(), rct::zero()}; -#else - kLRki.k = rct::sk2rct(account_k[creator][tdidx][0]); - kLRki.L = rct::pk2rct(account_L[creator][tdidx][0]); - kLRki.R = rct::pk2rct(account_R[creator][tdidx][0]); - MDEBUG("Starting with k " << kLRki.k); - MDEBUG("Starting with L " << kLRki.L); - MDEBUG("Starting with R " << kLRki.R); - for (size_t msidx = 0; msidx < total; ++msidx) - { - if (msidx == creator) - continue; - if (std::find(signers.begin(), signers.end(), msidx) == signers.end()) - continue; - for (size_t lr = 0; lr < account_L[msidx][tdidx].size(); ++lr) - { - if (used_L.find(account_L[msidx][tdidx][lr]) == used_L.end()) - { - used_L.insert(account_L[msidx][tdidx][lr]); - MDEBUG("Adding L " << account_L[msidx][tdidx][lr] << " (for k " << account_k[msidx][tdidx][lr] << ")"); - MDEBUG("Adding R " << account_R[msidx][tdidx][lr]); - rct::addKeys((rct::key&)kLRki.L, kLRki.L, rct::pk2rct(account_L[msidx][tdidx][lr])); - rct::addKeys((rct::key&)kLRki.R, kLRki.R, rct::pk2rct(account_R[msidx][tdidx][lr])); - break; - } - } - } std::vector<crypto::key_image> pkis; for (size_t msidx = 0; msidx < total; ++msidx) for (size_t n = 0; n < account_ki[msidx][tdidx].size(); ++n) @@ -292,8 +256,6 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry r = multisig::generate_multisig_composite_key_image(miner_account[0].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)kLRki.ki); CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image"); MDEBUG("composite ki: " << kLRki.ki); - MDEBUG("L: " << kLRki.L); - MDEBUG("R: " << kLRki.R); for (size_t n = 1; n < total; ++n) { rct::key ki; @@ -302,9 +264,8 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry CHECK_AND_ASSERT_MES(kLRki.ki == ki, false, "Composite key images do not match"); } } -#endif - // create a tx: we have 8 outputs, all from coinbase, so "fake" rct - use 2 + // prepare a tx: we have 8 outputs, all from coinbase, so "fake" rct - use 2 std::vector<tx_source_entry> sources; for (size_t n = 0; n < inputs; ++n) { @@ -322,7 +283,9 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry for (size_t m = 0; m <= mixin; ++m) { rct::ctkey ctkey; - ctkey.dest = rct::pk2rct(boost::get<txout_to_key>(blocks[m].miner_tx.vout[0].target).key); + crypto::public_key output_public_key; + cryptonote::get_output_public_key(blocks[m].miner_tx.vout[0], output_public_key); + ctkey.dest = rct::pk2rct(output_public_key); MDEBUG("using " << (m == n ? "real" : "fake") << " input " << ctkey.dest); ctkey.mask = rct::commit(blocks[m].miner_tx.vout[0].amount, rct::identity()); // since those are coinbases, the masks are known src.outputs.push_back(std::make_pair(m, ctkey)); @@ -333,11 +296,8 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry tx_destination_entry td; td.addr = miner_account[creator].get_keys().m_account_address; td.amount = amount_paid; - std::vector<tx_destination_entry> destinations; + std::vector<tx_destination_entry> destinations; //tx need two outputs since HF_VERSION_MIN_2_OUTPUTS destinations.push_back(td); - cryptonote::account_base dummy; - dummy.generate(); - td.addr = dummy.get_keys().m_account_address; td.amount = 0; destinations.push_back(td); @@ -346,18 +306,12 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry transaction tx; crypto::secret_key tx_key; -#ifdef NO_MULTISIG - rct::multisig_out *msoutp = NULL; -#else - rct::multisig_out msout; - rct::multisig_out *msoutp = &msout; -#endif std::vector<crypto::secret_key> additional_tx_secret_keys; + crypto::secret_key multisig_tx_key_entropy; auto sources_copy = sources; - r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofPaddedBulletproof, 0 }, msoutp); - CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); + multisig::signing::tx_builder_ringct_t tx_builder; + CHECK_AND_ASSERT_MES(tx_builder.init(miner_account[creator].get_keys(), {}, 0, 0, {0}, sources, destinations, {}, {rct::RangeProofPaddedBulletproof, 4}, true, false, tx_key, additional_tx_secret_keys, multisig_tx_key_entropy, tx), false, "error: multisig::signing::tx_builder_ringct_t::init"); -#ifndef NO_MULTISIG // work out the permutation done on sources std::vector<size_t> ins_order; for (size_t n = 0; n < sources.size(); ++n) @@ -371,15 +325,50 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry } } CHECK_AND_ASSERT_MES(ins_order.size() == sources.size(), false, "Failed to work out sources permutation"); -#endif -#ifndef NO_MULTISIG + struct { + rct::keyM total_alpha_G; + rct::keyM total_alpha_H; + rct::keyV c_0; + rct::keyV s; + } sig; + { + used_L.clear(); + sig.total_alpha_G.resize(sources.size(), rct::keyV(multisig::signing::kAlphaComponents, rct::identity())); + sig.total_alpha_H.resize(sources.size(), rct::keyV(multisig::signing::kAlphaComponents, rct::identity())); + sig.c_0.resize(sources.size()); + sig.s.resize(sources.size()); + for (std::size_t i = 0; i < sources.size(); ++i) { + rct::keyV alpha(multisig::signing::kAlphaComponents); + for (std::size_t m = 0; m < multisig::signing::kAlphaComponents; ++m) { + alpha[m] = rct::sk2rct(account_k[creator][ins_order[i]][m]); + sig.total_alpha_G[i][m] = rct::pk2rct(account_L[creator][ins_order[i]][m]); + sig.total_alpha_H[i][m] = rct::pk2rct(account_R[creator][ins_order[i]][m]); + for (size_t j = 0; j < total; ++j) { + if (j == creator) + continue; + if (std::find(other_signers.begin(), other_signers.end(), j) == other_signers.end()) + continue; + for (std::size_t n = 0; n < account_L[j][ins_order[i]].size(); ++n) { + if (used_L.find(account_L[j][ins_order[i]][n]) == used_L.end()) { + used_L.insert(account_L[j][ins_order[i]][n]); + rct::addKeys(sig.total_alpha_G[i][m], sig.total_alpha_G[i][m], rct::pk2rct(account_L[j][ins_order[i]][n])); + rct::addKeys(sig.total_alpha_H[i][m], sig.total_alpha_H[i][m], rct::pk2rct(account_R[j][ins_order[i]][n])); + break; + } + } + } + } + CHECK_AND_ASSERT_MES(tx_builder.first_partial_sign(i, sig.total_alpha_G[i], sig.total_alpha_H[i], alpha, sig.c_0[i], sig.s[i]), false, "error: multisig::signing::tx_builder_ringct_t::first_partial_sign"); + } + } + // sign std::unordered_set<crypto::secret_key> used_keys; const std::vector<crypto::secret_key> &msk0 = miner_account[creator].get_multisig_keys(); for (const auto &sk: msk0) - used_keys.insert(sk); - for (size_t signer: signers) + used_keys.insert(sk); //these were used in 'tx_builder.init() -> tx_builder.first_partial_sign()' + for (size_t signer: other_signers) { rct::key skey = rct::zero(); const std::vector<crypto::secret_key> &msk1 = miner_account[signer].get_multisig_keys(); @@ -393,38 +382,37 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry } } CHECK_AND_ASSERT_MES(!(skey == rct::zero()), false, "failed to find secret multisig key to sign transaction"); - std::vector<unsigned int> indices; - for (const auto &src: sources_copy) - indices.push_back(src.real_output); - rct::keyV k; - for (size_t tdidx = 0; tdidx < inputs; ++tdidx) - { - k.push_back(rct::zero()); - for (size_t n = 0; n < account_k[signer][tdidx].size(); ++n) - { - crypto::public_key L; - rct::scalarmultBase((rct::key&)L, rct::sk2rct(account_k[signer][tdidx][n])); - if (used_L.find(L) != used_L.end()) - { - sc_add(k.back().bytes, k.back().bytes, rct::sk2rct(account_k[signer][tdidx][n]).bytes); + rct::keyM k(sources.size(), rct::keyV(multisig::signing::kAlphaComponents)); + for (std::size_t i = 0; i < sources.size(); ++i) { + for (std::size_t j = 0; j < multisig::signing::kAlphaComponents; ++j) { + for (std::size_t n = 0; n < account_k[signer][i].size(); ++n) { + crypto::public_key L; + rct::scalarmultBase((rct::key&)L, rct::sk2rct(account_k[signer][i][n])); + if (used_L.find(L) != used_L.end()) { + k[i][j] = rct::sk2rct(account_k[signer][i][n]); + account_k[signer][i][n] = rct::rct2sk(rct::zero()); //demo: always clear nonces from long-term storage after use + break; + } } + CHECK_AND_ASSERT_MES(!(k[i][j] == rct::zero()), false, "failed to find k to sign transaction"); } - CHECK_AND_ASSERT_MES(!(k.back() == rct::zero()), false, "failed to find k to sign transaction"); } - tools::apply_permutation(ins_order, indices); tools::apply_permutation(ins_order, k); + multisig::signing::tx_builder_ringct_t signer_tx_builder; + CHECK_AND_ASSERT_MES(signer_tx_builder.init(miner_account[signer].get_keys(), {}, 0, 0, {0}, sources, destinations, {}, {rct::RangeProofPaddedBulletproof, 4}, true, true, tx_key, additional_tx_secret_keys, multisig_tx_key_entropy, tx), false, "error: multisig::signing::tx_builder_ringct_t::init"); MDEBUG("signing with k size " << k.size()); - MDEBUG("signing with k " << k.back()); + for (size_t n = 0; n < multisig::signing::kAlphaComponents; ++n) + MDEBUG("signing with k " << k.back()[n]); MDEBUG("signing with sk " << skey); for (const auto &sk: used_keys) MDEBUG(" created with sk " << sk); - MDEBUG("signing with c size " << msout.c.size()); - MDEBUG("signing with c " << msout.c.back()); - r = rct::signMultisig(tx.rct_signatures, indices, k, msout, skey); - CHECK_AND_ASSERT_MES(r, false, "failed to sign transaction"); + CHECK_AND_ASSERT_MES(signer_tx_builder.next_partial_sign(sig.total_alpha_G, sig.total_alpha_H, k, skey, sig.c_0, sig.s), false, "error: multisig::signing::tx_builder_ringct_t::next_partial_sign"); + + // in round-robin signing, the last signer finalizes the tx + if (signer == other_signers.back()) + CHECK_AND_ASSERT_MES(signer_tx_builder.finalize_tx(sources, sig.c_0, sig.s, tx), false, "error: multisig::signing::tx_builder_ringct_t::finalize_tx"); } -#endif // verify this tx is really to the expected address const crypto::public_key tx_pub_key2 = get_tx_pub_key_from_extra(tx, 0); @@ -433,10 +421,12 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry CHECK_AND_ASSERT_MES(r, false, "Failed to generate derivation"); uint64_t n_outs = 0, amount = 0; std::vector<crypto::key_derivation> additional_derivations; + crypto::public_key output_public_key; for (size_t n = 0; n < tx.vout.size(); ++n) { - CHECK_AND_ASSERT_MES(typeid(txout_to_key) == tx.vout[n].target.type(), false, "Unexpected tx out type"); - if (is_out_to_acc_precomp(subaddresses, boost::get<txout_to_key>(tx.vout[n].target).key, derivation, additional_derivations, n, hw::get_device(("default")))) + CHECK_AND_ASSERT_MES(typeid(txout_to_tagged_key) == tx.vout[n].target.type(), false, "Unexpected tx out type"); + cryptonote::get_output_public_key(tx.vout[n], output_public_key); + if (is_out_to_acc_precomp(subaddresses, output_public_key, derivation, additional_derivations, n, hw::get_device(("default")))) { ++n_outs; CHECK_AND_ASSERT_MES(tx.vout[n].amount == 0, false, "Destination amount is not zero"); @@ -451,7 +441,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry amount += rct::h2d(ecdh_info.amount); } } - CHECK_AND_ASSERT_MES(n_outs == 1, false, "Not exactly 1 output was received"); + CHECK_AND_ASSERT_MES(n_outs == 2, false, "Not exactly 2 outputs were received"); CHECK_AND_ASSERT_MES(amount == amount_paid, false, "Amount paid was not the expected amount"); if (post_tx) diff --git a/tests/core_tests/multisig.h b/tests/core_tests/multisig.h index e9a2cf5f3..948c19458 100644 --- a/tests/core_tests/multisig.h +++ b/tests/core_tests/multisig.h @@ -71,7 +71,7 @@ struct gen_multisig_tx_validation_base : public test_chain_unit_base bool generate_with(std::vector<test_event_entry>& events, size_t inputs, size_t mixin, uint64_t amount_paid, bool valid, - size_t threshold, size_t total, size_t creator, std::vector<size_t> signers, + size_t threshold, size_t total, size_t creator, std::vector<size_t> other_signers, const std::function<void(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations)> &pre_tx, const std::function<void(cryptonote::transaction &tx)> &post_tx) const; diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index 0926483fe..4e51ed713 100644 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -229,7 +229,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry std::vector<crypto::secret_key> additional_tx_keys; std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_accounts[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; - bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true, rct_config, NULL, use_view_tags); + bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true, rct_config, use_view_tags); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); if (post_tx) diff --git a/tests/core_tests/wallet_tools.cpp b/tests/core_tests/wallet_tools.cpp index fdc4753f9..a3b66e835 100644 --- a/tests/core_tests/wallet_tools.cpp +++ b/tests/core_tests/wallet_tools.cpp @@ -280,5 +280,5 @@ bool construct_tx_rct(tools::wallet2 * sender_wallet, std::vector<cryptonote::tx std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; rct::RCTConfig rct_config = {range_proof_type, bp_version}; - return construct_tx_and_get_tx_key(sender_wallet->get_account().get_keys(), subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, nullptr); + return construct_tx_and_get_tx_key(sender_wallet->get_account().get_keys(), subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config); } diff --git a/tests/performance_tests/rct_mlsag.h b/tests/performance_tests/rct_mlsag.h index 4cdbcd601..2163431fe 100644 --- a/tests/performance_tests/rct_mlsag.h +++ b/tests/performance_tests/rct_mlsag.h @@ -65,7 +65,7 @@ public: { sk[j] = xm[ind][j]; } - IIccss = MLSAG_Gen(rct::identity(), P, sk, NULL, NULL, ind, rows-1, hw::get_device("default")); + IIccss = MLSAG_Gen(rct::identity(), P, sk, ind, rows-1, hw::get_device("default")); return true; } @@ -75,7 +75,7 @@ public: if (ver) MLSAG_Ver(rct::identity(), P, IIccss, rows-1); else - MLSAG_Gen(rct::identity(), P, sk, NULL, NULL, ind, rows-1, hw::get_device("default")); + MLSAG_Gen(rct::identity(), P, sk, ind, rows-1, hw::get_device("default")); return true; } diff --git a/tests/performance_tests/sig_clsag.h b/tests/performance_tests/sig_clsag.h index 53c38c56b..043830ee6 100644 --- a/tests/performance_tests/sig_clsag.h +++ b/tests/performance_tests/sig_clsag.h @@ -117,7 +117,7 @@ class test_sig_clsag sk.dest = r[u]; sk.mask = s[u]; - sigs.push_back(proveRctCLSAGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],NULL,NULL,NULL,u,hw::get_device("default"))); + sigs.push_back(proveRctCLSAGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],u,hw::get_device("default"))); } return true; diff --git a/tests/performance_tests/sig_mlsag.h b/tests/performance_tests/sig_mlsag.h index 60a306753..46bdcde63 100644 --- a/tests/performance_tests/sig_mlsag.h +++ b/tests/performance_tests/sig_mlsag.h @@ -117,7 +117,7 @@ class test_sig_mlsag sk.dest = r[u]; sk.mask = s[u]; - sigs.push_back(proveRctMGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],NULL,NULL,u,hw::get_device("default"))); + sigs.push_back(proveRctMGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],u,hw::get_device("default"))); } return true; diff --git a/tests/unit_tests/bulletproofs.cpp b/tests/unit_tests/bulletproofs.cpp index 493eb9426..65f0d85f8 100644 --- a/tests/unit_tests/bulletproofs.cpp +++ b/tests/unit_tests/bulletproofs.cpp @@ -132,7 +132,8 @@ TEST(bulletproofs, multi_splitting) rct::ctkeyV outSk; rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 4 }; - rct::rctSig s = rct::genRctSimple(rct::zero(), sc, destinations, inamounts, outamounts, available, mixRing, amount_keys, NULL, NULL, index, outSk, rct_config, hw::get_device("default")); + + rct::rctSig s = rct::genRctSimple(rct::zero(), sc, destinations, inamounts, outamounts, available, mixRing, amount_keys, index, outSk, rct_config, hw::get_device("default")); ASSERT_TRUE(rct::verRctSimple(s)); for (size_t i = 0; i < n_outputs; ++i) { diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp index f3ca2b2b4..920ec7c5e 100644 --- a/tests/unit_tests/ringct.cpp +++ b/tests/unit_tests/ringct.cpp @@ -113,7 +113,7 @@ TEST(ringct, MG_sigs) sk[j] = xm[ind][j]; } key message = identity(); - mgSig IIccss = MLSAG_Gen(message, P, sk, NULL, NULL, ind, R, hw::get_device("default")); + mgSig IIccss = MLSAG_Gen(message, P, sk, ind, R, hw::get_device("default")); ASSERT_TRUE(MLSAG_Ver(message, P, IIccss, R)); //#MG sig: false one @@ -134,7 +134,7 @@ TEST(ringct, MG_sigs) sk[j] = xx[ind][j]; } sk[2] = skGen();//assume we don't know one of the private keys.. - IIccss = MLSAG_Gen(message, P, sk, NULL, NULL, ind, R, hw::get_device("default")); + IIccss = MLSAG_Gen(message, P, sk, ind, R, hw::get_device("default")); ASSERT_FALSE(MLSAG_Ver(message, P, IIccss, R)); } @@ -178,13 +178,13 @@ TEST(ringct, CLSAG) insk.mask = t; // bad message - clsag = rct::proveRctCLSAGSimple(zero(),pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(zero(),pubs,insk,t2,Cout,idx,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); // bad index at creation try { - clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,(idx + 1) % N,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,(idx + 1) % N,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); } catch (...) { /* either exception, or failure to verify above */ } @@ -195,7 +195,7 @@ TEST(ringct, CLSAG) ctkey insk2; insk2.dest = insk.dest; insk2.mask = skGen(); - clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,idx,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); } catch (...) { /* either exception, or failure to verify above */ } @@ -205,7 +205,7 @@ TEST(ringct, CLSAG) pubs[idx].mask = scalarmultBase(skGen()); try { - clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,idx,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); } catch (...) { /* either exception, or failure to verify above */ } @@ -217,7 +217,7 @@ TEST(ringct, CLSAG) ctkey insk2; insk2.dest = skGen(); insk2.mask = insk.mask; - clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,idx,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); } catch (...) { /* either exception, or failure to verify above */ } @@ -227,14 +227,14 @@ TEST(ringct, CLSAG) pubs[idx].dest = scalarmultBase(skGen()); try { - clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,idx,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); } catch (...) { /* either exception, or failure to verify above */ } pubs[idx] = backup; // Test correct signature - clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,idx,hw::get_device("default")); ASSERT_TRUE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); // empty s @@ -340,12 +340,12 @@ TEST(ringct, range_proofs) //compute rct data with mixin 3 - should fail since full type with > 1 input bool ok = false; - try { genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3, rct_config, hw::get_device("default")); } + try { genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3, rct_config, hw::get_device("default")); } catch(...) { ok = true; } ASSERT_TRUE(ok); //compute rct data with mixin 3 - rctSig s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config, hw::get_device("default")); + rctSig s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 0, 3, rct_config, hw::get_device("default")); //verify rct data ASSERT_TRUE(verRctSimple(s)); @@ -362,7 +362,7 @@ TEST(ringct, range_proofs) //compute rct data with mixin 3 - s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config, hw::get_device("default")); + s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 0, 3, rct_config, hw::get_device("default")); //verify rct data ASSERT_FALSE(verRctSimple(s)); @@ -410,7 +410,7 @@ TEST(ringct, range_proofs_with_fee) const rct::RCTConfig rct_config { RangeProofBorromean, 0 }; //compute rct data with mixin 3 - rctSig s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 1, 3, rct_config, hw::get_device("default")); + rctSig s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 1, 3, rct_config, hw::get_device("default")); //verify rct data ASSERT_TRUE(verRctSimple(s)); @@ -427,7 +427,7 @@ TEST(ringct, range_proofs_with_fee) //compute rct data with mixin 3 - s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 500, 3, rct_config, hw::get_device("default")); + s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 500, 3, rct_config, hw::get_device("default")); //verify rct data ASSERT_FALSE(verRctSimple(s)); @@ -486,7 +486,7 @@ TEST(ringct, simple) xmr_amount txnfee = 1; const rct::RCTConfig rct_config { RangeProofBorromean, 0 }; - rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, amount_keys, NULL, NULL, txnfee, 2, rct_config, hw::get_device("default")); + rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, amount_keys, txnfee, 2, rct_config, hw::get_device("default")); //verify ring ct signature ASSERT_TRUE(verRctSimple(s)); @@ -521,7 +521,7 @@ static rct::rctSig make_sample_rct_sig(int n_inputs, const uint64_t input_amount } const rct::RCTConfig rct_config { RangeProofBorromean, 0 }; - return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3, rct_config, hw::get_device("default")); + return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3, rct_config, hw::get_device("default")); } static rct::rctSig make_sample_simple_rct_sig(int n_inputs, const uint64_t input_amounts[], int n_outputs, const uint64_t output_amounts[], uint64_t fee) @@ -548,7 +548,7 @@ static rct::rctSig make_sample_simple_rct_sig(int n_inputs, const uint64_t input } const rct::RCTConfig rct_config { RangeProofBorromean, 0 }; - return genRctSimple(rct::zero(), sc, pc, destinations, inamounts, outamounts, amount_keys, NULL, NULL, fee, 3, rct_config, hw::get_device("default")); + return genRctSimple(rct::zero(), sc, pc, destinations, inamounts, outamounts, amount_keys, fee, 3, rct_config, hw::get_device("default")); } static bool range_proof_test(bool expected_valid, diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 9e8a28f7c..87571e5b5 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -594,7 +594,7 @@ TEST(Serialization, serializes_ringct_types) destinations.push_back(Pk); //compute rct data with mixin 3 const rct::RCTConfig rct_config{ rct::RangeProofPaddedBulletproof, 2 }; - s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config, hw::get_device("default")); + s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 0, 3, rct_config, hw::get_device("default")); ASSERT_FALSE(s0.p.MGs.empty()); ASSERT_TRUE(s0.p.CLSAGs.empty()); @@ -619,7 +619,7 @@ TEST(Serialization, serializes_ringct_types) ASSERT_EQ(bp0, bp1); const rct::RCTConfig rct_config_clsag{ rct::RangeProofPaddedBulletproof, 3 }; - s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config_clsag, hw::get_device("default")); + s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 0, 3, rct_config_clsag, hw::get_device("default")); ASSERT_FALSE(s0.p.CLSAGs.empty()); ASSERT_TRUE(s0.p.MGs.empty()); diff --git a/utils/fish/README.md b/utils/fish/README.md new file mode 100644 index 000000000..7d7c58c28 --- /dev/null +++ b/utils/fish/README.md @@ -0,0 +1,2 @@ +## Fish shell completions for Monero +This folder has basic Fish completions for `monerod`, `monero-wallet-cli`, and `monero-wallet-rpc`. To use them, put those files (or symlink them) inside `~/.config/fish/completions/` or wherever your Fish completion files are (see [https://fishshell.com/docs/current/completions.html#where-to-put-completions](https://fishshell.com/docs/current/completions.html#where-to-put-completions)) diff --git a/utils/fish/monero-wallet-cli.fish b/utils/fish/monero-wallet-cli.fish new file mode 100644 index 000000000..c9c878dba --- /dev/null +++ b/utils/fish/monero-wallet-cli.fish @@ -0,0 +1,61 @@ +complete -c monero-wallet-cli -f + +complete -c monero-wallet-cli -l help -d "Produce help message" +complete -c monero-wallet-cli -l version -d "Output version information" +complete -c monero-wallet-cli -l daemon-address -r -d "Use daemon instance at <host>:<port>" +complete -c monero-wallet-cli -l daemon-host -r -d "Use daemon instance at host <arg> instead of localhost" +complete -c monero-wallet-cli -l proxy -r -d "[<ip>:]<port> socks proxy to use for daemon connections" +complete -c monero-wallet-cli -l trusted-daemon -d "Enable commands which rely on a trusted" +complete -c monero-wallet-cli -l untrusted-daemon -d "Disable⋅commands⋅which⋅rely⋅on⋅a trusted daemon" +complete -c monero-wallet-cli -l password -r -d "Wallet⋅password⋅(escape/quote⋅as needed)" +complete -c monero-wallet-cli -l password-file -r -F -d "Wallet⋅password⋅file" +complete -c monero-wallet-cli -l daemon-port -r -d "Use daemon instance at port <arg> instead of 18081" +complete -c monero-wallet-cli -l daemon-login -r -d "Specify username[:password] for daemon RPC client" +complete -c monero-wallet-cli -l daemon-ssl -x -a "enabled disabled autodetect" -d "Enable SSL on daemon RPC connections. Default: autodetect" +complete -c monero-wallet-cli -l daemon-ssl-private-key -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format private key" +complete -c monero-wallet-cli -l daemon-ssl-certificate -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format certificate" +complete -c monero-wallet-cli -l daemon-ssl-ca-certificates -r -F -d "Path to file containing concatenated PEM format certificate(s) to replace system CA(s)." +complete -c monero-wallet-cli -l daemon-ssl-allowed-fingerprints -r -d "List of valid fingerprints of allowed RPC servers" +complete -c monero-wallet-cli -l daemon-ssl-allow-any-cert -d "Allow any SSL certificate from the daemon" +complete -c monero-wallet-cli -l daemon-ssl-allow-chained -d "Allow user (via --daemon-ssl-ca-certifi cates) chain certificates" +complete -c monero-wallet-cli -l testnet -d "For testnet. Daemon must also be launched with --testnet flag" +complete -c monero-wallet-cli -l stagenet -d "For stagenet. Daemon must also be launched with --stagenet flag" +complete -c monero-wallet-cli -l shared-ringdb-dir -r -F -d "Set shared ring database path" +complete -c monero-wallet-cli -l kdf-rounds -r -d "Number of rounds for the key derivation function. Default: 1" +complete -c monero-wallet-cli -l bitmessage-address -r -d "Use PyBitmessage instance at URL <arg>. Default: http://localhost:8442/" +complete -c monero-wallet-cli -l bitmessage-login -r -d "Specify <arg> as username:password for PyBitmessage API. Default: username:password" +complete -c monero-wallet-cli -l hw-device -r -d "HW device to use" +complete -c monero-wallet-cli -l hw-device-deriv-path -r -d "HW device wallet derivation path (e.g., SLIP-10)" +complete -c monero-wallet-cli -l tx-notify -r -d "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" +complete -c monero-wallet-cli -l no-dns -d "Do not use DNS" +complete -c monero-wallet-cli -l offline -d "Do not connect to a daemon, nor use DNS" +complete -c monero-wallet-cli -l extra-entropy -r -F -d "File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, which typically means more than 256 bits of data)" +complete -c monero-wallet-cli -l wallet-file -r -F -d "Use wallet <arg>" +complete -c monero-wallet-cli -l generate-new-wallet -r -F -d "Generate new wallet and save it to <arg>" +complete -c monero-wallet-cli -l generate-from-device -r -F -d "Generate new wallet from device and save it to <arg>" +complete -c monero-wallet-cli -l generate-from-view-key -r -d "Generate incoming-only wallet from view key" +complete -c monero-wallet-cli -l generate-from-spend-key -r -d "Generate deterministic wallet from spend key" +complete -c monero-wallet-cli -l generate-from-keys -r -d "Generate wallet from private keys" +complete -c monero-wallet-cli -l generate-from-multisig-keys -r -d "Generate a master wallet from multisig wallet keys" +complete -c monero-wallet-cli -l generate-from-json -r -k -a "(__fish_complete_suffix .json)" -d "Generate wallet from JSON format file" +complete -c monero-wallet-cli -l mnemonic-language -r -d "Language for mnemonic" +complete -c monero-wallet-cli -l command -d "" +complete -c monero-wallet-cli -l restore-deterministic-wallet -d "Recover wallet using Electrum-style mnemonic seed" +complete -c monero-wallet-cli -l restore-from-seed -d "alias for --restore-deterministic-wallet" +complete -c monero-wallet-cli -l restore-multisig-wallet -d "Recover multisig wallet using Electrum-style mnemonic seed" +complete -c monero-wallet-cli -l non-deterministic -d "Generate non-deterministic view and spend keys" +complete -c monero-wallet-cli -l electrum-seed -r -d "Specify Electrum seed for wallet recovery/creation" +complete -c monero-wallet-cli -l allow-mismatched-daemon-version -d "Allow communicating with a daemon that uses a different RPC version" +complete -c monero-wallet-cli -l restore-height -r -d "Restore from specific blockchain height. Default: 0" +complete -c monero-wallet-cli -l restore-date -r -d "Restore from estimated blockchain height on specified date" +complete -c monero-wallet-cli -l do-not-relay -d "The newly created transaction will not be relayed to the monero network" +complete -c monero-wallet-cli -l create-address-file -d "Create an address file for new wallets" +complete -c monero-wallet-cli -l subaddress-lookahead -r -d "Set subaddress lookahead sizes to <major>:<minor>" +complete -c monero-wallet-cli -l use-english-language-names -d "Display English language names" +complete -c monero-wallet-cli -l rpc-client-secret-key -r -d "Set RPC client secret key for RPC payments" +complete -c monero-wallet-cli -l log-file -r -F -d "Specify log file" +complete -c monero-wallet-cli -l log-level -r -a "0 1 2 3 4" -d "0-4 or categories" +complete -c monero-wallet-cli -l max-log-file-size -r -d "Specify maximum log file size [B]. Default: 104850000" +complete -c monero-wallet-cli -l max-log-files -r -d "Specify maximum number of rotated log files to be saved (no limit by setting to 0). Default: 50" +complete -c monero-wallet-cli -l max-concurrency -d "Max number of threads to use for a parallel job. Default: 1" +complete -c monero-wallet-cli -l config-file -r -F -d "Config file" diff --git a/utils/fish/monero-wallet-rpc.fish b/utils/fish/monero-wallet-rpc.fish new file mode 100644 index 000000000..a64e112ef --- /dev/null +++ b/utils/fish/monero-wallet-rpc.fish @@ -0,0 +1,65 @@ +complete -c monero-wallet-rpc -f + +complete -c monero-wallet-rpc -l help -d "Produce help message" +complete -c monero-wallet-rpc -l version -d "Output version information " +complete -c monero-wallet-rpc -l daemon-address -r -d "Use daemon instance at <host>:<port>" +complete -c monero-wallet-rpc -l daemon-host -r -d "Use daemon instance at host <arg> instead of localhost" +complete -c monero-wallet-rpc -l proxy -r -d "[<ip>:]<port> socks proxy to use for daemon connections" +complete -c monero-wallet-rpc -l trusted-daemon -d "Enable commands which rely on a trusted daemon" +complete -c monero-wallet-rpc -l untrusted-daemon -d "Disable commands which rely on a trusted daemon" +complete -c monero-wallet-rpc -l password -r -d "Wallet password (escape/quote as needed)" +complete -c monero-wallet-rpc -l password-file -r -F -d "Wallet password file" +complete -c monero-wallet-rpc -l daemon-port -r -d "Use daemon instance at port <arg> instead of 18081. Default: 0" +complete -c monero-wallet-rpc -l daemon-login -r -d "Specify username[:password] for daemon RPC client" +complete -c monero-wallet-rpc -l daemon-ssl -x -a "enabled disabled autodetect" -d "Enable SSL on daemon RPC connections. Default: autodetect" +complete -c monero-wallet-rpc -l daemon-ssl-private-key -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format private key" +complete -c monero-wallet-rpc -l daemon-ssl-certificate -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format certificate" +complete -c monero-wallet-rpc -l daemon-ssl-ca-certificates -r -F -d "Path to file containing concatenated PEM format certificate(s) to replace system CA(s)." +complete -c monero-wallet-rpc -l daemon-ssl-allowed-fingerprints -r -d "List of valid fingerprints of allowed RPC servers" +complete -c monero-wallet-rpc -l daemon-ssl-allow-any-cert -d "Allow any SSL certificate from the daemon" +complete -c monero-wallet-rpc -l daemon-ssl-allow-chained -d "Allow user (via --daemon-ssl-ca-certifi cates) chain certificates" +complete -c monero-wallet-rpc -l testnet -d "For testnet. Daemon must also be launched with --testnet flag" +complete -c monero-wallet-rpc -l stagenet -d "For stagenet. Daemon must also be launched with --stagenet flag" +complete -c monero-wallet-rpc -l shared-ringdb-dir -r -F -d "Set shared ring database path" +complete -c monero-wallet-rpc -l kdf-rounds -r -d "Number of rounds for the key derivation function. Default: 1" +complete -c monero-wallet-rpc -l bitmessage-address -r -d "Use PyBitmessage instance at URL <arg>. Default: http://localhost:8442/" +complete -c monero-wallet-rpc -l bitmessage-login -r -d "Specify <arg> as username:password for PyBitmessage API. Default: username:password" +complete -c monero-wallet-rpc -l hw-device -r -d "HW device to use" +complete -c monero-wallet-rpc -l hw-device-deriv-path -r -d "HW device wallet derivation path (e.g., SLIP-10)" +complete -c monero-wallet-rpc -l tx-notify -r -d "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" +complete -c monero-wallet-rpc -l no-dns -d "Do not use DNS" +complete -c monero-wallet-rpc -l offline -d "Do not connect to a daemon, nor use DNS" +complete -c monero-wallet-rpc -l extra-entropy -r -F -d "File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, which typically means more than 256 bits of data)" +complete -c monero-wallet-rpc -l rpc-bind-port -r -d "Sets bind port for server" +complete -c monero-wallet-rpc -l disable-rpc-login -d "Disable HTTP authentication for RPC connections served by this process" +complete -c monero-wallet-rpc -l restricted-rpc -d "Restricts to view-only commands" +complete -c monero-wallet-rpc -l rpc-bind-ip -r -d "Specify IP to bind RPC server. Default: 127.0.0.1" +complete -c monero-wallet-rpc -l rpc-bind-ipv6-address -r -d "Specify IPv6 address to bind RPC server. Default: ::1" +complete -c monero-wallet-rpc -l rpc-restricted-bind-ip -r -d "Specify IP to bind restricted RPC server. Default: 127.0.0.1" +complete -c monero-wallet-rpc -l rpc-restricted-bind-ipv6-address -r -d "Specify IPv6 address to bind restricted RPC server. Default: ::1" +complete -c monero-wallet-rpc -l rpc-use-ipv6 -d "Allow IPv6 for RPC" +complete -c monero-wallet-rpc -l rpc-ignore-ipv4 -d "Ignore unsuccessful IPv4 bind for RPC" +complete -c monero-wallet-rpc -l rpc-login -r -d "Specify username[:password] required for RPC server" +complete -c monero-wallet-rpc -l confirm-external-bind -d "Confirm rpc-bind-ip value is NOT a loopback (local) IP" +complete -c monero-wallet-rpc -l rpc-access-control-origins -r -d "Specify a comma separated list of origins to allow cross origin resource sharing" +complete -c monero-wallet-rpc -l rpc-ssl -x -a "enabled disabled autodetect" -d "Enable SSL on RPC connections. Default: autodetect" +complete -c monero-wallet-rpc -l rpc-ssl-private-key -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format private key" +complete -c monero-wallet-rpc -l rpc-ssl-certificate -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format certificate" +complete -c monero-wallet-rpc -l rpc-ssl-ca-certificates -r -F -d "Path to file containing concatenated PEM format certificate(s) to replace system CA(s)." +complete -c monero-wallet-rpc -l rpc-ssl-allowed-fingerprints -r -d "List of certificate fingerprints to allow" +complete -c monero-wallet-rpc -l rpc-ssl-allow-chained -d "Allow user (via --rpc-ssl-certificates) chain certificates" +complete -c monero-wallet-rpc -l disable-rpc-ban -d "Do not ban hosts on RPC errors" +complete -c monero-wallet-rpc -l wallet-file -r -F -d "Use wallet <arg>" +complete -c monero-wallet-rpc -l generate-from-json -r -k -a "(__fish_complete_suffix .json)" -d "Generate wallet from JSON format file" +complete -c monero-wallet-rpc -l wallet-dir -r -F -d "Directory for newly created wallets" +complete -c monero-wallet-rpc -l prompt-for-password -d "Prompts for password when not provided" +complete -c monero-wallet-rpc -l rpc-client-secret-key -r -d "Set RPC client secret key for RPC payments" +complete -c monero-wallet-rpc -l detach -d "Run as daemon" +complete -c monero-wallet-rpc -l pidfile -r -F -d "File path to write the daemon's PID to (optional, requires --detach)" +complete -c monero-wallet-rpc -l non-interactive -d "Run non-interactive" +complete -c monero-wallet-rpc -l log-file -r -F -d "Specify log file" +complete -c monero-wallet-rpc -l log-level -r -a "0 1 2 3 4" -d "0-4 or categories" +complete -c monero-wallet-rpc -l max-log-file-size -r -d "Specify maximum log file size [B]. Default: 104850000" +complete -c monero-wallet-rpc -l max-log-files -r -d "Specify maximum number of rotated log files to be saved (no limit by setting to 0). Default: 50" +complete -c monero-wallet-rpc -l max-concurrency -d "Max number of threads to use for a parallel job. Default: 1" +complete -c monero-wallet-rpc -l config-file -r -F -d "Config file" diff --git a/utils/fish/monerod.fish b/utils/fish/monerod.fish new file mode 100644 index 000000000..d2836a6b2 --- /dev/null +++ b/utils/fish/monerod.fish @@ -0,0 +1,113 @@ +complete -c monerod -f + +complete -c monerod -l help -d "Produce help message" +complete -c monerod -l version -d "Output version information" +complete -c monerod -l os-version -d "OS for which this executable was compiled" +complete -c monerod -l config-file -r -d "Specify configuration file" +complete -c monerod -l detach -d "Run as daemon" +complete -c monerod -l pidfile -r -F -d "File path to write the daemon's PID to (optional, requires --detach)" +complete -c monerod -l non-interactive -d "Run non-interactive" +complete -c monerod -l log-file -r -F -d "Specify log file" +complete -c monerod -l log-level -r -d "" +complete -c monerod -l max-log-file-size -r -d "Specify maximum log file size [B]. Default: 104850000" +complete -c monerod -l max-log-files -r -d "Specify maximum number of rotated log files to be saved (no limit by setting to 0). Default: 50" +complete -c monerod -l max-concurrency -r -d "Max number of threads to use for a parallel job. Default: 0" +complete -c monerod -l proxy -r -d "Network communication through proxy: <socks-ip:port> i.e. \"127.0.0.1:9050\"" +complete -c monerod -l proxy-allow-dns-leaks -d "Allow DNS leaks outside of proxy" +complete -c monerod -l public-node -d "Allow other users to use the node as a remote (restricted RPC mode, view-only commands) and advertise it over P2P" +complete -c monerod -l zmq-rpc-bind-ip -r -d "IP for ZMQ RPC server to listen on. Default: 127.0.0.1" +complete -c monerod -l zmq-rpc-bind-port -r -d "Port for ZMQ RPC server to listen on. Default: 18082, 28082 if 'testnet', 38082 if 'stagenet'" +complete -c monerod -l zmq-pub -r -d "Address for ZMQ pub - tcp://ip:port or ipc://path " +complete -c monerod -l no-zmq -d "Disable ZMQ RPC server [114/349]" +complete -c monerod -l data-dir -x -a "(__fish_complete_directories)" -d "Specify data directory" +complete -c monerod -l test-drop-download -d "For net tests: in download, discard ALL blocks instead checking/saving them (very fast)" +complete -c monerod -l test-drop-download-height -r -d "Like test-drop-download but discards only after around certain height. Default: 0" +complete -c monerod -l testnet -d "Run on testnet. The wallet must be launched with --testnet flag." +complete -c monerod -l stagenet -d "Run on stagenet. The wallet must be launched with --stagenet flag." +complete -c monerod -l regtest -d "Run in a regression testing mode." +complete -c monerod -l keep-fakechain -d "Don't delete any existing database when in fakechain mode." +complete -c monerod -l fixed-difficulty -r -d "Fixed difficulty used for testing. Default: 0" +complete -c monerod -l enforce-dns-checkpointing -d "checkpoints from DNS server will be enforced" +complete -c monerod -l prep-blocks-threads -r -d "Max number of threads to use when preparing block hashes in groups. Default: 4" +complete -c monerod -l fast-block-sync -r -d "Sync up most of the way by using embedded, known block hashes. Default: 1" +complete -c monerod -l show-time-stats -r -d "(=0) Show time-stats when processing blocks/txs and disk synchronization. Default: 0" +complete -c monerod -l block-sync-size -r -d "(=0) How many blocks to sync at once during chain synchronization (0 = adaptive). Default: 0" +complete -c monerod -l check-updates -x -a "disabled notify download update" -d "Check for new versions of monero. Default: notify" +complete -c monerod -l fluffy-blocks -d "Relay blocks as fluffy blocks (obsolete, now default)" +complete -c monerod -l no-fluffy-blocks -d "Relay blocks as normal blocks" +complete -c monerod -l test-dbg-lock-sleep -r -d "Sleep time in ms, defaults to 0 (off), used to debug before/after locking mutex. Values 100 to 1000 are good for tests." +complete -c monerod -l offline -d "Do not listen for peers, nor connect to any" +complete -c monerod -l disable-dns-checkpoints -d "Do not retrieve checkpoints from DNS" +complete -c monerod -l block-download-max-size -r -d "Set maximum size of block download queue in bytes (0 for default)" +complete -c monerod -l sync-pruned-blocks -d "Allow syncing from nodes with only pruned blocks" +complete -c monerod -l max-txpool-weight -r -d "Set maximum txpool weight in bytes. Default: 648000000" +complete -c monerod -l block-notify -r -d "Run a program for each new block, '%s' will be replaced by the block hash" +complete -c monerod -l prune-blockchain -d "Prune blockchain" +complete -c monerod -l reorg-notify -r -d "Run a program for each reorg, '%s' will be replaced by the split height, '%h' will be replaced by the new blockchain height, '%n' will be replaced by the number of new blocks in the new chain, and '%d' will be replaced by the number of blocks discarded from the old chain" +complete -c monerod -l block-rate-notify -r -d "Run a program when the block rate undergoes large fluctuations. This might be a sign of large amounts of hash rate going on and off the Monero network, and thus be of potential interest in predicting attacks. %t will be replaced by the number of minutes for the observation window, %b by the number of blocks observed within that window, and %e by the number of blocks that was expected in that window. It is suggested that this notification is used to automatically increase the number of confirmations required before a payment is acted upon." +complete -c monerod -l keep-alt-blocks -d "Keep alternative blocks on restart" +complete -c monerod -l extra-messages-file -r -F -d "Specify file for extra messages to include into coinbase transactions" +complete -c monerod -l start-mining -r -d "Specify wallet address to mining for" +complete -c monerod -l mining-threads -r -d "Specify mining threads count" +complete -c monerod -l bg-mining-enable -d "Enable background mining" +complete -c monerod -l bg-mining-ignore-battery -d "If true, assumes plugged in when unable to query system power status" +complete -c monerod -l bg-mining-min-idle-interval -r -d "Specify min lookback interval in seconds for determining idle state" +complete -c monerod -l bg-mining-idle-threshold -r -d "Specify minimum avg idle percentage over lookback interval" +complete -c monerod -l bg-mining-miner-target -r -d "Specify maximum percentage cpu use by miner(s)" +complete -c monerod -l db-sync-mode -r -d "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[<nblo cks_per_sync>[blocks]|<nbytes_per_sync> [bytes]]. Default: fast:async:250000000bytes" +complete -c monerod -l db-salvage -d "Try to salvage a blockchain database if it seems corrupted" +complete -c monerod -l p2p-bind-ip -r -d "Interface for p2p network protocol (IPv4). Default: 0.0.0.0" +complete -c monerod -l p2p-bind-ipv6-address -r -d "Interface for p2p network protocol (IPv6). Default: ::" +complete -c monerod -l p2p-bind-port -r -d "Port for p2p network protocol (IPv4). Default: 18080, 28080 if 'testnet', 38080 if 'stagenet'" +complete -c monerod -l p2p-bind-port-ipv6 -d "Port for p2p network protocol (IPv6). Default: 18080, 28080 if 'testnet', 38080 if 'stagenet'" +complete -c monerod -l p2p-use-ipv6 -d "Enable IPv6 for p2p" +complete -c monerod -l p2p-ignore-ipv4 -d "Ignore unsuccessful IPv4 bind for p2p" +complete -c monerod -l p2p-external-port -r -d "External port for p2p network protocol (if port forwarding used with NAT). Default: 0" +complete -c monerod -l allow-local-ip -d "Allow local ip add to peer list, mostly in debug purposes" +complete -c monerod -l add-peer -r -d "Manually add peer to local peerlist" +complete -c monerod -l add-priority-node -r -d "Specify list of peers to connect to and attempt to keep the connection open" +complete -c monerod -l add-exclusive-node -r -d "Specify list of peers to connect to only. If this option is given the options add-priority-node and seed-node are ignored" +complete -c monerod -l seed-node -r -d "Connect to a node to retrieve peer addresses, and disconnect" +complete -c monerod -l tx-proxy -r -d "Send local txes through proxy: <network-type>,<socks-ip:port>[,max_con nections][,disable_noise] i.e. \"tor,127.0.0.1:9050,100,disable_noise\"" +complete -c monerod -l anonymous-inbound -r -d "<hidden-service-address>,<[bind-ip:]port>[,max_connections] i.e. \"x.onion,127.0.0.1:18083,100\"" +complete -c monerod -l ban-list -r -F -d "Specify ban list file, one IP address per line" +complete -c monerod -l hide-my-port -d "Do not announce yourself as peerlist candidate" +complete -c monerod -l no-sync -d "Don't synchronize the blockchain with other peers" +complete -c monerod -l enable-dns-blocklist -d "Apply realtime blocklist from DNS" +complete -c monerod -l no-igd -d "Disable UPnP port mapping" +complete -c monerod -l igd -r -a "Enabled disabled enabled" -d "UPnP port mapping. Default: delayed" +complete -c monerod -l out-peers -r -d "Set max number of out peers. Default: -1" +complete -c monerod -l in-peers -r -d "Set max number of in peers. Default: -1" +complete -c monerod -l tos-flag -r -d "Set TOS flag. Default: -1" +complete -c monerod -l limit-rate-up -r -d "Set limit-rate-up [kB/s]. Default: 2048" +complete -c monerod -l limit-rate-down -r -d "Set limit-rate-down [kB/s]. Default: 8192" +complete -c monerod -l limit-rate -r -d "Set limit-rate [kB/s]. Default: -1" +complete -c monerod -l pad-transactions -d "Pad relayed transactions to help defend against traffic volume analysis" +complete -c monerod -l max-connections-per-ip -r -d "Maximum number of connections allowed from the same IP address. Default: 1" +complete -c monerod -l rpc-bind-port -r -d "Port for RPC server. Default: 18081, 28081 if 'testnet', 38081 if 'stagenet'" +complete -c monerod -l rpc-restricted-bind-port -r -d "Port for restricted RPC server" +complete -c monerod -l restricted-rpc -d "Restrict RPC to view only commands and do not return privacy sensitive data in RPC calls" +complete -c monerod -l bootstrap-daemon-address -r -d "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced. Use 'auto' to enable automatic public nodes discovering and bootstrap daemon switching" +complete -c monerod -l bootstrap-daemon-login -r -d "Specify username:password for the bootstrap daemon login" +complete -c monerod -l bootstrap-daemon-proxy -r -d "<ip>:<port> socks proxy to use for bootstrap daemon connections" +complete -c monerod -l rpc-bind-ip -r -d "Specify IP to bind RPC server. Default: 127.0.0.1" +complete -c monerod -l rpc-bind-ipv6-address -r -d "Specify IPv6 address to bind RPC server. Default: ::1" +complete -c monerod -l rpc-restricted-bind-ip -r -d "Specify IP to bind restricted RPC server. Default: 127.0.0.1" +complete -c monerod -l rpc-restricted-bind-ipv6-address -r -d "Specify IPv6 address to bind restricted RPC server. Default: ::1" +complete -c monerod -l rpc-use-ipv6 -d "Allow IPv6 for RPC" +complete -c monerod -l rpc-ignore-ipv4 -d "Ignore unsuccessful IPv4 bind for RPC" +complete -c monerod -l rpc-login -d "Specify username[:password] required for RPC server" +complete -c monerod -l confirm-external-bind -d "Confirm rpc-bind-ip value is NOT a loopback (local) IP" +complete -c monerod -l rpc-access-control-origins -r -d "Specify a comma separated list of origins to allow cross origin resource sharing" +complete -c monerod -l rpc-ssl -x -a "enabled disabled autodetect" -d "Enable SSL on RPC connections. Default: autodetect" +complete -c monerod -l rpc-ssl-private-key -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format private key" +complete -c monerod -l rpc-ssl-certificate -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format certificate" +complete -c monerod -l rpc-ssl-ca-certificates -r -F -d "Path to file containing concatenated PEM format certificate(s) to replace system CA(s)." +complete -c monerod -l rpc-ssl-allowed-fingerprints -r -d "List of certificate fingerprints to allow" +complete -c monerod -l rpc-ssl-allow-chained -d "Allow user (via --rpc-ssl-certificates) chain certificates" +complete -c monerod -l disable-rpc-ban -d "Do not ban hosts on RPC errors" +complete -c monerod -l rpc-ssl-allow-any-cert -d "Allow any peer certificate" +complete -c monerod -l rpc-payment-address -r -d "Restrict RPC to clients sending micropayment to this address" +complete -c monerod -l rpc-payment-difficulty -r -d "Restrict RPC to clients sending micropayment at this difficulty. Default: 1000" +complete -c monerod -l rpc-payment-credits -r -d "Restrict RPC to clients sending micropayment, yields that many credits per payment. Default: 100" +complete -c monerod -l rpc-payment-allow-free-loopback -d "Allow free access from the loopback address (ie, the local host)" |