From c6b51e3ae0826f5c5cd1605bfd020a3e5054581a Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 30 Dec 2021 01:38:29 +0200 Subject: [PATCH] feat/trezor: cache session on filesystem (#747) * save and read from cache * fix * read/write from/to file directly * add cache_dir to TrezorEthereum * fmt & clippy --- Cargo.lock | 1 + ethers-signers/Cargo.toml | 1 + ethers-signers/src/trezor/app.rs | 78 +++++++++++++++++++++++++----- ethers-signers/src/trezor/types.rs | 2 + 4 files changed, 70 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d65b683..f0914d35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1264,6 +1264,7 @@ dependencies = [ "futures-executor", "futures-util", "hex", + "home", "rand 0.8.4", "rusoto_core", "rusoto_kms", diff --git a/ethers-signers/Cargo.toml b/ethers-signers/Cargo.toml index 25306d18..020c6020 100644 --- a/ethers-signers/Cargo.toml +++ b/ethers-signers/Cargo.toml @@ -39,6 +39,7 @@ spki = { version = "0.5.2", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] eth-keystore = { version = "0.3.0" } +home = "0.5.3" [dev-dependencies] ethers-contract = { version = "^0.6.0", path = "../ethers-contract", features = ["eip712", "abigen"]} diff --git a/ethers-signers/src/trezor/app.rs b/ethers-signers/src/trezor/app.rs index 74768090..6282aac0 100644 --- a/ethers-signers/src/trezor/app.rs +++ b/ethers-signers/src/trezor/app.rs @@ -11,7 +11,14 @@ use ethers_core::{ }, utils::keccak256, }; -use std::convert::TryFrom; +use home; +use std::{ + convert::TryFrom, + env, fs, + io::{Read, Write}, + path, + path::PathBuf, +}; use thiserror::Error; use super::types::*; @@ -23,17 +30,38 @@ use super::types::*; pub struct TrezorEthereum { derivation: DerivationType, session_id: Vec, + cache_dir: PathBuf, pub(crate) chain_id: u64, pub(crate) address: Address, } const FIRMWARE_MIN_VERSION: &str = ">=2.4.2"; +// https://docs.trezor.io/trezor-firmware/common/communication/sessions.html +const SESSION_ID_LENGTH: usize = 32; +const SESSION_FILE_NAME: &str = "trezor.session"; + impl TrezorEthereum { - pub async fn new(derivation: DerivationType, chain_id: u64) -> Result { + pub async fn new( + derivation: DerivationType, + chain_id: u64, + cache_dir: Option, + ) -> Result { + let cache_dir = (match cache_dir.or_else(home::home_dir) { + Some(path) => path, + None => match env::current_dir() { + Ok(path) => path, + Err(e) => return Err(TrezorError::CacheError(e.to_string())), + }, + }) + .join(".ethers-rs") + .join("trezor") + .join("cache"); + let mut blank = Self { derivation: derivation.clone(), chain_id, + cache_dir, address: Address::from([0_u8; 20]), session_id: vec![], }; @@ -56,9 +84,32 @@ impl TrezorEthereum { Ok(()) } + fn get_cached_session(&self) -> Result>, TrezorError> { + let mut session = [0; SESSION_ID_LENGTH]; + + if let Ok(mut file) = fs::File::open(self.cache_dir.join(SESSION_FILE_NAME)) { + file.read_exact(&mut session).map_err(|e| TrezorError::CacheError(e.to_string()))?; + Ok(Some(session.to_vec())) + } else { + Ok(None) + } + } + + fn save_session(&mut self, session_id: Vec) -> Result<(), TrezorError> { + fs::create_dir_all(&self.cache_dir).map_err(|e| TrezorError::CacheError(e.to_string()))?; + + let mut file = fs::File::create(self.cache_dir.join(SESSION_FILE_NAME)) + .map_err(|e| TrezorError::CacheError(e.to_string()))?; + + file.write_all(&session_id).map_err(|e| TrezorError::CacheError(e.to_string()))?; + + self.session_id = session_id; + Ok(()) + } + fn initate_session(&mut self) -> Result<(), TrezorError> { let mut client = trezor_client::unique(false)?; - client.init_device(None)?; + client.init_device(self.get_cached_session()?)?; let features = client.features().ok_or(TrezorError::FeaturesError)?; @@ -69,7 +120,7 @@ impl TrezorEthereum { features.get_patch_version() ))?; - self.session_id = features.get_session_id().to_vec(); + self.save_session(features.get_session_id().to_vec())?; Ok(()) } @@ -213,7 +264,10 @@ mod tests { // 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(); + let trezor = + TrezorEthereum::new(DerivationType::TrezorLive(1), 1, Some(PathBuf::from("randomdir"))) + .await + .unwrap(); assert_eq!( trezor.get_address().await.unwrap(), "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap() @@ -227,7 +281,7 @@ mod tests { #[tokio::test] #[ignore] async fn test_sign_tx() { - let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap(); + let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap(); // approve uni v2 router 0xff let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(); @@ -246,7 +300,7 @@ mod tests { #[tokio::test] #[ignore] async fn test_sign_big_data_tx() { - let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap(); + let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap(); // invalid data let big_data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string()+ &"ff".repeat(1032*2) + "aa").unwrap(); @@ -266,7 +320,7 @@ mod tests { async fn test_sign_empty_txes() { // Contract creation (empty `to`), requires data. // To test without the data field, we need to specify a `to` address. - let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap(); + let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap(); { let tx_req = Eip1559TransactionRequest::new() .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::
().unwrap()) @@ -285,7 +339,7 @@ mod tests { // Contract creation (empty `to`, with data) should show on the trezor device as: // ` "0 Wei ETH // ` new contract?" - let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap(); + let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap(); { let tx_req = Eip1559TransactionRequest::new().data(data.clone()).into(); let tx = trezor.sign_transaction(&tx_req).await.unwrap(); @@ -299,7 +353,7 @@ mod tests { #[tokio::test] #[ignore] async fn test_sign_eip1559_tx() { - let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap(); + let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap(); // approve uni v2 router 0xff let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(); @@ -346,7 +400,7 @@ mod tests { #[tokio::test] #[ignore] async fn test_sign_message() { - let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap(); + let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap(); let message = "hello world"; let sig = trezor.sign_message(message).await.unwrap(); let addr = trezor.get_address().await.unwrap(); @@ -356,7 +410,7 @@ mod tests { #[tokio::test] #[ignore] async fn test_sign_eip712_struct() { - let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1u64).await.unwrap(); + let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1u64, None).await.unwrap(); let foo_bar = FooBar { foo: I256::from(10), diff --git a/ethers-signers/src/trezor/types.rs b/ethers-signers/src/trezor/types.rs index e6881856..05b272fc 100644 --- a/ethers-signers/src/trezor/types.rs +++ b/ethers-signers/src/trezor/types.rs @@ -51,6 +51,8 @@ pub enum TrezorError { UnsupportedFirmwareVersion(String), #[error("Does not support ENS.")] NoENSSupport, + #[error("Unable to access trezor cached session.")] + CacheError(String), } /// Trezor Transaction Struct