upstream: sync against wallet_keys_unlocker PR #9860
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021-2022, The Monero Project
|
||||
// Copyright (c) 2021-2024, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
@@ -40,6 +40,19 @@
|
||||
|
||||
namespace multisig
|
||||
{
|
||||
struct multisig_account_status
|
||||
{
|
||||
// is the multisig account active/initialized?
|
||||
bool multisig_is_active{false};
|
||||
// has the multisig account completed the main key exchange rounds?
|
||||
bool kex_is_done{false};
|
||||
// is the multisig account ready to use?
|
||||
bool is_ready{false};
|
||||
// multisig is: M-of-N
|
||||
std::uint32_t threshold{0}; // M
|
||||
std::uint32_t total{0}; // N
|
||||
};
|
||||
|
||||
/**
|
||||
* multisig account:
|
||||
*
|
||||
@@ -90,7 +103,8 @@ namespace multisig
|
||||
*
|
||||
* - prepares a kex msg for the first round of multisig key construction.
|
||||
* - the local account's kex msgs are signed with the base_privkey
|
||||
* - the first kex msg transmits the local base_common_privkey to other participants, for creating the group's common_privkey
|
||||
* - the first kex msg transmits the local base_common_privkey to other participants, for creating the group's
|
||||
* common_privkey
|
||||
*/
|
||||
multisig_account(const crypto::secret_key &base_privkey,
|
||||
const crypto::secret_key &base_common_privkey);
|
||||
@@ -142,7 +156,7 @@ namespace multisig
|
||||
const std::string& get_next_kex_round_msg() const { return m_next_round_kex_message; }
|
||||
|
||||
//account status functions
|
||||
// account has been intialized, and the account holder can use the 'common' key
|
||||
// account has been initialized, and the account holder can use the 'common' key
|
||||
bool account_is_active() const;
|
||||
// account has gone through main kex rounds, only remaining step is to verify all other participants are ready
|
||||
bool main_kex_rounds_done() const;
|
||||
@@ -177,24 +191,48 @@ namespace multisig
|
||||
* - If force updating with maliciously-crafted messages, the resulting account will be invalid (either unable
|
||||
* to complete signatures, or a 'hostage' to the malicious signer [i.e. can't sign without his participation]).
|
||||
*/
|
||||
void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
|
||||
const bool force_update_use_with_caution = false);
|
||||
void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, const bool force_update_use_with_caution = false);
|
||||
/**
|
||||
* brief: get_multisig_kex_round_booster - Create a multisig kex msg for the kex round that follows the kex round this
|
||||
* account is currently working on, in order to 'boost' another participant's kex setup.
|
||||
* - A booster message is for the round after the in-progress round because get_next_kex_round_msg() provides access
|
||||
* to the in-progress round's message.
|
||||
* - Useful for 'jumpstarting' the following kex round when you don't have messages from all other signers to complete
|
||||
* the current round.
|
||||
* - Sanitizes input messages and produces a new kex msg for round 'num_completed_rounds + 2'.
|
||||
*
|
||||
* - For example, in 2-of-3 escrowed purchasing, the [vendor, arbitrator] pair can boost the second round
|
||||
* of key exchange by calling this function with the 'round 1' messages of each other.
|
||||
* Then the [buyer] can use the resulting boost messages, in combination with [vender, arbitrator] round 1 messages,
|
||||
* to complete the address in one step. In other words, call initialize_kex() on the round 1 messages,
|
||||
* then call kex_update() on the round 2 booster messages to finish the multisig key.
|
||||
*
|
||||
* - Note: The 'threshold' and 'num_signers' are inputs here in case kex has not been initialized yet.
|
||||
* param: threshold - threshold for multisig (M in M-of-N)
|
||||
* param: num_signers - number of participants in multisig (N)
|
||||
* param: expanded_msgs - set of multisig kex messages to process
|
||||
* return: multisig kex message for next round
|
||||
*/
|
||||
multisig_kex_msg get_multisig_kex_round_booster(const std::uint32_t threshold,
|
||||
const std::uint32_t num_signers,
|
||||
const std::vector<multisig_kex_msg> &expanded_msgs) const;
|
||||
|
||||
private:
|
||||
// implementation of kex_update() (non-transactional)
|
||||
void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs, const bool incomplete_signer_set);
|
||||
/**
|
||||
* brief: initialize_kex_update - Helper for kex_update_impl()
|
||||
* - Collect the local signer's shared keys to ignore in incoming messages, build the aggregate ancillary key
|
||||
* if appropriate.
|
||||
* brief: get_kex_exclude_pubkeys - collect the local signer's shared keys to ignore in incoming messages
|
||||
* return: keys held by the local account corresponding to the 'in-progress round'
|
||||
* - If 'in-progress round' is the final round, these are the local account's shares of the final aggregate key.
|
||||
*/
|
||||
std::vector<crypto::public_key> get_kex_exclude_pubkeys() const;
|
||||
/**
|
||||
* brief: initialize_kex_update - initialize the multisig account for the first kex round
|
||||
* param: expanded_msgs - set of multisig kex messages to process
|
||||
* param: kex_rounds_required - number of rounds required for kex (not including post-kex verification round)
|
||||
* outparam: exclude_pubkeys_out - keys held by the local account corresponding to round 'current_round'
|
||||
* - If 'current_round' is the final round, these are the local account's shares of the final aggregate key.
|
||||
*/
|
||||
void initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
|
||||
const std::uint32_t kex_rounds_required,
|
||||
std::vector<crypto::public_key> &exclude_pubkeys_out);
|
||||
const std::uint32_t kex_rounds_required);
|
||||
/**
|
||||
* brief: finalize_kex_update - Helper for kex_update_impl()
|
||||
* param: kex_rounds_required - number of rounds required for kex (not including post-kex verification round)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021-2022, The Monero Project
|
||||
// Copyright (c) 2021-2024, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
@@ -395,7 +395,7 @@ namespace multisig
|
||||
* - Sanitizes input msgs.
|
||||
* - Require uniqueness in: 'exclude_pubkeys'.
|
||||
* - Requires each input pubkey be recommended by 'num_recommendations = expected_round' msg signers.
|
||||
* - For a final multisig key to be truly 'M-of-N', each of the the private key's components must be
|
||||
* - For a final multisig key to be truly 'M-of-N', each of the private key's components must be
|
||||
* shared by (N - M + 1) signers.
|
||||
* - Requires that msgs are signed by only keys in 'signers'.
|
||||
* - Requires that each key in 'signers' recommends [num_signers - 2 CHOOSE (expected_round - 1)] pubkeys.
|
||||
@@ -567,7 +567,7 @@ namespace multisig
|
||||
|
||||
// note: do NOT remove the local signer from the pubkey origins map, since the post-kex round can be force-updated with
|
||||
// just the local signer's post-kex message (if the local signer were removed, then the post-kex message's pubkeys
|
||||
// would be completely lost)
|
||||
// would be completely deleted)
|
||||
|
||||
// evaluate pubkeys collected
|
||||
|
||||
@@ -598,7 +598,7 @@ namespace multisig
|
||||
// note: if num_signers_required == signers.size(), then this test will ensure all signers are present in 'origins',
|
||||
// which contains only unique pubkeys
|
||||
CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), origin) != signers.end(),
|
||||
"An unknown origin recommended a multisig post-kex verification messsage.");
|
||||
"An unknown origin recommended a multisig post-kex verification message.");
|
||||
}
|
||||
|
||||
return pubkey_origins_map;
|
||||
@@ -608,7 +608,7 @@ namespace multisig
|
||||
* INTERNAL
|
||||
*
|
||||
* brief: multisig_kex_process_round_msgs - Process kex messages for the active kex round.
|
||||
* - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_next_msg().
|
||||
* - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_round_keys().
|
||||
* - In other words, evaluate the input messages and try to make a message for the next round.
|
||||
* - Note: Must be called on the final round's msgs to evaluate the final key components
|
||||
* recommended by other participants.
|
||||
@@ -623,7 +623,7 @@ namespace multisig
|
||||
* outparam: keys_to_origins_map_out - map between round keys and identity keys
|
||||
* - If in the final round, these are key shares recommended by other signers for the final aggregate key.
|
||||
* - Otherwise, these are the local account's DH derivations for the next round.
|
||||
* - See multisig_kex_make_next_msg() for an explanation.
|
||||
* - See multisig_kex_make_round_keys() for an explanation.
|
||||
* return: multisig kex message for next round, or empty message if 'current_round' is the final round
|
||||
*/
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
@@ -684,59 +684,67 @@ namespace multisig
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
// multisig_account: INTERNAL
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
|
||||
const std::uint32_t kex_rounds_required,
|
||||
std::vector<crypto::public_key> &exclude_pubkeys_out)
|
||||
std::vector<crypto::public_key> multisig_account::get_kex_exclude_pubkeys() const
|
||||
{
|
||||
// exclude all keys the local account recommends
|
||||
std::vector<crypto::public_key> exclude_pubkeys;
|
||||
|
||||
if (m_kex_rounds_complete == 0)
|
||||
{
|
||||
// the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys
|
||||
|
||||
// collect participants' base common privkey shares
|
||||
// note: duplicate privkeys are acceptable, and duplicates due to duplicate signers
|
||||
// will be blocked by duplicate-signer errors after this function is called
|
||||
std::vector<crypto::secret_key> participant_base_common_privkeys;
|
||||
participant_base_common_privkeys.reserve(expanded_msgs.size() + 1);
|
||||
|
||||
// add local ancillary base privkey
|
||||
participant_base_common_privkeys.emplace_back(m_base_common_privkey);
|
||||
|
||||
// add other signers' base common privkeys
|
||||
for (const auto &expanded_msg : expanded_msgs)
|
||||
{
|
||||
if (expanded_msg.get_signing_pubkey() != m_base_pubkey)
|
||||
{
|
||||
participant_base_common_privkeys.emplace_back(expanded_msg.get_msg_privkey());
|
||||
}
|
||||
}
|
||||
|
||||
// make common privkey
|
||||
make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey);
|
||||
|
||||
// set common pubkey
|
||||
CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_common_privkey, m_common_pubkey),
|
||||
"Failed to derive public key");
|
||||
|
||||
// if N-of-N, then the base privkey will be used directly to make the account's share of the final key
|
||||
if (kex_rounds_required == 1)
|
||||
{
|
||||
m_multisig_privkeys.clear();
|
||||
m_multisig_privkeys.emplace_back(m_base_privkey);
|
||||
}
|
||||
|
||||
// exclude all keys the local account recommends
|
||||
// - in the first round, only the local pubkey is recommended by the local signer
|
||||
exclude_pubkeys_out.emplace_back(m_base_pubkey);
|
||||
// in the first round, only the local pubkey is recommended by the local signer
|
||||
exclude_pubkeys.emplace_back(m_base_pubkey);
|
||||
}
|
||||
else
|
||||
{
|
||||
// in other rounds, kex msgs will contain participants' shared keys
|
||||
|
||||
// ignore shared keys the account helped create for this round
|
||||
// in other rounds, kex msgs will contain participants' shared keys, so ignore shared keys the account helped
|
||||
// create for this round
|
||||
for (const auto &shared_key_with_origins : m_kex_keys_to_origins_map)
|
||||
{
|
||||
exclude_pubkeys_out.emplace_back(shared_key_with_origins.first);
|
||||
}
|
||||
exclude_pubkeys.emplace_back(shared_key_with_origins.first);
|
||||
}
|
||||
|
||||
return exclude_pubkeys;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
// multisig_account: INTERNAL
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
|
||||
const std::uint32_t kex_rounds_required)
|
||||
{
|
||||
// initialization is only needed during the first round
|
||||
if (m_kex_rounds_complete > 0)
|
||||
return;
|
||||
|
||||
// the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys, so we prepare
|
||||
// them here
|
||||
|
||||
// collect participants' base common privkey shares
|
||||
// note: duplicate privkeys are acceptable, and duplicates due to duplicate signers
|
||||
// will be blocked by duplicate-signer errors after this function is called
|
||||
std::vector<crypto::secret_key> participant_base_common_privkeys;
|
||||
participant_base_common_privkeys.reserve(expanded_msgs.size() + 1);
|
||||
|
||||
// add local ancillary base privkey
|
||||
participant_base_common_privkeys.emplace_back(m_base_common_privkey);
|
||||
|
||||
// add other signers' base common privkeys
|
||||
for (const multisig_kex_msg &expanded_msg : expanded_msgs)
|
||||
{
|
||||
if (expanded_msg.get_signing_pubkey() != m_base_pubkey)
|
||||
participant_base_common_privkeys.emplace_back(expanded_msg.get_msg_privkey());
|
||||
}
|
||||
|
||||
// make common privkey
|
||||
make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey);
|
||||
|
||||
// set common pubkey
|
||||
CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_common_privkey, m_common_pubkey),
|
||||
"Failed to derive public key");
|
||||
|
||||
// if N-of-N, then the base privkey will be used directly to make the account's share of the final key
|
||||
if (kex_rounds_required == 1)
|
||||
{
|
||||
m_multisig_privkeys.clear();
|
||||
m_multisig_privkeys.emplace_back(m_base_privkey);
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
@@ -771,9 +779,7 @@ namespace multisig
|
||||
result_keys.reserve(result_keys_to_origins_map.size());
|
||||
|
||||
for (const auto &result_key_and_origins : result_keys_to_origins_map)
|
||||
{
|
||||
result_keys.emplace_back(result_key_and_origins.first);
|
||||
}
|
||||
|
||||
// compute final aggregate key, update local multisig privkeys with aggregation coefficients applied
|
||||
m_multisig_pubkey = generate_multisig_aggregate_key(std::move(result_keys), m_multisig_privkeys);
|
||||
@@ -811,7 +817,8 @@ namespace multisig
|
||||
// derived pubkey = multisig_key * G
|
||||
crypto::public_key_memsafe derived_pubkey;
|
||||
m_multisig_privkeys.push_back(
|
||||
calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey));
|
||||
calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey)
|
||||
);
|
||||
|
||||
// save the account's kex key mappings for this round [derived pubkey : other signers who will have the same key]
|
||||
m_kex_keys_to_origins_map[derived_pubkey] = std::move(derivation_and_origins.second);
|
||||
@@ -863,8 +870,7 @@ namespace multisig
|
||||
"Multisig kex has already completed all required rounds (including post-kex verification).");
|
||||
|
||||
// initialize account update
|
||||
std::vector<crypto::public_key> exclude_pubkeys;
|
||||
initialize_kex_update(expanded_msgs, kex_rounds_required, exclude_pubkeys);
|
||||
this->initialize_kex_update(expanded_msgs, kex_rounds_required);
|
||||
|
||||
// process messages into a [pubkey : {origins}] map
|
||||
multisig_keyset_map_memsafe_t result_keys_to_origins_map;
|
||||
@@ -875,12 +881,75 @@ namespace multisig
|
||||
m_threshold,
|
||||
m_signers,
|
||||
expanded_msgs,
|
||||
exclude_pubkeys,
|
||||
this->get_kex_exclude_pubkeys(),
|
||||
incomplete_signer_set,
|
||||
result_keys_to_origins_map);
|
||||
|
||||
// finish account update
|
||||
finalize_kex_update(kex_rounds_required, std::move(result_keys_to_origins_map));
|
||||
this->finalize_kex_update(kex_rounds_required, std::move(result_keys_to_origins_map));
|
||||
}
|
||||
//-----------------------------------------------------------------
|
||||
// multisig_account: EXTERNAL
|
||||
//-----------------------------------------------------------------
|
||||
multisig_kex_msg multisig_account::get_multisig_kex_round_booster(const std::uint32_t threshold,
|
||||
const std::uint32_t num_signers,
|
||||
const std::vector<multisig_kex_msg> &expanded_msgs) const
|
||||
{
|
||||
// the messages passed in should be required for the next kex round of this account (the round it is currently
|
||||
// working on)
|
||||
const std::uint32_t expected_msgs_round{m_kex_rounds_complete + 1};
|
||||
const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(num_signers, threshold)};
|
||||
|
||||
CHECK_AND_ASSERT_THROW_MES(num_signers > 1, "Must be at least one other multisig signer.");
|
||||
CHECK_AND_ASSERT_THROW_MES(num_signers <= config::MULTISIG_MAX_SIGNERS, "Too many multisig signers specified.");
|
||||
CHECK_AND_ASSERT_THROW_MES(expected_msgs_round < kex_rounds_required,
|
||||
"Multisig kex booster: this account has already completed all intermediate kex rounds so it can't make a kex "
|
||||
"booster (there is no round available to boost).");
|
||||
CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input kex message expected.");
|
||||
|
||||
// sanitize pubkeys from input msgs
|
||||
multisig_keyset_map_memsafe_t pubkey_origins_map;
|
||||
const std::uint32_t msgs_round{
|
||||
multisig_kex_msgs_sanitize_pubkeys(expanded_msgs, this->get_kex_exclude_pubkeys(), pubkey_origins_map)
|
||||
};
|
||||
CHECK_AND_ASSERT_THROW_MES(msgs_round == expected_msgs_round, "Kex messages were not for expected round.");
|
||||
|
||||
// remove the local signer from sanitized messages
|
||||
remove_key_from_mapped_sets(m_base_pubkey, pubkey_origins_map);
|
||||
|
||||
// make DH derivations for booster message
|
||||
multisig_keyset_map_memsafe_t derivation_to_origins_map;
|
||||
multisig_kex_make_round_keys(m_base_privkey, std::move(pubkey_origins_map), derivation_to_origins_map);
|
||||
|
||||
// collect keys for booster message
|
||||
std::vector<crypto::public_key> next_msg_keys;
|
||||
next_msg_keys.reserve(derivation_to_origins_map.size());
|
||||
|
||||
if (msgs_round + 1 == kex_rounds_required)
|
||||
{
|
||||
// final kex round: send DH derivation pubkeys in the message
|
||||
for (const auto &derivation_and_origins : derivation_to_origins_map)
|
||||
{
|
||||
// multisig_privkey = H(derivation)
|
||||
// derived pubkey = multisig_key * G
|
||||
crypto::public_key_memsafe derived_pubkey;
|
||||
calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey);
|
||||
|
||||
// save keys that should be recommended to other signers
|
||||
// - The keys multisig_key*G are sent to other participants in the message, so they can be used to produce the final
|
||||
// multisig key via generate_multisig_spend_public_key().
|
||||
next_msg_keys.push_back(derived_pubkey);
|
||||
}
|
||||
}
|
||||
else //(msgs_round + 1 < kex_rounds_required)
|
||||
{
|
||||
// intermediate kex round: send DH derivations directly in the message
|
||||
for (const auto &derivation_and_origins : derivation_to_origins_map)
|
||||
next_msg_keys.push_back(derivation_and_origins.first);
|
||||
}
|
||||
|
||||
// produce a kex message for the round after the round this account is currently working on
|
||||
return multisig_kex_msg{msgs_round + 1, m_base_privkey, std::move(next_msg_keys)}.get_msg();
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
} //namespace multisig
|
||||
|
||||
Reference in New Issue
Block a user