一個 Taproot 輸出的 scriptPubKey 固定是 34 位元組:1 位元組的 OP_1(代表見證版本 1)、1 位元組的推送長度 0x20,再加上 32 位元組的 x-only 公鑰。從區塊瀏覽器上看,它跟任何一個以密鑰路徑花費的 Taproot 輸出外觀一致——光看這 34 位元組,外界無法直接判定背後是否承諾了腳本樹、樹有多深或葉子有幾片。這個公鑰底下,可以同時承載多種多簽組合、帶有時間鎖的退款出口、硬體錢包的日常單簽通道,以及備用恢復密鑰。
花費資金時,交易發起方僅需掀開其中一片葉子。其餘條件在整個生命週期內都不會出現在鏈上,也不會佔用見證空間。這套機制的核心,正是 Taproot 腳本樹(即社群常稱的 MAST 結構)。理解其運作方式,需要從它解決的鏈上瓶頸、樹的組裝規則,以及兩種解鎖路徑的驗證邏輯逐一拆解。
舊結構的痛點:整份腳本都要上鏈
P2SH 與 P2WSH 都是把花費條件寫成一份完整腳本,並在花費時把整份腳本放進交易中。P2SH 把 redeem script 放入 scriptSig,P2WSH 則將 witness script 放進見證資料,兩者的共通限制是:只要想觸發其中一個條件,就必須將全部條件完整公開。
設想一個常見的企業錢包設計:三位主管各持一把密鑰,日常需要 2-of-3 簽名;如果連續 30 天沒有動用,財務總監可以單簽處理;若偵測到密鑰洩漏,冷備份密鑰配合 48 小時延遲可執行緊急轉移。寫成一份 P2WSH 腳本,內部會包含多個 OP_IF 分支、多組公鑰與時間鎖操作碼。花費當天即便只使用了兩位主管的簽名,鏈上依然會留下財務總監與冷備份的公鑰、延遲參數以及完整的邏輯分支結構。
這帶來了三重代價:在區塊空間方面,未執行的分支照樣佔用區塊容量與輸入資料,腳本越長手續費越高;在隱私方面,外界得以窺探組織的完整治理規則與權限架構;在可替換性方面,特徵明顯的複雜腳本會讓該筆網路上輸出在鏈上分析中格外醒目。
默克爾化腳本樹:把分支提升到結構層
MAST 的全稱為 Merkelized Abstract Syntax Tree,這是社群對此類默克爾化結構的通稱;BIP341 的規範術語則是 腳本樹(script tree)。在具體實現中,樹的葉子並非語法樹節點,而是一份份獨立且可直接執行的完整腳本。該機制把「一組候選花費條件」組織成了一棵默克爾樹。
葉子:一份完整腳本
每個葉子節點存儲獨立的腳本,例如「公鑰 A 與公鑰 B 的 2-of-2」或「公鑰 C 在 144 個區塊後可單簽」。葉子之間沒有 IF/ELSE 的巢狀關係,而是平行的花費選項。這與傳統腳本在單一腳本內使用 OP_IF 分支存在本質區別:OP_IF 的所有分支都寫在同一個腳本中,必然一起上鏈;腳本樹的分支各自獨立,未使用的葉子不會被公開。
中間節點:兩個子哈希的合併
兩個節點的哈希合併為父節點時,規則是先把兩個 32 位元組哈希按字典序排序(較小的在前),拼接後進行一次帶標籤的 SHA256 計算。字典序排序確保了同一對子節點只會產生唯一的父哈希,樹的根哈希與子節點的輸入順序無關,同時避免了雙向排列帶來的歧義。
葉子版本
每個葉子除腳本本身外,還包含 1 位元組的 leaf version。目前 Taproot 規範定義的標準版本為 0xc0,代表遵循 Tapscript 規則。此欄位為未來的軟分叉升級預留了擴展空間:當遇到非 0xc0 的未知葉子版本時,節點在驗證控制塊承諾匹配後不會執行腳本,而是直接視為有效花費,使得新規則可以在不中斷現有驗證邏輯的前提下向前相容。
內部公鑰、調整值與輸出公鑰的關係
Taproot 輸出的公鑰並非直接使用原始公鑰,而是經過了一次橢圓曲線標量調整(Tweak)。設內部公鑰為 P(32 位元組 x-only),腳本樹的默克爾根為 R,調整值 t 則是對 P 與 R 的拼接計算帶標籤哈希。最終的輸出公鑰計算為 Q = P + t·G,鎖定腳本中放置的是公鑰 Q 的 x 座標。
若該輸出僅打算使用密鑰路徑花費而未包含任何腳本樹,默克爾根 R 則為空,調整值僅對內部公鑰 P 自身進行哈希計算。這意味著即使沒有腳本條件,輸出的公鑰也不會直接等於內部公鑰,直接使用內部公鑰構造地址是不合規的。

