从go-ethereum实现的以太坊personal_sign(EIP-191)与ethers.js给出的签名结果不同。

huangapple go评论87阅读模式
英文:

Implementing Ethereum personal_sign (EIP-191) from go-ethereum gives different signature from ethers.js

问题

我正在尝试在Golang中生成类似ethers.js中实现的personal_sign类似的问题,但最终使用了常规的sign而不是个人的sign_implementation

Ethers.js代码:

// 数据的keccak256哈希
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

如您所见,哈希值是相同的,但签名是不同的:

0x469b07327fc41a2d85b7e69bcf4a9184098835c47cc7575375e3a306c3718ae35702af84f3a62aafeb8aab6a455d761274263d79e7fc99fbedfeaf759d8dc9361c(Ethers.js)

0xec56178d3dca77c3cee7aed83cdca2ffa2bec8ef1685ce5103cfa72c27beb61313d91b9ad9b9a644b0edf6352cb69f2f8acd25297e3c64cd060646242e0455ea00(Golang)

查看Ethers.js的源代码,我没有找到任何不同之处,除了填充的处理方式。

编辑
请查看已批准的答案

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))
}
英文:

I am attempting to generate a personal_sign in Golang like its implemented in ethers.js. Similar question but that ended up using the regular sign over the personal sign_implementation.

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

0xec56178d3dca77c3cee7aed83cdca2ffa2bec8ef1685ce5103cfa72c27beb61313d91b9ad9b9a644b0edf6352cb69f2f8acd25297e3c64cd060646242e0455ea00 Golang

Looking at the source code of Ethers.js I can't find anything different aside how the padding is managed.

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))
}

答案1

得分: 6

JavaScript代码中存在一个错误。

根据signer.signMessage()的文档(请参阅Note部分),字符串被UTF8编码,而二进制数据必须作为TypedArrayArray传递。<br>
Keccak哈希以十六进制编码返回,即作为字符串,因此它被错误地UTF8编码。相反,它必须转换为TypedArray。为此,该库提供了ethers.utils.arrayify()函数。

以下JavaScript代码基于发布的代码,但执行所需的十六进制解码:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

(async () =&gt; {
	let privateKey = &quot;0x8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f&quot;;
	let dataToSign = {&quot;data1&quot;:&quot;value1&quot;,&quot;data2&quot;:&quot;value2&quot;};

	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(&quot;signature&quot;).innerHTML = signature; // 0xfcc3e9431c139b5f943591af78c280b939595ce9df66210b7b8bb69565bdd2af7081a8acc0cbb5ea55bd0d673b176797966a5180c11ac297b7e6344c5822e66d1c
})();

<!-- language: lang-html -->

&lt;script src=&quot;https://cdn.ethers.io/lib/ethers-5.0.umd.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
&lt;p style=&quot;font-family:&#39;Courier New&#39;, monospace;&quot; id=&quot;signature&quot;&gt;&lt;/p&gt;

<!-- end snippet -->

生成以下签名:

0xfcc3e9431c139b5f943591af78c280b939595ce9df66210b7b8bb69565bdd2af7081a8acc0cbb5ea55bd0d673b176797966a5180c11ac297b7e6344c5822e66d1c

以下Go代码基于未修改的发布的Go代码,但使用来自JavaScript代码的密钥和数据进行比较:

package main

import (
	&quot;fmt&quot;
	&quot;github.com/ethereum/go-ethereum/common&quot;
    &quot;github.com/ethereum/go-ethereum/crypto&quot;
	&quot;encoding/hex&quot;
	&quot;encoding/json&quot;
	&quot;log&quot;
)

func signHash(data []byte) common.Hash {
    msg := fmt.Sprintf(&quot;\x19Ethereum Signed Message:\n%d%s&quot;, len(data), data)
    return crypto.Keccak256Hash([]byte(msg))
}

