跳至主要內容
入門

OP_RETURN

OP_RETURN:在比特幣區塊鏈上嵌入任意數據

12 分鐘

概述

OP_RETURN 是比特幣腳本中的一個操作碼,允許在交易輸出中嵌入少量任意數據。 這些輸出被標記為「可證明不可花費」(provably unspendable), 不會進入 UTXO 集合,避免了區塊鏈膨脹問題。

數據限制: Bitcoin Core 標準規則允許每個 OP_RETURN 輸出最多 80 bytes 數據。 共識規則本身沒有嚴格限制,但非標準交易難以傳播。

腳本格式

基本結構

OP_RETURN 輸出結構:

scriptPubKey:
┌─────────────────────────────────────┐
│ OP_RETURN (0x6a)                    │
├─────────────────────────────────────┤
│ [OP_PUSHBYTES_n] [data...]          │
└─────────────────────────────────────┘

執行行為:
1. OP_RETURN 立即終止腳本執行
2. 將結果標記為失敗
3. 但整個交易仍然有效(輸出不可花費)

範例:
6a                      # OP_RETURN
13                      # PUSH 19 bytes
48656c6c6f2c20426974636f696e21  # "Hello, Bitcoin!"

結果: 不可花費的輸出,包含 "Hello, Bitcoin!"

標準規則

Bitcoin Core 標準規則:

1. 數據大小限制
   - 最大 80 bytes (自 v0.12.0)
   - 之前是 40 bytes (v0.10.0)
   - 更早是 0 bytes (完全禁止)

2. 輸出數量限制
   - 每筆交易最多一個 OP_RETURN 輸出
   - 多個會被視為非標準

3. 輸出金額
   - 通常設為 0 satoshi
   - 大於 0 的金額會被「燒毀」

4. scriptPubKey 格式
   - 必須以 OP_RETURN 開頭
   - 後面跟隨 push 操作碼和數據

標準 vs 共識:
- 標準規則: 節點是否轉發/接受到 mempool
- 共識規則: 區塊是否有效
- 礦工可以包含非標準交易

TypeScript 實作

創建 OP_RETURN 輸出

// 創建 OP_RETURN scriptPubKey
function createOpReturnScript(data: Uint8Array): Uint8Array {
  if (data.length > 80) {
    throw new Error('OP_RETURN 數據超過 80 bytes 限制');
  }

  const parts: number[] = [];

  // OP_RETURN
  parts.push(0x6a);

  // 數據 push
  if (data.length === 0) {
    // 空數據,不需要 push
  } else if (data.length <= 75) {
    // OP_PUSHBYTES_n (直接 push)
    parts.push(data.length);
    parts.push(...data);
  } else {
    // OP_PUSHDATA1
    parts.push(0x4c); // OP_PUSHDATA1
    parts.push(data.length);
    parts.push(...data);
  }

  return new Uint8Array(parts);
}

// 使用文本創建
function createOpReturnText(text: string): Uint8Array {
  const encoder = new TextEncoder();
  const data = encoder.encode(text);
  return createOpReturnScript(data);
}

// 使用十六進制創建
function createOpReturnHex(hex: string): Uint8Array {
  const data = hexToBytes(hex);
  return createOpReturnScript(data);
}

// 範例
const script1 = createOpReturnText('Hello, Bitcoin!');
console.log(bytesToHex(script1));
// 6a0f48656c6c6f2c20426974636f696e21

const script2 = createOpReturnHex('deadbeef');
console.log(bytesToHex(script2));
// 6a04deadbeef

解析 OP_RETURN 數據

interface OpReturnData {
  isOpReturn: boolean;
  data: Uint8Array | null;
  text: string | null;
}

function parseOpReturn(scriptPubKey: Uint8Array): OpReturnData {
  // 檢查是否以 OP_RETURN 開頭
  if (scriptPubKey.length === 0 || scriptPubKey[0] !== 0x6a) {
    return { isOpReturn: false, data: null, text: null };
  }

  if (scriptPubKey.length === 1) {
    // 只有 OP_RETURN,無數據
    return { isOpReturn: true, data: new Uint8Array(0), text: '' };
  }

  // 解析 push 操作
  let offset = 1;
  const pushOp = scriptPubKey[offset];
  let dataLength: number;
  let dataStart: number;

  if (pushOp <= 75) {
    // OP_PUSHBYTES_n
    dataLength = pushOp;
    dataStart = offset + 1;
  } else if (pushOp === 0x4c) {
    // OP_PUSHDATA1
    dataLength = scriptPubKey[offset + 1];
    dataStart = offset + 2;
  } else if (pushOp === 0x4d) {
    // OP_PUSHDATA2
    dataLength = scriptPubKey[offset + 1] |
                 (scriptPubKey[offset + 2] << 8);
    dataStart = offset + 3;
  } else {
    return { isOpReturn: true, data: null, text: null };
  }

  const data = scriptPubKey.slice(dataStart, dataStart + dataLength);

  // 嘗試解碼為文本
  let text: string | null = null;
  try {
    const decoder = new TextDecoder('utf-8', { fatal: true });
    text = decoder.decode(data);
  } catch {
    // 不是有效的 UTF-8
  }

  return { isOpReturn: true, data, text };
}

