ledger_namada_rs/
lib.rs

1/*******************************************************************************
2*   (c) 2018 - 2023 ZondaX AG
3*
4*  Licensed under the Apache License, Version 2.0 (the "License");
5*  you may not use this file except in compliance with the License.
6*  You may obtain a copy of the License at
7*
8*      http://www.apache.org/licenses/LICENSE-2.0
9*
10*  Unless required by applicable law or agreed to in writing, software
11*  distributed under the License is distributed on an "AS IS" BASIS,
12*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*  See the License for the specific language governing permissions and
14*  limitations under the License.
15********************************************************************************/
16//! Support library for Namada Ledger Nano S/S+/X/Stax apps
17
18#![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/// Ledger App Error
51#[derive(Debug, thiserror::Error)]
52pub enum NamError<E>
53where
54    E: std::error::Error,
55{
56    #[error("Ledger | {0}")]
57    /// Common Ledger errors
58    Ledger(#[from] LedgerAppError<E>),
59    // /// Device related errors
60    // #[error("Secp256k1 error: {0}")]
61    // Secp256k1(#[from] k256::elliptic_curve::Error),
62
63    // /// Device related errors
64    // #[error("Ecdsa error: {0}")]
65    // Ecdsa(#[from] k256::ecdsa::Error),
66}
67
68/// Namada App
69pub 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    /// Create a new [`NamadaApp`] with the given transport
79    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    /// Retrieve the app version
92    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    /// Retrieves the public key and address
99    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    /// Sign wrapper transaction
161    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        // Transactions is signed - Retrieve signatures
196        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    /// Compute hash from signature section
219    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    /// Verify signature
260    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            &section_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        // Verify wrapper signature
290        let prefix: Vec<u8> = vec![0x03];
291        let raw_hash = self.hash_signature_sec(
292            vec![signature.pubkey.to_vec()],
293            &section_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    /// Retrieve masp keys from the Namada app
321    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    /// Get Randomness for Spend
386    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, // Send empty data
396        };
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    /// Get Randomness for convert
434    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, // Send empty data
444        };
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    /// Get Randomness for output
480    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, // Send empty data
490        };
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    /// Get Spend signature
528    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, // Send empty data
536        };
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    /// Sign Masp signing
574    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        // Transactions is signed - Retrieve signatures
609        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    /// Clean buffers
618    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, // Send empty data
626        };
627
628        self.apdu_transport
629            .exchange(&command)
630            .await
631            .map_err(LedgerAppError::TransportError)?;
632        Ok(())
633    }
634}