diff --git a/ERCS/erc-8043.md b/ERCS/erc-8043.md new file mode 100644 index 00000000000..1a5e5a6f316 --- /dev/null +++ b/ERCS/erc-8043.md @@ -0,0 +1,248 @@ +--- +eip: 8043 +title: Enable Mode for modules in ERC-7579 accounts +description: Install and use ERC-7579 modules within a single user operation +author: Ernesto Garcia (@ernestognw), Taek Lee (@leekt) +discussions-to: https://ethereum-magicians.org/t/erc-8043-enable-mode-for-module-installation-in-erc-7579-smart-accounts/25901 +status: Draft +type: Standards Track +category: ERC +created: 2025-10-02 +requires: 712, 1271, 4337, 7579 +--- + +## Abstract + +This ERC defines a standard mechanism for [ERC-7579] compliant smart accounts to install and immediately use modules within a single user operation. The standard specifies a magic nonce prefix, signature encoding format, and [EIP-712] domain structure to authorize _enable mode_ to install the module during the validation phase of the user operation while maintaining security through account owner authorization. + +[ERC-7579]: ./eip-7579.md +[EIP-712]: ./eip-712.md +[ERC-1271]: ./eip-1271.md +[ERC-4337]: ./eip-4337.md +[ERC-7579]: ./eip-7579.md + +## Motivation + +Current [ERC-7579] implementations require separate transactions for module installation and usage, which can hinder user experience. Use cases such as session key and spending limit modules would benefit from the ability to install and use modules in a single operation. This standard allows applications to opt in to install and use multiple modules under a single signature and user operation, enabling greater flexibility—particularly for scenarios where multiple modules need to work together atomically. + +This standard addresses several key issues: + +- **User Experience**: Reduces transaction count from N+1 to 1 for installing N modules and using them +- **Consistency across implementations**: Provides a standard pattern that works across different [ERC-7579] account implementations +- **Security**: Maintains strong authorization through [EIP-712] signatures from account owners + +Real-world usage in production systems like Kernel has validated the need and feasibility of this pattern, particularly for session key management and dynamic module installation. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +### Definitions + +- **Enable Mode**: The logic branch triggered during the validation phase allowing to install and use one or more [ERC-7579] modules within a single user operation +- **Installation Signature**: An [EIP-712] signature authorizing module installations +- **User Operation Signature**: The signature for validating the user operation itself + +### Magic Nonce Prefix + +Smart accounts implementing this standard MUST detect enable mode by checking for the magic prefix `0x01` in the most significant byte of the user operation nonce. + +```solidity +bytes1 constant ENABLE_MODE_PREFIX = 0x01; + +bytes1 result; +uint256 nonce = userOp.nonce; +assembly ("memory-safe") { + result := and(nonce, shl(248, not(0))) +} +if (result == ENABLE_MODE_PREFIX) { + // Enter enable mode +} +``` + +### Signature Encoding Format + +When the enable mode prefix is detected, the user operation signature MUST be encoded as: + +```solidity +abi.encode( + uint256[] moduleTypeIds, + address[] modules, + bytes[] initDatas, + bytes installationSignature, + bytes userOpSignature +) +``` + +Where: + +- `moduleTypeIds`: Array of [ERC-7579] module type identifiers +- `modules`: Array of module addresses to install +- `initDatas`: Array of initialization data to pass to each module's `onInstall` function +- `installationSignature`: [EIP-712] signature authorizing the module installations +- `userOpSignature`: Signature for validating the user operation + +### EIP-712 Message Structure + +The message structure MUST be: + +```solidity +bytes32 constant ENABLE_MODULES_TYPEHASH = keccak256( + "EnableModules(uint256[] moduleTypeIds,address[] modules,bytes[] initDatas,uint256 nonce)" +); +``` + +### Validation Flow + +Smart accounts implementing this standard on top of [ERC-4337]'s `validateUserOp` function: + +1. MUST detect the enable mode prefix (`0x01`) in user operation nonces +2. MUST decode the signature according to the specified format +3. MUST validate the EIP-712 `installationSignature` using `isValidSignature` +4. SHOULD install each module using the standard ERC-7579 `installModule` flow using the corresponding `moduleTypeIds[i]`, `modules[i]`, and `initDatas[i]` +5. MUST continue with normal user operation validation using the extracted `userOpSignature` +6. MUST return `SIG_VALIDATION_FAILED` (`1`) if signature decoding fails, enabling signature is invalid, or any module installation fails + +## Rationale + +### Magic Nonce Prefix Choice + +The prefix `0x01` was chosen because: + +- It's unlikely to conflict with existing nonce usage patterns +- It's easily detectable with bitwise operations +- It follows the pattern established by successful implementations like Kernel + +### Signature Structure Design + +The signature encoding separates concerns cleanly: + +- Module installation data is explicitly structured +- Installation authorization is separate from user operation validation +- The format is extensible for future enhancements + +### EIP-712 Integration + +Using EIP-712 for installation authorization provides: + +- Strong cryptographic guarantees +- Human-readable signature requests in wallets +- Standard domain separation +- Replay protection through nonce inclusion + +### Compatibility with ERC-7579 + +This standard extends ERC-7579 without breaking existing functionality: + +- Normal user operations continue to work unchanged +- Standard module installation flows remain available +- All ERC-7579 security guarantees are preserved + +## Backwards Compatibility + +This standard is fully backwards compatible with existing ERC-7579 implementations: + +- Accounts not implementing this standard ignore the magic nonce prefix +- Standard module installation methods remain unchanged +- Existing modules work without modification +- User operations without the magic prefix function normally + +## Reference Implementation + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {AccountERC7579} from "@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol"; +import {PackedUserOperation} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +import {ERC4337Utils} from "@openzeppelin/contracts/account/utils/draft-ERC4337Utils.sol"; +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; + +abstract contract EnableModeAccount is AccountERC7579, ... { + bytes1 private constant ENABLE_MODE_PREFIX = 0x01; + bytes32 private constant ENABLE_MODULES_TYPEHASH = + keccak256( + "EnableModules(uint256[] moduleTypeIds,address[] modules,bytes[] initDatas,uint256 nonce)" + ); + + function _validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash + ) internal virtual override returns (uint256) { + bytes1 result; + uint256 nonce = userOp.nonce; + assembly ("memory-safe") { + result := and(nonce, shl(248, not(0))) + } + if (result == ENABLE_MODE_PREFIX) { + return _validateEnableMode(userOp, userOpHash); + } + return super._validateUserOp(userOp, userOpHash); + } + + function _validateEnableMode( + PackedUserOperation calldata userOp, + bytes32 userOpHash + ) internal returns (uint256) { + ( + bool success, + uint256[] calldata moduleTypeIds, + address[] calldata modules, + bytes[] calldata initDatas, + bytes calldata installationSignature, + bytes calldata userOpSignature + ) = _tryDecodeEnableMode(userOp.signature); + + if (!success) { + return ERC4337Utils.SIG_VALIDATION_FAILED; + } + + bytes32 structHash = _installModulesStructHash( + moduleTypeIds, + modules, + initDatas, + userOp.nonce + ); + + if ( + isValidSignature(_hashTypedDataV4(structHash), installationSignature) == + IERC1271.isValidSignature.selector + ) { + // Install all modules + for (uint256 i = 0; i < modules.length; i++) { + _installModule(moduleTypeIds[i], modules[i], initDatas[i]); + } + + // Create modified userOp with extracted signature + PackedUserOperation memory modifiedUserOp = PackedUserOperation({ + sender: userOp.sender, + nonce: userOp.nonce, + initCode: userOp.initCode, + callData: userOp.callData, + accountGasLimits: userOp.accountGasLimits, + preVerificationGas: userOp.preVerificationGas, + gasFees: userOp.gasFees, + paymasterAndData: userOp.paymasterAndData, + signature: userOpSignature + }); + + return super._validateUserOp(modifiedUserOp, userOpHash); + } + + return ERC4337Utils.SIG_VALIDATION_FAILED; + } + + // Additional implementation methods... +} +``` + +## Security Considerations + +- **Authorization**: Only _account owners_ can authorize module installations through EIP-712 signatures +- **Replay Protection**: Nonce inclusion in EIP-712 message prevents replay attacks +- **Module Validation**: Standard ERC-7579 module validation applies (type checking, duplicate prevention) +- **Array Length Validation**: Implementations MUST ensure that `moduleTypeIds`, `modules`, and `initDatas` arrays have the same length to prevent mismatched installations + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md).