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: force update option for key exchange [master] #8329

Merged
merged 1 commit into from
Sep 26, 2022

Conversation

UkoeHB
Copy link
Contributor

@UkoeHB UkoeHB commented May 15, 2022

Motivation

In the default multisig key exchange (account setup) ceremony, every key exchange round requires a message from every signer. That requirement may not be strictly necessary in some real-world use-cases (where it may be safe to assume some or all signers are trustworthy during account setup), so this PR adds an option to 'force-update' key exchange.

Changes

  • Adds a force_update_use_with_caution flag to the multisig_account::kex_update() method (it defaults to false). That interface change is propagated up the call stack to the exchange_multisig_keys() method in wallet2 and the RPC.
    • When force-updating an intermediate key exchange round, you need at least num_signers - current_round_num messages from other signers (this is the bare minimum to actually make progress).
    • When force-updating the post-kex verification round, you only need to pass in the local account's post-kex verification message (which was the local account's output from the previous round).
  • Adds a unit test demoing the new method.
  • Miscellaneous cleanup related to multisig (gen_multisig.cpp, unit tests, etc.).

Future Work

  • Currently, simplewallet and the MMS don't have an interface for the force update flag (I just defaulted it to false). A future PR can update the interface there if it's desired (@rbrunner7 ).

Dangers

The new flag is a foot-gun that can enable the following problems.

  • A signer's account created through force-updating may be unable to participate in signing (i.e. the final multisig address they obtain is malformed if there is a malicious player during account setup).
  • If a signer force-updates the post-kex verification round, they may believe their account is complete and the multisig group is able to create signatures. In reality, there may not be a threshold of honest signers that completed their accounts. Any funds sent to the multisig address obtained by the force-updater might be lost forever.
  • If honest multisig signers force-update any intermediate key exchange round, a malicious player could execute an 'address hostage' attack where the final address produced can only make signatures if that malicious player participates (although he wouldn't be able to make signatures on his own).

@UkoeHB UkoeHB force-pushed the multisig_force_update branch from 3eb0000 to b1c33c4 Compare May 15, 2022 13:00
@UkoeHB UkoeHB force-pushed the multisig_force_update branch from b1c33c4 to 0f597c1 Compare August 16, 2022 17:30
@UkoeHB
Copy link
Contributor Author

UkoeHB commented Aug 16, 2022

Resolved merge conflicts. Updated wallet2::exchange_multisig_keys() method so if your account has completed the main kex rounds you can call this with NO kex messages to extract the account's final post-kex verification message (a kludge to avoid adding a whole new RPC endpoint). To migrate pre-v0.18 multisig accounts to v0.18, the following two workflows should be sufficient (assuming you have a multisig account that was completed before v0.18).

Workflow 1 (recommended)

  1. Let all multisig group members call exchange_multisig_keys() with NO key exchange messages. Save the output strings as R1, R2, ..., Rn.
  2. Each multisig group member calls exchange_multisig_keys(R1, R2, ..., Rn) again. Their accounts should be complete and usable after that.

Workflow 2 (fastest, less robust in general if there is a malicious group member)

  1. Call exchange_multisig_keys() with NO key exchange messages locally. Save the output string as R_local.
  2. Call exchange_multisig_keys(R_local, force_update_use_with_caution = true) (using the force update flag). The user's account should be complete and usable after that.

@UkoeHB UkoeHB force-pushed the multisig_force_update branch 2 times, most recently from 51c6f21 to 68f22f5 Compare August 16, 2022 22:01
@UkoeHB
Copy link
Contributor Author

UkoeHB commented Aug 16, 2022

I removed checks from exchange_multisig_keys() that would throw an error if calling it when your multisig account is already complete. This way you can always call it with no kex messages to extract the post-kex verification message.

If wallets can enter a state where the post-kex verification message can't be extracted, then malicious users could plausibly claim 'I can't get my post-kex verification message'.

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.

Nothing critical, just a few questions (that may or may not require changes).

@@ -181,7 +181,8 @@ namespace multisig
* Key aggregation via aggregation coefficients prevents key cancellation attacks.
* See: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf
* param: final_keys - address components (public keys) obtained from other participants (not shared with local)
* param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation coefficient (return by reference)
* param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation
Copy link
Contributor

Choose a reason for hiding this comment

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

Pinging @moneromooo-monero some whitespace changes ... allow ?

@@ -199,7 +200,8 @@ namespace multisig
for (std::size_t multisig_keys_index{0}; multisig_keys_index < privkeys_inout.size(); ++multisig_keys_index)
{
crypto::public_key pubkey;
CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey), "Failed to derive public key");
CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey),
Copy link
Contributor

