btclib_wallet.descriptors package¶
Submodules¶
btclib_wallet.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.
expansion is the third answer, and the one for a spend the signers do not make all at once: the scripts and the key origins BIP174’s Updater writes into a psbt, for signers to fill in at their own pace and psbt.finalize to assemble. psbt.update_psbt_input and psbt.update_psbt_output are that Updater, and read the Expansion this returns. This module imports nothing of psbt, 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, includingsh(wpkh())andsh(wsh())multi,sortedmulti(BIP383)addr,raw(BIP385)tr, with a key path and a script tree whose leaves arepk(),multi_a()andsortedmulti_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 itselfkey expressions: hex public keys (compressed, uncompressed and, inside
tr()andrawtr(), x-only), WIF private keys, xpub/xprv with a derivation path, key origin,/*and/*hwildcards, bothhand'hardened markersmusig, the BIP390 key expression: the BIP327 aggregate of its participants, inside atr()or arawtr()and nowhere else, with BIP328 derivation of the aggregate key where it has a path of its ownthe
<a;b>multipath form of BIP389, through multipath_descriptorsa BIP379 miniscript, in both of the positions that BIP allows: inside
wsh(), where it is a MiniscriptDescriptor, and as a leaf of atr()script tree, where it is a leaf beside thepk()and themulti_a()ones. Which expression is read as which is the order Bitcoin Core reads them in: the functions above are tried first, so thepk(),pkh()andmulti()that belong to both grammars are the descriptor functions they were before miniscript, and what is left is a miniscript – of the P2WSH dialect insidewsh()and of the tapscript one insidetr(), which is wheremulti_a()replacesmulti()
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.
parse refuses every <a;b> step, even one Bitcoin Core accepts, and
what reaches a Descriptor is one path per key. multipath_descriptors
expands the steps and hands each result to parse, so that one reader
names the faults; its docstring says which.
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. Expanding a descriptor takes the mapping back for the one thing an xpub cannot do, a hardened step, which is why script_pub_keys, satisfy, expansion 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(str(descriptor)) is the checksummed form: Core’s ToString, ToStringHelper writing none, and HWI’s and Electrum’s to_string. What it writes is public by construction, there being no private material left in a parsed descriptor to write.
infer_descriptor goes back from a script instead: Bitcoin Core’s InferDescriptor, reading the script and what a Provider knows of its keys and scripts. Descriptor.provider is what a descriptor expanded at an index knows. scantxoutset infers with the providers of every index of its range, merged in order.
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_wallet.descriptors.descriptors.AddrDescriptor(addr: str, *, network: str = 'mainnet')[source]¶
Bases:
Descriptoraddr(ADDR): the script the address expands to, BIP385.- property key_expressions: tuple[KeyExpression, ...]¶
Return no KEY expression, the descriptor fixing none.
- class btclib_wallet.descriptors.descriptors.ComboDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorcombo(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_wallet.descriptors.descriptors.Descriptor(*, network: str = 'mainnet')[source]¶
Bases:
ABCA 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.
- expand(index: int = 0, prv_keys: Mapping[str, str] | None = None) tuple[list[ScriptPubKey], Provider][source]¶
Return the scripts and the provider at index, from one derivation.
Each key is derived once. Bitcoin Core’s Descriptor::Expand. Calling script_pub_keys and then provider gives the same answers and derives each key twice.
- expansion(index: int = 0, prv_keys: Mapping[str, str] | None = None) Expansion[source]¶
Return what the descriptor expanded at index tells a psbt.
What BIP174’s Updater writes into a psbt for this descriptor’s script: the redeem script of a
sh(), the witness script of awsh(), the internal key, merkle root, leaf scripts and tree of atr(), and the origin of every key – which is what a hardware signer needs, and what KeyExpression.origin is kept for. Expansion says which field holds what.What it does not hold is what a descriptor does not know: the utxo, the sighash type, the signatures.
combo(),addr()andraw()refuse, each saying why.
- 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 btclib-org/btclib#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.
last_index is an integer, ranged descriptor or not, and a bool is refused: True would bound a ranged search at 1. A negative one is refused too, as it bounds no search: answering None for it would say the output is not this wallet’s.
- abstract property key_expressions: tuple[KeyExpression, ...]¶
Return every KEY expression the descriptor holds.
- provider(index: int = 0, prv_keys: Mapping[str, str] | None = None) Provider[source]¶
Return what the descriptor expanded at index tells a Provider.
Bitcoin Core’s Expand: each key with its origin, the script each
sh()andwsh()embeds, the p2wpkh script of acombo()of a compressed key, and the spend data of atr().A key without an origin is its own master, as in Core: a fixed key under the first four bytes of its hash160, an extended key under its own fingerprint with the path it derives. Where one fragment spells a key twice, the later origin goes in front of the earlier one; where two fragments spell it, the first one’s entry stays. Core does both.
- redeem_script(index: int = 0, prv_keys: Mapping[str, str] | None = None) bytes[source]¶
Return the script that
sh()orwsh()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 asortedmulti()– 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.
- btclib_wallet.descriptors.descriptors.DescriptorTree = btclib_wallet.descriptors.key_expression.KeyExpression | btclib_wallet.descriptors.descriptors.MultiA | btclib_wallet.descriptors.miniscript.Miniscript | tuple['DescriptorTree', 'DescriptorTree']¶
A DescriptorLeaf, or a branch, a tuple of two subtrees.
- class btclib_wallet.descriptors.descriptors.Expansion(hd_key_paths: ~collections.abc.Mapping[bytes, ~btclib_wallet.bip32.key_origin.BIP32KeyOrigin] = <factory>, redeem_script: bytes = b'', witness_script: bytes = b'', taproot_internal_key: bytes = b'', taproot_merkle_root: bytes | None = None, taproot_leaf_scripts: ~collections.abc.Mapping[bytes, tuple[bytes, int]] = <factory>, taproot_tree: list[tuple[int, int, bytes]] | None = None, taproot_hd_key_paths: ~collections.abc.Mapping[bytes, tuple[list[bytes], ~btclib_wallet.bip32.key_origin.BIP32KeyOrigin]] = <factory>, musig2_participant_pub_keys: ~collections.abc.Mapping[bytes, list[bytes]] = <factory>)[source]¶
Bases:
objectWhat a descriptor expanded at an index tells a psbt’s Updater.
Descriptor.expansion builds it, and BIP174’s Updater writes it into a psbt: each field is named after the field of an input or an output map it fills, and holds what that field takes. Provider is the expansion infer_descriptor reads instead.
hd_key_paths maps the SEC public key of each key to the origin it derives from. It is empty for a
tr()and arawtr(), whose keys BIP371 files by their x-only form in taproot_hd_key_paths, each with the tapleaf hash of every leaf it is in. redeem_script is the script ash()embeds and witness_script the one awsh()embeds, empty where nothing does.taproot_internal_key, taproot_merkle_root, taproot_leaf_scripts and taproot_tree are a
tr()’s: its x-only internal key, the root its output key commits to, every leaf script with its leaf version keyed by the control block that proves it, and every leaf with its depth, as BIP371’s PSBT_OUT_TAP_TREE lists them. Atr(KEY)has an empty root and an empty tree, so the two are None for every other descriptor, arawtr()included, whose internal key is empty. musig2_participant_pub_keys maps eachmusig()aggregate key to its participants, BIP373’s field.
- class btclib_wallet.descriptors.descriptors.MiniscriptDescriptor(node: Miniscript, *, network: str = 'mainnet')[source]¶
Bases:
DescriptorA BIP379 miniscript where a SCRIPT expression may be one.
Which is inside
wsh(): a miniscript insidetr()is a leaf of the script tree and not a SCRIPT expression, so DescriptorTree holds that one directly, the way it holds amulti_a(). What this holds is the Miniscript, whose own interface – the type properties, the resource bounds, the script both ways, the satisfaction – is btclib_wallet.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_wallet.descriptors.descriptors.MultiA(threshold: int, keys: tuple[KeyExpression, ...], sort: bool = False)[source]¶
Bases:
objectmulti_a(k,KEY,...)orsortedmulti_a(k,KEY,...): a leaf, BIP387.A leaf of a
tr()script tree, beside the bare KeyExpression that is apk()leaf, and not a Descriptor: BIP387 allows these two functions insidetr()and nowhere else, so no output pays to one of them – what an output pays to is thetr()that commits to it as one of its tapscripts.
- class btclib_wallet.descriptors.descriptors.MultiDescriptor(threshold: int, keys: tuple[KeyExpression, ...], sort: bool = False, *, network: str = 'mainnet')[source]¶
Bases:
Descriptormulti(k,KEY,...)andsortedmulti(k,KEY,...), BIP383.- property key_expressions: tuple[KeyExpression, ...]¶
Return the KEY expressions, in descriptor order.
- class btclib_wallet.descriptors.descriptors.PkDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorpk(KEY): a P2PK output, BIP381.- property key_expressions: tuple[KeyExpression, ...]¶
Return the one KEY expression, as the base class’s tuple.
- class btclib_wallet.descriptors.descriptors.PkhDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorpkh(KEY): a p2pkh output, BIP381.- property key_expressions: tuple[KeyExpression, ...]¶
Return the one KEY expression, as the base class’s tuple.
- class btclib_wallet.descriptors.descriptors.Provider(keys: Mapping[bytes, ~btclib_wallet.bip32.key_origin.BIP32KeyOrigin | None]=<factory>, scripts: tuple[bytes, ...]=(), trees: Mapping[bytes, tuple[bytes, bytes, ~collections.abc.Mapping[bytes, tuple[bytes, int]]]]=<factory>)[source]¶
Bases:
objectThe keys and scripts a caller knows, for infer_descriptor to read.
Bitcoin Core’s SigningProvider without the private keys, at bitcoin/bitcoin@9be056a8a7 (the v31.1 tag). Descriptor.provider is what a descriptor expanded at an index tells one, and merged joins several.
keys maps a SEC public key to the origin it derives from, None where it has none. scripts holds the scripts a
sh()or awsh()embeds, found by their hash160. trees maps the x-only output key of atr()to its x-only internal key, its merkle root (empty where it has no tree) and each leaf script with its leaf version, keyed by the control block that proves it.
- class btclib_wallet.descriptors.descriptors.RawDescriptor(script: bytes, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorraw(HEX): the script those bytes are, BIP385.- property key_expressions: tuple[KeyExpression, ...]¶
Return no KEY expression, the descriptor fixing none.
- class btclib_wallet.descriptors.descriptors.RawTrDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorrawtr(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 mentionsrawtr(); 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 arawtr()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 fromtr(KEY), which tweaks its internal key with an empty merkle root, and it is why this is not a TrDescriptor carryingtree=None.- property key_expressions: tuple[KeyExpression, ...]¶
Return the one KEY expression, as the base class’s tuple.
- class btclib_wallet.descriptors.descriptors.ShDescriptor(inner: Descriptor, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorsh(SCRIPT): the argument, p2sh-embedded, BIP381.- property key_expressions: tuple[KeyExpression, ...]¶
Return the wrapped SCRIPT’s KEY expressions.
- class btclib_wallet.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:
Descriptortr(KEY)ortr(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_wallet.descriptors.descriptors.WpkhDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorwpkh(KEY): a p2wpkh output, BIP382.- property key_expressions: tuple[KeyExpression, ...]¶
Return the one KEY expression, as the base class’s tuple.
- class btclib_wallet.descriptors.descriptors.WshDescriptor(inner: Descriptor, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorwsh(SCRIPT): the argument, P2WSH-embedded, BIP382.- property key_expressions: tuple[KeyExpression, ...]¶
Return the wrapped SCRIPT’s KEY expressions.
- btclib_wallet.descriptors.descriptors.add_checksum(descriptor: str) str[source]¶
Return the descriptor with its checksum, verifying a present one.
- btclib_wallet.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_wallet.descriptors.descriptors.checksum(descriptor: str) str[source]¶
Compute the descriptor checksum.
- btclib_wallet.descriptors.descriptors.derived_origin(key: KeyExpression, index: int = 0) BIP32KeyOrigin[source]¶
Return the origin of the key at index.
KeyExpression.origin is the path down to the extended key the descriptor holds, and what BIP174 carries is the path down to the key itself: the derivation the descriptor then does, the wildcard step at index included, appended to it. A signer given the shorter path would derive the wrong key from it.
A key without an origin is its own master, as in Bitcoin Core’s ConstPubkeyProvider and BIP32PubkeyProvider: a fixed key under the first four bytes of its hash160 with an empty path, an extended key under its own fingerprint with the derivation.
An index outside 0 to 2**31 - 1 is refused, whether the key is ranged or not, as KeyExpression.sec refuses it. So is a
musig()key: BIP373 gives an aggregate the fingerprint of BIP328’s synthetic xpub, and computing the aggregate takes the network and theprv_keysthat KeyExpression.aggregate takes, which this function does not.
- btclib_wallet.descriptors.descriptors.from_address(address: str) str[source]¶
Return the addr() descriptor of the address, checksummed.
- btclib_wallet.descriptors.descriptors.infer_descriptor(script: bytes | str | bytearray | memoryview, provider: Provider, network: str = 'mainnet') str[source]¶
Return a script’s descriptor, checksummed: Core’s InferDescriptor.
What scantxoutset answers in each unspents[].desc, at bitcoin/bitcoin@9be056a8a7 (the v31.1 tag), read from the script and from what provider knows. Each key is the public key the script holds, behind its origin where the provider knows one. A sorted multisig is a
multi()of its keys in the script’s order. A key in atr()is x-only, except in apkh(), and the leaves are in the order Core rebuilds them. A script nothing else describes is anaddr()where it has an address – arawtr()for a p2tr output whose key is a point – and araw()otherwise.
- btclib_wallet.descriptors.descriptors.multipath_descriptors(descriptor: str, network: str | None = None) 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. parse reads the descriptor first, each step on its first path, and then each result, so a step Core refuses is refused by parse as Core refuses it. That is a step that is not a whole path element of a key outside its origin, that has fewer than two elements, a repeated one or one that is no index (
<2;x>), that shares its key path with another step or with a stray “<” or “>”, or that is in amusig()whose path and participants both hold one.Steps of different length are refused by parse where Core refuses them: once it has read a
multi(), amulti_a()or amusig(), and at the end of a miniscript or of atr().The keys are read in network as parse reads them. With none, the network of a key is not checked, which is parse’s call to make on the result.
- btclib_wallet.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_wallet.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.
A WIF or an extended key of another network is refused here too, as Bitcoin Core’s parser does. The test networks share one WIF prefix and one extended-key version.
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_wallet.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_wallet.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 – for a BIP44 account, its
/1/*and/0/*chains. 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 amusig()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 barepk()among them; so is a top-levelmulti()orsortedmulti(), or apk()directly insidesh(). Not checked: that every key is pairwise distinct from every other.
- btclib_wallet.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_wallet.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.
A template whose own function is not one of BIP388’s SCRIPT expressions, or is a top-level
multi()orsortedmulti(), or is apk()directly insidesh(), is refused as wallet_policy refuses it.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_wallet.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_wallet.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', wif_prefix: bytes = b'')[source]¶
Bases:
objectA 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.wif_prefix is the version byte of the WIF a fixed key was read from, the key itself being public by then; it is empty for any other spelling.
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, and the constructor refuses one, so no key expression 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_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.An index outside 0 to 2**31 - 1 is refused, whether the key is ranged or not: Descriptor refuses the same.
btclib_wallet.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_wallet.descriptors.miniscript.Miniscript(fragment: str, context: str = 'P2WSH', subs: tuple[Miniscript, ...] = (), keys: tuple[KeyExpression, ...] = (), threshold: int = 0, data: bytes = b'')[source]¶
Bases:
objectA 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()andmulti_a(); threshold the number ofolder(),after(),thresh(),multi()andmulti_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.
- has_duplicate_keys(prv_keys: Mapping[str, str] | None = None) bool[source]¶
Answer whether one public key 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. The keys compared are the ones the KEY expressions derive at index 0, as Bitcoin Core’s KeyCompare does, so the origin, the spelling of a hardened step and the wildcard do not make two spellings of a key two keys. An x-only key counts as its even-y form, parity included in the comparison.
A hardened step is derived with prv_keys, as in KeyExpression.sec. A key it cannot derive is compared by its expression, origin and hardening symbol included, and never matches a key that can be derived: Bitcoin Core does the same since bitcoin/bitcoin@7b15e2cb44.
- insane_sub(prv_keys: Mapping[str, str] | None = None) Miniscript | None[source]¶
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. prv_keys is has_duplicate_keys’s.
- is_sane(prv_keys: Mapping[str, str] | None = None) bool[source]¶
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. prv_keys is has_duplicate_keys’s.
- is_sane_subexpression(prv_keys: Mapping[str, str] | None = None) bool[source]¶
Answer whether the expression means what it says, as a part.
prv_keys is has_duplicate_keys’s.
- property is_signature_required: bool¶
Answer whether every satisfaction requires a signature.
False for a malleable expression, of which BIP379 says “s” tells nothing.
- 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_wallet.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:
objectWhat 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 anafter()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 psbt.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_wallet.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 sugaredpkh()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_wallet.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_wallet.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_DROPwhere 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 what each tells a psbt.
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_wallet.descriptors.AddrDescriptor(addr: str, *, network: str = 'mainnet')[source]¶
Bases:
Descriptoraddr(ADDR): the script the address expands to, BIP385.- property key_expressions: tuple[KeyExpression, ...]¶
Return no KEY expression, the descriptor fixing none.
- class btclib_wallet.descriptors.ComboDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorcombo(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_wallet.descriptors.Descriptor(*, network: str = 'mainnet')[source]¶
Bases:
ABCA 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.
- expand(index: int = 0, prv_keys: Mapping[str, str] | None = None) tuple[list[ScriptPubKey], Provider][source]¶
Return the scripts and the provider at index, from one derivation.
Each key is derived once. Bitcoin Core’s Descriptor::Expand. Calling script_pub_keys and then provider gives the same answers and derives each key twice.
- expansion(index: int = 0, prv_keys: Mapping[str, str] | None = None) Expansion[source]¶
Return what the descriptor expanded at index tells a psbt.
What BIP174’s Updater writes into a psbt for this descriptor’s script: the redeem script of a
sh(), the witness script of awsh(), the internal key, merkle root, leaf scripts and tree of atr(), and the origin of every key – which is what a hardware signer needs, and what KeyExpression.origin is kept for. Expansion says which field holds what.What it does not hold is what a descriptor does not know: the utxo, the sighash type, the signatures.
combo(),addr()andraw()refuse, each saying why.
- 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 btclib-org/btclib#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.
last_index is an integer, ranged descriptor or not, and a bool is refused: True would bound a ranged search at 1. A negative one is refused too, as it bounds no search: answering None for it would say the output is not this wallet’s.
- abstract property key_expressions: tuple[KeyExpression, ...]¶
Return every KEY expression the descriptor holds.
- provider(index: int = 0, prv_keys: Mapping[str, str] | None = None) Provider[source]¶
Return what the descriptor expanded at index tells a Provider.
Bitcoin Core’s Expand: each key with its origin, the script each
sh()andwsh()embeds, the p2wpkh script of acombo()of a compressed key, and the spend data of atr().A key without an origin is its own master, as in Core: a fixed key under the first four bytes of its hash160, an extended key under its own fingerprint with the path it derives. Where one fragment spells a key twice, the later origin goes in front of the earlier one; where two fragments spell it, the first one’s entry stays. Core does both.
- redeem_script(index: int = 0, prv_keys: Mapping[str, str] | None = None) bytes[source]¶
Return the script that
sh()orwsh()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 asortedmulti()– 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.
- class btclib_wallet.descriptors.Expansion(hd_key_paths: ~collections.abc.Mapping[bytes, ~btclib_wallet.bip32.key_origin.BIP32KeyOrigin] = <factory>, redeem_script: bytes = b'', witness_script: bytes = b'', taproot_internal_key: bytes = b'', taproot_merkle_root: bytes | None = None, taproot_leaf_scripts: ~collections.abc.Mapping[bytes, tuple[bytes, int]] = <factory>, taproot_tree: list[tuple[int, int, bytes]] | None = None, taproot_hd_key_paths: ~collections.abc.Mapping[bytes, tuple[list[bytes], ~btclib_wallet.bip32.key_origin.BIP32KeyOrigin]] = <factory>, musig2_participant_pub_keys: ~collections.abc.Mapping[bytes, list[bytes]] = <factory>)[source]¶
Bases:
objectWhat a descriptor expanded at an index tells a psbt’s Updater.
Descriptor.expansion builds it, and BIP174’s Updater writes it into a psbt: each field is named after the field of an input or an output map it fills, and holds what that field takes. Provider is the expansion infer_descriptor reads instead.
hd_key_paths maps the SEC public key of each key to the origin it derives from. It is empty for a
tr()and arawtr(), whose keys BIP371 files by their x-only form in taproot_hd_key_paths, each with the tapleaf hash of every leaf it is in. redeem_script is the script ash()embeds and witness_script the one awsh()embeds, empty where nothing does.taproot_internal_key, taproot_merkle_root, taproot_leaf_scripts and taproot_tree are a
tr()’s: its x-only internal key, the root its output key commits to, every leaf script with its leaf version keyed by the control block that proves it, and every leaf with its depth, as BIP371’s PSBT_OUT_TAP_TREE lists them. Atr(KEY)has an empty root and an empty tree, so the two are None for every other descriptor, arawtr()included, whose internal key is empty. musig2_participant_pub_keys maps eachmusig()aggregate key to its participants, BIP373’s field.
- class btclib_wallet.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', wif_prefix: bytes = b'')[source]¶
Bases:
objectA 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.wif_prefix is the version byte of the WIF a fixed key was read from, the key itself being public by then; it is empty for any other spelling.
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, and the constructor refuses one, so no key expression 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_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.An index outside 0 to 2**31 - 1 is refused, whether the key is ranged or not: Descriptor refuses the same.
- class btclib_wallet.descriptors.Miniscript(fragment: str, context: str = 'P2WSH', subs: tuple[Miniscript, ...] = (), keys: tuple[KeyExpression, ...] = (), threshold: int = 0, data: bytes = b'')[source]¶
Bases:
objectA 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()andmulti_a(); threshold the number ofolder(),after(),thresh(),multi()andmulti_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.
- has_duplicate_keys(prv_keys: Mapping[str, str] | None = None) bool[source]¶
Answer whether one public key 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. The keys compared are the ones the KEY expressions derive at index 0, as Bitcoin Core’s KeyCompare does, so the origin, the spelling of a hardened step and the wildcard do not make two spellings of a key two keys. An x-only key counts as its even-y form, parity included in the comparison.
A hardened step is derived with prv_keys, as in KeyExpression.sec. A key it cannot derive is compared by its expression, origin and hardening symbol included, and never matches a key that can be derived: Bitcoin Core does the same since bitcoin/bitcoin@7b15e2cb44.
- insane_sub(prv_keys: Mapping[str, str] | None = None) Miniscript | None[source]¶
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. prv_keys is has_duplicate_keys’s.
- is_sane(prv_keys: Mapping[str, str] | None = None) bool[source]¶
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. prv_keys is has_duplicate_keys’s.
- is_sane_subexpression(prv_keys: Mapping[str, str] | None = None) bool[source]¶
Answer whether the expression means what it says, as a part.
prv_keys is has_duplicate_keys’s.
- property is_signature_required: bool¶
Answer whether every satisfaction requires a signature.
False for a malleable expression, of which BIP379 says “s” tells nothing.
- 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_wallet.descriptors.MiniscriptDescriptor(node: Miniscript, *, network: str = 'mainnet')[source]¶
Bases:
DescriptorA BIP379 miniscript where a SCRIPT expression may be one.
Which is inside
wsh(): a miniscript insidetr()is a leaf of the script tree and not a SCRIPT expression, so DescriptorTree holds that one directly, the way it holds amulti_a(). What this holds is the Miniscript, whose own interface – the type properties, the resource bounds, the script both ways, the satisfaction – is btclib_wallet.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_wallet.descriptors.MultiA(threshold: int, keys: tuple[KeyExpression, ...], sort: bool = False)[source]¶
Bases:
objectmulti_a(k,KEY,...)orsortedmulti_a(k,KEY,...): a leaf, BIP387.A leaf of a
tr()script tree, beside the bare KeyExpression that is apk()leaf, and not a Descriptor: BIP387 allows these two functions insidetr()and nowhere else, so no output pays to one of them – what an output pays to is thetr()that commits to it as one of its tapscripts.
- class btclib_wallet.descriptors.MultiDescriptor(threshold: int, keys: tuple[KeyExpression, ...], sort: bool = False, *, network: str = 'mainnet')[source]¶
Bases:
Descriptormulti(k,KEY,...)andsortedmulti(k,KEY,...), BIP383.- property key_expressions: tuple[KeyExpression, ...]¶
Return the KEY expressions, in descriptor order.
- class btclib_wallet.descriptors.PkDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorpk(KEY): a P2PK output, BIP381.- property key_expressions: tuple[KeyExpression, ...]¶
Return the one KEY expression, as the base class’s tuple.
- class btclib_wallet.descriptors.PkhDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorpkh(KEY): a p2pkh output, BIP381.- property key_expressions: tuple[KeyExpression, ...]¶
Return the one KEY expression, as the base class’s tuple.
- class btclib_wallet.descriptors.Provider(keys: Mapping[bytes, ~btclib_wallet.bip32.key_origin.BIP32KeyOrigin | None]=<factory>, scripts: tuple[bytes, ...]=(), trees: Mapping[bytes, tuple[bytes, bytes, ~collections.abc.Mapping[bytes, tuple[bytes, int]]]]=<factory>)[source]¶
Bases:
objectThe keys and scripts a caller knows, for infer_descriptor to read.
Bitcoin Core’s SigningProvider without the private keys, at bitcoin/bitcoin@9be056a8a7 (the v31.1 tag). Descriptor.provider is what a descriptor expanded at an index tells one, and merged joins several.
keys maps a SEC public key to the origin it derives from, None where it has none. scripts holds the scripts a
sh()or awsh()embeds, found by their hash160. trees maps the x-only output key of atr()to its x-only internal key, its merkle root (empty where it has no tree) and each leaf script with its leaf version, keyed by the control block that proves it.
- class btclib_wallet.descriptors.RawDescriptor(script: bytes, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorraw(HEX): the script those bytes are, BIP385.- property key_expressions: tuple[KeyExpression, ...]¶
Return no KEY expression, the descriptor fixing none.
- class btclib_wallet.descriptors.RawTrDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorrawtr(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 mentionsrawtr(); 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 arawtr()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 fromtr(KEY), which tweaks its internal key with an empty merkle root, and it is why this is not a TrDescriptor carryingtree=None.- property key_expressions: tuple[KeyExpression, ...]¶
Return the one KEY expression, as the base class’s tuple.
- class btclib_wallet.descriptors.ShDescriptor(inner: Descriptor, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorsh(SCRIPT): the argument, p2sh-embedded, BIP381.- property key_expressions: tuple[KeyExpression, ...]¶
Return the wrapped SCRIPT’s KEY expressions.
- class btclib_wallet.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:
objectWhat 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 anafter()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 psbt.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_wallet.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:
Descriptortr(KEY)ortr(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_wallet.descriptors.WpkhDescriptor(key: KeyExpression, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorwpkh(KEY): a p2wpkh output, BIP382.- property key_expressions: tuple[KeyExpression, ...]¶
Return the one KEY expression, as the base class’s tuple.
- class btclib_wallet.descriptors.WshDescriptor(inner: Descriptor, *, network: str = 'mainnet')[source]¶
Bases:
Descriptorwsh(SCRIPT): the argument, P2WSH-embedded, BIP382.- property key_expressions: tuple[KeyExpression, ...]¶
Return the wrapped SCRIPT’s KEY expressions.
- btclib_wallet.descriptors.add_checksum(descriptor: str) str[source]¶
Return the descriptor with its checksum, verifying a present one.
- btclib_wallet.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_wallet.descriptors.derived_origin(key: KeyExpression, index: int = 0) BIP32KeyOrigin[source]¶
Return the origin of the key at index.
KeyExpression.origin is the path down to the extended key the descriptor holds, and what BIP174 carries is the path down to the key itself: the derivation the descriptor then does, the wildcard step at index included, appended to it. A signer given the shorter path would derive the wrong key from it.
A key without an origin is its own master, as in Bitcoin Core’s ConstPubkeyProvider and BIP32PubkeyProvider: a fixed key under the first four bytes of its hash160 with an empty path, an extended key under its own fingerprint with the derivation.
An index outside 0 to 2**31 - 1 is refused, whether the key is ranged or not, as KeyExpression.sec refuses it. So is a
musig()key: BIP373 gives an aggregate the fingerprint of BIP328’s synthetic xpub, and computing the aggregate takes the network and theprv_keysthat KeyExpression.aggregate takes, which this function does not.
- btclib_wallet.descriptors.from_address(address: str) str[source]¶
Return the addr() descriptor of the address, checksummed.
- btclib_wallet.descriptors.infer_descriptor(script: bytes | str | bytearray | memoryview, provider: Provider, network: str = 'mainnet') str[source]¶
Return a script’s descriptor, checksummed: Core’s InferDescriptor.
What scantxoutset answers in each unspents[].desc, at bitcoin/bitcoin@9be056a8a7 (the v31.1 tag), read from the script and from what provider knows. Each key is the public key the script holds, behind its origin where the provider knows one. A sorted multisig is a
multi()of its keys in the script’s order. A key in atr()is x-only, except in apkh(), and the leaves are in the order Core rebuilds them. A script nothing else describes is anaddr()where it has an address – arawtr()for a p2tr output whose key is a point – and araw()otherwise.
- btclib_wallet.descriptors.multipath_descriptors(descriptor: str, network: str | None = None) 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. parse reads the descriptor first, each step on its first path, and then each result, so a step Core refuses is refused by parse as Core refuses it. That is a step that is not a whole path element of a key outside its origin, that has fewer than two elements, a repeated one or one that is no index (
<2;x>), that shares its key path with another step or with a stray “<” or “>”, or that is in amusig()whose path and participants both hold one.Steps of different length are refused by parse where Core refuses them: once it has read a
multi(), amulti_a()or amusig(), and at the end of a miniscript or of atr().The keys are read in network as parse reads them. With none, the network of a key is not checked, which is parse’s call to make on the result.
- btclib_wallet.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_wallet.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.
A WIF or an extended key of another network is refused here too, as Bitcoin Core’s parser does. The test networks share one WIF prefix and one extended-key version.
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_wallet.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_wallet.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 – for a BIP44 account, its
/1/*and/0/*chains. 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 amusig()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 barepk()among them; so is a top-levelmulti()orsortedmulti(), or apk()directly insidesh(). Not checked: that every key is pairwise distinct from every other.
- btclib_wallet.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_wallet.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.
A template whose own function is not one of BIP388’s SCRIPT expressions, or is a top-level
multi()orsortedmulti(), or is apk()directly insidesh(), is refused as wallet_policy refuses it.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.