之前在的文章 Solidity 智能合約 - EIP-712 類型結構化資料雜湊和簽名,說明了智能合約如何檢驗 EIP-712 的簽章。這篇文章將說明如何使用 Ethers.js 來進行 EIP-712 簽章。本篇文章使用 Ethers.js 6.7.1 版本。

簽章

signer.signTypedData

Ethers.js 使用下面函式進行簽署,參考之前的文章使用 Ethers.js 簽章與檢驗取得 Signer。

1
signer.signTypedData(domain, types, value): Promise<string>
  • domain: 合約中 eip712Domain 的資料。
  • types: 類別定義。
  • value: 訊息內容,直接參考下面範例說明。
  • 回傳值: 為 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;
}

// 0x536e54c54e6699204b424f41f6dea846ee38ac369afec3e7c141d2c92c65e67f
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);
}
}

對應的 Ethers.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
// 填入實際的 chainId,例如主網 1
const chainId = 1;

// 實際部署上鏈的合約地址
const verifyingContract = '0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8';

// 另外填入合約上填寫的 name 和 version
const domain = {
chainId,
verifyingContract,
name: 'Example',
version: '1'
};

const types = {
// 以下是自定義的結構型別
Mail: [
{ name: 'from', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'contents', type: 'string' }
]
};

// 實際的資料內容
const value = {
from: '0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8',
to: '0xDA9dfA130Df4dE4673b89022EE50ff26f6EA73Cf',
contents: 'Hello'
};

// 0x78c03866a5efa253157e62596194631b4c9757d38b3556994d6b879d79d713e666f9c0a3e21e48fd061c5cf5217fd7e3c3ced521aa39650fda179ce61e6763b11c
const signature = await signer.signTypedData(domain, types, value);

最後會得到沒有切開的簽章,可以參考之前的文章使用 Ethers.js 簽章與檢驗 來處理。

其他範例

之前的結構陣列範例為例

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
const types = {
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person[]' },
{ name: 'contents', type: 'string' }
],
Person: [
{ name: 'wallet', type: 'address' },
{ name: 'name', type: 'string' }
]
};

const value = {
from: {
wallet: '0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8',
name: 'Binance 7'
},
to: [
{
wallet: '0xDA9dfA130Df4dE4673b89022EE50ff26f6EA73Cf',
name: 'Kraken 13'
}
],
contents: 'Hello'
};

檢驗簽章

如果想在後端程式檢驗簽章,作為登入之類的用途,可以使用下面函式:

1
verifyTypedData(domain, types, value, signature: SignatureLike): string
  • domain: 合約中 eip712Domain 的資料。
  • types: 類別定義。
  • value: 訊息內容,直接參考下面範例說明。
  • signature: 簽章,可以是為切開的字串,或者是包含 vrs 的物件。
  • 回傳值: 簽署人地址。

範例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
import { verifyTypedData } from "ethers";

const signature = '0x78c03866a5efa253157e62596194631b4c9757d38b3556994d6b879d79d713e666f9c0a3e21e48fd061c5cf5217fd7e3c3ced521aa39650fda179ce61e6763b11c';

// or vrs
// const signature = {
// r: '0x78c03866a5efa253157e62596194631b4c9757d38b3556994d6b879d79d713e6',
// s: '0x66f9c0a3e21e48fd061c5cf5217fd7e3c3ced521aa39650fda179ce61e6763b1',
// v: 28
// };

// 0xDD980c315dFA75682F04381E98ea38BD2A151540
const address = verifyTypedData(domain, types, value, signature);

延伸閱讀

Solidity 智能合約 - EIP-712 類型結構化資料雜湊和簽名
使用 Web3.js 進行 EIP-712 類型結構化資料簽名與檢驗
Solidity 智能合約 - ecrecover 簽章檢驗
使用 Ethers.js 簽章與檢驗