在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 則是驗簽結果:
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