// 從交易中提取所有 OP_RETURN 數據
function extractOpReturnData(tx: Transaction): OpReturnData[] {
  const results: OpReturnData[] = [];

  for (const output of tx.outputs) {
    const parsed = parseOpReturn(output.scriptPubKey);
    if (parsed.isOpReturn) {
      results.push(parsed);
    }
  }

  return results;
}

完整交易構建

interface OpReturnTxParams {
  inputs: Array<{
    txid: string;
    vout: number;
    value: bigint;
    scriptPubKey: Uint8Array;
  }>;
  opReturnData: Uint8Array;
  changeAddress: string;
  feeRate: number; // sat/vB
}

function buildOpReturnTx(params: OpReturnTxParams): Uint8Array {
  const { inputs, opReturnData, changeAddress, feeRate } = params;

  // 計算輸入總額
  const totalInput = inputs.reduce((sum, i) => sum + i.value, 0n);

  // OP_RETURN 輸出
  const opReturnScript = createOpReturnScript(opReturnData);
  const opReturnOutput = {
    value: 0n,
    scriptPubKey: opReturnScript
  };

  // 估算交易大小
  const estimatedSize = estimateTxSize(inputs.length, 2); // 2 outputs
  const fee = BigInt(Math.ceil(estimatedSize * feeRate));

  // 找零輸出
  const changeValue = totalInput - fee;
  if (changeValue < 546n) {
    throw new Error('餘額不足以支付手續費');
  }

  const changeScript = addressToScriptPubKey(changeAddress);
  const changeOutput = {
    value: changeValue,
    scriptPubKey: changeScript
  };

  // 構建交易
  return buildTransaction({
    version: 2,
    inputs: inputs.map(i => ({
      txid: i.txid,
      vout: i.vout,
      sequence: 0xffffffff
    })),
    outputs: [opReturnOutput, changeOutput],
    locktime: 0
  });
}

// 使用範例
const tx = buildOpReturnTx({
  inputs: [{
    txid: 'abc123...',
    vout: 0,
    value: 100000n,
    scriptPubKey: hexToBytes('76a914...88ac')
  }],
  opReturnData: new TextEncoder().encode('Timestamp: 2024-01-01'),
  changeAddress: 'bc1q...',
  feeRate: 10
});

應用場景

時間戳證明

OpenTimestamps 協議:

目的: 證明某數據在特定時間之前存在

流程:
1. 計算文件雜湊: SHA256(document)
2. 創建 OP_RETURN: hash
3. 交易被確認後,區塊時間戳即為證明

格式範例:
OP_RETURN <SHA256 hash> (32 bytes)

優點:
- 不可篡改的時間證明
- 不暴露原始內容
- 低成本 (只需一筆交易)

應用:
- 智慧財產權登記
- 合約簽署時間
- 研究成果優先權

資產代幣 (Colored Coins)

Colored Coins / 資產協議:

概念: 在比特幣上發行和追蹤資產

OP_RETURN 格式:
┌─────────────────────────────────────┐
│ Protocol ID (魔術字節)              │
├─────────────────────────────────────┤
│ Asset ID                            │
├─────────────────────────────────────┤
│ Amount / Metadata                   │
└─────────────────────────────────────┘

範例協議:
- OMNI Layer (Tether USDT 原址)
- RGB Protocol
- Counterparty

限制:
- 80 bytes 限制複雜操作
- 需要專門軟體解析
- 不是原生支援

承諾方案

// 承諾-揭示方案 (Commit-Reveal)
interface Commitment {
  commitment: Uint8Array;
  secret: Uint8Array;
  data: Uint8Array;
}

function createCommitment(data: Uint8Array): Commitment {
  // 生成隨機秘密
  const secret = crypto.getRandomValues(new Uint8Array(32));

  // 計算承諾 = SHA256(secret || data)
  const combined = concatBytes([secret, data]);
  const commitment = sha256(combined);

  return { commitment, secret, data };
}

function verifyCommitment(
  commitment: Uint8Array,
  secret: Uint8Array,
  data: Uint8Array
): boolean {
  const combined = concatBytes([secret, data]);
  const expected = sha256(combined);
  return bytesEqual(commitment, expected);
}

