-
Notifications
You must be signed in to change notification settings - Fork 29
feat: [v0.8-develop, experimental]: sig validation in interface [2/N] #59
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 |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry | |
| import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; | ||
| import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; | ||
| import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; | ||
| import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; | ||
| import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; | ||
|
|
||
| import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; | ||
|
|
@@ -24,6 +25,7 @@ import { | |
| getAccountStorage, | ||
| getPermittedCallKey, | ||
| SelectorData, | ||
| toSetValue, | ||
| toFunctionReference, | ||
| toExecutionHook | ||
| } from "./AccountStorage.sol"; | ||
|
|
@@ -36,6 +38,7 @@ contract UpgradeableModularAccount is | |
| AccountStorageInitializable, | ||
| BaseAccount, | ||
| IERC165, | ||
| IERC1271, | ||
| IPluginExecutor, | ||
| IStandardExecutor, | ||
| PluginManagerInternals, | ||
|
|
@@ -55,6 +58,10 @@ contract UpgradeableModularAccount is | |
| bytes4 internal constant _INTERFACE_ID_INVALID = 0xffffffff; | ||
| bytes4 internal constant _IERC165_INTERFACE_ID = 0x01ffc9a7; | ||
|
|
||
| // bytes4(keccak256("isValidSignature(bytes32,bytes)")) | ||
| bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; | ||
| bytes4 internal constant _1271_INVALID = 0xffffffff; | ||
|
|
||
| event ModularAccountInitialized(IEntryPoint indexed entryPoint); | ||
|
|
||
| error AlwaysDenyRule(); | ||
|
|
@@ -67,6 +74,7 @@ contract UpgradeableModularAccount is | |
| error PreRuntimeValidationHookFailed(address plugin, uint8 functionId, bytes revertReason); | ||
| error RuntimeValidationFunctionMissing(bytes4 selector); | ||
| error RuntimeValidationFunctionReverted(address plugin, uint8 functionId, bytes revertReason); | ||
| error SignatureValidationInvalid(address plugin, uint8 functionId); | ||
| error UnexpectedAggregator(address plugin, uint8 functionId, address aggregator); | ||
| error UnrecognizedFunction(bytes4 selector); | ||
| error UserOpValidationFunctionMissing(bytes4 selector); | ||
|
|
@@ -310,6 +318,25 @@ contract UpgradeableModularAccount is | |
| super.upgradeToAndCall(newImplementation, data); | ||
| } | ||
|
|
||
| function isValidSignature(bytes32 hash, bytes calldata signature) public view override returns (bytes4) { | ||
| AccountStorage storage _storage = getAccountStorage(); | ||
|
|
||
| FunctionReference sigValidation = FunctionReference.wrap(bytes21(signature)); | ||
|
|
||
| (address plugin, uint8 functionId) = sigValidation.unpack(); | ||
| if (!_storage.signatureValidations.contains(toSetValue(sigValidation))) { | ||
| revert SignatureValidationInvalid(plugin, functionId); | ||
| } | ||
|
|
||
| if ( | ||
| IValidation(plugin).validateSignature(functionId, msg.sender, hash, signature[21:]) | ||
| == _1271_MAGIC_VALUE | ||
|
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. Since |
||
| ) { | ||
| return _1271_MAGIC_VALUE; | ||
| } | ||
| return _1271_INVALID; | ||
| } | ||
|
|
||
| /// @notice Gets the entry point for this account | ||
| /// @return entryPoint The entry point for this account | ||
| function entryPoint() public view override returns (IEntryPoint) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,6 @@ | |
| pragma solidity ^0.8.25; | ||
|
|
||
| import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | ||
| import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; | ||
| import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; | ||
| import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; | ||
| import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; | ||
|
|
@@ -39,7 +38,7 @@ import {ISingleOwnerPlugin} from "./ISingleOwnerPlugin.sol"; | |
| /// the account, violating storage access rules. This also means that the | ||
| /// owner of a modular account may not be another modular account if you want to | ||
| /// send user operations through a bundler. | ||
| contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin, IERC1271 { | ||
| contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { | ||
| using ECDSA for bytes32; | ||
| using MessageHashUtils for bytes32; | ||
|
|
||
|
|
@@ -52,6 +51,7 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin, IERC1271 { | |
|
|
||
| // bytes4(keccak256("isValidSignature(bytes32,bytes)")) | ||
| bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; | ||
| bytes4 internal constant _1271_INVALID = 0xffffffff; | ||
|
|
||
| mapping(address => address) internal _owners; | ||
|
|
||
|
|
@@ -112,18 +112,26 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin, IERC1271 { | |
| // ┃ Execution view functions ┃ | ||
| // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ | ||
|
|
||
| /// @inheritdoc IERC1271 | ||
| /// @inheritdoc IValidation | ||
| /// @dev The signature is valid if it is signed by the owner's private key | ||
| /// (if the owner is an EOA) or if it is a valid ERC-1271 signature from the | ||
| /// owner (if the owner is a contract). Note that unlike the signature | ||
| /// validation used in `validateUserOp`, this does///*not** wrap the digest in | ||
| /// an "Ethereum Signed Message" envelope before checking the signature in | ||
| /// the EOA-owner case. | ||
| function isValidSignature(bytes32 digest, bytes memory signature) external view override returns (bytes4) { | ||
| if (SignatureChecker.isValidSignatureNow(_owners[msg.sender], digest, signature)) { | ||
| return _1271_MAGIC_VALUE; | ||
| function validateSignature(uint8 functionId, address, bytes32 digest, bytes calldata signature) | ||
| external | ||
| view | ||
| override | ||
| returns (bytes4) | ||
| { | ||
| if (functionId == uint8(FunctionId.SIG_VALIDATION)) { | ||
| if (SignatureChecker.isValidSignatureNow(_owners[msg.sender], digest, signature)) { | ||
| return _1271_MAGIC_VALUE; | ||
| } | ||
| return _1271_INVALID; | ||
| } | ||
| return 0xffffffff; | ||
| revert NotImplemented(); | ||
| } | ||
|
|
||
| /// @inheritdoc ISingleOwnerPlugin | ||
|
|
@@ -144,17 +152,16 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin, IERC1271 { | |
| function pluginManifest() external pure override returns (PluginManifest memory) { | ||
| PluginManifest memory manifest; | ||
|
|
||
| manifest.executionFunctions = new bytes4[](3); | ||
| manifest.executionFunctions = new bytes4[](2); | ||
| manifest.executionFunctions[0] = this.transferOwnership.selector; | ||
| manifest.executionFunctions[1] = this.isValidSignature.selector; | ||
| manifest.executionFunctions[2] = this.owner.selector; | ||
| manifest.executionFunctions[1] = this.owner.selector; | ||
|
|
||
| ManifestFunction memory ownerValidationFunction = ManifestFunction({ | ||
| functionType: ManifestAssociatedFunctionType.SELF, | ||
| functionId: uint8(FunctionId.VALIDATION_OWNER_OR_SELF), | ||
| dependencyIndex: 0 // Unused. | ||
| }); | ||
| manifest.validationFunctions = new ManifestAssociatedFunction[](8); | ||
| manifest.validationFunctions = new ManifestAssociatedFunction[](7); | ||
| manifest.validationFunctions[0] = ManifestAssociatedFunction({ | ||
| executionSelector: this.transferOwnership.selector, | ||
| associatedFunction: ownerValidationFunction | ||
|
|
@@ -186,14 +193,13 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin, IERC1271 { | |
| dependencyIndex: 0 // Unused. | ||
| }); | ||
| manifest.validationFunctions[6] = ManifestAssociatedFunction({ | ||
| executionSelector: this.isValidSignature.selector, | ||
| associatedFunction: alwaysAllowRuntime | ||
| }); | ||
| manifest.validationFunctions[7] = ManifestAssociatedFunction({ | ||
| executionSelector: this.owner.selector, | ||
| associatedFunction: alwaysAllowRuntime | ||
| }); | ||
|
|
||
| manifest.signatureValidationFunctions = new uint8[](1); | ||
| manifest.signatureValidationFunctions[0] = uint8(FunctionId.SIG_VALIDATION); | ||
|
Comment on lines
+200
to
+201
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. If we're assuming we don't need functionId, perhaps we can check if
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. Yeah, really it should just be part of how validations are installed (a third option alongside user op and runtime validation), but that will have to be part of a later PR addressing user-supplied install configs: erc6900/resources#9 |
||
|
|
||
| return manifest; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious to understand the typical use cases for preHooks in signature validations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the simple ones are performing checks like limiting who can validate something - i.e. a session key that is allowed to sign
permitmessages, but only for USDC and only to a known spender.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, when the account gets a 1271 call, we only receive a
bytes32 hashof all the input parameters, we'll need to get the struct/type + rebuild it to be able to check against the struct parametersThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are 3 main dimensions of association to consider here - the validation plugin, the typehash, and the address of the app