跳至主要內容
高級

Bitcoin Script

比特幣腳本語言深入解析:操作碼、標準腳本類型和智能合約基礎

25 分鐘

概述

Bitcoin Script 是一種基於堆疊的腳本語言,用於定義交易輸出的花費條件。 它不是圖靈完備的,這是刻意設計以避免無限循環和複雜的安全問題。 每個比特幣交易都包含腳本,決定誰可以花費這些資金。

設計哲學: Script 故意保持簡單和受限。沒有循環、沒有狀態、執行時間可預測。 這讓每個節點都能快速驗證交易,確保網路安全。

堆疊機器

Script 使用兩個堆疊執行:主堆疊和替代堆疊。 操作碼從腳本中依序讀取,對堆疊進行操作。 執行結束時,如果主堆疊頂部是非零值(true),腳本成功。

執行流程

腳本: OP_2 OP_3 OP_ADD OP_5 OP_EQUAL

執行步驟:
1. OP_2     → 堆疊: [2]
2. OP_3     → 堆疊: [2, 3]
3. OP_ADD   → 堆疊: [5]        (2+3=5)
4. OP_5     → 堆疊: [5, 5]
5. OP_EQUAL → 堆疊: [1]        (5==5, true)

結果: 成功 (堆疊頂部為 true)

常用操作碼

常數操作

操作碼 Hex 說明
OP_0 / OP_FALSE 0x00 推入空字節數組(false)
OP_1 - OP_16 0x51-0x60 推入數字 1-16
OP_1NEGATE 0x4f 推入 -1
OP_PUSHDATA1/2/4 0x4c-0x4e 推入指定長度的數據

堆疊操作

操作碼 說明 範例
OP_DUP 複製堆疊頂部元素 [a] → [a, a]
OP_DROP 移除堆疊頂部元素 [a, b] → [a]
OP_SWAP 交換頂部兩元素 [a, b] → [b, a]
OP_PICK 複製第 n 個元素到頂部 [a, b, c, 2] → [a, b, c, a]
OP_ROLL 移動第 n 個元素到頂部 [a, b, c, 2] → [b, c, a]

加密操作

操作碼 說明
OP_SHA256 SHA-256 雜湊
OP_HASH160 RIPEMD160(SHA256(x))
OP_HASH256 SHA256(SHA256(x))
OP_CHECKSIG 驗證簽名
OP_CHECKMULTISIG 驗證多重簽名

流程控制

操作碼 說明
OP_IF / OP_NOTIF 條件執行
OP_ELSE 條件分支
OP_ENDIF 結束條件
OP_VERIFY 如果頂部不為 true 則失敗
OP_RETURN 標記輸出為不可花費

標準腳本類型

P2PKH (Pay to Public Key Hash)

最傳統的比特幣地址格式(以 1 開頭)。

scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
scriptSig:    <signature> <pubKey>

驗證流程:
1. 推入 signature
2. 推入 pubKey
3. OP_DUP: 複製 pubKey
4. OP_HASH160: 對 pubKey 做 HASH160
5. 推入 pubKeyHash
6. OP_EQUALVERIFY: 確認雜湊匹配
7. OP_CHECKSIG: 驗證簽名

P2SH (Pay to Script Hash)

允許複雜腳本,地址以 3 開頭。花費時提供完整腳本。

scriptPubKey: OP_HASH160 <scriptHash> OP_EQUAL
scriptSig:    <sig1> ... <sigN> <redeemScript>

範例 - 2-of-3 多簽:
redeemScript: OP_2 <pubKey1> <pubKey2> <pubKey3> OP_3 OP_CHECKMULTISIG

P2WPKH (SegWit v0)

原生 SegWit 地址(以 bc1q 開頭),簽名移到 witness。

scriptPubKey: OP_0 <20-byte-pubKeyHash>
witness:      <signature> <pubKey>

優點:
- 手續費更低(witness 數據折扣)
- 解決交易延展性問題
- 更簡潔的腳本

P2WSH (SegWit Script)

SegWit 版本的 P2SH,支援更複雜的腳本。

scriptPubKey: OP_0 <32-byte-scriptHash>
witness:      <witness items> <witnessScript>

scriptHash = SHA256(witnessScript)

P2TR (Taproot)

最新的地址格式(以 bc1p 開頭),支援 Schnorr 簽名和 MAST。

scriptPubKey: OP_1 <32-byte-tweaked-pubKey>

兩種花費路徑:
1. Key Path: 直接用調整後的私鑰簽名
2. Script Path: 揭露特定的腳本分支

優點:
- 隱私性更好(所有輸出看起來相同)
- 支援複雜條件但只揭露使用的分支
- Schnorr 簽名更高效

時間鎖

絕對時間鎖

OP_CHECKLOCKTIMEVERIFY (CLTV)
- 交易必須在指定時間/區塊後才能被包含
- 數值 < 500000000: 區塊高度
- 數值 >= 500000000: Unix 時間戳

範例: 2024年後才能花費
<1704067200> OP_CLTV OP_DROP <pubKey> OP_CHECKSIG

相對時間鎖

OP_CHECKSEQUENCEVERIFY (CSV)
- 相對於輸入 UTXO 被確認的時間
- 用於通道協議和時間條件合約

範例: 確認後 144 區塊(約 1 天)才能花費
<144> OP_CSV OP_DROP <pubKey> OP_CHECKSIG

多重簽名

M-of-N 多簽結構:
OP_M <pubKey1> <pubKey2> ... <pubKeyN> OP_N OP_CHECKMULTISIG

範例 - 2-of-3 多簽:
scriptPubKey:
OP_2
<pubKey1>
<pubKey2>
<pubKey3>
OP_3
OP_CHECKMULTISIG

scriptSig:
OP_0           // 修復 off-by-one bug
<sig1>
<sig2>

歷史 Bug: OP_CHECKMULTISIG 有一個 off-by-one bug,會從堆疊多彈出一個元素。 因此 scriptSig 開頭需要 OP_0。這個 bug 已成為共識規則的一部分。

HTLC (雜湊時間鎖合約)

閃電網路的核心構建塊:

OP_IF
    // Hash Path: 知道 preimage 可以立即花費
    OP_SHA256 <hash> OP_EQUALVERIFY
    <receiverPubKey> OP_CHECKSIG
OP_ELSE
    // Timeout Path: 超時後發送方可以取回
    <timeout> OP_CLTV OP_DROP
    <senderPubKey> OP_CHECKSIG
OP_ENDIF

使用方式:
- Hash Path: <signature> <preimage> OP_TRUE
- Timeout Path: <signature> OP_FALSE

Tapscript (SegWit v1)

Taproot 引入的改進腳本:

主要變化

  • Schnorr 簽名:使用 OP_CHECKSIG 驗證 64 字節 Schnorr 簽名
  • OP_CHECKSIGADD:替代 OP_CHECKMULTISIG,更靈活的多簽
  • OP_SUCCESS:保留操作碼,用於未來升級
  • 簽名雜湊改進:更安全的 sighash 計算

新的多簽方式

傳統 2-of-3:
OP_2 <pk1> <pk2> <pk3> OP_3 OP_CHECKMULTISIG

Tapscript 2-of-3:
<pk1> OP_CHECKSIG
<pk2> OP_CHECKSIGADD
<pk3> OP_CHECKSIGADD
OP_2 OP_NUMEQUAL

優點: 可以驗證任意組合,不限於連續簽名

TypeScript 實作

解析腳本

const OP_CODES: Record<number, string> = {
  0x00: 'OP_0',
  0x51: 'OP_1',
  0x52: 'OP_2',
  0x53: 'OP_3',
  0x76: 'OP_DUP',
  0x87: 'OP_EQUAL',
  0x88: 'OP_EQUALVERIFY',
  0xa9: 'OP_HASH160',
  0xac: 'OP_CHECKSIG',
  0xae: 'OP_CHECKMULTISIG',
};

