跳至主要內容
高級

Signature Operations (SigOps)

深入了解比特幣的簽名操作限制,如何防止 DoS 攻擊並確保區塊驗證效率。

10 分鐘

什麼是 SigOps?

Signature Operations(SigOps,簽名操作)是衡量交易驗證成本的指標。 比特幣對每個區塊的 SigOps 總數有限制,以防止攻擊者創建需要大量 CPU 時間驗證的區塊。

為什麼需要 SigOps 限制?

無限制的風險

  • • 區塊驗證時間過長
  • • DoS 攻擊可能
  • • 節點資源耗盡
  • • 網路同步延遲

SigOps 限制的保護

  • • 可預測的驗證時間
  • • 防止資源耗盡攻擊
  • • 確保網路健康
  • • 保護較弱的節點

SigOps 限制

區塊限制

時期 限制 計算方式
Pre-SegWit 20,000 SigOps 傳統計數
Post-SegWit 80,000 SigOps Weight-based(最大 4 MWU)

SegWit 的 SigOps 限制與區塊重量成比例:每 50 weight units 最多 1 個 SigOp

計數規則

傳統 SigOps 計數(Legacy):
┌────────────────────────────────┬──────────┐
│ 操作                           │ SigOps   │
├────────────────────────────────┼──────────┤
│ OP_CHECKSIG                    │ 1        │
│ OP_CHECKSIGVERIFY              │ 1        │
│ OP_CHECKMULTISIG (最多 20 keys)│ 20       │
│ OP_CHECKMULTISIGVERIFY         │ 20       │
└────────────────────────────────┴──────────┘

SegWit SigOps 計數(更精確):
┌────────────────────────────────┬──────────┐
│ 操作                           │ SigOps   │
├────────────────────────────────┼──────────┤
│ OP_CHECKSIG (native SegWit)    │ 1        │
│ OP_CHECKMULTISIG (n-of-m)      │ n        │
│ (實際公鑰數量,不是最大值 20)   │          │
└────────────────────────────────┴──────────┘

Taproot(BIP-342):
- 每個 OP_CHECKSIG 或 OP_CHECKSIGADD 計 1 個 SigOp
- 沒有 CHECKMULTISIG(使用 CHECKSIGADD)

實現細節

SigOps 計數

enum Opcode {
  OP_CHECKSIG = 0xac,
  OP_CHECKSIGVERIFY = 0xad,
  OP_CHECKMULTISIG = 0xae,
  OP_CHECKMULTISIGVERIFY = 0xaf,
  OP_CHECKSIGADD = 0xba,  // Taproot
}

interface SigOpCount {
  legacy: number;
  segwit: number;
}

// 傳統 SigOps 計數(保守估計)
function countLegacySigOps(script: Buffer): number {
  let count = 0;
  let i = 0;

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

    switch (opcode) {
      case Opcode.OP_CHECKSIG:
      case Opcode.OP_CHECKSIGVERIFY:
        count += 1;
        break;

      case Opcode.OP_CHECKMULTISIG:
      case Opcode.OP_CHECKMULTISIGVERIFY:
        // 傳統計數:假設最大 20 個公鑰
        count += 20;
        break;
    }

    i++;
  }

  return count;
}

// SegWit SigOps 計數(精確計數)
function countSegWitSigOps(
  script: Buffer,
  witness: Buffer[]
): number {
  let count = 0;

  // 對於 P2WPKH,計 1 個 SigOp
  if (isP2WPKH(script)) {
    return 1;
  }

  // 對於 P2WSH,分析見證腳本
  if (isP2WSH(script)) {
    const witnessScript = witness[witness.length - 1];
    return countWitnessSigOps(witnessScript, witness);
  }

  return count;
}

function countWitnessSigOps(
  witnessScript: Buffer,
  witness: Buffer[]
): number {
  let count = 0;
  let i = 0;

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

    switch (opcode) {
      case Opcode.OP_CHECKSIG:
      case Opcode.OP_CHECKSIGVERIFY:
        count += 1;
        break;

      case Opcode.OP_CHECKMULTISIG:
      case Opcode.OP_CHECKMULTISIGVERIFY:
        // SegWit: 使用實際的公鑰數量
        const nKeys = getMultisigKeyCount(witnessScript, i);
        count += nKeys;
        break;
    }

    i++;
  }

  return count;
}

