Coinbase Smart Wallet - hunter_w3b's results

Smart Wallet from Coinbase Wallet

General Information

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

Coinbase

Findings Distribution

Researcher Performance

Rank: 31/51

Findings: 2

Award: $34.97

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: K42

Also found by: 0x11singh99, 0xAnah, Hajime, SAQ, SM3_SS, albahaca, clara, dharma09, hunter_w3b, naman1778, shamsulhaq123, slvDev

Labels

bug
G (Gas Optimization)
grade-b
sufficient quality report
G-03

Awards

13.6948 USDC - $13.69

External Links

Gas-Optimization

[G-01] Use immutable for the entryPoint address

Since the entryPoint address is constant and set at construction, use the immutable keyword to save gas

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWallet.sol#L217-L219

    function entryPoint() public view virtual returns (address) {
        return 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789;
    }

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L304-L306

    function entryPoint() public pure returns (address) {
        return 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789;
    }
+  address immutable ENTRY_POINT = 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789;

+  function entryPoint() public view returns (address) {
+      return ENTRY_POINT;
}

[G-02] Use unchecked arithmetic can't overflow because of prevois if statment

Use the unchecked keyword to disable overflow and underflow checks for arithmetic operations that cannot underflow or overflow.

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L133-L138

        if (address(this).balance < withdrawAmount) {
            revert InsufficientBalance(withdrawAmount, address(this).balance);
        }

        // NOTE: Do not include the gas part in withdrawable funds as it will be handled in `postOp()`.
        _withdrawableETH[userOp.sender] += withdrawAmount - maxCost;

[G-03] Optimizing MultiOwnable.sol::isOwner Mapping

Instead of storing the owner's raw bytes as keys in the isOwner mapping, which can lead to high storage costs and gas usage, we can store a hash of the owner's bytes and use that as the key.

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/MultiOwnable.sol#L24

Replace:

    mapping(bytes account => bool isOwner_) isOwner;

With:

mapping(bytes32 => bool) isOwnerHash;

And update functions accordingly to use hashes.

[G-04] We can Avoid using libraries for simple functions

Avoid using libraries for simple functions that can be implemented in the contract. This way, you can avoid the extra SLOAD and SSTORE operations required to store the library address and call the library functions.

For example, instead of using the SafeTransferLib library for simple ETH transfers, you can implement the transfer function directly in the contract.

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L334

161            SafeTransferLib.forceSafeTransferETH(account, withdrawable, SafeTransferLib.GAS_STIPEND_NO_STORAGE_WRITES);

212    function entryPointDeposit(uint256 amount) external payable onlyOwner {
213        SafeTransferLib.safeTransferETH(entryPoint(), amount);
214    }


