跳至主要內容
高級

Witness Program

深入了解 SegWit 見證程式的結構、版本和執行規則。

12 分鐘

Witness Program(見證程式)是 SegWit 輸出的核心組成部分。它定義了如何驗證 花費該輸出的見證數據。不同版本的見證程式支持不同的功能。

見證程式結構

見證程式由版本號和數據組成:

scriptPubKey 格式:
[version] [witness_program]

version: OP_0 到 OP_16 (0-16)
witness_program: 2-40 bytes

示例:
v0 P2WPKH: OP_0 [20-byte pubkey_hash]
v0 P2WSH:  OP_0 [32-byte script_hash]
v1 Taproot: OP_1 [32-byte tweaked_pubkey]

版本 0:P2WPKH 和 P2WSH

P2WPKH(Pay to Witness Public Key Hash)

scriptPubKey:
OP_0 [20-byte hash160(pubkey)]

witness:
[signature] [pubkey]

驗證過程:
1. 檢查 witness 有 2 個元素
2. 構建隱式腳本: OP_DUP OP_HASH160 <hash> OP_EQUALVERIFY OP_CHECKSIG
3. 用 witness 數據執行腳本

地址格式: bc1q... (bech32)

P2WSH(Pay to Witness Script Hash)

scriptPubKey:
OP_0 [32-byte sha256(script)]

witness:
[...script_args] [script]

驗證過程:
1. 取 witness 最後一個元素作為 script
2. 檢查 SHA256(script) == witness_program
3. 用其他 witness 元素執行 script

地址格式: bc1q... (bech32, 更長)

版本 1:Taproot

Taproot(BIP-341)引入了版本 1 見證程式:

scriptPubKey:
OP_1 [32-byte tweaked_pubkey]

// Key path spend (僅簽名)
witness:
[schnorr_signature]

// Script path spend
witness:
[...script_args] [script] [control_block]

control_block:
[leaf_version | parity] [internal_pubkey] [merkle_path...]

地址格式: bc1p... (bech32m)

見證程式驗證規則

bool VerifyWitnessProgram(
    const CScriptWitness& witness,
    int witversion,
    const std::vector& program,
    unsigned int flags,
    const BaseSignatureChecker& checker,
    ScriptError* serror
) {
    if (witversion == 0) {
        if (program.size() == 20) {
            // P2WPKH
            if (witness.stack.size() != 2) {
                return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
            }
            // 構建並執行 P2PKH 腳本
            CScript scriptPubKey;
            scriptPubKey << OP_DUP << OP_HASH160 << program
                         << OP_EQUALVERIFY << OP_CHECKSIG;
            return ExecuteWitnessScript(witness.stack, scriptPubKey, ...);
        }
        else if (program.size() == 32) {
            // P2WSH
            if (witness.stack.empty()) {
                return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_EMPTY);
            }
            // 驗證腳本雜湊
            const std::vector& script = witness.stack.back();
            if (SHA256(script) != program) {
                return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
            }
            return ExecuteWitnessScript(witness.stack, CScript(script), ...);
        }
        return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH);
    }
    else if (witversion == 1) {
        // Taproot (BIP-341)
        if (program.size() == 32) {
            return VerifyTaprootCommitment(witness, program, ...);
        }
        return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH);
    }
    else if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) {
        // 拒絕未知版本(可升級性)
        return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM);
    }

    // 未知版本,任何人可花費(軟分叉升級空間)
    return true;
}

嵌套 SegWit(P2SH-Wrapped)

為了向後兼容,見證程式可以嵌套在 P2SH 中:

// P2SH-P2WPKH
scriptPubKey: OP_HASH160 [20-byte hash] OP_EQUAL
redeemScript: OP_0 [20-byte pubkey_hash]
witness: [signature] [pubkey]

// 地址格式: 3... (傳統 P2SH 格式)

// 驗證過程:
1. P2SH 驗證: HASH160(redeemScript) == hash
2. redeemScript 是見證程式,觸發 SegWit 驗證
3. 按 v0 P2WPKH 規則驗證 witness

見證版本升級

見證程式設計支持軟分叉升級:

版本 狀態 用途
v0 已啟用 P2WPKH, P2WSH
v1 已啟用 Taproot
v2-v16 保留 未來升級

軟分叉機制:舊節點將未知版本視為「任何人可花費」, 因此新規則可以通過軟分叉添加,無需所有節點升級。

創建見證輸出

import hashlib

def create_p2wpkh_script(pubkey_bytes):
    """創建 P2WPKH 輸出腳本"""
    pubkey_hash = hash160(pubkey_bytes)
    return bytes([0x00, 0x14]) + pubkey_hash  # OP_0 PUSH20

def create_p2wsh_script(witness_script):
    """創建 P2WSH 輸出腳本"""
    script_hash = hashlib.sha256(witness_script).digest()
    return bytes([0x00, 0x20]) + script_hash  # OP_0 PUSH32

def create_p2tr_script(tweaked_pubkey):
    """創建 Taproot 輸出腳本"""
    return bytes([0x51, 0x20]) + tweaked_pubkey  # OP_1 PUSH32

def hash160(data):
    """RIPEMD160(SHA256(data))"""
    sha = hashlib.sha256(data).digest()
    ripemd = hashlib.new('ripemd160', sha).digest()
    return ripemd

RPC 查詢

# 解碼地址
bitcoin-cli validateaddress bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
# 返回 witness_version 和 witness_program

# 獲取交易詳情
bitcoin-cli getrawtransaction <txid> true | jq '.vout[].scriptPubKey'

# 創建 SegWit 地址
bitcoin-cli getnewaddress "" bech32    # v0
bitcoin-cli getnewaddress "" bech32m   # v1 (Taproot)

腳本執行環境

不同見證版本有不同的腳本執行規則:

// v0 見證程式
- 使用 BIP-143 簽名雜湊
- 腳本大小限制: 10,000 bytes
- 堆棧元素限制: 520 bytes
- SigOps 按見證規則計算

// v1 Taproot
- 使用 BIP-341 簽名雜湊 (tagged hash)
- Schnorr 簽名 (64 bytes,無 sighash 標誌時)
- 腳本路徑使用 Tapscript (BIP-342)
- OP_SUCCESS 操作碼 (未來升級)
- 更高效的批量驗證

安全考慮

  • 程式長度:v0 必須是 20 或 32 bytes,否則無效
  • P2WPKH 驗證:必須恰好提供 2 個 witness 元素
  • 腳本雜湊:P2WSH 使用 SHA256(不是 HASH160)以防止長度擴展攻擊
  • 壓縮公鑰:v0 要求使用壓縮格式的公鑰
已複製連結
已複製到剪貼簿