Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added function to open near app #23

Merged
merged 3 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 9 additions & 0 deletions examples/open_application.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use near_ledger::{open_near_application, NEARLedgerError};

fn main() -> Result<(), NEARLedgerError> {
env_logger::builder().init();

open_near_application()?;

Ok(())
}
151 changes: 150 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,9 +22,17 @@ 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;

/// 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<u8>`. The goal is naming to help understand what the bytes to deal with
pub type BorshSerializedUnsignedTransaction<'a> = &'a [u8];
/// Alias of `Vec<u8>`. The goal is naming to help understand what the bytes to deal with
Expand Down Expand Up @@ -117,6 +127,145 @@ pub fn get_version() -> Result<NEARLedgerAppVersion, NEARLedgerError> {
}
}

fn running_app_name() -> Result<String, NEARLedgerError> {
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();

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" => {}
_ => {
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<Vec<u8>> = 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(),
)),
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))
}
}
}
Err(err) => Err(NEARLedgerError::LedgerHIDError(err)),
}
}

/// Gets PublicKey from the Ledger on the given `hd_path`
///
/// # Inputs
Expand Down
Loading