Skip to content

4.2: Integrating Addresses and Descriptors

You now have an understanding of your descriptor wallet and the variety of addresses that it can create. You've even seen how your descriptor wallet contains ranged descriptors for four sorts of addresses.

But you can also have non-ranged descriptors for individual addresses. This section examines them.

Examine an Address' Descriptor

You created a set of three addresses in the previous section. You can see the details of any individual address with the getaddressinfo command, including its individual descriptor:


bitcoin-cli getaddressinfo tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4

| {
|   "address": "tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4",
|   "scriptPubKey": "00142a4f27c78470206e1a5948b47478d5b37991aac4",
|   "ismine": true,
|   "solvable": true,
|   "desc": "wpkh([e18dae20/84h/1h/0h/0/2]02040bf9b12e48bbbcbf72ef5197bc18067db378411ae6220f1d0a77da2ee7dbba)#dqt0983r",
|   "parent_desc": "wpkh([e18dae20/84h/1h/0h]tpubDC4ujMbsd9REzpGk3gnTjkrfJFw1NnvCpx6QBbLj3CHBzcLmVzssTVP8meRAM1WW4pZnK6SCCPGyzi9eMfzSXoeFMNprqtgxG71VRXTmetu/0/*)#3658f8sn",
|   "iswatchonly": false,
|   "isscript": false,
|   "iswitness": true,
|   "witness_version": 0,
|   "witness_program": "2a4f27c78470206e1a5948b47478d5b37991aac4",
|   "pubkey": "02040bf9b12e48bbbcbf72ef5197bc18067db378411ae6220f1d0a77da2ee7dbba",
|   "ischange": false,
|   "timestamp": 1770329126,
|   "hdkeypath": "m/84h/1h/0h/0/2",
|   "hdseedid": "0000000000000000000000000000000000000000",
|   "hdmasterfingerprint": "e18dae20",
|   "labels": [
|     ""
|   ]
| }

This reveals the descriptor for this individual address:

| "desc": "wpkh([e18dae20/84h/1h/0h/0/2]02040bf9b12e48bbbcbf72ef5197bc18067db378411ae6220f1d0a77da2ee7dbba)#dqt0983r",

You can compare that to the parent_desc, which contains the ranged descriptor that this address descriptor is descended from (and that you also saw when you listed out all of your descriptors):

| "parent_desc": "wpkh([e18dae20/84h/1h/0h]tpubDC4ujMbsd9REzpGk3gnTjkrfJFw1NnvCpx6QBbLj3CHBzcLmVzssTVP8meRAM1WW4pZnK6SCCPGyzi9eMfzSXoeFMNprqtgxG71VRXTmetu/0/*)#3658f8sn",

