数字签名是什么数字签名,简单讲,就是一种证明「这份数据是我发的」的方法。本质上,就是用私钥去对一段消息去签名,对方用公钥去验证这份签名,证明这份私钥是由我发送的并且消息没有遭到篡改:https://learnblockchain.cn/shawn_shaw
数字签名,简单讲,就是一种 证明「这份数据是我发的」 的方法。本质上,就是用私钥去对一段消息去签名,对方用公钥去验证这份签名,证明这份私钥是由我发送的并且消息没有遭到篡改。 在以太坊上,使用到的数字签名(加密)算法是 ECDSA。
身份验证 数字签名能证明:消息确实是由某个持有私钥的人发出的。因为只有私钥持有者才能生成正确的签名,别人伪造不了。
消息完整
签名是基于消息的哈希生成的。如果消息内容哪怕只改动一丁点(哪怕一个标点符号),hash 就变了,签名也验证不了。
不可抵赖 一旦签了名,就无法否认自己签过。因为只有你拥有私钥,签名是你自己产生的,别人通过签名和消息可以恢复出来你的身份,别人无法伪造。
指得是原始的数据消息。例如一段字符串数据
Hello World
指的是原始数据经过 hash 算法生成的 32 byte 的数据。在以太坊中,这个消息会经过两次 keccak() 的算法进行生成。
hash
// 1. 直接把字符串hash
bytes32 messageHash = keccak256(bytes(message));hash
加上这个字符串标志的作用是防止重放攻击。细节我们在下面转化消息的步骤详解。
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
ECDSA 私钥本身,是一个 32 字节的大整数。生成方式如下(go-ethereum库)
privateKey, err := crypto.GenerateKey()
生成结果为:
0x9db7a2287bf11462793f2b5c726d12ce79f2e25fbc9d08316b55386061e35a4c
公钥是由私钥进行生成的。有两种公钥,压缩公钥和未压缩公钥。以太坊中使用的公钥为未压缩公钥。
未压缩公钥:
结构为:0x04 + X坐标+ Y 坐标。
大小为:uint8 + bytes32 + bytes32 = 65 字节
如:
0x049a9c81e4f44f721e610a9c3cb3c2f6a8ca57b142309ba5c51d6b135988594d8fe3e5c0c5649f413f62de7c3c3e3b4974600bc517a637da51967b15e79b3d9252
0x02或0x03 + X 坐标
如:0x021e2dda68120487e0807300c0f6e8e7d9e974a40f59ae3060d2ae6077fa66c092
在以太坊中,地址格式为未压缩公钥去掉 0x04后,经过 keccak() 哈希取后 20 字节(40 个 16 进制字符)而产生的。
如
0x49755928d2471581649f356f2a414e36d334055d
签名本质上也是一个 65 字节的字节序列,分别是有三个值(r、s、v),大小分别为 32 字节、32 字节、1 字节。r、s、v 简单拼接起来就是一个完整的签名消息。
如:
0x
2c6404e1145f38a8eb7b6a5d7648e6a711f3dfd32e7d7fa8a6c4b17e6b6c6b6d // r (32字节)
56c4f0a1b0494c6578723e1c5e8f302ff7f6ba674fbec6d1571318c43259b2e4 // s (32字节)
1b // v (1字节)
在以太坊中,可以通过 r、s、v 值以及以太坊签名消息来进行恢复出来公钥(可推导出地址)来进行验证。
如:
// 恢复 signer
address recovered = ECDSA.recover(ethSignedHash, v, r, s);
以太坊中使用到的签名算法是 ECDSA(一种基于椭圆曲线的签名算法),使用到的椭圆曲线是 secp256k1。
签名流程为:
Hash
用 keccak256 处理原始消息,得到 messageHash。
(如果是普通签名,还会加一段 \x19Ethereum Signed Message:\n32 前缀)messageHash 签名
使用 secp256k1 曲线和 ECDSA 签名算法,生成 (r, s, v)。r || s || v按顺序拼接成 65 字节数据。
hash)
// 1. 直接把字符串hash
bytes32 messageHash = keccak256(bytes(message));
第一次 hash 是直接对原始交易的数据进行使用 keccak() 函数进行 hash 化。
hash)
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
在这一步中,对第一步获得的 hash 值添加以太坊的字符串标识再进行一次 hash。通过添加以太坊的标识,明确告诉链上验证者「这是人为签名的消息,不是链上的交易或指令」。如果不加前缀,攻击者可以拿着你签名过的数据,在链上伪造一些危险的行为!这就是签名可重放攻击,例如:
from: 0xaaa...aaa
to: 0xbbb...bbb
value: 1 ETH
nonce: 5
gasLimit: 21000
gasPrice: 20 gwei
chainId: 1
假设我们有这一份链上交易数据。正常来说,直接使用 keccak256(hash) 后进行签名是可以发上以太坊网络进行交易发起的。
那么,假如此时有攻击者让你签了这个数据,拿到这个交易后直接发送到区块链网络,即发起了一次签名重放攻击。你的资金就发生了损失。(因为 signature 可以证明原交易数据的完整性)
但是,如果进行二次哈希,在第二次计算 hash 的时候,添加上以太坊的字符串。那么,这个签名则会被认为是离线签名,无法进行执行交易等危险行为。(因为最终 signature 只能保证 \x19Ethereum Signed Message:\n32 + hash 的完整性, 而不是可执行交易数据的完整性)
总得来说,二次签名并加上以太坊字符串标识是为了区分 链上交易 和 链下签名数据 两种类型。防签名重放攻击则是为了避免签名被使用在链上交易上面,避免资金丢失。
foundry 的 vm 来进行模拟签名。
通过使用私钥对消息 hash 进行签名,可以获取签名信息,也就是 r、s、v 三个值。
// 3. 签名,这一步和之前的步骤都发生在链下
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, ethSignedHash);
openzepplin 的 ECDSA 库,参数为: 消息哈希、r、s、v。 // 4. 恢复 signer
address recovered = ECDSA.recover(ethSignedHash, v, r, s);
recovered (一个地址),我们就可以进行签名人匹配,如果相等,则说明消息完整且发起者为 signer。
assertEq(recovered, signer, "Recovered signer does not match");contract TestSignVerifyOZ is Test {
using ECDSA for bytes32;
address signer;
uint256 privateKey;
function setUp() public {
privateKey = 123456789;
signer = vm.addr(privateKey);
}
function testSignAndRecoverOZ() public {
string memory message = "hello world";
// 1. 直接把字符串hash
bytes32 messageHash = keccak256(bytes(message));
// 2. 用 OZ 帮我们加前缀(标准 Ethereum Signed Message 格式)
bytes32 ethSignedHash = MessageHashUtils.toEthSignedMessageHash(messageHash);
console.log("message is:");
console.logBytes32( ethSignedHash );
// 3. 签名,这一步和之前的步骤都发生在链下
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, ethSignedHash);
console.log("signature is:");
console.logBytes( abi.encodePacked(r,s,v) );
// 4. 恢复 signer
address recovered = ECDSA.recover(ethSignedHash, v, r, s);
console.log("recovered is:");
console.logAddress( recovered );
console.log("signer is:");
console.log(signer);
assertEq(recovered, signer, "Recovered signer does not match");
}
}

如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!