use crate::{Purpose, CustomHDPath, Error, PathValue};
use std::convert::TryFrom;
#[cfg(feature = "with-bitcoin")]
use bitcoin::util::bip32::{ChildNumber, DerivationPath};
use std::str::FromStr;
use crate::traits::HDPath;
use std::fmt;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ShortHDPath {
pub purpose: Purpose,
pub coin_type: u32,
pub account: u32,
pub index: u32
}
impl HDPath for ShortHDPath {
fn len(&self) -> u8 {
4
}
fn get(&self, pos: u8) -> Option<PathValue> {
match pos {
0 => Some(self.purpose.as_value()),
1 => Some(PathValue::Hardened(self.coin_type)),
2 => Some(PathValue::Hardened(self.account)),
3 => Some(PathValue::Normal(self.index)),
_ => None
}
}
}
impl TryFrom<CustomHDPath> for ShortHDPath {
type Error = Error;
fn try_from(value: CustomHDPath) -> Result<Self, Self::Error> {
if value.0.len() != 4 {
return Err(Error::InvalidLength(value.0.len()))
}
if let Some(PathValue::Hardened(p)) = value.0.get(0) {
let purpose = Purpose::try_from(*p)?;
if let Some(PathValue::Hardened(coin_type)) = value.0.get(1) {
if let Some(PathValue::Hardened(account)) = value.0.get(2) {
if let Some(PathValue::Normal(index)) = value.0.get(3) {
return Ok(ShortHDPath {
purpose,
coin_type: *coin_type,
account: *account,
index: *index
})
}
}
}
Err(Error::InvalidStructure)
} else {
Err(Error::InvalidStructure)
}
}
}
impl TryFrom<&str> for ShortHDPath
{
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
ShortHDPath::from_str(value)
}
}
impl FromStr for ShortHDPath {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let value = CustomHDPath::from_str(s)?;
ShortHDPath::try_from(value)
}
}
impl fmt::Display for ShortHDPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "m/{}'/{}'/{}'/{}",
self.purpose.as_value().as_number(),
self.coin_type,
self.account,
self.index
)
}
}
#[cfg(feature = "with-bitcoin")]
impl std::convert::From<&ShortHDPath> for Vec<ChildNumber> {
fn from(value: &ShortHDPath) -> Self {
let result = [
ChildNumber::from_hardened_idx(value.purpose.as_value().as_number())
.expect("Purpose is not Hardened"),
ChildNumber::from_hardened_idx(value.coin_type)
.expect("Coin Type is not Hardened"),
ChildNumber::from_hardened_idx(value.account)
.expect("Account is not Hardened"),
ChildNumber::from_normal_idx(value.index)
.expect("Index is Hardened"),
];
return result.to_vec();
}
}
#[cfg(feature = "with-bitcoin")]
impl std::convert::From<ShortHDPath> for Vec<ChildNumber> {
fn from(value: ShortHDPath) -> Self {
Vec::<ChildNumber>::from(&value)
}
}
#[cfg(feature = "with-bitcoin")]
impl std::convert::From<ShortHDPath> for DerivationPath {
fn from(value: ShortHDPath) -> Self {
DerivationPath::from(Vec::<ChildNumber>::from(&value))
}
}
#[cfg(feature = "with-bitcoin")]
impl std::convert::From<&ShortHDPath> for DerivationPath {
fn from(value: &ShortHDPath) -> Self {
DerivationPath::from(Vec::<ChildNumber>::from(value))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn to_string_short() {
assert_eq!("m/44'/60'/0'/0", ShortHDPath {
purpose: Purpose::Pubkey,
coin_type: 60,
account: 0,
index: 0
}.to_string());
assert_eq!("m/44'/61'/0'/0", ShortHDPath {
purpose: Purpose::Pubkey,
coin_type: 61,
account: 0,
index: 0
}.to_string());
assert_eq!("m/101'/61'/0'/0", ShortHDPath {
purpose: Purpose::Custom(101),
coin_type: 61,
account: 0,
index: 0
}.to_string());
}
#[test]
pub fn to_string_short_all() {
let paths = vec![
"m/44'/0'/0'/0",
"m/44'/60'/0'/1",
"m/44'/60'/160720'/0",
"m/44'/60'/160720'/101",
];
for p in paths {
assert_eq!(p, ShortHDPath::try_from(p).unwrap().to_string())
}
}
}
#[cfg(all(test, feature = "with-bitcoin"))]
mod tests_with_bitcoin {
use super::*;
use std::convert::TryFrom;
use bitcoin::util::bip32::ChildNumber;
#[test]
pub fn convert_to_childnumbers() {
let hdpath = ShortHDPath::try_from("m/44'/60'/2'/100").unwrap();
let childs: Vec<ChildNumber> = hdpath.into();
assert_eq!(childs.len(), 4);
assert_eq!(childs[0], ChildNumber::from_hardened_idx(44).unwrap());
assert_eq!(childs[1], ChildNumber::from_hardened_idx(60).unwrap());
assert_eq!(childs[2], ChildNumber::from_hardened_idx(2).unwrap());
assert_eq!(childs[3], ChildNumber::from_normal_idx(100).unwrap());
}
}