btclib.wallet package

Submodules

btclib.wallet.descriptor_wallet module

A wallet of output descriptors: one per chain, the rest is theirs.

Descriptor answers everything about the scripts it describes and knows nothing about chains: it has one index, so a wallet is two of them – BIP44 puts receiving addresses under /0 and change under /1, and a wallet holding one and not the other cannot recognize its own change, which is a lost output rather than a missing feature. This module is that pairing, and the btclib.wallet vocabulary over it.

So the branch here is a label and not a derivation step: which of the descriptors, the path inside each having derived it already. The three ways in put the labels there:

  • the descriptors themselves, in a mapping from branch or in chain order;

  • from_descriptor, which reads BIP389’s <0;1> multipath spelling – one line of text for the pair, expanded positionally, so element 0 is branch 0. Named as ScriptPubKey.from_address is, and not parse: a parse classmethod in btclib reads octets out of a stream, and this reads a line of text;

  • from_account, which is descriptors.account_descriptors: an account xpub and its master fingerprint in, the receive and change descriptors out.

What this adds to a descriptor is the branch, the ledger of what has been handed out, and position_of; what it delegates is everything else, and descriptor(branch) hands back the Descriptor for the questions this does not ask – str(), key_expressions, satisfy for a shape the wallet-level one refuses. satisfy and the two psbt Updaters are here because their argument is a position, and a caller holding a psbt has one rather than an index.

A combo() is refused: it is four scripts at one index, so it is four addresses at one position, and a wallet position that means four addresses would make address(branch, index) a lie. descriptors.parse reads one, and a caller wanting it uses Descriptor.script_pub_keys directly.

A parsed descriptor holds no private key – descriptors.parse neuters an xprv to its xpub – so a wallet is watch-only unless it is handed the prv_keys mapping that module takes: the hardened steps an xpub cannot walk, keyed as it keys them. That mapping is also the whole of what is_watch_only reads, there being nothing else here that could sign.

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

class btclib.wallet.descriptor_wallet.DescriptorWallet(descriptors: Descriptor | Mapping[int, Descriptor] | Sequence[Descriptor], prv_keys: Mapping[str, str] | None = None)[source]

Bases: RangedWallet

One descriptor per chain, and the wallet questions over the pair.

property branches: tuple[int, ...]

The chains, which are the branches the descriptors came under.

descriptor(branch: int = 0) Descriptor[source]

Return the descriptor of a chain.

classmethod from_account(xkey: BIP32KeyData | bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview, master_fingerprint: bytes | str | bytearray | memoryview | None = None, script_type: Literal['p2pkh', 'p2wpkh-p2sh', 'p2wpkh', 'p2tr'] | None = None, prv_keys: Mapping[str, str] | None = None) DescriptorWallet[source]

Return the wallet of a BIP44 account, both of its chains.

descriptors.account_descriptors is the whole of it, and every argument is that function’s: the account path selects the encoding through its purpose, the master fingerprint is what the key origin needs, and the network is the extended key’s own.

BIP32KeyWallet is the same account without the descriptors, and the two answer the same addresses. What this one adds is what a descriptor carries: the text to hand to Bitcoin Core, the key origins a hardware signer wants, and the psbt Updaters.

classmethod from_descriptor(descriptor: str, network: str = 'mainnet', prv_keys: dict[str, str] | None = None) DescriptorWallet[source]

Return the wallet of a descriptor, checksum verified.

BIP389’s <0;1> is what makes one line of text a whole wallet, and it is expanded positionally: as many branches as the multipath steps have elements, branch 0 taking the first element of every step. A descriptor with no such step is one chain, and a wallet of one branch – which is what a caller watching a single chain has, and not an error.

property is_ranged: bool

Whether any chain describes a range of scripts.

A wallet of descriptors with no wildcard is one address per chain, index 0 and nothing else; Descriptor.is_ranged is the same question of one chain.

property is_watch_only: bool

Whether the wallet was handed no private key.

A parsed descriptor never holds one, descriptors.parse keeping the xpub of an xprv, so the prv_keys mapping is the only key material a wallet of descriptors can have.

position_of(script_pub_key: bytes | str | bytearray | memoryview | ScriptPubKey, last_index: int = 999) tuple[int, int] | None[source]

Return the position paying to this output, None where none is.

Descriptor.index_of per chain, in branches order, rather than a comparison written a second time here: it is the same whole- script comparison, it refuses the same spellings that name no output, and it is what bounds the search of a chain that is not ranged to the one script it has.

redeem_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a sh() at this position embeds.

Which is the redeem script of every shape sh() wraps: the script of a sh(multi()), and the p2wpkh or p2wsh program of the two wrapped segwit forms, that program being what a p2sh input pushes either way.

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

Return the script_sig and witness spending this position.

Descriptor.satisfy with the position resolved, and every word of its contract: the signatures are handed in and assembled rather than verified, a satisfaction short of what the script pops is an error rather than a shorter answer, and spend is what a miniscript branch reads beside the signatures.

update_psbt_input(psbt: Psbt, vin_i: int, branch: int = 0, index: int = 0) Psbt[source]

Return the psbt with an input told what this position is.

Descriptor.update_psbt_input, which is BIP174’s Updater: the scripts and the key origins of the position, for signers to fill in and psbt.finalize to assemble.

update_psbt_output(psbt: Psbt, vout_i: int, branch: int = 0, index: int = 0) Psbt[source]

Return the psbt with an output told what this position is.

Descriptor.update_psbt_output, which refuses unless the output being paid is the very script this position derives: marking an output as the wallet’s own is a claim about where money goes, and the whole script is the only evidence for it. position_of is the same claim asked as a question.

witness_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a wsh() at this position embeds.

Native or wrapped in a sh(), which is the same witness script in the same psbt field: the wrapping shows up in the redeem script and nowhere else.

btclib.wallet.key_wallet module

The wallets whose output is the hash of one key, and who signs for it.

btclib.ecc.bms says it in its own docstring – “at signing time, a wallet infrastructure is required to access the private key corresponding to a given address” – and then has none to reach for. This module is that infrastructure, and no more of it than the sentence asks for: a wallet holds a key source, hands out addresses, remembers what each was derived from, and answers which private key signs for one. sign(address, msg) is what the three together buy, a message signature addressed by address, which bms.sign cannot offer because it has no way to find the key.

