1#![allow(unused)]
2use trezor_client::client::{AccessListItem as Trezor_AccessListItem, Trezor};
3
4use futures_executor::block_on;
5use futures_util::lock::Mutex;
6
7use ethers_core::{
8 types::{
9 transaction::{eip2718::TypedTransaction, eip712::Eip712},
10 Address, NameOrAddress, Signature, Transaction, TransactionRequest, TxHash, H256, U256,
11 },
12 utils::keccak256,
13};
14use home;
15use std::{
16 convert::TryFrom,
17 env, fs,
18 io::{Read, Write},
19 path,
20 path::PathBuf,
21};
22use thiserror::Error;
23
24use super::types::*;
25
26#[derive(Debug)]
30pub struct TrezorEthereum {
31 derivation: DerivationType,
32 session_id: Vec<u8>,
33 cache_dir: PathBuf,
34 pub(crate) chain_id: u64,
35 pub(crate) address: Address,
36}
37
38const FIRMWARE_1_MIN_VERSION: &str = ">=1.11.1";
40const FIRMWARE_2_MIN_VERSION: &str = ">=2.5.1";
41
42const SESSION_ID_LENGTH: usize = 32;
44const SESSION_FILE_NAME: &str = "trezor.session";
45
46impl TrezorEthereum {
47 pub async fn new(
48 derivation: DerivationType,
49 chain_id: u64,
50 cache_dir: Option<PathBuf>,
51 ) -> Result<Self, TrezorError> {
52 let cache_dir = (match cache_dir.or_else(home::home_dir) {
53 Some(path) => path,
54 None => match env::current_dir() {
55 Ok(path) => path,
56 Err(e) => return Err(TrezorError::CacheError(e.to_string())),
57 },
58 })
59 .join(".ethers-rs")
60 .join("trezor")
61 .join("cache");
62
63 let mut blank = Self {
64 derivation: derivation.clone(),
65 chain_id,
66 cache_dir,
67 address: Address::from([0_u8; 20]),
68 session_id: vec![],
69 };
70
71 blank.initate_session()?;
73 blank.address = blank.get_address_with_path(&derivation).await?;
74 Ok(blank)
75 }
76
77 fn check_version(version: String) -> Result<(), TrezorError> {
78 let version = semver::Version::parse(&version)?;
79
80 let min_version = match version.major {
81 1 => FIRMWARE_1_MIN_VERSION,
82 2 => FIRMWARE_2_MIN_VERSION,
83 _ => return Ok(()),
86 };
87
88 let req = semver::VersionReq::parse(min_version)?;
89 if !req.matches(&version) {
91 return Err(TrezorError::UnsupportedFirmwareVersion(min_version.to_string()))
92 }
93
94 Ok(())
95 }
96
97 fn get_cached_session(&self) -> Result<Option<Vec<u8>>, TrezorError> {
98 let mut session = [0; SESSION_ID_LENGTH];
99
100 if let Ok(mut file) = fs::File::open(self.cache_dir.join(SESSION_FILE_NAME)) {
101 file.read_exact(&mut session).map_err(|e| TrezorError::CacheError(e.to_string()))?;
102 Ok(Some(session.to_vec()))
103 } else {
104 Ok(None)
105 }
106 }
107
108 fn save_session(&mut self, session_id: Vec<u8>) -> Result<(), TrezorError> {
109 fs::create_dir_all(&self.cache_dir).map_err(|e| TrezorError::CacheError(e.to_string()))?;
110
111 let mut file = fs::File::create(self.cache_dir.join(SESSION_FILE_NAME))
112 .map_err(|e| TrezorError::CacheError(e.to_string()))?;
113
114 file.write_all(&session_id).map_err(|e| TrezorError::CacheError(e.to_string()))?;
115
116 self.session_id = session_id;
117 Ok(())
118 }
119
120 fn initate_session(&mut self) -> Result<(), TrezorError> {
121 let mut client = trezor_client::unique(false)?;
122 client.init_device(self.get_cached_session()?)?;
123
124 let features = client.features().ok_or(TrezorError::FeaturesError)?;
125
126 Self::check_version(format!(
127 "{}.{}.{}",
128 features.major_version(),
129 features.minor_version(),
130 features.patch_version()
131 ))?;
132
133 self.save_session(features.session_id().to_vec())?;
134
135 Ok(())
136 }
137
138 fn get_client(&self, session_id: Vec<u8>) -> Result<Trezor, TrezorError> {
140 let mut client = trezor_client::unique(false)?;
141 client.init_device(Some(session_id))?;
142 Ok(client)
143 }
144
145 pub async fn get_address(&self) -> Result<Address, TrezorError> {
147 self.get_address_with_path(&self.derivation).await
148 }
149
150 pub async fn get_address_with_path(
152 &self,
153 derivation: &DerivationType,
154 ) -> Result<Address, TrezorError> {
155 let mut client = self.get_client(self.session_id.clone())?;
156 let address_str = client.ethereum_get_address(Self::convert_path(derivation))?;
157 let mut address_bytes = [0; 20];
158 hex::decode_to_slice(address_str, &mut address_bytes)?;
159 Ok(address_bytes.into())
160 }
161
162 pub async fn sign_tx(&self, tx: &TypedTransaction) -> Result<Signature, TrezorError> {
164 let mut client = self.get_client(self.session_id.clone())?;
165
166 let arr_path = Self::convert_path(&self.derivation);
167
168 let transaction = TrezorTransaction::load(tx)?;
169
170 let chain_id = Some(tx.chain_id().map(|id| id.as_u64()).unwrap_or(self.chain_id));
171
172 let signature = match tx {
173 TypedTransaction::Eip2930(_) | TypedTransaction::Legacy(_) => client.ethereum_sign_tx(
174 arr_path,
175 transaction.nonce,
176 transaction.gas_price,
177 transaction.gas,
178 transaction.to,
179 transaction.value,
180 transaction.data,
181 chain_id,
182 )?,
183 TypedTransaction::Eip1559(eip1559_tx) => client.ethereum_sign_eip1559_tx(
184 arr_path,
185 transaction.nonce,
186 transaction.gas,
187 transaction.to,
188 transaction.value,
189 transaction.data,
190 chain_id,
191 transaction.max_fee_per_gas,
192 transaction.max_priority_fee_per_gas,
193 transaction.access_list,
194 )?,
195 #[cfg(feature = "optimism")]
196 TypedTransaction::DepositTransaction(tx) => {
197 trezor_client::client::Signature { r: [0; 32], s: [0; 32], v: 0 }
198 }
199 };
200
201 Ok(Signature {
202 r: U256::from_big_endian(&signature.r),
203 s: U256::from_big_endian(&signature.s),
204 v: signature.v,
205 })
206 }
207
208 pub async fn sign_message<S: AsRef<[u8]>>(&self, message: S) -> Result<Signature, TrezorError> {
210 let message = message.as_ref();
211 let mut client = self.get_client(self.session_id.clone())?;
212 let apath = Self::convert_path(&self.derivation);
213
214 let signature = client.ethereum_sign_message(message.into(), apath)?;
215
216 Ok(Signature {
217 r: U256::from_big_endian(&signature.r),
218 s: U256::from_big_endian(&signature.s),
219 v: signature.v,
220 })
221 }
222
223 pub async fn sign_typed_struct<T>(&self, payload: &T) -> Result<Signature, TrezorError>
225 where
226 T: Eip712,
227 {
228 unimplemented!()
229 }
230
231 fn convert_path(derivation: &DerivationType) -> Vec<u32> {
233 let derivation = derivation.to_string();
234 let elements = derivation.split('/').skip(1).collect::<Vec<_>>();
235 let depth = elements.len();
236
237 let mut path = vec![];
238 for derivation_index in elements {
239 let hardened = derivation_index.contains('\'');
240 let mut index = derivation_index.replace('\'', "").parse::<u32>().unwrap();
241 if hardened {
242 index |= 0x80000000;
243 }
244 path.push(index);
245 }
246
247 path
248 }
249}
250
251#[cfg(all(test, feature = "trezor"))]
252mod tests {
253 use super::*;
254 use crate::Signer;
255 use ethers_core::types::{
256 transaction::eip2930::{AccessList, AccessListItem},
257 Address, Eip1559TransactionRequest, TransactionRequest, I256, U256,
258 };
259 use std::str::FromStr;
260
261 #[tokio::test]
262 #[ignore]
263 async fn test_get_address() {
265 let trezor =
267 TrezorEthereum::new(DerivationType::TrezorLive(1), 1, Some(PathBuf::from("randomdir")))
268 .await
269 .unwrap();
270 assert_eq!(
271 trezor.get_address().await.unwrap(),
272 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap()
273 );
274 assert_eq!(
275 trezor.get_address_with_path(&DerivationType::TrezorLive(0)).await.unwrap(),
276 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap()
277 );
278 }
279
280 #[tokio::test]
281 #[ignore]
282 async fn test_sign_tx() {
283 let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap();
284
285 let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();
287
288 let tx_req = TransactionRequest::new()
289 .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap())
290 .gas(1000000)
291 .gas_price(400e9 as u64)
292 .nonce(5)
293 .data(data)
294 .value(ethers_core::utils::parse_ether(100).unwrap())
295 .into();
296 let tx = trezor.sign_transaction(&tx_req).await.unwrap();
297 }
298
299 #[tokio::test]
300 #[ignore]
301 async fn test_sign_big_data_tx() {
302 let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap();
303
304 let big_data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string()+ &"ff".repeat(1032*2) + "aa").unwrap();
306 let tx_req = TransactionRequest::new()
307 .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap())
308 .gas(1000000)
309 .gas_price(400e9 as u64)
310 .nonce(5)
311 .data(big_data)
312 .value(ethers_core::utils::parse_ether(100).unwrap())
313 .into();
314 let tx = trezor.sign_transaction(&tx_req).await.unwrap();
315 }
316
317 #[tokio::test]
318 #[ignore]
319 async fn test_sign_empty_txes() {
320 let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap();
323 {
324 let tx_req = Eip1559TransactionRequest::new()
325 .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap())
326 .into();
327 let tx = trezor.sign_transaction(&tx_req).await.unwrap();
328 }
329 {
330 let tx_req = TransactionRequest::new()
331 .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap())
332 .into();
333 let tx = trezor.sign_transaction(&tx_req).await.unwrap();
334 }
335
336 let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();
337
338 let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap();
342 {
343 let tx_req = Eip1559TransactionRequest::new().data(data.clone()).into();
344 let tx = trezor.sign_transaction(&tx_req).await.unwrap();
345 }
346 {
347 let tx_req = TransactionRequest::new().data(data.clone()).into();
348 let tx = trezor.sign_transaction(&tx_req).await.unwrap();
349 }
350 }
351
352 #[tokio::test]
353 #[ignore]
354 async fn test_sign_eip1559_tx() {
355 let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap();
356
357 let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();
359
360 let lst = AccessList(vec![
361 AccessListItem {
362 address: "0x8ba1f109551bd432803012645ac136ddd64dba72".parse().unwrap(),
363 storage_keys: vec![
364 "0x0000000000000000000000000000000000000000000000000000000000000000"
365 .parse()
366 .unwrap(),
367 "0x0000000000000000000000000000000000000000000000000000000000000042"
368 .parse()
369 .unwrap(),
370 ],
371 },
372 AccessListItem {
373 address: "0x2ed7afa17473e17ac59908f088b4371d28585476".parse().unwrap(),
374 storage_keys: vec![
375 "0x0000000000000000000000000000000000000000000000000000000000000000"
376 .parse()
377 .unwrap(),
378 "0x0000000000000000000000000000000000000000000000000000000000000042"
379 .parse()
380 .unwrap(),
381 ],
382 },
383 ]);
384
385 let tx_req = Eip1559TransactionRequest::new()
386 .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap())
387 .gas(1000000)
388 .max_fee_per_gas(400e9 as u64)
389 .max_priority_fee_per_gas(400e9 as u64)
390 .nonce(5)
391 .data(data)
392 .access_list(lst)
393 .value(ethers_core::utils::parse_ether(100).unwrap())
394 .into();
395
396 let tx = trezor.sign_transaction(&tx_req).await.unwrap();
397 }
398
399 #[tokio::test]
400 #[ignore]
401 async fn test_sign_message() {
402 let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1, None).await.unwrap();
403 let message = "hello world";
404 let sig = trezor.sign_message(message).await.unwrap();
405 let addr = trezor.get_address().await.unwrap();
406 sig.verify(message, addr).unwrap();
407 }
408}