diff options
Diffstat (limited to 'src/multisig')
-rw-r--r-- | src/multisig/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/multisig/multisig.cpp | 2 | ||||
-rw-r--r-- | src/multisig/multisig.h | 2 | ||||
-rw-r--r-- | src/multisig/multisig_account.cpp | 2 | ||||
-rw-r--r-- | src/multisig/multisig_account.h | 47 | ||||
-rw-r--r-- | src/multisig/multisig_account_kex_impl.cpp | 177 | ||||
-rw-r--r-- | src/multisig/multisig_clsag_context.cpp | 2 | ||||
-rw-r--r-- | src/multisig/multisig_clsag_context.h | 2 | ||||
-rw-r--r-- | src/multisig/multisig_kex_msg.cpp | 2 | ||||
-rw-r--r-- | src/multisig/multisig_kex_msg.h | 2 | ||||
-rw-r--r-- | src/multisig/multisig_kex_msg_serialization.h | 2 | ||||
-rw-r--r-- | src/multisig/multisig_tx_builder_ringct.cpp | 2 | ||||
-rw-r--r-- | src/multisig/multisig_tx_builder_ringct.h | 2 |
13 files changed, 170 insertions, 76 deletions
diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt index 62ae00e80..751909330 100644 --- a/src/multisig/CMakeLists.txt +++ b/src/multisig/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2023, The Monero Project +# Copyright (c) 2017-2024, The Monero Project # # All rights reserved. # diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp index 70baa7f19..9e57dd331 100644 --- a/src/multisig/multisig.cpp +++ b/src/multisig/multisig.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, The Monero Project +// Copyright (c) 2017-2024, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h index 2ec2f4e66..cca96ef6c 100644 --- a/src/multisig/multisig.h +++ b/src/multisig/multisig.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, The Monero Project +// Copyright (c) 2017-2024, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_account.cpp b/src/multisig/multisig_account.cpp index 4f9711b15..012fdc3a5 100644 --- a/src/multisig/multisig_account.cpp +++ b/src/multisig/multisig_account.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2023, The Monero Project +// Copyright (c) 2021-2024, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h index 2ea8d0133..917f5c87a 100644 --- a/src/multisig/multisig_account.h +++ b/src/multisig/multisig_account.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2023, The Monero Project +// Copyright (c) 2021-2024, The Monero Project // // All rights reserved. // @@ -103,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); @@ -190,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) diff --git a/src/multisig/multisig_account_kex_impl.cpp b/src/multisig/multisig_account_kex_impl.cpp index ef0acf307..5d2f362e5 100644 --- a/src/multisig/multisig_account_kex_impl.cpp +++ b/src/multisig/multisig_account_kex_impl.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2023, 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 @@ -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 + // 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, 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.emplace_back(shared_key_with_origins.first); + } - // 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); + 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; - // add local ancillary base privkey - participant_base_common_privkeys.emplace_back(m_base_common_privkey); + // the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys, so we prepare + // them here - // 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()); - } - } + // 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); - // make common privkey - make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey); + // add local ancillary base privkey + participant_base_common_privkeys.emplace_back(m_base_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"); + // 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()); + } - // 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); - } + // make common privkey + make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_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); - } - else - { - // in other rounds, kex msgs will contain participants' shared keys + // 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"); - // 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); - } + // 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 diff --git a/src/multisig/multisig_clsag_context.cpp b/src/multisig/multisig_clsag_context.cpp index 81e471287..8eec81719 100644 --- a/src/multisig/multisig_clsag_context.cpp +++ b/src/multisig/multisig_clsag_context.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2023, The Monero Project +// Copyright (c) 2021-2024, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_clsag_context.h b/src/multisig/multisig_clsag_context.h index c184b83d6..20188bf3b 100644 --- a/src/multisig/multisig_clsag_context.h +++ b/src/multisig/multisig_clsag_context.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2023, The Monero Project +// Copyright (c) 2021-2024, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_kex_msg.cpp b/src/multisig/multisig_kex_msg.cpp index 321305283..a6643e8fc 100644 --- a/src/multisig/multisig_kex_msg.cpp +++ b/src/multisig/multisig_kex_msg.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2023, The Monero Project +// Copyright (c) 2021-2024, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_kex_msg.h b/src/multisig/multisig_kex_msg.h index 8cb8faa7c..180449a34 100644 --- a/src/multisig/multisig_kex_msg.h +++ b/src/multisig/multisig_kex_msg.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2023, The Monero Project +// Copyright (c) 2021-2024, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_kex_msg_serialization.h b/src/multisig/multisig_kex_msg_serialization.h index d1e07ea8a..4d931d491 100644 --- a/src/multisig/multisig_kex_msg_serialization.h +++ b/src/multisig/multisig_kex_msg_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2023, The Monero Project +// Copyright (c) 2021-2024, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp index 2653a70dd..e968fe00b 100644 --- a/src/multisig/multisig_tx_builder_ringct.cpp +++ b/src/multisig/multisig_tx_builder_ringct.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2023, The Monero Project +// Copyright (c) 2021-2024, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_tx_builder_ringct.h b/src/multisig/multisig_tx_builder_ringct.h index a1b72b177..0a9cfd1bc 100644 --- a/src/multisig/multisig_tx_builder_ringct.h +++ b/src/multisig/multisig_tx_builder_ringct.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2023, The Monero Project +// Copyright (c) 2021-2024, The Monero Project // // All rights reserved. // |