Smart Account Security: Auditing Account Abstraction

horsefacts
Code4rena
Published in
6 min readSep 21, 2023

It’s been an exciting year for Ethereum account abstraction. Onchain, the critical EntryPoint contract that makes smart accounts possible is finally audited and deployed. Offchain, production-ready bundlers are running and relaying UserOperations on mainnet and beyond. Outside the Ethereum ecosystem, adoption of new tech like passkeys in browsers and mobile devices could combine with account abstraction to simplify key management or replace seed phrase signers altogether.

It’s easy to get hyped about the many features account abstraction promises to unlock: social recovery systems, sponsored gas, recurring payments, temporary limited privilege session keys, programmable spending limits, swappable signature algorithms, flexible multisig policies, cheap atomic batch operations! All these features and more could be coming to your wallet soon. Or at least, as soon as your wallet builds them.

What’s in your wallet?

The ERC-4337 spec provides a solid foundation in the form of infrastructure and abstractions for smart accounts. But most of the new capabilities it enables will be built at the application layer, in smart contract wallet implementations themselves.

In fact, ERC-4337 is surprisingly minimal at the wallet layer. It only requires accounts to implement a simple IAccount interface and follow a few rules:

interface IAccount {
function validateUserOp(
UserOperation
calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256 validationData);
}

Smart accounts must ensure calls to their validateUserOp function come from a trusted entrypoint address, pay for their gas, and validate the operation signature. Everything else is left to wallet implementations, including parsing and executing calls from the entrypoint.

This flexibility makes for a powerful and extensible standard, but it also means wallet implementations are on the hook for their own correctness, safety, and security. The reference SmartAccount.sol included in the ERC-4337 repo is a great example of a simple single-owner smart account, but it intentionally includes a limited set of features. Building the really interesting stuff requires more custom code, which leaves plenty of room for bugs and vulnerabilities. If you're building or auditing a smart account, here are a few common design and security concerns to keep in mind.

Account frontrunning

Smart accounts are lazily deployed to counterfactual addresses. This very cool feature of ERC-4337 means it’s possible to receive assets at a known, deterministic address now and deploy a wallet contract to manage them later. If a wallet doesn’t exist, the entrypoint creates it at the time of an account’s first UserOperation by calling a user-provided factory contract.

It’s important that account factory contracts are designed such that the wallets they deploy can only be controlled by the user who authorized their deployment. Often that means making sure all relevant deployment parameters (owner address, enabled modules, deploy time initialization code) force a change in the counterfactual address. But since wallet contracts are typically upgradeable proxies, different deploy time initializer arguments may not change the contract’s generated CREATE2 address, unless they’re explicitly passed as constructor args or included in the salt.

If a wallet needs to perform post-deploy initialization like setting an owner address or enabling modules, ensure that any changes to these parameters also change the generated address. And ensure adversaries can’t call the proxy factory directly and manipulate the configuration of wallets deployed on behalf of others.

The 4337 spec recommends ensuring that generated contract addresses depend on the user’s initial signature. Since users sign over all UserOperation parameters, including the deployment initcode for their wallet, this is a simple way to ensure a unique counterfactual address.

Cross-chain account frontrunning is a potential concern, too: if deployments are not fully deterministic and factories are stateful (for example, storing specific deployment parameters), it may be possible for an adversary to deploy the factory on a new chain, change parameters, and deploy backdoored or manipulated wallets.

An ideal factory is simple, unowned, stateless, and deterministic: anyone should be able to deploy it at the same address on any chain and call it to deploy to a deterministic address with verifiable deployment parameters.

UserOperation validation and signature replay

From the perspective of the 4337 spec, a wallet’s most important job is validating UserOperation signatures. To prevent replay across chains and entry point implementations, it's important that signatures include not just the UserOperation hash, but also the chain ID and entry point address. This is part of the spec, and the userOpHash provided by the entry point includes these values. Don't diverge from the spec: it's important to sign over all the parameters included in the hash.

Although a signature over the UserOperation hash is the most common way to validate an operation, it's also possible to define programmatic rules that define valid operations in terms of conditions like onchain state rather than a caller-provided signature. In these cases, it's important to ensure every parameter in the UserOperation is validated against a rule or known good value. Failing to do so could lead to gas attacks, spoofed operations, or worse.

Many smart wallets provide a secondary path to execute wallet operations, usually callable by the wallet owner or an authorized account. If these functions are authorized by signatures (for example, a multisig smart wallet), take care to ensure that these signatures aren’t vulnerable to replay either: they should always include a nonce and be robust to replay across multiple chain IDs.

Smart contract signatures

Smart accounts may support both generating EIP-1271 smart contract signatures and verifying EIP-1271 signatures from other contracts. These can be surprisingly tricky to get right. If a caller is able to control the signing contract associated with a 1271 signature, or the signing contract’s address is not used in an authorization check of some sort, double-check your implementation logic.

Unprotected implementations

Some things never change, and selfdestruct-able modules and proxy implementations are always a concern for any proxy-based smart contract wallet. If your smart account is able to delegatecall, be sure there's no way to take over and destroy implementations or modules.

Additionally, since smart accounts often make use of ECDSA signatures, be especially wary of the null address in the context of signatures: if signature recovery uses ecrecover directly, it may still be possible to use an invalid signature to spoof ownership and capture or destroy an implementation, even if it's uninitializable.

Although selfdestruct is on the way out on Ethereum mainnet, it will take time for other EVM chains to adopt the same semantics. Since smart accounts are multichain by design, this attack is probably not going away for a while.

Other considerations

  • Function selector conflicts: Smart contract wallets that want to implement fine-grained access control often use 4-byte function selectors to represent authorized function calls. Keep an eye out for the effects of conflicting function selectors.
  • Access control: Don’t overlook double-checking basic access control. Execution functions should be callable only by the entry point and specific authorized callers, according to the wallet’s security model.
  • Proxy upgrades: Since most smart accounts are upgradeable proxies, take care to follow good practices for developing, testing, and deploying new upgrades.

EIP-4337 is a huge step forward for programmable accounts, and Ethereum builders and everyday users are excited about the standard for good reason. But moving from EOAs to account abstraction means shifting security responsibility from individuals managing keys to smart contracts managing accounts. It’s possible for smart accounts to become a better, safer alternative to EOAs, but only if those of us responsible for building, testing, and auditing smart wallet code get the implementation right.

Thanks to horsefacts for this guest post. You can find him on Code4rena here, and on Twitter here.

To learn more about Code4rena, visit our website.

--

--