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
This commit is contained in:
joshieDo 2021-12-30 01:38:29 +02:00 committed by GitHub
parent c5ea7bd60a
commit c6b51e3ae0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 12 deletions

1
Cargo.lock generated
View File

@ -1264,6 +1264,7 @@ dependencies = [
"futures-executor", "futures-executor",
"futures-util", "futures-util",
"hex", "hex",
"home",
"rand 0.8.4", "rand 0.8.4",
"rusoto_core", "rusoto_core",
"rusoto_kms", "rusoto_kms",

View File

@ -39,6 +39,7 @@ spki = { version = "0.5.2", optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
eth-keystore = { version = "0.3.0" } eth-keystore = { version = "0.3.0" }
home = "0.5.3"
[dev-dependencies] [dev-dependencies]
ethers-contract = { version = "^0.6.0", path = "../ethers-contract", features = ["eip712", "abigen"]} ethers-contract = { version = "^0.6.0", path = "../ethers-contract", features = ["eip712", "abigen"]}

View File

@ -11,7 +11,14 @@ use ethers_core::{
}, },
utils::keccak256, utils::keccak256,
}; };
use std::convert::TryFrom; use home;
use std::{
convert::TryFrom,
env, fs,
io::{Read, Write},
path,
path::PathBuf,
};
use thiserror::Error; use thiserror::Error;
use super::types::*; use super::types::*;
@ -23,17 +30,38 @@ use super::types::*;
pub struct TrezorEthereum { pub struct TrezorEthereum {
derivation: DerivationType, derivation: DerivationType,
session_id: Vec<u8>, session_id: Vec<u8>,
cache_dir: PathBuf,
pub(crate) chain_id: u64, pub(crate) chain_id: u64,
pub(crate) address: Address, pub(crate) address: Address,
} }
const FIRMWARE_MIN_VERSION: &str = ">=2.4.2"; 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 { impl TrezorEthereum {
pub async fn new(derivation: DerivationType, chain_id: u64) -> Result<Self, TrezorError> { pub async fn new(
derivation: DerivationType,
chain_id: u64,
cache_dir: Option<PathBuf>,
) -> Result<Self, TrezorError> {
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 { let mut blank = Self {
derivation: derivation.clone(), derivation: derivation.clone(),
chain_id, chain_id,
cache_dir,
address: Address::from([0_u8; 20]), address: Address::from([0_u8; 20]),
session_id: vec![], session_id: vec![],
}; };
@ -56,9 +84,32 @@ impl TrezorEthereum {
Ok(()) Ok(())
} }
fn get_cached_session(&self) -> Result<Option<Vec<u8>>, 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<u8>) -> 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> { fn initate_session(&mut self) -> Result<(), TrezorError> {
let mut client = trezor_client::unique(false)?; 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)?; let features = client.features().ok_or(TrezorError::FeaturesError)?;
@ -69,7 +120,7 @@ impl TrezorEthereum {
features.get_patch_version() features.get_patch_version()
))?; ))?;
self.session_id = features.get_session_id().to_vec(); self.save_session(features.get_session_id().to_vec())?;
Ok(()) Ok(())
} }
@ -213,7 +264,10 @@ mod tests {
// Replace this with your ETH addresses. // Replace this with your ETH addresses.
async fn test_get_address() { async fn test_get_address() {
// Instantiate it with the default trezor derivation path // 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!( assert_eq!(
trezor.get_address().await.unwrap(), trezor.get_address().await.unwrap(),
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap() "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap()
@ -227,7 +281,7 @@ mod tests {
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
async fn test_sign_tx() { 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 // approve uni v2 router 0xff
let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(); let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();
@ -246,7 +300,7 @@ mod tests {
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
async fn test_sign_big_data_tx() { 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 // invalid data
let big_data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string()+ &"ff".repeat(1032*2) + "aa").unwrap(); 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() { async fn test_sign_empty_txes() {
// Contract creation (empty `to`), requires data. // Contract creation (empty `to`), requires data.
// To test without the data field, we need to specify a `to` address. // 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() let tx_req = Eip1559TransactionRequest::new()
.to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap()) .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap())
@ -285,7 +339,7 @@ mod tests {
// Contract creation (empty `to`, with data) should show on the trezor device as: // Contract creation (empty `to`, with data) should show on the trezor device as:
// ` "0 Wei ETH // ` "0 Wei ETH
// ` new contract?" // ` 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_req = Eip1559TransactionRequest::new().data(data.clone()).into();
let tx = trezor.sign_transaction(&tx_req).await.unwrap(); let tx = trezor.sign_transaction(&tx_req).await.unwrap();
@ -299,7 +353,7 @@ mod tests {
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
async fn test_sign_eip1559_tx() { 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 // approve uni v2 router 0xff
let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(); let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();
@ -346,7 +400,7 @@ mod tests {
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
async fn test_sign_message() { 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 message = "hello world";
let sig = trezor.sign_message(message).await.unwrap(); let sig = trezor.sign_message(message).await.unwrap();
let addr = trezor.get_address().await.unwrap(); let addr = trezor.get_address().await.unwrap();
@ -356,7 +410,7 @@ mod tests {
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
async fn test_sign_eip712_struct() { 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 { let foo_bar = FooBar {
foo: I256::from(10), foo: I256::from(10),

View File

@ -51,6 +51,8 @@ pub enum TrezorError {
UnsupportedFirmwareVersion(String), UnsupportedFirmwareVersion(String),
#[error("Does not support ENS.")] #[error("Does not support ENS.")]
NoENSSupport, NoENSSupport,
#[error("Unable to access trezor cached session.")]
CacheError(String),
} }
/// Trezor Transaction Struct /// Trezor Transaction Struct