跳至主要內容
安全 進階

多重簽名

Multisig

又稱:多簽、Multi-signature

多重簽名(Multisig)是比特幣中一種強大的安全機制,要求 n 把私鑰中的至少 m 把才能花費資金。這種 m-of-n 配置消除了單點故障風險,廣泛應用於個人安全存儲、企業財務管理和託管服務。

多重簽名的本質

基本概念

單簽 vs 多簽:

單簽(1-of-1):
┌─────────────┐
│   私鑰 A    │ ──→ 簽名 ──→ 花費
└─────────────┘

多簽(2-of-3):
┌─────────────┐
│   私鑰 A    │ ──┐
├─────────────┤   │
│   私鑰 B    │ ──┼─→ 2個簽名 ──→ 花費
├─────────────┤   │
│   私鑰 C    │ ──┘(任選2個)
└─────────────┘

m-of-n 參數

m-of-n 的含義:

n = 總共多少把私鑰
m = 需要多少把簽名

常見配置:

1-of-2:任一方可單獨花費
2-of-2:雙方必須同時同意
2-of-3:三人中任兩人同意
3-of-5:五人中任三人同意

限制:
- m ≤ n(需要的簽名不能超過鑰匙總數)
- m ≥ 1(至少需要一個簽名)
- 傳統多簽 n ≤ 15(OP_CHECKMULTISIG 限制)
- P2WSH 多簽 n ≤ 20
- Taproot 無硬性限制

技術實現

傳統 P2SH 多簽

P2SH (Pay to Script Hash) 多簽:

贖回腳本(Redeem Script):
OP_2                    // m = 2
<pubkey1>               // 公鑰 1
<pubkey2>               // 公鑰 2
<pubkey3>               // 公鑰 3
OP_3                    // n = 3
OP_CHECKMULTISIG        // 驗證多簽

P2SH 地址生成:
1. 建構 redeem script
2. HASH160(redeem_script)
3. 加上版本前綴 05
4. Base58Check 編碼
5. 結果:3 開頭的地址

花費時:
<空> <sig1> <sig2> <redeem_script>

SegWit P2WSH 多簽

P2WSH (Pay to Witness Script Hash) 多簽:

見證腳本(Witness Script):
與 P2SH 的 redeem script 相同

P2WSH 地址生成:
1. 建構 witness script
2. SHA256(witness_script)
3. Bech32 編碼
4. 結果:bc1q...(較長)

優勢:
- 手續費更低(簽名在 witness)
- 沒有交易延展性問題
- 支持更多簽名者(20 vs 15)

花費結構:
witness: <空> <sig1> <sig2> <witness_script>

Taproot 多簽(MuSig2)

Taproot 多簽(密鑰聚合):

傳統多簽:
- 鏈上可見是多簽
- 需要提供所有公鑰
- 費用與簽名數成比例

Taproot MuSig2:
- 聚合公鑰 P = P1 + P2 + P3
- 聚合簽名 s = s1 + s2 + s3
- 鏈上只有一個公鑰和簽名
- 看起來像普通單簽交易

優勢:
- 極佳隱私性
- 最低手續費
- 無法區分單簽/多簽

常見配置詳解

2-of-3 個人安全存儲

最佳實踐配置:

┌────────────────────────────────────────────┐
│               2-of-3 配置                   │
├────────────────────────────────────────────┤
│                                            │
│  鑰匙 1:日常使用                           │
│  ├─ 硬體錢包(Ledger/Trezor)              │
│  └─ 放在家中安全位置                        │
│                                            │
│  鑰匙 2:備份                               │
│  ├─ 鋼板助記詞                             │
│  └─ 銀行保險箱                             │
│                                            │
│  鑰匙 3:緊急備用                           │
│  ├─ 另一個硬體錢包                         │
│  └─ 信任的家人處 或 另一地點保險箱          │
│                                            │
└────────────────────────────────────────────┘

正常使用:鑰匙 1 + 鑰匙 2
設備故障:鑰匙 2 + 鑰匙 3
緊急情況:任意兩把

優勢:
- 單一設備丟失/損壞不會損失資金
- 單一地點被盜不會損失資金
- 保留完全自主控制權

3-of-5 企業財務

企業級配置:

┌────────────────────────────────────────────┐
│             3-of-5 企業配置                 │
├────────────────────────────────────────────┤
│                                            │
│  鑰匙 1:CEO                                │
│  鑰匙 2:CFO                                │
│  鑰匙 3:COO                                │
│  鑰匙 4:董事會代表                         │
│  鑰匙 5:外部安全顧問                       │
│                                            │
└────────────────────────────────────────────┘

審批流程:
- 日常支出 < $10K:CEO + CFO + COO
- 大額支出 > $10K:需要董事會代表
- 緊急情況:任意三人

審計追蹤:
- 每筆支出都有明確的簽名者
- 無法單人挪用資金
- 符合內控要求

2-of-2 協作託管

託管服務配置:

用戶 + 服務商(2-of-2):
┌─────────────┐     ┌─────────────┐
│  用戶私鑰   │     │ 服務商私鑰  │
│  (用戶)   │     │  (服務)   │
└──────┬──────┘     └──────┬──────┘
       │                   │
       └───────┬───────────┘

         需要雙方同意

適用場景:
- 交易所冷錢包
- 託管服務
- 定期支付合約

風險考量:
- 任一方失聯 = 資金鎖死
- 通常搭配時間鎖備案
- 需要額外的恢復機制

地址類型比較

不同類型的多簽地址

同一個 2-of-3 多簽的不同表示:

P2SH(傳統):
地址:3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC
格式:Base58Check
手續費:最高

P2SH-P2WSH(嵌套 SegWit):
地址:3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy
格式:Base58Check(向後相容)
手續費:中等

P2WSH(原生 SegWit):
地址:bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej
格式:Bech32
手續費:較低

P2TR(Taproot):
地址:bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr
格式:Bech32m
手續費:最低(使用 MuSig2)

手續費比較

2-of-3 多簽花費成本比較(virtual bytes):

P2SH:
- 輸入大小:約 297 vB
- 包含:2個簽名 + 3個公鑰 + 腳本

P2WSH:
- 輸入大小:約 140 vB
- 簽名移到 witness(75% 折扣)

P2TR(MuSig2):
- 輸入大小:約 58 vB
- 只有一個聚合簽名
- 與單簽相同!

假設費率 20 sat/vB:
P2SH:  5,940 sats
P2WSH: 2,800 sats
P2TR:  1,160 sats(節省 80%!)

創建多簽錢包

使用 Bitcoin Core

# 創建 2-of-3 多簽(傳統方式)

# 1. 生成三個私鑰/公鑰對
bitcoin-cli getnewaddress "" legacy
bitcoin-cli getaddressinfo <address1>
# 記錄 pubkey1

bitcoin-cli getnewaddress "" legacy
bitcoin-cli getaddressinfo <address2>
# 記錄 pubkey2

bitcoin-cli getnewaddress "" legacy
bitcoin-cli getaddressinfo <address3>
# 記錄 pubkey3

# 2. 創建多簽地址
bitcoin-cli createmultisig 2 '["pubkey1","pubkey2","pubkey3"]'

# 輸出:
# {
#   "address": "3...",
#   "redeemScript": "5221...52ae",
#   "descriptor": "sh(multi(2,pubkey1,pubkey2,pubkey3))"
# }

# 3. 導入到錢包以追蹤餘額
bitcoin-cli importaddress "3..." "my-multisig" true

使用描述符(Descriptor)

# 現代方式:使用 Output Descriptors

# 創建描述符錢包
bitcoin-cli createwallet "multisig-wallet" true true "" false true

# P2WSH 多簽描述符
descriptor="wsh(multi(2,\
[fingerprint1/84'/0'/0']xpub1.../0/*,\
[fingerprint2/84'/0'/0']xpub2.../0/*,\
[fingerprint3/84'/0'/0']xpub3.../0/*))"

# 導入描述符
bitcoin-cli importdescriptors '[{
  "desc": "'$descriptor'",
  "timestamp": "now",
  "active": true,
  "internal": false,
  "range": [0, 100]
}]'

# 獲取新地址
bitcoin-cli getnewaddress "" bech32

JavaScript 實現

const bitcoin = require('bitcoinjs-lib');
const { ECPair } = require('ecpair');
const ecc = require('tiny-secp256k1');

// 創建 2-of-3 P2WSH 多簽

