-
Notifications
You must be signed in to change notification settings - Fork 29
feat(SingleSignerValidation): add replay safe hash and utility contracts for EIP-712 in modules [2/2] #141
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity ^0.8.25; | ||
|
|
||
| // A base for modules that use EIP-712 structured data signing. | ||
| // | ||
| // Unlike other EIP712 libraries, this mixin uses the salt field to hold the account address. | ||
| // | ||
| // It omits the name and version from the EIP-712 domain, as modules are intended to be deployed as | ||
| // immutable singletons, thus a different versions and instances would have a different module address. | ||
| // | ||
| // Due to depending on the account address to calculate the domain separator, this abstract contract does not | ||
| // implement EIP-5267, as the domain retrieval function does not provide a parameter to use for the account address | ||
| // (internally the verifyingContract), and the computed `msg.sender` for an `eth_call` without an override is | ||
| // address(0). | ||
| abstract contract ModuleEIP712 { | ||
| // keccak256( | ||
| // "EIP712Domain(uint256 chainId,address verifyingContract,bytes32 salt)" | ||
| // ) | ||
| bytes32 private constant _DOMAIN_SEPARATOR_TYPEHASH = | ||
| 0x71062c282d40422f744945d587dbf4ecfd4f9cfad1d35d62c944373009d96162; | ||
|
|
||
| function _domainSeparator(address account) internal view returns (bytes32) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will need public view func to get the EIP712Domain fields.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I discussed this in the PR body and in comments in the file - we can't safely expose that function from ERC-5267 because the domain separator changes by account, and the function does not take a parameter. We could do something non-standard by depending on
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, yeah, we will also need to install With what we have, we also are not completely compliant with EIP-712.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
How so? I think we're compliant with EIP-712, just not the extension EIP-5267.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Look at the definition of doamin-separator.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nvm, we are. Was not thinking the account converted to bytes32 would be the salt. Actually, we might still be compliant with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a cool idea, but I wonder if doing so requires implementers to keep track of this specific behaviour-- which is functionally equivalent to having a new function. I could be wrong but the tradeoff is either "use the old function, but be ready to handle a new special case" or "use a new function." Does that sound right?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now, we dont have a funciton. One would need to read the code and do custom compute. The tradeoff you (@Zer0dot ) mentioned is correct if we want to have a function. I also think it is valuable to add one.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think the 5267 function So, basically I don't think we can be fully compliant with 5267, because the caller will still always need module-specific context, at which point the caller can just use the known EIP712 domain rather than retrieving it via an
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, 5267 compatibility is tricky if routing happens offchain.. Not sure if 5267 compatibility is necessary though, I think its more of a nice to have as the SDK is being used to generate signatures. Since the SDK should know which module it's creating a 1271 signature for, it could call eip712Domain or an equivalent on the module when preparing the signature |
||
| return keccak256( | ||
| abi.encode( | ||
| _DOMAIN_SEPARATOR_TYPEHASH, block.chainid, address(this), bytes32(uint256(uint160(account))) | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity ^0.8.25; | ||
|
|
||
| import {ModuleEIP712} from "./ModuleEIP712.sol"; | ||
|
|
||
| import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; | ||
|
|
||
| // A contract mixin for modules that wish to use EIP-712 to wrap the hashes sent to the EIP-1271 function | ||
| // `isValidSignature`. | ||
| // This makes signatures generated by owners of contract accounts non-replayable across multiple accounts owned by | ||
| // the same owner. | ||
| abstract contract ReplaySafeWrapper is ModuleEIP712 { | ||
| // keccak256("ReplaySafeHash(bytes32 hash)") | ||
| bytes32 private constant _REPLAY_SAFE_HASH_TYPEHASH = | ||
| 0x294a8735843d4afb4f017c76faf3b7731def145ed0025fc9b1d5ce30adf113ff; | ||
|
|
||
| /// @notice Wraps a hash in an EIP-712 envelope to prevent cross-account replay attacks. | ||
| /// Uses the ModuleEIP712 domain separator, which includes the chainId, module address, and account address. | ||
| /// @param account The account that will validate the message hash. | ||
| /// @param hash The hash to wrap. | ||
| /// @return The the replay-safe hash, computed by wrapping the input hash in an EIP-712 struct. | ||
| function replaySafeHash(address account, bytes32 hash) public view virtual returns (bytes32) { | ||
| return MessageHashUtils.toTypedDataHash({ | ||
| domainSeparator: _domainSeparator(account), | ||
| structHash: _hashStruct(hash) | ||
| }); | ||
| } | ||
|
|
||
| function _hashStruct(bytes32 hash) internal view virtual returns (bytes32) { | ||
| return keccak256(abi.encode(_REPLAY_SAFE_HASH_TYPEHASH, hash)); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.