1#![allow(unused)]
2use coins_ledger::{
3 common::{APDUAnswer, APDUCommand, APDUData},
4 transports::{Ledger, LedgerAsync},
5};
6use futures_executor::block_on;
7use futures_util::lock::Mutex;
8
9use ethers_core::{
10 types::{
11 transaction::{eip2718::TypedTransaction, eip712::Eip712},
12 Address, NameOrAddress, Signature, Transaction, TransactionRequest, TxHash, H256, U256,
13 },
14 utils::keccak256,
15};
16use std::convert::TryFrom;
17use thiserror::Error;
18
19use super::types::*;
20
21#[derive(Debug)]
25pub struct LedgerEthereum {
26 transport: Mutex<Ledger>,
27 derivation: DerivationType,
28 pub(crate) chain_id: u64,
29 pub(crate) address: Address,
30}
31
32impl std::fmt::Display for LedgerEthereum {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 write!(
35 f,
36 "LedgerApp. Key at index {} with address {:?} on chain_id {}",
37 self.derivation, self.address, self.chain_id
38 )
39 }
40}
41
42const EIP712_MIN_VERSION: &str = ">=1.6.0";
43
44impl LedgerEthereum {
45 pub async fn new(derivation: DerivationType, chain_id: u64) -> Result<Self, LedgerError> {
57 let transport = Ledger::init().await?;
58 let address = Self::get_address_with_path_transport(&transport, &derivation).await?;
59
60 Ok(Self { transport: Mutex::new(transport), derivation, chain_id, address })
61 }
62
63 pub fn close(self) {}
65
66 pub async fn get_address(&self) -> Result<Address, LedgerError> {
68 self.get_address_with_path(&self.derivation).await
69 }
70
71 pub async fn get_address_with_path(
73 &self,
74 derivation: &DerivationType,
75 ) -> Result<Address, LedgerError> {
76 let data = APDUData::new(&Self::path_to_bytes(derivation));
77 let transport = self.transport.lock().await;
78 Self::get_address_with_path_transport(&transport, derivation).await
79 }
80
81 #[tracing::instrument(skip(transport))]
82 async fn get_address_with_path_transport(
83 transport: &Ledger,
84 derivation: &DerivationType,
85 ) -> Result<Address, LedgerError> {
86 let data = APDUData::new(&Self::path_to_bytes(derivation));
87
88 let command = APDUCommand {
89 ins: INS::GET_PUBLIC_KEY as u8,
90 p1: P1::NON_CONFIRM as u8,
91 p2: P2::NO_CHAINCODE as u8,
92 data,
93 response_len: None,
94 };
95
96 tracing::debug!("Dispatching get_address request to ethereum app");
97 let answer = block_on(transport.exchange(&command))?;
98 let result = answer.data().ok_or(LedgerError::UnexpectedNullResponse)?;
99
100 let address = {
101 let offset = 1 + result[0] as usize;
103 let address_str = &result[offset + 1..offset + 1 + result[offset] as usize];
104 let mut address = [0; 20];
105 address.copy_from_slice(&hex::decode(address_str)?);
106 Address::from(address)
107 };
108 tracing::debug!(?address, "Received address from device");
109 Ok(address)
110 }
111
112 pub async fn version(&self) -> Result<String, LedgerError> {
114 let transport = self.transport.lock().await;
115
116 let command = APDUCommand {
117 ins: INS::GET_APP_CONFIGURATION as u8,
118 p1: P1::NON_CONFIRM as u8,
119 p2: P2::NO_CHAINCODE as u8,
120 data: APDUData::new(&[]),
121 response_len: None,
122 };
123
124 tracing::debug!("Dispatching get_version");
125 let answer = block_on(transport.exchange(&command))?;
126 let result = answer.data().ok_or(LedgerError::UnexpectedNullResponse)?;
127 if result.len() < 4 {
128 return Err(LedgerError::ShortResponse { got: result.len(), at_least: 4 })
129 }
130 let version = format!("{}.{}.{}", result[1], result[2], result[3]);
131 tracing::debug!(version, "Retrieved version from device");
132 Ok(version)
133 }
134
135 pub async fn sign_tx(&self, tx: &TypedTransaction) -> Result<Signature, LedgerError> {
137 let mut tx_with_chain = tx.clone();
138 if tx_with_chain.chain_id().is_none() {
139 tx_with_chain.set_chain_id(self.chain_id);
141 }
142 let mut payload = Self::path_to_bytes(&self.derivation);
143 payload.extend_from_slice(tx_with_chain.rlp().as_ref());
144
145 let mut signature = self.sign_payload(INS::SIGN, &payload).await?;
146
147 let eip155_chain_id = self.chain_id * 2 + 35;
151 if eip155_chain_id + 1 > 255 {
152 let one_byte_chain_id = eip155_chain_id % 256;
153 let ecc_parity = if signature.v > one_byte_chain_id {
154 signature.v - one_byte_chain_id
155 } else {
156 one_byte_chain_id - signature.v
157 };
158
159 signature.v = match tx {
160 TypedTransaction::Eip2930(_) | TypedTransaction::Eip1559(_) => {
161 (ecc_parity % 2 != 1) as u64
162 }
163 TypedTransaction::Legacy(_) => eip155_chain_id + ecc_parity,
164 #[cfg(feature = "optimism")]
165 TypedTransaction::DepositTransaction(_) => 0,
166 };
167 }
168
169 Ok(signature)
170 }
171
172 pub async fn sign_message<S: AsRef<[u8]>>(&self, message: S) -> Result<Signature, LedgerError> {
174 let message = message.as_ref();
175
176 let mut payload = Self::path_to_bytes(&self.derivation);
177 payload.extend_from_slice(&(message.len() as u32).to_be_bytes());
178 payload.extend_from_slice(message);
179
180 self.sign_payload(INS::SIGN_PERSONAL_MESSAGE, &payload).await
181 }
182
183 pub async fn sign_typed_struct<T>(&self, payload: &T) -> Result<Signature, LedgerError>
185 where
186 T: Eip712,
187 {
188 let req = semver::VersionReq::parse(EIP712_MIN_VERSION)?;
191 let version = semver::Version::parse(&self.version().await?)?;
192
193 if !req.matches(&version) {
195 return Err(LedgerError::UnsupportedAppVersion(EIP712_MIN_VERSION.to_string()))
196 }
197
198 let domain_separator =
199 payload.domain_separator().map_err(|e| LedgerError::Eip712Error(e.to_string()))?;
200 let struct_hash =
201 payload.struct_hash().map_err(|e| LedgerError::Eip712Error(e.to_string()))?;
202
203 let mut payload = Self::path_to_bytes(&self.derivation);
204 payload.extend_from_slice(&domain_separator);
205 payload.extend_from_slice(&struct_hash);
206
207 self.sign_payload(INS::SIGN_ETH_EIP_712, &payload).await
208 }
209
210 #[tracing::instrument(err, skip_all, fields(command = %command, payload = hex::encode(payload)))]
211 pub async fn sign_payload(
214 &self,
215 command: INS,
216 payload: &Vec<u8>,
217 ) -> Result<Signature, LedgerError> {
218 if payload.is_empty() {
219 return Err(LedgerError::EmptyPayload)
220 }
221 let transport = self.transport.lock().await;
222 let mut command = APDUCommand {
223 ins: command as u8,
224 p1: P1_FIRST,
225 p2: P2::NO_CHAINCODE as u8,
226 data: APDUData::new(&[]),
227 response_len: None,
228 };
229
230 let mut answer = None;
231 let chunk_size =
234 (0..=255).rev().find(|i| payload.len() % i != 3).expect("true for any length");
235
236 let span = tracing::debug_span!("send_loop", index = 0, chunk = "");
238 let guard = span.entered();
239 for (index, chunk) in payload.chunks(chunk_size).enumerate() {
240 guard.record("index", index);
241 guard.record("chunk", hex::encode(chunk));
242 command.data = APDUData::new(chunk);
243
244 tracing::debug!("Dispatching packet to device");
245 answer = Some(block_on(transport.exchange(&command))?);
246
247 let data = answer.as_ref().expect("just assigned").data();
248 if data.is_none() {
249 return Err(LedgerError::UnexpectedNullResponse)
250 }
251 tracing::debug!(
252 response = hex::encode(data.expect("just checked")),
253 "Received response from device"
254 );
255
256 command.p1 = P1::MORE as u8;
258 }
259 drop(guard);
260 let answer = answer.expect("payload is non-empty, therefore loop ran");
261 let result = answer.data().expect("check in loop");
262 if result.len() < 65 {
263 return Err(LedgerError::ShortResponse { got: result.len(), at_least: 65 })
264 }
265 let v = result[0] as u64;
266 let r = U256::from_big_endian(&result[1..33]);
267 let s = U256::from_big_endian(&result[33..]);
268 let sig = Signature { r, s, v };
269 tracing::debug!(sig = %sig, "Received signature from device");
270 Ok(sig)
271 }
272
273 fn path_to_bytes(derivation: &DerivationType) -> Vec<u8> {
275 let derivation = derivation.to_string();
276 let elements = derivation.split('/').skip(1).collect::<Vec<_>>();
277 let depth = elements.len();
278
279 let mut bytes = vec![depth as u8];
280 for derivation_index in elements {
281 let hardened = derivation_index.contains('\'');
282 let mut index = derivation_index.replace('\'', "").parse::<u32>().unwrap();
283 if hardened {
284 index |= 0x80000000;
285 }
286
287 bytes.extend(index.to_be_bytes());
288 }
289
290 bytes
291 }
292}
293
294#[cfg(all(test, feature = "ledger"))]
295mod tests {
296 use super::*;
297 use crate::Signer;
298 use ethers_core::types::{
299 transaction::eip712::Eip712, Address, TransactionRequest, I256, U256,
300 };
301 use std::str::FromStr;
302
303 #[tokio::test]
304 #[ignore]
305 async fn test_get_address() {
307 let ledger = LedgerEthereum::new(DerivationType::LedgerLive(0), 1).await.unwrap();
309 assert_eq!(
310 ledger.get_address().await.unwrap(),
311 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap()
312 );
313 assert_eq!(
314 ledger.get_address_with_path(&DerivationType::Legacy(0)).await.unwrap(),
315 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap()
316 );
317 }
318
319 #[tokio::test]
320 #[ignore]
321 async fn test_sign_tx() {
322 let ledger = LedgerEthereum::new(DerivationType::LedgerLive(0), 1).await.unwrap();
323
324 let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();
326
327 let tx_req = TransactionRequest::new()
328 .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap())
329 .gas(1000000)
330 .gas_price(400e9 as u64)
331 .nonce(5)
332 .data(data)
333 .value(ethers_core::utils::parse_ether(100).unwrap())
334 .into();
335 let tx = ledger.sign_transaction(&tx_req).await.unwrap();
336 }
337
338 #[tokio::test]
339 #[ignore]
340 async fn test_version() {
341 let ledger = LedgerEthereum::new(DerivationType::LedgerLive(0), 1).await.unwrap();
342
343 let version = ledger.version().await.unwrap();
344 assert_eq!(version, "1.3.7");
345 }
346
347 #[tokio::test]
348 #[ignore]
349 async fn test_sign_message() {
350 let ledger = LedgerEthereum::new(DerivationType::Legacy(0), 1).await.unwrap();
351 let message = "hello world";
352 let sig = ledger.sign_message(message).await.unwrap();
353 let addr = ledger.get_address().await.unwrap();
354 sig.verify(message, addr).unwrap();
355 }
356}