跳至主要內容
進階

Tagged Hashes

了解 BIP-340 引入的標籤雜湊函數,為不同用途提供域分離的安全雜湊。

8 分鐘

標籤雜湊(Tagged Hash)是 BIP-340 為 Schnorr 簽名引入的雜湊函數設計, 通過在雜湊輸入前添加特定標籤,實現不同用途之間的域分離,防止跨域攻擊。

Tagged Hash 概念

Tagged Hash 定義:

tagged_hash(tag, msg) = SHA256(SHA256(tag) || SHA256(tag) || msg)

結構:
┌──────────────────────────────────────────┐
│ SHA256(tag)  │ SHA256(tag)  │    msg     │
│   32 bytes   │   32 bytes   │  variable  │
└──────────────────────────────────────────┘
              ↓
         SHA256(...)
              ↓
         32-byte hash

為什麼重複 SHA256(tag) 兩次?
- 總共 64 字節的前綴
- 正好是 SHA256 的一個區塊大小
- 允許預計算優化

優化:
SHA256(tag) 可以預先計算並重複使用
只需要對消息進行增量雜湊

為什麼需要 Tagged Hash?

域分離 (Domain Separation):

問題場景:
假設兩個不同的系統都使用 SHA256:
- 系統 A: hash = SHA256(data)
- 系統 B: hash = SHA256(data)

如果兩個系統的輸入碰巧相同,
輸出也會相同 → 潛在的混淆攻擊

範例攻擊:
1. 攻擊者構造特定數據
2. 數據在系統 A 中有一個含義
3. 相同雜湊在系統 B 中有不同含義
4. 攻擊者利用這種混淆

Tagged Hash 解決方案:
- 系統 A: hash = tagged_hash("SystemA", data)
- 系統 B: hash = tagged_hash("SystemB", data)

即使 data 相同:
- 因為標籤不同
- 雜湊結果完全不同
- 無法跨域利用

BIP-340 定義的標籤

Schnorr 簽名使用的標籤:

1. "BIP0340/aux"
   用途: 輔助隨機數處理
   輸入: 32 字節輔助隨機數據
   場景: 增強 nonce 生成的隨機性

2. "BIP0340/nonce"
   用途: 計算簽名 nonce
   輸入: 私鑰 || 公鑰 || 消息
   場景: 確定性 nonce 生成

3. "BIP0340/challenge"
   用途: 計算簽名挑戰值
   輸入: R || P || m
   場景: Schnorr 簽名的 e 值

Python 實現:
import hashlib

def tagged_hash(tag: str, msg: bytes) -> bytes:
    tag_hash = hashlib.sha256(tag.encode()).digest()
    return hashlib.sha256(tag_hash + tag_hash + msg).digest()

# 使用範例
challenge = tagged_hash("BIP0340/challenge", R + P + message)

Taproot 使用的標籤

Taproot (BIP-341/342) 的標籤:

1. "TapTweak"
   用途: 計算公鑰調整值
   輸入: 內部公鑰 || Merkle 根
   公式: t = tagged_hash("TapTweak", P || m)
   結果: Q = P + t·G

2. "TapLeaf"
   用途: 計算葉子雜湊
   輸入: leaf_version || script_length || script
   場景: 腳本樹的葉子節點

3. "TapBranch"
   用途: 計算分支雜湊
   輸入: left_hash || right_hash (排序後)
   場景: 合併兩個子節點

4. "TapSighash"
   用途: 計算 Taproot 簽名雜湊
   輸入: epoch || sighash_type || tx_data...
   場景: 簽名前的消息雜湊

範例 - TapLeaf 計算:
leaf_hash = tagged_hash("TapLeaf",
    bytes([0xc0]) +           # leaf version
    compact_size(script) +    # script length
    script                    # script content
)

實現細節

高效實現:

// 預計算標籤雜湊 (C++)
class TaggedHash {
private:
    CSHA256 hasher;

public:
    TaggedHash(const std::string& tag) {
        // 預計算 SHA256(tag)
        uint8_t tag_hash[32];
        CSHA256().Write((uint8_t*)tag.data(), tag.size())
                 .Finalize(tag_hash);

        // 初始化 hasher,預填充 tag_hash || tag_hash
        hasher.Write(tag_hash, 32);
        hasher.Write(tag_hash, 32);
    }

    void Write(const uint8_t* data, size_t len) {
        hasher.Write(data, len);
    }

    void Finalize(uint8_t hash[32]) {
        hasher.Finalize(hash);
    }
};

// 使用
TaggedHash hasher("TapTweak");
hasher.Write(internal_pubkey, 32);
hasher.Write(merkle_root, 32);
hasher.Finalize(tweak);

