// Copyright (c) 2021-2022, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#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 <cstdint>
#include <utility>
#include <vector>
#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<crypto::public_key>{}, 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<crypto::public_key> signers,
const crypto::secret_key &base_privkey,
const crypto::secret_key &base_common_privkey,
std::vector<crypto::secret_key> 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<crypto::public_key>{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_kex_rounds_required(m_signers.size(), m_threshold) + 1;
else
return false;
}
//----------------------------------------------------------------------------------------------------------------------
// multisig_account: INTERNAL
//----------------------------------------------------------------------------------------------------------------------
void multisig_account::set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> 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<crypto::public_key> signers,
const std::vector<multisig_kex_msg> &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);
*this = std::move(temp_account);
}
//----------------------------------------------------------------------------------------------------------------------
// multisig_account: EXTERNAL
//----------------------------------------------------------------------------------------------------------------------
void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs)
{
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);
*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;
}
//----------------------------------------------------------------------------------------------------------------------
} //namespace multisig