// Copyright (c) 2021-2023, The Monero Project // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "multisig_account.h" #include "crypto/crypto.h" #include "cryptonote_config.h" #include "include_base_utils.h" #include "multisig.h" #include "multisig_kex_msg.h" #include "ringct/rctOps.h" #include "ringct/rctTypes.h" #include #include #include #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "multisig" namespace multisig { //---------------------------------------------------------------------------------------------------------------------- // multisig_account: EXTERNAL //---------------------------------------------------------------------------------------------------------------------- multisig_account::multisig_account(const crypto::secret_key &base_privkey, const crypto::secret_key &base_common_privkey) : m_base_privkey{base_privkey}, m_base_common_privkey{base_common_privkey}, m_multisig_pubkey{rct::rct2pk(rct::identity())}, m_common_pubkey{rct::rct2pk(rct::identity())}, m_kex_rounds_complete{0}, m_next_round_kex_message{multisig_kex_msg{1, base_privkey, std::vector{}, base_common_privkey}.get_msg()} { CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey), "Failed to derive public key"); } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: EXTERNAL //---------------------------------------------------------------------------------------------------------------------- multisig_account::multisig_account(const std::uint32_t threshold, std::vector signers, const crypto::secret_key &base_privkey, const crypto::secret_key &base_common_privkey, std::vector multisig_privkeys, const crypto::secret_key &common_privkey, const crypto::public_key &multisig_pubkey, const crypto::public_key &common_pubkey, const std::uint32_t kex_rounds_complete, multisig_keyset_map_memsafe_t kex_origins_map, std::string next_round_kex_message) : m_base_privkey{base_privkey}, m_base_common_privkey{base_common_privkey}, m_multisig_privkeys{std::move(multisig_privkeys)}, m_common_privkey{common_privkey}, m_multisig_pubkey{multisig_pubkey}, m_common_pubkey{common_pubkey}, m_kex_rounds_complete{kex_rounds_complete}, m_kex_keys_to_origins_map{std::move(kex_origins_map)}, m_next_round_kex_message{std::move(next_round_kex_message)} { CHECK_AND_ASSERT_THROW_MES(kex_rounds_complete > 0, "multisig account: can't reconstruct account if its kex wasn't initialized"); CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey), "Failed to derive public key"); set_multisig_config(threshold, std::move(signers)); // kex rounds should not exceed post-kex verification round const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(m_signers.size(), m_threshold)}; CHECK_AND_ASSERT_THROW_MES(m_kex_rounds_complete <= kex_rounds_required + 1, "multisig account: tried to reconstruct account, but kex rounds complete counter is invalid."); // once an account is done with kex, the 'next kex msg' is always the post-kex verification message // i.e. the multisig account pubkey signed by the signer's privkey AND the common pubkey if (main_kex_rounds_done()) { m_next_round_kex_message = multisig_kex_msg{kex_rounds_required + 1, m_base_privkey, std::vector{m_multisig_pubkey, m_common_pubkey}}.get_msg(); } } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: EXTERNAL //---------------------------------------------------------------------------------------------------------------------- bool multisig_account::account_is_active() const { return m_kex_rounds_complete > 0; } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: EXTERNAL //---------------------------------------------------------------------------------------------------------------------- bool multisig_account::main_kex_rounds_done() const { if (account_is_active()) return m_kex_rounds_complete >= multisig_kex_rounds_required(m_signers.size(), m_threshold); else return false; } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: EXTERNAL //---------------------------------------------------------------------------------------------------------------------- bool multisig_account::multisig_is_ready() const { if (main_kex_rounds_done()) return m_kex_rounds_complete >= multisig_setup_rounds_required(m_signers.size(), m_threshold); else return false; } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: INTERNAL //---------------------------------------------------------------------------------------------------------------------- void multisig_account::set_multisig_config(const std::size_t threshold, std::vector signers) { // validate CHECK_AND_ASSERT_THROW_MES(threshold > 0 && threshold <= signers.size(), "multisig account: tried to set invalid threshold."); CHECK_AND_ASSERT_THROW_MES(signers.size() >= 2 && signers.size() <= config::MULTISIG_MAX_SIGNERS, "multisig account: tried to set invalid number of signers."); for (auto signer_it = signers.begin(); signer_it != signers.end(); ++signer_it) { // signer pubkeys must be in main subgroup, and not identity CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(*signer_it)) && !(*signer_it == rct::rct2pk(rct::identity())), "multisig account: tried to set signers, but a signer pubkey is invalid."); } // own pubkey should be in signers list CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), m_base_pubkey) != signers.end(), "multisig account: tried to set signers, but did not find the account's base pubkey in signer list."); // sort signers std::sort(signers.begin(), signers.end()); // signers should all be unique CHECK_AND_ASSERT_THROW_MES(std::adjacent_find(signers.begin(), signers.end()) == signers.end(), "multisig account: tried to set signers, but there are duplicate signers unexpectedly."); // set m_threshold = threshold; m_signers = std::move(signers); } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: EXTERNAL //---------------------------------------------------------------------------------------------------------------------- void multisig_account::initialize_kex(const std::uint32_t threshold, std::vector signers, const std::vector &expanded_msgs_rnd1) { CHECK_AND_ASSERT_THROW_MES(!account_is_active(), "multisig account: tried to initialize kex, but already initialized"); // only mutate account if update succeeds multisig_account temp_account{*this}; temp_account.set_multisig_config(threshold, std::move(signers)); temp_account.kex_update_impl(expanded_msgs_rnd1, false); *this = std::move(temp_account); } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: EXTERNAL //---------------------------------------------------------------------------------------------------------------------- void multisig_account::kex_update(const std::vector &expanded_msgs, const bool force_update_use_with_caution /*= false*/) { CHECK_AND_ASSERT_THROW_MES(account_is_active(), "multisig account: tried to update kex, but kex isn't initialized yet."); CHECK_AND_ASSERT_THROW_MES(!multisig_is_ready(), "multisig account: tried to update kex, but kex is already complete."); multisig_account temp_account{*this}; temp_account.kex_update_impl(expanded_msgs, force_update_use_with_caution); *this = std::move(temp_account); } //---------------------------------------------------------------------------------------------------------------------- // EXTERNAL //---------------------------------------------------------------------------------------------------------------------- std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold) { CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold, "num_signers must be >= threshold"); CHECK_AND_ASSERT_THROW_MES(threshold >= 1, "threshold must be >= 1"); return num_signers - threshold + 1; } //---------------------------------------------------------------------------------------------------------------------- // EXTERNAL //---------------------------------------------------------------------------------------------------------------------- std::uint32_t multisig_setup_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold) { return multisig_kex_rounds_required(num_signers, threshold) + 1; } //---------------------------------------------------------------------------------------------------------------------- } //namespace multisig