Copy
Trading Bots
Events

Bitcoin Output Scripts: How Locking Conditions Work from P2PKH to Taproot

2026-05-25 ·  7 days ago
041

A Bitcoin output script is the code that specifies who can spend a transaction output and under what conditions. Every UTXO on the Bitcoin network carries one. When you send bitcoin, you are not transferring a balance: you are creating a new output locked by a script that only the intended recipient can satisfy. Understanding how that locking mechanism works explains a significant amount of Bitcoin's security model, its privacy properties, and how Taproot changed the economics of complex transactions.




What a Bitcoin Output Script Actually Does


Bitcoin does not use accounts. It uses a ledger of UTXOs, each one locked by a Bitcoin output script stored in a field called the scriptPubKey. To spend a UTXO, a transaction must supply a corresponding scriptSig (for legacy inputs) or witness data (for SegWit inputs) that, when evaluated together with the scriptPubKey, resolves to TRUE on Bitcoin's stack machine.


This is a two-phase execution model that most explanations skip over.


Phase 1: The spending transaction provides its unlocking data. For a standard P2PKH input, this is a signature and a public key.


Phase 2: Bitcoin Script's interpreter pushes the unlocking data onto the stack, then executes the scriptPubKey opcodes in sequence. Each opcode manipulates the stack. If the final stack state is a single non-zero value, the script is valid and the UTXO can be spent. If not, the transaction is rejected.


The entire execution happens deterministically. There is no runtime environment, no loops, and no external data access. Bitcoin Script is intentionally not Turing-complete. This constraint is a security property, not a limitation: it makes script execution verifiable and its termination guaranteed.




The Stack Machine in Detail: P2PKH as a Worked Example


P2PKH (Pay-to-Public-Key-Hash) is the script type that dominated Bitcoin transactions from 2009 through roughly 2017. Understanding its opcode sequence is the foundation for understanding every more complex script type.


A P2PKH scriptPubKey looks like this:


OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG


The corresponding scriptSig (the unlocking data) is:


<signature> <pubKey>


Execution proceeds through the following stack states:


StepOperationStack
1Push <signature>[sig]
2Push <pubKey>[sig, pubKey]
3OP_DUP[sig, pubKey, pubKey]
4OP_HASH160[sig, pubKey, HASH160(pubKey)]
5Push <pubKeyHash> from script[sig, pubKey, HASH160(pubKey), expectedHash]
6OP_EQUALVERIFY[sig, pubKey] (fails if hashes don't match)
7OP_CHECKSIG[TRUE or FALSE]


OP_EQUALVERIFY is the critical opcode here. It confirms that the public key provided by the spender hashes to the same value embedded in the output script. Only then does OP_CHECKSIG verify the signature against that public key. The two-step check prevents an attacker from substituting a different key that they control.


This design also means the scriptPubKey never stores the actual public key, only its hash. The public key is revealed only at spend time. In Bitcoin's early days, this provided a modest additional layer of security against theoretical quantum attacks on exposed public keys, though the protection is partially eroded once an address is reused (since the public key becomes visible after the first spend).




P2SH: Moving Complexity to the Spender (BIP-16)


P2PKH works cleanly for single-signature payments, but multisignature setups and other complex conditions required long, unwieldy scriptPubKey fields that the payer had to construct. BIP-16, activated in April 2012, introduced P2SH (Pay-to-Script-Hash) to shift that burden.


Under P2SH, the scriptPubKey is simply:


OP_HASH160 <scriptHash> OP_EQUAL


The payer commits to a hash of the spending conditions without needing to know what those conditions actually are. The full redeem script, which might encode a 2-of-3 multisig or any other logic, is provided by the spender at spend time as part of the scriptSig.


This made multisig practical at scale. A business receiving payments could publish a P2SH address encoding a 2-of-3 multisig policy; senders needed only a standard address, not knowledge of the underlying script structure.


P2SH addresses are recognizable by their leading "3" prefix (versus the "1" prefix of P2PKH), a distinction encoded in how the hash is formatted before base58check encoding.




SegWit and the Witness Field: Solving Malleability (BIP-141)


Before SegWit, transaction IDs (txids) were computed over the entire transaction including the scriptSig. This created a known attack surface: transaction malleability. Because certain signature encodings had multiple valid representations, a third party could modify the scriptSig bytes of a transaction without invalidating the signature, changing the txid in the process. This was not merely theoretical: it was exploited in practice and was one of the contributing factors in the Mt. Gox collapse.


BIP-141 (SegWit, activated August 2017) separated witness data from the transaction data used to compute the txid. The scriptSig for SegWit inputs is empty or minimal; the actual unlocking data moves to a separate witness field not covered by the legacy txid commitment.


This introduced two new standard Bitcoin output script types:


P2WPKH (Pay-to-Witness-Public-Key-Hash): The native SegWit equivalent of P2PKH. The scriptPubKey is:


OP_0 <20-byte pubKeyHash>

P2WSH (Pay-to-Witness-Script-Hash): The native SegWit equivalent of P2SH, using a 32-byte SHA256 hash instead of a 20-byte HASH160:


OP_0 <32-byte scriptHash>


The larger hash in P2WSH is significant: SHA256 produces a 256-bit output, closing a theoretical vulnerability in the 160-bit hashes used by P2SH where a collision attack could, in principle, allow a spender to substitute a different redeem script.


Native SegWit addresses use bech32 encoding (the "bc1" prefix), distinguishing them visually from legacy and P2SH addresses.




Taproot and the Key Path / Script Path Distinction (BIP-341, BIP-342)


Taproot, activated at block 709,632 in November 2021, introduced P2TR (Pay-to-Taproot) and changed the output script model in a structurally significant way.


A P2TR scriptPubKey is:


OP_1 <32-byte tweaked pubKey>


The 32-byte value is not a hash of a public key. It is a tweaked public key: a key mathematically derived from an internal key and, optionally, a commitment to a Merkle tree of script conditions (MAST, Merkelized Abstract Syntax Trees).


This enables two distinct spending paths:


Key path: The spender provides a Schnorr signature (BIP-340) for the tweaked public key. From the blockchain's perspective, this looks identical to a single-signature spend regardless of how many parties control the underlying key or how complex the off-chain protocol is. A Lightning channel close, a multisig wallet, and a simple single-key payment are all indistinguishable on-chain when using the key path.


Script path: If a spending condition in the MAST tree is invoked, the spender reveals only the specific script branch being used, a Merkle proof that the branch is part of the committed tree, and the data to satisfy that branch. The other branches remain private. A 100-condition contract that resolves via condition 7 reveals only condition 7.


This is a fundamental change to how complex spending policies interact with on-chain privacy. Under P2SH or P2WSH, the full redeem script is revealed at spend time regardless of which branch executed. Under P2TR script path spending, unused branches are never disclosed. The economic implication is also material: fees depend on the revealed script size, not the total contract complexity.


Tapscript (BIP-342) defines the opcode rules for scripts within the Taproot script path. It deprecates OP_CHECKMULTISIG in favor of OP_CHECKSIGADD, which enables batch validation and eliminates the quirk where OP_CHECKMULTISIG consumed an extra stack element for historical reasons dating to a Satoshi-era implementation choice.




Why Output Script Type Affects Fees, Privacy, and Interoperability


The choice of Bitcoin output script type is not only a protocol-level concern. It has practical consequences for anyone operating on the Bitcoin network.


Transaction fees are proportional to transaction weight in virtual bytes (vbytes). SegWit inputs are discounted in the weight calculation because witness data is given a factor of 0.25 relative to legacy data. A P2WPKH input costs roughly 68 vbytes; a P2PKH input costs roughly 148 vbytes. Sending from a P2WPKH address to another P2WPKH address costs approximately 109 vbytes for a typical single-input, two-output transaction, compared to around 226 vbytes for the equivalent P2PKH transaction.


Privacy varies significantly. P2PKH and P2SH reveal the public key or full redeem script at spend time. P2TR key path spends reveal neither. Taproot key path transactions are homogeneous on-chain: a Schnorr signature and a 32-byte key, regardless of the underlying policy.


Interoperability depends on wallet and exchange support. P2TR adoption has grown but remains incomplete across custodial services. If you are withdrawing bitcoin to a self-custodied Taproot address, verify that the sending platform supports P2TR outputs before initiating the withdrawal.


For those tracking BTC transactions or monitoring network activity around these script types, BYDFi's BTC/USDT spot market provides live order book data alongside network-level metrics.




The Non-Standard Script Category


Bitcoin Core defines a set of "standard" script templates that nodes will relay and miners will include by default. Scripts that do not match these templates are classified as non-standard. They can still appear in blocks (if a miner includes them directly), but they will not propagate through the default peer-to-peer network.


Non-standard scripts enable experimentation: timestamping protocols, colored coin schemes, and various Layer 2 constructions have at different points relied on non-standard output patterns. OP_RETURN outputs, used for embedding arbitrary data in the blockchain with an unspendable output, are technically a standard type added specifically to give data-embedding a sanctioned path that does not bloat the UTXO set.


The UTXO set is Bitcoin's active state. Every spendable output that has ever been created and not yet spent lives there. Non-standard or malformed scripts that are unspendable but were inadvertently added to the UTXO set represent a category of permanent pollution. Pruned nodes can remove spent transaction data, but UTXO set entries cannot be pruned until spent.




Closing: From Script to Protocol Understanding


The Bitcoin output script is the mechanism through which Bitcoin's ownership model is expressed. Every version of that mechanism, from the opcode sequences in a 2009 P2PKH output through the tweaked Schnorr keys of a 2024 Taproot UTXO, reflects specific design tradeoffs between security, privacy, complexity, and fee efficiency. Taproot's script path spending in particular represents a shift from "prove your conditions at spend time" to "commit to your conditions at creation time, reveal only what you use." That change has architectural implications for how covenants, payment channels, and multi-party protocols are constructed at the protocol level.


BYDFi publishes ongoing technical coverage of Bitcoin protocol developments alongside market data, making it a practical resource for tracking how network-level changes translate to real transaction behavior.




FAQ


What is the difference between scriptPubKey and scriptSig?


scriptPubKey is the locking script embedded in a transaction output. It defines the conditions that must be met to spend that output. scriptSig is the unlocking script provided in a spending transaction's input. Bitcoin Script's interpreter executes them together: scriptSig data is pushed onto the stack first, then scriptPubKey opcodes run against it. SegWit moved the unlocking data to a separate witness field, but the conceptual distinction remains the same.


Why does Taproot use a tweaked public key instead of a hash?


A P2TR output commits to a public key tweaked by a hash of a Merkle tree root (or just an internal key if no script tree exists). This construction, defined in BIP-341, allows the key path and script path to coexist in a single 32-byte value. A hash of a public key, as used in P2PKH, cannot encode script tree commitments. The tweak formula is: outputKey = internalKey + hash(internalKey || merkleRoot) * G, using elliptic curve math over secp256k1.


Can a Bitcoin output script be modified after the output is created?


No. The scriptPubKey is finalized when a transaction is included in a block. The only way to change spending conditions is to spend the UTXO (satisfying the original script) and create a new output with a different scriptPubKey. This immutability is fundamental to Bitcoin's security model.


What makes a script "non-standard" in Bitcoin Core?


Bitcoin Core nodes relay and mine transactions whose inputs and outputs match a defined list of standard script templates: P2PKH, P2SH, P2WPKH, P2WSH, P2TR, and OP_RETURN (for data outputs). Any script structure outside these templates is non-standard. Non-standard transactions can still be included in blocks by miners directly, but they will not be forwarded by default nodes, making confirmation less reliable without direct miner communication.


How does OP_CHECKSIGADD in Tapscript differ from OP_CHECKMULTISIG?


OP_CHECKMULTISIG (legacy) takes N signatures and M public keys, verifies signatures against keys, and uses one additional stack element due to a historical off-by-one implementation bug patched by requiring a dummy OP_0 value. OP_CHECKSIGADD (BIP-342) checks a single signature against a single key and adds 1 or 0 to a running counter on the stack. Multisig is constructed by chaining multiple OP_CHECKSIGADD calls. This enables batch Schnorr signature validation and eliminates the dummy element quirk entirely.



This article is for educational purposes only and does not constitute financial advice.



0 Answer

    Create Answer