现在个好像越来越多Dapp用到链下签名,可以设计多步骤、需要不同私钥签署同意之后一起上链给智能合约验证执行。其中有名的例子包含了许多去中心化交易所使用的0xProtocal,个人觉得是个非常聪明的设计,这里就记录一下自己试着用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程序代码比较好懂:
简单的使用方法如下:把要进行签章的string ( orderHash)直接连同 privateKey 丢进函示就好。
其实是丢进去签名的 orderHash 要不要先转换成为 bytes 都可以,出来的结果会是相同的。 sign 函数回传的结果会包含 message 、 messageHash 以及 r s v 三个椭圆签名结果。其中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),最后一定会变成一个32bytes的东西。如果我们自己纯靠私要签名讯息的话,也势必要先通过这个函式来整理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 则是验签结果:
那么下面这个函式就应该回传我们所用来签名的public Key。注意到这里会使用 keccak256 来把 hash加上ETH规定的prefix ,我们很常可以在合约中看到这段文字,因为web3默认的签名就要这样来还原。当然,如果想要设计没有用prefix的,那么这一步就省了。
可以试试看到我deploy的合约上直接call这两个函示玩玩看结果:
Ropsten 地址:0x209ce2886420b27e497ce343e59574166400f1ab