#![allow(unused)] use trezor_client::client::{AccessListItem as Trezor_AccessListItem, Trezor}; use futures_executor::block_on; use futures_util::lock::Mutex; use ethers_core::{ types::{ transaction::{eip2718::TypedTransaction, eip712::Eip712}, Address, NameOrAddress, Signature, Transaction, TransactionRequest, TxHash, H256, U256, }, utils::keccak256, }; use std::convert::TryFrom; use thiserror::Error; use super::types::*; /// A Trezor Ethereum App. /// /// This is a simple wrapper around the [Trezor transport](Trezor) #[derive(Debug)] pub struct TrezorEthereum { derivation: DerivationType, session_id: Vec, pub(crate) chain_id: u64, pub(crate) address: Address, } const FIRMWARE_MIN_VERSION: &str = ">=2.4.2"; impl TrezorEthereum { pub async fn new(derivation: DerivationType, chain_id: u64) -> Result { let mut blank = Self { derivation: derivation.clone(), chain_id, address: Address::from([0_u8; 20]), session_id: vec![], }; // Check if reachable blank.initate_session()?; blank.address = blank.get_address_with_path(&derivation).await?; Ok(blank) } fn check_version(version: String) -> Result<(), TrezorError> { let req = semver::VersionReq::parse(FIRMWARE_MIN_VERSION)?; let version = semver::Version::parse(&version)?; // Enforce firmware version is greater than FIRMWARE_MIN_VERSION if !req.matches(&version) { return Err(TrezorError::UnsupportedFirmwareVersion(FIRMWARE_MIN_VERSION.to_string())) } Ok(()) } fn initate_session(&mut self) -> Result<(), TrezorError> { let mut client = trezor_client::unique(false)?; client.init_device(None)?; let features = client.features().ok_or(TrezorError::FeaturesError)?; Self::check_version(format!( "{}.{}.{}", features.get_major_version(), features.get_minor_version(), features.get_patch_version() ))?; self.session_id = features.get_session_id().to_vec(); Ok(()) } /// You need to drop(client) once you're done with it fn get_client(&self, session_id: Vec) -> Result { let mut client = trezor_client::unique(false)?; client.init_device(Some(session_id))?; Ok(client) } /// Get the account which corresponds to our derivation path pub async fn get_address(&self) -> Result { Ok(self.get_address_with_path(&self.derivation).await?) } /// Gets the account which corresponds to the provided derivation path pub async fn get_address_with_path( &self, derivation: &DerivationType, ) -> Result { let mut client = self.get_client(self.session_id.clone())?; let address_str = client.ethereum_get_address(Self::convert_path(derivation))?; let mut address = [0; 20]; address.copy_from_slice(&hex::decode(&address_str[2..])?); Ok(Address::from(address)) } /// Signs an Ethereum transaction (requires confirmation on the Trezor) pub async fn sign_tx(&self, tx: &TypedTransaction) -> Result { let mut client = self.get_client(self.session_id.clone())?; let arr_path = Self::convert_path(&self.derivation); let transaction = TrezorTransaction::load(tx)?; let signature = match tx { TypedTransaction::Eip2930(_) | TypedTransaction::Legacy(_) => client.ethereum_sign_tx( arr_path, transaction.nonce, transaction.gas_price, transaction.gas, transaction.to, transaction.value, transaction.data, self.chain_id, )?, TypedTransaction::Eip1559(eip1559_tx) => client.ethereum_sign_eip1559_tx( arr_path, transaction.nonce, transaction.gas, transaction.to, transaction.value, transaction.data, self.chain_id, transaction.max_fee_per_gas, transaction.max_priority_fee_per_gas, transaction.access_list, )?, }; Ok(Signature { r: signature.r, s: signature.s, v: signature.v }) } /// Signs an ethereum personal message pub async fn sign_message>(&self, message: S) -> Result { let message = message.as_ref(); let mut client = self.get_client(self.session_id.clone())?; let apath = Self::convert_path(&self.derivation); let signature = client.ethereum_sign_message(message.into(), apath)?; Ok(Signature { r: signature.r, s: signature.s, v: signature.v }) } /// Signs an EIP712 encoded domain separator and message pub async fn sign_typed_struct(&self, payload: &T) -> Result where T: Eip712, { unimplemented!() } // helper which converts a derivation path to [u32] fn convert_path(derivation: &DerivationType) -> Vec { let derivation = derivation.to_string(); let elements = derivation.split('/').skip(1).collect::>(); let depth = elements.len(); let mut path = vec![]; for derivation_index in elements { let hardened = derivation_index.contains('\''); let mut index = derivation_index.replace('\'', "").parse::().unwrap(); if hardened { index |= 0x80000000; } path.push(index); } path } } #[cfg(all(test, feature = "trezor"))] mod tests { use super::*; use crate::Signer; use ethers_contract::EthAbiType; use ethers_core::types::{ transaction::{ eip2930::{AccessList, AccessListItem}, eip712::Eip712, }, Address, Eip1559TransactionRequest, TransactionRequest, I256, U256, }; use ethers_derive_eip712::*; use std::str::FromStr; #[derive(Debug, Clone, Eip712, EthAbiType)] #[eip712( name = "Eip712Test", version = "1", chain_id = 1, verifying_contract = "0x0000000000000000000000000000000000000001", salt = "eip712-test-75F0CCte" )] struct FooBar { foo: I256, bar: U256, fizz: Vec, buzz: [u8; 32], far: String, out: Address, } #[tokio::test] #[ignore] // Replace this with your ETH addresses. async fn test_get_address() { // Instantiate it with the default trezor derivation path let trezor = TrezorEthereum::new(DerivationType::TrezorLive(1), 1).await.unwrap(); assert_eq!( trezor.get_address().await.unwrap(), "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap() ); assert_eq!( trezor.get_address_with_path(&DerivationType::TrezorLive(0)).await.unwrap(), "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap() ); } #[tokio::test] #[ignore] async fn test_sign_tx() { let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap(); // approve uni v2 router 0xff let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(); let tx_req = TransactionRequest::new() .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::
().unwrap()) .gas(1000000) .gas_price(400e9 as u64) .nonce(5) .data(data) .value(ethers_core::utils::parse_ether(100).unwrap()) .into(); let tx = trezor.sign_transaction(&tx_req).await.unwrap(); } #[tokio::test] #[ignore] async fn test_sign_big_data_tx() { let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap(); // invalid data let big_data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string()+ &"ff".repeat(1032*2) + &"aa".to_string()).unwrap(); let tx_req = TransactionRequest::new() .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::
().unwrap()) .gas(1000000) .gas_price(400e9 as u64) .nonce(5) .data(big_data) .value(ethers_core::utils::parse_ether(100).unwrap()) .into(); let tx = trezor.sign_transaction(&tx_req).await.unwrap(); } #[tokio::test] #[ignore] async fn test_sign_empty_txes() { let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap(); { let tx_req = Eip1559TransactionRequest::new().into(); let tx = trezor.sign_transaction(&tx_req).await.unwrap(); } { let tx_req = TransactionRequest::new().into(); let tx = trezor.sign_transaction(&tx_req).await.unwrap(); } } #[tokio::test] #[ignore] async fn test_sign_eip1559_tx() { let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap(); // approve uni v2 router 0xff let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(); let lst = AccessList(vec![ AccessListItem { address: "0x8ba1f109551bd432803012645ac136ddd64dba72".parse().unwrap(), storage_keys: vec![ "0x0000000000000000000000000000000000000000000000000000000000000000" .parse() .unwrap(), "0x0000000000000000000000000000000000000000000000000000000000000042" .parse() .unwrap(), ], }, AccessListItem { address: "0x2ed7afa17473e17ac59908f088b4371d28585476".parse().unwrap(), storage_keys: vec![ "0x0000000000000000000000000000000000000000000000000000000000000000" .parse() .unwrap(), "0x0000000000000000000000000000000000000000000000000000000000000042" .parse() .unwrap(), ], }, ]); let tx_req = Eip1559TransactionRequest::new() .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::
().unwrap()) .gas(1000000) .max_fee_per_gas(400e9 as u64) .max_priority_fee_per_gas(400e9 as u64) .nonce(5) .data(data) .access_list(lst) .value(ethers_core::utils::parse_ether(100).unwrap()) .into(); let tx = trezor.sign_transaction(&tx_req).await.unwrap(); } #[tokio::test] #[ignore] async fn test_sign_message() { let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap(); let message = "hello world"; let sig = trezor.sign_message(message).await.unwrap(); let addr = trezor.get_address().await.unwrap(); sig.verify(message, addr).unwrap(); } #[tokio::test] #[ignore] async fn test_sign_eip712_struct() { let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1u64).await.unwrap(); let foo_bar = FooBar { foo: I256::from(10), bar: U256::from(20), fizz: b"fizz".to_vec(), buzz: keccak256("buzz"), far: String::from("space"), out: Address::from([0; 20]), }; let sig = trezor.sign_typed_struct(&foo_bar).await.expect("failed to sign typed data"); let foo_bar_hash = foo_bar.encode_eip712().unwrap(); sig.verify(foo_bar_hash, trezor.address).unwrap(); } }