Two key sources, one class each. BIP32KeyWallet takes an extended key and the BIP44 account path it sits on, and derives the addresses of the two unhardened levels below it; KeyWallet takes individual keys, one address each, and is also the base the derivation is added to. Either one is watch-only when it holds no private key – an xpub, or public keys – and sign on one raises rather than returning something a caller might mistake for a signature.

Both are the one-key half of btclib.wallet: the address is a hash of a single public key, which is what makes a private key for it a single thing to look up. BIP44ScriptType is therefore the whole of what they encode – a p2wsh address is the hash of a script, and ScriptWallet is where a script goes.

Which encoding an account path means is bip44’s question and is answered there, not here: this module imports that mapping and those four encoders rather than keeping a second copy, so a purpose added to bip44’s data file is a purpose a wallet hands out. What it does not borrow is bip44.address_from_der_path, which walks the whole five-level path from the extended key it is given; a wallet has already derived down to the account at construction, and bip32.derive_from_account – which imposes the branch and index bounds – is the two levels left.

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

class btclib.wallet.key_wallet.BIP32KeyWallet(xkey: BIP32KeyData | bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview = 'm/84h/0h/0h', script_type: Literal['p2pkh', 'p2wpkh-p2sh', 'p2wpkh', 'p2tr'] | None = None)[source]

Bases: KeyWallet, RangedWallet

An extended key at a BIP44 account, and the addresses below it.

xkey is the master key, the account key, or any key between the two; der_path is always the whole account path from the master, m/purpose’/coin_type’/account’, because the purpose lives at the top of it and is what says whether the addresses are p2pkh, p2wpkh- p2sh, p2wpkh or p2tr. What is left of the path below the key is derived once, at construction.

Addresses come from the two unhardened levels BIP44 puts under an account, branch/index with branch 0 receiving and 1 change, which is also what public derivation can walk – so an account xpub is a complete watch-only wallet.

Both bases carry their weight: RangedWallet is the positions, and KeyWallet the signing and the loose keys, which an extended key does not stop a caller from adding.

property branches: tuple[int, ...]

The receiving and change chains, which is what BIP44 defines.

The two bip32.derive_from_account walks and the only two: a third would be a chain no BIP44 wallet looks at, so an address of it is an address nobody else recovers.

property is_watch_only: bool

Whether the wallet holds no private key at all.

An xpub account is the watch-only case; a private key handed to add alongside it is not derived from the account and does not make the account signable, but it does make the wallet hold key material, which is what this answers.

prv_key(address: bytes | str | bytearray | memoryview) str[source]

Return the WIF of the private key signing for an address.

redeem_script(branch: int = 0, index: int = 0) bytes[source]

Return the p2wpkh script a p2wpkh-p2sh output commits to.

The one script type here that has one: the other three pay to a key hash or to a key, so what a spend of them pushes is the key itself and there is no pre-image for a psbt to carry – which is the base class’s answer, and is deferred to rather than repeated.

class btclib.wallet.key_wallet.KeyWallet(keys: Iterable[int | bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint] = (), script_type: Literal['p2pkh', 'p2wpkh-p2sh', 'p2wpkh', 'p2tr'] = 'p2wpkh', network: str = 'mainnet')[source]

Bases: Wallet

Individual keys, the address of each, and who signs for it.

The addresses are handed out by add, one per key, all of them in the script type the wallet was built with; BIP32KeyWallet below is the same thing with an extended key underneath and derivation on top.

A public key is as welcome as a private one and makes that address watch-only. sign then raises, naming the address: the alternative would be a wallet that answers a signing request with something falsy, and every caller that forgets to check has published an unsigned message.

Not a RangedWallet: a key handed over is one address and there is no position it came from, so address(branch, index) would have nothing to compute. BIP32KeyWallet is where the positions are.

add(key: int | bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, script_type: Literal['p2pkh', 'p2wpkh-p2sh', 'p2wpkh', 'p2tr'] | None = None) str[source]

Take one key into the wallet, and return its address.

property is_watch_only: bool

Whether the wallet holds no private key at all.

prv_key(address: bytes | str | bytearray | memoryview) str[source]

Return the WIF of the private key signing for an address.

sign(address: bytes | str | bytearray | memoryview, msg: bytes | str | bytearray | memoryview) Sig[source]

Return the BMS signature of a message, by address.

The address is the argument bms.sign has no way to resolve on its own; everything after the lookup is bms.sign’s, this method signing nothing itself. The resolved address is passed on to it as well as the key, which is what makes the recovery flag name the right address type – BIP137’s 35..38 for the wrapped segwit spelling and 39..42 for the native one – rather than the compressed p2pkh flag that a key alone would produce.

BMS and no other scheme, which is what leaves p2tr out: a caller wanting BIP322 for one of these addresses passes prv_key and the address to btclib.bip322.sign, which signs for all four script types this module hands out.

btclib.wallet.script_wallet module

A wallet whose output is a script no descriptor states: template, groups.

DescriptorWallet covers every wallet whose script is sayable in BIP380 to BIP390, and refuses the ones that are not – correctly, and with nothing to offer instead. This module is the something else. Multisig wallets predating output descriptors are still holding coins, and their scripts miss the language by a detail: a <n> OP_CSV OP_DROP where miniscript writes OP_CSV OP_VERIFY, or a BIP67 sort applied to the derived keys of a quorum, which sortedmulti() follows exactly and which BIP379 has no fragment for inside a combinator. Both refusals are right, and “right to refuse” is not “nothing to answer”.

A ScriptWallet is three things and no more: a script template, the key groups the template writes into it, and an order for the keys of each group. That is enough to compute the script at a position, which is enough for the two questions a wallet is for – address(branch, index) and position_of(script_pub_key):

wallet = ScriptWallet(
    [
        "OP_IF",
        KeyGroup(2, [xpub_a, xpub_b, xpub_c]),
        "OP_ELSE",
        push_int(144),
        "OP_CHECKSEQUENCEVERIFY",
        "OP_DROP",
        KeyGroup(1, [xpub_recovery]),
        "OP_ENDIF",
    ],
    script_type="p2wsh",
    order="derived",
)

