btclib package

Subpackages

Submodules

btclib.alias module

The type aliases of the public API, and the input conventions they name.

Octets and String below are the same union, so mypy cannot tell one from the other: passing a text string where a hex-string is expected is a type error this file names but no checker can catch. The distinction is enforced at run time instead, by the converter each function calls on its way in – bytes_from_octets for Octets, str_from_string for String – and it is documented here because that is the only place it can be read as one piece.

Making them NewTypes would let mypy separate them, at the cost of every caller having to wrap its literals: Octets(“deadbeef”) instead of “deadbeef”, throughout a public API whose whole style is to accept whatever is convertible. That is a different library, not a fix to this one.

class btclib.alias.HashObject(*args, **kwargs)[source]

Bases: Protocol

The slice of a hashlib object this library reads, as a Protocol.

Structural: anything hashlib.new returns satisfies it, and the members carry hashlib’s own meanings.

property block_size: int

Return the internal block length in bytes.

copy() HashObject[source]

Return a clone that can absorb independently.

digest() bytes[source]

Return the digest of everything absorbed so far.

property digest_size: int

Return the digest length in bytes.

hexdigest() str[source]

Return the digest as a hex string.

property name: str

Return the name hashlib.new would accept.

update(data: Any, /) None[source]

Absorb more data, as hashlib’s update does.

btclib.alias.Octets = bytes | str | bytearray | memoryview

Bytes, or the hex-string that decodes to them, wherever raw bytes are asked for.

btclib.alias.TaprootScriptTree

A leaf, a one-element list holding a TaprootLeaf, or a branch, a two-element list of subtrees.

alias of list[tuple[int, list[int | str | bytes | bytearray | memoryview]] | TaprootScriptTree]

btclib.amount module

Monetary amounts: satoshi ints and BTC Decimals, never floats.

A BTC amount is an int number of satoshi (1 BTC is 100_000_000) or a Decimal with up to 8 decimals, e.g. Decimal(“0.12345678”). Not a float: binary floating point cannot hold most decimal fractions exactly (1.1 + 2.2 != 3.3), so a float between a rate and the satoshi it owes is a rounding error waiting for money to measure it. The functions here convert between the two spellings and refuse what no output can carry.

Amounts are never negative, here as in the protocol.

btclib.amount.btc_from_sats(amount: int) Decimal[source]

Return the BTC Decimal equivalent of the provided satoshi amount.

btclib.amount.sats_from_btc(amount: Decimal) int[source]

Return the satoshi equivalent of the provided BTC amount.

btclib.amount.valid_btc_amount(amount: Any, dust: Decimal = Decimal('0')) Decimal[source]

Return the BTC amount as a Decimal, refusing what no output holds.

None reads as zero, and anything str() renders as a decimal number is accepted. Refused: an amount below dust or above the 21 million cap, and one with more than 8 decimals, no output being able to carry a fraction of a satoshi.

btclib.amount.valid_sats_amount(amount: Any, dust: int = 0) int[source]

Return the satoshi amount as int, if valid and not less than dust.

btclib.b32 module

Segwit address functions.

The bitcoin semantics. p2wpkh, p2wsh and p2tr addresses: the witness version, the human-readable part of each network, and the length rules a witness program has to satisfy.

The encoding itself is btclib.bech32, which knows nothing about bitcoin, and the rule between the two is that direction: this module imports bech32, never the other way round. base58 and b58 are the same pair for the base58 address encoding.

The whole surface is exported, the two facilities below the addresses included: power_of_2_base_conversion is the convertbits of the reference implementation, which a caller reading or writing a witness program in five-bit groups needs as much as this module does, and bytes_from_witness_program is BIP141’s length rule, which btclib.b58 asks for the p2sh-wrapped forms.

Some of these functions are originally from https://github.com/sipa/bech32/tree/master/ref/python, with the following modifications:

  • type annotated Python3

  • avoided returning None or (None, None), throwing Exceptions instead

  • detailed error messages and extended safety checks

  • check that bech32 addresses are not longer than 90 characters, a bound bech32.decode deliberately leaves to this module

btclib.b32.address_from_witness(wit_ver: int, wit_prg: bytes | str | bytearray | memoryview, network: str = 'mainnet') str[source]

Encode a bech32 native segwit address from the witness.

btclib.b32.bytes_from_witness_program(wit_ver: int, wit_prg: bytes | str | bytearray | memoryview) bytes[source]

Return the witness program, refusing what BIP141 does not define.

A version outside 0..16, a program outside 2..40 bytes, or a v0 program that is neither 20 nor 32 bytes is refused.

btclib.b32.is_segwit_prefixed(addr: bytes | str | bytearray | memoryview) bool[source]

Answer whether the string starts as a bech32 address of any network.

The prefix alone – hrp and the 1 separator – is read; whether the rest decodes is witness_from_address’s answer.

btclib.b32.p2tr(output_key: bytes | str | bytearray | memoryview, network: str = 'mainnet') str[source]

Return the p2tr bech32 address corresponding to a taproot output key.

btclib.b32.p2wpkh(key: int | bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, network: str | None = None) str[source]

Return the p2wpkh bech32 address corresponding to a public key.

btclib.b32.p2wsh(script_pub_key: bytes | str | bytearray | memoryview, network: str = 'mainnet') str[source]

Return the p2wsh bech32 address corresponding to a script_pub_key.

btclib.b32.power_of_2_base_conversion(data: Iterable[int], from_bits: int, to_bits: int, pad: bool = True) list[int][source]

Convert a power-of-two digit sequence to another power-of-two base.

btclib.b32.witness_from_address(b32addr: bytes | str | bytearray | memoryview) tuple[int, bytes, str][source]

Return the witness from a bech32 native segwit address.

The returned data structure is: version, program, network.

btclib.b58 module

Base58 address and WIF functions.

The bitcoin semantics. Base58 encoding of public keys and scripts as addresses, and of private keys as WIFs: the version prefixes, the networks, p2pkh, p2sh, and the p2sh-wrapped segwit forms.

The encoding itself is btclib.base58, which knows nothing about bitcoin, and the rule between the two is that direction: this module imports base58, never the other way round. bech32 and b32 are the same pair for the segwit address encoding.

