neo3/neo_wallets/
ledger.rs1use std::{fmt, str::FromStr, sync::Arc};
2
3use crate::{
4 builder::Transaction, neo_clients::JsonRpcProvider, neo_wallets::WalletError, Address,
5 ScriptHashExtension,
6};
7use async_trait::async_trait;
8use coins_ledger::{
9 common::APDUCommand,
10 transports::{Ledger, LedgerAsync},
11};
12use p256::NistP256;
13use primitive_types::{H160, H256};
14use sha2::Digest;
15use signature::hazmat::{PrehashSigner, PrehashVerifier};
16
17pub mod apdu {
19 use coins_ledger::common::APDUCommand;
20
21 pub fn get_address(derivation_path: &[u32], display: bool) -> APDUCommand {
23 let mut data = Vec::new();
24 data.push(derivation_path.len() as u8);
25
26 for item in derivation_path.iter() {
27 data.push((*item >> 24) as u8);
28 data.push((*item >> 16) as u8);
29 data.push((*item >> 8) as u8);
30 data.push(*item as u8);
31 }
32
33 APDUCommand {
34 cla: 0x80,
35 ins: 0x02,
36 p1: if display { 0x01 } else { 0x00 },
37 p2: 0x00,
38 data: data.into(),
39 response_len: Some(65),
40 }
41 }
42
43 pub fn sign_tx(derivation_path: &[u32], tx_hash: &[u8]) -> APDUCommand {
45 let mut data = Vec::new();
46 data.push(derivation_path.len() as u8);
47
48 for item in derivation_path.iter() {
49 data.push((*item >> 24) as u8);
50 data.push((*item >> 16) as u8);
51 data.push((*item >> 8) as u8);
52 data.push(*item as u8);
53 }
54
55 data.extend_from_slice(tx_hash);
56
57 APDUCommand {
58 cla: 0x80,
59 ins: 0x04,
60 p1: 0x00,
61 p2: 0x00,
62 data: data.into(),
63 response_len: Some(64),
64 }
65 }
66
67 pub fn sign_message(derivation_path: &[u32], message_hash: &[u8]) -> APDUCommand {
69 let mut data = Vec::new();
70 data.push(derivation_path.len() as u8);
71
72 for item in derivation_path.iter() {
73 data.push((*item >> 24) as u8);
74 data.push((*item >> 16) as u8);
75 data.push((*item >> 8) as u8);
76 data.push(*item as u8);
77 }
78
79 data.extend_from_slice(message_hash);
80
81 APDUCommand {
82 cla: 0x80,
83 ins: 0x08,
84 p1: 0x00,
85 p2: 0x00,
86 data: data.into(),
87 response_len: Some(64),
88 }
89 }
90}
91
92#[derive(Debug, Clone)]
94pub enum HDPath {
95 LedgerLive(u32),
97 Legacy(u32),
99 Custom(Vec<u32>),
101}
102
103impl HDPath {
104 pub fn to_vec(&self) -> Vec<u32> {
106 match self {
107 HDPath::LedgerLive(index) => {
108 vec![44 + 0x80000000, 888 + 0x80000000, 0 + 0x80000000, 0, *index]
109 },
110 HDPath::Legacy(index) => {
111 vec![44 + 0x80000000, 888 + 0x80000000, 0 + 0x80000000, *index]
112 },
113 HDPath::Custom(path) => path.clone(),
114 }
115 }
116}
117
118pub struct LedgerWallet<T: LedgerAsync> {
120 pub(crate) ledger: Arc<T>,
122 pub(crate) derivation_path: HDPath,
124 pub(crate) address: Option<Address>,
126 pub(crate) network: Option<u64>,
128}
129
130impl<T: LedgerAsync> LedgerWallet<T> {
131 pub async fn new(ledger: T, derivation_path: HDPath) -> Result<Self, WalletError> {
133 let ledger = Arc::new(ledger);
134 let mut wallet = Self { ledger, derivation_path, address: None, network: None };
135
136 wallet.address = Some(wallet.get_address().await?);
138
139 Ok(wallet)
140 }
141
142 pub async fn get_address(&self) -> Result<Address, WalletError> {
144 let path = self.derivation_path.to_vec();
145 let command = apdu::get_address(&path, false);
146
147 let response = self
148 .ledger
149 .exchange(&command)
150 .await
151 .map_err(|e| WalletError::LedgerError(format!("Failed to get address: {e}")))?;
152
153 if response.retcode() != 0x9000 {
154 return Err(WalletError::LedgerError(format!(
155 "Ledger error: {:x}",
156 response.retcode()
157 )));
158 }
159
160 let data = response
163 .data()
164 .ok_or_else(|| WalletError::LedgerError("No data in response".to_string()))?;
165 if data.len() < 65 {
166 return Err(WalletError::LedgerError("Invalid response data length".to_string()));
167 }
168
169 let public_key = &data[0..65];
170 let address =
172 Address::from_str(&format!("0x{}", H160::from_slice(&public_key[1..21]).to_hex()))
173 .map_err(|e| WalletError::LedgerError(format!("Failed to derive address: {e}")))?;
174
175 Ok(address)
176 }
177
178 pub async fn sign_transaction<'a, P: JsonRpcProvider + 'static>(
180 &self,
181 tx: &Transaction<'a, P>,
182 ) -> Result<k256::ecdsa::Signature, WalletError> {
183 let path = self.derivation_path.to_vec();
184
185 let tx_hash = tx.get_hash_data().await?;
187
188 let command = apdu::sign_tx(&path, &tx_hash);
190
191 let response =
193 self.ledger.exchange(&command).await.map_err(|e| {
194 WalletError::LedgerError(format!("Failed to sign transaction: {e}"))
195 })?;
196
197 if response.retcode() != 0x9000 {
198 return Err(WalletError::LedgerError(format!(
199 "Ledger error: {:x}",
200 response.retcode()
201 )));
202 }
203
204 let data = response
206 .data()
207 .ok_or_else(|| WalletError::LedgerError("No data in response".to_string()))?;
208 if data.len() != 64 {
209 return Err(WalletError::LedgerError("Invalid signature length".to_string()));
210 }
211
212 let r = H256::from_slice(&data[0..32]);
214 let s = H256::from_slice(&data[32..64]);
215
216 let r_bytes: [u8; 32] = r.into();
218 let s_bytes: [u8; 32] = s.into();
219 let signature = k256::ecdsa::Signature::from_scalars(r_bytes, s_bytes)
220 .map_err(|e| WalletError::LedgerError(format!("Failed to create signature: {e}")))?;
221
222 Ok(signature)
223 }
224
225 pub async fn sign_message(
227 &self,
228 message: &[u8],
229 ) -> Result<k256::ecdsa::Signature, WalletError> {
230 let path = self.derivation_path.to_vec();
231
232 let message_hash = sha2::Sha256::digest(message);
234
235 let command = apdu::sign_message(&path, &message_hash);
237
238 let response = self
240 .ledger
241 .exchange(&command)
242 .await
243 .map_err(|e| WalletError::LedgerError(format!("Failed to sign message: {e}")))?;
244
245 if response.retcode() != 0x9000 {
246 return Err(WalletError::LedgerError(format!(
247 "Ledger error: {:x}",
248 response.retcode()
249 )));
250 }
251
252 let data = response
254 .data()
255 .ok_or_else(|| WalletError::LedgerError("No data in response".to_string()))?;
256 if data.len() != 64 {
257 return Err(WalletError::LedgerError("Invalid signature length".to_string()));
258 }
259
260 let r = H256::from_slice(&data[0..32]);
262 let s = H256::from_slice(&data[32..64]);
263
264 let r_bytes: [u8; 32] = r.into();
266 let s_bytes: [u8; 32] = s.into();
267 let signature = k256::ecdsa::Signature::from_scalars(r_bytes, s_bytes)
268 .map_err(|e| WalletError::LedgerError(format!("Failed to create signature: {e}")))?;
269
270 Ok(signature)
271 }
272}
273
274impl<T: LedgerAsync> fmt::Debug for LedgerWallet<T> {
276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 f.debug_struct("LedgerWallet")
278 .field("derivation_path", &self.derivation_path)
279 .field("address", &self.address)
280 .field("network", &self.network)
281 .finish()
282 }
283}