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