// 三個公鑰(實際應來自不同來源)
const pubkeys = [
  Buffer.from('02...', 'hex'),
  Buffer.from('03...', 'hex'),
  Buffer.from('02...', 'hex'),
].sort((a, b) => a.compare(b)); // 必須排序!

// 創建多簽腳本
const p2ms = bitcoin.payments.p2ms({
  m: 2, // 需要 2 個簽名
  pubkeys: pubkeys,
  network: bitcoin.networks.bitcoin,
});

// 包裝成 P2WSH
const p2wsh = bitcoin.payments.p2wsh({
  redeem: p2ms,
  network: bitcoin.networks.bitcoin,
});

console.log('多簽地址:', p2wsh.address);
console.log('Witness Script:', p2wsh.redeem.output.toString('hex'));

// 花費多簽
function spendMultisig(utxo, toAddress, amount, privkeys) {
  const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin });

  psbt.addInput({
    hash: utxo.txid,
    index: utxo.vout,
    witnessUtxo: {
      script: p2wsh.output,
      value: utxo.value,
    },
    witnessScript: p2wsh.redeem.output,
  });

  psbt.addOutput({
    address: toAddress,
    value: amount,
  });

  // 用兩個私鑰簽名
  privkeys.forEach((privkey) => {
    const keyPair = ECPair.fromPrivateKey(privkey);
    psbt.signInput(0, keyPair);
  });

  psbt.finalizeAllInputs();
  return psbt.extractTransaction().toHex();
}

PSBT 多簽工作流

什麼是 PSBT?

PSBT(Partially Signed Bitcoin Transaction):

用途:多簽協作工作流程

工作流程:
┌──────────────────────────────────────────────────┐
│                                                  │
│  協調者創建                                       │
│  ┌──────────┐                                    │
│  │   PSBT   │ ─── 未簽名交易                      │
│  └────┬─────┘                                    │
│       │                                          │
│       ▼                                          │
│  分發給簽名者                                     │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│  │ 簽名者 1 │  │ 簽名者 2 │  │ 簽名者 3 │        │
│  │  簽名    │  │  簽名    │  │  (備用)  │        │
│  └────┬─────┘  └────┬─────┘  └──────────┘        │
│       │             │                            │
│       ▼             ▼                            │
│  合併簽名                                         │
│  ┌──────────────────────┐                        │
│  │   完整簽名的 PSBT    │                        │
│  └──────────┬───────────┘                        │
│             │                                    │
│             ▼                                    │
│  廣播交易                                         │
│                                                  │
└──────────────────────────────────────────────────┘

PSBT 實際操作

# 完整的 PSBT 多簽流程

# 步驟 1:協調者創建 PSBT
bitcoin-cli walletcreatefundedpsbt \
  '[{"txid":"...","vout":0}]' \
  '[{"bc1q...":0.5}]'

# 輸出 PSBT(Base64 格式)
# cHNidP8BAFUCAAAAAQAAAAEAAAAAAAAAA...

# 步驟 2:分發給簽名者 1
bitcoin-cli walletprocesspsbt "cHNidP8..."
# 返回帶第一個簽名的 PSBT

# 步驟 3:分發給簽名者 2
bitcoin-cli walletprocesspsbt "cHNidP8...(帶簽名1)"
# 返回帶兩個簽名的 PSBT

# 步驟 4:合併並完成
bitcoin-cli combinepsbt '["psbt1", "psbt2"]'
bitcoin-cli finalizepsbt "combined_psbt"

# 步驟 5:廣播
bitcoin-cli sendrawtransaction "完整交易hex"

安全考量

備份要求

多簽備份清單:

必須備份的項目:

1. 所有私鑰/助記詞
   ┌─────────────────────────────────────┐
   │  鑰匙 1: abandon abandon ... zoo    │
   │  鑰匙 2: ability ability ... youth  │
   │  鑰匙 3: absorb absorb ... zebra    │
   └─────────────────────────────────────┘

2. 贖回腳本(Redeem Script)/ 見證腳本
   ┌─────────────────────────────────────┐
   │  522102...02...03...53ae             │
   │  或:描述符字串                       │
   └─────────────────────────────────────┘

3. 派生路徑(如果使用 HD 錢包)
   ┌─────────────────────────────────────┐
   │  m/48'/0'/0'/2' (標準多簽路徑)       │
   └─────────────────────────────────────┘

4. 公鑰順序
   ┌─────────────────────────────────────┐
   │  公鑰必須按字典序排列                 │
   │  順序錯誤 = 不同的地址!              │
   └─────────────────────────────────────┘

缺少任何一項 = 無法恢復資金!

常見錯誤

多簽常見錯誤:

❌ 只備份助記詞
   → 沒有贖回腳本無法花費

❌ 公鑰順序錯誤
   → 產生不同的地址

❌ 所有鑰匙放同一處
   → 失去多簽的安全意義

❌ 沒有測試恢復流程
   → 需要時才發現問題

❌ 使用不同軟體的非標準路徑
   → 恢復時路徑不匹配

✅ 正確做法:
   - 備份所有材料
   - 異地存放
   - 定期測試恢復
   - 使用標準派生路徑
   - 記錄軟體版本和設置

時間鎖備案

為 2-of-2 添加時間鎖備案:

問題:2-of-2 中一方失聯 = 資金鎖死

解決方案:時間鎖緊急路徑

Miniscript 表示:
or(
  and(pk(A), pk(B)),           // 正常:雙方同意
  and(pk(A), older(52560))     // 緊急:1年後 A 單獨
)

效果:
- 正常情況:需要 A 和 B 同時簽名
- 1 年後:A 可以單獨取回資金
- 防止資金永久鎖死

多簽工具和服務

專業多簽軟體

常用多簽軟體:

開源軟體:
┌────────────────────────────────────────────┐
│  Sparrow Wallet                            │
│  - 完整 PSBT 支援                          │
│  - 硬體錢包整合                            │
│  - 桌面版(Mac/Windows/Linux)             │
│  - 最推薦的多簽方案                         │
├────────────────────────────────────────────┤
│  Specter Desktop                           │
│  - 連接自己的節點                          │
│  - 硬體錢包協調                            │
│  - Web 界面                                │
├────────────────────────────────────────────┤
│  Caravan (Unchained)                       │
│  - 瀏覽器工具                              │
│  - 無需安裝                                │
│  - 開源                                    │
├────────────────────────────────────────────┤
│  Nunchuk                                   │
│  - 手機 + 桌面                             │
│  - 協作簽名                                │
│  - 聊天整合                                │
└────────────────────────────────────────────┘

託管服務:
- Casa:2-of-3 帶客服協助
- Unchained:商業級多簽服務

硬體錢包支援

硬體錢包多簽能力:

┌──────────────┬────────┬────────┬─────────┐
│   設備       │ P2SH   │ P2WSH  │  P2TR   │
├──────────────┼────────┼────────┼─────────┤
│ Ledger       │   ✓    │   ✓    │   ✓     │
│ Trezor       │   ✓    │   ✓    │   ✓     │
│ Coldcard     │   ✓    │   ✓    │   ✓     │
│ BitBox02     │   ✓    │   ✓    │   ✓     │
│ Keystone     │   ✓    │   ✓    │   ✓     │
└──────────────┴────────┴────────┴─────────┘

推薦配置:
- 使用不同品牌的硬體錢包
- 避免供應鏈攻擊風險
- 不同設備不同漏洞

進階主題

閾值簽名(TSS)

TSS vs 傳統多簽:

傳統多簽(on-chain):
- 多個獨立簽名
- 鏈上可見
- 需要所有公鑰

TSS(off-chain):
- 單一聚合簽名
- 鏈上不可見
- 私鑰分片

TSS 流程:
1. 私鑰生成協議(DKG)
   - 各方協作產生私鑰分片
   - 沒有人擁有完整私鑰

2. 簽名協議
   - 各方用分片協作簽名
   - 產生單一有效簽名

優點:
- 更好的隱私
- 更低的費用
- 可以輪換分片

缺點:
- 複雜的協議
- 需要互動
- 相對較新

Miniscript 複雜策略

Miniscript 允許複雜的花費條件:

範例:公司財務策略

or(
  // 主要路徑:CEO + CFO + 風控
  thresh(3, pk(CEO), pk(CFO), pk(Risk)),

  // 緊急路徑:董事會多數 + 30天等待
  and(
    thresh(3, pk(Board1), pk(Board2), pk(Board3), pk(Board4), pk(Board5)),
    older(4320)  // 約 30 天
  )
)

