跳至主要內容
高級

Taproot Annex

了解 Taproot 見證中的 Annex 欄位,為未來擴展預留的數據空間。

8 分鐘

Taproot Annex 是見證堆疊中可選的附加數據欄位,以 0x50 字節開頭。 它被設計為未來擴展的預留空間,目前任何使用 annex 的交易都是非標準的。

Annex 概述

Taproot Annex 結構:

見證堆疊(有 annex):
┌─────────────────────────────────────────┐
│ [0]: script argument 1                  │
│ [1]: script argument 2                  │
│ ...                                     │
│ [n-2]: script                           │
│ [n-1]: control block                    │
│ [n]: annex (以 0x50 開頭)               │
└─────────────────────────────────────────┘

Annex 識別:
- 見證堆疊最後一個元素
- 第一個字節是 0x50
- 長度至少 1 字節

Annex 結構:
┌────────┬─────────────────┐
│ 0x50   │ annex data      │
│ 1 byte │ variable        │
└────────┴─────────────────┘

// 0x50 是 OP_RESERVED,用作標識符

為什麼使用 0x50?

選擇 0x50 的原因:

1. 與控制塊區分
   控制塊以葉子版本開頭:
   - 0xc0, 0xc1 (TapScript)
   - 其他偶數/奇數版本

   Annex 以 0x50 開頭:
   - 0x50 不是有效的葉子版本
   - 可以明確區分兩者

2. 檢測邏輯
   if last_element[0] == 0x50:
       annex = last_element
       control_block = second_last_element
   else:
       annex = None
       control_block = last_element

3. 向後兼容
   - 0x50 是 OP_RESERVED
   - 在傳統腳本中會失敗
   - 不會與任何現有結構衝突

Key Path 中的 Annex:
見證只有簽名時:
- 如果簽名以 0x50 開頭 → 無效
- 如果見證有兩個元素且最後一個以 0x50 開頭 → 那是 annex

Sighash 計算中的 Annex

Annex 參與簽名雜湊計算:

BIP-341 Sighash:

spend_type 編碼:
spend_type = (ext_flag × 2) + annex_present

其中:
- ext_flag: 0 (key path) 或 1 (script path)
- annex_present: 1 如果有 annex,否則 0

如果有 annex:
sighash_data += sha256(compact_size(annex) || annex)

這意味著:
1. Annex 被簽名承諾
2. 修改 annex 會使簽名無效
3. Annex 不能在簽名後添加

為什麼要簽名 annex?
- 防止第三方添加數據
- 確保交易完整性
- 為未來語義做準備

當前狀態

Annex 的當前限制:

共識規則:
- Annex 可以存在於 Taproot 見證中
- 必須以 0x50 開頭
- 沒有大小限制(除了區塊限制)

策略規則(非標準):
- 任何包含 annex 的交易都是非標準的
- 不會被中繼或挖礦
- 需要直接提交給礦工

原因:
1. Annex 語義尚未定義
2. 防止濫用區塊空間
3. 為未來升級保留靈活性

檢查交易:
bitcoin-cli testmempoolaccept '[\"<raw_tx_with_annex>\"]'
{
  "allowed": false,
  "reject-reason": "non-standard (annex)"
}

// 目前不建議使用 annex

潛在用途

Annex 的可能未來用途:

1. 見證簽名雜湊擴展
   - 添加額外的 sighash 數據
   - 支持新的簽名模式
   - 類似 SIGHASH_ANYPREVOUT 的擴展

2. 腳本執行上下文
   - 傳遞數據給腳本
   - 無需在腳本中硬編碼
   - 類似環境變量

3. 費用相關數據
   - 費用市場信息
   - 動態費用調整
   - 礦工提示

4. 零知識證明
   - 額外的證明數據
   - 不在腳本中驗證
   - 鏈下驗證

5. 交易元數據
   - 不影響執行
   - 可被第三方讀取
   - 輔助索引

