跳至主要內容
入門

Sign Message

學習使用比特幣私鑰簽名消息,以及驗證簽名的方法。

8 分鐘

比特幣消息簽名允許用戶證明自己控制某個地址的私鑰,而無需進行鏈上交易。 這在驗證身份和證明所有權時非常有用。

基本用法

# 簽名消息
bitcoin-cli signmessage "<address>" "Hello, I own this address!"

# 返回 Base64 編碼的簽名
"H8K3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=="

# 驗證簽名
bitcoin-cli verifymessage "<address>" "<signature>" "Hello, I own this address!"

# 返回
true  # 簽名有效
false # 簽名無效

支持的地址類型

地址類型 signmessage verifymessage
P2PKH (1...)
P2SH-P2WPKH (3...)
P2WPKH (bc1q...)
P2TR (bc1p...) ✗ (暫不支持)

消息簽名格式

// Bitcoin 消息簽名格式

簽名的實際數據:
message_hash = SHA256(SHA256(
  "\x18Bitcoin Signed Message:\n" +
  varint(message.length) +
  message
))

// 簽名結構 (65 bytes)
signature = [
  recovery_flag (1 byte),  // 27-30 或 31-34 (壓縮公鑰)
  r (32 bytes),
  s (32 bytes)
]

// Base64 編碼後約 88 字符

恢復標誌 (Recovery Flag)

// 恢復標誌決定如何從簽名恢復公鑰

27-30: 未壓縮公鑰
  27: 偶數 y, 低 r
  28: 奇數 y, 低 r
  29: 偶數 y, 高 r
  30: 奇數 y, 高 r

31-34: 壓縮公鑰 (更常見)
  31: 偶數 y, 低 r
  32: 奇數 y, 低 r
  33: 偶數 y, 高 r
  34: 奇數 y, 高 r

// 驗證時,使用恢復標誌重建公鑰
// 然後檢查公鑰雜湊是否與地址匹配

編程實現

Python 驗證

import hashlib
import base64
from ecdsa import SECP256k1, VerifyingKey

def verify_message(address, signature, message):
    """驗證比特幣消息簽名"""

    # 解碼簽名
    sig_bytes = base64.b64decode(signature)
    if len(sig_bytes) != 65:
        return False

    recovery_flag = sig_bytes[0]
    r = int.from_bytes(sig_bytes[1:33], 'big')
    s = int.from_bytes(sig_bytes[33:65], 'big')

    # 計算消息雜湊
    prefix = b"\x18Bitcoin Signed Message:\n"
    msg_bytes = message.encode('utf-8')
    full_msg = prefix + bytes([len(msg_bytes)]) + msg_bytes
    msg_hash = hashlib.sha256(hashlib.sha256(full_msg).digest()).digest()

    # 從簽名恢復公鑰
    compressed = recovery_flag >= 31
    rec_id = (recovery_flag - 27) % 4

    try:
        pubkey = recover_public_key(msg_hash, r, s, rec_id, compressed)

        # 計算地址並比較
        recovered_address = pubkey_to_address(pubkey, address_type(address))
        return recovered_address == address
    except:
        return False

JavaScript 簽名

const bitcoin = require('bitcoinjs-lib');
const bitcoinMessage = require('bitcoinjs-message');

// 簽名
function signMessage(privateKeyWIF, message) {
  const keyPair = bitcoin.ECPair.fromWIF(privateKeyWIF);
  const signature = bitcoinMessage.sign(
    message,
    keyPair.privateKey,
    keyPair.compressed
  );
  return signature.toString('base64');
}

// 驗證
function verifyMessage(address, signature, message) {
  try {
    return bitcoinMessage.verify(message, address, signature);
  } catch (e) {
    return false;
  }
}

應用場景

1. 證明地址所有權

場景: 交易所需要驗證提現地址

1. 用戶請求將地址加入白名單
2. 交易所生成隨機消息: "Verify-12345-1700000000"
3. 用戶用該地址的私鑰簽名消息
4. 交易所驗證簽名
5. 確認用戶控制該地址

2. 證明身份

場景: 證明你是某筆交易的發送者

消息格式:
"I, the owner of bc1q..., confirm that I sent
transaction abc123... on 2024-01-15.
Timestamp: 1705300000"

簽名後,任何人都可以驗證:
1. 簽名者控制該地址
2. 該地址與交易相關

3. 空投資格證明

場景: 某協議向 BTC 持有者空投代幣

1. 協議拍照某個區塊高度的 UTXO 集
2. 用戶用持有 BTC 的地址簽名
3. 提交簽名和新鏈上的接收地址
4. 協議驗證後發放空投

消息示例:
"Claim airdrop to 0x123... for snapshot block 840000"

BIP-137 和 BIP-322

// BIP-137: 傳統消息簽名
// 就是上面描述的格式
// 只支持 P2PKH 風格的驗證

// BIP-322: 通用消息簽名
// 提議中,尚未廣泛實現
// 支持所有地址類型,包括 P2TR

BIP-322 格式:
to_spend = create_virtual_tx(message_hash)
to_sign = spend_tx(to_spend)
signature = witness_of(to_sign)

// 這使得可以用任何可花費的腳本來簽名
// 包括多簽、時間鎖等複雜腳本

安全考慮

  • 不要簽名未知消息:攻擊者可能構造惡意消息
  • 檢查消息內容:確保你理解你在簽名什麼
  • 時間戳:包含時間戳防止簽名被重複使用
  • 隨機數:驗證方應使用隨機挑戰
  • 離線簽名:敏感地址應使用離線設備簽名

常見錯誤

# 錯誤: 地址不在錢包中
bitcoin-cli signmessage "bc1q..." "message"
# error: Address does not refer to a key

# 解決: 確保地址屬於當前錢包

# 錯誤: 錢包已鎖定
bitcoin-cli signmessage "bc1q..." "message"
# error: Please enter the wallet passphrase with walletpassphrase first

# 解決: 先解鎖錢包
bitcoin-cli walletpassphrase "password" 60

# 錯誤: 消息格式問題
bitcoin-cli verifymessage "..." "..." "message\n"
# 注意換行符等特殊字符可能導致驗證失敗
已複製連結
已複製到剪貼簿