跳至主要內容

Bitcoin Script 實戰教學(一):基礎概念與堆疊操作

3 分鐘閱讀
Bitcoin Script 實戰教學(一):基礎概念與堆疊操作

這是「Bitcoin Script 實戰教學」系列的第一篇。本系列將從零開始,深入探索比特幣的腳本語言,理解交易如何被驗證和執行。

系列文章導航:


一、什麼是 Bitcoin Script?

1.1 從一個常見的誤解說起

許多人認為比特幣只能進行簡單的「A 發送給 B」這樣的轉帳操作。事實上,比特幣從一開始就內建了一套完整的程式語言——Bitcoin Script。這套語言讓比特幣不僅僅是數位現金,而是一個可程式化的貨幣系統。

中本聰在設計比特幣時,刻意選擇了一種受限但強大的腳本語言。這個選擇反映了一個核心設計哲學:安全性優先於功能性。比特幣網路處理的是真實的金錢,任何程式錯誤都可能導致資金永久損失,因此語言的可預測性和安全性至關重要。

1.2 Script 的本質:花費條件的定義

要理解 Bitcoin Script,首先需要理解比特幣的 UTXO(未花費交易輸出)模型。每一筆比特幣實際上都被「鎖」在一個 UTXO 中,而 Script 就是定義「如何解鎖」這筆錢的規則。

想像一個保險箱:

  • 鎖定腳本(ScriptPubKey):定義打開保險箱需要什麼條件,例如「需要持有特定私鑰的人簽名」
  • 解鎖腳本(ScriptSig):提供滿足條件的證據,例如「這是我的數位簽名」

當有人想花費一筆比特幣時,網路中的每個節點都會執行這段腳本,驗證解鎖條件是否被滿足。如果腳本執行成功(返回 TRUE),交易就是有效的;否則,交易會被拒絕。

1.3 為什麼 Script 故意設計成「不完整」?

Bitcoin Script 是非圖靈完備的,這意味著它沒有迴圈指令,無法執行無限次的運算。這是刻意的設計,原因有三:

第一,防止拒絕服務攻擊。 如果腳本可以無限迴圈,攻擊者可以創建一個永遠不會結束的腳本,讓驗證節點陷入死迴圈。在一個去中心化網路中,每個節點都必須驗證每筆交易,這種攻擊會癱瘓整個網路。

第二,確保可預測性。 沒有迴圈意味著腳本的執行時間可以被準確估計。節點可以在執行前就知道這個腳本大約需要多少運算資源,從而進行合理的資源分配。

第三,簡化安全審計。 一個非圖靈完備的語言更容易被完整分析。開發者可以更有信心地確認腳本會按預期執行,不會有隱藏的邏輯錯誤。

這種設計哲學與以太坊形成鮮明對比。以太坊選擇了圖靈完備的智能合約語言,獲得了更大的靈活性,但也因此面臨更多的安全挑戰(如著名的 The DAO 攻擊)。比特幣則選擇了保守路線,用功能的限制換取更高的安全性。

1.4 Script 能做什麼?

雖然 Script 有諸多限制,但它的能力遠超大多數人的想像:

基本支付:最常見的用途,要求接收者用對應的私鑰簽名才能花費。

多重簽名:要求多個人中的 M 個人同時簽名才能花費,例如「3 個董事中的 2 個必須同意」。這對公司資金管理、託管服務等場景非常有用。

時間鎖定:規定資金必須在特定時間後才能被花費。可以用於遺產規劃(「我死後 1 年,我的孩子可以取得這筆錢」)或定期解鎖的投資。

哈希鎖定:要求提供某個哈希值的原像才能花費。這是閃電網路和跨鏈原子交換的基礎。

條件支付:根據不同條件執行不同的花費邏輯,例如「Alice 可以立即花費,或者 Bob 在一週後可以花費」。

這些基本元件可以組合成更複雜的合約,實現去中心化託管、支付通道、甚至簡單的預測市場。


二、堆疊機模型

2.1 什麼是堆疊?

