Introducing: EIP-712 Structs in Python
What is EIP-712?
EIP-712 is a written standard for Ethereum that describes how we can manage a more “human friendly” data structure system. We won’t go too deep into the details here — the standard has the info you need! The important things to know are:
- EIP-712 defines how we can treat unstructured data as a deterministic bytestring, so we have a reliable way to sign data and verify those signatures.
- Support for this standard in Ethereum clients (such as MetaMask) would give some insight to users into what sort of messages they are signing — instead of a long random hexstring, they would see a human-readable data structure.
Why use this library?
Having come from a heavy Java background, it made the most sense to treat specialized data structures like this as classes and objects. The goal of this python library is to give “side-by-side” parity to data structure creation in both Python and Solidity, as well as handling the common operations needed for this library.
For more information, you can head straight to the GitHub repo @ https://github.com/ConsenSys/py-eip712-structs.
pip install eip712-structs
We want to represent the following solidity struct in python:
Okay!
So far all we have done is defined our data structures. Let’s instantiate them with some data.
Continuing in solidity:
And in python:
If you read the standard, you’ll note that it supports arbitrarily nesting structs within other structs. Lets look at how this works in Python.
Let’s start from the previous example — but instead of just sending mail to an anonymous address, we want to track more about the recipient. For example, what is the recipients name? We can group recipient info into its own dedicated Person
struct, which is then nested within the Mail
.
In solidity:
And in python:
Take note: In the python implementation, Person
is defined in Mail
without parentheses! This distinction is important. Basic types keep the parens, since some types need a little extra information.
For example, in solidity you have bytes1
, bytes2
, ..., bytes32
. You also have the dynamic bytes
type, which can be any arbitrary length.
In python:
Once you have a struct instantiated with some data, you can make it message-ready with a domain separator. More info on domain separators may be found in the spec — but in a nutshell, it’s a special kind of EIP-712 struct that can help differentiate between otherwise equivalent structs.
Working with Domain Separators
Although you could easily create a domain separator like any other struct, a convenience method is provided to help you skip the tedium.
Note that all arguments to make_domain
are optional - but at least one must be provided. Per the spec, if an argument is not provided then it is omitted from the struct entirely.
Convert a struct into a message
With the domain separator ready, we can convert the struct into the message format.
The above would print something like:
Note: The standard JSON Encoder doesn’t support bytes types. Use .to_message_json() to ensure bytes are correctly encoded into hexstrings during JSON encoding.
Included in the spec is also a deterministic method for converting a struct/domain pair into a reliable bytestring. This bytestring is suitable for tasks such as signing the message payload.
The above bytes follow the format: \x19\x01
+ <bytes from domain data>
+ <bytes from message data>
Since the EIP-712 message format includes all information about struct types, we can build the pythonic structs on-the-fly from JSON data. Check it:
By using this library, you’ll hopefully be up and running with EIP-712 messages quickly and painlessly!