ECDSA
以太坊签名为什么需要加前缀"\x19Ethereum Signed Message:\n32"
原因是为了防止重放攻击。
以太坊中的交易签名采用 ECDSA 算法。该算法生成的签名是可逆的,意味着可以从签名恢复出原始消息。这使得攻击者可以拿到某笔交易的签名,将其应用到其他交易上,从而发起重放攻击。
为了防止这种攻击,在对消息签名之前,需要在消息开头添加固定的前缀 "\x19Ethereum Signed Message:\n32"
。这会使原始消息发生变化,因此其产生的签名也会不同。即使攻击者拿到签名,也无法将其应用到未加前缀的原始消息上。
所以以太坊区块链中的交易签名都需要遵循这种标准,在消息签名之前添加特殊前缀,从而防止重放攻击的发生。这个前缀已经成为一个行业标准,被各种以太坊相关的签名实现广泛采用。
以太坊签名不加前缀 "\x19Ethereum Signed Message:\n32"
就直接对消息签名,会导致签名过程中产生的哈希值发生变化,从而在恢复公钥和地址时出现错误,具体原因如下:
以太坊签名采用 ECDSA 算法,该算法需要先对消息进行哈希运算才能签名。
普通哈希算法(如 SHA3)对任意长度的数据都可以产生固定长度的哈希值。但直接对消息哈希无法防止重放攻击。
为防止重放攻击,需要在消息前添加特定前缀,使同一条消息添加前缀前后产生不同的哈希值。
以太坊采用的前缀是
"\x19Ethereum Signed Message:\n32"
,添加后再做哈希。如果不添加这个前缀,直接对消息哈希,得到的哈希值会与标准不一样。
不同的哈希值导致签名中的签名参数 r、s 不同,从签名恢复公钥时也会不一样。
公钥不同导致最终 Derived 的地址也会不一样。
因此恢复出的地址就会错误,不匹配真实地址。
所以在以太坊生态必须添加标准前缀再签名,否则交易会失败,或恢复出错误地址。
ethers 里的provider.signMessage
和signer.signMessage
都默认添加了前缀
以太坊 ECDSA 恢复签名的基本原理
以太坊ECDSA恢复签名的基本原理是:
1. 对消息进行哈希运算,得到哈希值h
2. 选取一个私钥k,计算签名(r, s)
r = (h + k * G) mod n
s = (k^-1 * (h + r * privKey)) mod n
其中G是生成点,n是曲线阶
3. 恢复公钥:
R = r * G
e = hash(消息)
P = s^-1 * (e * G + R)
其中P就是恢复出的公钥
4. 从公钥P计算出地址:
a. 取公钥P的最后64字节作为字节数组
b. 对其取Keccak-256哈希
c. 取结果的最后20字节就是地址
5. 将原始消息、签名(v, r, s)、恢复出的公钥和地址进行对比,如果一致,则证明签名有效和恢复过程正确。
6. 在实际实现中,需要处理一些细节,比如v的值的处理,签名编码的规范等。
以上就是以太坊ECDSA签名的基本恢复过程,通过签名可以反向求解出地址,验证签名的正确性。
eth_sign
和personal_sign
的区别
eth_sign
和 personal_sign
都是用于对以太坊数据进行签名的方法,但有以下几点主要区别:
- 签名的数据格式不同
eth_sign
: 消息的数据格式为\x19Ethereum Signed Message:\n + 消息长度 + 消息。即消息数据前需要添加 Ethereum 特定的前缀。personal_sign
: 消息的数据格式为\x19Ethereum Signed Message:\n + 消息。不需要消息长度。
- 签名所用的账户不同
eth_sign
: 使用发送交易的账户签名,通常是解锁状态的账户。personal_sign
: 使用指定的任意账户签名,账号可以是锁定状态。
- 签名的计算不同
eth_sign
: 对消息数据的原始字节进行签名。personal_sign
: 对消息数据的 UTF-8 编码进行签名。
- 使用场景不同
eth_sign
: 用于对区块链交易进行签名。personal_sign
: 用于对数据消息进行签名,如登录签名、非交易类数据签名等。
总体来说,personal_sign
使用更灵活,适用场景更广泛,而eth_sign
更专注于交易签名。但都需要遵循特定的数据格式要求。
ethers 分割 signature
// V5版本
const { r, s, v } = ethers.utils.splitSignature(
"0x003ebcbc4da71f53dc34bbbb0cbef8553eb6af4ac8ca4e214fc4d0cc122bed6915d0a7240bafd396abab1dcb315a7bc6df49d5600719800db337cb87567ff8891c"
);
// V6版本
import { splitSignature } from "@ethersproject/bytes";
const { r, s, v } = splitSignature(
"0x003ebcbc4da71f53dc34bbbb0cbef8553eb6af4ac8ca4e214fc4d0cc122bed6915d0a7240bafd396abab1dcb315a7bc6df49d5600719800db337cb87567ff8891c"
);