From e33f73d1957e46b9ed17c319ed607bb3c7137944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 2 Dec 2024 19:44:44 -0600 Subject: [PATCH] Add missing ERC-7739 documentation --- .../utils/cryptography/ERC7739SignerECDSA.sol | 21 +++++++++++ .../utils/cryptography/MyContractDomain.sol | 20 +++++++++++ docs/modules/ROOT/nav.adoc | 1 + docs/modules/ROOT/pages/utilities.adoc | 36 +++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 contracts/mocks/docs/utils/cryptography/ERC7739SignerECDSA.sol create mode 100644 contracts/mocks/docs/utils/cryptography/MyContractDomain.sol create mode 100644 docs/modules/ROOT/pages/utilities.adoc diff --git a/contracts/mocks/docs/utils/cryptography/ERC7739SignerECDSA.sol b/contracts/mocks/docs/utils/cryptography/ERC7739SignerECDSA.sol new file mode 100644 index 0000000..fca13d3 --- /dev/null +++ b/contracts/mocks/docs/utils/cryptography/ERC7739SignerECDSA.sol @@ -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; + } +} diff --git a/contracts/mocks/docs/utils/cryptography/MyContractDomain.sol b/contracts/mocks/docs/utils/cryptography/MyContractDomain.sol new file mode 100644 index 0000000..e3eeb5e --- /dev/null +++ b/contracts/mocks/docs/utils/cryptography/MyContractDomain.sol @@ -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); + } +} diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 27f6f89..7c0d852 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -1 +1,2 @@ * xref:index.adoc[Overview] +* xref:utilities.adoc[Utilities] diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc new file mode 100644 index 0000000..9dcf744 --- /dev/null +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -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[] +----