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 HDPath::Legacy(index) =>
110 vec![44 + 0x80000000, 888 + 0x80000000, 0 + 0x80000000, *index],
111 HDPath::Custom(path) => path.clone(),
112 }
113 }
114}
115
116pub struct LedgerWallet<T: LedgerAsync> {
118 pub(crate) ledger: Arc<T>,
120 pub(crate) derivation_path: HDPath,
122 pub(crate) address: Option<Address>,
124 pub(crate) network: Option<u64>,
126}
127
128impl<T: LedgerAsync> LedgerWallet<T> {
129 pub async fn new(ledger: T, derivation_path: HDPath) -> Result<Self, WalletError> {
131 let ledger = Arc::new(ledger);
132 let mut wallet = Self { ledger, derivation_path, address: None, network: None };
133
134 wallet.address = Some(wallet.get_address().await?);
136
137 Ok(wallet)
138 }
139
140 pub async fn get_address(&self) -> Result<Address, WalletError> {
142 let path = self.derivation_path.to_vec();
143 let command = apdu::get_address(&path, false);
144
145 let response = self
146 .ledger
147 .exchange(&command)
148 .await
149 .map_err(|e| WalletError::LedgerError(format!("Failed to get address: {e}")))?;
150
151 if response.retcode() != 0x9000 {
152 return Err(WalletError::LedgerError(format!("Ledger error: {:x}", response.retcode())));
153 }
154
155 let data = response
158 .data()
159 .ok_or_else(|| WalletError::LedgerError("No data in response".to_string()))?;
160 if data.len() < 65 {
161 return Err(WalletError::LedgerError("Invalid response data length".to_string()));
162 }
163
164 let public_key = &data[0..65];
165 let address =
167 Address::from_str(&format!("0x{}", H160::from_slice(&public_key[1..21]).to_hex()))
168 .map_err(|e| WalletError::LedgerError(format!("Failed to derive address: {e}")))?;
169
170 Ok(address)
171 }
172
173 pub async fn sign_transaction<'a, P: JsonRpcProvider + 'static>(
175 &self,
176 tx: &Transaction<'a, P>,
177 ) -> Result<k256::ecdsa::Signature, WalletError> {
178 let path = self.derivation_path.to_vec();
179
180 let tx_hash = tx.get_hash_data().await?;
182
183 let command = apdu::sign_tx(&path, &tx_hash);
185
186 let response =
188 self.ledger.exchange(&command).await.map_err(|e| {
189 WalletError::LedgerError(format!("Failed to sign transaction: {e}"))
190 })?;
191
192 if response.retcode() != 0x9000 {
193 return Err(WalletError::LedgerError(format!("Ledger error: {:x}", response.retcode())));
194 }
195
196 let data = response
198 .data()
199 .ok_or_else(|| WalletError::LedgerError("No data in response".to_string()))?;
200 if data.len() != 64 {
201 return Err(WalletError::LedgerError("Invalid signature length".to_string()));
202 }
203
204 let r = H256::from_slice(&data[0..32]);
206 let s = H256::from_slice(&data[32..64]);
207
208 let r_bytes: [u8; 32] = r.into();
210 let s_bytes: [u8; 32] = s.into();
211 let signature = k256::ecdsa::Signature::from_scalars(r_bytes, s_bytes)
212 .map_err(|e| WalletError::LedgerError(format!("Failed to create signature: {e}")))?;
213
214 Ok(signature)
215 }
216
217 pub async fn sign_message(
219 &self,
220 message: &[u8],
221 ) -> Result<k256::ecdsa::Signature, WalletError> {
222 let path = self.derivation_path.to_vec();
223
224 let message_hash = sha2::Sha256::digest(message);
226
227 let command = apdu::sign_message(&path, &message_hash);
229
230 let response = self
232 .ledger
233 .exchange(&command)
234 .await
235 .map_err(|e| WalletError::LedgerError(format!("Failed to sign message: {e}")))?;
236
237 if response.retcode() != 0x9000 {
238 return Err(WalletError::LedgerError(format!("Ledger error: {:x}", response.retcode())));
239 }
240
241 let data = response
243 .data()
244 .ok_or_else(|| WalletError::LedgerError("No data in response".to_string()))?;
245 if data.len() != 64 {
246 return Err(WalletError::LedgerError("Invalid signature length".to_string()));
247 }
248
249 let r = H256::from_slice(&data[0..32]);
251 let s = H256::from_slice(&data[32..64]);
252
253 let r_bytes: [u8; 32] = r.into();
255 let s_bytes: [u8; 32] = s.into();
256 let signature = k256::ecdsa::Signature::from_scalars(r_bytes, s_bytes)
257 .map_err(|e| WalletError::LedgerError(format!("Failed to create signature: {e}")))?;
258
259 Ok(signature)
260 }
261}
262
263impl<T: LedgerAsync> fmt::Debug for LedgerWallet<T> {
265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266 f.debug_struct("LedgerWallet")
267 .field("derivation_path", &self.derivation_path)
268 .field("address", &self.address)
269 .field("network", &self.network)
270 .finish()
271 }
272}