miraland_sdk/
derivation_path.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 Miraland derivation paths.
6//! The standard Miraland BIP-44 derivation path prefix is
7//!
8//! > `m/44'/2002'`
9//!
10//! with 2002 being the Miraland coin type.
11
12use {
13    core::{iter::IntoIterator, slice::Iter},
14    derivation_path::{ChildIndex, DerivationPath as DerivationPathInner},
15    std::{
16        convert::{Infallible, TryFrom},
17        fmt,
18        str::FromStr,
19    },
20    thiserror::Error,
21    uriparse::URIReference,
22};
23
24const ACCOUNT_INDEX: usize = 2;
25const CHANGE_INDEX: usize = 3;
26
27/// Derivation path error.
28#[derive(Error, Debug, Clone, PartialEq, Eq)]
29pub enum DerivationPathError {
30    #[error("invalid derivation path: {0}")]
31    InvalidDerivationPath(String),
32    #[error("infallible")]
33    Infallible,
34}
35
36impl From<Infallible> for DerivationPathError {
37    fn from(_: Infallible) -> Self {
38        Self::Infallible
39    }
40}
41
42#[derive(Clone, PartialEq, Eq)]
43pub struct DerivationPath(DerivationPathInner);
44
45impl Default for DerivationPath {
46    fn default() -> Self {
47        Self::new_bip44(None, None)
48    }
49}
50
51impl TryFrom<&str> for DerivationPath {
52    type Error = DerivationPathError;
53    fn try_from(s: &str) -> Result<Self, Self::Error> {
54        Self::from_key_str(s)
55    }
56}
57
58impl AsRef<[ChildIndex]> for DerivationPath {
59    fn as_ref(&self) -> &[ChildIndex] {
60        self.0.as_ref()
61    }
62}
63
64impl DerivationPath {
65    fn new<P: Into<Box<[ChildIndex]>>>(path: P) -> Self {
66        Self(DerivationPathInner::new(path))
67    }
68
69    pub fn from_key_str(path: &str) -> Result<Self, DerivationPathError> {
70        Self::from_key_str_with_coin(path, Miraland)
71    }
72
73    fn from_key_str_with_coin<T: Bip44>(path: &str, coin: T) -> Result<Self, DerivationPathError> {
74        let master_path = if path == "m" {
75            path.to_string()
76        } else {
77            format!("m/{path}")
78        };
79        let extend = DerivationPathInner::from_str(&master_path)
80            .map_err(|err| DerivationPathError::InvalidDerivationPath(err.to_string()))?;
81        let mut extend = extend.into_iter();
82        let account = extend.next().map(|index| index.to_u32());
83        let change = extend.next().map(|index| index.to_u32());
84        if extend.next().is_some() {
85            return Err(DerivationPathError::InvalidDerivationPath(format!(
86                "key path `{path}` too deep, only <account>/<change> supported"
87            )));
88        }
89        Ok(Self::new_bip44_with_coin(coin, account, change))
90    }
91
92    pub fn from_absolute_path_str(path: &str) -> Result<Self, DerivationPathError> {
93        let inner = DerivationPath::_from_absolute_path_insecure_str(path)?
94            .into_iter()
95            .map(|c| ChildIndex::Hardened(c.to_u32()))
96            .collect::<Vec<_>>();
97        Ok(Self(DerivationPathInner::new(inner)))
98    }
99
100    fn _from_absolute_path_insecure_str(path: &str) -> Result<Self, DerivationPathError> {
101        Ok(Self(DerivationPathInner::from_str(path).map_err(
102            |err| DerivationPathError::InvalidDerivationPath(err.to_string()),
103        )?))
104    }
105
106    pub fn new_bip44(account: Option<u32>, change: Option<u32>) -> Self {
107        Self::new_bip44_with_coin(Miraland, account, change)
108    }
109
110    fn new_bip44_with_coin<T: Bip44>(coin: T, account: Option<u32>, change: Option<u32>) -> Self {
111        let mut indexes = coin.base_indexes();
112        if let Some(account) = account {
113            indexes.push(ChildIndex::Hardened(account));
114            if let Some(change) = change {
115                indexes.push(ChildIndex::Hardened(change));
116            }
117        }
118        Self::new(indexes)
119    }
120
121    pub fn account(&self) -> Option<&ChildIndex> {
122        self.0.path().get(ACCOUNT_INDEX)
123    }
124
125    pub fn change(&self) -> Option<&ChildIndex> {
126        self.0.path().get(CHANGE_INDEX)
127    }
128
129    pub fn path(&self) -> &[ChildIndex] {
130        self.0.path()
131    }
132
133    // Assumes `key` query-string key
134    pub fn get_query(&self) -> String {
135        if let Some(account) = &self.account() {
136            if let Some(change) = &self.change() {
137                format!("?key={account}/{change}")
138            } else {
139                format!("?key={account}")
140            }
141        } else {
142            "".to_string()
143        }
144    }
145
146    pub fn from_uri_key_query(uri: &URIReference<'_>) -> Result<Option<Self>, DerivationPathError> {
147        Self::from_uri(uri, true)
148    }
149
150    pub fn from_uri_any_query(uri: &URIReference<'_>) -> Result<Option<Self>, DerivationPathError> {
151        Self::from_uri(uri, false)
152    }
153
154    fn from_uri(
155        uri: &URIReference<'_>,
156        key_only: bool,
157    ) -> Result<Option<Self>, DerivationPathError> {
158        if let Some(query) = uri.query() {
159            let query_str = query.as_str();
160            if query_str.is_empty() {
161                return Ok(None);
162            }
163            let query = qstring::QString::from(query_str);
164            if query.len() > 1 {
165                return Err(DerivationPathError::InvalidDerivationPath(
166                    "invalid query string, extra fields not supported".to_string(),
167                ));
168            }
169            let key = query.get(QueryKey::Key.as_ref());
170            if let Some(key) = key {
171                // Use from_key_str instead of TryInto here to make it more explicit that this
172                // generates a Miraland bip44 DerivationPath
173                return Self::from_key_str(key).map(Some);
174            }
175            if key_only {
176                return Err(DerivationPathError::InvalidDerivationPath(format!(
177                    "invalid query string `{query_str}`, only `key` supported",
178                )));
179            }
180            let full_path = query.get(QueryKey::FullPath.as_ref());
181            if let Some(full_path) = full_path {
182                return Self::from_absolute_path_str(full_path).map(Some);
183            }
184            Err(DerivationPathError::InvalidDerivationPath(format!(
185                "invalid query string `{query_str}`, only `key` and `full-path` supported",
186            )))
187        } else {
188            Ok(None)
189        }
190    }
191}
192
193impl fmt::Debug for DerivationPath {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        write!(f, "m")?;
196        for index in self.0.path() {
197            write!(f, "/{index}")?;
198        }
199        Ok(())
200    }
201}
202
203impl<'a> IntoIterator for &'a DerivationPath {
204    type IntoIter = Iter<'a, ChildIndex>;
205    type Item = &'a ChildIndex;
206    fn into_iter(self) -> Self::IntoIter {
207        self.0.into_iter()
208    }
209}
210
211const QUERY_KEY_FULL_PATH: &str = "full-path";
212const QUERY_KEY_KEY: &str = "key";
213
214#[derive(Clone, Debug, Error, PartialEq, Eq)]
215#[error("invalid query key `{0}`")]
216struct QueryKeyError(String);
217
218enum QueryKey {
219    FullPath,
220    Key,
221}
222
223impl FromStr for QueryKey {
224    type Err = QueryKeyError;
225    fn from_str(s: &str) -> Result<Self, Self::Err> {
226        let lowercase = s.to_ascii_lowercase();
227        match lowercase.as_str() {
228            QUERY_KEY_FULL_PATH => Ok(Self::FullPath),
229            QUERY_KEY_KEY => Ok(Self::Key),
230            _ => Err(QueryKeyError(s.to_string())),
231        }
232    }
233}
234
235impl AsRef<str> for QueryKey {
236    fn as_ref(&self) -> &str {
237        match self {
238            Self::FullPath => QUERY_KEY_FULL_PATH,
239            Self::Key => QUERY_KEY_KEY,
240        }
241    }
242}
243
244impl std::fmt::Display for QueryKey {
245    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
246        let s: &str = self.as_ref();
247        write!(f, "{s}")
248    }
249}
250
251trait Bip44 {
252    const PURPOSE: u32 = 44;
253    const COIN: u32;
254
255    fn base_indexes(&self) -> Vec<ChildIndex> {
256        vec![
257            ChildIndex::Hardened(Self::PURPOSE),
258            ChildIndex::Hardened(Self::COIN),
259        ]
260    }
261}
262
263// MI: Solarti, Qthor, Mira, Miraland
264struct Solarti;
265
266impl Bip44 for Solarti {
267    const COIN: u32 = 1985;
268}
269
270struct Qthor;
271
272impl Bip44 for Qthor {
273    const COIN: u32 = 1986;
274}
275
276struct Mira;
277
278impl Bip44 for Mira {
279    const COIN: u32 = 1988;
280}
281
282struct Miraland;
283
284impl Bip44 for Miraland {
285    const COIN: u32 = 2002;
286}
287
288#[cfg(test)]
289mod tests {
290    use {super::*, assert_matches::assert_matches, uriparse::URIReferenceBuilder};
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/2002";
338        assert_eq!(
339            DerivationPath::from_absolute_path_str(s).unwrap(),
340            DerivationPath::default()
341        );
342        let s = "m/44'/2002'";
343        assert_eq!(
344            DerivationPath::from_absolute_path_str(s).unwrap(),
345            DerivationPath::default()
346        );
347        let s = "m/44'/2002'/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'/2002'/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-Miraland 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/2002'/0'/0/0";
372        assert_eq!(
373            DerivationPath::from_absolute_path_str(s).unwrap(),
374            DerivationPath::new(vec![
375                ChildIndex::Hardened(2002),
376                ChildIndex::Hardened(0),
377                ChildIndex::Hardened(0),
378                ChildIndex::Hardened(0),
379            ])
380        );
381        let s = "m/2002'/0'/0'/0'";
382        assert_eq!(
383            DerivationPath::from_absolute_path_str(s).unwrap(),
384            DerivationPath::new(vec![
385                ChildIndex::Hardened(2002),
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'/2002'".to_string());
783
784        let path = DerivationPath::new_bip44(Some(1), None);
785        assert_eq!(format!("{path:?}"), "m/44'/2002'/1'".to_string());
786
787        let path = DerivationPath::new_bip44(Some(1), Some(2));
788        assert_eq!(format!("{path:?}"), "m/44'/2002'/1'/2'".to_string());
789    }
790}