How scriptSig and scriptPubKey Combine at Validation
How scriptSig and scriptPubKey Combine at Validation
The validation of a Bitcoin input requires the node to combine the unlocking script and locking script into a single execution. This combination is the core of Bitcoin's security mechanism.
The Historical Concatenation Model
In very early Bitcoin, scripts were validated by literally concatenating the scriptSig and scriptPubKey and running them as one continuous script:
// Old model (DO NOT USE — UNSAFE):
Combined: <sig> <pubKey> OP_DUP OP_HASH160 <hash> OP_EQUALVERIFY OP_CHECKSIG
This model was changed in 2010 because it created a vulnerability: a malicious scriptSig could push opcodes that manipulated the execution of the locking script.
The Modern Two-Phase Model
Modern Bitcoin validation uses a two-phase execution model:
Phase 1: Run the scriptSig in isolation. Record the resulting stack state.
Phase 2: Run the scriptPubKey using the stack state from Phase 1 as the starting point.
# Pseudocode for modern validation:
def validate_input(scriptSig, scriptPubKey):
# Phase 1: execute unlocking script
stack = []
execute(scriptSig, stack)
# Snapshot stack (used for P2SH validation)
stack_snapshot = copy(stack)
# Phase 2: execute locking script
execute(scriptPubKey, stack)
# Check result
if not stack or stack[-1] == 0:
return False
# P2SH additional check
if is_p2sh(scriptPubKey):
return validate_p2sh(stack_snapshot)
return True
The P2SH Special Case
Pay-to-Script-Hash validation has an extra step. After the main two-phase execution, the node checks if the locking script is a P2SH pattern:
OP_HASH160 <20-byte-hash> OP_EQUAL
If it is, the node takes the last item pushed by the scriptSig (which should be the serialized redeem script), deserializes it, and executes it as a third script against the remaining stack items.
// Locking script:
OP_HASH160 <hash> OP_EQUAL
// Unlocking script:
OP_0 <sig1> <sig2> <serialized_redeem_script>
// Phase 1 executes scriptSig, leaving:
Stack: [OP_0, sig1, sig2, <serialized_redeem_script>]
// Phase 2 checks HASH160(<serialized_redeem_script>) == <hash>
// If true, Phase 3 executes the redeem script:
OP_2 <pubKey1> <pubKey2> <pubKey3> OP_3 OP_CHECKMULTISIG
// Using remaining stack: [OP_0, sig1, sig2]
SegWit Validation Differences
For native SegWit inputs, the scriptSig must be empty (for P2WPKH/P2WSH/P2TR). The witness data is provided separately and processed by a different validation path defined in BIP 141.
// P2WPKH validation path:
1. scriptSig must be empty (enforced by consensus)
2. scriptPubKey must be: OP_0 <20-byte-hash>
3. Witness: [<sig>, <pubKey>]
4. Implicit script: OP_DUP OP_HASH160 <hash> OP_EQUALVERIFY OP_CHECKSIG
5. Executed with witness data as the stack inputs
TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation: