Skip to content

Commit

Permalink
wallet: disallow migration of invalid or not-watched scripts
Browse files Browse the repository at this point in the history
The legacy wallet allowed to import any raw script, without checking if
it was valid or not. Appending it to the watch-only set.

This causes a crash in the migration process because we are only
expecting to find valid scripts inside the legacy spkm.

These stored scripts internally map to `ISMINE_NO` (same as if they
weren't stored at all..).

So we need to check for these special case, and take into account that
the legacy spkm could be storing invalid not watched scripts.

Which, in code words, means IsMineInner() returning IsMineResult::INVALID
for them.
  • Loading branch information
furszy committed Aug 10, 2023
1 parent d23fda0 commit 1de8a23
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 1 deletion.
17 changes: 16 additions & 1 deletion src/wallet/scriptpubkeyman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1714,8 +1714,23 @@ std::unordered_set<CScript, SaltedSipHasher> LegacyScriptPubKeyMan::GetScriptPub
}

// All watchonly scripts are raw
spks.insert(setWatchOnly.begin(), setWatchOnly.end());
for (const CScript& script : setWatchOnly) {
// As the legacy wallet allowed to import any script, we need to verify the validity here.
// LegacyScriptPubKeyMan::IsMine() return 'ISMINE_NO' for invalid or not watched scripts (IsMineResult::INVALID or IsMineResult::NO).
// e.g. a "sh(sh(pkh()))" which legacy wallets allowed to import!.
if (IsMine(script) != ISMINE_NO) spks.insert(script);
}

return spks;
}

std::unordered_set<CScript, SaltedSipHasher> LegacyScriptPubKeyMan::GetNotMineScriptPubKeys() const
{
LOCK(cs_KeyStore);
std::unordered_set<CScript, SaltedSipHasher> spks;
for (const CScript& script : setWatchOnly) {
if (IsMine(script) == ISMINE_NO) spks.insert(script);
}
return spks;
}

Expand Down
6 changes: 6 additions & 0 deletions src/wallet/scriptpubkeyman.h
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,12 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv
std::set<CKeyID> GetKeys() const override;
std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override;

/**
* Retrieves scripts that were imported by bugs into the legacy spkm and are
* simply invalid, such as a sh(sh(pkh())) script, or not watched.
*/
std::unordered_set<CScript, SaltedSipHasher> GetNotMineScriptPubKeys() const;

/** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan.
* Does not modify this ScriptPubKeyMan. */
std::optional<MigrationData> MigrateToDescriptor();
Expand Down
14 changes: 14 additions & 0 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3920,6 +3920,13 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
return false;
}

// Get all invalid or non-watched scripts that will not be migrated
std::set<CTxDestination> not_migrated_dests;
for (const auto& script : legacy_spkm->GetNotMineScriptPubKeys()) {
CTxDestination dest;
if (ExtractDestination(script, dest)) not_migrated_dests.emplace(dest);
}

for (auto& desc_spkm : data.desc_spkms) {
if (m_spk_managers.count(desc_spkm->GetID()) > 0) {
error = _("Error: Duplicate descriptors created during migration. Your wallet may be corrupted.");
Expand Down Expand Up @@ -4026,6 +4033,13 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
continue;
}
}

// Skip invalid/non-watched scripts that will not be migrated
if (not_migrated_dests.count(addr_pair.first) > 0) {
dests_to_delete.push_back(addr_pair.first);
continue;
}

// Not ours, not in watchonly wallet, and not in solvable
error = _("Error: Address book data in wallet cannot be identified to belong to migrated wallets");
return false;
Expand Down

0 comments on commit 1de8a23

Please sign in to comment.