TeachMeBitcoin

Test

From TeachMeBitcoin, the free encyclopedia Reading time: 4 min

2. How to Decode a scriptPubKey Manually

What Is a scriptPubKey?

The scriptPubKey is the locking script attached to each transaction output (UTXO). It defines the conditions that must be satisfied to spend that output. When a new transaction wants to spend a UTXO, it must provide a scriptSig (and possibly witness data) that, when combined with the scriptPubKey, results in a valid execution.

The scriptPubKey is embedded in the transaction output structure:

[value: 8 bytes little-endian][scriptPubKey length: varint][scriptPubKey bytes]

Common scriptPubKey Patterns

P2PK — Pay to Public Key

<pubkey length> <pubkey> OP_CHECKSIG

Example (uncompressed public key, 65 bytes):

41
04{64-byte-pubkey}
ac

Example (compressed public key, 33 bytes):

21
02{32-byte-pubkey}
ac

Manual decode:

41        → Push next 65 bytes
04...     → Uncompressed public key (04 prefix + 32-byte X + 32-byte Y)
ac        → OP_CHECKSIG

P2PKH — Pay to Public Key Hash

OP_DUP OP_HASH160 <20-byte-hash> OP_EQUALVERIFY OP_CHECKSIG

Hex template:

76 a9 14 {20 bytes} 88 ac

P2SH — Pay to Script Hash

OP_HASH160 <20-byte-hash> OP_EQUAL

Hex template:

a9 14 {20 bytes} 87

P2SH is recognizable because the script is only 23 bytes long and follows this exact pattern. The hash is of the redeem script that the spender must reveal.

P2WPKH — Pay to Witness Public Key Hash

OP_0 <20-byte-hash>

Hex template:

00 14 {20 bytes}

This is 22 bytes total. OP_0 (version byte 0) followed by a 20-byte push signals native SegWit v0 P2WPKH.

P2WSH — Pay to Witness Script Hash

OP_0 <32-byte-hash>

Hex template:

00 20 {32 bytes}

This is 34 bytes. The 32-byte hash is SHA256 of the witness script.

P2TR — Pay to Taproot (SegWit v1)

OP_1 <32-byte-x-only-pubkey>

Hex template:

51 20 {32 bytes}

0x51 = OP_1 (version byte 1). The 32 bytes are the x-only Taproot output key.

Manual Decode Workflow

def decode_scriptpubkey(hex_script: str) -> dict:
    data = bytes.fromhex(hex_script)
    length = len(data)

    # P2PKH: 76 a9 14 [20 bytes] 88 ac
    if (length == 25 and data[0] == 0x76 and data[1] == 0xa9
            and data[2] == 0x14 and data[23] == 0x88 and data[24] == 0xac):
        return {"type": "P2PKH", "hash": data[3:23].hex()}

    # P2SH: a9 14 [20 bytes] 87
    if length == 23 and data[0] == 0xa9 and data[1] == 0x14 and data[22] == 0x87:
        return {"type": "P2SH", "hash": data[2:22].hex()}

    # P2WPKH: 00 14 [20 bytes]
    if length == 22 and data[0] == 0x00 and data[1] == 0x14:
        return {"type": "P2WPKH", "hash": data[2:22].hex()}

    # P2WSH: 00 20 [32 bytes]
    if length == 34 and data[0] == 0x00 and data[1] == 0x20:
        return {"type": "P2WSH", "hash": data[2:34].hex()}

    # P2TR: 51 20 [32 bytes]
    if length == 34 and data[0] == 0x51 and data[1] == 0x20:
        return {"type": "P2TR", "key": data[2:34].hex()}

    # P2PK compressed
    if length == 35 and data[0] == 0x21 and data[34] == 0xac:
        return {"type": "P2PK", "pubkey": data[1:34].hex()}

    return {"type": "UNKNOWN", "raw": hex_script}

# Test
print(decode_scriptpubkey("76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac"))
# Output: {'type': 'P2PKH', 'hash': '89abcdefabbaabbaabbaabbaabbaabbaabbaabba'}

OP_RETURN Outputs

OP_RETURN scripts are used to embed arbitrary data in the blockchain. They are provably unspendable:

6a <varint length> <arbitrary data>

6a        → OP_RETURN
0c        → Push next 12 bytes
48656c6c6f20576f726c64  → "Hello World" in ASCII

These outputs carry zero value and cannot be spent, but their data is permanently recorded on-chain.

Pro Tip

When debugging scripts, always start with a high-level disassembly before diving into the stack trace. Tools like bitcoin-cli decodescript are your first line of defense in identifying standard script patterns.

☕ 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!