TeachMeBitcoin

a8 = OP_SHA256, 20 = push 32 bytes, 87 = OP_EQUAL

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

9. How to Write and Test Custom Scripts

Understanding Script Constraints

Before writing a custom script, understand the consensus constraints:

Writing a Hash Preimage Lock

A simple custom script that requires knowing a secret preimage:

scriptPubKey: OP_SHA256 <32-byte-hash> OP_EQUAL
scriptSig:    <preimage>

Build and test:

import hashlib

secret = b"my_secret_preimage"
hash_value = hashlib.sha256(secret).hexdigest()

script_pubkey = "a8" + "20" + hash_value + "87"
# a8 = OP_SHA256, 20 = push 32 bytes, 87 = OP_EQUAL

script_sig = "12" + secret.hex()  # 0x12 = 18 bytes
# Adjust push length to match actual secret length

print(f"scriptPubKey: {script_pubkey}")
print(f"scriptSig:    {script_sig}")

Test with btcdeb:

btcdeb "[OP_SHA256 $(echo -n "my_secret_preimage" | sha256sum | cut -d' ' -f1) OP_EQUAL]" \
  --scriptSig "$(echo -n "my_secret_preimage" | xxd -p -c 256)"

Writing a Time-Locked Script (CLTV)

OP_CHECKLOCKTIMEVERIFY (CLTV) rejects spending until a certain block height or timestamp:

scriptPubKey:
  <locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP
  OP_DUP OP_HASH160 <pubkeyhash> OP_EQUALVERIFY OP_CHECKSIG

In hex (locktime = block 800000 = 0x000C3500, little-endian = 0x0035C000):

locktime_hex = (800000).to_bytes(4, 'little').hex()  # "003dc00c"
cltv_script = (
    "04" + locktime_hex +  # push 4-byte locktime
    "b1" +                 # OP_CHECKLOCKTIMEVERIFY
    "75" +                 # OP_DROP
    "76" +                 # OP_DUP
    "a9" +                 # OP_HASH160
    "14" + pubkeyhash +    # push 20-byte hash
    "88" +                 # OP_EQUALVERIFY
    "ac"                   # OP_CHECKSIG
)

Encoding as P2SH

Wrap your custom script in P2SH for a standard-looking address:

import hashlib

def hash160(data: bytes) -> bytes:
    return hashlib.new('ripemd160', hashlib.sha256(data).digest()).digest()

redeem_script_bytes = bytes.fromhex(cltv_script)
script_hash = hash160(redeem_script_bytes).hex()

p2sh_scriptpubkey = "a9" + "14" + script_hash + "87"
print(f"P2SH scriptPubKey: {p2sh_scriptpubkey}")

Testing in Regtest

# Start regtest with non-standard scripts enabled
bitcoind -regtest -acceptnonstdtxn=1 -daemon

# Send to the custom P2SH address
bitcoin-cli -regtest sendtoaddress <p2sh-address> 1.0

# Mine a block
bitcoin-cli -regtest generatetoaddress 1 $(bitcoin-cli -regtest getnewaddress)

# Create spending transaction with redeem script in scriptSig
# Then testmempoolaccept to verify before broadcasting

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!