Bitcoin Script � The Transaction Programming Language
Bitcoin Script is a stack-based, non-Turing-complete programming language that defines conditions for spending UTXOs. Every Bitcoin transaction has a locking script (scriptPubKey) that defines who can spend it and an unlocking script (scriptSig) that satisfies those conditions.
Bitcoin Script Execution Model
- Stack machine: Script executes on a stack. Instructions (opcodes) push data or perform operations. A transaction is valid if the stack contains a single TRUE value after execution
- Non-Turing-complete: No loops, no arbitrary recursion. This is intentional � prevents infinite loops and makes script execution time bounded and predictable
- Two scripts combined: scriptSig (from the spending transaction's input) runs first, followed by scriptPubKey (from the UTXO being spent). The combined execution determines validity
- Pay-to-Public-Key-Hash (P2PKH): The most common script type. scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG. scriptSig: <signature> <pubKey>
- Pay-to-Script-Hash (P2SH): Allows complex scripts to be hashed. Anyone can send to the hash; the spender reveals the original script plus satisfying data. Enables multisig without requiring the sender to know details
- Multisig: OP_2 <pubKey1> <pubKey2> <pubKey3> OP_3 OP_CHECKMULTISIG requires any 2 of 3 signatures
Bitcoin Script Execution Simulator
class BitcoinScriptVM:
"""Stack-based Bitcoin Script interpreter (simplified)"""
def __init__(self):
self.stack: list = []
def execute(self, script_tokens: list) -> bool:
"""Execute a combined scriptSig + scriptPubKey"""
for token in script_tokens:
if token.startswith("OP_"):
self._execute_opcode(token)
else:
self.stack.append(token)
print(f" PUSH {token!r} -> Stack: {self.stack}")
# Valid if stack top is truthy
result = bool(self.stack and self.stack[-1] not in [False, 0, b"", ""])
return result
def _execute_opcode(self, opcode: str):
print(f" {opcode} -> Stack before: {self.stack}")
if opcode == "OP_DUP":
if self.stack:
self.stack.append(self.stack[-1])
elif opcode == "OP_HASH160":
import hashlib
if self.stack:
top = str(self.stack.pop()).encode()
sha256 = hashlib.sha256(top).digest()
ripemd = hashlib.new("ripemd160", sha256).hexdigest()
self.stack.append(ripemd)
elif opcode == "OP_EQUALVERIFY":
if len(self.stack) >= 2:
a, b = self.stack.pop(), self.stack.pop()
if a != b:
raise Exception(f"OP_EQUALVERIFY FAILED: {a!r} != {b!r}")
elif opcode == "OP_EQUAL":
if len(self.stack) >= 2:
a, b = self.stack.pop(), self.stack.pop()
self.stack.append(a == b)
elif opcode == "OP_CHECKSIG":
if len(self.stack) >= 2:
pubkey = self.stack.pop()
sig = self.stack.pop()
# Simplified: assume valid if sig contains pubkey prefix
is_valid = str(sig).startswith(str(pubkey)[:4])
self.stack.append(is_valid)
print(f" OP_CHECKSIG: sig={str(sig)[:8]}..., pubkey={str(pubkey)[:8]}..., valid={is_valid}")
print(f" Stack after: {self.stack}")
# Simulate P2PKH (Pay-to-Public-Key-Hash) script execution
vm = BitcoinScriptVM()
pubkey = "02a1b2c3d4..."
sig = "3044a1b2c3..." # Starts with "3044" (DER-encoded ECDSA prefix)
# Combined script: [scriptSig] + [scriptPubKey]
# scriptSig: <sig> <pubkey>
# scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
import hashlib
pubkey_hash = hashlib.new("ripemd160", hashlib.sha256(pubkey.encode()).digest()).hexdigest()
combined_script = [sig, pubkey, "OP_DUP", "OP_HASH160", pubkey_hash, "OP_EQUALVERIFY", "OP_CHECKSIG"]
print("=== P2PKH Script Execution ===")
try:
result = vm.execute(combined_script)
print(f"\nTransaction VALID: {result}")
except Exception as e:
print(f"\nTransaction INVALID: {e}")Common Mistakes
- Thinking Bitcoin Script is Turing-complete � Bitcoin Script deliberately has no loops. This makes transaction validation O(n) in script length � predictable and DoS-resistant. OP_CAT was removed partly over Turing completeness concerns
- Confusing scriptSig with the signature � scriptSig is the complete unlocking script, which for P2PKH contains both the signature AND the public key. Just having 'the signature' is not enough to spend a UTXO
Tip
Tip
Practice Bitcoin Script The Transaction Programming Language in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
Each block references the previous block's hash, forming an immutable chain
Practice Task
Note
Practice Task — (1) Write a working example of Bitcoin Script The Transaction Programming Language from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with Bitcoin Script The Transaction Programming Language is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready blockchain code.
Key Takeaways
- Bitcoin Script is a stack-based, non-Turing-complete programming language that defines conditions for spending UTXOs.
- Stack machine: Script executes on a stack. Instructions (opcodes) push data or perform operations. A transaction is valid if the stack contains a single TRUE value after execution
- Non-Turing-complete: No loops, no arbitrary recursion. This is intentional � prevents infinite loops and makes script execution time bounded and predictable
- Two scripts combined: scriptSig (from the spending transaction's input) runs first, followed by scriptPubKey (from the UTXO being spent). The combined execution determines validity