比特幣腳本語言入門
理解 Bitcoin Script 的基本指令與運作原理。
比特幣腳本語言入門
比特幣腳本(Bitcoin Script)是比特幣交易輸出中嵌入的簡單程式語言,用於定義花費比特幣的條件。不同於以太坊的 Solidity 等圖靈完整的智慧合約語言,比特幣腳本刻意設計為圖靈不完備的語言,這是出於安全性的深思熟慮——簡單的語法意味著可預測的執行結果和更少的攻擊面。
腳本基礎
堆疊式語言
比特幣腳本是一種堆疊式語言(Stack-based language),類似於 FORTH 語言。指令從左到右執行,數據和運算子都推入堆疊,運算元從堆疊彈出,運算結果再推回堆疊。
堆疊操作示意:
初始狀態:[]
執行 <signature>:["signature"]
執行 <pubKey>:["signature", "pubKey"]
執行 OP_DUP:["signature", "pubKey", "pubKey"]
執行 OP_HASH160:["signature", "pubKey", "pubKeyHash"]
執行 <pubKeyHash>:["signature", "pubKey", "pubKeyHash", "pubKeyHash"]
執行 OP_EQUALVERIFY:["signature", "pubKey"]
執行 OP_CHECKSIG:[]
結果:TRUE(交易有效)
基本結構
比特幣腳本分為兩個部分:
- 鎖定腳本(ScriptPubKey):定義花費條件
- 解鎖腳本(ScriptSig):提供滿足條件的數據
典型 P2PKH 交易結構:
┌─────────────────────────────────────────────────────────────┐
│ 解鎖腳本(ScriptSig) │
│ <signature> <pubKey> │
│ │
│ 鎖定腳本(ScriptPubKey) │
│ OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG │
└─────────────────────────────────────────────────────────────┘
合併後完整腳本:
<signature> <pubKey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
常見指令詳解
數據指令
| 指令 | 說明 | 示例 |
|---|---|---|
OP_PUSHBYTES_1 到 OP_PUSHBYTES_75 | 推入 1-75 位元組 | OP_PUSHBYTES_20 推入 20 位元組 |
OP_PUSHDATA1 | 推入可變長度(1 字節長度前綴) | |
OP_PUSHDATA2 | 推入可變長度(2 字節長度前綴) | |
OP_PUSHDATA4 | 推入可變長度(4 字節長度前綴) | |
OP_1NEGATE | 推入 -1 | |
OP_1 到 OP_16 | 推入數字 1-16 | OP_1 = 1, OP_16 = 16 |
堆疊操作指令
| 指令 | 說明 |
|---|---|
OP_TOALTSTACK | 將堆疊頂部移到替代堆疊 |
OP_FROMALTSTACK | 從替代堆疊移回主堆疊 |
OP_DUP | 複製堆疊頂部 |
OP_DROP | 彈出並丟棄堆疊頂部 |
OP_SWAP | 交換堆疊頂部兩個元素 |
OP_ROT | 旋轉前三個元素 |
OP_OVER | 複製第二個元素到頂部 |
OP_PICK | 複製第 n 個元素到頂部 |
密碼學指令
| 指令 | 說明 |
|---|---|
OP_HASH160 | RIPEMD160(SHA256(x)) |
OP_SHA256 | SHA256 哈希 |
OP_RIPEMD160 | RIPEMD160 哈希 |
OP_CHECKSIG | 驗證 ECDSA 簽名 |
OP_CHECKSIGVERIFY | 驗證簽名並檢查結果 |
OP_CHECKMULTISIG | 驗證多簽名 |
OP_CHECKMULTISIGVERIFY | 驗證多簽名並檢查結果 |
流程控制指令
| 指令 | 說明 |
|---|---|
OP_IF | 如果條件為真,執行分支 |
OP_ELSE | 否則執行此分支 |
OP_ENDIF | 結束條件分支 |
OP_VERIFY | 如果堆疊頂部為 FALSE,終止腳本 |
OP_RETURN | 無條件終止腳本(標記為不可花費) |
時間鎖指令
| 指令 | 說明 |
|---|---|
OP_CHECKLOCKTIMEVERIFY(CLTV) | 絕對時間鎖:驗證 locktime |
OP_CHECKSEQUENCEVERIFY(CSV) | 相對時間鎖:驗證 sequence |
常見腳本類型
1. P2PK(Pay to Public Key)
最簡單的腳本類型,直接支付給公鑰。早期比特幣區塊使用此格式。
腳本格式:
<signature> <pubKey> OP_CHECKSIG
特點:
- 公鑰直接暴露在區塊鏈上
- 簽名驗證簡單
- 隱私性較差(公鑰一旦使用即暴露)
2. P2PKH(Pay to Public Key Hash)
支付給公鑰的哈希,更安全。這是比特幣早期最廣泛使用的格式。
腳本格式:
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
特點:
- 公鑰在花費時才暴露
- 只需 20 位元組的地址
- 支援大多數錢包
3. P2SH(Pay to Script Hash)
支付給腳本哈希,支援複雜的鎖定條件。多簽名錢包常用此格式。
腳本格式(鎖定):
OP_HASH160 <scriptHash> OP_EQUAL
腳本格式(解鎖):
<signature1> <signature2> <redeemScript>
特點:
- 允許複雜的兌現條件
- 發送方只需知道腳本哈希
- 適合多簽名、HTLC 等場景
4. P2WPKH(Pay to Witness Public Key Hash)
SegWit 升級後的格式,見證數據在隔離見證區塊中。節省區塊空間。
腳本格式:
OP_0 <pubKeyHash>
特點:
- 區塊容量增加
- 交易費用降低(每位元組費用相同,但數據更少)
- 解決交易延展性問題
5. P2WSH(Pay to Witness Script Hash)
SegWit 升級後的腳本哈希格式。用於更複雜的見證腳本。
腳本格式:
OP_0 <scriptHash>
特點:
- 支援更大的腳本(最多 3.6MB)
- 更安全的多簽名實現
6. P2TR(Pay to Taproot)
Taproot 升級引入,支援更複雜的腳本同時保持隱私。
腳本格式:
OP_1 <taproot_output_key>
特點:
- 支援 Schnorr 簽名
- 可以隱藏複雜的兌現條件
- 更低的交易費用(對於複雜腳本)
腳本執行過程深度解析
腳本驗證流程
比特幣腳本執行分為兩個階段:
驗證流程:
┌─────────────────────────────────────────────────────────────┐
│ 第一階段:腳本合併 │
│ ScriptSig + ScriptPubKey → 完整腳本 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第二階段:腳本執行 │
│ 從左到右執行每個指令 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第三階段:結果驗證 │
│ 堆疊非空且最後元素為非零 → 交易有效 │
└─────────────────────────────────────────────────────────────┘
完整示例:P2PKH 驗證過程
讓我們逐步追蹤 P2PKH 腳本的執行:
假設數據:
- signature: 30440220...
- pubKey: 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
- pubKeyHash: 62E907B15CBF27D5425399EBF6F0FB50EBB88F18
完整腳本:
30440220... 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
OP_DUP OP_HASH160 62E907B15CBF27D5425399EBF6F0FB50EBB88F18 OP_EQUALVERIFY OP_CHECKSIG
步驟執行:
步驟 | 指令 | 堆疊狀態
──────┼──────────────────┼────────────────────────────────
1 | (signature) | [sig]
2 | (pubKey) | [sig, pk]
3 | OP_DUP | [sig, pk, pk]
4 | OP_HASH160 | [sig, pk, pk_hash160]
5 | (pubKeyHash) | [sig, pk, pk_hash160, pkh]
6 | OP_EQUALVERIFY | [sig, pk] (相等則繼續,否則失敗)
7 | OP_CHECKSIG | [result] (TRUE = 交易有效)
比特幣腳本地址格式
地址前綴
不同腳本類型使用不同的地址前綴:
| 類型 | 前綴(Base58Check) | 示例 |
|---|---|---|
| P2PKH 主網 | 1 | 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2 |
| P2SH 主網 | 3 | 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy |
| P2WPKH 主網 | bc1(Bech32) | bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq |
| P2WSH 主網 | bc1 | bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3 |
腳本限制
故意限制
比特幣腳本是圖靈不完備的,這是刻意設計:
禁止的指令:
- OP_LOOP(循環)
- OP_JUMP(無條件跳轉)
- OP_JUMPI(條件跳轉)
- 任何形式的遞歸
為什麼如此設計?
1. 可預測的執行時間(無無限迴圈)
2. 簡化的資源計算
3. 更容易的靜態分析
4. 減少攻擊面
其他限制
| 限制類型 | 數值 |
|---|---|
| 最大腳本大小(鎖定腳本) | 10,000 位元組 |
| 最大腳本大小(P2WSH) | 3,600,000 位元組 |
| 每個指令最大運算單位 | 500,000 |
| 最大見證數據大小 | 10,000,000 位元組 |
| 多簽名最大金鑰數 | 15 個 |
進階應用
1. 多簽名腳本(Multisig)
2-of-3 多簽名:
OP_2 <pubKey1> <pubKey2> <pubKey3> OP_3 OP_CHECKMULTISIG
鎖定腳本:
OP_2 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
02A60F608D6D3E1E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E
03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
OP_3 OP_CHECKMULTISIG
需要任意 2 個私鑰簽名才能花費。
2. 時間鎖(Time Lock)
OP_CHECKLOCKTIMEVERIFY(CLTV)
絕對時間鎖,指定最早可花費的區塊高度或時間。
腳本示例:
< expiry_time > OP_CHECKLOCKTIMEVERIFY OP_DROP <pubKey> OP_CHECKSIG
說明:
- 只有在 locktime 到達後才能花費
- locktime 可以是區塊高度或 Unix 時間戳
OP_CHECKSEQUENCEVERIFY(CSV)
相對時間鎖,指定從該輸出被確認後經過的時間。
腳本示例:
< sequence > OP_CHECKSEQUENCEVERIFY OP_DROP <pubKey> OP_CHECKSIG
說明:
- 只能用於相對於花費輸入的時間
- 常用於閃電網路通道
3. HTLC(Hash Time Locked Contract)
用於閃電網路和原子交換,實現跨鏈交易。
HTLC 鎖定腳本:
OP_IF
OP_HASH160 <hash(R)> OP_EQUALVERIFY <receiver_pubkey> OP_CHECKSIG
OP_ELSE
<timeout> OP_CHECKSEQUENCEVERIFY OP_DROP <sender_pubkey> OP_CHECKSIG
OP_ENDIF
說明:
- 如果提供 R 的原像(preimage),接收者可以立即獲得資金
- 如果超時,原則者可以取回資金
4. 秘密揭露(Hash Puzzle)
簡單的密碼學謎題:
OP_HASH160 <hash> OP_EQUAL
解鎖時需要提供滿足 hash(x) = <hash> 的 x
5. 條件分支
根據不同條件有不同的兌現方式:
OP_IF
<condition_A_pubkey> OP_CHECKSIG
OP_ELSE
OP_IF
<condition_B_pubkey> OP_CHECKSIG
OP_ELSE
<condition_C_pubkey> OP_CHECKSIG
OP_ENDIF
OP_ENDIF
實際交易解析
讓我們看一個真實的比特幣交易來理解腳本實際運作:
交易 ID: 0a3b6f4e2b3e5c8a9f7d6e5c8a9b7d6e5c8a9f7d6e5c8a9b7d6e5c8a9b7d6e
輸入結構:
{
"txid": "previous_transaction_id",
"vout": 0,
"scriptSig": "30440220... 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"sequence": 0xffffffff
}
輸出結構:
{
"value": 0.5 BTC,
"scriptPubKey": "OP_DUP OP_HASH160 62E907B15CBF27D5425399EBF6F0FB50EBB88F18 OP_EQUALVERIFY OP_CHECKSIG"
}
說明:
- 花費 0.5 BTC
- 鎖定到 P2PKH 地址:62E907B15CBF27D5425399EBF6F0FB50EBB88F18
- 對應地址:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
實際腳本範例與驗證
使用 Bitcoin Core RPC 創建腳本
以下展示如何使用 Bitcoin Core 的 createmultisig RPC 創建多簽名腳本:
# 創建 2-of-3 多簽名地址
$ bitcoin-cli createmultisig 2 '
[
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"02A60F608D6D3E1E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E",
"03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
]
'
# 回應:
# {
# "address": "3Dyt1gQjf4KdFNoxDvhGM4vCrVB5KzS2W8",
# "redeemScript": "52210279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817982102A60F608D6D3E1E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E2103FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53AE"
# }
解碼腳本範例
使用 decodescript 解析腳本結構:
$ bitcoin-cli decodescript "52210279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817982102A60F608D6D3E1E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E2103FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53AE"
# 回應:
# {
# "asm": "OP_2 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 02A60F608D6D3E1E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E 03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF OP_3 OP_CHECKMULTISIG",
# "type": "multisig",
# "reqSigs": 2,
# "addresses": [
# "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
# "1DDiZ4kKq7q5zEwH5Z4kKq7q5zEwH5Z4kK",
# "1EQw5Z4kKq7q5zEwH5Z4kKq7q5zEwH5Z4kK"
# ],
# "p2sh": "3Dyt1gQjf4KdFNoxDvhGM4vCrVB5KzS2W8"
# }
P2PKH 腳本完整驗證流程
以下展示完整的 P2PKH 腳本建立與花費流程:
// 使用 libbitcoin 庫建構 P2PKH 交易的完整範例
#include <bitcoin/bitcoin.hpp>
int main() {
// 1. 定義私鑰(請勿在實際環境暴露私鑰)
bc::ec_secret secret = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
};
// 2. 從私鑰導出公鑰
bc::ec_point public_key = bc::secret_to_public_key(secret);
// 3. 公鑰 SHA256 + RIPEMD160 = 公鑰哈希
bc::data_chunk pubkey_data(public_key.begin(), public_key.end());
bc::data_hash sha256_hash = bc::sha256_hash(pubkey_data);
bc::short_hash pubkey_hash = bc::ripemd160_hash(sha256_hash);
// 4. 建立 P2PKH 鎖定腳本
// OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
bc::script lock_script = bc::script::to_pay_key_hash_pattern(pubkey_hash);
// 5. 解鎖腳本(花費時提供)
bc::script unlock_script = bc::script::to_sign(secret);
// 6. 組合完整輸入腳本
bc::script input_script = bc::script::to_pay_key_hash_pattern(
unlock_script, lock_script);
return 0;
}
原子交換(Atomic Swap)腳本範例
比特幣與萊特幣之間的原子交換使用 HTLC 腳本:
// 原子交換腳本範例(比特幣側)
// 接收者的鎖定腳本:
// 如果提供 R 的原像,接收者在 timeout 之前可以取走資金
// 如果超時,發送者可以取回資金
const char* atomic_swap_lock = R"(
OP_IF
OP_HASH160 <hash(R)> OP_EQUALVERIFY
<receiver_pubkey> OP_CHECKSIG
OP_ELSE
<timeout> OP_CHECKSEQUENCEVERIFY OP_DROP
<sender_pubkey> OP_CHECKSIG
OP_ENDIF
)";
// 接收者解鎖(提供 R 的原像):
// <signature> <R> 1 <redeemScript>
// 發送者解鎖(超時後):
// <signature> 0 <redeemScript>
時間鎖定存款合約
建立一個需要等待特定區塊數後才能提取的存款合約:
// CLTV 時間鎖定腳本
// 資金鎖定至特定時間
const char* timelock_contract = R"(
<expiry_time> OP_CHECKLOCKTIMEVERIFY OP_DROP
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
)";
// 其中 expiry_time 可使用區塊高度或 Unix 時間戳
// 區塊高度格式:OP_CHECKLOCKTIMEVERIFY 使用區塊高度
// Unix 時間戳格式:OP_CHECKLOCKTIMEVERIFY + OP_CLTV 可用時間戳
秘密揭露腳本
實現「只有你知道秘密才能花費」的条件:
// 秘密揭露腳本範例
// 鎖定腳本:只接受能夠揭露秘密的人
// OP_HASH160 <secret_hash> OP_EQUAL
// 步驟 1: 選擇一個秘密(256 位元組隨機數)
const char* secret = "a1b2c3d4e5f6..."; // 實際使用真正的隨機數
// 步驟 2: 計算 SHA256 哈希作為鎖定值
// SHA256(secret) = 3d4e5f6a1b2c3d4... (作為鎖定值)
// 步驟 3: 建立鎖定腳本
// OP_HASH160 3d4e5f6a1b2c3d4... OP_EQUAL
// 解鎖時:
// <secret> (提供秘密本身)
// 驗證過程:
// 1. 執行 OP_HASH160,計算 secret 的哈希
// 2. 與鎖定值比較
// 3. 相等則允許花費
閃電網路通道腳本
閃電網路使用 CSV 和 HTLC 實現雙向支付通道:
// 閃電網路 commitment 交易輸出腳本
// 延遲兌現(CSV):延遲後雙方都能兌現
OP_IF
<revocation_pubkey> OP_CHECKSIG
OP_ELSE
OP_IF
// 接收者可以立即提取(延遲後)
<relative_delay> OP_CHECKSEQUENCEVERIFY OP_DROP
<receiver_pubkey> OP_CHECKSIG
OP_ELSE
// 發送者可以延遲後提取
<relative_delay> OP_CHECKSEQUENCEVERIFY OP_DROP
<sender_pubkey> OP_CHECKSIG
OP_ENDIF
OP_ENDIF
// HTLC 輸出腳本:
// 提供 HTLC 的三種兌現方式:
// 1. 接收者提供 R 原像(立即)
// 2. 發送者超時後撤回
// 3. 雙方簽名(協商關閉)
OP_RETURN 使用場景
OP_RETURN 用於將不可花費的數據寫入區塊鏈:
// 使用 Bitcoin Core RPC 創建 OP_RETURN 輸出
// 1. 簡單 OP_RETURN(嵌入數據)
$ bitcoin-cli createrawtransaction '[]' '{
"data": "48656c6c6f20576f726c64"
}'
// data 是十六進制編碼:"Hello World"
// OP_RETURN <data>
// 2. 多重 OP_RETURN
$ bitcoin-cli createrawtransaction '[]' '{
"data": "48656c6c6f20576f726c64",
"data": "4c6f766520426974636f696e"
}'
腳本指紋識別
不同腳本類型有獨特的「指紋」模式:
# 腳本類型識別範例
def identify_script(script_hex):
"""根據腳本字節識別腳本類型"""
# P2PKH: OP_DUP OP_HASH160 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG
if script_hex[:6] == "76a914" and script_hex[-8:] == "88ac":
return "P2PKH"
# P2SH: OP_HASH160 <20 bytes> OP_EQUAL
if script_hex[:4] == "a914" and script_hex[-2:] == "87":
return "P2SH"
# P2WPKH: OP_0 <20 bytes>
if script_hex[:4] == "0014":
return "P2WPKH"
# P2WSH: OP_0 <32 bytes>
if script_hex[:4] == "0020":
return "P2WSH"
# OP_RETURN: OP_RETURN <data>
if script_hex[:2] == "6a":
return "OP_RETURN"
return "Unknown"
比特幣腳本與 BIP 標準
比特幣腳本的發展受到多個比特幣改進提案(BIP)的規範與推動。以下是與腳本相關的重要 BIP:
BIP-13:P2SH 地址格式
BIP-13 定義了 Pay to Script Hash(P2SH)地址格式,允許複雜的鎖定腳本:
# P2SH 地址生成示例
import hashlib
import base58
def create_p2sh_address(redeem_script):
"""從贖回腳本生成 P2SH 地址"""
# RIPEMD160(SHA256(redeem_script))
sha256_hash = hashlib.sha256(redeem_script).digest()
ripemd160 = hashlib.new('ripemd160')
ripemd160.update(sha256_hash)
script_hash = ripemd160.digest()
# 版本位元組:0x05(P2SH 主網)
versioned_payload = bytes([0x05]) + script_hash
# 校驗碼
checksum = hashlib.sha256(hashlib.sha256(versioned_payload).digest()).digest()[:4]
# Base58Check 編碼
return base58.b58encode(versioned_payload + checksum)
# 示例:2-of-3 多簽名腳本的 P2SH 地址
redeem_script = bytes.fromhex('52210279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817982102A60F608D6D3E1E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E9F3D7E2103FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53AE')
p2sh_address = create_p2sh_address(redeem_script)
print(f"P2SH 地址: {p2sh_address.decode()}")
# 輸出: 3Dyt1gQjf4KdFNoxDvhGM4vCrVB5KzS2W8
BIP-143:交易簽名哈希計算
BIP-143 標準化了 SegWit 交易的簽名哈希計算,解決了舊標準的多個問題:
# BIP-143 簽名哈希計算示例
import hashlib
def bip143_sighash(tx_in_index, tx_out_value, script_code, hash_type, tx_to_spend, tx_to_sign):
"""BIP-143 標準化的簽名哈希計算"""
# 1. nVersion (4 bytes, little-endian)
hash_prevouts = hashlib.sha256(hashlib.sha256(tx_to_spend['prevouts']).digest()).digest()
# 2. nHashType (4 bytes, little-endian)
# 3. hashSequence
hash_sequence = hashlib.sha256(hashlib.sha256(tx_to_spend['sequences']).digest()).digest()
# 4. outpoint (36 bytes: 32-byte txid + 4-byte vout)
outpoint = tx_to_spend['txid'] + tx_to_spend['vout'].to_bytes(4, 'little')
# 5. scriptCode (variable length)
script_code_len = len(script_code).to_bytes(1, 'little')
# 6. amount (8 bytes, little-endian)
amount = tx_out_value.to_bytes(8, 'little')
# 7. nSequence (4 bytes, little-endian)
sequence = tx_to_spend['sequence'].to_bytes(4, 'little')
# 8. hashOutputs
hash_outputs = hashlib.sha256(hashlib.sha256(tx_to_sign['outputs']).digest()).digest()
# 9. nLocktime (4 bytes, little-endian)
locktime = tx_to_sign['locktime'].to_bytes(4, 'little')
# 10. sighash type
sighash_type = hash_type.to_bytes(4, 'little')
# 組合所有組件
data = (tx_to_sign['version'].to_bytes(4, 'little') +
hash_prevouts +
hash_sequence +
outpoint +
script_code_len +
script_code +
amount +
sequence +
hash_outputs +
locktime +
sighash_type)
return hashlib.sha256(hashlib.sha256(data).digest()).digest()
BIP-341 與 BIP-342:Taproot 腳本
Taproot 升級(2021年11月)引入 BIP-341(Taproot)和 BIP-342(Tapscript),這是比特幣腳本語言的重大演進:
# Taproot 地址生成示例
import hashlib
def taproot_tweak_public_key(public_key, script_tree=None):
"""根據 BIP-341 生成 Taproot 內部金鑰"""
# 如果有腳本樹,計算腳本樹的哈希
if script_tree:
script_tree_hash = hashlib.sha256(script_tree).digest()
else:
script_tree_hash = b'\x00' * 32 # 空腳本
# 計算調整後的金鑰
tweak = hashlib.sha256(public_key + script_tree_hash).digest()
tweaked_key = int.from_bytes(public_key, 'big') + int.from_bytes(tweak, 'big')
tweaked_key = tweaked_key % 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
return tweaked_key.to_bytes(32, 'big')
# Taproot 地址(bech32m 編碼)
# 輸出格式:OP_1 <tweaked_public_key>
# 以 bc1p 開頭
BIP-47:可重用支付代碼
BIP-47 定義了可重用支付代碼,允許用戶公開單一地址進行多次付款,同時保護隱私:
# BIP-47 支付代碼處理示例
import hashlib
import hmac
def derive_payment_code_private_key(payment_code, chain_code, index):
"""從支付代碼派生出子私鑰"""
# BIP-47 使用 ECIA(Elliptic Curve Instantiation)
# 基於 BIP-32 HD 錢包標準
data = payment_code + index.to_bytes(4, 'big')
h = hmac.new(chain_code, data, hashlib.sha512).digest()
# 返回子私鑰
child_private_key = int.from_bytes(h[:32], 'big')
child_chain_code = h[32:]
return child_private_key, child_chain_code
腳本執行環境與網路協議
比特幣腳本不僅是交易驗證的核心,還與比特幣網路協議密切互動。
腳本驗證的網路傳播
當用戶廣播一筆比特幣交易時,腳本的執行涉及以下網路協議層:
比特幣腳本網路傳播流程:
┌─────────────────────────────────────────────────────────────────┐
│ 第一層:交易創建 │
│ 節點 A 創建交易並使用私鑰簽名 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 第二層:交易驗證(本地) │
│ 節點 A 執行腳本驗證: │
│ - 輸入腳本 ScriptSig 解碼 │
│ - 輸出腳本 ScriptPubKey 解析 │
│ - 組合腳本並執行 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 第三層:INV 消息傳播 │
│ 驗證通過後,節點 A 向相鄰節點發送 INV 消息 │
│ 包含交易哈希(txid) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 第四層:GETDATA 請求 │
│ 收到 INV 的節點請求完整交易數據 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 第五層:TX 消息傳播 │
│ 完整交易數據傳播到網路各節點 │
│ 每個節點獨立驗證腳本 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 第六層:記憶池儲存 │
│ 驗證通過的交易進入記憶池,等待礦工打包 │
└─────────────────────────────────────────────────────────────────┘
腳本大小限制與區塊空間
比特幣區塊的容量限制直接影響腳本複雜度:
| 腳本類型 | 最大大小 | 每筆輸入所需空間 |
|---|---|---|
| P2PKH | 25 bytes | ~148 vbytes |
| P2SH | 520 bytes | ~148 vbytes |
| P2WPKH | 25 bytes | ~68 vbytes |
| P2WSH | 10,000 bytes | ~64 vbytes + 4 vbytes per 50 bytes |
| P2TR | 40 bytes | ~57.5 vbytes |
腳本運算單位(SigOps)限制
比特幣對腳本中的簽名操作數量有嚴格限制:
# SigOps 限制計算示例
def calculate_sigops(script):
"""計算腳本中的簽名操作數量"""
sigops_count = 0
op_codes = {
'OP_CHECKSIG': 1,
'OP_CHECKSIGVERIFY': 1,
'OP_CHECKMULTISIG': 1, # 實際每多簽名中的 N 個公鑰算 N
'OP_CHECKMULTISIGVERIFY': 1,
}
# 解析腳本字節
i = 0
while i < len(script):
op = script[i]
if op in [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10]:
# OP_1 到 OP_16(多簽名中的閾值)
if i > 0 and script[i-1] in range(0x51, 0x61):
sigops_count += script[i-1] - 0x50
i += 1
return sigops_count
Miniscript:結構化比特幣腳本
Miniscript 是一種在比特幣腳本之上的抽象層,允許以更高層級的方式表達腳本邏輯:
# Miniscript 表達式示例
# 簡單的 timelock:
# and_v(v:pkh(sender), older(144))
# 多簽名:
# thresh(2,pk(key1),pk(key2),pk(key3))
# 帶時間鎖的多簽名:
# andor(thresh(2,pk(key1),pk(key2)), older(1000), pk(recovery_key))
# 比特幣核心的 miniscript 解析:
# $ bitcoin-cli decodepsbt <psbt>
Miniscript 的優勢包括:
- 可組合性:小型腳本可組合成複雜邏輯
- 可分析性:可靜態分析腳本的語義
- 可升級性:腳本結構與時序無關
腳本安全注意事項
常見錯誤與防範
// 1. 金額驗證缺失(導致灰塵攻擊)
// 錯誤:
// OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
// 正確:檢查輸入金額
// <amount> OP_GREATERTHANVERIFY OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
// 2. 時間鎖使用錯誤
// 錯誤:locktime 類型混淆
// CLTV 需要交易別的 locktime 設置
// 正確:
// - OP_CHECKLOCKTIMEVERIFY 使用 nLockTime
// - OP_CHECKSEQUENCEVERIFY 使用 nSequence
// 3. 多簽名密鑰數限制
// 比特幣限制:最多 15 個公鑰
// OP_2 ... OP_16 OP_CHECKMULTISIG
// 4. 腳本大小限制
// - P2PKH: 最大 25 bytes
// - P2PK: 最大 35 bytes (compressed pubkey)
// - P2SH: 最大 520 bytes (推入的 redeemScript)
// - P2WSH: 最大 3,600,000 bytes
腳本驗證最佳實踐
// Bitcoin Core 腳本驗證流程
// 1. 語法檢查
bool check_script_syntax(const CScript& script) {
// 檢查無效 opcode
// 檢查編碼錯誤
// 檢查堆疊溢出風險
}
// 2. 腳本標誌驗證
unsigned int flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC |
SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_MINIMALDATA;
// 3. 執行腳本
bool result = EvalScript(stack, script, flags, checker, &serror);
// 4. 結果驗證
if (!result || stack.empty() || !CastToBool(stack.back())) {
return false; // 腳本驗證失敗
}
結論
比特幣腳本是一種安全、簡單的程式語言,專為比特幣交易設計。雖然功能有限,但足以實現各種支付條件:
- P2PKH:基礎支付
- P2SH:複雜條件(多簽名)
- P2WPKH/P2WSH:SegWit 升級
- P2TR:Taproot 升級
圖靈不完備的設計確保了腳本執行是可預測的,這也是比特幣安全性的重要基礎。理解比特幣腳本是深入理解比特幣運作機制的關鍵步驟。
本文包含
相關文章
- P2PKH 與 P2SH 腳本類型 — 從 Pay to Public Key Hash 到 Pay to Script Hash 的演進。
- 比特幣腳本協定層級深度解析 — 從密碼學證明到實際實現,深入分析比特幣腳本語言的數學基礎、協定規範與安全特性。
- Miniscript 應用完全指南 — 理解比特幣腳本的高級表示法 Miniscript,包括語法、類型系統與實際應用場景。
- 比特幣腳本語言深度教學:P2TR、P2WSH 與進階腳本 — 深入探討比特幣腳本語言的進階主題,涵蓋 Pay-to-Taproot(P2TR)、Pay-to-Witness-Script-Hash(P2WSH)的運作原理與實際應用,以及現代比特幣腳本的最新發展。
- 隔離見證 (SegWit) 技術解析 — 解決比特幣延展性問題的關鍵升級。
延伸閱讀與來源
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!