02.sign a message
2023-08-16 23:17:53 # 17.signature

sign a message

通常情况下,我们使用相同的私钥,对相同的消息签名,得到的签名是一样的,无论我们使用的是web3py, web3js还是ethersjs的工具库。

比如下面的python代码,就是生成了固定的签名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from eth_account import Account
from web3 import Web3

messagehash = Web3.keccak(text="Hello, world!")
print("message's hash",messagehash.hex())
privatekey ="0x0000000000000000000000000000000000000000000000000000000000000001"
signMessage = Account.signHash(message_hash=messagehash, private_key=privatekey)

print("r = ", Web3.to_hex(signMessage.r))
print("s = ", Web3.to_hex(signMessage.s))
print("v = ", Web3.to_hex(signMessage.v))

# the result
# message's hash 0xb6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e4
# r = 0xf34a885406bbff23b48777067309abfef26339490350c138ddd1b033901afff6
# s = 0x18d6b1fbf0d70207621e91beb0599ae01fc8e251d9bcb94f6203ce681fab71b6
# v = 0x1c

ethersjs的工具库也是类似的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const ethereumjsUtil = require('ethereumjs-util');

// 要签名的消息
const message = 'Hello, world!';

// 私钥(注意:这只是一个示例私钥,不应该在实际项目中使用)
const privateKey = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex');

// 生成消息的 Keccak-256 哈希
const messageHash = ethereumjsUtil.keccak256(message);

// 使用私钥对消息哈希进行签名
const signature = ethereumjsUtil.ecsign(messageHash, privateKey);

// 将签名结果进行格式化
const formattedSignature = {
v: signature.v,
r: signature.r.toString('hex'),
s: signature.s.toString('hex')
};

console.log('Message:', message);
console.log('Message Hash:', messageHash.toString('hex'));
console.log('Signature:', formattedSignature);

// the result
// Message: Hello, world!
// Message Hash: b6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e4
// Signature: {
// v: 28,
// r: 'f34a885406bbff23b48777067309abfef26339490350c138ddd1b033901afff6',
// s: '18d6b1fbf0d70207621e91beb0599ae01fc8e251d9bcb94f6203ce681fab71b6'
// }

这样看来,所有封装好的工具库都是得到相同的结果,这也印证了平时我们的认知:相同的私钥对相同的消息,只能生成相同的签名。其实不然,这些封装好的工具库其实是对其中一个参数固定了不取值:临时密钥。如果这个值修改,得到的签名就会不同。

比如在上面代码的js版本中,是通过ethereumjs-util这个库进行签名的,我们来追溯源码,最终找到:node_modules\ethereumjs-util\dist\signature.js:

1
2
3
4
5
6
7
8
9
10
exports.ecsign = function (msgHash, privateKey, chainId) {
var sig = secp256k1.sign(msgHash, privateKey});
var recovery = sig.recovery;
var ret = {
r: sig.signature.slice(0, 32),
s: sig.signature.slice(32, 64),
v: chainId ? recovery + (chainId * 2 + 35) : recovery + 27,
};
return ret;
};

还有node_modules\ethereumjs-util\dist\secp256k1v3-adapter.js:

1
2
3
exports.sign = function (message, privateKey, options) {
......
};

可以看到,secp256k1算法是还有一个参数options的,在ecsign实现的时候,这个值并没有传,使用了默认值(各大封装好的工具库都是一样使用这个默认值,好像是啥都没填写,没找到具体值,似乎是undifined)。那么如果我们将options给一个随机值,得到的签名就会不同,因此我们修改源码:

1
2
3
4
5
6
7
8
9
10
11
const { randomBytes } = require('crypto');
exports.ecsign = function (msgHash, privateKey, chainId) {
var sig = secp256k1.sign(msgHash, privateKey,{data: randomBytes(32)});
var recovery = sig.recovery;
var ret = {
r: sig.signature.slice(0, 32),
s: sig.signature.slice(32, 64),
v: chainId ? recovery + (chainId * 2 + 35) : recovery + 27,
};
return ret;
};

这样,我们每次签名,得到的结果都不一样了,成功做到:相同私钥,相同消息,不同签名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[Running] node "d:\ethersjs\practice\capturetheether.js"
Message: Hello, world!
Message Hash: b6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e4
Signature: {
v: 27,
r: '4706db97b1d93ceb09919e76cd964574c6742b78dd0052899b2e0988710abbd8',
s: '48a4bcf9d488aa5531f27adeef52388021461139eea8db19cc5bf73d8162ce21'
}

[Done] exited with code=0 in 0.386 seconds

[Running] node "d:\ethersjs\practice\capturetheether.js"
Message: Hello, world!
Message Hash: b6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e4
Signature: {
v: 28,
r: 'c192b6a1e32ff0c3400632f5bf5e196a2c9d7abf05884ea9b4de3cfabfd2d04a',
s: '446c55c8a713162564aeb3216fe1003ad901ff6ed7aef89d6f841c722bf73326'
}

我们知道,在solidity预编译的ecRecover合约的ecdsa算法中,使用的是secp256k1算法,临时密钥options,会影响到它验证吗?回答:不会影响。因为顾名思义,临时密钥options,只是一个临时性的东西,他取任何值都可以,不会影响结果的有效性。这就意味着,用相同的私钥,签名相同的消息,得到不同的签名结果,这些签名都是有效的,可以通过ecrecover的验证。