Skip to main content

kaspa_bip32/
xprivate_key.rs

1use hmac::Mac;
2use std::fmt::{self, Debug};
3use std::str::FromStr;
4use subtle::{Choice, ConstantTimeEq};
5use zeroize::{Zeroize, Zeroizing};
6
7use crate::{
8    ChildNumber, DerivationPath, ExtendedKey, ExtendedKeyAttrs, ExtendedPublicKey, Prefix, PrivateKey, PublicKey, result::Result,
9    types::*,
10};
11
12/// Derivation domain separator for BIP39 keys.
13const BIP39_DOMAIN_SEPARATOR: [u8; 12] = [0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x20, 0x73, 0x65, 0x65, 0x64];
14
15/// Extended private keys derived using BIP32.
16///
17/// Generic around a [`PrivateKey`] type.
18#[derive(Clone)]
19pub struct ExtendedPrivateKey<K: PrivateKey> {
20    /// Derived private key
21    private_key: K,
22
23    /// Extended key attributes.
24    attrs: ExtendedKeyAttrs,
25}
26
27impl<K> ExtendedPrivateKey<K>
28where
29    K: PrivateKey,
30{
31    /// Maximum derivation depth.
32    pub const MAX_DEPTH: Depth = u8::MAX;
33
34    /// Create the root extended key for the given seed value.
35    pub fn new<S>(seed: S) -> Result<Self>
36    where
37        S: AsRef<[u8]>,
38    {
39        if ![16, 32, 64].contains(&seed.as_ref().len()) {
40            return Err(Error::SeedLength);
41        }
42
43        let mut hmac = HmacSha512::new_from_slice(&BIP39_DOMAIN_SEPARATOR)?;
44        hmac.update(seed.as_ref());
45
46        let result = hmac.finalize().into_bytes();
47        //println!("seed hash {}", hex::encode(result));
48        let (secret_key, chain_code) = result.split_at(KEY_SIZE);
49        let private_key = PrivateKey::from_bytes(secret_key.try_into()?)?;
50        let attrs = ExtendedKeyAttrs {
51            depth: 0,
52            parent_fingerprint: KeyFingerprint::default(),
53            child_number: ChildNumber::default(),
54            chain_code: chain_code.try_into()?,
55        };
56
57        Ok(ExtendedPrivateKey { private_key, attrs })
58    }
59
60    /// Derive a child key for a particular [`ChildNumber`].
61    pub fn derive_child(&self, child_number: ChildNumber) -> Result<Self> {
62        let depth = self.attrs.depth.checked_add(1).ok_or(Error::Depth)?;
63
64        let mut hmac = HmacSha512::new_from_slice(&self.attrs.chain_code).map_err(Error::Hmac)?;
65
66        if child_number.is_hardened() {
67            hmac.update(&[0]);
68            hmac.update(&self.private_key.to_bytes());
69        } else {
70            hmac.update(&self.private_key.public_key().to_bytes());
71        }
72
73        hmac.update(&child_number.to_bytes());
74
75        let result = hmac.finalize().into_bytes();
76        let (child_key, chain_code) = result.split_at(KEY_SIZE);
77
78        // We should technically loop here if a `secret_key` is zero or overflows
79        // the order of the underlying elliptic curve group, incrementing the
80        // index, however per "Child key derivation (CKD) functions":
81        // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
82        //
83        // > "Note: this has probability lower than 1 in 2^127."
84        //
85        // ...so instead, we simply return an error if this were ever to happen,
86        // as the chances of it happening are vanishingly small.
87        let private_key = self.private_key.derive_child(child_key.try_into()?)?;
88
89        let attrs = ExtendedKeyAttrs {
90            parent_fingerprint: self.private_key.public_key().fingerprint(),
91            child_number,
92            chain_code: chain_code.try_into()?,
93            depth,
94        };
95
96        Ok(ExtendedPrivateKey { private_key, attrs })
97    }
98
99    pub fn derive_path(self, path: &DerivationPath) -> Result<Self> {
100        path.iter().try_fold(self, |key, child_num| key.derive_child(child_num))
101    }
102
103    /// Borrow the derived private key value.
104    pub fn private_key(&self) -> &K {
105        &self.private_key
106    }
107
108    /// Serialize the derived public key as bytes.
109    pub fn public_key(&self) -> ExtendedPublicKey<K::PublicKey> {
110        self.into()
111    }
112
113    /// Get attributes for this key such as depth, parent fingerprint,
114    /// child number, and chain code.
115    pub fn attrs(&self) -> &ExtendedKeyAttrs {
116        &self.attrs
117    }
118
119    /// Serialize the raw private key as a byte array.
120    pub fn to_bytes(&self) -> PrivateKeyBytes {
121        self.private_key.to_bytes()
122    }
123
124    /// Serialize this key as an [`ExtendedKey`].
125    pub fn to_extended_key(&self, prefix: Prefix) -> ExtendedKey {
126        // Add leading `0` byte
127        let mut key_bytes = [0u8; KEY_SIZE + 1];
128        key_bytes[1..].copy_from_slice(&self.to_bytes());
129
130        ExtendedKey { prefix, attrs: self.attrs.clone(), key_bytes }
131    }
132
133    pub fn to_string(&self, prefix: Prefix) -> Zeroizing<String> {
134        Zeroizing::new(self.to_extended_key(prefix).to_string())
135    }
136}
137
138impl<K> ConstantTimeEq for ExtendedPrivateKey<K>
139where
140    K: PrivateKey,
141{
142    fn ct_eq(&self, other: &Self) -> Choice {
143        let mut key_a = self.to_bytes();
144        let mut key_b = other.to_bytes();
145
146        let result = key_a.ct_eq(&key_b)
147            & self.attrs.depth.ct_eq(&other.attrs.depth)
148            & self.attrs.parent_fingerprint.ct_eq(&other.attrs.parent_fingerprint)
149            & self.attrs.child_number.0.ct_eq(&other.attrs.child_number.0)
150            & self.attrs.chain_code.ct_eq(&other.attrs.chain_code);
151
152        key_a.zeroize();
153        key_b.zeroize();
154
155        result
156    }
157}
158
159impl<K> Debug for ExtendedPrivateKey<K>
160where
161    K: PrivateKey,
162{
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        // TODO(tarcieri): use `finish_non_exhaustive` when stable
165        f.debug_struct("ExtendedPrivateKey").field("private_key", &"...").field("attrs", &self.attrs).finish()
166    }
167}
168
169/// NOTE: uses [`ConstantTimeEq`] internally
170impl<K> Eq for ExtendedPrivateKey<K> where K: PrivateKey {}
171
172/// NOTE: uses [`ConstantTimeEq`] internally
173impl<K> PartialEq for ExtendedPrivateKey<K>
174where
175    K: PrivateKey,
176{
177    fn eq(&self, other: &Self) -> bool {
178        self.ct_eq(other).into()
179    }
180}
181
182impl<K> FromStr for ExtendedPrivateKey<K>
183where
184    K: PrivateKey,
185{
186    type Err = Error;
187
188    fn from_str(xprv: &str) -> Result<Self> {
189        let key = ExtendedKey::from_str(xprv)?;
190        key.try_into()
191    }
192}
193
194impl<K> TryFrom<ExtendedKey> for ExtendedPrivateKey<K>
195where
196    K: PrivateKey,
197{
198    type Error = Error;
199
200    fn try_from(extended_key: ExtendedKey) -> Result<ExtendedPrivateKey<K>> {
201        if extended_key.prefix.is_private() && extended_key.key_bytes[0] == 0 {
202            Ok(ExtendedPrivateKey {
203                private_key: PrivateKey::from_bytes(extended_key.key_bytes[1..].try_into()?)?,
204                attrs: extended_key.attrs.clone(),
205            })
206        } else {
207            Err(Error::Crypto(secp256k1::Error::InvalidSecretKey))
208        }
209    }
210}