// 預計算的優勢:
// - 標籤雜湊只計算一次
// - 之後每次使用只需添加消息
// - 64 字節前綴正好是一個 SHA256 區塊

完整標籤列表

標籤 BIP 用途
BIP0340/aux 340 輔助隨機數
BIP0340/nonce 340 Nonce 生成
BIP0340/challenge 340 簽名挑戰
TapTweak 341 公鑰調整
TapLeaf 341 葉子雜湊
TapBranch 341 分支雜湊
TapSighash 341 簽名雜湊
KeyAgg list MuSig2 公鑰聚合
KeyAgg coefficient MuSig2 聚合係數

安全性分析

Tagged Hash 的安全屬性:

1. 域分離
   - 不同標籤產生不同的雜湊空間
   - 無法在不同域之間轉換雜湊
   - 防止跨協議攻擊

2. 抗碰撞
   - 繼承 SHA256 的碰撞抵抗性
   - 標籤不會減弱安全性
   - 128 位安全級別

3. 抗原像
   - 無法從雜湊反推輸入
   - 256 位安全級別

4. 確定性
   - 相同標籤和消息總是產生相同雜湊
   - 可預測和可驗證

潛在攻擊與緩解:

Q: 如果攻擊者控制標籤?
A: 標籤是硬編碼的,不由用戶控制

Q: 長度擴展攻擊?
A: SHA256 的標準屬性,標籤不影響

Q: 標籤碰撞?
A: 標籤本身也經過 SHA256,極難碰撞

程式範例

# Python 完整實現
import hashlib

class TaggedHasher:
    """可重用的 Tagged Hash 計算器"""

    # 預計算常用標籤
    _tag_hashes = {}

    @classmethod
    def get_tag_hash(cls, tag: str) -> bytes:
        if tag not in cls._tag_hashes:
            cls._tag_hashes[tag] = hashlib.sha256(tag.encode()).digest()
        return cls._tag_hashes[tag]

    @classmethod
    def hash(cls, tag: str, data: bytes) -> bytes:
        tag_hash = cls.get_tag_hash(tag)
        return hashlib.sha256(tag_hash + tag_hash + data).digest()


# 使用範例

# 1. Schnorr 挑戰值
def compute_challenge(R: bytes, P: bytes, msg: bytes) -> bytes:
    return TaggedHasher.hash("BIP0340/challenge", R + P + msg)

# 2. TapTweak
def compute_tweak(internal_pubkey: bytes, merkle_root: bytes) -> bytes:
    if merkle_root:
        return TaggedHasher.hash("TapTweak", internal_pubkey + merkle_root)
    else:
        return TaggedHasher.hash("TapTweak", internal_pubkey)

# 3. TapLeaf
def compute_leaf_hash(version: int, script: bytes) -> bytes:
    # Compact size encoding
    if len(script) < 0xfd:
        size_bytes = bytes([len(script)])
    elif len(script) <= 0xffff:
        size_bytes = b'\xfd' + len(script).to_bytes(2, 'little')
    else:
        size_bytes = b'\xfe' + len(script).to_bytes(4, 'little')

    return TaggedHasher.hash("TapLeaf",
        bytes([version]) + size_bytes + script)

# 4. TapBranch
def compute_branch_hash(left: bytes, right: bytes) -> bytes:
    # 排序確保一致性
    if left < right:
        return TaggedHasher.hash("TapBranch", left + right)
    else:
        return TaggedHasher.hash("TapBranch", right + left)

驗證與測試

# BIP-340 測試向量

# 預計算的標籤雜湊
assert sha256(b"BIP0340/challenge").hex() == \
    "7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c"

# 完整 tagged hash 測試
def test_tagged_hash():
    # 空消息
    result = tagged_hash("BIP0340/challenge", b"")
    # 驗證結果符合預期

    # 非空消息
    msg = bytes.fromhex("...")
    result = tagged_hash("TapTweak", msg)
    # 驗證結果

# Taproot 測試向量
test_vectors = [
    {
        "internal_pubkey": "...",
        "merkle_root": "...",
        "expected_tweak": "...",
        "expected_output_pubkey": "..."
    },
    # ...
]

for vec in test_vectors:
    tweak = compute_tweak(
        bytes.fromhex(vec["internal_pubkey"]),
        bytes.fromhex(vec["merkle_root"])
    )
    assert tweak.hex() == vec["expected_tweak"]

相關概念

  • Schnorr Signatures:Schnorr 簽名
  • Taproot:Taproot 升級
  • X-Only Pubkeys:X-Only 公鑰
  • MuSig2:多方簽名聚合
  • Key Path:密鑰路徑花費
已複製連結
已複製到剪貼簿