Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit

Permalink
Fixup sign_transaction; pass derivation_path by reference (#8194)
Browse files Browse the repository at this point in the history
* Fixup sign_transaction; pass derivation_path by reference

* Pass total message length as BE u16

* Remove live integration tests (to ledger-app-solana)

(cherry picked from commit 25d1f84)
  • Loading branch information
CriesofCarrots authored and Tyera Eulberg committed Feb 11, 2020
1 parent 34238d5 commit 6c1135a
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 123 deletions.
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ impl CliConfig {
derivation_path = derivation;
}
let ledger = get_ledger_from_info(remote_wallet_info)?;
return Ok(ledger.get_pubkey(derivation_path)?);
return Ok(ledger.get_pubkey(&derivation_path)?);
}
}
Ok(self.keypair.pubkey())
Expand Down
2 changes: 1 addition & 1 deletion keygen/src/keygen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ fn get_pubkey_from_matches(
derivation_path = derivation;
}
let ledger = get_ledger_from_info(remote_wallet_info)?;
Ok(ledger.get_pubkey(derivation_path)?)
Ok(ledger.get_pubkey(&derivation_path)?)
} else {
read_keypair_file(keypair).map(|keypair| keypair.pubkey())
}
Expand Down
148 changes: 35 additions & 113 deletions remote-wallet/src/ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ use std::{cmp::min, fmt, sync::Arc};

const APDU_TAG: u8 = 0x05;
const APDU_CLA: u8 = 0xe0;
const APDU_PAYLOAD_HEADER_LEN: usize = 7;
const APDU_PAYLOAD_HEADER_LEN: usize = 8;

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, 0x00, 0x94]; // 44'/148', Stellar

/// Ledger vendor ID
const LEDGER_VID: u16 = 0x2c97;
/// Ledger product IDs: Nano S and Nano X
Expand All @@ -30,8 +28,6 @@ const LEDGER_NANO_X_PIDS: [u16; 33] = [
];
const LEDGER_TRANSPORT_HEADER_LEN: usize = 5;

const MAX_CHUNK_SIZE: usize = 255;

const HID_PACKET_SIZE: usize = 64 + HID_PREFIX_ZERO;

#[cfg(windows)]
Expand All @@ -40,9 +36,10 @@ const HID_PREFIX_ZERO: usize = 1;
const HID_PREFIX_ZERO: usize = 0;

mod commands {
#[allow(dead_code)]
pub const GET_APP_CONFIGURATION: u8 = 0x06;
pub const GET_SOL_PUBKEY: u8 = 0x02;
pub const SIGN_SOL_TRANSACTION: u8 = 0x04;
pub const SIGN_SOL_TRANSACTION: u8 = 0x03;
}

/// Ledger Wallet device
Expand Down Expand Up @@ -100,14 +97,15 @@ impl LedgerWallet {
]);

