高級
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 要求使用壓縮格式的公鑰
已複製連結