function parseScript(script: Buffer): string[] {
  const result: string[] = [];
  let i = 0;

  while (i < script.length) {
    const opcode = script[i];

    if (opcode >= 0x01 && opcode <= 0x4b) {
      // Push data directly
      const data = script.slice(i + 1, i + 1 + opcode);
      result.push(data.toString('hex'));
      i += 1 + opcode;
    } else if (OP_CODES[opcode]) {
      result.push(OP_CODES[opcode]);
      i++;
    } else {
      result.push(`OP_UNKNOWN_${opcode.toString(16)}`);
      i++;
    }
  }

  return result;
}

// 範例
const p2pkh = Buffer.from(
  '76a914' + '89abcdefabbaabbaabbaabbaabbaabbaabbaabba' + '88ac',
  'hex'
);
console.log(parseScript(p2pkh));
// ['OP_DUP', 'OP_HASH160', '89abcdef...', 'OP_EQUALVERIFY', 'OP_CHECKSIG']

建立 P2PKH 腳本

import * as bitcoin from 'bitcoinjs-lib';
import { hash160 } from 'bitcoinjs-lib/src/crypto';

function createP2PKH(pubKey: Buffer): Buffer {
  const pubKeyHash = hash160(pubKey);

  return bitcoin.script.compile([
    bitcoin.opcodes.OP_DUP,
    bitcoin.opcodes.OP_HASH160,
    pubKeyHash,
    bitcoin.opcodes.OP_EQUALVERIFY,
    bitcoin.opcodes.OP_CHECKSIG,
  ]);
}

// 建立 P2SH 多簽
function createMultisigP2SH(
  m: number,
  pubKeys: Buffer[]
): { address: string; redeemScript: Buffer } {
  const redeemScript = bitcoin.script.compile([
    bitcoin.opcodes.OP_0 + m, // OP_M
    ...pubKeys,
    bitcoin.opcodes.OP_0 + pubKeys.length, // OP_N
    bitcoin.opcodes.OP_CHECKMULTISIG,
  ]);

  const scriptHash = hash160(redeemScript);
  const outputScript = bitcoin.script.compile([
    bitcoin.opcodes.OP_HASH160,
    scriptHash,
    bitcoin.opcodes.OP_EQUAL,
  ]);

  const address = bitcoin.address.fromOutputScript(
    outputScript,
    bitcoin.networks.bitcoin
  );

  return { address, redeemScript };
}

建立 HTLC

import * as crypto from 'crypto';

interface HTLCParams {
  receiverPubKey: Buffer;
  senderPubKey: Buffer;
  paymentHash: Buffer;
  timeout: number;
}

function createHTLC(params: HTLCParams): Buffer {
  return bitcoin.script.compile([
    bitcoin.opcodes.OP_IF,
      bitcoin.opcodes.OP_SHA256,
      params.paymentHash,
      bitcoin.opcodes.OP_EQUALVERIFY,
      params.receiverPubKey,
      bitcoin.opcodes.OP_CHECKSIG,
    bitcoin.opcodes.OP_ELSE,
      bitcoin.script.number.encode(params.timeout),
      bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY,
      bitcoin.opcodes.OP_DROP,
      params.senderPubKey,
      bitcoin.opcodes.OP_CHECKSIG,
    bitcoin.opcodes.OP_ENDIF,
  ]);
}

// 使用範例
const preimage = crypto.randomBytes(32);
const paymentHash = crypto.createHash('sha256').update(preimage).digest();

const htlc = createHTLC({
  receiverPubKey: Buffer.from('02...', 'hex'),
  senderPubKey: Buffer.from('03...', 'hex'),
  paymentHash,
  timeout: 800000, // 區塊高度
});

安全考量

  • 腳本大小限制:標準腳本最大 10,000 字節
  • 操作數限制:最多 201 個操作
  • 堆疊大小:每個元素最大 520 字節
  • 簽名檢查限制:每個區塊最多 80,000 次
  • 禁用操作碼OP_CATOP_MUL 等已禁用

相關資源

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