Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multisig: add post-kex verification round #8220

Merged
merged 1 commit into from
May 10, 2022

Conversation

UkoeHB
Copy link
Contributor

@UkoeHB UkoeHB commented Mar 17, 2022

Problem: When creating a group multisig account, each step of key exchange relies on messages from all other participants. If a malicious player only sends their final message to one honest player, but not others, then the other honest players will be stuck without completed accounts. If that one honest player receives funds to the address he created, then those funds will either be unspendable or only spendable with cooperation from malicious group members.

Solution: Add a round to multisig account creation to verify that all other participants have completed their accounts.

This PR also has some semantic and control-flow cleanup of the multisig kex implementation (I am not satisfied with the readability of my earlier work - cleanup will continue in PR #8203 and alongside the Seraphis library which adds support for aggregation-style multisig signing).

TODO (done: #8582): I will add an unsafe_update_use_with_care flag to the multisig_account::initialize_kex() and multisig_account::kex_update() methods. This flag will allow users to fast-forward through the extra round added by this PR, and under some conditions even do so safely (without needing to trust other participants).

Copy link
Collaborator

@moneromooo-monero moneromooo-monero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea if it's OK algorithm wise, I just reviewed C code, and it's good.

src/multisig/multisig_account_kex_impl.cpp Show resolved Hide resolved
@@ -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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly more robust in case of errors elsewhere.

Comment on lines +156 to +160
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.");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slight cleanup

@UkoeHB UkoeHB force-pushed the multisig_conf_round branch from 6fad16e to fbdd39d Compare March 23, 2022 18:55
@UkoeHB
Copy link
Contributor Author

UkoeHB commented Mar 23, 2022

I updated the multisig account implementation to always emit the post-kex verification msg once an account is complete. This way the msg is always available if needed. Previously, a lot of tests were using 'empty kex msg emitted' as a proxy for 'multisig account is ready'. Now you need to query if 'multisig is ready' directly (this is more intuitive and robust anyway). That change probably breaks the MMS @rbrunner7 .

Following the multisig kex protocol isn't sufficient by itself to robustly set up multisig accounts. No matter how many rounds you add, a malicious player can always selectively send their final message to only some other players. To get around that blocking vector, we need a solution in the messaging layer. There are a few options.

  1. After completing a multisig account, cache the final message set (plus your own final message). If someone says they are stuck on the last round, relay that message cache to them. This has a flaw though: if player A completes an account and publishes the multisig address, player A disappears, then the address receives funds, then if other players are account-blocked then player A won't be around to unblock them, which may mean funds are locked in the account even though there are enough players available to release them.
  2. Best practice: The better solution is to rebroadcast the full final message set (plus your own final message) to all other players immediately after completing your account. If automatic rebroadcast fails, then you can fall back to solution 1 (rebroadcast on request).
  3. Use a 'group' communication channel for the final communication round, to ensure all players will be able to complete their accounts simultaneously. This may be infeasible or difficult to set up, but is the fastest robust solution.
  4. Custom multisig implementations can decide how to handle rebroadcasts (if at all), or even use a 'force update' flag (to be implemented in a future PR) to skip the last round entirely. Customization risks opening attack vectors not present in the default best-practice solution, so implementer beware!

Note: I decided not to implement message caching in the wallet file because it is more appropriate for the message handling layer to take responsibility for handling messages (caching, transmitting, etc.). Also, this way you don't pay for what you don't use, if you don't need wallet-side caching.

@rbrunner7
Copy link
Contributor

As you predicted this needed some more small changes in simplewallet.cpp in order to work correctly. It was not only a question about the MMS; also the normal "manual" multisig workflow did not work correctly anymore. You can see the necessary changes here to copy them over into your code and update it: rbrunner7@fbb6055

I ran my usual tests with your code plus my changes, using the MMS, for 2/2, 2/3 and 3/5 multisig. Everything - building wallets, receiving a tx, sending a tx - worked flawlessly. Of course with an almost fully automatic system like the MMS you hardly notice the additional data exchange round.

@rbrunner7
Copy link
Contributor

To get around that blocking vector, we need a solution in the messaging layer. There are a few options.

I confess I don't fully understand everything you explain and propose here as options. But I brainstormed a little nevertheless how supporting such options could look in the MMS, and how much effort would be needed to implement.

I came up with an estimate of a few working days. It's not too hard, but not trivial either. It will probably need a handful of new commands, new logic within the mms next command and new message types.

With the low usage that the MMS sees and the corresponding low importance for the Monero ecosystem as a whole I don't see me implementing anything here in the near future, frankly.

Copy link
Contributor

@rbrunner7 rbrunner7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this PR it's the same as with the previous multisig-related one: I can't review the code, so this is not a proper code approval, but I did carefully test the code with 2/2, 2/3 and 3/5 multisig and can confirm that everything fully worked.

I think together with @moneromooo-monero 's look at the code from a technical point of view this should give something like a full review ...

@UkoeHB
Copy link
Contributor Author

UkoeHB commented Mar 25, 2022

@rbrunner7 thank you!

@UkoeHB
Copy link
Contributor Author

UkoeHB commented Mar 25, 2022

@rbrunner7 for MMS documentation, you could recommend that users manually verify with each other that their accounts are complete before sending any money to the multisig address. It's not automated like the other solutions, but would still work. (edit: doing that would be the manual version of method 3)

@UkoeHB
Copy link
Contributor Author

UkoeHB commented Apr 19, 2022

Update: the post-kex verification round must also check that all players have the same common pubkey (which is used as the view key by wallets).

Copy link
Contributor

@vtnerd vtnerd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comments are minor.

src/multisig/multisig_account_kex_impl.cpp Show resolved Hide resolved
src/multisig/multisig_account_kex_impl.cpp Outdated Show resolved Hide resolved
src/multisig/multisig_account_kex_impl.cpp Show resolved Hide resolved
Copy link
Contributor

@vtnerd vtnerd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of nagging things, sorry :/ Only the one is interesting (the unused parameter in a function).

src/multisig/multisig_account.cpp Show resolved Hide resolved
src/multisig/multisig_account_kex_impl.cpp Show resolved Hide resolved
@UkoeHB UkoeHB force-pushed the multisig_conf_round branch from 18aece4 to d844b00 Compare April 29, 2022 19:02
@UkoeHB UkoeHB force-pushed the multisig_conf_round branch from d844b00 to 0d6ecb1 Compare April 29, 2022 19:05
@UkoeHB
Copy link
Contributor Author

UkoeHB commented Apr 29, 2022

Squashed

@luigi1111 luigi1111 merged commit c1625a8 into monero-project:master May 10, 2022
@UkoeHB UkoeHB deleted the multisig_conf_round branch May 15, 2022 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants