Halborn Logo

Account Abstraction Schnorr MultiSig - InFlux Technologies


Prepared by:

Halborn Logo

HALBORN

Last Updated 01/10/2025

Date of Engagement by: December 23rd, 2024 - January 3rd, 2025

Summary

100% of all REPORTED Findings have been addressed

All findings

5

Critical

0

High

0

Medium

0

Low

2

Informational

3


1. Introduction

InFlux Technologies engaged Halborn to conduct a security assessment on their smart contracts revisions started on December 23th, 2024 and ending on January 3rd, 2025. The security assessment was scoped to the smart contracts provided to the Halborn team.

Commit hashes and further details can be found in the Scope section of this report.

2. Assessment Summary

The team at Halborn was provided 1 week and 2 days for the engagement and assigned a security engineer to evaluate the security of the smart contract.

The security engineer is a blockchain and smart-contract security expert with advanced penetration testing, smart-contract hacking, and deep knowledge of multiple blockchain protocols.

The purpose of this assessment is to:

    • Ensure that smart contract functions operate as intended.

    • Identify potential security issues with the smart contracts.


In summary, Halborn identified some improvements to reduce the likelihood and impact of risks, which were mostly acknowledged by the InFlux Technologies team. The main ones were the following: 

    • Add some checks for invalid time range.

    • Limit message length return.

    • Add some sanity checks.

3. Test Approach and Methodology

Halborn performed a combination of manual and automated security testing to balance efficiency, timeliness, practicality, and accuracy regarding the scope of this assessment. While manual testing is recommended to uncover flaws in logic, process, and implementation; automated testing techniques help enhance code coverage and quickly identify items that do not follow the security best practices. The following phases and associated tools were used during the assessment:

    • Research into architecture and purpose.

    • Smart contract manual code review and walkthrough.

    • Graphing out functionality and contract logic/connectivity/functions. (solgraph,draw.io)

    • Manual assessment of use and safety for the critical Solidity variables and functions in scope to identify any arithmetic related vulnerability classes.

    • Manual testing by custom scripts.

    • Static Analysis of security for scoped contract, and imported functions. (Slither)

    • Testnet deployment. (Hardhat,Foundry)

4. RISK METHODOLOGY

Every vulnerability and issue observed by Halborn is ranked based on two sets of Metrics and a Severity Coefficient. This system is inspired by the industry standard Common Vulnerability Scoring System.
The two Metric sets are: Exploitability and Impact. Exploitability captures the ease and technical means by which vulnerabilities can be exploited and Impact describes the consequences of a successful exploit.
The Severity Coefficients is designed to further refine the accuracy of the ranking with two factors: Reversibility and Scope. These capture the impact of the vulnerability on the environment as well as the number of users and smart contracts affected.
The final score is a value between 0-10 rounded up to 1 decimal place and 10 corresponding to the highest security risk. This provides an objective and accurate rating of the severity of security vulnerabilities in smart contracts.
The system is designed to assist in identifying and prioritizing vulnerabilities based on their level of risk to address the most critical issues in a timely manner.

4.1 EXPLOITABILITY

Attack Origin (AO):
Captures whether the attack requires compromising a specific account.
Attack Cost (AC):
Captures the cost of exploiting the vulnerability incurred by the attacker relative to sending a single transaction on the relevant blockchain. Includes but is not limited to financial and computational cost.
Attack Complexity (AX):
Describes the conditions beyond the attacker’s control that must exist in order to exploit the vulnerability. Includes but is not limited to macro situation, available third-party liquidity and regulatory challenges.
Metrics:
EXPLOITABILITY METRIC (mem_e)METRIC VALUENUMERICAL VALUE
Attack Origin (AO)Arbitrary (AO:A)
Specific (AO:S)
1
0.2
Attack Cost (AC)Low (AC:L)
Medium (AC:M)
High (AC:H)
1
0.67
0.33
Attack Complexity (AX)Low (AX:L)
Medium (AX:M)
High (AX:H)
1
0.67
0.33
Exploitability EE is calculated using the following formula:

E=meE = \prod m_e

4.2 IMPACT

