This specification describes an Ethereum EIP712 Signature Suite created in 2021 for the Linked Data Proof specification. The Signature Suite utilizes EIP712 signatures.

This is an experimental specification and is undergoing regular revisions. It is not fit for production deployment.

Introduction

This specification defines a cryptographic suite for the purpose of creating, verifying proofs for EIP712 signatures in conformance with the Linked Data Proofs [[LD-PROOFS]] specification.

In general the suites uses the JCS Algorithm [[JCS]] to transform an input document into its canonical form. The cannonical representation is then provided to the EIP712 signature function.

A signature scheme consists of a hashing algorithm and a signing algorithm. The signing algorithm of choice in Ethereum is secp256k1. The hashing algorithm of choice is keccak256.

EIP712 defines a standard API for Web3 Provider (e.g., wallets) to generate signatures over human-readable data where the signature can be verified either by a Smart Contract on the Ethereum Blockchain, or completely offchain.

The rational is to use existing Web3 Providers and their secure key management system to produce signatures that are compliant with the JSON-LD and more specifically, the Linked Data Signatures (LDS) data model.

Since the EIP712 signature function relies on JSON schemas, implementers need to ensure that the linked data document matches the EIP712 JSON schema that will be provided to the EIP712 signature function.

Terminology

The following terms are used to describe concepts involved in the generation and verification of the Linked Data Proof signature suite.

signature suite
A specified set of cryptographic primitives typically consisting of a canonicalization algorithm, a message digest algorithm, and a signature algorithm that are bundled together by cryptographers for developers for the purposes of safety and convenience.
canonicalization algorithm
An algorithm that takes an input document that has more than one possible representation and always transforms it into a canonical form. This process is sometimes also called normalization.
message digest algorithm
An algorithm that takes a message, prefferably in some canonical form and produces a cryptographic output called a digest that is often many orders of magnitude smaller than the input message. These algorithms are often 1) very fast, 2) non-reversible, 3) cause the output to change significantly when even one bit of the input message changes, and 4) make it infeasible to find two different inputs for the same output.
types generation algorithm
An algorithm that takes an input document and produces an EIP712 compatible types JSON object containing the types for the input document being signed.
canonical form
The output of applying a canonicalization algorithm to an input document.
signature algorithm
An algorithm that takes an input message and produces an output value where the receiver of the message can mathematically verify that the message has not been modified in transit and came from someone possessing a particular secret.
linked data document
A document comprised of linked data.
linked data proof
An object or mechanism for proving integrity of linked data documents, in the form specified by [[LD-PROOFS]].
EcdsaSecp256k1VerificationKey2019
A type of the verification method for the signature suite EcdsaSecp256k1Signature2019. See also EcdsaSecp256k1VerificationKey2019 in W3C CCG Security Vocabularity.
EcdsaSecp256k1RecoveryMethod2020
A type of the verification method for the signature suite EcdsaSecp256k1RecoverySignature2020. See also EcdsaSecp256k1RecoveryMethod2020 in W3C CCG Security Vocabularity.
EthereumEip712Signature2021
The type of the linked data proof for the signature suite EthereumEip712Signature2021.

Suite Definition

The Ethereum EIP 712 signature suite 2021 MUST be used in conjunction with the signing and verification algorithms in the Linked Data Proofs [[LD-PROOFS]] specification. The suite consists of the following algorithms:

Parameter Value Specification
canonicalization algorithm JCS [[JCS]]
message digest algorithm EIP712 uses Keccak-256 [[EIP712]]
signature algorithm EIP712 uses ECDSA K-256 [[EIP712]]

To generate the EIP712 signature, EIP712 requires TypedData which is a JSON object containing type information, domain separator parameters and the message object.

TypedData MUST be a JSON object according to the EIP712 specification and contains properties types, domain, primaryType and message. The types property of TypedData can be generated by the types generation algorithm if not provided as input.

Types Generation

If TypedData's types object is not provided to the signature suite, the suite MUST generate the JSON object by inferring types from the input document, and optionally a provided `primaryType`.

In case of ambiguous types, the algorithm SHOULD defer to using the most liberal option. For example, a number should be inferred as the uint256 type even if that specific number can fit in a uint8 type.

