derive/
index.rs

1// Modern, minimalistic & standard-compliant cold wallet library.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2020-2024 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2020-2024 LNP/BP Standards Association. All rights reserved.
9// Copyright (C) 2020-2024 Dr Maxim Orlovsky. All rights reserved.
10//
11// Licensed under the Apache License, Version 2.0 (the "License");
12// you may not use this file except in compliance with the License.
13// You may obtain a copy of the License at
14//
15//     http://www.apache.org/licenses/LICENSE-2.0
16//
17// Unless required by applicable law or agreed to in writing, software
18// distributed under the License is distributed on an "AS IS" BASIS,
19// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20// See the License for the specific language governing permissions and
21// limitations under the License.
22
23use std::cmp::Ordering;
24use std::hash::Hash;
25use std::num::ParseIntError;
26use std::ops::Range;
27use std::str::FromStr;
28
29/// Constant determining BIP32 boundary for u32 values after which index
30/// is treated as hardened
31pub 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    /// invalid index string representation - {0}
61    Parse(ParseIntError),
62
63    /// expected hardened index value instead of the provided unhardened {0}
64    HardenedRequired(String),
65}
66
67/// Trait defining basic index functionality without mathematics operations.
68pub trait IdxBase: Sized + Eq + Ord + Copy {
69    /// Detects whether path segment uses hardened index(es)
70    fn is_hardened(&self) -> bool;
71
72    /// Returns child number corresponding to this index.
73    ///
74    /// Child number is always a value in range of `0..`[`HARDENED_INDEX_BOUNDARY`]
75    fn child_number(&self) -> u32;
76
77    /// Returns value used during derivation, which for normal indexes must lie
78    /// in range `0..`[`HARDENED_INDEX_BOUNDARY`] and for hardened in range
79    /// of [`HARDENED_INDEX_BOUNDARY`]`..=u32::MAX`
80    fn index(&self) -> u32;
81}
82
83/// Trait defining common API for different types of indexes which may be
84/// present in a certain derivation path segment: hardened, unhardened, mixed.
85pub trait Idx: IdxBase {
86    /// Derivation path segment with index equal to minimal value.
87    const MIN: Self = Self::ZERO;
88
89    /// Derivation path segment with index equal to zero.
90    const ZERO: Self;
91
92    /// Derivation path segment with index equal to one.
93    const ONE: Self;
94
95    /// Derivation path segment with index equal to maximum value.
96    const MAX: Self;
97
98    /// Range covering all possible index values.
99    const RANGE: Range<Self> = Range {
100        start: Self::MIN,
101        end: Self::MAX,
102    };
103
104    /// Constructs index from a given child number.
105    ///
106    /// Child number is always a value in range of `0..`[`HARDENED_INDEX_BOUNDARY`]
107    fn from_child_number(child_no: impl Into<u16>) -> Self;
108
109    /// Constructs index from a given child number.
110    ///
111    /// Child number is always a value in range of `0..`[`HARDENED_INDEX_BOUNDARY`]
112    fn try_from_child_number(child_no: impl Into<u32>) -> Result<Self, IndexError>;
113
114    /// Constructs derivation path segment with specific derivation value, which
115    /// for normal indexes must lie in range `0..`[`HARDENED_INDEX_BOUNDARY`]
116    /// and for hardened in range of [`HARDENED_INDEX_BOUNDARY`]`..=u32::MAX`
117    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    /// Increments the index on one step; fails if the index value is already
122    /// maximum value.
123    #[must_use]
124    fn checked_inc(&self) -> Option<Self> { self.checked_add(1u8) }
125
126    /// Decrements the index on one step; fails if the index value is already
127    /// minimum value.
128    #[must_use]
129    fn checked_dec(&self) -> Option<Self> { self.checked_sub(1u8) }
130
131    /// Increments the index on one step saturating at the `Self::MAX` bounds
132    /// instead of overflowing.
133    #[must_use]
134    fn saturating_inc(&self) -> Self { self.saturating_add(1u8) }
135
136    /// Decrements the index on one step saturating at the `Self::MIN` bounds
137    /// instead of overflowing.
138    #[must_use]
139    fn saturating_dec(&self) -> Self { self.saturating_sub(1u8) }
140
141    /// Increments the index on one step; fails if the index value is already
142    /// maximum value.
143    #[must_use]
144    fn wrapping_inc(&self) -> Self { self.checked_add(1u8).unwrap_or(Self::MIN) }
145
146    /// Decrements the index on one step; fails if the index value is already
147    /// minimum value.
148    #[must_use]
149    fn wrapping_dec(&self) -> Self { self.checked_sub(1u8).unwrap_or(Self::MAX) }
150
151    /// Mutates the self by incrementing the index on one step; fails if the index
152    /// value is already maximum value.
153    fn checked_inc_assign(&mut self) -> Option<Self> { self.checked_add_assign(1u8) }
154
155    /// Mutates the self by decrementing the index on one step; fails if the index
156    /// value is already maximum value.
157    fn checked_dec_assign(&mut self) -> Option<Self> { self.checked_sub_assign(1u8) }
158
159    /// Mutates the self by incrementing the index on one step, saturating at the
160    /// `Self::MAX` bounds instead of overflowing.
161    fn saturating_inc_assign(&mut self) -> bool { self.saturating_add_assign(1u8) }
162
163    /// Mutates the self by decrementing the index on one step, saturating at the
164    /// `Self::MIN` bounds instead of overflowing.
165    fn saturating_dec_assign(&mut self) -> bool { self.saturating_sub_assign(1u8) }
166
167    /// Mutates the self by incrementing the index on one step; fails if the index
168    /// value is already maximum value.
169    fn wrapping_inc_assign(&mut self) { *self = self.wrapping_inc(); }
170
171    /// Mutates the self by decrementing the index on one step; fails if the index
172    /// value is already maximum value.
173    fn wrapping_dec_assign(&mut self) { *self = self.wrapping_inc(); }
174
175    /// Adds value the index; fails if the index value overflow happens.
176    #[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    /// Subtracts value the index; fails if the index value overflow happens.
184    #[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    /// Saturating index addition. Computes `self + add`, saturating at the
192    /// `Self::MAX` bounds instead of overflowing.
193    #[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    /// Saturating index subtraction. Computes `self - add`, saturating at
201    /// the `Self::MIN` bounds instead of overflowing.
202    #[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    /// Mutates the self by adding value the index; fails if the index value
210    /// overflow happens.
211    fn checked_add_assign(&mut self, add: impl Into<u32>) -> Option<Self>;
212
213    /// Mutates the self by subtracting value the index; fails if the index
214    /// value overflow happens.
215    fn checked_sub_assign(&mut self, sub: impl Into<u32>) -> Option<Self>;
216
217    /// Mutates the self by adding value the index saturating it at the
218    /// `Self::MAX` value in case of overflow. Returns boolean value
219    /// indicating if no overflow had happened.
220    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    /// Mutates the self by subtracting value from the index saturating
230    /// it at the `Self::MIN` value in case of overflow. Returns boolean value
231    /// indicating if no overflow had happened.
232    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/// Index for unhardened children derivation; ensures that the inner value
258/// is always < 2^31
259#[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    /// Returns unhardened index number.
302    #[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/// Index for hardened children derivation; ensures that the index always >=
370/// 2^31.
371#[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    /// The inner child number value; always reduced by [`HARDENED_INDEX_BOUNDARY`]
380    #[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    /// Returns hardened index number not offset by [`HARDENED_INDEX_BOUNDARY`]
407    /// (i.e. zero-based).
408    #[inline]
409    fn child_number(&self) -> u32 { self.0 }
410
411    /// Returns hardened index number offset by [`HARDENED_INDEX_BOUNDARY`].
412    #[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}