之前在的文章 Solidity 智能合約 - EIP-712 類型結構化資料雜湊和簽名,說明了智能合約如何檢驗 EIP-712 的簽章。這篇文章將說明如何使用 Web3.js 來進行 EIP-712 簽章。本篇文章使用 Web3.js 4.1.1 版本。
簽章
瀏覽器錢包簽章
Web3.js 可以使用下面函式來進行 EIP-712 的簽章:
- web3.eth.signTypedData
- web3.currentProvider.request
瀏覽器錢包可以使用上面的函式,但 Web3.js 沒有內建私鑰的簽署方式,所以後面會介紹另一個套件來處理。
web3.eth.signTypedData
這個函式在 4.1.0 加入
1
| web3.eth.signTypedData(address: string, typedData: Eip712TypedData): Promise<string>
|
- address: 簽署人地址。
- typedData: 簽署的內容,包含 EIP-712 的 domain、類別定義和訊息內容,直接參考下面範例說明。
- 回傳值: 為 Promise 物件,內容為簽章。
使用之前的範例合約來說明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
contract Example is EIP712 { struct Mail { address from; address to; string contents; }
bytes32 private constant MAIL_TYPEHASH = keccak256( "Mail(address from,address to,string contents)" );
constructor() EIP712("Example", "1") { }
function hashStruct(Mail memory mail) public pure returns (bytes32) { return keccak256( abi.encode( MAIL_TYPEHASH, mail.from, mail.to, keccak256(abi.encodePacked(mail.contents)) ) ); }
function recover(Mail memory mail, uint8 v, bytes32 r, bytes32 s) public view returns (address) { bytes32 structHash = hashStruct(mail); bytes32 hash = _hashTypedDataV4(structHash); return ecrecover(hash , v, r, s); } }
|
對應的 Web3.js 用法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| const address = '0xDD980c315dFA75682F04381E98ea38BD2A151540';
const chainId = 1;
const verifyingContract = '0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8';
const domain = { chainId, verifyingContract, name: 'Example', version: '1' };
const types = { EIP712Domain: [ { name: 'name', type: 'string', }, { name: 'version', type: 'string', }, { name: 'chainId', type: 'uint256', }, { name: 'verifyingContract', type: 'address', } ],
Mail: [ { name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'contents', type: 'string' } ] };
const message = { from: '0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8', to: '0xDA9dfA130Df4dE4673b89022EE50ff26f6EA73Cf', contents: 'Hello' };
const typedData = { types, domain,
primaryType: 'Mail', message };
const signature = await web3.eth.signTypedData(address, typedData);
|
最後會得到沒有切開的簽章,可以參考之前的文章使用 Web3.js 簽章與檢驗 來處理。
web3.currentProvider.request
這是通用的呼叫 RPC 函式,在比較舊的 Web3.js 版本,沒有上面的 signTypedData 可以使用,可以直接呼叫 RPC method。
1
| web3.currentProvider.request(args: Web3APIPayload): Promise<string>
|
- args: RPC 內容,參考下面範例。
- 回傳值: 為 Promise 物件,內容為 RPC 執行結果,這裡的結果為簽章。
上面的範例前面的部分都一樣,只要把最後一行的
1
| const signature = await web3.eth.signTypedData(address, typedData);
|
改成
1 2 3 4 5 6 7 8
| const params = [address, typedData]; const signature = await web3.currentProvider.request( { method: 'eth_signTypedData_v4', params, from: address, } );
|
私鑰錢包簽署
如果要使用私鑰錢包簽署,可以使用 Metamask 提供的套件 @metamask/eth-sig-util。
1
| signTypedData({ data: TypedMessage, privateKey: Buffer, version: SignTypedDataVersion }): string
|
- data: 簽署內容,和上面的 typedData 一樣。
- privateKey: 私鑰,Buffer 型別。
- version: EIP-712 版本。
- 回傳值: 簽章。
安裝完後首先要引用套件
1
| import { signTypedData, SignTypedDataVersion } from '@metamask/eth-sig-util';
|
接著一樣只需要把最後簽署的那行改掉
1 2 3 4 5 6
| const privatekey = '0x...'; const signature = signTypedData({ privateKey: Buffer.from(privatekey, 'hex'), data: typedData as any, version: SignTypedDataVersion.V4 });
|
其他範例
之前的結構陣列範例為例
1 2 3 4 5 6 7 8 9 10
| struct Mail { Person from; Person[] to; string contents; }
struct Person { address wallet; string name; }
|
types 和資料內容修改如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const types = { EIP712Domain: [ ],
Mail: [ { name: 'from', type: 'Person' }, { name: 'to', type: 'Person[]' }, { name: 'contents', type: 'string' } ], Person: [ { name: 'wallet', type: 'address' }, { name: 'name', type: 'string' } ] };
const message = { from: { wallet: '0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8', name: 'Binance 7' }, to: [ { wallet: '0xDA9dfA130Df4dE4673b89022EE50ff26f6EA73Cf', name: 'Kraken 13' } ], contents: 'Hello' };
|
檢驗簽章
如果想在後端程式檢驗簽章,作為登入之類的用途,同樣使用 @metamask/eth-sig-util 套件:
1
| recoverTypedSignature({ data: TypedMessage, signature: string, version: SignTypedDataVersion }): string
|
- data: 簽署內容。
- signature: 未切開的簽章。
- version: EIP-712 版本。
- 回傳值: 簽署人地址。
範例如下
1 2 3 4 5 6 7 8 9 10
| import { recoverTypedSignature, SignTypedDataVersion } from '@metamask/eth-sig-util';
const signature = '0x78c03866a5efa253157e62596194631b4c9757d38b3556994d6b879d79d713e666f9c0a3e21e48fd061c5cf5217fd7e3c3ced521aa39650fda179ce61e6763b11c';
const signer = recoverTypedSignature({ data: typedData, signature, version: SignTypedDataVersion.V4 });
|
總結
- 瀏覽器錢包使用 Web3.js v4.1.0 以上用
web3.eth.signTypedData
。 - 瀏覽器錢包使用 Web3.js 舊版本用
web3.currentProvider.request
。 - 私鑰簽署使用
@metamask/eth-sig-util
的 signTypedData
。 - 檢驗簽章使用
@metamask/eth-sig-util
的 recoverTypedSignature
。
延伸閱讀
Solidity 智能合約 - EIP-712 類型結構化資料雜湊和簽名
使用 Ethers.js 進行 EIP-712 類型結構化資料簽名
Solidity 智能合約 - ecrecover 簽章檢驗
使用 Web3.js 簽章與檢驗