Author

Topic: Air-gapping old legacy non-descriptor wallets, migration to descriptor wallet (Read 131 times)

staff
Activity: 3458
Merit: 6793
Just writing some code
You don't need to construct the descriptor import command, listdescriptors already gives it to you. It was designed so that you can just take the entirety of the "descriptors" field and pass it straight to importdescriptors.
legendary
Activity: 2702
Merit: 1468
I thought I share my experience migrating old legacy non-descriptor wallets to descriptor wallets
and setting the corresponding watch wallets for them. I wish there was an easier way to do this.

I used Bitcoin Core version 25.1

You need two 'bitcoin only' dedicated computers: OFFLINE and ONLINE.

On the OFFLINE computer:
-------------------------------

1. Restore your legacy, non-descriptor wallet in bitcoin-qt, name it "legacyNoDesc"
2. Open the console and run

Code:
migratewallet "legacyNoDesc" "password"
   
   Backup the wallet as "legacyDesc".
  
3. To get the range descriptors, run listdescriptors, make note of all the descriptors:

   for 1* addresses, the range descriptor looks like "pkh(***44'***)***", there are two, copy both of them
   for 3* addresses, the range descriptor looks like "sh(wpkh(***49'***))***", there are two, copy both of them
   for bc1q* addresses, the range descriptor looks like "wpkh(***84'***)***", there are two, copy both of them
   for bc1p* addresses, the range descriptor looks like "tr(***86'***)***", there are two, copy both of them
  
4. To get the one2one descriptors, run listaddressgroupings, and then listunspent command in the console
for each address you want to monitor. Look for descriptors in the format "***(***/0'***)***".

Those are ONE address <-> ONE descriptor, no range descriptors. You can run

Code:
deriveaddresses "pkh(***0'***)***"

to see/validate which address they derive.

For range descriptors create two commands for each address type:

Code:
importdescriptors "[{\"desc\": \"pkh(***44'***)..."\, "\range\": [0,999], \"timestamp\": 0, \"internal\": false, \"watchonly\": true, \"active\": true}]"
importdescriptors "[{\"desc\": \"pkh(***44'***)???"\, "\range\": [0,999], \"timestamp\": 0, \"internal\": true, \"watchonly\": true, \"active\": true}]"
Code:
importdescriptors "[{\"desc\": \"sh(wpkh(***49'***))..."\, "\range\": [0,999], \"timestamp\": 0, \"internal\": false, \"watchonly\": true, \"active\": true}]"
importdescriptors "[{\"desc\": \"sh(wpkh(***49'***))???"\, "\range\": [0,999], \"timestamp\": 0, \"internal\": true, \"watchonly\": true, \"active\": true}]"
Code:
importdescriptors "[{\"desc\": \"wpkh(***84'***)..."\, "\range\": [0,999], \"timestamp\": 0, \"internal\": false, \"watchonly\": true, \"active\": true}]"
importdescriptors "[{\"desc\": \"wpkh(***84'***)???"\, "\range\": [0,999], \"timestamp\": 0, \"internal\": true, \"watchonly\": true, \"active\": true}]"
Code:
importdescriptors "[{\"desc\": \"tr(***86'***)..."\, "\range\": [0,999], \"timestamp\": 0, \"internal\": false, \"watchonly\": true, \"active\": true}]"
importdescriptors "[{\"desc\": \"tr(***86'***)???"\, "\range\": [0,999], \"timestamp\": 0, \"internal\": true, \"watchonly\": true, \"active\": true}]"
  

You can leave timestamp 0 (if you want to rescan the whole chain, or put the current time.
Use https://www.epochconverter.com/ to pick the date/time you want as a starting point.

For the non-range descriptors the commands will be shorter:

Code:
importdescriptors "[{\"desc\": \"pkh(***0'***)..."\, "\range\": [0,999], \"timestamp\": 0}]"
importdescriptors "[{\"desc\": \"sh(wpkh(***0'***))..."\, "\range\": [0,999], \"timestamp\": 0}]"
importdescriptors "[{\"desc\": \"wpkh(***0'***)..."\, "\range\": [0,999], \"timestamp\": 0}]"
importdescriptors "[{\"desc\": \"tr(***0'***)..."\, "\range\": [0,999], \"timestamp\": 0}]"

Import descriptors for all addresses listed in listaddressgroupings command, or just the ones that have coins in them.

Save the above importdescriptors in a text file. You will run them in your WATCH wallet in step 7.

Now, on the ONLINE computer:
-------------------------------------

5. Sync up your client.
6. Create an EMPTY/BLANK descriptor wallet with NO PRIVATE keys. This will be your WATCH wallet.
7. Run the commands created in step 4.
   (If you use timestamp set to 0, each command will be re-scanning the whole chain.)

You can also import them with one command:

Code:
importdescriptors "[{***},{***},{***}]"

(replace *** with the descriptors your want to import)

Wait for the wallet to sync up, and your WATCH wallet should show the correct balance of your COLD wallet.

Now you can create unsigned transactions in your WATCH wallet (on the ONLINE computer),
and sign them in the COLD wallet (on the OFFLINE computer).

The OFFLINE computer should never be connected to any network, preferably have no WiFi/WAN
and Ethernet hardware. Never put any software on it, other than some hardened OS and the Bitcoin Core.

Same goes for the ONLINE computer, install some hardened OS on it, block all in/out connections, except out
TCP 53, 8333 for bitcoin-qt executable ONLY. Use some other safe computer to download binaries from
https://bitcoincore.org/en/download/ and verify the checksum before copying core binaries
to your 'bitcoin only' computers.

PS. I wish someone would add "createwatchwallet" command to the Bitcoin Core so that we don't have to
do this manually, maybe something like this:

Code:
createwatchwallet "cold_wallet_name" "watch_wallet_name"
Jump to: