Platform: Code4rena
Start Date: 14/03/2024
Pot Size: $49,000 USDC
Total HM: 3
Participants: 51
Period: 7 days
Judge: 3docSec
Id: 350
League: ETH
Rank: 39/51
Findings: 1
Award: $21.28
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: roguereggiant
Also found by: 0xbrett8571, 0xepley, Circolors, JCK, JcFichtner, LinKenji, MSK, Myd, SAQ, SBSecurity, albahaca, cheatc0d3, clara, emerald7017, fouzantanveer, foxb868, hunter_w3b, kaveyjoe, popeye, unique
21.2754 USDC - $21.28
The Smart Wallet is an ERC-4337 compatible smart contract wallet that supports multiple owners and signature schemes, including Ethereum addresses and WebAuthn public keys. It is designed to be upgradeable using the UUPS proxy pattern. It incorporates multiple signature schemes, including support for Ethereum addresses and WebAuthn public keys, enabling users to manage their assets with increased versatility and protection against unauthorized access.
Scope
My analysis covers the following contracts and libraries:
MultiOwnable
: Handles multiple owner management and access control.ERC1271
: Provides ERC-1271 signature validation with replay protection.CoinbaseSmartWalletFactory
: Factory contract for deploying new Smart Wallet instances.CoinbaseSmartWallet
: The main Smart Wallet contract, inheriting from MultiOwnable
, UUPSUpgradeable
, Receiver
, and ERC1271
.The review also considers the Smart Wallet's interactions with the ERC-4337 EntryPoint contract and its compliance with the ERC-4337 standard.
Methodology
The security analysis was conducted through a combination of manual code review, automated tools, and scenario-based testing. The following approach was taken:
Architecture Review: Analyzed the overall design, contract interactions, and inheritance hierarchy to identify potential weaknesses or attack vectors.
Code Quality Review: Examined the codebase for coding best practices, readability, and maintainability. Checked for any deviations from established standards or the presence of anti-patterns.
Access Control and Privilege Analysis: Reviewed the access control mechanisms, including modifiers and function visibility, to ensure proper restrictions and privilege separation.
Scenario-based Testing: Developed and executed test cases to simulate various attack scenarios, such as unauthorized fund movements, account bricking, and cross-chain replay attacks.
External Interactions: Analyzed the Smart Wallet's interactions with external contracts, specifically the ERC-4337 EntryPoint, to identify any potential risks or vulnerabilities.
Findings
Architecture Review
The Smart Wallet follows a modular architecture, separating concerns into different contracts and libraries. The use of the UUPS proxy pattern allows for upgradability, enabling future enhancements and bug fixes.
However, the upgradability mechanism itself poses a potential risk. If the contract owner's privileges are compromised or abused, a malicious implementation could be deployed, leading to unintended consequences. Proper access control and governance measures should be in place to mitigate this risk.
Multi-Owner Access Control: The Smart Wallet allows for multiple owners, each identified by their Ethereum address or WebAuthn public key. This multi-owner structure enhances security by requiring multiple approvals for critical actions, such as adding or removing owners and upgrading the contract.
ERC-4337 Compatibility: The Smart Wallet adheres to the ERC-4337 standard, enabling seamless integration with the Ethereum ecosystem and interoperability with other ERC-4337 compliant contracts. It supports the execution of user operations through the ERC-4337 EntryPoint contract.
Upgradability: The Smart Wallet utilizes the UUPS (Universal Upgradeable Proxy Standard) proxy pattern, allowing for contract upgrades without changing the contract address. This feature enables the addition of new functionalities, bug fixes, and improvements over time.
Signature Validation: The Smart Wallet incorporates robust signature validation mechanisms, including support for ERC-1271 signatures and WebAuthn authentication assertions. It ensures that only authorized owners can perform actions on the wallet.
Cross-Chain Replay Protection: The Smart Wallet implements measures to prevent cross-chain replay attacks. It includes a canSkipChainIdValidation
function that whitelists specific function selectors that can be executed without chain ID validation, mitigating the risk of unauthorized replays across different networks.
Risks
Owner Privilege Abuse
Implement multi-signature requirements for critical actions, such as adding or removing owners and upgrading the contract.
Establish a robust governance model that defines clear procedures for owner actions and requires consensus among owners.
Regularly monitor owner activity and implement alerts for suspicious behavior.
function addOwnerAddress(address owner) public virtual onlyOwner { _addOwner(abi.encode(owner)); }
Signature Replay Attacks
Ensure that the signature validation logic correctly verifies the uniqueness and validity of signatures.
Implement replay protection mechanisms, such as including nonces or timestamps in the signed messages.
Regularly audit and test the signature validation code to identify and fix any vulnerabilities.
function _validateSignature(bytes32 message, bytes calldata signature) internal view virtual override returns (bool) { SignatureWrapper memory sigWrapper = abi.decode(signature, (SignatureWrapper)); bytes memory ownerBytes = ownerAtIndex(sigWrapper.ownerIndex); // ... return SignatureCheckerLib.isValidSignatureNow(owner, message, sigWrapper.signatureData); }
Usage of REPLAYABLE_NONCE_KEY
in the SmartWallet contract to ensure that nonces are properly checked to prevent replay attacks while allowing intentional cross-chain replays for permitted functions.
The nonce mechanism is implemented in the validateUserOp
function:
function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) public payable virtual onlyEntryPoint payPrefund(missingAccountFunds) returns (uint256 validationData) { uint256 key = userOp.nonce >> 64; // 0xbf6ba1fc = bytes4(keccak256("executeWithoutChainIdValidation(bytes)")) if (userOp.callData.length >= 4 && bytes4(userOp.callData[0:4]) == 0xbf6ba1fc) { userOpHash = getUserOpHashWithoutChainId(userOp); if (key != REPLAYABLE_NONCE_KEY) { revert InvalidNonceKey(key); } } else { if (key == REPLAYABLE_NONCE_KEY) { revert InvalidNonceKey(key); } } // ... rest of the function ... }
The validateUserOp
function extracts the key
from the upper 192 bits of the userOp.nonce
field. The key
is used to determine whether the user operation is intended for cross-chain replay or not.
Cross-Chain Replayable User Operations:
userOp.callData
starts with the function selector 0xbf6ba1fc
(which corresponds to the executeWithoutChainIdValidation
function), it means the user operation is intended for cross-chain replay.key
is equal to the REPLAYABLE_NONCE_KEY
. If the key
doesn't match, it reverts with an InvalidNonceKey
error.userOpHash
is then computed using the getUserOpHashWithoutChainId
function, which excludes the chain ID from the hash calculation.Non-Replayable User Operations:
userOp.callData
doesn't start with the executeWithoutChainIdValidation
function selector, it means the user operation is not intended for cross-chain replay.key
is not equal to the REPLAYABLE_NONCE_KEY
. If the key
matches the REPLAYABLE_NONCE_KEY
, it reverts with an InvalidNonceKey
error.userOpHash
is computed normally, including the chain ID in the hash calculation.The purpose of this nonce mechanism is to differentiate between user operations that are intended for cross-chain replay and those that are not. The REPLAYABLE_NONCE_KEY
is used as a special value to identify cross-chain replayable user operations.
However, there are a few considerations and potential issues with this mechanism:
Nonce Uniqueness:
Nonce Ordering:
Nonce Reuse for Cross-Chain Replays:
To enhance the nonce mechanism and address these considerations, the following improvements can be made:
Nonce Uniqueness:
Nonce Ordering:
Cross-Chain Replay Safety:
Upgradeability Risks
Example code snippet:
function _authorizeUpgrade(address) internal view virtual override(UUPSUpgradeable) onlyOwner {}
Dependency Risks
Regularly monitor and update the dependencies to ensure they are using the latest secure versions.
Conduct thorough security audits of the external libraries and contracts used by the Smart Wallet.
Consider implementing fallback mechanisms or circuit breakers to mitigate the impact of potential issues with dependencies.
function entryPoint() public view virtual returns (address) { return 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789; }
Code Quality Review
The codebase demonstrates good coding practices, with clear naming conventions, modular design, and appropriate use of libraries and interfaces. The contracts are well-documented, providing clear explanations of their purpose and functionality.
One area of improvement could be the use of more descriptive error messages in certain functions. For example, in the MultiOwnable
contract, the InvalidOwnerBytesLength
error could provide more context about the expected length of owner bytes.
Access Control and Privilege Analysis
The Smart Wallet implements a multi-owner access control system through the MultiOwnable
contract. The onlyOwner
modifier restricts access to critical functions, such as adding or removing owners and upgrading the contract.
However, it's important to note that any current owner has the ability to add or remove other owners, including themselves. This poses a risk of account bricking if all owners are removed. Proper governance mechanisms and checks should be in place to prevent unintended owner removal.
The executeWithoutChainIdValidation
function is protected by the onlyEntryPoint
modifier, ensuring that only the ERC-4337 EntryPoint contract can call it. The canSkipChainIdValidation
function provides a whitelist of function selectors that can be executed without chain ID validation. It's crucial to keep this whitelist up to date and carefully review any new functions added in future upgrades to prevent potential cross-chain replay attacks.
These functions allowed to be called via executeWithoutChainIdValidation
have the necessary access control checks and validations to prevent unintended cross-chain replay.
The functions currently allowed to skip chain ID validation, as defined in the canSkipChainIdValidation
function, are:
MultiOwnable.addOwnerPublicKey
MultiOwnable.addOwnerAddress
MultiOwnable.removeOwnerAtIndex
UUPSUpgradeable.upgradeToAndCall
I examine each function individually:
MultiOwnable.addOwnerPublicKey
:function addOwnerPublicKey(bytes32 x, bytes32 y) public virtual onlyOwner { _addOwner(abi.encode(x, y)); }
Access control: This function uses the onlyOwner
modifier, which ensures that only an existing owner of the SmartWallet contract can call it.
Validation: The function takes the x
and y
coordinates of the WebAuthn public key as input. However, there is no explicit validation to ensure that the provided public key is valid or unique.
MultiOwnable.addOwnerAddress
:function addOwnerAddress(address owner) public virtual onlyOwner { _addOwner(abi.encode(owner)); }
Access control: This function also uses the onlyOwner
modifier, restricting access to only existing owners of the SmartWallet contract.
Validation: The function takes an owner
address as input. However, there is no explicit validation to ensure that the provided address is a valid Ethereum address or that it is not already an owner.
MultiOwnable.removeOwnerAtIndex
:function removeOwnerAtIndex(uint256 index) public virtual onlyOwner { bytes memory owner = ownerAtIndex(index); if (owner.length == 0) revert NoOwnerAtIndex(index); delete _getMultiOwnableStorage().isOwner[owner]; delete _getMultiOwnableStorage().ownerAtIndex[index]; emit RemoveOwner(index, owner); }
Access control: This function uses the onlyOwner
modifier, ensuring that only an existing owner can remove other owners.
Validation: The function verifies that an owner exists at the provided index
by checking the length of the owner
bytes. If no owner exists at the given index, it reverts with a NoOwnerAtIndex
error.
UUPSUpgradeable.upgradeToAndCall
:function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallUUPS(newImplementation, data, true); }
Access control: This function is inherited from the UUPSUpgradeable contract and uses the onlyProxy
modifier. In the SmartWallet contract, the _authorizeUpgrade
function is overridden to use the onlyOwner
modifier, ensuring that only owners can perform upgrades.
Validation: The _authorizeUpgrade
function is responsible for validating the newImplementation
address. However, the current implementation in the SmartWallet contract does not perform any explicit validation on the newImplementation
address.
Potential Vulnerabilities and Recommendations:
addOwnerPublicKey
and addOwnerAddress
functions do not perform sufficient validation on the input parameters. They should include checks to ensure the validity and uniqueness of the provided public key coordinates and Ethereum address.upgradeToAndCall
function does not validate the newImplementation
address. It should include checks to ensure that the new implementation address is a valid and trusted contract.onlyOwner
modifier to restrict access to existing owners, there are no additional checks to prevent malicious owners from exploiting these functions.Recommendations:
addOwnerPublicKey
, addOwnerAddress
, and upgradeToAndCall
functions.Scenario-based Testing
The Smart Wallet was subjected to various attack scenarios to assess its resilience. The tests covered unauthorized fund movements, account bricking, and cross-chain replay attacks.
The onlyEntryPoint
and onlyEntryPointOrOwner
modifiers effectively protect the execute
, executeBatch
, and executeWithoutChainIdValidation
functions, ensuring that only the EntryPoint or authorized owners can initiate transactions that move funds.
The validateUserOp
function correctly validates the supplied signature, preventing unauthorized parties from moving funds. The _validateSignature
internal function handles both EOA and public key-based signatures securely.
Fuzz testing did not uncover any unexpected reverts or storage changes, indicating the contract's robustness against edge cases and unexpected input.
External Interactions
The Smart Wallet's interactions with the ERC-4337 EntryPoint conform to the standard. The EntryPoint cannot modify the account's state beyond relaying valid user operations.
The gas usage and refund mechanism in validateUserOp
aligns with the ERC-4337 specifications, and no denial-of-service vectors were identified.
Centralization Risks (Severity: High)
Systematic Risks (Severity: Medium)
Mechanism Risks (Severity: Medium)
canSkipChainIdValidation
function is not regularly updated, it could leave the Smart Wallet vulnerable to replay attacks across different networks.Architecture
+------------------+ +------------------+ +------------------+ | | | | | | | MultiOwnable | | ERC1271 | | UUPSUpgradeable | | | | | | | +------------------+ +------------------+ +------------------+ ^ ^ ^ | | | | | | +------------------+ +------------------+ +------------------+ | | | | | | | Receiver | | EntryPoint | | WebAuthnLib | | | | (ERC-4337) | | | +------------------+ +------------------+ +------------------+ ^ ^ ^ | | | | | | +----------------------------+----------------------------+ | v +------------------+ | | | CoinbaseSmartWallet | | | +------------------+
Admin
+-------------+ +----------------+ +----------------+ | | | | | | | Contract | | Admin | | EntryPoint | | Owner | | (EOA) | | (ERC-4337) | | | | | | | +------+------+ +-------+--------+ +----------------+ | | | 1. Initiate Action | | (e.g., addOwner, | | removeOwner, | | upgrade) | | | +------------------------->| | | 2. Call Corresponding Function | (e.g., addOwnerAddress, | removeOwnerAtIndex, | upgradeToAndCall) | +--------------------------------+ | | | | | | | | | v +----------------+ | | | Smart Wallet | | | +----------------+
User
+--------------+ +----------------+ +----------------+ | | | | | | | User | | EntryPoint | | Smart Wallet | | (EOA) | | (ERC-4337) | | | | | | | | | +------+-------+ +-------+--------+ +----------------+ | | | 1. Create User Operation | | (e.g., execute, | | executeBatch) | | | +-------------------------->| | | 2. Validate User Operation | (validateUserOp) | +--------------------------------+ | | | 3. Execute User Operation | (execute, executeBatch) | v +----------------+ | | | Smart Wallet | | | +----------------+
Core
+------------------+ +------------------+ +------------------+ | | | | | | | MultiOwnable | | ERC1271 | | UUPSUpgradeable | | | | | | | +------------------+ +------------------+ +------------------+ ^ ^ ^ | | | | | | | Inherit | Inherit | Inherit | | | | | | +------------------+ +------------------+ +------------------+ | | | | | | | Receiver | | EntryPoint | | WebAuthnLib | | | | (ERC-4337) | | | +------------------+ +------------------+ +------------------+ ^ ^ ^ | | | | Inherit | Interact | Use | | | | | | +----------------------------+----------------------------+ | | Inherit | v +------------------+ | | | CoinbaseSmartWallet | | | +------------------+
Contract Analysis
Centralization Risks
Systematic Risks
Mechanism Review
canSkipChainIdValidation
function. If this mechanism is not effectively maintained, it could leave the Smart Wallet vulnerable to replay attacks across different networks.+------------------+ +------------------+ +------------------+ | | | | | | | MultiOwnable | | ERC1271 | | UUPSUpgradeable | | | | | | | | - Owner Mgmt | | - Signature | | - Upgradeability| | - Access Control| | Validation | | - Impl. Change | | | | | | | +------------------+ +------------------+ +------------------+ ^ ^ ^ | | | | | | | Inherit | Inherit | Inherit | | | | | | +------------------+ +------------------+ +------------------+ | | | | | | | Receiver | | EntryPoint | | WebAuthnLib | | | | (ERC-4337) | | | | - Receive ETH | | - UserOp Exec | | - WebAuthn Sig | | | | - Validation | | Verification | | | | - Single PoF | | | +------------------+ +------------------+ +------------------+ ^ ^ ^ | | | | Inherit | Interact | Use | | | | | | +----------------------------+----------------------------+ | | Inherit | v +------------------+ | | | CoinbaseSmartWallet | | | | - Owner Mgmt | | - UserOp Exec | | - Upgradeability| | - Sig Validation| | - Replay Protect| +------------------+
The Smart Wallet architecture and design introduce several centralization, systematic, and mechanism risks that need to be carefully considered and mitigated. Proper risk management, regular audits, and adherence to best practices in smart contract development and security are essential to ensure the integrity and security of the Smart Wallet.
Access Control Checks: a. Owner Management Functions:
addOwnerAddress
, addOwnerPublicKey
, and removeOwnerAtIndex
functions in the MultiOwnable
contract are protected by the onlyOwner
modifier, ensuring that only existing owners can add or remove owners.removeOwnerAtIndex
function, as it allows removing an owner without checking if it would leave the contract without any owners. This could lead to a situation where the contract becomes inaccessible if all owners are removed.b. Contract Upgrade Functions:
upgradeToAndCall
function in the UUPSUpgradeable
contract is protected by the onlyProxy
modifier, which is overridden in the SmartWallet
contract to use the onlyOwner
modifier.newImplementation
address to verify that it is a valid and trusted contract.c. Fund Movement Functions:
execute
, executeBatch
, and executeWithoutChainIdValidation
functions are protected by the onlyEntryPointOrOwner
and onlyEntryPoint
modifiers, respectively.External Contracts and Libraries:
a. Solady's Ownable
:
Ownable
contract from the Solady library is used to manage ownership in the SmartWallet
contract.Ownable
contract has been widely used and audited.Ownable
contract appears to be correct, with the onlyOwner
modifier being used appropriately.b. Solady's UUPSUpgradeable
:
UUPSUpgradeable
contract from the Solady library is used to enable upgradability in the SmartWallet
contract.UUPSUpgradeable
contract appears to be correct, with the _authorizeUpgrade
function being overridden to use the onlyOwner
modifier.c. WebAuthnSol:
WebAuthnSol
contract is used for verifying WebAuthn authentication assertions in the SmartWallet
contract.WebAuthnSol
contract has been thoroughly audited and is secure.WebAuthnSol
contract appears to be correct, with the verify
function being used appropriately in the _validateSignature
function.d. ERC1271:
ERC1271
contract is used for signature validation in the SmartWallet
contract.ERC1271
standard is widely used and has undergone security audits.ERC1271
contract appears to be correct, with the isValidSignature
function being implemented properly.Self-Destruct and Unusable State:
SmartWallet
contract does not contain any selfdestruct
or suicide
functions, which means it cannot be directly self-destructed.removeOwnerAtIndex
function, as mentioned earlier. If all owners are removed, the contract could become inaccessible and effectively unusable.removeOwnerAtIndex
function to ensure that at least one owner remains after the removal.Recommendations:
a. Implement a check in the removeOwnerAtIndex
function to prevent removing the last owner and leaving the contract inaccessible.
b. Consider adding additional checks in the upgradeToAndCall
function to verify that the newImplementation
address is a valid and trusted contract.
c. Thoroughly review and audit the WebAuthnSol
contract to ensure its security and correctness.
d. Regularly monitor and update the external contracts and libraries used in the SmartWallet
contract to ensure they remain secure and up to date.
Recommendations
Implement additional safeguards and checks to prevent unintended owner removal. For example, consider requiring multiple owner approvals for critical actions or enforcing a minimum number of owners.
Establish a robust governance process for contract upgrades. Ensure that any proposed upgrades are thoroughly reviewed, tested, and approved by the relevant stakeholders before deployment.
Regularly review and update the canSkipChainIdValidation
whitelist to account for any new functions added through upgrades. Ensure that only intended functions are allowed to skip chain ID validation.
Conduct periodic security audits and penetration testing to identify and address any emerging vulnerabilities or attack vectors.
Provide clear documentation and guidelines for users on securely managing their Smart Wallet accounts, including best practices for key management and owner responsibilities.
Conclusion
The Smart Wallet codebase demonstrates a well-designed and secure implementation of an ERC-4337 compatible smart contract wallet. The multi-owner access control system and the use of the UUPS proxy pattern provide flexibility and upgradability.
However, the upgradability mechanism and the potential for owner abuse pose certain risks that should be mitigated through proper governance and safeguards. Regular security audits and ongoing monitoring are essential to ensure the continued security and integrity of the Smart Wallet.
Overall, the Smart Wallet codebase provides a solid foundation for secure and flexible smart contract wallet functionality, with room for continuous improvement and enhancement.
29 hours
#0 - c4-pre-sort
2024-03-22T21:17:52Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2024-03-27T10:37:41Z
3docSec marked the issue as grade-c
#2 - c4-judge
2024-03-27T10:38:41Z
3docSec marked the issue as grade-b