mod apdu;
pub mod bip32;
mod comm;
mod error;
mod keystore;
use self::apdu::ApduBuilder;
use self::comm::sendrecv;
pub use self::error::Error;
pub use self::keystore::HdwalletCrypto;
use super::{to_arr, Address, Signature, ECDSA_SIGNATURE_BYTES};
use hidapi::{HidApi, HidDevice, HidDeviceInfo};
use std::str::{from_utf8, FromStr};
use std::{thread, time};
use hex;
const GET_ETH_ADDRESS: u8 = 0x02;
const SIGN_ETH_TRANSACTION: u8 = 0x04;
const CHUNK_SIZE: usize = 255;
const LEDGER_VID: u16 = 0x2c97;
const LEDGER_PID: u16 = 0x0001; const DERIVATION_INDEX_SIZE: usize = 4;
pub type DevicesList = Vec<(Address, String)>;
#[derive(Debug)]
struct Device {
fd: String,
address: Address,
hid_info: HidDeviceInfo,
}
impl PartialEq for Device {
fn eq(&self, other: &Device) -> bool {
self.fd == other.fd
}
}
impl From<HidDeviceInfo> for Device {
fn from(hid_info: HidDeviceInfo) -> Self {
let info = hid_info.clone();
Device {
fd: hid_info.path,
address: Address::default(),
hid_info: info,
}
}
}
pub struct WManager {
hid: HidApi,
devices: Vec<Device>,
hd_path: Option<Vec<u8>>,
}
impl WManager {
pub fn new(hd_path: Option<Vec<u8>>) -> Result<WManager, Error> {
Ok(Self {
hid: HidApi::new()?,
devices: Vec::new(),
hd_path,
})
}
fn pick_hd_path(&self, h: Option<Vec<u8>>) -> Result<Vec<u8>, Error> {
if self.hd_path.is_none() && h.is_none() {
return Err(Error::HDWalletError("HD path is not specified".to_string()));
}
Ok(h.or_else(|| self.hd_path.clone()).unwrap())
}
pub fn get_address(&self, fd: &str, hd_path: Option<Vec<u8>>) -> Result<Address, Error> {
let hd_path = self.pick_hd_path(hd_path)?;
let apdu = ApduBuilder::new(GET_ETH_ADDRESS)
.with_data(&hd_path)
.build();
debug!("DEBUG get address: {:?}", &fd);
let handle = self.open(fd)?;
let addr = sendrecv(&handle, &apdu)
.and_then(|res| match res.len() {
107 => Ok(res),
_ => Err(Error::HDWalletError(
"Address read returned invalid data length".to_string(),
)),
})
.and_then(|res: Vec<u8>| {
from_utf8(&res[67..107])
.map(|ptr| ptr.to_string())
.map_err(|e| {
Error::HDWalletError(format!("Can't parse address: {}", e.to_string()))
})
})
.and_then(|s| {
Address::from_str(&s).map_err(|e| {
Error::HDWalletError(format!("Can't parse address: {}", e.to_string()))
})
})?;
Ok(addr)
}
pub fn sign(
&self,
_fd: &str,
_data: &[u8],
_hd_path: &Option<Vec<u8>>,
) -> Result<Signature, Error> {
Err(Error::HDWalletError("Can't sign data".to_string()))
}
pub fn sign_transaction(
&self,
fd: &str,
tr: &[u8],
hd_path: Option<Vec<u8>>,
) -> Result<Signature, Error> {
let hd_path = self.pick_hd_path(hd_path)?;
let _mock = Vec::new();
let (init, cont) = match tr.len() {
0...CHUNK_SIZE => (tr, _mock.as_slice()),
_ => tr.split_at(CHUNK_SIZE - hd_path.len()),
};
let init_apdu = ApduBuilder::new(SIGN_ETH_TRANSACTION)
.with_p1(0x00)
.with_data(&hd_path)
.with_data(init)
.build();
let handle = self.open(fd)?;
let mut res = sendrecv(&handle, &init_apdu)?;
for chunk in cont.chunks(CHUNK_SIZE) {
let apdu_cont = ApduBuilder::new(SIGN_ETH_TRANSACTION)
.with_p1(0x80)
.with_data(chunk)
.build();
res = sendrecv(&handle, &apdu_cont)?;
}
debug!("Received signature: {:?}", hex::encode(&res));
match res.len() {
ECDSA_SIGNATURE_BYTES => {
let mut val: [u8; ECDSA_SIGNATURE_BYTES] = [0; ECDSA_SIGNATURE_BYTES];
val.copy_from_slice(&res);
Ok(Signature::from(val))
}
v => Err(Error::HDWalletError(format!(
"Invalid signature length. Expected: {}, received: {}",
ECDSA_SIGNATURE_BYTES, v
))),
}
}
pub fn devices(&self) -> DevicesList {
self.devices
.iter()
.map(|d| (d.address, d.fd.clone()))
.collect()
}
pub fn update(&mut self, hd_path: Option<Vec<u8>>) -> Result<(), Error> {
let hd_path = self.pick_hd_path(hd_path)?;
self.hid.refresh_devices();
let mut new_devices = Vec::new();
debug!("Start searching for devices: {:?}", self.hid.devices());
for hid_info in self.hid.devices() {
if hid_info.product_id != LEDGER_PID || hid_info.vendor_id != LEDGER_VID {
continue;
}
let mut d = Device::from(hid_info);
d.address = self.get_address(&d.fd, Some(hd_path.clone()))?;
new_devices.push(d);
}
self.devices = new_devices;
debug!("Devices found {:?}", self.devices);
Ok(())
}
fn open(&self, path: &str) -> Result<HidDevice, Error> {
for _ in 0..5 {
if let Ok(h) = self.hid.open(LEDGER_VID, LEDGER_PID) {
return Ok(h);
}
thread::sleep(time::Duration::from_millis(1000));
}
Err(Error::HDWalletError(format!("Can't open path: {}", path)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Transaction;
use crate::hdwallet::bip32::{path_to_arr, to_prefixed_path};
use hex;
use crate::tests::*;
pub const ETC_DERIVATION_PATH: [u8; 21] = [
5, 0x80, 0, 0, 44, 0x80, 0, 0, 60, 0x80, 0x02, 0x73, 0xd0, 0x80, 0, 0, 0, 0, 0, 0, 0,
];
#[test]
#[ignore]
pub fn should_sign_with_ledger() {
let mut manager = WManager::new(Some(ETC_DERIVATION_PATH.to_vec())).unwrap();
manager.update(None).unwrap();
if manager.devices().is_empty() {
return;
}
let tx = Transaction {
nonce: 0x00,
gas_price:
to_32bytes("0000000000000000000000000000000\
0000000000000000000000004e3b29200"),
gas_limit: 0x5208,
to: Some("78296F1058dD49C5D6500855F59094F0a2876397"
.parse::<Address>()
.unwrap()),
value:
to_32bytes("00000000000000000000000000000000\
00000000000000000de0b6b3a7640000"),
data: Vec::new(),
};
let chain: u8 = 61;
let rlp = tx.to_rlp(Some(chain));
let fd = &manager.devices()[0].1;
let sign = manager.sign_transaction(&fd, &rlp, None).unwrap();
assert_eq!(hex::encode(tx.raw_from_sig(Some(chain), &sign)),
"f86d80\
85\
04e3b29200\
82\
5208\
94\
78296f1058dd49c5d6500855f59094f0a2876397\
88\
0de0b6b3a7640000\
80\
81\
9d\
a0\
5cba84eb9aac6854c8ff6aa21b3e0c6c2036e07ebdee44bcf7ace95bab569d8f\
a0\
6eab3be528ef7565c887e147a2d53340c6c9fab5d6f56694681c90b518b64183");
}
#[test]
#[ignore]
pub fn should_sign_with_ledger_big_data() {
let mut manager = WManager::new(Some(ETC_DERIVATION_PATH.to_vec())).unwrap();
manager.update(None).unwrap();
if manager.devices().is_empty() {
return;
}
let mut data = Vec::new();
for _ in 0..64 {
data.push(0x11);
data.extend_from_slice(&[0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc]);
data.push(0x11);
}
let tx = Transaction {
nonce: 0x01,
gas_price:
to_32bytes("0000000000000000000000000000000\
0000000000000000000000004e3b29200"),
gas_limit: 0x5208,
to: Some("c0de379b51d582e1600c76dd1efee8ed024b844a"
.parse::<Address>()
.unwrap()),
value:
to_32bytes("00000000000000000000000000000000\
00000000000000000003f26fcfb7a224"),
data: data,
};
let rlp = tx.to_rlp(None);
let fd = &manager.devices()[0].1;
println!(">> RLP: {:?}", hex::encode(&rlp));
let sign = manager.sign_transaction(&fd, &rlp, None);
assert!(sign.is_ok());
debug!("Signature: {:?}", &sign.unwrap());
}
#[test]
#[ignore]
pub fn should_get_address_with_ledger() {
let mut manager = WManager::new(Some(ETC_DERIVATION_PATH.to_vec())).unwrap();
manager.update(None).unwrap();
if manager.devices().is_empty() {
return;
}
let fd = &manager.devices()[0].1;
let addr = manager.get_address(fd, None).unwrap();
assert_eq!(
"78296f1058dd49c5d6500855f59094f0a2876397",
hex::encode(&*addr)
);
}
#[test]
#[ignore]
pub fn should_pick_hd_path() {
let buf1 = vec![0];
let buf2 = vec![1];
let mut manager = WManager::new(None).unwrap();
assert_eq!(manager.pick_hd_path(Some(buf1.clone())).unwrap(), buf1);
manager.hd_path = Some(buf2.clone());
assert_eq!(manager.pick_hd_path(Some(buf2.clone())).unwrap(), buf2);
manager.hd_path = Some(buf1.clone());
assert_eq!(manager.pick_hd_path(None).unwrap(), buf1);
}
#[test]
pub fn should_parse_hd_path() {
let path_str = "m/44'/60'/160720'/0'/0";
assert_eq!(
ETC_DERIVATION_PATH[1..].to_vec(),
path_to_arr(&path_str).unwrap()
);
}
#[test]
pub fn should_fail_parse_hd_path() {
let mut path_str = "44'/60'/160720'/0'/0";
assert!(path_to_arr(&path_str).is_err());
path_str = "44'/60'/16011_11111111111111111zz1111111111111111111111111111111'/0'/0";
assert!(path_to_arr(&path_str).is_err());
}
#[test]
pub fn should_parse_hd_path_into_prefixed() {
let path_str = "m/44'/60'/160720'/0'/0";
assert_eq!(
ETC_DERIVATION_PATH.to_vec(),
to_prefixed_path(&path_str).unwrap()
);
}
}