Bitcoin core as cold storage, sign transactions offline and generate receiving addresses from the watch-only wallet.

TracaChang
5 min readMar 13, 2022

For long time I was interested in doing all this with Bitcoin Core instead of using other bitcoin wallets, now, thanks to descriptors it is possible and in a friendly way.

Minimum requirements: Just your PC, some linux distributions to boot from a DVD/USB like tails and a USB flash drive.

Recommended: An offline PC (any old pc will do the job) and a USB flash drive.

In this tutorial I am only using one PC with a bootable DVD with tails (https://tails.boum.org/) but it can be done with any other linux distribution.

Download Bitcoin Core or Bitcoin Knots (I advise you to try both and choose the one your prefer).

http://bitcoinknots.org/

https://bitcoin.org/en/download

Verify the signature and copy to your USB drive.

Now we will use the offline PC or boot with an offline linux distribution.

Copy bitcoin core/knots from the USB drive which we just downloaded, copy in documents, extract it and run bitcoin-qt. For those not used to with linux, to run it open terminal and execute it with ./

If you do not feel comfortable using the terminal just double click bitcoin-qt which is inside bitcoin-22.0/bin/ folder.

Bitcoin-qt will boot and ask us where to store data, just click ok, nothing will be downloaded since we are not even connected to the internet.

Create a new wallet:

Do not forget to select Descriptor wallet and encrypt it with a strong password. After your wallet is encrypted do backups and save it in different supports. (To backup, just click File -> Backup wallet)

Click Create a new receiving address, and check the address, we will compare that it matches when generating the receiving address with the watch-only wallet later:

If you use Bitcoin Knots, before click create new receiving address be sure that generate native segwit (Bech32) address is selected (in Bitcoin Core is selected by default). In this tutorial we are using bech32 addresses.

Then we will get the descriptors, this will be needed for our online instance of bitcoin core, we will need two descriptors, one will allow us to create receiving addresses and the other for change addresses:

Open the console (window → console, or Ctrl+T) and type:

listdescriptors

With this command we will get a list of 6 descriptors in total. We will be using those with path derivation 84'/0'/0.(We will be using this derivation for native segwit, bech32).

Copy in a text file both of them and save it to the USB flash drive, in this example my .txt file will look like this:

{
“desc”: “wpkh([66bb13d5/84'/0'/0']xpub6CtDSW4S3XVd5uYp9CgsLTZKQcKieJSmjehcvfVJBSy1rPbkKNU3T6UmZ3mn7DoSsTsM6uH8ZKem7LQh3PHyrBAtZopSvF2tonEE7foTWFe/1/*)#a9twa6j5”,
“timestamp”: 1647182091,
“active”: true,
“internal”: true,
“range”: [
0,
999
],
“next”: 0
},
{
“desc”: “wpkh([66bb13d5/84'/0'/0']xpub6CtDSW4S3XVd5uYp9CgsLTZKQcKieJSmjehcvfVJBSy1rPbkKNU3T6UmZ3mn7DoSsTsM6uH8ZKem7LQh3PHyrBAtZopSvF2tonEE7foTWFe/0/*)#v3w0q0zv”,
“timestamp”: 1647182091,
“active”: true,
“internal”: false,
“range”: [
0,
1000
],
“next”: 1
},

If you pay attention, one descriptor has the value internal:false while the other internal:true. Internal false will provide the information needed for the watch-only wallet to generate receiving addresses while internal true is for the change addresses.

Now we have all information we need so we can shut down, but before make sure you did a wallet backup. Wallet backup can be stored in a HDD, Micro SD, USB flash drive, keep it in a safe place.

Now we will switch to the online PC.

I already have bitcoin core installed and synced, if you do not have it yet, install it and sync it.

Create a watch wallet only:

Select Disable Private Keys, Make Blank Wallet and Descriptor Wallet.

Open the .txt where you saved both descriptors, go to console and import the both descriptors with their timestamp with the command importdescriptors.

In my example command will look like this:

first descriptor:

importdescriptors “[{\”desc\”: \”wpkh([66bb13d5/84'/0'/0']xpub6CtDSW4S3XVd5uYp9CgsLTZKQcKieJSmjehcvfVJBSy1rPbkKNU3T6UmZ3mn7DoSsTsM6uH8ZKem7LQh3PHyrBAtZopSvF2tonEE7foTWFe/1/*)#a9twa6j5\”, \”range\”: [0, 1000], \”timestamp\”: 1647182091, \”internal\”: true, \”watchonly\”: true, \”active\”: true}]”

and for second descriptor:

importdescriptors “[{\”desc\”: \”wpkh([66bb13d5/84'/0'/0']xpub6CtDSW4S3XVd5uYp9CgsLTZKQcKieJSmjehcvfVJBSy1rPbkKNU3T6UmZ3mn7DoSsTsM6uH8ZKem7LQh3PHyrBAtZopSvF2tonEE7foTWFe/0/*)#v3w0q0zv\”, \”range\”: [0, 1000], \”timestamp\”:1647182091, \”internal\”: false, \”watchonly\”: true, \”active\”: true}]”

You can copy paste my command and replace it with your descriptor and timestamp , review internal if must be true or false but copy paste will not work from medium since it changes quotation marks, you can copy from here:

https://justpaste.it/4dfno

When copy paste, be careful to not delete quotes \” wpkh…q0zv\”

If all correct it will return “success”: true.

This will import the descriptor with an initial keypool of 1000 (which is the default), any new addresses you request will come from their descriptors.

If you use Bitcoin Knots Go to settings→options→wallet and be sure that the address type is set to Native Segwit (bech32), which is the default for Bitcoin Core and the address type that we are using for this tutorial.

Now let’s do a test. If we have followed all steps, creating a new receiving address on this online instance should be the same address that we got when creating receiving address with the offline PC.

We see all match so now it is safe to use our watch wallet only for receiving payments.

In the next tutorial I will show you how to create transactions from the watch only wallet and sign them offline.

PD: I want to thank nc50lc from bitcointalk.org forum for his help adding the step to include the change (internal:true), since the first version of tutorial was only using the descriptor to receive addresses (internal:false).

Click the link to read the second part:

--

--