Output descriptors explained (BIP 380 to 386)
An output descriptor is a short line of text that tells a wallet exactly which output scripts belong to it: the script type, the keys, where those keys come from and which derivation path to follow. For example, wpkh([73c5da0a/84h/0h/0h]xpub.../0/*) means "native SegWit addresses from this account's receive chain". Descriptors are specified in BIPs 380 to 386 by Pieter Wuille and Ava Chow, and they fix what a seed phrase alone can't record.
Updated · 8 min read
What is an output descriptor?
BIP 380 defines Output Script Descriptors as "a simple language which can be used to describe collections of output scripts". A descriptor is written like a function call: the outer function names the script type, and the arguments are keys or other scripts. A wallet reads it and can then generate every address it describes, recognize incoming payments and sign for them.
Descriptors were first built into Bitcoin Core and then written up as a set of BIPs in 2021, all by Pieter Wuille and Ava Chow:
| BIP | What it specifies |
|---|---|
| 380 | General syntax, key expressions and the checksum |
| 381 | Non-SegWit scripts: pk(), pkh(), sh() |
| 382 | SegWit scripts: wpkh(), wsh() |
| 383 | Multisig: multi(), sortedmulti() |
| 384 | combo() |
| 385 | raw() and addr() |
| 386 | Taproot: tr() |
Later BIPs extend the language, as covered below.
What problem do descriptors solve?
For years, a wallet backup meant a seed phrase and nothing else. That worked while every address was the same type. After SegWit added new output types, the same keys could produce legacy, nested SegWit or native SegWit addresses, and later Taproot ones. BIP 380 puts it plainly: "Given just the private keys, it is not possible for restored wallets to know which kinds of output scripts and addresses to produce."
Standard derivation paths (BIPs 44, 49 and 84) helped, but not every wallet used them, and special key prefixes like ypub and zpub only covered a few cases. BIP 380 calls those prefixes "a layer violation". Descriptors replace the guessing with an explicit statement: this script type, these keys, this path.
Anatomy of a descriptor
Here is a complete descriptor for the receive addresses of a native SegWit account. It belongs to the public test seed used in the BIPs ("abandon" eleven times, then "about"), so never send real funds to it:
wpkh([73c5da0a/84h/0h/0h]xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V/0/*)#afwvtk2s
| Part | Meaning |
|---|---|
wpkh(...) |
Script expression: produce P2WPKH (native SegWit) outputs |
[73c5da0a/84h/0h/0h] |
Key origin: the master key's fingerprint, and the path from it to the key that follows |
xpub6Cat...PW6V |
The account's extended public key |
/0/* |
Derive child 0 (the receive chain), then every child index below it |
#afwvtk2s |
Checksum over everything before the # |
Its first address, at index 0, is bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu, the same address BIP 84 lists for path m/84'/0'/0'/0/0. The descriptor contains no private keys: it can watch the wallet but not spend from it.
Script expressions: pkh, wpkh, sh, wsh, tr and more
Each script expression produces one kind of output script:
| Expression | BIP | Produces |
|---|---|---|
pk(KEY) |
381 | Pay to public key (P2PK), usable inside other expressions |
pkh(KEY) |
381 | P2PKH, legacy addresses starting with 1 |
sh(SCRIPT) |
381 | P2SH wrapping another script, addresses starting with 3 |
wpkh(KEY) |
382 | P2WPKH, addresses starting with bc1q |
wsh(SCRIPT) |
382 | P2WSH wrapping another script, long bc1q addresses |
multi(k,KEY,...) and sortedmulti(k,KEY,...) |
383 | k-of-n OP_CHECKMULTISIG, on its own or inside sh() or wsh() |
combo(KEY) |
384 | P2PK and P2PKH, plus P2WPKH and P2SH-P2WPKH for compressed keys |
raw(HEX) and addr(ADDR) |
385 | Exactly the given script, or the script behind an address |
tr(KEY) and tr(KEY,TREE) |
386 | P2TR (Taproot), addresses starting with bc1p |
multi_a(k,KEY,...) and sortedmulti_a(k,KEY,...) |
387 | Tapscript k-of-n multisig, only inside tr() |
Expressions nest. Nested SegWit is sh(wpkh(KEY)); native SegWit multisig is wsh(sortedmulti(2,KEY1,KEY2,KEY3)). Some placements are not allowed: sh() and tr() must be at the top level, and wpkh() and wsh() only accept compressed public keys.
sortedmulti() sorts the keys before building the script, as BIP 67 does for P2SH multisig, so every cosigner gets the same address regardless of the order keys were entered. BIP 383 also records the key limits: at most 3 keys for bare multi(), 15 compressed keys inside sh() (because of the 520-byte script limit), and 20 otherwise. multi_a() allows up to 999.
Key expressions and key origin
A key expression can be a hex public key, a WIF private key, or an extended key (xpub or xprv) followed by derivation steps:
- Key origin in square brackets: exactly 8 hex characters of fingerprint, then the path from that master key, such as
[73c5da0a/84h/0h/0h]. Hardware signers use it to recognize their own keys. - Hardened steps can be written with
hor':84hand84'mean the same. /*at the end means "every unhardened child", and/*hevery hardened child.- Normalization: when a descriptor is exported without private keys, the exporter derives the xpub at the last hardened step and moves those steps into the key origin. That's why descriptors show an account-level xpub followed only by unhardened steps.
- Inside
tr(), keys are 32-byte x-only public keys, as used by Taproot.
Receive and change in one descriptor: <0;1>
A wallet usually needs two nearly identical descriptors: one ending /0/* for receive addresses and one ending /1/* for change. BIP 389, by Ava Chow, lets you write both at once with a multipath step:
wpkh([73c5da0a/84h/0h/0h]xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V/<0;1>/*)#qf45pmyh
The first value in <0;1> gives the receive descriptor and the second the change descriptor. Each key expression may contain only one <...> step, and when several keys have one, the tuples must be the same length so they derive in lockstep.
The descriptor checksum
The 8 characters after the # are an error-detecting checksum, similar in design to Bech32 and written in the same 32-character alphabet (qpzry9x8gf2tvdw0s3jn54khce6mua7l). According to BIP 380 it:
- always detects any single-character mistake, and any case error;
- always detects 2 or 3 errors in descriptors up to 49,154 characters, 4 errors up to 507 characters and 5 errors up to 77 characters;
- lets random errors through with a chance of only 1 in 2^40.
The checksum is optional when parsing, but software may refuse descriptors without one. BIP 380's simplest test vector is raw(deadbeef)#89f8spxm: change a single character and the checksum fails. Because it is computed over the exact text, even switching h to ' changes it.
Common descriptor examples
With keys shortened for readability (fp is a master fingerprint, xpub... an account xpub):
- Legacy, BIP 44:
pkh([fp/44h/0h/0h]xpub.../<0;1>/*) - Nested SegWit, BIP 49:
sh(wpkh([fp/49h/0h/0h]xpub.../<0;1>/*)) - Native SegWit, BIP 84:
wpkh([fp/84h/0h/0h]xpub.../<0;1>/*) - Taproot, BIP 86:
tr([fp/86h/0h/0h]xpub.../<0;1>/*) - 2-of-3 native SegWit multisig, BIP 48:
wsh(sortedmulti(2,[fpA/48h/0h/0h/2h]xpubA.../<0;1>/*,[fpB/48h/0h/0h/2h]xpubB.../<0;1>/*,[fpC/48h/0h/0h/2h]xpubC.../<0;1>/*)) - Taproot with a script tree:
tr(KEY_A,{pk(KEY_B),multi_a(2,KEY_C,KEY_D,KEY_E)}), which can be spent by key A alone or by revealing one of the two scripts. - MuSig2 key path:
tr(musig(xpubA...,xpubB...,xpubC...)/0/*), using themusig()key expression from BIP 390, which aggregates the keys with MuSig2 and derives children from the aggregate as BIP 328 describes.
Miniscript: descriptors for any spending policy
Miniscript (BIP 379), by Pieter Wuille, Andrew Poelstra, Sanket Kanjalkar, Antoine Poinsot and Ava Chow, is a structured way to write Bitcoin Script so that software can analyze it: what conditions allow spending, what a spend will cost, and whether the witness could be tampered with. Its fragments include pk(), older() and after() for timelocks, sha256() for hash locks, and_v(), or_d() and thresh().
BIP 379 says that "from a user's perspective, Miniscript is not a separate language, but rather a significant expansion of the descriptor language". It is used inside wsh() and tr(). For example:
wsh(and_v(v:pk(KEY_USER),or_d(pk(KEY_SERVICE),older(12960))))
This requires the user's signature plus either a co-signing service's signature or a wait of 12,960 blocks (about 90 days) since the coins were received. Because any wallet that understands Miniscript can work out how to sign for it, complex policies such as timelocked recovery paths don't each need custom signing software.
Wallet policies for hardware signers (BIP 388)
Full descriptors can be long, and hardware signing devices have little memory and small screens. BIP 388, by Salvatore Ingala, defines wallet policies: a descriptor template with numbered placeholders, plus a separate list of keys. The 2-of-3 example above becomes:
wsh(sortedmulti(2,@0/**,@1/**,@2/**))
where /** is shorthand for /<0;1>/*. The user registers the policy on the device once, checks it on the device's own screen, and from then on the device can verify that change goes back to the same policy.
Newer descriptor BIPs
The descriptor language keeps growing. Proposals extending it include:
- BIP 387:
multi_a()andsortedmulti_a()for Tapscript multisig, built onOP_CHECKSIGADD. - BIP 390: the
musig()key expression for MuSig2 aggregate keys. - BIP 392:
sp()descriptors for silent payments, with newspscanandspspendkey encodings. - BIP 393: annotations, recovery hints appended after a
?, such asbh(the block height to start scanning from),gl(the gap limit) andml(the highest silent payment label used). They don't change which scripts the descriptor produces, and the checksum covers them.
Check each BIP's page for its current status before relying on it.
How to back up a descriptor
A descriptor without private keys can't spend coins, but it does reveal every address in the wallet, and so your whole transaction history and balance. Treat it as private, but it is not as critical as your seed.
- Single-signature wallets: your seed phrase plus the descriptor is a complete backup. The seed alone usually works if the wallet followed a standard path, but the descriptor removes any doubt about script type, path and account.
- Multisig wallets: the descriptor is essential. Without every cosigner's xpub and the exact policy, a quorum of seeds still can't find or spend the coins. Keep a copy of the descriptor with each seed backup; see the multisig guide.
- Keep the checksum. It lets the restoring wallet catch transcription errors.
- Write down the first address too. After restoring, check that the wallet shows the same one.
- Check what you export. Some software can export descriptors that include private keys (
xprv). Those must be protected like a seed phrase.
Descriptors also travel well between programs: PSBTs carry the same key origin information, so a signer can confirm which of its keys a transaction uses.
Frequently asked questions
What is a descriptor wallet?
A descriptor wallet stores output descriptors instead of a loose collection of keys. Each descriptor states exactly which scripts the wallet watches, for example wpkh() for native SegWit or tr() for Taproot, along with the keys and derivation paths. This makes backups, watch-only exports and multisig setups unambiguous across different wallet software.
What does the checksum at the end of a descriptor mean?
The eight characters after the # are a checksum defined in BIP 380. They let software detect typos: any single-character error is always caught, and random errors slip through only about once in 2^40 cases. The checksum is optional when parsing, but keeping it in backups is strongly recommended.
What does <0;1> mean in a descriptor?
It is a multipath step from BIP 389. It packs two descriptors into one: the first uses 0 at that position (receive addresses) and the second uses 1 (change addresses). So xpub/<0;1>/* stands for both xpub/0/* and xpub/1/*, which saves storing two almost identical lines.
Is it safe to share an output descriptor?
A descriptor with only public keys cannot be used to spend your coins, but it reveals all of your addresses, balances and transaction history. Share it only with software or people you'd trust with that information, such as your own watch-only wallet or your multisig cosigners. Descriptors containing xprv keys must be kept secret.
What is the difference between a descriptor and a derivation path?
A derivation path only says where a key sits in an HD wallet's tree, such as m/84'/0'/0'. A descriptor includes the path but also the script type, the extended public key, the master fingerprint and, for multisig, every cosigner's key and the threshold. It is a complete description of which addresses a wallet owns.