_Follow along with the video lesson:_ --- ### EIP-712 To bring things back to Boss Bridge, for a moment, `MessageHashUtils`, is a library that ultimately assists developers in managing different standard messaging formats. One of which is ERC-191, which we discussed, and the other is [**EIP-712**](https://eips.ethereum.org/EIPS/eip-712). EIP-712 introduced a standardized way to handle hashing and signing typed and structured data. In a practical sense this has transactions formatting data in such a way that rather than messages being a bytesstring, they could be parsed into meaningful, human readable information. From this: ::image{src='/security-section-7/14-eip-712/eip-7121.png' style='width: 100%; height: auto;'} To this: ::image{src='/security-section-7/14-eip-712/eip-7122.png' style='width: 100%; height: auto;'} ### EIP-712 Example I've included an example contract, in the [**GitHub Repo for this course**](https://github.com/Cyfrin/security-and-auditing-full-course-s23/blob/main/eip712hashing.sol), which demonstrates how EIP-712 is meant to be employed. Let's take a look! <details> <summary>eip712hashing.sol</summary> ```js pragma solidity ^0.4.24; contract Example { struct EIP712Domain { string name; string version; uint256 chainId; address verifyingContract; } struct Person { string name; address wallet; } struct Mail { Person from; Person to; string contents; } bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); bytes32 constant PERSON_TYPEHASH = keccak256("Person(string name,address wallet)"); bytes32 constant MAIL_TYPEHASH = keccak256("Mail(Person from,Person to,string contents)Person(string name,address wallet)"); bytes32 DOMAIN_SEPARATOR; constructor() public { DOMAIN_SEPARATOR = hash( EIP712Domain({ name: "Ether Mail", version: "1", chainId: 1, // verifyingContract: this verifyingContract: 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC }) ); } function hash(EIP712Domain eip712Domain) internal pure returns (bytes32) { return keccak256( abi.encode( EIP712DOMAIN_TYPEHASH, keccak256(bytes(eip712Domain.name)), keccak256(bytes(eip712Domain.version)), eip712Domain.chainId, eip712Domain.verifyingContract ) ); } function hash(Person person) internal pure returns (bytes32) { return keccak256(abi.encode(PERSON_TYPEHASH, keccak256(bytes(person.name)), person.wallet)); } function hash(Mail mail) internal pure returns (bytes32) { return keccak256(abi.encode(MAIL_TYPEHASH, hash(mail.from), hash(mail.to), keccak256(bytes(mail.contents)))); } function verify(Mail mail, uint8 v, bytes32 r, bytes32 s) internal view returns (bool) { // Note: we need to use `encodePacked` here instead of `encode`. bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hash(mail))); return ecrecover(digest, v, r, s) == mail.from.wallet; } function test() public view returns (bool) { // Example signed message Mail memory mail = Mail({ from: Person({name: "Cow", wallet: 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826}), to: Person({name: "Bob", wallet: 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB}), contents: "Hello, Bob!" }); uint8 v = 28; bytes32 r = 0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d; bytes32 s = 0x07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562; assert(DOMAIN_SEPARATOR == 0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f); assert(hash(mail) == 0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e); assert(verify(mail, v, r, s)); return true; } } ``` </details> The test function at the bottom of the contract shows how everything ultimately comes together. ```js function test() public view returns (bool) { // Example signed message Mail memory mail = Mail({ from: Person({name: "Cow", wallet: 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826}), to: Person({name: "Bob", wallet: 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB}), contents: "Hello, Bob!" }); uint8 v = 28; bytes32 r = 0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d; bytes32 s = 0x07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562; assert(DOMAIN_SEPARATOR == 0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f); assert(hash(mail) == 0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e); assert(verify(mail, v, r, s)); return true; } ``` A `Mail` struct is created with our typed and formatted data. The contents of this struct is then hashed with the `from` and `to` data. This is what's being send in our transaction. ```js function hash(Mail mail) internal pure returns (bytes32) { return keccak256(abi.encode(MAIL_TYPEHASH, hash(mail.from), hash(mail.to), keccak256(bytes(mail.contents)))); } ``` On the other side of a transaction we can then verify this hashed data by leveraging ecrecover and the `v, r, and s` values provided (these come from the `ECDSA algorithm`). The ecrecover precompile returns the wallet which signed the transaction. If this matches our expected `from` address, the message is verified! ```js function verify(Mail mail, uint8 v, bytes32 r, bytes32 s) internal view returns (bool) { // Note: we need to use `encodePacked` here instead of `encode`. bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hash(mail))); return ecrecover(digest, v, r, s) == mail.from.wallet; } ``` ### Wrap Up This is complex stuff. Don't be discouraged if it doesn't click for you right away. I encourage you to take the time to read more into signatures and how `ecrecover` works in the verification process. You can read more on `ecrecover` [**here, in the Solidity documentation**](https://docs.soliditylang.org/en/latest/units-and-global-variables.html#mathematical-and-cryptographic-functions). In the next lesson we'll investigate a case study featuring `Polygon` where an overlooked return value from `ecrecover` wasn't verified and it had dire consequences. See you soon!
We dive into EIP-712 and introduce typed structured data hashing and signing with minimalistic examples.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Solidity Developer
Smart Contract SecurityDuration: 25min
Duration: 1h 18min
Duration: 35min
Duration: 2h 28min
Duration: 5h 03min
Duration: 5h 22min
Duration: 4h 33min
Duration: 2h 01min
Duration: 1h 40min
Testimonials
Read what our students have to say about this course.
Chainlink
Chainlink
Gustavo Gonzalez
Solutions Engineer at OpenZeppelin
Francesco Andreoli
Lead Devrel at Metamask
Albert Hu
DeForm Founding Engineer
Radek
Senior Developer Advocate at Ceramic
Boidushya
WalletConnect
Idris
Developer Relations Engineer at Axelar