@@ -2194,10 +2194,9 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons
2194
2194
THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index");
2195
2195
2196
2196
// if keys are encrypted, ask for password
2197
- if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && m_multisig_rescan_k.empty () && !m_background_syncing )
2197
+ if (is_key_encryption_enabled () && !m_multisig )
2198
2198
{
2199
- static critical_section password_lock;
2200
- CRITICAL_REGION_LOCAL(password_lock);
2199
+ const boost::lock_guard password_lock(m_encrypt_keys_after_refresh_mutex);
2201
2200
if (!m_encrypt_keys_after_refresh && !m_processing_background_cache)
2202
2201
{
2203
2202
boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? "output found in pool" : "output received");
@@ -2556,13 +2555,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
2556
2555
m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1;
2557
2556
if (output_tracker_cache)
2558
2557
(*output_tracker_cache)[std::make_pair(tx.vout[o].amount, td.m_global_output_index)] = m_transfers.size() - 1;
2559
- if (m_multisig)
2560
- {
2561
- THROW_WALLET_EXCEPTION_IF(m_multisig_rescan_k.empty() && !m_multisig_rescan_info.empty(),
2562
- error::wallet_internal_error, "NULL m_multisig_rescan_k");
2563
- if (!m_multisig_rescan_info.empty() && m_multisig_rescan_info.front().size() >= m_transfers.size())
2564
- update_multisig_rescan_info(m_multisig_rescan_k, m_multisig_rescan_info, m_transfers.size() - 1);
2565
- }
2558
+ if (is_multisig_key_image_rescan_possible(td.get_public_key()))
2559
+ update_multisig_rescan_info(m_transfers.size() - 1);
2566
2560
LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid);
2567
2561
if (!ignore_callbacks && 0 != m_callback)
2568
2562
m_callback->on_money_received(height, txid, tx, td.m_amount, 0, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time);
@@ -2631,13 +2625,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
2631
2625
}
2632
2626
if (output_tracker_cache)
2633
2627
(*output_tracker_cache)[std::make_pair(tx.vout[o].amount, td.m_global_output_index)] = kit->second;
2634
- if (m_multisig)
2635
- {
2636
- THROW_WALLET_EXCEPTION_IF(m_multisig_rescan_k.empty() && !m_multisig_rescan_info.empty(),
2637
- error::wallet_internal_error, "NULL m_multisig_rescan_k");
2638
- if (!m_multisig_rescan_info.empty() && m_multisig_rescan_info.front().size() >= m_transfers.size())
2639
- update_multisig_rescan_info(m_multisig_rescan_k, m_multisig_rescan_info, m_transfers.size() - 1);
2640
- }
2628
+ if (is_multisig_key_image_rescan_possible(td.get_public_key()))
2629
+ update_multisig_rescan_info(m_transfers.size() - 1);
2641
2630
THROW_WALLET_EXCEPTION_IF(td.get_public_key() != tx_scan_info[o].in_ephemeral.pub, error::wallet_internal_error, "Inconsistent public keys");
2642
2631
THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status");
2643
2632
@@ -3754,6 +3743,7 @@ void wallet2::update_pool_state_by_pool_query(std::vector<std::tuple<cryptonote:
3754
3743
process_txs.clear();
3755
3744
3756
3745
auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() {
3746
+ const boost::lock_guard encrypt_keys_after_refresh_lock(m_encrypt_keys_after_refresh_mutex);
3757
3747
m_encrypt_keys_after_refresh.reset();
3758
3748
});
3759
3749
@@ -3827,6 +3817,7 @@ void wallet2::update_pool_state_from_pool_data(bool incremental, const std::vect
3827
3817
{
3828
3818
MTRACE("update_pool_state_from_pool_data start");
3829
3819
auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() {
3820
+ const boost::lock_guard encrypt_keys_after_refresh_lock(m_encrypt_keys_after_refresh_mutex);
3830
3821
m_encrypt_keys_after_refresh.reset();
3831
3822
});
3832
3823
@@ -4080,6 +4071,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
4080
4071
start_height = 0;
4081
4072
4082
4073
auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() {
4074
+ const boost::lock_guard encrypt_keys_after_refresh_lock(m_encrypt_keys_after_refresh_mutex);
4083
4075
m_encrypt_keys_after_refresh.reset();
4084
4076
});
4085
4077
@@ -4249,12 +4241,6 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
4249
4241
if (m_background_syncing || m_is_background_wallet)
4250
4242
m_background_sync_data.first_refresh_done = true;
4251
4243
4252
- m_multisig_rescan_info = std::vector<std::vector<tools::wallet2::multisig_info>>{};
4253
- for (auto &v: m_multisig_rescan_k)
4254
- memwipe(v.data(), v.size() * sizeof(v[0]));
4255
-
4256
- m_multisig_rescan_k = std::vector<std::vector<rct::key>>{};
4257
-
4258
4244
LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all(false)) << ", unlocked: " << print_money(unlocked_balance_all(false)));
4259
4245
}
4260
4246
//----------------------------------------------------------------------------------------------------
@@ -6493,6 +6479,17 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
6493
6479
}
6494
6480
}
6495
6481
6482
+ // Populate `m_multsig_rescan_info` and `m_multisig_rescan_k` with existing data in `m_transfers`
6483
+ if (m_multisig)
6484
+ {
6485
+ for (const auto &td : m_transfers)
6486
+ {
6487
+ const crypto::public_key output_pubkey = td.get_public_key();
6488
+ m_multisig_rescan_info[output_pubkey] = td.m_multisig_info;
6489
+ m_multisig_rescan_k[output_pubkey] = td.m_multisig_k;
6490
+ }
6491
+ }
6492
+
6496
6493
cryptonote::block genesis;
6497
6494
generate_genesis(genesis);
6498
6495
crypto::hash genesis_hash = get_block_hash(genesis);
@@ -14497,6 +14494,8 @@ cryptonote::blobdata wallet2::export_multisig()
14497
14494
14498
14495
const crypto::public_key signer = get_multisig_signer_public_key();
14499
14496
14497
+ MDEBUG("Start export_multisig()");
14498
+
14500
14499
// For each transfer (output owned by the multisig wallet):
14501
14500
// 1) Record the output's partial key image (from the local signer), so other signers can assemble the output's full key image.
14502
14501
// 2) Prepare enough signing nonces for one signing attempt with each possible combination of 'threshold' signers
@@ -14544,6 +14543,7 @@ cryptonote::blobdata wallet2::export_multisig()
14544
14543
const rct::multisig_kLRki kLRki = get_multisig_kLRki(n, td.m_multisig_k.back());
14545
14544
info[n].m_LR.push_back({kLRki.L, kLRki.R});
14546
14545
}
14546
+ m_multisig_rescan_k[td.get_public_key()] = td.m_multisig_k;
14547
14547
14548
14548
info[n].m_signer = signer;
14549
14549
}
@@ -14562,34 +14562,88 @@ cryptonote::blobdata wallet2::export_multisig()
14562
14562
return MULTISIG_EXPORT_FILE_MAGIC + ciphertext;
14563
14563
}
14564
14564
//----------------------------------------------------------------------------------------------------
14565
- void wallet2::update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n)
14565
+ bool wallet2::is_multisig_key_image_rescan_possible(const crypto::public_key &onetime_pubkey) const
14566
+ {
14567
+ if (!m_multisig)
14568
+ return false; // not multisig
14569
+ const auto msinfo_it = m_multisig_rescan_info.find(onetime_pubkey);
14570
+ if (msinfo_it == m_multisig_rescan_info.cend())
14571
+ return false; // don't have exported multsig info for this output
14572
+ else if (msinfo_it->second.size() + 1 < m_multisig_threshold)
14573
+ return false; // don't have *enough* exported multsig info for this output
14574
+ return true;
14575
+ }
14576
+ //----------------------------------------------------------------------------------------------------
14577
+ void wallet2::update_multisig_rescan_info(const size_t n)
14566
14578
{
14579
+ CHECK_AND_ASSERT_THROW_MES(m_multisig, "Cannot update multisig rescan info in a non-multisig wallet");
14567
14580
CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad index in update_multisig_info");
14568
- CHECK_AND_ASSERT_THROW_MES(multisig_k.size() >= m_transfers.size(), "Mismatched sizes of multisig_k and info");
14569
14581
14570
- MDEBUG("update_multisig_rescan_info: updating index " << n);
14571
14582
transfer_details &td = m_transfers[n];
14572
- td.m_multisig_info.clear();
14573
- for (const auto &pi: info)
14583
+ const crypto::public_key output_pubkey = td.get_public_key();
14584
+ const auto msinfo_it = m_multisig_rescan_info.find(output_pubkey);
14585
+ CHECK_AND_ASSERT_THROW_MES(msinfo_it != m_multisig_rescan_info.cend(),
14586
+ "multisig rescan info missing for output pubkey: " << output_pubkey);
14587
+
14588
+ if (is_key_encryption_enabled() && !m_processing_background_cache)
14574
14589
{
14575
- CHECK_AND_ASSERT_THROW_MES(n < pi.size(), "Bad pi size");
14576
- td.m_multisig_info.push_back(pi[n]);
14590
+ const boost::lock_guard password_lock(m_encrypt_keys_after_refresh_mutex);
14591
+ if (!m_encrypt_keys_after_refresh)
14592
+ {
14593
+ boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password("output received");
14594
+ THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming monero"));
14595
+ THROW_WALLET_EXCEPTION_IF(!verify_password(*pwd), error::password_needed, tr("Invalid password: password is needed to compute key image for incoming monero"));
14596
+ m_encrypt_keys_after_refresh.reset(new wallet_keys_unlocker(*this, &*pwd));
14597
+ }
14598
+ }
14599
+
14600
+ MDEBUG("update_multisig_rescan_info: updating index " << n);
14601
+ const crypto::key_image old_key_image = td.m_key_image;
14602
+ std::vector<multisig_info> old_multisig_info = msinfo_it->second;
14603
+ std::swap(old_multisig_info, td.m_multisig_info);
14604
+ try
14605
+ {
14606
+ // try update key images and multisig exchange infos
14607
+ td.m_key_image = get_multisig_composite_key_image(n); // may throw
14608
+ m_key_images.erase(old_key_image);
14609
+ td.m_key_image_known = true;
14610
+ td.m_key_image_request = false;
14611
+ td.m_key_image_partial = false;
14612
+ m_key_images[td.m_key_image] = n;
14613
+
14614
+ // update local multisig keys
14615
+ const auto mskey_it = m_multisig_rescan_k.find(output_pubkey);
14616
+ if (mskey_it != m_multisig_rescan_k.cend())
14617
+ {
14618
+ memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0]));
14619
+ td.m_multisig_k = mskey_it->second;
14620
+ }
14621
+ }
14622
+ catch (const std::exception &e)
14623
+ {
14624
+ MERROR("Encountered exception while attempting to build composite multisig key image, reverting state: "
14625
+ << e.what());
14626
+ std::swap(old_multisig_info, td.m_multisig_info);
14577
14627
}
14578
- m_key_images.erase(td.m_key_image);
14579
- td.m_key_image = get_multisig_composite_key_image(n);
14580
- td.m_key_image_known = true;
14581
- td.m_key_image_request = false;
14582
- td.m_key_image_partial = false;
14583
- td.m_multisig_k = multisig_k[n];
14584
- m_key_images[td.m_key_image] = n;
14585
14628
}
14586
14629
//----------------------------------------------------------------------------------------------------
14587
14630
size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs)
14588
14631
{
14589
14632
CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
14633
+ CHECK_AND_ASSERT_THROW_MES(!m_processing_background_cache,
14634
+ "Multisig wallet is marked as proccesing background cache");
14635
+
14636
+ // Set `m_processing_background_cache=true` just long enough for calls to
14637
+ // `update_multisig_rescan_info()` to not ask for the password.
14638
+ m_processing_background_cache = true;
14639
+ const auto done_processing = epee::misc_utils::create_scope_leave_handler([this]() {
14640
+ m_processing_background_cache = false;
14641
+ });
14590
14642
14591
- if (!m_multisig_rescan_k.empty() && !m_multisig_rescan_info.empty())
14592
- refresh(false);
14643
+ // Unconditionally refresh to attempt to capture latest on-chain received enotes that other
14644
+ // participants have seen and we have not.
14645
+ try { refresh(false); }
14646
+ catch (...) {}
14593
14647
14594
14648
std::unordered_set<crypto::public_key> seen;
14595
14649
for (cryptonote::blobdata &data: blobs)
@@ -14614,6 +14668,9 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs)
14614
14668
MINFO("Multisig info from this wallet ignored");
14615
14669
continue;
14616
14670
}
14671
+ const bool is_signer_recognized = std::find(m_multisig_signers.cbegin(), m_multisig_signers.cend(), signer)
14672
+ != m_multisig_signers.cend();
14673
+ CHECK_AND_ASSERT_THROW_MES(is_signer_recognized, "Signer is not a member of this multisig wallet");
14617
14674
if (seen.find(signer) != seen.end())
14618
14675
{
14619
14676
MINFO("Duplicate multisig info ignored");
@@ -14648,63 +14705,67 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs)
14648
14705
}
14649
14706
}
14650
14707
14708
+ // Insert multisig infos into `m_multisig_rescan_info` based on current signer and output pubkeys in `m_transfers`.
14709
+ // We are forced to guess because the output pubkey that a multisig export is tied to isn't explicitly provided in
14710
+ // message. This causes problems if the order is different from when a peer exports to when we import (like during a
14711
+ // reorg). However, this isn't really fixable without introducing backwards-incompatible changes to the messaging
14712
+ // protocol or the API for multisig app developers.
14651
14713
MINFO(boost::format("%u outputs found") % boost::lexical_cast<std::string>(i.size()));
14652
- m_multisig_rescan_info.push_back(std::move(i));
14653
- }
14654
-
14655
- CHECK_AND_ASSERT_THROW_MES(m_multisig_rescan_info.size() + 1 <= m_multisig_signers.size() && m_multisig_rescan_info.size() + 1 >= m_multisig_threshold, "Wrong number of multisig sources");
14656
-
14657
- m_multisig_rescan_k.reserve(m_transfers.size());
14658
- for (const auto &td: m_transfers)
14659
- m_multisig_rescan_k.push_back(td.m_multisig_k);
14660
-
14661
- // how many outputs we're going to update
14662
- size_t n_outputs = m_transfers.size();
14663
- for (const auto &pi: m_multisig_rescan_info)
14664
- if (pi.size() < n_outputs)
14665
- n_outputs = pi.size();
14666
-
14667
- if (n_outputs == 0)
14668
- return 0;
14669
-
14670
- // check signers are consistent
14671
- for (const auto &pi: m_multisig_rescan_info)
14672
- {
14673
- CHECK_AND_ASSERT_THROW_MES(std::find(m_multisig_signers.begin(), m_multisig_signers.end(), pi[0].m_signer) != m_multisig_signers.end(),
14674
- "Signer is not a member of this multisig wallet");
14675
- for (size_t n = 1; n < n_outputs; ++n)
14676
- CHECK_AND_ASSERT_THROW_MES(pi[n].m_signer == pi[0].m_signer, "Mismatched signers in imported multisig info");
14714
+ for (std::size_t transfers_idx = 0; transfers_idx < i.size(); ++transfers_idx)
14715
+ {
14716
+ if (transfers_idx >= m_transfers.size())
14717
+ break;
14718
+ const crypto::public_key assumed_onetime_pubkey = m_transfers.at(transfers_idx).get_public_key();
14719
+ auto &ms_info_for_otpk = m_multisig_rescan_info[assumed_onetime_pubkey];
14720
+ const auto signer_it = std::find_if(ms_info_for_otpk.begin(), ms_info_for_otpk.end(),
14721
+ [&signer](const auto &x) { return x.m_signer == signer; });
14722
+ // if multisig info for this signer and OTA was already present, replace it, else append it
14723
+ if (signer_it != ms_info_for_otpk.end())
14724
+ *signer_it = i.at(transfers_idx);
14725
+ else
14726
+ ms_info_for_otpk.push_back(i.at(transfers_idx));
14727
+ }
14677
14728
}
14678
14729
14679
- // trim data we don't have info for from all participants
14680
- for (auto &pi: m_multisig_rescan_info)
14681
- pi.resize(n_outputs);
14682
-
14683
14730
// sort by signer
14684
- if (!m_multisig_rescan_info.empty() && ! m_multisig_rescan_info.front().empty() )
14731
+ for (auto &p : m_multisig_rescan_info)
14685
14732
{
14686
- std::sort(m_multisig_rescan_info.begin(), m_multisig_rescan_info.end(), [](const std::vector<tools::wallet2::multisig_info> &i0, const std::vector<tools::wallet2::multisig_info> &i1){ return memcmp(&i0[0].m_signer, &i1[0].m_signer, sizeof(i0[0].m_signer)) < 0; });
14733
+ std::sort(p.second.begin(), p.second.end(),
14734
+ [](const multisig_info &i0, const multisig_info &i1) { return i0.m_signer < i1.m_signer; });
14687
14735
}
14688
14736
14689
- // first pass to determine where to detach the blockchain
14690
- for (size_t n = 0; n < n_outputs; ++n)
14737
+ // First pass to determine where to detach the blockchain.
14738
+ // This allows us to find where enotes were spent on-chain next refresh, even if we have scanned past that point.
14739
+ for (const transfer_details &td : m_transfers)
14691
14740
{
14692
- const transfer_details &td = m_transfers[n];
14693
14741
if (!td.m_key_image_partial)
14694
14742
continue;
14695
14743
MINFO("Multisig info importing from block height " << td.m_block_height);
14696
14744
handle_reorg(td.m_block_height);
14697
14745
break;
14698
14746
}
14699
14747
14700
- for (size_t n = 0; n < n_outputs && n < m_transfers.size(); ++n)
14748
+ // Preliminary round of updating multisig infos. This updates multisig info for outputs which
14749
+ // have already been imported and their key images are calculated. These won't be touched in the
14750
+ // following refresh.
14751
+ for (std::size_t transfer_idx = 0; transfer_idx < m_transfers.size(); ++transfer_idx)
14701
14752
{
14702
- update_multisig_rescan_info(m_multisig_rescan_k, m_multisig_rescan_info, n);
14753
+ const transfer_details &td = m_transfers.at(transfer_idx);
14754
+ if (is_multisig_key_image_rescan_possible(td.get_public_key()))
14755
+ update_multisig_rescan_info(transfer_idx);
14703
14756
}
14704
14757
14705
-
14758
+ // refresh to find where enotes are spent and fill in key image details
14706
14759
refresh(false);
14707
14760
14761
+ // count number of known key images after refresh
14762
+ std::size_t n_outputs = 0;
14763
+ for (const transfer_details &td : m_transfers)
14764
+ {
14765
+ if (td.m_key_image_known && !td.m_key_image_partial)
14766
+ ++n_outputs;
14767
+ }
14768
+
14708
14769
return n_outputs;
14709
14770
}
14710
14771
//----------------------------------------------------------------------------------------------------
0 commit comments