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}