高級
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 成本
已複製連結