rustywallet_hd/
extended_key.rs

1//! Extended key types for BIP32 HD wallets.
2
3use crate::error::HdError;
4use crate::network::Network;
5use crate::path::{DerivationPath, HARDENED_BIT};
6use hmac::{Hmac, Mac};
7use ripemd::Ripemd160;
8use rustywallet_keys::private_key::PrivateKey;
9use rustywallet_keys::public_key::PublicKey;
10use secp256k1::{Secp256k1, SecretKey};
11use sha2::{Digest, Sha256, Sha512};
12use std::fmt;
13use zeroize::Zeroizing;
14
15type HmacSha512 = Hmac<Sha512>;
16
17/// Extended private key (BIP32).
18///
19/// Contains a private key and chain code for hierarchical derivation.
20///
21/// # Example
22///
23/// ```
24/// use rustywallet_hd::{ExtendedPrivateKey, DerivationPath, Network};
25///
26/// // Create from seed (typically from mnemonic)
27/// let seed = [0u8; 64];
28/// let master = ExtendedPrivateKey::from_seed(&seed, Network::Mainnet).unwrap();
29///
30/// // Derive child key
31/// let path = DerivationPath::parse("m/44'/0'/0'/0/0").unwrap();
32/// let child = master.derive_path(&path).unwrap();
33///
34/// // Export as xprv
35/// let xprv = child.to_xprv();
36/// ```
37pub struct ExtendedPrivateKey {
38    /// Private key bytes
39    private_key: Zeroizing<[u8; 32]>,
40    /// Chain code for derivation
41    chain_code: Zeroizing<[u8; 32]>,
42    /// Depth in derivation tree (0 for master)
43    depth: u8,
44    /// Parent key fingerprint (0x00000000 for master)
45    parent_fingerprint: [u8; 4],
46    /// Child number (0 for master)
47    child_number: u32,
48    /// Network
49    network: Network,
50}
51
52impl ExtendedPrivateKey {
53    /// Create master extended private key from seed.
54    ///
55    /// The seed should be 64 bytes (typically from BIP39 mnemonic).
56    pub fn from_seed(seed: &[u8], network: Network) -> Result<Self, HdError> {
57        if seed.len() != 64 {
58            return Err(HdError::InvalidSeedLength(seed.len()));
59        }
60
61        // HMAC-SHA512 with key "Bitcoin seed"
62        let mut mac =
63            HmacSha512::new_from_slice(b"Bitcoin seed").expect("HMAC can take key of any size");
64        mac.update(seed);
65        let result = mac.finalize().into_bytes();
66
67        let mut private_key = [0u8; 32];
68        let mut chain_code = [0u8; 32];
69        private_key.copy_from_slice(&result[..32]);
70        chain_code.copy_from_slice(&result[32..]);
71
72        // Validate private key
73        SecretKey::from_slice(&private_key).map_err(|_| HdError::InvalidDerivedKey)?;
74
75        Ok(Self {
76            private_key: Zeroizing::new(private_key),
77            chain_code: Zeroizing::new(chain_code),
78            depth: 0,
79            parent_fingerprint: [0; 4],
80            child_number: 0,
81            network,
82        })
83    }
84
85    /// Derive child extended private key at index.
86    ///
87    /// For hardened derivation, use index >= 0x80000000 or use `derive_hardened`.
88    pub fn derive_child(&self, index: u32) -> Result<Self, HdError> {
89        let secp = Secp256k1::new();
90        let parent_key =
91            SecretKey::from_slice(&*self.private_key).map_err(|_| HdError::InvalidDerivedKey)?;
92
93        let mut mac =
94            HmacSha512::new_from_slice(&*self.chain_code).expect("HMAC can take key of any size");
95
96        if index >= HARDENED_BIT {
97            // Hardened derivation: 0x00 || private_key || index
98            mac.update(&[0x00]);
99            mac.update(&*self.private_key);
100        } else {
101            // Normal derivation: public_key || index
102            let public_key = secp256k1::PublicKey::from_secret_key(&secp, &parent_key);
103            mac.update(&public_key.serialize());
104        }
105        mac.update(&index.to_be_bytes());
106
107        let result = mac.finalize().into_bytes();
108
109        let mut child_key_bytes = [0u8; 32];
110        let mut child_chain_code = [0u8; 32];
111        child_key_bytes.copy_from_slice(&result[..32]);
112        child_chain_code.copy_from_slice(&result[32..]);
113
114        // Add parent key to derived key (mod curve order)
115        let tweak =
116            SecretKey::from_slice(&child_key_bytes).map_err(|_| HdError::InvalidDerivedKey)?;
117        let child_key = parent_key
118            .add_tweak(&tweak.into())
119            .map_err(|_| HdError::InvalidDerivedKey)?;
120
121        let mut final_key = [0u8; 32];
122        final_key.copy_from_slice(&child_key.secret_bytes());
123
124        Ok(Self {
125            private_key: Zeroizing::new(final_key),
126            chain_code: Zeroizing::new(child_chain_code),
127            depth: self.depth.saturating_add(1),
128            parent_fingerprint: self.fingerprint(),
129            child_number: index,
130            network: self.network,
131        })
132    }
133
134    /// Derive hardened child at index (adds 0x80000000 to index).
135    pub fn derive_hardened(&self, index: u32) -> Result<Self, HdError> {
136        if index >= HARDENED_BIT {
137            return Err(HdError::InvalidChildNumber(index));
138        }
139        self.derive_child(index | HARDENED_BIT)
140    }
141
142    /// Derive key at derivation path.
143    pub fn derive_path(&self, path: &DerivationPath) -> Result<Self, HdError> {
144        let mut current = self.clone();
145        for child in path.components() {
146            current = current.derive_child(child.raw_index())?;
147        }
148        Ok(current)
149    }
150
151    /// Get the extended public key.
152    pub fn extended_public_key(&self) -> ExtendedPublicKey {
153        let secp = Secp256k1::new();
154        let secret_key =
155            SecretKey::from_slice(&*self.private_key).expect("Private key should be valid");
156        let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret_key);
157
158        ExtendedPublicKey {
159            public_key: public_key.serialize(),
160            chain_code: *self.chain_code,
161            depth: self.depth,
162            parent_fingerprint: self.parent_fingerprint,
163            child_number: self.child_number,
164            network: self.network,
165        }
166    }
167
168    /// Get the underlying private key.
169    pub fn private_key(&self) -> Result<PrivateKey, HdError> {
170        PrivateKey::from_bytes(*self.private_key).map_err(|_| HdError::InvalidDerivedKey)
171    }
172
173    /// Get the underlying public key.
174    pub fn public_key(&self) -> PublicKey {
175        self.private_key()
176            .expect("Private key should be valid")
177            .public_key()
178    }
179
180    /// Get fingerprint (first 4 bytes of HASH160 of public key).
181    pub fn fingerprint(&self) -> [u8; 4] {
182        let secp = Secp256k1::new();
183        let secret_key =
184            SecretKey::from_slice(&*self.private_key).expect("Private key should be valid");
185        let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret_key);
186
187        let sha256 = Sha256::digest(public_key.serialize());
188        let hash160 = Ripemd160::digest(sha256);
189
190        let mut fingerprint = [0u8; 4];
191        fingerprint.copy_from_slice(&hash160[..4]);
192        fingerprint
193    }
194
195    /// Get depth in derivation tree.
196    pub fn depth(&self) -> u8 {
197        self.depth
198    }
199
200    /// Get chain code.
201    pub fn chain_code(&self) -> &[u8; 32] {
202        &self.chain_code
203    }
204
205    /// Get network.
206    pub fn network(&self) -> Network {
207        self.network
208    }
209
210    /// Serialize to xprv string (Base58Check).
211    pub fn to_xprv(&self) -> String {
212        let mut data = Vec::with_capacity(78);
213
214        // Version (4 bytes)
215        data.extend_from_slice(&self.network.xprv_version());
216        // Depth (1 byte)
217        data.push(self.depth);
218        // Parent fingerprint (4 bytes)
219        data.extend_from_slice(&self.parent_fingerprint);
220        // Child number (4 bytes)
221        data.extend_from_slice(&self.child_number.to_be_bytes());
222        // Chain code (32 bytes)
223        data.extend_from_slice(&*self.chain_code);
224        // Key (33 bytes: 0x00 prefix + 32 bytes private key)
225        data.push(0x00);
226        data.extend_from_slice(&*self.private_key);
227
228        bs58::encode(data).with_check().into_string()
229    }
230
231    /// Parse from xprv string (Base58Check).
232    pub fn from_xprv(xprv: &str) -> Result<Self, HdError> {
233        let data = bs58::decode(xprv)
234            .with_check(None)
235            .into_vec()
236            .map_err(|_| HdError::InvalidChecksum)?;
237
238        if data.len() != 78 {
239            return Err(HdError::InvalidExtendedKey);
240        }
241
242        let mut version = [0u8; 4];
243        version.copy_from_slice(&data[0..4]);
244
245        let (network, is_private) =
246            Network::from_version(&version).ok_or(HdError::InvalidVersion)?;
247
248        if !is_private {
249            return Err(HdError::InvalidExtendedKey);
250        }
251
252        let depth = data[4];
253
254        let mut parent_fingerprint = [0u8; 4];
255        parent_fingerprint.copy_from_slice(&data[5..9]);
256
257        let child_number = u32::from_be_bytes([data[9], data[10], data[11], data[12]]);
258
259        let mut chain_code = [0u8; 32];
260        chain_code.copy_from_slice(&data[13..45]);
261
262        // Key starts with 0x00 for private key
263        if data[45] != 0x00 {
264            return Err(HdError::InvalidExtendedKey);
265        }
266
267        let mut private_key = [0u8; 32];
268        private_key.copy_from_slice(&data[46..78]);
269
270        // Validate private key
271        SecretKey::from_slice(&private_key).map_err(|_| HdError::InvalidDerivedKey)?;
272
273        Ok(Self {
274            private_key: Zeroizing::new(private_key),
275            chain_code: Zeroizing::new(chain_code),
276            depth,
277            parent_fingerprint,
278            child_number,
279            network,
280        })
281    }
282}
283
284
285impl Clone for ExtendedPrivateKey {
286    fn clone(&self) -> Self {
287        Self {
288            private_key: Zeroizing::new(*self.private_key),
289            chain_code: Zeroizing::new(*self.chain_code),
290            depth: self.depth,
291            parent_fingerprint: self.parent_fingerprint,
292            child_number: self.child_number,
293            network: self.network,
294        }
295    }
296}
297
298impl fmt::Display for ExtendedPrivateKey {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        write!(f, "{}", self.to_xprv())
301    }
302}
303
304impl fmt::Debug for ExtendedPrivateKey {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        write!(
307            f,
308            "ExtendedPrivateKey {{ depth: {}, fingerprint: {:02x}{:02x}{:02x}{:02x}, key: **** }}",
309            self.depth,
310            self.parent_fingerprint[0],
311            self.parent_fingerprint[1],
312            self.parent_fingerprint[2],
313            self.parent_fingerprint[3]
314        )
315    }
316}
317
318impl Drop for ExtendedPrivateKey {
319    fn drop(&mut self) {
320        // private_key and chain_code are Zeroizing, they auto-zeroize
321    }
322}
323
324/// Extended public key (BIP32).
325///
326/// Can derive child public keys for non-hardened paths only.
327#[derive(Clone)]
328pub struct ExtendedPublicKey {
329    /// Public key (33 bytes compressed)
330    public_key: [u8; 33],
331    /// Chain code for derivation
332    chain_code: [u8; 32],
333    /// Depth in derivation tree
334    depth: u8,
335    /// Parent key fingerprint
336    parent_fingerprint: [u8; 4],
337    /// Child number
338    child_number: u32,
339    /// Network
340    network: Network,
341}
342
343impl ExtendedPublicKey {
344    /// Derive child public key (normal derivation only).
345    ///
346    /// Returns error for hardened derivation (index >= 0x80000000).
347    pub fn derive_child(&self, index: u32) -> Result<Self, HdError> {
348        if index >= HARDENED_BIT {
349            return Err(HdError::HardenedFromPublic);
350        }
351
352        let secp = Secp256k1::new();
353        let parent_key = secp256k1::PublicKey::from_slice(&self.public_key)
354            .map_err(|_| HdError::InvalidDerivedKey)?;
355
356        let mut mac =
357            HmacSha512::new_from_slice(&self.chain_code).expect("HMAC can take key of any size");
358        mac.update(&self.public_key);
359        mac.update(&index.to_be_bytes());
360
361        let result = mac.finalize().into_bytes();
362
363        let mut tweak_bytes = [0u8; 32];
364        let mut child_chain_code = [0u8; 32];
365        tweak_bytes.copy_from_slice(&result[..32]);
366        child_chain_code.copy_from_slice(&result[32..]);
367
368        // Add tweak to parent public key
369        let tweak = SecretKey::from_slice(&tweak_bytes).map_err(|_| HdError::InvalidDerivedKey)?;
370        let child_key = parent_key
371            .add_exp_tweak(&secp, &tweak.into())
372            .map_err(|_| HdError::InvalidDerivedKey)?;
373
374        Ok(Self {
375            public_key: child_key.serialize(),
376            chain_code: child_chain_code,
377            depth: self.depth.saturating_add(1),
378            parent_fingerprint: self.fingerprint(),
379            child_number: index,
380            network: self.network,
381        })
382    }
383
384    /// Derive key at derivation path (normal derivation only).
385    pub fn derive_path(&self, path: &DerivationPath) -> Result<Self, HdError> {
386        if path.has_hardened() {
387            return Err(HdError::HardenedFromPublic);
388        }
389
390        let mut current = self.clone();
391        for child in path.components() {
392            current = current.derive_child(child.raw_index())?;
393        }
394        Ok(current)
395    }
396
397    /// Get the underlying public key.
398    pub fn public_key(&self) -> PublicKey {
399        PublicKey::from_compressed(&self.public_key).expect("Public key should be valid")
400    }
401
402    /// Get fingerprint (first 4 bytes of HASH160 of public key).
403    pub fn fingerprint(&self) -> [u8; 4] {
404        let sha256 = Sha256::digest(self.public_key);
405        let hash160 = Ripemd160::digest(sha256);
406
407        let mut fingerprint = [0u8; 4];
408        fingerprint.copy_from_slice(&hash160[..4]);
409        fingerprint
410    }
411
412    /// Get depth in derivation tree.
413    pub fn depth(&self) -> u8 {
414        self.depth
415    }
416
417    /// Get chain code.
418    pub fn chain_code(&self) -> &[u8; 32] {
419        &self.chain_code
420    }
421
422    /// Get network.
423    pub fn network(&self) -> Network {
424        self.network
425    }
426
427    /// Serialize to xpub string (Base58Check).
428    pub fn to_xpub(&self) -> String {
429        let mut data = Vec::with_capacity(78);
430
431        // Version (4 bytes)
432        data.extend_from_slice(&self.network.xpub_version());
433        // Depth (1 byte)
434        data.push(self.depth);
435        // Parent fingerprint (4 bytes)
436        data.extend_from_slice(&self.parent_fingerprint);
437        // Child number (4 bytes)
438        data.extend_from_slice(&self.child_number.to_be_bytes());
439        // Chain code (32 bytes)
440        data.extend_from_slice(&self.chain_code);
441        // Public key (33 bytes)
442        data.extend_from_slice(&self.public_key);
443
444        bs58::encode(data).with_check().into_string()
445    }
446
447    /// Parse from xpub string (Base58Check).
448    pub fn from_xpub(xpub: &str) -> Result<Self, HdError> {
449        let data = bs58::decode(xpub)
450            .with_check(None)
451            .into_vec()
452            .map_err(|_| HdError::InvalidChecksum)?;
453
454        if data.len() != 78 {
455            return Err(HdError::InvalidExtendedKey);
456        }
457
458        let mut version = [0u8; 4];
459        version.copy_from_slice(&data[0..4]);
460
461        let (network, is_private) =
462            Network::from_version(&version).ok_or(HdError::InvalidVersion)?;
463
464        if is_private {
465            return Err(HdError::InvalidExtendedKey);
466        }
467
468        let depth = data[4];
469
470        let mut parent_fingerprint = [0u8; 4];
471        parent_fingerprint.copy_from_slice(&data[5..9]);
472
473        let child_number = u32::from_be_bytes([data[9], data[10], data[11], data[12]]);
474
475        let mut chain_code = [0u8; 32];
476        chain_code.copy_from_slice(&data[13..45]);
477
478        let mut public_key = [0u8; 33];
479        public_key.copy_from_slice(&data[45..78]);
480
481        // Validate public key
482        secp256k1::PublicKey::from_slice(&public_key).map_err(|_| HdError::InvalidDerivedKey)?;
483
484        Ok(Self {
485            public_key,
486            chain_code,
487            depth,
488            parent_fingerprint,
489            child_number,
490            network,
491        })
492    }
493}
494
495impl fmt::Display for ExtendedPublicKey {
496    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
497        write!(f, "{}", self.to_xpub())
498    }
499}
500
501impl fmt::Debug for ExtendedPublicKey {
502    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503        write!(
504            f,
505            "ExtendedPublicKey {{ depth: {}, fingerprint: {:02x}{:02x}{:02x}{:02x} }}",
506            self.depth,
507            self.parent_fingerprint[0],
508            self.parent_fingerprint[1],
509            self.parent_fingerprint[2],
510            self.parent_fingerprint[3]
511        )
512    }
513}
514
515#[cfg(test)]
516mod tests {
517    use super::*;
518
519    // BIP32 Test Vector 2 (64 byte seed)
520    const TEST_SEED_2: &str = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542";
521    const TEST_XPRV_M2: &str = "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U";
522    const TEST_XPUB_M2: &str = "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB";
523
524    fn get_test_seed_64() -> Vec<u8> {
525        hex::decode(TEST_SEED_2).unwrap()
526    }
527
528    #[test]
529    fn test_master_from_seed() {
530        let seed = get_test_seed_64();
531        let master = ExtendedPrivateKey::from_seed(&seed, Network::Mainnet).unwrap();
532        assert_eq!(master.depth(), 0);
533        assert_eq!(master.to_xprv(), TEST_XPRV_M2);
534    }
535
536    #[test]
537    fn test_master_xpub() {
538        let seed = get_test_seed_64();
539        let master = ExtendedPrivateKey::from_seed(&seed, Network::Mainnet).unwrap();
540        let xpub = master.extended_public_key();
541        assert_eq!(xpub.to_xpub(), TEST_XPUB_M2);
542    }
543
544    #[test]
545    fn test_xprv_roundtrip() {
546        let seed = get_test_seed_64();
547        let master = ExtendedPrivateKey::from_seed(&seed, Network::Mainnet).unwrap();
548        let xprv = master.to_xprv();
549        let parsed = ExtendedPrivateKey::from_xprv(&xprv).unwrap();
550        assert_eq!(parsed.to_xprv(), xprv);
551    }
552
553    #[test]
554    fn test_xpub_roundtrip() {
555        let seed = get_test_seed_64();
556        let master = ExtendedPrivateKey::from_seed(&seed, Network::Mainnet).unwrap();
557        let xpub = master.extended_public_key();
558        let xpub_str = xpub.to_xpub();
559        let parsed = ExtendedPublicKey::from_xpub(&xpub_str).unwrap();
560        assert_eq!(parsed.to_xpub(), xpub_str);
561    }
562
563    #[test]
564    fn test_hardened_from_public_fails() {
565        let seed = get_test_seed_64();
566        let master = ExtendedPrivateKey::from_seed(&seed, Network::Mainnet).unwrap();
567        let xpub = master.extended_public_key();
568        let result = xpub.derive_child(HARDENED_BIT);
569        assert!(matches!(result, Err(HdError::HardenedFromPublic)));
570    }
571
572    #[test]
573    fn test_derive_path() {
574        let seed = get_test_seed_64();
575        let master = ExtendedPrivateKey::from_seed(&seed, Network::Mainnet).unwrap();
576        let path = DerivationPath::parse("m/0'").unwrap();
577        let child = master.derive_path(&path).unwrap();
578        assert_eq!(child.depth(), 1);
579    }
580
581    #[test]
582    fn test_debug_masked() {
583        let seed = get_test_seed_64();
584        let master = ExtendedPrivateKey::from_seed(&seed, Network::Mainnet).unwrap();
585        let debug = format!("{:?}", master);
586        assert!(debug.contains("****"));
587        assert!(!debug.contains(&master.to_xprv()));
588    }
589
590    #[test]
591    fn test_bip44_derivation() {
592        let seed = get_test_seed_64();
593        let master = ExtendedPrivateKey::from_seed(&seed, Network::Mainnet).unwrap();
594        let path = DerivationPath::bip44_bitcoin(0, 0, 0);
595        let child = master.derive_path(&path).unwrap();
596        assert_eq!(child.depth(), 5);
597    }
598
599    #[test]
600    fn test_public_key_derivation_consistency() {
601        let seed = get_test_seed_64();
602        let master = ExtendedPrivateKey::from_seed(&seed, Network::Mainnet).unwrap();
603        
604        // Derive child private key then get public
605        let child_priv = master.derive_child(0).unwrap();
606        let pub_from_priv = child_priv.extended_public_key();
607        
608        // Derive child public key directly
609        let master_pub = master.extended_public_key();
610        let pub_direct = master_pub.derive_child(0).unwrap();
611        
612        assert_eq!(pub_from_priv.to_xpub(), pub_direct.to_xpub());
613    }
614}