// 使用場景: 預測市場
// 1. 用戶提交預測的承諾 (OP_RETURN)
// 2. 等待結果公布
// 3. 揭示預測 (證明提前知道)

跨鏈錨定

側鏈/跨鏈橋錨定:

Liquid Network 範例:
- Peg-in: 將 BTC 發送到聯邦地址
- OP_RETURN 包含 Liquid 接收地址
- 聯邦驗證後在 Liquid 上發行 L-BTC

格式:
OP_RETURN <magic> <liquid_address>

其他應用:
- RSK (比特幣側鏈)
- Stacks
- 各種跨鏈橋

知名協議

Ordinals & Inscriptions

Ordinals 協議 (2023):

注意: Inscriptions 主要使用 witness 而非 OP_RETURN

但仍使用 OP_RETURN 的場景:
- 元協議標識
- 小型數據嵌入

Inscription 結構 (在 witness 中):
OP_FALSE
OP_IF
  OP_PUSH "ord"
  OP_PUSH content-type
  OP_PUSH 0
  OP_PUSH <content>
OP_ENDIF

這繞過了 80 bytes 限制,可嵌入任意大小數據

OMNI Layer

OMNI Layer (前身 Mastercoin):

格式:
OP_RETURN "omni" <tx_type> <payload>

交易類型:
- Simple Send (0)
- Create Property (50)
- Grant Tokens (55)
- Revoke Tokens (56)

歷史意義:
- 2014年推出
- Tether USDT 最初在此發行
- 後遷移至以太坊和 Tron

Counterparty

Counterparty 協議:

特點:
- 完整的智能合約平台
- 使用 OP_RETURN 嵌入交易數據

格式:
OP_RETURN "CNTRPRTY" <encrypted_data>

功能:
- 資產發行
- 去中心化交易所
- 差價合約 (CFD)
- 投注市場

XCP 代幣:
- 2014年通過「證明燒毀」創建
- 用於協議手續費

歷史演變

OP_RETURN 政策演變:

2010 之前:
- OP_RETURN 存在但無特殊處理
- 數據常被嵌入在假地址中

2013 (v0.9.0):
- 正式支援 OP_RETURN
- 40 bytes 限制
- 被視為「正確」的數據嵌入方式

2014:
- 社區辯論數據嵌入的利弊
- 開發者擔心區塊鏈膨脹

2015 (v0.11.0):
- 允許多個 push 操作
- 仍限制 40 bytes 總量

2016 (v0.12.0):
- 限制提升至 80 bytes
- 更多協議可以使用

現在:
- 80 bytes 標準限制
- Taproot 提供了替代的大數據嵌入方式

UTXO 與修剪

為什麼 OP_RETURN 重要:

問題: 在 OP_RETURN 之前
- 數據嵌入使用假地址 (如 1CounterpartyXXXXXXXX...)
- 這些「地址」永遠不可花費
- 但節點必須在 UTXO 集合中保存它們
- 造成永久的資源消耗

OP_RETURN 解決方案:
- 明確標記為不可花費
- 節點可以安全地修剪這些輸出
- 不增加 UTXO 集合大小

UTXO 集合對比:
┌─────────────────────────────────────┐
│ 假地址方式                          │
│ - 需要保存在 UTXO 集合              │
│ - 永久佔用記憶體                    │
│ - 每個節點都受影響                  │
├─────────────────────────────────────┤
│ OP_RETURN 方式                      │
│ - 不進入 UTXO 集合                  │
│ - 只佔用區塊空間                    │
│ - 可被修剪節點丟棄                  │
└─────────────────────────────────────┘

最佳實踐

使用 OP_RETURN 的建議:

1. 遵守大小限制
   - 保持在 80 bytes 以內
   - 考慮使用雜湊而非原始數據

2. 使用協議前綴
   - 前幾個 bytes 用於協議識別
   - 避免與其他協議衝突
   - 例如: "DOCPROOF", "OMNI", "RGB"

3. 最小化使用
   - 只嵌入必要的數據
   - 大數據使用鏈下存儲 + 雜湊錨定

4. 考慮替代方案
   - Taproot witness (更大空間)
   - 鏈下存儲 (IPFS, Arweave)
   - 側鏈 (更豐富的功能)

5. 手續費考量
   - OP_RETURN 輸出增加交易大小
   - 高費率時期成本增加

數據嵌入方式比較

方式 大小限制 UTXO 影響 成本
OP_RETURN 80 bytes
假地址 ~20 bytes 永久膨脹
Taproot Witness ~400 KB 中-高
多重簽名腳本 ~1.5 KB 花費前存在

相關資源

已複製連結
已複製到剪貼簿