Confidentiality (C):
Measures the impact to the confidentiality of the information resources managed by the contract due to a successfully exploited vulnerability. Confidentiality refers to limiting access to authorized users only.
Integrity (I):
Measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of data stored and/or processed on-chain. Integrity impact directly affecting Deposit or Yield records is excluded.
Availability (A):
Measures the impact to the availability of the impacted component resulting from a successfully exploited vulnerability. This metric refers to smart contract features and functionality, not state. Availability impact directly affecting Deposit or Yield is excluded.
Deposit (D):
Measures the impact to the deposits made to the contract by either users or owners.
Yield (Y):
Measures the impact to the yield generated by the contract for either users or owners.
Metrics:
IMPACT METRIC (mIm_I)METRIC VALUENUMERICAL VALUE
Confidentiality (C)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Integrity (I)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Availability (A)None (A:N)
Low (A:L)
Medium (A:M)
High (A:H)
Critical (A:C)
0
0.25
0.5
0.75
1
Deposit (D)None (D:N)
Low (D:L)
Medium (D:M)
High (D:H)
Critical (D:C)
0
0.25
0.5
0.75
1
Yield (Y)None (Y:N)
Low (Y:L)
Medium (Y:M)
High (Y:H)
Critical (Y:C)
0
0.25
0.5
0.75
1
Impact II is calculated using the following formula:

I=max(mI)+mImax(mI)4I = max(m_I) + \frac{\sum{m_I} - max(m_I)}{4}

4.3 SEVERITY COEFFICIENT

Reversibility (R):
Describes the share of the exploited vulnerability effects that can be reversed. For upgradeable contracts, assume the contract private key is available.
Scope (S):
Captures whether a vulnerability in one vulnerable contract impacts resources in other contracts.
Metrics:
SEVERITY COEFFICIENT (CC)COEFFICIENT VALUENUMERICAL VALUE
Reversibility (rr)None (R:N)
Partial (R:P)
Full (R:F)
1
0.5
0.25
Scope (ss)Changed (S:C)
Unchanged (S:U)
1.25
1
Severity Coefficient CC is obtained by the following product:

C=rsC = rs

The Vulnerability Severity Score SS is obtained by:

S=min(10,EIC10)S = min(10, EIC * 10)

The score is rounded up to 1 decimal places.
SeverityScore Value Range
Critical9 - 10
High7 - 8.9
Medium4.5 - 6.9
Low2 - 4.4
Informational0 - 1.9

5. SCOPE

Files and Repository
(a) Repository: account-abstraction
(b) Assessed Commit ID: 588c582
(c) Items in scope:
  • IMultiSigSmartAccount
  • IMultiSigSmartAccountFactory
  • interfaces/INonceManager
↓ Expand ↓
Out-of-Scope: Third Party Dependencies., Economic Attacks.
Out-of-Scope: New features/implementations after the remediation commit IDs.

6. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

0

Low

2

Informational

3

Security analysisRisk levelRemediation Date
Invalid Time Range can be returned by _intersectTimeRangeLowRisk Accepted - 01/08/2025
Unbounded Error Message Length in Paymaster External Calls Creates Gas UnpredictabilityLowNot Applicable
Missing sanity check for entryPointInformationalAcknowledged - 01/08/2025
Lack of Account Deployment Verification can lead to Unexpected Revert BehaviorInformationalAcknowledged - 01/08/2025
Overly Broad Unchecked Math Blocks in EntryPoint ContractInformationalAcknowledged - 01/08/2025

7. Findings & Tech Details

7.1 Invalid Time Range can be returned by _intersectTimeRange

// Low

Description

The _intersectTimeRange function in the Helper library contains a logical flaw in its time range validation mechanism. When intersecting time ranges between account and paymaster validations, the function fails to verify if the resulting time window is valid. The issue occurs in the following sequence:

  • The function takes the maximum value between account's and paymaster's validAfter 

  • The function takes the minimum value between account's and paymaster's validUntil 

  • No validation exists to ensure final validAfter <= validUntil

    // intersect account and paymaster ranges.
    function _intersectTimeRange(
        uint256 validationData,
        uint256 paymasterValidationData
    ) internal pure returns (ValidationData memory) {
        ValidationData memory accountValidationData = _parseValidationData(validationData);
        ValidationData memory pmValidationData = _parseValidationData(paymasterValidationData);
        address aggregator = accountValidationData.aggregator;
        if (aggregator == address(0)) {
            aggregator = pmValidationData.aggregator;
        }
        uint48 validAfter = accountValidationData.validAfter;
        uint48 validUntil = accountValidationData.validUntil;
        uint48 pmValidAfter = pmValidationData.validAfter;
        uint48 pmValidUntil = pmValidationData.validUntil;

        if (validAfter < pmValidAfter) validAfter = pmValidAfter;
        if (validUntil > pmValidUntil) validUntil = pmValidUntil;

        // @tocheck missing validation to ensure validAfter <= validUntil

        return ValidationData(aggregator, validAfter, validUntil);
    }

It could result in :

  • Invalid time ranges being accepted by the system

  • Broken time-based validation mechanisms

Proof of Concept

This POC can be reproduced using this code :

import { expect } from "chai";
import { ethers } from "hardhat";
import { HelperTest } from "../contracts/HelperTest.sol"; // Adjust import path as needed

describe("Helper", () => {
  let helperTest: HelperTest;

  beforeEach(async () => {
    const Helper = await ethers.getContractFactory("HelperTest");
    helperTest = await Helper.deploy();
  });

  it("should demonstrate validAfter > validUntil in _intersectTimeRange", async () => {
    // Create validation data where account is valid from t=100 to t=200
    const accountValidationData = helperTest.packValidationData({
      aggregator: ethers.ZeroAddress,
      validAfter: 100n,
      validUntil: 200n
    });

    // Create paymaster validation data where paymaster is valid from t=150 to t=120 (ERROR INTRODUCED)
    const paymasterValidationData = helperTest.packValidationData({
      aggregator: ethers.ZeroAddress,
      validAfter: 150n,
      validUntil: 120n
    });

    // Intersect the time ranges
    const result = await helperTest.intersectTimeRange(
      accountValidationData,
      paymasterValidationData
    );

    const validAfter = Number(result.validAfter);
    const validUntil = Number(result.validUntil);

    // validAfter (150) is greater than validUntil (120)
    expect(validAfter).to.be.greaterThan(validUntil);
    expect(validAfter).to.equal(150);
    expect(validUntil).to.equal(120);

    console.log(`ValidAfter: ${validAfter}, ValidUntil: ${validUntil}`);
  });
});

Result :

account-abstraction git:(master) ✗ npx hardhat test --grep "Helper"

  Helper
ValidAfter: 150, ValidUntil: 120
    ✔ should demonstrate validAfter > validUntil in _intersectTimeRange

  1 passing (979ms)
BVSS
Recommendation

It is recommended to add another validation check after the time range intersection and mark the validation as failed when an invalid range is detected.

Remediation

RISK ACCEPTED: An updated will be used for new chains. However, Influx will be using the current deployed on chain version for chains where is it possible.

References

7.2 Unbounded Error Message Length in Paymaster External Calls Creates Gas Unpredictability

// Low

Description

The EntryPoint contract handles error messages from paymaster calls without imposing size limits. This occurs in two critical locations where external calls to paymaster contracts are made: _validatePaymasterPrepayment and _handlePostOp. First instance in _validatePaymasterPrepayment:

function _validatePaymasterPrepayment(
    uint256 opIndex,
    UserOperation calldata op,
    UserOpInfo memory opInfo,
    uint256 requiredPreFund,
    uint256 gasUsedByValidateAccountPrepayment
) internal returns (bytes memory context, uint256 validationData) {
    // .... // 
    try IPaymaster(paymaster).validatePaymasterUserOp{gas: gas}(op, opInfo.userOpHash, requiredPreFund)
        returns (bytes memory _context, uint256 _validationData) {
            context = _context;
            validationData = _validationData;
    } catch Error(string memory revertReason) {
        revert FailedOp(opIndex, string.concat("AA33 reverted: ", revertReason));
    }
}

Second instance in _handlePostOp:

function _handlePostOp(
    uint256 opIndex,
    IPaymaster.PostOpMode mode,
    UserOpInfo memory opInfo,
    bytes memory context,
    uint256 actualGas
) private returns (uint256 actualGasCost) {
    // ... //
    try IPaymaster(paymaster).postOp{gas: mUserOp.verificationGasLimit}(mode, context, actualGasCost)
    {} catch Error(string memory reason) {
        revert FailedOp(opIndex, string.concat("AA50 postOp reverted: ", reason));
    }
}

While the contract defines REVERT_REASON_MAX_LEN = 2048, this limit is not enforced when handling these paymaster error messages.

Report as informational as it's paymaster handling but there are several impacts here :

  • Gas consumption becomes unpredictable when copying large error messages to memory

  • Malicious paymasters can force excessive gas consumption through large error messages

  • Bundle execution can fail unexpectedly due to out-of-gas conditions

BVSS
Recommendation

It is recommended to implement error message length limits using the existing REVERT_REASON_MAX_LEN constant.

Remediation

NOT APPLICABLE: Non concerning because the current deployed files does not use the affected code.

References

7.3 Missing sanity check for entryPoint

// Informational

Description

The BasePaymaster contract lacks interface validation for the _entryPoint parameter in its constructor. The current implementation directly sets the EntryPoint address without verifying if it implements a matching IEntryPoint interface.

constructor(IEntryPoint _entryPoint, address _owner) Ownable(_owner) {

	//E @tocheck missing sanity check for _entryPoint

	setEntryPoint(_entryPoint);
}
Score
Recommendation

