rustywallet_keys/
private_key.rs

1//! Private key implementation
2//!
3//! This module provides the [`PrivateKey`] type for working with secp256k1 private keys.
4
5use crate::encoding::{hex, wif};
6use crate::error::PrivateKeyError;
7use crate::network::Network;
8use rand::{rngs::OsRng, CryptoRng, RngCore};
9use secp256k1::{Secp256k1, SecretKey};
10use std::fmt;
11use zeroize::Zeroize;
12
13/// An iterator that generates random private keys.
14///
15/// This iterator is infinite and memory-efficient - it generates keys on demand
16/// without storing them. Use `.take(n)` to limit the number of keys generated.
17///
18/// # Example
19///
20/// ```
21/// use rustywallet_keys::private_key::PrivateKey;
22///
23/// // Generate 1000 keys efficiently
24/// let keys: Vec<_> = PrivateKey::batch().take(1000).collect();
25/// assert_eq!(keys.len(), 1000);
26///
27/// // Process keys one by one without storing all in memory
28/// for key in PrivateKey::batch().take(100) {
29///     println!("{}", key.to_hex());
30/// }
31/// ```
32pub struct PrivateKeyIterator<R: RngCore + CryptoRng> {
33    rng: R,
34    secp: Secp256k1<secp256k1::All>,
35}
36
37impl PrivateKeyIterator<OsRng> {
38    /// Create a new iterator using the OS random number generator.
39    fn new() -> Self {
40        Self {
41            rng: OsRng,
42            secp: Secp256k1::new(),
43        }
44    }
45}
46
47impl<R: RngCore + CryptoRng> PrivateKeyIterator<R> {
48    /// Create a new iterator with a custom RNG.
49    fn with_rng(rng: R) -> Self {
50        Self {
51            rng,
52            secp: Secp256k1::new(),
53        }
54    }
55}
56
57impl<R: RngCore + CryptoRng> Iterator for PrivateKeyIterator<R> {
58    type Item = PrivateKey;
59
60    fn next(&mut self) -> Option<Self::Item> {
61        let (secret_key, _) = self.secp.generate_keypair(&mut self.rng);
62        Some(PrivateKey { inner: secret_key })
63    }
64}
65
66/// A secp256k1 private key with secure memory handling.
67///
68/// This struct wraps a `secp256k1::SecretKey` and provides convenient methods
69/// for key generation, import, and export. The key is automatically zeroized
70/// when dropped for security.
71///
72/// # Example
73///
74/// ```
75/// use rustywallet_keys::private_key::PrivateKey;
76/// use rustywallet_keys::network::Network;
77///
78/// // Generate a random key
79/// let key = PrivateKey::random();
80///
81/// // Export to hex
82/// let hex = key.to_hex();
83/// assert_eq!(hex.len(), 64);
84///
85/// // Export to WIF
86/// let wif = key.to_wif(Network::Mainnet);
87/// assert!(wif.starts_with('K') || wif.starts_with('L'));
88/// ```
89pub struct PrivateKey {
90    inner: SecretKey,
91}
92
93impl PrivateKey {
94    /// Generate a new random private key using a cryptographically secure RNG.
95    ///
96    /// This method uses the operating system's secure random number generator
97    /// and automatically regenerates if an invalid key is produced (which is
98    /// extremely unlikely).
99    ///
100    /// # Example
101    ///
102    /// ```
103    /// use rustywallet_keys::private_key::PrivateKey;
104    ///
105    /// let key = PrivateKey::random();
106    /// ```
107    pub fn random() -> Self {
108        let secp = Secp256k1::new();
109        let (secret_key, _) = secp.generate_keypair(&mut OsRng);
110        Self { inner: secret_key }
111    }
112
113    /// Create an infinite iterator that generates random private keys.
114    ///
115    /// This is memory-efficient as keys are generated on demand. Use `.take(n)`
116    /// to limit the number of keys.
117    ///
118    /// # Example
119    ///
120    /// ```
121    /// use rustywallet_keys::private_key::PrivateKey;
122    ///
123    /// // Generate 1000 keys
124    /// let keys: Vec<_> = PrivateKey::batch().take(1000).collect();
125    ///
126    /// // Process without storing all in memory
127    /// for key in PrivateKey::batch().take(100) {
128    ///     let _ = key.to_hex();
129    /// }
130    /// ```
131    pub fn batch() -> PrivateKeyIterator<OsRng> {
132        PrivateKeyIterator::new()
133    }
134
135    /// Create an iterator with a custom RNG for deterministic key generation.
136    ///
137    /// # Example
138    ///
139    /// ```
140    /// use rustywallet_keys::private_key::PrivateKey;
141    /// use rand::SeedableRng;
142    /// use rand::rngs::StdRng;
143    ///
144    /// let rng = StdRng::seed_from_u64(12345);
145    /// let keys: Vec<_> = PrivateKey::batch_with_rng(rng).take(10).collect();
146    /// ```
147    pub fn batch_with_rng<R: RngCore + CryptoRng>(rng: R) -> PrivateKeyIterator<R> {
148        PrivateKeyIterator::with_rng(rng)
149    }
150
151    /// Create a private key from a 32-byte array.
152    ///
153    /// # Errors
154    ///
155    /// Returns [`PrivateKeyError::OutOfRange`] if the bytes represent a value
156    /// that is zero or greater than or equal to the curve order.
157    ///
158    /// # Example
159    ///
160    /// ```
161    /// use rustywallet_keys::private_key::PrivateKey;
162    ///
163    /// let bytes = [1u8; 32];
164    /// let key = PrivateKey::from_bytes(bytes).unwrap();
165    /// ```
166    pub fn from_bytes(bytes: [u8; 32]) -> Result<Self, PrivateKeyError> {
167        let secret_key = SecretKey::from_slice(&bytes).map_err(|_| PrivateKeyError::OutOfRange)?;
168        Ok(Self { inner: secret_key })
169    }
170
171    /// Check if a 32-byte array represents a valid private key.
172    ///
173    /// A valid private key must be non-zero and less than the secp256k1 curve order.
174    ///
175    /// # Example
176    ///
177    /// ```
178    /// use rustywallet_keys::private_key::PrivateKey;
179    ///
180    /// let valid_bytes = [1u8; 32];
181    /// assert!(PrivateKey::is_valid(&valid_bytes));
182    ///
183    /// let zero_bytes = [0u8; 32];
184    /// assert!(!PrivateKey::is_valid(&zero_bytes));
185    /// ```
186    pub fn is_valid(bytes: &[u8; 32]) -> bool {
187        SecretKey::from_slice(bytes).is_ok()
188    }
189
190    /// Create a private key from a hex string.
191    ///
192    /// The hex string must be exactly 64 characters (32 bytes).
193    /// Both uppercase and lowercase characters are accepted.
194    ///
195    /// # Errors
196    ///
197    /// - [`PrivateKeyError::InvalidLength`] if the hex string is not 64 characters
198    /// - [`PrivateKeyError::InvalidHex`] if the string contains invalid hex characters
199    /// - [`PrivateKeyError::OutOfRange`] if the decoded value is invalid
200    ///
201    /// # Example
202    ///
203    /// ```
204    /// use rustywallet_keys::private_key::PrivateKey;
205    ///
206    /// let hex = "0000000000000000000000000000000000000000000000000000000000000001";
207    /// let key = PrivateKey::from_hex(hex).unwrap();
208    /// ```
209    pub fn from_hex(hex_str: &str) -> Result<Self, PrivateKeyError> {
210        if hex_str.len() != 64 {
211            return Err(PrivateKeyError::InvalidLength(hex_str.len() / 2));
212        }
213
214        let bytes = hex::decode(hex_str).map_err(|e| PrivateKeyError::InvalidHex(e.to_string()))?;
215
216        let mut arr = [0u8; 32];
217        arr.copy_from_slice(&bytes);
218        Self::from_bytes(arr)
219    }
220
221    /// Create a private key from a WIF (Wallet Import Format) string.
222    ///
223    /// # Errors
224    ///
225    /// - [`PrivateKeyError::InvalidWif`] if the WIF format is invalid
226    /// - [`PrivateKeyError::InvalidChecksum`] if the checksum doesn't match
227    /// - [`PrivateKeyError::OutOfRange`] if the decoded key is invalid
228    ///
229    /// # Example
230    ///
231    /// ```
232    /// use rustywallet_keys::private_key::PrivateKey;
233    ///
234    /// let wif = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ";
235    /// let key = PrivateKey::from_wif(wif).unwrap();
236    /// ```
237    pub fn from_wif(wif_str: &str) -> Result<Self, PrivateKeyError> {
238        let (bytes, _network, _compressed) = wif::decode(wif_str).map_err(|e| match e {
239            wif::WifError::InvalidChecksum => PrivateKeyError::InvalidChecksum,
240            other => PrivateKeyError::InvalidWif(other.to_string()),
241        })?;
242        Self::from_bytes(bytes)
243    }
244
245    /// Export the private key as a 32-byte array.
246    ///
247    /// # Example
248    ///
249    /// ```
250    /// use rustywallet_keys::private_key::PrivateKey;
251    ///
252    /// let key = PrivateKey::random();
253    /// let bytes = key.to_bytes();
254    /// assert_eq!(bytes.len(), 32);
255    /// ```
256    pub fn to_bytes(&self) -> [u8; 32] {
257        self.inner.secret_bytes()
258    }
259
260    /// Export the private key as a lowercase hex string.
261    ///
262    /// # Example
263    ///
264    /// ```
265    /// use rustywallet_keys::private_key::PrivateKey;
266    ///
267    /// let key = PrivateKey::random();
268    /// let hex = key.to_hex();
269    /// assert_eq!(hex.len(), 64);
270    /// ```
271    pub fn to_hex(&self) -> String {
272        hex::encode(&self.to_bytes())
273    }
274
275    /// Export the private key as a WIF (Wallet Import Format) string.
276    ///
277    /// The WIF format includes the network version byte and uses compressed
278    /// public key format by default.
279    ///
280    /// # Example
281    ///
282    /// ```
283    /// use rustywallet_keys::private_key::PrivateKey;
284    /// use rustywallet_keys::network::Network;
285    ///
286    /// let key = PrivateKey::random();
287    /// let wif = key.to_wif(Network::Mainnet);
288    /// assert!(wif.starts_with('K') || wif.starts_with('L'));
289    /// ```
290    pub fn to_wif(&self, network: Network) -> String {
291        wif::encode(&self.to_bytes(), network, true)
292    }
293
294    /// Export the private key as a decimal string.
295    ///
296    /// This converts the 256-bit key to its decimal representation.
297    ///
298    /// # Example
299    ///
300    /// ```
301    /// use rustywallet_keys::private_key::PrivateKey;
302    ///
303    /// let key = PrivateKey::from_hex(
304    ///     "0000000000000000000000000000000000000000000000000000000000000001"
305    /// ).unwrap();
306    /// assert_eq!(key.to_decimal(), "1");
307    /// ```
308    pub fn to_decimal(&self) -> String {
309        let bytes = self.to_bytes();
310        bytes_to_decimal(&bytes)
311    }
312
313    /// Derive the corresponding public key.
314    ///
315    /// # Example
316    ///
317    /// ```
318    /// use rustywallet_keys::private_key::PrivateKey;
319    ///
320    /// let private_key = PrivateKey::random();
321    /// let public_key = private_key.public_key();
322    /// ```
323    pub fn public_key(&self) -> crate::public_key::PublicKey {
324        crate::public_key::PublicKey::from_private_key(self)
325    }
326}
327
328/// Convert a 32-byte array to decimal string
329fn bytes_to_decimal(bytes: &[u8; 32]) -> String {
330    // Handle zero case
331    if bytes.iter().all(|&b| b == 0) {
332        return "0".to_string();
333    }
334
335    // Convert bytes to decimal using repeated division
336    let mut result = Vec::new();
337    let mut temp = bytes.to_vec();
338
339    while temp.iter().any(|&b| b != 0) {
340        let mut remainder = 0u32;
341        for byte in temp.iter_mut() {
342            let value = (remainder << 8) | (*byte as u32);
343            *byte = (value / 10) as u8;
344            remainder = value % 10;
345        }
346        result.push((remainder as u8) + b'0');
347    }
348
349    result.reverse();
350    String::from_utf8(result).unwrap_or_else(|_| "0".to_string())
351}
352
353impl Drop for PrivateKey {
354    fn drop(&mut self) {
355        // SecretKey doesn't expose mutable access to its bytes,
356        // but secp256k1 crate handles zeroization internally.
357        // We create a temporary copy and zeroize it to ensure
358        // any stack copies are cleared.
359        let mut bytes = self.inner.secret_bytes();
360        bytes.zeroize();
361    }
362}
363
364impl fmt::Debug for PrivateKey {
365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366        write!(
367            f,
368            "PrivateKey {{ hex: {}, decimal: {} }}",
369            self.to_hex(),
370            self.to_decimal()
371        )
372    }
373}
374
375impl Clone for PrivateKey {
376    fn clone(&self) -> Self {
377        Self { inner: self.inner }
378    }
379}
380
381impl PartialEq for PrivateKey {
382    fn eq(&self, other: &Self) -> bool {
383        self.inner == other.inner
384    }
385}
386
387impl Eq for PrivateKey {}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392    use proptest::prelude::*;
393
394    /// Strategy to generate valid private key bytes
395    fn valid_private_key_bytes() -> impl Strategy<Value = [u8; 32]> {
396        // Generate random bytes and filter to valid keys
397        prop::array::uniform32(any::<u8>()).prop_filter("must be valid secp256k1 key", |bytes| {
398            PrivateKey::is_valid(bytes)
399        })
400    }
401
402    // **Feature: rustywallet-keys, Property 1: Random Key Validity**
403    // **Validates: Requirements 1.1, 1.2**
404    // For any randomly generated private key, the key SHALL be valid
405    // (non-zero and less than the secp256k1 curve order).
406    #[test]
407    fn property_random_key_validity() {
408        // Generate 100 random keys and verify each is valid
409        for _ in 0..100 {
410            let key = PrivateKey::random();
411            let bytes = key.to_bytes();
412
413            // Verify the key is valid
414            assert!(PrivateKey::is_valid(&bytes), "Random key should be valid");
415
416            // Verify the key is non-zero
417            assert!(
418                bytes.iter().any(|&b| b != 0),
419                "Random key should not be all zeros"
420            );
421
422            // Verify we can reconstruct the key from bytes
423            let reconstructed =
424                PrivateKey::from_bytes(bytes).expect("Should be able to reconstruct from bytes");
425            assert_eq!(
426                key, reconstructed,
427                "Reconstructed key should equal original"
428            );
429        }
430    }
431
432    // **Feature: rustywallet-keys, Property 2: Hex Round-Trip**
433    // **Validates: Requirements 2.1, 3.1, 3.5**
434    // For any valid private key, converting to hex and parsing back SHALL produce
435    // an equivalent private key.
436    proptest! {
437        #![proptest_config(ProptestConfig::with_cases(100))]
438        #[test]
439        fn property_hex_roundtrip(bytes in valid_private_key_bytes()) {
440            let key = PrivateKey::from_bytes(bytes).unwrap();
441            let hex = key.to_hex();
442            let recovered = PrivateKey::from_hex(&hex).unwrap();
443            prop_assert_eq!(key, recovered);
444        }
445    }
446
447    // **Feature: rustywallet-keys, Property 3: Bytes Round-Trip**
448    // **Validates: Requirements 2.2, 3.2, 3.5**
449    // For any valid private key, exporting to bytes and importing back SHALL produce
450    // an equivalent private key.
451    proptest! {
452        #![proptest_config(ProptestConfig::with_cases(100))]
453        #[test]
454        fn property_bytes_roundtrip(bytes in valid_private_key_bytes()) {
455            let key = PrivateKey::from_bytes(bytes).unwrap();
456            let exported = key.to_bytes();
457            let recovered = PrivateKey::from_bytes(exported).unwrap();
458            prop_assert_eq!(key, recovered);
459        }
460    }
461
462    // **Feature: rustywallet-keys, Property 4: WIF Round-Trip**
463    // **Validates: Requirements 2.3, 3.3, 3.4, 3.5**
464    // For any valid private key and network, encoding to WIF and decoding back
465    // SHALL produce an equivalent private key.
466    proptest! {
467        #![proptest_config(ProptestConfig::with_cases(100))]
468        #[test]
469        fn property_wif_roundtrip(
470            bytes in valid_private_key_bytes(),
471            use_mainnet in any::<bool>()
472        ) {
473            let network = if use_mainnet { Network::Mainnet } else { Network::Testnet };
474            let key = PrivateKey::from_bytes(bytes).unwrap();
475            let wif = key.to_wif(network);
476            let recovered = PrivateKey::from_wif(&wif).unwrap();
477            prop_assert_eq!(key, recovered);
478        }
479    }
480
481    // **Feature: rustywallet-keys, Property 5: Hex Case Insensitivity**
482    // **Validates: Requirements 2.5**
483    // For any valid hex string representing a private key, both uppercase and
484    // lowercase versions SHALL parse to equivalent keys.
485    proptest! {
486        #![proptest_config(ProptestConfig::with_cases(100))]
487        #[test]
488        fn property_hex_case_insensitivity(bytes in valid_private_key_bytes()) {
489            let key = PrivateKey::from_bytes(bytes).unwrap();
490            let hex_lower = key.to_hex();
491            let hex_upper = hex_lower.to_uppercase();
492
493            let from_lower = PrivateKey::from_hex(&hex_lower).unwrap();
494            let from_upper = PrivateKey::from_hex(&hex_upper).unwrap();
495
496            prop_assert_eq!(from_lower, from_upper);
497        }
498    }
499
500    // **Feature: rustywallet-keys, Property 6: Invalid Input Rejection**
501    // **Validates: Requirements 2.4, 4.1, 4.2, 4.3**
502    // For any byte array that is zero or >= curve order, the validation function
503    // SHALL return false and construction SHALL return an error.
504    #[test]
505    fn property_invalid_input_rejection() {
506        // Test zero key
507        let zero_bytes = [0u8; 32];
508        assert!(
509            !PrivateKey::is_valid(&zero_bytes),
510            "Zero key should be invalid"
511        );
512        assert!(
513            PrivateKey::from_bytes(zero_bytes).is_err(),
514            "Zero key should fail construction"
515        );
516
517        // Test curve order (n) - this is >= curve order so should be invalid
518        // secp256k1 curve order n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
519        let curve_order: [u8; 32] = [
520            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
521            0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C,
522            0xD0, 0x36, 0x41, 0x41,
523        ];
524        assert!(
525            !PrivateKey::is_valid(&curve_order),
526            "Curve order should be invalid"
527        );
528        assert!(
529            PrivateKey::from_bytes(curve_order).is_err(),
530            "Curve order should fail construction"
531        );
532
533        // Test value greater than curve order
534        let above_order: [u8; 32] = [0xFF; 32];
535        assert!(
536            !PrivateKey::is_valid(&above_order),
537            "Value above curve order should be invalid"
538        );
539        assert!(
540            PrivateKey::from_bytes(above_order).is_err(),
541            "Value above curve order should fail construction"
542        );
543
544        // Test that n-1 is valid (maximum valid key)
545        let max_valid: [u8; 32] = [
546            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
547            0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C,
548            0xD0, 0x36, 0x41, 0x40,
549        ];
550        assert!(PrivateKey::is_valid(&max_valid), "n-1 should be valid");
551        assert!(
552            PrivateKey::from_bytes(max_valid).is_ok(),
553            "n-1 should succeed construction"
554        );
555
556        // Test that 1 is valid (minimum valid key)
557        let min_valid: [u8; 32] = [
558            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
559            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
560            0x00, 0x00, 0x00, 0x01,
561        ];
562        assert!(PrivateKey::is_valid(&min_valid), "1 should be valid");
563        assert!(
564            PrivateKey::from_bytes(min_valid).is_ok(),
565            "1 should succeed construction"
566        );
567    }
568
569    // **Feature: rustywallet-keys, Property 11: Debug Output Format**
570    // **Validates: Requirements 8.5**
571    // For any private key, the Debug trait output SHALL contain the key in hex format.
572    proptest! {
573        #![proptest_config(ProptestConfig::with_cases(100))]
574        #[test]
575        fn property_debug_output_format(bytes in valid_private_key_bytes()) {
576            let key = PrivateKey::from_bytes(bytes).unwrap();
577            let debug_output = format!("{:?}", key);
578            let hex_output = key.to_hex();
579            let decimal_output = key.to_decimal();
580
581            // Debug output should contain both hex and decimal
582            let expected = format!("PrivateKey {{ hex: {}, decimal: {} }}", hex_output, decimal_output);
583            prop_assert_eq!(&debug_output, &expected);
584        }
585    }
586
587    // Test decimal conversion
588    #[test]
589    fn test_decimal_conversion() {
590        // Key = 1
591        let key = PrivateKey::from_hex(
592            "0000000000000000000000000000000000000000000000000000000000000001",
593        )
594        .unwrap();
595        assert_eq!(key.to_decimal(), "1");
596
597        // Key = 256
598        let key = PrivateKey::from_hex(
599            "0000000000000000000000000000000000000000000000000000000000000100",
600        )
601        .unwrap();
602        assert_eq!(key.to_decimal(), "256");
603
604        // Key = 65536
605        let key = PrivateKey::from_hex(
606            "0000000000000000000000000000000000000000000000000000000000010000",
607        )
608        .unwrap();
609        assert_eq!(key.to_decimal(), "65536");
610    }
611
612    // ==================== Known Test Vectors ====================
613
614    /// Test vector from Bitcoin Wiki
615    /// https://en.bitcoin.it/wiki/Wallet_import_format
616    #[test]
617    fn test_vector_wif_bitcoin_wiki() {
618        // Private key: 0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D
619        // WIF (uncompressed): 5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ
620        let hex = "0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D";
621        let expected_wif_uncompressed = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ";
622
623        let key = PrivateKey::from_hex(hex).unwrap();
624
625        // Import from WIF should work
626        let from_wif = PrivateKey::from_wif(expected_wif_uncompressed).unwrap();
627        assert_eq!(key, from_wif);
628    }
629
630    /// Test vector: Key value 1 (minimum valid key)
631    #[test]
632    fn test_vector_key_one() {
633        let hex = "0000000000000000000000000000000000000000000000000000000000000001";
634        let key = PrivateKey::from_hex(hex).unwrap();
635
636        // Verify hex round-trip
637        assert_eq!(key.to_hex(), hex.to_lowercase());
638
639        // Verify bytes
640        let mut expected_bytes = [0u8; 32];
641        expected_bytes[31] = 1;
642        assert_eq!(key.to_bytes(), expected_bytes);
643
644        // Verify public key derivation works
645        let public_key = key.public_key();
646        let compressed = public_key.to_compressed();
647        assert_eq!(compressed.len(), 33);
648        assert!(compressed[0] == 0x02 || compressed[0] == 0x03);
649    }
650
651    /// Test vector: Known public key derivation
652    /// Private key 1 should produce a specific public key
653    #[test]
654    fn test_vector_pubkey_derivation() {
655        let hex = "0000000000000000000000000000000000000000000000000000000000000001";
656        let key = PrivateKey::from_hex(hex).unwrap();
657        let public_key = key.public_key();
658
659        // Known compressed public key for private key = 1
660        // 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
661        let compressed_hex = public_key.to_hex(crate::public_key::PublicKeyFormat::Compressed);
662        assert_eq!(
663            compressed_hex.to_lowercase(),
664            "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
665        );
666    }
667
668    /// Test WIF encoding for testnet
669    #[test]
670    fn test_vector_wif_testnet() {
671        let key = PrivateKey::from_hex(
672            "0000000000000000000000000000000000000000000000000000000000000001",
673        )
674        .unwrap();
675
676        let wif_mainnet = key.to_wif(Network::Mainnet);
677        let wif_testnet = key.to_wif(Network::Testnet);
678
679        // Mainnet compressed WIF starts with K or L
680        assert!(
681            wif_mainnet.starts_with('K') || wif_mainnet.starts_with('L'),
682            "Mainnet WIF should start with K or L"
683        );
684
685        // Testnet compressed WIF starts with c
686        assert!(
687            wif_testnet.starts_with('c'),
688            "Testnet WIF should start with c"
689        );
690
691        // Both should decode back to the same key
692        let from_mainnet = PrivateKey::from_wif(&wif_mainnet).unwrap();
693        let from_testnet = PrivateKey::from_wif(&wif_testnet).unwrap();
694        assert_eq!(key, from_mainnet);
695        assert_eq!(key, from_testnet);
696    }
697}