此調整機制將兩種解鎖路徑無縫整合在同一個輸出中。腳本樹的默克爾根被承諾在輸出公鑰內,鏈上無需直接存儲根哈希。若走腳本路徑花費,見證資料必須提供足夠資訊以重新計算出 Q 並進行匹配;若走密鑰路徑花費,則需使用調整後的私鑰(即原始私鑰加上標量 t)進行 Schnorr 簽名。
三個帶標籤的哈希函數
Taproot 引入帶標籤的哈希(Tagged Hash)以隔離不同情境下的哈希計算。其構造方式是將特定的標籤字串進行 SHA256 運算兩次作為前綴,再拼接實際資料進行哈希計算,以防止跨協議情境下的哈希碰撞與誤用。
| 標籤名稱 | 輸入資料內容 | 用途說明 |
|---|---|---|
TapLeaf |
葉子版本 + CompactSize(腳本長度) + 腳本內容 | 計算單一葉子節點的哈希值 |
TapBranch |
經字典序排序後的兩個 32 位元組子節點哈希 | 將左右子節點合併為上一層父節點 |
TapTweak |
內部公鑰 P(+ 默克爾根 R) | 推導公鑰調整值 t |
在計算 TapLeaf 時,資料格式嚴格定義為葉子版本、腳本長度的 CompactSize 編碼以及腳本字節串。軟體實現時必須嚴格按照 BIP341 規範進行標準序列化,若長度編碼或位元組結構不符,推導出的根哈希將與共識層不一致,導致交易驗證失敗。
兩種解鎖路徑
密鑰路徑:單一簽名完成驗證
密鑰路徑(Key Path)花費的見證資料通常只有一個元素:即針對調整後公鑰 Q 的 Schnorr 簽名。由於採用 SIGHASH_DEFAULT(值為 0x00),簽名字節數通常固定為 64 位元組,無需額外附加簽名類型位元組。驗證節點只需直接驗證公鑰與簽名,完全不需要解析腳本樹。
在標準交易中,一個 P2TR 密鑰路徑輸入在整筆交易中約佔 57.5 vB(根據 Murch 在 2-of-3 Taproot 方案中的測算),相較於傳統 P2PKH 輸入約 148 vB 具備顯著的體積優勢。更關鍵的是隱私保護:鏈上完全不透露是否存在內部公鑰或腳本樹,其數據特徵與常規單簽 Taproot 轉帳完全無法區分。
腳本路徑:腳本、參數與控制塊
當無法使用密鑰路徑花費時,交易發起方可選擇走腳本路徑(Script Path)。見證資料依序包含:執行腳本所需的參數(如簽名、預設數值等)、被觸發的單一腳本本身,以及一個 控制塊(Control Block)。若見證資料大於等於兩個元素且最後一個元素以 0x50 開頭,該元素會被識別為 annex 剝離,供未來協議擴展使用。
鏈上只會揭露被選中的那一條葉子腳本,其他未觸發的葉子內容完全保密,實現了局部條件披露與空間節省。
控制塊結構與長度
根據 BIP341 規範,控制塊由三部分組成:1 位元組的葉子版本與奇偶校驗位、32 位元組的內部公鑰 P,以及默克爾路徑上每層 32 位元組的兄弟哈希。因此控制塊的總長度公式為:
(其中 m 代表從目標葉子到默克爾根的路徑長度/深度,規範上限 m ≤ 128,因此控制塊最大為 4129 位元組)
首字節的最高 7 位用於記錄葉子版本(如 0xc0),最低 1 位則記錄內部公鑰 P 在橢圓曲線上的 y 座標奇偶性。因此 Tapscript 的控制塊首字節常表現為 0xc0 或 0xc1。若腳本樹僅包含 1 片葉子(m = 0),默克爾路徑為空,控制塊長度即為基礎的 33 位元組。

