1. ECDSA 的魔法:不用公钥也能验签
在大多数区块链系统中,数字签名 的作用类似传统网银的U盾:证明“我是我”,却不必把公钥随身携带。以太坊主打的是 椭圆曲线数字签名算法(ECDSA)——它有一个非常性感的特性:公钥可以从签名中还原。这意味着在网络中传播的只有 签名(r、v、s),而无需额外传输 64~65 字节的 公钥(或地址);接收方只需根据这 3 组字节,就能还原公钥、校验签名是否有效。这不仅节省带宽,还降低了系统复杂度和攻击面。
核心关键词:ECDSA、数字签名、公钥恢复、以太坊
2. 拆解签名字段:r、s、v 各肩负什么使命?
- r(32 字节)
代表椭圆曲线上某点的 x 坐标,由签名时的随机数 k 决定,可理解为“签名随机种子的公共承诺”。 - s(32 字节)
与 k、私钥、消息哈希相关,用于验证对方是否真的持有私钥。 - v(1 字节,legacy 交易 27/28;EIP-155 后为 37/38 起)
又称 recovery id,为还原公钥提供“从多个候选中选谁”的最后一道保险。
只要手头有这三个字段,再知道“签的是哪段数据”,任何人都能把 公钥 算回来。
3. 手动还原公钥的整体流程
3.1 明确 sign hash
交易签名 or personal_sign 调用时都会对 message 做一次 Keccak-256 哈希:hash = keccak256(message)。
3.2 送入 ecrecover 预编译合约
以太坊内置 0x01 地址的预编译合约 ecrecover,输入参数:
input = [hash (32 bytes)] + [v (32 bytes)] + [r (32 bytes)] + [s (32 bytes)]调用 staticcall(gas, 0x01, input, 128, ret, 32),合约返回未压缩 64 字节公钥(或 0 表示非法签名)。
3.3 计算地址
对上一步得到的公钥再走一次 Keccak-256,取后 20 字节即 以太坊地址。把该地址与交易中 from 地址比对,即可完成验签。
4. Go 与 Solidity 示例代码
4.1 Go 实现(基于 go-ethereum)
package main
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func main() {
hash := crypto.Keccak256([]byte("hello world"))
sig := []byte{...} // 65 字节签名: r(32) + s(32) + v(1)
v := sig[64]
if v < 27 {
v += 27
}
pubKey, err := crypto.SigToPub(hash, append(sig[:64], byte(v-27)))
if err != nil {
panic(err)
}
addr := crypto.PubkeyToAddress(*pubKey)
fmt.Println("Recovered address:", addr.Hex())
}4.2 Solidity 实现
pragma solidity ^0.8.0;
library ECDSA {
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
require(v == 27 || v == 28, "Invalid v");
require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "Invalid s");
return ecrecover(hash, v, r, s);
}
}借助以上函数即可在链上验证签名的 from 字段是否匹配恢复后的地址。
👉 一图看懂 ECDSA 签名重建公钥全过程,点进来对照实战!
5. 常见误区:为什么签名只有 65 字节?
许多人误以为签名内含公钥。实际上,65 字节仅由 r(32) + s(32) + v(1) 组成,不含其他数据。能把“曲线点”压缩得如此紧凑,靠的正是 椭圆曲线公钥恢复算法。
6. 原子拆解:ecrecover 的底层细节
当调用 ecrecover 时,EVM 内部的闭源(circuit)流程如下:
- 借助 v 位,选定两条候选曲线。
- 使用 r 在椭圆曲线上找到两个同余点,再用 s 与消息哈希做一次倍点运算。
- 通过点加/点减恢复完整公钥,最后哈希取后 20 字节回报率地址。
通俗讲:v 让还原不再“二选一”,而是“一拍即中”。
7. 与 EIP-155 的恩怨情仇
若交易中使用了 EIP-155 链 ID 保护,v 的值会加上 2 * chainID + 35。在链外恢复公钥时需先还原原始 v,否则签名将被判无效。
链上合约无须操心,因为 ecrecover 会忽略 EIP-155 的保护位。
👉 链上链下地址不一致?教你 30 秒校准 EIP-155 恢复值!
8. FAQ:那些你最想问的问题
Q1:如果 r、s 或 v 写错,ecrecover 会返回空地址吗?
对。ecrecover 遇到无法恢复的场景(错误签名、点在曲线上不存在等)会返回 0x0 地址,这是一种快速失败机制。
Q2:Solidity 能否一次还原两个待选公钥?
理论上可行,但 Solidity 内不存在点乘/点加库,你需要自己用预编译合约或链下完成。通常不建议在链上操作复数公钥,因 gas 不可控。
Q3:为什么同样的消息同一私钥不同时间签名会得到不同公钥?
不会的。只要 v、r、s 一致,恢复出的公钥就是恒定的那一支。之所以会看到“不同签名”,是因为随机数 k 每次不同,所以 r、s 是会变化的。
Q4:可以用该机制做匿名登录吗?
可以,这正是 “以太坊登录(Sign-in with Ethereum)” 的核心思路。后端收到签名后还原地址,比对数据库即可完成免密码登录,兼顾匿名与安全。
Q5:Go 代码里为什么要把 v-27?
golang 的库默认 v 取值范围是 0 或 1,而大多数交易/钱包的 v 是 27/28,故需减 27 对齐。
Q6:私钥丢了还能用签名反推吗?
不能。签名恢复只能得到 公钥,逆向无法取得 私钥。这是椭圆曲线离散对数难题的根基。
9. 知识卡片总结
- 核心观点:以太坊通过 ECDSA 公钥恢复,省去每笔交易携带 64-65 字节公钥的麻烦。
- 关键词检测:签名太小程序、公钥恢复算法、ecrecover 预编译合约、以太坊验签、椭圆曲线算法。
- 安全锦囊:永远不要用过时库输入 v=0/1 以内的值,需手动对齐 EIP-155 及厂商差异。
牢记一句话:签名是通行证,公钥是身份证,但身份证其实一直藏在通行证里。