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