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

Use EIP-712 for computing user operation hash #517

Open
adamegyed opened this issue Dec 19, 2024 · 2 comments
Open

Use EIP-712 for computing user operation hash #517

adamegyed opened this issue Dec 19, 2024 · 2 comments

Comments

@adamegyed
Copy link

The EntryPoint computes and provides a user operation hash. This is used in emitted events, for the JSON-RPC methods like eth_getUserOperationByHash, and most account implementations in validateUserOp. However, as of v0.7, the hash calculation is done in a custom way:

  • First, the dynamic fields (excluding signature) are hashed: initCode, callData, and paymasterAndData.
  • Then, the static fields and the hashes of dynamic fields are encoded in a fixed order and hashed.
  • Then, this user op hash is included in an outer encoding that appends the entrypoint address and chainId.
  • Finally, that outer encoding is hashed.

This results in an opaque hash to sign to authorize a user op from a wallet. For wallets built up with 4337 in mind, this can be displayed in cleartext to the user, but for signers connected to the wallet externally (i.e. via a provider using EIP-1193), there is not a good way to present the option to sign over a user op such that the contents are visible. Most wallets default to including the user op hash in an EIP-191 signed message, but this is still opaque to users.

In the past, there were some proposals to add a new wallet RPC for signing a user operation, but it could be more easily solved on the base layer by using EIP-712 to construct the typed data hash to sign. This would have compatibiltiy with existing signers via the eth_signTypedData_v4 wallet RP.

For a future version of the EntryPoint contract, it would be useful to construct the user op hash as an EIP-712 typed data hash. For encoding the contents of the user op (the inner hash), it is essentially the same as the existing workflow, but with an added EIP-712 typehash at the front. The outer hashing step can also be replaced with using a domain separator that includes only the chainId and verifyingContract fields, since this accomplishes the same purpose of making the user op hash unique over these fields. For example, the domain separator could be keccak256("EIP712Domain(uint256 chainId,address verifyingContract)").

@drortirosh
Copy link
Contributor

I'm trying to understand what do you gain by using ERC-712 signatures.
ERC-712 signatures are meant for dApps to require a signature that the core networks (and wallets) don't recognize.
That is, the webapp requests the user to sign a structure, which the on-chain contract later validates. The wallet doesn't know the dApp structure - it only recognize the "metadata" which let it show the struct fields before signing.

ERC-4337, defines a transport protocol, not application protocol. The signed structure (UserOperation) must be known to the wallet itself (and actually, usually it is invisible to the dApp - both its webapp and backend contract). It is the wallet's duty not only to sign a UserOperation, but first fill it (encode account-specific "execute", estimate gas, add wallet-specific paymaster) and then show it in a meaningful way to the user.

@adamegyed
Copy link
Author

What you gain by using EIP-712 is better backwards compatibility when using existing wallets as signers on a smart account. For example, the Safe web wallet allows connecting a "wallet" that is only used as a signer for the smart account via the standard EIP-1193 provider interface. These can be things like Metamask, a Ledger, or a mobile wallet connected via walletconnect. These signers are only able to interact over the standardized wallet RPC interface, so it's usually limited to eth_sendTransaction, personal_sign, and eth_signTypedData_v4. They don't expose signing a raw hash for security reasons.

Most current 4337 smart account implementations check signatures over a personal_sign of the user op hash, which is unfortunately opaque to the end user. It is technically possible to use EIP-712 for this signature checking, if you recompute the entire user op hash from the user op contents. For example, OpenZeppelin did this approach here: https://github.com/OpenZeppelin/openzeppelin-community-contracts/blob/31291392ccabb84b68302703e3e3043b6e4a40b4/contracts/account/AccountCore.sol

But, this adds a cost to signature checking in validateUserOp, by repeating much of the steps that the EntryPoint already did in computing the user op hash. And, implementing EIP-712 is already fairly similar to what's currently being done, it just needs the addition of a typehash and to move the EntryPoint address and chain id to the domain separator.

Note that this wouldn't get full clarity into what the account is doing, since it's a packed user op, and decoding the calldata is context-dependent. But it would still generally help to assure the signing wallet that the "thing being signed" is a user op for a specific smart account, with the contents available to inspect if needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants