TeachMeBitcoin

Example: embed a hash

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

8. Fully Annotated OP_RETURN Data Embed

OP_RETURN is a special opcode that creates a provably unspendable output. It is used to embed arbitrary data in the Bitcoin blockchain without polluting the UTXO set.

What OP_RETURN Does

When the Bitcoin Script interpreter encounters OP_RETURN, it immediately terminates script execution and marks the script as invalid — meaning the output can never be spent. This is why OP_RETURN outputs are excluded from the UTXO set.

OP_RETURN script:
0x6a → OP_RETURN (marks immediate failure for any spending attempt)
<data>

Example:
6a 14 48656c6c6f2c20426974636f696e2057726c6421

Decoded:
6a → OP_RETURN
14 → push 20 bytes
48656c6c6f2c20426974636f696e20576f726c6421 → "Hello, Bitcoin World!"

The 80-Byte Data Limit

Bitcoin Core's relay policy allows OP_RETURN outputs with up to 80 bytes of data (83 bytes total: 1 byte OP_RETURN + 1 byte push opcode + 80 bytes data). This limit was raised from 40 bytes in Bitcoin Core 0.10.0.

def create_op_return_output(data: bytes) -> bytes:
    """
    Create an OP_RETURN output script.
    data: up to 80 bytes of arbitrary data
    """
    assert len(data) <= 80, "OP_RETURN data must be <= 80 bytes"

    script = bytes([0x6a])  # OP_RETURN

    if len(data) <= 75:
        # Direct push: length byte + data
        script += bytes([len(data)]) + data
    elif len(data) <= 255:
        # OP_PUSHDATA1
        script += bytes([0x4c, len(data)]) + data

    return script

# Example: embed a hash
document_hash = sha256(b"My important document")  # 32 bytes
op_return_script = create_op_return_output(document_hash)
# Result: 6a 20 <32-byte-hash>
# This proves the document existed at the time this transaction was confirmed

Real-World OP_RETURN Applications

# 1. OpenTimestamps (timestamping service):
# Format: 0x6a 0x08 <OTS_MAGIC> <merkle_root>
OTS_MAGIC = bytes.fromhex("0x4f70656e54696d65")  # "OpenTime"

# 2. Omni Layer (USDT was built on this):
# Format: 0x6a 0x14 "omni" <omni_data>
OMNI_HEADER = b"omni"

# 3. Colored Coins / RGB protocol:
# Format: 0x6a <protocol_magic> <commitment>

# 4. Counterparty protocol:
# Format: 0x6a 0x28 "CNTRPRTY" <encrypted_data>
COUNTERPARTY_PREFIX = b"CNTRPRTY"

# 5. Ordinals inscriptions (content hash):
# Ordinals uses witness data, not OP_RETURN, but some use OP_RETURN for metadata

# Generic protocol identifier pattern:
def create_protocol_op_return(protocol_id: bytes, data: bytes) -> bytes:
    """
    Standard pattern for protocol-tagged OP_RETURN.
    protocol_id: 4-8 bytes identifying the protocol
    data: protocol-specific payload
    """
    payload = protocol_id + data
    assert len(payload) <= 80
    return create_op_return_output(payload)

OP_RETURN and the UTXO Set

Normal output lifecycle:
Created → Added to UTXO set → Spent → Removed from UTXO set

OP_RETURN output lifecycle:
Created → NEVER added to UTXO set → Never spent

Why this matters:

- Bitcoin full nodes must keep the entire UTXO set in memory (fast access)

- As of 2026: UTXO set is ~5GB and growing

- OP_RETURN data does NOT bloat the UTXO set

- It only adds ~100 bytes to the blockchain history (not RAM-resident index)

- This is why OP_RETURN is the accepted way to embed data
  (vs fake P2PKH outputs to random addresses, which pollute the UTXO set)

Technical Insight

This topic covers essential mechanics for Chapter 12. Understanding these details is key to mastering advanced Bitcoin script constructions like Taproot and specialized covenants.

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