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:
- Derive
scriptPubKeys, generate addresses and track wallet-related transactions - 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:
- Minsc — Descriptors API Reference
- BIP 380 — Output Script Descriptors General Operation
- Bitcoin Core — Descriptors Documentation
- Bitcoin Optech — Descriptors Topic
- Learning Bitcoin from the Command Line — Descriptors Chapter
- Video Talk — Rethinking Wallet Architecture: Native Descriptor Wallets (by Ava Chow)
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 underlyingminiscript::Descriptorstructure, which has an explicitBarevariant.
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:
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/*);
// 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)