1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#![allow(clippy::upper_case_acronyms)]
//! Helpers for interacting with the Ethereum Trezor App
//! [Official Docs](https://github.com/TrezorHQ/app-ethereum/blob/master/doc/ethapp.asc)
use std::fmt;
use thiserror::Error;

use ethers_core::types::{transaction::eip2718::TypedTransaction, NameOrAddress, U256};
use trezor_client::client::AccessListItem as Trezor_AccessListItem;

#[derive(Clone, Debug)]
/// Trezor wallet type
pub enum DerivationType {
    /// Trezor Live-generated HD path
    TrezorLive(usize),
    /// Any other path. Attention! Trezor by default forbids custom derivation paths
    /// Run trezorctl set safety-checks prompt, to allow it
    Other(String),
}

impl fmt::Display for DerivationType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        write!(
            f,
            "{}",
            match self {
                DerivationType::TrezorLive(index) => format!("m/44'/60'/{index}'/0/0"),
                DerivationType::Other(inner) => inner.to_owned(),
            }
        )
    }
}

#[derive(Error, Debug)]
/// Error when using the Trezor transport
pub enum TrezorError {
    /// Underlying Trezor transport error
    #[error(transparent)]
    TrezorError(#[from] trezor_client::error::Error),
    #[error("Trezor was not able to retrieve device features")]
    FeaturesError,
    #[error("Not able to unpack value for TrezorTransaction.")]
    DataError,
    /// Error when converting from a hex string
    #[error(transparent)]
    HexError(#[from] hex::FromHexError),
    /// Error when converting a semver requirement
    #[error(transparent)]
    SemVerError(#[from] semver::Error),
    /// Error when signing EIP712 struct with not compatible Trezor ETH app
    #[error("Trezor ethereum app requires at least version: {0:?}")]
    UnsupportedFirmwareVersion(String),
    #[error("Does not support ENS.")]
    NoENSSupport,
    #[error("Unable to access trezor cached session.")]
    CacheError(String),
}

/// Trezor Transaction Struct
pub struct TrezorTransaction {
    pub nonce: Vec<u8>,
    pub gas: Vec<u8>,
    pub gas_price: Vec<u8>,
    pub value: Vec<u8>,
    pub to: String,
    pub data: Vec<u8>,
    pub max_fee_per_gas: Vec<u8>,
    pub max_priority_fee_per_gas: Vec<u8>,
    pub access_list: Vec<Trezor_AccessListItem>,
}

impl TrezorTransaction {
    fn to_trimmed_big_endian(_value: &U256) -> Vec<u8> {
        let mut trimmed_value = [0_u8; 32];
        _value.to_big_endian(&mut trimmed_value);
        trimmed_value[_value.leading_zeros() as usize / 8..].to_vec()
    }

    pub fn load(tx: &TypedTransaction) -> Result<Self, TrezorError> {
        let to: String = match tx.to() {
            Some(v) => match v {
                NameOrAddress::Name(_) => return Err(TrezorError::NoENSSupport),
                NameOrAddress::Address(value) => hex::encode_prefixed(value),
            },
            // Contract Creation
            None => "".to_string(),
        };

        let nonce = tx.nonce().map_or(vec![], Self::to_trimmed_big_endian);
        let gas = tx.gas().map_or(vec![], Self::to_trimmed_big_endian);
        let gas_price = tx.gas_price().map_or(vec![], |v| Self::to_trimmed_big_endian(&v));
        let value = tx.value().map_or(vec![], Self::to_trimmed_big_endian);
        let data = tx.data().map_or(vec![], |v| v.to_vec());

        match tx {
            TypedTransaction::Eip2930(_) | TypedTransaction::Legacy(_) => Ok(Self {
                nonce,
                gas,
                gas_price,
                value,
                to,
                data,
                max_fee_per_gas: vec![],
                max_priority_fee_per_gas: vec![],
                access_list: vec![],
            }),
            TypedTransaction::Eip1559(eip1559_tx) => {
                let max_fee_per_gas =
                    eip1559_tx.max_fee_per_gas.map_or(vec![], |v| Self::to_trimmed_big_endian(&v));

                let max_priority_fee_per_gas = eip1559_tx
                    .max_priority_fee_per_gas
                    .map_or(vec![], |v| Self::to_trimmed_big_endian(&v));

                let mut access_list: Vec<Trezor_AccessListItem> = Vec::new();
                for item in &eip1559_tx.access_list.0 {
                    let address: String = hex::encode_prefixed(item.address);
                    let mut storage_keys: Vec<Vec<u8>> = Vec::new();

                    for key in &item.storage_keys {
                        storage_keys.push(key.as_bytes().to_vec())
                    }

                    access_list.push(Trezor_AccessListItem { address, storage_keys })
                }

                Ok(Self {
                    nonce,
                    gas,
                    gas_price,
                    value,
                    to,
                    data,
                    max_fee_per_gas,
                    max_priority_fee_per_gas,
                    access_list,
                })
            }
            #[cfg(feature = "optimism")]
            TypedTransaction::DepositTransaction(_) => Ok(Self {
                nonce,
                gas,
                gas_price,
                value,
                to,
                data,
                max_fee_per_gas: vec![],
                max_priority_fee_per_gas: vec![],
                access_list: vec![],
            }),
        }
    }
}