1#![deny(warnings, trivial_casts, trivial_numeric_casts)]
19#![deny(unused_import_braces, unused_qualifications)]
20#![deny(missing_docs)]
21#![doc(html_root_url = "https://docs.rs/ledger-namada/0.0.2")]
22
23use ed25519_dalek::Verifier;
24use ledger_transport::{APDUCommand, APDUErrorCode, Exchange};
25use ledger_zondax_generic::{App, AppExt, ChunkPayloadType, Version};
26
27use sha2::{Digest, Sha256};
28use std::collections::HashMap;
29
30pub use ledger_zondax_generic::LedgerAppError;
31
32mod params;
33pub use params::{
34 InstructionCode, KeyResponse, NamadaKeys, ADDRESS_LEN, CLA, ED25519_PUBKEY_LEN,
35 PK_LEN_PLUS_TAG, SIG_LEN_PLUS_TAG,
36};
37use params::{KEY_LEN, PAYMENT_ADDR_LEN, SALT_LEN, XFVK_LEN};
38use utils::{
39 ResponseAddress, ResponseGetConvertRandomness, ResponseGetOutputRandomness,
40 ResponseGetSpendRandomness, ResponseMaspSign, ResponseProofGenKey, ResponsePubAddress,
41 ResponseSignature, ResponseSpendSignature, ResponseViewKey,
42};
43
44use std::convert::TryInto;
45use std::str;
46
47mod utils;
48pub use utils::BIP44Path;
49
50#[derive(Debug, thiserror::Error)]
52pub enum NamError<E>
53where
54 E: std::error::Error,
55{
56 #[error("Ledger | {0}")]
57 Ledger(#[from] LedgerAppError<E>),
59 }
67
68pub struct NamadaApp<E> {
70 apdu_transport: E,
71}
72
73impl<E: Exchange> App for NamadaApp<E> {
74 const CLA: u8 = CLA;
75}
76
77impl<E> NamadaApp<E> {
78 pub const fn new(transport: E) -> Self {
80 NamadaApp {
81 apdu_transport: transport,
82 }
83 }
84}
85
86impl<E> NamadaApp<E>
87where
88 E: Exchange + Send + Sync,
89 E::Error: std::error::Error,
90{
91 pub async fn version(&self) -> Result<Version, NamError<E::Error>> {
93 <Self as AppExt<E>>::get_version(&self.apdu_transport)
94 .await
95 .map_err(Into::into)
96 }
97
98 pub async fn get_address_and_pubkey(
100 &self,
101 path: &BIP44Path,
102 require_confirmation: bool,
103 ) -> Result<ResponseAddress, NamError<E::Error>> {
104 let serialized_path = path.serialize_path().unwrap();
105 let p1: u8 = if require_confirmation { 1 } else { 0 };
106 let command = APDUCommand {
107 cla: CLA,
108 ins: InstructionCode::GetAddressAndPubkey as _,
109 p1,
110 p2: 0x00,
111 data: serialized_path,
112 };
113
114 let response = self
115 .apdu_transport
116 .exchange(&command)
117 .await
118 .map_err(LedgerAppError::TransportError)?;
119
120 let response_data = response.data();
121 match response.error_code() {
122 Ok(APDUErrorCode::NoError) if response_data.len() < ED25519_PUBKEY_LEN => {
123 return Err(NamError::Ledger(LedgerAppError::InvalidPK))
124 }
125 Ok(APDUErrorCode::NoError) => {}
126 Ok(err) => {
127 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
128 err as _,
129 err.description(),
130 )))
131 }
132 Err(err) => {
133 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
134 err,
135 "[APDU_ERROR] Unknown".to_string(),
136 )))
137 }
138 }
139
140 let (raw_public_key, rest) = response_data.split_at(ED25519_PUBKEY_LEN + 1);
141 let (public_key_len, rest) = rest.split_first().expect("response too short");
142 let (_public_key, rest) = rest.split_at((*public_key_len).into());
143 let (address_len, rest) = rest.split_first().expect("response too short");
144 let (address_bytes, rest) = rest.split_at((*address_len).into());
145 if rest.len() > 0 {
146 panic!("response too long");
147 }
148
149 let address_str = str::from_utf8(&address_bytes)
150 .map_err(|_| LedgerAppError::Utf8)?
151 .to_owned();
152
153 Ok(ResponseAddress {
154 public_key: raw_public_key.try_into().unwrap(),
155 address_bytes: address_bytes.try_into().unwrap(),
156 address_str,
157 })
158 }
159
160 pub async fn sign(
162 &self,
163 path: &BIP44Path,
164 blob: &[u8],
165 ) -> Result<ResponseSignature, NamError<E::Error>> {
166 let first_chunk = path.serialize_path().unwrap();
167
168 let start_command = APDUCommand {
169 cla: CLA,
170 ins: InstructionCode::Sign as _,
171 p1: ChunkPayloadType::Init as u8,
172 p2: 0x00,
173 data: first_chunk,
174 };
175
176 let response =
177 <Self as AppExt<E>>::send_chunks(&self.apdu_transport, start_command, blob).await?;
178
179 match response.error_code() {
180 Ok(APDUErrorCode::NoError) => {}
181 Ok(err) => {
182 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
183 err as _,
184 err.description(),
185 )))
186 }
187 Err(err) => {
188 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
189 err,
190 "[APDU_ERROR] Unknown".to_string(),
191 )))
192 }
193 }
194
195 let rest = response.apdu_data();
197 let (pubkey, rest) = rest.split_at(PK_LEN_PLUS_TAG);
198 let (raw_salt, rest) = rest.split_at(SALT_LEN);
199 let (raw_signature, rest) = rest.split_at(SIG_LEN_PLUS_TAG);
200 let (wrapper_salt, rest) = rest.split_at(SALT_LEN);
201 let (wrapper_signature, rest) = rest.split_at(SIG_LEN_PLUS_TAG);
202 let (raw_indices_len, rest) = rest.split_at(1);
203 let (raw_indices, rest) = rest.split_at(raw_indices_len[0] as usize);
204 let (wrapper_indices_len, rest) = rest.split_at(1);
205 let (wrapper_indices, _rest) = rest.split_at(wrapper_indices_len[0] as usize);
206
207 Ok(ResponseSignature {
208 pubkey: pubkey.try_into().unwrap(),
209 raw_salt: raw_salt.try_into().unwrap(),
210 raw_signature: raw_signature.try_into().unwrap(),
211 wrapper_salt: wrapper_salt.try_into().unwrap(),
212 wrapper_signature: wrapper_signature.try_into().unwrap(),
213 raw_indices: raw_indices.into(),
214 wrapper_indices: wrapper_indices.into(),
215 })
216 }
217
218 pub fn hash_signature_sec(
220 &self,
221 pubkeys: Vec<Vec<u8>>,
222 hashes: &HashMap<usize, Vec<u8>>,
223 indices: Vec<u8>,
224 signature: Option<Vec<u8>>,
225 prefix: Option<Vec<u8>>,
226 ) -> Vec<u8> {
227 let mut hasher = Sha256::new();
228
229 if let Some(prefix) = prefix {
230 hasher.update(prefix);
231 }
232
233 hasher.update((indices.len() as u32).to_le_bytes());
234 for &index in &indices {
235 hasher.update(&hashes[&(index as usize)]);
236 }
237
238 hasher.update([0x01]);
239
240 hasher.update(&[pubkeys.len() as u8, 0, 0, 0]);
241 for pubkey in pubkeys {
242 hasher.update(pubkey);
243 }
244
245 match signature {
246 Some(sig) => {
247 hasher.update([1, 0, 0, 0]);
248 hasher.update([0x00]);
249 hasher.update(sig);
250 }
251 None => {
252 hasher.update([0, 0, 0, 0]);
253 }
254 }
255
256 hasher.finalize().to_vec()
257 }
258
259 pub fn verify_signature(
261 &self,
262 signature: &ResponseSignature,
263 section_hashes: HashMap<usize, Vec<u8>>,
264 pubkey: &[u8],
265 ) -> bool {
266 use ed25519_dalek::{Signature, VerifyingKey};
267
268 if pubkey != &signature.pubkey {
269 return false;
270 }
271
272 let mut public_key_bytes = [0u8; 32];
273 public_key_bytes.copy_from_slice(&signature.pubkey[1..33]);
274 let public_key = VerifyingKey::from_bytes(&public_key_bytes).unwrap();
275 let unsigned_raw_sig_hash = self.hash_signature_sec(
276 vec![],
277 §ion_hashes,
278 signature.raw_indices.clone(),
279 None,
280 None,
281 );
282 let mut raw_signature_bytes = [0u8; 64];
283 raw_signature_bytes.copy_from_slice(&signature.raw_signature[1..65]);
284 let raw_signature = Signature::from_bytes(&raw_signature_bytes);
285 let raw_sig = public_key
286 .verify(&unsigned_raw_sig_hash, &raw_signature)
287 .is_ok();
288
289 let prefix: Vec<u8> = vec![0x03];
291 let raw_hash = self.hash_signature_sec(
292 vec![signature.pubkey.to_vec()],
293 §ion_hashes,
294 signature.raw_indices.clone(),
295 Some(signature.raw_signature.to_vec()),
296 Some(prefix),
297 );
298
299 let mut tmp_hashes = section_hashes.clone();
300 tmp_hashes.insert(tmp_hashes.len() - 1, raw_hash);
301
302 let unsigned_wrapper_sig_hash = self.hash_signature_sec(
303 vec![],
304 &tmp_hashes,
305 signature.wrapper_indices.clone(),
306 None,
307 None,
308 );
309
310 let mut wrapper_signature_bytes = [0u8; 64];
311 wrapper_signature_bytes.copy_from_slice(&signature.wrapper_signature[1..65]);
312 let wrapper_signature = Signature::from_bytes(&wrapper_signature_bytes);
313 let wrapper_sig = public_key
314 .verify(&unsigned_wrapper_sig_hash, &wrapper_signature)
315 .is_ok();
316
317 raw_sig && wrapper_sig
318 }
319
320 pub async fn retrieve_keys(
322 &self,
323 path: &BIP44Path,
324 key_type: NamadaKeys,
325 require_confirmation: bool,
326 ) -> Result<KeyResponse, NamError<E::Error>> {
327 let serialized_path = path.serialize_path().unwrap();
328 let p1: u8 = if require_confirmation { 1 } else { 0 };
329
330 let p2: u8 = match key_type {
331 NamadaKeys::PublicAddress => 0,
332 NamadaKeys::ViewKey => 1,
333 NamadaKeys::ProofGenerationKey => 2,
334 };
335
336 let command = APDUCommand {
337 cla: CLA,
338 ins: InstructionCode::GetKeys as _,
339 p1,
340 p2,
341 data: serialized_path,
342 };
343
344 let response = self
345 .apdu_transport
346 .exchange(&command)
347 .await
348 .map_err(LedgerAppError::TransportError)?;
349
350 match response.error_code() {
351 Ok(APDUErrorCode::NoError) => {}
352 Ok(err) => {
353 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
354 err as _,
355 err.description(),
356 )))
357 }
358 Err(err) => {
359 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
360 err,
361 "[APDU_ERROR] Unknown".to_string(),
362 )))
363 }
364 }
365
366 let response_data = response.apdu_data();
367 match key_type {
368 NamadaKeys::PublicAddress => Ok(KeyResponse::Address(ResponsePubAddress {
369 public_address: response_data[..PAYMENT_ADDR_LEN].try_into().unwrap(),
370 })),
371 NamadaKeys::ViewKey => Ok(KeyResponse::ViewKey(ResponseViewKey {
372 xfvk: response_data[..XFVK_LEN].try_into().unwrap(),
373 })),
374 NamadaKeys::ProofGenerationKey => {
375 let (ak, rest) = response_data.split_at(KEY_LEN);
376 let (nsk, _) = rest.split_at(KEY_LEN);
377 Ok(KeyResponse::ProofGenKey(ResponseProofGenKey {
378 ak: ak.try_into().unwrap(),
379 nsk: nsk.try_into().unwrap(),
380 }))
381 }
382 }
383 }
384
385 pub async fn get_spend_randomness(
387 &self,
388 ) -> Result<ResponseGetSpendRandomness, NamError<E::Error>> {
389 let arr: &[u8] = &[];
390 let command = APDUCommand {
391 cla: CLA,
392 ins: InstructionCode::GetSpendRandomness as _,
393 p1: 0x00,
394 p2: 0x00,
395 data: arr, };
397
398 let response = self
399 .apdu_transport
400 .exchange(&command)
401 .await
402 .map_err(LedgerAppError::TransportError)?;
403
404 match response.error_code() {
405 Ok(APDUErrorCode::NoError) => {}
406 Ok(err) => {
407 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
408 err as _,
409 err.description(),
410 )))
411 }
412 Err(err) => {
413 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
414 err,
415 "[APDU_ERROR] Unknown".to_string(),
416 )))
417 }
418 }
419
420 let response_data = response.apdu_data();
421 if response_data.len() < 2 * KEY_LEN {
422 return Err(NamError::Ledger(LedgerAppError::InvalidMessageSize));
423 }
424
425 let (rcv, rest) = response_data.split_at(KEY_LEN);
426 let (alpha, _) = rest.split_at(KEY_LEN);
427 Ok(ResponseGetSpendRandomness {
428 rcv: rcv.try_into().unwrap(),
429 alpha: alpha.try_into().unwrap(),
430 })
431 }
432
433 pub async fn get_convert_randomness(
435 &self,
436 ) -> Result<ResponseGetConvertRandomness, NamError<E::Error>> {
437 let arr: &[u8] = &[];
438 let command = APDUCommand {
439 cla: CLA,
440 ins: InstructionCode::GetConvertRandomness as _,
441 p1: 0x00,
442 p2: 0x00,
443 data: arr, };
445
446 let response = self
447 .apdu_transport
448 .exchange(&command)
449 .await
450 .map_err(LedgerAppError::TransportError)?;
451
452 match response.error_code() {
453 Ok(APDUErrorCode::NoError) => {}
454 Ok(err) => {
455 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
456 err as _,
457 err.description(),
458 )))
459 }
460 Err(err) => {
461 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
462 err,
463 "[APDU_ERROR] Unknown".to_string(),
464 )))
465 }
466 }
467
468 let response_data = response.apdu_data();
469 if response_data.len() < KEY_LEN {
470 return Err(NamError::Ledger(LedgerAppError::InvalidMessageSize));
471 }
472
473 let (rcv, _) = response_data.split_at(KEY_LEN);
474 Ok(ResponseGetConvertRandomness {
475 rcv: rcv.try_into().unwrap(),
476 })
477 }
478
479 pub async fn get_output_randomness(
481 &self,
482 ) -> Result<ResponseGetOutputRandomness, NamError<E::Error>> {
483 let arr: &[u8] = &[];
484 let command = APDUCommand {
485 cla: CLA,
486 ins: InstructionCode::GetOutputRandomness as _,
487 p1: 0x00,
488 p2: 0x00,
489 data: arr, };
491
492 let response = self
493 .apdu_transport
494 .exchange(&command)
495 .await
496 .map_err(LedgerAppError::TransportError)?;
497
498 match response.error_code() {
499 Ok(APDUErrorCode::NoError) => {}
500 Ok(err) => {
501 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
502 err as _,
503 err.description(),
504 )))
505 }
506 Err(err) => {
507 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
508 err,
509 "[APDU_ERROR] Unknown".to_string(),
510 )))
511 }
512 }
513
514 let response_data = response.apdu_data();
515 if response_data.len() < 2 * KEY_LEN {
516 return Err(NamError::Ledger(LedgerAppError::InvalidMessageSize));
517 }
518
519 let (rcv, rest) = response_data.split_at(KEY_LEN);
520 let (rcm, _) = rest.split_at(KEY_LEN);
521 Ok(ResponseGetOutputRandomness {
522 rcv: rcv.try_into().unwrap(),
523 rcm: rcm.try_into().unwrap(),
524 })
525 }
526
527 pub async fn get_spend_signature(&self) -> Result<ResponseSpendSignature, NamError<E::Error>> {
529 let arr: &[u8] = &[];
530 let command = APDUCommand {
531 cla: CLA,
532 ins: InstructionCode::ExtractSpendSignature as _,
533 p1: 0x00,
534 p2: 0x00,
535 data: arr, };
537
538 let response = self
539 .apdu_transport
540 .exchange(&command)
541 .await
542 .map_err(LedgerAppError::TransportError)?;
543
544 match response.error_code() {
545 Ok(APDUErrorCode::NoError) => {}
546 Ok(err) => {
547 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
548 err as _,
549 err.description(),
550 )))
551 }
552 Err(err) => {
553 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
554 err,
555 "[APDU_ERROR] Unknown".to_string(),
556 )))
557 }
558 }
559
560 let response_data = response.apdu_data();
561 if response_data.len() < 2 * KEY_LEN {
562 return Err(NamError::Ledger(LedgerAppError::InvalidMessageSize));
563 }
564
565 let (rbar, rest) = response_data.split_at(KEY_LEN);
566 let (sbar, _) = rest.split_at(KEY_LEN);
567 Ok(ResponseSpendSignature {
568 rbar: rbar.try_into().unwrap(),
569 sbar: sbar.try_into().unwrap(),
570 })
571 }
572
573 pub async fn sign_masp_spends(
575 &self,
576 path: &BIP44Path,
577 blob: &[u8],
578 ) -> Result<ResponseMaspSign, NamError<E::Error>> {
579 let first_chunk = path.serialize_path().unwrap();
580
581 let start_command = APDUCommand {
582 cla: CLA,
583 ins: InstructionCode::SignMaspSpends as _,
584 p1: ChunkPayloadType::Init as u8,
585 p2: 0x00,
586 data: first_chunk,
587 };
588
589 let response =
590 <Self as AppExt<E>>::send_chunks(&self.apdu_transport, start_command, blob).await?;
591
592 match response.error_code() {
593 Ok(APDUErrorCode::NoError) => {}
594 Ok(err) => {
595 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
596 err as _,
597 err.description(),
598 )))
599 }
600 Err(err) => {
601 return Err(NamError::Ledger(LedgerAppError::AppSpecific(
602 err,
603 "[APDU_ERROR] Unknown".to_string(),
604 )))
605 }
606 }
607
608 let rest = response.apdu_data();
610 let (hash, _) = rest.split_at(KEY_LEN);
611
612 Ok(ResponseMaspSign {
613 hash: hash.try_into().unwrap(),
614 })
615 }
616
617 pub async fn clean_randomness_buffers(&self) -> Result<(), NamError<E::Error>> {
619 let arr: &[u8] = &[];
620 let command = APDUCommand {
621 cla: CLA,
622 ins: InstructionCode::CleanBuffers as _,
623 p1: ChunkPayloadType::Init as u8,
624 p2: 0x00,
625 data: arr, };
627
628 self.apdu_transport
629 .exchange(&command)
630 .await
631 .map_err(LedgerAppError::TransportError)?;
632 Ok(())
633 }
634}