The legacy wallet IsMine()
is essentially a black box that would tell us whether the wallet is watching an output script. In order to migrate legacy wallets to descriptor wallets, we need to be able to compute all of the output scripts that a legacy wallet would watch. The original approach for this was to understand IsMine()
and write a function which would be its inverse. This was partially done in the original migration code, and attempted to be completed in #30328. However, further analysis of IsMine()
has continued to reveal additional edge cases which make writing an inverse function increasingly difficult to verify correctness.
This PR instead changes migration to utilize IsMine()
to produce the output scripts by first computing a superset of all of the output scripts that IsMine()
would watch and testing each script against IsMine()
to filter for the ones that actually are watched. The superset is constructed by computing all possible output scripts for the keys and scripts in the wallet - for keys, every key could be a P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH; for scripts, every script could be an output script, the redeemScript of a P2SH, the witnessScript of a P2WSH, and the witnessScript of a P2SH-P2WSH.
Additionally, the legacy wallet can contain scripts that are redeemScripts and witnessScripts, while not watching for any output script utilizing that script. These are known as solvable scripts and are migrated to a separate “solvables” wallet. The previous approach to identifying these solvables was similar to identifying output scripts - finding known solvable conditions and computing the scripts. However, this also can miss scripts, so the solvables are now identified in a manner similar to the output scripts but using the function CanProvide()
. Using the same superset as before, all output scripts which are ISMINE_NO
are put through CanProvide()
which will perform a dummy signing and then a key lookup to determine whether the legacy wallet could provide any solving data for the output script. The scripts that pass will have their descriptors inferred and the script included in the solvables wallet.
The main downside of this approach is that IsMine()
and CanProvide()
can no longer be deleted. They will need to be refactored to be migration only code instead in #28710.
Lastly, I’ve added 2 test cases for the edge cases that prompted this change of approach. In particular, miniscript witnessScripts and rawtr()
output scripts are solvable and signable in a legacy wallet, although never ISMINE_SPENDABLE
.