Skip to content

Commit

Permalink
Add missing ERC-7739 documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
ernestognw committed Dec 3, 2024
1 parent 37e596a commit e33f73d
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 0 deletions.
21 changes: 21 additions & 0 deletions contracts/mocks/docs/utils/cryptography/ERC7739SignerECDSA.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// contracts/ERC7739SignerECDSA.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

import {ERC7739Signer} from "../../../../utils/cryptography/draft-ERC7739Signer.sol";

contract ERC7739SignerECDSA is ERC7739Signer {
address private immutable _signer;

constructor(address signerAddr) EIP712("ERC7739SignerECDSA", "1") {
_signer = signerAddr;
}

function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual override returns (bool) {
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature);
return _signer == recovered && err == ECDSA.RecoverError.NoError;
}
}
20 changes: 20 additions & 0 deletions contracts/mocks/docs/utils/cryptography/MyContractDomain.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// contracts/MyContractDomain.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

/// @dev Unsafe contract to demonstrate the use of EIP712 and ECDSA.
abstract contract MyContractDomain is EIP712 {
function validateSignature(
address mailTo,
string memory mailContents,
bytes memory signature
) internal view returns (address) {
bytes32 digest = _hashTypedDataV4(
keccak256(abi.encode(keccak256("Mail(address to,string contents)"), mailTo, keccak256(bytes(mailContents))))
);
return ECDSA.recover(digest, signature);
}
}
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
* xref:index.adoc[Overview]
* xref:utilities.adoc[Utilities]
36 changes: 36 additions & 0 deletions docs/modules/ROOT/pages/utilities.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
= Utilities

Multiple libraries and general purpose utilities included in the community version of OpenZeppelin Contracts. These are only a set of utility contracts. For the full list, check out the xref:api:utils.adoc[API Reference].

== Cryptography

=== Validating Typed Data Signatures

_For prior knowledge on how to validate signatures on-chain, check out the https://docs.openzeppelin.com/contracts/5.x/utilities#checking_signatures_on_chain[OpenZeppelin Contracts documentation]_

As opposed to validating plain-text messages, it is possible to let your users sign structured data (i.e. typed values) in a way that is still readable on their wallets. This is possible by implementing https://docs.openzeppelin.com/contracts/api/utils#EIP712[`EIP712`], a standard way to encode structured data into a typed data hash.

To start validating signed typed structures, just validate the https://docs.openzeppelin.com/contracts/api/utils#EIP712-_hashTypedDataV4-bytes32-[typed data hash]:

[source,solidity]
----
include::api:example$utils/cryptography/MyContractDomain.sol[]
----

As part of the message, EIP-712 requires implementers to include a domain separator, which is a hash that includes the current smart contract address and the chain id where it's deployed. This way, the smart contract can be sure that the structured message was signed for its specific domain, avoiding replayability of signatures in smart contracts.

==== Validating Nested EIP-712 Signatures

Accounts (i.e. Smart Contract Wallets or Smart Accounts) are particularly likely to be controlled by multiple signers. As such, it's important to make sure that signatures are:

1. Only valid for the intended domain and account.
2. Validated in a way that's readable for the end signer.

On one hand, making sure that the Account signature is only valid for an specific smart contract (i.e. an application) is difficult since it requires to validate a signature whose domain is the application but also the Account itself. For these reason, the community developed https://eips.ethereum.org/EIPS/eip-7739[ERC-7739]; a defensive rehashing mechanism that binds a signature to a single domain using a nested EIP-712 approach (i.e. an EIP-712 typed structure wrapping another).

In case your smart contract validates signatures, using xref:api:utils.adoc#ERC7739Signer[`ERC7739Signer`] will implement the https://docs.openzeppelin.com/contracts/api/interfaces#IERC1271[`IERC1271`] interface for validating smart contract signatures following the approach suggested by ERC-7739:

[source,solidity]
----
include::api:example$utils/cryptography/ERC7739SignerECDSA.sol[]
----

0 comments on commit e33f73d

Please sign in to comment.