Skip to main content

rialo_s_derivation_path/
lib.rs

1//! [BIP-44] derivation paths.
2//!
3//! [BIP-44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
4//!
5//! Includes definitions and helpers for Solana derivation paths.
6//! The standard Solana BIP-44 derivation path prefix is
7//!
8//! > `m/44'/501'`
9//!
10//! with 501 being the Solana coin type.
11
12use core::{iter::IntoIterator, slice::Iter};
13use std::{
14    convert::{Infallible, TryFrom},
15    fmt,
16    str::FromStr,
17};
18
19use derivation_path::{ChildIndex, DerivationPath as DerivationPathInner};
20use uriparse::URIReference;
21
22const ACCOUNT_INDEX: usize = 2;
23const CHANGE_INDEX: usize = 3;
24
25/// Derivation path error.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum DerivationPathError {
28    InvalidDerivationPath(String),
29    Infallible,
30}
31
32impl std::error::Error for DerivationPathError {}
33
34impl fmt::Display for DerivationPathError {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            DerivationPathError::InvalidDerivationPath(p) => {
38                write!(f, "invalid derivation path: {p}",)
39            }
40            DerivationPathError::Infallible => f.write_str("infallible"),
41        }
42    }
43}
44
45impl From<Infallible> for DerivationPathError {
46    fn from(_: Infallible) -> Self {
47        Self::Infallible
48    }
49}
50
51#[derive(Clone, PartialEq, Eq)]
52pub struct DerivationPath(DerivationPathInner);
53
54impl Default for DerivationPath {
55    fn default() -> Self {
56        Self::new_bip44(None, None)
57    }
58}
59
60impl TryFrom<&str> for DerivationPath {
61    type Error = DerivationPathError;
62    fn try_from(s: &str) -> Result<Self, Self::Error> {
63        Self::from_key_str(s)
64    }
65}
66
67impl AsRef<[ChildIndex]> for DerivationPath {
68    fn as_ref(&self) -> &[ChildIndex] {
69        self.0.as_ref()
70    }
71}
72
73impl DerivationPath {
74    fn new<P: Into<Box<[ChildIndex]>>>(path: P) -> Self {
75        Self(DerivationPathInner::new(path))
76    }
77
78    pub fn from_key_str(path: &str) -> Result<Self, DerivationPathError> {
79        Self::from_key_str_with_coin(path, Solana)
80    }
81
82    fn from_key_str_with_coin<T: Bip44>(path: &str, coin: T) -> Result<Self, DerivationPathError> {
83        let master_path = if path == "m" {
84            path.to_string()
85        } else {
86            format!("m/{path}")
87        };
88        let extend = DerivationPathInner::from_str(&master_path)
89            .map_err(|err| DerivationPathError::InvalidDerivationPath(err.to_string()))?;
90        let mut extend = extend.into_iter();
91        let account = extend.next().map(|index| index.to_u32());
92        let change = extend.next().map(|index| index.to_u32());
93        if extend.next().is_some() {
94            return Err(DerivationPathError::InvalidDerivationPath(format!(
95                "key path `{path}` too deep, only <account>/<change> supported"
96            )));
97        }
98        Ok(Self::new_bip44_with_coin(coin, account, change))
99    }
100
101    pub fn from_absolute_path_str(path: &str) -> Result<Self, DerivationPathError> {
102        let inner = DerivationPath::_from_absolute_path_insecure_str(path)?
103            .into_iter()
104            .map(|c| ChildIndex::Hardened(c.to_u32()))
105            .collect::<Vec<_>>();
106        Ok(Self(DerivationPathInner::new(inner)))
107    }
108
109    fn _from_absolute_path_insecure_str(path: &str) -> Result<Self, DerivationPathError> {
110        Ok(Self(DerivationPathInner::from_str(path).map_err(
111            |err| DerivationPathError::InvalidDerivationPath(err.to_string()),
112        )?))
113    }
114
115    pub fn new_bip44(account: Option<u32>, change: Option<u32>) -> Self {
116        Self::new_bip44_with_coin(Solana, account, change)
117    }
118
119    fn new_bip44_with_coin<T: Bip44>(coin: T, account: Option<u32>, change: Option<u32>) -> Self {
120        let mut indexes = coin.base_indexes();
121        if let Some(account) = account {
122            indexes.push(ChildIndex::Hardened(account));
123            if let Some(change) = change {
124                indexes.push(ChildIndex::Hardened(change));
125            }
126        }
127        Self::new(indexes)
128    }
129
130    pub fn account(&self) -> Option<&ChildIndex> {
131        self.0.path().get(ACCOUNT_INDEX)
132    }
133
134    pub fn change(&self) -> Option<&ChildIndex> {
135        self.0.path().get(CHANGE_INDEX)
136    }
137
138    pub fn path(&self) -> &[ChildIndex] {
139        self.0.path()
140    }
141
142    // Assumes `key` query-string key
143    pub fn get_query(&self) -> String {
144        if let Some(account) = &self.account() {
145            if let Some(change) = &self.change() {
146                format!("?key={account}/{change}")
147            } else {
148                format!("?key={account}")
149            }
150        } else {
151            "".to_string()
152        }
153    }
154
155    pub fn from_uri_key_query(uri: &URIReference<'_>) -> Result<Option<Self>, DerivationPathError> {
156        Self::from_uri(uri, true)
157    }
158
159    pub fn from_uri_any_query(uri: &URIReference<'_>) -> Result<Option<Self>, DerivationPathError> {
160        Self::from_uri(uri, false)
161    }
162
163    fn from_uri(
164        uri: &URIReference<'_>,
165        key_only: bool,
166    ) -> Result<Option<Self>, DerivationPathError> {
167        if let Some(query) = uri.query() {
168            let query_str = query.as_str();
169            if query_str.is_empty() {
170                return Ok(None);
171            }
172            let query = qstring::QString::from(query_str);
173            if query.len() > 1 {
174                return Err(DerivationPathError::InvalidDerivationPath(
175                    "invalid query string, extra fields not supported".to_string(),
176                ));
177            }
178            let key = query.get(QueryKey::Key.as_ref());
179            if let Some(key) = key {
180                // Use from_key_str instead of TryInto here to make it more explicit that this
181                // generates a Solana bip44 DerivationPath
182                return Self::from_key_str(key).map(Some);
183            }
184            if key_only {
185                return Err(DerivationPathError::InvalidDerivationPath(format!(
186                    "invalid query string `{query_str}`, only `key` supported",
187                )));
188            }
189            let full_path = query.get(QueryKey::FullPath.as_ref());
190            if let Some(full_path) = full_path {
191                return Self::from_absolute_path_str(full_path).map(Some);
192            }
193            Err(DerivationPathError::InvalidDerivationPath(format!(
194                "invalid query string `{query_str}`, only `key` and `full-path` supported",
195            )))
196        } else {
197            Ok(None)
198        }
199    }
200}
201
202impl fmt::Debug for DerivationPath {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        write!(f, "m")?;
205        for index in self.0.path() {
206            write!(f, "/{index}")?;
207        }
208        Ok(())
209    }
210}
211
212impl<'a> IntoIterator for &'a DerivationPath {
213    type IntoIter = Iter<'a, ChildIndex>;
214    type Item = &'a ChildIndex;
215    fn into_iter(self) -> Self::IntoIter {
216        self.0.into_iter()
217    }
218}
219
220const QUERY_KEY_FULL_PATH: &str = "full-path";
221const QUERY_KEY_KEY: &str = "key";
222
223#[derive(Clone, Debug, PartialEq, Eq)]
224struct QueryKeyError(String);
225
226impl std::error::Error for QueryKeyError {}
227
228impl fmt::Display for QueryKeyError {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        write!(f, "invalid query key `{}`", self.0)
231    }
232}
233
234enum QueryKey {
235    FullPath,
236    Key,
237}
238
239impl FromStr for QueryKey {
240    type Err = QueryKeyError;
241    fn from_str(s: &str) -> Result<Self, Self::Err> {
242        let lowercase = s.to_ascii_lowercase();
243        match lowercase.as_str() {
244            QUERY_KEY_FULL_PATH => Ok(Self::FullPath),
245            QUERY_KEY_KEY => Ok(Self::Key),
246            _ => Err(QueryKeyError(s.to_string())),
247        }
248    }
249}
250
251impl AsRef<str> for QueryKey {
252    fn as_ref(&self) -> &str {
253        match self {
254            Self::FullPath => QUERY_KEY_FULL_PATH,
255            Self::Key => QUERY_KEY_KEY,
256        }
257    }
258}
259
260impl std::fmt::Display for QueryKey {
261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        let s: &str = self.as_ref();
263        write!(f, "{s}")
264    }
265}
266
267trait Bip44 {
268    const PURPOSE: u32 = 44;
269    const COIN: u32;
270
271    fn base_indexes(&self) -> Vec<ChildIndex> {
272        vec![
273            ChildIndex::Hardened(Self::PURPOSE),
274            ChildIndex::Hardened(Self::COIN),
275        ]
276    }
277}
278
279struct Solana;
280
281impl Bip44 for Solana {
282    const COIN: u32 = 501;
283}
284
285#[cfg(test)]
286mod tests {
287    use assert_matches::assert_matches;
288    use uriparse::URIReferenceBuilder;
289
290    use super::*;
291
292    struct TestCoin;
293    impl Bip44 for TestCoin {
294        const COIN: u32 = 999;
295    }
296
297    #[test]
298    fn test_from_key_str() {
299        let s = "1/2";
300        assert_eq!(
301            DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
302            DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
303        );
304        let s = "1'/2'";
305        assert_eq!(
306            DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
307            DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
308        );
309        let s = "1\'/2\'";
310        assert_eq!(
311            DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
312            DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
313        );
314        let s = "1";
315        assert_eq!(
316            DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
317            DerivationPath::new_bip44_with_coin(TestCoin, Some(1), None)
318        );
319        let s = "1'";
320        assert_eq!(
321            DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
322            DerivationPath::new_bip44_with_coin(TestCoin, Some(1), None)
323        );
324        let s = "1\'";
325        assert_eq!(
326            DerivationPath::from_key_str_with_coin(s, TestCoin).unwrap(),
327            DerivationPath::new_bip44_with_coin(TestCoin, Some(1), None)
328        );
329
330        assert!(DerivationPath::from_key_str_with_coin("1/2/3", TestCoin).is_err());
331        assert!(DerivationPath::from_key_str_with_coin("other", TestCoin).is_err());
332        assert!(DerivationPath::from_key_str_with_coin("1o", TestCoin).is_err());
333    }
334
335    #[test]
336    fn test_from_absolute_path_str() {
337        let s = "m/44/501";
338        assert_eq!(
339            DerivationPath::from_absolute_path_str(s).unwrap(),
340            DerivationPath::default()
341        );
342        let s = "m/44'/501'";
343        assert_eq!(
344            DerivationPath::from_absolute_path_str(s).unwrap(),
345            DerivationPath::default()
346        );
347        let s = "m/44'/501'/1/2";
348        assert_eq!(
349            DerivationPath::from_absolute_path_str(s).unwrap(),
350            DerivationPath::new_bip44(Some(1), Some(2))
351        );
352        let s = "m/44'/501'/1'/2'";
353        assert_eq!(
354            DerivationPath::from_absolute_path_str(s).unwrap(),
355            DerivationPath::new_bip44(Some(1), Some(2))
356        );
357
358        // Test non-Solana Bip44
359        let s = "m/44'/999'/1/2";
360        assert_eq!(
361            DerivationPath::from_absolute_path_str(s).unwrap(),
362            DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
363        );
364        let s = "m/44'/999'/1'/2'";
365        assert_eq!(
366            DerivationPath::from_absolute_path_str(s).unwrap(),
367            DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
368        );
369
370        // Test non-bip44 paths
371        let s = "m/501'/0'/0/0";
372        assert_eq!(
373            DerivationPath::from_absolute_path_str(s).unwrap(),
374            DerivationPath::new(vec![
375                ChildIndex::Hardened(501),
376                ChildIndex::Hardened(0),
377                ChildIndex::Hardened(0),
378                ChildIndex::Hardened(0),
379            ])
380        );
381        let s = "m/501'/0'/0'/0'";
382        assert_eq!(
383            DerivationPath::from_absolute_path_str(s).unwrap(),
384            DerivationPath::new(vec![
385                ChildIndex::Hardened(501),
386                ChildIndex::Hardened(0),
387                ChildIndex::Hardened(0),
388                ChildIndex::Hardened(0),
389            ])
390        );
391    }
392
393    #[test]
394    fn test_from_uri() {
395        let derivation_path = DerivationPath::new_bip44(Some(0), Some(0));
396
397        // test://path?key=0/0
398        let mut builder = URIReferenceBuilder::new();
399        builder
400            .try_scheme(Some("test"))
401            .unwrap()
402            .try_authority(Some("path"))
403            .unwrap()
404            .try_path("")
405            .unwrap()
406            .try_query(Some("key=0/0"))
407            .unwrap();
408        let uri = builder.build().unwrap();
409        assert_eq!(
410            DerivationPath::from_uri(&uri, true).unwrap(),
411            Some(derivation_path.clone())
412        );
413
414        // test://path?key=0'/0'
415        let mut builder = URIReferenceBuilder::new();
416        builder
417            .try_scheme(Some("test"))
418            .unwrap()
419            .try_authority(Some("path"))
420            .unwrap()
421            .try_path("")
422            .unwrap()
423            .try_query(Some("key=0'/0'"))
424            .unwrap();
425        let uri = builder.build().unwrap();
426        assert_eq!(
427            DerivationPath::from_uri(&uri, true).unwrap(),
428            Some(derivation_path.clone())
429        );
430
431        // test://path?key=0\'/0\'
432        let mut builder = URIReferenceBuilder::new();
433        builder
434            .try_scheme(Some("test"))
435            .unwrap()
436            .try_authority(Some("path"))
437            .unwrap()
438            .try_path("")
439            .unwrap()
440            .try_query(Some("key=0\'/0\'"))
441            .unwrap();
442        let uri = builder.build().unwrap();
443        assert_eq!(
444            DerivationPath::from_uri(&uri, true).unwrap(),
445            Some(derivation_path)
446        );
447
448        // test://path?key=m
449        let mut builder = URIReferenceBuilder::new();
450        builder
451            .try_scheme(Some("test"))
452            .unwrap()
453            .try_authority(Some("path"))
454            .unwrap()
455            .try_path("")
456            .unwrap()
457            .try_query(Some("key=m"))
458            .unwrap();
459        let uri = builder.build().unwrap();
460        assert_eq!(
461            DerivationPath::from_uri(&uri, true).unwrap(),
462            Some(DerivationPath::new_bip44(None, None))
463        );
464
465        // test://path
466        let mut builder = URIReferenceBuilder::new();
467        builder
468            .try_scheme(Some("test"))
469            .unwrap()
470            .try_authority(Some("path"))
471            .unwrap()
472            .try_path("")
473            .unwrap();
474        let uri = builder.build().unwrap();
475        assert_eq!(DerivationPath::from_uri(&uri, true).unwrap(), None);
476
477        // test://path?
478        let mut builder = URIReferenceBuilder::new();
479        builder
480            .try_scheme(Some("test"))
481            .unwrap()
482            .try_authority(Some("path"))
483            .unwrap()
484            .try_path("")
485            .unwrap()
486            .try_query(Some(""))
487            .unwrap();
488        let uri = builder.build().unwrap();
489        assert_eq!(DerivationPath::from_uri(&uri, true).unwrap(), None);
490
491        // test://path?key=0/0/0
492        let mut builder = URIReferenceBuilder::new();
493        builder
494            .try_scheme(Some("test"))
495            .unwrap()
496            .try_authority(Some("path"))
497            .unwrap()
498            .try_path("")
499            .unwrap()
500            .try_query(Some("key=0/0/0"))
501            .unwrap();
502        let uri = builder.build().unwrap();
503        assert_matches!(
504            DerivationPath::from_uri(&uri, true),
505            Err(DerivationPathError::InvalidDerivationPath(_))
506        );
507
508        // test://path?key=0/0&bad-key=0/0
509        let mut builder = URIReferenceBuilder::new();
510        builder
511            .try_scheme(Some("test"))
512            .unwrap()
513            .try_authority(Some("path"))
514            .unwrap()
515            .try_path("")
516            .unwrap()
517            .try_query(Some("key=0/0&bad-key=0/0"))
518            .unwrap();
519        let uri = builder.build().unwrap();
520        assert_matches!(
521            DerivationPath::from_uri(&uri, true),
522            Err(DerivationPathError::InvalidDerivationPath(_))
523        );
524
525        // test://path?bad-key=0/0
526        let mut builder = URIReferenceBuilder::new();
527        builder
528            .try_scheme(Some("test"))
529            .unwrap()
530            .try_authority(Some("path"))
531            .unwrap()
532            .try_path("")
533            .unwrap()
534            .try_query(Some("bad-key=0/0"))
535            .unwrap();
536        let uri = builder.build().unwrap();
537        assert_matches!(
538            DerivationPath::from_uri(&uri, true),
539            Err(DerivationPathError::InvalidDerivationPath(_))
540        );
541
542        // test://path?key=bad-value
543        let mut builder = URIReferenceBuilder::new();
544        builder
545            .try_scheme(Some("test"))
546            .unwrap()
547            .try_authority(Some("path"))
548            .unwrap()
549            .try_path("")
550            .unwrap()
551            .try_query(Some("key=bad-value"))
552            .unwrap();
553        let uri = builder.build().unwrap();
554        assert_matches!(
555            DerivationPath::from_uri(&uri, true),
556            Err(DerivationPathError::InvalidDerivationPath(_))
557        );
558
559        // test://path?key=
560        let mut builder = URIReferenceBuilder::new();
561        builder
562            .try_scheme(Some("test"))
563            .unwrap()
564            .try_authority(Some("path"))
565            .unwrap()
566            .try_path("")
567            .unwrap()
568            .try_query(Some("key="))
569            .unwrap();
570        let uri = builder.build().unwrap();
571        assert_matches!(
572            DerivationPath::from_uri(&uri, true),
573            Err(DerivationPathError::InvalidDerivationPath(_))
574        );
575
576        // test://path?key
577        let mut builder = URIReferenceBuilder::new();
578        builder
579            .try_scheme(Some("test"))
580            .unwrap()
581            .try_authority(Some("path"))
582            .unwrap()
583            .try_path("")
584            .unwrap()
585            .try_query(Some("key"))
586            .unwrap();
587        let uri = builder.build().unwrap();
588        assert_matches!(
589            DerivationPath::from_uri(&uri, true),
590            Err(DerivationPathError::InvalidDerivationPath(_))
591        );
592    }
593
594    #[test]
595    fn test_from_uri_full_path() {
596        let derivation_path = DerivationPath::from_absolute_path_str("m/44'/999'/1'").unwrap();
597
598        // test://path?full-path=m/44/999/1
599        let mut builder = URIReferenceBuilder::new();
600        builder
601            .try_scheme(Some("test"))
602            .unwrap()
603            .try_authority(Some("path"))
604            .unwrap()
605            .try_path("")
606            .unwrap()
607            .try_query(Some("full-path=m/44/999/1"))
608            .unwrap();
609        let uri = builder.build().unwrap();
610        assert_eq!(
611            DerivationPath::from_uri(&uri, false).unwrap(),
612            Some(derivation_path.clone())
613        );
614
615        // test://path?full-path=m/44'/999'/1'
616        let mut builder = URIReferenceBuilder::new();
617        builder
618            .try_scheme(Some("test"))
619            .unwrap()
620            .try_authority(Some("path"))
621            .unwrap()
622            .try_path("")
623            .unwrap()
624            .try_query(Some("full-path=m/44'/999'/1'"))
625            .unwrap();
626        let uri = builder.build().unwrap();
627        assert_eq!(
628            DerivationPath::from_uri(&uri, false).unwrap(),
629            Some(derivation_path.clone())
630        );
631
632        // test://path?full-path=m/44\'/999\'/1\'
633        let mut builder = URIReferenceBuilder::new();
634        builder
635            .try_scheme(Some("test"))
636            .unwrap()
637            .try_authority(Some("path"))
638            .unwrap()
639            .try_path("")
640            .unwrap()
641            .try_query(Some("full-path=m/44\'/999\'/1\'"))
642            .unwrap();
643        let uri = builder.build().unwrap();
644        assert_eq!(
645            DerivationPath::from_uri(&uri, false).unwrap(),
646            Some(derivation_path)
647        );
648
649        // test://path?full-path=m
650        let mut builder = URIReferenceBuilder::new();
651        builder
652            .try_scheme(Some("test"))
653            .unwrap()
654            .try_authority(Some("path"))
655            .unwrap()
656            .try_path("")
657            .unwrap()
658            .try_query(Some("full-path=m"))
659            .unwrap();
660        let uri = builder.build().unwrap();
661        assert_eq!(
662            DerivationPath::from_uri(&uri, false).unwrap(),
663            Some(DerivationPath(DerivationPathInner::from_str("m").unwrap()))
664        );
665
666        // test://path?full-path=m/44/999/1, only `key` supported
667        let mut builder = URIReferenceBuilder::new();
668        builder
669            .try_scheme(Some("test"))
670            .unwrap()
671            .try_authority(Some("path"))
672            .unwrap()
673            .try_path("")
674            .unwrap()
675            .try_query(Some("full-path=m/44/999/1"))
676            .unwrap();
677        let uri = builder.build().unwrap();
678        assert_matches!(
679            DerivationPath::from_uri(&uri, true),
680            Err(DerivationPathError::InvalidDerivationPath(_))
681        );
682
683        // test://path?key=0/0&full-path=m/44/999/1
684        let mut builder = URIReferenceBuilder::new();
685        builder
686            .try_scheme(Some("test"))
687            .unwrap()
688            .try_authority(Some("path"))
689            .unwrap()
690            .try_path("")
691            .unwrap()
692            .try_query(Some("key=0/0&full-path=m/44/999/1"))
693            .unwrap();
694        let uri = builder.build().unwrap();
695        assert_matches!(
696            DerivationPath::from_uri(&uri, false),
697            Err(DerivationPathError::InvalidDerivationPath(_))
698        );
699
700        // test://path?full-path=m/44/999/1&bad-key=0/0
701        let mut builder = URIReferenceBuilder::new();
702        builder
703            .try_scheme(Some("test"))
704            .unwrap()
705            .try_authority(Some("path"))
706            .unwrap()
707            .try_path("")
708            .unwrap()
709            .try_query(Some("full-path=m/44/999/1&bad-key=0/0"))
710            .unwrap();
711        let uri = builder.build().unwrap();
712        assert_matches!(
713            DerivationPath::from_uri(&uri, false),
714            Err(DerivationPathError::InvalidDerivationPath(_))
715        );
716
717        // test://path?full-path=bad-value
718        let mut builder = URIReferenceBuilder::new();
719        builder
720            .try_scheme(Some("test"))
721            .unwrap()
722            .try_authority(Some("path"))
723            .unwrap()
724            .try_path("")
725            .unwrap()
726            .try_query(Some("full-path=bad-value"))
727            .unwrap();
728        let uri = builder.build().unwrap();
729        assert_matches!(
730            DerivationPath::from_uri(&uri, false),
731            Err(DerivationPathError::InvalidDerivationPath(_))
732        );
733
734        // test://path?full-path=
735        let mut builder = URIReferenceBuilder::new();
736        builder
737            .try_scheme(Some("test"))
738            .unwrap()
739            .try_authority(Some("path"))
740            .unwrap()
741            .try_path("")
742            .unwrap()
743            .try_query(Some("full-path="))
744            .unwrap();
745        let uri = builder.build().unwrap();
746        assert_matches!(
747            DerivationPath::from_uri(&uri, false),
748            Err(DerivationPathError::InvalidDerivationPath(_))
749        );
750
751        // test://path?full-path
752        let mut builder = URIReferenceBuilder::new();
753        builder
754            .try_scheme(Some("test"))
755            .unwrap()
756            .try_authority(Some("path"))
757            .unwrap()
758            .try_path("")
759            .unwrap()
760            .try_query(Some("full-path"))
761            .unwrap();
762        let uri = builder.build().unwrap();
763        assert_matches!(
764            DerivationPath::from_uri(&uri, false),
765            Err(DerivationPathError::InvalidDerivationPath(_))
766        );
767    }
768
769    #[test]
770    fn test_get_query() {
771        let derivation_path = DerivationPath::new_bip44_with_coin(TestCoin, None, None);
772        assert_eq!(derivation_path.get_query(), "".to_string());
773        let derivation_path = DerivationPath::new_bip44_with_coin(TestCoin, Some(1), None);
774        assert_eq!(derivation_path.get_query(), "?key=1'".to_string());
775        let derivation_path = DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2));
776        assert_eq!(derivation_path.get_query(), "?key=1'/2'".to_string());
777    }
778
779    #[test]
780    fn test_derivation_path_debug() {
781        let path = DerivationPath::default();
782        assert_eq!(format!("{path:?}"), "m/44'/501'".to_string());
783
784        let path = DerivationPath::new_bip44(Some(1), None);
785        assert_eq!(format!("{path:?}"), "m/44'/501'/1'".to_string());
786
787        let path = DerivationPath::new_bip44(Some(1), Some(2));
788        assert_eq!(format!("{path:?}"), "m/44'/501'/1'/2'".to_string());
789    }
790}