It is recommended to implement the same mechanism as it is done in eth-infinitism development branch repository which includes proper validation with an interface to be implemented:

constructor(IEntryPoint _entryPoint) Ownable(msg.sender) {

	_validateEntryPointInterface(_entryPoint);
	
	entryPoint = _entryPoint;

}

function _validateEntryPointInterface(IEntryPoint _entryPoint) internal virtual {
   require(IERC165(address(_entryPoint)).supportsInterface(type(IEntryPoint).interfaceId), "IEntryPoint interface mismatch");
}
Remediation

ACKNOWLEDGED: An on chain version of this file will be used instead of the one in the repository, but the project does not use the affected code.

References

7.4 Lack of Account Deployment Verification can lead to Unexpected Revert Behavior

// Informational

Description

The _validateAccountPrepayment function in EntryPoint.sol fails to verify the existence of code at the sender's address when initCode is empty. While the function deploys new accounts when initCode is provided, it assumes pre-existing accounts are already deployed without verification.

function _validateAccountPrepayment(
		uint256 opIndex,
		UserOperation calldata op,
		UserOpInfo memory opInfo,
		uint256 requiredPrefund
) internal returns (uint256 gasUsedByValidateAccountPrepayment, uint256 validationData) {
		unchecked {
			MemoryUserOp memory mUserOp = opInfo.mUserOp;
			address sender = mUserOp.sender;
			_createSenderIfNeeded(opIndex, opInfo, op.initCode);
			// Missing verification of sender.code.length
			try IAccount(sender).validateUserOp{gas: mUserOp.verificationGasLimit}(op,opInfo.userOpHash,missingAccountFunds)
			// ... // 

The issue occurs because the subsequent call to validateUserOp will revert at the EVM level if no code exists at the sender's address. This revert happens before entering the try-catch block, preventing the emission of the expected FailedOp error.


The risk is that Bundlers receive unclear error feedback when processing transactions for non-deployed accounts.

Score
Recommendation

It is recommended to add an explicit deployment check after _createSenderIfNeeded to ensure consistent error handling and provide clear feedback about account deployment status.

Remediation

ACKNOWLEDGED: An on chain version of this file will be used instead of the one in the repository, but the project does not use the affected code.

References

7.5 Overly Broad Unchecked Math Blocks in EntryPoint Contract

// Informational

Description

The EntryPoint contract contains multiple functions where entire function bodies are wrapped in unchecked blocks, rather than limiting these blocks to specific arithmetic operations that require them. This pattern is observed in critical functions including _validateAccountPrepayment_validatePaymasterPrepayment, and _execute instances:

function _validateAccountPrepayment(
    uint256 opIndex,
    UserOperation calldata op,
    UserOpInfo memory opInfo,
    uint256 requiredPrefund
) internal returns (uint256 gasUsedByValidateAccountPrepayment, uint256 validationData) {

    unchecked {
        // Entire function body (50+ lines) within unchecked block
        uint256 preGas = gasleft();
        // ... many operations that don't require unchecked math
        gasUsedByValidateAccountPrepayment = preGas - gasleft();
    }

}

function _validatePaymasterPrepayment(
    uint256 opIndex,
    UserOperation calldata op,
    UserOpInfo memory opInfo,
    uint256 requiredPreFund,
    uint256 gasUsedByValidateAccountPrepayment
) internal returns (bytes memory context, uint256 validationData) {
   
    unchecked {
        // Entire function body within unchecked block
        // ... including operations that don't require unchecked math
        paymasterInfo.deposit = uint112(deposit - requiredPreFund);
    }

}

Impacts:

  • Reduced code clarity and increased review complexity

  • Higher risk of arithmetic overflow/underflow vulnerabilities

Score
Recommendation

It is recommended to restrict unchecked blocks to specific arithmetic operations where overflow/underflow checks are unnecessary.

Remediation

ACKNOWLEDGED: An on chain version of this file will be used instead of the one in the repository, but the project does not use the affected code.

References

8. Automated Testing

Halborn used automated testing techniques to enhance the coverage of certain areas of the smart contracts in scope. Among the tools used was Slither, a Solidity static analysis framework.

After Halborn verified the smart contracts in the repository and was able to compile them correctly into their abis and binary format, Slither was run against the contracts. This tool can statically verify mathematical relationships between Solidity variables to detect invalid or inconsistent usage of the contracts' APIs across the entire code-base.

slither-1.pngslither-2.pngslither-3.png

All issues identified by Slither were proved to be false positives or have been added to the issue list in this report.

Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.

© Halborn 2024. All rights reserved.