2022-12-22 00:58:06 -08:00
|
|
|
//! [BIP-44] derivation paths.
|
|
|
|
//!
|
|
|
|
//! [BIP-44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
|
|
|
//!
|
|
|
|
//! Includes definitions and helpers for Solana derivation paths.
|
|
|
|
//! The standard Solana BIP-44 derivation path prefix is
|
|
|
|
//!
|
|
|
|
//! > `m/44'/501'`
|
|
|
|
//!
|
|
|
|
//! with 501 being the Solana coin type.
|
|
|
|
|
2021-04-16 15:03:24 -07:00
|
|
|
use {
|
2021-04-19 13:57:43 -07:00
|
|
|
core::{iter::IntoIterator, slice::Iter},
|
|
|
|
derivation_path::{ChildIndex, DerivationPath as DerivationPathInner},
|
2021-04-24 00:59:14 -07:00
|
|
|
std::{
|
|
|
|
convert::{Infallible, TryFrom},
|
|
|
|
fmt,
|
|
|
|
str::FromStr,
|
|
|
|
},
|
2021-04-16 15:03:24 -07:00
|
|
|
thiserror::Error,
|
2021-04-29 00:42:21 -07:00
|
|
|
uriparse::URIReference,
|
2021-04-16 15:03:24 -07:00
|
|
|
};
|
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
const ACCOUNT_INDEX: usize = 2;
|
|
|
|
const CHANGE_INDEX: usize = 3;
|
|
|
|
|
2021-04-16 15:03:24 -07:00
|
|
|
/// Derivation path error.
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Error, Debug, Clone, PartialEq, Eq)]
|
2021-04-16 15:03:24 -07:00
|
|
|
pub enum DerivationPathError {
|
|
|
|
#[error("invalid derivation path: {0}")]
|
|
|
|
InvalidDerivationPath(String),
|
2021-04-24 00:49:35 -07:00
|
|
|
#[error("infallible")]
|
|
|
|
Infallible,
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
|
2021-04-24 00:49:35 -07:00
|
|
|
impl From<Infallible> for DerivationPathError {
|
|
|
|
fn from(_: Infallible) -> Self {
|
|
|
|
Self::Infallible
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, PartialEq, Eq)]
|
2021-04-19 13:57:43 -07:00
|
|
|
pub struct DerivationPath(DerivationPathInner);
|
2021-04-16 15:03:24 -07:00
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
impl Default for DerivationPath {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new_bip44(None, None)
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:49:35 -07:00
|
|
|
impl TryFrom<&str> for DerivationPath {
|
|
|
|
type Error = DerivationPathError;
|
|
|
|
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
|
|
|
Self::from_key_str(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-03 18:58:56 -07:00
|
|
|
impl AsRef<[ChildIndex]> for DerivationPath {
|
|
|
|
fn as_ref(&self) -> &[ChildIndex] {
|
2021-06-18 06:34:46 -07:00
|
|
|
self.0.as_ref()
|
2021-05-03 18:58:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
impl DerivationPath {
|
|
|
|
fn new<P: Into<Box<[ChildIndex]>>>(path: P) -> Self {
|
|
|
|
Self(DerivationPathInner::new(path))
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
pub fn from_key_str(path: &str) -> Result<Self, DerivationPathError> {
|
|
|
|
Self::from_key_str_with_coin(path, Solana)
|
|
|
|
}
|
2021-04-16 15:03:24 -07:00
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
fn from_key_str_with_coin<T: Bip44>(path: &str, coin: T) -> Result<Self, DerivationPathError> {
|
2021-05-03 18:58:56 -07:00
|
|
|
let master_path = if path == "m" {
|
|
|
|
path.to_string()
|
|
|
|
} else {
|
2022-12-06 06:30:06 -08:00
|
|
|
format!("m/{path}")
|
2021-05-03 18:58:56 -07:00
|
|
|
};
|
|
|
|
let extend = DerivationPathInner::from_str(&master_path)
|
2021-04-19 13:57:43 -07:00
|
|
|
.map_err(|err| DerivationPathError::InvalidDerivationPath(err.to_string()))?;
|
|
|
|
let mut extend = extend.into_iter();
|
|
|
|
let account = extend.next().map(|index| index.to_u32());
|
|
|
|
let change = extend.next().map(|index| index.to_u32());
|
|
|
|
if extend.next().is_some() {
|
|
|
|
return Err(DerivationPathError::InvalidDerivationPath(format!(
|
2022-12-06 06:30:06 -08:00
|
|
|
"key path `{path}` too deep, only <account>/<change> supported"
|
2021-04-19 13:57:43 -07:00
|
|
|
)));
|
|
|
|
}
|
|
|
|
Ok(Self::new_bip44_with_coin(coin, account, change))
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
|
2022-09-07 11:31:40 -07:00
|
|
|
pub fn from_absolute_path_str(path: &str) -> Result<Self, DerivationPathError> {
|
2021-04-19 13:57:43 -07:00
|
|
|
let inner = DerivationPath::_from_absolute_path_insecure_str(path)?
|
|
|
|
.into_iter()
|
|
|
|
.map(|c| ChildIndex::Hardened(c.to_u32()))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
Ok(Self(DerivationPathInner::new(inner)))
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
fn _from_absolute_path_insecure_str(path: &str) -> Result<Self, DerivationPathError> {
|
2021-06-18 06:34:46 -07:00
|
|
|
Ok(Self(DerivationPathInner::from_str(path).map_err(
|
2021-04-19 13:57:43 -07:00
|
|
|
|err| DerivationPathError::InvalidDerivationPath(err.to_string()),
|
|
|
|
)?))
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
pub fn new_bip44(account: Option<u32>, change: Option<u32>) -> Self {
|
|
|
|
Self::new_bip44_with_coin(Solana, account, change)
|
|
|
|
}
|
2021-04-16 15:03:24 -07:00
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
fn new_bip44_with_coin<T: Bip44>(coin: T, account: Option<u32>, change: Option<u32>) -> Self {
|
|
|
|
let mut indexes = coin.base_indexes();
|
|
|
|
if let Some(account) = account {
|
|
|
|
indexes.push(ChildIndex::Hardened(account));
|
|
|
|
if let Some(change) = change {
|
|
|
|
indexes.push(ChildIndex::Hardened(change));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Self::new(indexes)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn account(&self) -> Option<&ChildIndex> {
|
|
|
|
self.0.path().get(ACCOUNT_INDEX)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn change(&self) -> Option<&ChildIndex> {
|
|
|
|
self.0.path().get(CHANGE_INDEX)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn path(&self) -> &[ChildIndex] {
|
|
|
|
self.0.path()
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
|
2021-04-29 00:42:21 -07:00
|
|
|
// Assumes `key` query-string key
|
2021-04-16 15:03:24 -07:00
|
|
|
pub fn get_query(&self) -> String {
|
2021-04-19 13:57:43 -07:00
|
|
|
if let Some(account) = &self.account() {
|
|
|
|
if let Some(change) = &self.change() {
|
2022-12-06 06:30:06 -08:00
|
|
|
format!("?key={account}/{change}")
|
2021-04-16 15:03:24 -07:00
|
|
|
} else {
|
2022-12-06 06:30:06 -08:00
|
|
|
format!("?key={account}")
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
"".to_string()
|
|
|
|
}
|
|
|
|
}
|
2021-04-29 00:42:21 -07:00
|
|
|
|
2021-05-03 18:58:56 -07:00
|
|
|
pub fn from_uri_key_query(uri: &URIReference<'_>) -> Result<Option<Self>, DerivationPathError> {
|
|
|
|
Self::from_uri(uri, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from_uri_any_query(uri: &URIReference<'_>) -> Result<Option<Self>, DerivationPathError> {
|
|
|
|
Self::from_uri(uri, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn from_uri(
|
|
|
|
uri: &URIReference<'_>,
|
|
|
|
key_only: bool,
|
|
|
|
) -> Result<Option<Self>, DerivationPathError> {
|
2021-04-29 00:42:21 -07:00
|
|
|
if let Some(query) = uri.query() {
|
|
|
|
let query_str = query.as_str();
|
|
|
|
if query_str.is_empty() {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
let query = qstring::QString::from(query_str);
|
|
|
|
if query.len() > 1 {
|
|
|
|
return Err(DerivationPathError::InvalidDerivationPath(
|
|
|
|
"invalid query string, extra fields not supported".to_string(),
|
|
|
|
));
|
|
|
|
}
|
2021-05-03 18:58:56 -07:00
|
|
|
let key = query.get(QueryKey::Key.as_ref());
|
|
|
|
if let Some(key) = key {
|
|
|
|
// Use from_key_str instead of TryInto here to make it more explicit that this
|
|
|
|
// generates a Solana bip44 DerivationPath
|
|
|
|
return Self::from_key_str(key).map(Some);
|
|
|
|
}
|
|
|
|
if key_only {
|
2021-04-29 00:42:21 -07:00
|
|
|
return Err(DerivationPathError::InvalidDerivationPath(format!(
|
2022-12-06 06:30:06 -08:00
|
|
|
"invalid query string `{query_str}`, only `key` supported",
|
2021-04-29 00:42:21 -07:00
|
|
|
)));
|
|
|
|
}
|
2021-05-03 18:58:56 -07:00
|
|
|
let full_path = query.get(QueryKey::FullPath.as_ref());
|
|
|
|
if let Some(full_path) = full_path {
|
|
|
|
return Self::from_absolute_path_str(full_path).map(Some);
|
|
|
|
}
|
|
|
|
Err(DerivationPathError::InvalidDerivationPath(format!(
|
2022-12-06 06:30:06 -08:00
|
|
|
"invalid query string `{query_str}`, only `key` and `full-path` supported",
|
2021-05-03 18:58:56 -07:00
|
|
|
)))
|
2021-04-29 00:42:21 -07:00
|
|
|
} else {
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
}
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
impl fmt::Debug for DerivationPath {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "m")?;
|
|
|
|
for index in self.0.path() {
|
2022-12-06 06:30:06 -08:00
|
|
|
write!(f, "/{index}")?;
|
2021-04-19 13:57:43 -07:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> IntoIterator for &'a DerivationPath {
|
|
|
|
type IntoIter = Iter<'a, ChildIndex>;
|
|
|
|
type Item = &'a ChildIndex;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
|
|
self.0.into_iter()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-03 18:58:56 -07:00
|
|
|
const QUERY_KEY_FULL_PATH: &str = "full-path";
|
|
|
|
const QUERY_KEY_KEY: &str = "key";
|
|
|
|
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, Error, PartialEq, Eq)]
|
2021-05-03 18:58:56 -07:00
|
|
|
#[error("invalid query key `{0}`")]
|
|
|
|
struct QueryKeyError(String);
|
|
|
|
|
|
|
|
enum QueryKey {
|
|
|
|
FullPath,
|
|
|
|
Key,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromStr for QueryKey {
|
|
|
|
type Err = QueryKeyError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
let lowercase = s.to_ascii_lowercase();
|
|
|
|
match lowercase.as_str() {
|
|
|
|
QUERY_KEY_FULL_PATH => Ok(Self::FullPath),
|
|
|
|
QUERY_KEY_KEY => Ok(Self::Key),
|
|
|
|
_ => Err(QueryKeyError(s.to_string())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AsRef<str> for QueryKey {
|
|
|
|
fn as_ref(&self) -> &str {
|
|
|
|
match self {
|
|
|
|
Self::FullPath => QUERY_KEY_FULL_PATH,
|
|
|
|
Self::Key => QUERY_KEY_KEY,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::fmt::Display for QueryKey {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
let s: &str = self.as_ref();
|
2022-12-06 06:30:06 -08:00
|
|
|
write!(f, "{s}")
|
2021-05-03 18:58:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
trait Bip44 {
|
|
|
|
const PURPOSE: u32 = 44;
|
|
|
|
const COIN: u32;
|
|
|
|
|
|
|
|
fn base_indexes(&self) -> Vec<ChildIndex> {
|
|
|
|
vec![
|
|
|
|
ChildIndex::Hardened(Self::PURPOSE),
|
|
|
|
ChildIndex::Hardened(Self::COIN),
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Solana;
|
|
|
|
|
|
|
|
impl Bip44 for Solana {
|
|
|
|
const COIN: u32 = 501;
|
|
|
|
}
|
|
|
|
|
2021-04-16 15:03:24 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2023-08-30 10:48:27 -07:00
|
|
|
use {super::*, assert_matches::assert_matches, uriparse::URIReferenceBuilder};
|
2021-04-16 15:03:24 -07:00
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
struct TestCoin;
|
|
|
|
impl Bip44 for TestCoin {
|
|
|
|
const COIN: u32 = 999;
|
|
|
|
}
|
|
|
|
|
2021-04-16 15:03:24 -07:00
|
|
|
#[test]
|
2021-04-19 13:57:43 -07:00
|
|
|
fn test_from_key_str() {
|
|
|
|
let s = "1/2";
|
|
|
|
assert_eq!(
|
|
|
|
DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
|
|
|
|
DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
|
|
|
|
);
|
|
|
|
let s = "1'/2'";
|
|
|
|
assert_eq!(
|
|
|
|
DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
|
|
|
|
DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
|
|
|
|
);
|
|
|
|
let s = "1\'/2\'";
|
|
|
|
assert_eq!(
|
|
|
|
DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
|
|
|
|
DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
|
|
|
|
);
|
|
|
|
let s = "1";
|
2021-04-16 15:03:24 -07:00
|
|
|
assert_eq!(
|
2021-04-19 13:57:43 -07:00
|
|
|
DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
|
|
|
|
DerivationPath::new_bip44_with_coin(TestCoin, Some(1), None)
|
2021-04-16 15:03:24 -07:00
|
|
|
);
|
2021-04-19 13:57:43 -07:00
|
|
|
let s = "1'";
|
2021-04-16 15:03:24 -07:00
|
|
|
assert_eq!(
|
2021-04-19 13:57:43 -07:00
|
|
|
DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
|
|
|
|
DerivationPath::new_bip44_with_coin(TestCoin, Some(1), None)
|
2021-04-16 15:03:24 -07:00
|
|
|
);
|
2021-04-19 13:57:43 -07:00
|
|
|
let s = "1\'";
|
|
|
|
assert_eq!(
|
|
|
|
DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
|
|
|
|
DerivationPath::new_bip44_with_coin(TestCoin, Some(1), None)
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(DerivationPath::from_key_str_with_coin("1/2/3", TestCoin).is_err());
|
|
|
|
assert!(DerivationPath::from_key_str_with_coin("other", TestCoin).is_err());
|
|
|
|
assert!(DerivationPath::from_key_str_with_coin("1o", TestCoin).is_err());
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-04-19 13:57:43 -07:00
|
|
|
fn test_from_absolute_path_str() {
|
|
|
|
let s = "m/44/501";
|
|
|
|
assert_eq!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_absolute_path_str(s).unwrap(),
|
2021-04-19 13:57:43 -07:00
|
|
|
DerivationPath::default()
|
|
|
|
);
|
|
|
|
let s = "m/44'/501'";
|
|
|
|
assert_eq!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_absolute_path_str(s).unwrap(),
|
2021-04-19 13:57:43 -07:00
|
|
|
DerivationPath::default()
|
|
|
|
);
|
|
|
|
let s = "m/44'/501'/1/2";
|
|
|
|
assert_eq!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_absolute_path_str(s).unwrap(),
|
2021-04-19 13:57:43 -07:00
|
|
|
DerivationPath::new_bip44(Some(1), Some(2))
|
|
|
|
);
|
|
|
|
let s = "m/44'/501'/1'/2'";
|
|
|
|
assert_eq!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_absolute_path_str(s).unwrap(),
|
2021-04-19 13:57:43 -07:00
|
|
|
DerivationPath::new_bip44(Some(1), Some(2))
|
|
|
|
);
|
2021-04-16 15:03:24 -07:00
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
// Test non-Solana Bip44
|
|
|
|
let s = "m/44'/999'/1/2";
|
|
|
|
assert_eq!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_absolute_path_str(s).unwrap(),
|
2021-04-19 13:57:43 -07:00
|
|
|
DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
|
|
|
|
);
|
|
|
|
let s = "m/44'/999'/1'/2'";
|
|
|
|
assert_eq!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_absolute_path_str(s).unwrap(),
|
2021-04-19 13:57:43 -07:00
|
|
|
DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
|
|
|
|
);
|
2021-04-16 15:03:24 -07:00
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
// Test non-bip44 paths
|
|
|
|
let s = "m/501'/0'/0/0";
|
|
|
|
assert_eq!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_absolute_path_str(s).unwrap(),
|
2021-04-19 13:57:43 -07:00
|
|
|
DerivationPath::new(vec![
|
|
|
|
ChildIndex::Hardened(501),
|
|
|
|
ChildIndex::Hardened(0),
|
|
|
|
ChildIndex::Hardened(0),
|
|
|
|
ChildIndex::Hardened(0),
|
|
|
|
])
|
|
|
|
);
|
|
|
|
let s = "m/501'/0'/0'/0'";
|
|
|
|
assert_eq!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_absolute_path_str(s).unwrap(),
|
2021-04-19 13:57:43 -07:00
|
|
|
DerivationPath::new(vec![
|
|
|
|
ChildIndex::Hardened(501),
|
|
|
|
ChildIndex::Hardened(0),
|
|
|
|
ChildIndex::Hardened(0),
|
|
|
|
ChildIndex::Hardened(0),
|
|
|
|
])
|
|
|
|
);
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
|
2021-04-29 00:42:21 -07:00
|
|
|
#[test]
|
2021-05-03 18:58:56 -07:00
|
|
|
fn test_from_uri() {
|
2021-04-29 00:42:21 -07:00
|
|
|
let derivation_path = DerivationPath::new_bip44(Some(0), Some(0));
|
|
|
|
|
|
|
|
// test://path?key=0/0
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("key=0/0"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
|
|
|
assert_eq!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, true).unwrap(),
|
2021-04-29 00:42:21 -07:00
|
|
|
Some(derivation_path.clone())
|
|
|
|
);
|
|
|
|
|
|
|
|
// test://path?key=0'/0'
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("key=0'/0'"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
|
|
|
assert_eq!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, true).unwrap(),
|
2021-04-29 00:42:21 -07:00
|
|
|
Some(derivation_path.clone())
|
|
|
|
);
|
|
|
|
|
|
|
|
// test://path?key=0\'/0\'
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("key=0\'/0\'"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
|
|
|
assert_eq!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, true).unwrap(),
|
2021-04-29 00:42:21 -07:00
|
|
|
Some(derivation_path)
|
|
|
|
);
|
|
|
|
|
2021-05-03 18:58:56 -07:00
|
|
|
// test://path?key=m
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("key=m"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
DerivationPath::from_uri(&uri, true).unwrap(),
|
|
|
|
Some(DerivationPath::new_bip44(None, None))
|
|
|
|
);
|
|
|
|
|
2021-04-29 00:42:21 -07:00
|
|
|
// test://path
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2021-05-03 18:58:56 -07:00
|
|
|
assert_eq!(DerivationPath::from_uri(&uri, true).unwrap(), None);
|
2021-04-29 00:42:21 -07:00
|
|
|
|
|
|
|
// test://path?
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some(""))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2021-05-03 18:58:56 -07:00
|
|
|
assert_eq!(DerivationPath::from_uri(&uri, true).unwrap(), None);
|
2021-04-29 00:42:21 -07:00
|
|
|
|
|
|
|
// test://path?key=0/0/0
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("key=0/0/0"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, true),
|
2021-04-29 00:42:21 -07:00
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-04-29 00:42:21 -07:00
|
|
|
|
|
|
|
// test://path?key=0/0&bad-key=0/0
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("key=0/0&bad-key=0/0"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, true),
|
2021-04-29 00:42:21 -07:00
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-04-29 00:42:21 -07:00
|
|
|
|
|
|
|
// test://path?bad-key=0/0
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("bad-key=0/0"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, true),
|
2021-04-29 00:42:21 -07:00
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-04-29 00:42:21 -07:00
|
|
|
|
|
|
|
// test://path?key=bad-value
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("key=bad-value"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, true),
|
2021-04-29 00:42:21 -07:00
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-04-29 00:42:21 -07:00
|
|
|
|
|
|
|
// test://path?key=
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("key="))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, true),
|
2021-04-29 00:42:21 -07:00
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-04-29 00:42:21 -07:00
|
|
|
|
|
|
|
// test://path?key
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("key"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, true),
|
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-05-03 18:58:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_from_uri_full_path() {
|
|
|
|
let derivation_path = DerivationPath::from_absolute_path_str("m/44'/999'/1'").unwrap();
|
|
|
|
|
|
|
|
// test://path?full-path=m/44/999/1
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("full-path=m/44/999/1"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
DerivationPath::from_uri(&uri, false).unwrap(),
|
|
|
|
Some(derivation_path.clone())
|
|
|
|
);
|
|
|
|
|
|
|
|
// test://path?full-path=m/44'/999'/1'
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("full-path=m/44'/999'/1'"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
DerivationPath::from_uri(&uri, false).unwrap(),
|
|
|
|
Some(derivation_path.clone())
|
|
|
|
);
|
|
|
|
|
|
|
|
// test://path?full-path=m/44\'/999\'/1\'
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("full-path=m/44\'/999\'/1\'"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
DerivationPath::from_uri(&uri, false).unwrap(),
|
|
|
|
Some(derivation_path)
|
|
|
|
);
|
|
|
|
|
|
|
|
// test://path?full-path=m
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("full-path=m"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
DerivationPath::from_uri(&uri, false).unwrap(),
|
|
|
|
Some(DerivationPath(DerivationPathInner::from_str("m").unwrap()))
|
|
|
|
);
|
|
|
|
|
|
|
|
// test://path?full-path=m/44/999/1, only `key` supported
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("full-path=m/44/999/1"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, true),
|
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-05-03 18:58:56 -07:00
|
|
|
|
|
|
|
// test://path?key=0/0&full-path=m/44/999/1
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("key=0/0&full-path=m/44/999/1"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, false),
|
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-05-03 18:58:56 -07:00
|
|
|
|
|
|
|
// test://path?full-path=m/44/999/1&bad-key=0/0
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("full-path=m/44/999/1&bad-key=0/0"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, false),
|
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-05-03 18:58:56 -07:00
|
|
|
|
|
|
|
// test://path?full-path=bad-value
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("full-path=bad-value"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, false),
|
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-05-03 18:58:56 -07:00
|
|
|
|
|
|
|
// test://path?full-path=
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("full-path="))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, false),
|
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-05-03 18:58:56 -07:00
|
|
|
|
|
|
|
// test://path?full-path
|
|
|
|
let mut builder = URIReferenceBuilder::new();
|
|
|
|
builder
|
|
|
|
.try_scheme(Some("test"))
|
|
|
|
.unwrap()
|
|
|
|
.try_authority(Some("path"))
|
|
|
|
.unwrap()
|
|
|
|
.try_path("")
|
|
|
|
.unwrap()
|
|
|
|
.try_query(Some("full-path"))
|
|
|
|
.unwrap();
|
|
|
|
let uri = builder.build().unwrap();
|
2023-08-30 10:48:27 -07:00
|
|
|
assert_matches!(
|
2021-05-03 18:58:56 -07:00
|
|
|
DerivationPath::from_uri(&uri, false),
|
2021-04-29 00:42:21 -07:00
|
|
|
Err(DerivationPathError::InvalidDerivationPath(_))
|
2023-08-30 10:48:27 -07:00
|
|
|
);
|
2021-04-29 00:42:21 -07:00
|
|
|
}
|
|
|
|
|
2021-04-16 15:03:24 -07:00
|
|
|
#[test]
|
2021-04-19 13:57:43 -07:00
|
|
|
fn test_get_query() {
|
|
|
|
let derivation_path = DerivationPath::new_bip44_with_coin(TestCoin, None, None);
|
|
|
|
assert_eq!(derivation_path.get_query(), "".to_string());
|
|
|
|
let derivation_path = DerivationPath::new_bip44_with_coin(TestCoin, Some(1), None);
|
|
|
|
assert_eq!(derivation_path.get_query(), "?key=1'".to_string());
|
|
|
|
let derivation_path = DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2));
|
|
|
|
assert_eq!(derivation_path.get_query(), "?key=1'/2'".to_string());
|
|
|
|
}
|
2021-04-16 15:03:24 -07:00
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
#[test]
|
|
|
|
fn test_derivation_path_debug() {
|
|
|
|
let path = DerivationPath::default();
|
2022-12-06 06:30:06 -08:00
|
|
|
assert_eq!(format!("{path:?}"), "m/44'/501'".to_string());
|
2021-04-16 15:03:24 -07:00
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
let path = DerivationPath::new_bip44(Some(1), None);
|
2022-12-06 06:30:06 -08:00
|
|
|
assert_eq!(format!("{path:?}"), "m/44'/501'/1'".to_string());
|
2021-04-16 15:03:24 -07:00
|
|
|
|
2021-04-19 13:57:43 -07:00
|
|
|
let path = DerivationPath::new_bip44(Some(1), Some(2));
|
2022-12-06 06:30:06 -08:00
|
|
|
assert_eq!(format!("{path:?}"), "m/44'/501'/1'/2'".to_string());
|
2021-04-16 15:03:24 -07:00
|
|
|
}
|
|
|
|
}
|