作者:Hiroki Gondo
來源:https://medium.com/nayuta-engineering-blog-en/understanding-taproot-assets-protocol-e2dfe3fc1e07
Taproot Assets 協議(曾用名 “Taro”,下文縮寫為 “TAP”)是一種在比特幣上表示基於 UTXO 的資產的協議。本文致力於解釋 TAP 是如何創建和轉移資產的。
完整的協議分成許多部分。本文主要解釋的是 bip-tap 和 bit-tap-ms-smt 。
Lightning Labs 作出的實現可見此處。
什麼是 Taproot?
“Taproot” 是一種新型的比特幣輸出,允許同時指定兩種類型的花費條件:密鑰花費(Key Path
)和腳本花費(Script Path
)。
Key Path
,就像傳統的 P2PKH 輸出一樣,使用一個公鑰的簽名來花費。在 Taproot 這裡也可以使用 Schnorr 簽名的密鑰聚合(多簽名)。
Script Path
,則像傳統的 P2SH 一樣,允許用一種腳本語言編程複雜的花費條件。並且 Taproot 還允許同時指定多個腳本。這些腳本不會直接序列化在 Taproot 輸出中,而是會構造成一棵默克爾樹、壓縮成為一個根哈希值。當輸出使用某個腳本來花費時,只需要暴露這一個腳本(而不需要曝光其它腳本)。
TAP 在 Script Pat
中嵌入數據(“資產樹(Asset Tree)”)。這些數據在比特幣節點看來是無法解讀的,也檢測不出來,因為經過了哈希。
Taproot Assets 中的資產表示
資產樹
資產樹是一種兩級的 “稀疏默克爾總和樹” 結構,表達 Taproot Assets 協議。底下的一級表示一種資產(由 “資產 id(asset_id
)” 標識)的 UTXO。上一級則聚合來自下一級的樹。
上一級默克爾樹的根哈希值(asset_tree_root)
)的值會嵌入一個 Taproot 輸出的 Script Path
中,從而承諾這棵樹的狀態。
資產的轉移還創建一棵新的資產樹,並且舊的那一棵也會更新。這可以通過發起新的一筆比特幣交易、花費舊的資產樹所在的 Taproot 輸出來實現;沒有滿足規範的轉移(例如,通脹、重複話費)會被認為是無效的。*
* 這裡用到的方法是一種叫做 “客戶端驗證” 的概念;熟悉比特幣的顯著共識的讀者可能會對這裡的規範的正確性的基礎感到好奇,但我會暫時忽略這個問題。
稀疏默克爾總和樹
“稀疏默克爾總和樹”(以下將縮寫為 “MS-SMT”)是默克爾樹的一個變種,由 bip-tap-ms-smt 定義。因為其關鍵字是 256 比特的,所以它有 2^256 個葉子。絕大部分葉子是空的。
每個葉子都包含一個數量,而每個分支節點都承諾其子樹上的葉子所表示的數量之和。即使一棵子樹的內容不可知,只需檢查分支節點,就可以知道該子樹所包含的總數量。樹根則承諾所有葉子所表示的總數量。
就像通用的默克爾樹,只需一棵修剪過的、包含目標葉子的樹,就可以提供這些目標葉子在樹上的證明(默克爾證明)。但 MS-SMT 還支持 “不包含證明”。這是通過一種限制來實現的:表示不存在的關鍵字的葉子所包含的數量必須顯式設定為一個表明其不存在(None)的值(因此,證明 “None” 的存在就構成了一種不包含證明)。因此,默認的 MS-SMT 會由 2^256 個表示 “None” 的葉子。
資產葉子(資產 UTXO)
資產樹的低層 MS-MST 以 asset_script_key
作為關鍵字,以資產葉子作為值。每一個資產葉子都代表著該資產的一個 UTXO(為簡潔,下文就直接表述為 “UTXO”*),並且,下列屬性(包括可選的一個)會被序列化為 “類型-長度-數值” 格式。
* 不是比特幣的 UTXO 哈。
taproot_asset_version
asset_genesis
asset_id
asset_type
amt
lock_time
relative_lock_time
prev_asset_witnesses
-prev_asset_id
-asset_witness
-split_commitment_proof
split_commitment
asset_script_version
asset_script_key
asset_group_key
canonical_universe
asset_genesis
就是推導出 asset_id
的原像。
asset_script_key
既是資產葉子的關鍵字,也是一個 Taproot 形式的公鑰(以一個獨立於比特幣的 tap-vm 定義),並且是花費由該資產葉子表示的 UTXO 的條件。
在花費一個 UTXO 時,首先要滿足(外部的)比特幣花費條件,然後是(內部的)TAP 花費條件,由 asset_script_key
指定,並且 asset_script_key
應該被 prev_asset_id
和 asset_witness
滿足。舉個例子,asset_witness
是被花費的 UTXO 的 asset_script_key
的簽名。
如果在一次資產轉移中分割了 UTXO,也需要 split_commitment
和 split_commitment_proof
。
split_commitment
是一棵 MS-SMT,指代分割之後的所有 UTXO(在這個意義上,資產樹其實有三層),而根中的和值是被轉移的所有數量。
split_commitment_proof
是 split_commitment
的一個默克爾證據,證明一次分割的存在。
在所有分割中,只有一個會有 prev_asset_id
、asset_witness
和 split_commitment
。所有其它分割只有 split_commitment_proof
。所有的分割共享 prev_asset_id
和 asset_witness
。
資產創建
創建一種資產需要在一筆比特幣交易的一個 Taproot 輸出中嵌入一棵包含新資產的 UTXO 的新的資產樹(該交易也就成為這種資產的創世交易)。資產的 ID(asset_id
)以下列公式確定。
asset_id := sha256(genesis_outpoint || sha256(asset_tag) || asset_meta_hash || output_index || asset_type)
genesis_outpoint
就是被這筆交易花費的前序交易輸出,而 output_index
就是包含這棵資產樹的輸出的索引號。這保證了 asset_id
是全局唯一的。
新創建的資產 UTXO 省去了 prev_asset_id
和 asset_witness
。
資產轉移
就像比特幣 UTXO,資產轉移時可能會合並或分割資產 UTXO。但我們先從一個簡單的例子 —— 既沒有分割,也沒有合併 —— 說起 *。
* 下文的解釋作了一些簡化。比如,比特幣交易的找零輸出,就省去了。
Alice 有 10 個某資產,全部轉移給 Bob。
Alice 創建並廣播一筆比特幣交易,花費自己的資產 UTXO 所在的 Taproot 輸出。這筆交易有兩個 Taproot 輸出。一個輸出包含了一棵新的資產樹,包含了一個新的擁有 10 個某資產、且 Bob 可以控制(比如使用 Bob 的比特幣公鑰)的 UTXO。另一個輸出包含了一棵資產樹,是由 Alice 控制的原版資產樹移除對應的資產葉子後形成的。
屬於 Bob 的新 UTXO 用 prev_asset_id
來索引 Alice 的前序 UTXO。在 asset_witness
中,放著前序 asset_script_key
的簽名。而在這個新 UTXO 的 asset_script_key
中,放著一個新的、由 Bob 提前給出的公鑰。
Bob 需要驗證花費條件得到了滿足,而且資產在轉移後沒有通脹,以確認接收這筆支付。
- Bob 新創建的資產樹是否包含了一個滿足花費條件的新的 UTXO ?
- 輸入 UXTO 是否已經在 Alice 更新後的資產樹上移除了?
- 交易中是否有別的 Taproot 輸出?它們是否包含另一棵資產樹?*
- 等等。
這都是通過 證明/驗證 MS-SMT 的 包含/非包含證據 以及 Taproot 輸出的證據和原像來實現的。
* 如果另一個 UTXO 被添加到了另一棵資產樹(而不是 Bob 的資產樹)上,並且也花費了輸入 UTXO,那就構成了重複花費。
合併 UTXO
Alice 有在兩個葉子中分別有 3 個、7 個某資產,要全部轉移給 Bob。
各個輸入 UTXO 可以屬於不同的資產樹,或同一資產樹上的不同關鍵字(asset_script_key
)。Bob 的新 UTXO 應該為這兩個花費掉的 UTXO 包含 prev_asset_id
和 asset_witness
。
分割 UTXO
Alice 有 7 個某資產,準備轉 3 個給 Bob。因此,Alice 會留下 3 個,Bob 會得到 7 個。
Alice 的找零 UTXO 將有 prev_asset_id
、asset_witness
和 split_commitment
;Bot 的新 UTXO 將僅有 split_commitment_proof
*。
我不會再解釋如何同時合併及分割 UTXO。
資產流轉
雖然前面沒有體積,但在資產轉移可被驗證之前,必須先驗證每一個輸入 UTXO 都是合法的。對於每一個輸入 UTXO,必須 證明/驗證 從該資產的創世交易到如今這個輸入、整個流轉路徑上的每一筆交易,都是正確執行的。
這條路徑是從創世交易到最新交易的一個複雜的圖。在上圖中,對 Tx A
,需要驗證所有藍色的交易(其祖先交易);而對 Tx B
,需要驗證所有藍色和紅色交易。這是重大的可擴展性挑戰,因為歷史會呈准指數級增長。
此外,因為驗證所需的原像和證據是沒有發佈在區塊鏈上的,如何在資產的發送者和接收者之間傳遞它們,也是一個問題。
可擴展性
逐漸增加的歷史是一個重大的可擴展性挑戰。已經提出了多種解決方案,但支持閃電網絡是最有希望的一個。鏈下的交易不會使歷史增加。
這套協議是新的,規範也不完整。舉個例子,我此前就指出了這個問題。