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}