// 計算區塊的總 SigOps
function calculateBlockSigOps(block: Block): number {
  let totalSigOps = 0;

  for (const tx of block.transactions) {
    // Legacy SigOps(輸出腳本 + P2SH 贖回腳本)
    for (const output of tx.outputs) {
      totalSigOps += countLegacySigOps(output.scriptPubKey) * 4;
    }

    // SegWit SigOps
    for (let i = 0; i < tx.inputs.length; i++) {
      if (tx.witness && tx.witness[i]) {
        totalSigOps += countSegWitSigOps(
          tx.inputs[i].prevout.scriptPubKey,
          tx.witness[i]
        );
      }
    }
  }

  return totalSigOps;
}

攻擊向量

歷史攻擊

2015 年的 SigOps 攻擊

攻擊者創建了包含大量簽名操作的區塊,導致驗證時間長達 25 秒。 這暴露了早期 SigOps 計數規則的問題。

  • 攻擊者使用 OP_CHECKMULTISIG 的最壞情況
  • 舊規則按輸出腳本計數,不考慮實際簽名
  • P2SH 贖回腳本可以包含大量 CHECKMULTISIG

SegWit 修復

SegWit 的改進

  • 精確計數: CHECKMULTISIG 按實際公鑰數量計數,不再假設最大值
  • Weight-based 限制: SigOps 限制與區塊重量成比例
  • 見證數據折扣: 見證腳本的 SigOps 成本更準確

Taproot 中的 SigOps

Taproot 的變化

Taproot (BIP-342) SigOps 規則:

1. Key-path spend:
   - 只需要 1 個簽名
   - 計 0 個 SigOps(包含在基礎成本中)

2. Script-path spend:
   - 使用 OP_CHECKSIG 和 OP_CHECKSIGADD
   - 沒有 OP_CHECKMULTISIG
   - 每個 CHECKSIG/CHECKSIGADD 計 1 個 SigOp

3. 預算系統:
   - 每個輸入有 SigOps 預算
   - 預算 = 50 + (見證大小)
   - 超過預算的交易無效

OP_CHECKSIGADD

OP_CHECKSIGADD 取代 OP_CHECKMULTISIG

傳統多簽(2-of-3):
OP_2 <pk1> <pk2> <pk3> OP_3 OP_CHECKMULTISIG
→ 計 3 個 SigOps(舊規則計 20 個)

Taproot 多簽(2-of-3):
<pk1> OP_CHECKSIG
<pk2> OP_CHECKSIGADD
<pk3> OP_CHECKSIGADD
OP_2 OP_NUMEQUAL
→ 計 3 個 SigOps(準確反映實際工作量)

相關 RPC 命令

# 查看區塊的 SigOps
bitcoin-cli getblockstats  '["sigops"]'
# { "sigops": 12345 }

# 查看區塊模板的 SigOps
bitcoin-cli getblocktemplate '{"rules": ["segwit"]}' | jq '.sigopcost'

# 解碼交易查看腳本
bitcoin-cli decoderawtransaction  | jq '.vout[].scriptPubKey'

# 測試 mempool 接受
bitcoin-cli testmempoolaccept '[""]'

實際影響

交易設計考量

腳本類型 SigOps 說明
P2PKH 1 單簽名
P2WPKH 1 SegWit 單簽名
P2TR (key-path) 0 包含在基礎成本
2-of-3 多簽 (Legacy) 20 保守計數
2-of-3 多簽 (SegWit) 3 精確計數
2-of-3 多簽 (Taproot) 3 CHECKSIGADD

最佳實踐

✓ 推薦做法

  • • 使用 SegWit/Taproot 減少 SigOps
  • • 避免不必要的複雜腳本
  • • 考慮 MuSig2 替代多簽
  • • 測試交易在 SigOps 限制內

⚠ 注意事項

  • • Legacy 多簽 SigOps 成本高
  • • 大型多簽可能接近限制
  • • 某些礦池有更嚴格的限制
  • • SigOps 影響打包優先級

總結

  • DoS 保護:SigOps 限制防止區塊驗證時間過長
  • 區塊限制:SegWit 後最多 80,000 SigOps 每區塊
  • 精確計數:SegWit/Taproot 按實際簽名數量計數
  • 使用現代腳本:Taproot key-path 沒有額外 SigOps 成本
已複製連結
已複製到剪貼簿