The template is a script.serialize command list with KeyGroup objects among the commands – a number in one is written with script.push_int, which is the op code below 17 and the CScriptNum encoding above it, so that a timelock of 16 blocks and one of 144 are written the same way – and a group writes what OP_CHECKMULTISIG reads: k <key>… n OP_CHECKMULTISIG, the keys being the ones derived at the position – or, with verify=True, OP_CHECKMULTISIGVERIFY in place of that last opcode, which is the form a required quorum takes rather than one left on the stack. So the template is Python objects and not text: there is no text format here, and no parser. Inventing a second, worse descriptor language is how this feature goes wrong, and a wallet that cannot be written down cannot be mistaken for one that can be handed to Bitcoin Core.

No `from_script`. A template is not recoverable from a script: the bytes at one position say which keys are in it and not which account keys derived them, so a class reading one back would be guessing, and guessing is the parser this module refuses to have. DescriptorWallet.from_descriptor is where a wallet written down in BIP380’s language is read.

`descriptor(branch)` is the other direction, and it is a lift rather than a conversion. No shape is spelled out for it: the wallet derives its own script at index 0, miniscript.from_script reads those bytes back into the expression they are, each derived key is put back as the KEY expression that produced it – [fingerprint/path]xpub/branch/* – and the text goes through descriptors.parse, so what comes back is the descriptor a reader of that text gets and not an object of this module’s own making. A shape this file has never heard of therefore comes out right, and the two shapes this class exists for come out not at all: NoDescriptorError for the OP_DROP timelock, which is no miniscript, and for a quorum ordered per index inside a combinator, which no ranged expression covers. Nothing is guessed and nothing is approximated, which is what the old “no conversion either way” was guarding against, and the guard is now the check rather than the absence: the addresses the descriptor derives are compared with the wallet’s own before it is handed back, so a lift that was not faithful is an exception and not a descriptor.

Not a signer, and an Updater. DescriptorWallet.satisfy can assemble a spend because miniscript knows what satisfies a fragment; a template does not, so no satisfaction is here and a caller holding such a script owns the spend. BIP174’s Updater is the role that does not need to know it: what it writes into a psbt is what the wallet has computed anyway – redeem_script and witness_script, the two pre-images, and the origin of each key – and update_psbt_input and update_psbt_output are those fields written at a position. The output half is what says an output comes back, and it refuses unless the output being paid is the very script the position derives.

An origin is a KeyGroup parameter because an account key cannot supply one: BIP174 carries the master fingerprint and the path from the master key down, and an extended key below the root records neither – the same reason descriptors.account_descriptors takes the fingerprint beside the key. A group given no origin is a wallet that computes addresses and writes no hd_key_paths, which is what a psbt of it then lacks – and what the [fingerprint/path] of its descriptor then lacks too, the two being one declaration read twice rather than two places to say it.

The order is a parameter because deployed wallets disagree about it, and one of the three is why this class exists at all:

  • “derived” sorts the keys of each group at every index, which is BIP67 on the derived keys, so the order is a property of the position and not of the wallet. sortedmulti() follows exactly that order and states it for a quorum that is the whole script; what states it for a quorum inside a combinator is nothing, BIP379 having no sortedmulti fragment and its multi() being the declared-order one. A timelocked branch is therefore where the order stops being expressible, which is the wallet #538 pins and the reason for this parameter – and the line descriptor() draws: one quorum and nothing else is a sortedmulti(), the same quorum inside a combinator is nothing;

  • “account” sorts the account keys once, before deriving, so the order is fixed and multi() states it by listing the keys in it. What this saves a caller is the sort, not an inexpressible wallet;

  • “none” leaves the keys in the order the group declares them.

sort_key changes what the sort compares, not when it runs: without one it is byte order, which is BIP67, on the derived public key or on the account key’s public key. A wallet that sorted its xpubs case-insensitively – neither a byte order nor a BIP, and deployed – is order=”account” with a sort_key of its own.

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

class btclib.wallet.script_wallet.KeyGroup(threshold: int, keys: Sequence[BIP32KeyData | bytes | str | bytearray | memoryview], verify: bool = False, origins: Sequence[BIP32KeyOrigin | None] | None = None)[source]

Bases: object

A quorum of extended keys, as a template writes it into a script.

threshold of as many keys as are given, which OP_CHECKMULTISIG needs stated twice: what a group expands to at a position is k <key>… n OP_CHECKMULTISIG, with each key derived down the two unhardened levels bip32.derive_from_account walks – so every key here is an account key, exactly as BIP32KeyWallet takes one.

An xprv is as welcome as an xpub and is what makes a wallet not watch-only; what goes in the script is the public key either way.

The order the keys are given in is the order the script carries them in, unless the wallet holding the group says otherwise: ordering is the wallet’s parameter, not the group’s, because the wallets deployed with a per-index order apply it to every quorum of the script.

verify=True writes OP_CHECKMULTISIGVERIFY in place of the last opcode and nothing else about the group – the same choice miniscript makes with its v: wrapper, and the form and_v(v:multi(…), …) compiles to.

origins is where each key comes from – the master fingerprint and the path down to the account key, which is what BIP174 carries and what an extended key below the root cannot say of itself – one entry per key and in the order the keys are given, None for a key whose origin the caller does not have. It changes no script: what reads it is ScriptWallet.update_psbt_input and update_psbt_output, and what a group without it writes into a psbt is nothing.

class btclib.wallet.script_wallet.ScriptWallet(template: Sequence[int | str | bytes | bytearray | memoryview | KeyGroup], script_type: Literal['p2sh', 'p2wsh', 'p2sh-p2wsh'] = 'p2wsh', order: Literal['none', 'account', 'derived'] = 'none', sort_key: Callable[[Any], Any] | None = None, network: str = 'mainnet')[source]

Bases: RangedWallet

A script template, its key groups, and the addresses they compute.

property branches: tuple[int, ...]

The receiving and change chains, which is what BIP44 defines.

The two bip32.derive_from_account walks, and the same two BIP32KeyWallet has: every key of every group is an account key, so what a branch means here is the step below one.

descriptor(branch: int = 0, checked_indexes: int = 2) Descriptor[source]

Return the ranged descriptor of a branch, confirmed, or refuse to.

The bridge between this class and DescriptorWallet, and the answer to “what do I hand a monitor, or Bitcoin Core’s importdescriptors, for this wallet”. DescriptorWallet.descriptor is the same question of a wallet that was built from one; this derives the answer, and the module docstring has how.

NoDescriptorError is the refusal, and it is about the wallet: the <n> OP_CSV OP_DROP timelock no miniscript fragment emits, a quorum ordered per index inside a combinator, a legacy p2sh script that is not a bare quorum. Each of those is a script BIP380 to BIP390 does not state, so a caller catching it has to watch the addresses themselves – which is what this class is for – rather than a bug to report.

The key origins are the groups’ own, the same ones a psbt gets: [fingerprint/44h/0h/0h] in front of a key expression is what a hardware signer recognises its key by, it changes no script, and a group given none is written without one – a descriptor BIP380 allows and Core imports, and the same thing an empty hd_key_paths says.

checked_indexes is how many positions the answer is confirmed at before it is handed back: the descriptor’s script is compared with the template’s at each, and a disagreement is a BTClibRuntimeError – the lift was not faithful, which is a failure of this code and not of the caller’s wallet. Two is the floor and the default, being what tells a substituted key from a fixed one; a caller with a committed span of addresses to stand behind passes its length, and pays a derivation of every key at every index for it.

A DescriptorWallet of both chains, where that is what a caller wants, is DescriptorWallet({b: w.descriptor(b) for b in w.branches}) – and the two wallets then answer the same addresses, from the two sources.

property is_watch_only: bool

Whether no key of any group is a private one.

redeem_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a p2sh output at this position commits to.

The template’s own script where the output is a plain p2sh, and the p2wsh program where a p2sh wraps one: what a p2sh input pushes is the pre-image of the hash in the output, which for p2sh-p2wsh is the witness program and not the script that program commits to.

update_psbt_input(psbt: Psbt, vin_i: int, branch: int = 0, index: int = 0) Psbt[source]

Return the psbt with an input told what this position is.

BIP174’s Updater, for an input spending an output of this wallet: the pre-image the output commits to – the redeem script of a p2sh, the witness script of a p2wsh, both where one wraps the other – and the origin of every key the script names, which is what a hardware signer derives its own key from. What still cannot be answered here is the satisfaction: psbt.finalize assembles such an input through the solver its caller passes, no language saying what satisfies this script.

A copy, the psbt handed in being left alone, and the fields of the copy written in place, as Descriptor.update_psbt_input does.

The origins are every key of the template’s, as for an output: which of them a spend will use is what the satisfaction decides, and a signer signs with the keys it holds whatever else it is told about.

What is not filled is what the wallet does not know: the utxo, the sighash type, the signatures. Nor is the script checked against the output being spent – an input may not carry it yet, and Psbt.assert_signable is the role after this one.

update_psbt_output(psbt: Psbt, vout_i: int, branch: int = 0, index: int = 0) Psbt[source]

Return the psbt with an output told what this position is.

The Updater’s other half, and what makes an output recognizable as the wallet’s own: a signer reads the script and the key origins, derives the script for itself, and sees that the money comes back rather than being asked to take it on trust.

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

Every key of the template, and not the quorum a spend will use: an output is not a signing instruction, and what a reader wants of it is the whole script, the branch nobody is spending included.

witness_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a p2wsh output at this position commits to.

The template’s own script, wrapped in a p2sh or not, and b”” for a plain p2sh, where the template script is the redeem script instead.

btclib.wallet.wallet module

What a wallet is asked, in the words every wallet here answers to.

Three classes in this package say “these are my addresses”, and they say it from three different sources – an extended key, an output descriptor, a script template. The questions put to them are one set, and this module is that set: it holds no key, no descriptor and no script of its own.

Wallet is the ledger half – the addresses handed out, what is remembered about each, and whether any private key is held – and KeyWallet, which takes individual keys and has no position to compute them from, is a wallet on that much alone.

RangedWallet is the half addressed by position, which is the branch and index BIP44 puts below an account, and it is where one vocabulary is worth an abstraction:

  • script_pub_key(branch, index) is what the wallet pays to there, and address(branch, index) the same output as text; next_address(branch) walks a branch, and addresses is what has been handed out;

  • redeem_script and witness_script are the two pre-images BIP174 asks an Updater for, empty where the output commits to a key rather than to a script;

  • position_of(script_pub_key) runs the comparison the other way, which is the question a caller gets wrong: an output is this wallet’s when the whole script derived at a position is the script being paid, and never because a key origin’s four-byte fingerprint matches. Descriptor.index_of asks it of one descriptor and answers the index alone; a wallet has branches, so the answer here is the pair. assert_derives runs it over a whole span at once, which is the question a caller has about a list of addresses it wrote down.

What a branch is differs, and the difference is the one thing not hidden: a key wallet and a script wallet derive it, branch/index below the account, so 0 is the receiving chain and 1 the change one, both bound by bip32.derive_from_account; a descriptor wallet has the derivation inside each descriptor already and reads the branch as the label of which one. The chains are the same two chains either way, named rather than derived.

What no wallet here does. No utxos, no balances, no transaction building, no persistence to disk, no encryption at rest. Each of those is a decision this package cannot take on its own: the first three need a view of the chain, which btclib does not have and does not fetch, and the last two need a file format and a key-derivation function that would outlive any release choosing them. Without them a wallet is a pure function of its source – the same source gives the same addresses in the same order, every time – which is what makes it testable and what keeps its whole state in memory, where the caller can see it. A spender is the larger reading of “wallet” and belongs above this, not inside it.

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

class btclib.wallet.wallet.AddressInfo(address: str, script_type: str, der_path: str, branch: int | None = None, index: int | None = None)[source]

Bases: object

What a wallet remembers about one address it has handed out.

No key material, deliberately: this is the record a caller prints, logs and compares, and the private key is one call away behind KeyWallet.prv_key, which reads as the request it is. Frozen for the same reason addresses hands back a tuple – what comes out of a wallet is a copy of what it knows, not a handle on it.

script_type is the wallet’s own spelling: bip44’s four for a key wallet, which is where p2wpkh-p2sh is a script type at all, and how the script becomes an output for the other two.

der_path is the whole path from the master key, so that it can be handed to bip32.derive or written into a psbt key origin as it stands. It is empty wherever there is not one path to write: a key that came into a KeyWallet on its own has none, and a script naming several keys has one per key, which a psbt key origin records and a single field cannot.

branch and index are the position the address was computed from, and None for an address that was not computed from one.

class btclib.wallet.wallet.RangedWallet(network: str = 'mainnet')[source]

Bases: Wallet, ABC

A wallet whose outputs are computed from a branch and an index.

Three methods are the subclass’s to answer – branches, the _script_pub_key at a position, and is_watch_only – and the rest of the surface is written once, here, in terms of those.

Idempotent, all of it: asking for the same position twice computes it twice and records it once, a wallet being a function of its source rather than a generator with a position of its own. What it does remember is how far it has been asked, which is what next_address reads.

address(branch: int = 0, index: int = 0) str[source]

Hand out the address at a position, and remember it.

A script with no address raises rather than recording the “” that names it: a wallet handing out the empty string has handed out nothing, and script_pub_key is what such a wallet answers with.

assert_derives(addresses: Sequence[bytes | str | bytearray | memoryview | ScriptPubKey], branch: int = 0, first_index: int = 0) None[source]

Refuse a span of outputs that is not what this branch derives.

A list of addresses written down – a whitelist, a monitor’s import, the deposit block a counterparty was given – read back against the wallet that is supposed to have derived it: the first one at first_index, the next at the index after it, and so on to the end of the span. What it catches is the list that was written under another key, or under this key before a threshold or a device changed it, or shifted by one position; and what makes it an answer rather than the same call twice is when it is asked – deriving a list and then checking it in the same breath says nothing, and asking it of the file an environment has been operating under says everything.

Two refusals, and each is a different accident. An address that is not what the position derives names the position, what was written and what the wallet computes. Two positions deriving one output is a wallet whose script ignores its index – a span that is then one address repeated, every one of them “correct” at the position it sits at – which the comparison above cannot see and nothing downstream would either.

An empty span is refused too: there is nothing to be right about, and a caller that has written an empty file has not written a span. Outputs are named however the caller holds them, as in position_of, and the wallet’s ledger is left alone – checking what was handed out is not handing it out again.

abstract property branches: tuple[int, ...]

The chains this wallet has outputs on, in ascending order.

BIP44’s two, (0, 1), wherever the branch is a derivation step; whatever the descriptors were given under, where it is a label. position_of searches these and address refuses anything else, so this is also the wallet’s answer to “which chains are there”.

next_address(branch: int = 0) str[source]

Hand out the next address of a branch not yet asked for.

position_of(script_pub_key: bytes | str | bytearray | memoryview | ScriptPubKey, last_index: int = 999) tuple[int, int] | None[source]

Return the position paying to this output, None where none is.

What makes an output this wallet’s, and the only thing that does: the script is computed at each position 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 position is this address” being the question a human has. Anything else raises, and so does a string that names no output, 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.

last_index bounds the search of every branch, 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 package has no view on. The branches are searched in branches order, one whole branch before the next, and the first match wins – two positions paying to one script is a wallet whose source repeats itself, not something this has to choose between.

redeem_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a p2sh output at this position commits to.

BIP174’s PSBT_IN_REDEEM_SCRIPT, and b”” where the output is not a p2sh – the empty answer being what “there is no such script” looks like everywhere else here, as ScriptPubKey.address is “” for a script with no address.

script_pub_key(branch: int = 0, index: int = 0) ScriptPubKey[source]

Return the output this wallet pays to at a position.

witness_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a p2wsh output at this position commits to.

BIP174’s PSBT_IN_WITNESS_SCRIPT, and b”” where the output is not a p2wsh, wrapped in a p2sh or not.

class btclib.wallet.wallet.Wallet(network: str = 'mainnet')[source]

Bases: ABC

The addresses a wallet has handed out, and what it knows of each.

A ledger and nothing else: what puts an address in it is each wallet’s own business – KeyWallet.add takes a key, and RangedWallet.address computes a position – and what it answers is the same either way.

address_info(address: bytes | str | bytearray | memoryview) AddressInfo[source]

Return what the wallet remembers about an address.

A miss raises rather than answering None. A wallet that has not handed an address out has no opinion about it – not “no key”, which is what a watch-only address has – and the two are worth different answers; and every other lookup in btclib raises, so a None here would be the one place a caller has to remember not to use the result. address in wallet is the question that wants a boolean.

A miss is also not evidence that the wallet cannot reach the address: a RangedWallet computes on demand, so an address of its own chains that it has not been asked for yet is a miss. address(branch, index) is what puts one in, and position_of(address) answers whether it is reachable at all.

property addresses: tuple[str, ...]

Every address handed out, in the order it was handed out.

abstract property is_watch_only: bool

Whether the wallet holds no private key at all.

A first-class state rather than a broken one: watching is most of what a wallet does, and every wallet here is asked, so that no caller has to know which kind of source it was built from.

Module contents

Wallets: the addresses one is holding, and what each was computed from.

Three sources of addresses, one vocabulary over them, and the vocabulary is the point: whatever a wallet computes its outputs from, it is asked address(branch, index), script_pub_key(branch, index), next_address(branch) and position_of(script_pub_key), and it remembers what it has handed out.

Four modules make it up, and each imports the ones before it and none after:

  • wallet is that vocabulary and holds no source of its own: Wallet is the ledger, and RangedWallet the half addressed by a branch and an index;

  • key_wallet is the wallets whose output hashes one key – KeyWallet for individual keys and BIP32KeyWallet for a BIP44 account – and they are the two that can sign(address, msg), a single key being a single thing to look up;

  • descriptor_wallet is DescriptorWallet, one Descriptor per chain, which covers every script BIP380 to BIP390 can state, reads the whole pair off BIP389’s <0;1> spelling, and delegates the spend to it;

  • script_wallet is ScriptWallet, a script template with KeyGroup quorums in it, for the wallets no descriptor states – and it imports descriptor_wallet not at all, a wallet of templates being no wallet of descriptors. What it does reach for is btclib.descriptors, one layer below both: ScriptWallet.descriptor lifts the script of a branch back into the expression it is, where there is one, so the wallets that turned out to have a descriptor after all can be handed to a monitor and the rest say so with NoDescriptorError.

The flat surface is this package’s: the classes above are read off btclib.wallet, the four modules being where they are written rather than how they are reached.

class btclib.wallet.AddressInfo(address: str, script_type: str, der_path: str, branch: int | None = None, index: int | None = None)[source]

Bases: object

What a wallet remembers about one address it has handed out.

No key material, deliberately: this is the record a caller prints, logs and compares, and the private key is one call away behind KeyWallet.prv_key, which reads as the request it is. Frozen for the same reason addresses hands back a tuple – what comes out of a wallet is a copy of what it knows, not a handle on it.

script_type is the wallet’s own spelling: bip44’s four for a key wallet, which is where p2wpkh-p2sh is a script type at all, and how the script becomes an output for the other two.

der_path is the whole path from the master key, so that it can be handed to bip32.derive or written into a psbt key origin as it stands. It is empty wherever there is not one path to write: a key that came into a KeyWallet on its own has none, and a script naming several keys has one per key, which a psbt key origin records and a single field cannot.

branch and index are the position the address was computed from, and None for an address that was not computed from one.

class btclib.wallet.BIP32KeyWallet(xkey: BIP32KeyData | bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview = 'm/84h/0h/0h', script_type: Literal['p2pkh', 'p2wpkh-p2sh', 'p2wpkh', 'p2tr'] | None = None)[source]

Bases: KeyWallet, RangedWallet

An extended key at a BIP44 account, and the addresses below it.

xkey is the master key, the account key, or any key between the two; der_path is always the whole account path from the master, m/purpose’/coin_type’/account’, because the purpose lives at the top of it and is what says whether the addresses are p2pkh, p2wpkh- p2sh, p2wpkh or p2tr. What is left of the path below the key is derived once, at construction.

Addresses come from the two unhardened levels BIP44 puts under an account, branch/index with branch 0 receiving and 1 change, which is also what public derivation can walk – so an account xpub is a complete watch-only wallet.

Both bases carry their weight: RangedWallet is the positions, and KeyWallet the signing and the loose keys, which an extended key does not stop a caller from adding.

property branches: tuple[int, ...]

The receiving and change chains, which is what BIP44 defines.

The two bip32.derive_from_account walks and the only two: a third would be a chain no BIP44 wallet looks at, so an address of it is an address nobody else recovers.

property is_watch_only: bool

Whether the wallet holds no private key at all.

An xpub account is the watch-only case; a private key handed to add alongside it is not derived from the account and does not make the account signable, but it does make the wallet hold key material, which is what this answers.

prv_key(address: bytes | str | bytearray | memoryview) str[source]

Return the WIF of the private key signing for an address.

redeem_script(branch: int = 0, index: int = 0) bytes[source]

Return the p2wpkh script a p2wpkh-p2sh output commits to.

The one script type here that has one: the other three pay to a key hash or to a key, so what a spend of them pushes is the key itself and there is no pre-image for a psbt to carry – which is the base class’s answer, and is deferred to rather than repeated.

class btclib.wallet.DescriptorWallet(descriptors: Descriptor | Mapping[int, Descriptor] | Sequence[Descriptor], prv_keys: Mapping[str, str] | None = None)[source]

Bases: RangedWallet

One descriptor per chain, and the wallet questions over the pair.

property branches: tuple[int, ...]

The chains, which are the branches the descriptors came under.

descriptor(branch: int = 0) Descriptor[source]

Return the descriptor of a chain.

classmethod from_account(xkey: BIP32KeyData | bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview, master_fingerprint: bytes | str | bytearray | memoryview | None = None, script_type: Literal['p2pkh', 'p2wpkh-p2sh', 'p2wpkh', 'p2tr'] | None = None, prv_keys: Mapping[str, str] | None = None) DescriptorWallet[source]

Return the wallet of a BIP44 account, both of its chains.

descriptors.account_descriptors is the whole of it, and every argument is that function’s: the account path selects the encoding through its purpose, the master fingerprint is what the key origin needs, and the network is the extended key’s own.

BIP32KeyWallet is the same account without the descriptors, and the two answer the same addresses. What this one adds is what a descriptor carries: the text to hand to Bitcoin Core, the key origins a hardware signer wants, and the psbt Updaters.

classmethod from_descriptor(descriptor: str, network: str = 'mainnet', prv_keys: dict[str, str] | None = None) DescriptorWallet[source]

Return the wallet of a descriptor, checksum verified.

BIP389’s <0;1> is what makes one line of text a whole wallet, and it is expanded positionally: as many branches as the multipath steps have elements, branch 0 taking the first element of every step. A descriptor with no such step is one chain, and a wallet of one branch – which is what a caller watching a single chain has, and not an error.

property is_ranged: bool

Whether any chain describes a range of scripts.

A wallet of descriptors with no wildcard is one address per chain, index 0 and nothing else; Descriptor.is_ranged is the same question of one chain.

property is_watch_only: bool

Whether the wallet was handed no private key.

A parsed descriptor never holds one, descriptors.parse keeping the xpub of an xprv, so the prv_keys mapping is the only key material a wallet of descriptors can have.

position_of(script_pub_key: bytes | str | bytearray | memoryview | ScriptPubKey, last_index: int = 999) tuple[int, int] | None[source]

Return the position paying to this output, None where none is.

Descriptor.index_of per chain, in branches order, rather than a comparison written a second time here: it is the same whole- script comparison, it refuses the same spellings that name no output, and it is what bounds the search of a chain that is not ranged to the one script it has.

redeem_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a sh() at this position embeds.

Which is the redeem script of every shape sh() wraps: the script of a sh(multi()), and the p2wpkh or p2wsh program of the two wrapped segwit forms, that program being what a p2sh input pushes either way.

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

Return the script_sig and witness spending this position.

Descriptor.satisfy with the position resolved, and every word of its contract: the signatures are handed in and assembled rather than verified, a satisfaction short of what the script pops is an error rather than a shorter answer, and spend is what a miniscript branch reads beside the signatures.

update_psbt_input(psbt: Psbt, vin_i: int, branch: int = 0, index: int = 0) Psbt[source]

Return the psbt with an input told what this position is.

Descriptor.update_psbt_input, which is BIP174’s Updater: the scripts and the key origins of the position, for signers to fill in and psbt.finalize to assemble.

update_psbt_output(psbt: Psbt, vout_i: int, branch: int = 0, index: int = 0) Psbt[source]

Return the psbt with an output told what this position is.

Descriptor.update_psbt_output, which refuses unless the output being paid is the very script this position derives: marking an output as the wallet’s own is a claim about where money goes, and the whole script is the only evidence for it. position_of is the same claim asked as a question.

witness_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a wsh() at this position embeds.

Native or wrapped in a sh(), which is the same witness script in the same psbt field: the wrapping shows up in the redeem script and nowhere else.

class btclib.wallet.KeyGroup(threshold: int, keys: Sequence[BIP32KeyData | bytes | str | bytearray | memoryview], verify: bool = False, origins: Sequence[BIP32KeyOrigin | None] | None = None)[source]

Bases: object

A quorum of extended keys, as a template writes it into a script.

threshold of as many keys as are given, which OP_CHECKMULTISIG needs stated twice: what a group expands to at a position is k <key>… n OP_CHECKMULTISIG, with each key derived down the two unhardened levels bip32.derive_from_account walks – so every key here is an account key, exactly as BIP32KeyWallet takes one.

An xprv is as welcome as an xpub and is what makes a wallet not watch-only; what goes in the script is the public key either way.

The order the keys are given in is the order the script carries them in, unless the wallet holding the group says otherwise: ordering is the wallet’s parameter, not the group’s, because the wallets deployed with a per-index order apply it to every quorum of the script.

verify=True writes OP_CHECKMULTISIGVERIFY in place of the last opcode and nothing else about the group – the same choice miniscript makes with its v: wrapper, and the form and_v(v:multi(…), …) compiles to.

origins is where each key comes from – the master fingerprint and the path down to the account key, which is what BIP174 carries and what an extended key below the root cannot say of itself – one entry per key and in the order the keys are given, None for a key whose origin the caller does not have. It changes no script: what reads it is ScriptWallet.update_psbt_input and update_psbt_output, and what a group without it writes into a psbt is nothing.

class btclib.wallet.KeyWallet(keys: Iterable[int | bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint] = (), script_type: Literal['p2pkh', 'p2wpkh-p2sh', 'p2wpkh', 'p2tr'] = 'p2wpkh', network: str = 'mainnet')[source]

Bases: Wallet

Individual keys, the address of each, and who signs for it.

The addresses are handed out by add, one per key, all of them in the script type the wallet was built with; BIP32KeyWallet below is the same thing with an extended key underneath and derivation on top.

A public key is as welcome as a private one and makes that address watch-only. sign then raises, naming the address: the alternative would be a wallet that answers a signing request with something falsy, and every caller that forgets to check has published an unsigned message.

Not a RangedWallet: a key handed over is one address and there is no position it came from, so address(branch, index) would have nothing to compute. BIP32KeyWallet is where the positions are.

add(key: int | bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, script_type: Literal['p2pkh', 'p2wpkh-p2sh', 'p2wpkh', 'p2tr'] | None = None) str[source]

Take one key into the wallet, and return its address.

property is_watch_only: bool

Whether the wallet holds no private key at all.

prv_key(address: bytes | str | bytearray | memoryview) str[source]

Return the WIF of the private key signing for an address.

sign(address: bytes | str | bytearray | memoryview, msg: bytes | str | bytearray | memoryview) Sig[source]

Return the BMS signature of a message, by address.

The address is the argument bms.sign has no way to resolve on its own; everything after the lookup is bms.sign’s, this method signing nothing itself. The resolved address is passed on to it as well as the key, which is what makes the recovery flag name the right address type – BIP137’s 35..38 for the wrapped segwit spelling and 39..42 for the native one – rather than the compressed p2pkh flag that a key alone would produce.

BMS and no other scheme, which is what leaves p2tr out: a caller wanting BIP322 for one of these addresses passes prv_key and the address to btclib.bip322.sign, which signs for all four script types this module hands out.

class btclib.wallet.RangedWallet(network: str = 'mainnet')[source]

Bases: Wallet, ABC

A wallet whose outputs are computed from a branch and an index.

Three methods are the subclass’s to answer – branches, the _script_pub_key at a position, and is_watch_only – and the rest of the surface is written once, here, in terms of those.

Idempotent, all of it: asking for the same position twice computes it twice and records it once, a wallet being a function of its source rather than a generator with a position of its own. What it does remember is how far it has been asked, which is what next_address reads.

address(branch: int = 0, index: int = 0) str[source]

Hand out the address at a position, and remember it.

A script with no address raises rather than recording the “” that names it: a wallet handing out the empty string has handed out nothing, and script_pub_key is what such a wallet answers with.

assert_derives(addresses: Sequence[bytes | str | bytearray | memoryview | ScriptPubKey], branch: int = 0, first_index: int = 0) None[source]

Refuse a span of outputs that is not what this branch derives.

A list of addresses written down – a whitelist, a monitor’s import, the deposit block a counterparty was given – read back against the wallet that is supposed to have derived it: the first one at first_index, the next at the index after it, and so on to the end of the span. What it catches is the list that was written under another key, or under this key before a threshold or a device changed it, or shifted by one position; and what makes it an answer rather than the same call twice is when it is asked – deriving a list and then checking it in the same breath says nothing, and asking it of the file an environment has been operating under says everything.

Two refusals, and each is a different accident. An address that is not what the position derives names the position, what was written and what the wallet computes. Two positions deriving one output is a wallet whose script ignores its index – a span that is then one address repeated, every one of them “correct” at the position it sits at – which the comparison above cannot see and nothing downstream would either.

An empty span is refused too: there is nothing to be right about, and a caller that has written an empty file has not written a span. Outputs are named however the caller holds them, as in position_of, and the wallet’s ledger is left alone – checking what was handed out is not handing it out again.

abstract property branches: tuple[int, ...]

The chains this wallet has outputs on, in ascending order.

BIP44’s two, (0, 1), wherever the branch is a derivation step; whatever the descriptors were given under, where it is a label. position_of searches these and address refuses anything else, so this is also the wallet’s answer to “which chains are there”.

next_address(branch: int = 0) str[source]

Hand out the next address of a branch not yet asked for.

position_of(script_pub_key: bytes | str | bytearray | memoryview | ScriptPubKey, last_index: int = 999) tuple[int, int] | None[source]

Return the position paying to this output, None where none is.

What makes an output this wallet’s, and the only thing that does: the script is computed at each position 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 position is this address” being the question a human has. Anything else raises, and so does a string that names no output, 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.

last_index bounds the search of every branch, 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 package has no view on. The branches are searched in branches order, one whole branch before the next, and the first match wins – two positions paying to one script is a wallet whose source repeats itself, not something this has to choose between.

redeem_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a p2sh output at this position commits to.

BIP174’s PSBT_IN_REDEEM_SCRIPT, and b”” where the output is not a p2sh – the empty answer being what “there is no such script” looks like everywhere else here, as ScriptPubKey.address is “” for a script with no address.

script_pub_key(branch: int = 0, index: int = 0) ScriptPubKey[source]

Return the output this wallet pays to at a position.

witness_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a p2wsh output at this position commits to.

BIP174’s PSBT_IN_WITNESS_SCRIPT, and b”” where the output is not a p2wsh, wrapped in a p2sh or not.

class btclib.wallet.ScriptWallet(template: Sequence[int | str | bytes | bytearray | memoryview | KeyGroup], script_type: Literal['p2sh', 'p2wsh', 'p2sh-p2wsh'] = 'p2wsh', order: Literal['none', 'account', 'derived'] = 'none', sort_key: Callable[[Any], Any] | None = None, network: str = 'mainnet')[source]

Bases: RangedWallet

A script template, its key groups, and the addresses they compute.

property branches: tuple[int, ...]

The receiving and change chains, which is what BIP44 defines.

The two bip32.derive_from_account walks, and the same two BIP32KeyWallet has: every key of every group is an account key, so what a branch means here is the step below one.

descriptor(branch: int = 0, checked_indexes: int = 2) Descriptor[source]

Return the ranged descriptor of a branch, confirmed, or refuse to.

The bridge between this class and DescriptorWallet, and the answer to “what do I hand a monitor, or Bitcoin Core’s importdescriptors, for this wallet”. DescriptorWallet.descriptor is the same question of a wallet that was built from one; this derives the answer, and the module docstring has how.

NoDescriptorError is the refusal, and it is about the wallet: the <n> OP_CSV OP_DROP timelock no miniscript fragment emits, a quorum ordered per index inside a combinator, a legacy p2sh script that is not a bare quorum. Each of those is a script BIP380 to BIP390 does not state, so a caller catching it has to watch the addresses themselves – which is what this class is for – rather than a bug to report.

The key origins are the groups’ own, the same ones a psbt gets: [fingerprint/44h/0h/0h] in front of a key expression is what a hardware signer recognises its key by, it changes no script, and a group given none is written without one – a descriptor BIP380 allows and Core imports, and the same thing an empty hd_key_paths says.

checked_indexes is how many positions the answer is confirmed at before it is handed back: the descriptor’s script is compared with the template’s at each, and a disagreement is a BTClibRuntimeError – the lift was not faithful, which is a failure of this code and not of the caller’s wallet. Two is the floor and the default, being what tells a substituted key from a fixed one; a caller with a committed span of addresses to stand behind passes its length, and pays a derivation of every key at every index for it.

A DescriptorWallet of both chains, where that is what a caller wants, is DescriptorWallet({b: w.descriptor(b) for b in w.branches}) – and the two wallets then answer the same addresses, from the two sources.

property is_watch_only: bool

Whether no key of any group is a private one.

redeem_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a p2sh output at this position commits to.

The template’s own script where the output is a plain p2sh, and the p2wsh program where a p2sh wraps one: what a p2sh input pushes is the pre-image of the hash in the output, which for p2sh-p2wsh is the witness program and not the script that program commits to.

update_psbt_input(psbt: Psbt, vin_i: int, branch: int = 0, index: int = 0) Psbt[source]

Return the psbt with an input told what this position is.

BIP174’s Updater, for an input spending an output of this wallet: the pre-image the output commits to – the redeem script of a p2sh, the witness script of a p2wsh, both where one wraps the other – and the origin of every key the script names, which is what a hardware signer derives its own key from. What still cannot be answered here is the satisfaction: psbt.finalize assembles such an input through the solver its caller passes, no language saying what satisfies this script.

A copy, the psbt handed in being left alone, and the fields of the copy written in place, as Descriptor.update_psbt_input does.

The origins are every key of the template’s, as for an output: which of them a spend will use is what the satisfaction decides, and a signer signs with the keys it holds whatever else it is told about.

What is not filled is what the wallet does not know: the utxo, the sighash type, the signatures. Nor is the script checked against the output being spent – an input may not carry it yet, and Psbt.assert_signable is the role after this one.

update_psbt_output(psbt: Psbt, vout_i: int, branch: int = 0, index: int = 0) Psbt[source]

Return the psbt with an output told what this position is.

The Updater’s other half, and what makes an output recognizable as the wallet’s own: a signer reads the script and the key origins, derives the script for itself, and sees that the money comes back rather than being asked to take it on trust.

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

Every key of the template, and not the quorum a spend will use: an output is not a signing instruction, and what a reader wants of it is the whole script, the branch nobody is spending included.

witness_script(branch: int = 0, index: int = 0) bytes[source]

Return the script a p2wsh output at this position commits to.

The template’s own script, wrapped in a p2sh or not, and b”” for a plain p2sh, where the template script is the redeem script instead.

class btclib.wallet.Wallet(network: str = 'mainnet')[source]

Bases: ABC

The addresses a wallet has handed out, and what it knows of each.

A ledger and nothing else: what puts an address in it is each wallet’s own business – KeyWallet.add takes a key, and RangedWallet.address computes a position – and what it answers is the same either way.

address_info(address: bytes | str | bytearray | memoryview) AddressInfo[source]

Return what the wallet remembers about an address.

A miss raises rather than answering None. A wallet that has not handed an address out has no opinion about it – not “no key”, which is what a watch-only address has – and the two are worth different answers; and every other lookup in btclib raises, so a None here would be the one place a caller has to remember not to use the result. address in wallet is the question that wants a boolean.

A miss is also not evidence that the wallet cannot reach the address: a RangedWallet computes on demand, so an address of its own chains that it has not been asked for yet is a miss. address(branch, index) is what puts one in, and position_of(address) answers whether it is reachable at all.

property addresses: tuple[str, ...]

Every address handed out, in the order it was handed out.

abstract property is_watch_only: bool

Whether the wallet holds no private key at all.

A first-class state rather than a broken one: watching is most of what a wallet does, and every wallet here is asked, so that no caller has to know which kind of source it was built from.