Table of Contents
Summary
- Type
- Account Abstraction (BTC)
- Timeline
- From 2024-01-31
- To 2024-02-06
- Languages
- Solidity
- Total Issues
- 7 (0 resolved, 1 partially resolved)
- Critical Severity Issues
- 0 (0 resolved)
- High Severity Issues
- 1 (0 resolved, 1 partially resolved)
- Medium Severity Issues
- 0 (0 resolved)
- Low Severity Issues
- 3 (0 resolved)
- Notes & Additional Information
- 3 (0 resolved)
Scope
We audited the Particle-Network/btc-smart-account repository at commit 61464ad.
In scope were the following files:
contracts
├── CustomSlotInitializable.sol
├── LightAccount.sol
├── LightAccountFactory.sol
└── SHA256L.sol
System Overview
This project aims to provide a system for generating robust, ERC-4337-compliant smart contract accounts that will operate on Bitcoin EVM-compatible L2 networks. These are L2 networks that utilize the EVM for execution, use BTC as the native token, and settle on the Bitcoin network. ERC-4337 creates a method for achieving account abstraction, which in this case allows users to create transactions on behalf of an owned smart contract rather than from their own Externally Owned Account (EOA). To this end, the Particle network team has created:
LightAccount
: An individually-upgradeable proxy contract that will validate user operations and execute calls.CustomSlotInitializable
: A contract that is inherited byLightAccount
and specifies howLightAccount
stores its proxy initialization data.LightAccountFactory
: A contract that will generate proxiedLightAccount
contracts as needed.SHA256L
: A library that implements SHA256 and aids BTC hashing and signature-related tasks.
All of this aims to allow Bitcoin EVM-compatible L2 users to interact with contracts using their smart wallets without needing to store new EVM keys.
Security Model and Trust Assumptions
Account Abstraction Functionality
ERC-4337 defines the architecture, participants, contracts, and restrictions associated with the account abstraction ecosystem. In this audit, we assumed this infrastructure is deployed and functioning correctly.
The Particle Network's LightAccount
and LightAccountFactory
contracts are based on the Ethereum Foundation's samples, which are designed to be compatible with the rest of the architecture with some configuration options.
In particular, the LightAccount
contract makes the following choices:
- The
owner
address can either be an EOA or a smart contract. In either case, it must endorse the user operation. - There are no restrictions on nonces other than the mandatory ones. This means that operations must be executed sequentially within a category (or "key", using the 4337 terminology), but can be executed in any order across categories. The category associated with each user operation is chosen by the owner.
External Ownership
Interestingly, EOAs sign messages that use the Bitcoin encoding envelope. This is because they function as proxies for Bitcoin addresses. Since Bitcoin and Ethereum use the same elliptic curve and signature scheme, the same ECDSA public key can be used to derive both Bitcoin and Ethereum addresses. After accounting for differences in encoding formats, this also implies that a Bitcoin signature is also a valid signature for the associated Ethereum address since they both correspond to the same private key. By setting this Ethereum address as the owner
, the LightAccount
can use existing Ethereum signature verification infrastructure to validate signatures that were created for the Bitcoin ecosystem.
Contract Ownership
Alternatively, the owner
can be a contract that uses the ERC-1271 mechanism to authorize user operations. However, it must also obey the ERC-7562 opcode restrictions to be compliant with the ERC-4337 requirements. In this case, the user operation digest is not wrapped in a Bitcoin encoding envelope.
Contract Authentication Functionality
The LightAccount
also supports ERC-1271 signature validation. This is independent of its account abstraction functionality and instead is a mechanism for this contract to endorse a generic message. It uses the same authorization mechanisms but the Bitcoin encoding envelope is not included whether or not the owner address is an EOA.
Privileged Roles
The owner
address has complete control of the LightAccount
contract. Either directly or using the account abstraction mechanism, the owner can:
- Execute any message (or a batch of messages) from the account, including sending ETH.
- Withdraw any ETH deposited with the
EntryPoint
on behalf of the account. - Transfer ownership to any non-zero address other than the account itself.
- Upgrade the implementation of the account arbitrarily.
High Severity
Incorrect SHA256 Implementation
We identified the following invalid behaviors in the preprocess
function of the SHA256L
implementation.
Incorrect Implementation
The preprocessing step involves padding the input to ensure that, along with the final 8-byte length field, it occupies an integer number of 64-byte blocks. However, when an extra block is needed, only 56 extra zero bytes (rather than 64) are added, resulting in the incorrect hash output. This will occur whenever the length of the input (in bytes) is higher than 55 modulo 64. In the current codebase, neither use case triggers this error.
Unsafe Memory Assumptions
The function implicitly assumes the input
was the last value allocated, but this is not necessarily true. As a result, the input padding will overwrite any memory that is saved after the input. The return values are also placed [number padding bytes] after the current free memory pointer, which is an arbitrary position if the free memory pointer did not start at the padding location.
Imprecise Return Values
The output
array has an entry for every 4-byte value in the input, but its length is set to 32 regardless of the input size. Its first record is set to 16 * rounds
, and then immediately overwritten by the loop. The function also allocates an extra word for the returned array.
Inefficient Algorithm
The k
value is the size of the whole input including the padding, but it is also used as the amount of zeroes to add. The function only needs to pad to the nearest 64-byte block but it could end up padding for several blocks.
Consider updating the algorithm accordingly. In addition, consider using the Solidity sha256
function for networks that support it.
Update: Partially resolved in pull request #1. The Particle team stated:
Indeed, according to the current implementation, if the user enters more than 55 bytes, there will be an erroneous result. However, we do not believe that it will happen in our scenario. As such, we have only fixed the following high-severity sub-issues:
- Incorrect Implementation
- Imprecise Return Values
However, we are not addressing following sub-issues for now:
1. Unsafe Memory Assumptions
2. Inefficient AlgorithmThese are primarily optimization issues so we are not in a hurry to solve them at the moment. May be we will fix them someday in the future or switch to the native SHA256 implementation by the layer 2.
Low Severity
Misleading Comments
We identified the following misleading comments:
- The
reinitializer
modifier inCustomSlotInitializable
has a comment stating that setting the version number to 255 will prevent any future reinitialization. However, as a 64-bit number, it can still be reinitialized up to "version"264 - 1
. - The
_validateSignature
function inLightAccount
has a comment describing an "Ethereum Signed Message" envelope, but it is actually a "Bitcoin Signed Message" envelope.
Consider updating these comments accordingly.
Update: Acknowledged, will resolve. The Particle team stated:
This issue is not urgent and will be resolved in the future.
Incomplete Docstrings
Throughout the codebase, there are multiple components that do not have docstrings:
- The
receive
function inLightAccount.sol
- The
accountImplementation
immutable inLightAccountFactory.sol
- The
round
function inSHA256L.sol
Moreover, the Initialized
event does not document the version
parameter, while the _transferOwnership
function and the hash
function do not document their parameters and return values.
Consider thoroughly documenting all events and functions (and their parameters) that are part of any contract's public API. Functions implementing sensitive functionality, even if not public
, should be clearly documented as well. When writing docstrings, consider following the Ethereum Natural Specification Format (NatSpec).
Update: Acknowledged, will resolve. The Particle team stated:
This issue is not urgent and will be resolved in the future.
Lack of Tests
Consider introducing a testing suite, particularly for a complex system like a smart account. It should cover functional tests of the main use cases as well as security tests for operations that should be rejected.
Update: Acknowledged, will resolve. The Particle team stated:
This issue is not urgent and will be resolved in the future.
Notes & Additional Information
Lack of Security Contact
Providing a specific security contact (such as an email or ENS name) within a smart contract significantly simplifies the process for individuals to communicate if they identify a vulnerability in the code. This practice is quite beneficial as it permits the code owners to dictate the communication channel for vulnerability disclosure, eliminating the risk of miscommunication or failure to report due to a lack of knowledge on how to do so. Furthermore, if the contract incorporates third-party libraries and a bug surfaces in those, it becomes easier for the maintainers of those libraries to make contact with the appropriate person about the problem and provide mitigation instructions.
Throughout the codebase, there are contracts that do not have a security contact:
- The
CustomSlotInitializable
abstract contract - The
LightAccount
contract - The
LightAccountFactory
contract - The
SHA256L
library
Consider adding a NatSpec comment containing a security contact above the contract definitions. Using the @custom:security-contact
convention is recommended as it has been adopted by the OpenZeppelin Wizard and the ethereum-lists.
Update: Acknowledged, will resolve. The Particle team stated:
This issue is not urgent and will be resolved in the future.
Code Simplifications
The isValidSignature
function of the LightAccount
contract could use the getMessageHash
function instead of reimplementing the same logic. Moreover, it appears to use an unnecessary layer of indirection. The encodeMessageData
function function creates an EIP-712 encoding of an arbitrary message wrapped in a LightAccountMessage
struct, but the message is always a bytes32
digest.
For simplicity, consider defining the LightAccountMessage
struct as directly containing the bytes32
digest.
Update: Acknowledged, will resolve. The Particle team stated:
This issue is not urgent and will be resolved in the future.
Missing Namespaced Storage NatSpec Tag
ERC-7201 specifies that namespaced storage structs be annotated with the NatSpec tag @custom:storage-location <FORMULA_ID>:<NAMESPACE_ID>
, where <FORMULA_ID>
identifies a formula used to compute the storage location where the namespace is rooted, based on the namespace id. There are multiple namespaced storage structs that do not have these tags:
However, it is clear from the code that they follow ERC-7201's formula for computing storage locations.
For improved code clarity and for tooling integration, consider adding ERC-7201 annotations to these storage structs.
Update: Acknowledged, will resolve. The Particle team stated:
This issue is not urgent and will be resolved in the future.
Conclusion
Particle Network has built contracts to facilitate account abstraction on Bitcoin EVM-compatible L2 networks. These contracts rely heavily on the ERC-4337 contracts developed by the Ethereum foundation and with BTC wallets in mind. We identified some issues in the implementation of the SHA256 algorithm. Nevertheless, overall, we found the codebase to be well-reasoned, well-documented, and understandable. The Particle Network team was available and responsive throughout the audit period to answer any questions we had.