高級
MuSig2
深入了解 MuSig2 多簽協議,實現高效的 Schnorr 簽名聚合。
15 分鐘
什麼是 MuSig2?
MuSig2 是一種多方 Schnorr 簽名協議,允許多個參與者協作創建單一簽名, 看起來與普通單簽名完全相同。這提供了卓越的隱私性和效率,是比特幣 Taproot 的關鍵技術。
MuSig 演進
| 版本 | 輪數 | 特點 |
|---|---|---|
| MuSig(原版) | 3 輪 | 安全但交互多 |
| MuSig-DN | 2 輪 | 確定性 nonce |
| MuSig2 | 2 輪 | 安全、高效、實用 |
優勢
MuSig2 vs 傳統多簽:
傳統 OP_CHECKMULTISIG(2-of-3):
├── 交易大小:~297 vbytes
├── 鏈上可見多簽結構
├── 暴露參與者數量
└── 費用較高
MuSig2(2-of-3):
├── 交易大小:~111 vbytes(與單簽相同)
├── 鏈上無法區分
├── 完全隱私
└── 費用最低
優勢總結:
┌─────────────────────────────────────────────────────────────┐
│ 1. 隱私:無法從鏈上區分單簽和多簽 │
│ 2. 效率:固定大小簽名,無論參與者數量 │
│ 3. 費用:與單簽相同的交易費用 │
│ 4. 可組合:與 Taproot 完美配合 │
└─────────────────────────────────────────────────────────────┘ 協議流程
MuSig2 簽名協議(2 輪):
參與者:Alice (A), Bob (B), Carol (C)
目標:創建 n-of-n 聚合簽名
═══════════════════════════════════════════════════════════════
第 0 步:密鑰聚合(一次性)
═══════════════════════════════════════════════════════════════
每個參與者:
├── 生成私鑰 sk_i,公鑰 P_i = sk_i * G
└── 廣播公鑰 P_i
聚合公鑰計算:
├── L = H(P_1 || P_2 || ... || P_n) # 密鑰列表哈希
├── a_i = H(L || P_i) # 每個密鑰的係數
└── P = Σ (a_i * P_i) # 聚合公鑰
═══════════════════════════════════════════════════════════════
第 1 輪:Nonce 交換
═══════════════════════════════════════════════════════════════
每個參與者生成兩個 nonce:
├── r_i1, r_i2(隨機數)
├── R_i1 = r_i1 * G
├── R_i2 = r_i2 * G
└── 廣播 (R_i1, R_i2)
═══════════════════════════════════════════════════════════════
第 2 輪:簽名
═══════════════════════════════════════════════════════════════
收到所有 nonce 後:
├── R_1 = Σ R_i1
├── R_2 = Σ R_i2
├── b = H(R_1 || R_2 || P || m) # 組合係數
├── R = R_1 + b * R_2 # 聚合 nonce 點
├── e = H(R || P || m) # 挑戰
│
├── 每個參與者計算:
│ r_i = r_i1 + b * r_i2
│ s_i = r_i + e * a_i * sk_i
│ 廣播 s_i
│
└── 聚合簽名:
s = Σ s_i
最終簽名:(R, s) 密鑰聚合
import hashlib
from typing import List
# 假設使用 secp256k1 曲線
# 這是簡化的示範代碼
def tagged_hash(tag: str, data: bytes) -> bytes:
"""BIP-340 tagged hash"""
tag_hash = hashlib.sha256(tag.encode()).digest()
return hashlib.sha256(tag_hash + tag_hash + data).digest()
def aggregate_public_keys(pubkeys: List[bytes]) -> bytes:
"""
聚合多個公鑰為單一公鑰
pubkeys: 排序後的公鑰列表(33 bytes 壓縮格式)
"""
# 1. 計算密鑰列表哈希 L
L = tagged_hash("KeyAgg list", b"".join(sorted(pubkeys)))
# 2. 計算每個密鑰的係數
coefficients = []
for pk in pubkeys:
a_i = tagged_hash("KeyAgg coefficient", L + pk)
coefficients.append(int.from_bytes(a_i, 'big'))
# 3. 聚合公鑰 P = Σ (a_i * P_i)
# 實際實現需要橢圓曲線點乘和加法
aggregated = point_add_weighted(pubkeys, coefficients)
return aggregated
# 使用示例
alice_pk = bytes.fromhex("02...")
bob_pk = bytes.fromhex("03...")
carol_pk = bytes.fromhex("02...")
# 聚合公鑰(看起來像普通公鑰)
agg_pk = aggregate_public_keys([alice_pk, bob_pk, carol_pk]) 簽名過程
# MuSig2 簽名示範(簡化版)
class MuSig2Signer:
def __init__(self, private_key: int, all_pubkeys: List[bytes]):
self.sk = private_key
self.pk = point_multiply(G, private_key)
self.all_pubkeys = sorted(all_pubkeys)
# 計算聚合係數
L = tagged_hash("KeyAgg list", b"".join(self.all_pubkeys))
self.coefficient = int.from_bytes(
tagged_hash("KeyAgg coefficient", L + self.pk),
'big'
)
def generate_nonces(self) -> tuple:
"""第 1 輪:生成兩個 nonce"""
import secrets
self.r1 = secrets.randbelow(CURVE_ORDER)
self.r2 = secrets.randbelow(CURVE_ORDER)
R1 = point_multiply(G, self.r1)
R2 = point_multiply(G, self.r2)
return (R1, R2)
def create_partial_sig(self,
all_nonces: List[tuple],
message: bytes) -> int:
"""第 2 輪:創建部分簽名"""
# 聚合 nonces
R1 = point_sum([n[0] for n in all_nonces])
R2 = point_sum([n[1] for n in all_nonces])
# 計算組合係數
P = aggregate_public_keys(self.all_pubkeys)
b = int.from_bytes(
tagged_hash("MuSig/noncecoef",
point_serialize(R1) +
point_serialize(R2) +
P + message),
'big'
)
# 聚合 nonce 點
R = point_add(R1, point_multiply(R2, b))
# 計算挑戰
e = int.from_bytes(
tagged_hash("BIP0340/challenge",
point_x(R) + P + message),
'big'
)
# 計算有效 nonce
r = (self.r1 + b * self.r2) % CURVE_ORDER
# 部分簽名
s_i = (r + e * self.coefficient * self.sk) % CURVE_ORDER
return s_i
def aggregate_signatures(partial_sigs: List[int],
R: bytes) -> tuple:
"""聚合所有部分簽名"""
s = sum(partial_sigs) % CURVE_ORDER
return (R, s.to_bytes(32, 'big')) 與 Taproot 整合
MuSig2 + Taproot 組合:
場景:2-of-3 多簽 + 備份腳本
┌─────────────────────────────────────────────────────────────┐
│ Taproot 輸出 │
│ │
│ Internal Key: MuSig2(Alice, Bob, Carol) │
│ │
│ Script Tree: │
│ [root] │
│ / \ │
│ 2-of-3 Timelock │
│ (腳本) (備份) │
└─────────────────────────────────────────────────────────────┘
花費路徑:
1. Key Path(最常用,最便宜)
├── 三方協作使用 MuSig2
├── 鏈上只有單一簽名
└── 無法區分是單簽還是多簽
2. Script Path(備份)
├── 使用傳統 2-of-3 腳本
└── 或時間鎖後的恢復腳本 # 使用 Bitcoin Core 創建 MuSig2 地址(實驗性)
# 需要 Bitcoin Core 26.0+ 和 musig 模組
# 1. 創建描述符錢包
bitcoin-cli createwallet "musig-test" false false "" false true
# 2. 獲取各方公鑰
alice_xpub="xpub..."
bob_xpub="xpub..."
carol_xpub="xpub..."
# 3. 創建 MuSig 描述符
# tr(musig(alice_pk, bob_pk, carol_pk))
# 4. 導入描述符
bitcoin-cli importdescriptors '[{
"desc": "tr(musig([fp1]xpub1/*,[fp2]xpub2/*,[fp3]xpub3/*))#checksum",
"timestamp": "now",
"active": true
}]' 安全考慮
MuSig2 安全要點:
1. Nonce 安全性
├── 絕對不能重用 nonce
├── 必須使用密碼學安全隨機數
└── 建議:存儲 nonce 直到簽名完成
2. 為什麼需要兩個 nonce?
├── 防止 Wagner 攻擊
├── 允許預生成 nonce
└── 減少到 2 輪交互
3. Nonce 預生成
├── 可以提前生成 (R1, R2) 對
├── 適用於冷存儲場景
└── 但每對只能使用一次!
4. 密鑰取消攻擊防護
├── 使用密鑰係數 a_i
├── 防止惡意參與者操縱聚合密鑰
└── 所有參與者必須先提交公鑰
⚠️ 關鍵警告:
┌─────────────────────────────────────────────────────────────┐
│ 1. Nonce 重用 = 私鑰洩露 │
│ 2. 必須驗證其他參與者的 nonce 承諾 │
│ 3. 不要在部分簽名後改變 nonce │
└─────────────────────────────────────────────────────────────┘ 門檻簽名
MuSig2 門檻變體(t-of-n):
標準 MuSig2 是 n-of-n,但可以擴展:
方法 1:預簽名樹
├── 為每種 t-of-n 組合預生成 MuSig 密鑰
├── 使用 Taproot 腳本樹
└── 缺點:組合數可能很大
方法 2:FROST(Flexible Round-Optimized Schnorr Threshold)
├── 真正的 t-of-n 門檻簽名
├── 基於 Shamir 秘密分享
└── 與 MuSig2 互補
示例:2-of-3 使用預簽名樹
┌─────────────────────────────────────────────────────────────┐
│ Taproot Script Tree: │
│ │
│ [root] │
│ / \ │
│ MuSig(A,B) [node] │
│ / \ │
│ MuSig(A,C) MuSig(B,C) │
│ │
│ 任意兩方可以協作簽名 │
└─────────────────────────────────────────────────────────────┘ 實現庫
| 庫 | 語言 | 狀態 |
|---|---|---|
| secp256k1-zkp | C | 生產就緒 |
| libsecp256k1 (musig module) | C | 實驗性 |
| rust-secp256k1 | Rust | 開發中 |
| bitcoinjs-lib | JavaScript | 實驗性 |
// 使用 libsecp256k1 musig 模組(C 語言示例)
#include
#include
// 1. 密鑰聚合
secp256k1_musig_keyagg_cache keyagg_cache;
secp256k1_pubkey agg_pk;
secp256k1_musig_pubkey_agg(ctx, NULL, &agg_pk,
&keyagg_cache, pubkeys, n_signers);
// 2. 生成 nonces
secp256k1_musig_secnonce secnonce;
secp256k1_musig_pubnonce pubnonce;
secp256k1_musig_nonce_gen(ctx, &secnonce, &pubnonce,
session_id, seckey, &agg_pk,
msg32, NULL, NULL);
// 3. 聚合 nonces
secp256k1_musig_aggnonce agg_nonce;
secp256k1_musig_nonce_agg(ctx, &agg_nonce, pubnonces, n_signers);
// 4. 創建部分簽名
secp256k1_musig_partial_sig partial_sig;
secp256k1_musig_session session;
secp256k1_musig_nonce_process(ctx, &session, &agg_nonce,
msg32, &keyagg_cache, NULL);
secp256k1_musig_partial_sign(ctx, &partial_sig, &secnonce,
&keypair, &keyagg_cache, &session);
// 5. 聚合簽名
unsigned char sig64[64];
secp256k1_musig_partial_sig_agg(ctx, sig64, &session,
partial_sigs, n_signers); 應用場景
適合場景
- • 多簽錢包(企業、家庭)
- • 閃電網路通道
- • 交易所冷存儲
- • 跨鏈原子交換
- • 隱私增強多簽
挑戰
- • 需要多輪交互
- • Nonce 管理複雜
- • 參與者必須同時在線
- • 實現複雜度高
總結
- ✓ 隱私:多簽看起來像單簽
- ✓ 效率:固定大小簽名,最低費用
- ✓ 2 輪:比原版 MuSig 更高效
- ⚠ 注意:需要謹慎的 nonce 管理
已複製連結