if sequence_number == 0 {
let data_len = data.len() + 5;
chunk[5..12].copy_from_slice(&[
let data_len = data.len() + 6;
chunk[5..13].copy_from_slice(&[
(data_len >> 8) as u8,
(data_len & 0xff) as u8,
APDU_CLA,
command,
p1,
p2,
(data.len() >> 8) as u8,
data.len() as u8,
]);
}
Expand Down Expand Up @@ -224,7 +222,7 @@ impl LedgerWallet {
self.read()
}

fn get_firmware_version(&self) -> Result<FirmwareVersion, RemoteWalletError> {
fn _get_firmware_version(&self) -> Result<FirmwareVersion, RemoteWalletError> {
let ver = self.send_apdu(commands::GET_APP_CONFIGURATION, 0, 0, &[])?;
if ver.len() != 4 {
return Err(RemoteWalletError::Protocol("Version packet size mismatch"));
Expand Down Expand Up @@ -258,7 +256,7 @@ impl RemoteWallet for LedgerWallet {
.serial_number
.clone()
.unwrap_or_else(|| "Unknown".to_owned());
self.get_pubkey(DerivationPath::default())
self.get_pubkey(&DerivationPath::default())
.map(|pubkey| RemoteWalletInfo {
model,
manufacturer,
Expand All @@ -267,10 +265,15 @@ impl RemoteWallet for LedgerWallet {
})
}

fn get_pubkey(&self, derivation: DerivationPath) -> Result<Pubkey, RemoteWalletError> {
fn get_pubkey(&self, derivation: &DerivationPath) -> Result<Pubkey, RemoteWalletError> {
let derivation_path = get_derivation_path(derivation);

let key = self.send_apdu(commands::GET_SOL_PUBKEY, 0, 0, &derivation_path)?;
let key = self.send_apdu(
commands::GET_SOL_PUBKEY,
0, // In the naive implementation, default request is for no device confirmation
0,
&derivation_path,
)?;
if key.len() != 32 {
return Err(RemoteWalletError::Protocol("Key packet size mismatch"));
}
Expand All @@ -279,44 +282,28 @@ impl RemoteWallet for LedgerWallet {

fn sign_transaction(
&self,
derivation: DerivationPath,
derivation: &DerivationPath,
transaction: Transaction,
) -> Result<Signature, RemoteWalletError> {
let mut chunk = [0_u8; MAX_CHUNK_SIZE];
let derivation_path = get_derivation_path(derivation);
let data = transaction.message_data();

let _firmware_version = self.get_firmware_version();

// Copy the address of the key (only done once)
chunk[0..derivation_path.len()].copy_from_slice(&derivation_path);

let key_length = derivation_path.len();
let max_payload_size = MAX_CHUNK_SIZE - key_length;
let data_len = data.len();

let mut result = Vec::new();
let mut offset = 0;

while offset < data_len {
let p1 = if offset == 0 { 0 } else { 0x80 };
let take = min(max_payload_size, data_len - offset);

// Fetch piece of data and copy it!
{
let (_key, d) = &mut chunk.split_at_mut(key_length);
let (dst, _rem) = &mut d.split_at_mut(take);
dst.copy_from_slice(&data[offset..(offset + take)]);
}

result = self.send_apdu(
commands::SIGN_SOL_TRANSACTION,
p1,
0,
&chunk[0..(key_length + take)],
)?;
offset += take;
let mut payload = get_derivation_path(derivation);
let mut data = transaction.message_data();
if data.len() > u16::max_value() as usize {
return Err(RemoteWalletError::InvalidInput(
"Message to sign is too long".to_string(),
));
}
for byte in (data.len() as u16).to_be_bytes().iter() {
payload.push(*byte);
}
payload.append(&mut data);
trace!("Serialized payload length {:?}", payload.len());

let result = self.send_apdu(
commands::SIGN_SOL_TRANSACTION,
1, // In the naive implementation, default request is for requred device confirmation
0,
&payload,
)?;

if result.len() != 64 {
return Err(RemoteWalletError::Protocol(
Expand All @@ -334,7 +321,7 @@ pub fn is_valid_ledger(vendor_id: u16, product_id: u16) -> bool {
}

/// Build the derivation path byte array from a DerivationPath selection
fn get_derivation_path(derivation: DerivationPath) -> Vec<u8> {
fn get_derivation_path(derivation: &DerivationPath) -> Vec<u8> {
let byte = if derivation.change.is_some() { 4 } else { 3 };
let mut concat_derivation = vec![byte];
concat_derivation.extend_from_slice(&SOL_DERIVATION_PATH_BE);
Expand Down Expand Up @@ -375,68 +362,3 @@ pub fn get_ledger_from_info(
};
wallet_manager.get_ledger(&wallet_base_pubkey)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::remote_wallet::initialize_wallet_manager;
use std::collections::HashSet;

/// This test can't be run without an actual ledger device connected with the `Ledger Wallet Solana application` running
#[test]
#[ignore]
fn ledger_pubkey_test() {
let wallet_manager = initialize_wallet_manager();

// Update device list
wallet_manager.update_devices().expect("No Ledger found, make sure you have a unlocked Ledger connected with the Ledger Wallet Solana running");
assert!(wallet_manager.list_devices().len() > 0);

// Fetch the base pubkey of a connected ledger device
let ledger_base_pubkey = wallet_manager
.list_devices()
.iter()
.filter(|d| d.manufacturer == "ledger".to_string())
.nth(0)
.map(|d| d.pubkey.clone())
.expect("No ledger device detected");

let ledger = wallet_manager
.get_ledger(&ledger_base_pubkey)
.expect("get device");

let mut pubkey_set = HashSet::new();
pubkey_set.insert(ledger_base_pubkey);

let pubkey_0_0 = ledger
.get_pubkey(DerivationPath {
account: 0,
change: Some(0),
})
.expect("get pubkey");
pubkey_set.insert(pubkey_0_0);
let pubkey_0_1 = ledger
.get_pubkey(DerivationPath {
account: 0,
change: Some(1),
})
.expect("get pubkey");
pubkey_set.insert(pubkey_0_1);
let pubkey_1 = ledger
.get_pubkey(DerivationPath {
account: 1,
change: None,
})
.expect("get pubkey");
pubkey_set.insert(pubkey_1);
let pubkey_1_0 = ledger
.get_pubkey(DerivationPath {
account: 1,
change: Some(0),
})
.expect("get pubkey");
pubkey_set.insert(pubkey_1_0);

assert_eq!(pubkey_set.len(), 5); // Ensure keys at various derivation paths are unique
}
}
7 changes: 5 additions & 2 deletions remote-wallet/src/remote_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub enum RemoteWalletError {
#[error("invalid derivation path: {0}")]
InvalidDerivationPath(String),

#[error("invalid input: {0}")]
InvalidInput(String),

#[error("invalid path: {0}")]
InvalidPath(String),

Expand Down Expand Up @@ -150,12 +153,12 @@ pub trait RemoteWallet {
) -> Result<RemoteWalletInfo, RemoteWalletError>;

/// Get solana pubkey from a RemoteWallet
fn get_pubkey(&self, derivation: DerivationPath) -> Result<Pubkey, RemoteWalletError>;
fn get_pubkey(&self, derivation: &DerivationPath) -> Result<Pubkey, RemoteWalletError>;

/// Sign transaction data with wallet managing pubkey at derivation path m/44'/501'/<account>'/<change>'.
fn sign_transaction(
&self,
derivation: DerivationPath,
derivation: &DerivationPath,
transaction: Transaction,
) -> Result<Signature, RemoteWalletError>;
}
Expand Down

0 comments on commit 6c1135a

Please sign in to comment.