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