1use std::cmp::Ordering;
24use std::hash::Hash;
25use std::num::ParseIntError;
26use std::ops::Range;
27use std::str::FromStr;
28
29pub const HARDENED_INDEX_BOUNDARY: u32 = 1 << 31;
32
33#[macro_export]
34macro_rules! h {
35 ($idx:literal) => {
36 HardenedIndex::from($idx as u16)
37 };
38 [$( $idx:literal ),+] => {
39 [$( HardenedIndex::from($idx as u16) ),+]
40 };
41}
42
43#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)]
44#[display("provided {what} {invalid} is invalid: it lies outside allowed range {start}..={end}")]
45pub struct IndexError {
46 pub what: &'static str,
47 pub invalid: u32,
48 pub start: u32,
49 pub end: u32,
50}
51
52#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
53#[display(doc_comments)]
54pub enum IndexParseError {
55 #[from]
56 #[display(inner)]
57 Invalid(IndexError),
58
59 #[from]
60 Parse(ParseIntError),
62
63 HardenedRequired(String),
65}
66
67pub trait IdxBase: Sized + Eq + Ord + Copy {
69 fn is_hardened(&self) -> bool;
71
72 fn child_number(&self) -> u32;
76
77 fn index(&self) -> u32;
81}
82
83pub trait Idx: IdxBase {
86 const MIN: Self = Self::ZERO;
88
89 const ZERO: Self;
91
92 const ONE: Self;
94
95 const MAX: Self;
97
98 const RANGE: Range<Self> = Range {
100 start: Self::MIN,
101 end: Self::MAX,
102 };
103
104 fn from_child_number(child_no: impl Into<u16>) -> Self;
108
109 fn try_from_child_number(child_no: impl Into<u32>) -> Result<Self, IndexError>;
113
114 fn try_from_index(index: u32) -> Result<Self, IndexError>;
118
119 fn to_be_bytes(&self) -> [u8; 4] { self.index().to_be_bytes() }
120
121 #[must_use]
124 fn checked_inc(&self) -> Option<Self> { self.checked_add(1u8) }
125
126 #[must_use]
129 fn checked_dec(&self) -> Option<Self> { self.checked_sub(1u8) }
130
131 #[must_use]
134 fn saturating_inc(&self) -> Self { self.saturating_add(1u8) }
135
136 #[must_use]
139 fn saturating_dec(&self) -> Self { self.saturating_sub(1u8) }
140
141 #[must_use]
144 fn wrapping_inc(&self) -> Self { self.checked_add(1u8).unwrap_or(Self::MIN) }
145
146 #[must_use]
149 fn wrapping_dec(&self) -> Self { self.checked_sub(1u8).unwrap_or(Self::MAX) }
150
151 fn checked_inc_assign(&mut self) -> Option<Self> { self.checked_add_assign(1u8) }
154
155 fn checked_dec_assign(&mut self) -> Option<Self> { self.checked_sub_assign(1u8) }
158
159 fn saturating_inc_assign(&mut self) -> bool { self.saturating_add_assign(1u8) }
162
163 fn saturating_dec_assign(&mut self) -> bool { self.saturating_sub_assign(1u8) }
166
167 fn wrapping_inc_assign(&mut self) { *self = self.wrapping_inc(); }
170
171 fn wrapping_dec_assign(&mut self) { *self = self.wrapping_inc(); }
174
175 #[must_use]
177 fn checked_add(&self, add: impl Into<u32>) -> Option<Self> {
178 let mut res = *self;
179 res.checked_add_assign(add)?;
180 Some(res)
181 }
182
183 #[must_use]
185 fn checked_sub(&self, sub: impl Into<u32>) -> Option<Self> {
186 let mut res = *self;
187 res.checked_sub_assign(sub)?;
188 Some(res)
189 }
190
191 #[must_use]
194 fn saturating_add(&self, add: impl Into<u32>) -> Self {
195 let mut res = *self;
196 let _ = res.saturating_add_assign(add);
197 res
198 }
199
200 #[must_use]
203 fn saturating_sub(&self, sub: impl Into<u32>) -> Self {
204 let mut res = *self;
205 let _ = res.saturating_sub_assign(sub);
206 res
207 }
208
209 fn checked_add_assign(&mut self, add: impl Into<u32>) -> Option<Self>;
212
213 fn checked_sub_assign(&mut self, sub: impl Into<u32>) -> Option<Self>;
216
217 fn saturating_add_assign(&mut self, add: impl Into<u32>) -> bool {
221 if self.checked_add_assign(add).is_none() {
222 *self = Self::MAX;
223 false
224 } else {
225 true
226 }
227 }
228
229 fn saturating_sub_assign(&mut self, sub: impl Into<u32>) -> bool {
233 if self.checked_sub_assign(sub).is_none() {
234 *self = Self::MIN;
235 false
236 } else {
237 true
238 }
239 }
240}
241
242fn checked_add_assign(index: &mut u32, add: impl Into<u32>) -> Option<u32> {
243 let add: u32 = add.into();
244 *index = index.checked_add(add)?;
245 if *index >= HARDENED_INDEX_BOUNDARY {
246 return None;
247 }
248 Some(*index)
249}
250
251fn checked_sub_assign(index: &mut u32, sub: impl Into<u32>) -> Option<u32> {
252 let sub: u32 = sub.into();
253 *index = index.checked_sub(sub)?;
254 Some(*index)
255}
256
257#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Debug, Hash, Default, Display, From)]
260#[cfg_attr(
261 feature = "serde",
262 derive(Serialize, Deserialize),
263 serde(crate = "serde_crate", transparent)
264)]
265#[display(inner)]
266pub struct NormalIndex(
267 #[from(u8)]
268 #[from(u16)]
269 u32,
270);
271
272impl PartialEq<u8> for NormalIndex {
273 fn eq(&self, other: &u8) -> bool { self.0 == *other as u32 }
274}
275
276impl PartialEq<u16> for NormalIndex {
277 fn eq(&self, other: &u16) -> bool { self.0 == *other as u32 }
278}
279
280impl PartialOrd<u8> for NormalIndex {
281 fn partial_cmp(&self, other: &u8) -> Option<Ordering> { self.0.partial_cmp(&(*other as u32)) }
282}
283
284impl PartialOrd<u16> for NormalIndex {
285 fn partial_cmp(&self, other: &u16) -> Option<Ordering> { self.0.partial_cmp(&(*other as u32)) }
286}
287
288impl From<&NormalIndex> for NormalIndex {
289 fn from(index: &NormalIndex) -> Self { *index }
290}
291
292impl NormalIndex {
293 pub const fn normal(child_number: u16) -> Self { NormalIndex(child_number as u32) }
294 pub(crate) const fn normal_unchecked(child_number: u32) -> Self { NormalIndex(child_number) }
295}
296
297impl IdxBase for NormalIndex {
298 #[inline]
299 fn index(&self) -> u32 { self.child_number() }
300
301 #[inline]
303 fn child_number(&self) -> u32 { self.0 }
304
305 #[inline]
306 fn is_hardened(&self) -> bool { false }
307}
308
309impl Idx for NormalIndex {
310 const ZERO: Self = Self(0);
311
312 const ONE: Self = Self(1);
313
314 const MAX: Self = Self(HARDENED_INDEX_BOUNDARY - 1);
315
316 #[inline]
317 fn from_child_number(child_no: impl Into<u16>) -> Self { Self(child_no.into() as u32) }
318
319 #[inline]
320 fn try_from_child_number(child_no: impl Into<u32>) -> Result<Self, IndexError> {
321 let index = child_no.into();
322 if index >= HARDENED_INDEX_BOUNDARY {
323 Err(IndexError {
324 what: "child number",
325 invalid: index,
326 start: 0,
327 end: HARDENED_INDEX_BOUNDARY,
328 })
329 } else {
330 Ok(Self(index))
331 }
332 }
333
334 #[inline]
335 fn try_from_index(index: u32) -> Result<Self, IndexError> {
336 Self::try_from_child_number(index).map_err(|mut err| {
337 err.what = "index";
338 err
339 })
340 }
341
342 #[inline]
343 fn checked_add_assign(&mut self, add: impl Into<u32>) -> Option<Self> {
344 checked_add_assign(&mut self.0, add).map(|_| *self)
345 }
346
347 #[inline]
348 fn checked_sub_assign(&mut self, sub: impl Into<u32>) -> Option<Self> {
349 checked_sub_assign(&mut self.0, sub).map(|_| *self)
350 }
351}
352
353impl TryFrom<DerivationIndex> for NormalIndex {
354 type Error = IndexError;
355
356 fn try_from(idx: DerivationIndex) -> Result<Self, Self::Error> {
357 NormalIndex::try_from_index(idx.index())
358 }
359}
360
361impl FromStr for NormalIndex {
362 type Err = IndexParseError;
363
364 fn from_str(s: &str) -> Result<Self, Self::Err> {
365 Ok(NormalIndex::try_from_child_number(u32::from_str(s)?)?)
366 }
367}
368
369#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, Display, From)]
372#[cfg_attr(
373 feature = "serde",
374 derive(Serialize, Deserialize),
375 serde(crate = "serde_crate", rename_all = "camelCase")
376)]
377#[display("{0}h", alt = "{0}'")]
378pub struct HardenedIndex(
379 #[from(u8)]
381 #[from(u16)]
382 pub(crate) u32,
383);
384
385impl PartialEq<u8> for HardenedIndex {
386 fn eq(&self, other: &u8) -> bool { self.0 == *other as u32 }
387}
388
389impl PartialEq<u16> for HardenedIndex {
390 fn eq(&self, other: &u16) -> bool { self.0 == *other as u32 }
391}
392
393impl PartialOrd<u8> for HardenedIndex {
394 fn partial_cmp(&self, other: &u8) -> Option<Ordering> { self.0.partial_cmp(&(*other as u32)) }
395}
396
397impl PartialOrd<u16> for HardenedIndex {
398 fn partial_cmp(&self, other: &u16) -> Option<Ordering> { self.0.partial_cmp(&(*other as u32)) }
399}
400
401impl HardenedIndex {
402 pub const fn hardened(child_number: u16) -> Self { HardenedIndex(child_number as u32) }
403}
404
405impl IdxBase for HardenedIndex {
406 #[inline]
409 fn child_number(&self) -> u32 { self.0 }
410
411 #[inline]
413 fn index(&self) -> u32 { self.0 + HARDENED_INDEX_BOUNDARY }
414
415 #[inline]
416 fn is_hardened(&self) -> bool { true }
417}
418
419impl Idx for HardenedIndex {
420 const ZERO: Self = Self(0);
421
422 const ONE: Self = Self(1);
423
424 const MAX: Self = Self(HARDENED_INDEX_BOUNDARY - 1);
425
426 #[inline]
427 fn from_child_number(child_no: impl Into<u16>) -> Self { Self(child_no.into() as u32) }
428
429 #[inline]
430 fn try_from_child_number(child_no: impl Into<u32>) -> Result<Self, IndexError> {
431 let index = child_no.into();
432 if index < HARDENED_INDEX_BOUNDARY {
433 Ok(Self(index))
434 } else {
435 Err(IndexError {
436 what: "child number",
437 invalid: index,
438 start: 0,
439 end: HARDENED_INDEX_BOUNDARY,
440 })
441 }
442 }
443
444 #[inline]
445 fn try_from_index(index: u32) -> Result<Self, IndexError> {
446 if index < HARDENED_INDEX_BOUNDARY {
447 Err(IndexError {
448 what: "index",
449 invalid: index,
450 start: HARDENED_INDEX_BOUNDARY,
451 end: u32::MAX,
452 })
453 } else {
454 Ok(Self(index - HARDENED_INDEX_BOUNDARY))
455 }
456 }
457
458 #[inline]
459 fn checked_add_assign(&mut self, add: impl Into<u32>) -> Option<Self> {
460 checked_add_assign(&mut self.0, add).map(|_| *self)
461 }
462
463 #[inline]
464 fn checked_sub_assign(&mut self, sub: impl Into<u32>) -> Option<Self> {
465 checked_sub_assign(&mut self.0, sub).map(|_| *self)
466 }
467}
468
469impl TryFrom<DerivationIndex> for HardenedIndex {
470 type Error = IndexError;
471
472 fn try_from(idx: DerivationIndex) -> Result<Self, Self::Error> {
473 HardenedIndex::try_from_index(idx.index())
474 }
475}
476
477impl FromStr for HardenedIndex {
478 type Err = IndexParseError;
479
480 fn from_str(s: &str) -> Result<Self, Self::Err> {
481 let s = s
482 .strip_suffix(['h', 'H', '\''])
483 .ok_or_else(|| IndexParseError::HardenedRequired(s.to_owned()))?;
484 Ok(HardenedIndex::try_from_child_number(u32::from_str(s)?)?)
485 }
486}
487
488#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
489#[cfg_attr(
490 feature = "serde",
491 derive(Serialize, Deserialize),
492 serde(crate = "serde_crate", rename_all = "camelCase")
493)]
494#[display(inner)]
495pub enum DerivationIndex {
496 #[from]
497 Normal(NormalIndex),
498 #[from]
499 Hardened(HardenedIndex),
500}
501
502impl From<u32> for DerivationIndex {
503 fn from(value: u32) -> Self { Self::from_index(value) }
504}
505
506impl DerivationIndex {
507 pub const fn normal(child_number: u16) -> Self {
508 Self::Normal(NormalIndex::normal(child_number))
509 }
510
511 pub const fn hardened(child_number: u16) -> Self {
512 Self::Hardened(HardenedIndex::hardened(child_number))
513 }
514
515 pub const fn from_index(value: u32) -> Self {
516 match value {
517 0..=0x0FFFFFFF => DerivationIndex::Normal(NormalIndex(value)),
518 _ => DerivationIndex::Hardened(HardenedIndex(value - HARDENED_INDEX_BOUNDARY)),
519 }
520 }
521}
522
523impl IdxBase for DerivationIndex {
524 fn child_number(&self) -> u32 {
525 match self {
526 DerivationIndex::Normal(idx) => idx.child_number(),
527 DerivationIndex::Hardened(idx) => idx.child_number(),
528 }
529 }
530
531 fn index(&self) -> u32 {
532 match self {
533 DerivationIndex::Normal(idx) => idx.index(),
534 DerivationIndex::Hardened(idx) => idx.index(),
535 }
536 }
537
538 fn is_hardened(&self) -> bool {
539 match self {
540 DerivationIndex::Normal(_) => false,
541 DerivationIndex::Hardened(_) => true,
542 }
543 }
544}
545
546impl Idx for DerivationIndex {
547 const ZERO: Self = DerivationIndex::Normal(NormalIndex::ZERO);
548 const ONE: Self = DerivationIndex::Normal(NormalIndex::ONE);
549 const MAX: Self = DerivationIndex::Normal(NormalIndex::MAX);
550
551 #[doc(hidden)]
552 fn from_child_number(_no: impl Into<u16>) -> Self { panic!("method must not be used") }
553
554 #[doc(hidden)]
555 fn try_from_child_number(_index: impl Into<u32>) -> Result<Self, IndexError> {
556 panic!("method must not be used")
557 }
558
559 fn try_from_index(index: u32) -> Result<Self, IndexError> { Ok(Self::from_index(index)) }
560
561 fn checked_add_assign(&mut self, add: impl Into<u32>) -> Option<Self> {
562 match self {
563 DerivationIndex::Normal(idx) => {
564 idx.checked_add_assign(add).map(DerivationIndex::Normal)
565 }
566 DerivationIndex::Hardened(idx) => {
567 idx.checked_add_assign(add).map(DerivationIndex::Hardened)
568 }
569 }
570 }
571
572 fn checked_sub_assign(&mut self, sub: impl Into<u32>) -> Option<Self> {
573 match self {
574 DerivationIndex::Normal(idx) => {
575 idx.checked_sub_assign(sub).map(DerivationIndex::Normal)
576 }
577 DerivationIndex::Hardened(idx) => {
578 idx.checked_sub_assign(sub).map(DerivationIndex::Hardened)
579 }
580 }
581 }
582}
583
584impl FromStr for DerivationIndex {
585 type Err = IndexParseError;
586
587 fn from_str(s: &str) -> Result<Self, Self::Err> {
588 match s.strip_suffix(['h', 'H', '*']) {
589 Some(_) => HardenedIndex::from_str(s).map(Self::Hardened),
590 None => NormalIndex::from_str(s).map(Self::Normal),
591 }
592 }
593}
594
595#[cfg(test)]
596mod test {
597 use super::*;
598
599 #[test]
600 fn macro_h_index() {
601 assert_eq!(h!(1), HardenedIndex::ONE);
602 }
603
604 #[test]
605 fn macro_h_path() {
606 let path = [HardenedIndex::from(86u8), HardenedIndex::from(1u8), HardenedIndex::from(0u8)];
607 assert_eq!(h![86, 1, 0], path);
608 }
609}