What Is Bitcoin Script? A Complete Technical Guide
By Kurt Wuckert Jr.
Bitcoin Script: The Language That Locks and Unlocks Bitcoin
When people think about bitcoin, they usually picture price charts and coins changing hands. Under the hood, though, bitcoin is really a global log of small computer programs that say who can spend what, under which conditions. Those little programs are written in a tiny, purpose built language called Bitcoin Script.
To understand Script, it helps to see where the idea came from and how it fits into the transaction format on the bitcoin ledger.
From FORTH to Bitcoin Script
In the late 1960s, Charles “Chuck” Moore created a language called FORTH. It was designed to run directly on small hardware, where memory was tight and you needed to interact with devices in real time. The programmer would type commands, define new words, test them, redefine them and keep going without recompiling a big project. Everything was “live.”
FORTH is a stack based language that uses Reverse Polish Notation (RPN). In RPN:
You put the data on the stack first.
Then you call the word (function) that operates on that data.
So instead of writing 2 * 3, you write 2 3 *. The * word pops the top two numbers off the stack, multiplies them, and pushes back the result 6. If there are not enough items on the stack, or they are not the right type, the operation fails.
Data in FORTH is basically raw bytes. A word can choose to treat those bytes as an integer, a string, or something else. The language builds complex behavior by composing simple primitive words, which is a nice fit for tiny systems and embedded use.
Bitcoin Script is a direct descendant of FORTH. It keeps the stack based model and simple opcodes, but trims away features that would make validation difficult, such as arbitrary loops and backward jumps. That design makes it predictable and safe for a global financial system.
Locking and Unlocking: How Script Appears in Transactions
Every transaction output on the bitcoin ledger contains a predicate, called a locking script (often written as scriptPubKey). This script describes the conditions that must be met in order to spend that output.
When someone spends that output later, their transaction input supplies an unlocking script (often called scriptSig). At validation time, the node:
Loads the unlocking script’s data onto the stack.
Appends the locking script.
Evaluates the combined script from start to finish.
If the script runs to completion and leaves exactly one non zero value on the main stack, then the spend condition is satisfied and that input is considered valid.
Bitcoin Script only allows opcodes that are part of the defined protocol. A script evaluation stops if:
An
OP_RETURNis executed.A
VERIFYstyle opcode sees a false value.The unlocking script is malformed.
The end of the script is reached.
A policy limit is violated, such as stack size or memory use.
Crucially, Script cannot jump backward. There are no loops that go around forever. For a transaction input to be valid, its script must run straight through until it ends under the rules, without halting early in an invalid state.
The Bitcoin Transaction Format
Script lives inside a structured container: the transaction. The bitcoin protocol defines a precise serialization format that all nodes follow. Conceptually, you can think of it like a simple protobuf: fields of known types, concatenated into a single byte string.
A transaction contains:
Version (4 bytes)
Number of inputs (a VarInt)
A list of inputs, where each input has:
Previous transaction ID (TXID), 32 byte little endian hash
Output index from that TXID (4 byte little endian integer)
Length of the unlocking script (VarInt)
The unlocking script (
scriptSig)nSequence(4 byte little endian integer)
Number of outputs (VarInt)
A list of outputs, where each output has:
Satoshi value (8 byte little endian integer)
Length of the locking script (VarInt)
The locking script (
scriptPubKey)
The transaction’s nLockTime (4 bytes)
All of this is serialized as a single byte vector. The transaction ID (TXID) is derived by hashing this serialization.
Transaction Breakdown: The Big Pieces
Version
The first 4 bytes are the version, in little endian:
01000000
This means version 1. Over time, this field can signal different transaction template rules or features. Nodes can interpret the data accordingly.
Number of Inputs
Next comes a VarInt that tells you how many inputs the transaction has:
01
Here there is a single input. VarInts allow this to scale up, but the general idea is simple: read the count, then read that many inputs.
The Input: Referencing and Unlocking a Previous Output
Each input points to a previous unspent output and provides the data to unlock it. The core fields are:
Previous TXID
Output index
Unlocking script (
scriptSig) and its lengthnSequence
Previous TXID
The TXID is stored in little endian. When you see something like:
c997a5e56e10...7fcd3704
that is the reversed byte order of the TXID as you might see it in a block explorer. It simply says, “This input spends output X from that transaction.”
Output Index
The next 4 bytes, such as:
00000000
tell you which output of that earlier transaction is being spent. This one references index 0, the “zeroth” output.
The output at that index contains the locking script that must be satisfied by this input. For example, an early style locking script might look like:
<public key> OP_CHECKSIG
This is a “Pay to Public Key” script. It says, “Only whoever can produce a valid ECDSA signature with the private key corresponding to this public key can spend these satoshis.”
Length of the Unlocking Script
Then we see a VarInt representing the script length:
48
Here 0x48 means 72 bytes in decimal. That is how long the unlocking script is.
Unlocking Script (scriptSig)
A simple unlocking script for a P2PK style output is often just one push: a DER encoded ECDSA signature plus a 1 byte SIGHASH flag.
For example:
47 30440220... (signature bytes) ...01
0x47is a pushdata opcode that says, “Push the next 71 bytes onto the stack.”Those bytes hold the signature and flag.
At evaluation time, the node loads this signature onto the stack first. Then it appends the locking script <public key> OP_CHECKSIG. OP_CHECKSIG pops the signature and public key, checks the signature against the hashed transaction data, and pushes True or False.
If it pushes True and all other conditions pass, the input is valid.
nSequence
Finally, each input has an nSequence value:
ffffffff
0xFFFFFFFF (UINT_MAX) means “final.” If the sequence is less than this, and nLockTime in the transaction is non zero, then the transaction can be treated as non final and updated or used in a payment channel pattern. When sequence is maxed out, the transaction is considered final regardless of nLockTime.
Outputs and VarInts
After the inputs list, the transaction specifies the number of outputs as a VarInt, such as:
02
This means there are two outputs. The VarInt format allows up to a very large number of outputs, but in practice only the first 4.3 billion or so can be referenced again as inputs, because the index is 4 bytes.
Output Structure
Each output has:
Value: an 8 byte little endian integer holding the number of satoshis.
Length of the locking script.
The locking script itself.
For example, an output might hold:
Value representing
1,000,000,000satoshis (10 bitcoin).A 67 byte locking script.
A script of the form:
<Hal Finney's public key> OP_CHECKSIG
That script says, “These 10 bitcoin are controlled by whoever can produce a valid signature for this public key.”
nLockTime and Payment Channels
At the end of the transaction is nLockTime:
00000000
nLockTime is used alongside nSequence to structure payment channels and non final transactions.
If
nLockTimeis in the future and at least one input hasnSequenceless thanUINT_MAX, the transaction is non final until that time or block height is reached.If
nLockTimeis zero, or all inputs havenSequence = UINT_MAX, the transaction is final.
Payment channels use this combination to create transactions that can be updated off chain and only settled on chain when needed, with nLockTime acting as a timeout.
The Script Evaluator Inside a Node
Every full node runs a script evaluator as part of its transaction validation logic.
When a node receives a transaction, it:
Checks basic properties such as size, input values, and output values.
Deconstructs the transaction into inputs and outputs.
For each input:
Locates the referenced previous output by TXID and index.
Retrieves that output’s locking script.
Appends the input’s unlocking script and the locking script together, inserting an
OP_CODESEPARATORat the boundary for signing semantics.Evaluates the combined script according to the protocol rules.
Every node must evaluate scripts in the same way. If one node interprets a script differently from others, they could disagree on whether a transaction is valid, which would cause a chain fork. So the behavior of each opcode and the grammar of Script are carefully defined.
A transaction is only valid if every input’s script evaluation finishes with exactly one non zero item on the main stack.
Data Types, Booleans, and Numeric Rules
In Bitcoin Script, everything on the stack is a byte sequence. How those bytes are interpreted depends on which opcode is reading them.
A byte sequence has:
A length, which must be between 0 and
2^32 - 1.A value, which may be interpreted as:
Raw data,
A boolean, or
An integer encoded in little endian.
When treated as a number:
The most significant bit is the sign bit.
0 means positive.
1 means negative.
The magnitude is the same regardless of sign.
Certain hex values like 0x80, 0x0080 or 0x00000080 are treated as “negative zero.” If a script finishes with negative zero as its only stack item, it fails, even though that is a valid byte sequence.
When treated as a boolean:
If the item is empty, or is all zeros (including negative zero), it is false.
Anything else is true.
Some opcodes require that their inputs be valid numeric values, and some can emit results that are not considered valid numeric values, such as very large products. Nodes treat any byte sequence up to 750,000 bytes as a valid numeric candidate for arithmetic, but there is a specific rule:
Numeric Value Size Rule
For a byte sequence to be a valid numeric value for numeric opcodes, its length must be at most 750,000 bytes. Longer sequences are still valid data but cannot be processed as integers by numeric opcodes.
Script Grammar and Validity Rules
Bitcoin Script has a formal grammar, which defines:
Which opcodes exist.
How they are spelled.
How they can be combined.
Script Components
A full unlocking plus locking script looks like:
unlocking script (from the spending input, often
scriptSig).locking script (from the UTXO being spent, often
scriptPubKey).
The locking script lives in the script field of the output. The unlocking script lives in the input that spends it.
Unlocking Script Opcode Rule
Consensus rules restrict unlocking scripts more tightly than locking scripts. An unlocking script can only contain:
Constant pushing opcodes (like
OP_1,OP_2, etc.)PUSHDATA style opcodes that push raw data onto the stack.
No other opcodes are allowed in an unlocking script. If an unlocking script contains something else, the transaction is invalid and must be rejected.
Disabled Opcodes
The protocol includes opcodes that are formally part of the grammar but are currently disabled. If a script tries to execute them, it fails. These include:
OP_2MULOP_2DIVOP_VEROP_VERIFOP_VERNOTIF
The behavior they provided can be replicated with combinations of other opcodes. For example:
OP_2 OP_MULreplacesOP_2MUL.OP_2 OP_DIVreplacesOP_2DIV.
Their numeric codes are still reserved, but they are not allowed in execution.
Clean Stack Rule
The Clean Stack Rule says that at the end of script execution:
Exactly one item must remain on the main stack.
That item must be non zero.
The altstack must be empty.
If there is more than one item, or the last item is zero, the script fails. This rule was introduced in node software after bitcoin’s launch as a policy and may not be a permanent protocol requirement, but it is enforced by common implementations.
The Main Stack and the Altstack
Bitcoin Script provides two stacks:
Main stack
Altstack
All normal opcodes read from and write to the main stack. For example, OP_ADD takes the top two items from the main stack, adds them, and pushes back the result.
There are various opcodes that manipulate the main stack:
Duplicate items.
Swap items.
Rotate or drop items.
These let you rearrange data and prepare the stack for the next operation.
There is effectively no hard protocol limit on how large a single stack item can be, up to the 4.3 GB limit implied by the largest PUSHDATA opcode, but transaction and script size policies apply. You can also push multiple large chunks and concatenate them in Script if you want to hash or timestamp very large data.
The Altstack
The altstack is a second, First In Last Out stack that is mostly used as scratch space. You can:
Move the top item from the main stack to the altstack with
OP_TOALTSTACK.Move the top item from the altstack back to the main stack with
OP_FROMALTSTACK.
This is handy for:
Saving counters.
Keeping track of parameters.
Simplifying complex scripts that need temporary storage.
The same memory and policy rules apply. Under the Clean Stack Rule, the altstack must be empty when the script finishes.
Constant Value and PUSHDATA Opcodes
There are two broad categories of opcodes that push data onto the main stack:
Constant value opcodes, which push small fixed integers.
PUSHDATA opcodes, which push arbitrary data of specified lengths.
Constant Value Opcodes
There are 18 constant opcodes:
OP_0orOP_FALSEpushes an empty byte array (null item).OP_1NEGATEpushes-1.OP_1orOP_TRUEpushes1.OP_2throughOP_16push the integer values 2 through 16.
These are small convenience opcodes. For example, in a multisignature script, you might see:
OP_2 <pubkey_1> <pubkey_2> <pubkey_3> OP_3 OP_CHECKMULTISIG
This describes a “2 of 3” multisig, where:
OP_2sets the requirement: 2 signatures are needed.OP_3says there are 3 public keys in the list.
PUSHDATA Opcodes
PUSHDATA opcodes take their parameters from the script bytes themselves, rather than from the stack. They tell the interpreter how many bytes to read next and push them as a data item.
The main variants are:
For data up to 75 bytes: the opcode value itself is the length.
Example:
0x14means “push the next 20 bytes.”
OP_PUSHDATA1: the next 1 byte specifies the length (up to 255).OP_PUSHDATA2: the next 2 bytes (little endian) specify the length (up to 65,535).OP_PUSHDATA4: the next 4 bytes (little endian) specify the length (up to about 4.3 GB).
Example small push:
0x48 <signature> 0x20 <public_key> OP_CODESEPARATOR OP_DUP OP_HASH160 0x14 <public_key_hash> OP_EQUALVERIFY OP_CHECKSIG
Here:
0x48pushes a 72 byte signature.0x20pushes a 32 byte public key.0x14pushes a 20 byte public key hash.
These are typical sizes for ECDSA signatures, compressed public keys and public key hashes in Pay to Public Key Hash (P2PKH) scripts.
Example larger push:
OP_PUSHDATA1 0x64 <100_byte_data> OP_PUSHDATA2 0xe803 <1000_byte_data> OP_PUSHDATA4 0x40420f00 <1_000_000_byte_data>
These push 100 bytes, 1 kilobyte, and 1 megabyte respectively.
Minimal Encoding Rule
Nodes enforce that pushdata operations use the minimal opcode needed to express the length. For example:
OP_PUSHDATA1 0x64 <data>is valid for 100 bytes.OP_PUSHDATA2 0x6400 <data>is not, since 100 fits in a single byte and must use thePUSHDATA1form or the simple “length as opcode” form if under 76 bytes.
Wallets and tooling usually handle the correct pushdata opcode automatically. When you write higher level Script, you often just write the data and trust the assembler to choose the right push instruction.
Flow Control with IF / ELSE / ENDIF
Bitcoin Script does not have while loops or general jumps, but it does provide a minimal set of conditional flow control opcodes:
OP_IFOP_NOTIFOP_ELSEOP_ENDIF
These let you describe “if this, then do that, otherwise do something else” patterns based on stack values.
Basic IF
A simple IF structure looks like:
<expression> OP_IF <true branch> OP_ENDIF
First,
<expression>is evaluated. Whatever it leaves on top of the stack is interpreted as a boolean.If the top stack item is true (non zero), the interpreter executes
<true branch>.If it is false, the interpreter skips to the instruction after
OP_ENDIF.
NOTIF
OP_NOTIF inverts the condition:
<expression> OP_NOTIF <false branch> OP_ENDIF
If <expression> leaves zero on top, the <false branch> is executed. Otherwise the code jumps to after OP_ENDIF.
Every OP_IF or OP_NOTIF must have a matching OP_ENDIF. If the grammar is broken, the script is invalid and the transaction is not accepted.
ELSE
You can insert an OP_ELSE between the branch start and OP_ENDIF:
<expression> OP_IF <true branch> OP_ELSE <false branch> OP_ENDIF
Now the script will execute either the true branch or the false branch, depending on the condition.
Nested Conditions
You can build multi condition logic by nesting IF blocks or by writing separate IF blocks with disjoint conditions. This allows you to model something similar to a case statement, where you check case 1, then case 2, then fall back to an “else” case.
NOP, VERIFY, and Their Variants
Alongside IF constructs, there are opcodes that directly affect whether a script continues or terminates.
OP_NOP
OP_NOP does nothing. It consumes nothing and pushes nothing. It is sometimes used in advanced templates as a way to pad or reserve space in scripts.
OP_VERIFY
OP_VERIFY:
Pops the top item from the stack.
If that item is true (non zero), execution continues.
If it is false, the script terminates in failure.
This lets you gate sections of code on specific conditions.
OP_EQUALVERIFY
OP_EQUALVERIFY combines an equality check with VERIFY:
It compares the top two items on the stack as raw byte sequences.
If they are equal, it pops them and continues.
If they are not equal, the script fails.
Functionally it is equivalent to:
OP_EQUAL OP_VERIFY
Example snippet:
OP_2 OP_ADD OP_3 OP_EQUALVERIFY
If the top of the stack is 1 when this executes, then:
OP_2pushes 2.OP_ADDadds 2 and 1, giving 3.OP_3pushes 3.OP_EQUALVERIFYchecks that the top two values are equal and continues.
Any different starting value would cause a failure.
OP_NUMEQUALVERIFY
OP_NUMEQUALVERIFY works like OP_EQUALVERIFY, but compares the numeric values rather than the raw bytes. That can be helpful if you are dealing with numbers whose byte lengths may vary.
Example:
OP_4 OP_SPLIT OP_DROP OP_1 OP_NUMEQUALVERIFY
This example takes some value, splits off the first 4 bytes, drops the rest, and checks whether those 4 bytes represent the number 1. If so, it continues. If not, it fails.
OP_CHECKSIGVERIFY
OP_CHECKSIGVERIFY combines a signature check and a verify step:
It consumes a signature and a public key from the top of the stack.
It checks whether the signature is valid for the transaction and that key.
If valid, it continues.
If invalid, the script fails.
Basic pattern:
<signature> <public_key> OP_CHECKSIGVERIFY
This is similar to OP_CHECKSIG followed by OP_VERIFY.
OP_CHECKMULTISIGVERIFY
OP_CHECKMULTISIGVERIFY is the verifying version of the multisignature check. It:
Consumes an encoding that includes the number of required signatures, the signatures, the count of public keys, and the public keys.
Checks if the multisignature condition is met.
Continues if it is, fails if not.
It is used in “m of n” multisignature scripts, where multiple people have to sign before an output can be spent.
OP_RETURN and FALSE RETURN Scripts
OP_RETURN is a special opcode:
It terminates the script immediately.
The outcome depends on what is on the stack at that moment:
If there is one non zero item, the result is success.
Otherwise, it is failure.
One pattern is to use OP_RETURN to shortcut script execution once some condition is satisfied.
Example:
OP_DEPTH OP_1 OP_EQUAL OP_IF <public_key> OP_CHECKSIG OP_RETURN OP_ENDIF <rest_of_script>
Here, if there is exactly one item on the stack when this snippet runs, we treat it as a signature, verify it, and end the script through OP_RETURN. Otherwise we skip to <rest_of_script> and evaluate more conditions.
False Return Scripts
A particular pattern has become common for carrying pure data: the False Return script, often called an “OP_RETURN output.”
It looks like this:
OP_FALSE OP_RETURN <data packet>
Because OP_FALSE pushes a false value, and OP_RETURN ends the script with that false at the top, the script is unspendable. It can never evaluate to a valid spend condition.
Protocol rules allow these outputs to carry an output value of zero satoshis precisely because they cannot be spent. They are used as data carriers: applications write structured data into these outputs so that it can be indexed, analyzed and time stamped on chain without representing spendable money.
This technique underpins many token schemes and data protocols that use bitcoin as a timestamping and audit layer.
Disabled and Removed Opcodes
Over the years, some opcodes have been removed or disabled:
Removed opcodes have had their byte values repurposed for different operations. Their original behavior can always be reconstructed with combinations of other opcodes, so they are not strictly necessary.
Disabled opcodes still have reserved codes, but any script that tries to use them fails. As mentioned earlier,
OP_2MUL,OP_2DIV,OP_VER,OP_VERIF, andOP_VERNOTIFare in this category.
The original semantics of some version related opcodes are more complex, for example:
OP_VERused to push the transaction version onto the stack.OP_VERIFandOP_VERNOTIFwould enter IF branches based on comparing the version to a stack value.
These behaviors can still be simulated through other patterns, though the techniques are more advanced and typically documented separately.
ELI5(ish...)
Imagine bitcoin as a giant notebook where everyone writes little “locks” and “keys” instead of just balances.
Every time someone sends you bitcoin, they are creating a locked box on this notebook with your name encoded in a special way. That lock is written as a tiny program called a locking script.
When you want to spend that money, you write the key for that box into a new line. That key is your unlocking script, and it usually includes your digital signature.
Bitcoin Script is the tiny language used to write those locks and keys.
It works with a stack, which is just a pile of items where you always work with the top. You push numbers or data on, and opcodes like “add,” “check signature,” or “if” pop things off, do something, and push results back.
Some important ideas:
A transaction is a structured bundle of data:
Inputs that say, “I am opening this old box.”
Outputs that say, “I am creating this new box.”
Each input points to a specific old output and provides an unlocking script.
Each output has a locking script that defines who can spend it next.
The script evaluator inside nodes takes an input’s unlocking script, grabs the old output’s locking script, sticks them together, and runs them. If the final result on the stack is one true value and nothing else, that input is good.
Data on the stack is just bytes. Depending on the opcode, those bytes might be treated as:
A number.
True or false.
A public key.
A signature.
Constant opcodes like
OP_2andOP_3just push small integers. PUSHDATA opcodes push bigger data chunks, like signatures, public keys, or entire data packets.Unlocking scripts are very restricted. They are only allowed to push data. All the logic and rules live in the locking script that was created when the coins were first sent.
Conditional opcodes like
OP_IF,OP_ELSE, andOP_ENDIFlet scripts say, “If this condition is true, do this part, otherwise do that part.” That is how you can create more flexible smart contracts, like multisig or different spending paths.OP_RETURNis a big red “stop” button. You can use it to end a script early when something is proven, or, combined withOP_FALSE, to create outputs that can never be spent and only act as data carriers.
All of this is carefully limited and standardized so that every node in the network can run the exact same script, see the exact same result, and agree on which transactions are valid. Bitcoin Script is not a general purpose programming language. It is a compact, stack based tool kit whose entire job is to say, “Who owns this box of satoshis, and are they really allowed to open it right now?”