驗證流程逐步拆解
- 檢查見證資料是否包含 annex:若元素數量 ≥ 2 且最後一個元素以 0x50 開頭,將其分離。
- 將剩餘見證資料的最後一個元素作為控制塊,倒數第二個元素作為目標執行腳本。
- 從控制塊首字節提取葉子版本(遮蔽最低位)與 y 座標奇偶位;後續 32 位元組提取為內部公鑰 P,剩餘部分按每 32 位元組切分為默克爾路徑陣列。
- 使用葉子版本與腳本內容計算
TapLeaf哈希,設為當前節點值。 - 沿著默克爾路徑逐層計算:將當前節點與路徑中的兄弟哈希按字典序排序後拼接,計算
TapBranch哈希,直到路徑遍歷完成,得出默克爾根 R(若路徑為空,R 即等於葉子哈希)。 - 根據奇偶位恢復完整的內部公鑰點 P(若標記為奇數則對點進行取反運算)。
- 結合內部公鑰 P 與計算出的默克爾根 R 算出調整值 t,推導出公鑰 Q = P + t·G,並比對 Q 的 x 座標是否與交易輸出的 32 位元組公鑰完全一致。不一致則立即判定失敗。
- 公鑰驗證通過後,若葉子版本為
0xc0,則依據 Tapscript 規則執行腳本;若為未知的葉子版本,則在承諾比對成功後直接視為有效花費。
Tapscript 帶來的規則變動
由 BIP 342 定義的 Tapscript 對傳統比特幣腳本規則進行了顯著優化:
- Schnorr 簽名支援:簽名固定為 64 或 65 位元組(含可選的非預設 sighash 類型),消除了 ECDSA 編碼引發的交易延展性問題,並支援批量驗證。
- 多簽機制升級:廢棄了存在歷史冗餘問題的 OP_CHECKMULTISIG,改用
OP_CHECKSIGADD。每個公鑰依序驗證簽名並累加計數器,最後與門檻值比對,堆疊邏輯清晰且節省空間。 - 空簽名規範:未參與簽名的私鑰對應位置推入空位元組向量(Empty Vector),檢查計為 0 且不中斷腳本,避免了不必要的簽名佔位。
- 簽名運算預算(Sigops Budget):取消了靜態操作碼數量上限,引入執行預算機制(初始預算為 50 + 腳本長度),每次非空簽名檢查消耗 50 點,耗盡則腳本終止。
- 擴展操作碼 OP_SUCCESSx:腳本在執行到 OP_SUCCESSx 系列操作碼時直接判定成功,為未來的軟分叉功能引入提供了無縫擴展通道。
- 簽名哈希覆蓋金額:BIP341 簽名承諾中納入了所有輸入的金額與鎖定腳本,硬體錢包無需盲簽,大幅提升了離線簽名的安全性。
樹的形狀如何影響成本與隱私
在腳本樹中,默克爾路徑長度 m 即為目標葉子的深度,控制塊每增加一層便增加 32 位元組:
- 深度為 1(2 葉平衡樹):控制塊長度為 33 + 32×1 = 65 位元組。
- 深度為 2(4 葉平衡樹):控制塊長度為 33 + 32×2 = 97 位元組。
- 深度為 3(8 葉平衡樹):控制塊長度為 33 + 32×3 = 129 位元組。
- 深度為 4(16 葉平衡樹):控制塊長度為 33 + 32×4 = 161 位元組(此時默克爾路徑部分為 128 位元組)。
這種線性增長特徵指明了樹形結構的優化方向:將高頻使用的花費條件置於淺層,將低頻或極端應急條件置於深層。非平衡樹的設計能讓常規花費享有更低的鏈上手續費。
在隱私層面,控制塊只會暴露被執行葉子的所在深度,外界無法得知整棵樹的葉子總數或其他未觸發分支的具體內容。若某個輸出構造時使用了深度為 4 的葉子,鏈上僅反映存在長度為 4 的哈希證明鏈,其餘兄弟節點的內容依舊處於哈希盲盒狀態。
密鑰路徑與腳本路徑的對比
| 比較維度 | 密鑰路徑(Key Path) | 腳本路徑(Script Path) |
|---|---|---|
| 見證資料結構 | 單一 64 位元組 Schnorr 簽名 | 執行參數 + 葉子腳本 + 控制塊(33 位元組起) |
| 解鎖密鑰要求 | 經標量調整後的內部私鑰 | 該葉子腳本所滿足的特定簽名或前置條件 |
| 鏈上資訊暴露 | 完全無結構暴露,等同於普通單簽 | 僅暴露當前觸發的葉子內容與路徑深度 |
| 典型應用場景 | 合作結算、日常支付、通道常規關閉 | 爭議處理、時間鎖退款、緊急資產恢復 |
常見問題(FAQ)
Q1: 單葉子腳本樹在花費時一定比 P2WSH 更省手續費嗎?
不一定。P2TR 腳本路徑花費時必須包含至少 33 位元組的控制塊,而 P2WSH 則不需要控制塊。在極簡腳本(如單簽或簡單多簽)場景下,控制塊帶來的字節開銷可能抵消 Schnorr 簽名與 x-only 公鑰帶來的字節節省。具體成本取決於腳本複雜度、簽名數量及是否能優先使用密鑰路徑結算。
Q2: 腳本樹構造完成並生成地址後,後續可以動態修改或增加分支嗎?
不可以。默克爾根在地址生成時就已透過標量調整承諾進了輸出公鑰 Q 中。一旦資金存入該地址,條件即被密碼學鎖定。若需調整花費條件或輪換密鑰,必須發起一筆鏈上交易將資金轉移至包含新腳本樹結構的新輸出。
Q3: 外界是否能透過暴力枚舉常見模板猜出樹內未公開的腳本?
在一般情況下難以達成。要驗證猜測的默克爾根是否與鏈上輸出 Q 匹配,驗證方除了需要推測完整的候選腳本集合外,還必須獲知內部公鑰 P。在純密鑰路徑花費或未揭露控制塊的情況下,內部公鑰 P 並未在鏈上直接公開。若錢包在構造腳本時額外加入了防碰撞的多餘資料,將進一步消除枚舉推測的可能性。
Q4: 如果只想強制所有花費都執行腳本路徑,該如何設計?
可以構造一個無已知私鑰的「不可花費內部公鑰」(Unspendable Internal Key)。常見做法是將標準橢圓曲線基點 G 的序列化哈希作為 x 座標,推導出曲線上對應的點作為內部公鑰 P。由於無人知曉該點的離散對數(私鑰),該輸出將無法透過密鑰路徑動用,從而強制所有資金轉移必須公開提交腳本與控制塊進行鏈上驗證。