There are a few differences between the two:

  • The wallet descriptor has a range of addresses 0/*, while the address descriptor displays one specific index in that range 0/2.
  • The wallet descriptor contains an account key (for all the addresses), while the address descriptor contains an account key (for the individual address).
  • The derivation path is split up differently, which is what shows that the wallet descriptor has an account key and the address descriptor an address key.
  • The checksums are different, as you'd expect due to the other changes.

That's all that's different between a wallet descriptor and an address descriptor (and that similarity is how the one is used to derive hundreds or thousands of the other).

Derive Addresses from a Descriptor

In fact, you can derive addresses from a descriptor on your own, without having to use the getnewaddress command again and again. This is done with the deriveaddresses command: you give it a ranged descriptor, then tell it how far to derive to:

bitcoin-cli deriveaddresses "wpkh([e18dae20/84h/1h/0h]tpubDC4ujMbsd9REzpGk3gnTjkrfJFw1NnvCpx6QBbLj3CHBzcLmVzssTVP8meRAM1WW4pZnK6SCCPGyzi9eMfzSXoeFMNprqtgxG71VRXTmetu/0/*)#3658f8sn" 2

| [
|   "tb1q05ua6g7njnjrtsjc0t9w3jc6g2leeznasf4ny9",
|   "tb1q0psqqqgy0fv5928wmk86ntu7hlax8dva7nl82p",
|   "tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4"
| ]

This example shows the derivation of addresses from the BIP-84 ranged descriptor up through index "2". If you check this against the addresses created in in the previous section, you'll see they're just the same. Which is of course the whole point of descriptors! They are deterministically derived in the same way every time.

The main purpose of the `deriveaddresses function would be to export addresses to other services (for example, if you wanted to export watch-only addresses to another wallet-app of if you wanted to watch a multisig address, as we will in Chapter 7).

📖 What is a watch-only address? A watch-only address allows you to watch for transactions related to an address (or to a whole family of addresses if you used a ranged descriptor), but not to spend funds on those addresses.

Import Descriptors

As shown in §3.4, you can also import descriptors from one wallet to the other using the importdescriptors command.

bitcoin-cli importdescriptors '[{ "desc": "wpkh(tprv8ZgxMBicQKsPd1dP4NpsFDpsLUCnZ7oyn4UEbYLw7if1EDVCxMgfSzAwP3aCr1YeRvX9GtGvHsCLdrM7zaDyh33jEj7joQoEeNEyJaSYm5p/84h/1h/0h/0/*)#grdqnase", "timestamp": "now", "active": true, "range": [0,10] }]'

Note that the JSON object that we're passing here (and in §3.4) can be reformatted for better clarity:

| [
|    {
|       "desc":"wpkh(tprv8ZgxMBicQKsPd1dP4NpsFDpsLUCnZ7oyn4UEbYLw7if1EDVCxMgfSzAwP3aCr1YeRvX9GtGvHsCLdrM7zaDyh33jEj7joQoEeNEyJaSYm5p/84h/1h/0h/0/*)#grdqnase",
|       "timestamp": "now",
|       "active":true,
|       "range":[
|          0,
|          10
|       ]
|    }
| ]

Here's a more extensive list at the four variables we're using

  • desc is the descriptor.
  • timestamp tells your server how far to go back looking for transactions related to this address.
  • active says that this descriptor should be used to generate new addresses of this type in your wallet.
  • range lists which addresses to import from this descriptor.

Create a Descriptor by Hand

So far we've been handed descriptors by various bitcoin-cli commands, but you can actually create them by hand as well (and doing so is often an important component of moving funds around).

The creation of a descriptor is simple because there's a designated format for each type. The Bitcoin Core GitHub has a listing of all them. Following are a few examples.

Create a Watch-Only Wallet

One thing before you get started: your default wallet ("") is set to hold private keys. You can use that if you're importing descriptors where you have the private key, like the example above. However, if you want to import descriptors without private keys, you need to create a special watchonly wallet:

bitcoin-cli createwallet "watchonly" true true

| {
|   "name": "watchonly"
| }

The two trues in this command are the magic sauce as shown in the help file:

| 1. wallet_name             (string, required) The name for the new wallet. If this is a path, the wallet will be created at the path location.
| 2. disable_private_keys    (boolean, optional, default=false) Disable the possibility of private keys (only watchonlys are possible in this mode).
| 3. blank                   (boolean, optional, default=false) Create a blank wallet. A blank wallet has no keys.

The first true disables the use of private keys, the second true tells the wallet not to create keys of its own.

Remember that you're going to have to use loadwallet and unloadwallet to cycle to the right wallet, or else use a -rpcwallet flag with every command to make sure you're using the wallet. (We'll do the latter in the following examples.)

Create an Address Descriptor

An address descriptor takes the form:

addr(ADDR)

Using this address descriptor would allow you to import one of the addresses you'd already created into another wallet.

Creating a descriptor and importing it into Bitcoin Core is always a three-step process:

  1. Create the descriptor.
addr(tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4)
  1. Feed the descriptor into getdescriptorinfo to get a checksum.
bitcoin-cli getdescriptorinfo "addr(tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4)"

| {
|   "descriptor": "addr(tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4)#4vmsvy3l",
|   "checksum": "4vmsvy3l",
|   "isrange": false,
|   "issolvable": false,
|   "hasprivatekeys": false
| }
  1. Import the descriptor with checksum.
bitcoin-cli -rpcwallet=watchonly importdescriptors '[{ "desc": "addr(tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4)#4vmsvy3l", "timestamp": "now" }]'

| [
|   {
|     "success": true
|   }
| ]

Looking at the newly imported address reveals that the metadata is somewhat different from what was in the original wallet:

bitcoin-cli -rpcwallet=watchonly getaddressinfo tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4

| {
|   "address": "tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4",
|   "scriptPubKey": "00142a4f27c78470206e1a5948b47478d5b37991aac4",
|   "ismine": true,
|   "solvable": false,
|   "parent_desc": "addr(tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4)#4vmsvy3l",
|   "iswatchonly": false,
|   "isscript": false,
|   "iswitness": true,
|   "witness_version": 0,
|   "witness_program": "2a4f27c78470206e1a5948b47478d5b37991aac4",
|   "ischange": false,
|   "labels": [
|     ""
|   ]
| }

For example, we no longer have the ranged parent_desc and this one is not solvable (we don't have the private key!). Nonetheless it's the same address, which means that it's unlocked in the same way (as shown by the identical scriptPubKey, a topic we'll return to).

Create a Keyed Descriptor

The pk, pkh, and wpkh descriptors are all equally easy to create, since they just take the form of function(key).

If we go back to our original getaddressinfo, we can find that the public key for the address tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4 is:

| "pubkey": "02040bf9b12e48bbbcbf72ef5197bc18067db378411ae6220f1d0a77da2ee7dbba",

That means the wpkh descriptor would be:

wpkh(02040bf9b12e48bbbcbf72ef5197bc18067db378411ae6220f1d0a77da2ee7dbba)

We retrieve a checksum for it:

bitcoin-cli getdescriptorinfo "wpkh(02040bf9b12e48bbbcbf72ef5197bc18067db378411ae6220f1d0a77da2ee7dbba)"

| {
|   "descriptor": "wpkh(02040bf9b12e48bbbcbf72ef5197bc18067db378411ae6220f1d0a77da2ee7dbba)#3303qrm5",
|   "checksum": "3303qrm5",
|   "isrange": false,
|   "issolvable": true,
|   "hasprivatekeys": false
| }

Then we import it:

bitcoin-cli -rpcwallet=watchonly importdescriptors '[{ "desc": "wpkh(02040bf9b12e48bbbcbf72ef5197bc18067db378411ae6220f1d0a77da2ee7dbba)#3303qrm5", "timestamp": "now" }]'

| [
|   {
|     "success": true
|   }
| ]

Voila, we have recreated our address using a descriptor and the public key:

bitcoin-cli -rpcwallet=watchonly getaddressesbylabel ""

| {
|   "tb1q9f8j03uywqsxuxjefz68g7x4kduer2ky6shsf4": {
|     "purpose": "receive"
|   }
| }

📖 Why didn't we supply a derivation path? Derivation paths derive child keys from master keys. If we wanted to create a ranged descriptor we'd need a derivation path so that all the indexed keys could be created. Similarly, if we knew the master key but not the address key, we'd need a derivation path. In this case, we had the specific key for this specific address, and so no derivation path was needed.

Create Other Descriptors

This process could be repeated in a number of different ways. You could create a descriptor with a private key instead of a public key, and import it into regular (non-watchonly) wallet. You could create a ranged descriptor by hand and import a whole set of addresses. Although it's not best practice, you could even use the same key to create different types of addresses. (Try it out: just replace the "wpkh" above with "pkh", get a new checksum, and import and you'll have a P2PKH address instead of a P2WPKH address, unlocked by the same key.)

The main purpose here is to show how descriptors work in practice, so that the link between descriptors and address is clear, and so you can easily create addresses from descriptors when it's helpful in the future, such as when we create multisigs in §7.2 and when we create descriptors from remote seeds in §10.3.

Summary: Integrating Addresses and Descriptors

In the modern Bitcoin ecosystem, addresses and descriptors go hand in hand—and it's not just that you use ranged descriptors to create sets of address.

You can also step through a life cycle of descriptors:

  • You can create descriptors by hand.
  • You can import descriptors from other wallets.
  • You can derive addresses directly from descriptors.
  • You can view descriptors from individual addresses.

These are powerful techniques that we may not use a lot on the command line, but which are crucial to an overall understanding of how Bitcoin works.

What's Next?

Continue "Preparing Your Bitcoin Addresses" with §4.3: Creating QR Codes for Address.