Skip to content

Commit

Permalink
core: fix ExtendedPath::from_derivation_path parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
yukibtc committed Feb 26, 2024
1 parent 9544c8c commit 091e006
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 42 deletions.
9 changes: 6 additions & 3 deletions keechain-core/src/bips/bip32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ pub fn account_extended_path(
// Path: m/<purpose>'/<coin>'/<account>'
let path: Vec<ChildNumber> = vec![
ChildNumber::from_hardened_idx(purpose)?,
ChildNumber::from_hardened_idx(u32::from(!network.eq(&Network::Bitcoin)))?,
ChildNumber::from_hardened_idx(match network {
Network::Bitcoin => 0,
_ => 1,
})?,
ChildNumber::from_hardened_idx(account.unwrap_or(0))?,
];
Ok(DerivationPath::from(path))
Expand All @@ -61,7 +64,7 @@ pub fn extended_path(
) -> Result<DerivationPath, Error> {
// Path: m/<purpose>'/<coin>'/<account>'/<change>
let base_path = account_extended_path(purpose, network, account)?;
let path: Vec<ChildNumber> = vec![ChildNumber::from_normal_idx(u32::from(change))?];
let path: [ChildNumber; 1] = [ChildNumber::from_normal_idx(u32::from(change))?];
Ok(base_path.extend(path))
}

Expand All @@ -74,6 +77,6 @@ pub fn get_path(
) -> Result<DerivationPath, Error> {
// Path: m/<purpose>'/<coin>'/<account>'/<change>/<index>
let base_path = extended_path(purpose, network, account, change)?;
let path: Vec<ChildNumber> = vec![ChildNumber::from_normal_idx(index.unwrap_or(0))?];
let path: [ChildNumber; 1] = [ChildNumber::from_normal_idx(index.unwrap_or(0))?];
Ok(base_path.extend(path))
}
109 changes: 72 additions & 37 deletions keechain-core/src/bips/bip44.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! <https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki>

use core::fmt;
use core::slice::Iter;

use super::bip32::{self, ChildNumber, DerivationPath};
use super::bip43::Purpose;
Expand Down Expand Up @@ -77,45 +78,18 @@ pub struct ExtendedPath {
impl ExtendedPath {
pub fn from_derivation_path(path: &DerivationPath) -> Result<Self, Error> {
let mut path = path.into_iter();
let purpose = path.next();
let coin = path.next();
let account = path.next();
let change = path.next();

let coin: u32 = match coin {
Some(ChildNumber::Hardened { index: 0 }) => 0,
Some(ChildNumber::Hardened { index: 1 }) => 1,
c => {
return Err(Error::UnsupportedDerivationPath(
UnsupportedDerivationPathError::Coin(c.copied()),
))
}
};

let account: u32 = match account {
Some(ChildNumber::Hardened { index }) => *index,
a => {
return Err(Error::UnsupportedDerivationPath(
UnsupportedDerivationPathError::Account(a.copied()),
))
}
};

let change: bool = match change {
Some(ChildNumber::Normal { index }) => *index != 0,
c => {
return Err(Error::UnsupportedDerivationPath(
UnsupportedDerivationPathError::Change(c.copied()),
))
}
};

let purpose: Option<&ChildNumber> = path.next();

let coin: u32 = extract_coin(&mut path)?;
let account: u32 = extract_account(&mut path)?;

match purpose {
Some(ChildNumber::Hardened { index: 44 }) => Ok(Self {
purpose: Purpose::BIP44,
coin,
account,
change,
change: extract_change(&mut path)?,
}),
Some(ChildNumber::Hardened { index: 48 }) => {
let script: ScriptType = match path.next() {
Expand All @@ -128,30 +102,91 @@ impl ExtendedPath {
purpose: Purpose::BIP48 { script },
coin,
account,
change,
change: extract_change(&mut path)?,
})
}
Some(ChildNumber::Hardened { index: 49 }) => Ok(Self {
purpose: Purpose::BIP49,
coin,
account,
change,
change: extract_change(&mut path)?,
}),
Some(ChildNumber::Hardened { index: 84 }) => Ok(Self {
purpose: Purpose::BIP84,
coin,
account,
change,
change: extract_change(&mut path)?,
}),
Some(ChildNumber::Hardened { index: 86 }) => Ok(Self {
purpose: Purpose::BIP86,
coin,
account,
change,
change: extract_change(&mut path)?,
}),
p => Err(Error::UnsupportedDerivationPath(
UnsupportedDerivationPathError::Purpose(p.copied()),
)),
}
}
}

fn extract_coin(path: &mut Iter<'_, ChildNumber>) -> Result<u32, Error> {
match path.next() {
Some(ChildNumber::Hardened { index: 0 }) => Ok(0),
Some(ChildNumber::Hardened { index: 1 }) => Ok(1),
c => Err(Error::UnsupportedDerivationPath(
UnsupportedDerivationPathError::Coin(c.copied()),
)),
}
}

fn extract_account(path: &mut Iter<'_, ChildNumber>) -> Result<u32, Error> {
match path.next() {
Some(ChildNumber::Hardened { index }) => Ok(*index),
a => Err(Error::UnsupportedDerivationPath(
UnsupportedDerivationPathError::Account(a.copied()),
)),
}
}

fn extract_change(path: &mut Iter<'_, ChildNumber>) -> Result<bool, Error> {
match path.next() {
Some(ChildNumber::Normal { index: 0 }) => Ok(false),
Some(ChildNumber::Normal { index: 1 }) => Ok(true),
c => Err(Error::UnsupportedDerivationPath(
UnsupportedDerivationPathError::Change(c.copied()),
)),
}
}

#[cfg(test)]
mod tests {
use core::str::FromStr;

use super::*;

#[test]
fn test_extended_path_parsing() {
// BIP44
// BIP48 path
let path = DerivationPath::from_str("m/86'/0'/22'/1").unwrap();
let p = ExtendedPath::from_derivation_path(&path).unwrap();
assert_eq!(p.purpose, Purpose::BIP86);
assert_eq!(p.coin, 0);
assert_eq!(p.account, 22);
assert!(p.change);

// BIP48 path
let path = DerivationPath::from_str("m/48'/1'/0'/3'/0").unwrap();
let p = ExtendedPath::from_derivation_path(&path).unwrap();
assert_eq!(
p.purpose,
Purpose::BIP48 {
script: ScriptType::P2TR
}
);
assert_eq!(p.coin, 1);
assert_eq!(p.account, 0);
assert!(!p.change);
}
}
4 changes: 2 additions & 2 deletions keechain-core/src/bips/bip48.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub fn account_extended_path(
) -> Result<DerivationPath, Error> {
// Path: m/<purpose>'/<coin_type>'/<account>'/<script_type>'
let base_path = bip32::account_extended_path(48, network, account)?;
let path: Vec<ChildNumber> = vec![ChildNumber::from_hardened_idx(script_type.as_u32())?];
let path: [ChildNumber; 1] = [ChildNumber::from_hardened_idx(script_type.as_u32())?];
Ok(base_path.extend(path))
}

Expand All @@ -43,7 +43,7 @@ pub fn extended_path(
) -> Result<DerivationPath, Error> {
// Path: m/<purpose>'/<coin>'/<account>'/<script_type>'/<change>
let base_path = account_extended_path(network, account, script_type)?;
let path: Vec<ChildNumber> = vec![ChildNumber::from_normal_idx(u32::from(change))?];
let path: [ChildNumber; 1] = [ChildNumber::from_normal_idx(u32::from(change))?];
Ok(base_path.extend(path))
}

Expand Down

0 comments on commit 091e006

Please sign in to comment.