The types generation algorithm is defined as follows:

  1. Creates a mapping output from string to TypedDataField[] types, where TypedDataField is an object consisting of two string properties - name and type
  2. Creates an empty array types of TypedDataField to collect all the fields
  3. Canonicalizes the input document using the canonicalization algorithm
  4. If `primaryType` is not provided, set `primaryType = "Document"` else use the provided value.
  5. For each property in the canonicalized input document, iterated in lexicographic order of property name according to RFC 8785 Section 3.2.3, the algorithm checks the type of the value specific to the implementation language.
  6. If the type of the value is a primitive boolean, number or string, push an object to types with the name set to the property name of the input document, and type set to the corresponding EIP712 primitive type
    1. boolean - maps to bool
    2. number - maps to uint256
    3. string - maps to string
  7. If the type of the value is an array, ensure each element of the array has the same primitive type. Push an object to types with the name set to the property name of the input document and type set to the corresponding EIP712 array type
    1. boolean[] - maps to bool[]
    2. number[] - maps to uint256[]
    3. string[] - maps to string[]

    WARNING: The current algorithm definition does not support auto generating types for arrays of structs. We need to work on that.

  8. If the type of the value is an object, call the function recursively on the inner object, and set the return value equal to _recursiveOutput.
    1. Set _recursiveTypes = _recursiveOutput[primaryType]
    2. Push an object to types with the name set to the property name of the input document and type set to the CapitalCased property name - propertyType
    3. Set output[propertyType] = _recursiveTypes
    4. Loop over _recursiveOutput, and if any keys other than primaryType are present, add them directly to output. If any such key already has an entry in output, raise an error.
  9. Finally, set output[primaryType] to the types array that was generated. Return output
The following is an example of the autogenerated schema. Given the following input document and no `primaryType`:
          {
            "@context": ["https://schema.org", "https://w3id.org/security/v2"],
            "@type": "Person",
            "name": {
              "first": "Jane",
              "last": "Doe"
            },
            "otherData": {
              "jobTitle": "Professor",
              "school": "University of ExampleLand"
            },
            "telephone": "(425) 123-4567",
            "email": "jane.doe@example.com"
          }
         
It will generate the following schema:
        {
          "Document": [
            { "name": "@context", type: "string[]" },
            { "name": "@type", type: "string" },
            { "name": "email", type: "string" },
            { "name": "name", type: "Name" },
            { "name": "otherData", type: "OtherData" },
            { "name": "telephone", type: "string" }
          ],
          "Name": [
            { "name": "first", type: "string" },
            { "name": "last", type: "string" }
          ],
          "OtherData": [
            { "name": "jobTitle", type: "string" },
            { "name": "school", type: "string" }
          ]
        }
        

Verification Method

The cryptographic material used to verify a linked data proof is called the verification method.

This signature suite does not define a new verfication method. EcdsaSecp256k1VerificationKey2019 and EcdsaSecp256k1RecoveryMethod2020 can be used with Ethereum EIP712 Signature 2021.

Proof Representation

The cryptographic material used to represent a linked data proof is called the proof type.

This specification relies on the output of the EIP712 signature function.

Ethereum EIP712 Signature 2021

The verificationMethod property of the proof SHOULD be a URI. Dereferencing the verificationMethod SHOULD result in an object of type EcdsaSecp256k1VerificationKey2019 or EcdsaSecp256k1RecoveryMethod2020.

The type property of the proof MUST be EthereumEip712Signature2021.

The created property of the proof MUST be an [ISO_8601] formated date string.

The proofPurpose property of the proof MUST be a string, and SHOULD match the verification relationship expressed by the verification method controller.

The proofValue property of the proof MUST be the hex encoded output of the EIP712 signature function according [EIP712].

The eip712Domain property MUST contain meta-information about the signature generation process that can be used when the signature is verified. It MUST contain the following properties:

  • messageSchema MUST be a URI that results in an object that contains the JSON schema that describes the message to be signed according to EIP712, or an object that contains the JSON schema itself.
  • domain MUST be the domain property of the EIP712 TypedData object.
  • primaryType MUST be the primaryType property of the EIP712 TypedData object.

The following is a non-normative example of an EthereumEip712Signature2021 proof:

            {
              "proof": {
                "type": "EthereumEip712Signature2021",
                "created": "2019-12-11T03:50:55Z",
                "proofPurpose": "assertionMethod",
                "proofValue": "0xc565d38982e1a5004efb5ee390fba0a08bb5e72b3f3e91094c66bc395c324f785425d58d5c1a601372d9c16164e380c63e89f1e0ea95fdefdf7b2854c4f938e81b",
                "verificationMethod": "did:example:aaaabbbb#issuerKey-1",
                "eip712Domain": {
                   "messageSchema": "https://example.com/schemas/v1",
                   "primaryType": "VerifiableCredential"
                }
              }
           }
           

Test Vectors

The following test vectors are provided to assist with implementers.

