Skip to main content

btc_keygen/
keygen.rs

1use crate::Error;
2use crate::entropy::{EntropyError, EntropySource};
3use crate::secret::SecretKeyHex;
4use secp256k1::SecretKey;
5use zeroize::{Zeroize, ZeroizeOnDrop};
6
7/// A validated secp256k1 private key that zeroizes its bytes on drop.
8///
9/// Created by [`generate`](crate::generate). The key is guaranteed to be a
10/// valid scalar in the range `[1, n-1]` where `n` is the secp256k1 curve order.
11///
12/// When this value goes out of scope, the underlying bytes are securely
13/// overwritten with zeros to prevent secrets from lingering in memory.
14///
15/// The 32 bytes live in a heap buffer behind a `Box`, so moving a `PrivateKey`
16/// moves a pointer rather than memcpying the key into a fresh stack slot that
17/// nothing would ever erase. Constructors fill that buffer in place for the
18/// same reason: the key material is never staged in a bare `[u8; 32]` local.
19#[derive(Zeroize, ZeroizeOnDrop)]
20pub struct PrivateKey {
21    // Boxed so that moving a PrivateKey moves a pointer: the bytes are written
22    // once and erased once, at the address they were born.
23    bytes: Box<[u8; 32]>,
24}
25
26impl PrivateKey {
27    /// A zeroed buffer for a constructor to fill in place.
28    ///
29    /// Private, and deliberately not `pub(crate)`: the value is not a valid key
30    /// until [`Self::validated`] has approved it.
31    fn zeroed() -> Self {
32        Self {
33            bytes: Box::new([0u8; 32]),
34        }
35    }
36
37    /// Consumes a filled buffer and enforces the scalar invariant, so no
38    /// unvalidated `PrivateKey` can escape this module.
39    fn validated(self) -> Result<PrivateKey, Error> {
40        if !is_valid_key(self.as_bytes()) {
41            return Err(Error("not a valid secp256k1 scalar".into()));
42        }
43        Ok(self)
44    }
45
46    /// Returns a reference to the raw 32-byte private key.
47    pub fn as_bytes(&self) -> &[u8; 32] {
48        &self.bytes
49    }
50
51    /// Converts the private key into a [`secp256k1::SecretKey`] for use with
52    /// the `secp256k1` crate directly.
53    ///
54    /// The returned type is outside this crate's erasure guarantees:
55    /// `SecretKey` is `Copy`, does not erase itself when dropped, and its
56    /// `non_secure_erase` is best-effort. Keep the value short-lived, erase it
57    /// by hand, and treat every copy of it as key material.
58    pub fn to_secret_key(&self) -> SecretKey {
59        SecretKey::from_byte_array(*self.as_bytes())
60            .expect("PrivateKey always holds a validated scalar")
61    }
62
63    /// Encodes the key as 64 lowercase hexadecimal ASCII bytes.
64    ///
65    /// The result is a [`SecretKeyHex`]: it erases itself on drop and redacts
66    /// its `Debug` output. The digits are written straight into that buffer, so
67    /// encoding a key allocates nothing beyond the buffer itself and leaves no
68    /// unerased temporaries on the heap.
69    ///
70    /// # Example
71    ///
72    /// ```
73    /// let hex = "0000000000000000000000000000000000000000000000000000000000000001";
74    /// let key = btc_keygen::PrivateKey::from_hex(hex)?;
75    /// assert_eq!(key.to_hex().expose_str(), hex);
76    /// # Ok::<(), btc_keygen::Error>(())
77    /// ```
78    #[must_use]
79    pub fn to_hex(&self) -> SecretKeyHex {
80        const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
81
82        let mut hex = SecretKeyHex::zeroed();
83        let digits = hex.bytes_mut();
84        for (i, byte) in self.bytes.iter().enumerate() {
85            digits[i * 2] = HEX_DIGITS[usize::from(byte >> 4)];
86            digits[i * 2 + 1] = HEX_DIGITS[usize::from(byte & 0x0f)];
87        }
88        hex
89    }
90
91    /// Creates a `PrivateKey` from 32 raw bytes, validating that they form a
92    /// valid secp256k1 scalar.
93    ///
94    /// Use this when you have your own source of private key material (for
95    /// example, physical entropy like dice rolls converted to hex) and want
96    /// to skip OS entropy generation.
97    ///
98    /// `bytes` is `Copy`, so the caller keeps its own array; erasing that copy
99    /// is the caller's job. This function erases the copy it receives.
100    ///
101    /// # Errors
102    ///
103    /// Returns [`Error`](crate::Error) if `bytes` is zero or greater than or
104    /// equal to the secp256k1 curve order `n`.
105    ///
106    /// # Example
107    ///
108    /// ```
109    /// let mut bytes = [0u8; 32];
110    /// bytes[31] = 0x01;
111    /// let key = btc_keygen::PrivateKey::from_bytes(bytes)?;
112    /// # Ok::<(), btc_keygen::Error>(())
113    /// ```
114    pub fn from_bytes(mut bytes: [u8; 32]) -> Result<PrivateKey, Error> {
115        let mut key = Self::zeroed();
116        key.bytes.copy_from_slice(&bytes);
117        bytes.zeroize();
118        key.validated()
119    }
120
121    /// Creates a `PrivateKey` from a 64-character hexadecimal string,
122    /// validating that the decoded bytes form a valid secp256k1 scalar.
123    ///
124    /// Convenience wrapper around [`from_bytes`](Self::from_bytes) for callers
125    /// that have the key material as a hex string (for example, from a CLI
126    /// argument or a text file).
127    ///
128    /// # Errors
129    ///
130    /// Returns [`Error`](crate::Error) if:
131    ///
132    /// - `hex` is not exactly 64 characters long.
133    /// - `hex` contains a character that is not a valid hexadecimal digit.
134    /// - The decoded bytes are zero or greater than or equal to the secp256k1
135    ///   curve order `n`.
136    ///
137    /// # Example
138    ///
139    /// ```
140    /// let hex = "0000000000000000000000000000000000000000000000000000000000000001";
141    /// let key = btc_keygen::PrivateKey::from_hex(hex)?;
142    /// # Ok::<(), btc_keygen::Error>(())
143    /// ```
144    pub fn from_hex(hex: &str) -> Result<PrivateKey, Error> {
145        if hex.len() != 64 {
146            return Err(Error(format!(
147                "expected 64 hex characters, got {}",
148                hex.len()
149            )));
150        }
151
152        // Decoded straight into the key's own buffer: no stack scratch array
153        // holds the assembled key, not even briefly.
154        let mut key = Self::zeroed();
155        for i in 0..32 {
156            key.bytes[i] = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16)
157                .map_err(|_| Error(format!("invalid hex at position {}", i * 2)))?;
158        }
159        key.validated()
160    }
161}
162
163/// Checks whether 32 bytes represent a valid secp256k1 private key.
164///
165/// A valid key is a scalar in `[1, n-1]` where `n` is the curve order.
166///
167/// Takes a reference so that testing a candidate does not copy it, and erases
168/// the `SecretKey` it builds internally.
169pub fn is_valid_key(bytes: &[u8; 32]) -> bool {
170    match SecretKey::from_byte_array(*bytes) {
171        Ok(mut key) => {
172            key.non_secure_erase();
173            true
174        }
175        Err(_) => false,
176    }
177}
178
179/// Generates a new private key using the provided entropy source.
180///
181/// Retries up to `MAX_RETRIES` times if the random bytes fall outside the
182/// valid secp256k1 scalar range. This is astronomically unlikely but handled
183/// for correctness.
184pub(crate) fn generate_with_entropy(
185    entropy: &dyn EntropySource,
186) -> Result<PrivateKey, EntropyError> {
187    for _ in 0..MAX_RETRIES {
188        // Filled in place: fresh entropy never lands in a stack array.
189        let mut key = PrivateKey::zeroed();
190        entropy.fill_bytes(&mut key.bytes[..])?;
191
192        if is_valid_key(key.as_bytes()) {
193            return Ok(key);
194        }
195        // Invalid scalar: dropping `key` zeroizes the buffer before the retry.
196    }
197
198    Err(EntropyError(
199        "failed to generate valid key after maximum retries".into(),
200    ))
201}
202
203/// Generates a new Bitcoin private key using OS-provided cryptographic randomness.
204///
205/// Returns a [`PrivateKey`] containing a validated secp256k1 scalar. The
206/// entropy comes from the operating system's CSPRNG (`getrandom` syscall on
207/// Linux, `getentropy` on macOS, `BCryptGenRandom` on Windows).
208///
209/// # Errors
210///
211/// Returns [`Error`](crate::Error) if the OS random number generator fails.
212///
213/// # Example
214///
215/// ```no_run
216/// let key = btc_keygen::generate().expect("key generation failed");
217/// ```
218pub fn generate() -> Result<PrivateKey, crate::Error> {
219    generate_with_entropy(&crate::entropy::OsEntropy).map_err(crate::Error::from)
220}
221
222/// Maximum retry attempts for key generation. A safety net against infinite
223/// loops: the probability of needing even one retry is ~10^-38.
224const MAX_RETRIES: u32 = 32;
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use crate::entropy::{FailingEntropy, FixedEntropy};
230
231    /// secp256k1 curve order n.
232    const CURVE_ORDER: [u8; 32] = [
233        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
234        0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36,
235        0x41, 0x41,
236    ];
237
238    /// n - 1: the maximum valid private key.
239    fn curve_order_minus_one() -> [u8; 32] {
240        let mut bytes = CURVE_ORDER;
241        bytes[31] -= 1;
242        bytes
243    }
244
245    /// n + 1: one above the curve order.
246    fn curve_order_plus_one() -> [u8; 32] {
247        let mut bytes = CURVE_ORDER;
248        bytes[31] += 1;
249        bytes
250    }
251
252    // ---------------------------------------------------------------
253    // 6.1: Private key boundary validation
254    // ---------------------------------------------------------------
255
256    #[test]
257    fn test_zero_key_rejected() {
258        let zero = [0u8; 32];
259        assert!(!is_valid_key(&zero), "zero must not be a valid private key");
260    }
261
262    #[test]
263    fn test_one_key_valid() {
264        let mut one = [0u8; 32];
265        one[31] = 1;
266        assert!(is_valid_key(&one), "scalar 1 must be a valid private key");
267    }
268
269    #[test]
270    fn test_curve_order_minus_one_valid() {
271        let n_minus_1 = curve_order_minus_one();
272        assert!(
273            is_valid_key(&n_minus_1),
274            "n-1 must be a valid private key (maximum scalar)"
275        );
276    }
277
278    #[test]
279    fn test_curve_order_rejected() {
280        assert!(
281            !is_valid_key(&CURVE_ORDER),
282            "the curve order n itself must not be a valid private key"
283        );
284    }
285
286    #[test]
287    fn test_curve_order_plus_one_rejected() {
288        let n_plus_1 = curve_order_plus_one();
289        assert!(
290            !is_valid_key(&n_plus_1),
291            "n+1 must not be a valid private key"
292        );
293    }
294
295    #[test]
296    fn test_all_ff_rejected() {
297        let all_ff = [0xFF; 32];
298        assert!(
299            !is_valid_key(&all_ff),
300            "all 0xFF bytes exceed curve order and must be rejected"
301        );
302    }
303
304    #[test]
305    fn test_valid_midrange_key() {
306        // A known midrange value well within [1, n-1].
307        let mut key = [0u8; 32];
308        key[0] = 0x0A;
309        key[31] = 0x0B;
310        assert!(is_valid_key(&key));
311    }
312
313    // ---------------------------------------------------------------
314    // 6.2: Deterministic key generation with injectable entropy
315    // ---------------------------------------------------------------
316
317    #[test]
318    fn test_fixed_entropy_produces_expected_key() {
319        let mut key_bytes = [0u8; 32];
320        key_bytes[31] = 0x01; // scalar = 1, valid
321        let entropy = FixedEntropy::new(key_bytes.to_vec());
322
323        let key = generate_with_entropy(&entropy).expect("generation should succeed");
324        assert_eq!(key.as_bytes(), &key_bytes);
325    }
326
327    #[test]
328    fn test_different_entropy_produces_different_keys() {
329        let mut bytes_a = [0u8; 32];
330        bytes_a[31] = 0x01;
331        let mut bytes_b = [0u8; 32];
332        bytes_b[31] = 0x02;
333
334        let key_a = generate_with_entropy(&FixedEntropy::new(bytes_a.to_vec())).unwrap();
335        let key_b = generate_with_entropy(&FixedEntropy::new(bytes_b.to_vec())).unwrap();
336
337        assert_ne!(key_a.as_bytes(), key_b.as_bytes());
338    }
339
340    #[test]
341    fn test_same_entropy_produces_same_key() {
342        let mut key_bytes = [0u8; 32];
343        key_bytes[31] = 0x05;
344
345        let key1 = generate_with_entropy(&FixedEntropy::new(key_bytes.to_vec())).unwrap();
346        let key2 = generate_with_entropy(&FixedEntropy::new(key_bytes.to_vec())).unwrap();
347
348        assert_eq!(key1.as_bytes(), key2.as_bytes());
349    }
350
351    #[test]
352    fn test_invalid_entropy_triggers_retry() {
353        // First 32 bytes: the curve order (invalid).
354        // Next 32 bytes: scalar 1 (valid).
355        let mut data = CURVE_ORDER.to_vec();
356        let mut valid = [0u8; 32];
357        valid[31] = 0x01;
358        data.extend_from_slice(&valid);
359
360        let entropy = FixedEntropy::new(data);
361        let key = generate_with_entropy(&entropy).expect("should succeed after retry");
362        assert_eq!(key.as_bytes(), &valid);
363    }
364
365    #[test]
366    fn test_entropy_failure_propagates() {
367        let result = generate_with_entropy(&FailingEntropy);
368        assert!(result.is_err(), "entropy failure must propagate as error");
369    }
370
371    #[test]
372    fn test_generated_key_converts_to_secret_key() {
373        let mut key_bytes = [0u8; 32];
374        key_bytes[31] = 0x01;
375        let key = generate_with_entropy(&FixedEntropy::new(key_bytes.to_vec())).unwrap();
376
377        // Must not panic: validates the internal invariant.
378        let _sk = key.to_secret_key();
379    }
380
381    // ---------------------------------------------------------------
382    // 6.12: PrivateKey::from_bytes validation
383    // ---------------------------------------------------------------
384
385    #[test]
386    fn test_from_bytes_accepts_valid_scalar() {
387        let mut bytes = [0u8; 32];
388        bytes[31] = 0x01;
389        let key = PrivateKey::from_bytes(bytes).expect("scalar 1 must be accepted");
390        assert_eq!(key.as_bytes(), &bytes);
391    }
392
393    #[test]
394    fn test_from_bytes_rejects_invalid_scalar() {
395        let zero = [0u8; 32];
396        assert!(
397            PrivateKey::from_bytes(zero).is_err(),
398            "invalid scalar must be rejected"
399        );
400    }
401
402    // ---------------------------------------------------------------
403    // 6.13: PrivateKey::from_hex parsing and validation
404    // ---------------------------------------------------------------
405
406    #[test]
407    fn test_from_hex_accepts_valid_scalar_one() {
408        let hex = "0000000000000000000000000000000000000000000000000000000000000001";
409        let key = PrivateKey::from_hex(hex).expect("scalar 1 must be accepted");
410        let mut expected = [0u8; 32];
411        expected[31] = 0x01;
412        assert_eq!(key.as_bytes(), &expected);
413    }
414
415    #[test]
416    fn test_to_hex_round_trips_from_hex() {
417        let hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
418        let key = PrivateKey::from_hex(hex).unwrap();
419        assert_eq!(key.to_hex().expose_str(), hex);
420    }
421
422    #[test]
423    fn test_to_hex_is_lowercase_and_64_bytes() {
424        let key = PrivateKey::from_hex(
425            "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140",
426        )
427        .unwrap();
428        let hex = key.to_hex();
429        assert_eq!(hex.expose_bytes().len(), 64);
430        assert_eq!(
431            hex.expose_str(),
432            "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"
433        );
434    }
435
436    #[test]
437    fn test_from_hex_rejects_wrong_length() {
438        assert!(
439            PrivateKey::from_hex("01").is_err(),
440            "hex not exactly 64 chars must be rejected"
441        );
442    }
443
444    #[test]
445    fn test_from_hex_rejects_non_hex_characters() {
446        let hex = "zz00000000000000000000000000000000000000000000000000000000000001";
447        assert!(
448            PrivateKey::from_hex(hex).is_err(),
449            "non-hex characters must be rejected"
450        );
451    }
452
453    #[test]
454    fn test_from_hex_propagates_invalid_scalar() {
455        let hex = "0000000000000000000000000000000000000000000000000000000000000000";
456        assert!(
457            PrivateKey::from_hex(hex).is_err(),
458            "invalid scalar from from_bytes must propagate"
459        );
460    }
461}