mod error;
mod apdu;
mod keystore;
mod comm;
use self::apdu::ApduBuilder;
use self::comm::sendrecv;
pub use self::error::Error;
pub use self::keystore::HdwalletCrypto;
use super::{Address, ECDSA_SIGNATURE_BYTES, Signature, to_arr, to_bytes};
use hidapi::{HidApi, HidDevice, HidDeviceInfo};
use regex::Regex;
use std::{thread, time};
use std::str::{FromStr, from_utf8};
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;
#[allow(dead_code)]
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,
];
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 fn path_to_arr(hd_str: &str) -> Result<Vec<u8>, Error> {
lazy_static! {
static ref INVALID_PATH_RE: Regex = Regex::new(r#"[^0-9'/]"#).unwrap();
}
if INVALID_PATH_RE.is_match(hd_str) {
return Err(Error::HDWalletError(
format!("Invalid `hd_path` format: {}", hd_str),
));
}
let mut buf = Vec::new();
{
let parse = |s: &str| {
let mut str = s.to_string();
let mut v: u64 = 0;
if str.ends_with("'") {
v += 0x80000000;
str.remove(s.len() - 1);
}
match str.parse::<u64>() {
Ok(d) => v += d,
Err(_) => {
return Err(Error::HDWalletError(
format!("Invalid `hd_path` format: {}", hd_str),
))
}
}
buf.extend(to_bytes(v, 4));
Ok(())
};
hd_str.split("/").map(parse).collect::<Vec<_>>();
}
Ok(buf)
}
pub fn to_prefixed_path(hd_str: &str) -> Result<Vec<u8>, Error> {
let v = path_to_arr(hd_str)?;
let count = (v.len() / DERIVATION_INDEX_SIZE) as u8;
let mut buf = Vec::with_capacity(v.len() + 1);
buf.push(count);
buf.extend(v);
Ok(buf)
}
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: 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(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_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()),
};
println!(
"Sign transaction with HD Wallet from address: {}",
self.get_address(fd, Some(hd_path.clone()))?
);
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: {:?}", 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.clone(), 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 {
match self.hid.open(LEDGER_VID, LEDGER_PID) {
Ok(h) => return Ok(h),
Err(_) => (),
}
thread::sleep(time::Duration::from_millis(1000));
}
Err(Error::HDWalletError(format!("Can't open path: {}", path)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::Transaction;
use rustc_serialize::hex::ToHex;
use tests::*;
#[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!(tx.raw_from_sig(chain, sign).to_hex(),
"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: {:?}", &rlp.to_hex());
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", addr.to_hex());
}
#[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 = "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'/160A+_0'/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 = "44'/60'/160720'/0'/0";
assert_eq!(
ETC_DERIVATION_PATH.to_vec(),
to_prefixed_path(&path_str).unwrap()
);
debug!("prefixed: {:?}", to_prefixed_path(&path_str).unwrap());
}
}