Bitcoin Script 使用一種稱為「堆疊機」的執行模型。如果你沒有接觸過這個概念,可以把堆疊想像成一疊盤子:

  • 放盤子(PUSH):你只能把新盤子放在最上面
  • 取盤子(POP):你只能從最上面拿走盤子
  • 後進先出(LIFO):最後放上去的盤子會最先被拿走

這種結構看似簡單,卻足以實現所有 Script 需要的運算。事實上,許多早期的計算機和計算器都使用堆疊式架構,因為它實現簡單且不容易出錯。

2.2 Script 的執行過程

當比特幣節點驗證一筆交易時,它會按照以下步驟執行腳本:

  1. 初始化空堆疊:開始時,堆疊是空的
  2. 從左到右讀取腳本:依序處理每個元素
  3. 遇到數據就推入堆疊:數字、公鑰、簽名等都會被放到堆疊頂端
  4. 遇到操作碼就執行操作:操作碼會從堆疊取出數據、進行運算、再把結果放回堆疊
  5. 檢查最終結果:執行完畢後,如果堆疊頂端是非零值(TRUE),腳本就成功了

讓我們用一個簡單的數學運算來說明這個過程:

1
2
3
腳本:2 3 OP_ADD 5 OP_EQUAL

這段腳本的意思是:「2 加 3 等於 5 嗎?」

執行過程:

步驟 1:讀取到數字 2,推入堆疊

1
堆疊:[2]

步驟 2:讀取到數字 3,推入堆疊

1
堆疊:[2, 3]  ← 3 在最上面

步驟 3:讀取到 OP_ADD,這個操作碼會:

  • 從堆疊彈出兩個數字(3 和 2)
  • 計算它們的和(5)
  • 把結果推回堆疊
    1
    
    堆疊:[5]
    

步驟 4:讀取到數字 5,推入堆疊

1
堆疊:[5, 5]

步驟 5:讀取到 OP_EQUAL,這個操作碼會:

  • 彈出兩個值
  • 比較它們是否相等
  • 如果相等推入 TRUE,否則推入 FALSE
    1
    
    堆疊:[TRUE]
    

結果:堆疊頂端是 TRUE,腳本執行成功!

2.3 主堆疊與輔助堆疊

Bitcoin Script 實際上有兩個堆疊:

主堆疊(Main Stack):所有主要運算都在這裡進行。上面的例子就是在主堆疊上執行的。

輔助堆疊(Alt Stack):一個臨時存儲區,用於暫存數據。當腳本邏輯比較複雜,需要暫時「保存」某個值稍後再用時,可以把它移到輔助堆疊。

兩個操作碼用於在堆疊間移動數據:

  • OP_TOALTSTACK:把主堆疊頂端的值移到輔助堆疊
  • OP_FROMALTSTACK:把輔助堆疊頂端的值移回主堆疊

這個機制讓複雜的腳本邏輯成為可能,同時保持了語言的簡潔性。


三、操作碼詳解

操作碼(Opcode)是 Script 的指令集。每個操作碼都是一個單字節的數值,對應一個特定的操作。Bitcoin Script 定義了大約 100 個操作碼,但其中一部分已被禁用(出於安全考慮)。

3.1 常量與數據推送

最基本的操作是把數據推入堆疊。Script 提供了多種方式:

小數字(0-16):有專門的操作碼 OP_0OP_16,只佔用 1 個字節。這是最節省空間的方式。

1
2
3
OP_0  (0x00):推入空字節數組,在布林運算中視為 FALSE
OP_1  (0x51):推入數字 1,也稱為 OP_TRUE
OP_2  (0x52) 到 OP_16 (0x60):推入對應的數字

任意數據推送:對於更長的數據(如公鑰、簽名),使用數據推送指令:

1
2
3
4
5
6
0x01 - 0x4b:直接指定後面跟隨的字節數
             例如 0x14 表示「後面 20 個字節是數據」

OP_PUSHDATA1 (0x4c):後面 1 個字節指定數據長度(最多 255 字節)
OP_PUSHDATA2 (0x4d):後面 2 個字節指定數據長度(最多 65535 字節)
OP_PUSHDATA4 (0x4e):後面 4 個字節指定數據長度(理論上可以很大)

