Skip to content

Commit c258117

Browse files
committed
Support retrieving MASP keys.
1 parent ca9da5e commit c258117

File tree

3 files changed

+156
-15
lines changed

3 files changed

+156
-15
lines changed

rs/src/lib.rs

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,16 @@ pub use ledger_zondax_generic::LedgerAppError;
3232
mod params;
3333
use params::SALT_LEN;
3434
pub use params::{
35-
InstructionCode, ADDRESS_LEN, CLA, ED25519_PUBKEY_LEN, PK_LEN_PLUS_TAG, SIG_LEN_PLUS_TAG,
35+
InstructionCode, ADDRESS_LEN, CLA, ED25519_PUBKEY_LEN, KEY_LENGTH, PK_LEN_PLUS_TAG, SIG_LEN_PLUS_TAG,
3636
};
37-
use utils::{ResponseAddress, ResponseSignature};
37+
use utils::{P1Values, ResponseAddress, ResponseSignature};
3838

3939
use std::convert::TryInto;
4040
use std::str;
4141

4242
mod utils;
4343
pub use utils::BIP44Path;
44+
pub use utils::{KeyResponse, NamadaKeys};
4445

4546
/// Ledger App Error
4647
#[derive(Debug, thiserror::Error)]
@@ -137,11 +138,11 @@ where
137138
let (_public_key, rest) = rest.split_at((*public_key_len).into());
138139
let (address_len, rest) = rest.split_first().expect("response too short");
139140
let (address_bytes, rest) = rest.split_at((*address_len).into());
140-
if rest.len() > 0 {
141+
if !rest.is_empty() {
141142
panic!("response too long");
142143
}
143144

144-
let address_str = str::from_utf8(&address_bytes)
145+
let address_str = str::from_utf8(address_bytes)
145146
.map_err(|_| LedgerAppError::Utf8)?
146147
.to_owned();
147148

@@ -152,6 +153,91 @@ where
152153
})
153154
}
154155

156+
/// Retrieves the public key and address
157+
pub async fn retrieve_keys(
158+
&self,
159+
path: &BIP44Path,
160+
key_type: NamadaKeys,
161+
show_in_device: bool,
162+
) -> Result<KeyResponse, NamError<E::Error>> {
163+
let serialized_path = path.serialize_path().unwrap();
164+
let p1: u8 = if show_in_device {
165+
P1Values::ShowAddressInDevice
166+
} else {
167+
P1Values::OnlyRetrieve
168+
} as _;
169+
let command = APDUCommand {
170+
cla: CLA,
171+
ins: InstructionCode::GetKeys as _,
172+
p1,
173+
p2: key_type as _,
174+
data: serialized_path,
175+
};
176+
177+
let response = self
178+
.apdu_transport
179+
.exchange(&command)
180+
.await
181+
.map_err(LedgerAppError::TransportError)?;
182+
183+
match response.error_code() {
184+
Ok(APDUErrorCode::NoError) => {}
185+
Ok(err) => {
186+
return Err(NamError::Ledger(LedgerAppError::AppSpecific(
187+
err as _,
188+
err.description(),
189+
)))
190+
}
191+
Err(err) => {
192+
return Err(NamError::Ledger(LedgerAppError::AppSpecific(
193+
err,
194+
"[APDU_ERROR] Unknown".to_string(),
195+
)))
196+
}
197+
}
198+
199+
let response_data = response.data();
200+
201+
match key_type {
202+
NamadaKeys::PublicAddress => {
203+
let (public_address, rest) = response_data.split_at(KEY_LENGTH);
204+
if !rest.is_empty() {
205+
panic!("response too long");
206+
}
207+
208+
Ok(KeyResponse::Address {
209+
public_address: public_address.try_into().unwrap(),
210+
})
211+
},
212+
NamadaKeys::ViewKey => {
213+
let (view_key, rest) = response_data.split_at(2*KEY_LENGTH);
214+
let (ovk, rest) = rest.split_at(KEY_LENGTH);
215+
let (ivk, rest) = rest.split_at(KEY_LENGTH);
216+
if !rest.is_empty() {
217+
panic!("response too long");
218+
}
219+
220+
Ok(KeyResponse::ViewKey {
221+
view_key: view_key.try_into().unwrap(),
222+
ovk: ovk.try_into().unwrap(),
223+
ivk: ivk.try_into().unwrap(),
224+
})
225+
},
226+
NamadaKeys::ProofGenerationKey => {
227+
let (ak, rest) = response_data.split_at(KEY_LENGTH);
228+
let (nsk, rest) = rest.split_at(KEY_LENGTH);
229+
if !rest.is_empty() {
230+
panic!("response too long");
231+
}
232+
233+
Ok(KeyResponse::ProofGenKey {
234+
ak: ak.try_into().unwrap(),
235+
nsk: nsk.try_into().unwrap(),
236+
})
237+
},
238+
}
239+
}
240+
155241
/// Sign wrapper transaction
156242
pub async fn sign(
157243
&self,
@@ -232,7 +318,7 @@ where
232318

233319
hasher.update([0x01]);
234320

235-
hasher.update(&[pubkeys.len() as u8, 0, 0, 0]);
321+
hasher.update([pubkeys.len() as u8, 0, 0, 0]);
236322
for pubkey in pubkeys {
237323
hasher.update(pubkey);
238324
}
@@ -260,7 +346,7 @@ where
260346
) -> bool {
261347
use ed25519_dalek::{Signature, VerifyingKey};
262348

263-
if pubkey != &signature.pubkey {
349+
if pubkey != signature.pubkey {
264350
return false;
265351
}
266352

rs/src/params.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,26 @@ pub const SIG_LEN_PLUS_TAG: usize = ED25519_SIGNATURE_LEN + 1;
3333
/// Salt Length
3434
pub const SALT_LEN: usize = 8;
3535
/// Hash Length
36-
// pub const HASH_LEN: usize = 32;
36+
pub const KEY_LENGTH: usize = 32;
3737
/// Available instructions to interact with the Ledger device
3838
#[repr(u8)]
3939
pub enum InstructionCode {
40+
/// Instruction to get app version
41+
GetVersion = 0,
4042
/// Instruction to retrieve Pubkey and Address
4143
GetAddressAndPubkey = 1,
4244
/// Instruction to sign a transaction
4345
Sign = 2,
44-
45-
/// Instruction to retrieve a signed section
46-
GetSignature = 0x0a,
46+
/// Instruction to get keys
47+
GetKeys = 3,
48+
/// Instruction to get spend randomness
49+
GetSpendRand = 4,
50+
/// Instruction to get output randomness
51+
GetOutputRand = 5,
52+
/// Instruction to get convert randomness
53+
GetConvertRand = 6,
54+
/// Instruction to sign MASP transaction
55+
SignMasp = 7,
56+
/// Instruction to extract spend signature
57+
ExtractSpendSign = 8,
4758
}

rs/src/utils.rs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::error::Error;
2121

2222
const HARDENED: u32 = 0x80000000;
2323

24-
use crate::params::{ADDRESS_LEN, ED25519_PUBKEY_LEN, PK_LEN_PLUS_TAG, SALT_LEN, SIG_LEN_PLUS_TAG};
24+
use crate::params::{ADDRESS_LEN, ED25519_PUBKEY_LEN, KEY_LENGTH, PK_LEN_PLUS_TAG, SALT_LEN, SIG_LEN_PLUS_TAG};
2525
use byteorder::{LittleEndian, WriteBytesExt};
2626

2727
pub struct ResponseAddress {
@@ -54,7 +54,7 @@ impl BIP44Path {
5454
pub fn serialize_path(&self) -> Result<Vec<u8>, Box<dyn Error>> {
5555
if !self.path.starts_with('m') {
5656
return Err(
57-
format!("Path should start with \"m\" (e.g \"m/44'/5757'/5'/0/3\")").into(),
57+
"Path should start with \"m\" (e.g \"m/44'/5757'/5'/0/3\")".to_string().into(),
5858
);
5959
}
6060

@@ -69,10 +69,9 @@ impl BIP44Path {
6969
.write_u8((path_array.len() - 1) as u8)
7070
.unwrap();
7171

72-
for i in 1..path_array.len() {
72+
for mut child in path_array.iter().skip(1).copied() {
7373
let mut value = 0;
74-
let mut child = path_array[i];
75-
if child.ends_with("'") {
74+
if child.ends_with('\'') {
7675
value += HARDENED;
7776
child = &child[..child.len() - 1];
7877
}
@@ -90,6 +89,51 @@ impl BIP44Path {
9089
}
9190
}
9291

92+
/// Kinds of keys that can be requested in get keys instruction
93+
#[derive(Copy, Clone)]
94+
pub enum NamadaKeys {
95+
/// Public address request
96+
PublicAddress = 0x00,
97+
/// Viewing key request
98+
ViewKey = 0x01,
99+
/// Proof generation key request
100+
ProofGenerationKey = 0x02,
101+
}
102+
103+
/// Kinds of data retrieval
104+
pub enum P1Values {
105+
/// Request data without displaying it
106+
OnlyRetrieve = 0x00,
107+
/// Retrieve data whilst showing on screen
108+
ShowAddressInDevice = 0x01,
109+
}
110+
111+
/// Response to the get keys instruction
112+
pub enum KeyResponse {
113+
/// Address response
114+
Address {
115+
/// Public address
116+
public_address: [u8; KEY_LENGTH],
117+
},
118+
/// Viewing key responsee
119+
ViewKey {
120+
/// Viewing key
121+
view_key: [u8; KEY_LENGTH*2],
122+
/// Incoming viewing key
123+
ivk: [u8; KEY_LENGTH],
124+
/// Outgoing viewing key
125+
ovk: [u8; KEY_LENGTH],
126+
},
127+
/// Proof generation key response
128+
ProofGenKey {
129+
/// Spend authorization address key
130+
ak: [u8; KEY_LENGTH],
131+
/// Nullifier private key
132+
nsk: [u8; KEY_LENGTH],
133+
},
134+
}
135+
136+
93137
#[cfg(test)]
94138
mod tests {
95139
use super::BIP44Path;

0 commit comments

Comments
 (0)