现在个好像越来约多Dapp用到链下签名,可以设计多步骤、需要不同私钥签署同意之后一起上链给智能合约验证执行。其中有名的例子包含了许多去中心化交易所使用的0x Protocal,个人觉得是个非常聪明的设计,这里就记录一下自己试着用web3玩玩链下签名的心得。

web3 签名

其实虽说签名的过程就是把一段讯息加上私钥进行ECDSA签张,但其实在Ethereum世界里的签名还加了一个小规则,就是要在要签名的message在Hash之前,还要在前面加上一小段prefix:

message= "\x19Ethereum Signed Message:\n" + message.length + message

在web3提供好给我们的sign函式(web3.eth.accounts.sign)当中,就已经包含了上述步骤。直接看web3.eth.accounts.sign程式码比较好懂:

GitHub连结

简单的使用方法如下:把要进行签名的string (orderHash)直接连同privateKey丢进函示就好。

其实是丢进去签名的orderHash要不要先转换成为bytes都可以,出来的结果会是相同的。sign函数回传的结果会包含message、messageHash以及rsv三个椭圆签名结果。其中message是原来我想要签名的内容(orderHash),messageHash则是程式中自动帮我们加上prefix,并且进行Sha3 Hash的结果,也就是真正被拿去用私钥签名的一段Hash值。

简而言之web3什么都帮你做好了,不要像我一样傻傻的自己想办法加prefix最后才发现做了两次。

secp256k1 签名

那么如果我们想要单纯用私钥签名一段资料,不要有Ethereum定义的那些prefix的话,就必须要直接调用secp256k1这一包library了。不过在用之前要知道,所有要丢给secp256k1签名的message,长度都必须是256 bits,也就是32 bytes。刚刚我们说web3的签名函式丢什么都可以,是因为它会帮我加上prefix之后再做sha3 Hash (keccak),最后一定会变成一个32 bytes的东西。如果我们自己纯靠私要签名讯息的话,也势必要先通过这个函式来整理input长度。我在这里举个例子,手动作上面web3的所有要丢给secp256k1签名的message,长度都必须是256 bits帮我们包好的流程,也就是自己以符合Ethereum协议的方法做一遍,比较方便我们验证结果。也就是32 bytes。刚刚我们说web3的签名函式丢什么都可以,是因为它会帮我加上prefix之后再做sha3 Hash (keccak),最后一定会变成一个32 bytes的东西。如果我们自己纯靠私要签名信息的话,也势必要先通过这个函式来整理input长度。我在这里举个例子,手动作上面web3的sign帮我们包好的流程,也就是自己以符合Ethereum协议的方法做一遍,比较方便我们验证结果。

所以一开始我们可以透过soliditySha3来把prefix跟orderHash混在一起然后进行hash,这一段的结果会跟上面产出的messageHash相同,也等同于在Solidity里面使用keccak:

keccak256("\x19Ethereum Signed Message:\n32", hash)

得到这串「要签名的hash」之后,在丢进secp256k1之前,要先转成bytes (长度会为32),存入buffer,然后才能进行签名。若是直接用string的话,会发生message length is invalid的错误。同理,用来签名的privateKey也要转换成Buffer才行。

使用secp256k1回传的物件里还需要自己解析出r,s,v三个元素,不过我是直接复制贴上web3里面包的做法。

所以说,如果自己使用secp256k1来签名的话,可以略过加上prefix那一段,未来在智能合约上验章也可以少一段,不过还是需要使用到keccak来进行杂凑就是了。

Solidity 验章

好不容易签好章当然就是要来线上验章了。Solidity上面验章很简单,只要使用ercrecover这个function就可以了。我们让hash是一个bytes32的数,套用我们前面的例子,就是最原始的orderHash值。而v,r,s则是签名结果:

bytes32 hash uint8 v bytes32 r bytes32 s

那么下面这个函式就应该回传我们所用来签名的public Key。注意到这里会使用keccak256来把hash加上ETH规定的prefix ,我们很常可以在合约中看到这段文字,因为web3预设的签名就要这样来还原。当然,如果想要设计没有用prefix的,那么这一步就省了。

ecrecover( keccak256("\x19Ethereum Signed Message:\n32", hash), v, r, s );

可以试试看到我deploy的合约上直接call这两个函示玩玩看结果:

Ropsten地址:0x209ce2886420b27e497ce343e59574166400f1ab

Ropsten Accounts, Address and Contracts

The Ethereum BlockChain Explorer, API and Analytics Platform

ropsten.etherscan.io

小结

虽然整理下来很简单,但是我可是花了好多时间才豁然开朗原来web3这么鸡婆。总之感觉是很多人可能遇到的问题,就整理一波吧~