Getting straight to the point here, check out json file number one (formatted like a legitimate EIP-712 hash):
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"AuthRequest": [
{
"name": "prompt",
"type": "string"
},
{
"name": "createdAt",
"type": "uint256"
}
]
},
"primaryType": "AuthRequest",
"domain": {
"name": "Example App",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"prompt": "Welcome! In order to authenticate to this website, sign this request and your public address will be sent to the server in a verifiable way.",
"createdAt": 1718570375196
}
}
Here is the second JSON address (different from the first):
{
"domain": {
"chainId": 1,
"name": "Example App",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
"version": "1"
},
"message": {
"prompt": "Welcome! In order to authenticate to this website, sign this request and your public address will be sent to the server in a verifiable way.",
"createdAt": 1718570375196
},
"primaryType": "AuthRequest",
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"AuthRequest": [
{
"name": "prompt",
"type": "string"
},
{
"name": "createdAt",
"type": "uint256"
}
]
}
}
If we create two files containing both of these json
excerpts in each one (repsectively), then go to sign said files (using the EIP-712
signature standard), we'll get the same sigrnature and that signature will validate against both sets of EIP-712
data.
Once you arrive at Remix IDE’s site, make sure you head to the deploy and run transactions
tab and set your environment to Remix VM(Cancun)
as shown below:
Once we’ve done that, we’re going to go to the files
panel (top left) and then create two files called eip712-compliant-version1.json
and eip712-compliant-version2.json
, respectively. Once that’s complete, all we have to do is right click on each file and then select the sign Typed Data
option (for EIP-712
compatible transaction data).
Doing so, should produce a result that’s easily readable in the console
of the application.
As shown above, the signature produced for both files is the exact same (if we’re using the EIP-712
encoding method).
Here's why we got the same signature for both JSON files:
-
EIP-712 Standardization: The EIP-712 standard specifies a precise way to encode and hash structured data. This process involves:
- Defining Types: The
types
object (e.g.,EIP712Domain
,AuthRequest
) defines the name and type of each field, and importantly, the order of these fields within their respective type definitions. - Encoding Data: When the data is encoded for hashing:
- The
domain
data is encoded according to the field order specified intypes.EIP712Domain
. - The
message
data (forAuthRequest
) is encoded according to the field order specified intypes.AuthRequest
.
- The
- Hashing: These encoded parts (domain separator and struct hash) are then combined and hashed.
- Defining Types: The
-
Content Equivalence:
types
Object: The definitions forEIP712Domain
andAuthRequest
(including the names, types, and order of their members) are identical in bothjson
excerpts.EIP712Domain: [ { "name": "name", "type": "string" }, { "name": "version", "type": "string" }, ... ]
AuthRequest: [ { "name": "prompt", "type": "string" }, { "name": "createdAt", "type": "uint256" } ]
primaryType
: Is"AuthRequest"
in both filesdomain
Object Values: While the order of keys (chainId
,name
, etc.) within thedomain
object itself differs between your two JSON files, the values associated with these keys are identical:name
: "Example App"version
: "1"chainId
: 1verifyingContract
: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" When the EIP-712 hashing algorithm constructs thedomainSeparator
, it will fetch these values based on the field names defined intypes.EIP712Domain
and encode them in the order specified there (name, then version, then chainId, then verifyingContract). Since the values are the same, the resulting encodeddomain
data is the same.
message
Object Values: Theprompt
andcreatedAt
values are identical in both files. The hashing will follow the order defined intypes.AuthRequest
.
-
Hashing Process is Order-Independent of JSON Key Order (for objects): The EIP-712 signing logic (e.g., in Remix, ethers.js, MetaMask) parses the JSON. When it needs to encode the
domain
object, for example, it looks at thetypes.EIP712Domain
definition. It sees it needsname
first, so it looks updomain.name
from the JSON. Then it needsversion
, so it looks updomain.version
, and so on. The order of keys in thedomain
JSON object itself doesn't change which value is fetched forname
,version
, etc. The same applies to the top-level keys (domain
,message
,primaryType
,types
). The signing utility knows what each of these top-level keys means and processes them according to the EIP-712 specification, regardless of their order in the JSON file.
In summary:
- The type definitions in
types
are identical (including field order). - The
primaryType
is identical. - The actual data values within
domain
andmessage
are identical.
The EIP-712
encoding and hashing algorithm will produce the exact same byte string to be signed. When this identical byte string is signed with the same private key (corresponding with 0x5B38...
in this case), the resulting cryptographic signature will also be identical.