parent
adcd2f14a5
commit
63813fe69f
|
@ -4525,6 +4525,8 @@ dependencies = [
|
||||||
"dialoguer",
|
"dialoguer",
|
||||||
"hidapi",
|
"hidapi",
|
||||||
"log 0.4.8",
|
"log 0.4.8",
|
||||||
|
"num-derive 0.3.0",
|
||||||
|
"num-traits",
|
||||||
"parking_lot 0.10.2",
|
"parking_lot 0.10.2",
|
||||||
"semver",
|
"semver",
|
||||||
"solana-sdk",
|
"solana-sdk",
|
||||||
|
|
|
@ -14,6 +14,8 @@ console = "0.10.1"
|
||||||
dialoguer = "0.6.2"
|
dialoguer = "0.6.2"
|
||||||
hidapi = { version = "1.2.1", default-features = false }
|
hidapi = { version = "1.2.1", default-features = false }
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
|
num-derive = { version = "0.3" }
|
||||||
|
num-traits = { version = "0.2" }
|
||||||
parking_lot = "0.10"
|
parking_lot = "0.10"
|
||||||
semver = "0.9"
|
semver = "0.9"
|
||||||
solana-sdk = { path = "../sdk", version = "1.2.0" }
|
solana-sdk = { path = "../sdk", version = "1.2.0" }
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
use crate::remote_wallet::{
|
use crate::{
|
||||||
DerivationPath, RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager,
|
ledger_error::LedgerError,
|
||||||
|
remote_wallet::{
|
||||||
|
DerivationPath, RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use console::Emoji;
|
use console::Emoji;
|
||||||
use dialoguer::{theme::ColorfulTheme, Select};
|
use dialoguer::{theme::ColorfulTheme, Select};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
use num_traits::FromPrimitive;
|
||||||
use semver::Version as FirmwareVersion;
|
use semver::Version as FirmwareVersion;
|
||||||
use solana_sdk::{pubkey::Pubkey, signature::Signature};
|
use solana_sdk::{pubkey::Pubkey, signature::Signature};
|
||||||
use std::{cmp::min, fmt, sync::Arc};
|
use std::{cmp::min, fmt, sync::Arc};
|
||||||
|
@ -30,6 +34,8 @@ const P2_EXTEND: u8 = 0x01;
|
||||||
const P2_MORE: u8 = 0x02;
|
const P2_MORE: u8 = 0x02;
|
||||||
const MAX_CHUNK_SIZE: usize = 255;
|
const MAX_CHUNK_SIZE: usize = 255;
|
||||||
|
|
||||||
|
const APDU_SUCCESS_CODE: usize = 0x9000;
|
||||||
|
|
||||||
const SOL_DERIVATION_PATH_BE: [u8; 8] = [0x80, 0, 0, 44, 0x80, 0, 0x01, 0xF5]; // 44'/501', Solana
|
const SOL_DERIVATION_PATH_BE: [u8; 8] = [0x80, 0, 0, 44, 0x80, 0, 0x01, 0xF5]; // 44'/501', Solana
|
||||||
|
|
||||||
/// Ledger vendor ID
|
/// Ledger vendor ID
|
||||||
|
@ -231,33 +237,7 @@ impl LedgerWallet {
|
||||||
let status =
|
let status =
|
||||||
(message[message.len() - 2] as usize) << 8 | (message[message.len() - 1] as usize);
|
(message[message.len() - 2] as usize) << 8 | (message[message.len() - 1] as usize);
|
||||||
trace!("Read status {:x}", status);
|
trace!("Read status {:x}", status);
|
||||||
#[allow(clippy::match_overlapping_arm)]
|
Self::parse_status(status)?;
|
||||||
match status {
|
|
||||||
// These need to be aligned with solana Ledger app error codes, and clippy allowance removed
|
|
||||||
0x6700 => Err(RemoteWalletError::Protocol(
|
|
||||||
"Solana app not open on Ledger device",
|
|
||||||
)),
|
|
||||||
0x6802 => Err(RemoteWalletError::Protocol("Invalid parameter")),
|
|
||||||
0x6803 => Err(RemoteWalletError::Protocol(
|
|
||||||
"Overflow: message longer than MAX_MESSAGE_LENGTH",
|
|
||||||
)),
|
|
||||||
0x6982 => Err(RemoteWalletError::Protocol(
|
|
||||||
"Security status not satisfied (Canceled by user)",
|
|
||||||
)),
|
|
||||||
0x6985 => Err(RemoteWalletError::UserCancel),
|
|
||||||
0x6a80 => Err(RemoteWalletError::Protocol("Invalid data")),
|
|
||||||
0x6a82 => Err(RemoteWalletError::Protocol("File not found")),
|
|
||||||
0x6b00 => Err(RemoteWalletError::Protocol("Incorrect parameters")),
|
|
||||||
0x6d00 => Err(RemoteWalletError::Protocol(
|
|
||||||
"Not implemented. Make sure the Ledger Solana Wallet app is running.",
|
|
||||||
)),
|
|
||||||
0x6faa => Err(RemoteWalletError::Protocol(
|
|
||||||
"Your Ledger device needs to be unplugged",
|
|
||||||
)),
|
|
||||||
0x6f00..=0x6fff => Err(RemoteWalletError::Protocol("Internal error")),
|
|
||||||
0x9000 => Ok(()),
|
|
||||||
_ => Err(RemoteWalletError::Protocol("Unknown error")),
|
|
||||||
}?;
|
|
||||||
let new_len = message.len() - 2;
|
let new_len = message.len() - 2;
|
||||||
message.truncate(new_len);
|
message.truncate(new_len);
|
||||||
Ok(message)
|
Ok(message)
|
||||||
|
@ -311,6 +291,16 @@ impl LedgerWallet {
|
||||||
fn outdated_app(&self) -> bool {
|
fn outdated_app(&self) -> bool {
|
||||||
self.version < DEPRECATE_VERSION_BEFORE
|
self.version < DEPRECATE_VERSION_BEFORE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_status(status: usize) -> Result<(), RemoteWalletError> {
|
||||||
|
if status == APDU_SUCCESS_CODE {
|
||||||
|
Ok(())
|
||||||
|
} else if let Some(err) = LedgerError::from_usize(status) {
|
||||||
|
Err(err.into())
|
||||||
|
} else {
|
||||||
|
Err(RemoteWalletError::Protocol("Unknown error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RemoteWallet for LedgerWallet {
|
impl RemoteWallet for LedgerWallet {
|
||||||
|
@ -582,4 +572,16 @@ mod tests {
|
||||||
assert!(!is_last_part(p2));
|
assert!(!is_last_part(p2));
|
||||||
assert!(is_last_part(p2 & !P2_MORE));
|
assert!(is_last_part(p2 & !P2_MORE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_status() {
|
||||||
|
assert_eq!(LedgerWallet::parse_status(APDU_SUCCESS_CODE).unwrap(), ());
|
||||||
|
if let RemoteWalletError::LedgerError(err) = LedgerWallet::parse_status(0x6985).unwrap_err()
|
||||||
|
{
|
||||||
|
assert_eq!(err, LedgerError::UserCancel);
|
||||||
|
}
|
||||||
|
if let RemoteWalletError::Protocol(err) = LedgerWallet::parse_status(0x6fff).unwrap_err() {
|
||||||
|
assert_eq!(err, "Unknown error");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
use num_derive::FromPrimitive;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Clone, FromPrimitive, PartialEq)]
|
||||||
|
pub enum LedgerError {
|
||||||
|
#[error("Solana app not open on Ledger device")]
|
||||||
|
NoAppResponse = 0x6700,
|
||||||
|
|
||||||
|
#[error("Ledger sdk exception")]
|
||||||
|
SdkException = 0x6801,
|
||||||
|
|
||||||
|
#[error("Ledger invalid parameter")]
|
||||||
|
SdkInvalidParameter = 0x6802,
|
||||||
|
|
||||||
|
#[error("Ledger overflow")]
|
||||||
|
SdkExceptionOverflow = 0x6803,
|
||||||
|
|
||||||
|
#[error("Ledger security exception")]
|
||||||
|
SdkExceptionSecurity = 0x6804,
|
||||||
|
|
||||||
|
#[error("Ledger invalid CRC")]
|
||||||
|
SdkInvalidCrc = 0x6805,
|
||||||
|
|
||||||
|
#[error("Ledger invalid checksum")]
|
||||||
|
SdkInvalidChecksum = 0x6806,
|
||||||
|
|
||||||
|
#[error("Ledger invalid counter")]
|
||||||
|
SdkInvalidCounter = 0x6807,
|
||||||
|
|
||||||
|
#[error("Ledger operation not supported")]
|
||||||
|
SdkNotSupported = 0x6808,
|
||||||
|
|
||||||
|
#[error("Ledger invalid state")]
|
||||||
|
SdkInvalidState = 0x6809,
|
||||||
|
|
||||||
|
#[error("Ledger timeout")]
|
||||||
|
SdkTimeout = 0x6810,
|
||||||
|
|
||||||
|
#[error("Ledger PIC exception")]
|
||||||
|
SdkExceptionPIC = 0x6811,
|
||||||
|
|
||||||
|
#[error("Ledger app exit exception")]
|
||||||
|
SdkExceptionAppExit = 0x6812,
|
||||||
|
|
||||||
|
#[error("Ledger IO overflow exception")]
|
||||||
|
SdkExceptionIoOverflow = 0x6813,
|
||||||
|
|
||||||
|
#[error("Ledger IO header exception")]
|
||||||
|
SdkExceptionIoHeader = 0x6814,
|
||||||
|
|
||||||
|
#[error("Ledger IO state exception")]
|
||||||
|
SdkExceptionIoState = 0x6815,
|
||||||
|
|
||||||
|
#[error("Ledger IO reset exception")]
|
||||||
|
SdkExceptionIoReset = 0x6816,
|
||||||
|
|
||||||
|
#[error("Ledger CX port exception")]
|
||||||
|
SdkExceptionCxPort = 0x6817,
|
||||||
|
|
||||||
|
#[error("Ledger system exception")]
|
||||||
|
SdkExceptionSystem = 0x6818,
|
||||||
|
|
||||||
|
#[error("Ledger out of space")]
|
||||||
|
SdkNotEnoughSpace = 0x6819,
|
||||||
|
|
||||||
|
#[error("Ledger invalid counter")]
|
||||||
|
NoApduReceived = 0x6982,
|
||||||
|
|
||||||
|
#[error("Ledger operation rejected by the user")]
|
||||||
|
UserCancel = 0x6985,
|
||||||
|
|
||||||
|
#[error("Ledger received invalid Solana message")]
|
||||||
|
SolanaInvalidMessage = 0x6a80,
|
||||||
|
|
||||||
|
#[error("Solana summary finalization failed on Ledger device")]
|
||||||
|
SolanaSummaryFinalizeFailed = 0x6f00,
|
||||||
|
|
||||||
|
#[error("Solana summary update failed on Ledger device")]
|
||||||
|
SolanaSummaryUpdateFailed = 0x6f01,
|
||||||
|
|
||||||
|
#[error("Ledger received unimplemented instruction")]
|
||||||
|
UnimplementedInstruction = 0x6d00,
|
||||||
|
|
||||||
|
#[error("Ledger received invalid CLA")]
|
||||||
|
InvalidCla = 0x6e00,
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod ledger;
|
pub mod ledger;
|
||||||
|
pub mod ledger_error;
|
||||||
pub mod remote_keypair;
|
pub mod remote_keypair;
|
||||||
pub mod remote_wallet;
|
pub mod remote_wallet;
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::ledger::{is_valid_ledger, LedgerWallet};
|
use crate::{
|
||||||
|
ledger::{is_valid_ledger, LedgerWallet},
|
||||||
|
ledger_error::LedgerError,
|
||||||
|
};
|
||||||
use log::*;
|
use log::*;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
|
@ -38,6 +41,9 @@ pub enum RemoteWalletError {
|
||||||
#[error("invalid path: {0}")]
|
#[error("invalid path: {0}")]
|
||||||
InvalidPath(String),
|
InvalidPath(String),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
LedgerError(#[from] LedgerError),
|
||||||
|
|
||||||
#[error("no device found")]
|
#[error("no device found")]
|
||||||
NoDeviceFound,
|
NoDeviceFound,
|
||||||
|
|
||||||
|
@ -64,6 +70,7 @@ impl From<RemoteWalletError> for SignerError {
|
||||||
RemoteWalletError::DeviceTypeMismatch => SignerError::Connection(err.to_string()),
|
RemoteWalletError::DeviceTypeMismatch => SignerError::Connection(err.to_string()),
|
||||||
RemoteWalletError::InvalidDevice => SignerError::Connection(err.to_string()),
|
RemoteWalletError::InvalidDevice => SignerError::Connection(err.to_string()),
|
||||||
RemoteWalletError::InvalidInput(input) => SignerError::InvalidInput(input),
|
RemoteWalletError::InvalidInput(input) => SignerError::InvalidInput(input),
|
||||||
|
RemoteWalletError::LedgerError(e) => SignerError::Protocol(e.to_string()),
|
||||||
RemoteWalletError::NoDeviceFound => SignerError::NoDeviceFound,
|
RemoteWalletError::NoDeviceFound => SignerError::NoDeviceFound,
|
||||||
RemoteWalletError::Protocol(e) => SignerError::Protocol(e.to_string()),
|
RemoteWalletError::Protocol(e) => SignerError::Protocol(e.to_string()),
|
||||||
RemoteWalletError::UserCancel => {
|
RemoteWalletError::UserCancel => {
|
||||||
|
@ -97,35 +104,54 @@ impl RemoteWalletManager {
|
||||||
let devices = usb.device_list();
|
let devices = usb.device_list();
|
||||||
let num_prev_devices = self.devices.read().len();
|
let num_prev_devices = self.devices.read().len();
|
||||||
|
|
||||||
let detected_devices = devices
|
let (detected_devices, errors) = devices
|
||||||
.filter(|&device_info| {
|
.filter(|&device_info| {
|
||||||
is_valid_hid_device(device_info.usage_page(), device_info.interface_number())
|
is_valid_hid_device(device_info.usage_page(), device_info.interface_number())
|
||||||
})
|
})
|
||||||
.fold(Vec::new(), |mut v, device_info| {
|
.fold(
|
||||||
if is_valid_ledger(device_info.vendor_id(), device_info.product_id()) {
|
(Vec::new(), Vec::new()),
|
||||||
match usb.open_path(&device_info.path()) {
|
|(mut devices, mut errors), device_info| {
|
||||||
Ok(device) => {
|
if is_valid_ledger(device_info.vendor_id(), device_info.product_id()) {
|
||||||
let mut ledger = LedgerWallet::new(device);
|
match usb.open_path(&device_info.path()) {
|
||||||
if let Ok(info) = ledger.read_device(&device_info) {
|
Ok(device) => {
|
||||||
ledger.pretty_path = info.get_pretty_path();
|
let mut ledger = LedgerWallet::new(device);
|
||||||
let path = device_info.path().to_str().unwrap().to_string();
|
let result = ledger.read_device(&device_info);
|
||||||
trace!("Found device: {:?}", info);
|
match result {
|
||||||
v.push(Device {
|
Ok(info) => {
|
||||||
path,
|
ledger.pretty_path = info.get_pretty_path();
|
||||||
info,
|
let path = device_info.path().to_str().unwrap().to_string();
|
||||||
wallet_type: RemoteWalletType::Ledger(Arc::new(ledger)),
|
trace!("Found device: {:?}", info);
|
||||||
})
|
devices.push(Device {
|
||||||
|
path,
|
||||||
|
info,
|
||||||
|
wallet_type: RemoteWalletType::Ledger(Arc::new(ledger)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
"Error connecting to ledger device to read info: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
errors.push(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("Error connecting to ledger device to read info: {}", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => error!("Error connecting to ledger device to read info: {}", e),
|
|
||||||
}
|
}
|
||||||
}
|
(devices, errors)
|
||||||
v
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
let num_curr_devices = detected_devices.len();
|
let num_curr_devices = detected_devices.len();
|
||||||
*self.devices.write() = detected_devices;
|
*self.devices.write() = detected_devices;
|
||||||
|
|
||||||
|
if num_curr_devices == 0 && !errors.is_empty() {
|
||||||
|
return Err(errors[0].clone());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(num_curr_devices - num_prev_devices)
|
Ok(num_curr_devices - num_prev_devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -220,7 +220,7 @@ pub enum SignerError {
|
||||||
#[error("no device found")]
|
#[error("no device found")]
|
||||||
NoDeviceFound,
|
NoDeviceFound,
|
||||||
|
|
||||||
#[error("device protocol error: {0}")]
|
#[error("{0}")]
|
||||||
Protocol(String),
|
Protocol(String),
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
|
|
Loading…
Reference in New Issue