TeachMeBitcoin

How scriptSig and scriptPubKey Combine at Validation

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

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
☕ Help support TeachMeBitcoin

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:

Ethereum: 0x578417C51783663D8A6A811B3544E1f779D39A85
Bitcoin: bc1q77k9e95rn669kpzyjr8ke9w95zhk7pa5s63qzz
Solana: 4ycT2ayqeMucixj3wS8Ay8Tq9NRDYRPKYbj3UGESyQ4J
Address copied to clipboard!