效果:
- 日常:CEO + CFO + 風控 同意即可
- 緊急:30天後,5位董事中3位可以接管

這在傳統 Script 中很難實現
Miniscript 使複雜策略變得可組合

開發者資源

Python 實現

from bitcoin import SelectParams
from bitcoin.core import *
from bitcoin.core.script import *
from bitcoin.wallet import *

SelectParams('mainnet')

def create_multisig(m, pubkeys):
    """創建 m-of-n 多簽地址"""
    # 公鑰必須排序
    sorted_pubkeys = sorted(pubkeys)

    # 構建贖回腳本
    redeem_script = CScript([
        m,  # 需要的簽名數
        *[bytes.fromhex(pk) for pk in sorted_pubkeys],
        len(pubkeys),  # 總公鑰數
        OP_CHECKMULTISIG
    ])

    # P2SH 地址
    script_hash = Hash160(redeem_script)
    p2sh_address = P2SHBitcoinAddress.from_bytes(script_hash)

    # P2WSH 地址
    script_sha256 = hashlib.sha256(redeem_script).digest()
    # ... Bech32 編碼

    return {
        'address': str(p2sh_address),
        'redeem_script': redeem_script.hex(),
        'm': m,
        'n': len(pubkeys)
    }

def verify_multisig_signature(tx, input_idx, redeem_script, signatures, pubkeys):
    """驗證多簽交易"""
    # ... 驗證邏輯
    pass

Bitcoin Core RPC

# 多簽相關 RPC 命令

# 創建多簽地址
bitcoin-cli createmultisig 2 '["pubkey1","pubkey2","pubkey3"]'

# 添加多簽地址到錢包
bitcoin-cli addmultisigaddress 2 '["pubkey1","pubkey2","pubkey3"]' "label"

# 獲取地址資訊
bitcoin-cli getaddressinfo "3..."

# 創建多簽交易
bitcoin-cli createrawtransaction \
  '[{"txid":"...","vout":0}]' \
  '[{"bc1q...":0.5}]'

# 簽名(需要提供贖回腳本)
bitcoin-cli signrawtransactionwithkey \
  "rawtx" \
  '["privkey1"]' \
  '[{"txid":"...","vout":0,"scriptPubKey":"...","redeemScript":"..."}]'

# 合併簽名
bitcoin-cli combinerawtransaction '["tx1", "tx2"]'

# 廣播
bitcoin-cli sendrawtransaction "完整交易"

常見問題

需要備份什麼?

完整備份清單:

最小備份(能恢復):
□ 所有助記詞(m個中的m個)
□ 贖回腳本 或 所有公鑰
□ 派生路徑
□ m 和 n 的值

推薦備份(更安全):
□ 所有助記詞(n個全部)
□ 主公鑰(xpub)
□ 完整描述符
□ 錢包設置檔
□ 使用的軟體版本

測試驗證:
□ 用備份恢復過錢包
□ 確認地址一致
□ 測試過小額花費

2-of-3 夠安全嗎?

2-of-3 的安全性分析:

攻擊向量:

單點攻擊(需要獲取 2 把鑰匙):
- 需要同時攻破兩個位置
- 或同時攻破兩個設備
- 難度大幅提高

共謀攻擊:
- 如果用於多人(公司)
- 需要兩人共謀
- 比單簽安全

物理攻擊:
- 攻擊者需要找到兩個藏匿點
- 異地存放很重要

結論:
- 對個人:2-of-3 通常足夠
- 對企業:考慮 3-of-5 或更高
- 關鍵是:異地、異設備、異人員

Taproot 多簽何時使用?

Taproot 多簽(MuSig2)的取捨:

優點:
✓ 最佳隱私(看起來像單簽)
✓ 最低手續費
✓ 鏈上更簡潔

缺點:
✗ 需要互動簽名
✗ 軟體支援較新
✗ 無法區分誰簽了名

適合:
- 預算敏感
- 隱私優先
- 技術成熟團隊

暫不適合:
- 需要審計追蹤
- 簽名者常離線
- 保守的企業環境
已複製到剪貼簿