Points Program: Earn rewards and contribute to the Fuel Network.Learn more →

Account

0x960CB71dA63191Cc1d285f1C6D4D0222Bc0990548445D0452975c1638bA7FEA50x960C...FEA5

Ethereum Connector
This connector supports EVM compatible EOA accounts such as MetaMask and Rabby.

Source Code
predicate;

use std::{
    b512::B512,
    bytes::Bytes,
    constants::ZERO_B256,
    tx::{
        tx_id,
        tx_witness_data,
    },
    vm::evm::{
        ecr::ec_recover_evm_address,
    },
};

/// Personal sign prefix for Ethereum inclusive of the 32 bytes for the length of the Tx ID.
///
/// # Additional Information
///
/// Take "\x19Ethereum Signed Message:\n32" and converted to hex.
/// The 00000000 at the end is the padding added by Sway to fill the word.
const ETHEREUM_PREFIX = 0x19457468657265756d205369676e6564204d6573736167653a0a333200000000;

struct SignedData {
    /// The id of the transaction to be signed.
    transaction_id: b256,
    /// EIP-191 personal sign prefix.
    ethereum_prefix: b256,
    /// Additional data used for reserving memory for hashing (hack).
    #[allow(dead_code)]
    empty: b256,
}

configurable {
    /// The Ethereum address that signed the transaction.
    SIGNER: b256 = ZERO_B256,
}

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();

    // Hash the Fuel Tx (as the signed message) and attempt to recover the signer from the signature.
    let result = ec_recover_evm_address(signature, personal_sign_hash(tx_id()));

    // If the signers match then the predicate has validated the Tx.
    if result.is_ok() {
        if SIGNER == result.unwrap().into() {
            return true;
        }
    }

    // Otherwise, an invalid signature has been passed and we invalidate the Tx.
    false
}

/// Return the Keccak-256 hash of the transaction ID in the format of EIP-191.
///
/// # Arguments
///
/// * `transaction_id`: [b256] - Fuel Tx ID.
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
}
Bytecode
0x1a403000504100301a445000ba49000032400481504100205d490000504100083240048220451300524510044a440000c58d08df0d31dfaaadd57de9f04d89b7abc88b28a2f45f522cc5c1fd8da679780000000000000088000000000000000000000000c2f309ec177b1d6044373cb969a27db5a1bc06fb19457468657265756d205369676e6564204d6573736167653a0a3332000000000000000000000000000000000000000000000000000000000000000000000000cccccccccccc00020000000000000b5c0000000000000b740000000000000b6c0000000000000680