btclib.script.engine package

Submodules

btclib.script.engine.flags module

Bitcoin Script verification flags.

A bitmask, not a list of plain strings asked “P2SH” in flags: that check accepts any string at all, so a misspelled name – “DERSING”, “MINMALIF” – silently disables a consensus rule instead of failing, in a script verification engine (issue #145).

Both spellings are checked. A caller’s name is looked up in the enum by to_script_flags, which refuses what it does not know, and every test the engine makes is ScriptFlag.X in flags, which a typo turns into an AttributeError rather than into a rule that never runs. A test asserts the two sets are the same: tests/script_engine/flags_test.py reads the engine’s own source, and no member may go unchecked there and no name checked there may be missing here.

Names and bit positions are Bitcoin Core’s SCRIPT_VERIFY_* of src/script/interpreter.h, so a flag means here what it means there, and Core’s vectors – script_tests.json and tx_valid.json carry theirs as a comma-separated string – can be passed as they are written.

One member is stricter than its name in Core, and it is named here because the rest of the file promises it is not: CONST_SCRIPTCODE refuses a signature check carried anywhere in the script_sig, executed or not, where Core errors only where its FindAndDelete finds the signature in an executed op. engine/__init__.py says why the class is refused up front.

class btclib.script.engine.flags.ScriptFlag(*values)[source]

Bases: Flag

A set of script verification rules to enforce.

One member per rule the engine implements, and none for a rule it does not: asking for a member is asking for a branch, never a request quietly ignored, which is the failure mode this enum exists to remove. So every SCRIPT_VERIFY_* Core spells is a member here, bit for bit and with no holes in the positions below.

btclib.script.engine.flags.to_script_flags(flags: ScriptFlag | str | Iterable[str] | None) ScriptFlag[source]

Return the rules to enforce, from any of the spellings accepted.

A ScriptFlag is returned as it is, a string is split on commas, and anything else is taken as an iterable of names; an unknown name raises rather than being skipped, which is the whole point of the enum.

None is ALL_FLAGS, the default set, while an empty string, an empty iterable and Core’s “NONE” are NO_FLAGS, no rule at all. Those two must stay tellable apart – the first says “whatever btclib enforces by default”, the second “check the script and nothing else” – so this does not collapse a falsy argument onto the default.

btclib.script.engine.script module

The legacy and segwit v0 interpreter loop of the script engine.

btclib.script.engine.script.assert_not_disabled(op_code: int) None[source]

Reject an op code disabled by CVE-2010-5137, executed or not.

btclib.script.engine.script.assert_nulldummy(dummy: bytes, flags: ScriptFlag) None[source]

Reject a non-empty dummy, the element OP_CHECKMULTISIG pops too many.

btclib.script.engine.script.assert_nullfail(flags: ScriptFlag, verified: bool, signatures: list[bytes], op: str) None[source]

Reject a signature that failed to verify and was not empty.

verified is refused rather than read for its truth, which is what separates it from verify_script’s final next door: every wrong value is true, so the misreading a non-bool makes is always the one the flag’s True stands for. final=True demands a true stack, so a non-bool there fails a script that would have passed; True here suppresses this refusal, so verified=”false” lets a non-empty signature that failed to verify through a consensus rule. CONTRIBUTING.md has that as the polarity a truth needs and this one has not (issue #884).

btclib.script.engine.script.assert_pub_key_num(pub_key_num: int) None[source]

Reject a public key count outside 0..20, before the keys are read.

Core’s SCRIPT_ERR_PUBKEY_COUNT, and the lower bound is the half that is not decoration: a negative count reaches range as an empty one, so nothing is popped, nothing underflows, and a count of -1 sails through a test for “more than twenty” – which left -1 -1 OP_CHECKMULTISIG pushing false and the script running on where Core ends it.

btclib.script.engine.script.assert_signature_num(signature_num: int, pub_key_num: int) None[source]

Reject a signature count outside 0..pub_key_num.

Core’s SCRIPT_ERR_SIG_COUNT, negative for the same reason as above.

btclib.script.engine.script.calculate_script_code(script_bytes: bytes, codesep_offset: int, signatures: list[bytes], const_scriptcode: bool, segwit: bool) bytes[source]

Return the script code the signature under check commits to.

Core’s two steps and in its order: CScript scriptCode(pbegincodehash, pend), a slice of the script’s own bytes from just past the last executed OP_CODESEPARATOR, and then — for a pre-segwit signature only — FindAndDelete of the signature itself, which BIP143 dropped for segwit v0 and which is why segwit is a parameter rather than a fact about the script.

The OP_CODESEPARATORs left in the slice stay in it. Eliding them is the legacy serializer’s, sig_hash.legacy doing it there because that is where Core does it, and doing it here as well would elide them before FindAndDelete rather than after.

btclib.script.engine.script.check_pub_key(pub_key: bytes, segwit: bool, flags: ScriptFlag) bool[source]

Answer whether the public key is well-formed enough to verify with.

Core’s CheckPubKeyEncoding, split the way Core splits it: a wrong length or prefix returns False, which op_checksig turns into a failed signature check rather than a script error, while the two flags that make the encoding itself the offence raise – STRICTENC for a hybrid 0x06/0x07 prefix, WITNESS_PUBKEYTYPE for an uncompressed key in a segwit script.

btclib.script.engine.script.dsa_verify(msg_hash: bytes, pub_key: bytes, sig: bytes) bool[source]

Verify an ECDSA signature, returning False if it is malformed.

The dispatch every delegation in this library makes, and this one has a second implementation to reach when it declines: ecc.dsa answers the same question in Python, so libsecp256k1 out of reach leaves an arm to take rather than an ImportError to raise.

hybrid=True is what the Python arm needs and the bindings do not: ec_pubkey_parse takes the 0x06/0x07 prefixes always (eckey_impl.h) while point_from_octets takes them only when asked, and consensus wants CHECKSIG to succeed for a hybrid key wherever STRICTENC is off. Both defects of issue #129 were in that arm and neither was a lax function – one was Sig.parse dropping a byte, the other this very prefix – so what the arm has to agree with the bindings about is the verdict on a whole transaction.

One try around both arms, because the contract is one: a signature or a public key that cannot be parsed is a failed verification, not an exception the interpreter loop sees. The bindings raise ValueError for it, point_from_octets raises BTClibValueError, which is one, and dsa.verify_ catches its own. DER strictness is enforced upstream either way, by fix_signature under the STRICT_DER_FLAGS below – and so is the lower-s form, which is what keeps the two arms from disagreeing about a high s: the bindings’ dsa.verify refuses one where _assert_as_valid_(…, lower_s=False) accepts it, and by the time either is asked fix_signature has already negated a high s wherever no flag refuses the signature outright, which is Core’s own behaviour.

bytes and nothing wider, which is what the three declare: the bindings would answer a float with “the message hash must be bytes”, which is true and is not btclib saying it. Every caller here hands stack elements, so the check costs a few nanoseconds of a microsecond-scale verification (issue #814).

btclib.script.engine.script.find_and_delete(script: bytes, target: bytes) tuple[bytes, int][source]

Delete every occurrence of target from script: (result, how many).

Core’s FindAndDelete, matched op code boundary by op code boundary and in one left-to-right pass, both of which are load-bearing. A bytes.replace loop is neither: it deletes a match lying inside the data of a push, which Core’s walk never reaches, and re-running it over the result deletes matches that only exist because an earlier deletion joined their halves. Either turns a script code Core leaves alone into one that is shorter, differently signed, or no longer a script at all.

Once a match is taken the walk resumes from just past it, wherever that lands — Core resumes its GetOp there too, which is how a deletion can leave the rest of the script read as something else. Bug for bug: the script code is consensus, not taste.

btclib.script.engine.script.fix_signature(signature: bytes, flags: ScriptFlag) bytes[source]

Return the signature the bindings can be asked to verify.

The bindings parse strict DER and refuse a high s, which is two rules where Core has none: CPubKey::Verify parses laxly and normalizes s before verifying, and it is CheckSignatureEncoding above it that refuses either – under the flags for it and not otherwise. So both are answered here, each in the direction its flags ask for: a lax encoding is re-serialized strict when no flag wants it refused, and a high s is negated when no flag wants it refused.

btclib.script.engine.script.op_checksig(signature: bytes, signatures: list[bytes], pub_key: bytes, script_bytes: bytes, codesep_offset: int, prevout_value: int, tx: Tx, i: int, flags: ScriptFlag, segwit: bool, precomputed: PrecomputedTxData | None = None, hash_types: list[int] | None = None) bool[source]

Verify one ECDSA signature over the script code it commits to.

Returns the boolean the op code pushes rather than raising: an empty signature, one that fails to verify, or a key or encoding refused under lax rules are all False, and an error only where a flag makes the encoding the offence – DERSIG/LOW_S/STRICTENC for the signature, STRICTENC/WITNESS_PUBKEYTYPE for the key.

signatures is what FindAndDelete removes from a pre-segwit script code: the whole set under check when called from OP_CHECKMULTISIG, the signature itself otherwise. The message hash is BIP143’s for a segwit v0 input and the legacy per-input serialization for the rest; tapscript signatures never reach here, tapscript.py verifying BIP340 on its own.

hash_types is verify_input’s collector, appended to here because this is where the last byte of a stack element is known to be a hash type at all.

btclib.script.engine.script.op_code_name(op_code: int) str[source]

Name an op code, rather than answer a missing key with a KeyError.

btclib.script.engine.script.prepare_script(script: list[int | str | bytes | bytearray | memoryview], flags: ScriptFlag, segwit: bool) None[source]

Refuse OP_CODESEPARATOR in a legacy script under CONST_SCRIPTCODE.

Only legacy: BIP143 keeps the op code meaningful in segwit v0, so the pre-segwit script code is the one the flag freezes.

btclib.script.engine.script.script_op_count(count: int, increment: int) int[source]

Add to the op code count, bounded by MAX_OPS_PER_SCRIPT.

Core’s accounting: pushes are free, every other op code costs one, and OP_CHECKMULTISIG adds its public key count on top.

btclib.script.engine.script.verify_script(script_bytes: bytes, stack: list[bytes], prevout_value: int, tx: Tx, i: int, flags: ScriptFlag, segwit: bool, final: bool = False, precomputed: PrecomputedTxData | None = None, hash_types: list[int] | None = None) None[source]

Execute the script over the caller’s stack, as Core’s EvalScript.

The stack is mutated in place, which is how the callers chain scripts: script_sig leaves what script_pub_key then reads. Any refusal – a BTClibValueError out of an op code, an IndexError out of a pop on a short stack – is re-raised as ScriptError carrying the index of the failing command and the stack depth. With final the script must end on a non-empty stack with a true top element, which is the caller saying no script runs after this one.

hash_types is verify_input’s collector, threaded through to op_checksig; chaining scripts over one stack is chaining them over one collector too.

btclib.script.engine.script_op_codes module

Bitcoin Script legacy op codes.

One function per op code, each the matching case of Core’s EvalScript; script.py and tapscript.py hold the interpreter loops that dispatch them. Two contracts are shared by every function here rather than restated on each:

  • an operand read as a number is a CScriptNum: at most 4 bytes on input, minimally encoded where MINIMALDATA asks, while a result may be pushed at 5 bytes and is refused only when an op code consumes it again – Core’s asymmetry, kept deliberately

  • a pop from a stack too short raises IndexError, which the loop reports as a stack underflow, and every other refusal is a BTClibValueError; the loop turns both into a ScriptError carrying the index of the failing command. The op codes that check the depth themselves do so only where popping would not fail on its own.

btclib.script.engine.script_op_codes.assert_balanced_if(condition_stack: list[bool]) None[source]

Reject a conditional the script never closed.

Core’s if (!vfExec.empty()) once the loop is over, and it is one of the two halves of the rule: op_else and op_endif refuse a branch that was never opened, this refuses one that was never shut. Counting OP_IF, OP_NOTIF and OP_ENDIF over the parsed script instead answers only the second half – the sum is what the depth ends at, so a script closing a branch before opening it (OP_ENDIF OP_1 OP_IF OP_1) counts to zero and Core rejects it.

btclib.script.engine.script_op_codes.assert_minimal_push(data: bytes, op_code: int, flags: ScriptFlag, serialize: Callable[[list[int | str | bytes | bytearray | memoryview]], bytes]) None[source]

Enforce MINIMALDATA on a pushdata command.

The serializer is the caller’s: script.py pushes with the legacy one, tapscript.py with the taproot one.

btclib.script.engine.script_op_codes.assert_stack_size(stack: list[bytes], altstack: list[bytes]) None[source]

Enforce Core’s MAX_STACK_SIZE on the two stacks together.

btclib.script.engine.script_op_codes.op_0notequal(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop a number and push whether it is non-zero.

btclib.script.engine.script_op_codes.op_1add(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop a number and push it incremented by one.

btclib.script.engine.script_op_codes.op_1negate(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Push the number -1.

btclib.script.engine.script_op_codes.op_1sub(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop a number and push it decremented by one.

btclib.script.engine.script_op_codes.op_2drop(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop the top two stack elements.

btclib.script.engine.script_op_codes.op_2dup(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Push copies of the top two stack elements, keeping their order.

btclib.script.engine.script_op_codes.op_2over(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Push copies of the third and fourth elements, keeping their order.

btclib.script.engine.script_op_codes.op_2rot(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Move the fifth and sixth elements to the top, keeping their order.

btclib.script.engine.script_op_codes.op_2swap(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Exchange the top pair of elements with the pair below it.

btclib.script.engine.script_op_codes.op_3dup(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Push copies of the top three stack elements, keeping their order.

btclib.script.engine.script_op_codes.op_abs(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop a number and push its absolute value.

btclib.script.engine.script_op_codes.op_add(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push their sum.

btclib.script.engine.script_op_codes.op_booland(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push whether both are non-zero.

btclib.script.engine.script_op_codes.op_boolor(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push whether either is non-zero.

btclib.script.engine.script_op_codes.op_checklocktimeverify(stack: list[bytes], tx: Tx, i: int, flags: ScriptFlag) None[source]

Refuse to spend before the absolute lock time on the stack, BIP65.

Reads the top element without popping it, as a number of up to 5 bytes. The refusals are BIP65’s: an empty stack, a negative lock time, a type mismatch – block height against timestamp, the two sides of the LOCKTIME_THRESHOLD threshold – a lock time the transaction’s has not reached, and a final input sequence, which would let the transaction bypass its own lock_time. A NOP when the flag is off, the op code being a redefined OP_NOP2.

btclib.script.engine.script_op_codes.op_checkmultisigverify(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) list[int | str | bytes | bytearray | memoryview][source]

Expand to OP_CHECKMULTISIG and OP_VERIFY, as op_equalverify.

btclib.script.engine.script_op_codes.op_checksequenceverify(stack: list[bytes], tx: Tx, i: int, flags: ScriptFlag) None[source]

Refuse to spend before the relative lock time on the stack, BIP112.

Reads the top element without popping it, as a number of up to 5 bytes. An operand with bit 31 set asks for nothing, per BIP112; otherwise the refusals are an empty stack, a negative operand, a transaction version below BIP68’s 2, an input sequence with the disable bit set, a unit mismatch on bit 22 – blocks against time – and a relative lock time above the input’s. A NOP when the flag is off, the op code being a redefined OP_NOP3.

btclib.script.engine.script_op_codes.op_checksigverify(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) list[int | str | bytes | bytearray | memoryview][source]

Expand to OP_CHECKSIG followed by OP_VERIFY, as op_equalverify.

btclib.script.engine.script_op_codes.op_depth(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Push the number of elements on the stack.

btclib.script.engine.script_op_codes.op_drop(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop the top stack element.

btclib.script.engine.script_op_codes.op_dup(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Push a copy of the top stack element.

btclib.script.engine.script_op_codes.op_else(condition_stack: list[bool]) None[source]

Toggle the innermost open branch, refusing one never opened.

A toggle rather than a one-shot alternative: Core flips vfExec.back(), so a second OP_ELSE in the same branch turns it back on, and this keeps that.

btclib.script.engine.script_op_codes.op_endif(condition_stack: list[bool]) None[source]

Close the innermost open branch, refusing one never opened.

btclib.script.engine.script_op_codes.op_equal(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two elements and push whether they are byte-equal.

Byte equality, not numeric: 0x00 and the empty element are both false to op_verify yet unequal here, which op_numequal answers the other way.

btclib.script.engine.script_op_codes.op_equalverify(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) list[int | str | bytes | bytearray | memoryview][source]

Expand to OP_EQUAL followed by OP_VERIFY.

The *VERIFY op codes are the pair they contract: the interpreter loop re-serializes the returned commands in front of the unread script and winds its counters back, so the pair runs without being counted twice.

btclib.script.engine.script_op_codes.op_fromaltstack(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Move the top altstack element back onto the stack.

btclib.script.engine.script_op_codes.op_greaterthan(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push whether the deeper is above the top.

btclib.script.engine.script_op_codes.op_greaterthanorequal(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push whether the deeper is at least the top.

btclib.script.engine.script_op_codes.op_hash160(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop the top element and push RIPEMD160 of its SHA256 digest.

btclib.script.engine.script_op_codes.op_hash256(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop the top element and push its double SHA256 digest.

btclib.script.engine.script_op_codes.op_if(stack: list[bytes], condition_stack: list[bool], flags: ScriptFlag, segwit_version: int) None[source]

Pop the condition and open a branch that executes on true.

Inside an unexecuted outer branch nothing is popped and False is appended, so nesting is tracked without evaluating anything. The minimal-condition rule – the empty element or 0x01, nothing else – is consensus in tapscript per BIP342 and opt-in through the MINIMALIF flag in segwit v0; a legacy script takes any element as its condition.

btclib.script.engine.script_op_codes.op_ifdup(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Push a copy of the top element only if it is true.

btclib.script.engine.script_op_codes.op_lessthan(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push whether the deeper is below the top.

btclib.script.engine.script_op_codes.op_lessthanorequal(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push whether the deeper is at most the top.

btclib.script.engine.script_op_codes.op_max(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push the larger.

btclib.script.engine.script_op_codes.op_min(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push the smaller.

btclib.script.engine.script_op_codes.op_negate(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop a number and push its negation.

btclib.script.engine.script_op_codes.op_nip(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Remove the element below the top, leaving the top in place.

btclib.script.engine.script_op_codes.op_nop(flags: ScriptFlag) None[source]

Do nothing, or refuse to under DISCOURAGE_UPGRADABLE_NOPS.

Serves OP_NOP1 and OP_NOP4..OP_NOP10, the op codes reserved for soft forks to redefine; the flag is the policy that keeps them out of scripts until a fork gives one a meaning, as BIP65 and BIP112 did to OP_NOP2 and OP_NOP3.

btclib.script.engine.script_op_codes.op_not(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop a number and push whether it is zero.

btclib.script.engine.script_op_codes.op_notif(stack: list[bytes], condition_stack: list[bool], flags: ScriptFlag, segwit_version: int) None[source]

Pop the condition and open a branch that executes on false.

The unexecuted-branch behaviour and the minimal-condition rule are op_if’s; only the sense of the popped condition is inverted.

btclib.script.engine.script_op_codes.op_numequal(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push whether they are equal.

Numeric equality, not byte equality: without MINIMALDATA, 0x00 equals the empty element here and not in op_equal.

btclib.script.engine.script_op_codes.op_numequalverify(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) list[int | str | bytes | bytearray | memoryview][source]

Expand to OP_NUMEQUAL followed by OP_VERIFY, as op_equalverify.

btclib.script.engine.script_op_codes.op_numnotequal(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push whether they differ.

btclib.script.engine.script_op_codes.op_over(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Push a copy of the element below the top.

btclib.script.engine.script_op_codes.op_pick(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop a depth n and push a copy of the element n deep.

Zero is the top; a negative depth is refused, one past the stack underflows.

btclib.script.engine.script_op_codes.op_return(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Fail the script unconditionally, leaving the stack as it is.

btclib.script.engine.script_op_codes.op_ripemd160(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop the top element and push its RIPEMD160 digest.

btclib.script.engine.script_op_codes.op_roll(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop a depth n and move the element n deep to the top.

Zero is the top and leaves the stack as it is; a negative depth or one past the stack is refused.

btclib.script.engine.script_op_codes.op_rot(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Rotate the top three elements, the third-deep going on top.

btclib.script.engine.script_op_codes.op_sha1(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop the top element and push its SHA1 digest.

btclib.script.engine.script_op_codes.op_sha256(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop the top element and push its SHA256 digest.

btclib.script.engine.script_op_codes.op_size(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Push the byte length of the top element, leaving it in place.

btclib.script.engine.script_op_codes.op_sub(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop two numbers and push the deeper one minus the top one.

btclib.script.engine.script_op_codes.op_swap(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Exchange the top two stack elements.

btclib.script.engine.script_op_codes.op_toaltstack(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Move the top stack element onto the altstack.

btclib.script.engine.script_op_codes.op_tuck(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Insert a copy of the top element below the one under it.

btclib.script.engine.script_op_codes.op_verify(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop the top element and fail the script if it is false.

btclib.script.engine.script_op_codes.op_within(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) None[source]

Pop max, min and x, and push whether min <= x < max.

Left-closed and right-open, which is Core’s comparison.

btclib.script.engine.script_op_codes.read_push_data(op_code: int, s: BytesIO, stack: list[bytes], skip_execution: bool, flags: ScriptFlag, serialize: Callable[[list[int | str | bytes | bytearray | memoryview]], bytes], element_size_limit: int | None = 520) None[source]

Read a pushdata command from the stream and push its data.

The read comes before the skip: the stream must advance past the data whether or not the branch executes, and only what executes is measured for minimality and pushed. The serializer is a parameter for assert_minimal_push’s reason, each engine measuring a push against its own script language.

The two refusals are Core’s, at the top of its EvalScript loop and both before the fExec test, which is why they sit before the skip and not after it: a push declaring more bytes than the script holds is SCRIPT_ERR_BAD_OPCODE, GetOp having failed to read it, and one over the element limit is SCRIPT_ERR_PUSH_SIZE. Core rejects either inside a branch nothing takes, and so does this.

The limit is a parameter, and None turns it off, because tapscript cannot answer here: an OP_SUCCESSx anywhere makes the script valid with the rest of it unexecuted, so taproot.parse defers the refusal to the end of its walk and the loop has nothing left to measure. Core defers it the same way, by scanning for OP_SUCCESSx before it executes anything at all.

btclib.script.engine.script_op_codes.unknown_op_code(op: str) NoReturn[source]

Reject a named op code the interpreter does not implement.

btclib.script.engine.tapscript module

The tapscript interpreter loop of the script engine, per BIP342.

btclib.script.engine.tapscript.get_hashtype(signature: bytes) int[source]

Read the sighash type off a taproot signature, per BIP341.

A 64-byte signature is SIGHASH_DEFAULT; a 65th byte carries the type and must not spell the default explicitly, the two encodings of one meaning being a malleability.

btclib.script.engine.tapscript.op_checksig(stack: list[bytes], script_bytes: bytes, codesep_pos: int, tx: Tx, i: int, prevouts: list[TxOut], annex: bytes, budget: int, flags: ScriptFlag, precomputed: PrecomputedTxData | None = None, hash_types: list[int] | None = None) int[source]

Verify one BIP340 signature in a script path: BIP342’s OP_CHECKSIG.

Pops public key and signature, pushes the result, and returns what is left of the sigops budget, every non-empty signature costing 50 whether or not it verifies. The refusals are BIP342’s: an empty public key, an exhausted budget, and a non-empty signature that does not verify – where the legacy op code pushes False, tapscript fails the script, its NULLFAIL being consensus. A key neither empty nor 32 bytes verifies nothing and succeeds, which is the upgrade room, refused only under DISCOURAGE_UPGRADABLE_PUBKEYTYPE. The message hash commits to the tapleaf and to the last executed OP_CODESEPARATOR through the BIP341 extension.

hash_types is verify_input’s collector, appended to where the hash type is read: an empty signature is not one, and neither is anything popped beside a public key BIP342 left upgradable.

btclib.script.engine.tapscript.op_checksigadd(stack: list[bytes], altstack: list[bytes], flags: ScriptFlag) list[int | str | bytes | bytearray | memoryview][source]

Expand OP_CHECKSIGADD to OP_CHECKSIG OP_ADD, per BIP342.

The op code pops signature, n and public key, and pushes n plus the check’s result; the swap puts n out of OP_CHECKSIG’s way, and the returned pair is re-run by the loop as the *VERIFY expansions are. BIP342 defines it as this composition, batch-verifiable where the CHECKMULTISIGs it replaces are not.

btclib.script.engine.tapscript.ssa_verify(msg_hash: bytes, pub_key: bytes, sig: bytes) bool[source]

Verify a BIP340 signature, returning False if it is malformed.

The dispatch engine.script.dsa_verify makes and for its reason: ecc.ssa answers the same question in Python, and the arm is what there is to reach with libsecp256k1 out of reach. No hybrid prefix to ask for here – an x-only key is 32 bytes and BIP340 says which of the two points it is – so the arm is the prepared spelling alone.

The bindings raise a ValueError on a signature or x-only public key that libsecp256k1 refuses to parse, and ssa.verify_ answers False for the same; the caller treats either as a failed verification and raises BTClibValueError itself.

bytes and nothing wider, as in engine.script.dsa_verify and for its reason.

btclib.script.engine.tapscript.verify_key_path(script_pub_key: bytes, stack: list[bytes], prevouts: list[TxOut], tx: Tx, i: int, annex: bytes, precomputed: PrecomputedTxData | None = None, hash_types: list[int] | None = None) None[source]

Verify a taproot key-path spend, per BIP341.

The single witness element is a BIP340 signature by the output key itself over the taproot sig_hash with no script committed to; a signature that does not verify is the only refusal, get_hashtype’s aside.

hash_types is verify_input’s collector; the one element of the stack is the one signature to report.

btclib.script.engine.tapscript.verify_script_path_vc0(script_bytes: bytes, stack: list[bytes], prevouts: list[TxOut], tx: Tx, i: int, annex: bytes, sigops_budget: int, flags: ScriptFlag, precomputed: PrecomputedTxData | None = None, hash_types: list[int] | None = None) None[source]

Execute a leaf-version-0xc0 tapscript, per BIP342.

The loop is the legacy engine’s with BIP342’s differences: no op code count and no script size limit, a sigops budget spent by signature instead, an OP_SUCCESSx that ends validation with success before anything runs, MINIMALIF as consensus, and the CHECKMULTISIGs gone in favour of OP_CHECKSIGADD. Refusals leave as ScriptError, as they do from the legacy loop, and the script must end with exactly one true element on the stack.

hash_types is verify_input’s collector, threaded to op_checksig as the legacy loop threads it to its own.

Module contents

The script engine: transaction verification against the consensus rules.

class btclib.script.engine.ScriptFlag(*values)[source]

Bases: Flag

A set of script verification rules to enforce.

One member per rule the engine implements, and none for a rule it does not: asking for a member is asking for a branch, never a request quietly ignored, which is the failure mode this enum exists to remove. So every SCRIPT_VERIFY_* Core spells is a member here, bit for bit and with no holes in the positions below.

btclib.script.engine.taproot_get_annex(witness: Witness) tuple[bytes, list[bytes]][source]

Split the annex off a taproot witness stack: (annex, the rest).

BIP341 makes the annex the last element of a stack of at least two whose first byte is 0x50; the empty bytes mean there is none, no annex being distinguishable from an empty one by construction.

btclib.script.engine.taproot_unwrap_script(script: bytes, stack: list[bytes]) tuple[bytes, list[bytes], int][source]

Take a script-path spend apart: (tapscript, stack, leaf version).

The last two witness elements are the control block and the script, per BIP341; the control block must prove the script was committed to by the output key, and check_output_pubkey is what verifies that merkle proof. Returns the stack without the two, leaving the caller’s list untouched.

btclib.script.engine.to_script_flags(flags: ScriptFlag | str | Iterable[str] | None) ScriptFlag[source]

Return the rules to enforce, from any of the spellings accepted.

A ScriptFlag is returned as it is, a string is split on commas, and anything else is taken as an iterable of names; an unknown name raises rather than being skipped, which is the whole point of the enum.

None is ALL_FLAGS, the default set, while an empty string, an empty iterable and Core’s “NONE” are NO_FLAGS, no rule at all. Those two must stay tellable apart – the first says “whatever btclib enforces by default”, the second “check the script and nothing else” – so this does not collapse a falsy argument onto the default.

btclib.script.engine.validate_push_only(script_sig: bytes) None[source]

Refuse a script_sig that is not push-only: Core’s CScript::IsPushOnly.

The rule the SIGPUSHONLY flag names and BIP16 makes consensus, and Core writes it as a walk over the bytes: an op code at or below OP_16 pushes a value, every byte above it is an operator, and GetOp failing ends the walk with a no – so a script_sig whose last push runs past the end is not push-only either, the parse marking that place rather than refusing the bytes (issue #123).

Over the bytes and not over the parsed commands, because a scan of the commands can only test the names, and the names do not draw Core’s line. The 69 bytes above OP_16 that no op-code table names parse as UNKNOWN_OP_CODE_n, which no test for an OP_ prefix takes for an operator, and OP_RESERVED parses as a name where 0x50 is below OP_16 and pushes nothing at all – 70 bytes answered wrongly in both directions (issue #220). Walking the bytes asks Core’s question of every byte, named or not.

No spend hangs on the difference today: the interpreter refuses every unnamed byte the moment it executes one, and putting one where it does not execute takes a conditional, which is named and refused here. That agreement is a coincidence between two modules, though, and a soft fork naming one of those 69 – which is what they are reserved for – is what ends it.

btclib.script.engine.verify_amounts(prevouts: list[TxOut], tx: Tx) None[source]

Refuse a transaction whose outputs exceed the outputs it spends.

The difference is the fee, and a negative fee is money printed; script validation never reads the amounts except through the sig_hash, so this is a check of its own rather than a flag.

btclib.script.engine.verify_input(prevouts: list[TxOut], tx: Tx, i: int, flags: ScriptFlag | str | Iterable[str] | None = None, precomputed: PrecomputedTxData | None = None, hash_types: list[int] | None = None) None[source]

Verify one input of a transaction against the output it spends.

flags are the rules to enforce, as a ScriptFlag or as the names of one: None is btclib’s default set, ALL_FLAGS, and an unknown name is refused rather than ignored (issue #145). A default that says “the consensus rules” is what an outside caller wants, and it matches verify_transaction’s.

precomputed is the transaction-wide part of the segwit sig_hashes, which verify_transaction builds once for its whole loop; verifying a single input has nothing to share it with, so it defaults to None and each sig_hash computes what it needs (issue #164).

hash_types is the list the interpreter reports into: the hash type of every stack element it consumed as a signature, in the order it met them, appended to whatever the caller passed. Which elements those were is what an outside caller cannot work out on its own – the control block of a single-leaf taproot tree is 65 bytes, exactly the shape of a BIP340 signature with an explicit hash type, and a data push in a script-path witness can be anything at all – so a rule about the hash types themselves has to be enforced from here or guessed at. None, the default, collects nothing: consensus has no such rule, and BIP322’s “all signatures MUST use SIGHASH_ALL” is the caller that has one (issue #514).

The split is Core’s: this function is VerifyScript – the two legacy runs on one stack, the p2sh unwrap, the malleation checks, the CLEANSTACK check – and the witness arms live behind _verify_witness_program, as they live behind Core’s VerifyWitnessProgram.

btclib.script.engine.verify_transaction(prevouts: list[TxOut], tx: Tx, flags: ScriptFlag | str | Iterable[str] | None = None, check_amounts: bool = True, hash_types: list[int] | None = None) None[source]

Verify every input of a transaction against the outputs it spends.

flags is what verify_input takes, converted once here rather than once per input; hash_types is what verify_input reports into, one list for the whole transaction, so the inputs’ signatures arrive in it in input order.