The following is an example ECDSA K-256 keypair that can be used to generate EIP712 signatures:

        {
          "keypair_0": {
            "id": "did:example:aaaabbbb#issuerKey-1",
            "type": "EcdsaSecp256k1VerificationKey2019",
            "controller": "did:example:aaaabbbb",
            "publicKeyJwk": {
              "kty": "EC",
              "crv": "secp256k1",
              "x": "cmbYyDC6cbm807_OmFNYP4CLEL0aB2F1UG683SxFkXM",
              "y": "zBw5HAh0cJM4YimSQvtYM1HFhzUXVUgrDhxJ70aajt0",
            },
            "privateKeyJwk": {
              "kty": "EC",
              "crv": "secp256k1",
              "x": "cmbYyDC6cbm807_OmFNYP4CLEL0aB2F1UG683SxFkXM",
              "y": "zBw5HAh0cJM4YimSQvtYM1HFhzUXVUgrDhxJ70aajt0",
              "d": "u7QuEl6W0XNppEY0iMVjATT99tC9acwV3Z2keEqvKGo"
            }
          }
        }              
      

The following is an example TypedData object before JCS normalization that will be provided to the EIP712 signature function:

        {
          "types":{
             "EIP712Domain":[
                {
                   "name":"name",
                   "type":"string"
                },
                {
                   "name":"version",
                   "type":"string"
                },
                {
                   "name":"chainId",
                   "type":"uint256"
                },
                {
                   "name":"salt",
                   "type":"bytes32"
                }
             ],
             "VerifiableCredential":[
                {
                   "name":"@context",
                   "type":"string[]"
                },
                {
                   "name":"type",
                   "type":"string[]"
                },
                {
                   "name":"id",
                   "type":"string"
                },
                {
                   "name":"issuer",
                   "type":"string"
                },
                {
                   "name":"issuanceDate",
                   "type":"string"
                },
                {
                   "name":"credentialSubject",
                   "type":"CredentialSubject"
                },
                {
                   "name":"credentialSchema",
                   "type":"CredentialSchema"
                },
                {
                  "proof":"proof",
                  "type":"Proof"
                }
             ],
             "CredentialSchema":[
                {
                   "name":"id",
                   "type":"string"
                },
                {
                   "name":"type",
                   "type":"string"
                }
             ],
             "CredentialSubject":[
                {
                   "name":"type",
                   "type":"string"
                },
                {
                   "name":"id",
                   "type":"string"
                },
                {
                   "name":"name",
                   "type":"string"
                },
                {
                   "name":"child",
                   "type":"Person"
                }
             ],
             "Person":[
                {
                   "name":"type",
                   "type":"string"
                }
                {
                   "name":"name",
                   "type":"string"
                }
             ],
             "Proof":[
                {
                  "name":"verificationMethod",
                  "type":"string"
                },
                {
                  "name":"created",
                  "type":"string"
                },
                {
                  "name":"proofPurpose",
                  "type":"string"
                },
                {
                  "name":"type",
                  "type":"string"
                }
             ]
          },
          "domain":{
             "name":"https://example.com",
             "version":"2",
             "chainId":4,
             "salt":"0x000000000000000000000000000000000000000000000000aaaabbbbccccdddd"
          },
          "primaryType":"VerifiableCredential",
          "message":{
             "@context":[
                "https://www.w3.org/2018/credentials/v1",
                "https://schema.org"
             ],
             "type":[
                "VerifiableCredential"
             ],
             "id":"https://example.org/person/1234",
             "issuer":"did:example:aaaabbbb",
             "issuanceDate":"2010-01-01T19:23:24Z",
             "credentialSubject":{
                "type":"Person",
                "id":"did:example:bbbbaaaa",
                "name":"Vitalik",
                "child":{
                   "type":"Person",
                   "name":"Ethereum"
                }
             },
             "credentialSchema":{
                "id":"https://example.com/schemas/v1",
                "type":"Eip712SchemaValidator2021"
             },
             "proof":{
                "verificationMethod":"did:example:aaaabbbb#issuerKey-1",
                "created":"2021-07-09T19:47:41Z",
                "proofPurpose":"assertionMethod",
                "type":"EthereumEip712Signature2021"
             }
          }
       }
      

