diff --git a/rs/src/lib.rs b/rs/src/lib.rs index 6d57466a..9188e202 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -32,15 +32,16 @@ pub use ledger_zondax_generic::LedgerAppError; mod params; use params::SALT_LEN; pub use params::{ - InstructionCode, ADDRESS_LEN, CLA, ED25519_PUBKEY_LEN, PK_LEN_PLUS_TAG, SIG_LEN_PLUS_TAG, + InstructionCode, ADDRESS_LEN, CLA, ED25519_PUBKEY_LEN, KEY_LENGTH, PK_LEN_PLUS_TAG, SIG_LEN_PLUS_TAG, }; -use utils::{ResponseAddress, ResponseSignature}; +use utils::{P1Values, ResponseAddress, ResponseSignature}; use std::convert::TryInto; use std::str; mod utils; pub use utils::BIP44Path; +pub use utils::{KeyResponse, NamadaKeys}; /// Ledger App Error #[derive(Debug, thiserror::Error)] @@ -137,11 +138,11 @@ where let (_public_key, rest) = rest.split_at((*public_key_len).into()); let (address_len, rest) = rest.split_first().expect("response too short"); let (address_bytes, rest) = rest.split_at((*address_len).into()); - if rest.len() > 0 { + if !rest.is_empty() { panic!("response too long"); } - let address_str = str::from_utf8(&address_bytes) + let address_str = str::from_utf8(address_bytes) .map_err(|_| LedgerAppError::Utf8)? .to_owned(); @@ -152,6 +153,91 @@ where }) } + /// Retrieves the public key and address + pub async fn retrieve_keys( + &self, + path: &BIP44Path, + key_type: NamadaKeys, + show_in_device: bool, + ) -> Result> { + let serialized_path = path.serialize_path().unwrap(); + let p1: u8 = if show_in_device { + P1Values::ShowAddressInDevice + } else { + P1Values::OnlyRetrieve + } as _; + let command = APDUCommand { + cla: CLA, + ins: InstructionCode::GetKeys as _, + p1, + p2: key_type as _, + data: serialized_path, + }; + + let response = self + .apdu_transport + .exchange(&command) + .await + .map_err(LedgerAppError::TransportError)?; + + match response.error_code() { + Ok(APDUErrorCode::NoError) => {} + Ok(err) => { + return Err(NamError::Ledger(LedgerAppError::AppSpecific( + err as _, + err.description(), + ))) + } + Err(err) => { + return Err(NamError::Ledger(LedgerAppError::AppSpecific( + err, + "[APDU_ERROR] Unknown".to_string(), + ))) + } + } + + let response_data = response.data(); + + match key_type { + NamadaKeys::PublicAddress => { + let (public_address, rest) = response_data.split_at(KEY_LENGTH); + if !rest.is_empty() { + panic!("response too long"); + } + + Ok(KeyResponse::Address { + public_address: public_address.try_into().unwrap(), + }) + }, + NamadaKeys::ViewKey => { + let (view_key, rest) = response_data.split_at(2*KEY_LENGTH); + let (ovk, rest) = rest.split_at(KEY_LENGTH); + let (ivk, rest) = rest.split_at(KEY_LENGTH); + if !rest.is_empty() { + panic!("response too long"); + } + + Ok(KeyResponse::ViewKey { + view_key: view_key.try_into().unwrap(), + ovk: ovk.try_into().unwrap(), + ivk: ivk.try_into().unwrap(), + }) + }, + NamadaKeys::ProofGenerationKey => { + let (ak, rest) = response_data.split_at(KEY_LENGTH); + let (nsk, rest) = rest.split_at(KEY_LENGTH); + if !rest.is_empty() { + panic!("response too long"); + } + + Ok(KeyResponse::ProofGenKey { + ak: ak.try_into().unwrap(), + nsk: nsk.try_into().unwrap(), + }) + }, + } + } + /// Sign wrapper transaction pub async fn sign( &self, @@ -232,7 +318,7 @@ where hasher.update([0x01]); - hasher.update(&[pubkeys.len() as u8, 0, 0, 0]); + hasher.update([pubkeys.len() as u8, 0, 0, 0]); for pubkey in pubkeys { hasher.update(pubkey); } @@ -260,7 +346,7 @@ where ) -> bool { use ed25519_dalek::{Signature, VerifyingKey}; - if pubkey != &signature.pubkey { + if pubkey != signature.pubkey { return false; } diff --git a/rs/src/params.rs b/rs/src/params.rs index 36ce7f6d..4f580ebd 100644 --- a/rs/src/params.rs +++ b/rs/src/params.rs @@ -33,15 +33,26 @@ pub const SIG_LEN_PLUS_TAG: usize = ED25519_SIGNATURE_LEN + 1; /// Salt Length pub const SALT_LEN: usize = 8; /// Hash Length -// pub const HASH_LEN: usize = 32; +pub const KEY_LENGTH: usize = 32; /// Available instructions to interact with the Ledger device #[repr(u8)] pub enum InstructionCode { + /// Instruction to get app version + GetVersion = 0, /// Instruction to retrieve Pubkey and Address GetAddressAndPubkey = 1, /// Instruction to sign a transaction Sign = 2, - - /// Instruction to retrieve a signed section - GetSignature = 0x0a, + /// Instruction to get keys + GetKeys = 3, + /// Instruction to get spend randomness + GetSpendRand = 4, + /// Instruction to get output randomness + GetOutputRand = 5, + /// Instruction to get convert randomness + GetConvertRand = 6, + /// Instruction to sign MASP transaction + SignMasp = 7, + /// Instruction to extract spend signature + ExtractSpendSign = 8, } diff --git a/rs/src/utils.rs b/rs/src/utils.rs index 125009b3..4cb5f325 100644 --- a/rs/src/utils.rs +++ b/rs/src/utils.rs @@ -21,7 +21,7 @@ use std::error::Error; const HARDENED: u32 = 0x80000000; -use crate::params::{ADDRESS_LEN, ED25519_PUBKEY_LEN, PK_LEN_PLUS_TAG, SALT_LEN, SIG_LEN_PLUS_TAG}; +use crate::params::{ADDRESS_LEN, ED25519_PUBKEY_LEN, KEY_LENGTH, PK_LEN_PLUS_TAG, SALT_LEN, SIG_LEN_PLUS_TAG}; use byteorder::{LittleEndian, WriteBytesExt}; pub struct ResponseAddress { @@ -54,7 +54,7 @@ impl BIP44Path { pub fn serialize_path(&self) -> Result, Box> { if !self.path.starts_with('m') { return Err( - format!("Path should start with \"m\" (e.g \"m/44'/5757'/5'/0/3\")").into(), + "Path should start with \"m\" (e.g \"m/44'/5757'/5'/0/3\")".to_string().into(), ); } @@ -69,10 +69,9 @@ impl BIP44Path { .write_u8((path_array.len() - 1) as u8) .unwrap(); - for i in 1..path_array.len() { + for mut child in path_array.iter().skip(1).copied() { let mut value = 0; - let mut child = path_array[i]; - if child.ends_with("'") { + if child.ends_with('\'') { value += HARDENED; child = &child[..child.len() - 1]; } @@ -90,6 +89,51 @@ impl BIP44Path { } } +/// Kinds of keys that can be requested in get keys instruction +#[derive(Copy, Clone)] +pub enum NamadaKeys { + /// Public address request + PublicAddress = 0x00, + /// Viewing key request + ViewKey = 0x01, + /// Proof generation key request + ProofGenerationKey = 0x02, +} + +/// Kinds of data retrieval +pub enum P1Values { + /// Request data without displaying it + OnlyRetrieve = 0x00, + /// Retrieve data whilst showing on screen + ShowAddressInDevice = 0x01, +} + +/// Response to the get keys instruction +pub enum KeyResponse { + /// Address response + Address { + /// Public address + public_address: [u8; KEY_LENGTH], + }, + /// Viewing key responsee + ViewKey { + /// Viewing key + view_key: [u8; KEY_LENGTH*2], + /// Incoming viewing key + ivk: [u8; KEY_LENGTH], + /// Outgoing viewing key + ovk: [u8; KEY_LENGTH], + }, + /// Proof generation key response + ProofGenKey { + /// Spend authorization address key + ak: [u8; KEY_LENGTH], + /// Nullifier private key + nsk: [u8; KEY_LENGTH], + }, +} + + #[cfg(test)] mod tests { use super::BIP44Path;