From e94104f2d3741eca20f2cba68a35f1a771b6b5dc Mon Sep 17 00:00:00 2001 From: akorchyn Date: Fri, 23 Aug 2024 15:07:09 +0300 Subject: [PATCH 1/3] ledger app manipulations --- Cargo.toml | 3 + examples/open_application.rs | 9 +++ src/lib.rs | 144 ++++++++++++++++++++++++++++++++++- 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 examples/open_application.rs diff --git a/Cargo.toml b/Cargo.toml index 60dbd85..bf2c88a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ keywords = ["ledger", "nearprotocol"] [[example]] name = "get_version" +[[example]] +name = "open_application" + [[example]] name = "get_public_key_display" path = "examples/get_public_key/display.rs" diff --git a/examples/open_application.rs b/examples/open_application.rs new file mode 100644 index 0000000..86b5a47 --- /dev/null +++ b/examples/open_application.rs @@ -0,0 +1,9 @@ +use near_ledger::{open_near_application, NEARLedgerError}; + +fn main() -> Result<(), NEARLedgerError> { + env_logger::builder().init(); + + open_near_application()?; + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index c05c578..66abe1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,8 @@ //! Provides a set of commands that can be executed to communicate with NEAR App installed on Ledger device: //! - Read PublicKey from Ledger device by HD Path //! - Sign a Transaction +use std::{thread::sleep, time::Duration}; + use borsh::BorshSerialize; use ed25519_dalek::PUBLIC_KEY_LENGTH; use ledger_apdu::APDUAnswer; @@ -20,9 +22,13 @@ const INS_SIGN_TRANSACTION: u8 = 2; // Instruction code to sign a transaction on const INS_SIGN_NEP413_MESSAGE: u8 = 7; // Instruction code to sign a nep-413 message with Ledger const INS_SIGN_NEP366_DELEGATE_ACTION: u8 = 8; // Instruction code to sign a nep-413 message with Ledger const NETWORK_ID: u8 = b'W'; // Instruction parameter 2 -const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger +const RETURN_CODE_OK: u16 = 0x9000; // APDUAnswer.retcode which means success from Ledger const CHUNK_SIZE: usize = 250; // Chunk size to be sent to Ledger +const RETURN_CODE_APP_MISSING: u16 = 0x6807; +const RETURN_CODE_ERROR_INPUT: u16 = 0x670A; +const RETURN_CODE_UNKNOWN_ERROR: u16 = 0x5515; + /// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with pub type BorshSerializedUnsignedTransaction<'a> = &'a [u8]; /// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with @@ -117,6 +123,142 @@ pub fn get_version() -> Result { } } +fn running_app_name() -> Result { + let transport = get_transport()?; + + let command = APDUCommand { + cla: 0xB0, + ins: 0x01, + p1: 0, + p2: 0, + data: vec![], + }; + + match transport.exchange(&command) { + Ok(response) => { + log::info!( + "APDU out: {}\nAPDU ret code: {:x}", + hex::encode(response.apdu_data()), + response.retcode(), + ); + + // Ok means we successfully exchanged with the Ledger + // but doesn't mean our request succeeded + // we need to check it based on `response.retcode` + match response.retcode() { + RETURN_CODE_OK => { + // Output format: + // * format u8 + // * ascii name length u8 + // * name in ascii + + let data = response.data(); + let app_name_len = data[1] as usize; + let app_name = String::from_utf8_lossy(&data[2..2 + app_name_len]).to_string(); + + return Ok(app_name); + } + RETURN_CODE_UNKNOWN_ERROR => Err(NEARLedgerError::APDUExchangeError( + "The ledger most likely is locked. Please unlock ledger or reconnect it" + .to_string(), + )), + retcode => { + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + Err(NEARLedgerError::APDUExchangeError(error_string)) + } + } + } + Err(err) => Err(NEARLedgerError::LedgerHIDError(err)), + } +} + +fn quit_open_application() -> Result<(), NEARLedgerError> { + let transport = get_transport()?; + + let command = APDUCommand { + cla: 0xB0, + ins: 0xa7, + p1: 0, + p2: 0, + data: vec![], + }; + + match transport.exchange(&command) { + Ok(response) => { + log::info!( + "APDU out: {}\nAPDU ret code: {:x}", + hex::encode(response.apdu_data()), + response.retcode(), + ); + + // Ok means we successfully exchanged with the Ledger + // but doesn't mean our request succeeded + // we need to check it based on `response.retcode` + match response.retcode() { + RETURN_CODE_OK => Ok(()), + retcode => { + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + Err(NEARLedgerError::APDUExchangeError(error_string)) + } + } + } + Err(err) => Err(NEARLedgerError::LedgerHIDError(err)), + } +} + +/// Open the NEAR application on the Ledger device +/// +/// This is needed to do before calling other NEAR application +/// related methods +pub fn open_near_application() -> Result<(), NEARLedgerError> { + match running_app_name()?.as_str() { + "NEAR" => return Ok(()), + // BOLOS is a ledger dashboard + "BOLOS" => {} + x => { + quit_open_application()?; + // It won't work if we don't wait for the Ledger to close the app + sleep(Duration::from_secs(1)); + } + } + + let transport = get_transport()?; + let data = vec![b'N', b'E', b'A', b'R']; + let command: APDUCommand> = APDUCommand { + cla: 0xE0, + ins: 0xD8, + p1: 0x00, + p2: 0x00, + data, + }; + + log::info!("APDU in: {}", hex::encode(command.serialize())); + + match transport.exchange(&command) { + Ok(response) => { + log::info!("APDU ret code: {:x}", response.retcode(),); + + // Ok means we successfully exchanged with the Ledger + // but doesn't mean our request succeeded + // we need to check it based on `response.retcode` + match response.retcode() { + RETURN_CODE_OK => Ok(()), + RETURN_CODE_APP_MISSING => Err(NEARLedgerError::APDUExchangeError( + "NEAR application is missing on the Ledger device".to_string(), + )), + RETURN_CODE_ERROR_INPUT => Err(NEARLedgerError::APDUExchangeError( + "Internal error: the input length of bytes is not correct".to_string(), + )), + retcode => { + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + Err(NEARLedgerError::APDUExchangeError(error_string)) + } + } + } + Err(err) => Err(NEARLedgerError::LedgerHIDError(err)), + } +} + /// Gets PublicKey from the Ledger on the given `hd_path` /// /// # Inputs From ca86e01566c27b1b3af8cd21d06ddae84d356ae1 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Fri, 23 Aug 2024 15:14:41 +0300 Subject: [PATCH 2/3] added extra error --- src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 66abe1d..0f722eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,10 @@ const RETURN_CODE_APP_MISSING: u16 = 0x6807; const RETURN_CODE_ERROR_INPUT: u16 = 0x670A; const RETURN_CODE_UNKNOWN_ERROR: u16 = 0x5515; +/// This error code is returned when the user declines to open the app. +/// But I couldn't find it in the any of the ledger documentation... +const RETURN_CODE_DECLINE: u16 = 0x5501; + /// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with pub type BorshSerializedUnsignedTransaction<'a> = &'a [u8]; /// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with @@ -215,7 +219,7 @@ pub fn open_near_application() -> Result<(), NEARLedgerError> { "NEAR" => return Ok(()), // BOLOS is a ledger dashboard "BOLOS" => {} - x => { + _ => { quit_open_application()?; // It won't work if we don't wait for the Ledger to close the app sleep(Duration::from_secs(1)); @@ -249,6 +253,9 @@ pub fn open_near_application() -> Result<(), NEARLedgerError> { RETURN_CODE_ERROR_INPUT => Err(NEARLedgerError::APDUExchangeError( "Internal error: the input length of bytes is not correct".to_string(), )), + RETURN_CODE_DECLINE => Err(NEARLedgerError::APDUExchangeError( + "User declined to open the NEAR app".to_string(), + )), retcode => { let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); Err(NEARLedgerError::APDUExchangeError(error_string)) From d96befb1efb89cd985bf58770e16931770744bf4 Mon Sep 17 00:00:00 2001 From: akorchyn Date: Fri, 23 Aug 2024 15:18:41 +0300 Subject: [PATCH 3/3] clippy --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0f722eb..49f4f7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -160,7 +160,7 @@ fn running_app_name() -> Result { let app_name_len = data[1] as usize; let app_name = String::from_utf8_lossy(&data[2..2 + app_name_len]).to_string(); - return Ok(app_name); + Ok(app_name) } RETURN_CODE_UNKNOWN_ERROR => Err(NEARLedgerError::APDUExchangeError( "The ledger most likely is locked. Please unlock ledger or reconnect it"