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        $crate::HardenedIndex::from($idx as u16)
37    };
38    [$( $idx:literal ),+] => {
39        [$( $crate::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(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
261#[display(inner)]
262pub struct NormalIndex(
263    #[from(u8)]
264    #[from(u16)]
265    u32,
266);
267
268impl PartialEq<u8> for NormalIndex {
269    fn eq(&self, other: &u8) -> bool { self.0 == *other as u32 }
270}
271
272impl PartialEq<u16> for NormalIndex {
273    fn eq(&self, other: &u16) -> bool { self.0 == *other as u32 }
274}
275
276impl PartialOrd<u8> for NormalIndex {
277    fn partial_cmp(&self, other: &u8) -> Option<Ordering> { self.0.partial_cmp(&(*other as u32)) }
278}
279
280impl PartialOrd<u16> for NormalIndex {
281    fn partial_cmp(&self, other: &u16) -> Option<Ordering> { self.0.partial_cmp(&(*other as u32)) }
282}
283
284impl From<&NormalIndex> for NormalIndex {
285    fn from(index: &NormalIndex) -> Self { *index }
286}
287
288impl NormalIndex {
289    pub const fn normal(child_number: u16) -> Self { NormalIndex(child_number as u32) }
290    pub(crate) const fn normal_unchecked(child_number: u32) -> Self { NormalIndex(child_number) }
291}
292
293impl IdxBase for NormalIndex {
294    #[inline]
295    fn index(&self) -> u32 { self.child_number() }
296
297    #[inline]
299    fn child_number(&self) -> u32 { self.0 }
300
301    #[inline]
302    fn is_hardened(&self) -> bool { false }
303}
304
305impl Idx for NormalIndex {
306    const ZERO: Self = Self(0);
307
308    const ONE: Self = Self(1);
309
310    const MAX: Self = Self(HARDENED_INDEX_BOUNDARY - 1);
311
312    #[inline]
313    fn from_child_number(child_no: impl Into<u16>) -> Self { Self(child_no.into() as u32) }
314
315    #[inline]
316    fn try_from_child_number(child_no: impl Into<u32>) -> Result<Self, IndexError> {
317        let index = child_no.into();
318        if index >= HARDENED_INDEX_BOUNDARY {
319            Err(IndexError {
320                what: "child number",
321                invalid: index,
322                start: 0,
323                end: HARDENED_INDEX_BOUNDARY,
324            })
325        } else {
326            Ok(Self(index))
327        }
328    }
329
330    #[inline]
331    fn try_from_index(index: u32) -> Result<Self, IndexError> {
332        Self::try_from_child_number(index).map_err(|mut err| {
333            err.what = "index";
334            err
335        })
336    }
337
338    #[inline]
339    fn checked_add_assign(&mut self, add: impl Into<u32>) -> Option<Self> {
340        checked_add_assign(&mut self.0, add).map(|_| *self)
341    }
342
343    #[inline]
344    fn checked_sub_assign(&mut self, sub: impl Into<u32>) -> Option<Self> {
345        checked_sub_assign(&mut self.0, sub).map(|_| *self)
346    }
347}
348
349impl TryFrom<DerivationIndex> for NormalIndex {
350    type Error = IndexError;
351
352    fn try_from(idx: DerivationIndex) -> Result<Self, Self::Error> {
353        NormalIndex::try_from_index(idx.index())
354    }
355}
356
357impl FromStr for NormalIndex {
358    type Err = IndexParseError;
359
360    fn from_str(s: &str) -> Result<Self, Self::Err> {
361        Ok(NormalIndex::try_from_child_number(u32::from_str(s)?)?)
362    }
363}
364
365#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, Display, From)]
368#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
369#[display("{0}h", alt = "{0}'")]
370pub struct HardenedIndex(
371    #[from(u8)]
373    #[from(u16)]
374    pub(crate) u32,
375);
376
377impl PartialEq<u8> for HardenedIndex {
378    fn eq(&self, other: &u8) -> bool { self.0 == *other as u32 }
379}
380
381impl PartialEq<u16> for HardenedIndex {
382    fn eq(&self, other: &u16) -> bool { self.0 == *other as u32 }
383}
384
385impl PartialOrd<u8> for HardenedIndex {
386    fn partial_cmp(&self, other: &u8) -> Option<Ordering> { self.0.partial_cmp(&(*other as u32)) }
387}
388
389impl PartialOrd<u16> for HardenedIndex {
390    fn partial_cmp(&self, other: &u16) -> Option<Ordering> { self.0.partial_cmp(&(*other as u32)) }
391}
392
393impl HardenedIndex {
394    pub const fn hardened(child_number: u16) -> Self { HardenedIndex(child_number as u32) }
395}
396
397impl IdxBase for HardenedIndex {
398    #[inline]
401    fn child_number(&self) -> u32 { self.0 }
402
403    #[inline]
405    fn index(&self) -> u32 { self.0 + HARDENED_INDEX_BOUNDARY }
406
407    #[inline]
408    fn is_hardened(&self) -> bool { true }
409}
410
411impl Idx for HardenedIndex {
412    const ZERO: Self = Self(0);
413
414    const ONE: Self = Self(1);
415
416    const MAX: Self = Self(HARDENED_INDEX_BOUNDARY - 1);
417
418    #[inline]
419    fn from_child_number(child_no: impl Into<u16>) -> Self { Self(child_no.into() as u32) }
420
421    #[inline]
422    fn try_from_child_number(child_no: impl Into<u32>) -> Result<Self, IndexError> {
423        let index = child_no.into();
424        if index < HARDENED_INDEX_BOUNDARY {
425            Ok(Self(index))
426        } else {
427            Err(IndexError {
428                what: "child number",
429                invalid: index,
430                start: 0,
431                end: HARDENED_INDEX_BOUNDARY,
432            })
433        }
434    }
435
436    #[inline]
437    fn try_from_index(index: u32) -> Result<Self, IndexError> {
438        if index < HARDENED_INDEX_BOUNDARY {
439            Err(IndexError {
440                what: "index",
441                invalid: index,
442                start: HARDENED_INDEX_BOUNDARY,
443                end: u32::MAX,
444            })
445        } else {
446            Ok(Self(index - HARDENED_INDEX_BOUNDARY))
447        }
448    }
449
450    #[inline]
451    fn checked_add_assign(&mut self, add: impl Into<u32>) -> Option<Self> {
452        checked_add_assign(&mut self.0, add).map(|_| *self)
453    }
454
455    #[inline]
456    fn checked_sub_assign(&mut self, sub: impl Into<u32>) -> Option<Self> {
457        checked_sub_assign(&mut self.0, sub).map(|_| *self)
458    }
459}
460
461impl TryFrom<DerivationIndex> for HardenedIndex {
462    type Error = IndexError;
463
464    fn try_from(idx: DerivationIndex) -> Result<Self, Self::Error> {
465        HardenedIndex::try_from_index(idx.index())
466    }
467}
468
469impl FromStr for HardenedIndex {
470    type Err = IndexParseError;
471
472    fn from_str(s: &str) -> Result<Self, Self::Err> {
473        let s = s
474            .strip_suffix(['h', 'H', '\''])
475            .ok_or_else(|| IndexParseError::HardenedRequired(s.to_owned()))?;
476        Ok(HardenedIndex::try_from_child_number(u32::from_str(s)?)?)
477    }
478}
479
480#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
481#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
482#[display(inner)]
483pub enum DerivationIndex {
484    #[from]
485    Normal(NormalIndex),
486    #[from]
487    Hardened(HardenedIndex),
488}
489
490impl From<u32> for DerivationIndex {
491    fn from(value: u32) -> Self { Self::from_index(value) }
492}
493
494impl DerivationIndex {
495    pub const fn normal(child_number: u16) -> Self {
496        Self::Normal(NormalIndex::normal(child_number))
497    }
498
499    pub const fn hardened(child_number: u16) -> Self {
500        Self::Hardened(HardenedIndex::hardened(child_number))
501    }
502
503    pub const fn from_index(value: u32) -> Self {
504        match value {
505            0..=0x0FFFFFFF => DerivationIndex::Normal(NormalIndex(value)),
506            _ => DerivationIndex::Hardened(HardenedIndex(value - HARDENED_INDEX_BOUNDARY)),
507        }
508    }
509}
510
511impl IdxBase for DerivationIndex {
512    fn child_number(&self) -> u32 {
513        match self {
514            DerivationIndex::Normal(idx) => idx.child_number(),
515            DerivationIndex::Hardened(idx) => idx.child_number(),
516        }
517    }
518
519    fn index(&self) -> u32 {
520        match self {
521            DerivationIndex::Normal(idx) => idx.index(),
522            DerivationIndex::Hardened(idx) => idx.index(),
523        }
524    }
525
526    fn is_hardened(&self) -> bool {
527        match self {
528            DerivationIndex::Normal(_) => false,
529            DerivationIndex::Hardened(_) => true,
530        }
531    }
532}
533
534impl Idx for DerivationIndex {
535    const ZERO: Self = DerivationIndex::Normal(NormalIndex::ZERO);
536    const ONE: Self = DerivationIndex::Normal(NormalIndex::ONE);
537    const MAX: Self = DerivationIndex::Normal(NormalIndex::MAX);
538
539    #[doc(hidden)]
540    fn from_child_number(_no: impl Into<u16>) -> Self { panic!("method must not be used") }
541
542    #[doc(hidden)]
543    fn try_from_child_number(_index: impl Into<u32>) -> Result<Self, IndexError> {
544        panic!("method must not be used")
545    }
546
547    fn try_from_index(index: u32) -> Result<Self, IndexError> { Ok(Self::from_index(index)) }
548
549    fn checked_add_assign(&mut self, add: impl Into<u32>) -> Option<Self> {
550        match self {
551            DerivationIndex::Normal(idx) => {
552                idx.checked_add_assign(add).map(DerivationIndex::Normal)
553            }
554            DerivationIndex::Hardened(idx) => {
555                idx.checked_add_assign(add).map(DerivationIndex::Hardened)
556            }
557        }
558    }
559
560    fn checked_sub_assign(&mut self, sub: impl Into<u32>) -> Option<Self> {
561        match self {
562            DerivationIndex::Normal(idx) => {
563                idx.checked_sub_assign(sub).map(DerivationIndex::Normal)
564            }
565            DerivationIndex::Hardened(idx) => {
566                idx.checked_sub_assign(sub).map(DerivationIndex::Hardened)
567            }
568        }
569    }
570}
571
572impl FromStr for DerivationIndex {
573    type Err = IndexParseError;
574
575    fn from_str(s: &str) -> Result<Self, Self::Err> {
576        match s.strip_suffix(['h', 'H', '*']) {
577            Some(_) => HardenedIndex::from_str(s).map(Self::Hardened),
578            None => NormalIndex::from_str(s).map(Self::Normal),
579        }
580    }
581}
582
583#[cfg(test)]
584mod test {
585    use super::*;
586
587    #[test]
588    fn macro_h_index() {
589        assert_eq!(h!(1), HardenedIndex::ONE);
590    }
591
592    #[test]
593    fn macro_h_path() {
594        let path = [HardenedIndex::from(86u8), HardenedIndex::from(1u8), HardenedIndex::from(0u8)];
595        assert_eq!(h![86, 1, 0], path);
596    }
597}