func main() {

	hexPrivateKey := &quot;8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f&quot;
	dataMap := map[string]string{&quot;data1&quot;:&quot;value1&quot;,&quot;data2&quot;:&quot;value2&quot;}
    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(&quot;0x&quot; + hex.EncodeToString(signatureBytes))
}

Go代码生成以下签名:

0xfcc3e9431c139b5f943591af78c280b939595ce9df66210b7b8bb69565bdd2af7081a8acc0cbb5ea55bd0d673b176797966a5180c11ac297b7e6344c5822e66d01

两个签名匹配,除了最后一个字节。

JavaScript代码以r|s|v的格式返回签名(参见此处)。v的大小为一个字节,是两个签名不同的值。

它是v = 27 + rid,其中rid是恢复ID。恢复ID的值介于0和3之间,因此v的值介于27和30或0x1b和0x1e之间(参见此处)。

另一方面,Go代码在最后一个字节中返回恢复ID而不是v。为了使Go代码的签名与JavaScript代码的签名在最后一个字节中也匹配,恢复ID必须替换为v

signatureBytes[64] += 27
fmt.Println(&quot;0x&quot; + hex.EncodeToString(signatureBytes))
英文:

There is a bug in the JavaScript code.

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.<br>
The Keccak hash is returned hex encoded, i.e. as string, and is therefore UTF8 encoded, which is incorrect. Instead, it must be converted to a TypedArray. For this purpose the library provides the function ethers.utils.arrayify().

The following JavaScript is based on the posted code, but performs the required hex decoding:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

(async () =&gt; {
	let privateKey = &quot;0x8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f&quot;;
	let dataToSign = {&quot;data1&quot;:&quot;value1&quot;,&quot;data2&quot;:&quot;value2&quot;};

	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(&quot;signature&quot;).innerHTML = signature; // 0xfcc3e9431c139b5f943591af78c280b939595ce9df66210b7b8bb69565bdd2af7081a8acc0cbb5ea55bd0d673b176797966a5180c11ac297b7e6344c5822e66d1c
})();

<!-- language: lang-html -->

&lt;script src=&quot;https://cdn.ethers.io/lib/ethers-5.0.umd.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
&lt;p style=&quot;font-family:&#39;Courier New&#39;, monospace;&quot; id=&quot;signature&quot;&gt;&lt;/p&gt;

<!-- end snippet -->

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:

package main

import (
	&quot;fmt&quot;
	&quot;github.com/ethereum/go-ethereum/common&quot;
    &quot;github.com/ethereum/go-ethereum/crypto&quot;
	&quot;encoding/hex&quot;
	&quot;encoding/json&quot;
	&quot;log&quot;
)

func signHash(data []byte) common.Hash {
    msg := fmt.Sprintf(&quot;\x19Ethereum Signed Message:\n%d%s&quot;, len(data), data)
    return crypto.Keccak256Hash([]byte(msg))
}

func main() {

	hexPrivateKey := &quot;8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f&quot;
	dataMap := map[string]string{&quot;data1&quot;:&quot;value1&quot;,&quot;data2&quot;:&quot;value2&quot;}
    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(&quot;0x&quot; + hex.EncodeToString(signatureBytes))
}

The Go Code gives the following signature:

0xfcc3e9431c139b5f943591af78c280b939595ce9df66210b7b8bb69565bdd2af7081a8acc0cbb5ea55bd0d673b176797966a5180c11ac297b7e6344c5822e66d01

Both signatures match except for the last byte.

The JavaScript code returns the signature in the format r|s|v (see here). v is one byte in size and is just the value in which both signatures differ.

It is v = 27 + rid where rid is the recovery ID. The recovery ID has values between 0 and 3, so v has values between 27 and 30 or 0x1b and 0x1e (see here).

The Go code, on the other hand, returns the recovery ID in the last byte instead of v. 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:

signatureBytes[64] += 27
fmt.Println(&quot;0x&quot; + hex.EncodeToString(signatureBytes))

huangapple
  • 本文由 发表于 2021年10月29日 07:42:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/69762108.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定