From 6d219a9250c10084a02ab150e420ede6eecd7aaf Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 28 May 2017 12:18:51 +0100 Subject: [PATCH 01/19] wallet: add multisig key generation Scheme by luigi1111 --- src/cryptonote_basic/account.cpp | 7 ++ src/cryptonote_basic/account.h | 1 + src/simplewallet/simplewallet.cpp | 144 +++++++++++++++++++++++++- src/simplewallet/simplewallet.h | 2 + src/wallet/wallet2.cpp | 163 +++++++++++++++++++++++++++++- src/wallet/wallet2.h | 22 +++- 6 files changed, 332 insertions(+), 7 deletions(-) diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index fb832d88e..9cc44f0f0 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -123,6 +123,13 @@ DISABLE_VS_WARNINGS(4244 4345) create_from_keys(address, fake, viewkey); } //----------------------------------------------------------------- + bool account_base::make_multisig(const crypto::secret_key &view_secret_key, const crypto::public_key &spend_public_key) + { + m_keys.m_account_address.m_spend_public_key = spend_public_key; + m_keys.m_view_secret_key = view_secret_key; + return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key); + } + //----------------------------------------------------------------- const account_keys& account_base::get_keys() const { return m_keys; diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h index e0d5447a2..ab837df3f 100644 --- a/src/cryptonote_basic/account.h +++ b/src/cryptonote_basic/account.h @@ -60,6 +60,7 @@ namespace cryptonote crypto::secret_key generate(const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false, bool two_random = false); void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); + bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::public_key &spend_public_key); const account_keys& get_keys() const; std::string get_public_address_str(bool testnet) const; std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index bbf794c05..4349b6b92 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -587,6 +587,133 @@ bool simple_wallet::print_fee_info(const std::vector &args/* = std: return true; } +bool simple_wallet::prepare_multisig(const std::vector &args) +{ + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is already multisig"); + return true; + } + + if(m_wallet->get_num_transfer_details()) + { + fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); + return true; + } + + const auto orig_pwd_container = get_and_verify_password(); + if(orig_pwd_container == boost::none) + { + fail_msg_writer() << tr("Your password is incorrect."); + return true; + } + + std::string multisig_info = m_wallet->get_multisig_info(); + success_msg_writer() << multisig_info; + success_msg_writer() << "Send this multisig info to all other participants, then use make_multisig [...] with others' multisig info"; + success_msg_writer() << "This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "; + return true; +} + +bool simple_wallet::make_multisig(const std::vector &args) +{ + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is already multisig"); + return true; + } + + if(m_wallet->get_num_transfer_details()) + { + fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); + return true; + } + + const auto orig_pwd_container = get_and_verify_password(); + if(orig_pwd_container == boost::none) + { + fail_msg_writer() << tr("Your original password was incorrect."); + return true; + } + + if (args.size() < 2) + { + fail_msg_writer() << tr("usage: make_multisig [...]"); + return true; + } + + // parse threshold + uint32_t threshold; + if (!string_tools::get_xtype_from_string(threshold, args[0])) + { + fail_msg_writer() << tr("Invalid threshold"); + return true; + } + + // parse all multisig info + std::vector secret_keys(args.size() - 1); + std::vector public_keys(args.size() - 1); + for (size_t i = 1; i < args.size(); ++i) + { + if (!tools::wallet2::verify_multisig_info(args[i], secret_keys[i - 1], public_keys[i - 1])) + { + fail_msg_writer() << tr("Bad multisig info: ") << args[i]; + return true; + } + } + + // remove duplicates + for (size_t i = 1; i < secret_keys.size(); ++i) + { + if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[0])) + { + message_writer() << tr("Duplicate key found, ignoring"); + secret_keys[i] = secret_keys.back(); + public_keys[i] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --i; + } + } + + // people may include their own, weed it out + for (size_t i = 0; i < secret_keys.size(); ++i) + { + if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(m_wallet->get_account().get_keys().m_view_secret_key)) + { + message_writer() << tr("Local key is present, ignoring"); + secret_keys[i] = secret_keys.back(); + public_keys[i] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --i; + } + else if (rct::pk2rct(public_keys[i]) == rct::pk2rct(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key)) + { + fail_msg_writer() << "Found local spend public key, but not local view secret key - something very weird"; + return true; + } + } + + LOCK_IDLE_SCOPE(); + + try + { + m_wallet->make_multisig(orig_pwd_container->password(), secret_keys, public_keys, threshold); + } + catch (const std::exception &e) + { + fail_msg_writer() << "Error creating multisig: " << e.what(); + return true; + } + + uint32_t total = secret_keys.size() + 1; + success_msg_writer() << std::to_string(threshold) << "/" << total << " multisig address: " + << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + + return true; +} + bool simple_wallet::set_always_confirm_transfers(const std::vector &args/* = std::vector()*/) { const auto pwd_container = get_and_verify_password(); @@ -1165,10 +1292,16 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("fee", boost::bind(&simple_wallet::print_fee_info, this, _1), tr("Print the information about the current fee and transaction backlog.")); + m_cmd_binder.set_handler("prepare_multisig", boost::bind(&simple_wallet::prepare_multisig, this, _1), + tr("Export data needed to create a multisig wallet")); + m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1), + tr("make_multisig [...]"), + tr("Turn this wallet into a multisig wallet")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("help []"), tr("Show the help section or the documentation about a .")); + m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::set_variable(const std::vector &args) @@ -2098,9 +2231,16 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) return false; } + std::string prefix; + uint32_t threshold, total; + if (m_wallet->watch_only()) + prefix = tr("Opened watch-only wallet"); + else if (m_wallet->multisig(&threshold, &total)) + prefix = (boost::format(tr("Opened %u/%u multisig wallet")) % threshold % total).str(); + else + prefix = tr("Opened wallet"); message_writer(console_color_white, true) << - (m_wallet->watch_only() ? tr("Opened watch-only wallet") : tr("Opened wallet")) << ": " - << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + prefix << ": " << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); // If the wallet file is deprecated, we should ask for mnemonic language again and store // everything in the new format. // NOTE: this is_deprecated() refers to the wallet file format before becoming JSON. It does not refer to the "old english" seed words form of "deprecated" used elsewhere. diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index ad174a636..676870a81 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -188,6 +188,8 @@ namespace cryptonote bool change_password(const std::vector& args); bool payment_id(const std::vector &args); bool print_fee_info(const std::vector &args); + bool prepare_multisig(const std::vector& args); + bool make_multisig(const std::vector& args); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index abd24295a..4ad527423 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2181,6 +2181,15 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? json.AddMember("watch_only", value2, json.GetAllocator()); + value2.SetInt(m_multisig ? 1 :0); + json.AddMember("multisig", value2, json.GetAllocator()); + + value2.SetUint(m_multisig_threshold); + json.AddMember("multisig_threshold", value2, json.GetAllocator()); + + value2.SetUint(m_multisig_total); + json.AddMember("multisig_total", value2, json.GetAllocator()); + value2.SetInt(m_always_confirm_transfers ? 1 :0); json.AddMember("always_confirm_transfers", value2, json.GetAllocator()); @@ -2292,6 +2301,9 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ { is_old_file_format = true; m_watch_only = false; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_total = 0; m_always_confirm_transfers = false; m_print_ring_members = false; m_default_mixin = 0; @@ -2328,6 +2340,12 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false, false); m_watch_only = field_watch_only; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig, int, Int, false, false); + m_multisig = field_multisig; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_threshold, unsigned int, Uint, m_multisig, 0); + m_multisig_threshold = field_multisig_threshold; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_total, unsigned int, Uint, m_multisig, 0); + m_multisig_total = field_multisig_total; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true); m_always_confirm_transfers = field_always_confirm_transfers; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true); @@ -2394,7 +2412,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ const cryptonote::account_keys& keys = m_account.get_keys(); r = epee::serialization::load_t_from_binary(m_account, account_data); r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - if(!m_watch_only) + if(!m_watch_only && !m_multisig) r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password); return true; @@ -2412,7 +2430,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ */ bool wallet2::verify_password(const epee::wipeable_string& password) const { - return verify_password(m_keys_file, password, m_watch_only); + return verify_password(m_keys_file, password, m_watch_only || m_multisig); } /*! @@ -2427,7 +2445,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) const * can be used prior to rewriting wallet keys file, to ensure user has entered the correct password * */ -bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only) +bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key) { wallet2::keys_file_data keys_file_data; std::string buf; @@ -2461,7 +2479,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip const cryptonote::account_keys& keys = account_data_check.get_keys(); r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - if(!watch_only) + if(!no_spend_key) r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); return r; } @@ -2489,6 +2507,9 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_account_public_address = m_account.get_keys().m_account_address; m_watch_only = false; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_total = 0; // -1 month for fluctuations in block time and machine date/time setup. // avg seconds per block @@ -2569,6 +2590,9 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_account.create_from_viewkey(account_public_address, viewkey); m_account_public_address = account_public_address; m_watch_only = true; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_total = 0; bool r = store_keys(m_keys_file, password, true); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); @@ -2605,6 +2629,69 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_account.create_from_keys(account_public_address, spendkey, viewkey); m_account_public_address = account_public_address; m_watch_only = false; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_total = 0; + + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + + cryptonote::block b; + generate_genesis(b); + m_blockchain.push_back(get_block_hash(b)); + + store(); +} + +void wallet2::make_multisig(const epee::wipeable_string &password, + const std::vector &view_keys, + const std::vector &spend_keys, + uint32_t threshold) +{ + CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys"); + CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes"); + CHECK_AND_ASSERT_THROW_MES(threshold > 0 && threshold <= spend_keys.size() + 1, "Invalid threshold"); + CHECK_AND_ASSERT_THROW_MES(threshold == spend_keys.size() || threshold == spend_keys.size() + 1, "Unsupported threshold case"); + + clear(); + + MINFO("Creating spend key..."); + rct::key spend_pkey = rct::pk2rct(get_account().get_keys().m_account_address.m_spend_public_key); + if (threshold == spend_keys.size() + 1) + { + // the multisig spend public key is the sum of all spend public keys + for (const auto &k: spend_keys) + rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); + } + else + { + // the multisig spend public key is the sum of keys derived from all spend public keys + const rct::key spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); + for (const auto &k: spend_keys) + { + rct::addKeys(spend_pkey, spend_pkey, rct::scalarmultBase(rct::cn_fast_hash(rct::scalarmultKey(rct::pk2rct(k), spend_skey)))); + } + } + + // the multisig view key is shared by all, make one all can derive + MINFO("Creating view key..."); + rct::key view_skey = rct::sk2rct(get_account().get_keys().m_view_secret_key); + for (const auto &k: view_keys) + sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes); + sc_reduce32(view_skey.bytes); + + MINFO("Creating multisig address..."); + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2pk(spend_pkey)), + "Failed to create multisig wallet due to bad keys"); + + m_account_public_address = m_account.get_keys().m_account_address; + m_watch_only = false; + m_multisig = true; + m_multisig_threshold = threshold; + m_multisig_total = spend_keys.size() + 1; bool r = store_keys(m_keys_file, password, false); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); @@ -2620,6 +2707,74 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& store(); } +std::string wallet2::get_multisig_info() const +{ + // It's a signed package of private view key and public spend key + const crypto::secret_key &skey = get_account().get_keys().m_view_secret_key; + const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key; + + std::string data; + data += std::string((const char *)&skey, sizeof(crypto::secret_key)); + data += std::string((const char *)&pkey, sizeof(crypto::public_key)); + + data.resize(data.size() + sizeof(crypto::signature)); + crypto::hash hash; + crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); + crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; + crypto::generate_signature(hash, pkey, get_account().get_keys().m_spend_secret_key, signature); + + return std::string("MultisigV1") + tools::base58::encode(data); +} + +bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey) +{ + const size_t header_len = strlen("MultisigV1"); + if (data.size() < header_len || data.substr(0, header_len) != "MultisigV1") + { + MERROR("Multisig info header check error"); + return false; + } + std::string decoded; + if (!tools::base58::decode(data.substr(header_len), decoded)) + { + MERROR("Multisig info decoding error"); + return false; + } + if (decoded.size() != sizeof(crypto::secret_key) + sizeof(crypto::public_key) + sizeof(crypto::signature)) + { + MERROR("Multisig info is corrupt"); + return false; + } + + size_t offset = 0; + skey = *(const crypto::secret_key*)(decoded.data() + offset); + offset += sizeof(skey); + pkey = *(const crypto::public_key*)(decoded.data() + offset); + offset += sizeof(pkey); + const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset); + + crypto::hash hash; + crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); + if (!crypto::check_signature(hash, pkey, signature)) + { + MERROR("Multisig info signature is invalid"); + return false; + } + + return true; +} + +bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const +{ + if (!m_multisig) + return false; + if (threshold) + *threshold = m_multisig_threshold; + if (total) + *total = m_multisig_total; + return true; +} + /*! * \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there) * \param wallet_name Name of wallet file (should exist) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index be8315468..9b068df3c 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -166,7 +166,7 @@ namespace tools //! Just parses variables. static std::unique_ptr make_dummy(const boost::program_options::variables_map& vm, const std::function(const char *, bool)> &password_prompter); - static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only); + static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key); wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {} @@ -418,6 +418,21 @@ namespace tools void generate(const std::string& wallet, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, const crypto::secret_key& viewkey = crypto::secret_key()); + /*! + * \brief Creates a multisig wallet + */ + void make_multisig(const epee::wipeable_string &password, + const std::vector &view_keys, + const std::vector &spend_keys, + uint32_t threshold); + /*! + * Get a packaged multisig information string + */ + std::string get_multisig_info() const; + /*! + * Verifies and extracts keys from a packaged multisig information string + */ + static bool verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey); /*! * \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there) * \param wallet_name Name of wallet file (should exist) @@ -512,6 +527,7 @@ namespace tools bool testnet() const { return m_testnet; } bool restricted() const { return m_restricted; } bool watch_only() const { return m_watch_only; } + bool multisig(uint32_t *threshold = NULL, uint32_t *total = NULL) const; // locked & unlocked balance of given or current subaddress account uint64_t balance(uint32_t subaddr_index_major) const; @@ -724,6 +740,7 @@ namespace tools bool delete_address_book_row(std::size_t row_id); uint64_t get_num_rct_outputs(); + size_t get_num_transfer_details() const { return m_transfers.size(); } const transfer_details &get_transfer_details(size_t idx) const; void get_hard_fork_info(uint8_t version, uint64_t &earliest_height); @@ -921,6 +938,9 @@ namespace tools std::string seed_language; /*!< Language of the mnemonics (seed). */ bool is_old_file_format; /*!< Whether the wallet file is of an old file format */ bool m_watch_only; /*!< no spend key */ + bool m_multisig; /*!< if > 1 spend secret key will not match spend public key */ + uint32_t m_multisig_threshold; + uint32_t m_multisig_total; bool m_always_confirm_transfers; bool m_print_ring_members; bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */ From 4c313324b1c80148dff1a8099aa26c51ab6c7e3a Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 3 Jun 2017 22:34:26 +0100 Subject: [PATCH 02/19] Add N/N multisig tx generation and signing Scheme by luigi1111: Multisig for RingCT on Monero 2 of 2 User A (coordinator): Spendkey b,B Viewkey a,A (shared) User B: Spendkey c,C Viewkey a,A (shared) Public Address: C+B, A Both have their own watch only wallet via C+B, a A will coordinate spending process (though B could easily as well, coordinator is more needed for more participants) A and B watch for incoming outputs B creates "half" key images for discovered output D: I2_D = (Hs(aR)+c) * Hp(D) B also creates 1.5 random keypairs (one scalar and 2 pubkeys; one on base G and one on base Hp(D)) for each output, storing the scalar(k) (linked to D), and sending the pubkeys with I2_D. A also creates "half" key images: I1_D = (Hs(aR)+b) * Hp(D) Then I_D = I1_D + I2_D Having I_D allows A to check spent status of course, but more importantly allows A to actually build a transaction prefix (and thus transaction). A builds the transaction until most of the way through MLSAG_Gen, adding the 2 pubkeys (per input) provided with I2_D to his own generated ones where they are needed (secret row L, R). At this point, A has a mostly completed transaction (but with an invalid/incomplete signature). A sends over the tx and includes r, which allows B (with the recipient's address) to verify the destination and amount (by reconstructing the stealth address and decoding ecdhInfo). B then finishes the signature by computing ss[secret_index][0] = ss[secret_index][0] + k - cc[secret_index]*c (secret indices need to be passed as well). B can then broadcast the tx, or send it back to A for broadcasting. Once B has completed the signing (and verified the tx to be valid), he can add the full I_D to his cache, allowing him to verify spent status as well. NOTE: A and B *must* present key A and B to each other with a valid signature proving they know a and b respectively. Otherwise, trickery like the following becomes possible: A creates viewkey a,A, spendkey b,B, and sends a,A,B to B. B creates a fake key C = zG - B. B sends C back to A. The combined spendkey C+B then equals zG, allowing B to spend funds at any time! The signature fixes this, because B does not know a c corresponding to C (and thus can't produce a signature). 2 of 3 User A (coordinator) Shared viewkey a,A "spendkey" j,J User B "spendkey" k,K User C "spendkey" m,M A collects K and M from B and C B collects J and M from A and C C collects J and K from A and B A computes N = nG, n = Hs(jK) A computes O = oG, o = Hs(jM) B anc C compute P = pG, p = Hs(kM) || Hs(mK) B and C can also compute N and O respectively if they wish to be able to coordinate Address: N+O+P, A The rest follows as above. The coordinator possesses 2 of 3 needed keys; he can get the other needed part of the signature/key images from either of the other two. Alternatively, if secure communication exists between parties: A gives j to B B gives k to C C gives m to A Address: J+K+M, A 3 of 3 Identical to 2 of 2, except the coordinator must collect the key images from both of the others. The transaction must also be passed an additional hop: A -> B -> C (or A -> C -> B), who can then broadcast it or send it back to A. N-1 of N Generally the same as 2 of 3, except participants need to be arranged in a ring to pass their keys around (using either the secure or insecure method). For example (ignoring viewkey so letters line up): [4 of 5] User: spendkey A: a B: b C: c D: d E: e a -> B, b -> C, c -> D, d -> E, e -> A Order of signing does not matter, it just must reach n-1 users. A "remaining keys" list must be passed around with the transaction so the signers know if they should use 1 or both keys. Collecting key image parts becomes a little messy, but basically every wallet sends over both of their parts with a tag for each. Thia way the coordinating wallet can keep track of which images have been added and which wallet they come from. Reasoning: 1. The key images must be added only once (coordinator will get key images for key a from both A and B, he must add only one to get the proper key actual key image) 2. The coordinator must keep track of which helper pubkeys came from which wallet (discussed in 2 of 2 section). The coordinator must choose only one set to use, then include his choice in the "remaining keys" list so the other wallets know which of their keys to use. You can generalize it further to N-2 of N or even M of N, but I'm not sure there's legitimate demand to justify the complexity. It might also be straightforward enough to support with minimal changes from N-1 format. You basically just give each user additional keys for each additional "-1" you desire. N-2 would be 3 keys per user, N-3 4 keys, etc. The process is somewhat cumbersome: To create a N/N multisig wallet: - each participant creates a normal wallet - each participant runs "prepare_multisig", and sends the resulting string to every other participant - each participant runs "make_multisig N A B C D...", with N being the threshold and A B C D... being the strings received from other participants (the threshold must currently equal N) As txes are received, participants' wallets will need to synchronize so that those new outputs may be spent: - each participant runs "export_multisig FILENAME", and sends the FILENAME file to every other participant - each participant runs "import_multisig A B C D...", with A B C D... being the filenames received from other participants Then, a transaction may be initiated: - one of the participants runs "transfer ADDRESS AMOUNT" - this partly signed transaction will be written to the "multisig_monero_tx" file - the initiator sends this file to another participant - that other participant runs "sign_multisig multisig_monero_tx" - the resulting transaction is written to the "multisig_monero_tx" file again - if the threshold was not reached, the file must be sent to another participant, until enough have signed - the last participant to sign runs "submit_multisig multisig_monero_tx" to relay the transaction to the Monero network --- .../cryptonote_boost_serialization.h | 15 + src/cryptonote_core/cryptonote_tx_utils.cpp | 144 ++-- src/cryptonote_core/cryptonote_tx_utils.h | 7 +- src/ringct/rctSigs.cpp | 106 ++- src/ringct/rctSigs.h | 16 +- src/ringct/rctTypes.h | 23 + src/simplewallet/simplewallet.cpp | 650 ++++++++++++++---- src/simplewallet/simplewallet.h | 5 + src/wallet/api/wallet2_api.h | 4 +- src/wallet/api/wallet_manager.cpp | 4 +- src/wallet/api/wallet_manager.h | 2 +- src/wallet/wallet2.cpp | 575 ++++++++++++++-- src/wallet/wallet2.h | 100 ++- src/wallet/wallet_errors.h | 19 +- src/wallet/wallet_rpc_server.cpp | 1 + src/wallet/wallet_rpc_server_commands_defs.h | 2 + tests/unit_tests/ringct.cpp | 18 +- tests/unit_tests/serialization.cpp | 2 +- 18 files changed, 1420 insertions(+), 273 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 760edf9b9..ed8239176 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -252,6 +252,21 @@ namespace boost // a & x.senderPk; // not serialized, as we do not use it in monero currently } + template + inline void serialize(Archive &a, rct::multisig_kLRki &x, const boost::serialization::version_type ver) + { + a & x.k; + a & x.L; + a & x.R; + a & x.ki; + } + + template + inline void serialize(Archive &a, rct::multisig_out &x, const boost::serialization::version_type ver) + { + a & x.c; + } + template inline typename std::enable_if::type serializeOutPk(Archive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) { diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 662420bef..799c27e06 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -45,6 +45,47 @@ using namespace crypto; namespace cryptonote { + //--------------------------------------------------------------- + void classify_addresses(const std::vector &destinations, const boost::optional& change_addr, size_t &num_stdaddresses, size_t &num_subaddresses, account_public_address &single_dest_subaddress) + { + num_stdaddresses = 0; + num_subaddresses = 0; + std::unordered_set unique_dst_addresses; + for(const tx_destination_entry& dst_entr: destinations) + { + if (change_addr && dst_entr.addr == change_addr) + continue; + if (unique_dst_addresses.count(dst_entr.addr) == 0) + { + unique_dst_addresses.insert(dst_entr.addr); + if (dst_entr.is_subaddress) + { + ++num_subaddresses; + single_dest_subaddress = dst_entr.addr; + } + else + { + ++num_stdaddresses; + } + } + } + LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << " subaddresses"); + } + //--------------------------------------------------------------- + bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) + { + crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); + bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + + r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); + + crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); + + crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); + return true; + } //--------------------------------------------------------------- bool construct_miner_tx(size_t height, size_t median_size, uint64_t already_generated_coins, size_t current_block_size, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) { tx.vin.clear(); @@ -161,19 +202,21 @@ namespace cryptonote return destinations[0].addr.m_view_public_key; } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map& subaddresses, std::vector& sources, const std::vector& destinations, const boost::optional& change_addr, std::vector extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector &additional_tx_keys, bool rct, bool bulletproof) + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map& subaddresses, std::vector& sources, const std::vector& destinations, const boost::optional& change_addr, std::vector extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector &additional_tx_keys, bool rct, bool bulletproof, rct::multisig_out *msout) { std::vector amount_keys; tx.set_null(); amount_keys.clear(); + if (msout) + { + msout->c.clear(); + } tx.version = rct ? 2 : 1; tx.unlock_time = unlock_time; tx.extra = extra; - keypair txkey; - txkey.sec = rct::rct2sk(rct::skGen()); - tx_key = txkey.sec; + crypto::public_key txkey_pub; // if we have a stealth payment id, find it and encrypt it with the tx key now std::vector tx_extra_fields; @@ -193,7 +236,7 @@ namespace cryptonote return false; } - if (!encrypt_payment_id(payment_id, view_key_pub, txkey.sec)) + if (!encrypt_payment_id(payment_id, view_key_pub, tx_key)) { LOG_ERROR("Failed to encrypt payment id"); return false; @@ -240,8 +283,17 @@ namespace cryptonote in_contexts.push_back(input_generation_context_data()); keypair& in_ephemeral = in_contexts.back().in_ephemeral; crypto::key_image img; - const auto& out_key = reinterpret_cast(src_entr.outputs[src_entr.real_output].second.dest); - if(!generate_key_image_helper(sender_account_keys, subaddresses, out_key, src_entr.real_out_tx_key, src_entr.real_out_additional_tx_keys, src_entr.real_output_in_tx_index, in_ephemeral, img)) + bool r; + if (msout) + { + r = generate_key_image_helper_old(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img); + } + else + { + const auto& out_key = reinterpret_cast(src_entr.outputs[src_entr.real_output].second.dest); + r = generate_key_image_helper(sender_account_keys, subaddresses, out_key, src_entr.real_out_tx_key, src_entr.real_out_additional_tx_keys, src_entr.real_output_in_tx_index, in_ephemeral, img); + } + if (!r) { LOG_ERROR("Key image generation failed!"); return false; @@ -261,7 +313,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 = img; + input_to_key.k_image = msout ? rct::rct2ki(src_entr.multisig_kLRki.ki) : img; //fill outputs array and use relative offsets for(const tx_source_entry::output_entry& out_entry: src_entr.outputs) @@ -293,47 +345,29 @@ namespace cryptonote // figure out if we need to make additional tx pubkeys size_t num_stdaddresses = 0; size_t num_subaddresses = 0; - std::unordered_set unique_dst_addresses; account_public_address single_dest_subaddress; - for(const tx_destination_entry& dst_entr: destinations) - { - if (change_addr && dst_entr.addr == *change_addr) - continue; - if (unique_dst_addresses.count(dst_entr.addr) == 0) - { - unique_dst_addresses.insert(dst_entr.addr); - if (dst_entr.is_subaddress) - { - ++num_subaddresses; - single_dest_subaddress = dst_entr.addr; - } - else - { - ++num_stdaddresses; - } - } - } - LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << "subaddresses"); + classify_addresses(destinations, change_addr, num_stdaddresses, num_subaddresses, single_dest_subaddress); // if this is a single-destination transfer to a subaddress, we set the tx pubkey to R=s*D if (num_stdaddresses == 0 && num_subaddresses == 1) { - txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(txkey.sec))); + txkey_pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(tx_key))); } else { - txkey.pub = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(txkey.sec))); + txkey_pub = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(tx_key))); } remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key)); - add_tx_pub_key_to_extra(tx, txkey.pub); + add_tx_pub_key_to_extra(tx, txkey_pub); std::vector additional_tx_public_keys; - additional_tx_keys.clear(); // we don't need to include additional tx keys if: // - all the destinations are standard addresses // - there's only one destination which is a subaddress bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1); + if (need_additional_txkeys) + CHECK_AND_ASSERT_MES(destinations.size() == additional_tx_keys.size(), false, "Wrong amount of additional tx keys"); uint64_t summary_outs_money = 0; //fill outputs @@ -348,7 +382,7 @@ namespace cryptonote keypair additional_txkey; if (need_additional_txkeys) { - additional_txkey.sec = rct::rct2sk(rct::skGen()); + additional_txkey.sec = additional_tx_keys[output_index]; if (dst_entr.is_subaddress) additional_txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(dst_entr.addr.m_spend_public_key), rct::sk2rct(additional_txkey.sec))); else @@ -359,20 +393,19 @@ namespace cryptonote if (change_addr && dst_entr.addr == *change_addr) { // sending change to yourself; derivation = a*R - r = crypto::generate_key_derivation(txkey.pub, sender_account_keys.m_view_secret_key, derivation); - CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << txkey.pub << ", " << sender_account_keys.m_view_secret_key << ")"); + r = crypto::generate_key_derivation(txkey_pub, sender_account_keys.m_view_secret_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << txkey_pub << ", " << sender_account_keys.m_view_secret_key << ")"); } else { // sending to the recipient; derivation = r*A (or s*C in the subaddress scheme) - r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec, derivation); - CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << (dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec) << ")"); + r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : tx_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << (dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : tx_key) << ")"); } if (need_additional_txkeys) { additional_tx_public_keys.push_back(additional_txkey.pub); - additional_tx_keys.push_back(additional_txkey.sec); } if (tx.version > 1) @@ -393,10 +426,11 @@ namespace cryptonote output_index++; summary_outs_money += dst_entr.amount; } + CHECK_AND_ASSERT_MES(additional_tx_public_keys.size() == additional_tx_keys.size(), false, "Internal error creating additional public keys"); remove_field_from_tx_extra(tx.extra, typeid(tx_extra_additional_pub_keys)); - LOG_PRINT_L2("tx pubkey: " << txkey.pub); + LOG_PRINT_L2("tx pubkey: " << txkey_pub); if (need_additional_txkeys) { LOG_PRINT_L2("additional tx pubkeys: "); @@ -492,6 +526,7 @@ namespace cryptonote rct::keyV destinations; std::vector inamounts, outamounts; std::vector index; + std::vector kLRki; for (size_t i = 0; i < sources.size(); ++i) { rct::ctkey ctkey; @@ -504,6 +539,10 @@ namespace cryptonote inSk.push_back(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) { @@ -553,9 +592,9 @@ namespace cryptonote get_transaction_prefix_hash(tx, tx_prefix_hash); 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, index, outSk, bulletproof); + 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, bulletproof); else - tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, sources[0].real_output, outSk, bulletproof); // same index assumption + 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, bulletproof); // same index assumption CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout"); @@ -567,13 +606,34 @@ namespace cryptonote return true; } //--------------------------------------------------------------- + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map& subaddresses, std::vector& sources, const std::vector& destinations, const boost::optional& change_addr, std::vector extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector &additional_tx_keys, bool rct, bool bulletproof, rct::multisig_out *msout) + { + keypair txkey = keypair::generate(); + tx_key = txkey.sec; + + // figure out if we need to make additional tx pubkeys + size_t num_stdaddresses = 0; + size_t num_subaddresses = 0; + account_public_address single_dest_subaddress; + classify_addresses(destinations, change_addr, num_stdaddresses, num_subaddresses, single_dest_subaddress); + bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1); + if (need_additional_txkeys) + { + additional_tx_keys.clear(); + for (const auto &d: destinations) + additional_tx_keys.push_back(keypair::generate().sec); + } + + return construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, bulletproof, msout); + } + //--------------------------------------------------------------- bool construct_tx(const account_keys& sender_account_keys, std::vector& sources, const std::vector& destinations, const boost::optional& change_addr, std::vector extra, transaction& tx, uint64_t unlock_time) { std::unordered_map subaddresses; subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0}; crypto::secret_key tx_key; std::vector additional_tx_keys; - return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys); + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, false, NULL); } //--------------------------------------------------------------- bool generate_genesis_block( diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index d72f5d13b..2b6cf26db 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -51,6 +51,7 @@ namespace cryptonote uint64_t amount; //money bool rct; //true if the output is rct rct::key mask; //ringct amount mask + rct::multisig_kLRki multisig_kLRki; //multisig info void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } @@ -87,8 +88,9 @@ namespace cryptonote //--------------------------------------------------------------- crypto::public_key get_destination_view_key_pub(const std::vector &destinations, const account_keys &sender_keys); - bool construct_tx(const account_keys& sender_account_keys, std::vector& sources, const std::vector& destinations, const boost::optional& change_addr, std::vector extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map& subaddresses, std::vector& sources, const std::vector& destinations, const boost::optional& change_addr, std::vector extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector &additional_tx_keys, bool rct = false, bool bulletproof = false); + bool construct_tx(const account_keys& sender_account_keys, std::vector &sources, const std::vector& destinations, const boost::optional& change_addr, std::vector extra, transaction& tx, uint64_t unlock_time); + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map& subaddresses, std::vector& sources, const std::vector& destinations, const boost::optional& change_addr, std::vector extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector &additional_tx_keys, bool rct = false, bool bulletproof = false, rct::multisig_out *msout = NULL); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map& subaddresses, std::vector& sources, const std::vector& destinations, const boost::optional& change_addr, std::vector extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector &additional_tx_keys, bool rct = false, bool bulletproof = false, rct::multisig_out *msout = NULL); bool generate_genesis_block( block& bl @@ -115,6 +117,7 @@ namespace boost a & x.amount; a & x.rct; a & x.mask; + a & x.multisig_kLRki; } template diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 38b213e8b..24ab08778 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -122,7 +122,7 @@ 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 unsigned int index, size_t dsRows) { + 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) { mgSig rv; size_t cols = pk.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!"); @@ -134,6 +134,8 @@ 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; @@ -148,13 +150,22 @@ namespace rct { toHash[0] = message; DP("here1"); for (i = 0; i < dsRows; i++) { - skpkGen(alpha[i], aG[i]); //need to save alphas for later.. - Hi = hashToPoint(pk[index][i]); - aHP[i] = scalarmultKey(Hi, alpha[i]); toHash[3 * i + 1] = pk[index][i]; - toHash[3 * i + 2] = aG[i]; - toHash[3 * i + 3] = aHP[i]; - rv.II[i] = scalarmultKey(Hi, xx[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 { + Hi = hashToPoint(pk[index][i]); + skpkGen(alpha[i], aG[i]); //need to save alphas for later.. + aHP[i] = scalarmultKey(Hi, alpha[i]); + toHash[3 * i + 2] = aG[i]; + toHash[3 * i + 3] = aHP[i]; + rv.II[i] = scalarmultKey(Hi, xx[i]); + } precomp(Ip[i].k, rv.II[i]); } size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper) @@ -198,7 +209,9 @@ namespace rct { } for (j = 0; j < rows; j++) { sc_mulsub(rv.ss[index][j].bytes, c.bytes, xx[j].bytes, alpha[j].bytes); - } + } + if (mscout) + *mscout = c; return rv; } @@ -393,7 +406,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, unsigned int index, key txnFeeKey) { + 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, key txnFeeKey) { mgSig mg; //setup vars size_t cols = pubs.size(); @@ -405,6 +418,7 @@ 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); @@ -437,7 +451,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.. } - return MLSAG_Gen(message, M, sk, index, rows); + return MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows); } @@ -448,12 +462,13 @@ 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, unsigned int 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) { mgSig mg; //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; @@ -464,7 +479,7 @@ namespace rct { sk[0] = copy(inSk.dest); sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes); } - return MLSAG_Gen(message, M, sk, index, rows); + return MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows); } @@ -598,13 +613,14 @@ 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 & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, bool bulletproof) { + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, bool bulletproof) { 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"); rctSig rv; rv.type = bulletproof ? RCTTypeFullBulletproof : RCTTypeFull; @@ -653,21 +669,23 @@ namespace rct { key txnFeeKey = scalarmultH(d2h(rv.txnFee)); rv.mixRing = mixRing; - rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv), rv.mixRing, inSk, outSk, rv.outPk, index, txnFeeKey)); + if (msout) + msout->c.resize(1); + rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv), rv.mixRing, inSk, outSk, rv.outPk, kLRki, msout ? &msout->c[0] : NULL, index, txnFeeKey)); return rv; } - rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector & amounts, const keyV &amount_keys, const int mixin) { + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin) { unsigned int index; ctkeyM mixRing; ctkeyV outSk; tie(mixRing, index) = populateFromBlockchain(inPk, mixin); - return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, index, outSk, false); + return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, kLRki, msout, index, outSk, false); } //RCT simple //for post-rct only - rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector &inamounts, const vector &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector & index, ctkeyV &outSk, bool bulletproof) { + rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector &inamounts, const vector &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector *kLRki, multisig_out *msout, const std::vector & index, ctkeyV &outSk, bool bulletproof) { CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations"); @@ -677,6 +695,10 @@ 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; rv.type = bulletproof ? RCTTypeSimpleBulletproof : RCTTypeSimple; @@ -736,13 +758,15 @@ namespace rct { DP(rv.pseudoOuts[i]); key full_message = get_pre_mlsag_hash(rv); + if (msout) + msout->c.resize(inamounts.size()); for (i = 0 ; i < inamounts.size(); i++) { - rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], rv.pseudoOuts[i], index[i]); + rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], rv.pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, index[i]); } return rv; } - rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector &inamounts, const vector &outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin) { + rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector &inamounts, const vector &outamounts, const keyV &amount_keys, const std::vector *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin) { std::vector index; index.resize(inPk.size()); ctkeyM mixRing; @@ -752,7 +776,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, index, outSk, false); + return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, kLRki, msout, index, outSk, false); } //RingCT protocol @@ -822,8 +846,14 @@ namespace rct { return true; } - catch(...) + catch (const std::exception &e) { + LOG_PRINT_L1("Error in verRct: " << e.what()); + return false; + } + catch (...) + { + LOG_PRINT_L1("Error in verRct, but not an actual exception"); return false; } } @@ -920,7 +950,16 @@ namespace rct { return true; } // we can get deep throws from ge_frombytes_vartime if input isn't valid - catch (...) { return false; } + catch (const std::exception &e) + { + LOG_PRINT_L1("Error in verRct: " << e.what()); + return false; + } + catch (...) + { + LOG_PRINT_L1("Error in verRct, but not an actual exception"); + return false; + } } //RingCT protocol @@ -988,4 +1027,27 @@ namespace rct { key mask; return decodeRctSimple(rv, sk, i, mask); } + + bool signMultisig(rctSig &rv, const std::vector &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { + CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeSimple || rv.type == RCTTypeFullBulletproof || rv.type == RCTTypeSimpleBulletproof, + 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.MGs.size(), false, "Mismatched k/MGs size"); + CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); + if (rv.type == RCTTypeFull || rv.type == RCTTypeFullBulletproof) + { + 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"); + } + + 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; + } } diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index 46c9cb2df..e83083a98 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -73,7 +73,7 @@ namespace rct { // the signer knows a secret key for each row in that column // Ver verifies that the MG sig was created correctly keyV keyImageV(const keyV &xx); - mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows); + 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); bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows); //mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index); @@ -95,8 +95,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, unsigned int index, key txnFee, const key &message); - mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index); + mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, key txnFee, const key &message); + 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); bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, key txnFee, const key &message); bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C); @@ -118,10 +118,10 @@ namespace rct { //decodeRct: (c.f. http://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 & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, bool bulletproof); - rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector & amounts, const keyV &amount_keys, const int mixin); - rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector & inamounts, const std::vector & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin); - rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector & inamounts, const std::vector & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector & index, ctkeyV &outSk, bool bulletproof); + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, bool bulletproof); + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector & inamounts, const std::vector & outamounts, const keyV &amount_keys, const std::vector *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector & inamounts, const std::vector & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector *kLRki, multisig_out *msout, const std::vector & index, ctkeyV &outSk, bool bulletproof); bool verRct(const rctSig & rv, bool semantics); static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); } bool verRctSimple(const rctSig & rv, bool semantics); @@ -130,6 +130,8 @@ namespace rct { xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i); xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask); xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i); + + bool signMultisig(rctSig &rv, const std::vector &indices, const keyV &k, const multisig_out &msout, const key &secret_key); } #endif /* RCTSIGS_H */ diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 50dfdb432..1f44c1a9e 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -99,6 +99,22 @@ namespace rct { typedef std::vector ctkeyV; typedef std::vector ctkeyM; + //used for multisig data + struct multisig_kLRki { + key k; + key L; + key R; + key ki; + }; + + struct multisig_out { + std::vector c; // for all inputs + + BEGIN_SERIALIZE_OBJECT() + FIELD(c) + END_SERIALIZE() + }; + //data for passing the amount to the receiver secretly // If the pedersen commitment to an amount is C = aG + bH, // "mask" contains a 32 byte key a @@ -504,6 +520,7 @@ inline std::ostream &operator <<(std::ostream &o, const rct::key &v) { BLOB_SERIALIZER(rct::key); BLOB_SERIALIZER(rct::key64); BLOB_SERIALIZER(rct::ctkey); +BLOB_SERIALIZER(rct::multisig_kLRki); BLOB_SERIALIZER(rct::boroSig); VARIANT_TAG(debug_archive, rct::key, "rct::key"); @@ -519,6 +536,8 @@ VARIANT_TAG(debug_archive, rct::rangeSig, "rct::rangeSig"); VARIANT_TAG(debug_archive, rct::boroSig, "rct::boroSig"); VARIANT_TAG(debug_archive, rct::rctSig, "rct::rctSig"); VARIANT_TAG(debug_archive, rct::Bulletproof, "rct::bulletproof"); +VARIANT_TAG(debug_archive, rct::multisig_kLRki, "rct::multisig_kLRki"); +VARIANT_TAG(debug_archive, rct::multisig_out, "rct::multisig_out"); VARIANT_TAG(binary_archive, rct::key, 0x90); VARIANT_TAG(binary_archive, rct::key64, 0x91); @@ -533,6 +552,8 @@ VARIANT_TAG(binary_archive, rct::rangeSig, 0x99); VARIANT_TAG(binary_archive, rct::boroSig, 0x9a); VARIANT_TAG(binary_archive, rct::rctSig, 0x9b); VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c); +VARIANT_TAG(binary_archive, rct::multisig_kLRki, 0x9d); +VARIANT_TAG(binary_archive, rct::multisig_out, 0x9e); VARIANT_TAG(json_archive, rct::key, "rct_key"); VARIANT_TAG(json_archive, rct::key64, "rct_key64"); @@ -547,5 +568,7 @@ VARIANT_TAG(json_archive, rct::rangeSig, "rct_rangeSig"); VARIANT_TAG(json_archive, rct::boroSig, "rct_boroSig"); VARIANT_TAG(json_archive, rct::rctSig, "rct_rctSig"); VARIANT_TAG(json_archive, rct::Bulletproof, "rct_bulletproof"); +VARIANT_TAG(json_archive, rct::multisig_kLRki, "rct_multisig_kLR"); +VARIANT_TAG(json_archive, rct::multisig_out, "rct_multisig_out"); #endif /* RCTTYPES_H */ diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 4349b6b92..539301f83 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -84,6 +84,7 @@ typedef cryptonote::simple_wallet sw; #define MIN_RING_SIZE 5 // Used to inform user about min ring size -- does not track actual protocol #define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" +#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" #define LOCK_IDLE_SCOPE() \ bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \ @@ -340,6 +341,106 @@ namespace } return true; } + + void handle_transfer_exception(const std::exception_ptr &e) + { + try + { + std::rethrow_exception(e); + } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << tr("daemon is busy. Please try again later."); + } + catch (const tools::error::no_connection_to_daemon&) + { + fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); + } + catch (const tools::error::wallet_rpc_error& e) + { + LOG_ERROR("RPC error: " << e.to_string()); + fail_msg_writer() << tr("RPC error: ") << e.what(); + } + catch (const tools::error::get_random_outs_error &e) + { + fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); + } + catch (const tools::error::not_enough_unlocked_money& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::not_enough_money& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % + print_money(e.available()) % + print_money(e.tx_amount() + e.fee()) % + print_money(e.tx_amount()) % + print_money(e.fee())); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); + } + catch (const tools::error::not_enough_outs_to_mix& e) + { + auto writer = fail_msg_writer(); + writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; + for (std::pair outs_for_amount : e.scanty_outs()) + { + writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; + } + } + catch (const tools::error::tx_not_constructed&) + { + fail_msg_writer() << tr("transaction was not constructed"); + } + catch (const tools::error::tx_rejected& e) + { + fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + std::string reason = e.reason(); + if (!reason.empty()) + fail_msg_writer() << tr("Reason: ") << reason; + } + catch (const tools::error::tx_sum_overflow& e) + { + fail_msg_writer() << e.what(); + } + catch (const tools::error::zero_destination&) + { + fail_msg_writer() << tr("one of destinations is zero"); + } + catch (const tools::error::tx_too_big& e) + { + fail_msg_writer() << tr("failed to find a suitable way to split transactions"); + } + catch (const tools::error::transfer_error& e) + { + LOG_ERROR("unknown transfer error: " << e.to_string()); + fail_msg_writer() << tr("unknown transfer error: ") << e.what(); + } + catch (const tools::error::multisig_export_needed& e) + { + LOG_ERROR("Multisig error: " << e.to_string()); + fail_msg_writer() << tr("Multisig error: ") << e.what(); + } + catch (const tools::error::wallet_internal_error& e) + { + LOG_ERROR("internal error: " << e.to_string()); + fail_msg_writer() << tr("internal error: ") << e.what(); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + } } bool parse_priority(const std::string& arg, uint32_t& priority) @@ -417,6 +518,11 @@ bool simple_wallet::print_seed(bool encrypted) bool success = false; std::string electrum_words; + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("wallet is multisig and has no seed"); + return true; + } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no seed"); @@ -468,6 +574,11 @@ bool simple_wallet::encrypted_seed(const std::vector &args/* = std: bool simple_wallet::seed_set_language(const std::vector &args/* = std::vector()*/) { + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("wallet is multisig and has no seed"); + return true; + } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no seed"); @@ -594,6 +705,11 @@ bool simple_wallet::prepare_multisig(const std::vector &args) fail_msg_writer() << tr("This wallet is already multisig"); return true; } + if (m_wallet->watch_only()) + { + fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); + return true; + } if(m_wallet->get_num_transfer_details()) { @@ -610,8 +726,8 @@ bool simple_wallet::prepare_multisig(const std::vector &args) std::string multisig_info = m_wallet->get_multisig_info(); success_msg_writer() << multisig_info; - success_msg_writer() << "Send this multisig info to all other participants, then use make_multisig [...] with others' multisig info"; - success_msg_writer() << "This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "; + success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig [...] with others' multisig info"); + success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "); return true; } @@ -622,6 +738,11 @@ bool simple_wallet::make_multisig(const std::vector &args) fail_msg_writer() << tr("This wallet is already multisig"); return true; } + if (m_wallet->watch_only()) + { + fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); + return true; + } if(m_wallet->get_num_transfer_details()) { @@ -663,23 +784,28 @@ bool simple_wallet::make_multisig(const std::vector &args) } // remove duplicates - for (size_t i = 1; i < secret_keys.size(); ++i) + for (size_t i = 0; i < secret_keys.size(); ++i) { - if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[0])) + for (size_t j = i + 1; j < secret_keys.size(); ++j) { - message_writer() << tr("Duplicate key found, ignoring"); - secret_keys[i] = secret_keys.back(); - public_keys[i] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --i; + if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j])) + { + message_writer() << tr("Duplicate key found, ignoring"); + secret_keys[j] = secret_keys.back(); + public_keys[j] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --j; + } } } // people may include their own, weed it out + const crypto::secret_key local_skey = m_wallet->get_account().get_keys().m_view_secret_key; + const crypto::public_key local_pkey = m_wallet->get_account().get_keys().m_account_address.m_spend_public_key; for (size_t i = 0; i < secret_keys.size(); ++i) { - if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(m_wallet->get_account().get_keys().m_view_secret_key)) + if (secret_keys[i] == local_skey) { message_writer() << tr("Local key is present, ignoring"); secret_keys[i] = secret_keys.back(); @@ -688,9 +814,9 @@ bool simple_wallet::make_multisig(const std::vector &args) public_keys.pop_back(); --i; } - else if (rct::pk2rct(public_keys[i]) == rct::pk2rct(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key)) + else if (public_keys[i] == local_pkey) { - fail_msg_writer() << "Found local spend public key, but not local view secret key - something very weird"; + fail_msg_writer() << tr("Found local spend public key, but not local view secret key - something very weird"); return true; } } @@ -703,17 +829,315 @@ bool simple_wallet::make_multisig(const std::vector &args) } catch (const std::exception &e) { - fail_msg_writer() << "Error creating multisig: " << e.what(); + fail_msg_writer() << tr("Error creating multisig: ") << e.what(); return true; } uint32_t total = secret_keys.size() + 1; - success_msg_writer() << std::to_string(threshold) << "/" << total << " multisig address: " + success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); return true; } +bool simple_wallet::export_multisig(const std::vector &args) +{ + if (!m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is not multisig"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: export_multisig_info "); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) + return true; + + const std::string filename = args[0]; + try + { + std::vector outs = m_wallet->export_multisig(); + + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << outs; + + std::string magic(MULTISIG_EXPORT_FILE_MAGIC, strlen(MULTISIG_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; + std::string header; + header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + crypto::hash hash; + cn_fast_hash(&m_wallet->get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); + header += std::string((const char *)&hash, sizeof(crypto::hash)); + std::string ciphertext = m_wallet->encrypt_with_view_secret_key(header + oss.str()); + bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext); + if (!r) + { + fail_msg_writer() << tr("failed to save file ") << filename; + return true; + } + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting multisig info: " << e.what()); + fail_msg_writer() << tr("Error exporting multisig info: ") << e.what(); + return true; + } + + success_msg_writer() << tr("Multisig info exported to ") << filename; + return true; +} + +bool simple_wallet::import_multisig(const std::vector &args) +{ + uint32_t threshold, total; + if (!m_wallet->multisig(&threshold, &total)) + { + fail_msg_writer() << tr("This wallet is not multisig"); + return true; + } + if (args.size() < threshold - 1) + { + fail_msg_writer() << tr("usage: import_multisig_info [...] - one for each other participant"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) + return true; + + std::vector> info; + std::unordered_set seen; + for (size_t n = 0; n < args.size(); ++n) + { + const std::string filename = args[n]; + std::string data; + bool r = epee::file_io_utils::load_file_to_string(filename, data); + if (!r) + { + fail_msg_writer() << tr("failed to read file ") << filename; + return true; + } + const size_t magiclen = strlen(MULTISIG_EXPORT_FILE_MAGIC); + if (data.size() < magiclen || memcmp(data.data(), MULTISIG_EXPORT_FILE_MAGIC, magiclen)) + { + fail_msg_writer() << tr("Bad multisig info file magic in ") << filename; + return true; + } + + try + { + data = m_wallet->decrypt_with_view_secret_key(std::string(data, magiclen)); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to decrypt ") << filename << ": " << e.what(); + return true; + } + + const size_t headerlen = 3 * sizeof(crypto::public_key); + if (data.size() < headerlen) + { + fail_msg_writer() << tr("Bad data size from file ") << filename; + return true; + } + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; + const crypto::hash &hash = *(const crypto::hash*)&data[2*sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; + if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) + { + fail_msg_writer() << (boost::format(tr("Multisig info from %s is for a different account")) % filename).str(); + return true; + } + crypto::hash this_hash; + cn_fast_hash(&m_wallet->get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&this_hash); + if (this_hash == hash) + { + message_writer() << (boost::format(tr("Multisig info from %s is from this wallet, ignored")) % filename).str(); + continue; + } + if (seen.find(hash) != seen.end()) + { + message_writer() << (boost::format(tr("Multisig info from %s already seen, ignored")) % filename).str(); + continue; + } + seen.insert(hash); + + try + { + std::string body(data, headerlen); + std::istringstream iss(body); + std::vector i; + boost::archive::portable_binary_iarchive ar(iss); + ar >> i; + message_writer() << (boost::format(tr("%u outputs found in %s")) % boost::lexical_cast(i.size()) % filename).str(); + info.push_back(std::move(i)); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); + return true; + } + } + + LOCK_IDLE_SCOPE(); + + // all read and parsed, actually import + try + { + size_t n_outputs = m_wallet->import_multisig(info); + // Clear line "Height xxx of xxx" + std::cout << "\r \r"; + success_msg_writer() << tr("Multisig info imported"); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); + return true; + } + if (m_trusted_daemon) + { + try + { + m_wallet->rescan_spent(); + } + catch (const std::exception &e) + { + message_writer() << tr("Failed to update spent status after importing multisig info: ") << e.what(); + } + } + else + { + message_writer() << tr("Untrusted daemon, spent status may be incorrect. Use a trusted daemon and run \"rescan_spent\""); + } + return true; +} + +bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) +{ + std::string extra_message; + return accept_loaded_tx([&txs](){return txs.m_ptx.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.m_ptx[n].construction_data;}, extra_message); +} + +bool simple_wallet::sign_multisig(const std::vector &args) +{ + if(!m_wallet->multisig()) + { + fail_msg_writer() << tr("This is not a multisig wallet"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: sign_multisig "); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + std::string filename = args[0]; + std::vector txids; + uint32_t signers = 0; + try + { + bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to sign multisig transaction"); + return true; + } + } + catch (const tools::error::multisig_export_needed& e) + { + fail_msg_writer() << tr("Multisig error: ") << e.what(); + return true; + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to sign multisig transaction: ") << e.what(); + return true; + } + + if (txids.empty()) + { + uint32_t threshold; + m_wallet->multisig(&threshold); + uint32_t signers_needed = threshold - signers - 1; + success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", " + << signers_needed << " more signer(s) needed"; + return true; + } + else + { + std::string txids_as_text; + for (const auto &txid: txids) + { + if (!txids_as_text.empty()) + txids_as_text += (", "); + txids_as_text += epee::string_tools::pod_to_hex(txid); + } + success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", txid " << txids_as_text; + success_msg_writer(true) << tr("It may be relayed to the network with submit_multisig"); + } + return true; +} + +bool simple_wallet::submit_multisig(const std::vector &args) +{ + uint32_t threshold; + if (!m_wallet->multisig(&threshold)) + { + fail_msg_writer() << tr("This is not a multisig wallet"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: submit_multisig "); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + if (!try_connect_to_daemon()) + return true; + + std::string filename = args[0]; + try + { + tools::wallet2::multisig_tx_set txs; + bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from file"); + return true; + } + if (txs.m_signers.size() < threshold) + { + fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) + % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); + return true; + } + + // actually commit the transactions + for (auto &ptx: txs.m_ptx) + { + m_wallet->commit_tx(ptx); + success_msg_writer(true) << tr("Transaction successfully submitted, transaction ") << get_transaction_hash(ptx.tx) << ENDL + << tr("You can check its status by using the `show_transfers` command."); + } + } + catch (const std::exception &e) + { + handle_transfer_exception(std::current_exception()); + } + catch (...) + { + LOG_ERROR("unknown error"); + fail_msg_writer() << tr("unknown error"); + } + + return true; +} + bool simple_wallet::set_always_confirm_transfers(const std::vector &args/* = std::vector()*/) { const auto pwd_container = get_and_verify_password(); @@ -1297,11 +1721,26 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1), tr("make_multisig [...]"), tr("Turn this wallet into a multisig wallet")); + m_cmd_binder.set_handler("export_multisig_info", + boost::bind(&simple_wallet::export_multisig, this, _1), + tr("export_multisig "), + tr("Export multisig info for other participants")); + m_cmd_binder.set_handler("import_multisig_info", + boost::bind(&simple_wallet::import_multisig, this, _1), + tr("import_multisig [...]"), + tr("Import multisig info from other participants")); + m_cmd_binder.set_handler("sign_multisig", + boost::bind(&simple_wallet::sign_multisig, this, _1), + tr("sign_multisig "), + tr("Sign a multisig transaction from a file")); + m_cmd_binder.set_handler("submit_multisig", + boost::bind(&simple_wallet::submit_multisig, this, _1), + tr("submit_multisig "), + tr("Submit a signed multisig transaction from a file")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("help []"), tr("Show the help section or the documentation about a .")); - m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::set_variable(const std::vector &args) @@ -2345,6 +2784,12 @@ bool simple_wallet::save(const std::vector &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::save_watch_only(const std::vector &args/* = std::vector()*/) { + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("wallet is multisig and cannot save a watch-only version"); + return true; + } + const auto pwd_container = tools::password_container::prompt(true, tr("Password for new watch-only wallet")); if (!pwd_container) @@ -2638,9 +3083,12 @@ bool simple_wallet::refresh(const std::vector& args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_balance_unlocked(bool detailed) { + std::string extra; + if (m_wallet->has_multisig_partial_key_images()) + extra = tr(" (Some owned outputs have partial key images - import_multisig_info needed)"); success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0}); success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance(m_current_subaddress_account)) << ", " - << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)); + << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)) << extra; std::map balance_per_subaddress = m_wallet->balance_per_subaddress(m_current_subaddress_account); std::map unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account); if (!detailed || balance_per_subaddress.empty()) @@ -2734,7 +3182,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector& args } std::string verbose_string; if (verbose) - verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : std::string('?', 64))).str(); + verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : td.m_key_image_partial ? (epee::string_tools::pod_to_hex(td.m_key_image) + "/p") : std::string(64, '?'))).str(); message_writer(td.m_spent ? console_color_magenta : console_color_green, false) << boost::format("%21s%8s%12s%8s%16u%68s%16u%s") % print_money(td.amount()) % @@ -2998,101 +3446,6 @@ bool simple_wallet::print_ring_members(const std::vector outs_for_amount : e.scanty_outs()) - { - writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; - } - } - catch (const tools::error::tx_not_constructed&) - { - fail_msg_writer() << tr("transaction was not constructed"); - } - catch (const tools::error::tx_rejected& e) - { - fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - std::string reason = e.reason(); - if (!reason.empty()) - fail_msg_writer() << tr("Reason: ") << reason; - } - catch (const tools::error::tx_sum_overflow& e) - { - fail_msg_writer() << e.what(); - } - catch (const tools::error::zero_destination&) - { - fail_msg_writer() << tr("one of destinations is zero"); - } - catch (const tools::error::tx_too_big& e) - { - fail_msg_writer() << tr("failed to find a suitable way to split transactions"); - } - catch (const tools::error::transfer_error& e) - { - LOG_ERROR("unknown transfer error: " << e.to_string()); - fail_msg_writer() << tr("unknown transfer error: ") << e.what(); - } - catch (const tools::error::wallet_internal_error& e) - { - LOG_ERROR("internal error: " << e.to_string()); - fail_msg_writer() << tr("internal error: ") << e.what(); - } - catch (const std::exception& e) - { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << tr("unexpected error: ") << e.what(); - } -} -//---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer_main(int transfer_type, const std::vector &args_) { // "transfer [index=[,,...]] [] []
[]" @@ -3419,7 +3772,19 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vectorwatch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -3515,7 +3880,19 @@ bool simple_wallet::sweep_unmixable(const std::vector &args_) } // actually commit the transactions - if (m_wallet->watch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -3734,7 +4111,19 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector &a } // actually commit the transactions - if (m_wallet->watch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -3925,7 +4314,19 @@ bool simple_wallet::sweep_single(const std::vector &args_) } // actually commit the transactions - if (m_wallet->watch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -4014,6 +4415,11 @@ bool simple_wallet::sweep_single(const std::vector &args_) LOG_ERROR("unknown transfer error: " << e.to_string()); fail_msg_writer() << tr("unknown transfer error: ") << e.what(); } + catch (const tools::error::multisig_export_needed& e) + { + LOG_ERROR("Multisig error: " << e.to_string()); + fail_msg_writer() << tr("Multisig error: ") << e.what(); + } catch (const tools::error::wallet_internal_error& e) { LOG_ERROR("internal error: " << e.to_string()); @@ -4226,6 +4632,11 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::signed_tx_set &txs) //---------------------------------------------------------------------------------------------------- bool simple_wallet::sign_transfer(const std::vector &args_) { + if(m_wallet->multisig()) + { + fail_msg_writer() << tr("This is a multisig wallet, it can only sign with sign_multisig"); + return true; + } if(m_wallet->watch_only()) { fail_msg_writer() << tr("This is a watch only wallet"); @@ -5519,6 +5930,11 @@ bool simple_wallet::sign(const std::vector &args) fail_msg_writer() << tr("wallet is watch-only and cannot sign"); return true; } + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is multisig and cannot sign"); + return true; + } if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } std::string filename = args[0]; std::string data; diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 676870a81..ebe830f69 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -190,6 +190,11 @@ namespace cryptonote bool print_fee_info(const std::vector &args); bool prepare_multisig(const std::vector& args); bool make_multisig(const std::vector& args); + bool export_multisig(const std::vector& args); + bool import_multisig(const std::vector& args); + bool accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs); + bool sign_multisig(const std::vector& args); + bool submit_multisig(const std::vector& args); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr); diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index b1f8369a3..8593bd1f9 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -812,10 +812,10 @@ struct WalletManager * @brief verifyWalletPassword - check if the given filename is the wallet * @param keys_file_name - location of keys file * @param password - password to verify - * @param watch_only - verify only view keys? + * @param no_spend_key - verify only view keys? * @return - true if password is correct */ - virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const = 0; + virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const = 0; /*! * \brief findWallets - searches for the wallet files by given path name recursively diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index ee69ec028..a6e5e551e 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -127,9 +127,9 @@ bool WalletManagerImpl::walletExists(const std::string &path) return false; } -bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const +bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const { - return tools::wallet2::verify_password(keys_file_name, password, watch_only); + return tools::wallet2::verify_password(keys_file_name, password, no_spend_key); } std::vector WalletManagerImpl::findWallets(const std::string &path) diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 978a2d411..ef5b8f91b 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -50,7 +50,7 @@ public: const std::string &spendKeyString = ""); virtual bool closeWallet(Wallet *wallet, bool store = true); bool walletExists(const std::string &path); - bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const; + bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const; std::vector findWallets(const std::string &path); std::string errorString() const; void setDaemonAddress(const std::string &address); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 4ad527423..af48e711e 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -56,6 +56,7 @@ using namespace epee; #include "mnemonics/electrum-words.h" #include "common/i18n.h" #include "common/util.h" +#include "common/apply_permutation.h" #include "rapidjson/document.h" #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" @@ -87,6 +88,7 @@ using namespace cryptonote; #define UNSIGNED_TX_PREFIX "Monero unsigned tx set\004" #define SIGNED_TX_PREFIX "Monero signed tx set\004" +#define MULTISIG_UNSIGNED_TX_PREFIX "Monero multisig unsigned tx set\001" #define RECENT_OUTPUT_RATIO (0.5) // 50% of outputs are from the recent zone #define RECENT_OUTPUT_ZONE ((time_t)(1.8 * 86400)) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al) @@ -524,6 +526,69 @@ uint8_t get_bulletproof_fork(bool testnet) return 255; // TODO } +bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) +{ + crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); + bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + + r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); + + crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); + + crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); + return true; +} + +bool wallet_generate_key_image_helper_old(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, bool multisig_export = false) +{ + if (!generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) + return false; + if (multisig_export) + { + // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig + crypto::generate_key_image(in_ephemeral.pub, ack.m_spend_secret_key, ki); + } + return true; +} + +crypto::hash8 get_short_payment_id(const tools::wallet2::pending_tx &ptx) +{ + crypto::hash8 payment_id8 = null_hash8; + std::vector tx_extra_fields; + if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) + return payment_id8; + cryptonote::tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key); + } + } + return payment_id8; +} + +tools::wallet2::tx_construction_data get_construction_data_with_decrypted_short_payment_id(const tools::wallet2::pending_tx &ptx) +{ + tools::wallet2::tx_construction_data construction_data = ptx.construction_data; + crypto::hash8 payment_id = get_short_payment_id(ptx); + if (payment_id != null_hash8) + { + // Remove encrypted + remove_field_from_tx_extra(construction_data.extra, typeid(cryptonote::tx_extra_nonce)); + // Add decrypted + std::string extra_nonce; + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); + THROW_WALLET_EXCEPTION_IF(!add_extra_nonce_to_tx_extra(construction_data.extra, extra_nonce), + tools::error::wallet_internal_error, "Failed to add decrypted payment id to tx extra"); + LOG_PRINT_L1("Decrypted payment ID: " << payment_id); + } + return construction_data; +} + + //----------------------------------------------------------------- } //namespace namespace tools @@ -846,7 +911,15 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & //---------------------------------------------------------------------------------------------------- void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map &tx_money_got_in_outs, std::vector &outs) { - bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + bool r; + if (m_multisig) + { + r = wallet_generate_key_image_helper_old(keys, tx_pub_key, i, tx_scan_info.in_ephemeral, tx_scan_info.ki); + } + else + { + r = cryptonote::generate_key_image_helper_precomp(keys, boost::get(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + } THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -1018,7 +1091,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_tx = (const cryptonote::transaction_prefix&)tx; td.m_txid = txid; td.m_key_image = tx_scan_info[o].ki; - td.m_key_image_known = !m_watch_only; + td.m_key_image_known = !m_watch_only && !m_multisig; + td.m_key_image_partial = m_multisig; td.m_amount = tx.vout[o].amount; td.m_pk_index = pk_index - 1; td.m_subaddr_index = tx_scan_info[o].received->index; @@ -1040,8 +1114,18 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_rct = false; } set_unspent(m_transfers.size()-1); - m_key_images[td.m_key_image] = m_transfers.size()-1; + if (!m_multisig) + m_key_images[td.m_key_image] = m_transfers.size()-1; m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1; + if (m_multisig) + { + THROW_WALLET_EXCEPTION_IF(!m_multisig_rescan_k && m_multisig_rescan_info, + error::wallet_internal_error, "NULL m_multisig_rescan_k"); + if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size()) + update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); + else + td.m_multisig_k = rct::skGen(); + } 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); @@ -1090,6 +1174,15 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_mask = rct::identity(); td.m_rct = false; } + if (m_multisig) + { + THROW_WALLET_EXCEPTION_IF(!m_multisig_rescan_k && m_multisig_rescan_info, + error::wallet_internal_error, "NULL m_multisig_rescan_k"); + if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size()) + update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); + else + td.m_multisig_k = rct::skGen(); + } THROW_WALLET_EXCEPTION_IF(td.get_public_key() != tx_scan_info[o].in_ephemeral.pub, error::wallet_internal_error, "Inconsistent public keys"); THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); @@ -2083,8 +2176,10 @@ void wallet2::detach_blockchain(uint64_t height) for(size_t i = i_start; i!= m_transfers.size();i++) { + if (!m_transfers[i].m_key_image_known || m_transfers[i].m_key_image_partial) + continue; auto it_ki = m_key_images.find(m_transfers[i].m_key_image); - THROW_WALLET_EXCEPTION_IF(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found"); + THROW_WALLET_EXCEPTION_IF(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found: index " + std::to_string(i) + ", ki " + epee::string_tools::pod_to_hex(m_transfers[i].m_key_image) + ", " + std::to_string(m_key_images.size()) + " key images known"); m_key_images.erase(it_ki); } @@ -2437,7 +2532,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) const * \brief verify password for specified wallet keys file. * \param keys_file_name Keys file to verify password for * \param password Password to verify - * \param watch_only If set = only verify view keys, otherwise also spend keys + * \param no_spend_key If set = only verify view keys, otherwise also spend keys * \return true if password is correct * * for verification only @@ -2653,8 +2748,8 @@ void wallet2::make_multisig(const epee::wipeable_string &password, { CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys"); CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes"); - CHECK_AND_ASSERT_THROW_MES(threshold > 0 && threshold <= spend_keys.size() + 1, "Invalid threshold"); - CHECK_AND_ASSERT_THROW_MES(threshold == spend_keys.size() || threshold == spend_keys.size() + 1, "Unsupported threshold case"); + CHECK_AND_ASSERT_THROW_MES(threshold > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold"); + CHECK_AND_ASSERT_THROW_MES(/*threshold == spend_keys.size() ||*/ threshold == spend_keys.size() + 1, "Unsupported threshold case"); clear(); @@ -2669,19 +2764,20 @@ void wallet2::make_multisig(const epee::wipeable_string &password, else { // the multisig spend public key is the sum of keys derived from all spend public keys - const rct::key spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); + const rct::key spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); // WRONG for (const auto &k: spend_keys) { - rct::addKeys(spend_pkey, spend_pkey, rct::scalarmultBase(rct::cn_fast_hash(rct::scalarmultKey(rct::pk2rct(k), spend_skey)))); + rct::addKeys(spend_pkey, spend_pkey, rct::scalarmultBase(rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(k), spend_skey)))); } } // the multisig view key is shared by all, make one all can derive MINFO("Creating view key..."); - rct::key view_skey = rct::sk2rct(get_account().get_keys().m_view_secret_key); + crypto::hash hash; + crypto::cn_fast_hash(&get_account().get_keys().m_view_secret_key, sizeof(crypto::secret_key), hash); + rct::key view_skey = rct::hash2rct(hash); for (const auto &k: view_keys) sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes); - sc_reduce32(view_skey.bytes); MINFO("Creating multisig address..."); CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2pk(spend_pkey)), @@ -2712,13 +2808,14 @@ std::string wallet2::get_multisig_info() const // It's a signed package of private view key and public spend key const crypto::secret_key &skey = get_account().get_keys().m_view_secret_key; const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key; + crypto::hash hash; std::string data; - data += std::string((const char *)&skey, sizeof(crypto::secret_key)); + crypto::cn_fast_hash(&skey, sizeof(crypto::secret_key), hash); + data += std::string((const char *)&hash, sizeof(crypto::hash)); data += std::string((const char *)&pkey, sizeof(crypto::public_key)); data.resize(data.size() + sizeof(crypto::signature)); - crypto::hash hash; crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; crypto::generate_signature(hash, pkey, get_account().get_keys().m_spend_secret_key, signature); @@ -2775,6 +2872,16 @@ bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const return true; } +bool wallet2::has_multisig_partial_key_images() const +{ + if (!m_multisig) + return false; + for (const auto &td: m_transfers) + if (td.m_key_image_partial) + return true; + return false; +} + /*! * \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there) * \param wallet_name Name of wallet file (should exist) @@ -3355,7 +3462,7 @@ void wallet2::rescan_spent() { transfer_details& td = m_transfers[i]; // a view wallet may not know about key images - if (!td.m_key_image_known) + if (!td.m_key_image_known || td.m_key_image_partial) continue; if (td.m_spent != (spent_status[i] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT)) { @@ -3695,23 +3802,6 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const return payment_id; } -crypto::hash8 wallet2::get_short_payment_id(const pending_tx &ptx) const -{ - crypto::hash8 payment_id8 = null_hash8; - std::vector tx_extra_fields; - if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) - return payment_id8; - cryptonote::tx_extra_nonce extra_nonce; - if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) - { - if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) - { - decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key); - } - } - return payment_id8; -} - //---------------------------------------------------------------------------------------------------- // take a pending tx and actually send it to the daemon void wallet2::commit_tx(pending_tx& ptx) @@ -3779,6 +3869,10 @@ void wallet2::commit_tx(pending_tx& ptx) set_spent(idx, 0); } + // tx generated, get rid of used k values + for (size_t idx: ptx.selected_transfers) + m_transfers[idx].m_multisig_k = rct::zero(); + //fee includes dust if dust policy specified it. LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL << "Commission: " << print_money(ptx.fee) << " (dust sent to dust addr: " << print_money((ptx.dust_added_to_fee ? 0 : ptx.dust)) << ")" << ENDL @@ -3801,27 +3895,10 @@ bool wallet2::save_tx(const std::vector& ptx_vector, const std::stri unsigned_tx_set txs; for (auto &tx: ptx_vector) { - tx_construction_data construction_data = tx.construction_data; // Short payment id is encrypted with tx_key. // Since sign_tx() generates new tx_keys and encrypts the payment id, we need to save the decrypted payment ID - // Get decrypted payment id from pending_tx - crypto::hash8 payment_id = get_short_payment_id(tx); - if (payment_id != null_hash8) - { - // Remove encrypted - remove_field_from_tx_extra(construction_data.extra, typeid(cryptonote::tx_extra_nonce)); - // Add decrypted - std::string extra_nonce; - set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); - if (!add_extra_nonce_to_tx_extra(construction_data.extra, extra_nonce)) - { - LOG_ERROR("Failed to add decrypted payment id to tx extra"); - return false; - } - LOG_PRINT_L1("Decrypted payment ID: " << payment_id); - } // Save tx construction_data to unsigned_tx_set - txs.txes.push_back(construction_data); + txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx)); } txs.transfers = m_transfers; @@ -3942,7 +4019,8 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f bool bulletproof = sd.use_rct && !ptx.tx.rct_signatures.p.bulletproofs.empty(); crypto::secret_key tx_key; std::vector additional_tx_keys; - 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, bulletproof); + 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, bulletproof, m_multisig ? &msout : NULL); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet); // 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, @@ -3978,6 +4056,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f ptx.selected_transfers = sd.selected_transfers; ptx.tx_key = rct::rct2sk(rct::identity()); // don't send it back to the untrusted view wallet ptx.dests = sd.dests; + ptx.msout = msout; ptx.construction_data = sd; txs.push_back(ptx); @@ -3987,7 +4066,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f signed_txes.key_images.resize(m_transfers.size()); for (size_t i = 0; i < m_transfers.size(); ++i) { - if (!m_transfers[i].m_key_image_known) + if (!m_transfers[i].m_key_image_known || m_transfers[i].m_key_image_partial) LOG_PRINT_L0("WARNING: key image not known in signing wallet at index " << i); signed_txes.key_images[i] = m_transfers[i].m_key_image; } @@ -4113,11 +4192,12 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector& ptx_vector, const std::string &filename) +{ + multisig_tx_set txs; + txs.m_ptx = ptx_vector; + crypto::hash hash; + cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); + txs.m_signers.insert(hash); + return save_multisig_tx(txs, filename); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func) +{ + std::string s; + boost::system::error_code errcode; + + if (!boost::filesystem::exists(filename, errcode)) + { + LOG_PRINT_L0("File " << filename << " does not exist: " << errcode); + return false; + } + if (!epee::file_io_utils::load_file_to_string(filename.c_str(), s)) + { + LOG_PRINT_L0("Failed to load from " << filename); + return false; + } + const size_t magiclen = strlen(MULTISIG_UNSIGNED_TX_PREFIX); + if (strncmp(s.c_str(), MULTISIG_UNSIGNED_TX_PREFIX, magiclen)) + { + LOG_PRINT_L0("Bad magic from " << filename); + return false; + } + try + { + s = decrypt_with_view_secret_key(std::string(s, magiclen)); + } + catch (const std::exception &e) + { + LOG_PRINT_L0("Failed to decrypt " << filename << ": " << e.what()); + return 0; + } + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> exported_txs; + } + catch (...) + { + LOG_PRINT_L0("Failed to parse data from " << filename); + return false; + } + + // sanity checks + for (const auto &ptx: exported_txs.m_ptx) + { + CHECK_AND_ASSERT_MES(ptx.selected_transfers.size() == ptx.tx.vin.size(), false, "Mismatched selected_transfers/vin sizes"); + for (size_t idx: ptx.selected_transfers) + CHECK_AND_ASSERT_MES(idx < m_transfers.size(), false, "Transfer index out of range"); + CHECK_AND_ASSERT_MES(ptx.construction_data.selected_transfers.size() == ptx.tx.vin.size(), false, "Mismatched cd selected_transfers/vin sizes"); + for (size_t idx: ptx.construction_data.selected_transfers) + CHECK_AND_ASSERT_MES(idx < m_transfers.size(), false, "Transfer index out of range"); + CHECK_AND_ASSERT_MES(ptx.construction_data.sources.size() == ptx.tx.vin.size(), false, "Mismatched sources/vin sizes"); + } + + LOG_PRINT_L1("Loaded multisig tx unsigned data from binary: " << exported_txs.m_ptx.size() << " transactions"); + for (auto &ptx: exported_txs.m_ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx)); + + if (accept_func && !accept_func(exported_txs)) + { + LOG_PRINT_L1("Transactions rejected by callback"); + return false; + } + + const bool is_signed = exported_txs.m_signers.size() >= m_multisig_threshold; + if (is_signed) + { + for (const auto &ptx: exported_txs.m_ptx) + { + const crypto::hash txid = get_transaction_hash(ptx.tx); + if (store_tx_info()) + { + m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); + m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys)); + } + } + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids) +{ + THROW_WALLET_EXCEPTION_IF(exported_txs.m_ptx.empty(), error::wallet_internal_error, "No tx found"); + + txids.clear(); + + // sign the transactions + for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) + { + tools::wallet2::pending_tx &ptx = exported_txs.m_ptx[n]; + 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) << + ", signed by " << exported_txs.m_signers.size() << "/" << m_multisig_threshold); + cryptonote::transaction tx; + rct::multisig_out msout = ptx.msout; + auto sources = sd.sources; + const bool bulletproof = sd.use_rct && (ptx.tx.rct_signatures.type == rct::RCTTypeFullBulletproof || ptx.tx.rct_signatures.type == rct::RCTTypeSimpleBulletproof); + 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, bulletproof, &msout); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet); + + 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 indices; + for (const auto &source: sources) + indices.push_back(source.real_output); + + rct::keyV k; + for (size_t idx: sd.selected_transfers) + k.push_back(get_multisig_k(idx)); + + THROW_WALLET_EXCEPTION_IF(!rct::signMultisig(ptx.tx.rct_signatures, indices, k, ptx.msout, rct::sk2rct(get_account().get_keys().m_spend_secret_key)), + error::wallet_internal_error, "Failed signing, transaction likely malformed"); + + const bool is_last = exported_txs.m_signers.size() + 1 >= m_multisig_threshold; + if (is_last) + { + const crypto::hash txid = get_transaction_hash(ptx.tx); + if (store_tx_info()) + { + m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); + m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys)); + } + txids.push_back(txid); + } + } + + // txes generated, get rid of used k values + 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) + m_transfers[idx].m_multisig_k = rct::zero(); + + crypto::hash hash; + cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); + exported_txs.m_signers.insert(hash); + + return save_multisig_tx(exported_txs, filename); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vector &txids, std::function accept_func) +{ + multisig_tx_set exported_txs; + if(!load_multisig_tx_from_file(filename, exported_txs)) + return false; + + crypto::hash hash; + cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(hash) != exported_txs.m_signers.end(), + error::wallet_internal_error, "Transaction already signed by this private key"); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() > m_multisig_threshold, + error::wallet_internal_error, "Transaction was signed by too many signers"); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() == m_multisig_threshold, + error::wallet_internal_error, "Transaction is already fully signed"); + + if (accept_func && !accept_func(exported_txs)) + { + LOG_PRINT_L1("Transactions rejected by callback"); + return false; + } + return sign_multisig_tx(exported_txs, filename, txids); +} +//---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) { static const uint64_t old_multipliers[3] = {1, 2, 3}; @@ -4749,6 +5040,10 @@ void wallet2::transfer_selected(const std::vector 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); + 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, false, m_multisig ? &msout : NULL); LOG_PRINT_L2("constructed tx, r="< 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, true, bulletproof); + 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, bulletproof, m_multisig ? &msout : NULL); LOG_PRINT_L2("constructed tx, r="< ins_order; + for (size_t n = 0; n < sources.size(); ++n) + { + for (size_t idx = 0; idx < sources_copy.size(); ++idx) + { + THROW_WALLET_EXCEPTION_IF((size_t)sources_copy[idx].real_output >= sources_copy[idx].outputs.size(), + error::wallet_internal_error, "Invalid real_output"); + if (sources_copy[idx].outputs[sources_copy[idx].real_output].second.dest == sources[n].outputs[sources[n].real_output].second.dest) + ins_order.push_back(idx); + } + } + THROW_WALLET_EXCEPTION_IF(ins_order.size() != sources.size(), error::wallet_internal_error, "Failed to work out sources permutation"); + LOG_PRINT_L2("gathering key images"); std::string key_images; bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool @@ -4964,13 +5281,15 @@ void wallet2::transfer_selected_rct(std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount())); for (size_t j = i + 1; j < m_transfers.size(); ++j) { const transfer_details& td2 = m_transfers[j]; - if (!td2.m_spent && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) + if (!td2.m_spent && !td.m_key_image_partial && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) { // update our picks if those outputs are less related than any we // already found. If the same, don't update, and oldest suitable outputs @@ -5665,7 +5984,7 @@ std::vector wallet2::create_transactions_2(std::vector>& x) { return x.first == index_minor; }; @@ -6041,7 +6360,7 @@ std::vector wallet2::create_transactions_all(uint64_t below for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { if (below == 0 || td.amount() < below) { @@ -6255,6 +6574,8 @@ std::vector wallet2::select_available_outputs(const std::functionm_spent) continue; + if (i->m_key_image_partial) + continue; if (!is_transfer_unlocked(*i)) continue; if (f(*i)) @@ -7235,7 +7556,7 @@ std::vector> wallet2::export_key bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, ki); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); - THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && ki != td.m_key_image, + THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && !td.m_key_image_partial && ki != td.m_key_image, error::wallet_internal_error, "key_image generated not matched with cached key image"); THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != pkey, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -7350,6 +7671,7 @@ uint64_t wallet2::import_key_images(const std::vector(td.m_tx.vout[td.m_internal_output_index].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast(i)); @@ -7645,6 +7968,138 @@ size_t wallet2::import_outputs(const std::vector wallet2::export_multisig() +{ + std::vector info; + + info.resize(m_transfers.size()); + for (size_t n = 0; n < m_transfers.size(); ++n) + { + transfer_details &td = m_transfers[n]; + crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); + cryptonote::keypair in_ephemeral; + crypto::key_image ki; + td.m_multisig_k = rct::skGen(); + const rct::multisig_kLRki kLRki = get_multisig_LRki(n, td.m_multisig_k); + const std::vector additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + // we want to export the partial key image, not the full one, so we can't use td.m_key_image + bool r = wallet_generate_key_image_helper_old(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki, true); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); + // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig + crypto::generate_key_image(in_ephemeral.pub, get_account().get_keys().m_spend_secret_key, ki); + info[n] = multisig_info({ki, kLRki.L, kLRki.R}); + } + + return info; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::update_multisig_rescan_info(const std::vector &multisig_k, const std::vector> &info, size_t n) +{ + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad index in update_multisig_info"); + CHECK_AND_ASSERT_THROW_MES(multisig_k.size() >= m_transfers.size(), "Mismatched sizes of multisig_k and info"); + + MDEBUG("update_multisig_rescan_info: updating index " << n); + transfer_details &td = m_transfers[n]; + td.m_multisig_info.clear(); + for (const auto &pi: info) + { + CHECK_AND_ASSERT_THROW_MES(n < pi.size(), "Bad pi size"); + td.m_multisig_info.push_back(pi[n]); + } + m_key_images.erase(td.m_key_image); + td.m_key_image = rct::rct2ki(get_multisig_composite_LRki(n, rct::skGen()).ki); + td.m_key_image_known = true; + td.m_key_image_partial = false; + td.m_multisig_k = multisig_k[n]; + m_key_images[td.m_key_image] = n; +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::import_multisig(std::vector> info) +{ + CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); + CHECK_AND_ASSERT_THROW_MES(info.size() + 1 == m_multisig_total, "Wrong number of multisig sources"); + + std::vector k; + k.reserve(m_transfers.size()); + for (const auto &td: m_transfers) + k.push_back(td.m_multisig_k); + + // how many outputs we're going to update + size_t n_outputs = m_transfers.size(); + for (const auto &pi: info) + if (pi.size() < n_outputs) + n_outputs = pi.size(); + + // trim data we don't have info for from all participants + for (auto &pi: info) + pi.resize(n_outputs); + + // first pass to determine where to detach the blockchain + for (size_t n = 0; n < n_outputs; ++n) + { + const transfer_details &td = m_transfers[n]; + if (!td.m_key_image_partial) + continue; + MINFO("Multisig info importing from block height " << td.m_block_height); + detach_blockchain(td.m_block_height); + break; + } + + MFATAL("import_multisig: updating from import"); + for (size_t n = 0; n < n_outputs && n < m_transfers.size(); ++n) + { + update_multisig_rescan_info(k, info, n); + } + + m_multisig_rescan_k = &k; + m_multisig_rescan_info = &info; + try + { + MFATAL("import_multisig: refreshing"); + refresh(); + } + catch (...) {} + m_multisig_rescan_info = NULL; + m_multisig_rescan_k = NULL; + + return n_outputs; +} +//---------------------------------------------------------------------------------------------------- std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const { crypto::chacha8_key key; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 9b068df3c..97abb1476 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -145,7 +145,7 @@ namespace tools }; private: - wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false),m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} + wallet2(const wallet2&) : m_multisig_rescan_info(NULL), m_multisig_rescan_k(NULL), m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false),m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} public: static const char* tr(const char* str); @@ -168,7 +168,20 @@ namespace tools static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key); - wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {} + wallet2(bool testnet = false, bool restricted = false) : m_multisig_rescan_info(NULL), m_multisig_rescan_k(NULL), m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {} + + struct multisig_info + { + crypto::key_image m_partial_key_image; + rct::key m_L; + rct::key m_R; + + BEGIN_SERIALIZE_OBJECT() + FIELD(m_partial_key_image) + FIELD(m_L) + FIELD(m_R) + END_SERIALIZE() + }; struct tx_scan_info_t { @@ -199,6 +212,9 @@ namespace tools bool m_key_image_known; size_t m_pk_index; cryptonote::subaddress_index m_subaddr_index; + bool m_key_image_partial; + rct::key m_multisig_k; + std::vector m_multisig_info; bool is_rct() const { return m_rct; } uint64_t amount() const { return m_amount; } @@ -219,6 +235,9 @@ namespace tools FIELD(m_key_image_known) FIELD(m_pk_index) FIELD(m_subaddr_index) + FIELD(m_key_image_partial) + FIELD(m_multisig_k) + FIELD(m_multisig_info) END_SERIALIZE() }; @@ -322,6 +341,7 @@ namespace tools crypto::secret_key tx_key; std::vector additional_tx_keys; std::vector dests; + rct::multisig_out msout; tx_construction_data construction_data; @@ -354,6 +374,12 @@ namespace tools std::vector key_images; }; + struct multisig_tx_set + { + std::vector m_ptx; + std::unordered_set m_signers; + }; + struct keys_file_data { crypto::chacha8_iv iv; @@ -433,6 +459,16 @@ namespace tools * Verifies and extracts keys from a packaged multisig information string */ static bool verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey); + /*! + * Export multisig info + * This will generate and remember new k values + */ + std::vector export_multisig(); + /*! + * Import a set of multisig info from multisig partners + * \return the number of inputs which were imported + */ + size_t import_multisig(std::vector> info); /*! * \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there) * \param wallet_name Name of wallet file (should exist) @@ -528,6 +564,7 @@ namespace tools bool restricted() const { return m_restricted; } bool watch_only() const { return m_watch_only; } bool multisig(uint32_t *threshold = NULL, uint32_t *total = NULL) const; + bool has_multisig_partial_key_images() const; // locked & unlocked balance of given or current subaddress account uint64_t balance(uint32_t subaddr_index_major) const; @@ -555,6 +592,8 @@ namespace tools void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector& ptx_vector); bool save_tx(const std::vector& ptx_vector, const std::string &filename); + bool save_multisig_tx(multisig_tx_set txs, const std::string &filename); + bool save_multisig_tx(const std::vector& ptx_vector, const std::string &filename); // load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector &ptx, std::function accept_func = NULL, bool export_raw = false); // sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI @@ -567,6 +606,9 @@ namespace tools std::vector create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, bool trusted_daemon); std::vector create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, bool trusted_daemon); std::vector create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector unused_transfers_indices, std::vector unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, bool trusted_daemon); + bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func = NULL); + bool sign_multisig_tx_from_file(const std::string &filename, std::vector &txids, std::function accept_func); + bool sign_multisig_tx(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids); std::vector create_unmixable_sweep_transactions(bool trusted_daemon); bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000); void get_transfers(wallet2::transfer_container& incoming_transfers) const; @@ -881,7 +923,6 @@ namespace tools void check_genesis(const crypto::hash& genesis_hash) const; //throws bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const; crypto::hash get_payment_id(const pending_tx &ptx) const; - crypto::hash8 get_short_payment_id(const pending_tx &ptx) const; void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; uint64_t get_upper_transaction_size_limit(); @@ -893,12 +934,15 @@ namespace tools void set_unspent(size_t idx); void get_outs(std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count); bool tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; - bool wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki); crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector &unused_transfers_indices, const std::vector &unused_dust_indices) const; std::vector get_only_rct(const std::vector &unused_dust_indices, const std::vector &unused_transfers_indices) const; void scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map &tx_money_got_in_outs, std::vector &outs); void trim_hashchain(); + rct::multisig_kLRki get_multisig_composite_LRki(size_t n, const rct::key &k) const; + rct::multisig_kLRki get_multisig_LRki(size_t n, const rct::key &k) const; + rct::key get_multisig_k(size_t idx) const; + void update_multisig_rescan_info(const std::vector &multisig_k, const std::vector> &info, size_t n); cryptonote::account_base m_account; boost::optional m_daemon_login; @@ -927,6 +971,8 @@ namespace tools std::unordered_map m_attributes; std::vector m_address_book; uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value + const std::vector> *m_multisig_rescan_info; + const std::vector *m_multisig_rescan_k; std::atomic m_run; @@ -976,7 +1022,9 @@ namespace tools }; } BOOST_CLASS_VERSION(tools::wallet2, 22) -BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 8) +BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 9) +BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) +BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2) BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7) @@ -985,7 +1033,7 @@ BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17) BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 2) -BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 2) +BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) namespace boost { @@ -1023,6 +1071,12 @@ namespace boost { x.m_subaddr_index = {}; } + if (ver < 9) + { + x.m_key_image_partial = false; + x.m_multisig_info.clear(); + x.m_multisig_k = rct::zero(); + } } template @@ -1096,6 +1150,29 @@ namespace boost return; } a & x.m_subaddr_index; + if (ver < 9) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_multisig_info; + a & x.m_multisig_k; + a & x.m_key_image_partial; + } + + template + inline void serialize(Archive &a, tools::wallet2::multisig_info &x, const boost::serialization::version_type ver) + { + a & x.m_partial_key_image; + a & x.m_L; + a & x.m_R; + } + + template + inline void serialize(Archive &a, tools::wallet2::multisig_tx_set &x, const boost::serialization::version_type ver) + { + a & x.m_ptx; + a & x.m_signers; } template @@ -1301,6 +1378,9 @@ namespace boost if (ver < 2) return; a & x.selected_transfers; + if (ver < 3) + return; + a & x.msout; } } } @@ -1478,6 +1558,10 @@ namespace tools src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_output = interted_it - src.outputs.begin(); src.real_output_in_tx_index = td.m_internal_output_index; + if (m_multisig) + src.multisig_kLRki = get_multisig_composite_LRki(idx, get_multisig_k(idx)); + else + src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()}); detail::print_source_entry(src); ++i; } @@ -1504,7 +1588,8 @@ namespace tools crypto::secret_key tx_key; std::vector additional_tx_keys; - 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); + rct::multisig_out msout; + 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, false, m_multisig ? &msout : NULL); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet); THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); @@ -1532,6 +1617,7 @@ namespace tools ptx.tx_key = tx_key; ptx.additional_tx_keys = additional_tx_keys; ptx.dests = dsts; + ptx.msout = msout; ptx.construction_data.sources = sources; ptx.construction_data.change_dts = change_dts; ptx.construction_data.splitted_dsts = splitted_dsts; diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 48fce40dd..234c22d85 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -50,6 +50,8 @@ namespace tools // wallet_internal_error // unexpected_txin_type // wallet_not_initialized + // multisig_export_needed + // multisig_import_needed // std::logic_error // wallet_logic_error * // file_exists @@ -186,7 +188,22 @@ namespace tools { } }; - + //---------------------------------------------------------------------------------------------------- + struct multisig_export_needed : public wallet_runtime_error + { + explicit multisig_export_needed(std::string&& loc) + : wallet_runtime_error(std::move(loc), "This signature was made with stale data: export fresh multisig data, which other participants must then use") + { + } + }; + //---------------------------------------------------------------------------------------------------- + struct multisig_import_needed : public wallet_runtime_error + { + explicit multisig_import_needed(std::string&& loc) + : wallet_runtime_error(std::move(loc), "Not enough multisig data was found to sign: import multisig data from more other participants") + { + } + }; //---------------------------------------------------------------------------------------------------- const char* const file_error_messages[] = { "file already exists", diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index e790b9954..ab911d0ef 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -324,6 +324,7 @@ namespace tools { res.balance = m_wallet->balance(req.account_index); res.unlocked_balance = m_wallet->unlocked_balance(req.account_index); + res.multisig_import_needed = m_wallet->multisig() && m_wallet->has_multisig_partial_key_images(); std::map balance_per_subaddress = m_wallet->balance_per_subaddress(req.account_index); std::map unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(req.account_index); std::vector transfers; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index e084d9e6d..cf35cc48d 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -78,11 +78,13 @@ namespace wallet_rpc { uint64_t balance; uint64_t unlocked_balance; + bool multisig_import_needed; std::vector per_subaddress; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(balance) KV_SERIALIZE(unlocked_balance) + KV_SERIALIZE(multisig_import_needed) KV_SERIALIZE(per_subaddress) END_KV_SERIALIZE_MAP() }; diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp index ef6151efb..6956179c1 100644 --- a/tests/unit_tests/ringct.cpp +++ b/tests/unit_tests/ringct.cpp @@ -111,7 +111,7 @@ TEST(ringct, MG_sigs) sk[j] = xm[ind][j]; } key message = identity(); - mgSig IIccss = MLSAG_Gen(message, P, sk, ind, R); + mgSig IIccss = MLSAG_Gen(message, P, sk, NULL, NULL, ind, R); ASSERT_TRUE(MLSAG_Ver(message, P, IIccss, R)); //#MG sig: false one @@ -132,7 +132,7 @@ TEST(ringct, MG_sigs) sk[j] = xx[ind][j]; } sk[2] = skGen();//asume we don't know one of the private keys.. - IIccss = MLSAG_Gen(message, P, sk, ind, R); + IIccss = MLSAG_Gen(message, P, sk, NULL, NULL, ind, R); ASSERT_FALSE(MLSAG_Ver(message, P, IIccss, R)); } @@ -171,7 +171,7 @@ TEST(ringct, range_proofs) destinations.push_back(Pk); //compute rct data with mixin 500 - rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3); //verify rct data ASSERT_TRUE(verRct(s)); @@ -188,7 +188,7 @@ TEST(ringct, range_proofs) //compute rct data with mixin 500 - s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3); //verify rct data ASSERT_FALSE(verRct(s)); @@ -235,7 +235,7 @@ TEST(ringct, range_proofs_with_fee) destinations.push_back(Pk); //compute rct data with mixin 500 - rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3); //verify rct data ASSERT_TRUE(verRct(s)); @@ -252,7 +252,7 @@ TEST(ringct, range_proofs_with_fee) //compute rct data with mixin 500 - s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3); //verify rct data ASSERT_FALSE(verRct(s)); @@ -310,7 +310,7 @@ TEST(ringct, simple) //compute sig with mixin 2 xmr_amount txnfee = 1; - rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, amount_keys, txnfee, 2); + rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, amount_keys, NULL, NULL, txnfee, 2); //verify ring ct signature ASSERT_TRUE(verRctSimple(s)); @@ -344,7 +344,7 @@ static rct::rctSig make_sample_rct_sig(int n_inputs, const uint64_t input_amount } } - return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3);; + return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3);; } 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) @@ -370,7 +370,7 @@ static rct::rctSig make_sample_simple_rct_sig(int n_inputs, const uint64_t input destinations.push_back(Pk); } - return genRctSimple(rct::zero(), sc, pc, destinations, inamounts, outamounts, amount_keys, fee, 3);; + return genRctSimple(rct::zero(), sc, pc, destinations, inamounts, outamounts, amount_keys, NULL, NULL, fee, 3);; } static bool range_proof_test(bool expected_valid, diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 0750ab7d1..9e76efadf 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -591,7 +591,7 @@ TEST(Serialization, serializes_ringct_types) rct::skpkGen(Sk, Pk); destinations.push_back(Pk); //compute rct data with mixin 500 - s0 = rct::genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + s0 = rct::genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3); mg0 = s0.p.MGs[0]; ASSERT_TRUE(serialization::dump_binary(mg0, blob)); From b84b3565f3e313a124ab5131effc6f1929893993 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Thu, 29 Jun 2017 11:48:34 +0100 Subject: [PATCH 03/19] tests: add multisig unit tests --- tests/unit_tests/CMakeLists.txt | 1 + tests/unit_tests/multisig.cpp | 178 ++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 tests/unit_tests/multisig.cpp diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index e37d34063..cfacd5688 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -53,6 +53,7 @@ set(unit_tests_sources memwipe.cpp mnemonics.cpp mul_div.cpp + multisig.cpp parse_amount.cpp serialization.cpp sha256.cpp diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp new file mode 100644 index 000000000..2fb201d82 --- /dev/null +++ b/tests/unit_tests/multisig.cpp @@ -0,0 +1,178 @@ +// Copyright (c) 2017, 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 "gtest/gtest.h" + +#include + +#include "wallet/wallet2.h" + +static const struct +{ + const char *address; + const char *spendkey; +} test_addresses[] = +{ + { + "9uvjbU54ZJb8j7Dcq1h3F1DnBRkxXdYUX4pbJ7mE3ghM8uF3fKzqRKRNAKYZXcNLqMg7MxjVVD2wKC2PALUwEveGSC3YSWD", + "2dd6e34a234c3e8b5d29a371789e4601e96dee4ea6f7ef79224d1a2d91164c01" + }, + { + "9ywDBAyDbb6QKFiZxDJ4hHZqZEQXXCR5EaYNcndUpqPDeE7rEgs6neQdZnhcDrWbURYK8xUjhuG2mVjJdmknrZbcG7NnbaB", + "fac47aecc948ce9d3531aa042abb18235b1df632087c55a361b632ffdd6ede0c" + }, + { + "9t6Hn946u3eah5cuncH1hB5hGzsTUoevtf4SY7MHN5NgJZh2SFWsyVt3vUhuHyRKyrCQvr71Lfc1AevG3BXE11PQFoXDtD8", + "bbd3175ef9fd9f5eefdc43035f882f74ad14c4cf1799d8b6f9001bc197175d02" + } +}; + +static void make_wallet(unsigned int idx, tools::wallet2 &wallet) +{ + ASSERT_TRUE(idx < sizeof(test_addresses) / sizeof(test_addresses[0])); + + crypto::secret_key spendkey; + epee::string_tools::hex_to_pod(test_addresses[idx].spendkey, spendkey); + + std::string basename = "/tmp/multisig-unit-test-" + std::to_string(idx); + try + { + boost::filesystem::remove(basename + ".keys"); + boost::filesystem::remove(basename + ".address.txt"); + boost::filesystem::remove(basename); + + wallet.init(""); + wallet.generate(basename, "", spendkey, true, false); + + boost::filesystem::remove(basename + ".keys"); + boost::filesystem::remove(basename + ".address.txt"); + boost::filesystem::remove(basename); + + ASSERT_TRUE(test_addresses[idx].address == wallet.get_account().get_public_address_str(true)); + } + catch (const std::exception &e) + { + MFATAL("Error creating test wallet: " << e.what()); + ASSERT_TRUE(0); + } +} + +static void make_M_2_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, unsigned int M) +{ + ASSERT_TRUE(M <= 2); + + make_wallet(0, wallet0); + make_wallet(1, wallet1); + + std::vector sk0(1), sk1(1); + std::vector pk0(1), pk1(1); + + std::string mi0 = wallet0.get_multisig_info(); + std::string mi1 = wallet1.get_multisig_info(); + + ASSERT_TRUE(wallet0.verify_multisig_info(mi1, sk0[0], pk0[0])); + ASSERT_TRUE(wallet1.verify_multisig_info(mi0, sk1[0], pk1[0])); + + ASSERT_FALSE(wallet0.multisig() || wallet1.multisig()); + wallet0.make_multisig("", sk0, pk0, M); + wallet1.make_multisig("", sk1, pk1, M); + + ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet1.get_account().get_public_address_str(true)); + + uint32_t threshold, total; + ASSERT_TRUE(wallet0.multisig(&threshold, &total)); + ASSERT_TRUE(threshold == M); + ASSERT_TRUE(total == 2); + ASSERT_TRUE(wallet1.multisig(&threshold, &total)); + ASSERT_TRUE(threshold == M); + ASSERT_TRUE(total == 2); +} + +static void make_M_3_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, tools::wallet2 &wallet2, unsigned int M) +{ + ASSERT_TRUE(M <= 3); + + make_wallet(0, wallet0); + make_wallet(1, wallet1); + make_wallet(2, wallet2); + + std::vector sk0(2), sk1(2), sk2(2); + std::vector pk0(2), pk1(2), pk2(2); + + std::string mi0 = wallet0.get_multisig_info(); + std::string mi1 = wallet1.get_multisig_info(); + std::string mi2 = wallet2.get_multisig_info(); + + ASSERT_TRUE(wallet0.verify_multisig_info(mi1, sk0[0], pk0[0])); + ASSERT_TRUE(wallet0.verify_multisig_info(mi2, sk0[1], pk0[1])); + ASSERT_TRUE(wallet1.verify_multisig_info(mi0, sk1[0], pk1[0])); + ASSERT_TRUE(wallet1.verify_multisig_info(mi2, sk1[1], pk1[1])); + ASSERT_TRUE(wallet2.verify_multisig_info(mi0, sk2[0], pk2[0])); + ASSERT_TRUE(wallet2.verify_multisig_info(mi1, sk2[1], pk2[1])); + + // not implemented yet + if (M < 3) + return; + + ASSERT_FALSE(wallet0.multisig() || wallet1.multisig() || wallet2.multisig()); + wallet0.make_multisig("", sk0, pk0, M); + wallet1.make_multisig("", sk1, pk1, M); + wallet2.make_multisig("", sk2, pk2, M); + + ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet1.get_account().get_public_address_str(true)); + ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet2.get_account().get_public_address_str(true)); + + uint32_t threshold, total; + ASSERT_TRUE(wallet0.multisig(&threshold, &total)); + ASSERT_TRUE(threshold == M); + ASSERT_TRUE(total == 3); + ASSERT_TRUE(wallet1.multisig(&threshold, &total)); + ASSERT_TRUE(threshold == M); + ASSERT_TRUE(total == 3); + ASSERT_TRUE(wallet2.multisig(&threshold, &total)); + ASSERT_TRUE(threshold == M); + ASSERT_TRUE(total == 3); +} + +TEST(multisig, make_2_2) +{ + tools::wallet2 wallet0, wallet1; + make_M_2_wallet(wallet0, wallet1, 2); +} + +TEST(multisig, make_3_3) +{ + tools::wallet2 wallet0, wallet1, wallet2; + make_M_3_wallet(wallet0, wallet1, wallet2, 3); +} + +TEST(multisig, make_2_3) +{ + tools::wallet2 wallet0, wallet1, wallet2; + make_M_3_wallet(wallet0, wallet1, wallet2, 2); +} From 95a21a793d22d95868d9e12bda64ac8bed8ef93c Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 30 Jun 2017 12:12:28 +0100 Subject: [PATCH 04/19] wallet2: allow empty wallet filename to avoid saving data Useful to speed tests up and avoid unnecessary leftover files --- src/wallet/wallet2.cpp | 86 +++++++++++++++++++++++------------ tests/unit_tests/multisig.cpp | 12 +---- 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index af48e711e..a497f3242 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2594,9 +2594,12 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip clear(); prepare_file_names(wallet_); - boost::system::error_code ignored_ec; - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } crypto::secret_key retval = m_account.generate(recovery_param, recover, two_random); @@ -2618,18 +2621,23 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_refresh_from_block_height = height >= blocks_per_month ? height - blocks_per_month : 0; } - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) MERROR("String with address text not saved"); + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); add_subaddress_account(tr("Primary account")); - store(); + if (!wallet_.empty()) + store(); + return retval; } @@ -2678,9 +2686,12 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& clear(); prepare_file_names(wallet_); - boost::system::error_code ignored_ec; - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } m_account.create_from_viewkey(account_public_address, viewkey); m_account_public_address = account_public_address; @@ -2689,18 +2700,22 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig_threshold = 0; m_multisig_total = 0; - bool r = store_keys(m_keys_file, password, true); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, true); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) MERROR("String with address text not saved"); + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); add_subaddress_account(tr("Primary account")); - store(); + if (!wallet_.empty()) + store(); } /*! @@ -2717,9 +2732,12 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& clear(); prepare_file_names(wallet_); - boost::system::error_code ignored_ec; - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } m_account.create_from_keys(account_public_address, spendkey, viewkey); m_account_public_address = account_public_address; @@ -2728,17 +2746,21 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig_threshold = 0; m_multisig_total = 0; - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) MERROR("String with address text not saved"); + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); - store(); + if (!wallet_.empty()) + store(); } void wallet2::make_multisig(const epee::wipeable_string &password, @@ -2789,18 +2811,22 @@ void wallet2::make_multisig(const epee::wipeable_string &password, m_multisig_threshold = threshold; m_multisig_total = spend_keys.size() + 1; - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + if (!m_wallet_file.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) MERROR("String with address text not saved"); + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); add_subaddress_account(tr("Primary account")); - store(); + if (!m_wallet_file.empty()) + store(); } std::string wallet2::get_multisig_info() const @@ -2889,6 +2915,8 @@ bool wallet2::has_multisig_partial_key_images() const */ void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_string& password) { + if (wallet_name.empty()) + return; prepare_file_names(wallet_name); boost::system::error_code ignored_ec; THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file); diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp index 2fb201d82..8a94012e5 100644 --- a/tests/unit_tests/multisig.cpp +++ b/tests/unit_tests/multisig.cpp @@ -59,20 +59,10 @@ static void make_wallet(unsigned int idx, tools::wallet2 &wallet) crypto::secret_key spendkey; epee::string_tools::hex_to_pod(test_addresses[idx].spendkey, spendkey); - std::string basename = "/tmp/multisig-unit-test-" + std::to_string(idx); try { - boost::filesystem::remove(basename + ".keys"); - boost::filesystem::remove(basename + ".address.txt"); - boost::filesystem::remove(basename); - wallet.init(""); - wallet.generate(basename, "", spendkey, true, false); - - boost::filesystem::remove(basename + ".keys"); - boost::filesystem::remove(basename + ".address.txt"); - boost::filesystem::remove(basename); - + wallet.generate("", "", spendkey, true, false); ASSERT_TRUE(test_addresses[idx].address == wallet.get_account().get_public_address_str(true)); } catch (const std::exception &e) From fff871a455a7829d2ccbceb04306365b30bd641e Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 30 Jun 2017 17:36:31 +0100 Subject: [PATCH 05/19] gen_multisig: generates multisig wallets if participants trust each other --- src/CMakeLists.txt | 1 + src/gen_multisig/CMakeLists.txt | 54 ++++++++ src/gen_multisig/gen_multisig.cpp | 213 ++++++++++++++++++++++++++++++ src/simplewallet/simplewallet.cpp | 1 + src/wallet/wallet2.cpp | 2 - src/wallet/wallet_args.cpp | 4 + src/wallet/wallet_args.h | 1 + src/wallet/wallet_rpc_server.cpp | 1 + tests/unit_tests/multisig.cpp | 16 +-- 9 files changed, 283 insertions(+), 10 deletions(-) create mode 100644 src/gen_multisig/CMakeLists.txt create mode 100644 src/gen_multisig/gen_multisig.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0281b1df6..d8b0bf211 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -129,6 +129,7 @@ endif() add_subdirectory(cryptonote_protocol) if(NOT IOS) add_subdirectory(simplewallet) + add_subdirectory(gen_multisig) add_subdirectory(daemonizer) add_subdirectory(daemon) add_subdirectory(blockchain_utilities) diff --git a/src/gen_multisig/CMakeLists.txt b/src/gen_multisig/CMakeLists.txt new file mode 100644 index 000000000..ff3c46862 --- /dev/null +++ b/src/gen_multisig/CMakeLists.txt @@ -0,0 +1,54 @@ +# Copyright (c) 2017, 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. + +set(gen_multisig_sources + gen_multisig.cpp) + +monero_add_executable(gen_multisig + ${gen_multisig_sources}) +target_link_libraries(gen_multisig + PRIVATE + wallet + cryptonote_core + cncrypto + common + epee + ${EPEE_READLINE} + ${Boost_CHRONO_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${Readline_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +add_dependencies(gen_multisig + version) +set_property(TARGET gen_multisig + PROPERTY + OUTPUT_NAME "monero-gen-trusted-multisig") +install(TARGETS gen_multisig DESTINATION bin) diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp new file mode 100644 index 000000000..358c1e4c0 --- /dev/null +++ b/src/gen_multisig/gen_multisig.cpp @@ -0,0 +1,213 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +/*! + * \file gen_multisig.cpp + * + * \brief Generates a set of multisig wallets + */ +#include +#include +#include +#include +#include +#include "include_base_utils.h" +#include "crypto/crypto.h" // for crypto::secret_key definition +#include "common/i18n.h" +#include "common/command_line.h" +#include "common/util.h" +#include "common/scoped_message_writer.h" +#include "wallet/wallet_args.h" +#include "wallet/wallet2.h" + +using namespace std; +using namespace epee; +using namespace cryptonote; +using boost::lexical_cast; +namespace po = boost::program_options; + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.gen_multisig" + +namespace genms +{ + const char* tr(const char* str) + { + return i18n_translate(str, "tools::gen_multisig"); + } + +} + +namespace +{ + const command_line::arg_descriptor arg_filename_base = {"filename-base", genms::tr("Base filename (-1, -2, etc suffixes will be appended as needed)"), ""}; + const command_line::arg_descriptor arg_scheme = {"scheme", genms::tr("Give threshold and participants at once as M/N"), ""}; + const command_line::arg_descriptor arg_participants = {"participants", genms::tr("How many participants wil share parts of the multisig wallet"), 0}; + const command_line::arg_descriptor arg_threshold = {"threshold", genms::tr("How many signers are required to sign a valid transaction"), 0}; + const command_line::arg_descriptor arg_testnet = {"testnet", genms::tr("Create testnet multisig wallets"), false}; + + const command_line::arg_descriptor< std::vector > arg_command = {"command", ""}; +} + +static bool generate_multisig(uint32_t threshold, uint32_t total, const std::string &basename, bool testnet) +{ + tools::msg_writer() << (boost::format(genms::tr("Generating %u %u/%u multisig wallets")) % total % threshold % total).str(); + + const auto pwd_container = tools::password_container::prompt(true, "Enter password for new multisig wallets"); + + try + { + // create M wallets first + std::vector> wallets(total); + for (size_t n = 0; n < total; ++n) + { + std::string name = basename + "-" + std::to_string(n + 1); + wallets[n].reset(new tools::wallet2(testnet)); + wallets[n]->init(""); + wallets[n]->generate(name, pwd_container->password(), rct::rct2sk(rct::skGen()), false, false); + } + + // gather the keys + std::vector sk(total); + std::vector pk(total); + for (size_t n = 0; n < total; ++n) + { + tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n]); + } + + // make the wallets multisig + std::stringstream ss; + for (size_t n = 0; n < total; ++n) + { + std::string name = basename + "-" + std::to_string(n + 1); + std::vector skn; + std::vector pkn; + for (size_t k = 0; k < total; ++k) + { + if (k != n) + { + skn.push_back(sk[k]); + pkn.push_back(pk[k]); + } + } + wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold); + ss << " " << name << std::endl; + } + + std::string address = wallets[0]->get_account().get_public_address_str(wallets[0]->testnet()); + tools::success_msg_writer() << genms::tr("Generated multisig wallets for address ") << address << std::endl << ss.str(); + } + catch (const std::exception &e) + { + tools::fail_msg_writer() << genms::tr("Error creating multisig wallets: ") << e.what(); + return false; + } + + return true; +} + +int main(int argc, char* argv[]) +{ + po::options_description desc_params(wallet_args::tr("Wallet options")); + command_line::add_arg(desc_params, arg_filename_base); + command_line::add_arg(desc_params, arg_scheme); + command_line::add_arg(desc_params, arg_threshold); + command_line::add_arg(desc_params, arg_participants); + command_line::add_arg(desc_params, arg_testnet); + + const auto vm = wallet_args::main( + argc, argv, + "monero-gen-multisig [--testnet] [--filename-base=] [--scheme=M/N] [--threshold=M] [--participants=N]", + genms::tr("This program generates a set of multisig wallets - use this simpler scheme only if all the participants trust each other"), + desc_params, + boost::program_options::positional_options_description(), + [](const std::string &s, bool emphasis){ tools::scoped_message_writer(emphasis ? epee::console_color_white : epee::console_color_default, true) << s; }, + "monero-gen-multisig.log" + ); + if (!vm) + return 1; + + bool testnet; + uint32_t threshold = 0, total = 0; + std::string basename; + + testnet = command_line::get_arg(*vm, arg_testnet); + if (command_line::has_arg(*vm, arg_scheme)) + { + if (sscanf(command_line::get_arg(*vm, arg_scheme).c_str(), "%u/%u", &threshold, &total) != 2) + { + tools::fail_msg_writer() << genms::tr("Error: expected N/M, but got: ") << command_line::get_arg(*vm, arg_scheme); + return 1; + } + } + if (!(*vm)["threshold"].defaulted()) + { + if (threshold) + { + tools::fail_msg_writer() << genms::tr("Error: either --scheme or both of --threshold and --participants may be given"); + return 1; + } + threshold = command_line::get_arg(*vm, arg_threshold); + } + if (!(*vm)["participants"].defaulted()) + { + if (total) + { + tools::fail_msg_writer() << genms::tr("Error: either --scheme or both of --threshold and --participants may be given"); + return 1; + } + total = command_line::get_arg(*vm, arg_participants); + } + if (threshold <= 1 || threshold > total) + { + tools::fail_msg_writer() << (boost::format(genms::tr("Error: expected N > 1 and N <= M, but got N==%u and M==%d")) % threshold % total).str(); + return 1; + } + if (!(*vm)["filename-base"].defaulted() && !command_line::get_arg(*vm, arg_filename_base).empty()) + { + basename = command_line::get_arg(*vm, arg_filename_base); + } + else + { + tools::fail_msg_writer() << genms::tr("Error: --filename-base is required"); + return 1; + } + + if (threshold != total-1 && threshold != total) + { + tools::fail_msg_writer() << genms::tr("Error: unsupported scheme: only N/N and N-1/N are supported"); + return 1; + } + if (!generate_multisig(threshold, total, basename, testnet)) + return 1; + + return 0; + //CATCH_ENTRY_L0("main", 1); +} diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 539301f83..09dbb1373 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -6396,6 +6396,7 @@ int main(int argc, char* argv[]) const auto vm = wallet_args::main( argc, argv, "monero-wallet-cli [--wallet-file=|--generate-new-wallet=] []", + sw::tr("This is the command line monero wallet. It needs to connect to a monero\ndaemon to work correctly."), desc_params, positional_options, [](const std::string &s, bool emphasis){ tools::scoped_message_writer(emphasis ? epee::console_color_white : epee::console_color_default, true) << s; }, diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a497f3242..d57d538ea 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8108,7 +8108,6 @@ size_t wallet2::import_multisig(std::vector main( int argc, char** argv, const char* const usage, + const char* const notice, boost::program_options::options_description desc_params, const boost::program_options::positional_options_description& positional_options, const std::function &print, @@ -179,6 +180,9 @@ namespace wallet_args mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str()); } + if (notice) + Print(print) << notice << ENDL; + if (!command_line::is_arg_defaulted(vm, arg_max_concurrency)) tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency)); diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index 8974098ad..212958988 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -48,6 +48,7 @@ namespace wallet_args boost::optional main( int argc, char** argv, const char* const usage, + const char* const notice, boost::program_options::options_description desc_params, const boost::program_options::positional_options_description& positional_options, const std::function &print, diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index ab911d0ef..aa521c9aa 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2361,6 +2361,7 @@ int main(int argc, char** argv) { const auto vm = wallet_args::main( argc, argv, "monero-wallet-rpc [--wallet-file=|--generate-from-json=|--wallet-dir=] [--rpc-bind-port=]", + tools::wallet_rpc_server::tr("This is the RPC monero wallet. It needs to connect to a monero\ndaemon to work correctly."), desc_params, po::positional_options_description(), [](const std::string &s, bool emphasis){ epee::set_console_color(emphasis ? epee::console_color_white : epee::console_color_default, true); std::cout << s << std::endl; if (emphasis) epee::reset_console_color(); }, diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp index 8a94012e5..4a105c51c 100644 --- a/tests/unit_tests/multisig.cpp +++ b/tests/unit_tests/multisig.cpp @@ -85,8 +85,8 @@ static void make_M_2_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, un std::string mi0 = wallet0.get_multisig_info(); std::string mi1 = wallet1.get_multisig_info(); - ASSERT_TRUE(wallet0.verify_multisig_info(mi1, sk0[0], pk0[0])); - ASSERT_TRUE(wallet1.verify_multisig_info(mi0, sk1[0], pk1[0])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk0[0], pk0[0])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk1[0], pk1[0])); ASSERT_FALSE(wallet0.multisig() || wallet1.multisig()); wallet0.make_multisig("", sk0, pk0, M); @@ -118,12 +118,12 @@ static void make_M_3_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, to std::string mi1 = wallet1.get_multisig_info(); std::string mi2 = wallet2.get_multisig_info(); - ASSERT_TRUE(wallet0.verify_multisig_info(mi1, sk0[0], pk0[0])); - ASSERT_TRUE(wallet0.verify_multisig_info(mi2, sk0[1], pk0[1])); - ASSERT_TRUE(wallet1.verify_multisig_info(mi0, sk1[0], pk1[0])); - ASSERT_TRUE(wallet1.verify_multisig_info(mi2, sk1[1], pk1[1])); - ASSERT_TRUE(wallet2.verify_multisig_info(mi0, sk2[0], pk2[0])); - ASSERT_TRUE(wallet2.verify_multisig_info(mi1, sk2[1], pk2[1])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk0[0], pk0[0])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi2, sk0[1], pk0[1])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk1[0], pk1[0])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi2, sk1[1], pk1[1])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk2[0], pk2[0])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk2[1], pk2[1])); // not implemented yet if (M < 3) From cd64c7990ce1b6a9d88ff24e749fb906154c95dc Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Mon, 27 Nov 2017 20:09:04 +0000 Subject: [PATCH 06/19] multisig address generation RPC --- src/wallet/wallet2.cpp | 26 ++- src/wallet/wallet2.h | 4 +- src/wallet/wallet_rpc_server.cpp | 234 +++++++++++++++++++ src/wallet/wallet_rpc_server.h | 10 + src/wallet/wallet_rpc_server_commands_defs.h | 126 ++++++++++ src/wallet/wallet_rpc_server_error_codes.h | 6 + 6 files changed, 400 insertions(+), 6 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d57d538ea..ee5bd0441 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4234,7 +4234,7 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector& ptx_vector, const std::string &filename) +bool wallet2::save_multisig_tx(const multisig_tx_set &txs, const std::string &filename) +{ + std::string ciphertext = save_multisig_tx(txs); + if (ciphertext.empty()) + return false; + return epee::file_io_utils::save_string_to_file(filename, ciphertext); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::save_multisig_tx(const std::vector& ptx_vector) { multisig_tx_set txs; txs.m_ptx = ptx_vector; crypto::hash hash; cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); txs.m_signers.insert(hash); - return save_multisig_tx(txs, filename); + return save_multisig_tx(txs); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::save_multisig_tx(const std::vector& ptx_vector, const std::string &filename) +{ + std::string ciphertext = save_multisig_tx(ptx_vector); + if (ciphertext.empty()) + return false; + return epee::file_io_utils::save_string_to_file(filename, ciphertext); } //---------------------------------------------------------------------------------------------------- bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 97abb1476..8567edc9a 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -592,7 +592,9 @@ namespace tools void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector& ptx_vector); bool save_tx(const std::vector& ptx_vector, const std::string &filename); - bool save_multisig_tx(multisig_tx_set txs, const std::string &filename); + std::string save_multisig_tx(multisig_tx_set txs); + bool save_multisig_tx(const multisig_tx_set &txs, const std::string &filename); + std::string save_multisig_tx(const std::vector& ptx_vector); bool save_multisig_tx(const std::vector& ptx_vector, const std::string &filename); // load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector &ptx, std::function accept_func = NULL, bool export_raw = false); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index aa521c9aa..e2bb0a5c6 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2339,6 +2339,240 @@ namespace tools } } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + res.multisig = m_wallet->multisig(&res.threshold, &res.total); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->multisig()) + { + er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; + er.message = "This wallet is already multisig"; + return false; + } + if (m_wallet->watch_only()) + { + er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; + er.message = "wallet is watch-only and cannot be made multisig"; + return false; + } + + res.multisig_info = m_wallet->get_multisig_info(); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->multisig()) + { + er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; + er.message = "This wallet is already multisig"; + return false; + } + if (m_wallet->watch_only()) + { + er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; + er.message = "wallet is watch-only and cannot be made multisig"; + return false; + } + + // parse all multisig info + std::vector secret_keys(req.multisig_info.size()); + std::vector public_keys(req.multisig_info.size()); + for (size_t i = 0; i < req.multisig_info.size(); ++i) + { + if (!m_wallet->verify_multisig_info(req.multisig_info[i], secret_keys[i], public_keys[i])) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO; + er.message = "Bad multisig info: " + req.multisig_info[i]; + return false; + } + } + + // remove duplicates + for (size_t i = 1; i < secret_keys.size(); ++i) + { + for (size_t j = i + 1; j < secret_keys.size(); ++j) + { + if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j])) + { + secret_keys[j] = secret_keys.back(); + public_keys[j] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --j; + } + } + } + + // people may include their own, weed it out + crypto::hash hash; + crypto::cn_fast_hash(&m_wallet->get_account().get_keys().m_view_secret_key, sizeof(crypto::secret_key), hash); + for (size_t i = 0; i < secret_keys.size(); ++i) + { + if (rct::sk2rct(secret_keys[i]) == rct::hash2rct(hash)) + { + secret_keys[i] = secret_keys.back(); + public_keys[i] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --i; + } + else if (public_keys[i] == m_wallet->get_account().get_keys().m_account_address.m_spend_public_key) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO; + er.message = "Found local spend public key, but not local view secret key - something very weird"; + return false; + } + } + + try + { + m_wallet->make_multisig(req.password, secret_keys, public_keys, req.threshold); + res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (!m_wallet->multisig()) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + + std::vector info; + try + { + info = m_wallet->export_multisig(); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + + res.info.resize(info.size()); + for (size_t n = 0; n < info.size(); ++n) + { + res.info[n].partial_key_image = epee::string_tools::pod_to_hex(info[n].m_partial_key_image); + res.info[n].L = epee::string_tools::pod_to_hex(info[n].m_L); + res.info[n].R = epee::string_tools::pod_to_hex(info[n].m_R); + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + uint32_t threshold, total; + if (!m_wallet->multisig(&threshold, &total)) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + + if (req.info.size() < threshold - 1) + { + er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; + er.message = "Needs multisig export info from more participants"; + return false; + } + + std::vector> info; + info.resize(req.info.size()); + for (size_t n = 0; n < info.size(); ++n) + { + info[n].resize(req.info[n].info.size()); + for (size_t i = 0; i < info[n].size(); ++i) + { + if (!epee::string_tools::hex_to_pod(req.info[n].info[i].partial_key_image, info[n][i].m_partial_key_image)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "Failed to parse partial key image from multisig info"; + return false; + } + if (!epee::string_tools::hex_to_pod(req.info[n].info[i].L, info[n][i].m_L) || !epee::string_tools::hex_to_pod(req.info[n].info[i].R, info[n][i].m_R)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_LR; + er.message = "Failed to parse L/R info from hex"; + return false; + } + } + } + + try + { + res.n_outputs = m_wallet->import_multisig(info); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Error calling import_multisig"; + return false; + } + + if (m_trusted_daemon) + { + try + { + m_wallet->rescan_spent(); + } + catch (const std::exception &e) + { + er.message = std::string("Success, but failed to update spent status after import multisig info: ") + e.what(); + } + } + else + { + er.message = "Success, but cannot update spent status after import multisig info as dameon is untrusted"; + } + + return true; + } } int main(int argc, char** argv) { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 9455c4769..10b2e9dbd 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -117,6 +117,11 @@ namespace tools MAP_JON_RPC_WE("get_languages", on_get_languages, wallet_rpc::COMMAND_RPC_GET_LANGUAGES) MAP_JON_RPC_WE("create_wallet", on_create_wallet, wallet_rpc::COMMAND_RPC_CREATE_WALLET) MAP_JON_RPC_WE("open_wallet", on_open_wallet, wallet_rpc::COMMAND_RPC_OPEN_WALLET) + MAP_JON_RPC_WE("is_multisig", on_is_multisig, wallet_rpc::COMMAND_RPC_IS_MULTISIG) + MAP_JON_RPC_WE("prepare_multisig", on_prepare_multisig, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG) + MAP_JON_RPC_WE("make_multisig", on_make_multisig, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG) + MAP_JON_RPC_WE("export_multisig_info", on_export_multisig, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG) + MAP_JON_RPC_WE("import_multisig_info", on_import_multisig, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG) END_JSON_RPC_MAP() END_URI_MAP2() @@ -171,6 +176,11 @@ namespace tools bool on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er); bool on_create_wallet(const wallet_rpc::COMMAND_RPC_CREATE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CREATE_WALLET::response& res, epee::json_rpc::error& er); bool on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er); + bool on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er); //json rpc v2 bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index cf35cc48d..806704d6e 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1487,5 +1487,131 @@ namespace wallet_rpc END_KV_SERIALIZE_MAP() }; }; + + struct COMMAND_RPC_IS_MULTISIG + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool multisig; + uint32_t threshold; + uint32_t total; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(multisig) + KV_SERIALIZE(threshold) + KV_SERIALIZE(total) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_PREPARE_MULTISIG + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string multisig_info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(multisig_info) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_MAKE_MULTISIG + { + struct request + { + std::vector multisig_info; + uint32_t threshold; + std::string password; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(multisig_info) + KV_SERIALIZE(threshold) + KV_SERIALIZE(password) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string address; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + END_KV_SERIALIZE_MAP() + }; + }; + + struct multisig_info_entry + { + std::string partial_key_image; + std::string L; + std::string R; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(partial_key_image) + KV_SERIALIZE(L) + KV_SERIALIZE(R) + END_KV_SERIALIZE_MAP() + }; + + struct COMMAND_RPC_EXPORT_MULTISIG + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(info) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_IMPORT_MULTISIG + { + struct participant_entry + { + std::vector info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(info) + END_KV_SERIALIZE_MAP() + }; + + struct request + { + std::vector info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(info) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t n_outputs; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(n_outputs) + END_KV_SERIALIZE_MAP() + }; + }; } } diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index c3f3e20d1..139fcf8ed 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -58,3 +58,9 @@ #define WALLET_RPC_ERROR_CODE_WRONG_KEY -25 #define WALLET_RPC_ERROR_CODE_BAD_HEX -26 #define WALLET_RPC_ERROR_CODE_BAD_TX_METADATA -27 +#define WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG -28 +#define WALLET_RPC_ERROR_CODE_WATCH_ONLY -29 +#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO -30 +#define WALLET_RPC_ERROR_CODE_NOT_MULTISIG -31 +#define WALLET_RPC_ERROR_CODE_WRONG_LR -32 +#define WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED -33 From f4eda44ce35c6e1ab77566a462470deaae5376ec Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 13 Aug 2017 15:29:31 +0100 Subject: [PATCH 07/19] N-1/N multisig --- src/cryptonote_basic/account.cpp | 10 +- src/cryptonote_basic/account.h | 6 +- src/gen_multisig/gen_multisig.cpp | 32 +- src/ringct/rctTypes.h | 5 + src/simplewallet/simplewallet.cpp | 81 ++- src/simplewallet/simplewallet.h | 1 + src/wallet/wallet2.cpp | 573 ++++++++++++++++--- src/wallet/wallet2.h | 110 +++- src/wallet/wallet_rpc_server.cpp | 104 +++- src/wallet/wallet_rpc_server.h | 1 + src/wallet/wallet_rpc_server_commands_defs.h | 43 +- tests/unit_tests/multisig.cpp | 26 +- 12 files changed, 849 insertions(+), 143 deletions(-) diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index 9cc44f0f0..ddc1fc7fc 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -64,6 +64,7 @@ DISABLE_VS_WARNINGS(4244 4345) void account_base::forget_spend_key() { m_keys.m_spend_secret_key = crypto::secret_key(); + m_keys.m_multisig_keys.clear(); } //----------------------------------------------------------------- crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random) @@ -123,13 +124,20 @@ DISABLE_VS_WARNINGS(4244 4345) create_from_keys(address, fake, viewkey); } //----------------------------------------------------------------- - bool account_base::make_multisig(const crypto::secret_key &view_secret_key, const crypto::public_key &spend_public_key) + bool account_base::make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector &multisig_keys) { m_keys.m_account_address.m_spend_public_key = spend_public_key; m_keys.m_view_secret_key = view_secret_key; + m_keys.m_spend_secret_key = spend_secret_key; + m_keys.m_multisig_keys = multisig_keys; return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key); } //----------------------------------------------------------------- + void account_base::finalize_multisig(const crypto::public_key &spend_public_key) + { + m_keys.m_account_address.m_spend_public_key = spend_public_key; + } + //----------------------------------------------------------------- const account_keys& account_base::get_keys() const { return m_keys; diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h index ab837df3f..50af36a9d 100644 --- a/src/cryptonote_basic/account.h +++ b/src/cryptonote_basic/account.h @@ -42,11 +42,13 @@ namespace cryptonote account_public_address m_account_address; crypto::secret_key m_spend_secret_key; crypto::secret_key m_view_secret_key; + std::vector m_multisig_keys; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(m_account_address) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_secret_key) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_secret_key) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_multisig_keys) END_KV_SERIALIZE_MAP() }; @@ -60,7 +62,8 @@ namespace cryptonote crypto::secret_key generate(const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false, bool two_random = false); void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); - bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::public_key &spend_public_key); + bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector &multisig_keys); + void finalize_multisig(const crypto::public_key &spend_public_key); const account_keys& get_keys() const; std::string get_public_address_str(bool testnet) const; std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const; @@ -72,6 +75,7 @@ namespace cryptonote bool store(const std::string& file_path); void forget_spend_key(); + const std::vector &get_multisig_keys() const { return m_keys.m_multisig_keys; } template inline void serialize(t_archive &a, const unsigned int /*ver*/) diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index 358c1e4c0..a9bc7b8fd 100644 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -99,10 +99,15 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str std::vector pk(total); for (size_t n = 0; n < total; ++n) { - tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n]); + if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n])) + { + tools::fail_msg_writer() << tr("Failed to verify multisig info"); + return false; + } } // make the wallets multisig + std::vector extra_info(total); std::stringstream ss; for (size_t n = 0; n < total; ++n) { @@ -117,10 +122,33 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str pkn.push_back(pk[k]); } } - wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold); + extra_info[n] = wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold); ss << " " << name << std::endl; } + // finalize step if needed + if (!extra_info[0].empty()) + { + std::unordered_set pkeys; + std::vector signers(total); + for (size_t n = 0; n < total; ++n) + { + if (!tools::wallet2::verify_extra_multisig_info(extra_info[n], pkeys, signers[n])) + { + tools::fail_msg_writer() << genms::tr("Error verifying multisig extra info"); + return false; + } + } + for (size_t n = 0; n < total; ++n) + { + if (!wallets[n]->finalize_multisig(pwd_container->password(), pkeys, signers)) + { + tools::fail_msg_writer() << genms::tr("Error finalizing multisig"); + return false; + } + } + } + std::string address = wallets[0]->get_account().get_public_address_str(wallets[0]->testnet()); tools::success_msg_writer() << genms::tr("Generated multisig wallets for address ") << address << std::endl << ss.str(); } diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 1f44c1a9e..5ea2dcc7c 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -517,6 +517,11 @@ inline std::ostream &operator <<(std::ostream &o, const rct::key &v) { } +namespace std +{ + template<> struct hash { std::size_t operator()(const rct::key &k) const { return reinterpret_cast(k); } }; +} + BLOB_SERIALIZER(rct::key); BLOB_SERIALIZER(rct::key64); BLOB_SERIALIZER(rct::ctkey); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 09dbb1373..ab09ace91 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -825,7 +825,14 @@ bool simple_wallet::make_multisig(const std::vector &args) try { - m_wallet->make_multisig(orig_pwd_container->password(), secret_keys, public_keys, threshold); + std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), secret_keys, public_keys, threshold); + if (!multisig_extra_info.empty()) + { + success_msg_writer() << tr("Another step is needed"); + success_msg_writer() << multisig_extra_info; + success_msg_writer() << tr("Send this multisig info to all other participants, then use finalize_multisig [...] with others' multisig info"); + return true; + } } catch (const std::exception &e) { @@ -840,6 +847,57 @@ bool simple_wallet::make_multisig(const std::vector &args) return true; } +bool simple_wallet::finalize_multisig(const std::vector &args) +{ + if (!m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is not multisig"); + return true; + } + + const auto orig_pwd_container = get_and_verify_password(); + if(orig_pwd_container == boost::none) + { + fail_msg_writer() << tr("Your original password was incorrect."); + return true; + } + + if (args.size() < 2) + { + fail_msg_writer() << tr("usage: finalize_multisig [...]"); + return true; + } + + // parse all multisig info + std::unordered_set public_keys; + std::vector signers(args.size(), crypto::null_pkey); + for (size_t i = 0; i < args.size(); ++i) + { + if (!tools::wallet2::verify_extra_multisig_info(args[i], public_keys, signers[i])) + { + fail_msg_writer() << tr("Bad multisig info: ") << args[i]; + return true; + } + } + + // we have all pubkeys now + try + { + if (!m_wallet->finalize_multisig(orig_pwd_container->password(), public_keys, signers)) + { + fail_msg_writer() << tr("Failed to finalize multisig"); + return true; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to finalize multisig: ") << e.what(); + return true; + } + + return true; +} + bool simple_wallet::export_multisig(const std::vector &args) { if (!m_wallet->multisig()) @@ -869,9 +927,8 @@ bool simple_wallet::export_multisig(const std::vector &args) std::string header; header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); - crypto::hash hash; - cn_fast_hash(&m_wallet->get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); - header += std::string((const char *)&hash, sizeof(crypto::hash)); + crypto::public_key signer = m_wallet->get_multisig_signer_public_key(); + header += std::string((const char *)&signer, sizeof(crypto::public_key)); std::string ciphertext = m_wallet->encrypt_with_view_secret_key(header + oss.str()); bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext); if (!r) @@ -908,7 +965,7 @@ bool simple_wallet::import_multisig(const std::vector &args) return true; std::vector> info; - std::unordered_set seen; + std::unordered_set seen; for (size_t n = 0; n < args.size(); ++n) { const std::string filename = args[n]; @@ -944,26 +1001,24 @@ bool simple_wallet::import_multisig(const std::vector &args) } const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; - const crypto::hash &hash = *(const crypto::hash*)&data[2*sizeof(crypto::public_key)]; + const crypto::public_key &signer = *(const crypto::public_key*)&data[2*sizeof(crypto::public_key)]; const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) { fail_msg_writer() << (boost::format(tr("Multisig info from %s is for a different account")) % filename).str(); return true; } - crypto::hash this_hash; - cn_fast_hash(&m_wallet->get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&this_hash); - if (this_hash == hash) + if (m_wallet->get_multisig_signer_public_key() == signer) { message_writer() << (boost::format(tr("Multisig info from %s is from this wallet, ignored")) % filename).str(); continue; } - if (seen.find(hash) != seen.end()) + if (seen.find(signer) != seen.end()) { message_writer() << (boost::format(tr("Multisig info from %s already seen, ignored")) % filename).str(); continue; } - seen.insert(hash); + seen.insert(signer); try { @@ -1721,6 +1776,10 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1), tr("make_multisig [...]"), tr("Turn this wallet into a multisig wallet")); + m_cmd_binder.set_handler("finalize_multisig", + boost::bind(&simple_wallet::finalize_multisig, this, _1), + tr("finalize_multisig [...]"), + tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets")); m_cmd_binder.set_handler("export_multisig_info", boost::bind(&simple_wallet::export_multisig, this, _1), tr("export_multisig "), diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index ebe830f69..73b3456db 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -190,6 +190,7 @@ namespace cryptonote bool print_fee_info(const std::vector &args); bool prepare_multisig(const std::vector& args); bool make_multisig(const std::vector& args); + bool finalize_multisig(const std::vector &args); bool export_multisig(const std::vector& args); bool import_multisig(const std::vector& args); bool accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index ee5bd0441..807248860 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1123,8 +1123,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote error::wallet_internal_error, "NULL m_multisig_rescan_k"); if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size()) update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); - else - td.m_multisig_k = rct::skGen(); } LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) @@ -1180,8 +1178,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote error::wallet_internal_error, "NULL m_multisig_rescan_k"); if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size()) update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); - else - td.m_multisig_k = rct::skGen(); } THROW_WALLET_EXCEPTION_IF(td.get_public_key() != tx_scan_info[o].in_ephemeral.pub, error::wallet_internal_error, "Inconsistent public keys"); THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); @@ -2252,6 +2248,7 @@ bool wallet2::clear() bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only) { std::string account_data; + std::string multisig_signers; cryptonote::account_base account = m_account; if (watch_only) @@ -2282,8 +2279,13 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetUint(m_multisig_threshold); json.AddMember("multisig_threshold", value2, json.GetAllocator()); - value2.SetUint(m_multisig_total); - json.AddMember("multisig_total", value2, json.GetAllocator()); + if (m_multisig) + { + bool r = ::serialization::dump_binary(m_multisig_signers, multisig_signers); + CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet multisig signers"); + value.SetString(multisig_signers.c_str(), multisig_signers.length()); + json.AddMember("multisig_signers", value, json.GetAllocator()); + } value2.SetInt(m_always_confirm_transfers ? 1 :0); json.AddMember("always_confirm_transfers", value2, json.GetAllocator()); @@ -2398,7 +2400,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_watch_only = false; m_multisig = false; m_multisig_threshold = 0; - m_multisig_total = 0; + m_multisig_signers.clear(); m_always_confirm_transfers = false; m_print_ring_members = false; m_default_mixin = 0; @@ -2439,8 +2441,27 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_multisig = field_multisig; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_threshold, unsigned int, Uint, m_multisig, 0); m_multisig_threshold = field_multisig_threshold; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_total, unsigned int, Uint, m_multisig, 0); - m_multisig_total = field_multisig_total; + if (m_multisig) + { + if (!json.HasMember("multisig_signers")) + { + LOG_ERROR("Field multisig_signers not found in JSON"); + return false; + } + if (!json["multisig_signers"].IsString()) + { + LOG_ERROR("Field multisig_signers found in JSON, but not String"); + return false; + } + const char *field_multisig_signers = json["multisig_signers"].GetString(); + std::string multisig_signers = std::string(field_multisig_signers, field_multisig_signers + json["multisig_signers"].GetStringLength()); + r = ::serialization::parse_binary(multisig_signers, m_multisig_signers); + if (!r) + { + LOG_ERROR("Field multisig_signers found in JSON, but failed to parse"); + return false; + } + } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true); m_always_confirm_transfers = field_always_confirm_transfers; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true); @@ -2607,7 +2628,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_watch_only = false; m_multisig = false; m_multisig_threshold = 0; - m_multisig_total = 0; + m_multisig_signers.clear(); // -1 month for fluctuations in block time and machine date/time setup. // avg seconds per block @@ -2698,7 +2719,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_watch_only = true; m_multisig = false; m_multisig_threshold = 0; - m_multisig_total = 0; + m_multisig_signers.clear(); if (!wallet_.empty()) { @@ -2744,7 +2765,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_watch_only = false; m_multisig = false; m_multisig_threshold = 0; - m_multisig_total = 0; + m_multisig_signers.clear(); if (!wallet_.empty()) { @@ -2763,7 +2784,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& store(); } -void wallet2::make_multisig(const epee::wipeable_string &password, +std::string wallet2::make_multisig(const epee::wipeable_string &password, const std::vector &view_keys, const std::vector &spend_keys, uint32_t threshold) @@ -2771,45 +2792,92 @@ void wallet2::make_multisig(const epee::wipeable_string &password, CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys"); CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes"); CHECK_AND_ASSERT_THROW_MES(threshold > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold"); - CHECK_AND_ASSERT_THROW_MES(/*threshold == spend_keys.size() ||*/ threshold == spend_keys.size() + 1, "Unsupported threshold case"); + CHECK_AND_ASSERT_THROW_MES(threshold == spend_keys.size() || threshold == spend_keys.size() + 1, "Unsupported threshold case"); + + std::string extra_multisig_info; + crypto::hash hash; clear(); MINFO("Creating spend key..."); - rct::key spend_pkey = rct::pk2rct(get_account().get_keys().m_account_address.m_spend_public_key); + std::vector multisig_keys; + rct::key spend_pkey, spend_skey; if (threshold == spend_keys.size() + 1) { // the multisig spend public key is the sum of all spend public keys + spend_pkey = rct::pk2rct(get_account().get_keys().m_account_address.m_spend_public_key); for (const auto &k: spend_keys) rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); + multisig_keys.push_back(get_account().get_keys().m_spend_secret_key); + spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); + } + else if (threshold == spend_keys.size()) + { + spend_pkey = rct::identity(); + spend_skey = rct::zero(); + + // create all our composite private keys + for (const auto &k: spend_keys) + { + rct::keyV data; + data.push_back(rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(get_account().get_keys().m_spend_secret_key))); + static const rct::key salt = { {'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 } }; + data.push_back(salt); + rct::key msk = rct::hash_to_scalar(data); + multisig_keys.push_back(rct::rct2sk(msk)); + sc_add(spend_skey.bytes, spend_skey.bytes, msk.bytes); + } + + // We need an extra step, so we package all the composite public keys + // we know about, and make a signed string out of them + std::string data; + const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key; + data += std::string((const char *)&pkey, sizeof(crypto::public_key)); + const crypto::public_key signer = get_multisig_signer_public_key(rct::rct2sk(spend_skey)); + data += std::string((const char *)&signer, sizeof(crypto::public_key)); + + for (const auto &msk: multisig_keys) + { + rct::key pmsk = rct::scalarmultBase(rct::sk2rct(msk)); + data += std::string((const char *)&pmsk, sizeof(crypto::public_key)); + } + + data.resize(data.size() + sizeof(crypto::signature)); + crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); + crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; + crypto::generate_signature(hash, pkey, get_account().get_keys().m_spend_secret_key, signature); + + extra_multisig_info = std::string("MultisigxV1") + tools::base58::encode(data); } else { - // the multisig spend public key is the sum of keys derived from all spend public keys - const rct::key spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); // WRONG - for (const auto &k: spend_keys) - { - rct::addKeys(spend_pkey, spend_pkey, rct::scalarmultBase(rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(k), spend_skey)))); - } + CHECK_AND_ASSERT_THROW_MES(false, "Unsupported threshold case"); } // the multisig view key is shared by all, make one all can derive MINFO("Creating view key..."); - crypto::hash hash; crypto::cn_fast_hash(&get_account().get_keys().m_view_secret_key, sizeof(crypto::secret_key), hash); rct::key view_skey = rct::hash2rct(hash); for (const auto &k: view_keys) sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes); MINFO("Creating multisig address..."); - CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2pk(spend_pkey)), + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys), "Failed to create multisig wallet due to bad keys"); m_account_public_address = m_account.get_keys().m_account_address; m_watch_only = false; m_multisig = true; m_multisig_threshold = threshold; - m_multisig_total = spend_keys.size() + 1; + if (threshold == spend_keys.size() + 1) + { + m_multisig_signers = spend_keys; + m_multisig_signers.push_back(get_multisig_signer_public_key()); + } + else + { + m_multisig_signers = std::vector(spend_keys.size() + 1, crypto::null_pkey); + } if (!m_wallet_file.empty()) { @@ -2827,6 +2895,65 @@ void wallet2::make_multisig(const epee::wipeable_string &password, if (!m_wallet_file.empty()) store(); + + return extra_multisig_info; +} + +bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unordered_set pkeys, std::vector signers) +{ + CHECK_AND_ASSERT_THROW_MES(!pkeys.empty(), "empty pkeys"); + + // add ours if not included + const crypto::public_key local_signer = get_multisig_signer_public_key(); + if (std::find(signers.begin(), signers.end(), local_signer) == signers.end()) + { + signers.push_back(local_signer); + for (const auto &msk: get_account().get_multisig_keys()) + { + pkeys.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(msk)))); + } + } + + CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size"); + + rct::key spend_public_key = rct::identity(); + for (const auto &pk: pkeys) + { + rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk)); + } + m_multisig_signers = signers; + std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)); }); + m_account_public_address.m_spend_public_key = rct::rct2pk(spend_public_key); + m_account.finalize_multisig(m_account_public_address.m_spend_public_key); + + if (!m_wallet_file.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } + + m_subaddresses.clear(); + m_subaddresses_inv.clear(); + m_subaddress_labels.clear(); + add_subaddress_account(tr("Primary account")); + + if (!m_wallet_file.empty()) + store(); + + return true; +} + +bool wallet2::wallet_generate_key_image_helper_export(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) const +{ + THROW_WALLET_EXCEPTION_IF(multisig_key_index >= ack.m_multisig_keys.size(), error::wallet_internal_error, "Bad multisig_key_index"); + if (!generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) + return false; + // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig + crypto::generate_key_image(in_ephemeral.pub, ack.m_multisig_keys[multisig_key_index], ki); + return true; } std::string wallet2::get_multisig_info() const @@ -2887,6 +3014,57 @@ bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key & return true; } +bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered_set &pkeys, crypto::public_key &signer) +{ + const size_t header_len = strlen("MultisigxV1"); + if (data.size() < header_len || data.substr(0, header_len) != "MultisigxV1") + { + MERROR("Multisig info header check error"); + return false; + } + std::string decoded; + if (!tools::base58::decode(data.substr(header_len), decoded)) + { + MERROR("Multisig info decoding error"); + return false; + } + if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature)) + { + MERROR("Multisig info is corrupt"); + return false; + } + if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key)) + { + MERROR("Multisig info is corrupt"); + return false; + } + + const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key); + size_t offset = 0; + const crypto::public_key &pkey = *(const crypto::public_key*)(decoded.data() + offset); + offset += sizeof(pkey); + signer = *(const crypto::public_key*)(decoded.data() + offset); + offset += sizeof(signer); + const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset + n_keys * sizeof(crypto::public_key)); + + crypto::hash hash; + crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); + if (!crypto::check_signature(hash, pkey, signature)) + { + MERROR("Multisig info signature is invalid"); + return false; + } + + for (size_t n = 0; n < n_keys; ++n) + { + crypto::public_key mspk = *(const crypto::public_key*)(decoded.data() + offset); + pkeys.insert(mspk); + offset += sizeof(mspk); + } + + return true; +} + bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const { if (!m_multisig) @@ -2894,7 +3072,7 @@ bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const if (threshold) *threshold = m_multisig_threshold; if (total) - *total = m_multisig_total; + *total = m_multisig_signers.size(); return true; } @@ -3899,7 +4077,7 @@ void wallet2::commit_tx(pending_tx& ptx) // tx generated, get rid of used k values for (size_t idx: ptx.selected_transfers) - m_transfers[idx].m_multisig_k = rct::zero(); + m_transfers[idx].m_multisig_k.clear(); //fee includes dust if dust policy specified it. LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL @@ -4084,7 +4262,6 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f ptx.selected_transfers = sd.selected_transfers; ptx.tx_key = rct::rct2sk(rct::identity()); // don't send it back to the untrusted view wallet ptx.dests = sd.dests; - ptx.msout = msout; ptx.construction_data = sd; txs.push_back(ptx); @@ -4241,7 +4418,7 @@ std::string wallet2::save_multisig_tx(multisig_tx_set txs) // txes generated, get rid of used k values for (size_t n = 0; n < txs.m_ptx.size(); ++n) for (size_t idx: txs.m_ptx[n].construction_data.selected_transfers) - m_transfers[idx].m_multisig_k = rct::zero(); + m_transfers[idx].m_multisig_k.clear(); // zero out some data we don't want to share for (auto &ptx: txs.m_ptx) @@ -4284,9 +4461,15 @@ std::string wallet2::save_multisig_tx(const std::vector& ptx_vector) { multisig_tx_set txs; txs.m_ptx = ptx_vector; - crypto::hash hash; - cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); - txs.m_signers.insert(hash); + + for (const auto &msk: get_account().get_multisig_keys()) + { + crypto::public_key pkey = get_multisig_signing_public_key(msk); + for (auto &ptx: txs.m_ptx) for (auto &sig: ptx.multisig_sigs) sig.signing_keys.insert(pkey); + } + + txs.m_signers.insert(get_multisig_signer_public_key()); + return save_multisig_tx(txs); } //---------------------------------------------------------------------------------------------------- @@ -4378,21 +4561,24 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids) +bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector &txids) { THROW_WALLET_EXCEPTION_IF(exported_txs.m_ptx.empty(), error::wallet_internal_error, "No tx found"); + const crypto::public_key local_signer = get_multisig_signer_public_key(); + txids.clear(); // sign the transactions 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) << ", signed by " << exported_txs.m_signers.size() << "/" << m_multisig_threshold); cryptonote::transaction tx; - rct::multisig_out msout = ptx.msout; + rct::multisig_out msout = ptx.multisig_sigs.front().msout; auto sources = sd.sources; const bool bulletproof = sd.use_rct && (ptx.tx.rct_signatures.type == rct::RCTTypeFullBulletproof || ptx.tx.rct_signatures.type == rct::RCTTypeSimpleBulletproof); 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, bulletproof, &msout); @@ -4406,16 +4592,51 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, const std::string for (const auto &source: sources) indices.push_back(source.real_output); - rct::keyV k; - for (size_t idx: sd.selected_transfers) - k.push_back(get_multisig_k(idx)); + for (auto &sig: ptx.multisig_sigs) + { + if (sig.ignore != local_signer) + { + ptx.tx.rct_signatures = sig.sigs; - THROW_WALLET_EXCEPTION_IF(!rct::signMultisig(ptx.tx.rct_signatures, indices, k, ptx.msout, rct::sk2rct(get_account().get_keys().m_spend_secret_key)), - error::wallet_internal_error, "Failed signing, transaction likely malformed"); + rct::keyV k; + for (size_t idx: sd.selected_transfers) + k.push_back(get_multisig_k(idx, sig.used_L)); + + rct::key skey = rct::zero(); + for (const auto &msk: get_account().get_multisig_keys()) + { + crypto::public_key pmsk = get_multisig_signing_public_key(msk); + + if (sig.signing_keys.find(pmsk) == sig.signing_keys.end()) + { + sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); + sig.signing_keys.insert(pmsk); + } + } + 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; + } + } 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 + bool found = false; + for (const auto &sig: ptx.multisig_sigs) + { + if (sig.ignore != local_signer && exported_txs.m_signers.find(sig.ignore) == exported_txs.m_signers.end()) + { + THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final"); + ptx.tx.rct_signatures = sig.sigs; + 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"); const crypto::hash txid = get_transaction_hash(ptx.tx); if (store_tx_info()) { @@ -4429,12 +4650,18 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, const std::string // txes generated, get rid of used k values 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) - m_transfers[idx].m_multisig_k = rct::zero(); + m_transfers[idx].m_multisig_k.clear(); - crypto::hash hash; - cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); - exported_txs.m_signers.insert(hash); + exported_txs.m_signers.insert(get_multisig_signer_public_key()); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_multisig_tx_from_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids) +{ + bool r = sign_multisig_tx(exported_txs, txids); + if (!r) + return false; return save_multisig_tx(exported_txs, filename); } //---------------------------------------------------------------------------------------------------- @@ -4444,9 +4671,8 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto if(!load_multisig_tx_from_file(filename, exported_txs)) return false; - crypto::hash hash; - cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); - THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(hash) != exported_txs.m_signers.end(), + const crypto::public_key signer = get_multisig_signer_public_key(); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(signer) != exported_txs.m_signers.end(), error::wallet_internal_error, "Transaction already signed by this private key"); THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() > m_multisig_threshold, error::wallet_internal_error, "Transaction was signed by too many signers"); @@ -4458,7 +4684,7 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto LOG_PRINT_L1("Transactions rejected by callback"); return false; } - return sign_multisig_tx(exported_txs, filename, txids); + return sign_multisig_tx_from_file(exported_txs, filename, txids); } //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) @@ -5012,6 +5238,8 @@ void wallet2::transfer_selected(const std::vector multisig_signers; + size_t n_multisig_txes = 0; + if (m_multisig && !m_transfers.empty()) + { + const crypto::public_key local_signer = get_multisig_signer_public_key(); + size_t n_available_signers = 1; + for (const crypto::public_key &signer: m_multisig_signers) + { + if (signer == local_signer) + continue; + multisig_signers.push_front(signer); + for (const auto &i: m_transfers[0].m_multisig_info) + { + if (i.m_signer == signer) + { + multisig_signers.pop_front(); + multisig_signers.push_back(signer); + ++n_available_signers; + break; + } + } + } + multisig_signers.push_back(local_signer); + MDEBUG("We can use " << n_available_signers << "/" << m_multisig_signers.size() << " other signers"); + THROW_WALLET_EXCEPTION_IF(n_available_signers+1 < m_multisig_threshold, error::multisig_import_needed); + n_multisig_txes = n_available_signers == m_multisig_signers.size() ? m_multisig_threshold : 1; + MDEBUG("We will create " << n_multisig_txes << " txes"); + } + uint64_t found_money = 0; for(size_t idx: selected_transfers) { @@ -5207,6 +5461,7 @@ void wallet2::transfer_selected_rct(std::vector sources; + std::unordered_set used_L; for(size_t idx: selected_transfers) { sources.resize(sources.size()+1); @@ -5249,7 +5504,10 @@ void wallet2::transfer_selected_rct(std::vector multisig_sigs; + if (m_multisig) + { + crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front(); + multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, {}, 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 signer_index = 1; signer_index < n_multisig_txes; ++signer_index) + { + std::unordered_set 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[src_idx]; + src.multisig_kLRki = get_multisig_composite_kLRki(idx, multisig_signers[signer_index], used_L, new_used_L); + ++src_idx; + } + + LOG_PRINT_L2("Creating supplementary multisig transaction"); + cryptonote::transaction ms_tx; + auto sources_copy_copy = sources_copy; + bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, &msout); + LOG_PRINT_L2("constructed tx, r="< bool @@ -5329,8 +5626,8 @@ void wallet2::transfer_selected_rct(std::vector &used_L) const +{ + 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) + { + rct::key L; + rct::scalarmultBase(L, k); + if (used_L.find(L) != used_L.end()) + return k; + } + 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 +{ + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad m_transfers index"); rct::multisig_kLRki kLRki; kLRki.k = k; rct::scalarmultBase(kLRki.L, kLRki.k); @@ -8030,27 +8363,77 @@ rct::multisig_kLRki wallet2::get_multisig_LRki(size_t n, const rct::key &k) cons return kLRki; } //---------------------------------------------------------------------------------------------------- -rct::multisig_kLRki wallet2::get_multisig_composite_LRki(size_t n, const rct::key &k) const +rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set &used_L, std::unordered_set &new_used_L) const { - rct::multisig_kLRki kLRki = get_multisig_LRki(n, k); + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad transfer index"); + + const transfer_details &td = m_transfers[n]; + rct::multisig_kLRki kLRki = get_multisig_kLRki(n, rct::skGen()); + + // pick a L/R pair from every other participant but one + size_t n_signers_used = 1; + for (const auto &p: m_transfers[n].m_multisig_info) + { + if (p.m_signer == ignore) + continue; + for (const auto &lr: p.m_LR) + { + if (used_L.find(lr.m_L) != used_L.end()) + continue; + used_L.insert(lr.m_L); + new_used_L.insert(lr.m_L); + rct::addKeys(kLRki.L, kLRki.L, lr.m_L); + rct::addKeys(kLRki.R, kLRki.R, lr.m_R); + ++n_signers_used; + break; + } + } + CHECK_AND_ASSERT_THROW_MES(n_signers_used >= m_multisig_threshold, "LR not found for enough participants"); + + return kLRki; +} +//---------------------------------------------------------------------------------------------------- +crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const +{ + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad output index"); + const transfer_details &td = m_transfers[n]; crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); cryptonote::keypair in_ephemeral; - bool r = wallet_generate_key_image_helper_old(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, (crypto::key_image&)kLRki.ki); + crypto::key_image ki; + bool r = wallet_generate_key_image_helper_old(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki); CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); + std::unordered_set used; + + // insert the ones we start from + for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) + { + crypto::key_image pki; + wallet_generate_key_image_helper_export(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, pki, m); + used.insert(pki); + } + for (const auto &info: td.m_multisig_info) { - rct::addKeys(kLRki.ki, kLRki.ki, rct::ki2rct(info.m_partial_key_image)); - rct::addKeys(kLRki.L, kLRki.L, info.m_L); - rct::addKeys(kLRki.R, kLRki.R, info.m_R); + for (const auto &pki: info.m_partial_key_images) + { + // don't add duplicates again + if (used.find(pki) != used.end()) + continue; + used.insert(pki); + + rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki)); + } } - return kLRki; + return ki; } //---------------------------------------------------------------------------------------------------- std::vector wallet2::export_multisig() { std::vector info; + const crypto::public_key signer = get_multisig_signer_public_key(); + info.resize(m_transfers.size()); for (size_t n = 0; n < m_transfers.size(); ++n) { @@ -8058,21 +8441,33 @@ std::vector wallet2::export_multisig() crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); cryptonote::keypair in_ephemeral; crypto::key_image ki; - td.m_multisig_k = rct::skGen(); - const rct::multisig_kLRki kLRki = get_multisig_LRki(n, td.m_multisig_k); - const std::vector additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); - // we want to export the partial key image, not the full one, so we can't use td.m_key_image - bool r = wallet_generate_key_image_helper_old(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki, true); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); - // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig - crypto::generate_key_image(in_ephemeral.pub, get_account().get_keys().m_spend_secret_key, ki); - info[n] = multisig_info({ki, kLRki.L, kLRki.R}); + td.m_multisig_k.clear(); + info[n].m_LR.clear(); + info[n].m_partial_key_images.clear(); + + 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 + bool r = wallet_generate_key_image_helper_export(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki, m); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); + info[n].m_partial_key_images.push_back(ki); + } + + size_t nlr = m_multisig_threshold < m_multisig_signers.size() ? m_multisig_threshold - 1 : 1; + for (size_t m = 0; m < nlr; ++m) + { + td.m_multisig_k.push_back(rct::skGen()); + const rct::multisig_kLRki kLRki = get_multisig_kLRki(n, td.m_multisig_k.back()); + info[n].m_LR.push_back({kLRki.L, kLRki.R}); + } + + info[n].m_signer = signer; } return info; } //---------------------------------------------------------------------------------------------------- -void wallet2::update_multisig_rescan_info(const std::vector &multisig_k, const std::vector> &info, size_t n) +void wallet2::update_multisig_rescan_info(const std::vector> &multisig_k, const std::vector> &info, size_t n) { CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad index in update_multisig_info"); CHECK_AND_ASSERT_THROW_MES(multisig_k.size() >= m_transfers.size(), "Mismatched sizes of multisig_k and info"); @@ -8086,7 +8481,7 @@ void wallet2::update_multisig_rescan_info(const std::vector &multisig_ td.m_multisig_info.push_back(pi[n]); } m_key_images.erase(td.m_key_image); - td.m_key_image = rct::rct2ki(get_multisig_composite_LRki(n, rct::skGen()).ki); + td.m_key_image = get_multisig_composite_key_image(n); td.m_key_image_known = true; td.m_key_image_partial = false; td.m_multisig_k = multisig_k[n]; @@ -8096,9 +8491,9 @@ void wallet2::update_multisig_rescan_info(const std::vector &multisig_ size_t wallet2::import_multisig(std::vector> info) { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); - CHECK_AND_ASSERT_THROW_MES(info.size() + 1 == m_multisig_total, "Wrong number of multisig sources"); + CHECK_AND_ASSERT_THROW_MES(info.size() + 1 <= m_multisig_signers.size() && info.size() + 1 >= m_multisig_threshold, "Wrong number of multisig sources"); - std::vector k; + std::vector> k; k.reserve(m_transfers.size()); for (const auto &td: m_transfers) k.push_back(td.m_multisig_k); @@ -8109,10 +8504,28 @@ size_t wallet2::import_multisig(std::vector &i0, const std::vector &i1){ return memcmp(&i0[0].m_signer, &i1[0].m_signer, sizeof(i0[0].m_signer)); }); + } + // first pass to determine where to detach the blockchain for (size_t n = 0; n < n_outputs; ++n) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 8567edc9a..5f973fef5 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -172,14 +172,25 @@ namespace tools struct multisig_info { - crypto::key_image m_partial_key_image; - rct::key m_L; - rct::key m_R; + struct LR + { + rct::key m_L; + rct::key m_R; + + BEGIN_SERIALIZE_OBJECT() + FIELD(m_L) + FIELD(m_R) + END_SERIALIZE() + }; + + crypto::public_key m_signer; + std::vector m_LR; + std::vector m_partial_key_images; // one per key the participant has BEGIN_SERIALIZE_OBJECT() - FIELD(m_partial_key_image) - FIELD(m_L) - FIELD(m_R) + FIELD(m_signer) + FIELD(m_LR) + FIELD(m_partial_key_images) END_SERIALIZE() }; @@ -213,8 +224,8 @@ namespace tools size_t m_pk_index; cryptonote::subaddress_index m_subaddr_index; bool m_key_image_partial; - rct::key m_multisig_k; - std::vector m_multisig_info; + std::vector m_multisig_k; + std::vector m_multisig_info; // one per other participant bool is_rct() const { return m_rct; } uint64_t amount() const { return m_amount; } @@ -327,6 +338,15 @@ namespace tools typedef std::vector transfer_container; typedef std::unordered_multimap payment_container; + struct multisig_sig + { + rct::rctSig sigs; + crypto::public_key ignore; + std::unordered_set used_L; + std::unordered_set signing_keys; + rct::multisig_out msout; + }; + // The convention for destinations is: // dests does not include change // splitted_dsts (in construction_data) does @@ -341,7 +361,7 @@ namespace tools crypto::secret_key tx_key; std::vector additional_tx_keys; std::vector dests; - rct::multisig_out msout; + std::vector multisig_sigs; tx_construction_data construction_data; @@ -357,6 +377,7 @@ namespace tools FIELD(additional_tx_keys) FIELD(dests) FIELD(construction_data) + FIELD(multisig_sigs) END_SERIALIZE() }; @@ -377,7 +398,7 @@ namespace tools struct multisig_tx_set { std::vector m_ptx; - std::unordered_set m_signers; + std::unordered_set m_signers; }; struct keys_file_data @@ -446,11 +467,17 @@ namespace tools const crypto::secret_key& viewkey = crypto::secret_key()); /*! * \brief Creates a multisig wallet + * \return empty if done, non empty if we need to send another string + * to other participants */ - void make_multisig(const epee::wipeable_string &password, + std::string make_multisig(const epee::wipeable_string &password, const std::vector &view_keys, const std::vector &spend_keys, uint32_t threshold); + /*! + * \brief Finalizes creation of a multisig wallet + */ + bool finalize_multisig(const epee::wipeable_string &password, std::unordered_set pkeys, std::vector signers); /*! * Get a packaged multisig information string */ @@ -459,6 +486,10 @@ namespace tools * Verifies and extracts keys from a packaged multisig information string */ static bool verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey); + /*! + * Verifies and extracts keys from a packaged multisig information string + */ + static bool verify_extra_multisig_info(const std::string &data, std::unordered_set &pkeys, crypto::public_key &signer); /*! * Export multisig info * This will generate and remember new k values @@ -610,7 +641,8 @@ namespace tools std::vector create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector unused_transfers_indices, std::vector unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, bool trusted_daemon); bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func = NULL); bool sign_multisig_tx_from_file(const std::string &filename, std::vector &txids, std::function accept_func); - bool sign_multisig_tx(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids); + bool sign_multisig_tx(multisig_tx_set &exported_txs, std::vector &txids); + bool sign_multisig_tx_from_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids); std::vector create_unmixable_sweep_transactions(bool trusted_daemon); bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000); void get_transfers(wallet2::transfer_container& incoming_transfers) const; @@ -890,6 +922,11 @@ namespace tools void set_attribute(const std::string &key, const std::string &value); std::string get_attribute(const std::string &key) const; + crypto::public_key get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const; + crypto::public_key get_multisig_signer_public_key() const; + crypto::public_key get_multisig_signing_public_key(size_t idx) const; + crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const; + private: /*! * \brief Stores wallet information to wallet file. @@ -936,15 +973,17 @@ namespace tools void set_unspent(size_t idx); void get_outs(std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count); bool tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; + bool wallet_generate_key_image_helper_export(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) const; crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector &unused_transfers_indices, const std::vector &unused_dust_indices) const; std::vector get_only_rct(const std::vector &unused_dust_indices, const std::vector &unused_transfers_indices) const; void scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map &tx_money_got_in_outs, std::vector &outs); void trim_hashchain(); - rct::multisig_kLRki get_multisig_composite_LRki(size_t n, const rct::key &k) const; - rct::multisig_kLRki get_multisig_LRki(size_t n, const rct::key &k) const; - rct::key get_multisig_k(size_t idx) const; - void update_multisig_rescan_info(const std::vector &multisig_k, const std::vector> &info, size_t n); + crypto::key_image get_multisig_composite_key_image(size_t n) const; + rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set &used_L, std::unordered_set &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 &used_L) const; + void update_multisig_rescan_info(const std::vector> &multisig_k, const std::vector> &info, size_t n); cryptonote::account_base m_account; boost::optional m_daemon_login; @@ -974,7 +1013,7 @@ namespace tools std::vector m_address_book; uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value const std::vector> *m_multisig_rescan_info; - const std::vector *m_multisig_rescan_k; + const std::vector> *m_multisig_rescan_k; std::atomic m_run; @@ -988,7 +1027,7 @@ namespace tools bool m_watch_only; /*!< no spend key */ bool m_multisig; /*!< if > 1 spend secret key will not match spend public key */ uint32_t m_multisig_threshold; - uint32_t m_multisig_total; + std::vector m_multisig_signers; bool m_always_confirm_transfers; bool m_print_ring_members; bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */ @@ -1026,6 +1065,7 @@ namespace tools BOOST_CLASS_VERSION(tools::wallet2, 22) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 9) BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) +BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2) BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) @@ -1036,6 +1076,7 @@ BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 2) BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) +BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0) namespace boost { @@ -1076,8 +1117,8 @@ namespace boost if (ver < 9) { x.m_key_image_partial = false; + x.m_multisig_k.clear(); x.m_multisig_info.clear(); - x.m_multisig_k = rct::zero(); } } @@ -1163,13 +1204,20 @@ namespace boost } template - inline void serialize(Archive &a, tools::wallet2::multisig_info &x, const boost::serialization::version_type ver) + inline void serialize(Archive &a, tools::wallet2::multisig_info::LR &x, const boost::serialization::version_type ver) { - a & x.m_partial_key_image; a & x.m_L; a & x.m_R; } + template + inline void serialize(Archive &a, tools::wallet2::multisig_info &x, const boost::serialization::version_type ver) + { + a & x.m_signer; + a & x.m_LR; + a & x.m_partial_key_images; + } + template inline void serialize(Archive &a, tools::wallet2::multisig_tx_set &x, const boost::serialization::version_type ver) { @@ -1352,6 +1400,16 @@ namespace boost a & x.selected_transfers; } + template + inline void serialize(Archive &a, tools::wallet2::multisig_sig &x, const boost::serialization::version_type ver) + { + a & x.sigs; + a & x.ignore; + a & x.used_L; + a & x.signing_keys; + a & x.msout; + } + template inline void serialize(Archive &a, tools::wallet2::pending_tx &x, const boost::serialization::version_type ver) { @@ -1382,7 +1440,7 @@ namespace boost a & x.selected_transfers; if (ver < 3) return; - a & x.msout; + a & x.multisig_sigs; } } } @@ -1458,6 +1516,8 @@ namespace tools // throw if attempting a transaction with no destinations THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); + THROW_WALLET_EXCEPTION_IF(m_multisig, error::wallet_internal_error, "Multisig wallets cannot spend non rct outputs"); + uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit(); uint64_t needed_money = fee; @@ -1560,10 +1620,7 @@ namespace tools src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_output = interted_it - src.outputs.begin(); src.real_output_in_tx_index = td.m_internal_output_index; - if (m_multisig) - src.multisig_kLRki = get_multisig_composite_LRki(idx, get_multisig_k(idx)); - else - src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()}); + src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()}); detail::print_source_entry(src); ++i; } @@ -1619,7 +1676,6 @@ namespace tools ptx.tx_key = tx_key; ptx.additional_tx_keys = additional_tx_keys; ptx.dests = dsts; - ptx.msout = msout; ptx.construction_data.sources = sources; ptx.construction_data.change_dts = change_dts; ptx.construction_data.splitted_dsts = splitted_dsts; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index e2bb0a5c6..472302a94 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2446,7 +2446,7 @@ namespace tools try { - m_wallet->make_multisig(req.password, secret_keys, public_keys, req.threshold); + res.multisig_info = m_wallet->make_multisig(req.password, secret_keys, public_keys, req.threshold); res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); } catch (const std::exception &e) @@ -2490,9 +2490,16 @@ namespace tools res.info.resize(info.size()); for (size_t n = 0; n < info.size(); ++n) { - res.info[n].partial_key_image = epee::string_tools::pod_to_hex(info[n].m_partial_key_image); - res.info[n].L = epee::string_tools::pod_to_hex(info[n].m_L); - res.info[n].R = epee::string_tools::pod_to_hex(info[n].m_R); + res.info[n].signer = epee::string_tools::pod_to_hex(info[n].m_signer); + res.info[n].LR.resize(info[n].m_LR.size()); + for (size_t l = 0; l < info[n].m_LR.size(); ++l) + { + res.info[n].LR[l].L = epee::string_tools::pod_to_hex(info[n].m_LR[l].m_L); + res.info[n].LR[l].R = epee::string_tools::pod_to_hex(info[n].m_LR[l].m_R); + } + res.info[n].partial_key_images.resize(info[n].m_partial_key_images.size()); + for (size_t l = 0; l < info[n].m_partial_key_images.size(); ++l) + res.info[n].partial_key_images[l] = epee::string_tools::pod_to_hex(info[n].m_partial_key_images[l]); } return true; @@ -2529,17 +2536,34 @@ namespace tools info[n].resize(req.info[n].info.size()); for (size_t i = 0; i < info[n].size(); ++i) { - if (!epee::string_tools::hex_to_pod(req.info[n].info[i].partial_key_image, info[n][i].m_partial_key_image)) + const auto &src = req.info[n].info[i]; + auto &dst = info[n][i]; + + if (!epee::string_tools::hex_to_pod(src.signer, dst.m_signer)) { - er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; - er.message = "Failed to parse partial key image from multisig info"; + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Failed to parse signer from multisig info"; return false; } - if (!epee::string_tools::hex_to_pod(req.info[n].info[i].L, info[n][i].m_L) || !epee::string_tools::hex_to_pod(req.info[n].info[i].R, info[n][i].m_R)) + dst.m_LR.resize(src.LR.size()); + for (size_t l = 0; l < src.LR.size(); ++l) { - er.code = WALLET_RPC_ERROR_CODE_WRONG_LR; - er.message = "Failed to parse L/R info from hex"; - return false; + if (!epee::string_tools::hex_to_pod(src.LR[l].L, dst.m_LR[l].m_L) || !epee::string_tools::hex_to_pod(src.LR[l].R, dst.m_LR[l].m_R)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_LR; + er.message = "Failed to parse L/R from multisig info"; + return false; + } + } + dst.m_partial_key_images.resize(src.partial_key_images.size()); + for (size_t l = 0; l < src.partial_key_images.size(); ++l) + { + if (!epee::string_tools::hex_to_pod(src.partial_key_images[l], dst.m_partial_key_images[l])) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "Failed to parse partial key image from multisig info"; + return false; + } } } } @@ -2573,6 +2597,64 @@ namespace tools return true; } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + uint32_t threshold, total; + if (!m_wallet->multisig(&threshold, &total)) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + + if (req.multisig_info.size() < threshold - 1) + { + er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; + er.message = "Needs multisig info from more participants"; + return false; + } + + // parse all multisig info + std::unordered_set public_keys; + std::vector signers(req.multisig_info.size(), crypto::null_pkey); + for (size_t i = 0; i < req.multisig_info.size(); ++i) + { + if (!m_wallet->verify_extra_multisig_info(req.multisig_info[i], public_keys, signers[i])) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO; + er.message = std::string("Bad multisig_info info: ") + req.multisig_info[i]; + return false; + } + } + + try + { + if (!m_wallet->finalize_multisig(req.password, public_keys, signers)) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Error calling finalize_multisig"; + return false; + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = std::string("Error calling finalize_multisig: ") + e.what(); + return false; + } + res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ } int main(int argc, char** argv) { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 10b2e9dbd..22eb8964e 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -181,6 +181,7 @@ namespace tools bool on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er); bool on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er); bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er); //json rpc v2 bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 806704d6e..d17af9980 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1546,26 +1546,39 @@ namespace wallet_rpc struct response { std::string address; + std::string multisig_info; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) + KV_SERIALIZE(multisig_info) END_KV_SERIALIZE_MAP() }; }; - struct multisig_info_entry + struct LR_entry { - std::string partial_key_image; std::string L; std::string R; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(partial_key_image) KV_SERIALIZE(L) KV_SERIALIZE(R) END_KV_SERIALIZE_MAP() }; + struct multisig_info_entry + { + std::string signer; + std::vector LR; + std::vector partial_key_images; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(signer) + KV_SERIALIZE(LR) + KV_SERIALIZE(partial_key_images) + END_KV_SERIALIZE_MAP() + }; + struct COMMAND_RPC_EXPORT_MULTISIG { struct request @@ -1613,5 +1626,29 @@ namespace wallet_rpc END_KV_SERIALIZE_MAP() }; }; + + struct COMMAND_RPC_FINALIZE_MULTISIG + { + struct request + { + std::string password; + std::vector multisig_info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(password) + KV_SERIALIZE(multisig_info) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string address; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + END_KV_SERIALIZE_MAP() + }; + }; + } } diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp index 4a105c51c..0e8a6b168 100644 --- a/tests/unit_tests/multisig.cpp +++ b/tests/unit_tests/multisig.cpp @@ -125,14 +125,26 @@ static void make_M_3_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, to ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk2[0], pk2[0])); ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk2[1], pk2[1])); - // not implemented yet - if (M < 3) - return; - ASSERT_FALSE(wallet0.multisig() || wallet1.multisig() || wallet2.multisig()); - wallet0.make_multisig("", sk0, pk0, M); - wallet1.make_multisig("", sk1, pk1, M); - wallet2.make_multisig("", sk2, pk2, M); + std::string mxi0 = wallet0.make_multisig("", sk0, pk0, M); + std::string mxi1 = wallet1.make_multisig("", sk1, pk1, M); + std::string mxi2 = wallet2.make_multisig("", sk2, pk2, M); + + const size_t nset = !mxi0.empty() + !mxi1.empty() + !mxi2.empty(); + ASSERT_TRUE((M < 3 && nset == 3) || (M == 3 && nset == 0)); + + if (nset > 0) + { + std::unordered_set pkeys; + std::vector signers(3, crypto::null_pkey); + ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi0, pkeys, signers[0])); + ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi1, pkeys, signers[1])); + ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi2, pkeys, signers[2])); + ASSERT_TRUE(pkeys.size() == 3); + ASSERT_TRUE(wallet0.finalize_multisig("", pkeys, signers)); + ASSERT_TRUE(wallet1.finalize_multisig("", pkeys, signers)); + ASSERT_TRUE(wallet2.finalize_multisig("", pkeys, signers)); + } ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet1.get_account().get_public_address_str(true)); ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet2.get_account().get_public_address_str(true)); From 66e34e85b1ef3e49ea9290bd69cce2974840fc32 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Tue, 26 Sep 2017 23:16:25 +0100 Subject: [PATCH 08/19] add multisig core test and factor multisig building blocks --- src/CMakeLists.txt | 1 + src/cryptonote_core/CMakeLists.txt | 1 + src/cryptonote_core/cryptonote_tx_utils.cpp | 16 +- src/multisig/CMakeLists.txt | 52 +++ src/multisig/multisig.cpp | 152 +++++++ src/multisig/multisig.h | 50 +++ src/simplewallet/simplewallet.cpp | 15 +- src/wallet/CMakeLists.txt | 2 + src/wallet/wallet2.cpp | 120 ++--- src/wallet/wallet2.h | 6 +- src/wallet/wallet_rpc_server.cpp | 15 +- tests/core_tests/CMakeLists.txt | 3 + tests/core_tests/chaingen.h | 48 ++ tests/core_tests/chaingen_main.cpp | 17 + tests/core_tests/chaingen_tests_list.h | 1 + tests/core_tests/multisig.cpp | 460 ++++++++++++++++++++ tests/core_tests/multisig.h | 187 ++++++++ 17 files changed, 1019 insertions(+), 127 deletions(-) create mode 100644 src/multisig/CMakeLists.txt create mode 100644 src/multisig/multisig.cpp create mode 100644 src/multisig/multisig.h create mode 100644 tests/core_tests/multisig.cpp create mode 100644 tests/core_tests/multisig.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d8b0bf211..79d2a232d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -114,6 +114,7 @@ add_subdirectory(ringct) add_subdirectory(checkpoints) add_subdirectory(cryptonote_basic) add_subdirectory(cryptonote_core) +add_subdirectory(multisig) if(NOT IOS) add_subdirectory(blockchain_db) endif() diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 169a38f0a..eeed881da 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -59,6 +59,7 @@ target_link_libraries(cryptonote_core common cncrypto blockchain_db + multisig ringct ${Boost_DATE_TIME_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 799c27e06..fb1f972b3 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -40,6 +40,7 @@ using namespace epee; #include "crypto/crypto.h" #include "crypto/hash.h" #include "ringct/rctSigs.h" +#include "multisig/multisig.h" using namespace crypto; @@ -72,21 +73,6 @@ namespace cryptonote LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << " subaddresses"); } //--------------------------------------------------------------- - bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) - { - crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); - bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); - - r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); - - crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); - - crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); - return true; - } - //--------------------------------------------------------------- bool construct_miner_tx(size_t height, size_t median_size, uint64_t already_generated_coins, size_t current_block_size, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) { tx.vin.clear(); tx.vout.clear(); diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt new file mode 100644 index 000000000..432865ad3 --- /dev/null +++ b/src/multisig/CMakeLists.txt @@ -0,0 +1,52 @@ +# Copyright (c) 2017, 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. + +set(multisig_sources + multisig.cpp) + +set(multisig_headers) + +set(multisig_private_headers + multisig.h) + +monero_private_headers(multisig + ${multisig_private_headers}) + +monero_add_library(multisig + ${multisig_sources} + ${multisig_headers} + ${multisig_private_headers}) + +target_link_libraries(multisig + PUBLIC + ringct + cryptonote_basic + common + cncrypto + PRIVATE + ${EXTRA_LIBRARIES}) diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp new file mode 100644 index 000000000..0a9933b13 --- /dev/null +++ b/src/multisig/multisig.cpp @@ -0,0 +1,152 @@ +// Copyright (c) 2017, 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 +#include "include_base_utils.h" +#include "crypto/crypto.h" +#include "ringct/rctOps.h" +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "multisig.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +using namespace std; + +namespace cryptonote +{ + //----------------------------------------------------------------- + bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) + { + crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); + bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + + r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); + + crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); + + crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); + return true; + } + //----------------------------------------------------------------- + void generate_multisig_N_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) + { + // the multisig spend public key is the sum of all spend public keys + multisig_keys.clear(); + spend_pkey = rct::pk2rct(keys.m_account_address.m_spend_public_key); + for (const auto &k: spend_keys) + rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); + multisig_keys.push_back(keys.m_spend_secret_key); + spend_skey = rct::sk2rct(keys.m_spend_secret_key); + } + //----------------------------------------------------------------- + void generate_multisig_N1_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) + { + multisig_keys.clear(); + spend_pkey = rct::identity(); + spend_skey = rct::zero(); + + // create all our composite private keys + for (const auto &k: spend_keys) + { + rct::keyV data; + data.push_back(rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(keys.m_spend_secret_key))); + static const rct::key salt = { {'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 } }; + data.push_back(salt); + rct::key msk = rct::hash_to_scalar(data); + multisig_keys.push_back(rct::rct2sk(msk)); + sc_add(spend_skey.bytes, spend_skey.bytes, msk.bytes); + } + } + //----------------------------------------------------------------- + crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector &skeys) + { + crypto::hash hash; + crypto::cn_fast_hash(&skey, sizeof(crypto::hash), hash); + rct::key view_skey = rct::hash2rct(hash); + for (const auto &k: skeys) + sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes); + return rct::rct2sk(view_skey); + } + //----------------------------------------------------------------- + crypto::public_key generate_multisig_N1_N_spend_public_key(const std::vector &pkeys) + { + rct::key spend_public_key = rct::identity(); + for (const auto &pk: pkeys) + { + rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk)); + } + return rct::rct2pk(spend_public_key); + } + //----------------------------------------------------------------- + bool generate_multisig_key_image(const account_keys &keys, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) + { + if (multisig_key_index >= keys.m_multisig_keys.size()) + return false; + if (!cryptonote::generate_key_image_helper_old(keys, tx_public_key, real_output_index, in_ephemeral, ki)) + return false; + // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig + crypto::generate_key_image(in_ephemeral.pub, keys.m_multisig_keys[multisig_key_index], ki); + return true; + } + //----------------------------------------------------------------- + void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R) + { + rct::scalarmultBase((rct::key&)L, rct::sk2rct(k)); + crypto::generate_key_image(pkey, k, (crypto::key_image&)R); + } + //----------------------------------------------------------------- + bool generate_multisig_composite_key_image(const account_keys &keys, const crypto::public_key &tx_public_key, size_t real_output_index, const std::vector &pkis, crypto::key_image &ki) + { + cryptonote::keypair in_ephemeral; + if (!cryptonote::generate_key_image_helper_old(keys, tx_public_key, real_output_index, in_ephemeral, ki)) + return false; + std::unordered_set used; + for (size_t m = 0; m < keys.m_multisig_keys.size(); ++m) + { + crypto::key_image pki; + bool r = cryptonote::generate_multisig_key_image(keys, tx_public_key, real_output_index, in_ephemeral, pki, m); + if (!r) + return false; + used.insert(pki); + } + for (const auto &pki: pkis) + { + if (used.find(pki) == used.end()) + { + used.insert(pki); + rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki)); + } + } + return true; + } + //----------------------------------------------------------------- +} diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h new file mode 100644 index 000000000..c5312182b --- /dev/null +++ b/src/multisig/multisig.h @@ -0,0 +1,50 @@ +// Copyright (c) 2017, 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 +#include +#include "crypto/crypto.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "ringct/rctTypes.h" + +namespace cryptonote +{ + struct account_keys; + + bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki); + + void generate_multisig_N_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); + void generate_multisig_N1_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); + crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector &skeys); + crypto::public_key generate_multisig_N1_N_spend_public_key(const std::vector &pkeys); + bool generate_multisig_key_image(const account_keys &keys, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index); + void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R); + bool generate_multisig_composite_key_image(const account_keys &keys, const crypto::public_key &tx_public_key, size_t real_output_index, const std::vector &pkis, crypto::key_image &ki); +} diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index ab09ace91..b0aec186c 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -868,22 +868,9 @@ bool simple_wallet::finalize_multisig(const std::vector &args) return true; } - // parse all multisig info - std::unordered_set public_keys; - std::vector signers(args.size(), crypto::null_pkey); - for (size_t i = 0; i < args.size(); ++i) - { - if (!tools::wallet2::verify_extra_multisig_info(args[i], public_keys, signers[i])) - { - fail_msg_writer() << tr("Bad multisig info: ") << args[i]; - return true; - } - } - - // we have all pubkeys now try { - if (!m_wallet->finalize_multisig(orig_pwd_container->password(), public_keys, signers)) + if (!m_wallet->finalize_multisig(orig_pwd_container->password(), args)) { fail_msg_writer() << tr("Failed to finalize multisig"); return true; diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index ab48bd7a2..2d664ba15 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -51,6 +51,7 @@ monero_add_library(wallet ${wallet_private_headers}) target_link_libraries(wallet PUBLIC + multisig common cryptonote_core mnemonics @@ -104,6 +105,7 @@ if (BUILD_GUI_DEPS) set(libs_to_merge wallet_api wallet + multisig cryptonote_core cryptonote_basic mnemonics diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 807248860..2bba6f9e1 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -46,6 +46,7 @@ using namespace epee; #include "rpc/core_rpc_server_commands_defs.h" #include "misc_language.h" #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "multisig/multisig.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" @@ -526,24 +527,9 @@ uint8_t get_bulletproof_fork(bool testnet) return 255; // TODO } -bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) -{ - crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); - bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); - - r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); - - crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); - - crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); - return true; -} - bool wallet_generate_key_image_helper_old(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, bool multisig_export = false) { - if (!generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) + if (!cryptonote::generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) return false; if (multisig_export) { @@ -909,6 +895,12 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & } } //---------------------------------------------------------------------------------------------------- +bool wallet2::wallet_generate_key_image_helper_export(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) const +{ + THROW_WALLET_EXCEPTION_IF(multisig_key_index >= ack.m_multisig_keys.size(), error::wallet_internal_error, "Bad multisig_key_index"); + return cryptonote::generate_multisig_key_image(ack, tx_public_key, real_output_index, in_ephemeral, ki, multisig_key_index); +} +//---------------------------------------------------------------------------------------------------- void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map &tx_money_got_in_outs, std::vector &outs) { bool r; @@ -2804,29 +2796,11 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, rct::key spend_pkey, spend_skey; if (threshold == spend_keys.size() + 1) { - // the multisig spend public key is the sum of all spend public keys - spend_pkey = rct::pk2rct(get_account().get_keys().m_account_address.m_spend_public_key); - for (const auto &k: spend_keys) - rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); - multisig_keys.push_back(get_account().get_keys().m_spend_secret_key); - spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); + cryptonote::generate_multisig_N_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey); } else if (threshold == spend_keys.size()) { - spend_pkey = rct::identity(); - spend_skey = rct::zero(); - - // create all our composite private keys - for (const auto &k: spend_keys) - { - rct::keyV data; - data.push_back(rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(get_account().get_keys().m_spend_secret_key))); - static const rct::key salt = { {'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 } }; - data.push_back(salt); - rct::key msk = rct::hash_to_scalar(data); - multisig_keys.push_back(rct::rct2sk(msk)); - sc_add(spend_skey.bytes, spend_skey.bytes, msk.bytes); - } + cryptonote::generate_multisig_N1_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey); // We need an extra step, so we package all the composite public keys // we know about, and make a signed string out of them @@ -2856,13 +2830,10 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, // the multisig view key is shared by all, make one all can derive MINFO("Creating view key..."); - crypto::cn_fast_hash(&get_account().get_keys().m_view_secret_key, sizeof(crypto::secret_key), hash); - rct::key view_skey = rct::hash2rct(hash); - for (const auto &k: view_keys) - sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes); + crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(get_account().get_keys().m_view_secret_key, view_keys); MINFO("Creating multisig address..."); - CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys), + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(view_skey, rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys), "Failed to create multisig wallet due to bad keys"); m_account_public_address = m_account.get_keys().m_account_address; @@ -2916,15 +2887,12 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size"); - rct::key spend_public_key = rct::identity(); - for (const auto &pk: pkeys) - { - rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk)); - } + crypto::public_key spend_public_key = cryptonote::generate_multisig_N1_N_spend_public_key(std::vector(pkeys.begin(), pkeys.end())); + m_account_public_address.m_spend_public_key = spend_public_key; + m_account.finalize_multisig(spend_public_key); + m_multisig_signers = signers; std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)); }); - m_account_public_address.m_spend_public_key = rct::rct2pk(spend_public_key); - m_account.finalize_multisig(m_account_public_address.m_spend_public_key); if (!m_wallet_file.empty()) { @@ -2946,14 +2914,20 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor return true; } -bool wallet2::wallet_generate_key_image_helper_export(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) const +bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::vector &info) { - THROW_WALLET_EXCEPTION_IF(multisig_key_index >= ack.m_multisig_keys.size(), error::wallet_internal_error, "Bad multisig_key_index"); - if (!generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) - return false; - // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig - crypto::generate_key_image(in_ephemeral.pub, ack.m_multisig_keys[multisig_key_index], ki); - return true; + // parse all multisig info + std::unordered_set public_keys; + std::vector signers(info.size(), crypto::null_pkey); + for (size_t i = 0; i < info.size(); ++i) + { + if (!verify_extra_multisig_info(info[i], public_keys, signers[i])) + { + MERROR("Bad multisig info"); + return false; + } + } + return finalize_multisig(password, public_keys, signers); } std::string wallet2::get_multisig_info() const @@ -4657,7 +4631,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector &txids) +bool wallet2::sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids) { bool r = sign_multisig_tx(exported_txs, txids); if (!r) @@ -4684,7 +4658,7 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto LOG_PRINT_L1("Transactions rejected by callback"); return false; } - return sign_multisig_tx_from_file(exported_txs, filename, txids); + return sign_multisig_tx_to_file(exported_txs, filename, txids); } //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) @@ -5591,7 +5565,7 @@ void wallet2::transfer_selected_rct(std::vector used; - - // insert the ones we start from - for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) - { - crypto::key_image pki; - wallet_generate_key_image_helper_export(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, pki, m); - used.insert(pki); - } - + std::vector pkis; for (const auto &info: td.m_multisig_info) - { for (const auto &pki: info.m_partial_key_images) - { - // don't add duplicates again - if (used.find(pki) != used.end()) - continue; - used.insert(pki); - - rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki)); - } - } + pkis.push_back(pki); + bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), tx_key, td.m_internal_output_index, pkis, ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); return ki; } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 5f973fef5..8abc42ff3 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -474,6 +474,10 @@ namespace tools const std::vector &view_keys, const std::vector &spend_keys, uint32_t threshold); + /*! + * \brief Finalizes creation of a multisig wallet + */ + bool finalize_multisig(const epee::wipeable_string &password, const std::vector &info); /*! * \brief Finalizes creation of a multisig wallet */ @@ -642,7 +646,7 @@ namespace tools bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func = NULL); bool sign_multisig_tx_from_file(const std::string &filename, std::vector &txids, std::function accept_func); bool sign_multisig_tx(multisig_tx_set &exported_txs, std::vector &txids); - bool sign_multisig_tx_from_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids); + bool sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids); std::vector create_unmixable_sweep_transactions(bool trusted_daemon); bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000); void get_transfers(wallet2::transfer_container& incoming_transfers) const; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 472302a94..ac991d861 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2622,22 +2622,9 @@ namespace tools return false; } - // parse all multisig info - std::unordered_set public_keys; - std::vector signers(req.multisig_info.size(), crypto::null_pkey); - for (size_t i = 0; i < req.multisig_info.size(); ++i) - { - if (!m_wallet->verify_extra_multisig_info(req.multisig_info[i], public_keys, signers[i])) - { - er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO; - er.message = std::string("Bad multisig_info info: ") + req.multisig_info[i]; - return false; - } - } - try { - if (!m_wallet->finalize_multisig(req.password, public_keys, signers)) + if (!m_wallet->finalize_multisig(req.password, req.multisig_info)) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "Error calling finalize_multisig"; diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt index 68f2e9816..d80d62135 100644 --- a/tests/core_tests/CMakeLists.txt +++ b/tests/core_tests/CMakeLists.txt @@ -36,6 +36,7 @@ set(core_tests_sources chaingen_main.cpp double_spend.cpp integer_overflow.cpp + multisig.cpp ring_signature_1.cpp transaction_tests.cpp tx_validation.cpp @@ -52,6 +53,7 @@ set(core_tests_headers double_spend.h double_spend.inl integer_overflow.h + multisig.h ring_signature_1.h transaction_tests.h tx_validation.h @@ -63,6 +65,7 @@ add_executable(core_tests ${core_tests_headers}) target_link_libraries(core_tests PRIVATE + multisig cryptonote_core p2p version diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 0dcbc7f0c..939b88109 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -505,6 +505,54 @@ inline bool do_replay_file(const std::string& filename) cryptonote::account_base account; \ account.generate(); +#define GENERATE_MULTISIG_ACCOUNT(account, threshold, total) \ + CHECK_AND_ASSERT_MES(threshold >= 2 && threshold <= total, false, "Invalid multisig scheme"); \ + std::vector account(total); \ + do \ + { \ + for (size_t msidx = 0; msidx < total; ++msidx) \ + account[msidx].generate(); \ + std::unordered_set all_multisig_keys; \ + std::vector> view_keys(total); \ + std::vector> spend_keys(total); \ + for (size_t msidx = 0; msidx < total; ++msidx) \ + { \ + for (size_t msidx_inner = 0; msidx_inner < total; ++msidx_inner) \ + { \ + if (msidx_inner != msidx) \ + { \ + crypto::hash vkh; \ + crypto::cn_fast_hash(&account[msidx_inner].get_keys().m_view_secret_key, sizeof(crypto::secret_key), vkh); \ + view_keys[msidx].push_back((const crypto::secret_key&)vkh); \ + spend_keys[msidx].push_back(account[msidx_inner].get_keys().m_account_address.m_spend_public_key); \ + } \ + } \ + } \ + for (size_t msidx = 0; msidx < total; ++msidx) \ + { \ + std::vector multisig_keys; \ + crypto::secret_key spend_skey; \ + crypto::public_key spend_pkey; \ + if (threshold == total) \ + cryptonote::generate_multisig_N_N(account[msidx].get_keys(), spend_keys[msidx], multisig_keys, (rct::key&)spend_skey, (rct::key&)spend_pkey); \ + else \ + cryptonote::generate_multisig_N1_N(account[msidx].get_keys(), spend_keys[msidx], multisig_keys, (rct::key&)spend_skey, (rct::key&)spend_pkey); \ + crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(account[msidx].get_keys().m_view_secret_key, view_keys[msidx]); \ + account[msidx].make_multisig(view_skey, spend_skey, spend_pkey, multisig_keys); \ + for (const auto &k: multisig_keys) \ + all_multisig_keys.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(k)))); \ + } \ + if (threshold < total) \ + { \ + std::vector spend_public_keys; \ + for (const auto &k: all_multisig_keys) \ + spend_public_keys.push_back(k); \ + crypto::public_key spend_pkey = cryptonote::generate_multisig_N1_N_spend_public_key(spend_public_keys); \ + for (size_t msidx = 0; msidx < total; ++msidx) \ + account[msidx].finalize_multisig(spend_pkey); \ + } \ + } while(0) + #define MAKE_ACCOUNT(VEC_EVENTS, account) \ cryptonote::account_base account; \ account.generate(); \ diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 9eba347cd..a7643292b 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -199,6 +199,23 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_rct_tx_pre_rct_altered_extra); GENERATE_AND_PLAY(gen_rct_tx_rct_altered_extra); + GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2); + GENERATE_AND_PLAY(gen_multisig_tx_valid_22_2_1); + GENERATE_AND_PLAY(gen_multisig_tx_valid_33_1_23); + GENERATE_AND_PLAY(gen_multisig_tx_valid_33_3_21); + GENERATE_AND_PLAY(gen_multisig_tx_valid_23_1_2); + GENERATE_AND_PLAY(gen_multisig_tx_valid_23_1_3); + GENERATE_AND_PLAY(gen_multisig_tx_valid_23_2_1); + GENERATE_AND_PLAY(gen_multisig_tx_valid_23_2_3); + GENERATE_AND_PLAY(gen_multisig_tx_valid_45_1_234); + GENERATE_AND_PLAY(gen_multisig_tx_valid_89_3_1245789); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_23_1__no_threshold); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_45_5_23_no_threshold); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_22_1__no_threshold); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_33_1__no_threshold); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_33_1_2_no_threshold); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_33_1_3_no_threshold); + el::Level level = (failed_tests.empty() ? el::Level::Info : el::Level::Error); MLOG(level, "\nREPORT:"); MLOG(level, " Test run: " << tests_count); diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen_tests_list.h index 26f88dddb..b9bbf03b4 100644 --- a/tests/core_tests/chaingen_tests_list.h +++ b/tests/core_tests/chaingen_tests_list.h @@ -41,6 +41,7 @@ #include "tx_validation.h" #include "v2_tests.h" #include "rct.h" +#include "multisig.h" /************************************************************************/ /* */ /************************************************************************/ diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp new file mode 100644 index 000000000..484b8b7ab --- /dev/null +++ b/tests/core_tests/multisig.cpp @@ -0,0 +1,460 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "ringct/rctSigs.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "multisig/multisig.h" +#include "chaingen.h" +#include "multisig.h" + +using namespace epee; +using namespace crypto; +using namespace cryptonote; + +//#define NO_MULTISIG + +//---------------------------------------------------------------------------------------------------------------------- +// Tests + +bool gen_multisig_tx_validation_base::generate_with(std::vector& events, + int mixin, uint64_t amount_paid, bool valid, + size_t threshold, size_t total, size_t creator, std::vector signers, + const std::function &sources, std::vector &destinations)> &pre_tx, + const std::function &post_tx) const +{ + uint64_t ts_start = 1338224400; + bool r; + + CHECK_AND_ASSERT_MES(total >= 2, false, "Bad scheme"); + CHECK_AND_ASSERT_MES(threshold <= total, false, "Bad scheme"); + CHECK_AND_ASSERT_MES(threshold >= total - 1, false, "Unsupported scheme"); +#ifdef NO_MULTISIG + CHECK_AND_ASSERT_MES(total <= 5, false, "Unsupported scheme"); +#endif + + // given as 1 based for clarity + --creator; + for (size_t &signer: signers) + --signer; + + CHECK_AND_ASSERT_MES(creator < total, false, "invalid creator"); + for (size_t signer: 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); + + // create 8 miner accounts, and have them mine the next 8 blocks + // they will have a coinbase with a single out that's pseudo rct + const size_t n_coinbases = 8; + cryptonote::account_base miner_accounts[n_coinbases]; + const cryptonote::block *prev_block = &blk_0; + cryptonote::block blocks[n_coinbases]; + for (size_t n = 0; n < n_coinbases; ++n) { + // the first block goes to the multisig account + miner_accounts[n].generate(); + account_base &account = n == 0 ? miner_account[creator] : miner_accounts[n]; + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, account, + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs, + 4, 4, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + crypto::hash(), 0, transaction(), std::vector(), 0, 1, 4), + false, "Failed to generate block"); + events.push_back(blocks[n]); + prev_block = blocks + n; + LOG_PRINT_L0("Initial miner tx " << n << ": " << obj_to_json_str(blocks[n].miner_tx)); + LOG_PRINT_L0("in block: " << obj_to_json_str(blocks[n])); + } + + // rewind + cryptonote::block blk_r, blk_last; + { + blk_last = blocks[n_coinbases - 1]; + for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) + { + cryptonote::block blk; + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_accounts[0], + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs, + 4, 4, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + crypto::hash(), 0, transaction(), std::vector(), 0, 1, 4), + false, "Failed to generate block"); + events.push_back(blk); + blk_last = blk; + } + blk_r = blk_last; + } + + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(blocks[0].miner_tx); + MDEBUG("tx_pub_key: " << tx_pub_key); + const crypto::public_key output_pub_key = boost::get(blocks[0].miner_tx.vout[0].target).key; + MDEBUG("output_pub_key: " << output_pub_key); + cryptonote::keypair in_ephemeral; + +#ifndef NO_MULTISIG + // create k/L/R/ki for that output we're going to spend + std::vector> account_k(total); + std::vector> account_L(total); + std::vector> account_R(total); + std::vector> account_ki(total); + for (size_t msidx = 0; msidx < total; ++msidx) + { + size_t nlr = threshold < total ? threshold - 1 : 1; + account_L[msidx].resize(nlr); + account_R[msidx].resize(nlr); + for (size_t n = 0; n < nlr; ++n) + { + account_k[msidx].push_back(rct::rct2sk(rct::skGen())); + cryptonote::generate_multisig_LR(output_pub_key, account_k[msidx][n], account_L[msidx][n], account_R[msidx][n]); + } + size_t numki = miner_account[msidx].get_multisig_keys().size(); + account_ki[msidx].resize(numki); + for (size_t kiidx = 0; kiidx < numki; ++kiidx) + { + r = cryptonote::generate_multisig_key_image(miner_account[msidx].get_keys(), tx_pub_key, 0, in_ephemeral, account_ki[msidx][kiidx], kiidx); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate multisig export key image"); + } + MDEBUG("Party " << msidx << ":"); + MDEBUG("spend: sec " << miner_account[msidx].get_keys().m_spend_secret_key << ", pub " << miner_account[msidx].get_keys().m_account_address.m_spend_public_key); + MDEBUG("view: sec " << miner_account[msidx].get_keys().m_view_secret_key << ", pub " << miner_account[msidx].get_keys().m_account_address.m_view_public_key); + for (const auto &k: miner_account[msidx].get_multisig_keys()) + MDEBUG("msk: " << k); + for (size_t n = 0; n < account_k[msidx].size(); ++n) + { + MDEBUG("k: " << account_k[msidx][n]); + MDEBUG("L: " << account_L[msidx][n]); + MDEBUG("R: " << account_R[msidx][n]); + } + for (const auto &ki: account_ki[msidx]) + MDEBUG("ki: " << ki); + } +#endif + + // create kLRki + rct::multisig_kLRki kLRki; +#ifdef NO_MULTISIG + kLRki = {rct::zero(), rct::zero(), rct::zero(), rct::zero()}; +#else + kLRki.k = rct::sk2rct(account_k[creator][0]); + kLRki.L = rct::pk2rct(account_L[creator][0]); + kLRki.R = rct::pk2rct(account_R[creator][0]); + MDEBUG("Starting with k " << kLRki.k); + MDEBUG("Starting with L " << kLRki.L); + MDEBUG("Starting with R " << kLRki.R); + std::unordered_set used_L; + 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].size(); ++lr) + { + if (used_L.find(account_L[msidx][lr]) == used_L.end()) + { + used_L.insert(account_L[msidx][lr]); + MDEBUG("Adding L " << account_L[msidx][lr] << " (for k " << account_k[msidx][lr] << ")"); + MDEBUG("Adding R " << account_R[msidx][lr]); + rct::addKeys((rct::key&)kLRki.L, kLRki.L, rct::pk2rct(account_L[msidx][lr])); + rct::addKeys((rct::key&)kLRki.R, kLRki.R, rct::pk2rct(account_R[msidx][lr])); + break; + } + } + } + std::vector pkis; + for (size_t msidx = 0; msidx < total; ++msidx) + for (size_t n = 0; n < account_ki[msidx].size(); ++n) + pkis.push_back(account_ki[msidx][n]); + r = cryptonote::generate_multisig_composite_key_image(miner_account[0].get_keys(), tx_pub_key, 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; + r = cryptonote::generate_multisig_composite_key_image(miner_account[n].get_keys(), tx_pub_key, 0, pkis, (crypto::key_image&)ki); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image"); + 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 + std::vector sources; + sources.resize(1); + tx_source_entry& src = sources.back(); + + src.real_output = 0; + src.amount = blocks[0].miner_tx.vout[0].amount; + src.real_out_tx_key = tx_pub_key; + src.real_output_in_tx_index = 0; + src.mask = rct::identity(); + src.rct = true; + src.multisig_kLRki = kLRki; + + for (int m = 0; m <= mixin; ++m) + { + rct::ctkey ctkey; + ctkey.dest = rct::pk2rct(boost::get(blocks[m].miner_tx.vout[0].target).key); + MDEBUG("using " << (m == 0 ? "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)); + } + + //fill outputs entry + tx_destination_entry td; + td.addr = miner_account[creator].get_keys().m_account_address; + td.amount = amount_paid; + std::vector destinations; + destinations.push_back(td); + + if (pre_tx) + pre_tx(sources, destinations); + + 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::unordered_map subaddresses; + subaddresses[miner_account[creator].get_keys().m_account_address.m_spend_public_key] = {0,0}; + std::vector additional_tx_keys; + r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector(), tx, 0, tx_key, additional_tx_keys, true, false, msoutp); + CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); + +#ifndef NO_MULTISIG + // sign + std::unordered_set used_keys; + const std::vector &msk0 = miner_account[creator].get_multisig_keys(); + for (const auto &sk: msk0) + used_keys.insert(sk); + for (size_t signer: signers) + { + rct::key skey = rct::zero(); + const std::vector &msk1 = miner_account[signer].get_multisig_keys(); + for (size_t n = 0; n < msk1.size(); ++n) + { + const crypto::secret_key &sk1 = msk1[n]; + if (used_keys.find(sk1) == used_keys.end()) + { + used_keys.insert(sk1); + sc_add(skey.bytes, skey.bytes, rct::sk2rct(sk1).bytes); + } + } + CHECK_AND_ASSERT_MES(!(skey == rct::zero()), false, "failed to find secret multisig key to sign transaction"); + std::vector indices; + for (const auto &src: sources) + indices.push_back(src.real_output); + rct::keyV k; + k.push_back(rct::zero()); + for (size_t n = 0; n < account_k[signer].size(); ++n) + { + crypto::public_key L; + rct::scalarmultBase((rct::key&)L, rct::sk2rct(account_k[signer][n])); + if (used_L.find(L) != used_L.end()) + { + sc_add(k.back().bytes, k.back().bytes, rct::sk2rct(account_k[signer][n]).bytes); + } + } + CHECK_AND_ASSERT_MES(!(k.back() == rct::zero()), false, "failed to find k to sign transaction"); + + MDEBUG("signing with k size " << k.size()); + MDEBUG("signing with k " << k.back()); + 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"); + } +#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); + crypto::key_derivation derivation; + r = crypto::generate_key_derivation(tx_pub_key2, miner_account[creator].get_keys().m_view_secret_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate derivation"); + uint64_t n_outs = 0, amount = 0; + std::vector additional_derivations; + 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(tx.vout[n].target).key, derivation, additional_derivations, n)) + { + ++n_outs; + CHECK_AND_ASSERT_MES(tx.vout[n].amount == 0, false, "Destination amount is not zero"); + rct::key Ctmp; + crypto::secret_key scalar1; + crypto::derivation_to_scalar(derivation, n, scalar1); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); + rct::key C = tx.rct_signatures.outPk[n].mask; + rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); + CHECK_AND_ASSERT_MES(rct::equalKeys(C, Ctmp), false, "Failed to decode amount"); + amount += rct::h2d(ecdh_info.amount); + } + } + CHECK_AND_ASSERT_MES(n_outs == 1, false, "Not exactly 1 output was received"); + CHECK_AND_ASSERT_MES(amount == amount_paid, false, "Amount paid was not the expected amount"); + + if (post_tx) + post_tx(tx); + + if (!valid) + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(tx); + LOG_PRINT_L0("Test tx: " << obj_to_json_str(tx)); + + return true; +} + +bool gen_multisig_tx_valid_22_1_2::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); +} + +bool gen_multisig_tx_valid_22_2_1::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, true, 2, 2, 2, {1}, NULL, NULL); +} + +bool gen_multisig_tx_valid_33_1_23::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, true, 3, 3, 1, {2, 3}, NULL, NULL); +} + +bool gen_multisig_tx_valid_33_3_21::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, true, 3, 3, 3, {2, 1}, NULL, NULL); +} + +bool gen_multisig_tx_valid_23_1_2::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, true, 2, 3, 1, {2}, NULL, NULL); +} + +bool gen_multisig_tx_valid_23_1_3::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, true, 2, 3, 1, {3}, NULL, NULL); +} + +bool gen_multisig_tx_valid_23_2_1::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, true, 2, 3, 2, {1}, NULL, NULL); +} + +bool gen_multisig_tx_valid_23_2_3::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, true, 2, 3, 2, {3}, NULL, NULL); +} + +bool gen_multisig_tx_valid_45_1_234::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, true, 4, 5, 1, {2, 3, 4}, NULL, NULL); +} + +bool gen_multisig_tx_valid_89_3_1245789::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, true, 8, 9, 3, {1, 2, 4, 5, 7, 8, 9}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_22_1__no_threshold::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, false, 2, 2, 1, {}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_33_1__no_threshold::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, false, 3, 3, 1, {}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_33_1_2_no_threshold::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, false, 3, 3, 1, {2}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_33_1_3_no_threshold::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, false, 3, 3, 1, {3}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_23_1__no_threshold::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, false, 2, 3, 1, {}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_45_5_23_no_threshold::generate(std::vector& events) const +{ + const int mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, mixin, amount_paid, false, 4, 5, 5, {2, 3}, NULL, NULL); +} diff --git a/tests/core_tests/multisig.h b/tests/core_tests/multisig.h new file mode 100644 index 000000000..735c3b73d --- /dev/null +++ b/tests/core_tests/multisig.h @@ -0,0 +1,187 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include "chaingen.h" + +struct gen_multisig_tx_validation_base : public test_chain_unit_base +{ + gen_multisig_tx_validation_base() + : m_invalid_tx_index(0) + , m_invalid_block_index(0) + { + REGISTER_CALLBACK_METHOD(gen_multisig_tx_validation_base, mark_invalid_tx); + REGISTER_CALLBACK_METHOD(gen_multisig_tx_validation_base, mark_invalid_block); + } + + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/) + { + if (m_invalid_tx_index == event_idx) + return tvc.m_verifivation_failed; + else + return !tvc.m_verifivation_failed && tx_added; + } + + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/) + { + if (m_invalid_block_index == event_idx) + return bvc.m_verifivation_failed; + else + return !bvc.m_verifivation_failed; + } + + bool mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector& /*events*/) + { + m_invalid_block_index = ev_index + 1; + return true; + } + + bool mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector& /*events*/) + { + m_invalid_tx_index = ev_index + 1; + return true; + } + + bool generate_with(std::vector& events, int mixin, + uint64_t amount_paid, bool valid, + size_t threshold, size_t total, size_t creator, std::vector signers, + const std::function &sources, std::vector &destinations)> &pre_tx, + const std::function &post_tx) const; + +private: + size_t m_invalid_tx_index; + size_t m_invalid_block_index; +}; + +template<> +struct get_test_options { + const std::pair hard_forks[3] = {std::make_pair(1, 0), std::make_pair(4, 1), std::make_pair(0, 0)}; + const cryptonote::test_options test_options = { + hard_forks + }; +}; + +// valid +struct gen_multisig_tx_valid_22_1_2: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_valid_22_2_1: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_valid_33_1_23: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_valid_33_3_21: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_valid_23_1_2: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_valid_23_1_3: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_valid_23_2_1: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_valid_23_2_3: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_valid_45_1_234: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; + +template<> struct get_test_options: public get_test_options {}; +struct gen_multisig_tx_valid_89_3_1245789: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +// invalid +struct gen_multisig_tx_invalid_22_1__no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_invalid_33_1__no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_invalid_33_1_2_no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_invalid_33_1_3_no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_invalid_23_1__no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_invalid_45_5_23_no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; From 265290388bd2134108d689818518f7d9c830292c Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 1 Oct 2017 14:06:54 +0100 Subject: [PATCH 09/19] wallet: guard against partly initialized multisig wallet --- src/simplewallet/simplewallet.cpp | 47 ++++++++++++++++---- src/wallet/wallet2.cpp | 4 +- src/wallet/wallet2.h | 2 +- src/wallet/wallet_rpc_server.cpp | 29 ++++++++++-- src/wallet/wallet_rpc_server_commands_defs.h | 2 + tests/unit_tests/multisig.cpp | 17 ++++--- 6 files changed, 82 insertions(+), 19 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index b0aec186c..40226ec34 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -849,11 +849,17 @@ bool simple_wallet::make_multisig(const std::vector &args) bool simple_wallet::finalize_multisig(const std::vector &args) { - if (!m_wallet->multisig()) + bool ready; + if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); return true; } + if (ready) + { + fail_msg_writer() << tr("This wallet is already finalized"); + return true; + } const auto orig_pwd_container = get_and_verify_password(); if(orig_pwd_container == boost::none) @@ -887,11 +893,17 @@ bool simple_wallet::finalize_multisig(const std::vector &args) bool simple_wallet::export_multisig(const std::vector &args) { - if (!m_wallet->multisig()) + bool ready; + if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); return true; } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } if (args.size() != 1) { fail_msg_writer() << tr("usage: export_multisig_info "); @@ -937,12 +949,18 @@ bool simple_wallet::export_multisig(const std::vector &args) bool simple_wallet::import_multisig(const std::vector &args) { + bool ready; uint32_t threshold, total; - if (!m_wallet->multisig(&threshold, &total)) + if (!m_wallet->multisig(&ready, &threshold, &total)) { fail_msg_writer() << tr("This wallet is not multisig"); return true; } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } if (args.size() < threshold - 1) { fail_msg_writer() << tr("usage: import_multisig_info [...] - one for each other participant"); @@ -1065,11 +1083,17 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) bool simple_wallet::sign_multisig(const std::vector &args) { - if(!m_wallet->multisig()) + bool ready; + if(!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This is not a multisig wallet"); return true; } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } if (args.size() != 1) { fail_msg_writer() << tr("usage: sign_multisig "); @@ -1103,7 +1127,7 @@ bool simple_wallet::sign_multisig(const std::vector &args) if (txids.empty()) { uint32_t threshold; - m_wallet->multisig(&threshold); + m_wallet->multisig(NULL, &threshold); uint32_t signers_needed = threshold - signers - 1; success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", " << signers_needed << " more signer(s) needed"; @@ -1126,12 +1150,18 @@ bool simple_wallet::sign_multisig(const std::vector &args) bool simple_wallet::submit_multisig(const std::vector &args) { + bool ready; uint32_t threshold; - if (!m_wallet->multisig(&threshold)) + if (!m_wallet->multisig(&ready, &threshold)) { fail_msg_writer() << tr("This is not a multisig wallet"); return true; } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } if (args.size() != 1) { fail_msg_writer() << tr("usage: submit_multisig "); @@ -2717,11 +2747,12 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) } std::string prefix; + bool ready; uint32_t threshold, total; if (m_wallet->watch_only()) prefix = tr("Opened watch-only wallet"); - else if (m_wallet->multisig(&threshold, &total)) - prefix = (boost::format(tr("Opened %u/%u multisig wallet")) % threshold % total).str(); + else if (m_wallet->multisig(&ready, &threshold, &total)) + prefix = (boost::format(tr("Opened %u/%u multisig wallet%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); else prefix = tr("Opened wallet"); message_writer(console_color_white, true) << diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 2bba6f9e1..9c2587f25 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3039,7 +3039,7 @@ bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered return true; } -bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const +bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const { if (!m_multisig) return false; @@ -3047,6 +3047,8 @@ bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const *threshold = m_multisig_threshold; if (total) *total = m_multisig_signers.size(); + if (ready) + *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())); return true; } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 8abc42ff3..79199a30c 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -598,7 +598,7 @@ namespace tools bool testnet() const { return m_testnet; } bool restricted() const { return m_restricted; } bool watch_only() const { return m_watch_only; } - bool multisig(uint32_t *threshold = NULL, uint32_t *total = NULL) const; + bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; bool has_multisig_partial_key_images() const; // locked & unlocked balance of given or current subaddress account diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index ac991d861..043890fd1 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2342,7 +2342,7 @@ namespace tools bool wallet_rpc_server::on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); - res.multisig = m_wallet->multisig(&res.threshold, &res.total); + res.multisig = m_wallet->multisig(&res.ready, &res.threshold, &res.total); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2468,12 +2468,19 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } - if (!m_wallet->multisig()) + bool ready; + if (!m_wallet->multisig(&ready)) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is not multisig"; return false; } + if (!ready) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is multisig, but not yet finalized"; + return false; + } std::vector info; try @@ -2514,13 +2521,20 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + bool ready; uint32_t threshold, total; - if (!m_wallet->multisig(&threshold, &total)) + if (!m_wallet->multisig(&ready, &threshold, &total)) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is not multisig"; return false; } + if (!ready) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is multisig, but not yet finalized"; + return false; + } if (req.info.size() < threshold - 1) { @@ -2607,13 +2621,20 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + bool ready; uint32_t threshold, total; - if (!m_wallet->multisig(&threshold, &total)) + if (!m_wallet->multisig(&ready, &threshold, &total)) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is not multisig"; return false; } + if (ready) + { + er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; + er.message = "This wallet is multisig, and already finalized"; + return false; + } if (req.multisig_info.size() < threshold - 1) { diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index d17af9980..bff4fdd7c 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1499,11 +1499,13 @@ namespace wallet_rpc struct response { bool multisig; + bool ready; uint32_t threshold; uint32_t total; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(multisig) + KV_SERIALIZE(ready) KV_SERIALIZE(threshold) KV_SERIALIZE(total) END_KV_SERIALIZE_MAP() diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp index 0e8a6b168..aab1ce420 100644 --- a/tests/unit_tests/multisig.cpp +++ b/tests/unit_tests/multisig.cpp @@ -94,11 +94,14 @@ static void make_M_2_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, un ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet1.get_account().get_public_address_str(true)); + bool ready; uint32_t threshold, total; - ASSERT_TRUE(wallet0.multisig(&threshold, &total)); + ASSERT_TRUE(wallet0.multisig(&ready, &threshold, &total)); + ASSERT_TRUE(ready); ASSERT_TRUE(threshold == M); ASSERT_TRUE(total == 2); - ASSERT_TRUE(wallet1.multisig(&threshold, &total)); + ASSERT_TRUE(wallet1.multisig(&ready, &threshold, &total)); + ASSERT_TRUE(ready); ASSERT_TRUE(threshold == M); ASSERT_TRUE(total == 2); } @@ -149,14 +152,18 @@ static void make_M_3_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, to ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet1.get_account().get_public_address_str(true)); ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet2.get_account().get_public_address_str(true)); + bool ready; uint32_t threshold, total; - ASSERT_TRUE(wallet0.multisig(&threshold, &total)); + ASSERT_TRUE(wallet0.multisig(&ready, &threshold, &total)); + ASSERT_TRUE(ready); ASSERT_TRUE(threshold == M); ASSERT_TRUE(total == 3); - ASSERT_TRUE(wallet1.multisig(&threshold, &total)); + ASSERT_TRUE(wallet1.multisig(&ready, &threshold, &total)); + ASSERT_TRUE(ready); ASSERT_TRUE(threshold == M); ASSERT_TRUE(total == 3); - ASSERT_TRUE(wallet2.multisig(&threshold, &total)); + ASSERT_TRUE(wallet2.multisig(&ready, &threshold, &total)); + ASSERT_TRUE(ready); ASSERT_TRUE(threshold == M); ASSERT_TRUE(total == 3); } From 7f4c220b70b5ef46bd80e4530a3581063d6cdbd5 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 6 Oct 2017 11:24:46 +0100 Subject: [PATCH 10/19] simplewallet: add multisig to wallet type in wallet_info output --- src/simplewallet/simplewallet.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 40226ec34..505237027 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -5987,10 +5987,20 @@ bool simple_wallet::status(const std::vector &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::wallet_info(const std::vector &args) { + bool ready; + uint32_t threshold, total; + message_writer() << tr("Filename: ") << m_wallet->get_wallet_file(); message_writer() << tr("Description: ") << m_wallet->get_description(); message_writer() << tr("Address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); - message_writer() << tr("Watch only: ") << (m_wallet->watch_only() ? tr("Yes") : tr("No")); + std::string type; + if (m_wallet->watch_only()) + type = tr("Watch only"); + else if (m_wallet->multisig(&ready, &threshold, &total)) + type = (boost::format(tr("%u/%u multisig%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); + else + type = tr("Normal"); + message_writer() << tr("Type: ") << type; message_writer() << tr("Testnet: ") << (m_wallet->testnet() ? tr("Yes") : tr("No")); return true; } From dffa0dceaf54e00266525d2f3edd18cce05d0e16 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 7 Oct 2017 21:20:28 +0100 Subject: [PATCH 11/19] simplewallet: add export_raw_multisig command It exports raw transactions, so they may be used by other tools, for instance to be relayed to the network externally. --- src/simplewallet/simplewallet.cpp | 73 +++++++++++++++++++++++++++++++ src/simplewallet/simplewallet.h | 1 + 2 files changed, 74 insertions(+) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 505237027..98d7ea14f 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1210,6 +1210,75 @@ bool simple_wallet::submit_multisig(const std::vector &args) return true; } +bool simple_wallet::export_raw_multisig(const std::vector &args) +{ + bool ready; + uint32_t threshold; + if (!m_wallet->multisig(&ready, &threshold)) + { + fail_msg_writer() << tr("This is not a multisig wallet"); + return true; + } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: export_raw_multisig "); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + std::string filename = args[0]; + try + { + tools::wallet2::multisig_tx_set txs; + bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from file"); + return true; + } + if (txs.m_signers.size() < threshold) + { + fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) + % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); + return true; + } + + // save the transactions + std::string filenames; + for (auto &ptx: txs.m_ptx) + { + const crypto::hash txid = cryptonote::get_transaction_hash(ptx.tx); + const std::string filename = std::string("raw_multisig_monero_tx_") + epee::string_tools::pod_to_hex(txid); + if (!filenames.empty()) + filenames += ", "; + filenames += filename; + if (!epee::file_io_utils::save_string_to_file(filename, cryptonote::tx_to_blob(ptx.tx))) + { + fail_msg_writer() << tr("Failed to export multisig transaction to file ") << filename; + return true; + } + } + success_msg_writer() << tr("Saved exported multisig transaction file(s): ") << filenames; + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + fail_msg_writer() << tr("unknown error"); + } + + return true; +} + bool simple_wallet::set_always_confirm_transfers(const std::vector &args/* = std::vector()*/) { const auto pwd_container = get_and_verify_password(); @@ -1813,6 +1882,10 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::submit_multisig, this, _1), tr("submit_multisig "), tr("Submit a signed multisig transaction from a file")); + m_cmd_binder.set_handler("export_raw_multisig_tx", + boost::bind(&simple_wallet::export_raw_multisig, this, _1), + tr("export_raw_multisig "), + tr("Export a signed multisig transaction to a file")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("help []"), diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 73b3456db..c0c33f6b8 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -196,6 +196,7 @@ namespace cryptonote bool accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs); bool sign_multisig(const std::vector& args); bool submit_multisig(const std::vector& args); + bool export_raw_multisig(const std::vector& args); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr); From fa5697127f5a99ddd20311cec8180f6a89b31ceb Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 21 Oct 2017 12:14:31 +0100 Subject: [PATCH 12/19] make multisig work with subaddresses Thanks to kenshi84 for help getting this work --- .../cryptonote_format_utils.cpp | 53 +++++++++++++++++-- src/cryptonote_core/cryptonote_tx_utils.cpp | 17 ++---- src/cryptonote_core/cryptonote_tx_utils.h | 6 ++- src/multisig/multisig.cpp | 28 ++-------- src/multisig/multisig.h | 6 +-- src/wallet/wallet2.cpp | 42 +++++---------- src/wallet/wallet2.h | 1 - tests/core_tests/multisig.cpp | 18 ++++--- 8 files changed, 89 insertions(+), 82 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index a22c3bdea..8f7ab94db 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -80,6 +80,31 @@ static std::atomic tx_hashes_cached_count(0); static std::atomic block_hashes_calculated_count(0); static std::atomic block_hashes_cached_count(0); +#define CHECK_AND_ASSERT_THROW_MES_L1(expr, message) {if(!(expr)) {MWARNING(message); throw std::runtime_error(message);}} + +namespace cryptonote +{ + static inline unsigned char *operator &(ec_point &point) { + return &reinterpret_cast(point); + } + static inline const unsigned char *operator &(const ec_point &point) { + return &reinterpret_cast(point); + } + + // a copy of rct::addKeys, since we can't link to libringct to avoid circular dependencies + static void add_public_key(crypto::public_key &AB, const crypto::public_key &A, const crypto::public_key &B) { + ge_p3 B2, A2; + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, &B) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A2, &A) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast(__LINE__)); + ge_cached tmp2; + ge_p3_to_cached(&tmp2, &B2); + ge_p1p1 tmp3; + ge_add(&tmp3, &A2, &tmp2); + ge_p1p1_to_p3(&A2, &tmp3); + ge_p3_tobytes(&AB, &A2); + } +} + namespace cryptonote { //--------------------------------------------------------------- @@ -182,6 +207,7 @@ namespace cryptonote crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b // step 2: add Hs(a || index_major || index_minor) + crypto::secret_key subaddr_sk; crypto::secret_key scalar_step2; if (received_index.is_zero()) { @@ -189,13 +215,32 @@ namespace cryptonote } else { - crypto::secret_key m = get_subaddress_secret_key(ack.m_view_secret_key, received_index); - sc_add((unsigned char*)&scalar_step2, (unsigned char*)&scalar_step1, (unsigned char*)&m); + subaddr_sk = get_subaddress_secret_key(ack.m_view_secret_key, received_index); + sc_add((unsigned char*)&scalar_step2, (unsigned char*)&scalar_step1, (unsigned char*)&subaddr_sk); } in_ephemeral.sec = scalar_step2; - crypto::secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key, false, "key image helper precomp: given output pubkey doesn't match the derived one"); + + if (ack.m_multisig_keys.empty()) + { + // when not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase + CHECK_AND_ASSERT_MES(crypto::secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub), false, "Failed to derive public key"); + } + else + { + // when in multisig, we only know the partial spend secret key. but we do know the full spend public key, so the output pubkey can be obtained by using the standard CN key derivation + CHECK_AND_ASSERT_MES(crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub), false, "Failed to derive public key"); + // and don't forget to add the contribution from the subaddress part + if (!received_index.is_zero()) + { + crypto::public_key subaddr_pk; + CHECK_AND_ASSERT_MES(crypto::secret_key_to_public_key(subaddr_sk, subaddr_pk), false, "Failed to derive public key"); + add_public_key(in_ephemeral.pub, in_ephemeral.pub, subaddr_pk); + } + } + + CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key, + false, "key image helper precomp: given output pubkey doesn't match the derived one"); } crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index fb1f972b3..89f24a4d4 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -269,24 +269,15 @@ namespace cryptonote in_contexts.push_back(input_generation_context_data()); keypair& in_ephemeral = in_contexts.back().in_ephemeral; crypto::key_image img; - bool r; - if (msout) - { - r = generate_key_image_helper_old(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img); - } - else - { - const auto& out_key = reinterpret_cast(src_entr.outputs[src_entr.real_output].second.dest); - r = generate_key_image_helper(sender_account_keys, subaddresses, out_key, src_entr.real_out_tx_key, src_entr.real_out_additional_tx_keys, src_entr.real_output_in_tx_index, in_ephemeral, img); - } - if (!r) + const auto& out_key = reinterpret_cast(src_entr.outputs[src_entr.real_output].second.dest); + if(!generate_key_image_helper(sender_account_keys, subaddresses, out_key, src_entr.real_out_tx_key, src_entr.real_out_additional_tx_keys, src_entr.real_output_in_tx_index, in_ephemeral, img)) { LOG_ERROR("Key image generation failed!"); return false; } - //check that derivated key is equal with real output key - if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) + //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) ) { 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:" diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 2b6cf26db..5947522e2 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -64,6 +64,7 @@ namespace cryptonote FIELD(amount) FIELD(rct) FIELD(mask) + FIELD(multisig_kLRki) if (real_output >= outputs.size()) return false; @@ -100,7 +101,7 @@ namespace cryptonote } -BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 0) +BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 1) BOOST_CLASS_VERSION(cryptonote::tx_destination_entry, 1) namespace boost @@ -117,7 +118,10 @@ namespace boost a & x.amount; a & x.rct; a & x.mask; + if (ver < 1) + return; a & x.multisig_kLRki; + a & x.real_out_additional_tx_keys; } template diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp index 0a9933b13..a99f66e64 100644 --- a/src/multisig/multisig.cpp +++ b/src/multisig/multisig.cpp @@ -41,21 +41,6 @@ using namespace std; namespace cryptonote { - //----------------------------------------------------------------- - bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) - { - crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); - bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); - - r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); - - crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); - - crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); - return true; - } //----------------------------------------------------------------- void generate_multisig_N_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) { @@ -107,14 +92,11 @@ namespace cryptonote return rct::rct2pk(spend_public_key); } //----------------------------------------------------------------- - bool generate_multisig_key_image(const account_keys &keys, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) + bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki) { if (multisig_key_index >= keys.m_multisig_keys.size()) return false; - if (!cryptonote::generate_key_image_helper_old(keys, tx_public_key, real_output_index, in_ephemeral, ki)) - return false; - // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig - crypto::generate_key_image(in_ephemeral.pub, keys.m_multisig_keys[multisig_key_index], ki); + crypto::generate_key_image(out_key, keys.m_multisig_keys[multisig_key_index], ki); return true; } //----------------------------------------------------------------- @@ -124,16 +106,16 @@ namespace cryptonote crypto::generate_key_image(pkey, k, (crypto::key_image&)R); } //----------------------------------------------------------------- - bool generate_multisig_composite_key_image(const account_keys &keys, const crypto::public_key &tx_public_key, size_t real_output_index, const std::vector &pkis, crypto::key_image &ki) + bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector& additional_tx_public_keys, size_t real_output_index, const std::vector &pkis, crypto::key_image &ki) { cryptonote::keypair in_ephemeral; - if (!cryptonote::generate_key_image_helper_old(keys, tx_public_key, real_output_index, in_ephemeral, ki)) + if (!cryptonote::generate_key_image_helper(keys, subaddresses, out_key, tx_public_key, additional_tx_public_keys, real_output_index, in_ephemeral, ki)) return false; std::unordered_set used; for (size_t m = 0; m < keys.m_multisig_keys.size(); ++m) { crypto::key_image pki; - bool r = cryptonote::generate_multisig_key_image(keys, tx_public_key, real_output_index, in_ephemeral, pki, m); + bool r = cryptonote::generate_multisig_key_image(keys, m, out_key, pki); if (!r) return false; used.insert(pki); diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h index c5312182b..5cb469c1b 100644 --- a/src/multisig/multisig.h +++ b/src/multisig/multisig.h @@ -38,13 +38,11 @@ namespace cryptonote { struct account_keys; - bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki); - void generate_multisig_N_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); void generate_multisig_N1_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector &skeys); crypto::public_key generate_multisig_N1_N_spend_public_key(const std::vector &pkeys); - bool generate_multisig_key_image(const account_keys &keys, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index); + bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki); void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R); - bool generate_multisig_composite_key_image(const account_keys &keys, const crypto::public_key &tx_public_key, size_t real_output_index, const std::vector &pkis, crypto::key_image &ki); + bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector& additional_tx_public_keys, size_t real_output_index, const std::vector &pkis, crypto::key_image &ki); } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 9c2587f25..4ad6d852d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -527,18 +527,6 @@ uint8_t get_bulletproof_fork(bool testnet) return 255; // TODO } -bool wallet_generate_key_image_helper_old(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, bool multisig_export = false) -{ - if (!cryptonote::generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) - return false; - if (multisig_export) - { - // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig - crypto::generate_key_image(in_ephemeral.pub, ack.m_spend_secret_key, ki); - } - return true; -} - crypto::hash8 get_short_payment_id(const tools::wallet2::pending_tx &ptx) { crypto::hash8 payment_id8 = null_hash8; @@ -895,26 +883,22 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & } } //---------------------------------------------------------------------------------------------------- -bool wallet2::wallet_generate_key_image_helper_export(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) const -{ - THROW_WALLET_EXCEPTION_IF(multisig_key_index >= ack.m_multisig_keys.size(), error::wallet_internal_error, "Bad multisig_key_index"); - return cryptonote::generate_multisig_key_image(ack, tx_public_key, real_output_index, in_ephemeral, ki, multisig_key_index); -} -//---------------------------------------------------------------------------------------------------- void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map &tx_money_got_in_outs, std::vector &outs) { - bool r; + THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index"); if (m_multisig) { - r = wallet_generate_key_image_helper_old(keys, tx_pub_key, i, tx_scan_info.in_ephemeral, tx_scan_info.ki); + tx_scan_info.in_ephemeral.pub = boost::get(tx.vout[i].target).key; + tx_scan_info.in_ephemeral.sec = crypto::null_skey; + tx_scan_info.ki = rct::rct2ki(rct::zero()); } else { - r = cryptonote::generate_key_image_helper_precomp(keys, boost::get(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get(tx.vout[i].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); } - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); - THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get(tx.vout[i].target).key, - error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); outs.push_back(i); if (tx_scan_info.money_transfered == 0) @@ -8373,13 +8357,14 @@ crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad output index"); const transfer_details &td = m_transfers[n]; - crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); + const crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); + const std::vector additional_tx_keys = cryptonote::get_additional_tx_pub_keys_from_extra(td.m_tx); crypto::key_image ki; std::vector pkis; for (const auto &info: td.m_multisig_info) for (const auto &pki: info.m_partial_key_images) pkis.push_back(pki); - bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), tx_key, td.m_internal_output_index, pkis, ki); + bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); return ki; } @@ -8394,8 +8379,7 @@ std::vector wallet2::export_multisig() for (size_t n = 0; n < m_transfers.size(); ++n) { transfer_details &td = m_transfers[n]; - crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); - cryptonote::keypair in_ephemeral; + const std::vector additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); crypto::key_image ki; td.m_multisig_k.clear(); info[n].m_LR.clear(); @@ -8404,7 +8388,7 @@ std::vector wallet2::export_multisig() 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 - bool r = wallet_generate_key_image_helper_export(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki, m); + bool r = generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki); CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); info[n].m_partial_key_images.push_back(ki); } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 79199a30c..45f82fa1a 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -977,7 +977,6 @@ namespace tools void set_unspent(size_t idx); void get_outs(std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count); bool tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; - bool wallet_generate_key_image_helper_export(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki, size_t multisig_key_index) const; crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector &unused_transfers_indices, const std::vector &unused_dust_indices) const; std::vector get_only_rct(const std::vector &unused_dust_indices, const std::vector &unused_transfers_indices) const; diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp index 484b8b7ab..cf63d20f7 100644 --- a/tests/core_tests/multisig.cpp +++ b/tests/core_tests/multisig.cpp @@ -132,8 +132,14 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector> account_L(total); std::vector> account_R(total); std::vector> account_ki(total); + std::vector additional_tx_keys; + std::unordered_map subaddresses; + subaddresses[miner_account[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; for (size_t msidx = 0; msidx < total; ++msidx) { + CHECK_AND_ASSERT_MES(miner_account[msidx].get_keys().m_account_address.m_spend_public_key == miner_account[0].get_keys().m_account_address.m_spend_public_key, + false, "Mismatched spend public keys"); + size_t nlr = threshold < total ? threshold - 1 : 1; account_L[msidx].resize(nlr); account_R[msidx].resize(nlr); @@ -146,7 +152,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector subaddresses; - subaddresses[miner_account[creator].get_keys().m_account_address.m_spend_public_key] = {0,0}; - std::vector additional_tx_keys; - r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector(), tx, 0, tx_key, additional_tx_keys, true, false, msoutp); + std::vector additional_tx_secret_keys; + r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector(), tx, 0, tx_key, additional_tx_secret_keys, true, false, msoutp); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); #ifndef NO_MULTISIG From a36c261d7adc9bef61730a581fe92c0edc72ddce Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 21 Oct 2017 18:31:30 +0100 Subject: [PATCH 13/19] wallet2: fix slow multisig unit tests with subaddress patch While there, move the wallet2 ctor to the cpp file as it's a huge amount of init list now, and remove an unused one. --- src/wallet/wallet2.cpp | 47 ++++++++++++++++++++++++++++++++--- src/wallet/wallet2.h | 8 +++--- tests/unit_tests/multisig.cpp | 1 + 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 4ad6d852d..013ad6a75 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -573,6 +573,41 @@ const size_t MAX_SPLIT_ATTEMPTS = 30; constexpr const std::chrono::seconds wallet2::rpc_timeout; const char* wallet2::tr(const char* str) { return i18n_translate(str, "tools::wallet2"); } +wallet2::wallet2(bool testnet, bool restricted): + m_multisig_rescan_info(NULL), + m_multisig_rescan_k(NULL), + m_run(true), + m_callback(0), + m_testnet(testnet), + m_always_confirm_transfers(true), + m_print_ring_members(false), + m_store_tx_info(true), + m_default_mixin(0), + m_default_priority(0), + m_refresh_type(RefreshOptimizeCoinbase), + m_auto_refresh(true), + m_refresh_from_block_height(0), + m_confirm_missing_payment_id(true), + m_ask_password(true), + m_min_output_count(0), + m_min_output_value(0), + m_merge_destinations(false), + m_confirm_backlog(true), + m_is_initialized(false), + m_restricted(restricted), + is_old_file_format(false), + m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), + m_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR), + m_subaddress_lookahead_minor(SUBADDRESS_LOOKAHEAD_MINOR), + m_light_wallet(false), + m_light_wallet_scanned_block_height(0), + m_light_wallet_blockchain_height(0), + m_light_wallet_connected(false), + m_light_wallet_balance(0), + m_light_wallet_unlocked_balance(0) +{ +} + bool wallet2::has_testnet_option(const boost::program_options::variables_map& vm) { return command_line::get_arg(vm, options().testnet); @@ -764,9 +799,9 @@ void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index) { // add new accounts cryptonote::subaddress_index index2; - for (index2.major = m_subaddress_labels.size(); index2.major < index.major + SUBADDRESS_LOOKAHEAD_MAJOR; ++index2.major) + for (index2.major = m_subaddress_labels.size(); index2.major < index.major + m_subaddress_lookahead_major; ++index2.major) { - for (index2.minor = 0; index2.minor < (index2.major == index.major ? index.minor : 0) + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor) + for (index2.minor = 0; index2.minor < (index2.major == index.major ? index.minor : 0) + m_subaddress_lookahead_minor; ++index2.minor) { if (m_subaddresses_inv.count(index2) == 0) { @@ -783,7 +818,7 @@ void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index) { // add new subaddresses cryptonote::subaddress_index index2 = index; - for (index2.minor = m_subaddress_labels[index.major].size(); index2.minor < index.minor + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor) + for (index2.minor = m_subaddress_labels[index.major].size(); index2.minor < index.minor + m_subaddress_lookahead_minor; ++index2.minor) { if (m_subaddresses_inv.count(index2) == 0) { @@ -813,6 +848,12 @@ void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, co m_subaddress_labels[index.major][index.minor] = label; } //---------------------------------------------------------------------------------------------------- +void wallet2::set_subaddress_lookahead(size_t major, size_t minor) +{ + m_subaddress_lookahead_major = major; + m_subaddress_lookahead_minor = minor; +} +//---------------------------------------------------------------------------------------------------- /*! * \brief Tells if the wallet file is deprecated. */ diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 45f82fa1a..83a4fd2e1 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -144,10 +144,6 @@ namespace tools RefreshDefault = RefreshOptimizeCoinbase, }; - private: - wallet2(const wallet2&) : m_multisig_rescan_info(NULL), m_multisig_rescan_k(NULL), m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false),m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} - - public: static const char* tr(const char* str); static bool has_testnet_option(const boost::program_options::variables_map& vm); @@ -168,7 +164,7 @@ namespace tools static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key); - wallet2(bool testnet = false, bool restricted = false) : m_multisig_rescan_info(NULL), m_multisig_rescan_k(NULL), m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {} + wallet2(bool testnet = false, bool restricted = false); struct multisig_info { @@ -583,6 +579,7 @@ namespace tools void expand_subaddresses(const cryptonote::subaddress_index& index); std::string get_subaddress_label(const cryptonote::subaddress_index& index) const; void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label); + void set_subaddress_lookahead(size_t major, size_t minor); /*! * \brief Tells if the wallet file is deprecated. */ @@ -1049,6 +1046,7 @@ namespace tools bool m_is_initialized; NodeRPCProxy m_node_rpc_proxy; std::unordered_set m_scanned_pool_txs[2]; + size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor; // Light wallet bool m_light_wallet; /* sends view key to daemon for scanning */ diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp index aab1ce420..8b2c7e5f8 100644 --- a/tests/unit_tests/multisig.cpp +++ b/tests/unit_tests/multisig.cpp @@ -62,6 +62,7 @@ static void make_wallet(unsigned int idx, tools::wallet2 &wallet) try { wallet.init(""); + wallet.set_subaddress_lookahead(1, 1); wallet.generate("", "", spendkey, true, false); ASSERT_TRUE(test_addresses[idx].address == wallet.get_account().get_public_address_str(true)); } From e36f5b6021eb541d72fee4b2d5643ba42fd4d9dd Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 4 Nov 2017 10:39:17 +0000 Subject: [PATCH 14/19] Match surae's recommendation to derive multisig keys --- src/multisig/multisig.cpp | 33 +++++++++++++++++++------------ src/multisig/multisig.h | 1 + src/simplewallet/simplewallet.cpp | 5 +++-- src/wallet/wallet2.cpp | 31 +++++++++++++++-------------- src/wallet/wallet_rpc_server.cpp | 6 +++--- tests/core_tests/chaingen.h | 10 ++++++---- 6 files changed, 49 insertions(+), 37 deletions(-) diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp index a99f66e64..39d0e1c4b 100644 --- a/src/multisig/multisig.cpp +++ b/src/multisig/multisig.cpp @@ -39,18 +39,29 @@ using namespace std; +static const rct::key multisig_salt = { {'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 } }; + namespace cryptonote { + //----------------------------------------------------------------- + crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key) + { + rct::keyV data; + data.push_back(rct::sk2rct(key)); + data.push_back(multisig_salt); + return rct::rct2sk(rct::hash_to_scalar(data)); + } //----------------------------------------------------------------- void generate_multisig_N_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) { // the multisig spend public key is the sum of all spend public keys multisig_keys.clear(); - spend_pkey = rct::pk2rct(keys.m_account_address.m_spend_public_key); + const crypto::secret_key spend_secret_key = get_multisig_blinded_secret_key(keys.m_spend_secret_key); + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(spend_secret_key, (crypto::public_key&)spend_pkey), "Failed to derive public key"); for (const auto &k: spend_keys) rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); - multisig_keys.push_back(keys.m_spend_secret_key); - spend_skey = rct::sk2rct(keys.m_spend_secret_key); + multisig_keys.push_back(spend_secret_key); + spend_skey = rct::sk2rct(spend_secret_key); } //----------------------------------------------------------------- void generate_multisig_N1_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) @@ -60,23 +71,19 @@ namespace cryptonote spend_skey = rct::zero(); // create all our composite private keys + crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key); for (const auto &k: spend_keys) { - rct::keyV data; - data.push_back(rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(keys.m_spend_secret_key))); - static const rct::key salt = { {'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 } }; - data.push_back(salt); - rct::key msk = rct::hash_to_scalar(data); - multisig_keys.push_back(rct::rct2sk(msk)); - sc_add(spend_skey.bytes, spend_skey.bytes, msk.bytes); + rct::key sk = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey)); + crypto::secret_key msk = get_multisig_blinded_secret_key(rct::rct2sk(sk)); + multisig_keys.push_back(msk); + sc_add(spend_skey.bytes, spend_skey.bytes, (const unsigned char*)msk.data); } } //----------------------------------------------------------------- crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector &skeys) { - crypto::hash hash; - crypto::cn_fast_hash(&skey, sizeof(crypto::hash), hash); - rct::key view_skey = rct::hash2rct(hash); + rct::key view_skey = rct::sk2rct(get_multisig_blinded_secret_key(skey)); for (const auto &k: skeys) sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes); return rct::rct2sk(view_skey); diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h index 5cb469c1b..f29b47987 100644 --- a/src/multisig/multisig.h +++ b/src/multisig/multisig.h @@ -38,6 +38,7 @@ namespace cryptonote { struct account_keys; + crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key); void generate_multisig_N_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); void generate_multisig_N1_N(const account_keys &keys, const std::vector &spend_keys, std::vector &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector &skeys); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 98d7ea14f..145d90d91 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -60,6 +60,7 @@ #include "rapidjson/document.h" #include "common/json_util.h" #include "ringct/rctSigs.h" +#include "multisig/multisig.h" #include "wallet/wallet_args.h" #include @@ -801,8 +802,8 @@ bool simple_wallet::make_multisig(const std::vector &args) } // people may include their own, weed it out - const crypto::secret_key local_skey = m_wallet->get_account().get_keys().m_view_secret_key; - const crypto::public_key local_pkey = m_wallet->get_account().get_keys().m_account_address.m_spend_public_key; + const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(m_wallet->get_account().get_keys().m_view_secret_key); + const crypto::public_key local_pkey = m_wallet->get_multisig_signer_public_key(m_wallet->get_account().get_keys().m_spend_secret_key); for (size_t i = 0; i < secret_keys.size(); ++i) { if (secret_keys[i] == local_skey) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 013ad6a75..29ca3dd2f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2830,8 +2830,6 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, // We need an extra step, so we package all the composite public keys // we know about, and make a signed string out of them std::string data; - const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key; - data += std::string((const char *)&pkey, sizeof(crypto::public_key)); const crypto::public_key signer = get_multisig_signer_public_key(rct::rct2sk(spend_skey)); data += std::string((const char *)&signer, sizeof(crypto::public_key)); @@ -2844,7 +2842,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, data.resize(data.size() + sizeof(crypto::signature)); crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, pkey, get_account().get_keys().m_spend_secret_key, signature); + crypto::generate_signature(hash, signer, get_multisig_blinded_secret_key(rct::rct2sk(spend_skey)), signature); extra_multisig_info = std::string("MultisigxV1") + tools::base58::encode(data); } @@ -2958,19 +2956,18 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std std::string wallet2::get_multisig_info() const { // It's a signed package of private view key and public spend key - const crypto::secret_key &skey = get_account().get_keys().m_view_secret_key; - const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key; + const crypto::secret_key skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); + const crypto::public_key pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); crypto::hash hash; std::string data; - crypto::cn_fast_hash(&skey, sizeof(crypto::secret_key), hash); - data += std::string((const char *)&hash, sizeof(crypto::hash)); + data += std::string((const char *)&skey, sizeof(crypto::secret_key)); data += std::string((const char *)&pkey, sizeof(crypto::public_key)); data.resize(data.size() + sizeof(crypto::signature)); crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, pkey, get_account().get_keys().m_spend_secret_key, signature); + crypto::generate_signature(hash, pkey, get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), signature); return std::string("MultisigV1") + tools::base58::encode(data); } @@ -3027,28 +3024,26 @@ bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered MERROR("Multisig info decoding error"); return false; } - if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature)) + if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::signature)) { MERROR("Multisig info is corrupt"); return false; } - if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key)) + if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key)) { MERROR("Multisig info is corrupt"); return false; } - const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key); + const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key); size_t offset = 0; - const crypto::public_key &pkey = *(const crypto::public_key*)(decoded.data() + offset); - offset += sizeof(pkey); signer = *(const crypto::public_key*)(decoded.data() + offset); offset += sizeof(signer); const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset + n_keys * sizeof(crypto::public_key)); crypto::hash hash; crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); - if (!crypto::check_signature(hash, pkey, signature)) + if (!crypto::check_signature(hash, signer, signature)) { MERROR("Multisig info signature is invalid"); return false; @@ -8313,13 +8308,19 @@ size_t wallet2::import_outputs(const std::vectorget_account().get_keys().m_view_secret_key, sizeof(crypto::secret_key), hash); + crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(m_wallet->get_account().get_keys().m_view_secret_key); for (size_t i = 0; i < secret_keys.size(); ++i) { - if (rct::sk2rct(secret_keys[i]) == rct::hash2rct(hash)) + if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(local_skey)) { secret_keys[i] = secret_keys.back(); public_keys[i] = public_keys.back(); diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 939b88109..9fed95183 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -521,10 +521,12 @@ inline bool do_replay_file(const std::string& filename) { \ if (msidx_inner != msidx) \ { \ - crypto::hash vkh; \ - crypto::cn_fast_hash(&account[msidx_inner].get_keys().m_view_secret_key, sizeof(crypto::secret_key), vkh); \ - view_keys[msidx].push_back((const crypto::secret_key&)vkh); \ - spend_keys[msidx].push_back(account[msidx_inner].get_keys().m_account_address.m_spend_public_key); \ + crypto::secret_key vkh = cryptonote::get_multisig_blinded_secret_key(account[msidx_inner].get_keys().m_view_secret_key); \ + view_keys[msidx].push_back(vkh); \ + crypto::secret_key skh = cryptonote::get_multisig_blinded_secret_key(account[msidx_inner].get_keys().m_spend_secret_key); \ + crypto::public_key pskh; \ + crypto::secret_key_to_public_key(skh, pskh); \ + spend_keys[msidx].push_back(pskh); \ } \ } \ } \ From 2fa707d1a54c2e2c00c542edb3757ae553b5b84a Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Mon, 27 Nov 2017 20:09:16 +0000 Subject: [PATCH 15/19] wallet: add multisig sign/submit RPC --- src/wallet/wallet2.cpp | 62 +-- src/wallet/wallet2.h | 6 + src/wallet/wallet_rpc_server.cpp | 453 +++++++++++++++---- src/wallet/wallet_rpc_server.h | 5 + src/wallet/wallet_rpc_server_commands_defs.h | 54 +++ src/wallet/wallet_rpc_server_error_codes.h | 3 + 6 files changed, 470 insertions(+), 113 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 29ca3dd2f..07f986e02 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4477,25 +4477,12 @@ bool wallet2::save_multisig_tx(const std::vector& ptx_vector, const return epee::file_io_utils::save_string_to_file(filename, ciphertext); } //---------------------------------------------------------------------------------------------------- -bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func) +bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported_txs, std::function accept_func) { - std::string s; - boost::system::error_code errcode; - - if (!boost::filesystem::exists(filename, errcode)) - { - LOG_PRINT_L0("File " << filename << " does not exist: " << errcode); - return false; - } - if (!epee::file_io_utils::load_file_to_string(filename.c_str(), s)) - { - LOG_PRINT_L0("Failed to load from " << filename); - return false; - } const size_t magiclen = strlen(MULTISIG_UNSIGNED_TX_PREFIX); if (strncmp(s.c_str(), MULTISIG_UNSIGNED_TX_PREFIX, magiclen)) { - LOG_PRINT_L0("Bad magic from " << filename); + LOG_PRINT_L0("Bad magic from multisig tx data"); return false; } try @@ -4504,8 +4491,8 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t } catch (const std::exception &e) { - LOG_PRINT_L0("Failed to decrypt " << filename << ": " << e.what()); - return 0; + LOG_PRINT_L0("Failed to decrypt multisig tx data: " << e.what()); + return false; } try { @@ -4515,7 +4502,7 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t } catch (...) { - LOG_PRINT_L0("Failed to parse data from " << filename); + LOG_PRINT_L0("Failed to parse multisig tx data"); return false; } @@ -4557,12 +4544,43 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t return true; } //---------------------------------------------------------------------------------------------------- +bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func) +{ + std::string s; + boost::system::error_code errcode; + + if (!boost::filesystem::exists(filename, errcode)) + { + LOG_PRINT_L0("File " << filename << " does not exist: " << errcode); + return false; + } + if (!epee::file_io_utils::load_file_to_string(filename.c_str(), s)) + { + LOG_PRINT_L0("Failed to load from " << filename); + return false; + } + + if (!load_multisig_tx(s, exported_txs, accept_func)) + { + LOG_PRINT_L0("Failed to parse multisig tx data from " << filename); + return false; + } + return true; +} +//---------------------------------------------------------------------------------------------------- bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector &txids) { THROW_WALLET_EXCEPTION_IF(exported_txs.m_ptx.empty(), error::wallet_internal_error, "No tx found"); const crypto::public_key local_signer = get_multisig_signer_public_key(); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(local_signer) != exported_txs.m_signers.end(), + error::wallet_internal_error, "Transaction already signed by this private key"); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() > m_multisig_threshold, + error::wallet_internal_error, "Transaction was signed by too many signers"); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() == m_multisig_threshold, + error::wallet_internal_error, "Transaction is already fully signed"); + txids.clear(); // sign the transactions @@ -4667,14 +4685,6 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto if(!load_multisig_tx_from_file(filename, exported_txs)) return false; - const crypto::public_key signer = get_multisig_signer_public_key(); - THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(signer) != exported_txs.m_signers.end(), - error::wallet_internal_error, "Transaction already signed by this private key"); - THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() > m_multisig_threshold, - error::wallet_internal_error, "Transaction was signed by too many signers"); - THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() == m_multisig_threshold, - error::wallet_internal_error, "Transaction is already fully signed"); - if (accept_func && !accept_func(exported_txs)) { LOG_PRINT_L1("Transactions rejected by callback"); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 83a4fd2e1..7a8becb3c 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -395,6 +395,11 @@ namespace tools { std::vector m_ptx; std::unordered_set m_signers; + + BEGIN_SERIALIZE_OBJECT() + FIELD(m_ptx) + FIELD(m_signers) + END_SERIALIZE() }; struct keys_file_data @@ -640,6 +645,7 @@ namespace tools std::vector create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, bool trusted_daemon); std::vector create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, bool trusted_daemon); std::vector create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector unused_transfers_indices, std::vector unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, bool trusted_daemon); + bool load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function accept_func = NULL); bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func = NULL); bool sign_multisig_tx_from_file(const std::string &filename, std::vector &txids, std::function accept_func); bool sign_multisig_tx(multisig_tx_set &exported_txs, std::vector &txids); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index e6e5445b2..0157a6f71 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -610,11 +610,6 @@ namespace tools return false; } - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hash - res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx)); if (req.get_tx_key) { res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key); @@ -623,18 +618,45 @@ namespace tools } res.fee = ptx_vector.back().fee; - if (req.get_tx_hex) + if (m_wallet->multisig()) { - cryptonote::blobdata blob; - tx_to_blob(ptx_vector.back().tx, blob); - res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); + res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); + if (res.multisig_txset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } } - if (req.get_tx_metadata) + else { - std::ostringstream oss; - binary_archive ar(oss); - ::serialization::serialize(ar, ptx_vector.back()); - res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); + + // populate response with tx hash + res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx)); + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx_vector.back().tx, blob); + res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx_vector.back(); + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } } return true; } @@ -675,17 +697,9 @@ namespace tools ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); - if (!req.do_not_relay) - { - LOG_PRINT_L2("on_transfer_split calling commit_tx"); - m_wallet->commit_tx(ptx_vector); - LOG_PRINT_L2("on_transfer_split called commit_tx"); - } - // populate response with tx hashes for (const auto & ptx : ptx_vector) { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); @@ -699,19 +713,56 @@ namespace tools res.amount_list.push_back(ptx_amount); res.fee_list.push_back(ptx.fee); + } - if (req.get_tx_hex) + if (m_wallet->multisig()) + { + res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); + if (res.multisig_txset.empty()) { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; } - if (req.get_tx_metadata) + } + + // populate response with tx hashes + for (const auto & ptx : ptx_vector) + { + if (!req.do_not_relay) { - std::ostringstream oss; - binary_archive ar(oss); - ::serialization::serialize(ar, const_cast(ptx)); - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + LOG_PRINT_L2("on_transfer_split calling commit_tx"); + m_wallet->commit_tx(ptx_vector); + LOG_PRINT_L2("on_transfer_split called commit_tx"); + } + + // populate response with tx hashes + for (auto & ptx : ptx_vector) + { + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } } @@ -739,30 +790,65 @@ namespace tools { std::vector ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes for (const auto & ptx : ptx_vector) { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); } res.fee_list.push_back(ptx.fee); - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); - } - if (req.get_tx_metadata) + } + + if (m_wallet->multisig()) + { + for (tools::wallet2::pending_tx &ptx: ptx_vector) { std::ostringstream oss; - binary_archive ar(oss); - ::serialization::serialize(ar, const_cast(ptx)); - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } + } + else + { + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); + + // populate response with tx hashes + for (auto & ptx : ptx_vector) + { + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } } @@ -804,29 +890,64 @@ namespace tools uint64_t mixin = m_wallet->adjust_mixin(req.mixin); std::vector ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes for (const auto & ptx : ptx_vector) { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); } - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); - } - if (req.get_tx_metadata) + } + + if (m_wallet->multisig()) + { + for (tools::wallet2::pending_tx &ptx: ptx_vector) { std::ostringstream oss; - binary_archive ar(oss); - ::serialization::serialize(ar, const_cast(ptx)); - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } + } + else + { + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); + + // populate response with tx hashes + for (auto & ptx : ptx_vector) + { + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } } @@ -888,37 +1009,59 @@ namespace tools er.message = "Multiple transactions are created, which is not supposed to happen"; return false; } - if (ptx_vector[0].selected_transfers.size() > 1) + const wallet2::pending_tx &ptx = ptx_vector[0]; + if (ptx.selected_transfers.size() > 1) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "The transaction uses multiple inputs, which is not supposed to happen"; return false; } - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes - const wallet2::pending_tx &ptx = ptx_vector[0]; - res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); if (req.get_tx_key) { res.tx_key = epee::string_tools::pod_to_hex(ptx.tx_key); } - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); - } - if (req.get_tx_metadata) - { - std::ostringstream oss; - binary_archive ar(oss); - ::serialization::serialize(ar, const_cast(ptx)); - res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); - } + if (m_wallet->multisig()) + { + res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); + if (res.multisig_txset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + } + else + { + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); + + // populate response with tx hashes + res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } + } return true; } catch (const tools::error::daemon_busy& e) @@ -954,13 +1097,14 @@ namespace tools return false; } - std::stringstream ss; - ss << blob; - binary_archive ba(ss); - tools::wallet2::pending_tx ptx; - bool r = ::serialization::serialize(ba, ptx); - if (!r) + try + { + std::istringstream iss(blob); + boost::archive::portable_binary_iarchive ar(iss); + ar >> ptx; + } + catch (...) { er.code = WALLET_RPC_ERROR_CODE_BAD_TX_METADATA; er.message = "Failed to parse tx metadata."; @@ -2663,6 +2807,141 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + bool ready; + uint32_t threshold, total; + if (!m_wallet->multisig(&ready, &threshold, &total)) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + if (!ready) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is multisig, but not yet finalized"; + return false; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + tools::wallet2::multisig_tx_set txs; + bool r = m_wallet->load_multisig_tx(blob, txs, NULL); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA; + er.message = "Failed to parse multisig tx data."; + return false; + } + + std::vector txids; + try + { + bool r = m_wallet->sign_multisig_tx(txs, txids); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE; + er.message = "Failed to sign multisig tx"; + return false; + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE; + er.message = std::string("Failed to sign multisig tx: ") + e.what(); + return false; + } + + res.tx_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(txs)); + if (!txids.empty()) + { + for (const crypto::hash &txid: txids) + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(txid)); + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + bool ready; + uint32_t threshold, total; + if (!m_wallet->multisig(&ready, &threshold, &total)) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + if (!ready) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is multisig, but not yet finalized"; + return false; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + tools::wallet2::multisig_tx_set txs; + bool r = m_wallet->load_multisig_tx(blob, txs, NULL); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA; + er.message = "Failed to parse multisig tx data."; + return false; + } + + if (txs.m_signers.size() < threshold) + { + er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; + er.message = "Not enough signers signed this transaction."; + return false; + } + + try + { + for (auto &ptx: txs.m_ptx) + { + m_wallet->commit_tx(ptx); + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION; + er.message = std::string("Failed to submit multisig tx: ") + e.what(); + return false; + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ } int main(int argc, char** argv) { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 22eb8964e..79f589623 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -122,6 +122,9 @@ namespace tools MAP_JON_RPC_WE("make_multisig", on_make_multisig, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG) MAP_JON_RPC_WE("export_multisig_info", on_export_multisig, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG) MAP_JON_RPC_WE("import_multisig_info", on_import_multisig, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG) + MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG) + MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG) + MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG) END_JSON_RPC_MAP() END_URI_MAP2() @@ -182,6 +185,8 @@ namespace tools bool on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er); bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er); bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er); //json rpc v2 bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index bff4fdd7c..07917a683 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -318,6 +318,7 @@ namespace wallet_rpc uint64_t fee; std::string tx_blob; std::string tx_metadata; + std::string multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -326,6 +327,7 @@ namespace wallet_rpc KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) + KV_SERIALIZE(multisig_txset) END_KV_SERIALIZE_MAP() }; }; @@ -378,6 +380,7 @@ namespace wallet_rpc std::list fee_list; std::list tx_blob_list; std::list tx_metadata_list; + std::string multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -386,6 +389,7 @@ namespace wallet_rpc KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) + KV_SERIALIZE(multisig_txset) END_KV_SERIALIZE_MAP() }; }; @@ -423,6 +427,7 @@ namespace wallet_rpc std::list fee_list; std::list tx_blob_list; std::list tx_metadata_list; + std::list multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -430,6 +435,7 @@ namespace wallet_rpc KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) + KV_SERIALIZE(multisig_txset) END_KV_SERIALIZE_MAP() }; }; @@ -483,6 +489,7 @@ namespace wallet_rpc std::list fee_list; std::list tx_blob_list; std::list tx_metadata_list; + std::list multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -490,6 +497,7 @@ namespace wallet_rpc KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) + KV_SERIALIZE(multisig_txset) END_KV_SERIALIZE_MAP() }; }; @@ -530,6 +538,7 @@ namespace wallet_rpc uint64_t fee; std::string tx_blob; std::string tx_metadata; + std::string multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -537,6 +546,7 @@ namespace wallet_rpc KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) + KV_SERIALIZE(multisig_txset) END_KV_SERIALIZE_MAP() }; }; @@ -1652,5 +1662,49 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_SIGN_MULTISIG + { + struct request + { + std::string tx_data_hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_data_hex) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_data_hex; + std::list tx_hash_list; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_data_hex) + KV_SERIALIZE(tx_hash_list) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SUBMIT_MULTISIG + { + struct request + { + std::string tx_data_hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_data_hex) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::list tx_hash_list; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_hash_list) + END_KV_SERIALIZE_MAP() + }; + }; + } } diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index 139fcf8ed..578413e38 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -64,3 +64,6 @@ #define WALLET_RPC_ERROR_CODE_NOT_MULTISIG -31 #define WALLET_RPC_ERROR_CODE_WRONG_LR -32 #define WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED -33 +#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA -34 +#define WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE -35 +#define WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION -36 From 31a97e761e6b7570efb5e1b58fd744a0edcad953 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Mon, 13 Nov 2017 16:24:42 +0000 Subject: [PATCH 16/19] wallet: use raw encrypted data in multisig import/export RPC --- src/simplewallet/simplewallet.cpp | 80 +------------------- src/wallet/wallet2.cpp | 61 ++++++++++++++- src/wallet/wallet2.h | 4 +- src/wallet/wallet_rpc_server.cpp | 54 ++----------- src/wallet/wallet_rpc_server_commands_defs.h | 37 +-------- 5 files changed, 73 insertions(+), 163 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 145d90d91..5c94f1704 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -85,7 +85,6 @@ typedef cryptonote::simple_wallet sw; #define MIN_RING_SIZE 5 // Used to inform user about min ring size -- does not track actual protocol #define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" -#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" #define LOCK_IDLE_SCOPE() \ bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \ @@ -916,21 +915,9 @@ bool simple_wallet::export_multisig(const std::vector &args) const std::string filename = args[0]; try { - std::vector outs = m_wallet->export_multisig(); + cryptonote::blobdata ciphertext = m_wallet->export_multisig(); - std::stringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - ar << outs; - - std::string magic(MULTISIG_EXPORT_FILE_MAGIC, strlen(MULTISIG_EXPORT_FILE_MAGIC)); - const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; - std::string header; - header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); - header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); - crypto::public_key signer = m_wallet->get_multisig_signer_public_key(); - header += std::string((const char *)&signer, sizeof(crypto::public_key)); - std::string ciphertext = m_wallet->encrypt_with_view_secret_key(header + oss.str()); - bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext); + bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); if (!r) { fail_msg_writer() << tr("failed to save file ") << filename; @@ -970,8 +957,7 @@ bool simple_wallet::import_multisig(const std::vector &args) if (m_wallet->ask_password() && !get_and_verify_password()) return true; - std::vector> info; - std::unordered_set seen; + std::vector info; for (size_t n = 0; n < args.size(); ++n) { const std::string filename = args[n]; @@ -982,65 +968,7 @@ bool simple_wallet::import_multisig(const std::vector &args) fail_msg_writer() << tr("failed to read file ") << filename; return true; } - const size_t magiclen = strlen(MULTISIG_EXPORT_FILE_MAGIC); - if (data.size() < magiclen || memcmp(data.data(), MULTISIG_EXPORT_FILE_MAGIC, magiclen)) - { - fail_msg_writer() << tr("Bad multisig info file magic in ") << filename; - return true; - } - - try - { - data = m_wallet->decrypt_with_view_secret_key(std::string(data, magiclen)); - } - catch (const std::exception &e) - { - fail_msg_writer() << tr("Failed to decrypt ") << filename << ": " << e.what(); - return true; - } - - const size_t headerlen = 3 * sizeof(crypto::public_key); - if (data.size() < headerlen) - { - fail_msg_writer() << tr("Bad data size from file ") << filename; - return true; - } - const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; - const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; - const crypto::public_key &signer = *(const crypto::public_key*)&data[2*sizeof(crypto::public_key)]; - const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; - if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) - { - fail_msg_writer() << (boost::format(tr("Multisig info from %s is for a different account")) % filename).str(); - return true; - } - if (m_wallet->get_multisig_signer_public_key() == signer) - { - message_writer() << (boost::format(tr("Multisig info from %s is from this wallet, ignored")) % filename).str(); - continue; - } - if (seen.find(signer) != seen.end()) - { - message_writer() << (boost::format(tr("Multisig info from %s already seen, ignored")) % filename).str(); - continue; - } - seen.insert(signer); - - try - { - std::string body(data, headerlen); - std::istringstream iss(body); - std::vector i; - boost::archive::portable_binary_iarchive ar(iss); - ar >> i; - message_writer() << (boost::format(tr("%u outputs found in %s")) % boost::lexical_cast(i.size()) % filename).str(); - info.push_back(std::move(i)); - } - catch (const std::exception &e) - { - fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); - return true; - } + info.push_back(std::move(data)); } LOCK_IDLE_SCOPE(); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 07f986e02..635264d70 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -103,6 +103,8 @@ using namespace cryptonote; #define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002" +#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" + namespace { // Create on-demand to prevent static initialization order fiasco issues. @@ -8421,7 +8423,7 @@ crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const return ki; } //---------------------------------------------------------------------------------------------------- -std::vector wallet2::export_multisig() +cryptonote::blobdata wallet2::export_multisig() { std::vector info; @@ -8456,7 +8458,19 @@ std::vector wallet2::export_multisig() info[n].m_signer = signer; } - return info; + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << info; + + std::string magic(MULTISIG_EXPORT_FILE_MAGIC, strlen(MULTISIG_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + std::string header; + header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + header += std::string((const char *)&signer, sizeof(crypto::public_key)); + std::string ciphertext = encrypt_with_view_secret_key(header + oss.str()); + + return MULTISIG_EXPORT_FILE_MAGIC + ciphertext; } //---------------------------------------------------------------------------------------------------- void wallet2::update_multisig_rescan_info(const std::vector> &multisig_k, const std::vector> &info, size_t n) @@ -8480,9 +8494,50 @@ void wallet2::update_multisig_rescan_info(const std::vector> info) +size_t wallet2::import_multisig(std::vector blobs) { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); + + std::vector> info; + std::unordered_set seen; + for (cryptonote::blobdata &data: blobs) + { + const size_t magiclen = strlen(MULTISIG_EXPORT_FILE_MAGIC); + THROW_WALLET_EXCEPTION_IF(data.size() < magiclen || memcmp(data.data(), MULTISIG_EXPORT_FILE_MAGIC, magiclen), + error::wallet_internal_error, "Bad multisig info file magic in "); + + data = decrypt_with_view_secret_key(std::string(data, magiclen)); + + const size_t headerlen = 3 * sizeof(crypto::public_key); + THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, "Bad data size"); + + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; + const crypto::public_key &signer = *(const crypto::public_key*)&data[2*sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + THROW_WALLET_EXCEPTION_IF(public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key, + error::wallet_internal_error, "Multisig info is for a different account"); + if (get_multisig_signer_public_key() == signer) + { + MINFO("Multisig info from this wallet ignored"); + continue; + } + if (seen.find(signer) != seen.end()) + { + MINFO("Duplicate multisig info ignored"); + continue; + } + seen.insert(signer); + + std::string body(data, headerlen); + std::istringstream iss(body); + std::vector i; + boost::archive::portable_binary_iarchive ar(iss); + ar >> i; + MINFO(boost::format("%u outputs found") % boost::lexical_cast(i.size())); + info.push_back(std::move(i)); + } + CHECK_AND_ASSERT_THROW_MES(info.size() + 1 <= m_multisig_signers.size() && info.size() + 1 >= m_multisig_threshold, "Wrong number of multisig sources"); std::vector> k; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 7a8becb3c..cb9d7e980 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -499,12 +499,12 @@ namespace tools * Export multisig info * This will generate and remember new k values */ - std::vector export_multisig(); + cryptonote::blobdata export_multisig(); /*! * Import a set of multisig info from multisig partners * \return the number of inputs which were imported */ - size_t import_multisig(std::vector> info); + size_t import_multisig(std::vector info); /*! * \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there) * \param wallet_name Name of wallet file (should exist) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 0157a6f71..4c14433f4 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2626,7 +2626,7 @@ namespace tools return false; } - std::vector info; + cryptonote::blobdata info; try { info = m_wallet->export_multisig(); @@ -2638,20 +2638,7 @@ namespace tools return false; } - res.info.resize(info.size()); - for (size_t n = 0; n < info.size(); ++n) - { - res.info[n].signer = epee::string_tools::pod_to_hex(info[n].m_signer); - res.info[n].LR.resize(info[n].m_LR.size()); - for (size_t l = 0; l < info[n].m_LR.size(); ++l) - { - res.info[n].LR[l].L = epee::string_tools::pod_to_hex(info[n].m_LR[l].m_L); - res.info[n].LR[l].R = epee::string_tools::pod_to_hex(info[n].m_LR[l].m_R); - } - res.info[n].partial_key_images.resize(info[n].m_partial_key_images.size()); - for (size_t l = 0; l < info[n].m_partial_key_images.size(); ++l) - res.info[n].partial_key_images[l] = epee::string_tools::pod_to_hex(info[n].m_partial_key_images[l]); - } + res.info = epee::string_tools::buff_to_hex_nodelimer(info); return true; } @@ -2687,42 +2674,15 @@ namespace tools return false; } - std::vector> info; + std::vector info; info.resize(req.info.size()); for (size_t n = 0; n < info.size(); ++n) { - info[n].resize(req.info[n].info.size()); - for (size_t i = 0; i < info[n].size(); ++i) + if (!epee::string_tools::parse_hexstr_to_binbuff(req.info[n], info[n])) { - const auto &src = req.info[n].info[i]; - auto &dst = info[n][i]; - - if (!epee::string_tools::hex_to_pod(src.signer, dst.m_signer)) - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; - er.message = "Failed to parse signer from multisig info"; - return false; - } - dst.m_LR.resize(src.LR.size()); - for (size_t l = 0; l < src.LR.size(); ++l) - { - if (!epee::string_tools::hex_to_pod(src.LR[l].L, dst.m_LR[l].m_L) || !epee::string_tools::hex_to_pod(src.LR[l].R, dst.m_LR[l].m_R)) - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_LR; - er.message = "Failed to parse L/R from multisig info"; - return false; - } - } - dst.m_partial_key_images.resize(src.partial_key_images.size()); - for (size_t l = 0; l < src.partial_key_images.size(); ++l) - { - if (!epee::string_tools::hex_to_pod(src.partial_key_images[l], dst.m_partial_key_images[l])) - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; - er.message = "Failed to parse partial key image from multisig info"; - return false; - } - } + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; } } diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 07917a683..57cc01e27 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1567,30 +1567,6 @@ namespace wallet_rpc }; }; - struct LR_entry - { - std::string L; - std::string R; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(L) - KV_SERIALIZE(R) - END_KV_SERIALIZE_MAP() - }; - - struct multisig_info_entry - { - std::string signer; - std::vector LR; - std::vector partial_key_images; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(signer) - KV_SERIALIZE(LR) - KV_SERIALIZE(partial_key_images) - END_KV_SERIALIZE_MAP() - }; - struct COMMAND_RPC_EXPORT_MULTISIG { struct request @@ -1601,7 +1577,7 @@ namespace wallet_rpc struct response { - std::vector info; + std::string info; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(info) @@ -1611,18 +1587,9 @@ namespace wallet_rpc struct COMMAND_RPC_IMPORT_MULTISIG { - struct participant_entry - { - std::vector info; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(info) - END_KV_SERIALIZE_MAP() - }; - struct request { - std::vector info; + std::vector info; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(info) From 98db7ee467fb4f73ce5e748d4891326b84a4c93a Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 18 Nov 2017 11:24:38 +0000 Subject: [PATCH 17/19] wallet: factor multisig info parsing --- src/simplewallet/simplewallet.cpp | 57 +++---------------------------- src/wallet/wallet2.cpp | 54 +++++++++++++++++++++++++++++ src/wallet/wallet2.h | 8 +++++ src/wallet/wallet_rpc_server.cpp | 51 +-------------------------- 4 files changed, 68 insertions(+), 102 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 5c94f1704..64e665fb3 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -771,61 +771,13 @@ bool simple_wallet::make_multisig(const std::vector &args) return true; } - // parse all multisig info - std::vector secret_keys(args.size() - 1); - std::vector public_keys(args.size() - 1); - for (size_t i = 1; i < args.size(); ++i) - { - if (!tools::wallet2::verify_multisig_info(args[i], secret_keys[i - 1], public_keys[i - 1])) - { - fail_msg_writer() << tr("Bad multisig info: ") << args[i]; - return true; - } - } - - // remove duplicates - for (size_t i = 0; i < secret_keys.size(); ++i) - { - for (size_t j = i + 1; j < secret_keys.size(); ++j) - { - if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j])) - { - message_writer() << tr("Duplicate key found, ignoring"); - secret_keys[j] = secret_keys.back(); - public_keys[j] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --j; - } - } - } - - // people may include their own, weed it out - const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(m_wallet->get_account().get_keys().m_view_secret_key); - const crypto::public_key local_pkey = m_wallet->get_multisig_signer_public_key(m_wallet->get_account().get_keys().m_spend_secret_key); - for (size_t i = 0; i < secret_keys.size(); ++i) - { - if (secret_keys[i] == local_skey) - { - message_writer() << tr("Local key is present, ignoring"); - secret_keys[i] = secret_keys.back(); - public_keys[i] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --i; - } - else if (public_keys[i] == local_pkey) - { - fail_msg_writer() << tr("Found local spend public key, but not local view secret key - something very weird"); - return true; - } - } - LOCK_IDLE_SCOPE(); try { - std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), secret_keys, public_keys, threshold); + auto local_args = args; + local_args.erase(local_args.begin()); + std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), local_args, threshold); if (!multisig_extra_info.empty()) { success_msg_writer() << tr("Another step is needed"); @@ -840,7 +792,8 @@ bool simple_wallet::make_multisig(const std::vector &args) return true; } - uint32_t total = secret_keys.size() + 1; + uint32_t total; + m_wallet->multisig(NULL, &threshold, &total); success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 635264d70..70180e080 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2895,6 +2895,60 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, return extra_multisig_info; } +std::string wallet2::make_multisig(const epee::wipeable_string &password, + const std::vector &info, + uint32_t threshold) +{ + // parse all multisig info + std::vector secret_keys(info.size()); + std::vector public_keys(info.size()); + for (size_t i = 0; i < info.size(); ++i) + { + THROW_WALLET_EXCEPTION_IF(!verify_multisig_info(info[i], secret_keys[i], public_keys[i]), + error::wallet_internal_error, "Bad multisig info: " + info[i]); + } + + // remove duplicates + for (size_t i = 0; i < secret_keys.size(); ++i) + { + for (size_t j = i + 1; j < secret_keys.size(); ++j) + { + if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j])) + { + MDEBUG("Duplicate key found, ignoring"); + secret_keys[j] = secret_keys.back(); + public_keys[j] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --j; + } + } + } + + // people may include their own, weed it out + const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); + const crypto::public_key local_pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); + for (size_t i = 0; i < secret_keys.size(); ++i) + { + if (secret_keys[i] == local_skey) + { + MDEBUG("Local key is present, ignoring"); + secret_keys[i] = secret_keys.back(); + public_keys[i] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --i; + } + else + { + THROW_WALLET_EXCEPTION_IF(public_keys[i] == local_pkey, error::wallet_internal_error, + "Found local spend public key, but not local view secret key - something very weird"); + } + } + + return make_multisig(password, secret_keys, public_keys, threshold); +} + bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unordered_set pkeys, std::vector signers) { CHECK_AND_ASSERT_THROW_MES(!pkeys.empty(), "empty pkeys"); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index cb9d7e980..399287c3e 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -471,6 +471,14 @@ namespace tools * \return empty if done, non empty if we need to send another string * to other participants */ + std::string make_multisig(const epee::wipeable_string &password, + const std::vector &info, + uint32_t threshold); + /*! + * \brief Creates a multisig wallet + * \return empty if done, non empty if we need to send another string + * to other participants + */ std::string make_multisig(const epee::wipeable_string &password, const std::vector &view_keys, const std::vector &spend_keys, diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 4c14433f4..0482b9dd6 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2539,58 +2539,9 @@ namespace tools return false; } - // parse all multisig info - std::vector secret_keys(req.multisig_info.size()); - std::vector public_keys(req.multisig_info.size()); - for (size_t i = 0; i < req.multisig_info.size(); ++i) - { - if (!m_wallet->verify_multisig_info(req.multisig_info[i], secret_keys[i], public_keys[i])) - { - er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO; - er.message = "Bad multisig info: " + req.multisig_info[i]; - return false; - } - } - - // remove duplicates - for (size_t i = 1; i < secret_keys.size(); ++i) - { - for (size_t j = i + 1; j < secret_keys.size(); ++j) - { - if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j])) - { - secret_keys[j] = secret_keys.back(); - public_keys[j] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --j; - } - } - } - - // people may include their own, weed it out - crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(m_wallet->get_account().get_keys().m_view_secret_key); - for (size_t i = 0; i < secret_keys.size(); ++i) - { - if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(local_skey)) - { - secret_keys[i] = secret_keys.back(); - public_keys[i] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --i; - } - else if (public_keys[i] == m_wallet->get_account().get_keys().m_account_address.m_spend_public_key) - { - er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO; - er.message = "Found local spend public key, but not local view secret key - something very weird"; - return false; - } - } - try { - res.multisig_info = m_wallet->make_multisig(req.password, secret_keys, public_keys, req.threshold); + res.multisig_info = m_wallet->make_multisig(req.password, req.multisig_info, req.threshold); res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); } catch (const std::exception &e) From 55c2845d1a6d36e2a8686b2d1afb2d38a78bbccb Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Thu, 23 Nov 2017 13:24:26 +0000 Subject: [PATCH 18/19] core_tests: multisig test now tests multiple inputs --- tests/core_tests/chaingen_main.cpp | 2 + tests/core_tests/multisig.cpp | 327 +++++++++++++++++------------ tests/core_tests/multisig.h | 16 +- 3 files changed, 209 insertions(+), 136 deletions(-) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index a7643292b..95284c11a 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -200,6 +200,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_rct_tx_rct_altered_extra); GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2); + GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2_many_inputs); GENERATE_AND_PLAY(gen_multisig_tx_valid_22_2_1); GENERATE_AND_PLAY(gen_multisig_tx_valid_33_1_23); GENERATE_AND_PLAY(gen_multisig_tx_valid_33_3_21); @@ -208,6 +209,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_multisig_tx_valid_23_2_1); GENERATE_AND_PLAY(gen_multisig_tx_valid_23_2_3); GENERATE_AND_PLAY(gen_multisig_tx_valid_45_1_234); + GENERATE_AND_PLAY(gen_multisig_tx_valid_45_4_135_many_inputs); GENERATE_AND_PLAY(gen_multisig_tx_valid_89_3_1245789); GENERATE_AND_PLAY(gen_multisig_tx_invalid_23_1__no_threshold); GENERATE_AND_PLAY(gen_multisig_tx_invalid_45_5_23_no_threshold); diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp index cf63d20f7..79a3a7cf4 100644 --- a/tests/core_tests/multisig.cpp +++ b/tests/core_tests/multisig.cpp @@ -31,6 +31,7 @@ #include "ringct/rctSigs.h" #include "cryptonote_basic/cryptonote_basic.h" #include "multisig/multisig.h" +#include "common/apply_permutation.h" #include "chaingen.h" #include "multisig.h" @@ -44,7 +45,7 @@ using namespace cryptonote; // Tests bool gen_multisig_tx_validation_base::generate_with(std::vector& events, - int mixin, uint64_t amount_paid, bool valid, + size_t inputs, size_t mixin, uint64_t amount_paid, bool valid, size_t threshold, size_t total, size_t creator, std::vector signers, const std::function &sources, std::vector &destinations)> &pre_tx, const std::function &post_tx) const @@ -58,6 +59,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector= 1 && inputs <= 8, false, "Inputs should between 1 and 8"); // given as 1 based for clarity --creator; @@ -83,14 +85,14 @@ bool gen_multisig_tx_validation_base::generate_with(std::vectortimestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long @@ -120,126 +122,147 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector(blocks[0].miner_tx.vout[0].target).key; - MDEBUG("output_pub_key: " << output_pub_key); cryptonote::keypair in_ephemeral; + crypto::public_key tx_pub_key[n_coinbases]; + crypto::public_key output_pub_key[n_coinbases]; + for (size_t n = 0; n < n_coinbases; ++n) + { + 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(blocks[n].miner_tx.vout[0].target).key; + MDEBUG("output_pub_key: " << output_pub_key); + } + + std::unordered_map 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> account_k(total); - std::vector> account_L(total); - std::vector> account_R(total); - std::vector> account_ki(total); + std::vector>> account_k(total); + std::vector>> account_L(total); + std::vector>> account_R(total); + std::vector>> account_ki(total); std::vector additional_tx_keys; - std::unordered_map subaddresses; - subaddresses[miner_account[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; for (size_t msidx = 0; msidx < total; ++msidx) { CHECK_AND_ASSERT_MES(miner_account[msidx].get_keys().m_account_address.m_spend_public_key == miner_account[0].get_keys().m_account_address.m_spend_public_key, false, "Mismatched spend public keys"); size_t nlr = threshold < total ? threshold - 1 : 1; - account_L[msidx].resize(nlr); - account_R[msidx].resize(nlr); - for (size_t n = 0; n < nlr; ++n) + account_k[msidx].resize(inputs); + account_L[msidx].resize(inputs); + account_R[msidx].resize(inputs); + account_ki[msidx].resize(inputs); + for (size_t tdidx = 0; tdidx < inputs; ++tdidx) { - account_k[msidx].push_back(rct::rct2sk(rct::skGen())); - cryptonote::generate_multisig_LR(output_pub_key, account_k[msidx][n], account_L[msidx][n], account_R[msidx][n]); + account_L[msidx][tdidx].resize(nlr); + account_R[msidx][tdidx].resize(nlr); + for (size_t n = 0; n < nlr; ++n) + { + account_k[msidx][tdidx].push_back(rct::rct2sk(rct::skGen())); + cryptonote::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) + { + r = cryptonote::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"); + } + MDEBUG("Party " << msidx << ":"); + MDEBUG("spend: sec " << miner_account[msidx].get_keys().m_spend_secret_key << ", pub " << miner_account[msidx].get_keys().m_account_address.m_spend_public_key); + MDEBUG("view: sec " << miner_account[msidx].get_keys().m_view_secret_key << ", pub " << miner_account[msidx].get_keys().m_account_address.m_view_public_key); + for (const auto &k: miner_account[msidx].get_multisig_keys()) + MDEBUG("msk: " << k); + for (size_t n = 0; n < account_k[msidx][tdidx].size(); ++n) + { + MDEBUG("k: " << account_k[msidx][tdidx][n]); + MDEBUG("L: " << account_L[msidx][tdidx][n]); + MDEBUG("R: " << account_R[msidx][tdidx][n]); + } + for (const auto &ki: account_ki[msidx][tdidx]) + MDEBUG("ki: " << ki); } - size_t numki = miner_account[msidx].get_multisig_keys().size(); - account_ki[msidx].resize(numki); - for (size_t kiidx = 0; kiidx < numki; ++kiidx) - { - r = cryptonote::generate_multisig_key_image(miner_account[msidx].get_keys(), kiidx, output_pub_key, account_ki[msidx][kiidx]); - CHECK_AND_ASSERT_MES(r, false, "Failed to generate multisig export key image"); - } - MDEBUG("Party " << msidx << ":"); - MDEBUG("spend: sec " << miner_account[msidx].get_keys().m_spend_secret_key << ", pub " << miner_account[msidx].get_keys().m_account_address.m_spend_public_key); - MDEBUG("view: sec " << miner_account[msidx].get_keys().m_view_secret_key << ", pub " << miner_account[msidx].get_keys().m_account_address.m_view_public_key); - for (const auto &k: miner_account[msidx].get_multisig_keys()) - MDEBUG("msk: " << k); - for (size_t n = 0; n < account_k[msidx].size(); ++n) - { - MDEBUG("k: " << account_k[msidx][n]); - MDEBUG("L: " << account_L[msidx][n]); - MDEBUG("R: " << account_R[msidx][n]); - } - for (const auto &ki: account_ki[msidx]) - MDEBUG("ki: " << ki); } #endif // create kLRki - rct::multisig_kLRki kLRki; -#ifdef NO_MULTISIG - kLRki = {rct::zero(), rct::zero(), rct::zero(), rct::zero()}; -#else - kLRki.k = rct::sk2rct(account_k[creator][0]); - kLRki.L = rct::pk2rct(account_L[creator][0]); - kLRki.R = rct::pk2rct(account_R[creator][0]); - MDEBUG("Starting with k " << kLRki.k); - MDEBUG("Starting with L " << kLRki.L); - MDEBUG("Starting with R " << kLRki.R); + std::vector kLRkis; std::unordered_set used_L; - for (size_t msidx = 0; msidx < total; ++msidx) + for (size_t tdidx = 0; tdidx < inputs; ++tdidx) { - if (msidx == creator) - continue; - if (std::find(signers.begin(), signers.end(), msidx) == signers.end()) - continue; - for (size_t lr = 0; lr < account_L[msidx].size(); ++lr) + 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 (used_L.find(account_L[msidx][lr]) == used_L.end()) + 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) { - used_L.insert(account_L[msidx][lr]); - MDEBUG("Adding L " << account_L[msidx][lr] << " (for k " << account_k[msidx][lr] << ")"); - MDEBUG("Adding R " << account_R[msidx][lr]); - rct::addKeys((rct::key&)kLRki.L, kLRki.L, rct::pk2rct(account_L[msidx][lr])); - rct::addKeys((rct::key&)kLRki.R, kLRki.R, rct::pk2rct(account_R[msidx][lr])); - break; + 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 pkis; - for (size_t msidx = 0; msidx < total; ++msidx) - for (size_t n = 0; n < account_ki[msidx].size(); ++n) - pkis.push_back(account_ki[msidx][n]); - r = cryptonote::generate_multisig_composite_key_image(miner_account[0].get_keys(), subaddresses, output_pub_key, tx_pub_key, 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; - r = cryptonote::generate_multisig_composite_key_image(miner_account[n].get_keys(), subaddresses, output_pub_key, tx_pub_key, additional_tx_keys, 0, pkis, (crypto::key_image&)ki); + std::vector pkis; + for (size_t msidx = 0; msidx < total; ++msidx) + for (size_t n = 0; n < account_ki[msidx][tdidx].size(); ++n) + pkis.push_back(account_ki[msidx][tdidx][n]); + r = cryptonote::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"); - CHECK_AND_ASSERT_MES(kLRki.ki == ki, false, "Composite key images do not match"); + MDEBUG("composite ki: " << kLRki.ki); + MDEBUG("L: " << kLRki.L); + MDEBUG("R: " << kLRki.R); + for (size_t n = 1; n < total; ++n) + { + rct::key ki; + r = cryptonote::generate_multisig_composite_key_image(miner_account[n].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)ki); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image"); + 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 + // create a tx: we have 8 outputs, all from coinbase, so "fake" rct - use 2 std::vector sources; - sources.resize(1); - tx_source_entry& src = sources.back(); - - src.real_output = 0; - src.amount = blocks[0].miner_tx.vout[0].amount; - src.real_out_tx_key = tx_pub_key; - src.real_output_in_tx_index = 0; - src.mask = rct::identity(); - src.rct = true; - src.multisig_kLRki = kLRki; - - for (int m = 0; m <= mixin; ++m) + for (size_t n = 0; n < inputs; ++n) { - rct::ctkey ctkey; - ctkey.dest = rct::pk2rct(boost::get(blocks[m].miner_tx.vout[0].target).key); - MDEBUG("using " << (m == 0 ? "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)); + sources.resize(sources.size() + 1); + tx_source_entry& src = sources.back(); + + src.real_output = n; + src.amount = blocks[n].miner_tx.vout[0].amount; + src.real_out_tx_key = tx_pub_key[n]; + src.real_output_in_tx_index = 0; + src.mask = rct::identity(); + src.rct = true; + src.multisig_kLRki = kLRkis[n]; + + for (size_t m = 0; m <= mixin; ++m) + { + rct::ctkey ctkey; + ctkey.dest = rct::pk2rct(boost::get(blocks[m].miner_tx.vout[0].target).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)); + } } //fill outputs entry @@ -261,9 +284,26 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector additional_tx_secret_keys; + auto sources_copy = sources; r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector(), tx, 0, tx_key, additional_tx_secret_keys, true, false, msoutp); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); +#ifndef NO_MULTISIG + // work out the permutation done on sources + std::vector ins_order; + for (size_t n = 0; n < sources.size(); ++n) + { + for (size_t idx = 0; idx < sources_copy.size(); ++idx) + { + CHECK_AND_ASSERT_MES((size_t)sources_copy[idx].real_output < sources_copy[idx].outputs.size(), + false, "Invalid real_output"); + if (sources_copy[idx].outputs[sources_copy[idx].real_output].second.dest == sources[n].outputs[sources[n].real_output].second.dest) + ins_order.push_back(idx); + } + } + CHECK_AND_ASSERT_MES(ins_order.size() == sources.size(), false, "Failed to work out sources permutation"); +#endif + #ifndef NO_MULTISIG // sign std::unordered_set used_keys; @@ -285,20 +325,25 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector indices; - for (const auto &src: sources) + for (const auto &src: sources_copy) indices.push_back(src.real_output); rct::keyV k; - k.push_back(rct::zero()); - for (size_t n = 0; n < account_k[signer].size(); ++n) + for (size_t tdidx = 0; tdidx < inputs; ++tdidx) { - crypto::public_key L; - rct::scalarmultBase((rct::key&)L, rct::sk2rct(account_k[signer][n])); - if (used_L.find(L) != used_L.end()) + k.push_back(rct::zero()); + for (size_t n = 0; n < account_k[signer][tdidx].size(); ++n) { - sc_add(k.back().bytes, k.back().bytes, rct::sk2rct(account_k[signer][n]).bytes); + 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); + } } + CHECK_AND_ASSERT_MES(!(k.back() == 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); MDEBUG("signing with k size " << k.size()); MDEBUG("signing with k " << k.back()); @@ -353,112 +398,126 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); +} + +bool gen_multisig_tx_valid_22_1_2_many_inputs::generate(std::vector& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 4, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); } bool gen_multisig_tx_valid_22_2_1::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, true, 2, 2, 2, {1}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 2, {1}, NULL, NULL); } bool gen_multisig_tx_valid_33_1_23::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, true, 3, 3, 1, {2, 3}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 1, {2, 3}, NULL, NULL); } bool gen_multisig_tx_valid_33_3_21::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, true, 3, 3, 3, {2, 1}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 3, {2, 1}, NULL, NULL); } bool gen_multisig_tx_valid_23_1_2::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, true, 2, 3, 1, {2}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {2}, NULL, NULL); } bool gen_multisig_tx_valid_23_1_3::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, true, 2, 3, 1, {3}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {3}, NULL, NULL); } bool gen_multisig_tx_valid_23_2_1::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, true, 2, 3, 2, {1}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {1}, NULL, NULL); } bool gen_multisig_tx_valid_23_2_3::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, true, 2, 3, 2, {3}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {3}, NULL, NULL); } bool gen_multisig_tx_valid_45_1_234::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, true, 4, 5, 1, {2, 3, 4}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, true, 4, 5, 1, {2, 3, 4}, NULL, NULL); +} + +bool gen_multisig_tx_valid_45_4_135_many_inputs::generate(std::vector& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 4, mixin, amount_paid, true, 4, 5, 4, {1, 3, 5}, NULL, NULL); } bool gen_multisig_tx_valid_89_3_1245789::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, true, 8, 9, 3, {1, 2, 4, 5, 7, 8, 9}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, true, 8, 9, 3, {1, 2, 4, 5, 7, 8, 9}, NULL, NULL); } bool gen_multisig_tx_invalid_22_1__no_threshold::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, false, 2, 2, 1, {}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, false, 2, 2, 1, {}, NULL, NULL); } bool gen_multisig_tx_invalid_33_1__no_threshold::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, false, 3, 3, 1, {}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {}, NULL, NULL); } bool gen_multisig_tx_invalid_33_1_2_no_threshold::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, false, 3, 3, 1, {2}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {2}, NULL, NULL); } bool gen_multisig_tx_invalid_33_1_3_no_threshold::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, false, 3, 3, 1, {3}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {3}, NULL, NULL); } bool gen_multisig_tx_invalid_23_1__no_threshold::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, false, 2, 3, 1, {}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, false, 2, 3, 1, {}, NULL, NULL); } bool gen_multisig_tx_invalid_45_5_23_no_threshold::generate(std::vector& events) const { - const int mixin = 4; + const size_t mixin = 4; const uint64_t amount_paid = 10000; - return generate_with(events, mixin, amount_paid, false, 4, 5, 5, {2, 3}, NULL, NULL); + return generate_with(events, 2, mixin, amount_paid, false, 4, 5, 5, {2, 3}, NULL, NULL); } diff --git a/tests/core_tests/multisig.h b/tests/core_tests/multisig.h index 735c3b73d..62a1c6a35 100644 --- a/tests/core_tests/multisig.h +++ b/tests/core_tests/multisig.h @@ -69,7 +69,7 @@ struct gen_multisig_tx_validation_base : public test_chain_unit_base return true; } - bool generate_with(std::vector& events, int mixin, + bool generate_with(std::vector& events, size_t inputs, size_t mixin, uint64_t amount_paid, bool valid, size_t threshold, size_t total, size_t creator, std::vector signers, const std::function &sources, std::vector &destinations)> &pre_tx, @@ -95,6 +95,12 @@ struct gen_multisig_tx_valid_22_1_2: public gen_multisig_tx_validation_base }; template<> struct get_test_options: public get_test_options {}; +struct gen_multisig_tx_valid_22_1_2_many_inputs: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + struct gen_multisig_tx_valid_22_2_1: public gen_multisig_tx_validation_base { bool generate(std::vector& events) const; @@ -141,8 +147,14 @@ struct gen_multisig_tx_valid_45_1_234: public gen_multisig_tx_validation_base { bool generate(std::vector& events) const; }; - template<> struct get_test_options: public get_test_options {}; + +struct gen_multisig_tx_valid_45_4_135_many_inputs: public gen_multisig_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_test_options {}; + struct gen_multisig_tx_valid_89_3_1245789: public gen_multisig_tx_validation_base { bool generate(std::vector& events) const; From ceabc4f92b8b3a5c3426e4257654608cdb54797a Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Wed, 29 Nov 2017 16:55:50 +0000 Subject: [PATCH 19/19] change the N-1/N multisig second message signer for auth --- src/wallet/wallet2.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 70180e080..04c6ee236 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2832,7 +2832,8 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, // We need an extra step, so we package all the composite public keys // we know about, and make a signed string out of them std::string data; - const crypto::public_key signer = get_multisig_signer_public_key(rct::rct2sk(spend_skey)); + crypto::public_key signer; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(rct::rct2sk(spend_skey), signer), "Failed to derive public spend key"); data += std::string((const char *)&signer, sizeof(crypto::public_key)); for (const auto &msk: multisig_keys) @@ -2844,7 +2845,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, data.resize(data.size() + sizeof(crypto::signature)); crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, signer, get_multisig_blinded_secret_key(rct::rct2sk(spend_skey)), signature); + crypto::generate_signature(hash, signer, rct::rct2sk(spend_skey), signature); extra_multisig_info = std::string("MultisigxV1") + tools::base58::encode(data); } @@ -2954,7 +2955,9 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor CHECK_AND_ASSERT_THROW_MES(!pkeys.empty(), "empty pkeys"); // add ours if not included - const crypto::public_key local_signer = get_multisig_signer_public_key(); + crypto::public_key local_signer; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, local_signer), + "Failed to derive public spend key"); if (std::find(signers.begin(), signers.end(), local_signer) == signers.end()) { signers.push_back(local_signer); @@ -8381,13 +8384,9 @@ crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_ crypto::public_key wallet2::get_multisig_signer_public_key() const { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); - if (m_multisig_threshold == m_multisig_signers.size()) - { - crypto::public_key signer; - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, signer), "Failed to generate signer public key"); - return signer; - } - return get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); + crypto::public_key signer; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, signer), "Failed to generate signer public key"); + return signer; } //---------------------------------------------------------------------------------------------------- crypto::public_key wallet2::get_multisig_signing_public_key(const crypto::secret_key &msk) const