use std::str::FromStr;
use bpstd::{DerivationIndex, DerivationPath, HardenedIndex, Idx, IdxBase, NormalIndex};
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Error, Display)]
#[display(doc_comments)]
pub enum ParseBip43Error {
InvalidBlockchainName(String),
UnhardenedBlockchainIndex(u32),
InvalidIdentityIndex(String),
InvalidPurposeIndex(String),
UnimplementedBip(u16),
UnrecognizedBipScheme,
InvalidBip43Scheme,
InvalidBip48Scheme,
InvalidDerivationPath(String),
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)]
#[cfg_attr(feature = "clap", derive(ValueEnum))]
#[non_exhaustive]
pub enum Bip43 {
#[display("bip44", alt = "m/44h")]
Bip44,
#[display("bip84", alt = "m/84h")]
Bip84,
#[display("bip49", alt = "m/49h")]
Bip49,
#[display("bip86", alt = "m/86h")]
Bip86,
#[display("bip45", alt = "m/45h")]
Bip45,
#[display("bip48-nested", alt = "m/48h//1h")]
Bip48Nested,
#[display("bip48-native", alt = "m/48h//2h")]
Bip48Native,
#[display("bip87", alt = "m/87h")]
Bip87,
#[display("bip43/{purpose}", alt = "m/{purpose}")]
#[cfg_attr(feature = "clap", clap(skip))]
Bip43 {
purpose: HardenedIndex,
},
}
impl FromStr for Bip43 {
type Err = ParseBip43Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
let bip = s.strip_prefix("bip").or_else(|| s.strip_prefix("m/"));
Ok(match bip {
Some("44") => Bip43::Bip44,
Some("84") => Bip43::Bip84,
Some("49") => Bip43::Bip49,
Some("86") => Bip43::Bip86,
Some("45") => Bip43::Bip45,
Some(bip48) if bip48.starts_with("48//") => match bip48
.strip_prefix("48//")
.and_then(|index| HardenedIndex::from_str(index).ok())
{
Some(script_type) if script_type == 1u8 => Bip43::Bip48Nested,
Some(script_type) if script_type == 2u8 => Bip43::Bip48Native,
_ => {
return Err(ParseBip43Error::InvalidBip48Scheme);
}
},
Some("48-nested") => Bip43::Bip48Nested,
Some("48-native") => Bip43::Bip48Native,
Some("87") => Bip43::Bip87,
Some(bip43) if bip43.starts_with("43/") => match bip43.strip_prefix("43/") {
Some(purpose) => {
let purpose = HardenedIndex::from_str(purpose)
.map_err(|_| ParseBip43Error::InvalidPurposeIndex(purpose.to_owned()))?;
Bip43::Bip43 { purpose }
}
None => return Err(ParseBip43Error::InvalidBip43Scheme),
},
Some(_) | None => return Err(ParseBip43Error::UnrecognizedBipScheme),
})
}
}
impl Bip43 {
pub const PKH: Bip43 = Bip43::Bip44;
pub const WPKH_SH: Bip43 = Bip43::Bip49;
pub const WPKH: Bip43 = Bip43::Bip84;
pub const TR_SINGLE: Bip43 = Bip43::Bip86;
pub const MULTI_SH_SORTED: Bip43 = Bip43::Bip45;
pub const MULTI_WSH_SH: Bip43 = Bip43::Bip48Nested;
pub const MULTI_WSH: Bip43 = Bip43::Bip48Native;
pub const DESCRIPTOR: Bip43 = Bip43::Bip87;
}
pub trait DerivationStandard: Eq + Clone {
fn deduce(derivation: &DerivationPath) -> Option<Self>
where Self: Sized;
fn purpose(&self) -> Option<HardenedIndex>;
fn account_depth(&self) -> Option<u8>;
fn coin_type_depth(&self) -> Option<u8>;
fn is_account_last_hardened(&self) -> Option<bool>;
fn is_testnet(&self, path: &DerivationPath) -> Result<bool, Option<DerivationIndex>>;
fn extract_coin_type(
&self,
path: &DerivationPath,
) -> Result<HardenedIndex, Option<NormalIndex>> {
let coin = self.coin_type_depth().and_then(|i| path.get(i as usize)).ok_or(None)?;
match coin {
DerivationIndex::Normal(idx) => Err(Some(*idx)),
DerivationIndex::Hardened(idx) => Ok(*idx),
}
}
fn extract_account_index(
&self,
path: &DerivationPath,
) -> Result<HardenedIndex, Option<NormalIndex>> {
let coin = self.account_depth().and_then(|i| path.get(i as usize)).ok_or(None)?;
match coin {
DerivationIndex::Normal(idx) => Err(Some(*idx)),
DerivationIndex::Hardened(idx) => Ok(*idx),
}
}
fn account_template_string(&self, testnet: bool) -> String;
fn to_origin_derivation(&self, testnet: bool) -> DerivationPath<HardenedIndex>;
fn to_account_derivation(
&self,
account_index: HardenedIndex,
testnet: bool,
) -> DerivationPath<HardenedIndex>;
fn to_key_derivation(
&self,
account_index: HardenedIndex,
testnet: bool,
keychain: NormalIndex,
index: NormalIndex,
) -> DerivationPath;
}
impl DerivationStandard for Bip43 {
fn deduce(derivation: &DerivationPath) -> Option<Bip43> {
let mut iter = derivation.into_iter();
let first = iter.next().map(HardenedIndex::try_from).transpose().ok()??;
let fourth = iter.nth(3).map(HardenedIndex::try_from);
Some(match (first.child_number(), fourth) {
(44, ..) => Bip43::Bip44,
(84, ..) => Bip43::Bip84,
(49, ..) => Bip43::Bip49,
(86, ..) => Bip43::Bip86,
(45, ..) => Bip43::Bip45,
(87, ..) => Bip43::Bip87,
(48, Some(Ok(script_type))) if script_type == 1u8 => Bip43::Bip48Nested,
(48, Some(Ok(script_type))) if script_type == 2u8 => Bip43::Bip48Native,
(48, _) => return None,
(purpose, ..) if derivation.len() > 2 && purpose > 2 => Bip43::Bip43 {
purpose: HardenedIndex::hardened(purpose as u16),
},
_ => return None,
})
}
fn purpose(&self) -> Option<HardenedIndex> {
Some(match self {
Bip43::Bip44 => HardenedIndex::hardened(44),
Bip43::Bip84 => HardenedIndex::hardened(84),
Bip43::Bip49 => HardenedIndex::hardened(49),
Bip43::Bip86 => HardenedIndex::hardened(86),
Bip43::Bip45 => HardenedIndex::hardened(45),
Bip43::Bip48Nested | Bip43::Bip48Native => HardenedIndex::hardened(48),
Bip43::Bip87 => HardenedIndex::hardened(87),
Bip43::Bip43 { purpose } => *purpose,
})
}
fn account_depth(&self) -> Option<u8> {
Some(match self {
Bip43::Bip45 => return None,
Bip43::Bip44
| Bip43::Bip84
| Bip43::Bip49
| Bip43::Bip86
| Bip43::Bip87
| Bip43::Bip48Nested
| Bip43::Bip48Native
| Bip43::Bip43 { .. } => 3,
})
}
fn coin_type_depth(&self) -> Option<u8> {
Some(match self {
Bip43::Bip45 => return None,
Bip43::Bip44
| Bip43::Bip84
| Bip43::Bip49
| Bip43::Bip86
| Bip43::Bip87
| Bip43::Bip48Nested
| Bip43::Bip48Native
| Bip43::Bip43 { .. } => 2,
})
}
fn is_account_last_hardened(&self) -> Option<bool> {
Some(match self {
Bip43::Bip45 => false,
Bip43::Bip44
| Bip43::Bip84
| Bip43::Bip49
| Bip43::Bip86
| Bip43::Bip87
| Bip43::Bip43 { .. } => true,
Bip43::Bip48Nested | Bip43::Bip48Native => false,
})
}
fn is_testnet(&self, path: &DerivationPath) -> Result<bool, Option<DerivationIndex>> {
match self.extract_coin_type(path) {
Err(None) => Err(None),
Err(Some(idx)) => Err(Some(idx.into())),
Ok(HardenedIndex::ZERO) => Ok(false),
Ok(HardenedIndex::ONE) => Ok(true),
Ok(idx) => Err(Some(idx.into())),
}
}
fn account_template_string(&self, testnet: bool) -> String {
let coin_type = if testnet { HardenedIndex::ONE } else { HardenedIndex::ZERO };
match self {
Bip43::Bip45
| Bip43::Bip44
| Bip43::Bip84
| Bip43::Bip49
| Bip43::Bip86
| Bip43::Bip87
| Bip43::Bip43 { .. } => format!("{:#}/{}/*h", self, coin_type),
Bip43::Bip48Nested => {
format!("{:#}", self).replace("//", &format!("/{}/*h/", coin_type))
}
Bip43::Bip48Native => {
format!("{:#}", self).replace("//", &format!("/{}/*h/", coin_type))
}
}
}
fn to_origin_derivation(&self, testnet: bool) -> DerivationPath<HardenedIndex> {
let mut path = Vec::with_capacity(2);
if let Some(purpose) = self.purpose() {
path.push(purpose)
}
path.push(if testnet { HardenedIndex::ONE } else { HardenedIndex::ZERO });
path.into()
}
fn to_account_derivation(
&self,
account_index: HardenedIndex,
testnet: bool,
) -> DerivationPath<HardenedIndex> {
let mut path = Vec::with_capacity(4);
path.push(account_index);
if self == &Bip43::Bip48Native {
path.push(HardenedIndex::from(2u8));
} else if self == &Bip43::Bip48Nested {
path.push(HardenedIndex::ONE);
}
let mut derivation = self.to_origin_derivation(testnet);
derivation.extend(&path);
derivation
}
fn to_key_derivation(
&self,
account_index: HardenedIndex,
testnet: bool,
keychain: NormalIndex,
index: NormalIndex,
) -> DerivationPath {
let mut derivation = self
.to_account_derivation(account_index, testnet)
.into_iter()
.map(DerivationIndex::from)
.collect::<DerivationPath>();
derivation.push(keychain.into());
derivation.push(index.into());
derivation
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bip43_str_round_trip() {
fn assert_from_str_to_str(bip43: Bip43) {
let str = bip43.to_string();
let from_str = Bip43::from_str(&str).unwrap();
assert_eq!(bip43, from_str);
}
assert_from_str_to_str(Bip43::Bip44);
assert_from_str_to_str(Bip43::Bip84);
assert_from_str_to_str(Bip43::Bip49);
assert_from_str_to_str(Bip43::Bip86);
assert_from_str_to_str(Bip43::Bip45);
assert_from_str_to_str(Bip43::Bip48Nested);
assert_from_str_to_str(Bip43::Bip48Native);
assert_from_str_to_str(Bip43::Bip87);
assert_from_str_to_str(Bip43::Bip43 {
purpose: HardenedIndex::hardened(1),
});
}
}