Choose a reason for hiding this comment

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

Here again.

return true;
}

bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms) {
CHECK_MULTISIG_ENABLED();
Copy link
Contributor

Choose a reason for hiding this comment

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

Was CHECK_MULTISIG_ENABLED() intentionally removed? Its not used in mms_next.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, this was a rebase mistake, will fix.

{
bool ready{false};
CHECK_AND_ASSERT_THROW_MES(multisig(&ready), "The wallet is not multisig");
CHECK_AND_ASSERT_THROW_MES(!ready, "Multisig wallet creation process has already been finished");
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was the !ready check removed? Seems like that should be done, even with the new if statement below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Existing multisig wallets (which have ready == true) have no way to recover the post-kex verification message, which may be needed by other signers even after the local signer has done their post-kex verification round.

@@ -5126,8 +5125,18 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
""
};

// 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)
if (kex_messages.size() == 0)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this just to retrieve the post-kex message multiple times or something? Otherwise I don't see why this was added.

Copy link
Contributor Author

@UkoeHB UkoeHB Aug 18, 2022

Choose a reason for hiding this comment

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

Yes, because existing multisig wallets are stuck, unable to complete the post-kex verification round that was added in v0.18 (an oversight in testing the multisig changes).

Copy link
Contributor

Choose a reason for hiding this comment

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

They aren't guaranteed to be stuck though, right? Seems like this would only happen if other signers lost someone's post-kex message? If that could happen, couldn't any of the steps get stuck? I guess that's why the force option was added?

Copy link
Contributor Author

@UkoeHB UkoeHB Sep 9, 2022

Choose a reason for hiding this comment

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

The post-kex round did not exist before v0.18, and the only way to get a post-kex message right now is from exchange_multisig_keys() as the output of a kex round (but existing accounts already completed their last kex round without getting a post-kex message).

I guess I am overloading this PR a little so we can fix multisig faster, the post-kex stuff isn't directly related to the force update option. However, you need force updating to get your account past the post-kex step if you aren't able to get post-kex messages from all other group members (which may be the case for old accounts).

@@ -4146,13 +4146,6 @@ namespace tools
er.message = "This wallet is not multisig";
return false;
}

if (ready)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why remove this check?

Copy link
Contributor Author

@UkoeHB UkoeHB Aug 18, 2022

Choose a reason for hiding this comment

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

Existing multisig wallets (which have ready == true) have no way to recover the post-kex verification message, which may be needed by other signers even after the local signer has done their post-kex verification round.

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.

Ok, so the hold up has been me confused by the naming scheme with the post-kex messaging. multisig_kex_rounds_required(...) appears to be the number of kex messages, not including the "post-round" message to verify that all participants are active. multisig_is_ready() only returns true after this additional message has been verified.

wallet2::multisig(...) check calls the first function, which was the source of my confusion in my prior comments. I kept transposing that function call to the latter function. It's also confusing, because there's a sense in which the local wallet truly is ready - this particular wallet can immediately sign valid messages - but it is not certain about the status of the remaining wallets. Ouch, kind of a mess, but I think you've done the a decent job correcting all this.

@UkoeHB UkoeHB force-pushed the multisig_force_update branch from 6b2e453 to 125f093 Compare September 10, 2022 19:04
@UkoeHB
Copy link
Contributor Author

UkoeHB commented Sep 10, 2022

Squashed commits

@UkoeHB
Copy link
Contributor Author

UkoeHB commented Sep 12, 2022

@tmoravec do you want to test this PR? It should fix #8541

@tmoravec
Copy link
Contributor

@tmoravec do you want to test this PR? It should fix #8541

I'd love to but I'd need to implement the simplewallet parts first. I can do that, but not today or tomorrow unfortunately :( .

@selsta
Copy link
Collaborator

selsta commented Sep 20, 2022

@tmoravec I would like to include this in the next release, it would be great if you could test this in the next days :)

@tmoravec
Copy link
Contributor

tmoravec commented Sep 20, 2022

Thanks for the reminder, selsta :) .

I've added the new parameter to simplewallet to facilitate testing: UkoeHB#4

Trying to follow the Workflow 2, I'm getting an error: Error: Failed to perform multisig keys exchange: Multisig post-kex round messages from other signers did not all contain two pubkeys.

It looks like the check CHECK_AND_ASSERT_THROW_MES(pubkey_origins_map.size() == 2 in src/multisig/multisig_account_kex_impl.cpp is not taking incomplete_signer_set into account but I might be missing something.

EDIT: Here are the commands (on stagenet):

[wallet 51sehD (no daemon)]: exchange_multisig_keys force-update
Wallet password:
Another step is needed
MultisigxV2Rn1WBRa6XZrJ3A1kmifR2yXjDcbPGmrKfk6fknJiY9p4xf9AXnwJ8Gijm23iV9U3Vj8zHB8haMVVgsaMSkhBtksPzHfn1QuGXja3Hhifv9HSDNfsNQ7icCYh2oX6GAuKBc3uvj3bdGt9jyGyNWxxih4t88sT8XzrqUrU8guMkgXnoWDymd1gCttzgd2toGzVV3XUtPEGTfmWV6ETou95fNboLMuSaF2Ds
Send this multisig info to all other participants, then use exchange_multisig_keys <info1> [<info2>...] with others' multisig info
[wallet 51sehD (no daemon)]: exchange_multisig_keys force-update MultisigxV2Rn1WBRa6XZrJ3A1kmifR2yXjDcbPGmrKfk6fknJiY9p4xf9AXnwJ8Gijm23iV9U3Vj8zHB8haMVVgsaMSkhBtksPzHfn1QuGXja3Hhifv9HSDNfsNQ7icCYh2oX6GAuKBc3uvj3bdGt9jyGyNWxxih4t88sT8XzrqUrU8guMkgXnoWDymd1gCttzgd2toGzVV3XUtPEGTfmWV6ETou95fNboLMuSaF2Ds
Wallet password:
Error: Failed to perform multisig keys exchange: Multisig post-kex round messages from other signers did not all contain two pubkeys.

Comment on lines -5101 to -5108
// open kex messages
std::vector<multisig::multisig_kex_msg> expanded_msgs;
expanded_msgs.reserve(kex_messages.size());

for (const auto &msg : kex_messages)
expanded_msgs.emplace_back(msg);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved this so expanded_msgs is defined right before its use.

@UkoeHB
Copy link
Contributor Author

UkoeHB commented Sep 20, 2022

@tmoravec ok updated to hopefully fix that issue (also added your changes for simplewallet - note that I use the longer force-update-use-with-caution flag because it REALLY needs to be used with caution).

I am wondering if removing PRINT_USAGE(USAGE_EXCHANGE_MULTISIG_KEYS); will cause problems... is there another way to get the usage?

@UkoeHB UkoeHB force-pushed the multisig_force_update branch from 99c9e3d to 2b6a716 Compare September 20, 2022 23:50
@tmoravec
Copy link
Contributor

tmoravec commented Sep 21, 2022

I am wondering if removing PRINT_USAGE(USAGE_EXCHANGE_MULTISIG_KEYS); will cause problems... is there another way to get the usage?

Now it supports zero arguments and any number of arguments. I can't think of wrong usage that would trigger this generic error. Happy to put it back if we can make up the condition.

FWIW There's always help exchange_multisig_keys. Plus more specific error messages, for example:

[wallet 51sehD (no daemon)]: exchange_multisig_keys test
Wallet password:
Error: Failed to perform multisig keys exchange: basic_string::substr: __pos (which is 13) > this->size() (which is 4)

@tmoravec
Copy link
Contributor

ok updated to hopefully fix that issue

I've tested this and it works 👍 . What I did:

  • Open two parts of a 2/3 multisig wallet created with v0.17.2.0.
  • Finish the multisig setup with Workflow 2 above using exchange_multisig_keys force-update-use-with-caution ...
  • Verify that the addresses match the previous address (as seen in v0.17.2.0 software).
  • Refresh, exchange multisig info, receive transfer, send transfer.

Thank you, @UkoeHB !

@UkoeHB UkoeHB force-pushed the multisig_force_update branch from 2b6a716 to 4b0785f Compare September 21, 2022 17:38
@UkoeHB
Copy link
Contributor Author

UkoeHB commented Sep 21, 2022

squashed commits

@UkoeHB UkoeHB changed the title multisig: force update option for key exchange multisig: force update option for key exchange [master] Sep 21, 2022
@luigi1111 luigi1111 merged commit 0a10a4f into monero-project:master Sep 26, 2022
@UkoeHB UkoeHB deleted the multisig_force_update branch June 28, 2023 14:38
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.

5 participants