code-423n4 / 2024-03-zksync-findings

1 stars 1 forks source link

`TransactionHelper.sol` violates EIP-712 #43

Closed c4-bot-3 closed 4 months ago

c4-bot-3 commented 5 months ago

Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/libraries/TransactionHelper.sol#L84-L87 https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/libraries/TransactionHelper.sol#L25-L71 https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/libraries/TransactionHelper.sol#L118-L136

Vulnerability details

Impact

According to EIP-712 spec, the typehash should contain all the fields defined in the struct. The current implementation of EIP712_TRANSACTION_TYPE_HASH, however, misses the signature field.

Proof of Concept

File: TransactionHelper.sol

bytes32 constant EIP712_TRANSACTION_TYPE_HASH =
        keccak256(
            "Transaction(uint256 txType,uint256 from,uint256 to,uint256 gasLimit,uint256 gasPerPubdataByteLimit,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,uint256 paymaster,uint256 nonce,uint256 value,bytes data,bytes32[] factoryDeps,bytes paymasterInput)"
        );

EIP712_TRANSACTION_TYPE_HASH is defined as above.

File: TransactionHelper.sol

struct Transaction {
    // The type of the transaction.
    uint256 txType;
    // The caller.
    uint256 from;
    // The callee.
    uint256 to;
    // The gasLimit to pass with the transaction.
    // It has the same meaning as Ethereum's gasLimit.
    uint256 gasLimit;
    // The maximum amount of gas the user is willing to pay for a byte of pubdata.
    uint256 gasPerPubdataByteLimit;
    // The maximum fee per gas that the user is willing to pay.
    // It is akin to EIP1559's maxFeePerGas.
    uint256 maxFeePerGas;
    // The maximum priority fee per gas that the user is willing to pay.
    // It is akin to EIP1559's maxPriorityFeePerGas.
    uint256 maxPriorityFeePerGas;
    // The transaction's paymaster. If there is no paymaster, it is equal to 0.
    uint256 paymaster;
    // The nonce of the transaction.
    uint256 nonce;
    // The value to pass with the transaction.
    uint256 value;
    // In the future, we might want to add some
    // new fields to the struct. The `txData` struct
    // is to be passed to account and any changes to its structure
    // would mean a breaking change to these accounts. In order to prevent this,
    // we should keep some fields as "reserved".
    // It is also recommended that their length is fixed, since
    // it would allow easier proof integration (in case we will need
    // some special circuit for preprocessing transactions).
    uint256[4] reserved;
    // The transaction's calldata.
    bytes data;
    // The signature of the transaction.
    bytes signature;
    // The properly formatted hashes of bytecodes that must be published on L1
    // with the inclusion of this transaction. Note, that a bytecode has been published
    // before, the user won't pay fees for its republishing.
    bytes32[] factoryDeps;
    // The input to the paymaster.
    bytes paymasterInput;
    // Reserved dynamic type for the future use-case. Using it should be avoided,
    // But it is still here, just in case we want to enable some additional functionality.
    bytes reservedDynamic;
}

While struct Transaction is defined as above.

We can clearly see, that signature field defined in Transaction struct is not defined in the EIP712_TRANSACTION_TYPE_HASH.

Moreover, when we'll examine _encodeHashEIP712Transaction() function - the signature is also missing there.

File: TransactionHelper.sol

function _encodeHashEIP712Transaction(Transaction calldata _transaction) private view returns (bytes32) {
        bytes32 structHash = keccak256(
            abi.encode(
                EIP712_TRANSACTION_TYPE_HASH,
                _transaction.txType,
                _transaction.from,
                _transaction.to,
                _transaction.gasLimit,
                _transaction.gasPerPubdataByteLimit,
                _transaction.maxFeePerGas,
                _transaction.maxPriorityFeePerGas,
                _transaction.paymaster,
                _transaction.nonce,
                _transaction.value,
                EfficientCall.keccak(_transaction.data),
                keccak256(abi.encodePacked(_transaction.factoryDeps)),
                EfficientCall.keccak(_transaction.paymasterInput)
            )
        );

Tools Used

Manual code review

Recommended Mitigation Steps

Add missing signature field.

Assessed type

Other

c4-sponsor commented 5 months ago

saxenism (sponsor) disputed

saxenism commented 5 months ago

Considered invalid because the signed version can not contain signature.

alex-ppg commented 4 months ago

The Warden specifies that the TransactionHelper violates the EIP-712 specification; this is incorrect as the specification does not concern itself with the actual underlying data structure; only with the arguments hashed and the type-hash utilized alongside those arguments.

As the type-hash matches the arguments hashed in the TransactionHelper::_encodeHashEIP712Transaction function, no incompatibility with the standard is observed. Moreover, introducing the signature as part of both the type-hash and the aforementioned function is incorrect given that a signature cannot sign itself.

c4-judge commented 4 months ago

alex-ppg marked the issue as unsatisfactory: Invalid