ic-evm-sign 0.1.3

This library enables Internet Computer canisters to sign transactions for EVM-compatible blockchains
Documentation
use easy_hasher::easy_hasher;
use ic_cdk::export::Principal;

pub fn get_derivation_path(caller: Principal) -> Vec<u8> {
    caller.as_slice().to_vec()
}

pub fn get_address_from_public_key(public_key: Vec<u8>) -> Result<String, String> {
    if public_key.len() != 33 {
        return Err("Invalid length of public key".to_string());
    }

    let pub_key_arr: [u8; 33] = public_key[..].try_into().unwrap();
    let pub_key = libsecp256k1::PublicKey::parse_compressed(&pub_key_arr)
        .map_err(|e| format!("{}", e))?
        .serialize();

    let keccak256 = easy_hasher::raw_keccak256(pub_key[1..].to_vec());
    let keccak256_hex = keccak256.to_hex_string();
    let address: String = "0x".to_owned() + &keccak256_hex[24..];

    Ok(address)
}

pub fn get_transfer_data(address: &str, amount: u64) -> Result<String, String> {
    if address.len() != 42 {
        return Err("Invalid address".to_string());
    }
    let method_sig = "transfer(address,uint256)";
    let keccak256 = easy_hasher::raw_keccak256(method_sig.as_bytes().to_vec());
    let method_id = &keccak256.to_hex_string()[..8];

    let address_64 = format!("{:0>64}", &address[2..]);

    let amount_hex = format!("{:02x}", amount);
    let amount_64 = format!("{:0>64}", amount_hex);

    Ok(method_id.to_owned() + &address_64 + &amount_64)
}

pub fn string_to_vec_u8(str: &str) -> Vec<u8> {
    let starts_from: usize;
    if str.starts_with("0x") {
        starts_from = 2;
    } else {
        starts_from = 0;
    }

    (starts_from..str.len())
        .step_by(2)
        .map(|i| u8::from_str_radix(&str[i..i + 2], 16).unwrap())
        .collect::<Vec<u8>>()
}

pub fn remove_leading(vec: Vec<u8>, element: u8) -> Vec<u8> {
    let start = vec.iter().position(|&x| x != element).unwrap();
    let result = &vec[start..];
    result.to_vec()
}

pub fn u64_to_vec_u8(u: &u64) -> Vec<u8> {
    u.to_be_bytes()
        .into_iter()
        .skip_while(|&x| x == 0)
        .collect()
}

pub fn vec_u8_to_string(vec: &Vec<u8>) -> String {
    vec.iter()
        .map(|r| format!("{:02x}", r))
        .collect::<Vec<String>>()
        .join("")
        .to_string()
}

pub fn vec_u8_to_u64(vec: &Vec<u8>) -> u64 {
    let mut _vec = [0; 8];
    _vec[8 - vec.len()..].copy_from_slice(&vec);
    u64::from_be_bytes(_vec).try_into().unwrap()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn get_address_from_public_key_valid() {
        let expected = "0x907dc4d0be5d691970cae886fcab34ed65a2cd66";
        let public_key_str = "02c397f23149d3464517d57b7cdc8e287428407f9beabfac731e7c24d536266cd1";
        let public_key_to_vec = string_to_vec_u8(&public_key_str);
        let result = get_address_from_public_key(public_key_to_vec).unwrap();
        assert_eq!(result, expected);
    }

    #[test]
    fn get_address_from_public_with_zeros() {
        let expected = Err("Invalid public key".to_string());
        let public_key_str = "000000000000000000000000000000000000000000000000000000000000000000";
        let public_key_to_vec = string_to_vec_u8(&public_key_str);
        let result = get_address_from_public_key(public_key_to_vec);
        assert_eq!(result, expected);
    }

    #[test]
    fn get_address_from_public_with_empty_public_key() {
        let expected = Err("Invalid length of public key".to_string());
        let public_key_str = "";
        let public_key_to_vec = string_to_vec_u8(&public_key_str);
        let result = get_address_from_public_key(public_key_to_vec);
        assert_eq!(result, expected);
    }

    #[test]
    fn get_transfer_data_valid() {
        let expected ="a9059cbb000000000000000000000000907dc4d0be5d691970cae886fcab34ed65a2cd660000000000000000000000000000000000000000000000000000000000000001";

        let address = "0x907dc4d0be5d691970cae886fcab34ed65a2cd66";
        let amount = 1;
        let result = get_transfer_data(address, amount).unwrap();
        assert_eq!(result, expected);
    }

    #[test]
    fn get_transfer_data_with_invalid_address() {
        let expected = Err("Invalid address".to_string());
        let address = "0x00";
        let value = 1;
        let result = get_transfer_data(address, value);
        assert_eq!(result, expected);
    }
}