Skip to content

Commit

Permalink
once a multisig account is complete, always emit a post-kex verificat…
Browse files Browse the repository at this point in the history
…ion message
  • Loading branch information
UkoeHB committed Mar 23, 2022
1 parent e0b762c commit fbdd39d
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 59 deletions.
31 changes: 20 additions & 11 deletions src/multisig/multisig_account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,20 @@ namespace multisig
CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey),
"Failed to derive public key");
set_multisig_config(threshold, std::move(signers));

// kex rounds should not exceed post-kex verification round
const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(m_signers.size(), m_threshold)};
CHECK_AND_ASSERT_THROW_MES(m_kex_rounds_complete <= kex_rounds_required + 1,
"multisig account: tried to reconstruct account, but kex rounds complete counter is invalid.");

// once an account is complete, the 'next kex msg' is always the post-kex verification message
// i.e. the multisig account pubkey signed by the signer's privkey
if (multisig_is_ready())
{
m_next_round_kex_message = multisig_kex_msg{kex_rounds_required + 1,
m_base_privkey,
std::vector<crypto::public_key>{m_multisig_pubkey}}.get_msg();
}
}
//----------------------------------------------------------------------------------------------------------------------
// multisig_account: EXTERNAL
Expand All @@ -113,7 +127,7 @@ namespace multisig
bool multisig_account::multisig_is_ready() const
{
if (main_kex_rounds_done())
return m_kex_rounds_complete == multisig_kex_rounds_required(m_signers.size(), m_threshold) + 1;
return m_kex_rounds_complete >= multisig_kex_rounds_required(m_signers.size(), m_threshold) + 1;
else
return false;
}
Expand All @@ -129,10 +143,6 @@ namespace multisig

for (auto signer_it = signers.begin(); signer_it != signers.end(); ++signer_it)
{
// signers should all be unique
CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signer_it, *signer_it) == signer_it,
"multisig account: tried to set signers, but found a duplicate signer unexpectedly.");

// signer pubkeys must be in main subgroup, and not identity
CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(*signer_it)) && !(*signer_it == rct::rct2pk(rct::identity())),
"multisig account: tried to set signers, but a signer pubkey is invalid.");
Expand All @@ -143,12 +153,11 @@ namespace multisig
"multisig account: tried to set signers, but did not find the account's base pubkey in signer list.");

// sort signers
std::sort(signers.begin(), signers.end(),
[](const crypto::public_key &key1, const crypto::public_key &key2) -> bool
{
return memcmp(&key1, &key2, sizeof(crypto::public_key)) < 0;
}
);
std::sort(signers.begin(), signers.end());

// signers should all be unique
CHECK_AND_ASSERT_THROW_MES(std::adjacent_find(signers.begin(), signers.end()) == signers.end(),
"multisig account: tried to set signers, but there are duplicate signers unexpectedly.");

// set
m_threshold = threshold;
Expand Down
39 changes: 9 additions & 30 deletions src/multisig/multisig_account_kex_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,31 +287,6 @@ namespace multisig
/**
* INTERNAL
*
* brief: multisig_kex_make_msg - Construct a kex msg for any round > 1 of multisig key construction.
* param: base_privkey - account's base private key, for performing DH exchanges and signing messages
* param: round - the round of the message that should be produced
* param: kex_rounds_required - the number of rounds to complete kex
* param: msg_pubkeys - pubkeys to include in the kex message
* return: multisig kex message for the specified round
*/
//----------------------------------------------------------------------------------------------------------------------
static multisig_kex_msg multisig_kex_make_msg(const crypto::secret_key &base_privkey,
const std::uint32_t round,
const std::uint32_t kex_rounds_required,
std::vector<crypto::public_key> msg_pubkeys)
{
CHECK_AND_ASSERT_THROW_MES(round > 1, "Invalid call to multisig_kex_make_msg().");

// short-circuit to an empty message once the post-kex verification round is complete
if (round > kex_rounds_required + 1)
return multisig_kex_msg{};

return multisig_kex_msg{round, base_privkey, std::move(msg_pubkeys)};
}
//----------------------------------------------------------------------------------------------------------------------
/**
* INTERNAL
*
* brief: check_messages_round - Check that a set of messages have an expected round number.
* param: expanded_msgs - set of multisig kex messages to process
* param: expected_round - round number the kex messages should have
Expand Down Expand Up @@ -712,6 +687,10 @@ namespace multisig
{
// post-kex verification round: check that the multisig pubkey was recommended by other signers
CHECK_AND_ASSERT_THROW_MES(result_keys_to_origins_map.find(m_multisig_pubkey) != result_keys_to_origins_map.end(), "");

// save key that should be recommended to other signers
// - for convenience, re-recommend the post-kex verification message once an account is complete
next_msg_keys.push_back(m_multisig_pubkey);
}
else if (m_kex_rounds_complete + 1 == kex_rounds_required)
{
Expand Down Expand Up @@ -789,11 +768,11 @@ namespace multisig
// a full set of msgs has been collected and processed, so the 'round is complete'
++m_kex_rounds_complete;

// make next round's message
m_next_round_kex_message = multisig_kex_make_msg(m_base_privkey,
m_kex_rounds_complete + 1,
kex_rounds_required,
std::move(next_msg_keys)).get_msg();
// make next round's message (or reproduce the post-kex verification round if kex is complete)
m_next_round_kex_message = multisig_kex_msg{
(m_kex_rounds_complete > kex_rounds_required ? kex_rounds_required : m_kex_rounds_complete) + 1,
m_base_privkey,
std::move(next_msg_keys)}.get_msg();
}
//----------------------------------------------------------------------------------------------------------------------
// multisig_account: INTERNAL
Expand Down
3 changes: 2 additions & 1 deletion src/wallet/wallet_rpc_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4132,7 +4132,8 @@ namespace tools
try
{
res.multisig_info = m_wallet->exchange_multisig_keys(req.password, req.multisig_info);
if (res.multisig_info.empty())
m_wallet->multisig(&ready);
if (ready)
{
res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype());
}
Expand Down
8 changes: 2 additions & 6 deletions tests/core_tests/multisig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ static bool make_multisig_accounts(std::vector<cryptonote::account_base> &accoun
for (std::size_t account_index{0}; account_index < accounts.size(); ++account_index)
{
multisig_accounts[account_index].initialize_kex(threshold, signers, round_msgs);

if (!multisig_accounts[account_index].multisig_is_ready())
temp_round_msgs[account_index] = multisig_accounts[account_index].get_next_kex_round_msg();
temp_round_msgs[account_index] = multisig_accounts[account_index].get_next_kex_round_msg();
}

// perform key exchange rounds
Expand All @@ -94,9 +92,7 @@ static bool make_multisig_accounts(std::vector<cryptonote::account_base> &accoun
for (std::size_t account_index{0}; account_index < multisig_accounts.size(); ++account_index)
{
multisig_accounts[account_index].kex_update(round_msgs);

if (!multisig_accounts[account_index].multisig_is_ready())
temp_round_msgs[account_index] = multisig_accounts[account_index].get_next_kex_round_msg();
temp_round_msgs[account_index] = multisig_accounts[account_index].get_next_kex_round_msg();
}
}

Expand Down
13 changes: 7 additions & 6 deletions tests/functional_tests/multisig.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,13 @@ def create_multisig_wallets(self, M_threshold, N_total, expected_address):
assert res.total == N_total

while True:
n_empty = 0
for i in range(len(next_stage)):
if len(next_stage[i]) == 0:
n_empty += 1
assert n_empty == 0 or n_empty == len(next_stage)
if n_empty == len(next_stage):
n_ready = 0
for i in range(N_total):
res = self.wallet[i].is_multisig()
if res.ready == True:
n_ready += 1
assert n_ready == 0 or n_ready == N_total
if n_ready == N_total:
break
info = next_stage
next_stage = []
Expand Down
9 changes: 4 additions & 5 deletions tests/unit_tests/multisig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ static void check_results(const std::vector<std::string> &intermediate_infos,

for (size_t i = 0; i < wallets.size(); ++i)
{
EXPECT_TRUE(intermediate_infos[i].empty());
EXPECT_TRUE(!intermediate_infos[i].empty());
bool ready;
uint32_t threshold, total;
EXPECT_TRUE(wallets[i].multisig(&ready, &threshold, &total));
Expand Down Expand Up @@ -203,13 +203,12 @@ static void make_wallets(std::vector<tools::wallet2>& wallets, unsigned int M)
++rounds_complete;

// perform kex rounds until kex is complete
while (!intermediate_infos[0].empty())
bool ready{false};
while (!ready)
{
bool ready{false};
wallets[0].multisig(&ready);
EXPECT_FALSE(ready);

intermediate_infos = exchange_round(wallets, intermediate_infos);
wallets[0].multisig(&ready);

++rounds_complete;
}
Expand Down

0 comments on commit fbdd39d

Please sign in to comment.