Skip to content

Commit

Permalink
Support retrieving MASP keys.
Browse files Browse the repository at this point in the history
  • Loading branch information
murisi committed Jul 22, 2024
1 parent ca9da5e commit c258117
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 15 deletions.
98 changes: 92 additions & 6 deletions rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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();

Expand All @@ -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<KeyResponse, NamError<E::Error>> {
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,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -260,7 +346,7 @@ where
) -> bool {
use ed25519_dalek::{Signature, VerifyingKey};

if pubkey != &signature.pubkey {
if pubkey != signature.pubkey {
return false;
}

Expand Down
19 changes: 15 additions & 4 deletions rs/src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
54 changes: 49 additions & 5 deletions rs/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -54,7 +54,7 @@ impl BIP44Path {
pub fn serialize_path(&self) -> Result<Vec<u8>, Box<dyn Error>> {
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(),
);
}

Expand All @@ -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];
}
Expand All @@ -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;
Expand Down

0 comments on commit c258117

Please sign in to comment.