Skip to content

Instantly share code, notes, and snippets.

@FoobarProtocol
Last active May 6, 2025 13:02
Show Gist options
  • Save FoobarProtocol/7802b5f69fd93f97d3f0eb2aec0c4ca1 to your computer and use it in GitHub Desktop.
Save FoobarProtocol/7802b5f69fd93f97d3f0eb2aec0c4ca1 to your computer and use it in GitHub Desktop.
Two EIP-712 Compliant JSON Files Evoke Same Signature

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.

Steps to Reproduce

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:

image

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).

image

Doing so, should produce a result that’s easily readable in the console of the application.

image

As shown above, the signature produced for both files is the exact same (if we’re using the EIP-712 encoding method).

Explanation on Why

Here's why we got the same signature for both JSON files:

  1. 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 in types.EIP712Domain.
      • The message data (for AuthRequest) is encoded according to the field order specified in types.AuthRequest.
    • Hashing: These encoded parts (domain separator and struct hash) are then combined and hashed.
  2. Content Equivalence:

    • types Object: The definitions for EIP712Domain and AuthRequest (including the names, types, and order of their members) are identical in both json excerpts.
      • EIP712Domain: [ { "name": "name", "type": "string" }, { "name": "version", "type": "string" }, ... ]
      • AuthRequest: [ { "name": "prompt", "type": "string" }, { "name": "createdAt", "type": "uint256" } ]
    • primaryType: Is "AuthRequest" in both files
    • domain Object Values: While the order of keys (chainId, name, etc.) within the domain object itself differs between your two JSON files, the values associated with these keys are identical:
      • name: "Example App"
      • version: "1"
      • chainId: 1
      • verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" When the EIP-712 hashing algorithm constructs the domainSeparator, it will fetch these values based on the field names defined in types.EIP712Domain and encode them in the order specified there (name, then version, then chainId, then verifyingContract). Since the values are the same, the resulting encoded domain data is the same.
    • message Object Values: The prompt and createdAt values are identical in both files. The hashing will follow the order defined in types.AuthRequest.
  3. 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 the types.EIP712Domain definition. It sees it needs name first, so it looks up domain.name from the JSON. Then it needs version, so it looks up domain.version, and so on. The order of keys in the domain JSON object itself doesn't change which value is fetched for name, 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 and message 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment