use std::fmt::{self, Display};
use std::str::FromStr;
use crate::Error;
#[derive(Default, PartialEq, Eq, Copy, Clone, Debug)]
pub enum HDPurpose {
#[default]
BIP32,
BIP44,
BIP49,
BIP84,
}
impl HDPurpose {
pub fn to_shortform_num(&self) -> u32 {
let path_index: HDPathIndex = self.into();
path_index.to_shortform_num()
}
pub fn to_full_num(&self) -> u32 {
let path_index: HDPathIndex = self.into();
path_index.to_full_num()
}
pub fn default_path_specify(
&self,
coin_id: u32,
account: u32,
change: u32,
address_index: u32,
) -> String {
format!(
"m/{}/{}/{}/{}/{}",
self,
HDPathIndex::IndexHardened(coin_id),
HDPathIndex::IndexHardened(account),
HDPathIndex::IndexNotHardened(change),
HDPathIndex::IndexNotHardened(address_index)
)
}
}
impl From<&HDPurpose> for HDPathIndex {
fn from(purpose: &HDPurpose) -> Self {
match purpose {
HDPurpose::BIP32 => HDPathIndex::IndexHardened(0),
HDPurpose::BIP44 => HDPathIndex::IndexHardened(44),
HDPurpose::BIP49 => HDPathIndex::IndexHardened(49),
HDPurpose::BIP84 => HDPathIndex::IndexHardened(84),
}
}
}
impl TryFrom<HDPathIndex> for HDPurpose {
type Error = Error;
fn try_from(path_index: HDPathIndex) -> Result<Self, Error> {
match path_index {
HDPathIndex::IndexHardened(0) => Ok(HDPurpose::BIP32),
HDPathIndex::IndexHardened(44) => Ok(HDPurpose::BIP44),
HDPathIndex::IndexHardened(49) => Ok(HDPurpose::BIP49),
HDPathIndex::IndexHardened(84) => Ok(HDPurpose::BIP84),
_ => Err(Error::Invalid(format!(
"Cannot convert {} to HDPurpose",
path_index
))),
}
}
}
impl FromStr for HDPurpose {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s {
"0'" | "0h" => Ok(HDPurpose::BIP32),
"44'" | "44h" => Ok(HDPurpose::BIP44),
"49'" | "49h" => Ok(HDPurpose::BIP49),
"84'" | "84h" => Ok(HDPurpose::BIP84),
_ => Err(Error::FromStr(format!(
"Unknown purpose, unknown deriv type {}",
s
))),
}
}
}
impl fmt::Display for HDPurpose {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let path_index: HDPathIndex = self.into();
write!(f, "{}", path_index)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum HDPathIndex {
Master,
IndexHardened(u32),
IndexNotHardened(u32),
}
impl HDPathIndex {
pub fn hardened_full_num(num: u32) -> u32 {
num + (1 << 31)
}
pub fn hardened_shortform_num(full_num: u32) -> u32 {
full_num - (1 << 31)
}
pub fn to_shortform_num(&self) -> u32 {
match self {
HDPathIndex::Master => 0,
HDPathIndex::IndexHardened(num) => *num,
HDPathIndex::IndexNotHardened(num) => *num,
}
}
pub fn to_full_num(&self) -> u32 {
match self {
HDPathIndex::Master => 0,
HDPathIndex::IndexHardened(num) => HDPathIndex::hardened_full_num(*num),
HDPathIndex::IndexNotHardened(num) => *num,
}
}
pub fn new_master() -> HDPathIndex {
HDPathIndex::Master
}
pub fn new_index(num: u32, hardened: bool) -> HDPathIndex {
if hardened {
HDPathIndex::IndexHardened(num)
} else {
HDPathIndex::IndexNotHardened(num)
}
}
}
impl Display for HDPathIndex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
HDPathIndex::Master => {
write!(f, "m")?;
}
HDPathIndex::IndexHardened(num) => {
write!(f, "{}'", num)?;
}
HDPathIndex::IndexNotHardened(num) => {
write!(f, "{}", num)?;
}
}
Ok(())
}
}
impl FromStr for HDPathIndex {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
if s == "m" {
return Ok(HDPathIndex::Master);
}
let chars = s.chars();
let mut is_hardened = false;
let mut num = String::new();
for c in chars {
if c == '\'' || c == 'h' {
is_hardened = true;
} else {
num.push(c);
}
}
let num: u32 = num
.parse::<u32>()
.map_err(|e| Error::FromStr(e.to_string()))?;
if is_hardened {
Ok(HDPathIndex::IndexHardened(num))
} else {
Ok(HDPathIndex::IndexNotHardened(num))
}
}
}
#[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct HDPath {
path: Vec<HDPathIndex>,
}
impl HDPath {
pub fn new(path: &str) -> Result<Self, Error> {
HDPath::from_str(path)
}
pub fn push(&mut self, index: HDPathIndex) {
self.path.push(index);
}
pub fn len(&self) -> usize {
self.path.len()
}
pub fn is_empty(&self) -> bool {
self.path.is_empty()
}
pub fn derive_path_str_to_list(deriv_path: &str) -> Result<Vec<String>, Error> {
let deriv_path_list: Vec<String> = deriv_path.split('/').map(|s| s.to_string()).collect();
if deriv_path_list.is_empty() || deriv_path_list[0] != *"m" {
return Err(Error::Invalid(format!(
"Derivation Path {} is Invalid",
deriv_path
)));
}
Ok(deriv_path_list)
}
pub fn to_vec(&self) -> Vec<HDPathIndex> {
self.path.clone()
}
pub fn derive_path_str_to_info(deriv_path: &str) -> Result<Vec<HDPathIndex>, Error> {
let mut deriv_path_info: Vec<HDPathIndex> = Vec::new();
let deriv_path_list = Self::derive_path_str_to_list(deriv_path)?;
for item in deriv_path_list {
deriv_path_info.push(HDPathIndex::from_str(&item)?);
}
Ok(deriv_path_info)
}
pub fn builder() -> HDPathBuilder {
HDPathBuilder::new()
}
pub fn at(&self, index: usize) -> Result<HDPathIndex, Error> {
if index < self.path.len() {
Ok(self.path[index])
} else {
Err(Error::IndexOutOfRange {
index,
max: self.path.len() - 1,
})
}
}
pub fn purpose(&self) -> Result<HDPurpose, Error> {
let purpose: HDPurpose = self.at(1)?.try_into()?;
Ok(purpose)
}
pub fn coin_type(&self) -> Result<HDPathIndex, Error> {
self.at(2)
}
pub fn account(&self) -> Result<HDPathIndex, Error> {
self.at(3)
}
pub fn change(&self) -> Result<HDPathIndex, Error> {
self.at(4)
}
pub fn address(&self) -> Result<HDPathIndex, Error> {
self.at(5)
}
}
impl fmt::Display for HDPath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (i, p) in self.path.iter().enumerate() {
if i == 0 {
write!(f, "{}", p)?;
} else {
write!(f, "/{}", p)?;
}
}
Ok(())
}
}
impl FromStr for HDPath {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
let mut path = Vec::new();
for p in s.split('/') {
path.push(HDPathIndex::from_str(p)?);
}
Ok(HDPath { path })
}
}
impl From<Vec<HDPathIndex>> for HDPath {
fn from(path: Vec<HDPathIndex>) -> Self {
HDPath { path }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HDPathBuilder {
pub purpose: Option<u32>,
pub purpose_hardened: bool,
pub coin_type: Option<u32>,
pub coin_type_hardened: bool,
pub account: Option<u32>,
pub account_hardened: bool,
pub change: Option<u32>,
pub change_hardened: bool,
pub address_index: Option<u32>,
pub address_index_hardened: bool,
}
impl Default for HDPathBuilder {
fn default() -> Self {
HDPathBuilder {
purpose: None,
purpose_hardened: true,
coin_type: None,
coin_type_hardened: true,
account: Some(0),
account_hardened: true,
change: Some(0),
change_hardened: false,
address_index: Some(0),
address_index_hardened: false,
}
}
}
impl HDPathBuilder {
pub fn new() -> Self {
HDPathBuilder::default()
}
pub fn purpose_index(&mut self, purpose: u32) -> &mut Self {
self.purpose = Some(purpose);
self
}
pub fn hardened_purpose(&mut self) -> &mut Self {
self.purpose_hardened = true;
self
}
pub fn non_hardened_purpose(&mut self) -> &mut Self {
self.purpose_hardened = false;
self
}
pub fn coin_type_index(&mut self, coin_type: u32) -> &mut Self {
self.coin_type = Some(coin_type);
self
}
pub fn hardened_coin_type(&mut self) -> &mut Self {
self.coin_type_hardened = true;
self
}
pub fn non_hardened_coin_type(&mut self) -> &mut Self {
self.coin_type_hardened = false;
self
}
pub fn account_index(&mut self, account: u32) -> &mut Self {
self.account = Some(account);
self
}
pub fn hardened_account(&mut self) -> &mut Self {
self.account_hardened = true;
self
}
pub fn non_hardened_account(&mut self) -> &mut Self {
self.account_hardened = false;
self
}
pub fn change_index(&mut self, change: u32) -> &mut Self {
self.change = Some(change);
self
}
pub fn hardened_change(&mut self) -> &mut Self {
self.change_hardened = true;
self
}
pub fn non_hardened_change(&mut self) -> &mut Self {
self.change_hardened = false;
self
}
pub fn address_index(&mut self, address_index: u32) -> &mut Self {
self.address_index = Some(address_index);
self
}
pub fn hardened_address(&mut self) -> &mut Self {
self.address_index_hardened = true;
self
}
pub fn non_hardened_address(&mut self) -> &mut Self {
self.address_index_hardened = false;
self
}
pub fn no_purpose_index(&mut self) -> &mut Self {
self.purpose = None;
self
}
pub fn no_coin_type_index(&mut self) -> &mut Self {
self.coin_type = None;
self
}
pub fn no_account_index(&mut self) -> &mut Self {
self.account = None;
self
}
pub fn no_change_index(&mut self) -> &mut Self {
self.change = None;
self
}
pub fn no_address_index(&mut self) -> &mut Self {
self.address_index = None;
self
}
pub fn build(&mut self) -> HDPath {
let mut path = Vec::new();
path.push(HDPathIndex::Master);
if let Some(purpose) = self.purpose {
path.push(HDPathIndex::new_index(purpose, self.purpose_hardened));
if let Some(coin_type) = self.coin_type {
path.push(HDPathIndex::new_index(coin_type, self.coin_type_hardened));
if let Some(account) = self.account {
path.push(HDPathIndex::new_index(account, self.account_hardened));
if let Some(change) = self.change {
path.push(HDPathIndex::new_index(change, self.change_hardened));
if let Some(address_index) = self.address_index {
path.push(HDPathIndex::new_index(
address_index,
self.address_index_hardened,
));
}
}
}
}
}
HDPath { path }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_derive_type() {
let dt = HDPurpose::BIP32;
assert_eq!(format!("{}", dt), "0'");
}
}