[英]Implementing Ethereum personal_sign (EIP-191) from go-ethereum gives different signature from ethers.js
I am attempting to generate a personal_sign
in Golang like its implemented in ethers.js.我试图在 Golang 中生成一个personal_sign
,就像它在 ethers.js 中实现的一样。 Similar question but that ended up using the regular sign
over the personal sign_implementation
. 类似的问题,但最终在个人sign_implementation
上使用了常规sign
。
Ethers醚类
// keccak256 hash of the data
let dataHash = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(JSON.stringify(dataToSign))
);
//0x8d218fc37d2fd952b2d115046b786b787e44d105cccf156882a2e74ad993ee13
let signature = await wallet.signMessage(dataHash); // 0x469b07327fc41a2d85b7e69bcf4a9184098835c47cc7575375e3a306c3718ae35702af84f3a62aafeb8aab6a455d761274263d79e7fc99fbedfeaf759d8dc9361c
Golang:高朗:
func signHash(data []byte) common.Hash {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
return crypto.Keccak256Hash([]byte(msg))
}
privateKey, err := crypto.HexToECDSA(hexPrivateKey)
if err != nil {
log.Fatal(err)
}
dataHash := crypto.Keccak256Hash(dataToSign) //0x8d218fc37d2fd952b2d115046b786b787e44d105cccf156882a2e74ad993ee13
signHash := signHash(dataHash.Bytes())
signatureBytes, err := crypto.Sign(signHash.Bytes(), privateKey)
if err != nil {
log.Fatal(err)
}
// signatureBytes 0xec56178d3dca77c3cee7aed83cdca2ffa2bec8ef1685ce5103cfa72c27beb61313d91b9ad9b9a644b0edf6352cb69f2f8acd25297e3c64cd060646242e0455ea00
As you can see the hash is the same, but the signature is different:如您所见,哈希值相同,但签名不同:
0x469b07327fc41a2d85b7e69bcf4a9184098835c47cc7575375e3a306c3718ae35702af84f3a62aafeb8aab6a455d761274263d79e7fc99fbedfeaf759d8dc9361c
Ethers 0x469b07327fc41a2d85b7e69bcf4a9184098835c47cc7575375e3a306c3718ae35702af84f3a62aafeb8aab6a455d761274263d79e7fc99fbedfeaf759d8dc9361c
0xec56178d3dca77c3cee7aed83cdca2ffa2bec8ef1685ce5103cfa72c27beb61313d91b9ad9b9a644b0edf6352cb69f2f8acd25297e3c64cd060646242e0455ea00
Golang 0xec56178d3dca77c3cee7aed83cdca2ffa2bec8ef1685ce5103cfa72c27beb61313d91b9ad9b9a644b0edf6352cb69f2f8acd25297e3c64cd060646242e0455ea00
Golang
Looking at the source code of Ethers.js I can't find anything different aside how the padding is managed.查看 Ethers.js 的源代码,除了填充的管理方式之外,我找不到任何不同的东西。
Edit Check the approved answer编辑检查批准的答案
signHash(data []byte) common.Hash {
hexData := hexutil.Encode(data)
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(hexData), hexData)
return crypto.Keccak256Hash([]byte(msg))
}
There is a bug in the JavaScript code. JavaScript 代码中存在错误。
From the documentation of signer.signMessage()
(see the Note section), it appears that a string is UTF8 encoded and binary data must be passed as TypedArray
or Array
.从signer.signMessage()
的文档(请参阅注释部分)来看,字符串似乎是 UTF8 编码的,并且二进制数据必须作为TypedArray
或Array
传递。
The Keccak hash is returned hex encoded, ie as string, and is therefore UTF8 encoded, which is incorrect. Keccak 哈希返回十六进制编码,即作为字符串,因此是 UTF8 编码,这是不正确的。 Instead, it must be converted to a TypedArray
.相反,它必须转换为TypedArray
。 For this purpose the library provides the function ethers.utils.arrayify()
.为此,库提供了函数ethers.utils.arrayify()
。
The following JavaScript is based on the posted code, but performs the required hex decoding:以下 JavaScript 基于发布的代码,但执行所需的十六进制解码:
(async () => { let privateKey = "0x8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f"; let dataToSign = {"data1":"value1","data2":"value2"}; let dataHash = ethers.utils.keccak256( ethers.utils.toUtf8Bytes(JSON.stringify(dataToSign)) ); dataHashBin = ethers.utils.arrayify(dataHash) let wallet = new ethers.Wallet(privateKey); let signature = await wallet.signMessage(dataHashBin); document.getElementById("signature").innerHTML = signature; // 0xfcc3e9431c139b5f943591af78c280b939595ce9df66210b7b8bb69565bdd2af7081a8acc0cbb5ea55bd0d673b176797966a5180c11ac297b7e6344c5822e66d1c })();
<script src="https://cdn.ethers.io/lib/ethers-5.0.umd.min.js" type="text/javascript"></script> <p style="font-family:'Courier New', monospace;" id="signature"></p>
which produces the following signature:产生以下签名:
0xfcc3e9431c139b5f943591af78c280b939595ce9df66210b7b8bb69565bdd2af7081a8acc0cbb5ea55bd0d673b176797966a5180c11ac297b7e6344c5822e66d1c
The Go code below is based on the unmodified posted Go code, but using key and data from the JavaScript code for a comparison:下面的 Go 代码基于未修改发布的 Go 代码,但使用来自 JavaScript 代码的键和数据进行比较:
package main
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"encoding/hex"
"encoding/json"
"log"
)
func signHash(data []byte) common.Hash {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
return crypto.Keccak256Hash([]byte(msg))
}
func main() {
hexPrivateKey := "8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f"
dataMap := map[string]string{"data1":"value1","data2":"value2"}
dataToSign, _ := json.Marshal(dataMap)
privateKey, err := crypto.HexToECDSA(hexPrivateKey)
if err != nil {
log.Fatal(err)
}
dataHash := crypto.Keccak256Hash(dataToSign) //0x8d218fc37d2fd952b2d115046b786b787e44d105cccf156882a2e74ad993ee13
signHash := signHash(dataHash.Bytes())
signatureBytes, err := crypto.Sign(signHash.Bytes(), privateKey)
if err != nil {
log.Fatal(err)
}
fmt.Println("0x" + hex.EncodeToString(signatureBytes))
}
The Go Code gives the following signature: Go 代码给出了以下签名:
0xfcc3e9431c139b5f943591af78c280b939595ce9df66210b7b8bb69565bdd2af7081a8acc0cbb5ea55bd0d673b176797966a5180c11ac297b7e6344c5822e66d01
Both signatures match except for the last byte.除了最后一个字节外,两个签名都匹配。
The JavaScript code returns the signature in the format r|s|v
(see here ). JavaScript 代码以r|s|v
格式返回签名(请参阅此处)。 v
is one byte in size and is just the value in which both signatures differ. v
大小为 1 个字节,只是两个签名不同的值。
It is v = 27 + rid
where rid
is the recovery ID.它是v = 27 + rid
,其中rid
是恢复ID。 The recovery ID has values between 0 and 3, so v
has values between 27 and 30 or 0x1b and 0x1e (see here ).恢复 ID 的值介于 0 和 3 之间,因此v
值介于 27 和 30 或 0x1b 和 0x1e 之间(请参阅此处)。
The Go code, on the other hand, returns the recovery ID in the last byte instead of v
.另一方面,Go 代码在最后一个字节而不是v
返回恢复 ID。 So that the signature of the Go code matches that of the JavaScript code in the last byte as well, the recovery ID must be replaced by v
:为了使 Go 代码的签名也与最后一个字节中的 JavaScript 代码的签名相匹配,恢复 ID 必须替换为v
:
signatureBytes[64] += 27
fmt.Println("0x" + hex.EncodeToString(signatureBytes))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.