Skip to content

Descriptors

Introduction

Output script descriptors, or just descriptors, are a human-readable, precise, interoperable way to describe the keys and scripts used by a wallet, containing all the information necessary in order to:

  1. Derive scriptPubKeys, generate addresses and track wallet-related transactions
  2. Given the relevant secret keys (and, for advanced Miniscript, hash preimages and satisfied timelocks), construct finalized signed transactions spending from the wallet

An example descriptor looks like this: wpkh(xpub661MyMwAqRbcFvXwqbgwigDczeocqeBEibKMCkoc31RiyB464Ybc1z8sWMnR38JdeCBJPPkSM7mKahcBX2nPX9KYVTz3cotpLmSkMxrp99L/0/*)

Descriptors improve cross-wallet compatibility, enabling a more seamless transition between different descriptor-based wallets. They also improve backups, by making derivation paths and script types explicit and avoiding the ambiguities that would exists if only key material was backed up.

When combined with Miniscript, descriptors can represent complex spending conditions beyond simple key ownership. Descriptors also pair well with PSBT, providing signing devices with all the information necessary to produce signatures and simplifying collaborative signing in a multi-party setting.

Minsc ships with extensive support for descriptors, including all descriptor types, multi-path descriptors, wildcard/ranged descriptors, and Taproot descriptors with Miniscript trees.

Resources:

Descriptor Types

There are six types of descriptors:

Public Key Script Condition
Legacy pkh() sh() , bare()
Segwit wpkh() wsh()
Taproot tr()
Also known as P2PKH, P2SH, P2WPKH, P2WSH and P2TR

wpkh() and wsh() are the Segwitv0 descriptors. wpkh() is over a simple public key, wsh() is over a Miniscript.

pkh() and sh() are their legacy, pre-Segwit counterparts. They're included for completeness, but there's little reason to use them today.

tr() is the latest addition, for Taproot, and is a hybrid — can represent just a simple public key (the internal key), but also a script tree of Miniscripts.

bare() represents output scripts that embed the raw Script directly in the scriptPubKey, without the hash commit-reveal scheme used by the other script descriptor types. This includes P2PK and bare multi-sig.

bare() doesn't match the standard used by Bitcoin Core, where bare descriptors are simply the Miniscript fragment with no explicit descriptor wrapper. It follows the underlying miniscript::Descriptor structure, which has an explicit Bare variant.

Descriptor Keys

All descriptors include keys in them. The simply key descriptors, wpkh() and pkh(), wrap over a key directly. wsh(), sh(), tr() and bare() wrap over Miniscript, which itself contains one or more keys (it is illegal to have none).

Below is an overview of how keys are used with descriptors. See the full Key Construction and Key Structure under the Keys guide for more information.

For more information, see the full Key Construction and Key Structure guide.

To take the simplest example, this is a pkh() descriptor over an origin-less single key:

pkh(03a88a1e54440b909438e087f883faefd7fba5cf2d1128d455c40ede48b91baf04)

We can use it to generate the scriptPubKey and address:

>>> scriptPubKey(pkh(03a88a1e54440b909438e087f883faefd7fba5cf2d1128d455c40ede48b91baf04))
>>> address(pkh(03a88a1e54440b909438e087f883faefd7fba5cf2d1128d455c40ede48b91baf04))

Constructing a PSBT with the utxo provided as a descriptor makes it possible to compute sighashes, sign the transaction and prepare the finalized witness/scriptSig, all according to the descriptor type. For example:

psbt::sign_extract([
  "input": [
    "prevout": e5ac2caa4379baa9041ffa9894ebb294d9d8a2214b6c2a15e1c450c23adcf3ca:1,
    "utxo": wpkh(03a88a1e54440b909438e087f883faefd7fba5cf2d1128d455c40ede48b91baf04):0.01 BTC,
  ],
  "output": tb1qvaqh0hlczskzl028cx896p6z4enmz9leha65ne:0.00999 BTC,
], cSrFTPEe9CgBhjTJWD2fDiLXUZsGFbaRGyedbMBPQQkFLgSuUsV3) // WIF secret key for 03a88a1e544…

Key Origin

Descriptor keys can have a BIP 32 key origin associated to them. The origin has no effect on the scriptPubKey, but is made available to signers (through the PSBT bip32_derivation and tap_key_origins fields) to enable them to derive child keys for signing and to verify change outputs.

This is an example single key descriptor with an origin:

wpkh([568788ed/1/7]03a88a1e54440b909438e087f883faefd7fba5cf2d1128d455c40ede48b91baf04)

With the origin set, it becomes possible to sign using the Xpriv for the [568788ed] master key, which will get derived at m/1/7 to produce a signature for the 03a88a1e5444 child key:

assert::eq(xprv9s21ZrQH143K4UoPkwYARjF43hpw7GXMCSL4DqiBm4n81ho2cg26oicMML9BMVWFPVMPzxetkZn7pPmvs8CS8hK3iNxWTYev5GCqdjPSd5A->fingerprint, 0x568788ed);

psbt::sign_extract([
  "input": [
    "prevout": e5ac2caa4379baa9041ffa9894ebb294d9d8a2214b6c2a15e1c450c23adcf3ca:1,
    "utxo": wpkh([568788ed/1/7]03a88a1e54440b909438e087f883faefd7fba5cf2d1128d455c40ede48b91baf04):0.01 BTC,
  ],
  "output": tb1qvaqh0hlczskzl028cx896p6z4enmz9leha65ne:0.00999 BTC,
], xprv9s21ZrQH143K4UoPkwYARjF43hpw7GXMCSL4DqiBm4n81ho2cg26oicMML9BMVWFPVMPzxetkZn7pPmvs8CS8hK3iNxWTYev5GCqdjPSd5A) // Xpriv for the [568788ed] master key

Xpubs

Descriptor can include Xpub keys, which can be derived using the / derivation operator.

For example, the descriptor below describes the same key and scriptPubKey as the one above, only that instead of providing the single child key material and the derivation path to it, it provides the Xpub key material for the [568788ed] master key and the derivation path from it:

wpkh(xpub661MyMwAqRbcGxsrry5AnsBnbjfRWjFCZfFf2E7oKQK6tW8BADLMMWvqCaJWoVeaZ6T2gKZd9NcFwWBaQU35xWC543d6WwoTS4uZEE4eM3y/1/7)
Example

To test this, we can see that both descriptors generate the same address and populate the same PSBT fields:

```minsc-exec id="desc-xpub-1" linenums="1" assert::eq(xpub661MyMwAqRbcGxsrry5AnsBnbjfRWjFCZfFf2E7oKQK6tW8BADLMMWvqCaJWoVeaZ6T2gKZd9NcFwWBaQU35xWC543d6WwoTS4uZEE4eM3y->fingerprint, 0x568788ed);

$a = wpkh([568788ed/1/7]03a88a1e54440b909438e087f883faefd7fba5cf2d1128d455c40ede48b91baf04); $b = wpkh(xpub661MyMwAqRbcGxsrry5AnsBnbjfRWjFCZfFf2E7oKQK6tW8BADLMMWvqCaJWoVeaZ6T2gKZd9NcFwWBaQU35xWC543d6WwoTS4uZEE4eM3y/1/7);

assert::eq(address($a), address($b));

$psbt_a = psbt[ "input": [ "prevout": e5ac2caa4379baa9041ffa9894ebb294d9d8a2214b6c2a15e1c450c23adcf3ca:1, "utxo": $a:0.01 BTC, ], "output": tb1qvaqh0hlczskzl028cx896p6z4enmz9leha65ne:0.00999 BTC, ];

assert::eq($psbt_a, psbt[ "input": [ "prevout": e5ac2caa4379baa9041ffa9894ebb294d9d8a2214b6c2a15e1c450c23adcf3ca:1, "utxo": $b:0.01 BTC, ], "output": tb1qvaqh0hlczskzl028cx896p6z4enmz9leha65ne:0.00999 BTC, ]); ```

Wildcard Descriptors

When the descriptor's inner Xpub keys use the /* wildcard modifier as their final derivation step, it indicates that the descriptor is indefinite and expected to be derived one more time before being used as a definite scriptPubKey.

Wildcard descriptors are also sometimes referred to as ranged descriptors.

See Wildcard Modifier and Definite vs Indefinite for more information.

$alice = xpub661MyMwAqRbcGxsrry5AnsBnbjfRWjFCZfFf2E7oKQK6tW8BADLMMWvqCaJWoVeaZ6T2gKZd9NcFwWBaQU35xWC543d6WwoTS4uZEE4eM3y;
$descriptor = wpkh($alice/1/*);
address($descriptor/7) // wpkh(xpub661MyMwAqRbcGx…/1/7)

Multi-Path Descriptors

Descriptor Range

Byte Encoding


Using in transaction outputs

Spending using PSBT

Script Descriptors

SortedMulti

Taproot Descriptors

→ See the Taproot guide


wpkh() is a Segwit (v0) descriptor over a simple public key, wsh()

Key generation:

```minsc-exec source="above" // Not really random to keep the example static :) fn xpriv::rand() = xprv9s21ZrQH143K3fqiXTrhaeZ3H6cJik1PwVZMfcRmTXycyUhqCVHHua1QdzRUYSQuHfbTdLK8gK4eASZ6n9fg8J5A11NS9FsLgqVbwtQvvAp; ;;; $xprv = xpriv::rand(); $xpub = pubkey($xprv);

Descriptor address:
```minsc-exec
$xpub = xpub661MyMwAqRbcG9vBdVPhwnVmq8So8CjFJiUxTzqP1sWbrH2yk2bYTNKtVE4bCbsoqx18JgVAdVD59BmntGu6VEzhSgU3zCZS6p2kL7BbCaV;
>>> address(wpkh($xpub/5))

$xpub = xpub661MyMwAqRbcG9vBdVPhwnVmq8So8CjFJiUxTzqP1sWbrH2yk2bYTNKtVE4bCbsoqx18JgVAdVD59BmntGu6VEzhSgU3zCZS6p2kL7BbCaV;
>>> scriptPubKey(pkh($xpub/5))

Descriptor setup: ```minsc-exec source="above" id="desc-wpkh" $sk = xprv9s21ZrQH143K3fqiXTrhaeZ3H6cJik1PwVZMfcRmTXycyUhqCVHHua1QdzRUYSQuHfbTdLK8gK4eASZ6n9fg8J5A11NS9FsLgqVbwtQvvAp; $pk = pubkey($sk); ;;; $descriptor = wpkh($pk/*);

Address generation:
```minsc-exec continues="desc-wpkh"
>>> address($descriptor/10)


// Not really random to keep the example static
fn xpriv::rand() = xprv9s21ZrQH143K3fqiXTrhaeZ3H6cJik1PwVZMfcRmTXycyUhqCVHHua1QdzRUYSQuHfbTdLK8gK4eASZ6n9fg8J5A11NS9FsLgqVbwtQvvAp;

>>> $sk=xpriv::rand(); $sk

```minsc-exec source="above" $sk = xprv9s21ZrQH143K3fqiXTrhaeZ3H6cJik1PwVZMfcRmTXycyUhqCVHHua1QdzRUYSQuHfbTdLK8gK4eASZ6n9fg8J5A11NS9FsLgqVbwtQvvAp;

$pk=pubkey($sk); $pk

Key and descriptor setup:
```minsc-exec id="desc-1" 
$alice = xpub661MyMwAqRbcG9vBdVPhwnVmq8So8CjFJiUxTzqP1sWbrH2yk2bYTNKtVE4bCbsoqx18JgVAdVD59BmntGu6VEzhSgU3zCZS6p2kL7BbCaV;
$descriptor = wpkh($alice/0/*);

Address generation: ```minsc-exec continues="desc-1"

address($descriptor/10)

```minsc-exec continues="desc-1"
>>> {$descriptor/10}->script_pubkey