Verification method using EIP-712
See original GitHub issueEIP-712 is a way for an Ethereum identity (secp256k1 keypair) to sign structured data. This is supposed to be good for security and user autonomy since rather than asking the user to sign arbitrary bytes, instead the user is prompted with some structured data where it can be more obvious what the meaning of it is. To use EIP-712 for linked data proofs on verifiable credentials and presentations, existing proof types such EcdsaSecp256k1Signature2019 cannot be used, because they rely on the user being able to sign arbitrary bytes; instead, a new verification method / proof type would be needed. Would this be a potentially useful addition to did:ethr
DID Documents?
Here is a screenshot of MetaMask showing a EIP-712 signing request of a hypothetical verifiable credential (not actually working yet, this is just to show what it could look like):
Source: demo.html.txt
Issue Analytics
- State:
- Created 3 years ago
- Comments:14 (6 by maintainers)
Top GitHub Comments
I’m closing this issue, as the idea has been implemented, and a specification is being drafted as a work item of the W3C Credentials Community Group: https://github.com/w3c-ccg/community/issues/194.
EthereumEip712Signature2021
maps linked data documents and proof objects to EIP-712 TypedData using a JSON-based approach, with type definitions. There is an issue to make type generation automatic: https://github.com/uport-project/ethereum-eip712-signature-2021-spec/issues/9 and an issue about preserving the RDF data model: https://github.com/uport-project/ethereum-eip712-signature-2021-spec/issues/10. @spruceid’s implementation can be seen at https://github.com/spruceid/ssi/pull/213.We also still have the suite using arrays of RDF statements (
Eip712Signature2021
) described earlier in this thread, but are preferring the one that is in CCG, for interoperability.We’ve also been exploring using
personal_sign
in linked data signatures, for situations where EIP-712 signing is not available: https://github.com/spruceid/ssi/pull/212 (EthereumPersonalSignature2021
).While working out the usability implications of signing linked data, we are exploring use of “signing input explainer” tools, to help users make sense of what they are being asked to sign, e.g. https://github.com/spruceid/tzvm2021-explainer
EthereumEip712Signature2021
is specified as a proof type that can be used with existing verification method types (EcdsaSecp256k1VerificationKey2019
orEcdsaSecp256k1RecoveryMethod2020
), rather than defining a new corresponding verification method type. Therefore, DID documents, methods and resolvers don’t have to be updated to support the signature suite, only signers (VC issuers/presenters), and verifiers need to support the suite. CCG’s VC HTTP API has also been updated to accommodate this use case, enabling issuers/presenters to select which proof type they want to created: https://github.com/w3c-ccg/vc-http-api/pull/197 (although admittedly, signing in the browser might not need VC HTTP API).Hi folks,
I’ve implemented a working version of this verification method, in Rust as part of ssi/DIDKit: https://github.com/spruceid/ssi/pull/99. It is integrated into a demo application: https://github.com/spruceid/degen-issuer/pull/7.
Changes
Since the original proposal, it is now more linked-data-based rather than JSON-based. There are two main reasons for this.
Encoding
A linked data proof signing request is encoded for EIP-712 as a struct,
LDPSigningRequest
, with two fields,document
andproof
, representing the linked data document (e.g. credential or presentation) and proof, respectively.document
andproof
are both 2D arrays of strings, where each inner array encodes an RDF statement, containing 3 or 4 strings which are RDF terms as they would be encoded in NQuads. This is like NQuads but an array of lines instead of a single multi-line string, and an array of strings for each line.This encoding aims to be similar to the usual way signing input for a linked data proof is created, while enabling the user to view and inspect the data they would sign. Encoding with 2D string arrays is supposed to help with readability. More descriptive encoding could be used, such as having each RDF statement be
{"subject": …, "predicate": …, "object": …}
rather than an array of strings; but in the MetaMask signing UI, struct field names take up horizontal space, reducing space for the content. Encoding terms as EIP-712 structs rather than N-Quads-encoded strings may be possible, but a solution would be needed for encoding the polymorphism of RDF terms without creating a lot of dangling empty properties, and there would still be the issue of horizontal space.Until recently (https://github.com/MetaMask/metamask-extension/pull/10485), strings in MetaMask’s signing request window would overflow with an ellipsis rather than wrapping, as you can see in the screenshot at the top of this thread compared to the ones below. Now that that has been fixed, it might make sense to changing the encoding of the RDF statements to use a single string per statement (N-Quads line), rather than an array of strings per line, so that it would be closer to standard N-Quads and require less custom encoding. But it probably should still be to be one string per line rather than a single multi-line string, since I don’t think the UI would respect embedded newlines.
Lastly, note that there appears to be an inconsistency between eth-sig-util as used from MetaMask, and the EIP-712 specification, in hashing arrays: https://github.com/MetaMask/eth-sig-util/issues/106. The encoding I am proposing is affected by this, since it relies on arrays in the EIP-712 data. The implementation in DIDKit now follows
eth-sig-util
’ssignTypedData_v4
implementation. We will need to keep an eye on this ifeth-sig-util
/MetaMask changes its behavior, or if there are other implementations that implement it differently, or if there are updates to EIP-712.EIP-712 typed data for a linked data proof signing request
Example
Here is the proposed EIP-712 encoding for a signing request for an example verifiable credential: eip712vm-typeddata.json.txt
The signing request in MetaMask looks like this:
The resulting verifiable credential: eip712vm-vc.jsonld.txt
On-chain considerations
As @wyc noted, our use cases for this verification method are more for Ethereum wallet users to be able to use verifiable credentials and presentations, and I didn’t have on-chain use cases in mind when opening this issue. But I think it would be great to support on-chain use cases to the extent possible. I think it would be difficult to implement linked data proofs on-chain in general, because of the computation needed to canonicalize, encode and hash the document and proof data, but I think it would be possible to do within some constraints. I also don’t personally have experience in developing smart contracts, so my view here is limited.
Minimize blank nodes
One way to reduce the cost of linked data proofs could be to limit use of blank nodes. Canonicalizing a RDF dataset using URDNA2015 involves assigning canonical names to blank node identifiers in the dataset (e.g.
_:c14n0
as seen in the screenshot). This may be expensive because it relies on SHA-256 hashing and intermediate data structures. Blank nodes generally result from JSON-LD objects that don’t contain an@id
property. If you can ensure that your input data does not have blank nodes, by giving every object anid
, or contains at most one blank node so it is trivially canonicalized, you may be able to skip the full URDNA2015 and canonicalize the dataset simply by sorting the lines (as N-Quads).Optimize sorting
URDNA2015 requires sorting RDF statements lexicographically. Deserializing JSON-LD to RDF also involves encoding data in a canonical lexical form which for JSON literals involves sorting keys lexicographically. There could be optimizations regarding this sorting that could help make on-chain issuance and/or verification more feasible. Issuance could work by constructing signing input with a template, rather than in a fully general way, while taking care that the input is in the canonical form. It could help with verification to require input to be pre-sorted and pre-canonicalized. Consider disallowing data types that are expensive to check if they are in canonical form, such as perhaps
rdf:JSON
.Hashing
This thread suggests the cost of SHA-256 may be about 700-1300 gas: https://ethereum.stackexchange.com/questions/76110/gas-cost-of-a-sha256-hash. I think this hashing would only be needed for blank node canonicalization in URDNA2015.
String processing
Encoding as N-Quads or N-Quads-like strings requires some string processing, which might not be otherwise needed if terms were instead encoded as native EIP-712 structs. But fully using EIP-712 structs might be more inconvenient in the UI, and require more custom processing compared to N-Quads. Is this an acceptable trade-off?
EIP-1812
I didn’t know about this, so thanks @jaredweinfurtner for pointing it out. It looks great for Ethereum-based uses. But I think it would be valuable to integrate with the W3C VC Data Model, to potentially interoperate with many issuers, holders and verifiers. A linked data proof type based on EIP-712 could enable existing wallet users to make use of these standards while remaining authoritative over what they are signing with their keys.