btclib.b58.address_from_h160(script_type: Literal['nulldata', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2tr', 'p2wpkh', 'p2wsh', 'unknown', 'witness_unknown'], h160: bytes | str | bytearray | memoryview, network: str = 'mainnet') str[source]

Return a base58 address from the payload.

btclib.b58.h160_from_address(b58addr: bytes | str | bytearray | memoryview) tuple[Literal['nulldata', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2tr', 'p2wpkh', 'p2wsh', 'unknown', 'witness_unknown'], bytes, str][source]

Return the payload from a base58 address.

btclib.b58.p2pkh(key: int | bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, network: str | None = None, compressed: bool | None = None) str[source]

Return the p2pkh base58 address corresponding to a public key.

btclib.b58.p2sh(script_pub_key: bytes | str | bytearray | memoryview, network: str = 'mainnet') str[source]

Return the p2sh base58 address corresponding to a script_pub_key.

btclib.b58.p2wpkh_p2sh(key: int | bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, network: str | None = None) str[source]

Return the base58 p2sh-wrapped address of a p2wpkh.

btclib.b58.p2wsh_p2sh(redeem_script: bytes | str | bytearray | memoryview, network: str = 'mainnet') str[source]

Return the base58 p2sh-wrapped address of a p2wsh.

btclib.b58.wif_from_prv_key(prv_key: int | bytes | str | bytearray | memoryview | BIP32KeyData, network: str | None = None, compressed: bool | None = None) str[source]

Return the WIF encoding of a private key.

btclib.base58 module

Base58 encoding and decoding functions.

The codec. This module is base58 itself, with no bitcoin in it: bytes in, ascii out, a checksum, and nothing that knows what the bytes mean. What gives them meaning is btclib.b58 – WIF, p2pkh, p2sh, the version prefixes and the networks – and the rule between the two is that direction: b58 imports base58, never the other way round.

The split is the one the standard library draws between base64 and whatever uses it, and the two names are meant to be read as a pair: base58 the encoding, b58 the bitcoin semantics. bech32 and b32 are the same pair for the segwit address encoding.

Binary-to-text encoding schemes are used to transport binary data across channels designed to deal with textual data. In Bitcoin they are mostly used to represent large integers as alphanumeric text.

Base58 is similar to Base64, which uses 10 digits, 26 lowercase characters, 26 uppercase characters, ‘+’ (plus sign), and ‘/’ (forward slash). Base58 omits the similar-looking letters 0 (zero), O (capital o), I (capital i), and l (lower case L) to avoid ambiguity when printed; moreover, it removes ‘+’ and ‘/’ so that a double-click does select the whole string.

Base58Check is the checksummed version of Base58, using hash256(v)[:4] as checksum suffix before encoding; at the decoding stage the checksum validity ensure data integrity.

This implementation of Base58 and Base58Check is originally from https://github.com/keis/base58, with the following modifications:

  • type annotated Python3

  • using native Python3 int.from_bytes() and i.to_bytes()

  • added optional check on output size for decode()

  • interface mimics the native Python3 base64 interface, i.e. it supports encoding bytes-like objects to ASCII bytes, and decoding ASCII bytes-like objects or ASCII strings to bytes.

btclib.base58.decode(v: bytes | str | bytearray | memoryview, out_size: int | None = None) bytes[source]

Decode a Base58Check encoded bytes-like object or ASCII string.

Optionally, it also ensures required output size.

btclib.base58.encode(v: bytes | str | bytearray | memoryview, in_size: int | None = None) bytes[source]

Encode a bytes-like object using Base58Check.

btclib.bech32 module

Bech32(m) encoding and decoding functions.

The codec. This module is bech32 and bech32m themselves, with no bitcoin in them: data in, a checksummed string out, and nothing that knows what a witness program is. What gives it meaning is btclib.b32 – p2wpkh, p2wsh, p2tr, the witness version and the network prefixes – and the rule between the two is that direction: b32 imports bech32, never the other way round. base58 and b58 are the same pair for the base58 address encoding.

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

This implementation of bech32 is originally from https://github.com/sipa/bech32/tree/master/ref/python, with the following modifications:

  • the reference’s single segwit_addr.py file is split in two: the codec here, the bitcoin semantics in b32.py

  • type annotated Python3

  • avoided returning (None, None), throwing Exceptions instead

  • no 90-character string limit, a bitcoin address bound that b32 enforces instead

  • the checksum’s inner loop is a 32-entry table of tap combinations, the same taps selected by a lookup rather than by five conditional XORs per character

  • detailed error messages

  • interface mimics the native Python3 base64 interface, i.e. it supports encoding bytes-like objects to ASCII bytes, and decoding ASCII bytes-like objects or ASCII strings to bytes.

btclib.bech32.decode(bech: bytes | str | bytearray | memoryview, m: int | None = None) tuple[str, list[int]][source]

Return (hrp, data) from a bech32 string, verifying its checksum.

m picks bech32 or bech32m; None reads it off the first data value, the witness version choosing the constant per BIP350.

btclib.bech32.encode(hrp: str, data: list[int], m: int | None = None) bytes[source]

Compute a bech32 string given HRP and data values.

Every value is one 5-bit digit, and each is checked rather than left to the alphabet lookup to fail: _ALPHABET[-1] is “l” and _ALPHABET[-32] is “q”, Python indexing from the end, so a negative digit writes a different address and says nothing at all. A digit above 31 at least raises, and raises IndexError; a float raises TypeError. Neither is caught by the except BTClibValueError this library invites.

The pair of checks walks the digits a second time, which is a fraction of what encoding them costs and a smaller fraction of the key derivation that produced them – an address is encoded once, never in an inner loop.

btclib.bip21 module

BIP21 payment URI: bitcoin:<address>[?amount=&label=&message=].

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

The gap between what a user pastes or scans and the typed surface this library offers. It is pure string handling and it sits above the encodings: the address goes to b32/b58, the amount to amount, and the network type to network; nothing else in btclib imports this module, so the dependency graph the README draws gains no edge.

Four rules carry the whole of it, and each is the thing an implementation gets wrong:

  • an unknown parameter whose name starts with req- makes the URI invalid, and only those: an unknown parameter without the prefix is ignored. That is the entire forward-compatibility story of the scheme

  • amount is decimal BTC, not satoshi and never a float

  • a repeated key is an error, not last-one-wins

  • label and message are percent-encoded, and a bech32 address is legally uppercase – the QR-code case – so nothing here lowercases what it hands to the address decoders

class btclib.bip21.Bip21(address: str, amount: Any = None, label: str | None = None, message: str | None = None, others: Mapping[str, str] | None = None, *, check_validity: bool = True)[source]

Bases: object

A parsed bitcoin: payment URI.

others holds the parameters BIP21 says to ignore: kept rather than dropped, because “ignore” is a rule about not rejecting them, and a caller that recognises one is better served by being handed it. Nothing here treats them as meaningful.

assert_valid() None[source]

Refuse a URI without a decodable address, or a bad amount.

property network_type: Literal['main', 'test']

Return “main” or “test”, what the address says about its chain.

Not the network, which this was called and could not deliver: a tb1 address is testnet, signet and testnet4 at once, and a 0x6f base58 one is those three and regtest. “main or test” is the whole of what an address carries – issue #207 – and it is the question a payment URI actually raises, a payer needing to know that a request is not for real bitcoin.

classmethod parse(uri: str, *, check_validity: bool = True) Bip21[source]

Return the Bip21 of a bitcoin: URI.

A str and not the String the octet decoders take: a URI is text, and a BTClibTypeError for what is not it – the rule CONTRIBUTING.md states, this parameter declaring one type.

serialize(*, check_validity: bool = True) str[source]

Return the bitcoin: URI of this payment request.

btclib.bip322 module

BIP322 signed messages: a script is satisfied, not a key recovered.

ecc.bms signs with a key and lets the verifier recover it, which is why it can only speak about the addresses that are a public key hash – p2pkh, and by Electrum’s extension the two p2wpkh spellings. A taproot address is a tweaked BIP340 key and a p2wsh address is a hash of a script, and no recovery flag names either.

BIP322 asks the other question. The address becomes the script_pub_key of a virtual output, and the signature is whatever spends it: a witness stack, a whole transaction, or a psbt. Verification is then the script interpreter – script.engine – rather than a key comparison, so every script btclib can run is a script that can sign, multisig and timelocks included. The signature commits to the public key too, which the compact ECDSA of BMS does not.

The two virtual transactions are the whole of the construction:

  • to_spend pays 0 satoshi to the address, and is spendable by nobody: its single input is the null outpoint of a coinbase, and its script_sig is OP_0 PUSH32 message_hash, the BIP340-tagged hash of the message under the BIP0322-signed-message tag. Message and address are therefore both inside its txid

  • to_sign spends that output and pays 0 satoshi to an OP_RETURN. Its witness – or script_sig – is the signature

A verifier rebuilds to_spend from the message and the address it was given, so a signature made for another message or another address spends a different output and satisfies nothing. Neither transaction can be broadcast, to_spend’s own input being unspendable.

Three encodings, all base64 with a three-character prefix in front of it, and Sig holds whichever came:

  • smp, the simple variant: the witness stack alone, which is enough where the rest of to_sign is fixed – native segwit, i.e. p2wpkh, p2wsh and p2tr

  • ful, the full variant: the whole to_sign transaction, which is what a script_sig (p2pkh, p2sh), a version or a lock time needs

  • pof, the proof of funds variant: a finalized psbt of to_sign, carrying further inputs the signer also controls, with the utxo of each. Whether those outputs exist and are unspent is the chain’s answer and not this module’s

A signature with no prefix is read as simple, which BIP322 allows for compatibility with the implementations that predate the prefixes. The legacy variant is BMS: assert_as_valid hands a 65-byte compact signature to ecc.bms, and only for a p2pkh address, the BIP restricting it to that one.

Verification answers three states, as the BIP does. Valid is a return; invalid is a BTClibValueError, whatever failed being what it says; and inconclusive is InconclusiveError, which is the state for a signature that today’s rules cannot judge – a to_sign whose version is neither 0 nor 2, an upgradeable NOP, a witness program of a version this library does not know. verify collapses all three to a boolean, and an inconclusive signature is not a valid one.

What is enforced is BIP322’s list, through the engine’s own flags: the consensus rules, then LOW_S, STRICTENC, NULLFAIL, MINIMALDATA, CLEANSTACK, MINIMALIF and CONST_SCRIPTCODE for the required ones, and the DISCOURAGE_ family for the upgradeable ones – the backticks because a name ending in an underscore is a link reference to docutils, which sphinx runs with -W. The one rule of the list that is not a flag is “all signatures MUST use SIGHASH_ALL”, which no set of flags can express: it is a rule about the stack elements the interpreter consumed as signatures, and which elements those were is not readable from the witness – the control block of a single-leaf taproot tree is 65 bytes, exactly the shape of a BIP340 signature with an explicit hash type. So the engine reports them, through verify_input’s hash_types, and the rule is enforced over what it reports.

The fourth flow of the BIP is not a signature encoding at all: a multisig signature is coordinated as a psbt, and the psbt says what is being signed through PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE = 0x09, the global field BIP322 adds to BIP174’s registry. Psbt.signed_message holds it; here to_sign_psbt is the Creator that writes one and signed_message the question a Signer puts to what it received – “is this a BIP322 psbt, and for which message” – so that a device shows “signing message m for address A” rather than “spending 0 satoshi”, which is the promise the field exists to let it keep.

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

class btclib.bip322.Sig(payload: Witness | Tx | Psbt)[source]

Bases: object

A BIP322 signature: what the variant carries, and nothing beside it.

One field, because the variant is not a second fact: a witness stack is a simple signature, a transaction a full one, and a psbt a proof of funds, so variant reads the payload rather than being stored where it could disagree with it.

There is no parse, and that is the format rather than an omission: the three payloads are three unrelated serializations and only the prefix of the text form says which one follows, so b64decode is where a signature is read and b64encode where it is written.

classmethod b64decode(data: bytes | str | bytearray | memoryview, *, check_validity: bool = True) Sig[source]

Return the signature the text encodes, whichever variant it is.

A prefix that is not one of the three is not stripped and not guessed at: it is base64 or it is nothing, and the three characters are then part of the witness stack, which is what refuses it. Absent a prefix the signature is simple, which is what BIP322 says a verifier may assume of the implementations that predate them.

b64encode(*, check_validity: bool = True) str[source]

Return the signature as BIP322 writes it: prefix, then base64.

serialize(*, check_validity: bool = True) bytes[source]

Return the payload’s own serialization, without the prefix.

The transaction is serialized with its witness, that being where a full signature keeps the signature.

property variant: str

Return the three-character prefix this signature is written with.

btclib.bip322.assert_as_valid(msg: bytes | str | bytearray | memoryview, addr: bytes | str | bytearray | memoryview, sig: Sig | bytes | str | bytearray | memoryview, *, legacy: bool = True) None[source]

Refuse a signature that does not spend the address’s own output.

The message and the address rebuild to_spend here, so what the signature is checked against is never what it claims to be: a signature for another message, or for another address, satisfies a script that is not this one.

legacy accepts a BMS signature – the 65-byte compact one, with no prefix – for a p2pkh address, which is the compatibility BIP322 keeps and the only address type it keeps it for. False refuses it, for a caller that wants BIP322 proper and nothing else.

Raises InconclusiveError for a signature that is not invalid and cannot be judged valid; see the module docstring for that state.

btclib.bip322.assert_signed_message(psbt: Psbt) bytes[source]

Return the message this psbt is the BIP322 challenge of, or refuse.

The Signer’s own question, and it is not “does the psbt carry a message”: a message that the transaction does not commit to is what a device showing “signing message m” would be lying about. So the field is one of five conditions and the other four are the psbt being a to_sign – an input to spend, its outpoint being output 0 of the to_spend this very message and this very challenge script rebuild, and the one output that pays nothing to an OP_RETURN.

The challenge script comes from the psbt itself, which is what leaves the caller nothing to be told: assert_as_valid is handed an address and checks a signature against it, where a Signer has not been told an address and is working out what it would be signing.

btclib.bip322.message_hash(msg: bytes | str | bytearray | memoryview) bytes[source]

Return the BIP340-tagged hash of the message, under BIP322’s tag.

The message enters as it is: no magic string around it, no length in front of it, and no terminator after it. The tag is what keeps this hash from meaning anything under any other protocol, which is the job BMS gives to its “Bitcoin Signed Message:” envelope.

btclib.bip322.sign(msg: bytes | str | bytearray | memoryview, prv_key: int | bytes | str | bytearray | memoryview | BIP32KeyData, addr: bytes | str | bytearray | memoryview) Sig[source]

Return the BIP322 signature of a message for a single-key address.

The address is the argument and not something worked out from the key, one key owning an address of each type: it is the challenge being signed, and BIP322 has no default for it.

p2pkh, p2wpkh, p2sh-p2wpkh and p2tr are what one private key satisfies on its own, so they are what this signs; the taproot case is the key path, with no script tree. The variant follows the BIP: simple where the address is native segwit and the rest of to_sign is therefore fixed, full where a script_sig has to be carried.

Any other script – multisig, a script path, a time lock – is a Psbt of to_sign signed and finalized by btclib.psbt, or a Descriptor.satisfy over the signatures it needs, and then a Sig of what comes out. This function is the case that needs neither.

btclib.bip322.signed_message(psbt: Psbt) bytes | None[source]

Return the message the psbt signs, or None if it signs no message.

assert_signed_message collapsed to what a signing device does with it: a message to show in place of the spend, or nothing and the spend as usual. None is both “no such field” and “a field the transaction does not bear out”, the second being the one worth an exception, so a caller that has to tell them apart asks the other one and reads what it says.

btclib.bip322.to_sign(to_spend_tx: Tx, script_sig: bytes | str | bytearray | memoryview = b'', witness: Witness | None = None, *, version: int = 0, lock_time: int = 0, sequence: int = 0, extra_inputs: list[TxIn] | None = None) Tx[source]

Return the virtual transaction that spends to_spend_tx.

The signature is script_sig, witness, or both: what satisfies the challenge script, whichever half of an input carries it.

The three keyword arguments are the fields the full variant may set and the simple variant may not, all three of them 0 there: a version of 2 and a lock time for a CHECKLOCKTIMEVERIFY script, a sequence for a CHECKSEQUENCEVERIFY one. extra_inputs are the outputs a proof of funds shows control of, appended after the one input every signature has.

btclib.bip322.to_sign_psbt(msg: bytes | str | bytearray | memoryview, addr: bytes | str | bytearray | memoryview) Psbt[source]

Return the psbt a Creator hands the signers of this challenge.

BIP322’s fourth flow: a signature that several keys make together is coordinated as a psbt, so what a Signer receives is the unsigned to_sign rather than a witness stack to fill in. Two fields make it one – the message, in the global PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE, and the output being spent, without which no signature can be made – and signed_message is the question this answers on the other side.

The whole of to_spend goes in as the non-witness utxo rather than its one output as a witness utxo: it is the answer for a challenge script of any type, where a witness utxo is the answer for the segwit ones alone, and the transaction is virtual but it is a transaction. Nothing is signed here, and nothing is finalized: what comes back is what a Signer signs, psbt.sign and psbt.finalize being the roles that follow, and Sig of the finalized psbt the proof-of-funds encoding.

btclib.bip322.to_spend(msg: bytes | str | bytearray | memoryview, script_pub_key: bytes | str | bytearray | memoryview) Tx[source]

Return the virtual transaction the message and the script commit to.

Nobody can spend it and nobody can broadcast it: its one input is the null outpoint a coinbase carries, and a coinbase is valid in a block and nowhere else. What it is for is its txid, which the message hash in its script_sig and the challenge script in its output both enter – so a to_sign built on this txid is a signature for this message and this address alone.

btclib.bip322.verify(msg: bytes | str | bytearray | memoryview, addr: bytes | str | bytearray | memoryview, sig: Sig | bytes | str | bytearray | memoryview, *, legacy: bool = True) bool[source]

Verify the BIP322 signature of a message for an address.

False for an inconclusive signature as well as for an invalid one: the two states are worth telling apart, and assert_as_valid is where they are, but neither of them is a signature that verified.

btclib.bip44 module

BIP44 address: an extended key and m/purpose/coin/account/change/index.

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

The composition every wallet performs and no single call here did: bip32.derive walks the path, b58 and b32 encode the address, and the purpose level – BIP43’s, the first of the five – says which of the four encodings the path means. Nothing in this module derives or encodes anything itself; what it adds is the mapping that makes a path unambiguous, and the two checks that keep it honest.

The module sits above script, and taproot is the reason: a p2tr address encodes the tweaked output key of BIP341, which script.taproot.output_pubkey computes, and bip32 is below script and may not import it. slip132 sits beside it at the top level for a narrower version of the same shape: it needs b58 and b32, which import bip32, so it cannot live inside the package whose keys it derives addresses from either.

What imports this module is what the mapping and the checks are for, and in one direction only: wallet takes the encoders and the purpose lookup rather than keeping a second copy of either, and descriptors takes the path checks and the same lookup for the account descriptor pair it builds. Neither is imported back – a descriptor is what a wallet exports, and this is what a path means.

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

Return the address of a BIP44 derivation path.

der_path is the whole five-level path, m/purpose’/coin_type’/account’/change/address_index, in any spelling bip32.derive accepts; xkey is the extended key it starts from, which may be the master key or any key already partway down it – an account xpub, typically, the depth saying how much of the path is behind it.

The purpose selects the encoding: 44 is p2pkh, 49 p2wpkh-p2sh, 84 p2wpkh, 86 p2tr. A purpose outside that mapping raises, unless script_type names one of those four encodings, which then overrides the mapping for known purposes too.

The network is the extended key’s own; the coin type has to agree with it, 0 for mainnet and 1 for any test network, or the path and the key are describing different chains and neither wins.

btclib.bip85 module

BIP85 deterministic entropy from a BIP32 keychain.

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

One root key, many wallets. A fully hardened path off a BIP32 root reaches a child private key k, and HMAC-SHA512(key=”bip-entropy-from-k”, msg=k) turns it into 512 bits of entropy; the path says which application the entropy is for, and the application takes as many of those bits as it needs and truncates the rest. So one backup stands behind a BIP39 wallet, a Bitcoin Core hdseed and a keychain of its own, none of which shares a key with the others.

The HMAC is what makes the entropy hardened whatever the path was: BIP85 mandates hardened derivation but cannot enforce it, and a child key used both as a key and as entropy would otherwise leak the one through the other.

The module sits at the top level, beside bip44 and slip132, and for the same reason: the applications below need b58 for a WIF, b32 for a bech32-encoded key and mnemonic.bip39 for a sentence, and all of those are above bip32, which btclib/bip32/ may not import back. Nothing in the library imports this module.

entropy_from_der_path is the derivation itself and answers for any path, the applications no function here formats included. Every application BIP85 defines is formatted beside it: 39’ (a BIP39 mnemonic), 2’ (the Bitcoin Core hdseed WIF), 32’ (an xprv), 128002’ (a NIP-19 Nostr nsec), 128169’ (raw bytes, which the BIP calls HEX), 707764’ and 707785’ (a base64 and a base85 password), 89101’ (dice rolls) and 828365’ (RSA).

The last two read BIP85-DRNG-SHAKE256 rather than the 64 bytes: a function whose appetite is not known in advance needs a stream, so the entropy seeds a SHAKE256 one and BIP85DRNG.read squeezes it. RSA is where that matters and where btclib stops: the BIP defines the path and the stream to feed a key generator, not how the primes are found, so rsa_drng_from_root_key hands back the reader an RSA library is to be given – and no two libraries handed the same stream need agree on the key, which is why the BIP publishes vectors for every other application and none for this one.

class btclib.bip85.BIP85DRNG(entropy: bytes | str | bytearray | memoryview)[source]

Bases: object

BIP85-DRNG-SHAKE256: the 64 entropy bytes as a stream.

The entropy of a path is 64 bytes and no more, which is not enough for a function whose appetite is not known until it has finished – RSA key generation is the BIP’s example. So the 64 bytes seed a SHAKE256 extendable-output function, and read squeezes as many as are asked for, each call continuing where the last one stopped.

The seed must be exactly 64 bytes, which is what the BIP requires: a shorter one is a different stream that no other implementation reaches, so it is refused rather than padded.

read is shake_256(seed).digest(cursor + num_bytes)[cursor:], which is a squeeze of the whole prefix each time rather than a resumed one – hashlib publishes no incremental squeeze. The output is the same either way, SHAKE256’s output at a given length being a prefix of its output at any greater one, and that identity is also what makes a stream read in small pieces equal to the same stream read in one: bipsea, the reference implementation, reads it the same way.

read(num_bytes: int) bytes[source]

Return the next num_bytes of the stream, and advance it.

btclib.bip85.base64_password_from_root_key(root_key: BIP32KeyData | bytes | str | bytearray | memoryview, pwd_len: int, index: int = 0) str[source]

Return a base64 password, BIP85’s application 707764’.

The path is m/83696968h/707764h/{pwd_len}h/{index}h: all 64 bytes of the entropy are base64-encoded and the leading pwd_len characters are the password. pwd_len is bounded to 20..86 inclusive, and the upper end is what keeps the slice clear of the “=” padding those 64 bytes encode to.

btclib.bip85.base85_password_from_root_key(root_key: BIP32KeyData | bytes | str | bytearray | memoryview, pwd_len: int, index: int = 0) str[source]

Return a base85 password, BIP85’s application 707785’.

The path is m/83696968h/707785h/{pwd_len}h/{index}h: all 64 bytes of the entropy are base85-encoded and the leading pwd_len characters are the password. pwd_len is bounded to 10..80 inclusive.

The alphabet is the one base64.b85encode writes, which is RFC1924’s; the BIP names no alphabet and its vector is in this one.

btclib.bip85.bytes_entropy_from_root_key(root_key: BIP32KeyData | bytes | str | bytearray | memoryview, num_bytes: int = 32, index: int = 0) bytes[source]

Return raw entropy bytes, BIP85’s application 128169’.

The path is m/83696968h/128169h/{num_bytes}h/{index}h, and the entropy is truncated to num_bytes, which the BIP bounds to 16..64 inclusive. The BIP calls this application HEX and prints its output as hex; the bytes are what btclib hands back, .hex() being the spelling.

btclib.bip85.drng_from_der_path(root_key: BIP32KeyData | bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview) BIP85DRNG[source]

Return the BIP85-DRNG seeded with the entropy of a path.

The path is any BIP85 path, entropy_from_der_path’s own rules applying to it: what this adds is the stream on top of the 64 bytes, for an application that needs more of them than there are.

btclib.bip85.entropy_from_der_path(root_key: BIP32KeyData | bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview) bytes[source]

Return the 64 bytes of entropy BIP85 derives for a path.

The path is the whole of it, m/83696968h/{app}h/… as the BIP writes it, and every level must be hardened. Each application truncates what it needs off the front; this is the answer for an application no function here formats, the caller doing the truncation and the formatting.

The root key must be private, hardened derivation having no public form. BIP85 assumes a master root key and this does not check the depth: neither does bipsea, the reference implementation, and a derived key is a legitimate root of a keychain of its own – but it is a different one, so entropy derived from it is reproducible only from that same key.

btclib.bip85.mnemonic_from_root_key(root_key: BIP32KeyData | bytes | str | bytearray | memoryview, words: int = 12, lang: str = 'en', index: int = 0) str[source]

Return a BIP39 mnemonic, BIP85’s application 39’.

The path is m/83696968h/39h/{language}h/{words}h/{index}h: the entropy is truncated to what a sentence of that many words encodes and handed to BIP39, which appends its checksum. words is one of 12, 15, 18, 21 and 24, and lang one of the ten of BIP85’s Language Table, which are ten of the twelve mnemonic.bip39 writes.

btclib.bip85.nsec_from_root_key(root_key: BIP32KeyData | bytes | str | bytearray | memoryview, identity: int, account_index: int) str[source]

Return a NIP-19 nsec, BIP85’s application 128002’.

The path is m/83696968h/128002h/{identity}h/{account_index}h: the leading 256 bits of the entropy are the secp256k1 secret key, exactly as in the HD-Seed WIF application above, and NIP-19 bech32-encodes them with the nsec human-readable part – plain bech32, not bech32m, and with no witness-version digit in front of the key the way a segwit address carries one.

identity is an independent, unlinkable Nostr key namespace and account_index a distinct key within it. Both must be 1 or more: the BIP reserves index 0’ of either for a future NIP’s key-management use – proof-of-linkage between an identity’s keys, rotation, revocation – and defines no signing key there, so neither defaults.

A scalar of zero or beyond the curve order is refused rather than encoded, the same hard failure wif_from_root_key documents and the same curve-order footnote this section of the BIP cross-references from the WIF one: at odds of about 2**-127 the answer is for the caller to move to the next index.

btclib.bip85.rolls_from_root_key(root_key: BIP32KeyData | bytes | str | bytearray | memoryview, rolls: int, sides: int = 6, index: int = 0) list[int][source]

Return dice rolls, BIP85’s application 89101’.

The path is m/83696968h/89101h/{sides}h/{rolls}h/{index}h – the sides before the rolls, where this signature takes the rolls first, a die having a customary number of sides and a session no customary length. Each roll is in 0..sides-1, which is what BIP85 defines and what a caller printing them as a die’s faces adds one to.

The rolls are read off the DRNG rather than off the 64 bytes: enough of them exhaust any fixed entropy, and a trial landing at or beyond sides is skipped rather than folded, so that every face stays equally likely.

Nothing bounds rolls from above here beyond what a path level can hold, and the wait is the caller’s: a session is rolls reads of a stream and takes as long as it takes.

mnemonic.entropy.bin_str_entropy_from_rolls is the other direction, dice into entropy for a wallet that does not exist yet; its docstring says how the two number a die’s faces.

btclib.bip85.rsa_drng_from_root_key(root_key: BIP32KeyData | bytes | str | bytearray | memoryview, key_bits: int, key_index: int = 0, sub_key: int | None = None) BIP85DRNG[source]

Return the DRNG of an RSA key, BIP85’s application 828365’.

The path is m/83696968h/828365h/{key_bits}h/{key_index}h, with a further {sub_key}h level for the GPG sub-keys the BIP allocates: 0’ encrypts, 1’ authenticates, 2’ signs, and the key at key_index itself is the one that certifies.

What comes back is the stream, not a key: BIP85 says an RSA generator should take the DRNG as its source of randomness and says nothing about how the primes are found, so the key belongs to whatever library is handed this reader. btclib generates no RSA key and the BIP publishes no vector for one.

A GPG key built this way has one more rule the BIP states and this cannot enforce: the creation date must be UNIX Epoch timestamp 1231006505, the fingerprint being a function of it.

btclib.bip85.wif_from_root_key(root_key: BIP32KeyData | bytes | str | bytearray | memoryview, index: int = 0) str[source]

Return a compressed WIF, BIP85’s application 2’.

The path is m/83696968h/2h/{index}h, and the leading 256 bits of the entropy are the secret exponent: this is the hdseed a Bitcoin Core wallet takes. The network is the root key’s own, as the WIF prefix has to name one.

btclib.bip85.xprv_from_root_key(root_key: BIP32KeyData | bytes | str | bytearray | memoryview, index: int = 0) str[source]

Return an extended private key, BIP85’s application 32’.

The path is m/83696968h/32h/{index}h, and the 64 entropy bytes are read in the order BIP85 states and BIP32 reverses: the first 32 are the chain code and the second 32 the private key. Depth, index and parent fingerprint are zero, the answer being the root of a keychain of its own.

The version is the network’s own xprv or tprv, which is what BIP85 asks for – a testnet root emits a tprv and nothing else does. It is not the root key’s own four bytes: a SLIP132 yprv says which script type that tree is derived for, and the tree this key roots is a new one no such claim has been made about.

btclib.consensus module

The consensus constants and the per-network table, below every package.

Bitcoin Core keeps a consensus rule’s numbers in two shapes and so does this module: the bounds that are the same on every chain, which are consensus/consensus.h, and the ones that are a fact about one network, which are Consensus::Params and are ConsensusParams below.

Nothing of btclib is imported here, and that is the whole of why the table can live at this depth: btclib.block, btclib.tx and btclib.script all read something of it, btclib.network reads the table to give each Network its consensus row, and any import back would close a cycle on a half-initialized package – issue #147’s shape, and what tests/imports_test.py reports.

MAX_BLOCK_WEIGHT and WITNESS_SCALE_FACTOR are here rather than in btclib.block.limits, which is where the rest of that header is and where a caller reading a block’s own rules goes, because of who else divides by them: a transaction’s input and output counts (btclib.tx.limits) and a witness stack’s element count are arithmetic on MAX_BLOCK_WEIGHT. btclib.block.limits re-exports both, so nothing that reads them from there has to move.

MAX_WITNESS_STACK_ITEMS is here and not in btclib.script.limits for a second reason on top of the layering. That module holds the caps the script engine enforces, and reading an execution limit in a decoder is what let a 1443-byte push be refused as unparsable when it was merely unspendable (issue #123). The bound below is not MAX_STACK_SIZE: it refuses a count no block could carry, not a witness no script could run.

## The per-network table

CONSENSUS_PARAMS answers for every network btclib.network.NETWORKS names, and NETWORKS[name].consensus is the same row reached from the encoding side, so the two tables cannot disagree about which networks exist. A row is what a validator needs and a node’s own file has no claim on: an activation height, the subsidy interval, the easiest target the network allows, and the exceptions the chain’s own history forces. What identifies a node rather than a network – a port, a DNS seed, a pruning floor, a custom signet’s p2p magic – is not here, and bitcoin_core_rpc is where the last of those lives.

Every value is transcribed from src/kernel/chainparams.cpp at Bitcoin Core v31.1 (bitcoin/bitcoin@9be056a8a7), with the line it was read at beside it, and tests/consensus_test.py holds each field to that transcription. A released tag rather than a master tip, for two reasons: these are the numbers a node on the network enforces, and a tag names a blob that cannot move under a line citation. master also no longer keeps all of them in that one file – taproot’s deployment is gone from it, buried the way script_flags_at below describes.

What a row carries that chainparams.cpp does not hold is cited to src/validation.cpp at the same tag: bip30_exceptions is IsBIP30Repeat there. So is the rule that combines a row’s heights into script flags, GetBlockScriptFlags, which script_flags_at names.

Fields of Consensus::Params that are deliberately absent, so that a reader looking for one knows it was decided rather than missed:

  • hashGenesisBlock is Network.genesis_block, which every one of these rows is reached through

  • BIP34Hash is the hash Core compares at bip34_height to decide whether it may stop making the BIP30 check above it. A validator that keeps making it answers the same, BIP34 being what makes a duplicate coinbase impossible once the coinbase commits to its own height, so the field buys work rather than an accept or a reject

  • vDeployments is BIP9 signalling, which a table of heights does not model and no caller of this table asks for

  • MinBIP9WarningHeight is the height below which a node stays quiet about an unknown deployment, which is a warning and not a rule

  • defaultAssumeValid is what a node skips signature checks below by default, which is a startup option and not a fact about the chain

  • signet_challenge is per deployment rather than per network, which is the reason btclib.network gives for keeping the p2p magic out too

  • signet_blocks is the switch in front of the check that reads that challenge, so a caller given the one without the other could not act on it; whoever holds the challenge holds this with it

A row validates nothing it is built with, alone among this library’s dataclasses: validating means raising a BTClibTypeError, which means importing btclib.exceptions, which is the import this module does not take. Nothing parses a row – the five below are constants of this module – and the boundary that does read json is Network.from_dict, which asks Network.assert_valid whether its consensus field is one of these.

class btclib.consensus.ConsensusParams(name: str, subsidy_halving_interval: int, bip34_height: int, bip66_height: int, bip65_height: int, csv_height: int, segwit_height: int, pow_limit_bits: bytes, pow_allow_min_difficulty_blocks: bool, enforce_bip94: bool, pow_no_retargeting: bool, pow_target_spacing: int, pow_target_timespan: int, minimum_chain_work: int, bip30_exceptions: tuple[tuple[int, bytes], ...], script_flag_exceptions: tuple[tuple[bytes, tuple[str, ...]], ...])[source]

Bases: object

The consensus parameters of one network: heights, limits, exceptions.

Bitcoin Core’s Consensus::Params for the fields a validator reads off a chain rather than off a block. The module docstring says which of Core’s fields are deliberately not here, and where each value was transcribed from.

Frozen and hashable, as Network is: a row is a value, the five in CONSENSUS_PARAMS are read by every caller at once, and a Network carrying one must stay usable as a dict key.

property difficulty_adjustment_interval: int

Return how many blocks a difficulty period holds on this network.

Core’s Consensus::Params::DifficultyAdjustmentInterval, a derived quantity there too rather than a stored one: 2016 blocks on every network but regtest, whose 144 comes from a one-day window over the same ten-minute spacing.

script_flags_at(height: int, block_hash: Octets | None = None) btclib.script.engine.flags.ScriptFlag[source]

Return the script rules a block at this height is checked with.

Bitcoin Core’s GetBlockScriptFlags (src/validation.cpp, at bitcoin/bitcoin@9be056a8a7), which is what btclib.script.engine.verify_input takes as its flags.

P2SH, segwit v0 and taproot are on for every block of every chain, and the height decides only the four that Core gates on a buried deployment: DERSIG, CHECKLOCKTIMEVERIFY, CHECKSEQUENCEVERIFY, and NULLDUMMY with segwit. The blocks that predate a rule and would fail it are exceptions by hash, not by height – pass block_hash to have them answered, and the flags such a block gets are the entry’s own rather than the default set, with the height-gated rules still added on top.

The standardness flags are never returned: a block breaking one of those is valid, which is btclib.script.engine.flags’s own split between what ALL_FLAGS carries and what it leaves off.

btclib.consensus.subsidy(height: int, halving_interval: int = 210000) int[source]

Return the block reward at height: Bitcoin Core’s GetBlockSubsidy.

Fifty bitcoin, right-shifted once per halving_interval blocks and forced to zero once that shift is undefined for a native int (src/validation.cpp, at bitcoin/bitcoin@9be056a8a7). height is what a block’s coinbase pays for building it; halving_interval defaults to mainnet’s own, and a caller building for another network passes CONSENSUS_PARAMS[name].subsidy_halving_interval, regtest’s 150 among them, rather than this function asserting a chain’s schedule for it.

btclib.core_import module

The requests Bitcoin Core’s importdescriptors takes.

https://github.com/bitcoin/bitcoin/blob/master/src/wallet/rpc/backup.cpp

What a wallet does with the descriptors descriptors.account_descriptors builds: hand them to a node, which then watches every script they describe. A request is one json object per descriptor, and this module is the object – no rpc call and no client, deliberately. The caller already has one, bitcoin-core-rpc being a package of its own that btclib.fetch.bitcoin_core builds on, and a second way to reach a node would be a second thing to keep working.

import_request is one object; account_import_requests is the pair a BIP44 account is, the receiving chain and the change chain marked internal. That mark is why the pair is a function of its own: change imported as a receiving chain is money the wallet reports as incoming payments, and the mistake is invisible until a balance is wrong.

Every rule Core enforces on a request is enforced here, where the error can still say which field it was rather than arriving as an rpc failure half a rescan later:

  • the descriptor carries its checksum, Parse being called there with require_checksum = true;

  • an active descriptor is ranged, an unranged one having no keypool to be the active source of;

  • a range belongs to a ranged descriptor and to no other;

  • both ends of a range are what ParseRange and ParseDescriptorRange in src/rpc/util.cpp take: ordered, non-negative, an end below 2**31, and fewer than a million indexes between them;

  • next_index is inside that range;

  • a label goes with neither internal nor a range;

  • a timestamp is a number or the exact string now, which is what GetImportTimestamp accepts and nothing else – “NOW” is not it.

Two things are not written, where HWI’s getkeypool writes them: watchonly and keypool are importmulti fields – the other rpc that dict targets – and importdescriptors defines neither. A descriptor wallet is watch-only by holding no private key, which descriptors.parse guarantees of every descriptor it returns.

A multipath descriptor is not built here either: Core takes one and reads the second element of a two-element step as the internal descriptor, while descriptors.parse refuses a <a;b> step outright and multipath_descriptors is what expands one. So the pair of requests is what this module has, and it says the same thing in two objects.

Two of Core’s answers are read here too, for the reason the requests are built here: both are knowledge of how the node behaves rather than of the protocol, and neither needs a node to be reached, being a function of a reply the caller already has. assert_imported reads what importdescriptors answered, which reports a refusal inside the reply instead of failing the call. watched_range reads what listdescriptors answered, and widened_range turns it into the range a second import may ask for: Core widens any ranged import to its keypool and then refuses every later one that would narrow what it widened to, so importing the same descriptor twice is idempotent only for a caller that asks for at least what is there already.

btclib.core_import.account_import_requests(receive: Descriptor, change: Descriptor, timestamp: int | str = 'now', *, active: bool = True, key_range: tuple[int, int] = (0, 999)) list[dict[str, Any]][source]

Return the two requests a BIP44 account is imported with.

The pair descriptors.account_descriptors builds, with the second marked internal: that mark is what keeps a wallet from reporting its own change as incoming payments, and it is the one thing a caller writing the two requests by hand gets wrong.

Both chains or neither: a wallet holding the receiving chain alone cannot recognize the change it makes itself, which is an output it stops seeing rather than a feature it lacks.

btclib.core_import.assert_imported(requests: Sequence[Mapping[str, Any]], answers: Sequence[Mapping[str, Any]]) None[source]

Refuse an importdescriptors reply that did not honour every request.

Core answers one object per request instead of failing the call, so a request it did not honour arrives as success: false inside what the rpc layer calls a reply – a result, which nothing under the caller has any reason to doubt. Left unread, a wallet goes on watching less than its owner believes it does, and the first thing to say so is a balance short of a deposit.

The request is what names the failure: an answer carries the error and not the descriptor it was for, so the two are read in step, and a reply of the wrong length is itself a node that did not answer this.

warnings is not read, that being what Core says about a request it did honour – “Range not given, using default keypool range” is the one DEFAULT_RANGE exists to avoid – and neither is a refusal turned into a value error: the request was one Core parsed, and what it refused is the state of a wallet, which no argument of the caller’s spells.

btclib.core_import.import_request(descriptor: Descriptor | str, timestamp: int | str = 'now', *, internal: bool = False, active: bool = True, key_range: tuple[int, int] | None = (0, 999), next_index: int | None = None, label: str = '') dict[str, Any][source]

Return the importdescriptors request for one descriptor.

descriptor is a Descriptor or the text of one; either way what the request carries is the checksummed text, which is what Core requires of a descriptor it imports.

timestamp is where the rescan starts, in Unix time, NOW being Core’s own way of saying “do not rescan”: right for a descriptor whose scripts have never been used, and wrong for one being restored, where the time of the wallet’s first use is what finds its history. NOW is the default for the reason there is no default restore date – this module cannot know one, and a silent 0 would rescan the whole chain. A number or NOW itself, and no other string: Core takes those two and refuses the rest, “NOW” included.

internal marks the change chain, which Core then keeps out of what it reports as incoming payments.

active makes the descriptor the wallet’s source of new addresses for that output type and externality, which is what an import for spending wants and a bare watch of some scripts does not. Core requires an active descriptor to be ranged, so this does too.

key_range is the inclusive pair Core takes, both ends included – Core adds one to the second itself. None leaves the field out, which is what an unranged descriptor takes. Ordered, non-negative, an end below 2**31 and fewer than a million indexes wide, which are ParseDescriptorRange’s bounds.

next_index is where an active ranged descriptor hands out its next address, and has to be inside the range, as Core checks.

label names the address, and Core allows one only for a single unranged receiving descriptor: not for change, and not for a range.

btclib.core_import.watched_range(descriptor: Descriptor | str, reply: Mapping[str, Any]) tuple[int, int] | None[source]

Return the range of a descriptor a wallet watches, None for none.

reply is what listdescriptors answered, which is a wallet’s account of itself: one entry per descriptor it holds, echoing the expression it was imported with and the range it ended up with. This is the question a caller has to ask before importing a descriptor a second time, Core refusing an import that would narrow that range, and widened_range is what the answer is for.

None is a descriptor the wallet does not hold, and equally one it holds unranged: an entry with no range watches one script and has no index to be widened from, which is the same “nothing to include” to whoever is building the next request.

The union of the entries where a wallet holds the same expression more than once – the same descriptor imported as both the receiving and the change chain, which Core allows and which listdescriptors distinguishes only for an active descriptor, internal being defined for those alone. Asking for the union is accepted for either of them, where asking for one entry’s range can be a narrowing of the other’s.

btclib.core_import.widened_range(wanted: tuple[int, int], watched: tuple[int, int] | None = None) tuple[int, int][source]

Return the range to import: the one wanted, and never a narrower one.

Core widens every ranged import to its keypool – next_index plus a thousand scripts by default, whatever the request asked for – and then refuses any later import that would narrow what it widened to: “New range must include current range” is what the second request is answered, and the whole import fails on it. So a caller whose range has grown asks for the union of the two, which is this, and importing a descriptor again is idempotent because of it.

watched is what watched_range read of the wallet, and None is the descriptor it does not hold yet: DEFAULT_RANGE stands in for it, that being what Core widens a first import to anyway – asking for it makes the reply state which indexes were imported instead of leaving a caller to assume them. A node whose keypool is not the default needs no allowance here: whatever it widened to is what the next listdescriptors says, and the union with that is what the next import asks for.

What the widening costs is worth stating where it is decided: a wallet watches every script of the range, so the addresses past the ones a caller meant are the node’s too, and money paid to one of them is money that wallet reports. An import of exactly what is meant, and no keypool, is what an unranged descriptor per script is for.

btclib.exceptions module

Exception classes.

These exist only to tell an exception raised by btclib from one raised by any other code: each derives from the built-in that says what kind of failure it is, and adds nothing to it.

BTClibException is what makes that telling apart a single except rather than a tuple of three a caller has to keep in step with this hierarchy. It is inherited beside the built-in and not instead of it, which is the half that matters: BTClibValueError is a ValueError as it always was, so code catching the built-in keeps catching what it caught, and json.JSONDecodeError is the standard library doing the same. Libraries that give up the built-in – requests, sqlalchemy and httpx among them – leave an except ValueError not catching their value errors, which is the cost this avoids by inheriting from both.

It is caught and never raised: every raise below is one of the three, and which one answers a question the base cannot carry – whether the value was wrong, the type was, or neither was and a check failed anyway. A caller with something to do about that difference names the specific class; except BTClibException is for the caller who only needs to know it came from here, and it catches every failure of btclib’s: no public function lets a native KeyError, IndexError or OverflowError escape uncaught.

The exception is the few classes below carrying a field: what a peer got wrong, the node’s rpc error code, an HTTP status. Those are values a caller acts on, and reading them back out of a message is what the field spares them, so a caller after one of them still names the specific class rather than the base.

Each of those hands every constructor argument to BaseException.__init__ and composes its message in __str__, which is what subprocess.CalledProcessError and UnicodeDecodeError do, and what makes it picklable: BaseException.__reduce__ returns (cls, self.args), so a class whose args is the composed message alone is rebuilt by calling it with one argument, and one argument is not what it takes. That is a TypeError out of pickle, out of copy.copy and out of copy.deepcopy – and out of a ProcessPoolExecutor, which cannot send the exception back and reports a broken pool instead of the failure the worker died of. Composing in __str__ is the half that keeps the round trip faithful rather than merely possible: a message composed in __init__ from an argument that is itself a composed message gains a second (command 3, stack depth 2) every time.

The visible price is args, which is a tuple of the arguments now and not a one-tuple of the message, and repr, which names the fields with it. str is the message it always was.

exception btclib.exceptions.BTClibException[source]

Bases: Exception

Anything btclib raised, whatever kind of failure it is.

The one name to catch for a caller who handles the standard library’s exceptions anyway and needs to know which came from here. Never raised: the three below it are, and each says which kind of failure it was.

exception btclib.exceptions.BTClibRuntimeError[source]

Bases: BTClibException, RuntimeError

A check that failed on valid inputs, e.g. a failed verification.

exception btclib.exceptions.BTClibTypeError[source]

Bases: BTClibException, TypeError

An input of a type no conversion accepts: a caller error.

exception btclib.exceptions.BTClibUserWarning[source]

Bases: UserWarning

A btclib warning: the call worked, but not the way it should have.

A plain warn(…) defaults to UserWarning, which is also what any other library and the application itself emit: a caller wanting to silence btclib alone, or to promote it to an error, then has nothing to name but the message text or the module. This category is that name, and it stays a UserWarning so that code filtering that keeps filtering this.

The test suite relies on it too: filterwarnings = [“error”] is only worth having if the places that provoke a btclib warning silence that warning and nothing else.

Not a BTClibException, though it comes from btclib as much as any of them: a warning is not a failure and is not caught but filtered, so an except BTClibException sweeping one up – which filterwarnings = [“error”] is enough to make happen – would catch a call that worked as if it had not.

exception btclib.exceptions.BTClibValueError[source]

Bases: BTClibException, ValueError

A value no valid input could carry; the library’s usual refusal.

exception btclib.exceptions.BorromeanRingError(message: str, ring: int | None, position: int | None)[source]

Bases: BTClibRuntimeError

A borromean ring signature check failed, and where names it.

ring is the index into pubk_rings and position the index within that ring: an e-value landing on zero, and the point at infinity its one-in-n neighbour lands a ring’s nonce or its r on instead, each happen at one ring and one position, and btclib.ecc.borromean.sign and assert_as_valid already have both in hand at every one of their raises. Naming them is what tells a caller building on this primitive which key rejected the signature rather than only that the whole thing did.

Both are None for the one failure with no ring of its own: the final e0 not matching what every ring converges on is a property of the whole signature, not of any single ring in it.

A BTClibRuntimeError and not InvalidContributionError: that class names a party to an interactive multi-round protocol – MuSig2 – and which of its contributions was wrong, where a borromean ring signature is not interactive and has no parties to accuse, only positions in a signature that either close their ring or do not. A BTClibRuntimeError still, so code catching that keeps catching this, as verify already does.

exception btclib.exceptions.FetchError[source]

Bases: BTClibRuntimeError

A backend did not answer, or did not answer this.

A RuntimeError and not a ValueError, which is the distinction worth keeping: nothing the caller passed is wrong. The node is down, the credentials are stale, the explorer sent html, the transaction is not in the index – retrying later can work, and correcting the argument cannot.

It covers the conversion of an answer too. A backend that replies with something which is not a transaction has failed, and reporting that as the BTClibValueError Tx.parse raised would name the parser rather than the host that has to be fixed.

Declared here rather than taken from bitcoin_core_rpc, which raises a class of the same name: that package declares zero dependencies and imports nothing of btclib’s, so its FetchError derives from a BTClibRuntimeError of its own, and an except BTClibRuntimeError written against this module would not catch it. btclib.fetch.fetcher.client_errors is the one place the two meet.

exception btclib.exceptions.HttpError(message: str, status: int)[source]

Bases: FetchError

A backend failed at the HTTP layer, and status is what it said.

A field because acting on a status is the caller’s job and btclib retries nothing: a 401 says the credentials are wrong and will stay wrong until they are changed, while a 503 from bitcoind says its rpc work queue is full and the same request works when the queue drains. A caller writing that policy needs to recognise the status, and matching on the text of a message is what a field spares them.

Not every FetchError carries one, and that is the distinction: a refused connection and an expired timeout are failures of an exchange that never produced a status, and stay a plain FetchError.

A FetchError still, so code catching that keeps catching this.

exception btclib.exceptions.IncompleteMessageError(message: str, missing: int)[source]

Bases: BTClibRuntimeError

A p2p message is not all there yet, and missing says by how much.

What btclib.p2p.Message.parse raises where the octets end inside a message: fewer than the header’s, or a header whose payload length the octets after it do not reach. missing is how many more would take the parse past where this one stopped – the rest of the header, or the rest of the payload once the header has been read – so a caller accumulating from a socket has a number to ask for rather than a guess.

A BTClibRuntimeError and not a BTClibValueError, which is the class every other short read in this library raises: nothing the caller passed is wrong. BTClibValueError is “a value no valid input could carry” and these octets are the valid input’s own prefix; what failed is a check on it, which is what BTClibRuntimeError says. Reading more can fix it and correcting an argument cannot, which is FetchError’s reasoning for the same base.

Not a FetchError itself, kin though the reasoning is: that class is a backend that did not answer, and nothing in btclib.p2p goes out and asks – the caller already holds the octets, and holds the socket this library never opens. Nor a bare BTClibRuntimeError: the whole point is that a socket caller tells this from every other refusal parse gives, and one class two answers share is one a caller cannot branch on. Everything else parse raises is final – a magic no further octet changes, a length over btclib.p2p.limits.MAX_PROTOCOL_MESSAGE_LENGTH, a checksum that does not verify – and the peer that sent it is the thing to drop.

exception btclib.exceptions.InconclusiveError[source]

Bases: BTClibValueError

Not invalid, and not something today’s rules can call valid.

BIP322 answers a signature with one of three states rather than two, and this is the third: a to_sign whose version is neither 0 nor 2, an upgradeable NOP, a witness program of an unknown version. Each of them satisfies the script as it runs today, and each is what a soft fork can give a meaning to, so a validator saying “valid” would be speaking for rules it does not have.

A BTClibValueError, so code catching that keeps catching this, and so that btclib.bip322.verify answers False without a second except: an inconclusive signature is not one that verified. A caller that means to tell the two apart names this class.

exception btclib.exceptions.InvalidContributionError(signer: int | None, contrib: str)[source]

Bases: BTClibRuntimeError

A party to an interactive protocol sent a value that does not check out.

Which party, and which of its contributions: signer is the index in the list the caller passed, None for the aggregator – who has no index, having no key – and contrib names what was wrong, one of “pubkey”, “pubnonce”, “aggnonce”, “aggothernonce”, “psig” or “adaptor”. That is the whole point of the class: a multi-round protocol that merely fails leaves every participant a suspect, and the answer a caller needs is who to hold accountable and to exclude from the next attempt.

A BTClibRuntimeError and not a BTClibValueError, which is the other obvious base and the one BIP327 keeps separate: its reference implementation raises ValueError for an argument that breaks a precondition – the caller’s own mistake, a 33-byte tweak – and this for a peer misbehaving, and the MuSig2 test vectors distinguish the two case by case. Sharing a base would put the two beyond telling apart by except, and would let every except ValueError in the library swallow an accusation.

exception btclib.exceptions.InvalidPrvKeyError[source]

Bases: BTClibValueError

The format was recognised and the content is wrong: stop here.

The counterpart of NotAPrvKeyError. A WIF whose version prefix says mainnet but whose payload is the wrong size is not something another format might accept: reporting it is more use than trying the input as a hex string and telling the caller it was not a private key.

A BTClibValueError, so code catching that keeps catching this.

exception btclib.exceptions.NoDescriptorError[source]

Bases: BTClibValueError

No output descriptor states this script: it is not that a lift failed.

What wallet.ScriptWallet.descriptor refuses with, and the one refusal there that is a fact about the wallet rather than about the code asking: a script spelling its timelock <n> OP_CSV OP_DROP, or ordering a quorum after derivation inside a combinator, is a script BIP380 to BIP390 cannot write down – and will still be one at the next release. A caller catching this has an answer (“watch these addresses instead”), where a caller catching a parse failure has a bug report.

A BTClibValueError, so code catching that keeps catching this: the refusal it most often stands in front of is miniscript.from_script’s, which is one.

exception btclib.exceptions.NotAPrvKeyError[source]

Bases: BTClibValueError

The input is not in this private key format at all: try the next one.

The library accepts a private key as a WIF, a BIP32 xprv, octets, or an int, and works out which by trying them in turn. That only reads well when a failed attempt says which kind of failure it was, and this is the kind that means “wrong format, keep going”.

A BTClibValueError, so code catching that keeps catching this.

exception btclib.exceptions.RpcError(message: str, code: int, data: Any = None)[source]

Bases: FetchError

bitcoind answered with a JSON-RPC error object, and this is it.

code is the node’s, from src/rpc/protocol.h: -5 is RPC_INVALID_ADDRESS_OR_KEY, which is what getrawtransaction returns for a transaction it cannot find – including every non-wallet transaction on a node running without -txindex. A caller that means to tell “no such transaction” from “the node is unreachable” needs the number, and parsing it back out of the message is what having a field avoids.

data is JSON-RPC’s optional third member of an error object, kept as it arrived. Core leaves it out today, so it is None for every error a node sends; a method that starts sending one – or a proxy between the two adding its own – would otherwise have it dropped here, which is the one place it cannot be recovered from.

A FetchError still, so code catching that keeps catching this.

exception btclib.exceptions.ScriptError(message: str, index: int, stack_depth: int)[source]

Bases: BTClibValueError

A script verification failure, and where in the script it happened.

Only the two interpreter loops know the index of the command being executed and the depth of the stack; the op code implementations, which are handed the stack alone, do not. They raise a plain BTClibValueError with what went wrong, and the loop re-raises it as this, adding where. A BTClibValueError still, so that code catching that keeps catching this.

exception btclib.exceptions.SignerError(message: str, code: int | None = None)[source]

Bases: BTClibRuntimeError

An external signer failed, and code is the number it gave.

What btclib.psbt_signer’s contract fails with, and what btclib.hwi raises around HWI’s structured errors: the JSON CLI answers {“error”: <msg>, “code”: <n>}, and the number is the part a caller acts on. -14 is ACTION_CANCELED, which is somebody pressing the button that says no and is not worth a retry; -3 is DEVICE_CONN_ERROR, which is a cable and is worth one; -9 is UNAVAILABLE_ACTION, which says this model will never do it. Matching on the text of a message is what a field spares a caller writing that policy.

A RuntimeError for the reason FetchError is one: nothing the caller passed is wrong. The device is unplugged, locked, busy, or its owner said no – retrying can work, and correcting an argument cannot. The numbers HWI reserves for a bad argument (-2, -7) arrive here too, that being the one thing the code says and the class cannot.

code is None where the failure produced no number: a backend that could not be started, an answer that was not JSON, an output past the limit. Those are failures of the exchange rather than of the device.

exception btclib.exceptions.SignerNotFoundError(message: str, code: int | None = None)[source]

Bases: SignerError

The backend an adapter runs is not installed.

A SignerError still, so a caller catching that keeps catching this, and separate because it is the one failure that is not about a device: nothing is unplugged, locked or busy, and no retry will change it. A caller that offers signers of several kinds tells “there is no hardware here” from “the hardware could not be reached” on this class, and the two are not the same thing to report before a signing operation.

Without it the distinction is not recoverable. btclib.hwi turns every OSError into a SignerError – a missing executable and a permission the udev rules do not grant arrive as one class with one code of None – so a caller had to either match on the text of a message or look for the executable itself, and looking for it is asking a second question that can disagree with the first.

code is None, as for every failure of the exchange rather than of a device.

btclib.fee module

Fee rates, the fee a virtual size owes, package fees, and dust.

Each answer is a function of its arguments alone: what a fee rate is, what fee a transaction of a given virtual size owes at that rate, what a child owes when the outputs it spends are unconfirmed, and how small an output has to be before the network refuses to relay it.

A fee rate is a price, and the unit is where the mistakes are. Bitcoin Core stores it in satoshi per kilo-virtual-byte, quotes it in BTC per kilo-virtual-byte wherever its RPC replies mention one, and wallets and their users state it in satoshi per virtual byte: three units and two factors that a bare int does not record. FeeRate names the unit in every constructor and accessor, so no factor is the caller’s to remember, and it refuses a rate it could not hold exactly rather than truncating one.

Explicitly not here, and the boundary is the point: everything downstream of a network. Mempool histograms, a fee estimate for a confirmation target, an ETA, a “how many blocks” slider – those are policy fed by live data. They need a node, they answer differently every minute, and they belong to an application rather than to a library. package_fee is on this side of that line and its inputs are on the other: which ancestors are unconfirmed and what each of them paid is what a node answers, the arithmetic over those totals is not. What this module computes it computes from its arguments and from Bitcoin Core’s constants, and the same arguments give the same answer forever.

class btclib.fee.FeeRate(*, sats_per_kvbyte: int)[source]

Bases: object

A fee rate, held as an integer number of satoshi per kvB.

sat/kvB as an int is the exact representation, and the only one that is. It is what Core stores, so no conversion stands between this number and the fee a node computes; it is integral, so the arithmetic is Python’s unbounded int rather than a float that stops counting in ones somewhere below MAX_MONEY; and it is fine enough to hold every rate a user can state, sat/vB being quoted to three decimals at most – 1.5 sat/vB is exactly 1500 sat/kvB. Storing sat/vB instead would make the common unit the lossy one: 1.5 is not representable as an int, and as a float it is one of the few decimals that happen to be exact, which is worse than none of them being.

Keyword-only, so that no bare number is ever passed to the constructor without its unit beside it: FeeRate(3000) is the off-by-a-thousand this class exists to prevent, and it is a TypeError. There is no from_sats_per_kvbyte classmethod either – the field is that constructor, already spelled with its unit, and a second spelling of one thing is a menu rather than an API.

Ordered, because comparing two prices is the question a caller actually asks of them, and the comparison is exact for the same reason the storage is.

classmethod from_btc_per_kvbyte(btc_per_kvbyte: Any) FeeRate[source]

Return the same rate in sat/kvB from a BTC/kvB quote.

BTC/kvB is the unit Bitcoin Core quotes a rate in wherever an RPC reply carries one – estimatesmartfee’s feerate, getmempoolinfo’s mempoolminfee and minrelaytxfee, getnetworkinfo’s relayfee – and it is a unit of the interface rather than of the node: what those numbers are compared against internally is the sat/kvB this class holds. getblockstats is the exception worth knowing, quoting its minfeerate and avgfeerate in sat/vB, which is the constructor above.

A rate per kvB scales from BTC to satoshi by the factor an amount does, so sats_from_btc is the conversion and its refusals are the ones that apply: what does not read as a decimal number, what is not finite, a negative quote, and one naming a fraction of a satoshi – what sat/kvB cannot hold exactly. Its complaints name a BTC amount, which is what is being converted.

classmethod from_sats_per_vbyte(sats_per_vbyte: Any) FeeRate[source]

Return the same rate in sat/kvB from a sat/vB quote.

The quote is a Decimal, an int, a string, or anything else str() renders as a decimal number; a float is read through its repr, so 1.1 is the 1.1 that was written rather than the binary fraction nearest to it. Refused: what does not read as a decimal number, is not finite, or is not a whole number of millisatoshi per virtual byte – what sat/kvB cannot hold exactly.

property sats_per_vbyte: Decimal

Return the rate in satoshi per virtual byte, exactly.

A Decimal and not a float, for the reason the class docstring gives: the unit users speak is the unit a binary fraction cannot hold, and handing back 1.1000000000000001 would undo the conversion that refused to truncate on the way in.

btclib.fee.dust_threshold(script_pub_key: bytes | str | bytearray | memoryview, fee_rate: FeeRate = FeeRate(sats_per_kvbyte=3000)) int[source]

Return the smallest relayable value, in satoshi, for such an output.

Core’s GetDustThreshold, computed and not tabulated: the fee, at the dust relay rate, of the output itself plus the input that will one day spend it. An output worth less than that costs more to spend than it holds.

Computing it is what makes it follow the script type on its own. p2tr answers 330 without p2tr being named anywhere below, and so will whatever output type comes next; a table of one limit per type is accurate the day it is written and needs an edit every time the network grows a type.

An unspendable output has no input to pay for, so its threshold is zero and no value is dust.

fee_rate is the dust relay rate, defaulting to Core’s 3000 sat/kvB. An output is dust when its value is below the returned satoshi; one worth exactly it is not.

btclib.fee.fee_from_vsize(vsize: int, fee_rate: FeeRate) int[source]

Return the fee in satoshi owed by a transaction of that virtual size.

Rounded up, which is what Core’s CFeeRate::GetFee does and the reason it does it: a fee one satoshi short of the rate is a fee below the rate, and a transaction paying it does not relay. A virtual size that is not an int, or is negative, is refused.

btclib.fee.package_fee(vsize: int, fee_rate: FeeRate, *, ancestor_vsize: int = 0, ancestor_fee: int = 0) int[source]

Return the fee in satoshi owed by a transaction and its ancestors.

Child pays for parent. A transaction spending an unconfirmed output is mined with what it depends on or not at all, so the rate a miner reads is the package’s – Core’s mempool scores a transaction by fees.ancestor over ancestorsize, both of getmempoolentry – and buying a rate for the child means buying it for everything unconfirmed behind it, less what that already paid.

The answer is the larger of two fees: what the child owes for its own virtual size, and what the package owes for the sum of the sizes, less ancestor_fee. The second is what lifts a package its ancestors underpay. The first is what stands when they pay the rate or more, where the difference would be a discount on the child – a child cheaper than the rate because its parent overpaid, which is a transaction that may not relay on its own and a fee no caller asked for.

ancestor_vsize and ancestor_fee are totals over the unconfirmed ancestors, the child itself excluded; zero for both, the default, is a child with nothing unconfirmed behind it and the answer is fee_from_vsize. Which transactions those are, whether the set stops at the parents or walks the whole graph, and what each of them paid is a mempool question, and it stays with the caller: an ancestor’s own descendants are not in it, and neither are Core’s -limitancestorcount and -limitancestorsize, which bound what it will accept but not what a fee is.

Keyword-only, the two of them, because they are same-typed non-negative ints beside each other: a swapped pair is a wrong fee that nothing else in the arithmetic can notice.

btclib.hashes module

The hash functions of bitcoin.

ripemd160 and sha1 through sha256, the hash160 and hash256 pairs, BIP340’s tagged hash, SipHash-2-4, the BMS magic envelope, and the merkle roots and branches of a block.

btclib.hashes.hash160(octets: bytes | str | bytearray | memoryview) bytes[source]

Return the HASH160=RIPEMD160(SHA256) of the input octet sequence.

btclib.hashes.hash256(octets: bytes | str | bytearray | memoryview) bytes[source]

Return the SHA256(SHA256(*)) of the input octet sequence.

btclib.hashes.magic_message(msg: bytes | str | bytearray | memoryview) bytes[source]

Return the hash BMS signs: the message in Core’s magic envelope.

Both the “Bitcoin Signed Message:” magic and the message enter var_int-length-prefixed, then hash256 of the whole; the envelope is what keeps a signed message from being a valid transaction signature.

btclib.hashes.merkle_root(data: Sequence[bytes], hf: Callable[[bytes | str | bytearray | memoryview], bytes]) bytes[source]

Return the merkle tree root of a list of binary hashes.

The merkle tree is a binary tree constructed with the provided list of binary data as bottom level, then recursively going up one level by hashing every hash value pair in the current level, until a single value (root) is obtained.

The root alone does not tell whether the list is the CVE-2012-2459 mutation of a shorter one; whoever validates a block must use merkle_root_and_mutated and reject a mutated tree.

btclib.hashes.merkle_root_and_mutated(data: Sequence[bytes], hf: Callable[[bytes | str | bytearray | memoryview], bytes]) tuple[bytes, bool][source]

Return the merkle tree root, and whether the tree is mutated.

The merkle tree is a binary tree constructed with the provided list of binary data as bottom level, then recursively going up one level by hashing every hash value pair in the current level, until a single value (root) is obtained.

See merkle_root_and_mutated_from_hashes for the mutation flag, and for the variant taking a bottom level of hashes.

btclib.hashes.merkle_root_and_mutated_from_hashes(hashes: Sequence[bytes], hf: Callable[[bytes | str | bytearray | memoryview], bytes]) tuple[bytes, bool][source]

Return the merkle tree root, and whether the tree is mutated.

The bottom level is the provided list of hashes, taken as they are: this is the tree over values that are hashes already, as the witness tree of a block is (its coinbase leaf, all zeros, is the hash of nothing at all). Core’s ComputeMerkleRoot is the same function; merkle_root_and_mutated is the one hashing the leaves first.

The second returned value flags CVE-2012-2459: a level holding two equal siblings has the same root as the shorter list carrying only one of them, so the root does not commit to the list it was computed from. Bitcoin Core computes the same flag (the mutated out parameter of BlockMerkleRoot) and rejects such a block.

btclib.hashes.merkle_root_from_branch(leaf: bytes | str | bytearray | memoryview, branch: Sequence[bytes | str | bytearray | memoryview], index: int, hf: Callable[[bytes | str | bytearray | memoryview], bytes], check_inner_node: Callable[[bytes], None] | None = None) bytes[source]

Return the merkle root a branch proves, in internal byte order.

The verifier’s side of merkle_root_and_mutated_from_hashes: given a leaf, the siblings met on the way up and the leaf’s position, this is the root the tree must have had. Equal to a header’s merkle_root, the leaf was in that block – the arithmetic behind Core’s verifytxoutproof, and behind every light client.

index is the leaf’s position in the bottom level; its bits say left child or right child at each step, lowest bit first. branch holds one sibling per level, bottom-up. Both are what they are in the tree, so both are in internal byte order, as this module’s other The merkle functions are: btclib.block.merkle_proof is the entry point taking the reversed order that a txid and a header are displayed in.

A branch is evidence only together with the header that carries the root, and only about the tree: proving that a leaf is a transaction of the block needs one more check, which is check_inner_node. It is called with the 64 bytes each level hashes, and it is a parameter rather than code here because refusing them means knowing what a transaction looks like – a layer this module sits below, and must not import.

btclib.hashes.reduce_to_hlen(msg: bytes | str | bytearray | memoryview, hf: Callable[[], ~btclib.alias.HashObject]=<built-in function openssl_sha256>) bytes[source]

Return the message digested by hf, one digest long.

Step 4 of SEC 1 v.2 section 4.1.3: what the un-underscored signing and verifying spellings do to a message before handing it to their trailing-underscore twins.

btclib.hashes.ripemd160(octets: bytes | str | bytearray | memoryview) bytes[source]

Return the RIPEMD160(*) of the input octet sequence.

btclib.hashes.sha1(octets: bytes | str | bytearray | memoryview) bytes[source]

Return the SHA1(*) of the input octet sequence.

btclib.hashes.sha256(octets: bytes | str | bytearray | memoryview) bytes[source]

Return the SHA256(*) of the input octet sequence.

btclib.hashes.siphash(k0: int, k1: int, octets: bytes | str | bytearray | memoryview) int[source]

Return SipHash-2-4 of octets, keyed by the 128-bit (k0, k1).

k0 and k1 are the two 64-bit words of the key, in the order Core’s CSipHasher(k0, k1) takes them (crypto/siphash.h) and its Python mirror siphash(k0, k1, data) (test/functional/test_framework/crypto/siphash.py) reads them out of a key: two rounds of compression per eight-byte word of octets, padded with a trailing byte counting the input length mod 256, and four rounds of finalization. The result is an unsigned 64-bit integer, v0 ^ v1 ^ v2 ^ v3 of the finalized state, never negative and never wider than 8 bytes.

A hash keyed on a peer- or block-derived secret and not a general-purpose digest: BIP158’s filter and BIP152’s short transaction IDs both build their key from data no counterparty chooses, which is what keeps this fast, non-cryptographic construction from being a hash-flooding target.

Bitcoin Core’s Python test framework offers a second entry point, siphash256(k0, k1, num), for hashing a 256-bit integer – num is Core’s uint256 read as a Python int, and the wrapper is only num.to_bytes(32, ‘little’) ahead of the call above. btclib represents a 32-byte hash as bytes already in that same internal order (hashes.merkle_root and its callers), so a caller here passes such a value to octets directly, and no such wrapper is provided: it would restate the conversion this library never needed in the first place.

btclib.hashes.tagged_hash(tag: bytes, m: bytes, hf: Callable[[], ~btclib.alias.HashObject]=<built-in function openssl_sha256>) bytes[source]

Return BIP340’s tagged hash: hf(hf(tag) || hf(tag) || m).

The doubled tag digest is what makes a hash under one tag invalid under every other.

btclib.hwi module

A PsbtSigner over Bitcoin Core HWI’s JSON command line.

https://github.com/bitcoin-core/HWI

HWI speaks to Trezor, Ledger, KeepKey, Digital Bitbox, Coldcard, BitBox02 and Jade over HID, USB, serial and emulator transports, and publishes a JSON command line so that other software need not. This module is that other software: it runs hwi as a subprocess. Five of its commands answer the contract btclib.psbt_signer defines; a sixth, registerdescriptor, is wrapped beside them with no protocol of its own to answer – registering a policy is not a question psbt_signer asks.

Nothing is imported from hwilib, and nothing has to be installed for btclib to work. HWI declares hidapi, libusb1, cbor2, pyserial, noiseprotocol, protobuf and vendor libraries, and a Python range narrower than btclib’s; a mandatory dependency on that is the thing issue #381 rules out. What this module needs at runtime is an executable, named by the caller and absent until a device is actually being used, so the import costs nothing outside the standard library and the tests run with no HWI at all.

An optional extra importing hwilib beside this was weighed and refused (#469), and the reason is that Python range: HWI declares ^3.9,<3.13, where this library supports 3.10 to 3.14 and pypy, so an extra nobody can install on the two newest interpreters is a second and narrower support matrix rather than an option. A subprocess has no such problem, the executable living in an environment of its own. What a caller who does hold an open hwilib device writes instead is a psbt_signer.PsbtSigner of their own: that contract names an in-process driver as one of its shapes, and it is met in the caller’s environment rather than in this one.

enumerate_devices is the one call that names no device; everything else is HwiSigner, which is selected by fingerprint and passes –fingerprint to every command it runs. That is issue #381’s own rule, and the reason selection is not optional: HWI’s –device-type connects to “the first device of this type enumerated”, so two devices of one vendor make which one signs a question of enumeration order.

The subprocess is bounded twice. timeout is how long a command may run – a device waiting for a button press is the ordinary case, so the default is generous and a caller signing unattended should lower it – and max_output is how much of its answer is accepted: HWI answers with one json object, and a backend that sends megabytes is one whose output is not read to the end. It bounds stdout and stderr separately, and it bounds what is written rather than what is parsed: both streams go to temporary files whose size is watched while the child runs, so a backend past the limit is killed where it stands rather than read to EOF into memory and measured afterwards.

Failures come back as exceptions.SignerError, carrying HWI’s own error code where there is one: -14 is the user pressing the button that says no, -3 is a cable, -9 is a model that will never do it. The one failure that is not about a device – the executable is not installed – is the SignerNotFoundError subclass, so that a caller which also offers signers of other kinds can tell “no hardware here” from “the hardware could not be reached” without matching on the text of a message. is_available is that one question asked before anything is run, which is when a caller deciding what to offer at all has to have the answer: a refusal is the right answer to a question that was asked, and the wrong way to find out there was nothing to ask.

## Wallet policies, and the address displayaddress still cannot show

A Ledger will not display or sign a multisig it has not been shown first. BIP388 wallet policies are how it is shown, and registration is a one-time exchange ending in an HMAC the host keeps and replays on every later call. hwilib/_cli.py exposes registerdescriptor for that exchange, and HwiSigner.register_descriptor wraps it the way getxpub and signmessage are wrapped: one request, one opaque answer, nothing here to check it against.

No HWI release through 3.2.0 carries that subcommand – it is on master – and .github/workflows/integration-hwi.yml installs 3.2.0, so the weekly integration-hwi job runs a command line register_descriptor cannot reach. Raising that workflow’s HWI_VERSION to the first release whose hwilib/_cli.py adds registerdescriptor is what ends the wait, and what makes this paragraph removable.

displayaddress’s BIP388 policy mode – –registration, –index, –multipath-index – is not wrapped, and is on master rather than in a release for the same reason registerdescriptor is. psbt_signer.display_address exists to compare a device’s screen with the address a Descriptor computes, and descriptors.wallet_policy_address is now that address for a policy too: descriptors.wallet_policy builds the @N template and key-information vector BIP388’s own /** describes, from a receive and a change Descriptor of one account – account_descriptors’ own pair – or the narrower /* form from one Descriptor alone, and wallet_policy_descriptor/wallet_policy_address read either pair back into the descriptor and the address the policy describes at an index. What is still missing is the wiring, not the computation (issue #1588): no method here takes –registration, –index or –multipath-index and no protocol this library defines carries a multipath index for psbt_signer.display_address to pass one through. A caller with a registered policy still reads the address off the device’s own screen; that is what the screen is for, whether or not this module checks it too.

Staying aligned with a project this does not import is two things, and neither is a copy of it. tests/hwi_test.py writes out the surface used – the commands, the flags, the answer keys, the error codes – and tests/_data/README.md pins hwilib/_cli.py and hwilib/errors.py to the revisions it was read from, so the weekly upstream re-check reports a command line that moved. What that already caught: signtx answers signed beside the psbt, which sign_psbt now holds the two strings to.

class btclib.hwi.HwiDevice(type: str, model: str, path: str, fingerprint: bytes | None = None, needs_pin_sent: bool = False, needs_passphrase_sent: bool = False, error: str = '', code: int | None = None)[source]

Bases: object

One entry of what hwi enumerate answers.

fingerprint is None for a device that cannot be asked for one yet – a locked Trezor, a Ledger with no app open – and error says why, with HWI’s own code beside it. Such a device is enumerated on purpose: what a caller does about a locked device is unlock it, and a list that left it out would say it is not there.

property is_usable: bool

Answer whether the device answered a fingerprint and no error.

class btclib.hwi.HwiSigner(fingerprint: bytes | str | bytearray | memoryview | None = None, *, executable: str | Sequence[str] = 'hwi', network: str = 'mainnet', timeout: float = 120.0, max_output: int = 1048576, emulators: bool = False, capabilities: SignerCapabilities = SignerCapabilities(taproot=False, musig2=False))[source]

Bases: object

One device, selected by fingerprint, answering the signer contract.

btclib.psbt_signer’s three protocols over five HWI commands: getxpub, signtx, signmessage, displayaddress, and enumerate for the selection. Everything a caller should check about the answers is checked by the functions of that module – request_signatures, display_address, sign_message – and not here: this is the transport, and a transport that also decided what to trust would be two things. register_descriptor is a sixth command with no protocol of its own; the module docstring’s “Wallet policies” says why.

The fingerprint is what a device is named by. Passing one selects it; passing none enumerates and refuses unless exactly one device is usable, because “the first one enumerated” is not a choice a library makes for a caller holding two.

capabilities is the caller’s word: HWI’s JSON CLI does not report what a model supports, and the matrix that does is maintained per vendor and per firmware in HWI’s own documentation. A default of nothing supported is the honest one, and a caller that knows its device says so.

property capabilities: SignerCapabilities

Return what the caller said this device can be asked to sign.

close() None[source]

Refuse further commands; there is no connection to release.

A subprocess per command is what a command line is, so nothing is held open between two of them and closing is a decision rather than a release. Making it refuse afterwards is what gives a caller the same shape as a signer that does hold something – and contextlib.closing then works over either.

display_address(descriptor: Descriptor, index: int = 0) str[source]

Return the address the device shows: HWI’s displayaddress.

The descriptor is sent with the index written into it rather than as the ranged one it may be: HWI derives a ranged descriptor at index 0 whatever was meant, so a caller asking for index 5 would be shown index 0 and told it was 5. descriptors.at_index is what names the one script, and psbt_signer.display_address is what then compares the answer with the address that descriptor describes.

Checksummed, which is what HWI’s –desc parser requires of anything it is given.

property master_fingerprint: bytes

Return the fingerprint this signer was selected by.

Not asked of the device again: it is what every command carries as –fingerprint, so HWI has refused to talk to a device answering anything else before any of them ran.

register_descriptor(name: str, descriptor: Descriptor) str[source]

Register a wallet policy with the device: HWI’s registerdescriptor.

A Ledger will not display or sign a multisig it has not been shown first (module docstring, “Wallet policies”); this is that showing. What comes back is opaque – an HMAC on Ledger, nothing at all on a device that needs none – and is the caller’s to persist and pass back as –registration on a later displayaddress, which this module does not wrap: descriptors.wallet_policy_address computes what a device would show under that flag, but nothing here takes the registration, the index and the multipath index and passes them to displayaddress to compare it against.

The descriptor goes out ranged, whole rather than at one index: registration is of the policy, and displayaddress –index is what later asks for one address of it.

Checksummed, which is what HWI’s parser requires of anything it is given, –desc included.

No HWI release through 3.2.0 has registerdescriptor at all, so this needs a build of master; the module docstring’s Wallet policies says what ends that.

sign_message(message: bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview) str[source]

Return the compact signature of a message: HWI’s signmessage.

Octets as everywhere else in btclib, so a str is the hex of the message and bytes are the message: ecc.bms reads it that way and the two have to agree, a signature being verified here against what was signed there.

What goes on the command line is text, HWI’s signmessage taking a string and passing it to its own signer as one. The bytes are decoded as utf-8 for that, and a message that is not utf-8 is one this backend cannot be asked for – which is a limit of the command line rather than of the device, and is said as such.

sign_psbt(psbt: Psbt) Psbt[source]

Return what hwi signtx answered, parsed and otherwise untouched.

Untouched deliberately: what the answer contains is checked against the psbt that was sent by psbt_signer.request_signatures, which is the caller of this and the one place that comparison belongs.

What is checked here is the other thing, and only this layer can: signtx answers signed beside the psbt – HWI computes it as “the base64 I return is not the base64 I was given” – so the flag and the two strings have to agree. A device claiming it signed while handing back what it was sent, or denying it while handing back something else, has answered inconsistently, and the psbt is not the place that shows it: the comparison is over the very strings that crossed the boundary.

A device that signed nothing is not an error and does not raise. One signer of an m-of-n answers for its own key and for no other, which is the same answer psbt.sign gives by adding nothing; what a caller compares is the psbt it gets back.

xpub(der_path: str | Sequence[int] | int | bytes | bytearray | memoryview) str[source]

Return the extended public key at a path: HWI’s getxpub.

btclib.hwi.enumerate_devices(*, executable: str | Sequence[str] = 'hwi', network: str = 'mainnet', timeout: float = 120.0, max_output: int = 1048576, emulators: bool = False) list[HwiDevice][source]

Return the devices HWI can see, the ones it cannot talk to included.

emulators is HWI’s own –emulators, off by default: an emulator is a device with no secure element and no owner, and enumerating one without being asked would make a test fixture look like a signer.

btclib.hwi.is_available(executable: str | Sequence[str] = 'hwi') bool[source]

Whether the command line this module runs is there to be run.

Asked before running it, rather than read off a failure afterwards. A caller that offers signers of several kinds decides which to offer at all – what devices to enumerate, whether to fall back to a software signer, what to put in front of a user – and that decision comes before there is a device to ask about, so a refusal is not the shape of the answer: enumerate_devices on a host with no HWI raises SignerNotFoundError, which is the right answer to a question that was asked and the wrong way to find out that there was nothing to ask.

What is looked for is argv[0] of what would be run, which is why this belongs here and not in the caller: an executable is a name on the PATH or a whole argv – [“python”, “-m”, “hwilib”], a wrapper with flags of its own – and which part of it has to be on the PATH is this module’s own convention, _executable’s. A caller writing shutil.which(“hwi”) beside it writes the default name a second time and takes that convention as read.

True is not a promise that a device will answer, or that what is on the PATH is HWI at all: it is that there is something to run, which is the half of it a caller cannot find out without running one.

btclib.kdf module

Key derivation functions: SEC 1’s ANSI-X9.63-KDF and RFC 5869’s HKDF.

A KDF stretches one secret into keying data of the length a protocol asks for, under a hash function the caller chooses. ansi_x9_63_kdf is SEC 1’s, one hash per block over a four-octet counter, and ecc.dh.diffie_hellman is the agreement built on it. hkdf is RFC 5869’s, extract then expand, and it is the one piece of BIP324’s key schedule btclib implements: a construction over a hash rather than over a cipher, which is the near side of the line ecc.ellswift draws and ecc.ecies argues.

Both are hashlib and hmac and nothing else: no elliptic curve, and no bitcoin.

Why they are here rather than beside the agreement that uses one (issue 1080). SEC 1 defines ANSI-X9.63-KDF in section 3.6.1 for the agreement of section 6.1, so ecc.dh is where it reads naturally, and with one KDF in the tree that costs nothing. HKDF has no such tie – RFC 5869 has no Diffie-Hellman in it, and nothing that wants HKDF here holds a public key – so a drawer under ecc leaves either a KDF reached through a package of signature schemes or two KDFs in two places for the next one to have to choose between. One drawer, at the layer its contents belong to, is the answer to both; RELEASE_NOTES.md has the spellings it costs.

btclib.kdf.ansi_x9_63_kdf(z: bytes, size: int, hf: Callable[[], HashObject], shared_info: bytes | None) bytes[source]

Return keying data according to ANSI-X9.63-KDF.

Return a keying data octet sequence of the requested size according to ANSI-X9.63-KDF specifications for the key derivation function.

size is a positive number of octets, SEC 1’s keydatalen: a BTClibTypeError if it is no integer, a BTClibValueError if it is zero, negative, or above what the hash function can derive.

http://www.secg.org/sec1-v2.pdf, section 3.6.1

btclib.kdf.hkdf(ikm: bytes, size: int, hf: Callable[[], HashObject], salt: bytes | None, info: bytes | None) bytes[source]

Return keying data according to HKDF, extract then expand.

The composition of the two steps above, which is how RFC 5869 is used unless the caller already holds a uniformly random key: extract concentrates the entropy of ikm under salt, expand stretches the result to size octets under info.

https://www.rfc-editor.org/rfc/rfc5869.html, section 2

btclib.kdf.hkdf_expand(prk: bytes, size: int, hf: Callable[[], HashObject], info: bytes | None) bytes[source]

Return output keying material of the requested size.

prk is a pseudorandom key, hkdf_extract’s answer or a uniformly random key of at least a digest’s length. info is optional context – a protocol label, a party’s identity – which binds the output to an application, so that one prk yields independent keys.

size is a positive number of octets, RFC 5869’s L: a BTClibTypeError if it is no integer, a BTClibValueError if it is zero, negative, or above 255 digests. A prk below a digest is a BTClibValueError too.

https://www.rfc-editor.org/rfc/rfc5869.html, section 2.3

btclib.kdf.hkdf_extract(ikm: bytes, salt: bytes | None, hf: Callable[[], HashObject]) bytes[source]

Return the pseudorandom key of HKDF’s extract step.

ikm is the input keying material, which need not be uniformly distributed – concentrating whatever entropy it has into one digest is what this step is for. salt is an optional non-secret value, which may be reused. The answer is one digest of hf, and is what hkdf_expand takes as its prk.

https://www.rfc-editor.org/rfc/rfc5869.html, section 2.2

An ikm that is already a uniformly random key of full length does not need this step: RFC 5869 section 3.3 says to skip it and expand that key directly, which hkdf_expand is separately public for.

btclib.key module

The canonical form of a bitcoin key, parsed once and carried.

What this is for. A public key has several spellings – SEC octets, a hex string of them, a point, an xpub – and a private key has as many. Every converter in this library takes all of them and answers a tuple, so the canonical form is what comes out of a conversion and never what goes in: a caller that has one has to spell it back for the next call, which parses it again. That round trip is what bip32.derive_ and to_pub_key._sec_from_pub_key work around locally – issues 886 and 887. Issue 896 is the same round trip where script.taproot reads an internal key, and there it is this module that answers it.

PubKeyData and PrvKeyData are that cut: the spellings stay at the boundary, the parse happens once, and what travels afterwards is an object that knows which half it is.

Why the SEC octets are the field and the point is derived. The two conversions do not cost the same. Serializing a point is a byte concatenation; parsing a compressed one is a modular square root, whose cost curves.sec_point.point_from_octets records beside the two arms that pay it. A write is cheaper than a lift and a lift than a derivation. The first of those two gaps is widest exactly where it matters, at the compressed form that bitcoin uses.

So the cheap direction is paid on the way in, the dear one on first use and kept, and PrvKeyData.pub is where laziness buys most. The CHANGELOG entry for this module carries the measurements and the command that took them: one fact in one place, and this is not the place.

That is not only a cache. `point` is also the proof: a length and a prefix are what the constructor checks, and whether those octets are a point of the curve is the question point answers. It is the contract to_pub_key._sec_from_pub_key already states – “the guarantee is the caller’s to complete” – made explicit and paid once, rather than left to each caller and paid again at every one.

Why one type and not two. A PubKeySecData beside a PubKeyPointData would hand the caller a choice that is a cache decision rather than a meaning, which is the burden this module exists to remove; any function taking either would take a union again; and the same key in the two types could not compare equal without performing the very conversion the split was meant to avoid.

Why frozen, and why not `slots=True`. Frozen for the reason BIP32KeyData is (issue 727): a public function handed one may trust it instead of revalidating, and a mutable field would let check_validity=False stay unchecked forever. Not slotted, because functools.cached_property stores into the instance __dict__ and a slotted dataclass has none – it raises TypeError: No ‘__dict__’ attribute. Equality and hashing read the declared fields alone, so what a lazy property has computed never changes either, and two objects for one key compare equal whichever of them has been asked for a point.

The compressed and uncompressed spellings of one key are not equal here, and that is right rather than an oversight: they hash to different addresses.

class btclib.key.PrvKeyData(q: int, network: str = 'mainnet', compressed: bool = True, *, check_validity: bool = True)[source]

Bases: object

A private key as its scalar, on a named network, compressed or not.

compressed is not decoration: it is what a WIF carries and what decides the SEC form of pub, so without it the public key this derives would not be one key but two.

pub is the derivation, and it is the most expensive conversion in this module – a scalar multiplication, dearer than lifting a compressed point – so it is where laziness pays most.

assert_valid() None[source]

Refuse a scalar outside 1..n-1, and an unknown network.

property curve: Curve

Return the curve of the network the key is on.

property pub: PubKeyData[source]

Return the public key this one derives, multiplying once.

check_validity=False, and it is the one call in this module entitled to it. What makes the skip safe is not that assert_valid ran – on an object built with check_validity=False it never did – but that the two lines above ask everything it would: self.curve resolves the network name through network_from_name, which refuses one no network has, and bytes_from_prv_key_int asserts compressed is a bool and answers the SEC form by construction. Asking again is what CONTRIBUTING.md’s “checking them a second time buys nothing” names, and it would be this module’s own argument run backwards.

class btclib.key.PubKeyData(sec: bytes | str | bytearray | memoryview, network: str = 'mainnet', *, check_validity: bool = True)[source]

Bases: object

A public key as its SEC octets, on a named network.

The octets are the field because serializing a point is cheap and parsing one is not; point is the lift, paid on first use and kept. The module docstring carries the reasoning and the CHANGELOG entry the measurements.

assert_valid() None[source]

Refuse octets no SEC public key has, and an unknown network.

A length and a prefix, and not a point: see point, which is where the curve is asked. That sec is bytes at all is not asked here, bytes_from_octets having refused anything else in the constructor whatever check_validity said; network is asked, _normalized being a coercion that refuses nothing.

property curve: Curve

Return the curve of the network the key is on.

property is_compressed: bool

Answer whether the key is in the compressed SEC form.

is_ and not compressed, which is what PrvKeyData calls the same idea: there it is a field, a kind the caller states and a WIF carries, and here it is a question about octets already in hand. CONTRIBUTING.md’s vocabulary is what makes the two names differ, and the difference is the point.

property point: tuple[int, int][source]

Return the curve point the octets encode, lifting it once.

The lift is the proof: assert_valid reads a length and a prefix, and whether what they frame is a point of the curve is what this answers. A key nobody asks this of has never been proved one – deliberately, that being the trade to_pub_key._sec_from_pub_key already makes for the callers that hand the octets straight to a call which parses them anyway.

btclib.muhash module

MuHash3072: a rolling, order-independent commitment to a multiset.

Core’s MuHash3072 (src/crypto/muhash.h/.cpp, at bitcoin/bitcoin@9be056a8a7, tag v31.1) represents a multiset as a fraction of two 3072-bit numbers modulo the largest 3072-bit safe prime, 2**3072 - 1103717: inserting an element multiplies it into the numerator, removing one multiplies it into the denominator, and the two operations are exact inverses of each other regardless of order, or of what else has been inserted or removed meanwhile. gettxoutsetinfo’s own digest of the UTXO set is one such multiset, one element per unspent output, which is what makes MuHash incremental in a way a plain hash of the sorted set is not: adding or removing one output is one multiplication or division, never a walk of what else the set holds.

insert/remove/digest here are Core’s Insert/Remove/Finalize, lower-cased, matched against src/test/crypto_tests.cpp’s own muhash_tests case (tests/_data/muhash_vectors.json, tests/_data/README.md pins the revision).

The arithmetic is native Python int: pow, % and pow(x, -1, m) for the modular inverse digest needs, in place of Core’s own limb-by-limb Num3072 – a fixed-width C++ integer split into 32- or 64-bit limbs for a CPU register that has no 3072-bit width, which Python’s own arbitrary-precision int already is without that machinery. What is committed to – the modulus, the per-element hash, the byte order – is unchanged; only the representation is native rather than reimplementing Core’s carry-and-reduce trick. One divergence this buys for free rather than by design: Num3072’s own “overflow” state (a value held between the modulus and 2**3072, only reduced lazily by FullReduce) has no counterpart here, because % _MODULUS after every multiply keeps the numerator and the denominator always fully reduced – % on a Python int is the exact residue whatever the operand’s magnitude, so nothing is lost by reducing eagerly. crypto_tests.cpp’s own overflow vector is matched regardless (muhash_test.py’s test_muhash_overflow_vector): a value Core carries unreduced until Finalize is folded in here immediately instead, with the same result either way. Not constant-time either – pow(x, -1, m) is the extended Euclidean algorithm – which is not a concern here: every input insert/remove sees is public UTXO set data, never key material, so there is no secret-dependent branch to time.

## The per-element hash, and ISS #1066’s line

_num3072, matching MuHash3072::ToNum3072: SHA256 of the element’s own bytes (a single SHA256, not bitcoin’s usual double one) keys a ChaCha20 stream cipher seeded at nonce zero and block counter zero – Core’s own default, ToNum3072 never calling Seek – whose first 384 bytes of keystream are read as one little-endian 3072-bit integer, matching Num3072::ToBytes, which packs each limb little-endian and the limbs least-significant first. _chacha20_block below is RFC 8439’s block function – the same QUARTERROUND rotation amounts (16, 12, 8, 7) and the same column-then-diagonal ordering chacha20.cpp’s own unrolled REPEAT10 carries – fed the block counter in word 12 and an all-zero 96-bit nonce in words 13-15. Six blocks (_KEYSTREAM_BLOCKS) cover the 384 bytes Num3072::BYTE_SIZE names; the 32-bit block counter never overflows into the nonce word Core’s own ++j12; if (!j12) ++j13; carries into, six being nowhere near 2**32.

[ISS #1066](https://github.com/btclib-org/btclib/issues/1066) put chacha20 on tf2’s side of the line: btclib takes a cipher from its caller rather than shipping one, because a hand-rolled cipher would be the only implementation, on by default, on every installation, on a network path. This module is not an exception to that rule – it is an instance of it read correctly. ChaCha20 enters here as a private function computing one 3072-bit pseudorandom integer per element hashed; nothing is encrypted with it, nothing decodes through it, no byte of its output travels anywhere, and no caller can reach it: __all__ names MuHash3072 and nothing else, so the tree offers no cipher, which is what ISS #1066 protects against. _chacha20_keystream below carries a nonce_words/counter pair for exactly one reason – so that tests/muhash_test.py can drive it against RFC 7539/8439’s own vectors, at a nonce and counter Seek sets and _num3072 never uses – and stays private regardless: a caller-reachable knob over an otherwise-fixed keystream is still a keystream a caller can reach.

class btclib.muhash.MuHash3072[source]

Bases: object

A running numerator/denominator over _MODULUS – Core’s MuHash3072.

The module docstring is where the construction, the per-element hash and the “no overflow state” divergence from Core’s own Num3072 are all argued.

classmethod deserialize(data: bytes | str | bytearray | memoryview) MuHash3072[source]

Parse the 768 bytes serialize produced.

property digest: bytes

The 32-byte commitment – MuHash3072::Finalize.

Core’s own Finalize divides the numerator by the denominator in place and resets the denominator to one – a normalization that leaves the represented value unchanged (its own comment says so) but is otherwise pure bookkeeping, so this reads the digest without mutating self: an accumulator keeps accumulating after a caller reads its digest, unlike Core’s own single-use MuHash3072 acc locals in crypto_tests.cpp. A @property and not a call, this class offering nothing else digest() would be read against: MuHash3072 mirrors no library whose own API fixes the shape, the way btclib.alias.HashObject mirrors hashlib’s.

insert(data: bytes | str | bytearray | memoryview) None[source]

Multiply data into the numerator – MuHash3072::Insert.

remove(data: bytes | str | bytearray | memoryview) None[source]

Multiply data into the denominator – MuHash3072::Remove.

The exact inverse of insert on the same bytes, in either order and regardless of anything else inserted or removed meanwhile: insert/remove only ever multiply the numerator and the denominator independently, and a factor common to both cancels out at digest’s own division whatever else multiplied either one in between.

property serialize: bytes

Return the numerator, then the denominator, each 384 bytes LE.

768 bytes in total; MuHash3072::SERIALIZE_METHODS serializes the same two numbers in the same order.

classmethod singleton(data: bytes | str | bytearray | memoryview) MuHash3072[source]

Build a set holding exactly data – the MuHash3072(span) ctor.

btclib.network module

The Network dataclass, NETWORKS, and the lookups over them.

What this module exports is the dataclass, the catalogue, the three questions asked of a key-value pair and the three asked of an extended-key version, plus the two version sets a caller matches against.

A Network is an encoding table, and NETWORKS is fixed at import. The fields are the prefixes and version bytes a network spells its keys and addresses with, plus the genesis block; every one of them is the same for every deployment of that network, so there is nothing here for a caller to register and the catalogue is a read-only mapping.

What is per deployment – a custom signet’s p2p magic, which its challenge determines – is a fact about a node rather than about an encoding, and lives where the node is spoken to: bitcoin_core_rpc.magic_from_signet_challenge, and BitcoinCoreFetcher(…, signet_challenge=…) for the check it feeds. Fixed at import is what lets the reverse lookups below be tables built once: a network registered afterwards would be found by a scan and missed by a precomputed index, which is the disagreement issue 683 recorded.

The one field that is not an encoding is consensus, and it is a reference rather than a copy: btclib.consensus.CONSENSUS_PARAMS holds the rules each of these networks validates by, this module holds the spellings a chain’s keys and addresses are written in, and a Network carries the row so that the two tables cannot disagree about which networks exist. That module imports nothing of btclib, so reading an activation height costs no import of this one, which is the direction that decides where the table lives.

datadir stays out, and this is where that decision is recorded: it is where this package keeps the five json files loaded at the bottom of this file, so it answers a question about the installation and not one about a network, and the only code that reads it is the loop it is written for – btclib.curves.curve has a datadir of its own for its own catalogues. It is still btclib.network.datadir for a caller who wants the path.

class btclib.network.Network(curve: Curve, genesis_block: bytes | str | bytearray | memoryview, wif: bytes | str | bytearray | memoryview, p2pkh: bytes | str | bytearray | memoryview, p2sh: bytes | str | bytearray | memoryview, hrp: str, bip32_prv: bytes | str | bytearray | memoryview, bip32_pub: bytes | str | bytearray | memoryview, slip132_p2wpkh_prv: bytes | str | bytearray | memoryview, slip132_p2wpkh_pub: bytes | str | bytearray | memoryview, slip132_p2wpkh_p2sh_prv: bytes | str | bytearray | memoryview, slip132_p2wpkh_p2sh_pub: bytes | str | bytearray | memoryview, slip132_p2wsh_prv: bytes | str | bytearray | memoryview, slip132_p2wsh_pub: bytes | str | bytearray | memoryview, slip132_p2wsh_p2sh_prv: bytes | str | bytearray | memoryview, slip132_p2wsh_p2sh_pub: bytes | str | bytearray | memoryview, network_type: Literal['main', 'test'] = 'test', *, consensus: ConsensusParams, check_validity: bool = True)[source]

Bases: object

The encoding table of one network: prefixes, versions, genesis.

What tells a mainnet spelling from a test one – wif and address prefixes, the bech32 hrp, the BIP32 and SLIP132 version bytes – plus the genesis block hash and the consensus row of the chain those spell keys for. No consensus parameter is written out here, consensus being a reference to btclib.consensus’s own table; and no p2p one at all: the message start belongs to the code that speaks to a node, bitcoin_core_rpc.magic_from_chain being where it is, because a custom signet’s is a function of its challenge and therefore not a field any table can hold. NETWORKS holds the built-in instances, and the *_from_network and *_from_xkeyversion functions below are the lookups.

assert_valid() None[source]

Refuse a field of the wrong type, size, or network_type value.

classmethod from_dict(dict_: Mapping[str, Any], *, check_validity: bool = True) Network[source]

Build a Network from the dict shape to_dict writes.

to_dict(*, check_validity: bool = True) dict[str, str | None][source]

Return the network as a dict of hex strings and names.

btclib.network.curve_from_xkeyversion(xkeyversion: bytes) Curve[source]

Return the curve of the network the version bytes belong to.

btclib.network.network_from_key_value(key: Literal['curve', 'network_type', 'consensus', 'genesis_block', 'wif', 'p2pkh', 'p2sh', 'hrp', 'bip32_prv', 'bip32_pub', 'slip132_p2wpkh_p2sh_prv', 'slip132_p2wpkh_p2sh_pub', 'slip132_p2wsh_p2sh_prv', 'slip132_p2wsh_p2sh_pub', 'slip132_p2wpkh_prv', 'slip132_p2wpkh_pub', 'slip132_p2wsh_prv', 'slip132_p2wsh_pub'], prefix: str | bytes | Curve) str | None[source]

Return the oldest network with the (key, value) pair, else None.

Oldest, i.e. ‘testnet’ for the prefixes testnet, regtest, signet and testnet4 share, ‘regtest’ for the bcrt hrp that is regtest’s alone, ‘mainnet’ for mainnet’s. That is the network to encode with: the candidates differ in the genesis block, which no encoding here reads, so the bytes it yields are right for all of them. It is not an answer to “which chain is this”: use network_type_from_key_value for what the prefix does say, or networks_from_key_value for the candidates.

btclib.network.network_from_name(network: str = 'mainnet') Network[source]

Return the Network a name names, in any case and spaced how it likes.

The one place a network: str becomes a Network, and what every caller of a network name should reach for rather than indexing NETWORKS itself: a name no network has is refused here, where NETWORKS[network] answers a bare KeyError. That matters beyond tidiness, KeyError being a LookupError – so no except BTClibValueError written against this library catches it, and a caller filtering bad input sees an exception nothing told it to expect.

NETWORKS stays exported for a caller iterating the five, which is a different question from resolving one name.

btclib.network.network_from_xkeyversion(xkeyversion: bytes) str[source]

Return the oldest network with the xkey version prefix.

‘testnet’ for a testnet, regtest, signet or testnet4 version, those four being the same bytes: the network to derive and re-encode with, since all four agree on every version prefix. It is not an answer to “which chain is this” – network_type_from_xkeyversion is what a prefix can answer, networks_from_xkeyversion the candidates.

btclib.network.network_type_from_key_value(key: Literal['curve', 'network_type', 'consensus', 'genesis_block', 'wif', 'p2pkh', 'p2sh', 'hrp', 'bip32_prv', 'bip32_pub', 'slip132_p2wpkh_p2sh_prv', 'slip132_p2wpkh_p2sh_pub', 'slip132_p2wsh_p2sh_prv', 'slip132_p2wsh_p2sh_pub', 'slip132_p2wpkh_prv', 'slip132_p2wpkh_pub', 'slip132_p2wsh_prv', 'slip132_p2wsh_pub'], prefix: str | bytes | Curve) Literal['main', 'test'] | None[source]

Return “main” or “test” from a (key, value) pair, None if unknown.

Unambiguous where the network name is not: no prefix of a test network equals a mainnet prefix, on any field, so every candidate has the same type and the first one speaks for all.

btclib.network.network_type_from_network(network: str = 'mainnet') Literal['main', 'test'][source]

Return the “main”/”test” type of a network name.

btclib.network.network_type_from_xkeyversion(xkeyversion: bytes) Literal['main', 'test'][source]

Return “main” or “test” from an xkey version prefix.

Unambiguous where the network name is not: no test network version equals a mainnet one, so an xprv is either the real thing or not.

btclib.network.networks_from_key_value(key: Literal['curve', 'network_type', 'consensus', 'genesis_block', 'wif', 'p2pkh', 'p2sh', 'hrp', 'bip32_prv', 'bip32_pub', 'slip132_p2wpkh_p2sh_prv', 'slip132_p2wpkh_p2sh_pub', 'slip132_p2wsh_p2sh_prv', 'slip132_p2wsh_p2sh_pub', 'slip132_p2wpkh_prv', 'slip132_p2wpkh_pub', 'slip132_p2wsh_prv', 'slip132_p2wsh_pub'], prefix: str | bytes | Curve) list[str][source]

Return every network with the (key, value) pair, oldest first.

The list is the ordinal the singular lookups below hide: [0] is the canonical answer, [n] the nth network sharing those bytes – and its length says how many there are, which is what “testnet” alone could never say. Mostly it holds the four test networks (one set of prefixes between them) or exactly one (mainnet’s bytes, and regtest’s bcrt hrp, are unique).

A scan where the xkey-version trio is a table, and not for want of a key: prefix is whatever a caller passes, so a dict would answer an unhashable one with a TypeError where the comparison answers “no network carries this”. Five networks and one getattr each is what that costs.

A key that names no field of Network is refused rather than scanned for: getattr would raise AttributeError, which is neither a ValueError nor something a caller of this library is told to catch, and answering [] instead – “no network carries this prefix” – would be worse, a typo in a field name reading as a fact about the prefix.

btclib.network.networks_from_xkeyversion(xkeyversion: bytes) list[str][source]

Return every network with the xkey version prefix, oldest first.

One lookup, where asking each network in turn rebuilt two version lists per network asked – issue 683 measured what that cost the address path, and the table it answers from is why NETWORKS is fixed at import.

btclib.network.xprvversions_from_network(network: str = 'mainnet') list[bytes][source]

Return every xprv version of the network, BIP32 and SLIP132.

btclib.network.xpubversion_from_xprvversion(xprvversion: bytes) bytes[source]

Return the xpub version paired with an xprv version.

The same network and the same script type: xprv to xpub, yprv to ypub, Zprv to Zpub. What neutering re-labels a key with, and the one question here a version pair answers rather than a version alone – which is why it is a table and not two positions in the sets above.

btclib.network.xpubversions_from_network(network: str = 'mainnet') list[bytes][source]

Return every xpub version of the network, BIP32 and SLIP132.

A fresh list off the table built at import, so that a caller sorting or trimming the answer is not editing the table every other lookup reads.

btclib.number_theory module

Number theory and modular arithmetic functions.

Implementations originally from https://en.wikibooks.org/wiki/Algorithm_Implementation/Mathematics/Extended_Euclidean_algorithm and https://codereview.stackexchange.com/questions/43210/tonelli_var-shanks-algorithm-implementation-of-prime-modular-square-root/43267 with the following modifications:

  • type annotated Python3

  • minor improvements

  • added extensive unit test

btclib.number_theory.legendre_symbol_var(a: int, p: int) int[source]

Compute the Legendre symbol a|p, as a binary Jacobi symbol.

p is a prime, a is relatively prime to p (if p divides a, then a|p = 0). It returns 1 if a has a square root modulo p, -1 otherwise. The Jacobi symbol is what is computed, and for a prime modulus the two are the same number.

By the reciprocity recursion rather than by Euler’s criterion, which is pow(a, (p - 1) // 2, p) – an exponentiation the size of the square root the caller is asking about, where this is a gcd, several times cheaper on secp256k1’s p over 3000 calls, best of seven. The factors of two come out all at once, a & -a being the lowest set bit, which is libsecp256k1’s secp256k1_ctz64_var; asking a gcd rather than an exponent is what its secp256k1_fe_is_square_var does, through secp256k1_jacobi64_maybe_var and never through a power. Its own recursion is the safegcd one, which in bytecode loses as every safegcd does – curves.curve_group_2 keeps the list.

The loop’s length follows a, where an exponentiation’s did not. SECURITY.md publishes the Python path as variable-time, and nothing in the tree asks this about a value that has to stay hidden: curves.curve._is_x_coordinate_var is the one caller, and what reaches it is a signature’s r, the x-coordinate of a serialized xpub, or a candidate x of an ElligatorSwift encoding – each of them public, and on secp256k1 each answered by the bindings before this is reached.

btclib.number_theory.mod_inv(a: int, m: int) int[source]

Return the inverse of a (mod m), timed on a random value instead.

What mod_inv_var is for an operand that is public, this is for one that is secret. The extended Euclid under that one takes the iterations its input asks for, and for an operand drawn uniformly below m what its duration carries is the operand’s bit-length: on secp256k1’s order a 256-bit scalar takes about twice what a 128-bit one does, falling at every step between them. That correlation is what the Minerva attack collects – an ECDSA nonce is such a scalar, and a few thousand signing times sorted by it are a lattice away from the private key.

(b*a)^-1 * b is a^-1 for every b invertible mod m, so drawing b at random leaves an inverse whose iteration count follows b and tells an observer nothing about a: 1.02x across the same range of operands, where mod_inv_var is 2.06x. Two multiplications, two reductions and a draw from secrets, which is 1.11x on a 256-bit operand and 1.5% of the whole Python signature it sits in. Fermat’s pow(a, m - 2, m) is the alternative and is flat for a different reason, its ladder running on the fixed exponent rather than on a random operand; it is not the one chosen, at 8.38x.

The draw is what costs, so a small modulus pays proportionally more – 4.8x on an order of 11, where the Euclid is a few iterations and secrets is the same syscall. Nothing signs with such an order outside the test suite.

Not a constant-time inverse, and no more claiming to be one than curve_group._blinded_jac does: the duration is still an extended Euclid’s and still visible, and what the blinding changes is whose it is. SECURITY.md publishes the Python path as variable-time, and CONTRIBUTING.md has what a name in this library does and does not promise about duration.

btclib.number_theory.mod_inv_batch(a: Sequence[int], m: int) list[int][source]

Return every inverse, each timed on a random value instead.

What mod_inv is to mod_inv_var, this is to mod_inv_batch_var: the twin a secret may be handed. Montgomery’s trick inverts one running product for the whole sequence, so the single extended Euclid it spends is timed on a value every element went into – which is the channel, the same one and no smaller for being shared.

Each element is blinded with a factor of its own rather than the sequence with one: inv(a_i * b_i) * b_i is inv(a_i), and the product the batch forms is then a product of blinded values. One factor for all of them would blind that product and leave the ratios a_i / a_j in the peeled-back inverses, which is what the running products are made of.

So it costs n draws from secrets and 2n multiplications on top of the trick, and the draws are the whole of it: measured on secp256k1’s p over 16 random elements, best of nine alternating rounds, 2.1x mod_inv_batch_var, and the difference is what those draws cost.

It is still the trick, which is the point of it being a batch at all: over the same 16 elements, blinding the batch is 3.6x cheaper than blinding one at a time, where the unblinded batch is 6.3x cheaper than the unblinded singles. The saving shrinks as the draws grow with n and the one Euclid does not; the trick still wins at every size worth batching.

Not constant-time, for the reasons mod_inv gives at length. The empty sequence is not an error here either.

btclib.number_theory.mod_inv_batch_var(a: Sequence[int], m: int) list[int][source]

Return the inverse of every element of a (mod m), in its order.

m does not have to be a prime, and every element has to be invertible modulo it, as mod_inv_var requires of its one operand.

Montgomery’s trick, which libsecp256k1 spells secp256k1_fe_inv_all_var: the running products a[0], a[0]*a[1], …, a[0]*…*a[n-1] are formed, the last of them is inverted once, and the individual inverses are peeled back off it. So n inverses cost one inverse and 3(n-1) products, where n calls to mod_inv_var are n extended Euclids – an inverse modulo a 256-bit prime being some thirty times a product.

An empty sequence has no inverses and is not an error: it is what a caller that filtered its own input is left with.

btclib.number_theory.mod_inv_var(a: int, m: int) int[source]

Return the inverse of a (mod m).

m does not have to be a prime.

pow(a, -1, m) is an Extended Euclidean Algorithm too – CPython’s long_invmod, which is the loop xgcd_var runs with the second cofactor dropped – so this delegates the same algorithm to C rather than interpreting it, and that is the whole of why it is called. It is not a constant-time inverse and neither was the bytecode one; SECURITY.md publishes the Python path as variable-time.

Its duration follows the operand, so a secret one goes to mod_inv instead: what that duration carries is measured there, and it is enough to recover a private key from a signature.

What pow does not carry is this module’s contract, so the checks stay above it and the message below it: a non-invertible operand leaves pow as a bare ValueError naming neither operand, and rebuilding the message says more than chaining that one would.

btclib.number_theory.mod_sqrt_var(a: int, p: int) int[source]

Return a quadratic residue (mod p) of a; p must be a prime.

Solve the equation:

x^2 = a mod p

and return x; p - x is also a root.

If a simple solution is not available for p, then the Tonelli-Shanks algorithm is used.

https://codereview.stackexchange.com/questions/43210/tonelli_var-shanks-algorithm-implementation-of-prime-modular-square-root/43267

btclib.number_theory.tonelli_var(a: int, p: int) int[source]

Return a quadratic residue (mod p) of a; p must be a prime.

The Tonelli-Shanks algorithm is used.

https://codereview.stackexchange.com/questions/43210/tonelli_var-shanks-algorithm-implementation-of-prime-modular-square-root/43267

btclib.number_theory.xgcd_var(a: int, b: int) tuple[int, int, int][source]

Return (g, x, y) such that a*x + b*y = g = gcd(x, y).

based on Extended Euclidean Algorithm, see https://en.wikibooks.org/wiki/Algorithm_Implementation/Mathematics/Extended_Euclidean_algorithm

btclib.psbt_signer module

The contract an external signer answers, and the checks on its answers.

A hardware wallet, a signing service, another process: something that holds keys btclib does not have and answers questions about them. The protocols here are what it implements; the functions beside them are what a caller should run over its answers, and they are the point of the module – a protocol alone is an interface, and every one of these answers arrives from outside and can be wrong.

This is not psbt.sign, which is the Signer role played over a KeyManager btclib calls in-process: that one derives keys and signs, and its answers are btclib’s own. Here the psbt goes out and comes back untrusted, so the two need different trust models and are two contracts.

What each function checks, which is what the caller would otherwise have to remember:

  • request_signatures holds the returned psbt to the one that was sent – psbt.assert_signatures_only, so nothing but signatures came back – and only then combines the two;

  • export_account builds the descriptors of an account from the fingerprint and the xpub a signer answers with, and descriptors.account_descriptors is what refuses an xpub that is not the account the path names;

  • display_address compares the address a device shows with the one the descriptor describes, which is the whole point of asking a device to show one;

  • sign_message verifies the signature against the address the caller says it must open to.

Nothing here sends a private key anywhere, and nothing can: a Descriptor holds no key that signs, descriptors.parse having neutered what it read, and assert_public is what says so of one built by hand rather than parsed. A psbt has no field for a private key at all.

Selecting which device answers, the transport it answers over, and the timeouts and output limits a subprocess needs are the adapter’s, not this module’s: this is the contract such an adapter implements (issue #381).

SignerDecorator is the other thing a caller writes against the contract: a signer wrapping a signer, for the rules that are the caller’s own – a whitelist of outputs, a limit on what may be spent, a prompt somebody has to confirm. The rule belongs nowhere near this library and the forwarding does, being what goes wrong when it is written by hand.

class btclib.psbt_signer.AddressDisplay(*args, **kwargs)[source]

Bases: Protocol

A signer that can show an address on a screen of its own.

Optional, and separate from PsbtSigner for the reason the issue behind this module gives: a signer that cannot show anything is still a signer, and a caller asks with isinstance rather than being told.

display_address(descriptor: Descriptor, index: int = 0) str[source]

Return the address the signer shows for a descriptor at an index.

class btclib.psbt_signer.MessageSigner(*args, **kwargs)[source]

Bases: Protocol

A signer that can sign a message with a key it holds.

Optional in the same way, and the message is not a transaction: what comes back is a BIP137 compact signature, which sign_message checks against the address the caller says it must open to.

sign_message(message: bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview) str[source]

Return the compact signature of a message, by the key at a path.

class btclib.psbt_signer.PsbtSigner(*args, **kwargs)[source]

Bases: Protocol

What every external signer answers: keys, a signature, an end.

The three questions a caller cannot answer for itself and one piece of housekeeping. Nothing here is about a device in particular – a subprocess adapter, a signing service and a software signer implement the same four – which is what makes it the boundary rather than a driver.

property capabilities: SignerCapabilities

Return what this signer can be asked to sign.

A property for master_fingerprint’s reason: what every implementation answers is a value it was given or built once.

close() None[source]

Release whatever the signer holds: a handle, a process, a socket.

Idempotent, so that a caller may close a signer it is not sure about; contextlib.closing is the customary way to run one.

property master_fingerprint: bytes

Return the four bytes identifying the master key, BIP32’s own.

A property, and that is a promise the contract makes: reading it is free. Every implementation there is keeps it – HwiSigner holds the fingerprint it was selected by and says so (“not asked of the device again”), SoftwareSigner derives it from a key it already has, and a decorator forwards – so nothing is asked to pay for the shape (issue #814).

It is worth knowing what would ask for the shape back. HWI’s own HardwareWalletClient.get_master_fingerprint is a device call: it fetches the key at m/0h and reads the parent fingerprint off it. An adapter written against that library rather than against the command line would want a method here, and the way to give it one is to relax this to a method again – which is a change to the contract, made deliberately, and not something to leave room for in advance.

sign_psbt(psbt: Psbt) Psbt[source]

Return the psbt with the signatures this signer can add.

The answer is untrusted, whatever the transport: what a caller does with it is request_signatures, which holds it to the psbt that was sent before merging anything.

xpub(der_path: str | Sequence[int] | int | bytes | bytearray | memoryview) str[source]

Return the extended public key at a derivation path.

class btclib.psbt_signer.SignerCapabilities(taproot: bool = False, musig2: bool = False)[source]

Bases: object

What a signer can be asked to sign, in the terms btclib acts on.

Two flags and not a device matrix: which models support what is a table HWI maintains per vendor and per firmware version, and a library that copied it would be wrong the week after. What belongs in a contract is what a caller does differently on the answer, and there are two such facts – a taproot input needs a signer that knows BIP341, and a MuSig2 session needs one that knows BIP327 and BIP373.

Which operations a signer offers is not here either: that is what the optional protocols below say, and a caller asks with isinstance, both being runtime-checkable. A flag saying “I can display an address” beside a display_address method would be the same fact twice, and the two would disagree.

class btclib.psbt_signer.SignerDecorator(signer: PsbtSigner)[source]

Bases: object

A signer that wraps a signer, for a caller adding a rule to one.

The shape every “sign, but only if” is: a rule of the caller’s own in front of sign_psbt, and everything else answered by the signer underneath. A whitelist of outputs a device may pay to, a limit on what a psbt may spend, a log of every request, a prompt somebody has to confirm – what those have in common is not the rule, which is the caller’s business and belongs nowhere near this library, but the four other methods, which have to keep answering exactly what the wrapped signer answers. A subclass overrides the one it is about:

class Whitelisted(SignerDecorator):
    def sign_psbt(self, psbt: Psbt) -> Psbt:
        for out in psbt.tx.vout:
            ...          # the caller's rule, before the device
        return super().sign_psbt(psbt)

Written out here because forwarding is what goes wrong when it is written by hand: a wrapper that answers capabilities for itself tells a caller a taproot input cannot be signed by a signer that can, and one that forgets close leaves a subprocess running after the caller closed what it was holding.

Wrapping does not hide what the signer offers. AddressDisplay and MessageSigner are optional protocols a caller asks about with isinstance, so a wrapper that never carries them turns a device that can show an address into one that cannot, and a wrapper that always declares them turns a signer that cannot into one that fails when asked. Each operation is therefore bound on the instance, and only where the wrapped signer has it, so isinstance answers what the signer offers – and a subclass that writes one of its own keeps it, an attribute written by a class being what says the subclass means to answer that question itself.

On the instance rather than through __getattr__, which is the way this is usually written and is wrong in a way nothing reports: since 3.12 a runtime-checkable protocol is checked with inspect.getattr_static, which does not call __getattr__, so a wrapper delegating that way satisfies isinstance on 3.10 and 3.11 and stops satisfying it on 3.12 and after – the same wrapper, the same signer, a different answer per interpreter.

Nothing else is forwarded. What this is is the contract, not the surface of the adapter underneath: a caller that wants an attribute of the signer it wrapped reads .signer, which is what it passed in.

property capabilities: SignerCapabilities

Return what the wrapped signer can be asked to sign.

close() None[source]

Close the wrapped signer, and whatever it was holding open.

property master_fingerprint: bytes

Return the wrapped signer’s master fingerprint.

sign_psbt(psbt: Psbt) Psbt[source]

Return what the wrapped signer answers, this adding no rule.

xpub(der_path: str | Sequence[int] | int | bytes | bytearray | memoryview) str[source]

Return the wrapped signer’s extended public key at a path.

class btclib.psbt_signer.SignerDevice(*args, **kwargs)[source]

Bases: Protocol

What a caller knows of a device before it has a signer for it.

Selecting which device answers is the one thing a caller does with no signer in hand, and it is the same rule for every transport: the fingerprint identifies the master key, so it identifies the device that holds it, whatever answered for it. hwi.HwiDevice satisfies this; so does a caller’s own record of a signer that is not a device at all.

fingerprint is None for a device that cannot be asked for one yet – a locked Trezor, a Ledger with no app open – and error says why. Such a device is listed on purpose: what a caller does about a locked device is unlock it, and a list that left it out would say it is not there.

property error: str

Return why the device could not be asked, empty if it could.

property fingerprint: bytes | None

Return the master fingerprint, or None if it cannot be asked.

class btclib.psbt_signer.SoftwareSigner(xkey: BIP32KeyData | bytes | str | bytearray | memoryview, *, musig2: bool = False)[source]

Bases: object

Keys in this process, at known paths, answering the contract above.

The reference implementation of PsbtSigner, AddressDisplay and MessageSigner, and what makes the contract testable without hardware: a deterministic signer is a signer whose answers a test can predict, so every psbt shape the library builds can be signed end to end here before any device is involved.

It is not a way to sign with a key you hold – psbt.sign over a KeyManager is that, and this calls it. What this adds is the boundary: it answers only what the protocol asks, by deriving what it is told to derive, and it holds nothing about the caller. Which is why it is worth having beside a device rather than instead of one – an adapter and this answer the same questions, so a caller can be developed against this and run against that.

A key is answered for when the origin’s fingerprint is this signer’s and the path derives to the very public key the psbt names. That second half is the check a device makes too: a psbt saying “this key is at that path” is a psbt somebody else wrote, and signing with what the path derives to without looking would sign with a key the caller was not told about.

One key or several is the same model: what is held is a key at a path from the master, and SoftwareSigner(xkey) is the case where that path is empty and the key is the master. from_accounts is the other case, where a device exported accounts and kept its master – the shape a psbt names, its key origins being a master fingerprint and a path from it.

property capabilities: SignerCapabilities

Return what this signer can be asked to sign.

Taproot always: psbt.sign signs the key path and every leaf the psbt names this signer’s keys in, which is both halves of a BIP341 output for a signer holding what they ask for. MuSig2 is a constructor argument and defaults to False, because the rounds of BIP373 are psbt.musig2’s and are played by a caller holding the secret nonce between them – this signer signs in one call and cannot answer for a session it does not hold.

close() None[source]

Mark the signer closed; there is nothing to release.

A software signer holds no handle and no process, so this exists for the contract: a caller that closes every signer it opens is a caller that works with a device too. Asking a closed signer anything raises, which is what makes the difference visible in a test rather than only against hardware.

Nothing survives a signature here, which is what keeps that true: sign_schnorr_script_path below owns its ssa.Signer for the length of a call and wipes it on the way out, so there is no keypair whose release rests on a caller remembering this method.

display_address(descriptor: Descriptor, index: int = 0) str[source]

Return the address the descriptor describes, as a screen would.

There is no screen here, so what this answers is what a device would show if it agreed – which makes display_address above a check of the descriptor against itself when this signer is the one asked. That is what a reference implementation is for: the caller’s flow runs unchanged, and the check that matters is the one made against a device that could have disagreed.

classmethod from_accounts(master_fingerprint: Octets, accounts: Mapping[DerPath, BIP32Key], *, musig2: bool = False) SoftwareSigner[source]

Return a signer holding accounts, for the master they came from.

What a device that exported its accounts leaves behind, and what SoftwareSigner(xkey) cannot express: the fingerprint a psbt names is the master’s, and an account key’s own is a different four bytes, so a signer built on an account would answer for no origin the device’s psbts carry.

The fingerprint is therefore told rather than computed, and is a claim: nothing in an extended key records where it came from, so an account paired with the wrong master answers for origins whose keys it does not hold – and the public key check refuses each of them, one derivation later.

Each path is the account’s own, from that master. An origin is answered by the account whose path is a prefix of it, the remainder being what is derived; where two accounts prefix the same origin the longer one answers, having less left to derive. Fingerprint, then prefix, then the public key: the three are what HWI’s ledger driver matches on and what electrum’s keystore tries first, which is the shape a psbt written by a wallet has.

An origin naming an account’s own fingerprint rather than the master’s is the other thing a wallet writes, and it is SoftwareSigner(account_xkey): a signer answers one fingerprint, master_fingerprint being a single question, so which of the two a psbt carries decides which constructor reads it.

What is not done is electrum’s third attempt, which ignores the fingerprint and tries the last few indexes against the public key anyway. It is a search for a key the psbt did not say is there, and a match found that way is a coincidence a signature would make binding.

property is_watch_only: bool

Answer whether this signer holds no key that signs.

property master_fingerprint: bytes

Return the fingerprint of the key this signer was built on.

The master fingerprint of the contract, which is this key’s own and is therefore a claim only as true as the key handed in: a signer built on an account xpub answers that account’s fingerprint, and a key origin naming it would send another signer looking for a master key nobody has.

sign_ecdsa(pub_key: bytes, origin: BIP32KeyOrigin | None, msg_hash: bytes) bytes | None[source]

Return the DER signature of msg_hash by pub_key, or None.

sign_message(message: bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview) str[source]

Return the BIP137 compact signature of a message, base64.

The address the signature opens to is the p2pkh one of the key at the path, which is what ecc.bms signs with by default and what a caller checks it against.

sign_psbt(psbt: Psbt) Psbt[source]

Return the psbt with a signature for every key this one holds.

psbt.sign over a KeyManager this class implements, which is the whole of it: the roles are btclib’s already, and a reference signer that re-derived the sig_hash itself would be a second implementation to keep right.

A watch-only signer raises rather than answering the psbt unchanged: “I hold none of these keys” and “I hold no key at all” are different answers, and only the first is a psbt somebody else can carry on with.

sign_schnorr(pub_key: bytes, origin: BIP32KeyOrigin | None, msg_hash: bytes, merkle_root: bytes) bytes | None[source]

Return the BIP340 signature of msg_hash by pub_key, or None.

Tweaked by the merkle root before signing, which is the KeyManager contract: the signature has to be the output key’s, and sign never holds what tweaking a private key needs.

sign_schnorr_script_path(pub_key: bytes, origin: BIP32KeyOrigin | None, msg_hash: bytes, leaf_hash: bytes) bytes | None[source]

Return the BIP342 signature of msg_hash by pub_key, or None.

Untweaked, which is the KeyManager contract for a script path: what the spend proves is the leaf, and the output key’s tweak is proved by the control block instead.

Every leaf the psbt names this key in is signed for. A device would show the leaf script and ask, and leaf_hash is what it would find it by; a signer holding keys at known paths and answering in one call has no user to ask, so what it answers for is decided by the same three conditions as the other two methods.

ssa.Signer and not ssa.sign_, for one leaf as for many: what it saves is the Sig that sign_ builds and serialize takes apart again, and a psbt wants the octets. The keypair it holds is built and wiped inside the call.

Holding one across calls was measured and is not done. Those leaves are the one place this library signs BIP340 more than once under one key, and a keypair kept between them is cheaper per leaf – but only from the second leaf of a key onward, and a key in a single leaf is the ordinary shape. The saving is the smaller half of what Signer buys and the only half that makes a secret outlive the call that needed it, which is not a trade to make for a case that may not arise.

property xkey: str

Return the key this signer was built on, where it was built on one.

A signer holding accounts was built on none: there is no key of which the others are derivations, and answering one of them would be answering a key the caller did not ask about.

xpub(der_path: str | Sequence[int] | int | bytes | bytearray | memoryview) str[source]

Return the extended public key at a path, neutered whatever it is.

Public whether this signer holds a private key or not: what the contract asks for is an xpub, and answering an xprv would put a key that signs where a caller expects one that cannot.

btclib.psbt_signer.assert_public(descriptor: Descriptor) None[source]

Raise if any key of the descriptor is one that signs.

Nothing descriptors.parse returns can fail this: it neuters every xprv it reads and hands the private spelling back to its caller. What this catches is a descriptor built by hand, which the fragment classes are public enough to allow – and the moment before it is sent to something outside the process is the moment to catch it.

btclib.psbt_signer.display_address(signer: AddressDisplay, descriptor: Descriptor, index: int = 0) str[source]

Return the address the signer shows, having checked it is the right one.

The whole point of asking a device to display an address is that the screen is the one part of it a compromised host cannot rewrite – so what the device says has to be compared with what the descriptor describes, and a caller that shows the user its own answer instead has checked nothing.

The descriptor is checked to hold no key that signs before it is sent anywhere, which is assert_public.

btclib.psbt_signer.export_account(signer: PsbtSigner, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview, script_type: Literal['p2pkh', 'p2wpkh-p2sh', 'p2wpkh', 'p2tr'] | None = None) tuple[Descriptor, Descriptor][source]

Return the receive and change descriptors of an account of a signer.

Two questions to the signer and one composition: descriptors.account_descriptors builds the pair from the master fingerprint and the xpub at the account path, and is what refuses an xpub that is not the account the path names – its depth and its own index say which account it is, and a purpose the mapping does not know is refused rather than guessed.

What cannot be checked here is that the xpub descends from that fingerprint at all: an extended key records nothing about where it came from, and a signer that answered with another key would need a second, independent path to be caught – an address the device shows for the same descriptor, which is display_address.

btclib.psbt_signer.merge_devices(*sources: Sequence[SignerDevice]) list[SignerDevice][source]

Return one list of devices from several, the earlier source winning.

A caller with more than one way of reaching a signer – a command line, an in-process driver, keys of its own – has one question to answer that none of the adapters can: which of them answers for a fingerprint two of them offer. Order is that answer, and it is the caller’s to state, so the sources are positional and the first one that names a fingerprint keeps it.

Devices that cannot be asked for a fingerprint yet are all kept: there is no fingerprint to be a duplicate of, and dropping them would say a locked device is not plugged in.

btclib.psbt_signer.request_signatures(signer: PsbtSigner, psbt: Psbt) Psbt[source]

Return the psbt with what the signer added, checked and merged.

Three steps and the middle one is why this exists: the psbt goes out, the answer is held to it – everything that is not a signature comes back as it was sent, the signature fields may only have gained entries, and every signature that arrived verifies – and only then are the two combined.

Skipping that check is not a smaller version of this call: combine takes the union of what it is given and resolves a conflict by picking a side, so an answer that changed an amount, an outpoint or somebody else’s signature would be merged in without a word.

The psbt handed in is left alone, combine returning a copy of its own, so a caller can ask several signers with the same request and combine the answers itself.

btclib.psbt_signer.select_device(devices: Sequence[SignerDevice], fingerprint: Octets) SignerDevice[source]

Return the one device answering for a fingerprint, or raise.

Two failures and they are different news, so they are different messages: no device answers for it, or one does and it said why it cannot be asked – which is a device to unlock rather than a device to look for.

More than one is not among them: merge_devices is where a caller states which source wins, and a single source answering one fingerprint twice is two cables to one device, so the first is taken.

btclib.psbt_signer.sign_message(signer: MessageSigner, message: bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview, address: bytes | str | bytearray | memoryview) str[source]

Return the signature of a message, verified against an address.

The address is a parameter and not something this works out: a BIP137 signature carries a recovery flag that says which address type it is for, and which address a caller means is a fact about the key it asked for rather than about the signature that came back. What is checked is the one thing that matters – the signature opens to that address – and ecc.bms.assert_as_valid is the check.

btclib.psbt_signer_contract module

Check an implementation of psbt_signer’s contract, from outside it.

psbt_signer says what a signer answers; this says whether one does. A protocol is a promise the type checker reads and nothing runs, so an adapter that returns a five-byte fingerprint, or a psbt it edited, or an xpub it derived from the wrong path, type-checks and is wrong at the first spend.

btclib’s own adapters are checked by btclib’s tests, which is no help to the caller writing the next one: an implementer outside this repository had no way to ask the library whether their signer answers what callers of it will assume. assert_psbt_signer is that question, and it takes any PsbtSigner – a command line adapter, an in-process driver, a signing service, a signer that is not a device at all.

It is a function and not a test suite so that it belongs to no test framework: call it from pytest, from unittest, from a script run against the hardware on a bench. It raises on the first breach, with what was expected and what came back, because the first breach is the one to fix and a list of consequences of it is not more information.

What it checks is what a type cannot say. A method returning the wrong type is what the implementer’s own type checker reports, and repeating that here would be a second, weaker copy of it; a fingerprint of the wrong width, an xpub that is not one, a key answered privately, a signature added to somebody else’s input – none of those are type errors, and all of them type-check.

What it does not check is whether the signatures are right. That is request_signatures, which holds an answer to the psbt that was sent and verifies every signature that arrived – the check that matters most is the one a caller runs on every spend, not one a conformance pass runs once. What this adds is the shape of the answers around it: the things request_signatures assumes and does not restate.

Two of the checks need material only the caller has, and both are optional. A signer holds keys at paths this module cannot guess, so der_path is asked for rather than defaulted – a wrong guess would report a conforming signer as broken. And a psbt the signer can actually sign is the only way to see it sign, so signable is what turns a shape check into an end-to-end one.

btclib.psbt_signer_contract.assert_psbt_signer(signer: object, *, der_path: DerPath | None = None, signable: object = None) None[source]

Check a signer against the contract, raising at the first breach.

der_path is a path the signer holds a key at; without one the xpub checks are skipped, since a path guessed here would report a conforming signer as broken. signable is a psbt the signer can sign; without one the checks are of shape alone, and nothing sees it sign.

Both it and the signer are typed object rather than what they have to be. A function whose subject is what a type cannot promise is one that has to be callable with what a type would have refused, and it says what arrived instead of the caller’s type checker saying it first: an adapter written without one is exactly the caller this exists for.

Closing is checked last and twice, close being documented as idempotent – a caller closes a signer it is not sure about – so the signer is spent when this returns.

btclib.psbt_signer_contract.optional_protocols(signer: object) tuple[bool, bool][source]

Return which optional protocols the signer offers, as a caller asks.

isinstance against the two runtime-checkable protocols, which is the whole of it: what display_address and sign_message answer is checked by the functions of psbt_signer that call them, against the descriptor and the address the caller says the answer must match, and a conformance pass has neither.

btclib.psbt_signer_contract.unsignable_psbt(fingerprint: bytes) Psbt[source]

Return a psbt of one input no signer of this fingerprint can sign.

A p2wpkh whose key origin names somebody else’s master, which is the shape of the psbt a caller sends to every signer it has and expects most of them to hand straight back. Built from the signer’s own fingerprint with a bit flipped, so it is somebody else’s for this signer whoever it is, and no caller has to supply a key to find out what the signer does with a psbt that is not its business.

btclib.silent_payments module

Silent payments, according to BIP352.

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

A silent payment address is published once and reused; every payment to it lands on a different taproot output, so nothing on chain links two payments to the same recipient. The recipient publishes two keys, a scan key and a spend key, and the sender derives the output from an ECDH shared secret between its own input keys and the scan key – so the output is one the sender can compute and only the recipient can spend, with no interaction and nothing extra in the transaction.

Where the shared secret comes from is the whole design. It is not the sender’s ephemeral key, which would need a place in the transaction to publish it; it is the sum of the private keys the sender is signing the inputs with. The recipient recovers the same secret from the public keys of those inputs, which the transaction already carries, so scanning is one multiplication per transaction and the transaction is an ordinary taproot spend.

The pieces, bottom-up:

  • pub_key_from_input reads the public key of one input, and answers None for an input BIP352 does not count. Only p2pkh, p2wpkh, p2sh-p2wpkh and p2tr count: an input with conditional branches or several keys could be re-signed with a different set after the output was derived, which in a coinjoin is somebody else’s malleability, and uncompressed keys are excluded as BIP143 already recommends.

  • prv_key_sum and pub_key_sum are the two sides of the same sum, the taproot negation included: an x-only key has two private keys, and sender and recipient have to pick the same one.

  • input_hash binds the sum to the transaction’s smallest outpoint, so that the same input keys spent in two transactions derive two different outputs. tweak_data is that hash times the public sum, which is what a light-client server can publish per transaction (BIP352’s Appendix A) and all a scanner needs.

  • shared_secret is the multiplication both parties do, from either end.

  • output_keys is the sender’s whole operation, and scan_outputs the recipient’s – for a caller holding a light client’s tweak, per BIP352’s Appendix A; scan_transaction_outputs is the same recipient’s operation for a caller holding the transaction itself, outpoints and input public keys, which is what lets it reach the bindings the way output_keys does.

  • label_tweak, labeled_address_from_keys and label_lookup are the optional third piece: one published address per purpose, all sharing one scan key, at the cost of a subtraction per output while scanning.

What is not here is the transaction-level policy, which is a wallet’s: BIP352 says a transaction is worth scanning when it has a taproot output, has an eligible input, and spends no output of segwit version above 1, and that a sender must sign with a sighash flag that fixes the inputs – SIGHASH_ANYONECANPAY breaks the protocol, the inputs being what the secret is derived from. None of the three is a function here; the module docstring is where they are stated, and btclib.script.sig_hash is where the flags are.

secp256k1 and sha256 are not parameters, as in btclib.ecc.musig2: BIP352 is defined for that pair, and the 33-byte compressed points, the 32-byte scalars and the three tags below are its serialization.

class btclib.silent_payments.SilentPaymentOutput(pub_key: bytes, prv_key_tweak: int)[source]

Bases: object

A silent payment output a scan found, and what it takes to spend it.

  • pub_key is the 32-byte x-only taproot output key, which is what the transaction carries and what identifies the output

  • prv_key_tweak is the scalar to add to the spend private key, t_k plus the label tweak where a label was used: prv_key_from_tweak does that addition

btclib.silent_payments.address_from_keys(B_scan: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, B_m: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, network: str = 'mainnet') str[source]

Return the bech32m silent payment address of a key pair.

B_m is the spend key, or the spend key plus a label tweak: labeled_address_from_keys is the spelling that applies the tweak.

btclib.silent_payments.input_hash(outpoints: Sequence[OutPoint], A_sum: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint) int[source]

Return the scalar binding the input keys to this transaction.

The smallest outpoint lexicographically, hashed with the public sum: without it the same input keys spent in two transactions would derive the same outputs, and a sender could be made to pay twice to one address. Lexicographically on the 36 wire bytes, which are little-endian – so the ordering is the transaction’s own and a wallet parsing a serialized transaction reorders nothing.

An empty sequence has no smallest outpoint and no input hash.

btclib.silent_payments.keys_from_address(address: bytes | str | bytearray | memoryview) tuple[tuple[int, int], tuple[int, int], Literal['main', 'test']][source]

Return (B_scan, B_m, network type) from a silent payment address.

The network type and not a network: BIP352 has one hrp for mainnet and one for every test network, so “tsp” says testnet, signet, testnet4 or regtest without saying which.

A version above 0 is read as far as v0 defines it – the first 66 bytes of the payload, the rest discarded – so that a v0 sender can pay a later address. v31 is refused instead, being the version BIP352 reserves for a change that breaks exactly that.

btclib.silent_payments.label_lookup(b_scan: int | bytes | str | bytearray | memoryview | BIP32KeyData, m_values: Iterable[int]) dict[bytes, bytes][source]

Precompute the {label point: label tweak} map a scan reads.

Once per wallet, not once per transaction, which is the point of it: scanning subtracts the candidate output from P_k and asks whether the difference is a label, so a wallet with M labels pays M multiplications here instead of M point additions per output for ever. Include 0 among the values unless the wallet is certain it never paid itself.

The tweak is 32 bytes, big-endian – silentpayments.scan_outputs’s own spelling of a label cache, Mapping[bytes, bytes], which refuses a bytearray or a memoryview as a key and is what scan_transaction_outputs hands the bindings unconverted where they serve. scan_outputs’s Python loop reads the same bytes through int_from_prv_key, which already accepts 32-octet SEC input, so neither arm pays a conversion this function did not already do once, per wallet rather than per scan.

btclib.silent_payments.label_tweak(b_scan: int | bytes | str | bytearray | memoryview | BIP32KeyData, m: int) int[source]

Return the scalar labelling an address with the integer m.

The scan private key is what the tweak is derived from, so the recipient can recognize its own labels while scanning without holding the spend key: BIP352 exports the scan key on purpose, and a label derived from the spend key would have undone that.

btclib.silent_payments.labeled_address_from_keys(b_scan: int | bytes | str | bytearray | memoryview | BIP32KeyData, B_spend: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, m: int, network: str = 'mainnet') str[source]

Return the address of the spend key labelled with the integer m.

m = 0 is the change label, reserved by convention for the outputs a sending wallet pays to itself; BIP352 asks a scanner to check it always, which is what makes that convention safe to rely on when recovering a wallet from a seed alone.

btclib.silent_payments.output_key(secret: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, B_m: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, k: int) bytes[source]

Return the x-only taproot output key of one recipient of a group.

The last step of BIP352’s derivation, and the one a caller that already holds the shared secret needs on its own: btclib.psbt.silent_payments reaches this point from an ECDH share a psbt carries rather than from a private key, so what the two paths share is this and not output_keys.

k is the recipient’s position in its group, which is what stops two payments to one scan key landing on one output.

btclib.silent_payments.output_keys(prv_keys: Sequence[tuple[int | bytes | str | bytearray | memoryview | BIP32KeyData, bytes | str | bytearray | memoryview]], outpoints: Sequence[OutPoint], addresses: Sequence[bytes | str | bytearray | memoryview]) list[bytes][source]

Return the x-only taproot output keys to pay a list of addresses.

One key per address, in the order the addresses are given; btclib.script.script_pub_key.ScriptPubKey.p2tr turns each into the output to put in the transaction. Repeat an address to pay it twice: the k that separates two outputs of one recipient is its position in that recipient’s group, so two payments to one address are two different outputs.

prv_keys pairs each input’s private key with the script_pub_key it spends, as prv_key_sum takes them, and every eligible input of the transaction must be there – the recipient sums all of them. The outpoints are the transaction’s, eligible or not: what the input hash binds to is the transaction.

Every key returned must be in the final transaction. The k of a group is what a scanner increments, and it stops at the first k it does not find: dropping the i-th output of a group hides every later one from its recipient.

Grouping is by scan key, so two labelled addresses of one recipient share a group and get consecutive k. That is deliberate: reusing one t_k for both would make the difference of the two output keys equal the difference of the two published addresses, which is the recipient named in public.

Where the bindings serve secp256k1 – BIP352 has no other curve to ask them for – this is silentpayments.create_outputs’s own derivation rather than the Python arithmetic below: one keypair build per taproot input and one shared-secret multiplication per recipient group inside libsecp256k1, in place of mult and _mult_sec_var here. a and h are computed either way, for the refusal a zero private-key sum or an empty outpoint sequence already has a specific message for – see _delegated_output_keys.

btclib.silent_payments.prv_key_from_tweak(b_spend: int | bytes | str | bytearray | memoryview | BIP32KeyData, prv_key_tweak: int) int[source]

Return the private key that spends a found output.

b_spend plus the tweak scan_outputs reported, modulo n. The taproot output is x-only, so a signer negates this key if it has to; that is BIP340’s business and btclib.ecc.ssa.sign does it.

btclib.silent_payments.prv_key_sum(prv_keys: Sequence[tuple[int | bytes | str | bytearray | memoryview | BIP32KeyData, bytes | str | bytearray | memoryview]]) int[source]

Return the sum of the input private keys, taproot ones negated.

Each pair is one input’s private key and the script_pub_key it spends. The script and not a flag beside it: whether to negate is is_p2tr of that script, and a caller keeping a boolean in step with it is a caller with one more thing to get wrong.

The negation is BIP340’s two private keys per x-only key, d and n-d: the recipient sums the x-only public keys and so assumes the even-y one, and a sender that summed the other derives an output nobody finds.

A sum of zero is BIP352’s “fail”, and it is not the same as an empty sequence: Input keys sum up to zero is a real vector – two inputs whose keys are negatives – and the payment cannot be made, the shared secret being the point at infinity. An intermediate zero is fine and is a vector too.

btclib.silent_payments.pub_key_from_input(script_pub_key: bytes | str | bytearray | memoryview, script_sig: bytes | str | bytearray | memoryview = b'', witness: Witness | None = None) tuple[int, int] | None[source]

Return the public key of one input, or None if it does not count.

None is BIP352’s “skip”: the four eligible output types are p2pkh, p2wpkh, p2sh-p2wpkh and p2tr, and inside them an uncompressed key, a taproot NUMS internal key, a p2sh wrapping anything but p2wpkh, and a scriptSig or witness that carries no key at all are each skipped rather than refused. A transaction of nothing but skipped inputs is a transaction no silent payment can be made from, which is the caller’s to notice – pub_key_sum of an empty sequence says so.

btclib.silent_payments.pub_key_sum(pub_keys: Sequence[bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint]) tuple[int, int][source]

Return the sum of the input public keys, refusing infinity.

Infinity is BIP352’s “skip the transaction” on the receiving side and the failure prv_key_sum reports on the sending one; a caller scanning rather than paying reads it as the skip it is. An empty sequence is the same answer, and is what a transaction of nothing but skipped inputs sums to.

One keys.pubkey_sum of all the terms rather than a running total added one at a time: what kept it here was that an intermediate sum at infinity is a BIP352 vector and infinity is what libsecp256k1 has no public key for, and _sum_var is where that stopped being a reason – a sum at infinity comes back as a value now, and this function still refuses it.

btclib.silent_payments.scan_outputs(b_scan: int | bytes | str | bytearray | memoryview | BIP32KeyData, B_spend: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, tweak: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, outputs_to_check: Sequence[bytes | str | bytearray | memoryview], labels: Mapping[bytes, bytes] | None = None) list[SilentPaymentOutput][source]

Return the outputs of one transaction that belong to this wallet.

tweak is the tweak data of tweak_data, input_hash*A_sum – the light client’s entry point, BIP352’s Appendix A: a server hands a light client exactly this and nothing else about the transaction, which is why this stays the Python arithmetic below whatever the bindings serve. scan_transaction_outputs is the counterpart for a caller holding the transaction itself. outputs_to_check are the x-only keys of every taproot output of the transaction, spent ones included – a wallet recovering its history is looking for outputs it has already spent. labels is label_lookup’s map, and BIP352 asks for the change label m = 0 in it whatever else the wallet used.

The scan walks k upwards and stops at the first k that matches nothing, which is what makes it one multiplication per transaction rather than one per output. That stopping rule is also why the decision to continue must be the cryptographic match and nothing else: an output found and then dropped by a wallet policy – dust, say – still has to advance k, or every later output of the same sender is missed.

btclib.silent_payments.scan_transaction_outputs(b_scan: int | bytes | str | bytearray | memoryview | BIP32KeyData, B_spend: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, outpoints: Sequence[OutPoint], pub_keys: Sequence[tuple[bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, bytes | str | bytearray | memoryview]], outputs_to_check: Sequence[bytes | str | bytearray | memoryview], labels: Mapping[bytes, bytes] | None = None) list[SilentPaymentOutput][source]

Return the outputs of one transaction, from data a full node has.

scan_outputs is the light client’s entry point, tweak the one value BIP352’s Appendix A hands it. A full node holds the transaction itself instead: pub_keys pairs each eligible input’s public key with the script_pub_key it spends, exactly as output_keys’s prv_keys does on the sending side, and every eligible input must be there for the same reason pub_key_sum there needs all of them. outpoints is the transaction’s, eligible or not: what the input hash binds to is the transaction, precisely as output_keys reads it.

Where the bindings serve secp256k1, this reaches silentpayments.scan_outputs with a prevouts_summary computed once from pub_keys and outpoints – the shape a wallet scanning a block needs, and the one issue #910’s own measurement found 6.4x faster than the Python loop at a hundred outputs once a label is in play, which BIP352 asks every wallet to check (m = 0, the change label). Without a label the two arms are close, the Python one ahead at a hundred outputs, but that case is not what decided this: see the issue’s own comments for the numbers.

pub_key_sum and input_hash run unconditionally, before either arm is chosen, for the same reason output_keys computes a and h either way: a zero-sum refusal or an empty outpoint sequence gets the specific message those two functions already give it, rather than libsecp256k1’s coarser one.

labels is label_lookup’s map – 33-byte label to 32-byte tweak, the bindings’ own spelling – and reaches the delegated arm unconverted; the Python arm, scan_outputs, reads the same bytes through int_from_prv_key.

btclib.silent_payments.shared_secret(scalar: int | bytes | str | bytearray | memoryview | BIP32KeyData, point: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint) tuple[int, int][source]

Return scalar*point, the ECDH shared secret of BIP352.

One function for both ends, because there is one secret: the sender multiplies input_hash*a by the recipient’s B_scan, the recipient multiplies its b_scan by the tweak data input_hash*A, and commutativity is the protocol.

The public key stays octets rather than becoming a point: nothing here reads a coordinate of it, and _mult_sec_var is that multiplication without the round trip through one. The point itself is the answer, which is why ecdh.shared_secret of the bindings is no substitute – it hashes, and BIP352 tags this point with a counter of its own; btclib.ecc.dh has that verdict for all four of the library’s ECDH-shaped computations.

btclib.silent_payments.tweak_data(outpoints: Sequence[OutPoint], A_sum: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint) tuple[int, int][source]

Return input_hash*A_sum, the one value a scanner needs per tx.

BIP352’s Appendix A calls it the tweak data, and it is what a light client asks a server for: it is derived from the transaction alone, reveals nothing about any recipient, and a scanner multiplies it by its scan key to reach the shared secret. Which is why scan_outputs takes it rather than the outpoints – a light client never sees them.

btclib.slip132 module

SLIP132 address.

https://github.com/satoshilabs/slips/blob/master/slip-0132.md

btclib.slip132.address_from_xkey(xkey: BIP32KeyData | bytes | str | bytearray | memoryview) str[source]

Return the SLIP132 base58/bech32 address.

The address is always derived from the compressed public key, as this is the default public key representation in BIP32.

btclib.slip132.address_from_xpub(xpub: BIP32KeyData | bytes | str | bytearray | memoryview) str[source]

Return the SLIP132 base58/bech32 address.

The address is always derived from the compressed public key, as this is the default public key representation in BIP32.

btclib.slip132.p2pkh_xkey(xkey: BIP32KeyData | bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview = 'm/44h/0h/0h', check_root_xkey: bool = True) str[source]

Return a p2pkh BIP32 xprv/xpub key at the derivation path.

btclib.slip132.p2wpkh_p2sh_xkey(xkey: BIP32KeyData | bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview = 'm/49h/0h/0h', check_root_xkey: bool = True) str[source]

Return a p2wpkh-p2sh BIP32 yprv/ypub key at the derivation path.

btclib.slip132.p2wpkh_xkey(xkey: BIP32KeyData | bytes | str | bytearray | memoryview, der_path: str | Sequence[int] | int | bytes | bytearray | memoryview = 'm/84h/0h/0h', check_root_xkey: bool = True) str[source]

Return a p2wpkh BIP32 zprv/zpub master key at the derivation path.

btclib.to_prv_key module

Functions for conversions between different private key formats.

btclib.to_prv_key.int_from_prv_key(prv_key: int | bytes | str | bytearray | memoryview | BIP32KeyData, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) int[source]

Return a verified-as-valid private key integer.

It supports:

  • WIF (bytes or string)

  • BIP32 extended keys (bytes, string, or BIP32KeyData)

  • SEC Octets (bytes or hex-string, with 02, 03, or 04 prefix)

  • integer (native int or hex-string)

Network and compressed information from the input key are not used.

btclib.to_prv_key.prv_keyinfo_from_prv_key(prv_key: int | bytes | str | bytearray | memoryview | BIP32KeyData, network: str | None = None, compressed: bool | None = None) tuple[int, str, bool][source]

Return (int key, network, compressed) from any private key spelling.

A WIF or an xprv carries its own network and compression, and a contradicting argument is refused rather than overridden; an int or octets carry neither, so the arguments – mainnet, compressed – fill in.

btclib.to_pub_key module

Functions for conversions between different public key formats.

btclib.to_pub_key.point_from_key(key: int | bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]

Return a point tuple from any possible key representation.

It supports:

  • BIP32 extended keys (bytes, string, or BIP32KeyData)

  • SEC Octets (bytes or hex-string, with 02, 03, or 04 prefix)

  • native tuple

btclib.to_pub_key.point_from_pub_key(pub_key: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]

Return an elliptic curve point tuple from a public key.

btclib.to_pub_key.pub_keyinfo_from_key(key: int | bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, network: str | None = None, compressed: bool | None = None) tuple[bytes, str][source]

Return the pub key tuple (SEC-bytes, network) from a pub/prv key.

btclib.to_pub_key.pub_keyinfo_from_prv_key(prv_key: int | bytes | str | bytearray | memoryview | BIP32KeyData, network: str | None = None, compressed: bool | None = None) tuple[bytes, str][source]

Return the pub key tuple (SEC-bytes, network) from a private key.

btclib.to_pub_key.pub_keyinfo_from_pub_key(pub_key: bytes | str | bytearray | memoryview | BIP32KeyData | tuple[int, int] | PreparedPoint, network: str | None = None, compressed: bool | None = None) tuple[bytes, str][source]

Return the pub key tuple (SEC-bytes, network) from a public key.

btclib.tx_builder module

Compose the psbt spending a set of outputs, at a fee rate, with change.

Every part of this is elsewhere and nothing composes them, so every caller composes them again: Psbt holds what is being built, psbt.prevouts says what its inputs are worth, Psbt.vsize_estimate says how large the signed transaction will be, fee.fee_from_vsize prices that size and fee.dust_threshold says whether the change is worth creating. build_psbt is that composition, and the three decisions it makes are the ones a hand-written builder gets wrong.

The fee comes from the rate and the size, and the size comes from the psbt. A transaction that is not signed has no size to be priced by – Tx.vsize is read off a serialization, and the signatures are not written yet – so the object built here is a psbt: Psbt.vsize_estimate sizes the missing signatures from each input’s utxo and scripts, which is the one thing in the tree that answers before there is anything to sign. The unsigned transaction is built.psbt.tx, a property away, so a second entry point answering with a Tx would be a second spelling of one answer rather than a second answer.

Change is an output or it is fee. Below dust_threshold for its own script an output cannot be relayed, so it is not created and its value is left to the fee; and the transaction is then smaller than the one that was priced, so the fee it owes is computed again rather than reused. change_index is which output it is, or None for the branch that dropped it.

An input is a `PsbtIn`: the outpoint it spends, the output that outpoint names – witness_utxo or non_witness_utxo, whichever its kind of input takes – and whatever else says how it will be unlocked, a redeem script or a witness script included. Two things follow from taking the psbt’s own map rather than a pair of an outpoint and a TxOut. Nothing here fetches anything, an outpoint alone saying neither what it is worth nor what it spends, and a builder that fetches is a builder with a node in it; and an input whose script is wrapped or multisig is estimated exactly, its redeem or witness script being a field of the map that arrives rather than an argument this function would have to grow. The outputs are TxOut and not PsbtOut because the asymmetry is real: the input map is read, every byte a signature will take being computed from it, where nothing computed here reads an output map – what a wallet writes into one, descriptorsupdate_psbt_output on the change output at change_index included, is written after this returns and changes no size.

Explicitly not here, both of them boundaries this library draws elsewhere too:

  • coin selection. Which utxos to spend is policy with a literature behind it, and keeping it out is what lets a caller bring its own; this spends the ones it is given, all of them. tx.input_weight is the number that caller prices a candidate with – one input, before there is a psbt to put it in – where what a fee is bought by here is the whole transaction, which Psbt.vsize_estimate is the one arithmetic for.

  • a node, an rpc, or wallet state. The same arguments give the same answer forever, which is fee’s own boundary: what is downstream of a network – a fee estimate for a confirmation target, which utxos are confirmed, the block height that would make an anti-fee-sniping lock time – is fed in rather than fetched.

class btclib.tx_builder.FundedPsbt(psbt: Psbt, fee: int, change_index: int | None)[source]

Bases: object

A psbt whose fee is paid, and what paying it decided.

The three answers Bitcoin Core’s fundrawtransaction gives, which is the same triple under its own names: the transaction, fee, and changepos – spelled here as an index into the psbt’s outputs, and None where Core writes -1 for the transaction that has no change output.

fee is what the inputs are worth less what the outputs hold. It is at least what fee_rate asked of the estimated size and can exceed it, by exactly the change that was too small to create.

property change: int

Return the satoshi the change output holds, 0 where there is none.

btclib.tx_builder.build_psbt(inputs: Sequence[PsbtIn], outputs: Sequence[TxOut], fee_rate: FeeRate, change_script_pub_key: bytes | str | bytearray | memoryview | None = None, *, tx_version: int = 2, lock_time: int = 0, dust_fee_rate: FeeRate = FeeRate(sats_per_kvbyte=3000), sizer: Callable[[PsbtIn, TxIn], list[int] | None] | None = None) FundedPsbt[source]

Return the psbt spending these inputs at this rate, and its change.

inputs are the psbt’s own input maps, each carrying the outpoint it spends and the output that outpoint names; outputs are what is being paid. What is left over pays the fee, and change_script_pub_key is where the rest of it goes – to an output of that script when it would be worth more than dust_threshold asks, and to the fee when it would not. No change script at all is every leftover satoshi to the fee, which is what a caller sweeping an address means and what a caller who forgot the argument gets, so it is spelled rather than defaulted.

Raised, all as BTClibValueError: no inputs, no outputs left to pay, an outpoint spent twice, an input carrying no utxo, an input whose type the psbt does not determine – psbt_size’s rule, and sizer is where a caller answers for one – and inputs that do not cover the outputs and the fee.

tx_version and lock_time are the transaction’s, defaulting to Core’s own 2 and to no lock time: the block height that would make a lock time worth setting is a node’s answer, and this function has no node. Each input’s sequence is its own PsbtIn.sequence, and an input naming none spends with the final sequence – no lock time and no BIP125 replacement, which a caller wanting either sets on the input rather than having overwritten here.

dust_fee_rate is the rate the dust threshold is computed at, Core’s -dustrelayfee default; it is not fee_rate, an output being dust by what the network will relay rather than by what this transaction chose to pay.

The psbt is version 0, which every Signer reads; Psbt.to_v2 is the other one.

btclib.tx_or_psbt module

One entry point for a transaction or a psbt, in whatever it arrives as.

A transaction copied from a block explorer is hex, a walletcreatefundedpsbt reply is base64, a QR code and a file are bytes – and which of Tx.parse, Psbt.parse and Psbt.b64decode applies is a question a caller holding one of them should not have to answer first. It is also a question with an unambiguous answer: BIP174’s five-byte <magic> is what a psbt begins with and what a transaction cannot, which is the very reason the 0xff is in it.

So this module sniffs, and delegates. The parsers stay whole – one refuses what does not deserialize, the other refuses what follows a psbt (issue #179) – and no byte either of them reads is read here: merging the two into one lenient reader is how a dispatcher stops being a dispatcher, and it is the parsers that would pay for it.

It sits above tx and psbt rather than inside either, because its answer is one or the other and tx may not import psbt.

btclib.tx_or_psbt.tx_or_psbt_from_any(data: bytes | str | bytearray | memoryview, *, check_validity: bool = True) Tx | Psbt[source]

Return the Psbt or the Tx the data holds, in whatever encoding.

hex, base64 or bytes; a Psbt when BIP174’s magic is what the bytes begin with, a Tx when it is not.

btclib.utils module

Assorted conversion utilities.

Most conversions from SEC 1 v.2 2.3 are included.

https://www.secg.org/sec1-v2.pdf

read_exactly and assert_no_trailing are the two halves of what every parse in this library owes its caller, and they live here because all of them owe it:

  • a field is as long as its encoding says it is, so a short read is an error and not a value. That is structural rather than semantic, so check_validity does not gate it: skipped, a truncated buffer becomes an object that serializes back zero-padded, and two buffers map to the one object that serializes to only the longer of them

  • octets are one whole object, so bytes after it are refused; a caller’s stream is not, so parsing consumes the object and leaves the stream on the byte after it, which is how a transaction is read out of a block

A fixed-size object read whole – a 78-byte bip32 key, a 65-byte bms signature, an 80-byte block header – reports its own decoded length instead of naming a field, the buffer being the object and not a part of one; that check is unconditional for the same reason.

What check_validity does gate is the semantic half, and where it can be asked is not the same at the three boundaries the flag appears at. The object (serialize) and the json (to_dict/from_dict) ask “is this object well formed”; the octets (parse) ask “do these octets decode into something that is not” – and for a class whose invariants are exactly the widths of its fields, nothing can. The decoding enforces them by construction, so at that boundary the flag is unreachable by design rather than unchecked, and the class is one in good order rather than one missing a check. OutPoint is such a class: 32 octets of tx_id and four of vout, and every value of that shape is an outpoint Bitcoin Core accepts. A class whose only invalidable child is one – TxIn, whose own fields are of the same kind – inherits the property.

The two are not the same question asked twice, which is why an object can be asked one and not the other: an invalidity of type rather than of value survives the json and not the octets, a bool being an int that reads back as the number one, while an amount above MoneyRange survives the octets and not the json, the conversion to BTC asking what assert_valid would ask. tests/check_validity_test.py is where the cases live, one per class and boundary, and this is the rule they are read under.

btclib.utils.assert_no_trailing(data: BytesIO | bytes | str | bytearray | memoryview, stream: BytesIO, what: str) None[source]

Refuse bytes left over after a complete octet encoding.

Octets are one whole object, so what follows the object in them is malleability: two buffers deserializing to the one object that serializes back to only the shorter of them. A caller’s stream is the other case, and nothing is checked there – what follows in it is the caller’s, a transaction inside a block being read from the very stream the block is read from – so parse leaves the stream on the byte after the object.

Bitcoin Core splits the two the same way, between Unserialize and DecodeRawPSBT’s “extra data after PSBT”.

btclib.utils.assert_type(value: Any, expected: Any, what: str) None[source]

Refuse a value of a type the signature does not declare.

expected is what isinstance takes: one type, or a tuple of them. bytes_from_octets and str_from_string are the two coercions this library has, and each refuses what it cannot convert; this is the refusal for a position that takes neither – a bool flag deciding which of two serializations is written, the text of a URI or a descriptor, the magic bytes an envelope is read against. Every one of those was compared, walked or handed to a builtin unasked, and left as a complaint about that builtin.

value is Any rather than the declared type, which is what makes the check reachable: mypy proves the argument cannot be wrong, and the caller who has not run mypy is who this is for.

btclib.utils.bytes_from_octets(octets: bytes | str | bytearray | memoryview, out_size: int | Iterable[int] | None = None) bytes[source]

Return bytes from a hex-string, stripping leading/trailing spaces.

A bytearray or a memoryview is copied, which is what makes the bytes this promises true: handed back as it came, either is still the caller’s own object, so a write to it afterwards reaches into whatever kept the return value – the chain code of a BIP32KeyData, and the xprv that key serializes to. The copy is also what reads as octets everywhere the result goes: a memoryview has no + to concatenate with, and a bytearray keys no dict.

Optionally, it also ensures required output size: one size, or any iterable of them, and a bool is neither – out_size=True would accept a single octet and say it had checked a size.

A non-contiguous memoryview – what a strided slice such as mv[::2] gives – is refused rather than copied, and so is one whose format is not unsigned bytes: _assert_byte_shaped says why neither is the octets it looks like. The refusal is raised through the exception contract at the one place every Octets parameter passes, rather than left to whichever consumer trips over it first.

btclib.utils.bytesio_from_binarydata(stream: BytesIO | bytes | str | bytearray | memoryview) BytesIO[source]

Return a BytesIO stream object from a BytesIO or from Octets.

A BytesIO is the caller’s own and is handed back as it came, the position it is left at being how a transaction is read out of a block. Anything else is octets, and is wrapped in one.

A BytesIO and not any binary stream: deserialize_map asks the result for getbuffer(), which a file object does not have, so accepting one here would only move the failure. read_exactly is the one that takes a BinaryIO, and its docstring says why.

btclib.utils.decode_num(data: bytes) int[source]

Decode a number to the bitcoin-specific little endian format.

A number is encoded as little-endian variable-length byte vector with the most significant bit (MSB) determining the sign.

  • 0x01 is 1

  • 0x81 is -1

Zero has three spellings, and this reads all three: the empty vector that encode_num writes and Core’s CScriptNum::set_vch answers 0 for, 0x00 – “positive” zero – and 0x80, “negative” zero. Only the first is minimal; refusing the other two belongs to the reader that knows whether MINIMALDATA is in force, which is the engine’s _to_num, and not here.

Not bounded the way encode_num is: this is the reader, and its two callers ask different things of it. The engine’s _to_num caps an operand at four bytes – five for CLTV and CSV – before it gets here, and Block.height decodes whatever a coinbase pushed, BIP34 being a byte comparison rather than a number, so an int64 bound here would refuse a coinbase the network accepts.

btclib.utils.encode_num(i: int) bytes[source]

Encode a number to the bitcoin-specific little endian format.

A number is encoded as little-endian variable-length byte vector with the most significant bit (MSB) determining the sign.

  • 0x01 is 1

  • 0x81 is -1

Zero is the empty vector, which is Core’s CScriptNum::serialize and is the only spelling of it the interpreter reads back as a number: 0x00 and 0x80 – “positive” and “negative” zero – decode to zero and are refused as operands under MINIMALDATA, (vch.back() & 0x7f) == 0 with nothing before it being what Core’s CScriptNum throws on. A push of the empty vector is OP_0, so the shortest command and the encoded number agree here and nowhere else (issue #646).

The number is a CScriptNum, i.e. an int64, and a Python int outside that range is refused rather than encoded: what it would write is a push no node can have built, and one the interpreter – capping every operand at four bytes, five for CLTV and CSV – cannot read back either.

The bound is on the value and not on the width: the most negative int64 is in range and takes nine octets, sign-magnitude having no room for its magnitude in eight, which is what Core’s CScriptNum::serialize writes for it as well.

btclib.utils.fields_from_json_object(dict_: Any, what: str) Mapping[str, Any][source]

Return the fields of a json object, refusing what is not one.

The first line of every from_dict, and it answers the two questions that boundary owes its caller before a field is read:

  • a Mapping[str, Any] is what the signature declares, and the check refuses what is not one before it is walked: without it, dict_[“version”] on a None is a TypeError about subscripting, on a str a TypeError about string indices, and neither says btclib refused anything

  • a mapping that is one and has not got the field is a value no valid input carries, so it is a BTClibValueError naming the field – from_dict is fed whatever a schema mistake produced, and a bare KeyError is neither a BTClibException nor a ValueError

what names the object, the caller knowing what it is reading and this not, as read_exactly names a field. .get is untouched and stays the spelling for a field that may be absent.

Any rather than the Mapping every caller declares, for the reason assert_type takes one: the check is here for the caller mypy did not read.

btclib.utils.hex_string(i: bytes | str | bytearray | memoryview | int) str[source]

Return a hex-string from many positive integer representations.

Negative integers are not allowed.

The resulting hex-string has an even number of hex-digits and includes a space every four bytes (i.e. every eight hex-digits).

btclib.utils.int_from_bits(octets: bytes | str | bytearray | memoryview, nlen: int) int[source]

Return the leftmost nlen bits.

Take as input a sequence of blen bits and calculate a non-negative integer i that is less than 2^nlen according to SEC 1 v.2 section 4.1.3 (5); ensuring 0 < i < n would take a further reduction modulo n, which is the caller’s.

int_from_bits is not the reverse of i.to_bytes, even for input sequences of length nlen: i.to_bytes will add some bits on the left, while int_from_bits will discard some bits on the right. i.to_bytes is the reverse of int_from_bits only when nlen is a multiple of 8 and bit sequences already have length nlen. See: - https://www.rfc-editor.org/rfc/rfc6979.html#section-2.3.5

btclib.utils.int_from_integer(i: bytes | str | bytearray | memoryview | int) int[source]

Return an int from many possible integer representations.

A bool is not one of them, is_integer being where this library says so: every Integer parameter runs through here, so the refusal is stated once and inherited (issue #1206).

Allowed integer representations are:

  • 3735928559

  • -3735928559

  • “0xdeadbeef”

  • “-0xdeadbeef”

  • “deadbeef”

  • b’xdexadxbexef’

A str is always read as a hex-string, with or without the “0x” prefix: int_from_integer(“1234”) is 4660, not one thousand two hundred and thirty-four, and “9” raises ValueError for being a hex-string of odd length rather than evaluating to nine. A decimal representation is what int itself is for, so pass int(“1234”).

The binary representation is not allowed because there is no way to discriminate it from a valid hex-string (e.g. “0b11011110101011011011111011101111”).

btclib.utils.int_from_json_number(value: Any, what: str) int[source]

Return the int of a whole number out of json, a bool not being one.

from_dict feeds a constructor a json object, where a whole number may arrive as a float – 1.0 for 1 – which is why the int fields of the dataclasses coerce rather than refuse. A boolean is not one of those numbers: true decodes to True, int(True) is 1, and a schema mistake would become a version, a depth or an index instead of an error beside the input that caused it.

A whole number: 1.0 is the json spelling of 1 and coerces, 1.5 is the spelling of nothing this library has a field for, and int truncates it to 1 rather than refusing – silently, and to a number the caller did write, which is what makes it worse than a type error. float.is_integer() asks that of the value, nan and inf being no more whole than 1.5 is.

is_integer is the same decision where there is nothing to coerce.

btclib.utils.is_integer(value: Any) bool[source]

Return whether the value is an integer, a bool not being one.

isinstance(x, int) is True for True and False, bool being a subclass of int – so every field of this library whose contract is an integer quantity accepted a boolean as the number one or zero, and int(True) == True slips through a conversion-and-equality check as well. What makes that worth a refusal rather than a shrug is the json boundary: true decodes to True, so a schema mistake became one satoshi, one virtual byte, one index or a one-sat/kvB fee rate instead of failing next to the input that caused it.

A boolean is not another spelling of a number, which is the difference from the strings and bytes much of this library accepts: “1” is a number written down, True is a different type that Python’s inheritance makes indistinguishable from one.

isinstance and not type(value) is int, so an IntEnum – what issue #273 asks about for the sighash types – and any other deliberate integer subclass stay integers. bool is the one subclass excluded, and by name.

btclib.utils.is_octets(value: Any) TypeIs[bytes | str | bytearray | memoryview][source]

Return whether the value is one Octets, rather than a sequence of them.

An Octetsstr, bytes, bytearray or memoryview – is itself iterable, so a function that takes a sequence of them and guards against being handed one instead cannot ask isinstance(value, Sequence): every Octets answers that too. The guard asks this question instead, once, so a spelling Octets gains later is refused at every caller of this rather than at whichever remembered to list it (issue #1261).

TypeIs rather than bool: a caller dispatching on this narrows on both branches, str | bytes | bytearray | memoryview where it is true and whatever is left of the wider type where it is false, which is what lets a site written as a hand-listed isinstance tuple – invisible to a census keyed on that tuple’s own element order – call this instead without losing the narrowing mypy strict mode otherwise needs the tuple for (issue #1433).

btclib.utils.list_from_json_array(value: Any, what: str) list[Any][source]

Return the list of a json array, a str and a mapping not being one.

What fields_from_json_object is to the object, for the arrays a from_dict walks: the inputs of a transaction, the transactions of a block, the stack of a witness. Unasked, a non-iterable is “not iterable” from underneath the library, and the two iterables that are not arrays are worse than that – a str is a list of its characters and a Mapping a list of its keys, so each element is refused for what it is not rather than the whole for what it is. is_octets is the four Octets spellings named once rather than listed here, so a spelling Octets gains later is refused the same way (issue #1420); Mapping is refused beside it for a reason of its own and stays a named check.

btclib.utils.read_exactly(stream: BinaryIO, size: int, what: str) bytes[source]

Return size octets from the stream, or raise: a short read is truncation.

BytesIO.read answers with whatever is left when the buffer holds less than was asked for, and int.from_bytes takes the short answer without a word. The size is what makes the field boundary, so it is checked whatever check_validity says: see this module’s docstring for why that is not the same question.

what names the field in the error message, the caller knowing which one it was reading and the stream not.

BinaryIO and not the BytesIO of alias.BinaryData: .read is the whole of what a short read is about, so a file object is as much an answer here as a buffer, and btclib.psbt.psbt_view reads from one – a view over a psbt is the one reader in this library that does not consume the stream it is given, so it does not need one the rest of the library can also getbuffer().

btclib.utils.str_from_string(s: bytes | str | bytearray | memoryview, what: str) str[source]

Return the text of a String, whether it came as text or as ascii bytes.

What bytes_from_octets is to Octets, in the direction the addresses go: an address, a WIF and an xkey are ascii, so a byte outside it is an invalid character like any other and gets the same answer – a UnicodeDecodeError let out would fly past every caller written to catch a BTClibValueError.

what names the string in both messages, the caller knowing what it was reading and this not, exactly as read_exactly names a field.

Nothing is stripped and nothing is lowered: which of those is right is the caller’s to know, a message to be signed being the one String whose blanks are part of it.

btclib.var_bytes module

Varbytes encoding and decoding: a var_int length, then the bytes.

btclib.var_bytes.parse(stream: BytesIO | bytes | str | bytearray | memoryview, forbid_zero_size: bool = False) bytes[source]

Return the variable-length octets read from a stream.

forbid_zero_size is read for its truth and not asked for its type, which is the convention check_validity is read under: it decides only whether a check runs, so no value of it changes the octets this answers with – where taproot.parse’s exit_on_op_success decides which of two answers is computed and is therefore refused unless it is a bool.

btclib.var_bytes.serialize(octets: bytes | str | bytearray | memoryview) bytes[source]

Return the var_int(len(octets)) + octets serialization of octets.

btclib.var_int module

Varint encoding and decoding functions.

Bitcoin’s variable-length integer, Core’s CompactSize: what the wire uses to say how many fields follow or how long the next field is. Not the base-128 varint of other protocols – the encoding is its own.

Up to 0xfc, a var_int is 1 byte; a greater integer is expanded as [1 byte prefix][number]:

  • prefix 0xfd marks the next two bytes as the number;

  • prefix 0xfe marks the next four bytes as the number;

  • prefix 0xff marks the next eight bytes as the number.

Only the shortest encoding of a given number is valid: Bitcoin Core rejects the others as “non-canonical ReadCompactSize()”. Were they accepted, the same transaction would have two serializations, hence two txids.

btclib.var_int.parse(stream: BytesIO | bytes | str | bytearray | memoryview, max_size: int = 33554432) int[source]

Return the variable-length integer read from a stream.

max_size is the range check of Bitcoin Core’s ReadCompactSize; raise it only for a var_int that is neither a length nor a count. It is an integer and a bool is not one, is_integer being the same predicate every integer field of the library is held to: max_size=True is a cap of one, so a caller who meant “no cap” would get a var_int too big for every count above one – and true is what a json configuration decodes to.

btclib.var_int.serialize(i: int) bytes[source]

Return the var_int bytes encoding of an integer.

An integer, and a bool is not one: bytes([True]) is the single octet one, so a boolean would encode as the count one or the length zero – the number saying how many of something there are, taken from a value that says whether. A float or a string leaves through the TypeError of bytes() or of a comparison, from underneath the library rather than through its exception contract, which is what is_integer is here to answer instead.

Module contents

The btclib package: what it publishes, and the version metadata.

__all__ here is the root of the library’s public tree: the packages and top-level modules a caller reaches from this name. Each of those, and each module below them, declares its own __all__, so a walk that starts here has a declared surface at every node – which is why the list is not pkgutil.iter_modules: discovery would answer the file tree, and a module added to the directory would publish itself rather than being published.

That walk is also what docs/proposals/cli.md reads to build the command tree of the out-of-repo command line, and there it is a starting point rather than the whole answer: a module can be published and carry nothing a command should spell. The command tree is this tree minus the exclusions that proposal records, which is a distinction the published surface cannot express and does not try to.

name is not in it, nor are the metadata dunders. name is the distribution’s name and not a member of the tree, __version__ bound by a star import would overwrite the importing module’s own, and each is still an attribute here: btclib.__version__ is how a caller reads the version and btclib.name how it reads the name.

Nothing is imported eagerly. A module is imported when it is first asked for, through the __getattr__ at the bottom of this file, so import btclib stays what it was – the metadata lookup below and nothing else – and the import graph keeps its shape: importing every module here would put the whole library in sys.modules before any single module of it could be imported first, which is the situation tests/imports_test.py exists to make impossible, and btclib.b58 would pull btclib.script in through this file rather than not at all.

What that costs is worth stating: mypy reads a module-level __getattr__ as a promise that any attribute may exist, so btclib.b59 is Any to it and a misspelling on this package is a runtime AttributeError rather than a reported error. The spellings a caller actually writes – from btclib import b58, import btclib.b58, from btclib.b58 import p2pkh – resolve against the real modules and stay checked, which is why the trade is one attribute lookup’s worth of strictness for an import graph that stays acyclic and a root that publishes its tree.