iop_keyvault/
bip32.rs

1//! Generic data structures and algorithms for [BIP-0032](
2//! https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) and
3//! [SLIP-0010](https://github.com/satoshilabs/slips/blob/master/slip-0010.md) compatible
4//! child-key derivation for building hierarchical deterministic wallets.
5
6use std::str::FromStr;
7
8use super::*;
9
10/// Entry point to generate extended private keys in a hierarchical deterministic wallet starting from a seed based
11/// on the BIP-0032 standard (and the SLIP-0010 for crypto suites other than Secp256k1).
12#[derive(Clone, Debug)]
13pub struct Bip32;
14
15impl Bip32 {
16    /// Calculates the master extended private key based on the crypto suite used by a given subtree.
17    pub fn master<C: KeyDerivationCrypto>(
18        &self, seed: &Seed, subtree: &'static dyn Subtree<Suite = C>,
19    ) -> Bip32Node<C> {
20        let path = Default::default();
21        let xsk = subtree.master(seed);
22        Bip32Node { path, xsk, subtree }
23    }
24}
25
26#[derive(Clone)]
27/// In BIP-0032 each extended private key has the same operations, independently from the actual path. This struct represents such an extended private
28/// key in a given subtree.
29pub struct Bip32Node<C: KeyDerivationCrypto + 'static> {
30    path: bip32::Path,
31    xsk: <C as KeyDerivationCrypto>::ExtendedPrivateKey,
32    subtree: &'static dyn Subtree<Suite = C>,
33}
34
35impl<C: KeyDerivationCrypto + 'static> fmt::Debug for Bip32Node<C> {
36    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
37        formatter
38            .debug_struct("Bip32Node")
39            .field("path", &self.path)
40            .field("xsk", &"...")
41            .field("subtree", &self.subtree)
42            .finish()
43    }
44}
45
46impl<C: KeyDerivationCrypto + 'static> Bip32Node<C> {
47    /// Backdoor for advanced or fringe non-standard use-cases. Use [`Bip32.master`] instead for normal use-cases.
48    pub fn new(
49        path: bip32::Path, xsk: <C as KeyDerivationCrypto>::ExtendedPrivateKey,
50        subtree: &'static dyn Subtree<Suite = C>,
51    ) -> Self {
52        Self { path, xsk, subtree }
53    }
54
55    /// Accessor for the BIP32 path of this node
56    pub fn path(&self) -> &bip32::Path {
57        &self.path
58    }
59
60    /// Accessor for the extended private key of this node
61    pub fn xsk(&self) -> &<C as KeyDerivationCrypto>::ExtendedPrivateKey {
62        &self.xsk
63    }
64
65    /// Accessor for the subtree of this node
66    pub fn subtree(&self) -> &'static dyn Subtree<Suite = C> {
67        self.subtree
68    }
69
70    /// Removes the ability to sign and derive hardened keys. The public node it returns is still able to provide
71    /// normal derivation and signature verifications.
72    pub fn neuter(&self) -> Bip32PublicNode<C> {
73        let xpk = self.xsk.neuter();
74        Bip32PublicNode::new(self.path.clone(), xpk, self.subtree)
75    }
76
77    /// Create a new node with normal (public) derivation with the given index.
78    pub fn derive_normal(&self, idx: i32) -> Result<Bip32Node<C>> {
79        let path = self.path.append(ChildIndex::Normal(idx));
80        let xsk = self.xsk.derive_normal_child(idx)?;
81        let subtree = self.subtree;
82        Ok(Self { path, xsk, subtree })
83    }
84
85    /// Create a new node with hardened (private) derivation with the given index.
86    pub fn derive_hardened(&self, idx: i32) -> Result<Bip32Node<C>> {
87        let path = self.path.append(ChildIndex::Hardened(idx));
88        let xsk = self.xsk.derive_hardened_child(idx)?;
89        let subtree = self.subtree;
90        Ok(Self { path, xsk, subtree })
91    }
92
93    /// Creates the private key that belongs to this node for authenticating actions.
94    pub fn private_key(&self) -> C::PrivateKey {
95        self.xsk.private_key()
96    }
97}
98
99#[derive(Clone)]
100/// In BIP-0032 a neutered extended private key is an extended public key. This struct represents
101/// such an extended public key in a given subtree. It is able to do normal (public) derivation,
102/// signature verification, creating and validating key identifiers
103pub struct Bip32PublicNode<C: KeyDerivationCrypto + 'static> {
104    path: bip32::Path,
105    xpk: <C as KeyDerivationCrypto>::ExtendedPublicKey,
106    subtree: &'static dyn Subtree<Suite = C>,
107}
108
109impl<C: KeyDerivationCrypto + 'static> Bip32PublicNode<C> {
110    /// Backdoor for advanced or fringe non-standard use-cases. Use [`Bip32.master`] instead for normal use-cases.
111    pub fn new(
112        path: bip32::Path, xpk: <C as KeyDerivationCrypto>::ExtendedPublicKey,
113        subtree: &'static dyn Subtree<Suite = C>,
114    ) -> Self {
115        Self { path, xpk, subtree }
116    }
117
118    /// Accessor for the BIP32 path of this node
119    pub fn path(&self) -> &bip32::Path {
120        &self.path
121    }
122
123    /// Accessor for the extended public key of this node
124    pub fn xpk(&self) -> &<C as KeyDerivationCrypto>::ExtendedPublicKey {
125        &self.xpk
126    }
127
128    /// Accessor for the subtree of this node
129    pub fn subtree(&self) -> &'static dyn Subtree<Suite = C> {
130        self.subtree
131    }
132
133    /// Create a new node with normal (public) derivation with the given index.
134    pub fn derive_normal(&self, idx: i32) -> Result<Bip32PublicNode<C>> {
135        let path = self.path.append(ChildIndex::Normal(idx));
136        let xpk = self.xpk.derive_normal_child(idx)?;
137        let subtree = self.subtree;
138        Ok(Self { path, xpk, subtree })
139    }
140
141    /// Creates the public key that belongs to this node for verifying authentications done by the corresponding private key.
142    pub fn public_key(&self) -> C::PublicKey {
143        self.xpk.public_key()
144    }
145
146    /// Creates the key identifier for the public key. This is an extra layer of security for single-use keys, so the
147    /// revealing of the public key can be delayed to the point when the authenticated action (spending some coin or
148    /// revoking access) makes the public key irrelevant after the action is successful.
149    pub fn key_id(&self) -> C::KeyId {
150        let pk = self.public_key();
151        self.subtree.key_id(&pk)
152    }
153}
154
155impl<C: KeyDerivationCrypto + 'static> fmt::Debug for Bip32PublicNode<C> {
156    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
157        formatter
158            .debug_struct("Bip32PublicNode")
159            .field("path", &self.path)
160            .field("xpk", &"...")
161            .field("subtree", &self.subtree)
162            .finish()
163    }
164}
165
166/// An item in the [BIP-0032](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
167/// derivation [path](struct.Path.html). A combination of a 31-bit unsigned integer and a flag, which derivation
168/// method (normal or hardened) to use.
169#[derive(Clone, Debug, Eq, PartialEq)]
170pub enum ChildIndex {
171    /// Normal (aka. public) derivation allows deriving a child extended public key
172    /// based on a parent extended public key.
173    Normal(i32),
174    /// Hardened (aka. private) derivation only allows deriving a child extended private key
175    /// based on a parent extended private key, but having only an extended public key does
176    /// not help deriving hardened children of any kind.
177    Hardened(i32),
178}
179
180fn is_hardened_suffix_char(c: char) -> bool {
181    ['\'', 'h', 'H'].contains(&c)
182}
183
184impl FromStr for ChildIndex {
185    type Err = anyhow::Error;
186    fn from_str(mut src: &str) -> Result<Self> {
187        let hardened = src.ends_with(is_hardened_suffix_char);
188        if hardened {
189            src = &src[..src.len() - 1];
190        };
191        let idx = src.parse::<i32>()?;
192        if idx < 0 {
193            bail!("BIP32 derivation index cannot be negative");
194        }
195        Ok(if hardened { ChildIndex::Hardened(idx) } else { ChildIndex::Normal(idx) })
196    }
197}
198
199impl fmt::Display for ChildIndex {
200    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), fmt::Error> {
201        match self {
202            ChildIndex::Normal(idx) => formatter.write_fmt(format_args!("{}", idx)),
203            ChildIndex::Hardened(idx) => formatter.write_fmt(format_args!("{}'", idx)),
204        }
205    }
206}
207
208/// An absolute [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) derivation
209/// path that starts from the master keypair. This is useful to create a [hierarchical deterministic
210/// tree](https://bitcoin.org/en/developer-guide#hierarchical-deterministic-key-creation) of keypairs
211/// for [any cryptography](https://github.com/satoshilabs/slips/blob/master/slip-0010.md) that supports
212/// child key derivation.
213#[derive(Clone, Debug, Default, Eq, PartialEq)]
214pub struct Path {
215    path: Vec<ChildIndex>,
216}
217
218impl Path {
219    /// Creates a new path that has a new derivation index appended to the current path
220    pub fn append(&self, child: ChildIndex) -> Self {
221        let mut path = self.path.clone();
222        path.push(child);
223        Self { path }
224    }
225}
226
227impl FromStr for Path {
228    type Err = anyhow::Error;
229    fn from_str(src: &str) -> Result<Self> {
230        let mut pieces = src.split('/');
231
232        let first_opt = pieces.next();
233        if let Some(first) = first_opt {
234            if first != "m" && first != "M" {
235                bail!("BIP32 derivation path needs to start with 'm'");
236            }
237        } else {
238            bail!("BIP32 derivation path cannot be empty");
239        }
240
241        let (mut successes, errors): (Vec<_>, Vec<_>) =
242            pieces.map(|p: &str| (p, p.parse::<ChildIndex>())).partition(|(_p, i)| i.is_ok());
243
244        if !errors.is_empty() {
245            bail!("BIP32 derivation path contains invalid child indices: {:?}", errors);
246        }
247
248        // because of the above partitioning, successes only contain parse results
249        // that can be unwrapped without causing a panic
250        let path = successes.drain(..).map(|(_p, i)| i.unwrap()).collect();
251        Ok(Path { path })
252    }
253}
254
255impl From<Vec<ChildIndex>> for Path {
256    fn from(path: Vec<ChildIndex>) -> Self {
257        Self { path }
258    }
259}
260
261impl fmt::Display for Path {
262    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), fmt::Error> {
263        use fmt::Write as _;
264        formatter.write_char('m')?;
265        for c in &self.path {
266            write!(formatter, "/{}", c)?;
267        }
268        Ok(())
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use crate::*;
275
276    #[test]
277    fn childidx_fromstr() {
278        assert_eq!("0".parse::<ChildIndex>().unwrap(), ChildIndex::Normal(0));
279        assert_eq!("0h".parse::<ChildIndex>().unwrap(), ChildIndex::Hardened(0));
280        assert_eq!("0H".parse::<ChildIndex>().unwrap(), ChildIndex::Hardened(0));
281        assert_eq!("0'".parse::<ChildIndex>().unwrap(), ChildIndex::Hardened(0));
282        assert_eq!("2147483647".parse::<ChildIndex>().unwrap(), ChildIndex::Normal(2_147_483_647));
283        assert_eq!(
284            "2147483647'".parse::<ChildIndex>().unwrap(),
285            ChildIndex::Hardened(2_147_483_647)
286        );
287        assert!("2147483648".parse::<ChildIndex>().is_err());
288        assert!("-1".parse::<ChildIndex>().is_err());
289        assert!("-2147483648".parse::<ChildIndex>().is_err());
290        assert!("522147483648".parse::<ChildIndex>().is_err());
291        assert!("h".parse::<ChildIndex>().is_err());
292        assert!("-h".parse::<ChildIndex>().is_err());
293        assert!("0a".parse::<ChildIndex>().is_err());
294        assert!("a".parse::<ChildIndex>().is_err());
295    }
296
297    #[test]
298    fn path_fromstr() {
299        assert_eq!("m".parse::<Path>().unwrap(), Path { path: Default::default() });
300        assert_eq!("M".parse::<Path>().unwrap(), Path { path: vec![] });
301        assert_eq!("m/0".parse::<Path>().unwrap(), Path { path: vec![ChildIndex::Normal(0)] });
302        assert_eq!("M/44'".parse::<Path>().unwrap(), Path { path: vec![ChildIndex::Hardened(44)] });
303        assert_eq!(
304            "m/44'/0h/0H/0".parse::<Path>().unwrap(),
305            Path {
306                path: vec![
307                    ChildIndex::Hardened(44),
308                    ChildIndex::Hardened(0),
309                    ChildIndex::Hardened(0),
310                    ChildIndex::Normal(0)
311                ]
312            }
313        );
314        assert_eq!(
315            "m/2147483647'/2147483647".parse::<Path>().unwrap(),
316            Path {
317                path: vec![ChildIndex::Hardened(2_147_483_647), ChildIndex::Normal(2_147_483_647)]
318            }
319        );
320        assert!("".parse::<Path>().is_err());
321        assert!("m/".parse::<Path>().is_err());
322        assert!("m/m".parse::<Path>().is_err());
323        assert!("m/2147483648".parse::<Path>().is_err());
324        assert!("m/522147483648".parse::<Path>().is_err());
325    }
326
327    // macro_rules! assert_fmt {
328    //     ($actual:expr, $($arg:tt)+) => {
329    //         assert_eq!(format!("{:?}", $actual), format!($($arg)+));
330    //     }
331    // }
332
333    // fn test_path(path_str: &str) {
334    //     let seed = crate::Seed::generate_new();
335    //     let master = Bip32.master(&seed, &TestNetwork);
336    //     let path = path_str.parse::<Path>().unwrap();
337    //     assert_fmt!(
338    //         TestCrypto::bip32_ext_priv_key(&master, &path).unwrap(),
339    //         "xprv(sk({}))",
340    //         path_str
341    //     );
342    //     assert_fmt!(
343    //         TestCrypto::bip32_ext_pub_key(&master, &path).unwrap(),
344    //         "xpub(pk({}))",
345    //         path_str
346    //     );
347    //     assert_fmt!(TestCrypto::bip32_priv_key(&master, &path).unwrap(), "sk({})", path_str);
348    //     assert_fmt!(TestCrypto::bip32_pub_key(&master, &path).unwrap(), "pk({})", path_str);
349    //     assert_fmt!(TestCrypto::bip32_key_id(&master, &path).unwrap(), "id({})", path_str);
350    // }
351
352    // #[test]
353    // fn apply_path() {
354    //     test_path("m");
355    //     test_path("m/0'");
356    //     test_path("m/44'/0'/0'/0/0");
357    // }
358
359    #[test]
360    fn derivation() -> Result<()> {
361        let phrase = "blast cargo razor option vote shoe stock cruel mansion boy spot never album crop reflect kangaroo blouse slam empty shoot cable vital crane manual";
362        let seed = Bip39::new().phrase(phrase)?.password(Seed::PASSWORD);
363        let net = &secp256k1::hyd::Mainnet;
364        let master = Bip32.master(&seed, net);
365        let bip44 = master.derive_hardened(44)?;
366        let hyd = bip44.derive_hardened(4_741_444)?;
367        let account = hyd.derive_hardened(0)?;
368        let receives = account.derive_normal(0)?;
369        let key = receives.derive_normal(0)?;
370
371        assert_eq!(master.to_xprv(net), "HYDMVzMRRudwQ4sYtEeUUTuvgSjfFdA4eEEs9tnexVi7wzLh1biJ23KpwowPwpanAcppzuoUuQnDyPG41BV6Haecn1Zzy5fBnEqvqm3EfYsJHfm2");
372        assert_eq!(master.neuter().to_xpub(net), "hydmVzu5j2FCm7zqZBeSU9i6QAU5wQTJCFZyU5nP2w1m7fzrFG9gn3CQhgnchDCuEF5LTsdEgqZsnATTisx8sY5bvf2xqgDbkHtw4o3qxKvjxz5X");
373        assert_eq!(master.path(), &"m".parse()?);
374        assert_eq!(bip44.to_xprv(net), "HYDMVzQELuRPBY1JTQZ1LQD3oJmX9X3atztB57mEaCQEWZMAstsvMp36KGHyPSDR5dfCY1Fc4DLcLk5WwSTwvmwHwioV5dRd6kvqnewQPBBCoWjD");
375        assert_eq!(bip44.neuter().to_xpub(net), "hydmVzwte22eYb8b8MYyL61DX2VwqJLpT2DHPJkxedhsgF1L7ZKK7oug599C8ppMqKWqznGdxp2q28s3TQykNkJE6YFWNjuRc686X6XT9Yojtm9E");
376        assert_eq!(bip44.path(), &"m/44'".parse()?);
377        assert_eq!(hyd.to_xprv(net), "HYDMVzSE83q9tomV2q935JF5mRZXUB37urS6ZpAvi17d5tFNK7K45ddZmjMZJKYQ8yLjKrq4HFewhXuL5AjDzb9Ft5efNT8upEy1ftARyhmxRFZH");
378        assert_eq!(hyd.neuter().to_xpub(net), "hydmVzytRASRFrtmhn914z3FV9Hx9xLMTsmCt1AenSRGFZuXYmkSqdW9XcCn3iAWcCZgEap3diAfhYPXRa5mpHuuxvWehM4j2ZS3V1rZpRF8xTr5");
379        assert_eq!(hyd.path(), &"m/44'/4741444'".parse()?);
380        assert_eq!(account.to_xprv(net), "HYDMVzUVgP7S8GNPrKWhvoFPivfS25QnhfKi1iydA7jbWRwVuMJTVZzBQvBV86zpNJg83rrtvj6SWsftT3nNg5PQ9kwEdzTSpEDH5KbZsjbKBbhs");
381        assert_eq!(account.neuter().to_xpub(net), "hydmW129yVihVKVgXGWfvV3ZSePrhri2FgepKuyMEZ3Eg7bf91jrFZrmAo2hsVcS49dTRP5wZM7A8vudxWk5n8J2Ci12CSNvLy76CsnRaMK4A2b5");
382        assert_eq!(account.path(), &"m/44'/4741444'/0'".parse()?);
383        assert_eq!(receives.to_xprv(net), "HYDMVzVXwQGdZsas8KUKcmTzXtR4Le1fioCn4r8aMFa1CLvCzXJJa28ogEUPdZD1BrSTuBLDGRv5SaPHStH7ZYABhdD6Tf6dCPLd5eaJ9aArC8po");
384        assert_eq!(receives.neuter().to_xpub(net), "hydmW13CEWstvvi9oGUHcTGAFc9V2RJuGpXtP38JRgseN2aNEBjhL21PS7KcNwqbz2jh787bMSPNkd7sYsdVm5rzdeyweau32iL6qzCgpn79R4o2");
385        assert_eq!(receives.path(), &"m/44'/4741444'/0'/0".parse()?);
386        assert_eq!(key.to_xprv(net), "HYDMVzXDVKq5jELcWwpPea9GzS7R8sPmoVo6MjhvGYhenJFBrjzgLw5LPoLzEZY1GyKRqbpPBxZNEvP6TnAq9Qpdi9wo3Bhb9NJNMC79egmr486W");
387        assert_eq!(key.neuter().to_xpub(net), "hydmW14snSSM6HTuBtpMeFwSi9qqpeh1MX8CfvheLz1HwyuM6QS56vwv9gCCyx9GMfhYfMxnn89ynj3y3STp2UxpwT3kFfeGenAkzJRHsuYCLMJW");
388        assert_eq!(key.path(), &"m/44'/4741444'/0'/0/0".parse()?);
389
390        Ok(())
391    }
392
393    #[test]
394    fn parsing() -> Result<()> {
395        let account_xprv = "HYDMVzUVgP7S8GNPrKWhvoFPivfS25QnhfKi1iydA7jbWRwVuMJTVZzBQvBV86zpNJg83rrtvj6SWsftT3nNg5PQ9kwEdzTSpEDH5KbZsjbKBbhs";
396        let account_xpub = "hydmW129yVihVKVgXGWfvV3ZSePrhri2FgepKuyMEZ3Eg7bf91jrFZrmAo2hsVcS49dTRP5wZM7A8vudxWk5n8J2Ci12CSNvLy76CsnRaMK4A2b5";
397        let account_path = "m/44'/4741444'/0'";
398        let net = &secp256k1::hyd::Mainnet;
399
400        let private = Bip32Node::from_xprv(account_path.parse()?, account_xprv, net)?;
401        assert_eq!(private.neuter().to_xpub(net), account_xpub);
402
403        let public = Bip32PublicNode::from_xpub(account_path.parse()?, account_xpub, net)?;
404        let receives = public.derive_normal(0)?;
405        let key = receives.derive_normal(0)?;
406
407        assert_eq!(receives.to_xpub(net), "hydmW13CEWstvvi9oGUHcTGAFc9V2RJuGpXtP38JRgseN2aNEBjhL21PS7KcNwqbz2jh787bMSPNkd7sYsdVm5rzdeyweau32iL6qzCgpn79R4o2");
408        assert_eq!(receives.path(), &"m/44'/4741444'/0'/0".parse()?);
409        assert_eq!(key.to_xpub(net), "hydmW14snSSM6HTuBtpMeFwSi9qqpeh1MX8CfvheLz1HwyuM6QS56vwv9gCCyx9GMfhYfMxnn89ynj3y3STp2UxpwT3kFfeGenAkzJRHsuYCLMJW");
410        assert_eq!(key.path(), &"m/44'/4741444'/0'/0/0".parse()?);
411
412        Ok(())
413    }
414}