1use {
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#[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 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 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
263struct 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}