一個有趣的細節是,比特幣要求使用最短的可能編碼。如果你用 OP_PUSHDATA2 來推送一個只有 10 字節的數據,交易會被視為非標準的。這個規則防止了交易膨脹和某些類型的攻擊。

3.2 堆疊操作

這類操作碼用於操縱堆疊本身,不涉及數學運算:

OP_DUP(複製):複製堆疊頂端的元素。這是最常用的操作之一,因為很多時候我們需要同時保留原值並對其進行運算。

1
2
3
執行前:[A, B, C]
OP_DUP
執行後:[A, B, C, C]

OP_DROP(丟棄):移除堆疊頂端的元素。當某個值已經用完,不再需要時使用。

1
2
3
執行前:[A, B, C]
OP_DROP
執行後:[A, B]

OP_SWAP(交換):交換堆疊頂端的兩個元素。

1
2
3
執行前:[A, B, C]
OP_SWAP
執行後:[A, C, B]

OP_ROT(旋轉):將第三個元素移到頂端。

1
2
3
執行前:[A, B, C]
OP_ROT
執行後:[B, C, A]

這些基本操作組合起來,可以實現對堆疊的任意重排。雖然看起來原始,但對於腳本的表達能力已經足夠。

3.3 算術運算

Script 支援基本的整數運算,但有一些重要限制:

支援的操作

  • OP_ADD:加法
  • OP_SUB:減法
  • OP_1ADD:加 1(比 1 OP_ADD 更節省空間)
  • OP_1SUB:減 1
  • OP_NEGATE:取負數
  • OP_ABS:取絕對值
  • OP_MIN:取兩數中較小的
  • OP_MAX:取兩數中較大的

被禁用的操作

  • OP_MUL:乘法
  • OP_DIV:除法
  • OP_MOD:取模
  • 以及一些位運算

這些操作在 2010 年被禁用,原因是它們的實現存在漏洞,可能被利用來進行攻擊。雖然修復這些漏洞在技術上是可能的,但比特幣社群選擇了保守的做法——直接禁用它們。畢竟,絕大多數腳本不需要乘除法。

數值範圍:Script 中的數字是有符號的,使用變長編碼。實際上,大多數運算被限制在 4 字節(約 ±21 億)範圍內。這足以處理比特幣的金額(最大約 21 億聰)。

3.4 密碼學操作

這是 Bitcoin Script 最核心的部分,實現了交易安全性的基礎:

哈希函數

1
2
3
4
5
OP_RIPEMD160:計算 RIPEMD-160 哈希(160 位元輸出)
OP_SHA1:計算 SHA-1 哈希(已知不安全,但仍保留)
OP_SHA256:計算 SHA-256 哈希
OP_HASH160:先 SHA-256 再 RIPEMD-160(用於生成地址)
OP_HASH256:兩次 SHA-256(用於交易 ID 等)

OP_HASH160 特別重要,因為它是比特幣地址生成的核心。公鑰經過 OP_HASH160 處理後,再加上版本前綴和校驗和,就成為我們熟悉的比特幣地址。

簽名驗證

1
2
3
4
OP_CHECKSIG:驗證單個簽名
OP_CHECKSIGVERIFY:驗證簽名,失敗則中止(相當於 OP_CHECKSIG OP_VERIFY)
OP_CHECKMULTISIG:驗證多重簽名
OP_CHECKMULTISIGVERIFY:驗證多重簽名,失敗則中止

OP_CHECKSIG 是整個 Script 系統的核心。它從堆疊彈出一個簽名和一個公鑰,然後驗證這個簽名是否是用對應的私鑰對當前交易簽署的。這個操作確保了只有私鑰的持有者才能花費對應的比特幣。

3.5 流程控制

雖然 Script 沒有迴圈,但它有條件分支:

1
2
3
4
OP_IF:如果堆疊頂端非零,執行後面的程式碼
OP_NOTIF:如果堆疊頂端為零,執行後面的程式碼
OP_ELSE:否則執行這部分
OP_ENDIF:結束條件區塊