提案討論:
- SIGHASH_GROUP (使用 annex)
- 見證擴展提案
- 內省操作碼支持

技術細節

Annex 處理的實現:

// 解析見證中的 annex
std::optional<std::vector<uint8_t>> ParseAnnex(
    const CScriptWitness& witness)
{
    if (witness.stack.empty()) {
        return std::nullopt;
    }

    const auto& last = witness.stack.back();
    if (!last.empty() && last[0] == ANNEX_TAG) {
        return last;
    }

    return std::nullopt;
}

// 計算 sighash 時處理 annex
void ComputeTaprootSighash(
    const CTransaction& tx,
    size_t input_index,
    uint8_t hash_type,
    const std::optional<std::vector<uint8_t>>& annex,
    uint256& sighash)
{
    CHashWriter ss(HASHER_TAPSIGHASH);

    // ... 其他 sighash 數據 ...

    // spend_type
    uint8_t spend_type = ext_flag * 2;
    if (annex) {
        spend_type |= 1;
    }
    ss << spend_type;

    // 如果有 annex,添加其雜湊
    if (annex) {
        CHashWriter sha_annex(SER_GETHASH);
        sha_annex << *annex;
        ss << sha_annex.GetSHA256();
    }

    // ...
}

const uint8_t ANNEX_TAG = 0x50;

Key Path 與 Annex

Key Path 花費中的 Annex:

標準 Key Path 見證:
witness = [signature]  # 64 或 65 bytes

帶 Annex 的 Key Path:
witness = [signature, annex]

識別邏輯:
if len(witness) == 1:
    # 只有簽名,無 annex
    signature = witness[0]
    annex = None
elif len(witness) == 2 and witness[1][0] == 0x50:
    # 有 annex
    signature = witness[0]
    annex = witness[1]
else:
    # 無效的 key path 見證
    fail

注意事項:
1. 簽名本身不能以 0x50 開頭
   - Schnorr 簽名的 r 值不會是 0x50...
   - 因為 r 是曲線點的 x 座標

2. 見證大小
   - 無 annex: 1 個元素
   - 有 annex: 2 個元素
   - 更多元素: 無效

Script Path 與 Annex

Script Path 花費中的 Annex:

標準 Script Path 見證:
witness = [
    script_arg_1,
    script_arg_2,
    ...,
    script,
    control_block
]

帶 Annex 的 Script Path:
witness = [
    script_arg_1,
    script_arg_2,
    ...,
    script,
    control_block,
    annex
]

識別邏輯:
# 最後一個元素
last = witness[-1]

if last[0] == 0x50:
    # 有 annex
    annex = last
    control_block = witness[-2]
    script = witness[-3]
    args = witness[:-3]
else:
    # 無 annex
    annex = None
    control_block = last
    script = witness[-2]
    args = witness[:-2]

# 驗證 control_block 的第一個字節
# 確保它是有效的葉子版本(不是 0x50)

安全與最佳實踐

  • 不要使用 Annex:目前任何使用 annex 的交易都是非標準的
  • 等待規範:annex 語義尚未定義,使用可能導致問題
  • 簽名包含 Annex:修改 annex 會使簽名無效
  • 監控提案:關注未來 BIP 對 annex 的定義

未來展望

Annex 的發展方向:

短期:
- 保持非標準狀態
- 社區討論具體用途
- 可能的 BIP 提案

中期:
- 定義第一個 annex 用途
- 軟分叉啟用
- 工具支持

長期:
- 多種 annex 應用
- 版本化的 annex 格式
- 廣泛的生態系統支持

注意:
- Annex 已經在共識層預留
- 添加新功能只需策略層變更
- 設計使升級盡可能簡單

// Annex 是 Taproot 未來擴展的重要機制

相關概念

  • Leaf Versions:葉子版本機制
  • Key Path:密鑰路徑花費
  • Script Path:腳本路徑花費
  • Sighash Types:簽名雜湊類型
  • Taproot:Taproot 升級概述
已複製連結
已複製到剪貼簿