使用 Ethers.js 簽章與檢驗
之前在 Solidity 智能合約 - ecrecover 簽章檢驗這篇文章,說明了智能合約如何檢驗簽章,這篇文章將說明如何使用 Ethers.js 來進行簽章與檢驗。本篇文章使用 Ethers.js 6.7.1 版本。
Signer
後面內容要使用 Signer 物件,這邊先簡單說明如何取得。
私鑰 signer
1 | import { JsonRpcProvider, Wallet } from "ethers"; |
瀏覽器錢包 signer
1 | import { BrowserProvider } from "ethers"; |
簽章
相較於 Web3.js 有很多種方法, Ethers.js 有下面同步和非同步的版本:
- signer.signMessage
- signer.signMessageSync
關於 EIP-712 相關的方法,參考使用 Ethers.js 進行 EIP-712 類型結構化資料簽名。
signer.signMessage
固定使用 EIP-191 前綴
1 | signer.signMessage(message: string | Uint8Array): Promise<string> |
- message: 簽署的內容,如果是字串則視為純文字,二進位資料使用 Uint8Array。
- 回傳值: 為 Promise 物件,內容為簽章。
加上前綴,簽署內容為
1 | “\x19Ethereum Signed Message:\n” + message.length + message |
例如我們簽署二進位內容
1 | import { getBytes } from "ethers"; |
在 Metamask 會跳出簽章訊息如下
可以用下面函式切開簽章
1 | import { Signature } from "ethers"; |
對應在智能合約使用前綴的方式來檢驗:
1 | bytes32 messageHash = 0xc1af4b94166cd32fc49b7b926cbb91ee421de2d04450e8ae57857b9b56ac7e53; |
或是簽署純文字內容
1 | const message = 'OK!'; |
會跳出簽章訊息如下
智能合約
1 | string memory message = "OK!"; |
注意文字如果有 UTF-8 的話,在鏈上的簽署的長度要是 bytes 長度。例如:
1 | // 6 |
signer.signMessageSync
signMessage 的同步版本,只能在 Node.js 使用
1 | signer.signMessageSync(message: string | Uint8Array): string |
- message: 簽署的內容,如果是字串則視為純文字,二進位資料使用 Uint8Array。
- 回傳值: 簽章。
可以直接取得簽章
1 | const signature = signer.signMessageSync(message); |
其他和 signMessage 一樣,不重複說明。
簽章格式
上面簽章有切開和未切開的兩種格式,切開就是照固定長度切開,前面 32 bytes 為 r,中間 32 bytes 為 s,最後 1 byte 為 v。拿上面的例子來看:
signature: 0xe1077fb9321c187d8a43926896abac5455ce6add269e098f855ff059d6b846a356320be5f6d79c4d0e5583d6b9a2e50fae78f1fb5ff0553541e69c66dae2b2f81b
r: 0xe1077fb9321c187d8a43926896abac5455ce6add269e098f855ff059d6b846a3
s: 0x56320be5f6d79c4d0e5583d6b9a2e50fae78f1fb5ff0553541e69c66dae2b2f8
v: 0x1b
所以要切開簽章除了使用上面提的方法之外,也可以自己簡單的處理:
1 | function splitSignature(signature) { |
檢驗簽章
除了在智能合約上的檢驗之外,我們也可以使用 Ethers.js 來檢驗,可以作為後端程式登入之類的用途。可以用下面的函式:
- verifyMessage
- recoverAddress
verifyMessage
固定使用 EIP-191 前綴,簽書內容可輸入純文字或二進位資料。
1 | verifyMessage(message: Uint8Array | string, sig: SignatureLike): string |
- message: 簽署的內容,如果是字串則視為純文字,二進位資料使用 Uint8Array。
- sig: 簽章,可以是未切開的字串,或者是包含 vrs 的物件。
- 回傳值: 簽署人地址。
二進位內容
1 | import { getBytes, verifyMessage } from "ethers"; |
純文字內容
1 | const message = 'OK!'; |
recoverAddress
固定使用 EIP-191 前綴,簽書內容只能輸入二進位資料。
1 | recoverAddress(digest: Uint8Array | string, signature: SignatureLike): string |
- digest: 簽署的內容,字串必須是 0x 開頭的字串或使用 Uint8Array。
- sig: 簽章,可以是為切開的字串,或者是包含 vrs 的物件。
- 回傳值: 簽署人地址。
0x 開頭的字串可以直接丟入,用 getBytes 轉換也可以。
1 | import { recoverAddress } from "ethers"; |
簽名延展性 (Signature Malleability)
Ethers.js 有處理簽名延展性 (Signature Malleability) 問題,篡改過的簽章會出現 INVALID_ARGUMENT 的錯誤。
總結
- 簽署使用
signMessage
比較通用。 - 檢驗簽章純文字可使用
verifyMessage
。 - 檢驗簽章二進位內容可使用
recoverAddress
。
延伸閱讀
Solidity 智能合約 - ecrecover 簽章檢驗
Solidity 智能合約 - 簽名延展性 (Signature Malleability)
使用 Web3.js 簽章與檢驗
使用 Ethers.js 進行 EIP-712 類型結構化資料簽名