這讓我們可以創建「根據情況選擇不同花費路徑」的腳本。例如:

1
2
3
4
5
OP_IF
  <Alice 的公鑰> OP_CHECKSIG
OP_ELSE
  <Bob 的公鑰> OP_CHECKSIG
OP_ENDIF

這個腳本的意思是:如果提供 TRUE,就驗證 Alice 的簽名;如果提供 FALSE,就驗證 Bob 的簽名。花費者可以選擇用哪條路徑來解鎖資金。

OP_VERIFY 是另一個重要的流程控制操作碼。它檢查堆疊頂端是否為 TRUE:如果是,就移除它並繼續;如果不是,整個腳本立即失敗。這讓我們可以在腳本中設置多個「檢查點」。


四、腳本的組合與執行

4.1 交易中的兩部分腳本

一筆比特幣交易涉及兩部分腳本:

鎖定腳本(ScriptPubKey / 輸出腳本)

  • 存在於交易的輸出中
  • 定義「解鎖條件」
  • 創建 UTXO 時就已確定
  • 通常由接收者的地址決定

解鎖腳本(ScriptSig / 輸入腳本)

  • 存在於交易的輸入中
  • 提供「解鎖證明」
  • 花費時才創建
  • 通常包含簽名和公鑰

當有人想花費一個 UTXO 時,他需要創建一筆新交易,其中輸入部分包含解鎖腳本。網路節點會把解鎖腳本和鎖定腳本組合起來執行,驗證花費是否合法。

4.2 驗證過程的演化

在比特幣早期,驗證過程是直接把兩個腳本串接起來執行:

1
組合腳本 = ScriptSig + ScriptPubKey

但這種方式存在安全隱患。攻擊者可以構造特殊的 ScriptSig,在執行完後留下一個 TRUE 在堆疊上,無論 ScriptPubKey 是什麼都能通過驗證。

因此,現在的驗證過程改為分開執行

  1. 先執行 ScriptSig,記錄堆疊狀態
  2. 用這個堆疊狀態作為初始狀態,執行 ScriptPubKey
  3. 如果最終堆疊頂端是 TRUE 且沒有其他問題,驗證成功

這種方式確保了 ScriptPubKey 的邏輯不會被 ScriptSig 繞過。

4.3 實例:P2PKH 的完整驗證

讓我們走過一個最常見的腳本類型 —— P2PKH(Pay to Public Key Hash)的完整驗證過程。

假設 Alice 要花費一筆之前收到的比特幣:

鎖定腳本(之前的輸出中)

1
OP_DUP OP_HASH160 <Alice的公鑰哈希> OP_EQUALVERIFY OP_CHECKSIG

解鎖腳本(Alice 創建的)

1
<Alice的簽名> <Alice的公鑰>

驗證過程

階段一:執行解鎖腳本

步驟 1:推入 Alice 的簽名

1
堆疊:[sig]

步驟 2:推入 Alice 的公鑰

1
堆疊:[sig, pubkey]

階段二:執行鎖定腳本

步驟 3:OP_DUP - 複製公鑰

1
堆疊:[sig, pubkey, pubkey]

步驟 4:OP_HASH160 - 對頂端公鑰計算哈希

1
堆疊:[sig, pubkey, pubkey_hash]

步驟 5:推入預期的公鑰哈希

1
堆疊:[sig, pubkey, pubkey_hash, expected_hash]

步驟 6:OP_EQUALVERIFY - 比較兩個哈希

  • 如果相等:移除這兩個值,繼續執行
  • 如果不等:立即失敗
    1
    
    堆疊:[sig, pubkey]
    

步驟 7:OP_CHECKSIG - 驗證簽名

  • 用 pubkey 驗證 sig 是否是對當前交易的有效簽名
  • 如果有效,推入 TRUE
  • 如果無效,推入 FALSE
    1
    
    堆疊:[TRUE]
    

最終結果:堆疊頂端是 TRUE,交易有效!

