Merge pull request #8203
ddf3af1
add key exchange round booster to multisig_account (koe)
This commit is contained in:
commit
15b47b481c
|
@ -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)
|
||||
|
|
|
@ -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,13 +684,38 @@ 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);
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -702,13 +727,11 @@ namespace multisig
|
|||
participant_base_common_privkeys.emplace_back(m_base_common_privkey);
|
||||
|
||||
// add other signers' base common privkeys
|
||||
for (const auto &expanded_msg : expanded_msgs)
|
||||
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);
|
||||
|
@ -723,21 +746,6 @@ namespace multisig
|
|||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
// in other rounds, kex msgs will contain participants' shared keys
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
// multisig_account: INTERNAL
|
||||
|
@ -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
|
||||
|
|
|
@ -1354,6 +1354,21 @@ std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &inf
|
|||
return string();
|
||||
}
|
||||
|
||||
std::string WalletImpl::getMultisigKeyExchangeBooster(const std::vector<std::string> &info,
|
||||
const std::uint32_t threshold,
|
||||
const std::uint32_t num_signers) {
|
||||
try {
|
||||
clearStatus();
|
||||
|
||||
return m_wallet->get_multisig_key_exchange_booster(epee::wipeable_string(m_password), info, threshold, num_signers);
|
||||
} catch (const exception& e) {
|
||||
LOG_ERROR("Error on boosting multisig key exchange: " << e.what());
|
||||
setStatusError(string(tr("Failed to boost multisig key exchange: ")) + e.what());
|
||||
}
|
||||
|
||||
return string();
|
||||
}
|
||||
|
||||
bool WalletImpl::exportMultisigImages(string& images) {
|
||||
try {
|
||||
clearStatus();
|
||||
|
|
|
@ -148,6 +148,7 @@ public:
|
|||
std::string getMultisigInfo() const override;
|
||||
std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override;
|
||||
std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution = false) override;
|
||||
std::string getMultisigKeyExchangeBooster(const std::vector<std::string> &info, const uint32_t threshold, const uint32_t num_signers) override;
|
||||
bool exportMultisigImages(std::string& images) override;
|
||||
size_t importMultisigImages(const std::vector<std::string>& images) override;
|
||||
bool hasMultisigPartialKeyImages() const override;
|
||||
|
|
|
@ -802,6 +802,15 @@ struct Wallet
|
|||
* @return new info string if more rounds required or an empty string if wallet creation is done
|
||||
*/
|
||||
virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution) = 0;
|
||||
/**
|
||||
* @brief getMultisigKeyExchangeBooster - obtain partial information for the key exchange round after the in-progress round,
|
||||
* to speed up another signer's key exchange process
|
||||
* @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call
|
||||
* @param threshold - number of required signers to make valid transaction. Must be <= number of participants.
|
||||
* @param num_signers - total number of multisig participants.
|
||||
* @return new info string if more rounds required or exception if no more rounds (i.e. no rounds to boost)
|
||||
*/
|
||||
virtual std::string getMultisigKeyExchangeBooster(const std::vector<std::string> &info, const uint32_t threshold, const uint32_t num_signers) = 0;
|
||||
/**
|
||||
* @brief exportMultisigImages - exports transfers' key images
|
||||
* @param images - output paramter for hex encoded array of images
|
||||
|
|
|
@ -5509,32 +5509,77 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
|
|||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::string wallet2::make_multisig(const epee::wipeable_string &password,
|
||||
const std::vector<std::string> &initial_kex_msgs,
|
||||
const std::uint32_t threshold)
|
||||
epee::misc_utils::auto_scope_leave_caller wallet2::decrypt_account_for_multisig(const epee::wipeable_string &password)
|
||||
{
|
||||
// decrypt account keys
|
||||
// note: this conditional's clauses are old and undocumented
|
||||
epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
|
||||
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
|
||||
{
|
||||
crypto::chacha_key chacha_key;
|
||||
crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
|
||||
m_account.encrypt_viewkey(chacha_key);
|
||||
m_account.decrypt_keys(chacha_key);
|
||||
this->decrypt_keys(chacha_key);
|
||||
keys_reencryptor = epee::misc_utils::create_scope_leave_handler(
|
||||
[&, this, chacha_key]()
|
||||
[this, chacha_key]()
|
||||
{
|
||||
m_account.encrypt_keys(chacha_key);
|
||||
m_account.decrypt_viewkey(chacha_key);
|
||||
this->encrypt_keys(chacha_key);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// create multisig account
|
||||
multisig::multisig_account multisig_account{
|
||||
multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key),
|
||||
multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key)
|
||||
return keys_reencryptor;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::get_uninitialized_multisig_account(multisig::multisig_account &account_out) const
|
||||
{
|
||||
// create uninitialized multisig account
|
||||
account_out = multisig::multisig_account{
|
||||
// k_base = H(normal private spend key)
|
||||
multisig::get_multisig_blinded_secret_key(this->get_account().get_keys().m_spend_secret_key),
|
||||
// k_view = H(normal private view key)
|
||||
multisig::get_multisig_blinded_secret_key(this->get_account().get_keys().m_view_secret_key)
|
||||
};
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::get_reconstructed_multisig_account(multisig::multisig_account &account_out) const
|
||||
{
|
||||
const multisig::multisig_account_status ms_status{this->get_multisig_status()};
|
||||
CHECK_AND_ASSERT_THROW_MES(ms_status.multisig_is_active,
|
||||
"The wallet is not multisig, so the multisig account couldn't be reconstructed");
|
||||
|
||||
// reconstruct multisig account
|
||||
crypto::public_key common_pubkey;
|
||||
crypto::secret_key_to_public_key(this->get_account().get_keys().m_view_secret_key, common_pubkey);
|
||||
|
||||
multisig::multisig_keyset_map_memsafe_t kex_origins_map;
|
||||
for (const auto &derivation : m_multisig_derivations)
|
||||
kex_origins_map[derivation];
|
||||
|
||||
account_out = multisig::multisig_account{
|
||||
m_multisig_threshold,
|
||||
m_multisig_signers,
|
||||
this->get_account().get_keys().m_spend_secret_key,
|
||||
this->get_account().get_keys().m_view_secret_key,
|
||||
this->get_account().get_keys().m_multisig_keys,
|
||||
this->get_account().get_keys().m_view_secret_key,
|
||||
m_account_public_address.m_spend_public_key,
|
||||
common_pubkey,
|
||||
m_multisig_rounds_passed,
|
||||
std::move(kex_origins_map),
|
||||
""
|
||||
};
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::string wallet2::make_multisig(const epee::wipeable_string &password,
|
||||
const std::vector<std::string> &initial_kex_msgs,
|
||||
const std::uint32_t threshold)
|
||||
{
|
||||
// decrypt account keys
|
||||
epee::misc_utils::auto_scope_leave_caller keys_reencryptor{this->decrypt_account_for_multisig(password)};
|
||||
|
||||
// create multisig account
|
||||
multisig::multisig_account multisig_account;
|
||||
this->get_uninitialized_multisig_account(multisig_account);
|
||||
|
||||
// open initial kex messages, validate them, extract signers
|
||||
std::vector<multisig::multisig_kex_msg> expanded_msgs;
|
||||
|
@ -5542,7 +5587,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
|
|||
expanded_msgs.reserve(initial_kex_msgs.size());
|
||||
signers.reserve(initial_kex_msgs.size() + 1);
|
||||
|
||||
for (const auto &msg : initial_kex_msgs)
|
||||
for (const std::string &msg : initial_kex_msgs)
|
||||
{
|
||||
expanded_msgs.emplace_back(msg);
|
||||
|
||||
|
@ -5551,17 +5596,19 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
|
|||
CHECK_AND_ASSERT_THROW_MES(expanded_msgs.back().get_round() == 1,
|
||||
"Trying to make multisig with message that has invalid multisig kex round (should be '1').");
|
||||
|
||||
// 2. duplicate signers not allowed
|
||||
// 2. duplicate signers not allowed (the number of signers is implied by the number of initial kex messages passed
|
||||
// in, so we can't just ignore duplicates here)
|
||||
CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), expanded_msgs.back().get_signing_pubkey()) == signers.end(),
|
||||
"Duplicate signers not allowed when converting a wallet to multisig.");
|
||||
|
||||
// add signer (skip self for now)
|
||||
if (expanded_msgs.back().get_signing_pubkey() != multisig_account.get_base_pubkey())
|
||||
// add signer
|
||||
signers.push_back(expanded_msgs.back().get_signing_pubkey());
|
||||
}
|
||||
|
||||
// add self to signers
|
||||
signers.push_back(multisig_account.get_base_pubkey());
|
||||
// expect that self is in the input list (this guarantees that the input list size always equals the number of intended
|
||||
// signers for the account [when combined with duplicate checking])
|
||||
CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), multisig_account.get_base_pubkey()) != signers.end(),
|
||||
"The local account's signer key was not found in initial multisig kex messages when converting a wallet to multisig.");
|
||||
|
||||
// intialize key exchange
|
||||
multisig_account.initialize_kex(threshold, signers, expanded_msgs);
|
||||
|
@ -5572,12 +5619,13 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
|
|||
{
|
||||
// Save the original i.e. non-multisig keys so the MMS can continue to use them to encrypt and decrypt messages
|
||||
// (making a wallet multisig overwrites those keys, see account_base::make_multisig)
|
||||
m_original_address = get_account().get_keys().m_account_address;
|
||||
m_original_view_secret_key = get_account().get_keys().m_view_secret_key;
|
||||
m_original_address = this->get_account().get_keys().m_account_address;
|
||||
m_original_view_secret_key = this->get_account().get_keys().m_view_secret_key;
|
||||
m_original_keys_available = true;
|
||||
}
|
||||
|
||||
clear();
|
||||
// clear wallet caches
|
||||
this->clear();
|
||||
|
||||
// account base
|
||||
MINFO("Creating multisig address...");
|
||||
|
@ -5587,14 +5635,14 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
|
|||
multisig_account.get_multisig_privkeys()),
|
||||
"Failed to create multisig wallet account due to bad keys");
|
||||
|
||||
init_type(hw::device::device_type::SOFTWARE);
|
||||
this->init_type(hw::device::device_type::SOFTWARE);
|
||||
m_original_keys_available = true;
|
||||
m_multisig = true;
|
||||
m_multisig_threshold = threshold;
|
||||
m_multisig_signers = signers;
|
||||
m_multisig_rounds_passed = 1;
|
||||
|
||||
// derivations stored (should be empty in last round)
|
||||
// derivations stored (note: should be empty in last kex round)
|
||||
m_multisig_derivations.clear();
|
||||
m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size());
|
||||
|
||||
|
@ -5608,12 +5656,12 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
|
|||
keys_reencryptor = epee::misc_utils::auto_scope_leave_caller();
|
||||
|
||||
if (!m_wallet_file.empty())
|
||||
create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
|
||||
this->create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
|
||||
|
||||
setup_new_blockchain();
|
||||
this->setup_new_blockchain();
|
||||
|
||||
if (!m_wallet_file.empty())
|
||||
store();
|
||||
this->store();
|
||||
|
||||
return multisig_account.get_next_kex_round_msg();
|
||||
}
|
||||
|
@ -5622,45 +5670,15 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
|
|||
const std::vector<std::string> &kex_messages,
|
||||
const bool force_update_use_with_caution /*= false*/)
|
||||
{
|
||||
const multisig::multisig_account_status ms_status{get_multisig_status()};
|
||||
const multisig::multisig_account_status ms_status{this->get_multisig_status()};
|
||||
CHECK_AND_ASSERT_THROW_MES(ms_status.multisig_is_active, "The wallet is not multisig");
|
||||
|
||||
// decrypt account keys
|
||||
epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
|
||||
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
|
||||
{
|
||||
crypto::chacha_key chacha_key;
|
||||
crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
|
||||
m_account.encrypt_viewkey(chacha_key);
|
||||
m_account.decrypt_keys(chacha_key);
|
||||
keys_reencryptor = epee::misc_utils::create_scope_leave_handler(
|
||||
[&, this, chacha_key]()
|
||||
{
|
||||
m_account.encrypt_keys(chacha_key);
|
||||
m_account.decrypt_viewkey(chacha_key);
|
||||
}
|
||||
);
|
||||
}
|
||||
epee::misc_utils::auto_scope_leave_caller keys_reencryptor{this->decrypt_account_for_multisig(password)};
|
||||
|
||||
// reconstruct multisig account
|
||||
multisig::multisig_keyset_map_memsafe_t kex_origins_map;
|
||||
|
||||
for (const auto &derivation : m_multisig_derivations)
|
||||
kex_origins_map[derivation];
|
||||
|
||||
multisig::multisig_account multisig_account{
|
||||
m_multisig_threshold,
|
||||
m_multisig_signers,
|
||||
get_account().get_keys().m_spend_secret_key,
|
||||
crypto::null_skey, //base common privkey: not used
|
||||
get_account().get_keys().m_multisig_keys,
|
||||
get_account().get_keys().m_view_secret_key,
|
||||
m_account_public_address.m_spend_public_key,
|
||||
m_account_public_address.m_view_public_key,
|
||||
m_multisig_rounds_passed,
|
||||
std::move(kex_origins_map),
|
||||
""
|
||||
};
|
||||
multisig::multisig_account multisig_account;
|
||||
this->get_reconstructed_multisig_account(multisig_account);
|
||||
|
||||
// KLUDGE: early return if there are no kex messages and main kex is complete (will return the post-kex verification round
|
||||
// message) (it's a kludge because this behavior would be more appropriate for a standalone wallet method)
|
||||
|
@ -5676,7 +5694,7 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
|
|||
std::vector<multisig::multisig_kex_msg> expanded_msgs;
|
||||
expanded_msgs.reserve(kex_messages.size());
|
||||
|
||||
for (const auto &msg : kex_messages)
|
||||
for (const std::string &msg : kex_messages)
|
||||
expanded_msgs.emplace_back(msg);
|
||||
|
||||
// update multisig kex
|
||||
|
@ -5712,27 +5730,27 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
|
|||
|
||||
if (!m_wallet_file.empty())
|
||||
{
|
||||
bool r = store_keys(m_keys_file, password, false);
|
||||
bool r = this->store_keys(m_keys_file, password, false);
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
|
||||
|
||||
if (boost::filesystem::exists(m_wallet_file + ".address.txt"))
|
||||
{
|
||||
r = save_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype), true);
|
||||
r = this->save_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype), true);
|
||||
if(!r) MERROR("String with address text not saved");
|
||||
}
|
||||
}
|
||||
|
||||
m_subaddresses.clear();
|
||||
m_subaddress_labels.clear();
|
||||
add_subaddress_account(tr("Primary account"));
|
||||
this->add_subaddress_account(tr("Primary account"));
|
||||
|
||||
if (!m_wallet_file.empty())
|
||||
store();
|
||||
this->store();
|
||||
}
|
||||
|
||||
// wallet/file relationship
|
||||
if (!m_wallet_file.empty())
|
||||
create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
|
||||
this->create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
|
||||
|
||||
return multisig_account.get_next_kex_round_msg();
|
||||
}
|
||||
|
@ -5740,16 +5758,63 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
|
|||
std::string wallet2::get_multisig_first_kex_msg() const
|
||||
{
|
||||
// create multisig account
|
||||
multisig::multisig_account multisig_account{
|
||||
// k_base = H(normal private spend key)
|
||||
multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key),
|
||||
// k_view = H(normal private view key)
|
||||
multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key)
|
||||
};
|
||||
multisig::multisig_account multisig_account;
|
||||
this->get_uninitialized_multisig_account(multisig_account);
|
||||
|
||||
return multisig_account.get_next_kex_round_msg();
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::string wallet2::get_multisig_key_exchange_booster(const epee::wipeable_string &password,
|
||||
const std::vector<std::string> &kex_messages,
|
||||
const std::uint32_t threshold,
|
||||
const std::uint32_t num_signers)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(kex_messages.size() > 0, "No key exchange messages passed in.");
|
||||
|
||||
// decrypt account keys
|
||||
epee::misc_utils::auto_scope_leave_caller keys_reencryptor{this->decrypt_account_for_multisig(password)};
|
||||
|
||||
// prepare multisig account
|
||||
multisig::multisig_account multisig_account;
|
||||
|
||||
const multisig::multisig_account_status ms_status{this->get_multisig_status()};
|
||||
CHECK_AND_ASSERT_THROW_MES(!ms_status.is_ready, "Multisig wallet creation process has already been finished.");
|
||||
|
||||
if (ms_status.multisig_is_active)
|
||||
{
|
||||
// case: this wallet is in the middle of multisig key exchange
|
||||
// - boost the round that comes after the in-progress round
|
||||
|
||||
CHECK_AND_ASSERT_THROW_MES(threshold == m_multisig_threshold,
|
||||
"Expected threshold does not match multisig wallet setting.");
|
||||
CHECK_AND_ASSERT_THROW_MES(num_signers == m_multisig_signers.size(),
|
||||
"Expected number of signers does not match multisig wallet setting.");
|
||||
|
||||
// reconstruct multisig account
|
||||
this->get_reconstructed_multisig_account(multisig_account);
|
||||
}
|
||||
else
|
||||
{
|
||||
// case: make_multisig() has not been called
|
||||
// DANGER: If 'num_signers - threshold > 1', but this wallet's future multisig settings
|
||||
// will be 'num_signers - threshold == 1', then the booster message WILL leak the
|
||||
// future multisig wallet's private keys in this case where the wallet2 multisig wallet is uninitialized.
|
||||
|
||||
this->get_uninitialized_multisig_account(multisig_account);
|
||||
}
|
||||
|
||||
// open kex messages
|
||||
std::vector<multisig::multisig_kex_msg> expanded_msgs;
|
||||
expanded_msgs.reserve(kex_messages.size());
|
||||
|
||||
for (const std::string &msg : kex_messages)
|
||||
expanded_msgs.emplace_back(msg);
|
||||
|
||||
// get kex booster message
|
||||
// note: booster does not change wallet state other than decrypting/reencrypting account keys
|
||||
return multisig_account.get_multisig_kex_round_booster(threshold, num_signers, expanded_msgs).get_msg();
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
multisig::multisig_account_status wallet2::get_multisig_status() const
|
||||
{
|
||||
multisig::multisig_account_status ret;
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
|
||||
class Serialization_portability_wallet_Test;
|
||||
class wallet_accessor_test;
|
||||
namespace multisig { class multisig_account; }
|
||||
|
||||
namespace tools
|
||||
{
|
||||
|
@ -892,6 +893,23 @@ private:
|
|||
*/
|
||||
void restore(const std::string& wallet_, const epee::wipeable_string& password, const std::string &device_name, bool create_address_file = false);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief Decrypts the account keys
|
||||
* \return an RAII reencryptor for the account keys
|
||||
*/
|
||||
epee::misc_utils::auto_scope_leave_caller decrypt_account_for_multisig(const epee::wipeable_string &password);
|
||||
/*!
|
||||
* \brief Creates an uninitialized multisig account
|
||||
* \outparam: the uninitialized multisig account
|
||||
*/
|
||||
void get_uninitialized_multisig_account(multisig::multisig_account &account_out) const;
|
||||
/*!
|
||||
* \brief Reconstructs a multisig account from wallet2 state
|
||||
* \outparam: the reconstructed multisig account
|
||||
*/
|
||||
void get_reconstructed_multisig_account(multisig::multisig_account &account_out) const;
|
||||
public:
|
||||
/*!
|
||||
* \brief Creates a multisig wallet
|
||||
* \return empty if done, non empty if we need to send another string
|
||||
|
@ -913,6 +931,13 @@ private:
|
|||
* \return string to send to other participants
|
||||
*/
|
||||
std::string get_multisig_first_kex_msg() const;
|
||||
/*!
|
||||
* \brief Use multisig kex messages for an in-progress kex round to 'boost' the following round for another group member
|
||||
*/
|
||||
std::string get_multisig_key_exchange_booster(const epee::wipeable_string &password,
|
||||
const std::vector<std::string> &kex_messages,
|
||||
const std::uint32_t threshold,
|
||||
const std::uint32_t num_signers);
|
||||
/*!
|
||||
* Export multisig info
|
||||
* This will generate and remember new k values
|
||||
|
|
|
@ -4248,6 +4248,47 @@ namespace tools
|
|||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
bool wallet_rpc_server::on_get_multisig_key_exchange_booster(const wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::request& req, wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::response& res, epee::json_rpc::error& er, const connection_context *ctx)
|
||||
{
|
||||
if (!m_wallet) return not_open(er);
|
||||
if (m_restricted)
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_DENIED;
|
||||
er.message = "Command unavailable in restricted mode.";
|
||||
return false;
|
||||
}
|
||||
const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()};
|
||||
|
||||
if (ms_status.is_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() == 0)
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
|
||||
er.message = "Needs multisig info from more participants";
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
res.multisig_info = m_wallet->get_multisig_key_exchange_booster(req.password,
|
||||
req.multisig_info,
|
||||
req.threshold,
|
||||
req.num_signers);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
|
||||
er.message = std::string("Error calling exchange_multisig_info_booster: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
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, const connection_context *ctx)
|
||||
{
|
||||
if (!m_wallet) return not_open(er);
|
||||
|
|
|
@ -151,6 +151,7 @@ namespace tools
|
|||
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("exchange_multisig_keys", on_exchange_multisig_keys, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS)
|
||||
MAP_JON_RPC_WE("get_multisig_key_exchange_booster", on_get_multisig_key_exchange_booster, wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER)
|
||||
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)
|
||||
MAP_JON_RPC_WE("validate_address", on_validate_address, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS)
|
||||
|
@ -242,6 +243,7 @@ namespace tools
|
|||
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, const connection_context *ctx = NULL);
|
||||
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, const connection_context *ctx = NULL);
|
||||
bool on_exchange_multisig_keys(const wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::request& req, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
|
||||
bool on_get_multisig_key_exchange_booster(const wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::request& req, wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
|
||||
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, const connection_context *ctx = NULL);
|
||||
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, const connection_context *ctx = NULL);
|
||||
bool on_validate_address(const wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
|
||||
|
|
|
@ -2481,6 +2481,35 @@ namespace wallet_rpc
|
|||
typedef epee::misc_utils::struct_init<response_t> response;
|
||||
};
|
||||
|
||||
struct COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER
|
||||
{
|
||||
struct request_t
|
||||
{
|
||||
std::string password;
|
||||
std::vector<std::string> multisig_info;
|
||||
uint32_t threshold;
|
||||
uint32_t num_signers;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(password)
|
||||
KV_SERIALIZE(multisig_info)
|
||||
KV_SERIALIZE(threshold)
|
||||
KV_SERIALIZE(num_signers)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
typedef epee::misc_utils::struct_init<request_t> request;
|
||||
|
||||
struct response_t
|
||||
{
|
||||
std::string multisig_info;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(multisig_info)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
typedef epee::misc_utils::struct_init<response_t> response;
|
||||
};
|
||||
|
||||
struct COMMAND_RPC_SIGN_MULTISIG
|
||||
{
|
||||
struct request_t
|
||||
|
|
|
@ -149,6 +149,7 @@ static void check_results(const std::vector<std::string> &intermediate_infos,
|
|||
std::unordered_set<crypto::secret_key> unique_privkeys;
|
||||
rct::key composite_pubkey = rct::identity();
|
||||
|
||||
ASSERT_TRUE(wallets.size() > 0);
|
||||
wallets[0].decrypt_keys("");
|
||||
crypto::public_key spend_pubkey = wallets[0].get_account().get_keys().m_account_address.m_spend_public_key;
|
||||
crypto::secret_key view_privkey = wallets[0].get_account().get_keys().m_view_secret_key;
|
||||
|
@ -156,32 +157,48 @@ static void check_results(const std::vector<std::string> &intermediate_infos,
|
|||
EXPECT_TRUE(crypto::secret_key_to_public_key(view_privkey, view_pubkey));
|
||||
wallets[0].encrypt_keys("");
|
||||
|
||||
for (size_t i = 0; i < wallets.size(); ++i)
|
||||
// at the end of multisig kex, all wallets should emit a post-kex message with the same two pubkeys
|
||||
std::vector<crypto::public_key> post_kex_msg_pubkeys;
|
||||
ASSERT_TRUE(intermediate_infos.size() == wallets.size());
|
||||
for (const std::string &intermediate_info : intermediate_infos)
|
||||
{
|
||||
EXPECT_TRUE(!intermediate_infos[i].empty());
|
||||
const multisig::multisig_account_status ms_status{wallets[i].get_multisig_status()};
|
||||
multisig::multisig_kex_msg post_kex_msg;
|
||||
EXPECT_TRUE(!intermediate_info.empty());
|
||||
EXPECT_NO_THROW(post_kex_msg = intermediate_info);
|
||||
|
||||
if (post_kex_msg_pubkeys.size() != 0)
|
||||
EXPECT_TRUE(post_kex_msg_pubkeys == post_kex_msg.get_msg_pubkeys()); //assumes sorting is always the same
|
||||
else
|
||||
post_kex_msg_pubkeys = post_kex_msg.get_msg_pubkeys();
|
||||
|
||||
EXPECT_TRUE(post_kex_msg_pubkeys.size() == 2);
|
||||
}
|
||||
|
||||
// the post-kex pubkeys should equal the account's public view and spend keys
|
||||
EXPECT_TRUE(std::find(post_kex_msg_pubkeys.begin(), post_kex_msg_pubkeys.end(), spend_pubkey) != post_kex_msg_pubkeys.end());
|
||||
EXPECT_TRUE(std::find(post_kex_msg_pubkeys.begin(), post_kex_msg_pubkeys.end(), view_pubkey) != post_kex_msg_pubkeys.end());
|
||||
|
||||
// each wallet should have the same state (private view key, public spend key), and the public spend key should be
|
||||
// reproducible from the private spend keys found in each account
|
||||
for (tools::wallet2 &wallet : wallets)
|
||||
{
|
||||
wallet.decrypt_keys("");
|
||||
const multisig::multisig_account_status ms_status{wallet.get_multisig_status()};
|
||||
EXPECT_TRUE(ms_status.multisig_is_active);
|
||||
EXPECT_TRUE(ms_status.kex_is_done);
|
||||
EXPECT_TRUE(ms_status.is_ready);
|
||||
EXPECT_TRUE(ms_status.threshold == M);
|
||||
EXPECT_TRUE(ms_status.total == wallets.size());
|
||||
|
||||
wallets[i].decrypt_keys("");
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
// "equals" is transitive relation so we need only to compare first wallet's address to each others' addresses.
|
||||
// no need to compare 0's address with itself.
|
||||
EXPECT_TRUE(wallets[0].get_account().get_public_address_str(cryptonote::TESTNET) ==
|
||||
wallets[i].get_account().get_public_address_str(cryptonote::TESTNET));
|
||||
wallet.get_account().get_public_address_str(cryptonote::TESTNET));
|
||||
|
||||
EXPECT_EQ(spend_pubkey, wallets[i].get_account().get_keys().m_account_address.m_spend_public_key);
|
||||
EXPECT_EQ(view_privkey, wallets[i].get_account().get_keys().m_view_secret_key);
|
||||
EXPECT_EQ(view_pubkey, wallets[i].get_account().get_keys().m_account_address.m_view_public_key);
|
||||
}
|
||||
EXPECT_EQ(spend_pubkey, wallet.get_account().get_keys().m_account_address.m_spend_public_key);
|
||||
EXPECT_EQ(view_privkey, wallet.get_account().get_keys().m_view_secret_key);
|
||||
EXPECT_EQ(view_pubkey, wallet.get_account().get_keys().m_account_address.m_view_public_key);
|
||||
|
||||
// sum together unique multisig keys
|
||||
for (const auto &privkey : wallets[i].get_account().get_keys().m_multisig_keys)
|
||||
for (const auto &privkey : wallet.get_account().get_keys().m_multisig_keys)
|
||||
{
|
||||
EXPECT_NE(privkey, crypto::null_skey);
|
||||
|
||||
|
@ -189,17 +206,17 @@ static void check_results(const std::vector<std::string> &intermediate_infos,
|
|||
{
|
||||
unique_privkeys.insert(privkey);
|
||||
crypto::public_key pubkey;
|
||||
crypto::secret_key_to_public_key(privkey, pubkey);
|
||||
EXPECT_TRUE(crypto::secret_key_to_public_key(privkey, pubkey));
|
||||
EXPECT_NE(privkey, crypto::null_skey);
|
||||
EXPECT_NE(pubkey, crypto::null_pkey);
|
||||
EXPECT_NE(pubkey, rct::rct2pk(rct::identity()));
|
||||
rct::addKeys(composite_pubkey, composite_pubkey, rct::pk2rct(pubkey));
|
||||
}
|
||||
}
|
||||
wallets[i].encrypt_keys("");
|
||||
wallet.encrypt_keys("");
|
||||
}
|
||||
|
||||
// final key via sums should equal the wallets' public spend key
|
||||
// final key via sum of privkeys should equal the wallets' public spend key
|
||||
wallets[0].decrypt_keys("");
|
||||
EXPECT_EQ(wallets[0].get_account().get_keys().m_account_address.m_spend_public_key, rct::rct2pk(composite_pubkey));
|
||||
wallets[0].encrypt_keys("");
|
||||
|
@ -257,6 +274,104 @@ static void make_wallets(const unsigned int M, const unsigned int N, const bool
|
|||
check_results(intermediate_infos, wallets, M);
|
||||
}
|
||||
|
||||
static void make_wallets_boosting(std::vector<tools::wallet2>& wallets, unsigned int M)
|
||||
{
|
||||
ASSERT_TRUE(wallets.size() > 1 && wallets.size() <= KEYS_COUNT);
|
||||
ASSERT_TRUE(M <= wallets.size());
|
||||
std::uint32_t kex_rounds_required = multisig::multisig_kex_rounds_required(wallets.size(), M);
|
||||
std::uint32_t rounds_required = multisig::multisig_setup_rounds_required(wallets.size(), M);
|
||||
std::uint32_t rounds_complete{0};
|
||||
|
||||
// initialize wallets, get first round multisig kex msgs
|
||||
std::vector<std::string> initial_infos(wallets.size());
|
||||
|
||||
for (size_t i = 0; i < wallets.size(); ++i)
|
||||
{
|
||||
make_wallet(i, wallets[i]);
|
||||
|
||||
wallets[i].decrypt_keys("");
|
||||
initial_infos[i] = wallets[i].get_multisig_first_kex_msg();
|
||||
wallets[i].encrypt_keys("");
|
||||
}
|
||||
|
||||
// wallets should not be multisig yet
|
||||
for (const auto &wallet: wallets)
|
||||
{
|
||||
const multisig::multisig_account_status ms_status{wallet.get_multisig_status()};
|
||||
ASSERT_FALSE(ms_status.multisig_is_active);
|
||||
}
|
||||
|
||||
// get round 2 booster messages for wallet0 (if appropriate)
|
||||
auto initial_infos_truncated = initial_infos;
|
||||
initial_infos_truncated.erase(initial_infos_truncated.begin());
|
||||
|
||||
std::vector<std::string> wallet0_booster_infos;
|
||||
wallet0_booster_infos.reserve(wallets.size() - 1);
|
||||
|
||||
if (rounds_complete + 1 < kex_rounds_required)
|
||||
{
|
||||
for (size_t i = 1; i < wallets.size(); ++i)
|
||||
{
|
||||
wallet0_booster_infos.push_back(
|
||||
wallets[i].get_multisig_key_exchange_booster("", initial_infos_truncated, M, wallets.size())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// make wallets multisig
|
||||
std::vector<std::string> intermediate_infos(wallets.size());
|
||||
|
||||
for (size_t i = 0; i < wallets.size(); ++i)
|
||||
intermediate_infos[i] = wallets[i].make_multisig("", initial_infos, M);
|
||||
|
||||
++rounds_complete;
|
||||
|
||||
// perform all kex rounds
|
||||
// boost wallet0 each round, so wallet0 is always 1 round ahead
|
||||
std::string wallet0_intermediate_info;
|
||||
std::vector<std::string> new_infos(intermediate_infos.size());
|
||||
multisig::multisig_account_status ms_status{wallets[0].get_multisig_status()};
|
||||
while (!ms_status.is_ready)
|
||||
{
|
||||
// use booster infos to update wallet0 'early'
|
||||
if (rounds_complete < kex_rounds_required)
|
||||
new_infos[0] = wallets[0].exchange_multisig_keys("", wallet0_booster_infos);
|
||||
else
|
||||
{
|
||||
// force update the post-kex round with wallet0's post-kex message since wallet0 is 'ahead' of the other wallets
|
||||
wallet0_booster_infos = {wallets[0].exchange_multisig_keys("", {})};
|
||||
new_infos[0] = wallets[0].exchange_multisig_keys("", wallet0_booster_infos, true);
|
||||
}
|
||||
|
||||
// get wallet0 booster infos for next round
|
||||
if (rounds_complete + 1 < kex_rounds_required)
|
||||
{
|
||||
// remove wallet0 info for this round (so boosters have incomplete kex message set)
|
||||
auto intermediate_infos_truncated = intermediate_infos;
|
||||
intermediate_infos_truncated.erase(intermediate_infos_truncated.begin());
|
||||
|
||||
// obtain booster messages from all other wallets
|
||||
for (size_t i = 1; i < wallets.size(); ++i)
|
||||
{
|
||||
wallet0_booster_infos[i-1] =
|
||||
wallets[i].get_multisig_key_exchange_booster("", intermediate_infos_truncated, M, wallets.size());
|
||||
}
|
||||
}
|
||||
|
||||
// update other wallets
|
||||
for (size_t i = 1; i < wallets.size(); ++i)
|
||||
new_infos[i] = wallets[i].exchange_multisig_keys("", intermediate_infos);
|
||||
|
||||
intermediate_infos = new_infos;
|
||||
++rounds_complete;
|
||||
ms_status = wallets[0].get_multisig_status();
|
||||
}
|
||||
|
||||
EXPECT_EQ(rounds_required, rounds_complete);
|
||||
|
||||
check_results(intermediate_infos, wallets, M);
|
||||
}
|
||||
|
||||
TEST(multisig, make_1_2)
|
||||
{
|
||||
make_wallets(1, 2, false);
|
||||
|
@ -293,6 +408,12 @@ TEST(multisig, make_2_4)
|
|||
make_wallets(2, 4, true);
|
||||
}
|
||||
|
||||
TEST(multisig, make_2_4_boosting)
|
||||
{
|
||||
std::vector<tools::wallet2> wallets(4);
|
||||
make_wallets_boosting(wallets, 2);
|
||||
}
|
||||
|
||||
TEST(multisig, multisig_kex_msg)
|
||||
{
|
||||
using namespace multisig;
|
||||
|
|
|
@ -540,6 +540,20 @@ class Wallet(object):
|
|||
}
|
||||
return self.rpc.send_json_rpc_request(exchange_multisig_keys)
|
||||
|
||||
def get_multisig_key_exchange_booster(self, multisig_info, threshold, num_signers, password = ''):
|
||||
exchange_multisig_keys = {
|
||||
'method': 'get_multisig_key_exchange_booster',
|
||||
'params' : {
|
||||
'multisig_info': multisig_info,
|
||||
'threshold': threshold,
|
||||
'num_signers': num_signers,
|
||||
'password': password,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_multisig_key_exchange_booster)
|
||||
|
||||
def export_multisig_info(self):
|
||||
export_multisig_info = {
|
||||
'method': 'export_multisig_info',
|
||||
|
|
Loading…
Reference in New Issue