Fuel logo

Fuel Labs Connectors Predicates Security Review Report

September 2024

Summary

Total number of findings
2

Weaknesses

This section contains the list of discovered weaknesses.

FUEL7-11 | CONNECTOR PREDICATE MAY REVERT INSTEAD OF RETURNING FALSE IF THE WITNESS INDEX IS GREATER OR EQUAL TO THE WITNESS COUNT

Severity:

Informational

Status:

Acknowledged

Description:

The fn main() from evm-predicates and solana-predicates may revert if the witness_index is greater or equal to the witness count.

fn main(witness_index: u64) -> bool {
    // Retrieve the Ethereum signature from the witness data in the Tx at the specified index.
    let signature: B512 = tx_witness_data(witness_index).unwrap();
fn main(witness_index: u64) -> bool {
    let signature: B512 = tx_witness_data(witness_index).unwrap();

The function tx.sw::tx_witness_data() may return None if the witness index is >= tx_witnesses_count().

pub fn tx_witness_data<T>(index: u64) -> Option<T> {
    if index >= tx_witnesses_count() {
        return None
    }

In that case where tx.sw::tx_witness_data() returns None, option.sw::unwrap() is called on None, and as a result it will revert with panic.

pub fn unwrap(self) -> T {
    match self {
        Self::Some(inner_value) => inner_value,
        _ => revert(0),
    }

Remediation:

Consider catching None before unwrapping and return false in this case. This approach may be better because it will provide better information for this case.

FUEL7-12 THE SIGNATURES ARE NOT USER-FRIENDLY

Severity:

Informational

Status:

Acknowledged

Description:

Both predicates accept signatures in the form of:

  • Ethereum: the prefix + tx id (32 bytes hash)
  • Solana: tx id (32 bytes hash) translated to ASCII This means that users will not be able to visually verify the validity of the data they are about to sign if they don't manually compute and match the tx id.

Modern signature schemes usually contain transaction details in plain text (e.g., EIP-712) to eliminate any risks of blind signatures.

packages/evm-predicates/predicate/src/main.sw#L62-L98

fn personal_sign_hash(transaction_id: b256) -> b256 {
    // Hack, allocate memory to reduce manual `asm` code.
    let data = SignedData {
        transaction_id,
        ethereum_prefix: ETHEREUM_PREFIX,
        empty: ZERO_B256,
    };
 
    // Pointer to the data we have signed external to Sway.
    let data_ptr = asm(ptr: data.transaction_id) {
        ptr
    };
 
    // The Ethereum prefix is 28 bytes (plus padding we exclude).
    // The Tx ID is 32 bytes at the end of the prefix.
    let len_to_hash = 28 + 32;
 
    // Create a buffer in memory to overwrite with the result being the hash.
    let mut buffer = b256::min();
 
    // Copy the Tx ID to the end of the prefix and hash the exact len of the prefix and id (without
    // the padding at the end because that would alter the hash).
    asm(
        hash: buffer,
        tx_id: data_ptr,
        end_of_prefix: data_ptr + len_to_hash,
        prefix: data.ethereum_prefix,
        id_len: 32,
        hash_len: len_to_hash,
    ) {
        mcp end_of_prefix tx_id id_len;
        k256 hash prefix hash_len;
    }
 
    // The buffer contains the hash.
    buffer
}

packages/solana-connector/predicate/src/main.sw#L25-L55

pub fn b256_to_ascii_bytes(val: b256) -> Bytes {
    let bytes = Bytes::from(val);
    let mut ascii_bytes = Bytes::with_capacity(64);
    let mut idx = 0;
 
    while idx < 32 {
        let b = bytes.get(idx).unwrap();
        ascii_bytes.push(ASCII_MAP[(b >> 4).as_u64()]);
        ascii_bytes.push(ASCII_MAP[(b & 15).as_u64()]);
        idx = idx + 1;
    }
 
    ascii_bytes
}
 
configurable {
    SIGNER: b256 = ZERO_B256,
}
 
fn main(witness_index: u64) -> bool {
    let signature: B512 = tx_witness_data(witness_index).unwrap();
    let encoded = b256_to_ascii_bytes(tx_id());
    let result = ed_verify(SIGNER, signature, encoded);
 
    if result.is_ok() {
        return true;
    }
 
    // Otherwise, an invalid signature has been passed and we invalidate the Tx.
    false
}

Remediation:

We recommend using a more explicit signature scheme containing the fields of a fuel transaction in plain text.

Table of contents