入門
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"
# 注意換行符等特殊字符可能導致驗證失敗
已複製連結