btclib.tx package¶
Submodules¶
btclib.tx.coin module¶
The Coin dataclass: a prevout, and what a rule about spending it needs.
Bitcoin Core’s Coin (src/coins.h, at bitcoin/bitcoin@9be056a8a7) is the shape matched – an output, the height of the block that created it, and whether that block’s own coinbase created it – and not matched in full: Core’s Coin also runs the output through TxOutCompression, a storage optimization no caller of btclib.tx.tx_context needs, and issue #1123 already declined it. How a node keeps one on disk is that node’s decision and not this class’s: no parse and no serialize, to_dict and from_dict included, since there is no wire or json shape to round-trip.
A caller checking a mempool acceptance rather than a block connecting builds a Coin at one past the active chain’s tip – Core’s own MEMPOOL_HEIGHT reading – rather than this module special-casing the mempool: the rules in btclib.tx.tx_context read Coin.height and nothing about where it came from.
- class btclib.tx.coin.Coin(tx_out: TxOut, height: int, is_coinbase: bool, *, check_validity: bool = True)[source]¶
Bases:
objectOne prevout: an output, the height it was created at, a coinbase bit.
Everything btclib.tx.tx_context’s rules need about an output that the output’s own bytes do not carry – Consensus::CheckTxInputs’s coinbase maturity check and the BIP68 sequence lock both read a prevout’s creation height, which is chain state and not part of the spending transaction.
btclib.tx.limits module¶
How many inputs and outputs a transaction may declare.
And the numbers btclib.tx.tx_context’s rules compare a lock time or a sequence against.
A module of its own, as block/limits.py and script/limits.py are and for the same reason: tx.py is the dataclass and its serialization, while each name here is a rule about the transaction the wire declares.
Core has no constant for either count. What it has is the weight a block may not exceed, and a transaction that does not fit in a block is one no parser has to allocate for: each count below is MAX_BLOCK_WEIGHT divided by the weight of the smallest thing it counts, so neither is a number this library picked (issue #569). btclib.consensus is where the two constants divided here come from, and its docstring is why they are not read from btclib.block.limits beside the rest of Core’s header.
The minimum sizes are the same arithmetic’s other half, and they are tests/tx’s to check against a serialization rather than to be believed.
LOCKTIME_THRESHOLD, SEQUENCE_FINAL and the four SEQUENCE_LOCKTIME_* constants are Core’s src/script/script.h and src/primitives/ transaction.h, at bitcoin/bitcoin@9be056a8a7; COINBASE_MATURITY is src/consensus/consensus.h, same tag. block/limits.py used to carry a paragraph explaining why COINBASE_MATURITY was absent from it – “a rule about spending an output, so it needs the chain the output was created on” – and that reasoning is what put it here instead: a btclib.tx.coin.Coin carrying its own creation height is that chain, so the rule is a function of its arguments and belongs beside the module that reads it rather than beside block/limits.py’s header rules, which btclib.tx cannot import.
btclib.tx.out_point module¶
The OutPoint dataclass; the class docstring has the contract.
- class btclib.tx.out_point.OutPoint(tx_id: bytes | str | bytearray | memoryview = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', vout: int = 4294967295, *, check_validity: bool = True)[source]¶
Bases:
objectThe output a transaction input spends: (tx_id, vout).
tx_id is held in the byte order hex explorers print, reversed on the wire by serialize and back by parse. Frozen and hashable, so an OutPoint can key the dict a utxo set is; the default instance is the coinbase marker, all-zero tx_id and 0xFFFFFFFF vout, which is_coinbase is the question about.
What is refused is a field no four bytes or thirty-two hold, and nothing about the pair: an all-zero tx_id with a real vout, and a real tx_id with the sentinel vout, are both outpoints Bitcoin Core accepts, checking the pair here being stricter than consensus. is_coinbase reads the pair instead, and it is what Tx.assert_valid uses to refuse a coinbase input in a transaction that is not one.
- assert_valid() None[source]¶
Refuse a tx_id not of 32 bytes and a vout no 4 bytes hold.
The two fields and not the pair, since checking the pair here would be stricter than consensus. Core’s COutPoint::IsNull is the conjunction – hash.IsNull() && n == NULL_INDEX – and CheckTransaction refuses a null outpoint in a non-coinbase and nothing else, so 000…000:0 is a transaction Core parses and checks, unspendable though it is. Refusing it here refused a transaction that exists: BIP322’s proof-of-funds vectors carry exactly that shape as the funding transaction of an input they prove control of, and Psbt.parse could not read them (issue 513).
What reads the pair is is_coinbase, which is Core’s own conjunction, and Tx.assert_valid through it: a coinbase input in a transaction that is not a coinbase is refused there, which is the rule consensus actually has.
- classmethod from_dict(dict_: Mapping[str, Any], *, check_validity: bool = True) OutPoint[source]¶
Build an OutPoint from the dict shape to_dict writes.
- classmethod parse(data: BytesIO | bytes | str | bytearray | memoryview, *, check_validity: bool = True) OutPoint[source]¶
Return an OutPoint from the first 36 bytes of the provided data.
btclib.tx.tx module¶
The Tx dataclass and join; the class docstring has the contract.
- class btclib.tx.tx.Tx(version: int = 1, lock_time: int = 0, vin: Sequence[TxIn] | None = None, vout: Sequence[TxOut] | None = None, *, check_validity: bool = True)[source]¶
Bases:
objectA Bitcoin transaction: version, lock time, inputs, outputs.
Mutable, being what a builder and a signer edit in place; id and hash are computed from the current state on every read, so they follow the edits. assert_valid checks what CheckTransaction checks of a lone transaction – field ranges, the stripped size against the block weight cap, the inputs and outputs one by one, the coinbase script size, no outpoint spent twice, the MAX_MONEY bound on the output sum; whether it spends what it claims needs the prevouts, which is script.engine.verify_transaction’s question.
- assert_standard() None[source]¶
Refuse what assert_valid refuses, plus a non-standard version.
Core v31.1’s relay policy, TX_MIN_STANDARD_VERSION through TX_MAX_STANDARD_VERSION: 1 and 2 both v27.2 and v31.1 relay, 3 is BIP431’s TRUC, standard since v28.0, and every version outside that window fails here and not in assert_valid, whose four-byte range takes it.
- assert_valid(*, unsigned_template: bool = False) None[source]¶
Assert that this is a valid transaction.
unsigned_template=True drops the two rules a PSBT’s global unsigned transaction cannot satisfy, and only those: at least one input and at least one output. Everything else still applies – the version and lock time ranges, each input and output on its own, no outpoint spent twice, the MAX_MONEY bound on their sum.
Those two rules are right for a transaction, Core’s CheckTransaction rejecting an empty vin or vout, and wrong for a PSBT’s: that one is incomplete by construction, which is the whole point of the format. BIP174 lists two zero-input PSBTs as valid and btclib refused both (issue 170).
- classmethod from_dict(dict_: Mapping[str, Any], *, check_validity: bool = True) Tx[source]¶
Build a Tx from the dict shape to_dict writes.
- property is_segwit: bool¶
Answer whether any input carries a witness.
The inputs and not the outputs: paying to a segwit script does not make the paying transaction segwit, spending one does.
- classmethod parse(data: BytesIO | bytes | str | bytearray | memoryview, *, check_validity: bool = True) Tx[source]¶
Return a Tx by parsing binary data.
A BIP144 marker and flag over witnesses that are all empty is refused, which is Core’s “Superfluous witness record” of UnserializeTransaction in src/primitives/transaction.h. The refusal is what keeps the property this library holds every wire class to – btclib.p2p.inventory’s module docstring states it, Headers.parse and Verack.parse refuse for it: serialize writes the marker only when some input carries a witness, so those octets are ones this class could not write back.
It does not answer to check_validity, and assert_no_trailing beside it is the shape being copied: that flag gates assert_valid, which asks about the transaction, and the transaction here is valid – what is malformed is the encoding, which no field records and nothing downstream could ask about.
- serialize(include_witness: bool, *, check_validity: bool = True) bytes[source]¶
Return the wire serialization, BIP144’s where a witness rides.
include_witness False gives the stripped serialization, what the txid is computed over; True adds the marker and the witnesses only if some input has one, a marker over empty witnesses not being what the wire writes.
A bool and nothing else, which is the line tests/built_object_contract_test.py draws: this flag decides what is computed rather than whether a check runs, so a value read for its truth would answer a stripped serialization – a different transaction id – where the wire one was meant.
- property sig_op_count: int¶
Return the legacy sigop count, Core’s GetLegacySigOpCount.
Every input’s script_sig and every output’s script_pub_key, counted from the bytes by script.sig_ops.sig_op_count: the p2sh and witness sigops Core adds in ConnectBlock and in the mempool’s PreChecks need the outputs being spent, which a transaction does not carry, and script.engine.sig_op_cost is the whole cost given them. The block rule this feeds, Block.assert_valid_sig_op_count, is the one the legacy count is enough for.
- to_dict(*, check_validity: bool = True) dict[str, str | int | list[Any]][source]¶
Return the transaction as a dict of json-friendly values.
The keys are Bitcoin Core’s decoderawtransaction ones; txid, hash, size, vsize and weight are derived for the reader and ignored by from_dict, which recomputes them.
- btclib.tx.tx.join(txs: Sequence[Tx], enforce_same_version: bool, enforce_same_lock_time: bool, shuffle_inp: bool, shuffle_out: bool) Tx[source]¶
Join transactions into one, concatenating inputs and outputs.
Outputs are concatenated and never merged, and no parameter asks for it: coalescing two outputs that pay the same script changes the output set, so it invalidates every signature already made over the old one – and with the shuffle below the result would depend on the order the merge ran in. Summing two payments into one output is the caller’s to do before signing.
btclib.tx.tx_context module¶
What a transaction owes the chain before it: finality and locks.
Sequence locks, coinbase maturity, and the coinbase’s own value ceiling.
Tx.assert_valid is Core’s CheckTransaction, and script.engine’s verify_amounts/verify_input are what a transaction can be held to against its prevouts. Between those two and a block connecting there is a third set of rules, Core’s src/consensus/tx_verify.cpp plus bad-cb-amount in src/validation.cpp’s ConnectBlock, that each need one fact per prevout beyond the output itself – the height it was created at, and whether by a coinbase, which btclib.tx.coin.Coin carries – and a height and a time to judge against. Every function here takes those as parameters rather than reading them off a UTXO set or a block index: btclib must not learn what either is, the same precedent btclib.block.header_context’s ParentOf sets for a header walking the chain through a callable rather than an index.
Bitcoin Core v31.1 (bitcoin/bitcoin@9be056a8a7) is the reference for every rule, src/consensus/tx_verify.cpp and src/validation.cpp, cited beside the code that transcribes it.
## What each function does not take, and why
is_final’s block_time is a cutoff, not a choice: Core evaluates IsFinalTx in ContextualCheckBlock against pindexPrev-> GetMedianTimePast() once BIP113 (the CSV deployment) is active on the chain, and against the block’s own timestamp before that – a decision made from the chain’s own activation height, which this module does not carry. The caller passes whichever Core would have passed; the function’s own docstring repeats this rather than deciding it.
assert_sequence_locks takes no enforce_bip68 flag. Core’s own fEnforceBIP68 is tx.version >= 2 && DeploymentActiveAt(…, DEPLOYMENT_CSV): the first half is a fact about the transaction and stays in this function, the second is chain state a caller already knows before it is worth building a Coin sequence at all – a function that only evaluates the locks once asked, with activation decided outside, is the smaller contract, and the one that cannot be handed the wrong flag by accident.
A mempool acceptance, where a coin has no block yet, is not special-cased here either: coin.py’s docstring is where that is, and it is the caller’s Coin to build, at Core’s MEMPOOL_HEIGHT reading, rather than a branch these functions would otherwise need.
assert_coinbase_value takes the fee sum and the subsidy rather than every other transaction of the block and their own prevouts: Core’s own ConnectBlock accumulates nFees transaction by transaction as it connects them, and btclib.consensus.subsidy is already the halving arithmetic – summing fees from a block’s own transactions a second time here would be a second implementation of what a caller connecting a block already does once.
- btclib.tx.tx_context.assert_coinbase_maturity(prevouts: Sequence[Coin], spend_height: int) None[source]¶
Refuse a spend of a coinbase output not yet COINBASE_MATURITY deep.
Core’s Consensus::CheckTxInputs, bad-txns-premature-spend-of-coinbase (src/consensus/tx_verify.cpp, at bitcoin/bitcoin@9be056a8a7): spend_height - coin.height < COINBASE_MATURITY, for whichever of prevouts is a coinbase output.
spend_height is not always the height of the block connecting the spend: a mempool acceptance passes one past the active chain’s own tip instead, matching Core’s own MemPoolAccept::PreChecks (m_active_chainstate.m_chain.Height() + 1, src/validation.cpp, same tag).
- btclib.tx.tx_context.assert_coinbase_value(coinbase: Tx, subsidy: int, fees: int) None[source]¶
Refuse a coinbase paying more than the subsidy plus the fees it collects.
Core’s bad-cb-amount (ConnectBlock, src/validation.cpp, at bitcoin/bitcoin@9be056a8a7): blockReward = nFees + GetBlockSubsidy(…), refused if coinbase.GetValueOut() > blockReward. subsidy is btclib.consensus.subsidy’s answer at the block’s own height; fees is the caller’s own sum of what every other transaction of the block pays in – the module docstring says why summing them again here would be a second implementation of a block connector’s own bookkeeping.
- btclib.tx.tx_context.assert_sequence_locks(tx: Tx, prevouts: Sequence[Coin], height: int, tip_median_time_past: int, ancestor_median_time_past: Callable[[int], int]) None[source]¶
Refuse tx if a BIP68 relative lock time of its inputs is unmet.
Core’s CalculateSequenceLocks and EvaluateSequenceLocks, combined as SequenceLocks is (src/consensus/tx_verify.cpp, at bitcoin/bitcoin@9be056a8a7): prevouts aligned with tx.vin, one Coin per input, in place of a freshly-read CCoinsViewCache.
A transaction below version 2 is skipped, matching BIP68 – the module docstring says why no enforce_bip68 flag is here to skip the rest of it too. An input whose sequence carries SEQUENCE_LOCKTIME_DISABLE_FLAG is skipped the same way, on its own rather than the whole transaction’s.
height is Core’s own block.nHeight – the height of the block the transaction is connecting into, one past its own parent’s – and tip_median_time_past is that parent’s GetMedianTimePast(), the reference a height-based lock is compared against directly and a time-based one after ancestor_median_time_past has turned each input’s own relative lock into an absolute one. ancestor_median_time_past(h) is header_context.median_time_past of the block at height h, called once per time-locked input at max(coin.height - 1, 0) – the block before the one that confirmed the coin, matching Core’s own comment on why: “the smallest allowed timestamp of the block containing the txout being spent”.
Refuses with bad-txns-nonfinal, the message Core’s own ConnectBlock reports a failure of this rule with.
- btclib.tx.tx_context.is_final(tx: Tx, height: int, block_time: int) bool[source]¶
Answer whether tx is final at height, against a block_time cutoff.
Core’s IsFinalTx (src/consensus/tx_verify.cpp, at bitcoin/bitcoin@9be056a8a7): a zero lock_time is always final; otherwise tx is final once height or block_time – whichever lock_time’s own units name, decided against LOCKTIME_THRESHOLD – has passed it, or regardless if every one of tx’s own inputs opts out of lock_time by carrying SEQUENCE_FINAL.
Which instant block_time is, is the caller’s: pindexPrev-> GetMedianTimePast() once BIP113 (the CSV deployment) is active on the chain being validated against, the block’s own timestamp before that – Core’s ContextualCheckBlock decides it from the chain’s own activation height, which this function does not carry. A caller checking a block reports False here as Core’s own bad-txns-nonfinal.
btclib.tx.tx_in module¶
The TxIn dataclass and input_weight; the class docstring has the contract.
- class btclib.tx.tx_in.TxIn(prev_out: OutPoint | None = None, script_sig: bytes | str | bytearray | memoryview = b'', sequence: int = 0, script_witness: Witness | None = None, *, check_validity: bool = True)[source]¶
Bases:
objectOne input of a transaction.
The outpoint it spends, the script_sig, the sequence, and the witness – which serialize omits and parse leaves empty, the wire placing all witnesses after the inputs: Tx.serialize and Tx.parse are what write and read them. Mutable, a transaction being built input by input; TxIn() is the placeholder a builder starts from, its prev_out the coinbase marker.
- assert_valid() None[source]¶
Refuse an invalid prev_out, sequence, or witness.
The script_sig is deliberately not judged; the comment below carries the reasoning.
- classmethod from_dict(dict_: Mapping[str, Any], *, check_validity: bool = True) TxIn[source]¶
Build a TxIn from the dict shape to_dict writes.
- property is_segwit: bool¶
Answer whether the input carries a witness.
The input alone can only answer for what it holds: whether the spent output demands a witness is written in that output’s script_pub_key, which is not here.
- classmethod parse(data: BytesIO | bytes | str | bytearray | memoryview, *, check_validity: bool = True) TxIn[source]¶
Build a TxIn by parsing its wire serialization.
The witness comes back empty, sitting elsewhere on the wire; Tx.parse fills it in.
- btclib.tx.tx_in.input_weight(script_sig: bytes | str | bytearray | memoryview, witness: Witness | None = None) int[source]¶
Return the weight one input adds to a transaction, per BIP141.
Bitcoin Core’s calculate_input_weight, of test/functional/test_framework/wallet_util.py: the non-witness bytes weigh WITNESS_SCALE_FACTOR each – the 36-byte outpoint, the script_sig behind its var_int length, the 4-byte sequence – and the serialized witness stack weighs one each. Which output is spent and what the sequence says do not enter the answer, both fields being fixed-width, so neither is an argument.
It is computable before the input exists, which is the point: a fee is chosen before the transaction is signed, so the weight the fee is computed from cannot be read off the finished transaction. Tx.weight is the same arithmetic over one already built.
witness is None for an input of a transaction with no witness section and a Witness for one of a transaction that has it – Witness() being the empty stack a non-segwit input carries there. The two differ by a byte, that stack still serializing the var_int announcing no elements, and a caller that cannot tell them apart misprices an input by one. Core, taking a list of hex items, has to document the distinction as a rule about None; here it is the type’s.
btclib.tx.tx_out module¶
The TxOut dataclass; the class docstring has the contract.
- class btclib.tx.tx_out.TxOut(value: int, script_pub_key: ScriptPubKey | bytes | str | bytearray | memoryview, *, check_validity: bool = True)[source]¶
Bases:
objectOne output of a transaction: an amount and who can spend it.
The value is in satoshi; the script_pub_key is a ScriptPubKey, built from bytes when the caller passes them. assert_valid judges the amount and not the script, a script_pub_key on chain not having to parse.
- classmethod from_address(value: int, address: bytes | str | bytearray | memoryview) TxOut[source]¶
Build a TxOut paying the address, network included.
- classmethod from_dict(dict_: Mapping[str, Any], *, check_validity: bool = True) TxOut[source]¶
Build a TxOut from the dict shape to_dict writes.
A null value is not an amount here, where valid_sats_amount reads None as zero for the caller it was written for: a transaction output declares what it pays, so at the json boundary that value is a field the file has not filled in rather than a request for nothing. Unasked, {“value”: null} built an output of zero satoshi – legal, an OP_RETURN paying one and BIP322’s proof-of-funds transactions carrying one, so nothing downstream refused it and what came back said something the file did not.
PsbtOut.amount is the same value read the other way, and rightly: a psbt output may declare no amount yet, where a transaction output cannot.
- classmethod parse(data: BytesIO | bytes | str | bytearray | memoryview, *, check_validity: bool = True) TxOut[source]¶
Build a TxOut by parsing its wire serialization.
The network is mainnet, the wire not carrying one; the script is taken as it comes, scripts on chain not having to parse. The value is read as CAmount, Core’s signed int64_t: with check_validity=False, a high bit set parses negative – as it does for Core – rather than as an amount above MAX_MONEY.
- serialize(*, check_validity: bool = True) bytes[source]¶
Return the wire serialization: the value, then the script.
The value is written as CAmount, Core’s signed int64_t: a valid amount is never negative, but check_validity=False lets one through and it must still fit in the eight bytes it came from.
- to_dict(*, check_validity: bool = True) dict[str, Any][source]¶
Return the output as a dict of json-friendly values.
The value is a BTC string, not satoshi; type, addresses and network are derived for the reader and ignored by from_dict, which rebuilds them from the script – network excepted, read back with mainnet as its default.
Module contents¶
Transactions: OutPoint, TxIn, TxOut, Tx, Coin, join, and input_weight.
- class btclib.tx.Coin(tx_out: TxOut, height: int, is_coinbase: bool, *, check_validity: bool = True)[source]¶
Bases:
objectOne prevout: an output, the height it was created at, a coinbase bit.
Everything btclib.tx.tx_context’s rules need about an output that the output’s own bytes do not carry – Consensus::CheckTxInputs’s coinbase maturity check and the BIP68 sequence lock both read a prevout’s creation height, which is chain state and not part of the spending transaction.
- class btclib.tx.OutPoint(tx_id: bytes | str | bytearray | memoryview = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', vout: int = 4294967295, *, check_validity: bool = True)[source]¶
Bases:
objectThe output a transaction input spends: (tx_id, vout).
tx_id is held in the byte order hex explorers print, reversed on the wire by serialize and back by parse. Frozen and hashable, so an OutPoint can key the dict a utxo set is; the default instance is the coinbase marker, all-zero tx_id and 0xFFFFFFFF vout, which is_coinbase is the question about.
What is refused is a field no four bytes or thirty-two hold, and nothing about the pair: an all-zero tx_id with a real vout, and a real tx_id with the sentinel vout, are both outpoints Bitcoin Core accepts, checking the pair here being stricter than consensus. is_coinbase reads the pair instead, and it is what Tx.assert_valid uses to refuse a coinbase input in a transaction that is not one.
- assert_valid() None[source]¶
Refuse a tx_id not of 32 bytes and a vout no 4 bytes hold.
The two fields and not the pair, since checking the pair here would be stricter than consensus. Core’s COutPoint::IsNull is the conjunction – hash.IsNull() && n == NULL_INDEX – and CheckTransaction refuses a null outpoint in a non-coinbase and nothing else, so 000…000:0 is a transaction Core parses and checks, unspendable though it is. Refusing it here refused a transaction that exists: BIP322’s proof-of-funds vectors carry exactly that shape as the funding transaction of an input they prove control of, and Psbt.parse could not read them (issue 513).
What reads the pair is is_coinbase, which is Core’s own conjunction, and Tx.assert_valid through it: a coinbase input in a transaction that is not a coinbase is refused there, which is the rule consensus actually has.
- classmethod from_dict(dict_: Mapping[str, Any], *, check_validity: bool = True) OutPoint[source]¶
Build an OutPoint from the dict shape to_dict writes.
- classmethod parse(data: BytesIO | bytes | str | bytearray | memoryview, *, check_validity: bool = True) OutPoint[source]¶
Return an OutPoint from the first 36 bytes of the provided data.
- class btclib.tx.Tx(version: int = 1, lock_time: int = 0, vin: Sequence[TxIn] | None = None, vout: Sequence[TxOut] | None = None, *, check_validity: bool = True)[source]¶
Bases:
objectA Bitcoin transaction: version, lock time, inputs, outputs.
Mutable, being what a builder and a signer edit in place; id and hash are computed from the current state on every read, so they follow the edits. assert_valid checks what CheckTransaction checks of a lone transaction – field ranges, the stripped size against the block weight cap, the inputs and outputs one by one, the coinbase script size, no outpoint spent twice, the MAX_MONEY bound on the output sum; whether it spends what it claims needs the prevouts, which is script.engine.verify_transaction’s question.
- assert_standard() None[source]¶
Refuse what assert_valid refuses, plus a non-standard version.
Core v31.1’s relay policy, TX_MIN_STANDARD_VERSION through TX_MAX_STANDARD_VERSION: 1 and 2 both v27.2 and v31.1 relay, 3 is BIP431’s TRUC, standard since v28.0, and every version outside that window fails here and not in assert_valid, whose four-byte range takes it.
- assert_valid(*, unsigned_template: bool = False) None[source]¶
Assert that this is a valid transaction.
unsigned_template=True drops the two rules a PSBT’s global unsigned transaction cannot satisfy, and only those: at least one input and at least one output. Everything else still applies – the version and lock time ranges, each input and output on its own, no outpoint spent twice, the MAX_MONEY bound on their sum.
Those two rules are right for a transaction, Core’s CheckTransaction rejecting an empty vin or vout, and wrong for a PSBT’s: that one is incomplete by construction, which is the whole point of the format. BIP174 lists two zero-input PSBTs as valid and btclib refused both (issue 170).
- classmethod from_dict(dict_: Mapping[str, Any], *, check_validity: bool = True) Tx[source]¶
Build a Tx from the dict shape to_dict writes.
- property is_segwit: bool¶
Answer whether any input carries a witness.
The inputs and not the outputs: paying to a segwit script does not make the paying transaction segwit, spending one does.
- classmethod parse(data: BytesIO | bytes | str | bytearray | memoryview, *, check_validity: bool = True) Tx[source]¶
Return a Tx by parsing binary data.
A BIP144 marker and flag over witnesses that are all empty is refused, which is Core’s “Superfluous witness record” of UnserializeTransaction in src/primitives/transaction.h. The refusal is what keeps the property this library holds every wire class to – btclib.p2p.inventory’s module docstring states it, Headers.parse and Verack.parse refuse for it: serialize writes the marker only when some input carries a witness, so those octets are ones this class could not write back.
It does not answer to check_validity, and assert_no_trailing beside it is the shape being copied: that flag gates assert_valid, which asks about the transaction, and the transaction here is valid – what is malformed is the encoding, which no field records and nothing downstream could ask about.
- serialize(include_witness: bool, *, check_validity: bool = True) bytes[source]¶
Return the wire serialization, BIP144’s where a witness rides.
include_witness False gives the stripped serialization, what the txid is computed over; True adds the marker and the witnesses only if some input has one, a marker over empty witnesses not being what the wire writes.
A bool and nothing else, which is the line tests/built_object_contract_test.py draws: this flag decides what is computed rather than whether a check runs, so a value read for its truth would answer a stripped serialization – a different transaction id – where the wire one was meant.
- property sig_op_count: int¶
Return the legacy sigop count, Core’s GetLegacySigOpCount.
Every input’s script_sig and every output’s script_pub_key, counted from the bytes by script.sig_ops.sig_op_count: the p2sh and witness sigops Core adds in ConnectBlock and in the mempool’s PreChecks need the outputs being spent, which a transaction does not carry, and script.engine.sig_op_cost is the whole cost given them. The block rule this feeds, Block.assert_valid_sig_op_count, is the one the legacy count is enough for.
- to_dict(*, check_validity: bool = True) dict[str, str | int | list[Any]][source]¶
Return the transaction as a dict of json-friendly values.
The keys are Bitcoin Core’s decoderawtransaction ones; txid, hash, size, vsize and weight are derived for the reader and ignored by from_dict, which recomputes them.
- class btclib.tx.TxIn(prev_out: OutPoint | None = None, script_sig: bytes | str | bytearray | memoryview = b'', sequence: int = 0, script_witness: Witness | None = None, *, check_validity: bool = True)[source]¶
Bases:
objectOne input of a transaction.
The outpoint it spends, the script_sig, the sequence, and the witness – which serialize omits and parse leaves empty, the wire placing all witnesses after the inputs: Tx.serialize and Tx.parse are what write and read them. Mutable, a transaction being built input by input; TxIn() is the placeholder a builder starts from, its prev_out the coinbase marker.
- assert_valid() None[source]¶
Refuse an invalid prev_out, sequence, or witness.
The script_sig is deliberately not judged; the comment below carries the reasoning.
- classmethod from_dict(dict_: Mapping[str, Any], *, check_validity: bool = True) TxIn[source]¶
Build a TxIn from the dict shape to_dict writes.
- property is_segwit: bool¶
Answer whether the input carries a witness.
The input alone can only answer for what it holds: whether the spent output demands a witness is written in that output’s script_pub_key, which is not here.
- classmethod parse(data: BytesIO | bytes | str | bytearray | memoryview, *, check_validity: bool = True) TxIn[source]¶
Build a TxIn by parsing its wire serialization.
The witness comes back empty, sitting elsewhere on the wire; Tx.parse fills it in.
- class btclib.tx.TxOut(value: int, script_pub_key: ScriptPubKey | bytes | str | bytearray | memoryview, *, check_validity: bool = True)[source]¶
Bases:
objectOne output of a transaction: an amount and who can spend it.
The value is in satoshi; the script_pub_key is a ScriptPubKey, built from bytes when the caller passes them. assert_valid judges the amount and not the script, a script_pub_key on chain not having to parse.
- classmethod from_address(value: int, address: bytes | str | bytearray | memoryview) TxOut[source]¶
Build a TxOut paying the address, network included.
- classmethod from_dict(dict_: Mapping[str, Any], *, check_validity: bool = True) TxOut[source]¶
Build a TxOut from the dict shape to_dict writes.
A null value is not an amount here, where valid_sats_amount reads None as zero for the caller it was written for: a transaction output declares what it pays, so at the json boundary that value is a field the file has not filled in rather than a request for nothing. Unasked, {“value”: null} built an output of zero satoshi – legal, an OP_RETURN paying one and BIP322’s proof-of-funds transactions carrying one, so nothing downstream refused it and what came back said something the file did not.
PsbtOut.amount is the same value read the other way, and rightly: a psbt output may declare no amount yet, where a transaction output cannot.
- classmethod parse(data: BytesIO | bytes | str | bytearray | memoryview, *, check_validity: bool = True) TxOut[source]¶
Build a TxOut by parsing its wire serialization.
The network is mainnet, the wire not carrying one; the script is taken as it comes, scripts on chain not having to parse. The value is read as CAmount, Core’s signed int64_t: with check_validity=False, a high bit set parses negative – as it does for Core – rather than as an amount above MAX_MONEY.
- serialize(*, check_validity: bool = True) bytes[source]¶
Return the wire serialization: the value, then the script.
The value is written as CAmount, Core’s signed int64_t: a valid amount is never negative, but check_validity=False lets one through and it must still fit in the eight bytes it came from.
- to_dict(*, check_validity: bool = True) dict[str, Any][source]¶
Return the output as a dict of json-friendly values.
The value is a BTC string, not satoshi; type, addresses and network are derived for the reader and ignored by from_dict, which rebuilds them from the script – network excepted, read back with mainnet as its default.
- btclib.tx.assert_coinbase_maturity(prevouts: Sequence[Coin], spend_height: int) None[source]¶
Refuse a spend of a coinbase output not yet COINBASE_MATURITY deep.
Core’s Consensus::CheckTxInputs, bad-txns-premature-spend-of-coinbase (src/consensus/tx_verify.cpp, at bitcoin/bitcoin@9be056a8a7): spend_height - coin.height < COINBASE_MATURITY, for whichever of prevouts is a coinbase output.
spend_height is not always the height of the block connecting the spend: a mempool acceptance passes one past the active chain’s own tip instead, matching Core’s own MemPoolAccept::PreChecks (m_active_chainstate.m_chain.Height() + 1, src/validation.cpp, same tag).
- btclib.tx.assert_coinbase_value(coinbase: Tx, subsidy: int, fees: int) None[source]¶
Refuse a coinbase paying more than the subsidy plus the fees it collects.
Core’s bad-cb-amount (ConnectBlock, src/validation.cpp, at bitcoin/bitcoin@9be056a8a7): blockReward = nFees + GetBlockSubsidy(…), refused if coinbase.GetValueOut() > blockReward. subsidy is btclib.consensus.subsidy’s answer at the block’s own height; fees is the caller’s own sum of what every other transaction of the block pays in – the module docstring says why summing them again here would be a second implementation of a block connector’s own bookkeeping.
- btclib.tx.assert_sequence_locks(tx: Tx, prevouts: Sequence[Coin], height: int, tip_median_time_past: int, ancestor_median_time_past: Callable[[int], int]) None[source]¶
Refuse tx if a BIP68 relative lock time of its inputs is unmet.
Core’s CalculateSequenceLocks and EvaluateSequenceLocks, combined as SequenceLocks is (src/consensus/tx_verify.cpp, at bitcoin/bitcoin@9be056a8a7): prevouts aligned with tx.vin, one Coin per input, in place of a freshly-read CCoinsViewCache.
A transaction below version 2 is skipped, matching BIP68 – the module docstring says why no enforce_bip68 flag is here to skip the rest of it too. An input whose sequence carries SEQUENCE_LOCKTIME_DISABLE_FLAG is skipped the same way, on its own rather than the whole transaction’s.
height is Core’s own block.nHeight – the height of the block the transaction is connecting into, one past its own parent’s – and tip_median_time_past is that parent’s GetMedianTimePast(), the reference a height-based lock is compared against directly and a time-based one after ancestor_median_time_past has turned each input’s own relative lock into an absolute one. ancestor_median_time_past(h) is header_context.median_time_past of the block at height h, called once per time-locked input at max(coin.height - 1, 0) – the block before the one that confirmed the coin, matching Core’s own comment on why: “the smallest allowed timestamp of the block containing the txout being spent”.
Refuses with bad-txns-nonfinal, the message Core’s own ConnectBlock reports a failure of this rule with.
- btclib.tx.input_weight(script_sig: bytes | str | bytearray | memoryview, witness: Witness | None = None) int[source]¶
Return the weight one input adds to a transaction, per BIP141.
Bitcoin Core’s calculate_input_weight, of test/functional/test_framework/wallet_util.py: the non-witness bytes weigh WITNESS_SCALE_FACTOR each – the 36-byte outpoint, the script_sig behind its var_int length, the 4-byte sequence – and the serialized witness stack weighs one each. Which output is spent and what the sequence says do not enter the answer, both fields being fixed-width, so neither is an argument.
It is computable before the input exists, which is the point: a fee is chosen before the transaction is signed, so the weight the fee is computed from cannot be read off the finished transaction. Tx.weight is the same arithmetic over one already built.
witness is None for an input of a transaction with no witness section and a Witness for one of a transaction that has it – Witness() being the empty stack a non-segwit input carries there. The two differ by a byte, that stack still serializing the var_int announcing no elements, and a caller that cannot tell them apart misprices an input by one. Core, taking a list of hex items, has to document the distinction as a rule about None; here it is the type’s.
- btclib.tx.is_final(tx: Tx, height: int, block_time: int) bool[source]¶
Answer whether tx is final at height, against a block_time cutoff.
Core’s IsFinalTx (src/consensus/tx_verify.cpp, at bitcoin/bitcoin@9be056a8a7): a zero lock_time is always final; otherwise tx is final once height or block_time – whichever lock_time’s own units name, decided against LOCKTIME_THRESHOLD – has passed it, or regardless if every one of tx’s own inputs opts out of lock_time by carrying SEQUENCE_FINAL.
Which instant block_time is, is the caller’s: pindexPrev-> GetMedianTimePast() once BIP113 (the CSV deployment) is active on the chain being validated against, the block’s own timestamp before that – Core’s ContextualCheckBlock decides it from the chain’s own activation height, which this function does not carry. A caller checking a block reports False here as Core’s own bad-txns-nonfinal.
- btclib.tx.join(txs: Sequence[Tx], enforce_same_version: bool, enforce_same_lock_time: bool, shuffle_inp: bool, shuffle_out: bool) Tx[source]¶
Join transactions into one, concatenating inputs and outputs.
Outputs are concatenated and never merged, and no parameter asks for it: coalescing two outputs that pay the same script changes the output set, so it invalidates every signature already made over the old one – and with the shuffle below the result would depend on the order the merge ran in. Summing two payments into one output is the caller’s to do before signing.