btclib.descriptors package

Submodules

btclib.descriptors.descriptors module

Output descriptors: the checksum, the parser, the scripts, the spend.

A descriptor says which scripts a wallet owns, in one line of text. This module reads that line and answers with both halves of what a wallet does with it: parse returns a Descriptor, whose script_pub_keys gives what the descriptor pays to at an index – the receiving side – and whose satisfy gives the script_sig and the witness that spend one of those scripts, the signatures being handed in. The fragment classes below are one per grammar function, which is what lets satisfaction be a method of each rather than a dispatch table.

satisfy assembles and does not verify. A signature is checked against the hash it committed to, that hash is a property of the spending transaction, and a descriptor has no transaction – which is also why the signatures are a parameter rather than something this module makes. psbt.finalize does have one and does check, and builds the same bytes from a psbt carrying the same signatures.

update_psbt_input and update_psbt_output are the third answer, and the one for a spend the signers do not make all at once: BIP174’s Updater, writing the scripts and the key origins into a psbt, for signers to fill in at their own pace and psbt.finalize to assemble. The output half is also what tells change from a payment, and it makes that claim only where the descriptor derives the very script being paid – index_of is the same question asked the other way round, and neither answers from a key origin whose fingerprint happens to match. This module imports psbt for all of it and nothing there imports back, which is the direction of the layering: a psbt is a transaction being built, and a descriptor is what a wallet knows about the outputs it holds.

BIP380: https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki

The grammar read is BIP380 to BIP390:

  • pk, pkh, wpkh, combo (BIP381, BIP382, BIP384)

  • sh, wsh, including sh(wpkh()) and sh(wsh())

  • multi, sortedmulti (BIP383)

  • addr, raw (BIP385)

  • tr, with a key path and a script tree whose leaves are pk(), multi_a() and sortedmulti_a() (BIP386, BIP387)

  • rawtr, which no BIP specifies: Bitcoin Core’s doc/descriptors.md is what defines it, and the key it takes is the output key itself

  • key expressions: hex public keys (compressed, uncompressed and, inside tr() and rawtr(), x-only), WIF private keys, xpub/xprv with a derivation path, key origin, /* and /*h wildcards, both h and ' hardened markers

  • musig, the BIP390 key expression: the BIP327 aggregate of its participants, inside a tr() or a rawtr() and nowhere else, with BIP328 derivation of the aggregate key where it has a path of its own

  • the <a;b> multipath form of BIP389, through multipath_descriptors

  • a BIP379 miniscript, in both of the positions that BIP allows: inside wsh(), where it is a MiniscriptDescriptor, and as a leaf of a tr() script tree, where it is a leaf beside the pk() and the multi_a() ones. Which expression is read as which is the order Bitcoin Core reads them in: the functions above are tried first, so the pk(), pkh() and multi() that belong to both grammars are the descriptor functions they were before miniscript, and what is left is a miniscript – of the P2WSH dialect inside wsh() and of the tapscript one inside tr(), which is where multi_a() replaces multi()

satisfy takes a SpendContext beside the signatures for the one fragment that reads more than they carry: a miniscript satisfaction chooses among branches, and the choice reads hash preimages and the lock times the transaction being built will carry. Every other fragment ignores it, having neither a branch to choose nor a preimage to look up.

miniscript_solver is the same satisfaction reached from the other side, where there is a psbt and no descriptor: an InputSolver for psbt.finalize, which reads the witness script back into the expression it is and satisfies it from the input’s own fields. It lives here rather than in psbt because of the direction above – this module imports that one – and a caller passes it: finalize(psbt, solver=miniscript_solver).

BIP390’s rule that a multipath musig() may not hold multipath participants has no counterpart here, and cannot: parse refuses a <a;b> step anywhere, multipath_descriptors expands it textually as BIP389 defines, and what reaches a Descriptor is one path per key. Such a descriptor is refused, and for the more general reason.

A parsed descriptor holds no key that signs. parse neuters an xprv to the xpub the KeyExpression then carries, and hands the private spelling back through the prv_keys mapping a caller passes in – Bitcoin Core’s Parse(desc, out, error), whose descriptor keeps no reference to the FlatSigningProvider it filled. Expansion takes the mapping back for the one thing an xpub cannot do, a hardened step, which is why script_pub_keys, satisfy, the two update_psbt halves and the rest all end in an optional prv_keys. A WIF is not in that mapping: it was already reduced to its public key on the way in, this module deriving nothing from it and signing nothing with it.

The network is a parameter of parse and not part of a descriptor: a descriptor names keys and scripts, and the same one means something on any chain, which is why Bitcoin Core takes the chain from its own context too. addr() is the exception, an address carrying the network in its own prefix.

str(descriptor) is the way back: the descriptor as text, without its checksum, add_checksum being what appends one. Bitcoin Core splits it the same way – ToString writes none and the rpc layer appends it – where HWI and Electrum name the checksummed form to_string. What it writes is public by construction, there being no private material left in a parsed descriptor to write.

account_descriptors is the other way in, for the one shape a wallet exports rather than reads: the receive and change descriptors of a BIP44 account, built from an account xpub and the master fingerprint of the key it came from. The purpose says which of the four encodings the account means, which is bip44’s mapping and is taken from there rather than copied – this module imports that one and bip44 imports nothing back.

normalized is the other direction Bitcoin Core has, its ToNormalizedString: the same descriptor with the xpub at each last hardened step and the hardened prefix moved into the key origin, so that a holder of no private key can compute every script it describes. That is what getdescriptorinfo answers with, and what an export to a watch-only wallet wants.

What this module exports is the checksum functions, parse and multipath_descriptors, and the fragment classes a parsed descriptor is made of – DescriptorTree and the MultiA a tree leaf may be among them, both being names a caller reads off TrDescriptor.tree. KeyExpression and PrvKeys are key_expression’s and the Miniscript a MiniscriptDescriptor holds is miniscript’s; the package __init__ re-exports the first two, being what a caller reads off Descriptor.key_expressions and annotates a mapping with. INPUT_CHARSET, CHECKSUM_CHARSET and GENERATOR stay out: they are the three tables BIP380’s checksum is computed from, which is what checksum, add_checksum and strip_checksum answer.

class btclib.descriptors.descriptors.AddrDescriptor(addr: str, *, network: str = 'mainnet')[source]

Bases: Descriptor

addr(ADDR): the script the address expands to, BIP385.

property key_expressions: tuple[KeyExpression, ...]

Return no KEY expression, the descriptor fixing none.

class btclib.descriptors.descriptors.ComboDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]

Bases: Descriptor

combo(KEY): the scripts an old wallet would have used, BIP384.

p2pk and p2pkh, plus p2wpkh and p2sh-p2wpkh when the key is compressed – an uncompressed key is not allowed in a witness program.

property key_expressions: tuple[KeyExpression, ...]

Return the one KEY expression, as the base class’s tuple.

class btclib.descriptors.descriptors.Descriptor(*, network: str = 'mainnet')[source]

Bases: ABC

A parsed output descriptor: the scripts it describes, on demand.

Keyword-only so that the fragments below can add positional fields of their own: a dataclass field with a default followed by one without is a TypeError, and network has a default.

address(index: int = 0, prv_keys: Mapping[str, str] | None = None) str[source]

Return the address of the script at index, if it has one.

addresses(index: int = 0, prv_keys: Mapping[str, str] | None = None) list[str][source]

Return the address of each script at index, empty where none.

index_of(script_pub_key: bytes | str | bytearray | memoryview | ScriptPubKey, last_index: int = 999, prv_keys: Mapping[str, str] | None = None) int | None[source]

Return the index whose script is this one, None where none is.

What makes an output this wallet’s, and the only thing that does: the script is derived and compared whole. A key origin whose fingerprint matches is not an answer – four bytes of a hash160 collide, and a psbt is written by whoever sends it, so an output marked as change on a fingerprint is an output a wallet may hand to somebody else believing it keeps it.

The output is named however the caller holds it: the ScriptPubKey that script_pub_key returns, that script as bytes or as a hex-string, or the address it renders as – “which index is this address” being the question a human has, and ScriptPubKey.from_address being what answers it. What is compared is the script in every case: an address is read for the script it encodes, and the network its prefix carries is not part of the answer, the same key paying to the same script on every chain.

Anything else is a BTClibTypeError, and a string that names no output – neither hex nor an address, the “” that a script with no address renders as among them – a BTClibValueError, because None is not “you passed the wrong thing” here: it is this output is not this wallet’s, which is the answer a caller acts on to say that an address is somebody else’s or that an output is not its own change (issue #540).

last_index bounds the search, both ends included, and is the caller’s: how far ahead of its own gap limit a wallet is willing to look is a policy this module has no view on. A descriptor that is not ranged has one script and answers 0 or None.

property is_ranged: bool

Return True if the descriptor describes a range of scripts.

abstract property key_expressions: tuple[KeyExpression, ...]

Return every KEY expression the descriptor holds.

redeem_script(index: int = 0, prv_keys: Mapping[str, str] | None = None) bytes[source]

Return the script that sh() or wsh() embeds this one as.

satisfy(signatures: Mapping[bytes | str | bytearray | memoryview, bytes | str | bytearray | memoryview], index: int = 0, prv_keys: Mapping[str, str] | None = None, spend: SpendContext | None = None) tuple[bytes, Witness][source]

Return the script_sig and witness that spend the script at index.

signatures maps a public key to the signature made with it, which is the shape psbt.PsbtIn.partial_sigs has. Keyed by key and not a sequence because the order the signatures go on the stack is the descriptor’s own knowledge – the key order of a multi(), the sorted order of a sortedmulti() – and a caller that had to know it would be building the script itself.

Both halves come back and one of the two is always empty: a legacy script has no witness, and a native segwit one has the empty script_sig BIP141 requires.

A signature short of what the script pops is an error and not a shorter answer. A 2-of-3 holding one signature is a psbt waiting for the second, psbt.PsbtIn.partial_sigs is where that state belongs, and bytes that do not spend would be a second and weaker spelling of it.

spend is what a miniscript satisfaction reads beside the signatures – hash preimages, and the lock times the transaction being built will carry – and is ignored by every other fragment, none of which has a branch to choose or a preimage to look up. A wsh() holding a miniscript is the one shape that needs it, and it says so: without one it refuses the fragments that would have read it.

script_pub_key(index: int = 0, prv_keys: Mapping[str, str] | None = None) ScriptPubKey[source]

Return the one script the descriptor describes at index.

script_pub_keys(index: int = 0, prv_keys: Mapping[str, str] | None = None) list[ScriptPubKey][source]

Return the scripts the descriptor describes at index.

A list because combo() is a set of scripts and not one script; every other fragment answers with exactly one.

update_psbt_input(psbt: Psbt, vin_i: int, index: int = 0, prv_keys: Mapping[str, str] | None = None) Psbt[source]

Return the psbt with input vin_i told what the descriptor knows.

BIP174’s Updater, for the one input this descriptor describes: the redeem script of a sh(), the witness script of a wsh(), the internal key, merkle root and leaf scripts of a tr(), and the origin of every key that carries one – which is what a hardware signer needs, and what KeyExpression.origin is kept for. psbt.finalize then assembles the same bytes satisfy does, from the signatures the signers filled in at their own pace: that pipeline is what a psbt is for, and what satisfy cannot answer, refusing a partial satisfaction rather than returning bytes that do not spend.

A copy, the psbt handed in being left alone, and the fields of the copy mutated in place: finalize is the same construction, and BIP174’s roles read as steps that update a psbt rather than as functions that return a field at a time.

What is not filled is what a descriptor does not know: the utxo, the sighash type, the signatures. Nor is the script checked against the output being spent – an input may not carry it yet, and Psbt.assert_signable asks that question for every input at once, being the role after this one.

update_psbt_output(psbt: Psbt, vout_i: int, index: int = 0, prv_keys: Mapping[str, str] | None = None) Psbt[source]

Return the psbt with output vout_i told what the descriptor knows.

The Updater’s other half, and what makes an output recognizable as the wallet’s own: the redeem script of a sh(), the witness script of a wsh(), the internal key and the whole script tree of a tr(), and the origin of every key that carries one. A signing device reads them to tell change from a payment – it can derive the script itself and see that the money comes back – and a wallet reading a psbt somebody else built reads them for the same reason.

Unlike the input half, the script is checked: the output being paid is in the psbt already, so this refuses unless the descriptor derives exactly that script at index. Marking an output as one’s own is a claim about where money goes, and the only evidence for it is the whole script – never a key origin whose four-byte fingerprint matches, which is what index_of is for and what it says.

The output tree is BIP371’s PSBT_OUT_TAP_TREE and not the leaf script an input carries: an output has no leaf being spent, so what it publishes is every leaf, each with its depth, which is what lets a reader rebuild the tree and check the output key for itself.

btclib.descriptors.descriptors.DescriptorTree = btclib.descriptors.key_expression.KeyExpression | btclib.descriptors.descriptors.MultiA | btclib.descriptors.miniscript.Miniscript | tuple['DescriptorTree', 'DescriptorTree']

A DescriptorLeaf, or a branch, a tuple of two subtrees.

class btclib.descriptors.descriptors.MiniscriptDescriptor(node: Miniscript, *, network: str = 'mainnet')[source]

Bases: Descriptor

A BIP379 miniscript where a SCRIPT expression may be one.

Which is inside wsh(): a miniscript inside tr() is a leaf of the script tree and not a SCRIPT expression, so DescriptorTree holds that one directly, the way it holds a multi_a(). What this holds is the Miniscript, whose own interface – the type properties, the resource bounds, the script both ways, the satisfaction – is btclib.descriptors.miniscript’s.

A fragment like the others in what it answers: the script at an index, the KEY expressions it names, and the psbt fields those keys fill. Unlike the others it does not satisfy: a miniscript satisfaction needs more than a mapping of public keys to signatures, so satisfy refuses and says so.

property key_expressions: tuple[KeyExpression, ...]

Return the KEY expressions of the fragments, left to right.

class btclib.descriptors.descriptors.MultiA(threshold: int, keys: tuple[KeyExpression, ...], sort: bool = False)[source]

Bases: object

multi_a(k,KEY,...) or sortedmulti_a(k,KEY,...): a leaf, BIP387.

A leaf of a tr() script tree, beside the bare KeyExpression that is a pk() leaf, and not a Descriptor: BIP387 allows these two functions inside tr() and nowhere else, so no output pays to one of them – what an output pays to is the tr() that commits to it as one of its tapscripts.

class btclib.descriptors.descriptors.MultiDescriptor(threshold: int, keys: tuple[KeyExpression, ...], sort: bool = False, *, network: str = 'mainnet')[source]

Bases: Descriptor

multi(k,KEY,...) and sortedmulti(k,KEY,...), BIP383.

property key_expressions: tuple[KeyExpression, ...]

Return the KEY expressions, in descriptor order.

class btclib.descriptors.descriptors.PkDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]

Bases: Descriptor

pk(KEY): a P2PK output, BIP381.

property key_expressions: tuple[KeyExpression, ...]

Return the one KEY expression, as the base class’s tuple.

class btclib.descriptors.descriptors.PkhDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]

Bases: Descriptor

pkh(KEY): a p2pkh output, BIP381.

property key_expressions: tuple[KeyExpression, ...]

Return the one KEY expression, as the base class’s tuple.

class btclib.descriptors.descriptors.RawDescriptor(script: bytes, *, network: str = 'mainnet')[source]

Bases: Descriptor

raw(HEX): the script those bytes are, BIP385.

property key_expressions: tuple[KeyExpression, ...]

Return no KEY expression, the descriptor fixing none.

class btclib.descriptors.descriptors.RawTrDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]

Bases: Descriptor

rawtr(KEY): the key as the output key itself, no tweak at all.

No BIP specifies this function. BIP386 specifies tr(), the tree expression and the x-only key inside them and never mentions rawtr(); what defines it is Bitcoin Core’s own doc/descriptors.md, which also carries the warning this docstring keeps: an output key whose internal key nobody knows cannot be shown to have no hidden script path, so a rawtr() describes an output a wallet already holds rather than one to build.

The key is BIP341’s output key, written into OP_1 <32 bytes> as it is. That is the whole difference from tr(KEY), which tweaks its internal key with an empty merkle root, and it is why this is not a TrDescriptor carrying tree=None.

property key_expressions: tuple[KeyExpression, ...]

Return the one KEY expression, as the base class’s tuple.

class btclib.descriptors.descriptors.ShDescriptor(inner: Descriptor, *, network: str = 'mainnet')[source]

Bases: Descriptor

sh(SCRIPT): the argument, p2sh-embedded, BIP381.

property key_expressions: tuple[KeyExpression, ...]

Return the wrapped SCRIPT’s KEY expressions.

class btclib.descriptors.descriptors.TrDescriptor(internal_key: KeyExpression, tree: KeyExpression | MultiA | Miniscript | tuple[KeyExpression | MultiA | Miniscript | tuple[DescriptorTree, DescriptorTree], KeyExpression | MultiA | Miniscript | tuple[DescriptorTree, DescriptorTree]] | None = None, *, network: str = 'mainnet')[source]

Bases: Descriptor

tr(KEY) or tr(KEY,TREE): a p2tr output, BIP386.

property key_expressions: tuple[KeyExpression, ...]

Return the internal key and every leaf key, in tree order.

taproot_leaf_scripts(index: int = 0, prv_keys: Mapping[str, str] | None = None) dict[bytes, tuple[bytes, int]][source]

Return every leaf script and its version, keyed by control block.

The shape of BIP371’s PSBT_IN_TAP_LEAF_SCRIPT, and the field an Updater is needed for rather than convenient: a control block holds the merkle path from its leaf to the root, which is the whole tree seen from that leaf, and a psbt carrying one leaf’s script has no way to compute another’s.

taproot_merkle_root(index: int = 0, prv_keys: Mapping[str, str] | None = None) bytes[source]

Return the root the output key commits to, b”” where there is none.

BIP371’s PSBT_IN_TAP_MERKLE_ROOT, and empty is how that field says “key path only”: a tr(KEY) tweaks its internal key with no tree, which is not the same as tweaking it with an empty one.

taproot_tree(index: int = 0, prv_keys: Mapping[str, str] | None = None) list[tuple[int, int, bytes]][source]

Return every leaf with its depth: BIP371’s PSBT_OUT_TAP_TREE.

The whole tree, where an input publishes the one leaf it spends and the control block that proves it: an output has no leaf being spent yet, so what it carries is each script with the depth it sits at, in the order a depth-first walk reaches them. A reader rebuilds the tree from those two facts and can then check the output key itself – which is why the field is the tree and not the merkle root, a root proving nothing about the scripts underneath it.

Empty for a tr(KEY), whose output key commits to no script at all, and which BIP371 says so about by leaving the field out.

class btclib.descriptors.descriptors.WpkhDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]

Bases: Descriptor

wpkh(KEY): a p2wpkh output, BIP382.

property key_expressions: tuple[KeyExpression, ...]

Return the one KEY expression, as the base class’s tuple.

class btclib.descriptors.descriptors.WshDescriptor(inner: Descriptor, *, network: str = 'mainnet')[source]

Bases: Descriptor

wsh(SCRIPT): the argument, P2WSH-embedded, BIP382.

property key_expressions: tuple[KeyExpression, ...]

Return the wrapped SCRIPT’s KEY expressions.

btclib.descriptors.descriptors.account_descriptors(xkey: BIP32KeyData | bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview, master_fingerprint: bytes | str | bytearray | memoryview | None = None, script_type: Literal['p2pkh', 'p2wpkh-p2sh', 'p2wpkh', 'p2tr'] | None = None) tuple[Descriptor, Descriptor][source]

Return the receive and change descriptors of a BIP44 account.

der_path is the three-level account path, m/purpose’/coin_type’/account’, in any spelling bip32.derive accepts; xkey is the extended key it starts from, which may be the master key or any key already partway down it – the account xpub itself, typically, which is what a wallet exports and what a hardware signer answers with.

The purpose selects the encoding, as it does for a BIP44 address: 44 is pkh(), 49 sh(wpkh()), 84 wpkh(), 86 tr(). A purpose outside bip44.SCRIPT_TYPE_FROM_PURPOSE raises unless script_type names one of those four, which then overrides the mapping for known purposes too. The network is the extended key’s own, and the coin type has to agree with it.

Both chains come back because a wallet is both: BIP44 puts receiving addresses under /0 and change under /1, and a wallet that imported one and not the other cannot recognize its own change – which is a lost output rather than a missing feature. They differ in that step and in nothing else, the key origin and the wildcard being the same.

The master fingerprint is what the key origin needs and what an extended key below the root cannot supply, so it is a parameter; where xkey is the master key it is computed, and a value handed in beside it has to match.

add_checksum(str(descriptor)) is the text Bitcoin Core takes. The BIP389 spelling of the pair – one descriptor with a <0;1> step – is text and not a parsed descriptor: multipath_descriptors expands one and parse refuses one, so what this returns is the two.

btclib.descriptors.descriptors.add_checksum(descriptor: str) str[source]

Return the descriptor with its checksum, verifying a present one.

btclib.descriptors.descriptors.at_index(descriptor: Descriptor, index: int = 0) Descriptor[source]

Return the descriptor of one index, with no wildcard left in it.

A ranged descriptor describes a range of scripts and names none of them; this names one, by writing the index the wildcard stands for into the derivation path – .../0/* at index 5 becomes .../0/5. The scripts are the same scripts: what changes is that the answer is a descriptor of one, which is what a reader wanting this script has to be given, an external signer displaying an address among them.

Bitcoin Core’s deriveaddresses is the same operation with the address as its answer rather than the descriptor.

A descriptor with no wildcard comes back unchanged, index 0 being the only index it has; the participants of a musig() are walked too, the range being on either side of the aggregation and never on both.

btclib.descriptors.descriptors.checksum(descriptor: str) str[source]

Compute the descriptor checksum.

btclib.descriptors.descriptors.from_address(address: str) str[source]

Return the addr() descriptor of the address, checksummed.

btclib.descriptors.descriptors.miniscript_sizer(psbt_in: PsbtIn, tx_in: TxIn) list[int] | None[source]

Size the spend of a psbt input whose witness script is a miniscript.

A SolutionSizer, which is what psbt_size.estimated_input_sizes takes for the inputs whose spend it cannot read – and it lives here for miniscript_solver’s reason, the layering: descriptors imports psbt and nothing there imports back. A caller passes it:

script_sig, witness = estimated_input_sizes(
    psbt_in, tx_in, sizer=miniscript_sizer
)

The witness of a p2wsh spend is the satisfaction and then the witness script, so that is the list: Miniscript.max_witness_stack and the script’s own length. It needs no signature and no preimage, an estimate being made before either exists – the size of a signature is the context’s, and BIP379 fixes a preimage at 32 bytes.

None where the input is not this sizer’s business: no witness script, one that is no miniscript, or one no witness can satisfy at all. The caller then answers as it did before, which for the last of those is a refusal: a script nobody can spend has no spend to estimate.

btclib.descriptors.descriptors.miniscript_solver(psbt: Psbt, vin_i: int) tuple[bytes, Witness] | None[source]

Spend a psbt input whose witness script is a miniscript.

An InputSolver, which is what psbt.finalize takes for the inputs whose spend is the caller’s to know – and a miniscript is one of them, for a reason of layering rather than of design: descriptors imports psbt and nothing there imports back, so the finalizer cannot reach the language that reads its witness script. Passing this is what closes that circle from the outside:

final = psbt.finalize(unsigned, solver=miniscript_solver)

Everything it needs is in the psbt. The witness script is read back into the expression it is – which is what miniscript.from_script is for, and what makes a signer able to spend a script nobody handed it a descriptor for – and the satisfaction reads the input’s own fields: the signatures of partial_sigs, the four preimage mappings BIP174 gave fields to, and the lock times of the transaction the psbt is building. A pk_h() names its key by a hash160, so the keys the input knows are offered for that lookup.

None where the input is not this solver’s business: no witness script, or one that is not a miniscript, both of which finalize then answers for as it did before. Where the script is a miniscript and the signatures do not satisfy it, the refusal is the satisfier’s and says so – a witness built from a guess would be worse, being one the network refuses after the transaction is broadcast rather than one this refuses while it is built.

btclib.descriptors.descriptors.multipath_descriptors(descriptor: str) list[str][source]

Return the single-path descriptors of a BIP389 multipath one.

A descriptor with no <a;b> step is one descriptor, returned checksummed and otherwise unchanged. One with such steps is as many descriptors as a step has elements, the first taking the first element of every step, the second the second, and so on – which is what makes the two-element form a receiving chain and a change chain.

The expansion is textual, as BIP389 defines it, and each result is a descriptor to be parsed on its own.

btclib.descriptors.descriptors.normalized(descriptor: Descriptor, prv_keys: Mapping[str, str] | None = None) Descriptor[source]

Return the descriptor with the xpub at each last hardened step.

Bitcoin Core’s ToNormalizedString, which is what getdescriptorinfo answers with and what an export to a watch-only wallet wants: every key of it derives from an xpub, so a holder of no private key can compute every script the descriptor describes.

The private material is the caller’s, as everywhere else here, and is needed for exactly the keys that have a hardened step to re-root at. Where one of those is missing this raises rather than handing back a descriptor that quietly still needs a key.

The hardening symbol is h throughout the result, whichever was read: a normalized descriptor is a canonical spelling and not the one that came in, which is Core’s rule – “always use h for hardened derivation” is how its own interface states it.

btclib.descriptors.descriptors.parse(descriptor: str, network: str = 'mainnet', prv_keys: dict[str, str] | None = None) Descriptor[source]

Return the Descriptor of a descriptor string, checksum verified.

A str, an output descriptor being text: what was neither reached strip_checksum untouched and left as an AttributeError about partition, or as a TypeError about mixing bytes and str – neither of them a word about the descriptor that was passed.

The network is asked for here rather than where a script is finally written: a name no network has was carried into the Descriptor and refused by the encoder that came to use it, which is a complaint about a key or an address, one call later than the argument that was wrong. Normalized as well, so that what the object holds is the name network_from_name would answer to.

prv_keys is a mapping, and what is not one was walked anyway: the lookups below are in and [], so a list of pairs answered “not found” for every key rather than saying it is not a mapping. None is asked for with it, that being the other type the annotation declares – and one call rather than a branch, which is what keeps the two spellings of “a declared type” in one place.

btclib.descriptors.descriptors.satisfaction_sizer(keys: Iterable[bytes | str | bytearray | memoryview]) Callable[[PsbtIn, TxIn], list[int] | None][source]

Return a SolutionSizer for the satisfaction these keys will build.

miniscript_sizer answers “how large could this get”, every branch open and every signature assumed – right where a caller does not yet know which branch a spend will take, and an overpay where it does. A quorum with a timelocked recovery quorum behind the same address is exactly that second caller: which of the two a transaction takes is decided before anything is estimated, the recovery spend being a separate operation with its own inputs and its own signatures.

Miniscript.satisfy already answers the narrower question – given which keys will sign, which branch and how large – so this is that answer as a SolutionSizer, beside miniscript_sizer and not a mode of it:

witness = estimated_input_sizes(
    psbt_in, tx_in, sizer=satisfaction_sizer(recovery_keys)
)

satisfy never checks a signature against anything, so a filler one of psbt_size.SIG_SIZE bytes for each key does exactly what a real one would for sizing, and none of them has to exist yet, any more than the ones miniscript_sizer assumes do. The preimages are the psbt input’s own, and the sequence is the input being sized, so an older() branch is measured against what this spend actually carries. An after() branch is not, locktime being the transaction’s and not this call’s to read – it is answered as unmet, and a script whose only open branch needs one refuses rather than guesses.

None for that refusal and the two miniscript_sizer already answers with it: no witness script, or one that is no miniscript. A caller then falls back exactly as it would for any other sizer answering “not mine”.

btclib.descriptors.descriptors.strip_checksum(descriptor: str) str[source]

Return the descriptor without its checksum, verifying a present one.

A descriptor without one comes back unchanged: the checksum is optional in the language, and only some of Bitcoin Core’s RPCs require it. What is never accepted is a checksum that does not match, which is what the eight characters are there for.

btclib.descriptors.descriptors.wallet_policy(descriptor: Descriptor, change: Descriptor | None = None) tuple[str, tuple[KeyExpression, ...]][source]

Return the BIP388 wallet-policy template of a Descriptor, and its keys.

Every KEY expression is written as its @N placeholder, in the order it is first read – BIP388’s own pair, a wallet descriptor template and the key information vector it indexes into, which is what an external signer’s registration flow reads and what wallet_policy_descriptor reads back.

change is the account’s change descriptor, descriptor its receive one – account_descriptors’ own pair, or two built by hand the same way. Given one, this writes BIP388’s own /<M;N>/* (/** where the pair is <0;1>), the shorthand its Test Vectors use throughout: descriptor and change must share every function, threshold and tree shape (_skeleton is the check), and must differ, key by key, in nothing but one explicit, unhardened step in front of the wildcard – BIP389’s own two-element multipath, read back out of two already single-path descriptors rather than out of the <a;b> text multipath_descriptors expands away before either one is parsed.

Left at its default, change is None and descriptor is read alone: the only placeholder this writes then is the plain, unhardened /* BIP388’s “Optional derivation paths” section allows as an implementation-specific pattern, a Descriptor holding one derivation path and /<M;N>/* needing two. What cannot be written either way is refused rather than silently dropped: a fixed public key, a hardened or explicit step before the wildcard beyond the one unhardened chain digit change accounts for, a musig() with derivation on its participants, receive and change disagreeing on a key or on the shape around it, and a key or a musig() group written more than once with a chain digit that is not disjoint from an earlier use – each with the fragment that was wrong. So is a descriptor whose own function is not one of BIP388’s SCRIPT expressions – combo(), addr(), raw(), rawtr() and a bare pk() among them. Not checked: BIP388’s position for a function within another (_assert_policy_top_level says so), and that every key is pairwise distinct from every other.

btclib.descriptors.descriptors.wallet_policy_address(template: str, key_info: Sequence[KeyExpression], index: int = 0, multipath_index: int = 0, network: str = 'mainnet') str[source]

Return the address a BIP388 wallet policy describes at index.

wallet_policy_descriptor resolved at multipath_index, then Descriptor.address at index – the derivation and the address encoding are answered by the descriptor code every other caller of this module uses, a wallet policy being a second way to name one of its descriptors and not a second way to compute one.

btclib.descriptors.descriptors.wallet_policy_descriptor(template: str, key_info: Sequence[KeyExpression], multipath_index: int = 0, network: str = 'mainnet') Descriptor[source]

Return the ranged Descriptor a BIP388 wallet-policy template describes.

template and key_info are the pair wallet_policy returns, or the same pair read off any other source of them – BIP388’s own test vectors, or an external signer’s registration flow. Every placeholder is replaced with its key-information text, and every trailing wildcard is resolved at multipath_index: BIP389’s /<M;N>/* positions M at index 0 and N at index 1, in that order and never by which digit is the smaller, and /** is /<0;1>/* by definition; the plain /* BIP388 also allows takes neither branch and reads the same descriptor whatever multipath_index is. The result still holds the index-selecting wildcard: Descriptor.address and the rest of what a ranged descriptor answers are what multipath_index was resolved for.

No prv_keys: every wildcard a wallet-policy template writes is unhardened, which is the whole of BIP388’s “the seed alone is no longer enough” guarantee working in reverse – the account xpub in key_info computes every script the policy describes, hardened step or not.

btclib.descriptors.key_expression module

BIP380 KEY expressions: what a descriptor names a public key with.

The bottom of the descriptor package, and the half of BIP380’s grammar that says nothing about scripts: a KEY expression is a public key, an extended key with a derivation path, or BIP390’s musig() aggregate of either, optionally behind the [fingerprint/path] key origin a signer needs. KeyExpression is what one parses to and sec is what it derives.

A module of its own because two modules above it read the same grammar. descriptors reads it inside pk(), multi(), tr() and the rest; miniscript reads it inside pk_k(), pk_h(), multi() and multi_a() – BIP379’s “Key expressions are specified in BIP380”, the same production and not a second dialect of it. Both import this one and this one imports neither, which is the direction of the layering.

Three text helpers come with it, for the same reason: _expression, _split_arguments and _split_function are what write a function out and read one back, and both halves of the grammar are written in functions.

BIP380: https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki BIP390: https://github.com/bitcoin/bips/blob/master/bip-0390.mediawiki

class btclib.descriptors.key_expression.KeyExpression(origin: BIP32KeyOrigin | None = None, pub_key: bytes | None = None, xkey: str = '', der_path: tuple[int, ...] = (), wildcard: int | None = None, x_only: bool = False, participants: tuple[KeyExpression, ...] = (), hardening: str = 'h')[source]

Bases: object

A BIP380 KEY expression: an origin, a key, a derivation path.

One of three things is the key. Either pub_key is the one the descriptor fixes, in SEC bytes; or xkey is the extended key that der_path and wildcard derive from; or participants are the KEY expressions BIP390’s musig() aggregates, der_path and wildcard then deriving from the aggregate key as BIP328 prescribes. An x-only key is held as its even-y SEC form, which is what BIP340 says those 32 bytes mean, so that everything downstream sees one representation of a public key.

origin never changes the script. It says which master key and which path the key came from, which is what a hardware signer needs and what BIP174 carries in a PSBT.

xkey is public whatever the descriptor spelled: parse neuters an xprv and hands the private material back to its caller, so no part of a parsed descriptor holds a key that signs. What that costs is a hardened step, which an xpub cannot take – sec takes the keys back as a parameter for it, the way Bitcoin Core’s expansion takes a SigningProvider.

aggregate(index: int = 0, network: str = 'mainnet', prv_keys: Mapping[str, str] | None = None) bytes[source]

Return the key BIP390’s participants aggregate to, derived.

KeyAgg over the sorted participants, and nothing more: the /NUM/.../* path of the expression derives from this key, and sec is what walks it. The two are separate because BIP373 keys its MuSig2 psbt fields by the aggregate key itself even where what the script holds is a child of it – an aggregate key is what the participants make, and a derivation of it is a key the same group answers for.

property is_aggregate: bool

Answer whether the key is the aggregate of BIP390 participants.

property is_compressed: bool

Return False for an uncompressed SEC public key, True otherwise.

An extended key is compressed by construction: BIP32 has no uncompressed serialization.

property is_ranged: bool

Answer whether the key has a wildcard to derive at an index.

A musig() is ranged where its own path has one and where a participant has one: BIP390 forbids both at once, so whichever it is, the index is read in one place.

participant_keys(index: int = 0, network: str = 'mainnet', prv_keys: Mapping[str, str] | None = None) list[bytes][source]

Return the participant keys in the order they are aggregated in.

Which is KeySort’s order and not the descriptor’s: BIP390 sorts after all derivation and before aggregation, so that the order the keys were written in does not change the key – a set of keys is what MuSig2 is about, and a descriptor that was not backed up does not need the order guessed as well. BIP373 stores the participants of a psbt in aggregation order too, which is what makes this the list that goes into PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS.

sec(index: int = 0, network: str = 'mainnet', prv_keys: Mapping[str, str] | None = None) bytes[source]

Return the SEC public key bytes, derived at index if ranged.

prv_keys is what parse handed back, and is needed for a hardened step and for nothing else: an unhardened path derives from the xpub the descriptor holds. A key it does not name is left as it is, so a mapping covering some of a multisig’s keys answers for those and no more.

A musig() answers with the key its participants aggregate to, derived along its own path where it has one: BIP328’s synthetic xpub is that key at depth zero with the fixed chain code an aggregate has instead of one of its own, and derive_ is then what refuses a hardened step – there being no aggregate private key to take one with. The index derives the participants or the aggregate and never both, BIP390 allowing a wildcard on one side only.

btclib.descriptors.miniscript module

Miniscript: the expression, its type, its script, both ways, BIP379.

A miniscript is a bitcoin script written as a tree of fragments, which is what makes a non-trivial spending condition something a wallet can read rather than something it has to recognize. parse reads the text BIP379 defines and from_script reads a script back into it; str and Miniscript.script are the two ways out. The round trip is the point: from_script(node.script()) is node again, so a signer handed a witness script can say what it means without being told. reads_back is that round trip asked of a script instead of assumed of it – whether the bytes in hand are the expression they look like – which is the question a caller has about a script somebody else wrote.

satisfy is the third thing it does: the witness that spends the script, which for a miniscript is a choice and not an assembly – several branches may be open at once, and which one to take is what BIP379’s non-malleable satisfaction algorithm decides. It reads the signatures a caller has and a SpendContext: the hash preimages, and the lock times the transaction being built will carry, because an older() or an after() is a branch only the right transaction opens. Non-malleable or refused, which is Bitcoin Core’s default too: a witness a third party could rewrite is worse than none.

The bounds are the same analysis read statically: max_ops, max_stack_items, max_exec_stack_items and max_witness_size answer what a spend may cost before there is a spend, and is_sane is the conjunction Bitcoin Core requires of a miniscript before it accepts a descriptor holding one. max_witness_stack is the last of those broken into its elements, which is what an estimator needs: the largest witness this can be satisfied by, element by element, with every signature assumed and every lock time taken as met. Its bytes and max_witness_size are the same number by two roads – one over the type tables, one over the satisfaction – and the test that they agree on every vector is what says neither transcription drifted.

The type system is why a fragment can be trusted to compose. Every expression has one of four basic types – “B” base, “V” verify, “K” key, “W” wrapped – and a set of properties saying how it consumes the stack, whether it can be dissatisfied, and whether a third party can rewrite a witness for it. Miniscript.properties is that set, one character per BIP379 property, and an expression whose properties are empty is one the rules refuse: parse refuses it too, naming the innermost fragment that failed, because “which fragment” is the answer a caller wants.

Two contexts, P2WSH and TAPSCRIPT, because BIP379 has two: the fragments are the same, but multi() belongs to the first and multi_a() to the second, a key is 33 bytes there and 32 here, the d: wrapper is “u” only under tapscript – MINIMALIF being consensus for taproot and policy for P2WSH – and each context bounds its own resources. The context is therefore a parameter of everything, and descriptors passes the one the position gives: P2WSH inside wsh(), TAPSCRIPT for a leaf of a tr() script tree.

Above key_expression, whose KEY expressions the fragments hold, and below descriptors, which reads a miniscript wherever a SCRIPT expression may be one. This module imports the first and not the second.

BIP379: https://github.com/bitcoin/bips/blob/master/bip-0379.md

class btclib.descriptors.miniscript.Miniscript(fragment: str, context: str = 'P2WSH', subs: tuple[Miniscript, ...] = (), keys: tuple[KeyExpression, ...] = (), threshold: int = 0, data: bytes = b'')[source]

Bases: object

A miniscript expression: a fragment, its arguments, its own type.

The fragment is the name BIP379 gives it, a wrapper keeping the colon it is written with. subs are the subexpressions; keys the KEY expressions of pk_k(), pk_h(), multi() and multi_a(); threshold the number of older(), after(), thresh(), multi() and multi_a(); and data the digest of a hash fragment. One field per kind of argument, no fragment using all four.

What is derived is computed once, when the node is built: the type properties, the script’s length, and the bounds on ops, stack and witness. Not for speed but for depth – an expression nests as deep as its script is long, so a value computed on demand would be computed by a recursion the length of the tree, where a parser building the tree bottom-up has each subexpression’s answer already.

property has_duplicate_keys: bool

Answer whether one KEY expression appears more than once.

Which BIP379’s malleability analysis assumes away: a signature made for one check of a key satisfies every other check of it, so an expression naming a key twice has satisfactions the type system does not predict. Two KEY expressions are the same where they are equal – the same text, in effect – which is the comparison Bitcoin Core’s descriptor layer makes too.

property insane_sub: Miniscript | None

Return the deepest subexpression that is not sane, or None.

The deepest, because that is the one to name: an expression is insane where one of its parts is, so the part is the answer and the whole is the symptom.

property is_non_malleable: bool

Answer whether every satisfaction can be made non-malleable.

property is_sane: bool

Answer whether the expression is safe as a script on its own.

Which adds to its parts being sane the two things only a whole script is asked: that it is a “B” of a size its context allows, and that it cannot be satisfied without a signature – without one, an attacker is free to change the nSequence and the nLockTime the timelocks were checked against, and to rewrite the witness.

property is_sane_subexpression: bool

Answer whether the expression means what it says, as a part.

property is_satisfiable: bool

Answer whether any satisfaction exists at all.

property is_signature_required: bool

Answer whether every satisfaction requires a signature.

property is_valid: bool

Answer whether the expression is typed and fits its context.

property is_valid_top_level: bool

Answer whether the expression can be a script on its own.

Which asks one thing beyond validity: the type must be “B”, a script being satisfied by what it leaves on the stack.

property is_within_resource_limits: bool

Answer whether a satisfaction is guaranteed to be spendable.

The limits that depend on the satisfaction rather than on the script: the ops of a p2wsh spend and its hundred witness elements, both standardness, and the thousand elements consensus allows on the stack of a tapscript while it runs.

property key_expressions: tuple[KeyExpression, ...]

Return every KEY expression of the tree, left to right.

property max_exec_stack_items: int | None

Return the elements the stack may hold while it runs, or None.

The bound consensus puts at a thousand, and the one a tapscript can reach without reaching any other: nothing bounds the size of a tapscript witness, so the stack during execution is what bounds the script.

property max_ops: int | None

Return the ops a satisfaction may cost, None where none exists.

The non-push op codes of the script plus the keys of every OP_CHECKMULTISIG that may be executed, which is what BIP141 counts against the 201 of a p2wsh spend.

property max_stack_items: int | None

Return the witness elements a satisfaction needs, None where none.

The initial stack of the script, which is what a p2wsh witness carries and what standardness bounds at a hundred.

property max_witness_size: int | None

Return the bytes a satisfying witness may take, None where none.

The stack elements alone: what pushes them is the witness script, and a caller counting a whole input adds it.

property max_witness_stack: tuple[int, ...] | None

Return the size of every element of the largest satisfying witness.

In witness order, the script excluded, and None where no satisfaction exists at all. What max_witness_size answers as one number, broken up: an estimator wants the elements, because it is the transaction’s layout that turns them into bytes – a count prefix and a length prefix each – and only the transaction knows that.

Estimated and not satisfied: every signature and preimage is assumed to turn up and every lock time to be met, so what comes back is the largest witness a signer could end up broadcasting rather than the one a particular caller can build now. That is what a fee wants to be computed from, and it is why this asks for nothing: a signature’s size is the context’s, not the key’s.

property mixes_timelocks: bool

Answer whether a satisfaction needs incompatible timelocks.

A height lock and a time lock of the same kind in one branch: the script is valid and that branch is unspendable, which is the expression saying something other than what it means.

satisfy(signatures: Mapping[bytes | str | bytearray | memoryview, bytes | str | bytearray | memoryview] | None = None, spend: SpendContext | None = None, index: int = 0, network: str = 'mainnet', prv_keys: Mapping[str, str] | None = None) list[bytes][source]

Return the witness elements that satisfy the expression.

In witness order, the script itself excluded: what a p2wsh spend puts in front of the witness script, or a tapscript spend in front of the script and its control block.

Non-malleable or refused, which is BIP379’s algorithm and Bitcoin Core’s default: of the stacks that would satisfy this script, the one reported is the one no third party could rewrite, and where every candidate is rewritable there is no answer – a witness that is valid and malleable is worse than none, because it is one a relay can change under the transaction that carries it. A satisfaction with no signature in it is refused for the same reason: without one, the nLockTime and the nSequence the timelocks were checked against are a third party’s to change too.

signatures maps a public key to the signature made with it, as Descriptor.satisfy takes it; spend is the rest of what a satisfaction reads. The refusal says which of the two was short, because adding to either changes the answer: a preimage or a higher sequence can turn “none” into a satisfaction, and can also turn a non-malleable one malleable, which is why the two are separate messages.

script(index: int = 0, network: str = 'mainnet', prv_keys: Mapping[str, str] | None = None) bytes[source]

Return the script of the expression, its keys derived at index.

The three parameters are KeyExpression.sec’s: an index for a ranged key, a network for the extended keys, and the private material that a hardened step needs and an xpub cannot take.

class btclib.descriptors.miniscript.SpendContext(sha256_preimages: Mapping[bytes, bytes]=<factory>, hash256_preimages: Mapping[bytes, bytes]=<factory>, ripemd160_preimages: Mapping[bytes, bytes]=<factory>, hash160_preimages: Mapping[bytes, bytes]=<factory>, locktime: int = 0, sequence: int = 0, version: int = 2)[source]

Bases: object

What a miniscript satisfaction reads beside the signatures.

The signatures are Descriptor.satisfy’s own parameter and are not here: one source of truth for them, and this is the rest of what a satisfaction may need – the preimage of a hash fragment, and the lock times the transaction being built will carry, which say whether an older() or an after() can be met at all.

The four preimage mappings are PsbtIn’s four, field for field, and keyed the same way: the digest to the bytes that hash to it – bytes and not “bytes or hex”, where the signatures of satisfy take either, because these come from a psbt rather than from a keyboard. A psbt carries them because BIP174 gave them fields, which is what lets descriptors.miniscript_solver build a context out of one.

sequence is the input’s own, locktime the transaction’s, and version matters for the same reason it matters to the interpreter: BIP68’s relative locks are enforced from version 2, so an older() in a version-1 transaction is a branch nothing can spend.

btclib.descriptors.miniscript.from_script(script: bytes | str | bytearray | memoryview, context: str = 'P2WSH', key_hashes: Mapping[bytes | str | bytearray | memoryview, bytes | str | bytearray | memoryview] | None = None) Miniscript[source]

Return the Miniscript a script is, refusing one that is not miniscript.

The other direction of Miniscript.script, and its inverse: what this returns writes back the very bytes it was read from, so a wallet handed a witness script can say what spends it. Not every script is a miniscript, and this is what answers the question – a caller asking it catches the refusal.

key_hashes maps a hash160 to the public key behind it, for the one fragment that keeps no key in the script: pk_h() and its sugared pkh() write the hash alone, so a script holding one is readable only where the key is supplied. Bitcoin Core asks a signing provider the same question, and the answer is held to it: a key filed under a hash that is not its own is refused rather than read, that being the one input for which the inverse above did not hold.

reads_back is this question asked without the refusal, for a caller that holds a script and wants to know whether any language reads it.

btclib.descriptors.miniscript.parse(expression: str, context: str = 'P2WSH', prv_keys: dict[str, str] | None = None) Miniscript[source]

Return the Miniscript of a BIP379 expression, in its context.

Refused where the type system refuses it, naming the innermost fragment that failed, and refused where the top-level expression is not a “B” of a size the context allows: a miniscript that is not both is not a script, and every caller of this wants a script.

prv_keys is descriptors.parse’s: the mapping an extended private key is filed in, under the extended public key that replaces it, so that what a parsed expression holds is public.

A str, a BIP379 expression being text, and refused as a type for the reason descriptors.parse gives: what was neither reached the slicing below and left as “object of type X has no len()”.

The context is one of the two BIP379 has, and prv_keys a mapping or None, as descriptors.parse asks for the same pair: a context no fragment table knows was compared against TAPSCRIPT, found unequal, and every rule then read as the p2wsh one, so an expression was type-checked under a context that does not exist.

btclib.descriptors.miniscript.reads_back(script: bytes | str | bytearray | memoryview, context: str = 'P2WSH', key_hashes: Mapping[bytes | str | bytearray | memoryview, bytes | str | bytearray | memoryview] | None = None) bool[source]

Whether a script is the miniscript it reads as.

The round trip as a question: the script is read back into an expression and the expression writes a script, and the answer is whether those are the same bytes. What it is asked about is a script somebody else wrote – a witness script off a psbt, the pre-image a wallet computes – where “this is a 2-of-3 with a timelock” is an intention, and reading it back is the only thing that says the bytes agree with it. A script that is well formed by accident hashes to a perfectly good address, and nothing else notices.

False is the answer wherever no language reads the script: not every script is a miniscript – an OP_DROP where nothing drops, a quorum whose count does not match its keys – and a caller wanting to know what is wrong with it calls from_script and reads the refusal.

Written as the round trip rather than as “from_script accepted it”, which is what it comes to today: the decoder refuses every second spelling of one expression – a non-minimal push, a number with a byte to spare, a VERIFY written as two op codes – and a key answered for the wrong hash, so what it accepts writes itself back. That is the claim, and this is the proof of it rather than a restatement.

Module contents

Output descriptors: the checksum, the parser, the scripts, the spend.

The flat surface is descriptors’: parse and what a parsed descriptor answers. Three modules make it up, and each imports the ones before it and none after:

  • key_expression is BIP380’s KEY expressions, the public keys a descriptor names and how they derive;

  • miniscript is BIP379’s language, which is the SCRIPT expressions written as a tree of fragments rather than as a function, and the non-malleable witness that satisfies one;

  • descriptors is the rest of BIP380 to BIP390: the checksum, the functions, the scripts each describes, and the psbt each fills.

miniscript is named beside the flat surface because a caller reaches it by name – miniscript.parse, miniscript.from_script and the Miniscript a MiniscriptDescriptor holds are its own interface, not the descriptor one – while key_expression is not: KeyExpression and PrvKeys are re-exported below, being names a caller reads off a parsed descriptor.

class btclib.descriptors.AddrDescriptor(addr: str, *, network: str = 'mainnet')[source]

Bases: Descriptor

addr(ADDR): the script the address expands to, BIP385.

property key_expressions: tuple[KeyExpression, ...]

Return no KEY expression, the descriptor fixing none.

class btclib.descriptors.ComboDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]

Bases: Descriptor

combo(KEY): the scripts an old wallet would have used, BIP384.

p2pk and p2pkh, plus p2wpkh and p2sh-p2wpkh when the key is compressed – an uncompressed key is not allowed in a witness program.

property key_expressions: tuple[KeyExpression, ...]

Return the one KEY expression, as the base class’s tuple.

class btclib.descriptors.Descriptor(*, network: str = 'mainnet')[source]

Bases: ABC

A parsed output descriptor: the scripts it describes, on demand.

Keyword-only so that the fragments below can add positional fields of their own: a dataclass field with a default followed by one without is a TypeError, and network has a default.

address(index: int = 0, prv_keys: Mapping[str, str] | None = None) str[source]

Return the address of the script at index, if it has one.

addresses(index: int = 0, prv_keys: Mapping[str, str] | None = None) list[str][source]

Return the address of each script at index, empty where none.

index_of(script_pub_key: bytes | str | bytearray | memoryview | ScriptPubKey, last_index: int = 999, prv_keys: Mapping[str, str] | None = None) int | None[source]

Return the index whose script is this one, None where none is.

What makes an output this wallet’s, and the only thing that does: the script is derived and compared whole. A key origin whose fingerprint matches is not an answer – four bytes of a hash160 collide, and a psbt is written by whoever sends it, so an output marked as change on a fingerprint is an output a wallet may hand to somebody else believing it keeps it.

The output is named however the caller holds it: the ScriptPubKey that script_pub_key returns, that script as bytes or as a hex-string, or the address it renders as – “which index is this address” being the question a human has, and ScriptPubKey.from_address being what answers it. What is compared is the script in every case: an address is read for the script it encodes, and the network its prefix carries is not part of the answer, the same key paying to the same script on every chain.

Anything else is a BTClibTypeError, and a string that names no output – neither hex nor an address, the “” that a script with no address renders as among them – a BTClibValueError, because None is not “you passed the wrong thing” here: it is this output is not this wallet’s, which is the answer a caller acts on to say that an address is somebody else’s or that an output is not its own change (issue #540).

last_index bounds the search, both ends included, and is the caller’s: how far ahead of its own gap limit a wallet is willing to look is a policy this module has no view on. A descriptor that is not ranged has one script and answers 0 or None.

property is_ranged: bool

Return True if the descriptor describes a range of scripts.

abstract property key_expressions: tuple[KeyExpression, ...]

Return every KEY expression the descriptor holds.

redeem_script(index: int = 0, prv_keys: Mapping[str, str] | None = None) bytes[source]

Return the script that sh() or wsh() embeds this one as.

satisfy(signatures: Mapping[bytes | str | bytearray | memoryview, bytes | str | bytearray | memoryview], index: int = 0, prv_keys: Mapping[str, str] | None = None, spend: SpendContext | None = None) tuple[bytes, Witness][source]

Return the script_sig and witness that spend the script at index.

signatures maps a public key to the signature made with it, which is the shape psbt.PsbtIn.partial_sigs has. Keyed by key and not a sequence because the order the signatures go on the stack is the descriptor’s own knowledge – the key order of a multi(), the sorted order of a sortedmulti() – and a caller that had to know it would be building the script itself.

Both halves come back and one of the two is always empty: a legacy script has no witness, and a native segwit one has the empty script_sig BIP141 requires.

A signature short of what the script pops is an error and not a shorter answer. A 2-of-3 holding one signature is a psbt waiting for the second, psbt.PsbtIn.partial_sigs is where that state belongs, and bytes that do not spend would be a second and weaker spelling of it.

spend is what a miniscript satisfaction reads beside the signatures – hash preimages, and the lock times the transaction being built will carry – and is ignored by every other fragment, none of which has a branch to choose or a preimage to look up. A wsh() holding a miniscript is the one shape that needs it, and it says so: without one it refuses the fragments that would have read it.

script_pub_key(index: int = 0, prv_keys: Mapping[str, str] | None = None) ScriptPubKey[source]

Return the one script the descriptor describes at index.

script_pub_keys(index: int = 0, prv_keys: Mapping[str, str] | None = None) list[ScriptPubKey][source]

Return the scripts the descriptor describes at index.

A list because combo() is a set of scripts and not one script; every other fragment answers with exactly one.

update_psbt_input(psbt: Psbt, vin_i: int, index: int = 0, prv_keys: Mapping[str, str] | None = None) Psbt[source]

Return the psbt with input vin_i told what the descriptor knows.

BIP174’s Updater, for the one input this descriptor describes: the redeem script of a sh(), the witness script of a wsh(), the internal key, merkle root and leaf scripts of a tr(), and the origin of every key that carries one – which is what a hardware signer needs, and what KeyExpression.origin is kept for. psbt.finalize then assembles the same bytes satisfy does, from the signatures the signers filled in at their own pace: that pipeline is what a psbt is for, and what satisfy cannot answer, refusing a partial satisfaction rather than returning bytes that do not spend.

A copy, the psbt handed in being left alone, and the fields of the copy mutated in place: finalize is the same construction, and BIP174’s roles read as steps that update a psbt rather than as functions that return a field at a time.

What is not filled is what a descriptor does not know: the utxo, the sighash type, the signatures. Nor is the script checked against the output being spent – an input may not carry it yet, and Psbt.assert_signable asks that question for every input at once, being the role after this one.

update_psbt_output(psbt: Psbt, vout_i: int, index: int = 0, prv_keys: Mapping[str, str] | None = None) Psbt[source]

Return the psbt with output vout_i told what the descriptor knows.

The Updater’s other half, and what makes an output recognizable as the wallet’s own: the redeem script of a sh(), the witness script of a wsh(), the internal key and the whole script tree of a tr(), and the origin of every key that carries one. A signing device reads them to tell change from a payment – it can derive the script itself and see that the money comes back – and a wallet reading a psbt somebody else built reads them for the same reason.

Unlike the input half, the script is checked: the output being paid is in the psbt already, so this refuses unless the descriptor derives exactly that script at index. Marking an output as one’s own is a claim about where money goes, and the only evidence for it is the whole script – never a key origin whose four-byte fingerprint matches, which is what index_of is for and what it says.

The output tree is BIP371’s PSBT_OUT_TAP_TREE and not the leaf script an input carries: an output has no leaf being spent, so what it publishes is every leaf, each with its depth, which is what lets a reader rebuild the tree and check the output key for itself.

class btclib.descriptors.KeyExpression(origin: BIP32KeyOrigin | None = None, pub_key: bytes | None = None, xkey: str = '', der_path: tuple[int, ...] = (), wildcard: int | None = None, x_only: bool = False, participants: tuple[KeyExpression, ...] = (), hardening: str = 'h')[source]

Bases: object

A BIP380 KEY expression: an origin, a key, a derivation path.

One of three things is the key. Either pub_key is the one the descriptor fixes, in SEC bytes; or xkey is the extended key that der_path and wildcard derive from; or participants are the KEY expressions BIP390’s musig() aggregates, der_path and wildcard then deriving from the aggregate key as BIP328 prescribes. An x-only key is held as its even-y SEC form, which is what BIP340 says those 32 bytes mean, so that everything downstream sees one representation of a public key.

origin never changes the script. It says which master key and which path the key came from, which is what a hardware signer needs and what BIP174 carries in a PSBT.

xkey is public whatever the descriptor spelled: parse neuters an xprv and hands the private material back to its caller, so no part of a parsed descriptor holds a key that signs. What that costs is a hardened step, which an xpub cannot take – sec takes the keys back as a parameter for it, the way Bitcoin Core’s expansion takes a SigningProvider.

aggregate(index: int = 0, network: str = 'mainnet', prv_keys: Mapping[str, str] | None = None) bytes[source]

Return the key BIP390’s participants aggregate to, derived.

KeyAgg over the sorted participants, and nothing more: the /NUM/.../* path of the expression derives from this key, and sec is what walks it. The two are separate because BIP373 keys its MuSig2 psbt fields by the aggregate key itself even where what the script holds is a child of it – an aggregate key is what the participants make, and a derivation of it is a key the same group answers for.

property is_aggregate: bool

Answer whether the key is the aggregate of BIP390 participants.

property is_compressed: bool

Return False for an uncompressed SEC public key, True otherwise.

An extended key is compressed by construction: BIP32 has no uncompressed serialization.

property is_ranged: bool

Answer whether the key has a wildcard to derive at an index.

A musig() is ranged where its own path has one and where a participant has one: BIP390 forbids both at once, so whichever it is, the index is read in one place.

participant_keys(index: int = 0, network: str = 'mainnet', prv_keys: Mapping[str, str] | None = None) list[bytes][source]

Return the participant keys in the order they are aggregated in.

Which is KeySort’s order and not the descriptor’s: BIP390 sorts after all derivation and before aggregation, so that the order the keys were written in does not change the key – a set of keys is what MuSig2 is about, and a descriptor that was not backed up does not need the order guessed as well. BIP373 stores the participants of a psbt in aggregation order too, which is what makes this the list that goes into PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS.

sec(index: int = 0, network: str = 'mainnet', prv_keys: Mapping[str, str] | None = None) bytes[source]

Return the SEC public key bytes, derived at index if ranged.

prv_keys is what parse handed back, and is needed for a hardened step and for nothing else: an unhardened path derives from the xpub the descriptor holds. A key it does not name is left as it is, so a mapping covering some of a multisig’s keys answers for those and no more.

A musig() answers with the key its participants aggregate to, derived along its own path where it has one: BIP328’s synthetic xpub is that key at depth zero with the fixed chain code an aggregate has instead of one of its own, and derive_ is then what refuses a hardened step – there being no aggregate private key to take one with. The index derives the participants or the aggregate and never both, BIP390 allowing a wildcard on one side only.

class btclib.descriptors.Miniscript(fragment: str, context: str = 'P2WSH', subs: tuple[Miniscript, ...] = (), keys: tuple[KeyExpression, ...] = (), threshold: int = 0, data: bytes = b'')[source]

Bases: object

A miniscript expression: a fragment, its arguments, its own type.

The fragment is the name BIP379 gives it, a wrapper keeping the colon it is written with. subs are the subexpressions; keys the KEY expressions of pk_k(), pk_h(), multi() and multi_a(); threshold the number of older(), after(), thresh(), multi() and multi_a(); and data the digest of a hash fragment. One field per kind of argument, no fragment using all four.

What is derived is computed once, when the node is built: the type properties, the script’s length, and the bounds on ops, stack and witness. Not for speed but for depth – an expression nests as deep as its script is long, so a value computed on demand would be computed by a recursion the length of the tree, where a parser building the tree bottom-up has each subexpression’s answer already.

property has_duplicate_keys: bool

Answer whether one KEY expression appears more than once.

Which BIP379’s malleability analysis assumes away: a signature made for one check of a key satisfies every other check of it, so an expression naming a key twice has satisfactions the type system does not predict. Two KEY expressions are the same where they are equal – the same text, in effect – which is the comparison Bitcoin Core’s descriptor layer makes too.

property insane_sub: Miniscript | None

Return the deepest subexpression that is not sane, or None.

The deepest, because that is the one to name: an expression is insane where one of its parts is, so the part is the answer and the whole is the symptom.

property is_non_malleable: bool

Answer whether every satisfaction can be made non-malleable.

property is_sane: bool

Answer whether the expression is safe as a script on its own.

Which adds to its parts being sane the two things only a whole script is asked: that it is a “B” of a size its context allows, and that it cannot be satisfied without a signature – without one, an attacker is free to change the nSequence and the nLockTime the timelocks were checked against, and to rewrite the witness.

property is_sane_subexpression: bool

Answer whether the expression means what it says, as a part.

property is_satisfiable: bool

Answer whether any satisfaction exists at all.

property is_signature_required: bool

Answer whether every satisfaction requires a signature.

property is_valid: bool

Answer whether the expression is typed and fits its context.

property is_valid_top_level: bool

Answer whether the expression can be a script on its own.

Which asks one thing beyond validity: the type must be “B”, a script being satisfied by what it leaves on the stack.

property is_within_resource_limits: bool

Answer whether a satisfaction is guaranteed to be spendable.

The limits that depend on the satisfaction rather than on the script: the ops of a p2wsh spend and its hundred witness elements, both standardness, and the thousand elements consensus allows on the stack of a tapscript while it runs.

property key_expressions: tuple[KeyExpression, ...]

Return every KEY expression of the tree, left to right.

property max_exec_stack_items: int | None

Return the elements the stack may hold while it runs, or None.

The bound consensus puts at a thousand, and the one a tapscript can reach without reaching any other: nothing bounds the size of a tapscript witness, so the stack during execution is what bounds the script.

property max_ops: int | None

Return the ops a satisfaction may cost, None where none exists.

The non-push op codes of the script plus the keys of every OP_CHECKMULTISIG that may be executed, which is what BIP141 counts against the 201 of a p2wsh spend.

property max_stack_items: int | None

Return the witness elements a satisfaction needs, None where none.

The initial stack of the script, which is what a p2wsh witness carries and what standardness bounds at a hundred.

property max_witness_size: int | None

Return the bytes a satisfying witness may take, None where none.

The stack elements alone: what pushes them is the witness script, and a caller counting a whole input adds it.

property max_witness_stack: tuple[int, ...] | None

Return the size of every element of the largest satisfying witness.

In witness order, the script excluded, and None where no satisfaction exists at all. What max_witness_size answers as one number, broken up: an estimator wants the elements, because it is the transaction’s layout that turns them into bytes – a count prefix and a length prefix each – and only the transaction knows that.

Estimated and not satisfied: every signature and preimage is assumed to turn up and every lock time to be met, so what comes back is the largest witness a signer could end up broadcasting rather than the one a particular caller can build now. That is what a fee wants to be computed from, and it is why this asks for nothing: a signature’s size is the context’s, not the key’s.

property mixes_timelocks: bool

Answer whether a satisfaction needs incompatible timelocks.

A height lock and a time lock of the same kind in one branch: the script is valid and that branch is unspendable, which is the expression saying something other than what it means.

satisfy(signatures: Mapping[bytes | str | bytearray | memoryview, bytes | str | bytearray | memoryview] | None = None, spend: SpendContext | None = None, index: int = 0, network: str = 'mainnet', prv_keys: Mapping[str, str] | None = None) list[bytes][source]

Return the witness elements that satisfy the expression.

In witness order, the script itself excluded: what a p2wsh spend puts in front of the witness script, or a tapscript spend in front of the script and its control block.

Non-malleable or refused, which is BIP379’s algorithm and Bitcoin Core’s default: of the stacks that would satisfy this script, the one reported is the one no third party could rewrite, and where every candidate is rewritable there is no answer – a witness that is valid and malleable is worse than none, because it is one a relay can change under the transaction that carries it. A satisfaction with no signature in it is refused for the same reason: without one, the nLockTime and the nSequence the timelocks were checked against are a third party’s to change too.

signatures maps a public key to the signature made with it, as Descriptor.satisfy takes it; spend is the rest of what a satisfaction reads. The refusal says which of the two was short, because adding to either changes the answer: a preimage or a higher sequence can turn “none” into a satisfaction, and can also turn a non-malleable one malleable, which is why the two are separate messages.

script(index: int = 0, network: str = 'mainnet', prv_keys: Mapping[str, str] | None = None) bytes[source]

Return the script of the expression, its keys derived at index.

The three parameters are KeyExpression.sec’s: an index for a ranged key, a network for the extended keys, and the private material that a hardened step needs and an xpub cannot take.

class btclib.descriptors.MiniscriptDescriptor(node: Miniscript, *, network: str = 'mainnet')[source]

Bases: Descriptor

A BIP379 miniscript where a SCRIPT expression may be one.

Which is inside wsh(): a miniscript inside tr() is a leaf of the script tree and not a SCRIPT expression, so DescriptorTree holds that one directly, the way it holds a multi_a(). What this holds is the Miniscript, whose own interface – the type properties, the resource bounds, the script both ways, the satisfaction – is btclib.descriptors.miniscript’s.

A fragment like the others in what it answers: the script at an index, the KEY expressions it names, and the psbt fields those keys fill. Unlike the others it does not satisfy: a miniscript satisfaction needs more than a mapping of public keys to signatures, so satisfy refuses and says so.

property key_expressions: tuple[KeyExpression, ...]

Return the KEY expressions of the fragments, left to right.

class btclib.descriptors.MultiA(threshold: int, keys: tuple[KeyExpression, ...], sort: bool = False)[source]

Bases: object

multi_a(k,KEY,...) or sortedmulti_a(k,KEY,...): a leaf, BIP387.

A leaf of a tr() script tree, beside the bare KeyExpression that is a pk() leaf, and not a Descriptor: BIP387 allows these two functions inside tr() and nowhere else, so no output pays to one of them – what an output pays to is the tr() that commits to it as one of its tapscripts.

class btclib.descriptors.MultiDescriptor(threshold: int, keys: tuple[KeyExpression, ...], sort: bool = False, *, network: str = 'mainnet')[source]

Bases: Descriptor

multi(k,KEY,...) and sortedmulti(k,KEY,...), BIP383.

property key_expressions: tuple[KeyExpression, ...]

Return the KEY expressions, in descriptor order.

class btclib.descriptors.PkDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]

Bases: Descriptor

pk(KEY): a P2PK output, BIP381.

property key_expressions: tuple[KeyExpression, ...]

Return the one KEY expression, as the base class’s tuple.

class btclib.descriptors.PkhDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]

Bases: Descriptor

pkh(KEY): a p2pkh output, BIP381.

property key_expressions: tuple[KeyExpression, ...]

Return the one KEY expression, as the base class’s tuple.

class btclib.descriptors.RawDescriptor(script: bytes, *, network: str = 'mainnet')[source]

Bases: Descriptor

raw(HEX): the script those bytes are, BIP385.

property key_expressions: tuple[KeyExpression, ...]

Return no KEY expression, the descriptor fixing none.

class btclib.descriptors.RawTrDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]

Bases: Descriptor

rawtr(KEY): the key as the output key itself, no tweak at all.

No BIP specifies this function. BIP386 specifies tr(), the tree expression and the x-only key inside them and never mentions rawtr(); what defines it is Bitcoin Core’s own doc/descriptors.md, which also carries the warning this docstring keeps: an output key whose internal key nobody knows cannot be shown to have no hidden script path, so a rawtr() describes an output a wallet already holds rather than one to build.

The key is BIP341’s output key, written into OP_1 <32 bytes> as it is. That is the whole difference from tr(KEY), which tweaks its internal key with an empty merkle root, and it is why this is not a TrDescriptor carrying tree=None.

property key_expressions: tuple[KeyExpression, ...]

Return the one KEY expression, as the base class’s tuple.

class btclib.descriptors.ShDescriptor(inner: Descriptor, *, network: str = 'mainnet')[source]

Bases: Descriptor

sh(SCRIPT): the argument, p2sh-embedded, BIP381.

property key_expressions: tuple[KeyExpression, ...]

Return the wrapped SCRIPT’s KEY expressions.

class btclib.descriptors.SpendContext(sha256_preimages: Mapping[bytes, bytes]=<factory>, hash256_preimages: Mapping[bytes, bytes]=<factory>, ripemd160_preimages: Mapping[bytes, bytes]=<factory>, hash160_preimages: Mapping[bytes, bytes]=<factory>, locktime: int = 0, sequence: int = 0, version: int = 2)[source]

Bases: object

What a miniscript satisfaction reads beside the signatures.

The signatures are Descriptor.satisfy’s own parameter and are not here: one source of truth for them, and this is the rest of what a satisfaction may need – the preimage of a hash fragment, and the lock times the transaction being built will carry, which say whether an older() or an after() can be met at all.

The four preimage mappings are PsbtIn’s four, field for field, and keyed the same way: the digest to the bytes that hash to it – bytes and not “bytes or hex”, where the signatures of satisfy take either, because these come from a psbt rather than from a keyboard. A psbt carries them because BIP174 gave them fields, which is what lets descriptors.miniscript_solver build a context out of one.

sequence is the input’s own, locktime the transaction’s, and version matters for the same reason it matters to the interpreter: BIP68’s relative locks are enforced from version 2, so an older() in a version-1 transaction is a branch nothing can spend.

class btclib.descriptors.TrDescriptor(internal_key: KeyExpression, tree: KeyExpression | MultiA | Miniscript | tuple[KeyExpression | MultiA | Miniscript | tuple[DescriptorTree, DescriptorTree], KeyExpression | MultiA | Miniscript | tuple[DescriptorTree, DescriptorTree]] | None = None, *, network: str = 'mainnet')[source]

Bases: Descriptor

tr(KEY) or tr(KEY,TREE): a p2tr output, BIP386.

property key_expressions: tuple[KeyExpression, ...]

Return the internal key and every leaf key, in tree order.

taproot_leaf_scripts(index: int = 0, prv_keys: Mapping[str, str] | None = None) dict[bytes, tuple[bytes, int]][source]

Return every leaf script and its version, keyed by control block.

The shape of BIP371’s PSBT_IN_TAP_LEAF_SCRIPT, and the field an Updater is needed for rather than convenient: a control block holds the merkle path from its leaf to the root, which is the whole tree seen from that leaf, and a psbt carrying one leaf’s script has no way to compute another’s.

taproot_merkle_root(index: int = 0, prv_keys: Mapping[str, str] | None = None) bytes[source]

Return the root the output key commits to, b”” where there is none.

BIP371’s PSBT_IN_TAP_MERKLE_ROOT, and empty is how that field says “key path only”: a tr(KEY) tweaks its internal key with no tree, which is not the same as tweaking it with an empty one.

taproot_tree(index: int = 0, prv_keys: Mapping[str, str] | None = None) list[tuple[int, int, bytes]][source]

Return every leaf with its depth: BIP371’s PSBT_OUT_TAP_TREE.

The whole tree, where an input publishes the one leaf it spends and the control block that proves it: an output has no leaf being spent yet, so what it carries is each script with the depth it sits at, in the order a depth-first walk reaches them. A reader rebuilds the tree from those two facts and can then check the output key itself – which is why the field is the tree and not the merkle root, a root proving nothing about the scripts underneath it.

Empty for a tr(KEY), whose output key commits to no script at all, and which BIP371 says so about by leaving the field out.

class btclib.descriptors.WpkhDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]

Bases: Descriptor

wpkh(KEY): a p2wpkh output, BIP382.

property key_expressions: tuple[KeyExpression, ...]

Return the one KEY expression, as the base class’s tuple.

class btclib.descriptors.WshDescriptor(inner: Descriptor, *, network: str = 'mainnet')[source]

Bases: Descriptor

wsh(SCRIPT): the argument, P2WSH-embedded, BIP382.

property key_expressions: tuple[KeyExpression, ...]

Return the wrapped SCRIPT’s KEY expressions.

btclib.descriptors.account_descriptors(xkey: BIP32KeyData | bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview, master_fingerprint: bytes | str | bytearray | memoryview | None = None, script_type: Literal['p2pkh', 'p2wpkh-p2sh', 'p2wpkh', 'p2tr'] | None = None) tuple[Descriptor, Descriptor][source]

Return the receive and change descriptors of a BIP44 account.

der_path is the three-level account path, m/purpose’/coin_type’/account’, in any spelling bip32.derive accepts; xkey is the extended key it starts from, which may be the master key or any key already partway down it – the account xpub itself, typically, which is what a wallet exports and what a hardware signer answers with.

The purpose selects the encoding, as it does for a BIP44 address: 44 is pkh(), 49 sh(wpkh()), 84 wpkh(), 86 tr(). A purpose outside bip44.SCRIPT_TYPE_FROM_PURPOSE raises unless script_type names one of those four, which then overrides the mapping for known purposes too. The network is the extended key’s own, and the coin type has to agree with it.

Both chains come back because a wallet is both: BIP44 puts receiving addresses under /0 and change under /1, and a wallet that imported one and not the other cannot recognize its own change – which is a lost output rather than a missing feature. They differ in that step and in nothing else, the key origin and the wildcard being the same.

The master fingerprint is what the key origin needs and what an extended key below the root cannot supply, so it is a parameter; where xkey is the master key it is computed, and a value handed in beside it has to match.

add_checksum(str(descriptor)) is the text Bitcoin Core takes. The BIP389 spelling of the pair – one descriptor with a <0;1> step – is text and not a parsed descriptor: multipath_descriptors expands one and parse refuses one, so what this returns is the two.

btclib.descriptors.add_checksum(descriptor: str) str[source]

Return the descriptor with its checksum, verifying a present one.

btclib.descriptors.at_index(descriptor: Descriptor, index: int = 0) Descriptor[source]

Return the descriptor of one index, with no wildcard left in it.

A ranged descriptor describes a range of scripts and names none of them; this names one, by writing the index the wildcard stands for into the derivation path – .../0/* at index 5 becomes .../0/5. The scripts are the same scripts: what changes is that the answer is a descriptor of one, which is what a reader wanting this script has to be given, an external signer displaying an address among them.

Bitcoin Core’s deriveaddresses is the same operation with the address as its answer rather than the descriptor.

A descriptor with no wildcard comes back unchanged, index 0 being the only index it has; the participants of a musig() are walked too, the range being on either side of the aggregation and never on both.

btclib.descriptors.checksum(descriptor: str) str[source]

Compute the descriptor checksum.

btclib.descriptors.from_address(address: str) str[source]

Return the addr() descriptor of the address, checksummed.

btclib.descriptors.miniscript_sizer(psbt_in: PsbtIn, tx_in: TxIn) list[int] | None[source]

Size the spend of a psbt input whose witness script is a miniscript.

A SolutionSizer, which is what psbt_size.estimated_input_sizes takes for the inputs whose spend it cannot read – and it lives here for miniscript_solver’s reason, the layering: descriptors imports psbt and nothing there imports back. A caller passes it:

script_sig, witness = estimated_input_sizes(
    psbt_in, tx_in, sizer=miniscript_sizer
)

The witness of a p2wsh spend is the satisfaction and then the witness script, so that is the list: Miniscript.max_witness_stack and the script’s own length. It needs no signature and no preimage, an estimate being made before either exists – the size of a signature is the context’s, and BIP379 fixes a preimage at 32 bytes.

None where the input is not this sizer’s business: no witness script, one that is no miniscript, or one no witness can satisfy at all. The caller then answers as it did before, which for the last of those is a refusal: a script nobody can spend has no spend to estimate.

btclib.descriptors.miniscript_solver(psbt: Psbt, vin_i: int) tuple[bytes, Witness] | None[source]

Spend a psbt input whose witness script is a miniscript.

An InputSolver, which is what psbt.finalize takes for the inputs whose spend is the caller’s to know – and a miniscript is one of them, for a reason of layering rather than of design: descriptors imports psbt and nothing there imports back, so the finalizer cannot reach the language that reads its witness script. Passing this is what closes that circle from the outside:

final = psbt.finalize(unsigned, solver=miniscript_solver)

Everything it needs is in the psbt. The witness script is read back into the expression it is – which is what miniscript.from_script is for, and what makes a signer able to spend a script nobody handed it a descriptor for – and the satisfaction reads the input’s own fields: the signatures of partial_sigs, the four preimage mappings BIP174 gave fields to, and the lock times of the transaction the psbt is building. A pk_h() names its key by a hash160, so the keys the input knows are offered for that lookup.

None where the input is not this solver’s business: no witness script, or one that is not a miniscript, both of which finalize then answers for as it did before. Where the script is a miniscript and the signatures do not satisfy it, the refusal is the satisfier’s and says so – a witness built from a guess would be worse, being one the network refuses after the transaction is broadcast rather than one this refuses while it is built.

btclib.descriptors.multipath_descriptors(descriptor: str) list[str][source]

Return the single-path descriptors of a BIP389 multipath one.

A descriptor with no <a;b> step is one descriptor, returned checksummed and otherwise unchanged. One with such steps is as many descriptors as a step has elements, the first taking the first element of every step, the second the second, and so on – which is what makes the two-element form a receiving chain and a change chain.

The expansion is textual, as BIP389 defines it, and each result is a descriptor to be parsed on its own.

btclib.descriptors.normalized(descriptor: Descriptor, prv_keys: Mapping[str, str] | None = None) Descriptor[source]

Return the descriptor with the xpub at each last hardened step.

Bitcoin Core’s ToNormalizedString, which is what getdescriptorinfo answers with and what an export to a watch-only wallet wants: every key of it derives from an xpub, so a holder of no private key can compute every script the descriptor describes.

The private material is the caller’s, as everywhere else here, and is needed for exactly the keys that have a hardened step to re-root at. Where one of those is missing this raises rather than handing back a descriptor that quietly still needs a key.

The hardening symbol is h throughout the result, whichever was read: a normalized descriptor is a canonical spelling and not the one that came in, which is Core’s rule – “always use h for hardened derivation” is how its own interface states it.

btclib.descriptors.parse(descriptor: str, network: str = 'mainnet', prv_keys: dict[str, str] | None = None) Descriptor[source]

Return the Descriptor of a descriptor string, checksum verified.

A str, an output descriptor being text: what was neither reached strip_checksum untouched and left as an AttributeError about partition, or as a TypeError about mixing bytes and str – neither of them a word about the descriptor that was passed.

The network is asked for here rather than where a script is finally written: a name no network has was carried into the Descriptor and refused by the encoder that came to use it, which is a complaint about a key or an address, one call later than the argument that was wrong. Normalized as well, so that what the object holds is the name network_from_name would answer to.

prv_keys is a mapping, and what is not one was walked anyway: the lookups below are in and [], so a list of pairs answered “not found” for every key rather than saying it is not a mapping. None is asked for with it, that being the other type the annotation declares – and one call rather than a branch, which is what keeps the two spellings of “a declared type” in one place.

btclib.descriptors.satisfaction_sizer(keys: Iterable[bytes | str | bytearray | memoryview]) Callable[[PsbtIn, TxIn], list[int] | None][source]

Return a SolutionSizer for the satisfaction these keys will build.

miniscript_sizer answers “how large could this get”, every branch open and every signature assumed – right where a caller does not yet know which branch a spend will take, and an overpay where it does. A quorum with a timelocked recovery quorum behind the same address is exactly that second caller: which of the two a transaction takes is decided before anything is estimated, the recovery spend being a separate operation with its own inputs and its own signatures.

Miniscript.satisfy already answers the narrower question – given which keys will sign, which branch and how large – so this is that answer as a SolutionSizer, beside miniscript_sizer and not a mode of it:

witness = estimated_input_sizes(
    psbt_in, tx_in, sizer=satisfaction_sizer(recovery_keys)
)

satisfy never checks a signature against anything, so a filler one of psbt_size.SIG_SIZE bytes for each key does exactly what a real one would for sizing, and none of them has to exist yet, any more than the ones miniscript_sizer assumes do. The preimages are the psbt input’s own, and the sequence is the input being sized, so an older() branch is measured against what this spend actually carries. An after() branch is not, locktime being the transaction’s and not this call’s to read – it is answered as unmet, and a script whose only open branch needs one refuses rather than guesses.

None for that refusal and the two miniscript_sizer already answers with it: no witness script, or one that is no miniscript. A caller then falls back exactly as it would for any other sizer answering “not mine”.

btclib.descriptors.strip_checksum(descriptor: str) str[source]

Return the descriptor without its checksum, verifying a present one.

A descriptor without one comes back unchanged: the checksum is optional in the language, and only some of Bitcoin Core’s RPCs require it. What is never accepted is a checksum that does not match, which is what the eight characters are there for.

btclib.descriptors.wallet_policy(descriptor: Descriptor, change: Descriptor | None = None) tuple[str, tuple[KeyExpression, ...]][source]

Return the BIP388 wallet-policy template of a Descriptor, and its keys.

Every KEY expression is written as its @N placeholder, in the order it is first read – BIP388’s own pair, a wallet descriptor template and the key information vector it indexes into, which is what an external signer’s registration flow reads and what wallet_policy_descriptor reads back.

change is the account’s change descriptor, descriptor its receive one – account_descriptors’ own pair, or two built by hand the same way. Given one, this writes BIP388’s own /<M;N>/* (/** where the pair is <0;1>), the shorthand its Test Vectors use throughout: descriptor and change must share every function, threshold and tree shape (_skeleton is the check), and must differ, key by key, in nothing but one explicit, unhardened step in front of the wildcard – BIP389’s own two-element multipath, read back out of two already single-path descriptors rather than out of the <a;b> text multipath_descriptors expands away before either one is parsed.

Left at its default, change is None and descriptor is read alone: the only placeholder this writes then is the plain, unhardened /* BIP388’s “Optional derivation paths” section allows as an implementation-specific pattern, a Descriptor holding one derivation path and /<M;N>/* needing two. What cannot be written either way is refused rather than silently dropped: a fixed public key, a hardened or explicit step before the wildcard beyond the one unhardened chain digit change accounts for, a musig() with derivation on its participants, receive and change disagreeing on a key or on the shape around it, and a key or a musig() group written more than once with a chain digit that is not disjoint from an earlier use – each with the fragment that was wrong. So is a descriptor whose own function is not one of BIP388’s SCRIPT expressions – combo(), addr(), raw(), rawtr() and a bare pk() among them. Not checked: BIP388’s position for a function within another (_assert_policy_top_level says so), and that every key is pairwise distinct from every other.

btclib.descriptors.wallet_policy_address(template: str, key_info: Sequence[KeyExpression], index: int = 0, multipath_index: int = 0, network: str = 'mainnet') str[source]

Return the address a BIP388 wallet policy describes at index.

wallet_policy_descriptor resolved at multipath_index, then Descriptor.address at index – the derivation and the address encoding are answered by the descriptor code every other caller of this module uses, a wallet policy being a second way to name one of its descriptors and not a second way to compute one.

btclib.descriptors.wallet_policy_descriptor(template: str, key_info: Sequence[KeyExpression], multipath_index: int = 0, network: str = 'mainnet') Descriptor[source]

Return the ranged Descriptor a BIP388 wallet-policy template describes.

template and key_info are the pair wallet_policy returns, or the same pair read off any other source of them – BIP388’s own test vectors, or an external signer’s registration flow. Every placeholder is replaced with its key-information text, and every trailing wildcard is resolved at multipath_index: BIP389’s /<M;N>/* positions M at index 0 and N at index 1, in that order and never by which digit is the smaller, and /** is /<0;1>/* by definition; the plain /* BIP388 also allows takes neither branch and reads the same descriptor whatever multipath_index is. The result still holds the index-selecting wildcard: Descriptor.address and the rest of what a ranged descriptor answers are what multipath_index was resolved for.

No prv_keys: every wildcard a wallet-policy template writes is unhardened, which is the whole of BIP388’s “the seed alone is no longer enough” guarantee working in reverse – the account xpub in key_info computes every script the policy describes, hardened step or not.