EVM-compatible chains obey the same rules for cryptographic computations, such as hash functions and digital signatures. The EIP-712 message is a standard for signing typed data, which is widely used in the Ethereum ecosystem. This article will introduce the usage of EIP-712 message for Orderly Network.
EIP-712 Types Definition
Unlike the transaction signatures, which are verified onchain to validate the transaction, the EIP-712 typed data is used to sign offchain messages. The typed data is a JSON object that describes the data structure to be signed. The typed formatted message is signed by the user’s private key, and the signature is then verified either by the contract or the offchain component to authenticate the signer’s identity.
{
"EIP712Domain": [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
],
"Registration": [
{name: "brokerId", type: "string"},
{name: "chainId", type: "uint256"},
{name: "timestamp", type: "uint64"},
{name: "registrationNonce", type: "uint256"}
],
}
Shown above is an example of the EIP-712 types definition. The EIP712Domain
is a common structure that is shared by all EIP-712 typed data structure.
name
is the name of the domain, such asOrderly Network
.version
is the version of the typed data structure, such as1.0
.chainId
is the chain id of linked network, such as1
for Ethereum mainnet.verifyingContract
is the address of the contract that will verify the signature, if the signature is verified offchain, the default value is0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC
.
The Registration
is a specific message type that is used to register an account under a broker on the Orderly Network.
brokerId
is used to identify the broker.chainId
is the chain id of the linked network.timestamp
is used to detect an expired registration message by the verifying service.registrationNonce
is used to prevent DDoS attack, working like an access token.
EIP-712 Message Structure
Before signing the message, the message should be formatted according to the defined EIP-712 type message. A complete EIP-712 typed message is a JSON object that contains the following fields:
const eip712Message = {
"domain": {
"name": "Orderly Network",
"version": "1.0",
"chainId": 4460, // the chainId of the connected network by Metamask
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" // to indicate the signanature is verified offchain
},
"message":{
"brokerId": "woofi_pro",
"chainId": 4460,
"timestamp": 1685973017064,
"registrationNonce": "983740230482834645859323"
}
"primaryType": "Registration",
"types": {
"EIP712Domain": [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
],
"Registration": [
{name: "brokerId", type: "string"},
{name: "chainId", type: "uint256"},
{name: "timestamp", type: "uint64"},
{name: "registrationNonce", type: "uint256"}
]
}
}
- The field
types
is the core of the EIP-712 message, which defines the types of the message. - The
primaryType
is the name of the message type, which is used to identify the message type in the signature. domain
andmessage
are the fields to contain the data forEIP712Domain
type and definedprimaryType
type, here is theRegistration
type.
EIP-712 Message Signing
The message is signed by the user’s private key, and the signature is then verified by the contract or the offchain component to authenticate the signer’s identity. There are several tools to provide the signing function for EIP-712 message, such as ethers.js
and web3.js
. Here is an example of using ethers.js
to sign the message:
const wallet = new ethers.Wallet(privateKey);
const domain = eip712Message['domain'];
const message = eip712Message['message'];
const types = {
[eip712Message["primaryType"]]: eip712Message['types'][eip712Message["primaryType"]]
};
// the above types is equivalent to the following content
// const types = {
// Registration: [
// { name: 'brokerId', type: 'string' },
// { name: 'chainId', type: 'uint256' },
// { name: 'timestamp', type: 'uint64' },
// { name: 'registrationNonce', type: 'uint256' }
// ]
// }
const signature = await wallet.signTypedData(domain, types, message);
Of course, injected wallets like Metamask also provide the signing function for EIP-712 message. The user can sign the message with the private key of the connected account.
To test more signing functionalities, you can try this demo page provided by Metamask.
const method = 'eth_signTypedData_v4';
const params = [window.userWalletAddress, JSON.stringify(eip712Message)];
const signature = await window.ethereum.request({ method, params });
EIP-712 Message Verification
The signature of the EIP-712 message can be verified by the contract or the offchain component. If the signature is verified offchain, the verifyingContract
field in the domain
object should be set to 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC
. The offchain component can use the ethers.js
library to verify the signature.
const domain = eip712Message['domain'];
const message = eip712Message['message'];
const types = {
[eip712Message["primaryType"]]: eip712Message['types'][eip712Message["primaryType"]]
};
const recoveredAddress = ethers.verifyTypedData(domain, types, message, signature);
If the signature is supposed to be verified onchain, the verifyingContract
field in the domain
object should be set to the address of the contract that will execute the verification. The contract can use the ecrecover
function to verify the signature.
struct RegistrationData {
string brokerId;
uint256 chainId;
uint64 timestamp;
uint256 registrationNonce;
uint8 v;
bytes32 r;
bytes32 s;
address signer;
}
function verifyRegitraion(RegistrationData memory data) public view return (bool) {
bytes32 typeDomainHash = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
bytes32 eip712DomainHash = keccak256(abi.encode(typeHash, keccak256(bytes("Orderly Network")), keccak256(bytes("1.0")), block.chainId, address(this)));
bytes32 typeStructHash = keccak256(
abi.encode(
keccak256("Registration(string brokerId,uint256 chainId,uint64 timestamp,uint256 registrationNonce)"),
keccak256(abi.encodePacked(data.brokerId)),
data.chainId,
data.timestamp,
data.registrationNonce
)
);
bytes32 hash = keccak256(abi.encodePacked("\x19\x01", eip712DomainHash, typeStructHash));
return ECDSA.recover(hash, data.v, data.r, data.s) == signer;
}
Conclusion
In this article, the usage of EIP-712 message is explained by giveing an example of registering an account on Orderly Network. The EIP-712 message is a standard for signing typed data, which is widely used in the Ethereum ecosystem. The EIP-712 message is used to sign offchain messages, and the signature is then verified either by the contract or the offchain component to authenticate the signer’s identity. The EIP-712 message is a powerful tool for the offchain component to verify the user’s identity and the integrity of the message.