The following is the JSON document after JCS normalization (added linespace after 90 characters):

        {"domain":{"chainId":4,"name":"https://example.com","salt":"0x0000000000000000000000000000
        00000000000000000000aaaabbbbccccdddd","version":"2"},"message":{"@context":["https://www.w
        3.org/2018/credentials/v1","https://schema.org"],"credentialSchema":{"id":"https://example
        .com/schemas/v1","type":"Eip712SchemaValidator2021"},"credentialSubject":{"child":{"name":
        "Ethereum","type":"Person"},"id":"did:example:bbbbaaaa","name":"Vitalik","type":"Person"},
        "id":"https://example.org/person/1234","issuanceDate":"2010-01-01T19:23:24Z","issuer":"did
        :example:aaaabbbb","proof":{"created":"2021-07-09T19:47:41Z","proofPurpose":"assertionMeth
        od","type":"EthereumEip712Signature2021","verificationMethod":"did:example:aaaabbbb#issuer
        Key-1"},"type":["VerifiableCredential"]},"primaryType":"VerifiableCredential","types":{"Cr
        edentialSchema":[{"name":"id","type":"string"},{"name":"type","type":"string"}],"Credentia
        lSubject":[{"name":"type","type":"string"},{"name":"id","type":"string"},{"name":"name","t
        ype":"string"},{"name":"child","type":"Person"}],"EIP712Domain":[{"name":"name","type":"st
        ring"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"sal
        t","type":"bytes32"}],"Person":[{"name":"type","type":"string"},{"name":"name","type":"str
        ing"}],"Proof":[{"name":"verificationMethod","type":"string"},{"name":"created","type":"st
        ring"},{"name":"proofPurpose","type":"string"},{"name":"type","type":"string"}],"Verifiabl
        eCredential":[{"name":"@context","type":"string[]"},{"name":"type","type":"string[]"},{"na
        me":"id","type":"string"},{"name":"issuer","type":"string"},{"name":"issuanceDate","type":
        "string"},{"name":"credentialSubject","type":"CredentialSubject"},{"name":"credentialSchem
        a","type":"CredentialSchema"},{"name":"proof","type":"Proof"}]}}
      

The following is the resulting proof object:

        {
          "type": "EthereumEip712Signature2021",
          "created": "2021-07-09T19:47:41Z",
          "proofPurpose": "assertionMethod",
          "proofValue": "0x5fb8f18f21f54c2df8a2720d0afcee7dbbb18e4b7a22ce6e8183633d63b076d329122584db769cd78b6cd5a7094ede5ceaa43317907539187f1f0d8875f99e051b",
          "verificationMethod": "did:example:aaaabbbb#issuerKey-1",
          "eip712Domain": {
            "domain": {
              "chainId": 4,
              "name": "https://example.com",
              "salt": "0x000000000000000000000000000000000000000000000000aaaabbbbccccdddd",
              "version": "2"
            },
            "messageSchema": {
              "CredentialSchema": [
                {
                  "name": "id",
                  "type": "string"
                },
                {
                  "name": "type",
                  "type": "string"
                }
              ],
              "CredentialSubject": [
                {
                  "name": "type",
                  "type": "string"
                },
                {
                  "name": "id",
                  "type": "string"
                },
                {
                  "name": "name",
                  "type": "string"
                },
                {
                  "name": "child",
                  "type": "Person"
                }
              ],
              "EIP712Domain": [
                {
                  "name": "name",
                  "type": "string"
                },
                {
                  "name": "version",
                  "type": "string"
                },
                {
                  "name": "chainId",
                  "type": "uint256"
                },
                {
                  "name": "salt",
                  "type": "bytes32"
                }
              ],
              "Person": [
                {
                  "name": "type",
                  "type": "string"
                },
                {
                  "name": "name",
                  "type": "string"
                }
              ],
              "Proof": [
                {
                  "name": "verificationMethod",
                  "type": "string"
                },
                {
                  "name": "created",
                  "type": "string"
                },
                {
                  "name": "proofPurpose",
                  "type": "string"
                },
                {
                  "name": "type",
                  "type": "string"
                }
              ],
              "VerifiableCredential": [
                {
                  "name": "@context",
                  "type": "string[]"
                },
                {
                  "name": "type",
                  "type": "string[]"
                },
                {
                  "name": "id",
                  "type": "string"
                },
                {
                  "name": "issuer",
                  "type": "string"
                },
                {
                  "name": "issuanceDate",
                  "type": "string"
                },
                {
                  "name": "credentialSubject",
                  "type": "CredentialSubject"
                },
                {
                  "name": "credentialSchema",
                  "type": "CredentialSchema"
                },
                {
                  "name": "proof",
                  "type": "Proof"
                }
              ]
            },
            "primaryType": "VerifiableCredential"
          }
        }
      

A conforming document is any concrete expression of the data model that complies with the normative statements in this specification. Specifically, all relevant normative statements in Sections and of this document MUST be enforced.

A conforming processor is any algorithm realized as software and/or hardware that generates or consumes a conforming document. Conforming processors MUST produce errors when non-conforming documents are consumed.

This document also contains examples that contain JSON and JSON-LD content. Some of these examples contain characters that are invalid JSON, such as inline comments (//) and the use of ellipsis (...) to denote information that adds little value to the example. Implementers are cautioned to remove this content if they desire to use the information as valid JSON or JSON-LD.

Security Considerations

The following section describes security considerations that developers implementing this specification should be aware of in order to create secure software.

This specification relies on JCS, please review [[JCS]].

This specification relies on EIP712, please review [[EIP712]].

TODO: We need to add a complete list of security considerations, e.g., what happens if EIP712 JSON schema does not match the message to be signed.