What is Bitcoin Script?
Learn what Bitcoin Script is, how it validates spends, and why its limited design enables multisig, timelocks, SegWit, Taproot, and Lightning.

Introduction
Bitcoin Script is the rule system Bitcoin uses to decide whether a coin can be spent. Every spend is a claim (I am allowed to move this output) and Script is the mechanism nodes use to check that claim without trusting the spender, the wallet, or the miner.
That design creates an immediate tension. If Bitcoin had no scripting system at all, it could only support the simplest ownership model: perhaps “whoever knows this key may spend.” But if it had a fully general programming language with loops, mutable state, and arbitrary computation, every node on the network would need to run potentially dangerous or unpredictable programs just to verify blocks. Bitcoin Script sits in the narrow space between those extremes. It is programmable enough to express useful spending conditions, but constrained enough that validation stays bounded and consensus remains tractable.
The key to understanding Script is this: **Bitcoin does not store account permissions; it stores challenge conditions on outputs. ** A transaction output is not “Alice’s balance” in a database. It is a small predicate attached to a coin, and that predicate says what evidence must appear later for the coin to move. When the coin is spent, the spender supplies data (signatures, public keys, hashes, preimages, scripts, witness elements) and every node executes the relevant Script to see whether the predicate evaluates to true.
That simple idea explains a surprising amount of Bitcoin’s behavior. It explains why ownership is really the ability to satisfy a condition, why multisig and timelocks are native features rather than add-ons, why Lightning can rely on on-chain enforcement, and why Bitcoin’s scripting language is much more limited than what people usually mean by “smart contracts.”
How does Bitcoin Script execute and validate a spend?
Bitcoin Script is a stack-based, Forth-like language. It is evaluated from left to right, operating mainly on a stack of byte strings. Opcodes push data, duplicate values, hash data, compare values, check signatures, and control simple conditional branches. The interpreter ultimately cares about one thing: whether execution completes without failure and leaves a truthy value on top of the stack.
This is a different model from a general-purpose virtual machine. Script has no loops, and in normal use it does not maintain long-lived mutable state inside the language itself. That is not an omission to be fixed later; it is part of the design. Consensus software has to be boring in the best sense: deterministic, bounded, and hard to abuse. If a block could force all nodes to run arbitrary unbounded programs, denial-of-service and implementation divergence would become much more dangerous.
The stack model matters because it keeps the language close to a predicate calculus rather than an application platform. A script is best thought of as a verifier. It does not do business logic in the Ethereum sense. It checks whether provided evidence matches a spending condition. If the evidence matches, the spend is valid. If not, it fails.
At a high level, older Bitcoin outputs are spent by combining two pieces: data from the spending input and conditions from the output being spent. Informally, the spender brings the proof, and the old output provides the lock. The interpreter executes them together. In native SegWit and Taproot forms, some of this data moves into the witness rather than the older scriptSig field, but the underlying idea remains the same: there is a spending condition attached to the coin, and later there is witness data intended to satisfy it.
How is a standard P2PKH payment validated on Bitcoin?
The easiest nontrivial example is the familiar public-key-hash payment pattern. Suppose an output says, in effect: to spend this coin, provide a public key whose hash matches this committed hash, and provide a valid signature under that public key for this transaction. That is the logic behind the classic pay-to-public-key-hash style.
Now imagine Bob wants to spend that output. His transaction input provides two pieces of data: a signature and a public key. During execution, Script duplicates the public key, hashes it, compares the result to the hash committed in the old output, and fails if they differ. If they match, Script proceeds to signature verification. The signature check asks a narrower question than many newcomers expect: not “is Bob a known account owner?” but “does this signature verify against this public key for the transaction digest required by the signing rules?” If yes, the stack ends in a true value and the spend is valid.
What is important here is the direction of proof. The blockchain never needed to know Bob in advance. It did not store an account row for him. The output simply committed to a condition, and Bob later supplied satisfying evidence. **Ownership in Bitcoin is therefore conditional control over spendable outputs, not an account entry maintained by the chain. **
This way of thinking also explains why receiving funds and spending funds are asymmetric. Receiving can be simple because the sender only needs a compact commitment; often derived from a public key hash, a script hash, or a witness program. Spending is where complexity appears, because the spender must reveal enough data for every validating node to check the condition.
Why is Bitcoin Script deliberately limited instead of Turing-complete?
A smart reader often asks: if Script is programmable, why not make it more expressive? Why no loops, no rich storage, no unrestricted arithmetic, no direct external calls?
Here the core constraint is consensus. In a blockchain, the validator is not one machine under one operator. It is a global set of independently implemented nodes that must all reach the same result. The more complex the execution environment, the more room there is for implementation bugs, resource exhaustion, and edge-case disagreement. Bitcoin’s design leans toward minimizing those surfaces.
That is why Script has tight limits. Bitcoin Core defines constants such as a maximum pushed element size of 520 bytes, a maximum script size of 10,000 bytes in pre-Tapscript contexts, a maximum of 201 non-push opcodes per script in those same contexts, and a maximum interpreter stack size of 1,000 elements. These are not aesthetic choices. They are resource bounds intended to keep script evaluation predictable and resistant to abuse.
The language is also historically conservative. Some opcodes exist only as disabled relics because they were considered unsafe. Others were added or redefined later by soft fork in narrowly controlled ways, often by reusing OP_NOP slots or, in Taproot’s script system, via OP_SUCCESSx upgrade points. The pattern is consistent: Bitcoin expands scripting capability slowly, under bounded semantics, rather than turning Script into a broad computation engine.
The consequence is important. Bitcoin Script is less expressive than virtual machines built for general-purpose smart contracts, but that is precisely why it can serve as a robust, low-level adjudication layer for money. **The language is not trying to compute everything. It is trying to settle disputes about who may spend a coin, under which cryptographic and temporal conditions, with minimal ambiguity. **
Consensus vs. policy: which Script rules affect block validity and which affect relay?
| Rule type | Enforced by | Determines | Who decides | Common example |
|---|---|---|---|---|
| Consensus | All full validating nodes | Block validity | Network-wide consensus | Signature encoding |
| Policy | Individual nodes/operators | Mempool relay behavior | Node operator configuration | Standardness and dust rules |
To understand Script clearly, you have to separate consensus from policy.
Consensus rules are the rules every fully validating node must enforce to agree on whether a block is valid. If a transaction violates consensus, it cannot be mined into a valid block. Policy rules are local choices made by nodes about what they will relay or accept into their mempool before confirmation. A transaction can be consensus-valid but non-standard under default policy, meaning miners could include it directly in a block even if many nodes would not relay it by default.
This distinction matters because many practical statements about Script are really policy statements. For example, standardness checks constrain script forms, scriptSig structure, witness sizes, dust handling, and some undefined future-extension features. Bitcoin Core’s policy code explicitly notes that it implements local node policy, not consensus. By contrast, rules like signature encoding in certain contexts, interpreter failure conditions, or activated opcode semantics are consensus-critical.
A good mental model is that consensus defines the legal language of final settlement, while policy defines the default etiquette for unconfirmed traffic. If you mix them together, Script seems arbitrary. If you separate them, its behavior becomes much easier to reason about.
How does P2SH let you hide complex spending conditions behind a hash?
| Address type | Commitment stored | Revealed at spend | Max redeem size | Who supplies script |
|---|---|---|---|---|
| P2PKH | 20‑byte pubkey hash | Pubkey + signature | Small (single pubkey) | Spender provides pubkey |
| P2SH | 20‑byte script hash | Serialized redeemScript | Bound by 520‑byte push limit | Spender provides redeemScript |
| P2WSH | 32‑byte witness script hash | Witness script in witness stack | Up to 10,000 bytes (witness) | Spender provides witness script |
Early on, a practical problem appeared. Complex spending policies were possible in Script, but making senders construct them directly was awkward. A sender might need to understand a long multisig or escrow script just to pay someone. That pushed complexity onto the wrong party.
Pay-to-Script-Hash, or P2SH, changed this by letting the sender pay to the hash of a script instead of to the script itself. The canonical output form is OP_HASH160 <20-byte-hash> OP_EQUAL. The sender only commits to a 20-byte hash. The receiver (more precisely, the future spender) later provides the full serialized redeemScript and the data needed to satisfy it.
That shift is subtle but powerful. It moves responsibility for revealing and satisfying the actual conditions from the payer to the redeemer. User interfaces become simpler because a complex policy can be represented by a compact address-like commitment. The chain still enforces the real logic, but only at spend time.
Mechanically, a P2SH spend works in three stages after activation of the P2SH rules. First, the input-side script must contain only push-data operations. Second, the provided serialized script is hashed and compared to the hash committed in the output. Third, if the hashes match, that serialized script is executed as the effective locking condition using the remaining stack items as inputs.
This is the first place many readers see that Bitcoin Script has layers of indirection. A coin can commit not to a public key or pubkey hash, but to an entire hidden script. That made multisig and more elaborate spending policies much more usable. But it also inherited limits from Script’s push-data model. Because pushed elements are capped at 520 bytes, a P2SH redeem script cannot exceed that size when pushed for redemption, which in practice constrains how large a P2SH-embedded multisig can be.
How do timelocks make Bitcoin spends depend on time (CLTV)?
Simple signature checks answer who may spend. Timelocks add when may they spend.
Bitcoin has two major timelock ideas, and the distinction matters. Absolute timelocks refer to a specific block height or timestamp. Relative timelocks refer to how much time or how many blocks must pass after the output being spent was confirmed. The first is anchored to the chain’s global clock. The second starts counting from the age of a specific coin.
[OP_CHECKLOCKTIMEVERIFY](https://bips.dev/65), or CLTV, exposes absolute timelocks to Script. It lets an output be made unspendable until some future block height or time. The opcode checks that the locktime provided in the script is compatible with the transaction’s nLockTime, that the transaction is not finalized in a way that disables the check for that input, and that the transaction’s locktime has reached at least the required value. If those conditions hold, execution continues; otherwise the script fails.
The underlying mechanism is worth noticing. CLTV does not create a separate clock inside Script. It verifies consistency between the script’s claimed minimum time and the transaction-level locktime field. That is typical of Bitcoin design: Script often works by constraining or exposing existing transaction fields rather than inventing an entirely separate execution environment.
This enables patterns that would otherwise require awkward interaction. An escrow can say: before some date, spend requires one branch of conditions; after that date, a refund branch becomes available. Refund transactions no longer need fragile pre-signing tricks in the same way earlier constructions did. Absolute timelocks are especially useful when the relevant deadline is tied to a calendar-like event.
Why do Lightning channels require relative timelocks (CSV) in addition to CLTV?
Absolute timelocks are not always enough. In payment channels, what matters is often not the date on the wall but how long after this output confirms someone has to react. That is a relative notion.
Bitcoin introduced consensus-enforced relative lock-time semantics through the transaction input sequence number in BIP 68, and exposed them to Script through [OP_CHECKSEQUENCEVERIFY](https://bips.dev/112), or CSV, in BIP 112. Together, they let a script branch say, in effect, this path is only valid once the output being spent has reached at least this age.
The details matter because sequence numbers are overloaded. Under BIP 68, for transactions with version at least 2, if bit 31 of an input’s sequence number is unset, the sequence field can encode a relative lock-time. Bit 22 chooses units: unset means blocks; set means time in 512-second units. Only the low 16 bits carry the actual relative value. CSV checks that the script operand is nonnegative and compatible with the input sequence semantics, that the transaction version supports the mechanism, that the disable flag is not defeating the meaning, that the type matches, and that the required relative age is not greater than what the input sequence commits to.
Conceptually, CSV turns output age into a spend condition. That is exactly what channel protocols need. If a revoked or stale state is broadcast, the honest party needs a guaranteed reaction window that begins when that transaction confirms, not at some pre-chosen global deadline that may already be near expiry. This is why relative timelocks are foundational for Lightning-style penalty and timeout designs.
In Lightning’s on-chain scripts, this appears directly. The to_local output in a commitment transaction is a delayed P2WSH script with two branches: a revocation branch that can be spent immediately by the counterparty if the state is revoked, and a delayed branch where the owner can spend only after to_self_delay enforced by CSV. The mechanism is not decorative. It is the enforcement anchor that makes off-chain promises credibly punishable on-chain.
How did SegWit change witness data and Script spending (P2WPKH/P2WSH)?
Script existed before SegWit, but Segregated Witness changed how many Script spends are represented and validated in practice.
SegWit separates witness data (notably signatures and scripts needed only for validity) from the part of the transaction that determines its effects. This creates a new witness structure, a new witness-inclusive identifier wtxid, and a witness-program mechanism for new output types. For Script, the important consequence is that spending data can live in the witness rather than in the legacy scriptSig, and versioned witness programs can define new validation semantics cleanly.
This solved a major practical issue: nonintentional transaction malleability. Before SegWit, changing aspects of signature encoding could change the txid without changing the transaction’s economic effect. That created operational hazards, especially for protocols that chained unconfirmed transactions. SegWit removes signature data from the legacy transaction hash, making these identifier changes far less problematic and enabling more reliable contract constructions, including Lightning’s dependency chains.
SegWit also introduced two important script-related output forms for version 0 witness programs. A 20-byte program is P2WPKH, analogous in purpose to pubkey-hash payments. A 32-byte program is P2WSH, which commits to the SHA256 hash of a full witness script and allows scripts up to 10,000 bytes in that witnessScript. That is a major practical improvement over P2SH’s 520-byte redeem-script push bottleneck.
The spending pattern echoes P2SH in spirit but with a different serialization context. In P2WSH, the witness stack ends with the actual script, and the program commits to its hash. The script remains hidden until spend time, but now it is delivered through witness data rather than the older scriptSig path.
SegWit also changed signature hashing for version 0 witness programs through BIP 143. The digest algorithm now commits to the input amount and reuses transaction-wide hashes such as hashPrevouts, hashSequence, and hashOutputs, reducing hashing overhead from quadratic to linear in important cases. That is not merely an optimization. Including the amount improves offline signing safety, and the hashing redesign reduced a class of performance problems in verification.
What do Taproot and Tapscript change about spending paths and signatures?
| Spend path | On-chain reveal | Signature type | Privacy | When used |
|---|---|---|---|---|
| Key path | No script revealed | Schnorr single-sig | High (looks like single key) | Cooperative/co-signed closes |
| Script path | Reveal leaf + Merkle proof | Schnorr/Tapscript-validated | Lower (reveals branch) | Non-cooperative fallback or disputes |
Taproot is best seen as a redesign of how complex spending policies are committed to and revealed, not as a sudden turn toward arbitrary smart contracts.
A Taproot output is a native SegWit v1 output with a 32-byte witness program. It can be spent along two paths. In the key path, a Schnorr signature authorizing the tweaked output key is enough. In the script path, the spender reveals a script leaf, the control data proving that leaf belongs to the committed Merkle tree, and the witness elements satisfying that script.
The conceptual win is that cooperative cases can look simple on-chain. If all participants can agree on a key-path spend, the chain sees what looks like a single-key spend even if the internal policy was more complex. Only when non-cooperative resolution is needed does a script path get revealed. This improves privacy and efficiency because unused branches stay hidden.
Taproot depends on Schnorr signatures standardized in BIP 340. Schnorr matters here because it is non-malleable in the relevant sense, supports linear constructions useful for key aggregation, and enables efficient batch verification. Bitcoin uses 32-byte x-only public keys and 64-byte signatures under this scheme, with key-prefixing in the challenge hash to resist related-key attacks.
Taproot’s script system, Tapscript, then updates the opcode environment. OP_CHECKSIG and OP_CHECKSIGVERIFY verify Schnorr signatures using the Taproot signature message rules. Legacy OP_CHECKMULTISIG and OP_CHECKMULTISIGVERIFY are disabled in Tapscript, and OP_CHECKSIGADD is introduced as a more batch-friendly primitive for expressing multisignature policies. Tapscript also changes resource accounting: instead of relying on the older block-wide sigop approach in the same way, it uses a per-script sigops budget tied to witness size.
There are also new upgrade hooks and new sharp edges. Encountering OP_SUCCESSx makes a tapscript unconditionally valid, which is useful as a soft-fork extension mechanism but unsafe to use before semantics are defined. Tapscript enforces MINIMALIF at consensus, reducing ambiguity in conditional execution. And unknown public key types are treated in a forward-compatible way that current users should understand carefully, because upgradeability often means some values are reserved rather than fully rejected.
What kinds of contracts and patterns are practical with Bitcoin Script?
At this point Script can seem like a bag of opcodes. It helps to reframe it around what it can guarantee.
Script is good at enforcing conditions that are local, cryptographic, and finitely checkable by every node. Signature possession is local and checkable. Hash preimage revelation is local and checkable. Absolute and relative timelocks are local and checkable because they refer to transaction fields and chain context nodes already validate. Merkle inclusion for Taproot script leaves is local and checkable. These properties are why Script supports multisig vault-like setups, escrows, inheritance-style delays, refund paths, HTLCs, and channel enforcement.
Take a hashed timelock contract idea as a narrative example. A script can say: if you know a 32-byte secret preimage that hashes to this committed value, you may claim the output with your signature; otherwise, after some timeout, the sender may reclaim it with a different signature path. Nothing about this requires a general-purpose VM. The whole contract is just a conjunction of checks nodes already know how to perform: hash equality, branch selection, signature verification, and timelock enforcement. That is enough to build atomic-style transfer logic and, in Lightning, HTLC settlement and timeout behavior.
What Script is not good at is open-ended computation over rich application state. There is no native model of contracts storing evolving variables and exposing arbitrary methods. If you want repeated interaction, you usually represent state transitions with new transactions and outputs, often negotiated off-chain and enforced on-chain only in dispute or settlement cases. Bitcoin’s contract model is thus closer to state encoded in coins and transitions than to objects with mutable storage.
Common misunderstandings about Bitcoin Script, addresses, and relay vs consensus
The most common misunderstanding is to compare Script directly to a general smart-contract language and conclude that Bitcoin “has no smart contracts” or “has weak smart contracts.” Both are misleading. Bitcoin absolutely has programmable contracts in the sense that coins can be locked under expressive, composable spending conditions. But the design goal is narrower: enforceable predicates for money movement, not a universal application runtime.
A second confusion is to treat an address as the thing being owned. In reality, addresses are convenient encodings of commitments to spending conditions. Sometimes that commitment is to a key hash, sometimes to a script hash, sometimes to a witness program, sometimes to a Taproot output key. The underlying object of enforcement is always the output script condition.
A third confusion is to miss the difference between a script being valid in consensus and being relayed under default node policy. This matters for dust, nonstandard forms, witness annex handling, scriptSig shape, and other practical edge cases. If you are building with Script, you need both layers in your head.
Conclusion
Bitcoin Script is best understood as a deliberately small language for proving that a coin may move. It is stack-based, bounded, and not Turing-complete because its job is not to host arbitrary programs; its job is to let every node verify spending conditions safely and deterministically.
Once that clicks, the rest follows. P2SH hides complex conditions behind a hash until spend time. CLTV and CSV make those conditions depend on absolute or relative time. SegWit moves witness data out of the legacy transaction hash and makes advanced scripts more practical. Taproot lets cooperative policies look simple while keeping richer fallback scripts available when needed. Across all of it, the invariant stays the same: **a Bitcoin output is a locked condition, and spending is the act of presenting evidence that unlocks it. **
How do you buy Bitcoin?
Buy or trade Bitcoin on Cube Exchange to get practical exposure after studying Script. Fund your Cube account with fiat or a crypto transfer, then use the BTC Market to take a position or accumulate holdings while choosing the execution style that fits your needs.
- Fund your Cube account: use the fiat on‑ramp or deposit BTC (choose Bitcoin mainnet) so the deposit address and network match.
- Open the BTC/USD or BTC/USDC market on Cube and choose an order type: Market for immediate execution or Limit to control price.
- Enter the BTC amount or fiat/USDC spend, review estimated fill, fees, and slippage, then submit the order.
- If you need the coins off‑exchange, withdraw to your Bitcoin address after confirmations; verify the address and network and wait 3–6 confirmations for external transfers.
Frequently Asked Questions
- Why isn’t Bitcoin Script Turing-complete — why are loops and general storage disallowed? +
- Because Bitcoin nodes must deterministically and safely validate every block, Script intentionally omits unbounded constructs like loops and general mutable storage so execution is bounded, deterministic, and hard to abuse; its goal is to express spend-verifying predicates, not serve as a general VM.
- How does Pay-to-Script-Hash (P2SH) make complex scripts more usable, and what limitations does it introduce? +
- P2SH lets senders pay to a 20-byte hash instead of embedding a full script, shifting the burden of revealing the actual spending conditions to the redeemer and making complex policies usable, but the redeemScript must be pushed at spend time and is subject to the 520-byte pushed-element limit which constrains how large an embedded script (e.g., big multisig) can be.
- What is the practical difference between CHECKLOCKTIMEVERIFY (CLTV) and CHECKSEQUENCEVERIFY (CSV), and when should each be used? +
- CLTV enforces absolute lock-times tied to a transaction’s nLockTime (a specific block height or timestamp), while CSV enforces relative lock-times tied to the age of the output being spent via the input sequence number; use CLTV for calendar-like deadlines and CSV when you need a guaranteed reaction window that starts when the output confirms (e.g., Lightning channel timeouts).
- How did SegWit change Script spending and why did it matter for transaction malleability and contract design? +
- Segregated Witness moved signatures and other spending data into a separate witness structure, removing them from the legacy txid (fixing malleability), introduced P2WPKH and P2WSH witness programs (with P2WSH allowing up to 10,000-byte witness scripts), and changed the sighash algorithm for v0 witness programs to include amounts and use transaction-wide hashes for efficiency and safety.
- What practical benefits does Taproot (and Tapscript) give compared with older script forms? +
- Taproot commits many alternative script branches or aggregated keys in a single 32‑byte witness program so cooperative spends can use a single-key Schnorr signature (key path) while non-cooperative resolution reveals only the needed script leaf and its Merkle proof (script path), improving privacy and efficiency by hiding unused branches until they are actually used.
- Why did Taproot adopt Schnorr signatures, and are there new security risks to watch for? +
- Schnorr signatures (used by Taproot) are non‑malleable in the relevant sense, enable linear constructions useful for key aggregation and efficient batch verification, but they require careful nonce generation (or two‑party protocols) because reused or biased nonces can break security.
- What are the concrete resource limits in Script (script size, push size, stack, opcodes) and are those limits consensus rules or just node policy? +
- Some script limits (for example the 520‑byte maximum pushed element, the ~10,000‑byte pre‑Tapscript max script size, the 201 non‑push opcode cap, and a 1,000‑element stack limit) exist to bound resource usage, but whether a particular bound is enforced as consensus versus as relay/mempool policy can be subtle and implementers must consult the consensus rules versus Bitcoin Core’s policy code.
- What kinds of contracts is Bitcoin Script well suited for, and what kinds is it poor at expressing? +
- Bitcoin Script expresses conditions that are local and finitely checkable—signature checks, hash preimages, absolute and relative timelocks, and Merkle inclusion—which lets you build HTLCs, multisig vaults, refunds and Lightning channels, but it is not designed to encode contracts with on‑chain mutable state or arbitrary application logic; repeated interaction is modeled as new transactions and outputs rather than in‑contract storage.
- What’s the difference between consensus rules and policy rules when it comes to Script validity and relay? +
- The consensus/policy distinction means a transaction can be fully consensus‑valid yet considered non‑standard by default relaying nodes (so it might not propagate through the mempool); consensus defines what must be accepted in a block, while policy governs what a node will relay or mine by default.
- Mechanically, how does a Taproot spend choose between the key-path and script-path, and what data is revealed in each case? +
- A Taproot output can be spent either by producing a valid Schnorr signature for the tweaked output key (key path) or by revealing a script leaf plus its Merkle control data and satisfying that script via the witness stack (script path); cooperative flows typically use the compact key path while disagreements fall back to the script path which reveals only the used leaf.