协议演进 / BIP/比特币改进提案 / BIP-137
BIP-137
为用比特币私钥签署任意消息定统一签名格式:65 字节签名加一个头字节,用头字节的取值区间区分 P2PKH、P2SH 隔离见证与 Bech32 地址,使不同客户端能互相验签。本 BIP 目前处于已部署状态。
一、档案卡
| 编号 | BIP-137 |
|---|---|
| 标题 | Signatures of Messages using Private Keys |
| 作者 | Christopher Gilliard <[email protected]> |
| 指定日期 | 2019-02-16 |
| 层级 | Applications(应用) |
| 类型 | Specification(规范类) |
| 状态 | Deployed(已部署) |
| 阅读版 | https://bips.dev/137 |
| 官方原文 | https://github.com/bitcoin/bips/blob/master/bip-0137.mediawiki |
二、放在哪一族
消息签名与地址类型辨认族。BIP-137 管的不是链上交易签名,而是链下用同一把私钥给一段文本消息签名、让别人验“这话确实是持钥人签的”。
它在族里的位置需要和后继者一起看:
BIP-322:通用消息签名(generic signmessage)。原文开篇即说明:已有一个新签名格式被定义出来,相对本 BIP 有若干优势;保留 BIP-137 是为了与既有实现向后兼容。细节应以 BIP-322 为准去看新方案。
BIP-141:隔离见证。正是 SegWit 带来 P2SH 包裹与 Bech32 两类新地址后,“这把钥匙对应哪类地址”才成为验签时必须说清的问题,本 BIP 要补的正是这块标准空白。
所以这一族的读法是:BIP-137 记录并统一了当时各客户端已在用的做法,BIP-322 是面向之后的新格式。
三、它要解决什么
比特币私钥不只能签交易,也能签任意消息,这早已成为习惯用法。原文列出的用途包括:抵押用的资金证明、信用证明、活动入场、空投、审计等。
问题出在地址多样化之后:
- 用 P2PKH 地址的私钥签消息,做法早已为人熟知,当时并没有 BIP 把它写下来;
- SegWit 出现后有了 P2SH 形式与 Bech32 形式的地址,若没有具体标准,验签方无从判断一个签名对应的是 P2PKH、P2SH 还是 Bech32 地址。
BIP-137 因此要做两件事:把既成做法写成明确标准,并用签名格式本身把地址类型编码进去,让不同客户端之间能签、能验、结果一致。
四、规则本身
4.1 签名的构成
一个比特币消息签名共 65 字节,序列化为:
[1 字节头][32 字节 r 值][32 字节 s 值]
其中 r 与 s 是 ECDSA 签名的两个分量,可用标准 ECDSA 库函数算出。头字节像一个位掩码,携带恢复公钥所需的信息。
头字节的低 2 位存 recId,取值 0 到 3,由 ECDSA 库在签名时给出;验签时靠它恢复出公钥。其余含义靠给 recId 加一个表示地址类型的常数来编码。
4.2 头字节的取值区间
原文规定的对应关系是本 BIP 的核心:
- 27–30:P2PKH,未压缩公钥(基准常数 27);
- 31–34:P2PKH,压缩公钥(常数 31);
- 35–38:SegWit P2SH,即 P2SH-P2WPKH(常数 35);
- 39–42:SegWit Bech32,即 P2WPKH 版本 0 见证地址(常数 39)。
原文正文以区间方式复述同一规则:头值 31 到 34 表示压缩地址,35 到 38(含)表示 P2SH SegWit 地址,39 到 42 表示 Bech32 地址。
4.3 验证怎么做
验签时,从头字节减去对应常数,得到 recId;再用 recId、r、s 与消息哈希恢复公钥,并据头值区间判断公钥应按压缩还是非压缩、对应哪类地址来比对。
原文给出了改编自 BitcoinJ 的 Java 示例代码,演示完整流程:Base64 解码、检查长度至少 65 字节、检查头值在 27 到 42 之间、按区间调整头值与压缩标志、对格式化后的消息做双重 SHA256、最后用 ECDSA 恢复公钥。头值越界(如小于 27 或大于 42)应报签名异常。
4.4 原文的背景铺垫
规范正文在讲格式之前,先用一节给不熟悉 ECDSA 的读者补背景,本页只摘其与签名格式直接相关的定义:
私钥是一个只有生成者知道的秘密数,在比特币中是单个无符号 256 位整数(32 字节),持有与资金对应的私钥即可花费资金。
公钥由私钥算出、不能反推,分压缩与非压缩两种:压缩公钥 33 字节,非压缩公钥 65 字节。头字节区间要区分压缩与否,根源就在这里。
签名在密码学上由被签内容的哈希加私钥生成,其本身是 r 与 s 两个数;验方用公钥即可判断签名是否真实,无须知道私钥。
消息签名复用的正是这套交易签名背后的同一数学,只是被签对象从交易换成了任意消息文本。
4.5 向后兼容的提醒
因为格式包含 P2PKH 的情形,它是向后兼容的。但原文提醒:有些软件对头值范围有旧的检查,会把较新的 SegWit 头值类型报为错误。也就是说,老软件验不了新地址类型的签名,是预期内的兼容代价,不是签名本身无效。
五、谁必须动
普通持币人:不必改动任何链上设置。需要签消息时,用支持相应地址类型的钱包签,验方需用认得该头值区间的软件验。
钱包开发者:这是主要实现方。要按区间生成与解析头字节、正确处理压缩与非压缩公钥、用 recId 恢复公钥,并决定是否同时支持 BIP-322 的新格式。
全节点与矿工:无须动。消息签名是链下应用行为,不进区块、不影响交易验证。
共识层:不动。本 BIP 不提出任何共识规则变化,原文的影响部分强调的是钱包之间应可互操作、用同一套约定判断签名是否有效。
六、现在算不算数,以及一个常见误读
状态为 Deployed(已部署)。按原文的定位,它描述的是“今天已在市面上的不同客户端之间”签、验消息所用的标准,并因包含 P2PKH 而与旧做法兼容;同时原文主动指路 BIP-322:新格式已有定义且有若干优势,本 BIP 的价值主要是与既有实现保持兼容。读时应把两者分工记清,而不是把本页当作消息签名的最终方案。
原文还留了一句可扩展的安排:本 BIP 可以随新签名格式的出现而更新。
常见误读:
“能验过一条 BIP-137 消息签名,就等于链上转了一笔账、或这把钥匙现在还控制着币。”
不对。消息签名只证明签名当时持钥人对那段消息做过签署,它不是交易,不移动任何输出,也不能单独证明当下时刻的持币状态;原文列举的资金证明等用途,都要结合具体场景另行核验,不能把“验签通过”直接等同于“资金已转移或仍在”。
七、延伸
- 阅读版:https://bips.dev/137
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0137.mediawiki
- 相关:BIP-322 通用消息签名、BIP-141 隔离见证(共识层)
- 仓库总目录:https://github.com/bitcoin/bips