這個過程展示了 P2PKH 的設計邏輯:

  1. 先檢查提供的公鑰是否與預期的哈希匹配(確認是正確的人)
  2. 再檢查簽名是否有效(確認他確實授權了這筆交易)

兩層驗證缺一不可,這就是為什麼只有掌握私鑰的人才能花費對應的比特幣。


五、調試與實驗

5.1 使用 btcdeb 學習腳本

理論學習很重要,但實際操作更能加深理解。btcdeb 是一個專門用於調試 Bitcoin Script 的工具,可以讓你逐步執行腳本,觀察堆疊的變化。

安裝 btcdeb

1
2
3
4
5
6
7
# 在 macOS 或 Linux 上
git clone https://github.com/bitcoin-core/btcdeb.git
cd btcdeb
./autogen.sh
./configure
make
sudo make install

基本使用

1
2
# 執行一個簡單的腳本
btcdeb '[2 3 OP_ADD 5 OP_EQUAL]'

程式會進入互動模式,你可以使用以下命令:

  • steps:執行下一步
  • stack:顯示當前堆疊
  • continuec:執行到結束
  • help:查看所有命令

觀察執行過程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ btcdeb '[2 3 OP_ADD 5 OP_EQUAL]'

btcdeb 0.4.22 -- type `btcdeb -h` for start up options
valid script
5 op script loaded. type `help` for usage information
script                                   |  stack
-----------------------------------------+--------
2                                        |
3                                        |
OP_ADD                                   |
5                                        |
OP_EQUAL                                 |
#0001 2

btcdeb> step
        <> PUSH stack 02
script                                   |  stack
-----------------------------------------+--------
3                                        |      02
OP_ADD                                   |
5                                        |
OP_EQUAL                                 |

每一步都清楚顯示堆疊的變化,這對理解腳本執行流程非常有幫助。

5.2 練習題

練習 1:基本運算

預測以下腳本的執行結果:

1
4 2 OP_SUB 1 OP_ADD

提示:先計算 4-2,再加 1。

練習 2:堆疊操作

以下腳本執行後,堆疊內容是什麼?

1
1 2 3 OP_DROP OP_SWAP

提示:OP_DROP 移除頂端元素,OP_SWAP 交換頂端兩個元素。

練習 3:理解條件分支

這個腳本定義了什麼樣的花費條件?

1
2
3
4
5
OP_IF
  <pubkey_A> OP_CHECKSIG
OP_ELSE
  <pubkey_B> OP_CHECKSIG
OP_ENDIF

練習 4:分析多重簽名

解釋這個腳本的含義:

1
OP_2 <pubkey_1> <pubkey_2> <pubkey_3> OP_3 OP_CHECKMULTISIG

提示:這是一個 M-of-N 多重簽名腳本。


六、總結與展望

本篇重點回顧

  1. Script 的本質:Bitcoin Script 是一種定義花費條件的程式語言。它故意設計成非圖靈完備,以確保安全性和可預測性。

  2. 堆疊機模型:Script 使用後進先出的堆疊來進行運算。數據被推入堆疊,操作碼從堆疊取出數據、處理、再把結果放回。

  3. 操作碼類別:包括常量推送、堆疊操作、算術運算、密碼學操作和流程控制。其中簽名驗證是核心功能。

  4. 腳本驗證:交易驗證涉及解鎖腳本和鎖定腳本的組合執行。兩者分開執行以防止安全漏洞。

下一篇預告

在下一篇文章中,我們將深入探討各種標準腳本類型:

  • P2PK:最早的支付方式,直接使用公鑰
  • P2PKH:最常見的傳統地址格式(1 開頭)
  • P2SH:支援複雜腳本的革命性改進(3 開頭)
  • P2WPKHP2WSH:SegWit 帶來的新格式(bc1q 開頭)

我們會詳細解釋每種類型的設計動機、優缺點,以及它們在實際應用中的使用場景。


參考資源

官方文檔

學習工具

延伸閱讀

// 分享

CP

Cypherpunks Taiwan

密碼學使自由和隱私再次偉大。Cryptography makes freedom and privacy great again.

// 留言