Suite Definition
The Ethereum EIP 712 signature suite 2021 MUST be used in conjunction with the signing and verification algorithms in the Data Integrity [[DATA-INTEGRITY]] specification. The suite consists of the following algorithms:
Parameter | Value | Specification |
---|---|---|
canonicalization algorithm (if automatically-generated `types` object is present) | 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. Note: in the case of this generation, elements will be normalized according to [JCS].
-
types
MUST be a JSON array with at least two entries. The first entry refers to theEIP712Domain
property that contains the JSON schema according to the EIP712 specification. Remaining entries MUST be the JSON schemas of the message to be signed in the EIP712 format.types
MUST contain a propertyproof
of typeProof
which contains the JSON schema for all properties of the final data integrity proofproof
property that need to be signed. -
message
MUST be the unsigned data document that contains the message to be signed. The message MUST contain aproof
property with values set to the values of the properties in the resulting data-cite="DATA-INTEGRITY#dfn-signed-data-document">signed data document'sproof
property that are expected to be signed. -
domain
MUST have the value as defined inEIP712
. Ifdomain
is not provided, a default object is applied with the only property beingname
with valueEthereumEip712Signature2021
-
primaryType
MUST have the value as defined inEIP712
.primaryType
represents the top-level type of the object in the EIP712message
but does not have to correspond to any of the types in themessage
.
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:
- Creates a mapping
output
fromstring
toTypedDataField[]
types, whereTypedDataField
is an object consisting of two string properties -name
andtype
- Creates an empty array
types
ofTypedDataField
to collect all the fields - Canonicalizes the input document using the canonicalization algorithm
- If `primaryType` is not provided, set `primaryType = "Document"` else use the provided value.
- 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.
- If the type of the value is a primitive
boolean
,number
orstring
, push an object totypes
with thename
set to the property name of the input document, andtype
set to the corresponding EIP712 primitive typeboolean
- maps tobool
number
- maps touint256
string
- maps tostring
- 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 thename
set to the property name of the input document andtype
set to the corresponding EIP712 array typeboolean[]
- maps tobool[]
number[]
- maps touint256[]
string[]
- maps tostring[]
WARNING: The current algorithm definition does not support auto generating types for arrays of structs. We need to work on that.
- 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
.- Set
_recursiveTypes = _recursiveOutput[primaryType]
- Push an object to
types
with thename
set to the property name of the input document andtype
set to the CapitalCased property name -propertyType
- Set
output[propertyType] = _recursiveTypes
- Loop over
_recursiveOutput
, and if any keys other thanprimaryType
are present, add them directly tooutput
. If any such key already has an entry inoutput
, raise an error.
- Set
- Finally, set
output[primaryType]
to thetypes
array that was generated. Returnoutput
{ "@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": "[email protected]" }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 data integrity proof is called the verification method.
This signature suite does not define a new verification method type. The following verification method types can be used with Ethereum EIP712 Signature 2021:
Proof Representation
The cryptographic material used to represent a data integrity 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
, EcdsaSecp256k1RecoveryMethod2020
, or JsonWebKey2020
. If the dereferenced verification method object is of type JsonWebKey2020
, it MUST contain a property publicKeyJwk
, containing a secp256k1 public key represented as a JSON Web Key (JWK) according to RFC 8812 Section 3.1.
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 eip712
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:
-
types
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 thedomain
property of the EIP712TypedData
object. -
primaryType
MUST be theprimaryType
property of the EIP712TypedData
object.
The canonicalizationHash
property of the proof, if present, MUST contain a value computed from the input document and proof as specified in Linked Data Canonicalization Hash.
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", "eip712": { "types": "https://example.com/schemas/v1", "primaryType": "VerifiableCredential" } } }
JSON-LD Context
The @context
property of a JSON-LD document using this signature suite SHOULD include the following URI string: https://w3id.org/security/suites/eip712sig-2021/v1
.
To ensure the integrity of a JSON-LD document's RDF data model, use the proof canonicalizationHash
property described in .
Linked Data Canonicalization Hash
- IRI
https://w3id.org/security/suites/eip712sig-2021#canonicalizationHash
- Status
- unstable
- Domain
- sec:Signature
- Range
- xsd:string
Linked data proof suites conventionally create a proof verification hash for signing and verifying the document and proof as JSON-LD/RDF, using JSON-LD context expansion, RDF Deserialization, and [[RDF-DATASET-CANONICALIZATION]]. This signature suite instead signs a structured TypedData
object derived from the JSON-LD document, without JSON-LD or RDF processing. For use cases where JSON-LD and RDF processing is needed, this document specifies an optional property of a EthereumEip712Signature2021 proof, canonicalizationHash
, for a verification hash computed with JSON-LD/RDF processing and URDNA2015. Signing over this property and verifying it during proof verification secures the RDF data model of the signed document. Construction of the canonicalizationHash
value is defined in .
The input document provided to MUST be the document as it would be returned after proof creation (with the eip712
proof property) except without the proofValue
proof property.
When preparing the TypedData
structure for signing or verification, the canonicalizationHash
value MUST be included in the message's proof object.
Note that although the proof object in the input document provided to includes the eip712
property, the proof object in the message in the TypedData
structure does not include the eip712
property. That is because the properties of the eip712
object are instead included in non-message parts of the TypedData
structure; but for the purpose of linked data integrity, it is desired to include the eip712
properties as they would be in the returned document. After signing, proof properties eip712
, proofValue
and canonicalizationHash
are inserted into the proof object before returning the document.
Linked Data Canonicalization Hash Algorithm
Given an input unsigned data document document which includes a proof
property with object value proof, the value for canonicalizationHash
is computed as follows:
- Convert document from JSON-LD to an RDF dataset, dataset, according to JSON-LD 1.1 Processing Algorithms and API § 8.1 Deserialize JSON-LD to RDF Algorithm.
- Canonicalize dataset according to [[RDF-DATASET-CANONICALIZATION]].
- Serialize dataset in N-Quads format as nquads.
- Sort the lines of nquads lexicographically (comparing each N-Quad line as a UTF-8 byte string, sorting in ascending order).
- Compute the SHA-256 digest of nquads (including the trailing newline) as digest (encoded as a lowercase hexadecimal value).
- Return digest.
Test Vectors
The following test vectors are provided to assist implementers. Some of the given test vectors specify inputOptions
which are options to be passed when creating a proof. These can include options specifying the domain
, types
, primaryType
, verificationMethod
, date
, embedAsURI
, and embed
.
The following is an example Ethereum-compatible hexadecimal private key, and corresponding did:pkh
verificationMethod
that can be used to assist with test vectors:
{ "privateKey": "0x149195a4059ac8cafe2d56fc612f613b6b18b9265a73143c9f6d7cfbbed76b7e", "verificationMethod": "did:pkh:eip155:1:0xAED7EA8035eEc47E657B34eF5D020c7005487443#blockchainAccountId" }
The following are some example input documents that will be provided to the Ethereum EIP712 Signature Suite, to generate various type of output proofs:
{ "testBasicDocument": { "@context": ["https://schema.org", "https://w3id.org/security/v2"], "@type": "Person", "firstName": "Jane", "lastName": "Does", "jobTitle": "Professor", "telephone": "(425) 123-4567", "email": "[email protected]" }, "testNestedDocument": { "@context": ["https://schema.org", "https://w3id.org/security/v2"], "@type": "Person", "data": { "name": { "firstName": "John", "lastName": "Doe" }, "job": { "jobTitle": "Professor", "employer": "University of Waterloo" } }, "telephone": "(425) 123-4567" } }
Basic Document - Types Generation - No Embedding
This test vector has not yet been verified by more than one independent implementation.
With the following inputOptions
provided to the signature suite along with the testBasicDocument
input document:
{ "date": "2021-08-30T13:28:02Z", "verificationMethod": "did:pkh:eip155:1:0xAED7EA8035eEc47E657B34eF5D020c7005487443#blockchainAccountId", "domain": { "name": "Test" } }
The following is the resulting proof
object:
{ "created": "2021-08-30T13:28:02Z", "proofPurpose": "assertionMethod", "proofValue": "0xbbdf2914c7572185bbc263e066dfb43f3136e4441fddb3fe3ea4541bbf7fd1f00d8e5af3ce4fbb1f2ebd5256f39b22cef7f285189df2976ea0c385c77f0a42791b", "type": "EthereumEip712Signature2021", "verificationMethod": "did:pkh:eip155:1:0xAED7EA8035eEc47E657B34eF5D020c7005487443#blockchainAccountId", }
Nested Document - TypedData Provided - Embedded EIP712 Properties
With the following inputOptions
provided to the signature suite along with the testNestedDocument
input document:
{ "verificationMethod": "did:pkh:eip155:1:0xAED7EA8035eEc47E657B34eF5D020c7005487443#blockchainAccountId", "types": { "Data": [ { "name": "job", "type": "Job" }, { "name": "name", "type": "Name" } ], "Document": [ { "name": "@context", "type": "string[]" }, { "name": "@type", "type": "string" }, { "name": "data", "type": "Data" }, { "name": "telephone", "type": "string" }, { "name": "proof", "type": "Proof" } ], "Job": [ { "name": "employer", "type": "string" }, { "name": "jobTitle", "type": "string" } ], "Proof": [ { "name": "created", "type": "string" }, { "name": "proofPurpose", "type": "string" }, { "name": "type", "type": "string" }, { "name": "verificationMethod", "type": "string" } ], "Name": [ { "name": "firstName", "type": "string" }, { "name": "lastName", "type": "string" } ] }, "domain": { "name": "Test" }, "date": "2021-08-30T13:28:02Z", "embed": true }
The following is the resulting proof
object:
{ "created": "2021-08-30T13:28:02Z", "eip712": { "domain": { "name": "Test", }, "primaryType": "Document", "types": { "Data": [ { "name": "job", "type": "Job", }, { "name": "name", "type": "Name", }, ], "Document": [ { "name": "@context", "type": "string[]", }, { "name": "@type", "type": "string", }, { "name": "data", "type": "Data", }, { "name": "telephone", "type": "string", }, { "name": "proof", "type": "Proof", }, ], "Job": [ { "name": "employer", "type": "string", }, { "name": "jobTitle", "type": "string", }, ], "Name": [ { "name": "firstName", "type": "string", }, { "name": "lastName", "type": "string", }, ], "Proof": [ { "name": "created", "type": "string", }, { "name": "proofPurpose", "type": "string", }, { "name": "type", "type": "string", }, { "name": "verificationMethod", "type": "string", }, ], }, }, "proofPurpose": "assertionMethod", "proofValue": "0xcf5844be1f1a5c1a083565d492ab4bee93bd0e24a4573bd8ff47331ad225b9d11c4831aade8d071f4abb8c9e266aaaf30612c582c2bc8f082b8788448895fa4a1b", "type": "EthereumEip712Signature2021", "verificationMethod": "did:pkh:eip155:1:0xAED7EA8035eEc47E657B34eF5D020c7005487443#blockchainAccountId", }
Nested Document - Types Generation - TypedData Schema as URI
With the following inputOptions
provided to the signature suite along with the testNestedDocument
input document:
{ "embedAsURI": true, "date": "2021-08-30T13:28:02Z", "verificationMethod": "did:pkh:eip155:1:0xAED7EA8035eEc47E657B34eF5D020c7005487443#blockchainAccountId", "domain": { "name": "Test" } }
The following is the resulting proof
object:
{ "created": "2021-08-30T13:28:02Z", "proofPurpose": "assertionMethod", "type": "EthereumEip712Signature2021", "verificationMethod": "did:pkh:eip155:1:0xAED7EA8035eEc47E657B34eF5D020c7005487443#blockchainAccountId", "proofValue": "0x8327ad5e4b2426eac7626400c75f000c3e04caf2a863b888988e4e85533880183d4b9cc6870183e55dabfa96b9486624f45ef849bb146257d123f297a2dbf3a11c", "eip712": { "domain": { "name": "Test" }, "types": "https://example.org/types.json", "primaryType": "Document" } }
Dereferencing the types
URI should result in the following object:
{ "Data": [ { "name": "job", "type": "Job" }, { "name": "name", "type": "Name" } ], "Job": [ { "name": "employer", "type": "string" }, { "name": "jobTitle", "type": "string" } ], "Name": [ { "name": "firstName", "type": "string" }, { "name": "lastName", "type": "string" } ], "Document": [ { "name": "@context", "type": "string[]" }, { "name": "@type", "type": "string" }, { "name": "data", "type": "Data" }, { "name": "proof", "type": "Proof" }, { "name": "telephone", "type": "string" } ], "Proof": [ { "name": "created", "type": "string" }, { "name": "proofPurpose", "type": "string" }, { "name": "type", "type": "string" }, { "name": "verificationMethod", "type": "string" } ] }
The example URI provided above is not a real URI that would dereference, but outlines the expected behaviour.
Nested Document - Types Generation - Types Embedded
With the following inputOptions
provided to the signature suite along with the testNestedDocument
input document:
{ "date": "2021-08-30T13:28:02Z", "verificationMethod": "did:pkh:eip155:1:0xAED7EA8035eEc47E657B34eF5D020c7005487443#blockchainAccountId", "domain": { "name": "Test" }, "embed": true }
The following is the resulting proof
object:
{ "created": "2021-08-30T13:28:02Z", "eip712": { "domain": { "name": "EthereumEip712Signature2021", }, "primaryType": "Document", "types": { "Data": [ { "name": "job", "type": "Job", }, { "name": "name", "type": "Name", }, ], "Document": [ { "name": "@context", "type": "string[]", }, { "name": "@type", "type": "string", }, { "name": "data", "type": "Data", }, { "name": "proof", "type": "Proof", }, { "name": "telephone", "type": "string", }, ], "Job": [ { "name": "employer", "type": "string", }, { "name": "jobTitle", "type": "string", }, ], "Name": [ { "name": "firstName", "type": "string", }, { "name": "lastName", "type": "string", }, ], "Proof": [ { "name": "created", "type": "string", }, { "name": "proofPurpose", "type": "string", }, { "name": "type", "type": "string", }, { "name": "verificationMethod", "type": "string", }, ], }, }, "proofPurpose": "assertionMethod", "proofValue": "0x7d57ace2be9cc3944aac023f66130935e489bbb1c9b469a4a5b4f16e5c298b57291bc80d52c6f873b11f4bf45c97c6e2506419af7506eaac5374e9ed381fcc5b1b", "type": "EthereumEip712Signature2021", "verificationMethod": "did:pkh:eip155:1:0xAED7EA8035eEc47E657B34eF5D020c7005487443#blockchainAccountId", }
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, which is used to generate the `types` object deterministically if not provided. please review [[JCS]] for details.
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.
Signing over JSON-LD expanded terms is optional.
Linked data signatures suites typically use JSON-LD to RDF Deserialization, RDF Dataset Canonicalization and serialization as N-Quads, as part of constructing the data to sign. This signature suite differs by instead signing based on the JSON document structure more directly, without conversion to RDF. This is supposed to enable a more human-readable signing input. However, it means that information from the JSON-LD context is not included in the signing input that otherwise would be. If the referenced JSON-LD context files are changed, changing the definition of some terms, it is possible that the proof signature may remain valid but the underlying JSON-LD/RDF data could be different.
In some cases, this could create security issues if unmitigated, because the semantic disambiguation information is not included in the signing method's integrity guarantees. One common method for additionally securing those linked data documents is to add an additional, but optional, "semantic integrity" hash to the proof object before URDNA canonicalization. This digest then acts as a kind of checksum that the verifier can use to check the integrity of the expanded context. The algorithms for generating this digest (and implicitly, the algorithm for how to verify it) can be found in the Linked Data Proof specification.
To prevent this kind of issue, this specification defines a mechanism for including a cryptographic digest of the RDF data in the proof property, which is included in the signing input: (canonicalizationHash
proof property). While it is not expected that EIP-712 signers will be able to natively understand this canonicalization hash, signers and verifiers of this proof suite using JSON-LD processing can use it to ensure the integrity of the signed data document as a linked data document. When using this signature suite with JSON-LD documents, SHOULD be used.