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