334    function _withdraw(address asset, address to, uint256 amount) internal {
335        if (asset == address(0)) {
336            SafeTransferLib.safeTransferETH(to, amount);
337        } else {
338            SafeTransferLib.safeTransfer(asset, to, amount);
339        }

[G-05] We can Reducing SLOAD Operations in MultiOwnable::removeOwnerAtIndex function

Update functions to avoid redundant storage reads. For example, cache commonly used values locally to avoid multiple SLOAD operations.

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/MultiOwnable.sol#L102

Before:

function removeOwnerAtIndex(uint256 index) public virtual onlyOwner {
    bytes memory owner = ownerAtIndex(index);
    // Use owner...
}

After:

function removeOwnerAtIndex(uint256 index) public virtual onlyOwner {
    bytes storage owner = _getMultiOwnableStorage().ownerAtIndex[index];
    // Use owner...
}

[G-06] storage layout can be optimized MultiOwnable::MultiOwnableStorage struct

The storage layout can be optimized by rearranging the order of struct members to reduce SLOAD operations.

Instead of:

struct MultiOwnableStorage {
    uint256 nextOwnerIndex;
    mapping(uint256 index => bytes owner) ownerAtIndex;
    mapping(bytes account => bool) isOwner_;
}

Consider:

struct MultiOwnableStorage {
    mapping(uint256 => bytes) ownerAtIndex;
    mapping(bytes32 => bool) isOwnerHash;
    uint256 nextOwnerIndex;
}

This layout minimizes the number of SLOADs required to access storage variables.

[G‑07] INTERNAL functions only called once can be inlined to save Gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

File: src/SmartWallet/MultiOwnable.sol

201    function _checkOwner() internal view virtual {

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/MultiOwnable.sol#L201

File: src/SmartWallet/ERC1271.sol

121    function _eip712Hash(bytes32 hash) internal view virtual returns (bytes32) {

133    function _hashStruct(bytes32 hash) internal view virtual returns (bytes32) {

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/ERC1271.sol#L121

File: src/SmartWallet/CoinbaseSmartWallet.sol

291    function _validateSignature(bytes32 message, bytes calldata signature)

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWallet.sol#L291

[G-08] A MODIFIER used only once and not being inherited should be inlined to save gas

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWallet.sol#-L100

    modifier payPrefund(uint256 missingAccountFunds) virtual {
        _;

        assembly ("memory-safe") {
            if missingAccountFunds {
                // Ignore failure (it's EntryPoint's job to verify, not the account's).
                pop(call(gas(), caller(), missingAccountFunds, codesize(), 0x00, codesize(), 0x00))
            }
        }
    }

This modifier is only used in this function it should inilned to save extra JUMP instructions and additional stack operations

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWallet.sol#L142

    function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds)
        public
        payable
        virtual
        onlyEntryPoint
        payPrefund(missingAccountFunds)
        returns (uint256 validationData)
    {

[G-09] Expressions for constant values such as a call to  KECCAK256(), should use IMMUTABLE rather than CONSTANT

File: src/SmartWallet/ERC1271.sol

23    bytes32 private constant _MESSAGE_TYPEHASH = keccak256("CoinbaseSmartWalletMessage(bytes32 hash)");

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/ERC1271.sol#L23

File: src/WebAuthnSol/WebAuthn.sol

55    bytes32 private constant EXPECTED_TYPE_HASH = keccak256('"type":"webauthn.get"');

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/WebAuthnSol/WebAuthn.sol#L55

[G-10] Assigning STATE VARIABLES directly with named struct constructors wastes gas

Using named arguments for struct means that the compiler needs to organize the fields in memory before doing the assignment, which wastes gas. Set each field directly in storage (use dot-notation), or use the unnamed version of the constructor.

Leverage mapping and dot notation for struct assignment

In dot notation, values are directly written to storage variable, When we use the current method in the code the compiler will allocate some memory to store the struct instance first before writing it to storage.

File: src/SmartWallet/ERC1271.sol

70        if (_validateSignature({message: replaySafeHash(hash), signature: signature})) {

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/ERC1271.sol#L70

File: src/SmartWallet/CoinbaseSmartWallet.sol

321            return WebAuthn.verify({challenge: abi.encode(message), requireUV: false, webAuthnAuth: auth, x: x, y: y});

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWallet.sol#L321

[G-11] Access MAPPINGS directly rather than using accessor functions

When you have a mapping, accessing its values through accessor functions involves an additional layer of indirection, which can incur some gas cost. This is because accessing a value from a mapping typically involves two steps: first, locating the key in the mapping, and second, retrieving the corresponding value.

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L299-L301

    function nonceUsed(address account, uint256 nonce) external view returns (bool) {
        return _nonceUsed[nonce][account];
    }

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/MultiOwnable.sol#L117-L148

    function isOwnerAddress(address account) public view virtual returns (bool) {
        return _getMultiOwnableStorage().isOwner[abi.encode(account)];
    }

    function isOwnerPublicKey(bytes32 x, bytes32 y) public view virtual returns (bool) {
        return _getMultiOwnableStorage().isOwner[abi.encode(x, y)];
    }

    function isOwnerBytes(bytes memory account) public view virtual returns (bool) {
        return _getMultiOwnableStorage().isOwner[account];
    }

    function ownerAtIndex(uint256 index) public view virtual returns (bytes memory) {
        return _getMultiOwnableStorage().ownerAtIndex[index];
    }

[G-12] Shorten the array rather than copying to a new one

ASSEMBLY can be used to shorten the array by changing the length slot, so that the entries don't have to be copied to a new, shorter array

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWallet.sol#L104

        bytes[] memory owners = new bytes[](1);

[G-13] Expensive operation inside a for loop

The loop is very expensive in every iteration has external call and send value.

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWallet.sol#L205-L212

    function executeBatch(Call[] calldata calls) public payable virtual onlyEntryPointOrOwner {
        for (uint256 i; i < calls.length;) {
            _call(calls[i].target, calls[i].value, calls[i].data);
            unchecked {
                ++i;
            }
        }
    }

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWallet.sol#L272-L279

    function _call(address target, uint256 value, bytes memory data) internal {
        (bool success, bytes memory result) = target.call{value: value}(data);
        if (!success) {
            assembly ("memory-safe") {
                revert(add(result, 32), mload(result))
            }
        }
    }

#0 - raymondfam

2024-03-22T21:31:18Z

13 generic G.

#1 - c4-pre-sort

2024-03-22T21:31:22Z

raymondfam marked the issue as sufficient quality report

#2 - c4-judge

2024-03-27T13:18:06Z

3docSec marked the issue as grade-b

Awards

21.2754 USDC - $21.28

Labels

analysis-advanced
grade-b
sufficient quality report
A-12

External Links

Analysis - Smart Wallet from Coinbase Wallet Contest

Smart_Wallet Protocol

Description overview of Smart Wallet Contest

A smart wallet is a type of cryptocurrency wallet that supports advanced features such as passkey ownership, multi-owner support, and signature-based withdrawals. It is a smart contract wallet that is compliant with the ERC-4337 standard and can be used with paymasters such as MagicSpend. The WebAuthnSol library is used to verify WebAuthn Authentication Assertions on-chain, while the FreshCryptoLib library is used to check for signature malleability. The MultiOwnable contract allows for multiple owners to control a smart wallet, while the ERC1271 contract is used to validate signatures via WebAuthnSol or FreshCryptoLib. The MagicSpend contract allows for signature-based withdrawals and can also be used to pay transaction gas.

  • Functionality:

    • Supports multiple owners and validates their signatures via WebAuthnSol, a library for verifying WebAuthn Authentication Assertions on-chain
    • ERC-4337 compliant, which is a new standard for account abstraction that aims to improve the user experience of interacting with smart contracts
    • Can be used with paymasters such as MagicSpend, which is a contract that allows for signature-based withdrawals and also allows using withdrawals to pay transaction gas
  • Key Features:

    • Multi-owner support: allows for multiple owners to control the smart wallet, improving security and reducing the risk of loss due to a single point of failure
    • Signature validation: uses WebAuthnSol to validate signatures, providing an additional layer of security and allowing for more flexible authentication methods
    • Replay protection: allows for signing account-changing user operations such that they can be replayed across any EVM chain where the account has the same address, improving usability and reducing the risk of errors
    • ERC-4337 compliance: adheres to the new account abstraction standard, improving interoperability with other contracts and providing a better user experience

System Overview

Scope

FreshCryptoLib

  • FCL.sol: The Fresh Crypto library provides a comprehensive set of functionalities for ECDSA signature verification and elliptic curve operations. It includes methods to verify ECDSA signatures and perform operations related to elliptic curve arithmetic.

    • Key Features:

      1. ECDSA Signature Verification: The ecdsa_verify function takes a message hash and signature parameters (r, s) along with the public key coordinates (Qx, Qy) and verifies whether the signature is valid for the given message and public key.

      2. Elliptic Curve Arithmetic: The library implements functions to perform arithmetic operations on elliptic curve points in short Weierstrass form, such as point addition, point doubling, and scalar multiplication.

      3. Modular Inversion: Modular inversion functions (FCL_nModInv, FCL_pModInv) are provided using precompiled contracts to efficiently compute the multiplicative inverse modulo n and p, where n is the order of the curve and p is the prime field modulus.

    • Core Logic:

      • ECDSA Verification:

        • The ecdsa_verify function first checks if the provided signature parameters (r and s) are within the valid range (0 < r, s < n), where n is the curve order.

        • It then verifies if the provided public key (Qx, Qy) lies on the curve using ecAff_isOnCurve function.

        • Next, it computes intermediate values for signature verification using modular inverses and scalar multiplications.

        • Finally, it checks if the computed x1 value is equal to zero, which determines the validity of the signature.

      • Elliptic Curve Arithmetic:

        • The library provides functions for point addition (ecAff_add), point doubling (ecZZ_Dbl), and scalar multiplication (ecZZ_mulmuladd_S_asm), implemented using assembly for efficiency.

        • These functions handle operations in affine coordinates as well as in projective coordinates (ZZ representation).

      • Modular Inversion:

        • Modular inversion is performed using precompiled contracts for efficient computation, utilizing the little Fermat theorem to compute inverses as a^(n-2).
    • Addition Features

      • Precompiled Contracts: The library utilizes precompiled contracts (MODEXP_PRECOMPILE = 0x0000000000000000000000000000000000000005) for modular exponentiation and inversion, enhancing efficiency in cryptographic operations.

      • Constant Definitions: Constants such as curve parameters (p, a, b, gx, gy) and precomputed values (minus_2, minus_2modn, minus_1) are defined for the specific curve (sec256R1) and used throughout the library. al Features:**

      • Precompiled Contracts: The library utilizes precompiled contracts (MODEXP_PRECOMPILE = 0x0000000000000000000000000000000000000005) for modular exponentiation and inversion, enhancing efficiency in cryptographic operations.

      • Constant Definitions: Constants such as curve parameters (p, a, b, gx, gy) and precomputed values (minus_2, minus_2modn, minus_1) are defined for the specific curve (sec256R1) and used throughout the library.

MagicSpend

  • MagicSpend.sol: MagicSpend contract is an implementation of the ERC-4337 Paymaster standard, which is used for sponsoring gas fees. It is compatible with the Entrypoint v0.6 interface. The contract allows users to deposit ETH, which can be used to pay for gas fees on their behalf when they execute transactions. The contract also allows users to withdraw excess ETH that was not used for gas fees.

    • Key Features:

      • ERC4337 Paymaster: The contract conforms to the ERC4337 Paymaster standard, which allows it to be used as a paymaster for UserOperations in the EntryPoint v0.6 contract.
      • User Withdrawals: Users can withdraw funds from the contract by submitting a signed withdraw request. The request must include the user's address, the asset to withdraw, the amount to withdraw, a nonce, and an expiry timestamp.
      • Owner Withdrawals: The owner of the contract can withdraw funds from the contract at any time.
      • EntryPoint Deposits and Withdrawals: The owner of the contract can deposit funds into the EntryPoint and withdraw funds from the EntryPoint.
      • EntryPoint Stake Management: The owner of the contract can add stake to the EntryPoint, unlock stake, and withdraw stake from the EntryPoint.
    • Core Logic:

      • validatePaymasterUserOp: validates a UserOperation (transaction) and ensures that the user has enough ETH in the contract to cover the gas fees. It also checks that the withdrawRequest included in the UserOperation is valid and has not expired or been replayed. If the UserOperation is valid, the contract sets the validationData to include the expiry time of the withdrawRequest and a flag indicating whether the signature in the withdrawRequest is valid.

      • postOp: called by the Entrypoint after a UserOperation has been executed. It transfers the remaining ETH that was not used for gas fees back to the user.

      • withdrawGasExcess: allows a user to withdraw excess ETH that was not used for gas fees.

      • withdraw: allows a user to withdraw ETH from the contract by providing a valid withdrawRequest.

      • ownerWithdraw: allows the owner of the contract to withdraw all ETH from the contract.

      • entryPointDeposit, entryPointWithdraw, entryPointAddStake, entryPointUnlockStake, entryPointWithdrawStake: allow the owner of the contract to interact with the Entrypoint, including depositing ETH, withdrawing ETH, adding stake, unlocking stake, and withdrawing stake.

    • Additional Features:

      • onlyEntryPoint modifier: ensures that only the Entrypoint can call certain functions in the contract.

      • _validateRequest: validates a withdrawRequest to ensure that it has not been replayed.

      • _withdraw: low-level function to withdraw ETH from the contract.

      • isValidWithdrawSignature: checks the signature in a withdrawRequest to ensure that it is valid.

      • getHash: generates a hash of a withdrawRequest that can be used to validate its signature.

      • nonceUsed: checks whether a nonce has already been used in a withdrawRequest.

      • entryPoint: returns the address of the canonical ERC-4337 Entrypoint v0.6 contract.

SmartWallet

  • **CoinbaseSmartWallet.sol:**The Coinbase Smart Wallet allow users to securely store and manage their assets, and to execute transactions.

    • Functionality:

      • The wallet supports multiple owners, each of whom can execute transactions on behalf of the wallet. Owners can be added and removed using the addOwnerAddress and removeOwnerAtIndex functions.
      • The wallet uses a signature validation mechanism to ensure that transactions are authorized by the appropriate owners. Signatures can be provided using either ERC1271 or WebAuthn authentication.
      • The wallet can execute transactions on behalf of the user, either individually or in batches, using the execute and executeBatch functions.
      • The wallet includes a custom implementation of the ERC1271 _validateSignature function that is used both for classic ERC-1271 signature validation and for UserOperation validation.
    • Key Features:

      • Multiple owners with flexible signature validation
      • Upgradeable using UUPS pattern
      • Custom implementation of ERC1271 _validateSignature function
      • Batch execution of transactions
      • Cross-chain replayable transactions using REPLAYABLE_NONCE_KEY
    • Core Logic:

      • The wallet uses a custom SignatureWrapper struct to tie a signature to its signer, and a custom Call struct to describe a raw call to execute.
      • The wallet uses a modifier onlyEntryPoint to ensure that only the EntryPoint (i.e. msg.sender) can execute certain functions, such as validateUserOp and executeWithoutChainIdValidation.
      • The validateUserOp function is used to validate a UserOperation before it is executed. The function checks that the UserOperation key is valid, that the signature is valid, and that the UserOperation is authorized to skip the chain ID validation if necessary.
      • The execute and executeBatch functions are used to execute transactions on behalf of the user. The former executes a single transaction, while the latter executes a batch of transactions.
      • The entryPoint function returns the address of the EntryPoint v0.6.
      • The getUserOpHashWithoutChainId function computes the hash of a UserOperation in the same way as EntryPoint v0.6, but leaves out the chain ID. This allows accounts to sign a hash that can be used on many chains.
  • CoinbaseSmartWalletFactory.sol: Coinbase Smart Wallet Factory contract is a factory for creating Coinbase Smart Wallets, which are based on the ERC-4337 standard. The factory deploys new accounts behind a minimal ERC1967 proxy whose implementation points to a registered ERC-4337 implementation.

    • Functionality:

      • The factory is initialized with an ERC-4337 implementation address, which is used to deploy new accounts.
      • The createAccount function deploys a new account and returns its deterministic address. It takes a list of initial owners and a nonce as inputs. The owners can be a set of addresses and/or public keys depending on the signature scheme used.
      • The getAddress function returns the deterministic address of an account created via createAccount using the given owners and nonce.
      • The initCodeHash function returns the initialization code hash of the account (a minimal ERC1967 proxy).
      • The _getSalt function returns the deterministic salt for a specific set of owners and nonce.
    • Key Features:

      • The factory uses a deterministic address generation mechanism to ensure that the same set of owners and nonce will always produce the same account address.
      • The factory uses a minimal ERC1967 proxy to deploy new accounts, which allows for upgradeability and flexibility in terms of the ERC-4337 implementation used.
      • The factory supports both ERC-1271 and Webauthn signature schemes for authentication.
    • Core Logic:

      • The createAccount function first checks if the owners array is empty and reverts with an error if it is.
      • It then uses the LibClone library to create a deterministic ERC1967 account address using the provided implementation and salt.
      • The account is cast as a CoinbaseSmartWallet and initialized with the provided owners if it has not been deployed before.
      • The getAddress function uses the LibClone library to predict the deterministic address of an account created with the given owners and nonce.
      • The initCodeHash function uses the LibClone library to return the initialization code hash of a minimal ERC1967 proxy.
      • The _getSalt function uses the keccak256 hash function to generate a deterministic salt from the provided owners and nonce.
  • ERC1271.sol: This contract is an abstract implementation of ERC-1271, a standard for creating contracts that can validate signatures. The contract extends the ERC-1271 standard by adding a layer of protection against cross-account signature replay attacks. This is achieved by introducing a new EIP-712 compliant hash that includes the chain ID and address of the contract, making it impossible for the same signature to be validated on different accounts owned by the same signer.

    • Key Features:

      • The contract defines a constant _MESSAGE_TYPEHASH which is a precomputed typeHash used to produce EIP-712 compliant hash when applying the anti cross-account-replay layer.
      • The contract includes a function eip712Domain() that returns information about the EIP712Domain used to create EIP-712 compliant hashes.
      • The contract defines a function isValidSignature(bytes32 hash, bytes calldata signature) that validates a signature against a given hash. The contract applies the anti cross-account-replay layer on the given hash before performing the signature validation.
      • The contract defines a function replaySafeHash(bytes32 hash) that produces a replay-safe hash from the given hash. This function is a wrapper around _eip712Hash() and returns an EIP-712 compliant replay-safe hash.
      • The contract defines a function domainSeparator() that returns the domainSeparator used to create EIP-712 compliant hashes.
      • The contract defines a function _eip712Hash(bytes32 hash) that returns the EIP-712 typed hash of the CoinbaseSmartWalletMessage(bytes32 hash) data structure.
      • The contract defines a function _hashStruct(bytes32 hash) that returns the EIP-712 hashStruct result of the CoinbaseSmartWalletMessage(bytes32 hash) data structure.
      • The contract defines two abstract functions, _domainNameAndVersion() and _validateSignature(bytes32 message, bytes calldata signature), that must be implemented by the contract's subclass.
    • Core Logic:

      • The contract's main logic is in the isValidSignature(bytes32 hash, bytes calldata signature) function, which validates a signature against a given hash. The function applies the anti cross-account-replay layer on the given hash before performing the signature validation.
      • The contract's anti cross-account-replay layer is implemented in the replaySafeHash(bytes32 hash) function. This function produces a replay-safe hash from the given hash by including the chain ID and address of the contract in the EIP-712 compliant hash.
    • Additional Features:

      • The contract follows ERC-5267, which defines a standard for EIP-712 domains.
      • The contract includes a function eip712Domain() that returns information about the EIP712Domain used to create EIP-712 compliant hashes. This function is used to ensure that the contract is compliant with ERC-5267.
      • The contract defines a constant fields that is a bitmap of used fields in the EIP712Domain. The value of fields is set to hex"0f", which is equivalent to 0b1111.
      • The contract defines a function _domainNameAndVersion() that returns the domain name and version to use when creating EIP-712 signatures. This function must be implemented by the contract's subclass.
      • The contract defines a function _validateSignature(bytes32 message, bytes calldata signature) that validates the signature against the given message. This function must be implemented by the contract's subclass. The function's signature is designed to be flexible, and the implementation can choose to interpret the signature argument differently depending on its use case.
  • MultiOwnable.sol: It allows for multiple owners, each identified by raw bytes, and provides functionality for adding, removing, and verifying owners. The owners can be identified by Ethereum addresses or passkeys (public keys). The contract is designed to follow ERC-7201, which provides a standard for storage layout in contracts.

    • Functionality:

      • Allows adding and removing owners.
      • Checks if an address or public key is an owner.
      • Enforces access control by ensuring that only owners can call certain functions.
    • Core Logic:

      • addOwnerAddress(): Adds a new owner address.
      • addOwnerPublicKey(): Adds a new owner passkey (public key).
      • removeOwnerAtIndex(): Removes an owner from the given index.
      • isOwnerAddress(), isOwnerPublicKey(), isOwnerBytes(): Check if a given address, public key, or raw bytes is an owner.
      • ownerAtIndex(): Returns the owner bytes at a given index.
      • nextOwnerIndex(): Returns the next index that will be used to add a new owner.
      • _initializeOwners(): Initializes the owners of this contract.
      • _addOwner() and _addOwnerAtIndex(): Adds an owner at a given index or the next available index.
    • Additional Features:

      • The contract uses inline assembly in the _getMultiOwnableStorage() function to get a storage reference to the MultiOwnableStorage struct. This is done to comply with the ERC-7201 standard for storage layout.
      • The contract is designed to be extensible, with virtual functions that can be overridden in derived contracts.

WebAuthnSol

  • WebAuthn.sol: This library for verifying WebAuthn Authentication Assertions. It is built off the work of Daimo and uses the RIP-7212 precompile for signature verification, falling back to FreshCryptoLib if precompile verification fails.

    • Functionality:

      • The library contains a single function verify that takes in a challenge, a boolean indicating whether user verification is required, a WebAuthnAuth struct, and the x and y coordinates of the public key. It returns a boolean indicating whether the authentication assertion passed validation.
    • Key Features:

      • Uses RIP-7212 precompile for signature verification, falling back to FreshCryptoLib if precompile verification fails.
      • Verifies that the authenticator data indicates a well-formed assertion with the user present bit set and that the client JSON is of type "webauthn.get".
    • Core Logic:

      • The verify function first checks that the s value of the signature is less than or equal to the secp256r1 curve order divided by 2 to prevent signature malleability.
      • It then verifies that the client data JSON has the correct type and challenge.
      • It skips steps 13, 14, 15 and verifies that the user present bit is set in the authenticator data.
      • It uses the RIP-7212 precompile address to verify the signature, falling back to FreshCryptoLib if precompile verification fails.

Roles

  • Owners: addresses or public keys that have control over the smart contract wallet. Owners can add or remove other owners, execute transactions, and initialize the wallet.
  • EntryPoint: a specific address that is used to execute certain functions in the smart contract wallet, such as validateUserOp and executeWithoutChainIdValidation.
  • User: an address that initiates transactions through the smart contract wallet. Users can have one or more owners who control their wallet.
  • Relying Party: an entity that relies on the WebAuthn authentication assertion produced by the smart contract wallet. The Relying Party is responsible for providing the challenge that is used in the authentication process.
  • Authenticator: a device or service that produces WebAuthn authentication assertions. The authenticator is responsible for verifying the user's presence and enforcing user verification if required.
  • Verifier: a contract or library that verifies the WebAuthn authentication assertion produced by the smart contract wallet. The verifier checks that the assertion is well-formed and that the signature is valid.
  • Factory: a contract that deploys new instances of the smart contract wallet. The factory is responsible for initializing the wallet with its first set of owners.
  • Paymaster: a contract that can sponsor gas for a user's transaction. The paymaster is used in the context of ERC-4337 to allow users to execute transactions without having to pay gas fees themselves.

Invariants Generated

MultiOwnable.sol

  1. Consistency Between isOwner and ownerAtIndex Mappings:
    • Invariant: The entries in the isOwner mapping should match the corresponding entries in the ownerAtIndex mapping.
    • Formula: For each index i, isOwner[ownerAtIndex[i]] should be true if and only if the owner at index i exists.

ERC1271.sol

  1. Signature Validation: The _validateSignature function should always correctly validate the signature against the given message. This is the core functionality of the ERC-1271 standard.

CoinbaseSmartWallet.sol

  1. The UserOperation key must be either REPLAYABLE_NONCE_KEY or a valid nonce for the account.

    • The validateUserOp function checks if the UserOperation key is either REPLAYABLE_NONCE_KEY or a valid nonce for the account. If it is not, the function reverts with the InvalidNonceKey error.
  2. If the UserOperation key is REPLAYABLE_NONCE_KEY, then the UserOperation call selector must be whitelisted to skip the chain ID validation.

    • The executeWithoutChainIdValidation function checks if the UserOperation key is REPLAYABLE_NONCE_KEY.If it is, the function checks if the UserOperation call selector is whitelisted to skip the chain ID validation.If the call selector is not whitelisted, the function reverts with the SelectorNotAllowed error.
  3. The UserOperation signature must be valid for the account.

    • The validateUserOp function checks if the UserOperation signature is valid for the account. If the signature is not valid, the function reverts with the InvalidSignature error.

MagicSpend.sol

  1. The WithdrawRequest signature must be valid for the given account and withdrawRequest.

    • The isValidWithdrawSignature function checks if the WithdrawRequest signature is valid for the given account and withdrawRequest. If the signature is not valid, the function returns false.
  2. The WithdrawRequest nonce must not have been used before by the given account.

    • The _validateRequest function checks if the WithdrawRequest nonce has been used before by the given account. If the nonce has been used before, the function reverts with the InvalidNonce error.

If either of these invariants is violated, the contract could be vulnerable to attack.

For example, an attacker could create a fake WithdrawRequest with a forged signature and use it to withdraw funds from the contract. Or, an attacker could replay a previously used WithdrawRequest to withdraw funds multiple times.

Approach Taken-in Evaluating Smart Wallet Protocol

Accordingly, I analyzed and audited the subject in the following steps;

  1. Core Protocol Contract Overview:

    I focused on thoroughly understanding the codebase and providing recommendations to improve its functionality. The main goal was to take a close look at the important contracts and how they work together in the Smart Wallet.

    I start with the following contracts, which play crucial roles in the Smart Wallet:

    CoinbaseSmartWallet.sol CoinbaseSmartWalletFactory.sol ERC1271.sol WebAuthn.sol MagicSpend.sol

    I started my analysis by examining the intricate structure and functionalities of the Smart Wallet protocol, which includes the CoinbaseSmartWallet.sol and CoinbaseSmartWalletFactory.sol contracts. The Smart Wallet allows users to securely store and manage their assets while supporting multiple owners and executing transactions on behalf of the user. It employs a signature validation mechanism using either ERC1271 or WebAuthn authentication to ensure transactions are authorized by appropriate owners.

    The Smart Wallet Factory is a factory for creating Coinbase Smart Wallets based on the ERC-4337 standard. It deploys new accounts behind a minimal ERC1967 proxy, whose implementation points to a registered ERC-4337 implementation. The factory uses a deterministic address generation mechanism and a minimal ERC1967 proxy to deploy new accounts, allowing for upgradeability and flexibility in terms of the ERC-4337 implementation used.

    In addition to the Smart Wallet contracts, I also analyzed the FreshCryptoLib (FCL) library, which provides functionalities for ECDSA signature verification and elliptic curve operations. The FCL library includes methods to verify ECDSA signatures and perform operations related to elliptic curve arithmetic, making it an essential component of the Smart Wallet protocol.

    Furthermore, I examined the WebAuthn library, which is used for verifying WebAuthn Authentication Assertions in the Smart Wallet protocol. It employs the RIP-7212 precompile for signature verification and falls back to FreshCryptoLib if precompile verification fails.

    By understanding the structure and functionalities of these contracts and libraries, I can gain insights into the overall design and security of the Smart Wallet protocol.

  2. Documentation Review:

    Then went to Review these README of every file, for a more detailed and technical explanation see that video of the Smart Wallet and play around with that site.

  3. Compiling code and running provided tests with Solidity 0.8.23:

  4. Manuel Code Review In this phase, I initially conducted a line-by-line analysis, following that, I engaged in a comparison mode.

    • Line by Line Analysis: Pay close attention to the contract's intended functionality and compare it with its actual behavior on a line-by-line basis.

    • Comparison Mode: Compare the implementation of each function with established standards or existing implementations, focusing on the function names to identify any deviations.

Codebase Quality

Overall, I consider the quality of the Smart Wallet from Coinbase Wallet protocol codebase to be Good. The code appears to be mature and well-developed. We have noticed the implementation of various standards adhere to appropriately. Details are explained below:

Codebase Quality CategoriesComments
Architecture & DesignThe protocol features a modular design, segregating functionality into distinct contracts (e.g., FreshCryptoLib, MagicSpend, SmartWallet, WebAuthnSol) for clarity and ease of maintenance.
Error Handling & Input ValidationFunctions check for conditions and validate inputs to prevent invalid operations, though the depth of validation (e.g., for edge cases transactions) would benefit from closer examination. The Smart Wallet protocol use errors to define custom error messages that can be reverted with. This allows more descriptive errors than just generic revert messages.Input validation is done by defining expected input formats upfront (e.g. structured types for withdraw requests) and validating inputs match these formats on entry.Validation includes checks for things like:Valid signature formats and owners,Withdraw request field values like expiry, nonce, amount.Nonce validation prevents replay attacks by tracking already used nonces.Chain ID validation is done for operations that require it, with whitelist for certain functions.Validation is separated from logic - validated inputs are passed to internal methods that assume validity.Errors are thrown on failed validation rather than returning boolean, following EIP practices.Signature validation utilizes signature library for modular validation logic.
Code Maintainability and ReliabilityThe contracts are written with emphasis on sustainability and simplicity. The functions are single-purpose with little branching and low cyclomatic complexity. The protocol includes a novel mechanism for collecting fees that offers an incentive for this work to be done by outside actors,thereby removing the associated complexity.
Code CommentsThe different types of comments are used for in the Smart Wallet protocol Single-line comments, Multi-line comments, Documentation comments: documentation, explanations, TODOs, and separating/structuring the code for readability. The NatSpec tags allow automatically generating documentation as well.The contracts are accompanied by comprehensive comments, facilitating an understanding of the functional logic and critical operations within the code. Functions are described purposefully, and complex sections are elucidated with comments to guide readers through the logic. Despite this, certain areas, particularly those involving intricate mechanics or tokenomics, could benefit from even more detailed commentary to ensure clarity and ease of understanding for developers new to the project or those auditing the code.
TestingThe contracts exhibit a commendable level of test coverage 95% but with aim to 100% for indicative of a robust testing regime. This coverage ensures that a wide array of functionalities and edge cases are tested, contributing to the reliability and security of the code. However, to further enhance the testing framework, the incorporation of fuzz testing and invariant testing is recommended. These testing methodologies can uncover deeper, systemic issues by simulating extreme conditions and verifying the invariants of the contract logic, thereby fortifying the codebase against unforeseen vulnerabilities.
Code Structure and FormattingThe codebase benefits from a consistent structure and formatting, adhering to the stylistic conventions and best practices of Solidity programming. Logical grouping of functions and adherence to naming conventions contribute significantly to the readability and navigability of the code. While the current structure supports clarity, further modularization and separation of concerns could be achieved by breaking down complex contracts into smaller, more focused components. This approach would not only simplify individual contract logic but also facilitate easier updates and maintenance.
StrengthsThe strength of the protocol lies in its comprehensive set of functionalities for ECDSA signature verification and elliptic curve operations. The FreshCryptoLib provides key features such as ECDSA signature verification, elliptic curve arithmetic, and modular inversion using precompiled contracts. The library also utilizes constant definitions for the specific curve (sec256R1) and precomputed values to enhance efficiency.The SmartWallet contract allows users to securely store and manage their assets, and to execute transactions, while supporting multiple owners and signature validation mechanisms.
DocumentationWhile the NatSpec and README provides comprehensive details for all external functions and there are helpful inline comments throughout, there is currently no external documentation available for users or integrators. It is crucial to develop external documentation to offer a comprehensive understanding of the contract's functionality, purpose, and interaction methods. This documentation should encompass detailed explanations of the contract's features, guidance on utilizing and integrating its functions, along with relevant use cases and examples. By providing clear and thorough external documentation, users and integrators can gain a complete understanding of the contract and effectively utilize its capabilities

Systemic Risks, Centralization Risks, Technical Risks & Integration Risks

  • MultiOwnable.sol

    1. Systemic Risks:

      • Owner index manipulation: By manipulating the owner indexes through addition/removal, it may enable vectoring in replay attacks if indexes are ever used to gate access or privileges. This centralizes control using indexes.

      • Lack of owner key revocation: Once an account is added as an owner, its privileges are permanent and cannot be revoked even if the private key is compromised.

    2. Technical Risks:

      • Manipulable owner indexes: Owner indexes can be manipulated by removal and addition of owners, allowing replay attacks.

      • Unchecked Array Length: In the _initializeOwners function, there's a loop that iterates over the provided owners array without checking its length against potential gas limits. If the array is excessively large, it could lead to out-of-gas errors function calls.

          function _initializeOwners(bytes[] memory owners) internal virtual {
          for (uint256 i; i < owners.length; i++) {
              if (owners[i].length != 32 && owners[i].length != 64) {
                  revert InvalidOwnerBytesLength(owners[i]);
              }
      
              if (owners[i].length == 32 && uint256(bytes32(owners[i])) > type(uint160).max) {
                  revert InvalidEthereumAddressOwner(owners[i]);
              }
      
              _addOwnerAtIndex(owners[i], _getMultiOwnableStorage().nextOwnerIndex++);
          }
      }
  • ERC1271.sol

    1. Systemic Risks:

      • block.chainid: Uses block.chainid potentially lead to issues if the chain ID is not properly handled. For example, if the chain ID is not correctly set, it could lead to the creation of an invalid domain separator.
          function domainSeparator() public view returns (bytes32) {
              (string memory name, string memory version) = _domainNameAndVersion();
              return keccak256(
                  abi.encode(
                      keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                      keccak256(bytes(name)),
                      keccak256(bytes(version)),
                      block.chainid,
                      address(this)
                  )
              );
          }
    2. Technical Risks:

      • Complexity in Signature Validation: The ERC1271.sol contract abstract the signature validation process, leaving it to be implemented by inheriting contracts. Depending on the implementation of _validateSignature, there could be risks related to incorrect or insufficient validation logic, leading to vulnerabilities such as signature forgery.
  • CoinbaseSmartWalletFactory.sol

    1. Stemic Risks:

      • Predictive A Cross-Chain Replayable Transactions: The contract uses a reserved nonce key (REPLAYABLE_NONCE_KEY) for cross-chain replayable transactions. If there's a flaw in the implementation, it could potentially lead to replay attacks across different chains.
    2. Technical Risks:

      • Unvalidated inputs: The CoinbaseSmartWalletFactory contract does not validate the input parameters, such as the erc4337 address provided in the constructor or the owners and nonce parameters in the createAccount function.

      • Deterministic Address Generation: The contract relies on deterministic address generation for deploying smart wallets. While deterministic address generation can provide predictability, any flaws in the salt generation or address computation logic could lead to address collisions or vulnerabilities in the deployed smart wallets.

    3. Centralization Risks:

      • Denial-of-service attack on the factory: A potential risk is an attacker deliberately deploying a large number of accounts through the factory contract, causing a state bloat that could lead to increased gas costs and potential Denial-of-Service for legitimate users. This
    4. Integration Risks:

      • Custom owner representation: The CoinbaseSmartWalletFactory.sol contract accepts owners as a bytes[] parameter, which could represent a mix of addresses and public keys, depending on the signature scheme used. Integrating this contract with other systems or contracts that do not support this custom owner representation or require a different format could lead to compatibility issues.
  • CoinbaseSmartWallet.sol

    1. Systemic Risks:

      • Cross-Chain Replayable Transactions: The contract uses a reserved nonce key (REPLAYABLE_NONCE_KEY) for cross-chain replayable transactions. If there's a flaw in the implementation, it could potentially lead to replay attacks across different chains.

      • Replayable transactions without chain ID validation: The CoinbaseSmartWallet.sol allows for certain transactions to be replayed across chains without validating the chain ID. This could allow an attacker to execute a transaction on one chain that has already been executed on another chain, even if the two chains have different chain IDs.

      • Custom signature verification function: The contract uses a custom signature verification function that is not based on any standard. This could make it difficult to verify the authenticity of signatures and could increase the risk of signature forgery.

    2. Centralization Risks:

      • Hardcoded EntryPoint Address: The contract has a hardcoded EntryPoint address. This could lead to centralization risks as the hardcoded EntryPoint has significant control over the contract's operations.
    3. Technical Risks:

      • Unvalidated Inputs: The contract does not validate inputs in some functions. For example, in the execute and executeBatch functions, there are no checks on the target, value, and data parameters.
        function executeBatch(Call[] calldata calls) public payable virtual onlyEntryPointOrOwner {
              for (uint256 i; i < calls.length;) {
                  _call(calls[i].target, calls[i].value, calls[i].data);
                  unchecked {
                      ++i;
                  }
              }
          }

      Use of payPrefund modifier: The payPrefund modifier is used to send missing funds to the EntryPoint. If there's a bug in the implementation, it could lead to loss of funds.

            modifier payPrefund(uint256 missingAccountFunds) virtual {
                _;
      
                assembly ("memory-safe") {
                    if missingAccountFunds {
                        // Ignore failure (it's EntryPoint's job to verify, not the account's).
                        pop(call(gas(), caller(), missingAccountFunds, codesize(), 0x00, codesize(), 0x00))
                    }
                }
            }
    4. Integration Risks:

      • Use of WebAuthn.verify: The contract uses the WebAuthn.verify function from the "WebAuthnSol" library for signature validation. If there are issues with the WebAuthn.verify function or if it's not integrated correctly, this could lead to integration risks.
  • WebAuthn.sol

    1. Technical Risks:

      • Signature Malleability: The contract checks if the 's' value of the secp256r1.s signature is greater than P256_N_DIV_2 to guard against signature malleability. However, this only covers one type of malleability attack. Other types of malleability attacks could still be possible.
      if (webAuthnAuth.s > P256_N_DIV_2) {
    2. Integration Risks:

      • clientDataJSON: The contract assumes that the clientDataJSON is well-formed and does not contain any malicious data. If the clientDataJSON is not properly validated before being passed to the contract, it could lead to issues.

      • Public Key: The contract assumes that the x and y coordinates of the public key are valid. If the public key is not properly validated before being passed to the contract, it could lead to issues.

      • Challenge: The contract assumes that the challenge provided by the relying party is valid. If the challenge is not properly validated before being passed to the contract, it could lead to issues.

      • requireUV: The contract assumes that the requireUV flag is set correctly. If this flag is not set correctly, it could lead to issues with user verification.

      • Fallback to FreshCryptoLib: The contract falls back to FreshCryptoLib if precompile verification fails. If FreshCryptoLib is not available or contains bugs, this could lead to issues.

      • Gas Limit: The contract does not have any checks to prevent exceeding the gas limit. If the contract's operations require more gas than available, it could lead to issues.

  • FCL.sol

    1. Systemic Risks:

      • Bias in random number generation: Functions like ecZZ_mulmuladd_S_asm rely on unbiased randomness which chain-based execution may not provide.
    2. Centralization Risks:

      • Single Point of Failure: The contract uses a single precompiled contract for certain operations. If this precompiled contract becomes unavailable or is compromised, it could lead to issues with the contract.
    3. Integration Risks:

      • Incorrect Inputs: The contract assumes that the inputs provided to it (message, r, s, Qx, Qy) are valid. If these inputs are not properly validated before being passed to the contract, it could lead to issues.
  • MagicSpend.sol

    1. Systemic Risks:

      • Nonce Reuse Vulnerability: The contract uses nonces to prevent replay attacks on withdraw requests. However, if nonces are not generated securely or if they are reused, it could lead to unauthorized fund withdrawals or denial service attacks.

      • Contract Balance Depletion: During the postOp() function execution, all remaining funds are transferred to the user account. If the contract's balance is insufficient to cover the remaining funds, it could result in failed transactions and unexpected behavior.

      • There is a tight coupling with the EntryPoint contract (0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789). If the EntryPoint contract is upgraded or changes its interface, this contract may break.

    2. Centralization Risks:

      • The contract has an owner (set in the constructor) who has significant control over the contract. The owner can withdraw funds, deposit/withdraw from the EntryPoint, add/unlock/withdraw stake from the EntryPoint, and potentially sign withdraw requests.
    3. Technical Risks:

      • Signature replay attacks: Signatures are not hashed with chainId or contract address.
    4. Integration Risks:

      • The MagicSpend assumes that the actualGasCost returned by the EntryPoint contract in the postOp function is correct. If the EntryPoint contract returns an incorrect actualGasCost, it could lead to incorrect calculations and fund transfers.

Suggestions

What ideas can be incorporated?

  1. Improve Signature Validation: The ERC1271 contract could be extended to support additional signature schemes beyond just ERC-1271 and WebAuthn. For example, it could add support for other common signature schemes like EIP-2098 or EIP-2612.

  2. Enhance Replay Protection: The ERC1271 contract's anti-replay protection could be further strengthened by incorporating additional contextual information, such as the transaction nonce or the current block timestamp, into the EIP-712 hash to make it even more difficult to replay signatures.

  3. Modularize Functionality: The various contracts (FreshCryptoLib, MagicSpend, CoinbaseSmartWallet, CoinbaseSmartWalletFactory, ERC1271, MultiOwnable, WebAuthn) could be further modularized and decoupled, allowing for easier maintainability and the ability to mix-and-match functionalities as needed.

  4. Add Support for More Curves: The FreshCryptoLib could be extended to support additional elliptic curves beyond just secp256r1, allowing for greater flexibility and compatibility with a wider range of cryptographic applications.

  5. Implement Batch Signature Verification: The FreshCryptoLib could be enhanced to support batch verification of multiple ECDSA signatures, improving the efficiency of verifying large sets of signatures.

  6. Add Support for ERC-4337 Meta-Transactions: The CoinbaseSmartWallet and CoinbaseSmartWalletFactory contracts could be further enhanced to support the ERC-4337 meta-transaction standard, allowing users to execute transactions without having to pay gas fees directly.

What’s unique?

  1. Gas Sponsorship via ERC-4337 Paymaster (MagicSpend): The protocol incorporates the ERC-4337 Paymaster standard, which allows users to deposit ETH into the MagicSpend contract to sponsor gas fees for their transactions. This feature eliminates the need for users to hold ETH for gas payments, making it more accessible and user-friendly.

  2. Flexible Multi-Signature Wallet (CoinbaseSmartWallet): The CoinbaseSmartWallet supports multiple owners with flexible signature validation mechanisms, including both traditional ERC-1271 and modern WebAuthn authentication. This allows for increased security and adaptability to different authentication methods.

  3. Cross-Account Replay Protection (ERC1271): The ERC1271 implementation in the protocol introduces a layer of protection against cross-account signature replay attacks. This is achieved by using EIP-712 compliant hashes that include the chain ID and contract address, making it impossible for the same signature to be validated on different accounts owned by the same signer.

  4. Deterministic Wallet Deployment (CoinbaseSmartWalletFactory): The CoinbaseSmartWalletFactory generates deterministic addresses for new wallets based on the owners and a nonce. This ensures that the same set of owners and nonce will always produce the same wallet address, simplifying wallet management and recovery.

  5. WebAuthn Authentication Support (WebAuthn): The protocol includes support for WebAuthn, a modern authentication standard that utilizes hardware security keys or platform authenticators (e.g., fingerprint sensors, facial recognition) for enhanced security. This aligns with the growing adoption of WebAuthn in the industry.

  6. Efficient Cryptographic Operations (FreshCryptoLib): The FreshCryptoLib library provides efficient implementations of cryptographic operations, such as ECDSA signature verification and elliptic curve arithmetic, by utilizing precompiled contracts. This improves the overall performance and gas efficiency of the protocol.

  7. Batch Transaction Execution (CoinbaseSmartWallet): The CoinbaseSmartWallet supports batch execution of transactions, allowing users to bundle multiple transactions into a single operation. This can save gas costs and improve the overall efficiency of the wallet.

Architecture

System Workflow

The overall workflow involves users creating a CoinbaseSmartWallet instance through the CoinbaseSmartWalletFactory, depositing ETH into the MagicSpend contract for gas sponsorship, and executing transactions through their wallet. The wallet validates signatures using ERC1271 or WebAuthn, and the MagicSpend contract handles gas fee sponsorship and excess ETH refunds. The FreshCryptoLib and WebAuthn libraries provide essential cryptographic functions for signature verification and authentication.

  1. User Setup:

    • Users create a Coinbase Smart Wallet using the CoinbaseSmartWalletFactory.
    • The wallet is initialized with multiple owners and a nonce.
    • The wallet's address is deterministically generated based on the owners and nonce.
  2. Transaction Execution:

    • Users sign transactions using either ERC-1271 or WebAuthn authentication.
    • The CoinbaseSmartWallet contract validates the signatures and ensures that the transaction is authorized by the appropriate owners.
    • The wallet executes the transaction on behalf of the user using the MagicSpend contract as a paymaster.
  3. Gas Fee Payment:

    • The MagicSpend contract pays for the gas fees associated with the transaction using ETH deposited by the user.
    • If the transaction is successful, the remaining ETH is returned to the user.
  4. Cross-Chain Transactions:

    • The CoinbaseSmartWallet contract supports cross-chain transactions using a replayable nonce.
    • Users can sign a hash that is valid on multiple chains, allowing them to execute the same transaction on different blockchains.
  5. WebAuthn Authentication:

    • To authenticate via WebAuthn, users initiate an assertion flow through web browsers' native Webauthn interface.
      • After successful biometric authentication at client-side (e.g., fingerprint), browser generates Relying Party assertion containing public key credential information which includes authenticator data and client data JSON
      • Assertion is then sent back to server (i.e., MagicSpend)
      • Server verifies assertion against Authenticator Data received from assertion
  6. Wallet Management:

    • Owners can add and remove other owners from the wallet using the MultiOwnable contract.
    • Owners can withdraw funds from the wallet or interact with the EntryPoint contract.
  7. Signature Verification:

    • The ERC1271 contract provides a standard for verifying signatures.
    • The CoinbaseSmartWallet contract uses an anti cross-account-replay layer to prevent signature replay attacks.
    • The WebAuthn library is used to verify WebAuthn authentication assertions.
  8. Withdrawal:

    • Users can withdraw funds from their wallets by providing necessary information such as withdrawal request details etc.
File NameCore FunctionalityTechnical CharacteristicsImportance and Management
MultiOwnable.solThe MultiOwnable contract allows for multiple owners to be registered, each identified by their raw bytes, enabling decentralized control and management of contract operations.Utilizing a storage layout specified by ERC-7201, the contract employs mappings and modifiers to manage owner registration and access control, ensuring secure of contract functions.The MultiOwnable provides functions to add and remove owners, check owner status, and initialize owners during deployment, facilitating flexible management of ownership rights and responsibilities within the contract's structure.
ERC1271.solThe ERC1271.sol contract provides an abstract implementation of ERC-1271 with additional safeguards against cross-account replay attacks, ensuring secure validation of signatures while preventing misuse of signatures across different accounts owned by the same signer.Leveraging EIP-712 compliant hashing mechanisms, the contract introduces an anti cross-account-replay layer to mitigate the risk of replay attacks, enhancing the security and reliability of signature verification processes within Ethereum smart contracts.The ERC1271.sol defines functions and internal methods to compute replay-safe hashes, validate signatures, and retrieve domain separators, facilitating seamless integration and management of signature validation functionalities within contracts.
CoinbaseSmartWalletFactory.solThe CoinbaseSmartWalletFactory.sol serves a factory for deploying Coinbase Smart Wallets, utilizing ERC-4337 implementation behind minimal ERC1967 proxies, allowing for the creation of deterministic addresses for each account.Leveraging Solady's ERC4337Factory, the contract enables the creation of new Coinbase Smart Wallets with a specified set of owners and a nonce, ensuring secure and efficient deployment of smart wallet contracts.The CoinbaseSmartWalletFactory.sol contract defines functions to create ERC-4337 accounts, retrieve deterministic addresses, and compute initialization code hashes and salts, providing essential tools for managing and deploying Coinbase Smart Wallets within Ethereum smart contracts.
CoinbaseSmartWallet.solThe CoinbaseSmartWallet.sol functions as an ERC4337-compatible smart contract wallet, enabling the creation and management of smart wallets with multi-owner functionality, signature validation, and execution of arbitrary calls.Leveraging Solady's ERC4337 implementation and incorporating features from Alchemy's LightAccount and Daimo's DaimoAccount, the contract provides a secure and efficient management of funds and operations, including signature validation, execution of batch calls, and support for WebAuthn authentication.The contract facilitates account initialization with specified owners, validates user operations including nonce-based replay protection, and supports dynamic upgrades via UUPSUpgradeable, offering a comprehensive solution for managing smart wallet functionality.
WebAuthn.solThe contract provides a library for verifying WebAuthn Authentication Assertions, incorporating the WebAuthn protocol and leveraging precompiles for signature verification.Utilizing concepts from Daimo's work, it verifies assertions by checking authenticator data, client data JSON, and signatures, with fallback to FreshCryptoLib if precompile verification fails.The library facilitates the verification of WebAuthn assertions by validating challenge values, user verification flags, and signature authenticity.
FCL.solThe Solidity library FCL provides functions for elliptic curve digital signature algorithm (ECDSA) verification, implementing core operations such as verifying signatures and checking if points are on the curve.The library leverages optimized algorithms and assembly code to efficiently perform elliptic curve arithmetic, including point addition, doubling, inversion, and modular exponentiation, ensuring secure and reliable ECDSA verification.This FCL.sol is crucial for secure cryptographic operations relying on ECDSA for digital signature verification.
MagicSpend.solMagicSpend is an ERC4337 Paymaster contract facilitating signed withdraw requests for ETH and ensuring validation of requests before executing fund transfers.MagicSpend utilizes ECDSA signatures, maintains nonces to prevent replays, Signed Message hashes for signature validation.MagicSpend validating withdraw requests, manages nonces to prevent replay attacks, and ensures efficient fund transfers while minimizing gas usage.

Issues surfaced from Attack Ideas

  1. Gas Fee Calculation Logic:

    • Incorrect Calculation of Gas Repayment: A vulnerability that allows attackers or malicious Bundlers to arbitrarily increase compensation by adding zero bytes to calldata. This could lead to financial loss for participating entities.
    • Gas Estimation Error of Bundler: If the gas estimation performed by Bundler during simulation is lower than the actual gas cost required for on-chain execution, the transaction will fail with an out-of-gas error. This could lead to transaction failures and potential financial loss.
  2. Signature Generation & Usage:

    • Insufficient verification of generated signatures: Incorrect signature verification could allow attackers to impersonate an account and execute arbitrary transactions, leading to severe issues such as theft of user funds.
    • Use of Unsigned Variables: A vulnerability that allows one of the arguments, tokenGasPriceFactor, to be arbitrarily set by the Relayer. This could lead to accounts being charged more than the actual gas cost, potentially resulting in theft of funds.
  3. Reuse of Signatures:

    • Setting Arbitrary Validator via Reuse of UserOperation Signatures: A vulnerability that allows attackers to set the address specified by the attacker as validatorStorage.validator. This could lead to ownership or fund theft of the wallet.
    • Reuse of Signatures for the Paymaster: A vulnerability that allows an attacker to upgrade the implementation of an account to a malicious one using delegatecall. This could lead to the reuse of the same Paymaster signature to repeatedly send the same transaction, depleting the Paymaster's funds.
  4. Front-running:

    • In the ERC4337 standard, if one UserOperation fails, the entire transaction gets reverted. This could be exploited by an attacker using front-running to execute the last UserOperation in the UserOperation array first, causing the entire set of UserOperations to be reverted.
  5. ERC4337 Compliance:

    • Non-compliance with the ERC4337 standard could lead to unexpected bugs or incompatibility with other protocols during future interactions. This could potentially lead to user fund theft or other unintended behaviors.
  6. Implementation Protection:

    • Lack of protection of the implementation could allow destruction of the implementation by the authorized entity or the service itself. This could lead to the fund freeze of the accounts pointing to the implementation.
  7. Unintended Transaction Failure:

    • Unintended transaction failure could lead to financial loss of Relayers and unincentivize their activities. This could also result in economic losses for Bundlers.

Time spent:

35 hours

#0 - c4-pre-sort

2024-03-22T21:14:48Z

raymondfam marked the issue as sufficient quality report

#1 - c4-judge

2024-03-27T10:29:54Z

3docSec marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter