Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ERC7739 and ERC7739Utils #5243

Closed
Closed
Show file tree
Hide file tree
Changes from 77 commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
6d4cbbf
Add ERC4337 and ERC7579 utilities
ernestognw Oct 8, 2024
6dc7749
Lint
ernestognw Oct 8, 2024
efa9e2b
Lint
ernestognw Oct 8, 2024
211770b
Remove linting for vendored contracts and change to explicit imports
ernestognw Oct 8, 2024
1a25df6
Codespell
ernestognw Oct 8, 2024
fae755e
Merge branch 'master' into aa/utils-interfaces
ernestognw Oct 8, 2024
353f278
Complete tests
ernestognw Oct 9, 2024
33b0c1b
Fix transpilation
ernestognw Oct 9, 2024
a013306
Exclude vendored contracts from slither detection
ernestognw Oct 9, 2024
90fffbf
Fix transpilation (?)
ernestognw Oct 9, 2024
913e522
Filter constants un ERC7579Utils
ernestognw Oct 9, 2024
01e19a1
Make StakeManager's deposit's private
ernestognw Oct 9, 2024
8030e42
up
ernestognw Oct 9, 2024
81b0caa
up
ernestognw Oct 9, 2024
d83ed9e
up
ernestognw Oct 9, 2024
d6767b9
up
ernestognw Oct 9, 2024
8aecee3
Add doc
ernestognw Oct 9, 2024
3a1efa4
Add ERC1271TypedSigner and MessageEnvelopeUtils
ernestognw Oct 9, 2024
c6b5aac
Fix coverage
ernestognw Oct 9, 2024
b50eb21
Merge branch 'aa/utils-interfaces' into aa/typed-signer
ernestognw Oct 9, 2024
d1bc68d
draft-ERC433Utils -> draft-ERC4337Utils
ernestognw Oct 9, 2024
866be61
Merge branch 'aa/utils-interfaces' into aa/typed-signer
ernestognw Oct 9, 2024
4dab8d0
Remove mixed contracts
ernestognw Oct 9, 2024
bbb5271
Merge branch 'aa/utils-interfaces' into aa/typed-signer
ernestognw Oct 9, 2024
93b6892
Fix slither false positive
ernestognw Oct 9, 2024
cb961c5
Add changesets
ernestognw Oct 9, 2024
99c0b7c
reset vendored contract to the match the source
Amxx Oct 9, 2024
7934421
reset vendored contract to the match the source
Amxx Oct 9, 2024
75d470e
Merge branch 'account-abstraction' into aa/utils-interfaces
Amxx Oct 9, 2024
a194289
remove contracts/abstraction, that was replaced by contracts/account
Amxx Oct 9, 2024
8be86a7
remove duplicated
Amxx Oct 9, 2024
77f9821
refactor mock
Amxx Oct 9, 2024
a183548
don't include vendored erc4337-entrypoint in the nopm package.
Amxx Oct 9, 2024
176f151
change imports to aliased in vendor/erc4337-entrypoint for upgradeabl…
Amxx Oct 9, 2024
21e950b
fix
Amxx Oct 9, 2024
6a8cfde
fix
Amxx Oct 9, 2024
71f8ec8
codespell
Amxx Oct 9, 2024
7ac541a
exlude vendor contract from namespace transformation
Amxx Oct 10, 2024
8341a4f
test
Amxx Oct 10, 2024
8bac09c
Fix transpilation and testing of upgradeable contracts
Amxx Oct 10, 2024
c2e9839
update
Amxx Oct 10, 2024
09a5691
Applying review
ernestognw Oct 10, 2024
6216b5c
Update contracts/interfaces/draft-IERC4337.sol
ernestognw Oct 10, 2024
afbb6e4
Remove IEntryPointSimulation
ernestognw Oct 10, 2024
2a50933
Fix conflicts
ernestognw Oct 10, 2024
5bb9757
/// comments
Amxx Oct 10, 2024
cd3e388
minor refactor
Amxx Oct 10, 2024
bdf55bb
update
Amxx Oct 10, 2024
b72e3b1
Update .changeset/small-seahorses-bathe.md
ernestognw Oct 10, 2024
0a2fb48
Update .codecov.yml
ernestognw Oct 10, 2024
2073799
rename global comparators
Amxx Oct 10, 2024
bda0f5f
Merge branch 'aa/utils-interfaces' into aa/typed-signer
ernestognw Oct 10, 2024
7927a33
Rename to ERC7739
ernestognw Oct 10, 2024
0a3c4ef
Get rid of envelope
ernestognw Oct 10, 2024
75377ee
up
ernestognw Oct 11, 2024
8bb0438
Merge branch 'account-abstraction' into aa/typed-signer
ernestognw Oct 11, 2024
7129f38
Improve tests
ernestognw Oct 11, 2024
7873042
Fix edge case tests
ernestognw Oct 11, 2024
c65e137
Add docs
ernestognw Oct 11, 2024
b9696bd
Fix tryValidateContentsType
ernestognw Oct 11, 2024
cdae537
Update .changeset/sweet-grapes-raise.md
Amxx Oct 11, 2024
4d109f8
Merge branch 'account-abstraction' into aa/typed-signer
ernestognw Oct 14, 2024
3224ee6
Use Bytes library
ernestognw Oct 14, 2024
2ac663f
fix minor bug + small refactors
Amxx Oct 15, 2024
d592192
add missing test case
Amxx Oct 15, 2024
dd6caea
cleanup tests
Amxx Oct 15, 2024
b3cef39
add Mock suffix
Amxx Oct 15, 2024
22e4216
Update contracts/utils/cryptography/draft-ERC7739Utils.sol
Amxx Oct 16, 2024
c2e2ef7
Update contracts/utils/README.adoc
Amxx Oct 16, 2024
6626d3e
Update contracts/utils/README.adoc
Amxx Oct 16, 2024
8b9ede9
rename functions
Amxx Oct 16, 2024
05ea45e
Merge branch 'aa/typed-signer' of https://github.com/ernestognw/openz…
Amxx Oct 16, 2024
422889b
simlify
Amxx Oct 16, 2024
21f08cd
remove broken reference
Amxx Oct 16, 2024
1786b69
rewrite tryValidateContentsType to only perform one loop, avoid copy …
Amxx Oct 16, 2024
2f6f154
loosen first letter restrictions on the contentsTypeName
Amxx Oct 16, 2024
8fa48f9
codespell
Amxx Oct 16, 2024
f3111a5
Fix ERC7739Signer behavior with ECDSA signer
Amxx Oct 17, 2024
8f244fa
fix
Amxx Oct 17, 2024
c3b61dc
contentsType and contentsTypeName are strings
Amxx Oct 17, 2024
79af185
erc7739 helpers
Amxx Oct 17, 2024
c0fa4b7
working on tests
Amxx Oct 17, 2024
cfdb514
tests done
Amxx Oct 17, 2024
2702222
up
Amxx Oct 17, 2024
dbe84c2
rename/reorder utils
Amxx Oct 17, 2024
893f680
update doc
Amxx Oct 17, 2024
8dfe87e
simplify
Amxx Oct 17, 2024
391c92c
revert changes to eip712
Amxx Oct 17, 2024
48211e4
rename
Amxx Oct 17, 2024
211bb38
I guess that is what we should be doing
Amxx Oct 17, 2024
00aef29
naming
Amxx Oct 17, 2024
8a33b80
test with Permit objects
Amxx Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sweet-grapes-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`ERC7739Utils`: Add a library that implements a defensive rehashing mechanism to prevent replayability of smart contract signatures based on the ERC-7739.
5 changes: 5 additions & 0 deletions .changeset/tidy-yaks-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`ERC7739Signer`: An abstract contract to validate signatures following the rehashing scheme from `ERC7739Utils`.
20 changes: 20 additions & 0 deletions contracts/mocks/ERC7739SignerECDSAMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

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

contract ERC7739SignerECDSAMock 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;
}
}
23 changes: 23 additions & 0 deletions contracts/mocks/ERC7739SignerP256Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

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

contract ERC7739SignerP256Mock is ERC7739Signer {
bytes32 private immutable _qx;
bytes32 private immutable _qy;

constructor(bytes32 qx, bytes32 qy) EIP712("ERC7739SignerP256", "1") {
_qx = qx;
_qy = qy;
}

function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual override returns (bool) {
bytes32 r = bytes32(signature[0x00:0x20]);
bytes32 s = bytes32(signature[0x20:0x40]);
return P256.verify(hash, r, s, _qx, _qy);
}
}
21 changes: 21 additions & 0 deletions contracts/mocks/ERC7739SignerRSAMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

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

contract ERC7739SignerRSAMock is ERC7739Signer {
bytes private _e;
bytes private _n;

constructor(bytes memory e, bytes memory n) EIP712("ERC7739SignerRSA", "1") {
_e = e;
_n = n;
}

function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual override returns (bool) {
return RSA.pkcs1Sha256(hash, signature, _e, _n);
}
}
6 changes: 6 additions & 0 deletions contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Miscellaneous contracts and libraries containing utility functions you can use t
* {Hashes}: Commonly used hash functions.
* {MerkleProof}: Functions for verifying https://en.wikipedia.org/wiki/Merkle_tree[Merkle Tree] proofs.
* {EIP712}: Contract with functions to allow processing signed typed structure data according to https://eips.ethereum.org/EIPS/eip-712[EIP-712].
* {ERC7739Utils}: Utilities to support ERC-7739, a defensive rehashing scheme that prevents replayability of smart contract signatures.
* {ERC7739Signer}: A base contract to implement ERC-1271 signatures using {ERC7739Utils}.
* {ReentrancyGuard}: A modifier that can prevent reentrancy during certain functions.
* {ReentrancyGuardTransient}: Variant of {ReentrancyGuard} that uses transient storage (https://eips.ethereum.org/EIPS/eip-1153[EIP-1153]).
* {Pausable}: A common emergency response mechanism that can pause functionality while a remediation is pending.
Expand Down Expand Up @@ -67,6 +69,10 @@ Because Solidity does not support generic types, {EnumerableMap} and {Enumerable

{{EIP712}}

{{ERC7739Utils}}

{{ERC7739Signer}}

{{MessageHashUtils}}

{{SignatureChecker}}
Expand Down
105 changes: 105 additions & 0 deletions contracts/utils/cryptography/draft-ERC7739Signer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {IERC1271} from "../../interfaces/IERC1271.sol";
import {EIP712} from "./EIP712.sol";
import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ERC7739Utils} from "./draft-ERC7739Utils.sol";
import {ShortStrings} from "../ShortStrings.sol";

/**
* @dev Validates signatures wrapping the message hash in a nested EIP712 type. See {ERC7739Utils}.
*
* Linking the signature to the EIP-712 domain separator is a security measure to prevent signature replay across different
* EIP-712 domains (e.g. a single offchain owner of multiple contracts).
*
* This contract requires implementing the {_validateSignature} function, which passes the wrapped message hash,
* which may be either an typed data or a personal sign nested type.
*
* NOTE: {EIP712} uses {ShortStrings} to optimize gas costs for short strings (up to 31 characters).
* Consider that strings longer than that will use storage, which may limit the ability of the signer to
* be used within the ERC-4337 validation phase (due to ERC-7562 storage access rules).
Comment on lines +19 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this accurate? The account is able to read its own storage during validation so I don't think there will be any issues.

Suggested change
*
* NOTE: {EIP712} uses {ShortStrings} to optimize gas costs for short strings (up to 31 characters).
* Consider that strings longer than that will use storage, which may limit the ability of the signer to
* be used within the ERC-4337 validation phase (due to ERC-7562 storage access rules).

Copy link
Collaborator

@Amxx Amxx Oct 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is in case account A relies on another other contract B for validation:

Callstack is something like:

  • ERC-4337 Entrypoint: handleOps
    • ERC-4337 Account: validateUserOp.
      Lets say that is implemented as a multisig that, so it will interpret the signature as a bytes[] and parse it. For each sub-signature, it will verify it against an identity. Identity could be an EOA, but it could also be another contract that implements ERC-1271. Under the hood, the ERC-1271 may do a further multisig, or use P256/RSA as a signing scheme
      • ERC-1271 Identify contract: isValidSignature: This is where the identity contract might use ERC-7739, because it has control over multiple ERC-4337 Accounts. In that case, there are storage restrictions that apply.

Copy link
Collaborator

@Amxx Amxx Oct 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@frangio would that be good ?

Suggested change
*
* NOTE: {EIP712} uses {ShortStrings} to optimize gas costs for short strings (up to 31 characters).
* Consider that strings longer than that will use storage, which may limit the ability of the signer to
* be used within the ERC-4337 validation phase (due to ERC-7562 storage access rules).
*
* NOTE: {EIP712} uses {ShortStrings} to optimize gas costs for short strings (up to 31 characters). Consider that
* strings longer than that, and strings of any size for the upgradable variants of this contract, will be placed in
* storage. This may limit the ability of the signer to be used within the ERC-4337 validation. If the sender of the
* user operation performs an ERC-1271 check against this signer, the ERC-7562 storage access rules will be broken.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is in case account A relies on another other contract B for validation:

Yes but this is a much larger issue than EIP-712 name and version strings, because the public keys will usually be in storage.

IMO mentioning this here is a distraction.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are ways to put the public keys outside of storage (for example using a Proxy with immutable args) ... but the dependency on ERC712 prevents is just too much.

I don't have a strong opinion either way :/

I'm considering merging with (or without) the message for the audit, and have PR with all the proposed changes (in this case adding or removing) that we'll discuss in parallele of the audit.

*/
abstract contract ERC7739Signer is EIP712, IERC1271 {
using ERC7739Utils for *;

/**
* @dev Attempts validating the signature in a nested EIP-712 type.
*
* A nested EIP-712 type might be presented in 2 different ways:
*
* - As a nested EIP-712 typed data
* - As a _personal_ signature (an EIP-712 mimic of the `eth_personalSign` for a smart contract)
*/
function isValidSignature(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4 result) {
return _isValidSignature(hash, signature) ? IERC1271.isValidSignature.selector : bytes4(0xffffffff);
}

/**
* @dev Internal version of {isValidSignature} that returns a boolean.
*/
function _isValidSignature(bytes32 hash, bytes calldata signature) internal view virtual returns (bool) {
return
_isValidNestedPersonalSignSignature(hash, signature) || _isValidNestedTypedDataSignature(hash, signature);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me whether this matches the spec: https://ercs.ethereum.org/ERCS/erc-7739#signature-verification-workflow-deduction

If the signature contains the correct data to reconstruct the hash, the isValidSignature function MUST perform the TypedDataSign workflow. Otherwise, the isValidSignature function MUST perform the PersonalSign workflow.

}

/**
* @dev Nested personal signature verification.
*/
function _isValidNestedPersonalSignSignature(
bytes32 hash,
bytes calldata signature
) internal view virtual returns (bool) {
return _validateSignature(_domainSeparatorV4().toNestedPersonalSignHash(hash), signature);
}

/**
* @dev Nested EIP-712 typed data verification.
*/
function _isValidNestedTypedDataSignature(
bytes32 hash,
bytes calldata signature
) internal view virtual returns (bool) {
// decode signature
(bytes calldata nestedSignature, bytes32 separator, bytes32 contents, bytes calldata contentsType) = signature
.decodeSignature();

// fetch domain details
(
,
string memory name,
string memory version,
,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
) = eip712Domain();

// Check that hash is the correct nested typed data hash, and that the nested signature is valid.
return
hash ==
ERC7739Utils.toNestedTypedDataHash(
separator,
ERC7739Utils.typedDataNestedStructHash(
contentsType,
contents,
name,
version,
verifyingContract,
salt,
extensions
)
) &&
_validateSignature(hash, nestedSignature);
}

/**
* @dev Signature validation algorithm.
*
* WARNING: Implementing a signature validation algorithm is a security-sensitive operation as it involves
* cryptographic verification. It is important to review and test thoroughly before deployment. Consider
* using one of the signature verification libraries ({ECDSA}, {P256} or {RSA}).
*/
function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual returns (bool);
}
Loading