crypto/keys/
bip44.rs

1// Copyright 2023 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::keys::slip10::{self, Segment};
5
6#[cfg(feature = "ed25519")]
7pub mod ed25519 {
8    use super::*;
9    use crate::signatures::ed25519;
10
11    impl slip10::ToChain<Bip44> for ed25519::SecretKey {
12        type Chain = [slip10::Hardened; 5];
13        fn to_chain(bip44_chain: &Bip44) -> [slip10::Hardened; 5] {
14            [
15                Bip44::PURPOSE.harden(),
16                bip44_chain.coin_type.harden(),
17                bip44_chain.account.harden(),
18                bip44_chain.change.harden(),
19                bip44_chain.address_index.harden(),
20            ]
21        }
22    }
23}
24
25#[cfg(feature = "secp256k1")]
26pub mod secp256k1 {
27    use super::*;
28    use crate::signatures::secp256k1_ecdsa;
29
30    impl slip10::ToChain<Bip44> for secp256k1_ecdsa::SecretKey {
31        type Chain = [u32; 5];
32        fn to_chain(bip44_chain: &Bip44) -> [u32; 5] {
33            [
34                Bip44::PURPOSE.harden().into(),
35                bip44_chain.coin_type.harden().into(),
36                bip44_chain.account.harden().into(),
37                bip44_chain.change,
38                bip44_chain.address_index,
39            ]
40        }
41    }
42}
43
44/// Type of BIP44 chains that apply hardening rules depending on the derived key type.
45///
46/// For Ed225519 secret keys the final chain is as follows (all segments are hardened):
47/// m / purpose' / coin_type' / account' / change' / address_index'
48///
49/// For Secp256k1 ECDSA secret keys the final chain is as follows (the first three segments are hardened):
50/// m / purpose' / coin_type' / account' / change / address_index
51#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53pub struct Bip44 {
54    pub coin_type: u32,
55    pub account: u32,
56    pub change: u32,
57    pub address_index: u32,
58}
59
60impl Bip44 {
61    pub const PURPOSE: u32 = 44;
62
63    pub fn new(coin_type: u32) -> Self {
64        Self {
65            coin_type,
66            account: 0,
67            change: 0,
68            address_index: 0,
69        }
70    }
71
72    pub fn with_coin_type(mut self, s: u32) -> Self {
73        self.coin_type = s;
74        self
75    }
76
77    pub fn with_account(mut self, s: u32) -> Self {
78        self.account = s;
79        self
80    }
81
82    pub fn with_change(mut self, s: u32) -> Self {
83        self.change = s;
84        self
85    }
86
87    pub fn with_address_index(mut self, s: u32) -> Self {
88        self.address_index = s;
89        self
90    }
91
92    pub fn to_chain<K: slip10::ToChain<Self>>(&self) -> <K as slip10::ToChain<Self>>::Chain {
93        K::to_chain(self)
94    }
95
96    pub fn derive<K>(&self, mk: &slip10::Slip10<K>) -> slip10::Slip10<K>
97    where
98        K: slip10::Derivable
99            + slip10::WithSegment<<<K as slip10::ToChain<Bip44>>::Chain as IntoIterator>::Item>
100            + slip10::ToChain<Bip44>,
101        <K as slip10::ToChain<Bip44>>::Chain: IntoIterator,
102        <<K as slip10::ToChain<Bip44>>::Chain as IntoIterator>::Item: Segment,
103    {
104        mk.derive(self.to_chain::<K>().into_iter())
105    }
106
107    pub fn derive_from_seed<K, S>(&self, seed: &S) -> slip10::Slip10<K>
108    where
109        K: slip10::IsSecretKey
110            + slip10::WithSegment<<<K as slip10::ToChain<Bip44>>::Chain as IntoIterator>::Item>
111            + slip10::ToChain<Bip44>,
112        <K as slip10::ToChain<Bip44>>::Chain: IntoIterator,
113        <<K as slip10::ToChain<Bip44>>::Chain as IntoIterator>::Item: Segment,
114        S: AsRef<[u8]>,
115    {
116        self.derive(&slip10::Slip10::from_seed(seed))
117    }
118
119    /// Derive a number of children keys with optimization as follows:
120    ///
121    /// mk = m / purpose* / coin_type* / account* / change*
122    /// child_i = mk / (address_index + i)*
123    /// return (child_0, .., child_{address_count - 1})
124    ///
125    /// Star (*) denotes hardening rule specific for key type `K`.
126    ///
127    /// Address space should not overflow, if `k` is the first index such that `address_index + k` overflows (31-bit),
128    /// then only the first `k` children are returned.
129    pub fn derive_address_range<K, S>(
130        &self,
131        m: &slip10::Slip10<K>,
132        address_count: usize,
133    ) -> impl ExactSizeIterator<Item = slip10::Slip10<K>>
134    where
135        K: slip10::Derivable + slip10::WithSegment<S> + slip10::ToChain<Bip44, Chain = [S; 5]>,
136        S: Segment + TryFrom<u32>,
137        <S as TryFrom<u32>>::Error: core::fmt::Debug,
138    {
139        let chain: [_; 5] = self.to_chain::<K>();
140
141        // maximum number segments is 2^31, trim usize value to fit u32
142        let address_count = core::cmp::min(1 << 31, address_count) as u32;
143
144        // BIP44 conversion rules are strict, the last element is address_index
145        let address_start = chain[4];
146        let hardening_bit: u32 = address_start.into() & slip10::HARDEN_MASK;
147        // strip hardening bit as it may interfere and overflow
148        let unhardened_start: u32 = address_start.unharden().into();
149        // this is guaranteed to not overflow and be <= 2^31
150        let unhardened_end: u32 = core::cmp::min(1_u32 << 31, unhardened_start + address_count);
151
152        // this is the final range guaranteed to not overflow address_index space
153        let child_segments = (unhardened_start..unhardened_end).map(move |unhardened_address_index| -> S {
154            let address_index = hardening_bit | unhardened_address_index;
155            // SAFETY: address_index is guaranteed to have the correct hardening as the target type `S`, so unwrap()
156            // can't fail
157            address_index.try_into().unwrap()
158        });
159
160        let mk = if child_segments.len() > 0 {
161            m.derive(chain[..4].iter().copied())
162        } else {
163            // no need to derive mk if there's no child_segments, just use empty/zero one
164            slip10::Slip10::new()
165        };
166        mk.children(child_segments)
167    }
168}
169
170#[derive(Clone, Copy, Debug, Eq, PartialEq)]
171#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
172pub struct BadPurpose;
173
174impl From<BadPurpose> for crate::Error {
175    fn from(inner: BadPurpose) -> Self {
176        crate::Error::Bip44Error(inner)
177    }
178}
179
180impl TryFrom<[u32; 5]> for Bip44 {
181    type Error = BadPurpose;
182    fn try_from(segments: [u32; 5]) -> Result<Self, Self::Error> {
183        if let [Bip44::PURPOSE, coin_type, account, change, address_index] = segments {
184            Ok(Self {
185                coin_type,
186                account,
187                change,
188                address_index,
189            })
190        } else {
191            Err(BadPurpose)
192        }
193    }
194}
195
196impl From<[u32; 4]> for Bip44 {
197    fn from(segments: [u32; 4]) -> Self {
198        let [coin_type, account, change, address_index] = segments;
199        Self {
200            coin_type,
201            account,
202            change,
203            address_index,
204        }
205    }
206}
207
208impl From<&Bip44> for [u32; 5] {
209    fn from(bip44_chain: &Bip44) -> [u32; 5] {
210        [
211            Bip44::PURPOSE,
212            bip44_chain.coin_type,
213            bip44_chain.account,
214            bip44_chain.change,
215            bip44_chain.address_index,
216        ]
217    }
218}
219
220impl IntoIterator for Bip44 {
221    type Item = u32;
222    type IntoIter = core::array::IntoIter<u32, 5>;
223    fn into_iter(self) -> Self::IntoIter {
224        <[u32; 5]>::from(&self).into_iter()
225    }
226}