Skip to main content

btc_keygen/
keygen.rs

1use crate::entropy::{EntropyError, EntropySource};
2use secp256k1::SecretKey;
3use zeroize::{Zeroize, ZeroizeOnDrop};
4
5/// A validated secp256k1 private key that zeroizes its bytes on drop.
6///
7/// Created by [`generate`](crate::generate). The key is guaranteed to be a
8/// valid scalar in the range `[1, n-1]` where `n` is the secp256k1 curve order.
9///
10/// When this value goes out of scope, the underlying bytes are securely
11/// overwritten with zeros to prevent secrets from lingering in memory.
12#[derive(Zeroize, ZeroizeOnDrop)]
13pub struct PrivateKey {
14    bytes: [u8; 32],
15}
16
17impl PrivateKey {
18    /// Returns a reference to the raw 32-byte private key.
19    pub fn as_bytes(&self) -> &[u8; 32] {
20        &self.bytes
21    }
22
23    /// Converts the private key into a [`secp256k1::SecretKey`] for use with
24    /// the `secp256k1` crate directly.
25    pub fn to_secret_key(&self) -> SecretKey {
26        SecretKey::from_slice(&self.bytes).expect("PrivateKey always holds a validated scalar")
27    }
28
29    /// Creates a `PrivateKey` from raw bytes without entropy generation.
30    ///
31    /// Caller must ensure bytes represent a valid secp256k1 scalar.
32    #[cfg(test)]
33    pub(crate) fn from_bytes(bytes: [u8; 32]) -> Self {
34        Self { bytes }
35    }
36}
37
38/// Checks whether 32 bytes represent a valid secp256k1 private key.
39///
40/// A valid key is a scalar in `[1, n-1]` where `n` is the curve order.
41pub fn is_valid_key(bytes: &[u8; 32]) -> bool {
42    SecretKey::from_slice(bytes).is_ok()
43}
44
45/// Generates a new private key using the provided entropy source.
46///
47/// Retries up to `MAX_RETRIES` times if the random bytes fall outside the
48/// valid secp256k1 scalar range. This is astronomically unlikely but handled
49/// for correctness.
50pub(crate) fn generate_with_entropy(
51    entropy: &dyn EntropySource,
52) -> Result<PrivateKey, EntropyError> {
53    for _ in 0..MAX_RETRIES {
54        let mut bytes = [0u8; 32];
55        entropy.fill_bytes(&mut bytes)?;
56
57        if is_valid_key(&bytes) {
58            return Ok(PrivateKey { bytes });
59        }
60        // Invalid scalar — zeroize and retry.
61        bytes.zeroize();
62    }
63
64    Err(EntropyError(
65        "failed to generate valid key after maximum retries".into(),
66    ))
67}
68
69/// Generates a new Bitcoin private key using OS-provided cryptographic randomness.
70///
71/// Returns a [`PrivateKey`] containing a validated secp256k1 scalar. The
72/// entropy comes from the operating system's CSPRNG (`getrandom` syscall on
73/// Linux, `getentropy` on macOS, `BCryptGenRandom` on Windows).
74///
75/// # Errors
76///
77/// Returns [`Error`](crate::Error) if the OS random number generator fails.
78///
79/// # Example
80///
81/// ```no_run
82/// let key = btc_keygen::generate().expect("key generation failed");
83/// ```
84pub fn generate() -> Result<PrivateKey, crate::Error> {
85    generate_with_entropy(&crate::entropy::OsEntropy).map_err(crate::Error::from)
86}
87
88/// Maximum retry attempts for key generation. A safety net against infinite
89/// loops — the probability of needing even one retry is ~10^-38.
90const MAX_RETRIES: u32 = 32;
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::entropy::{FailingEntropy, FixedEntropy};
96
97    /// secp256k1 curve order n.
98    const CURVE_ORDER: [u8; 32] = [
99        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
100        0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36,
101        0x41, 0x41,
102    ];
103
104    /// n - 1: the maximum valid private key.
105    fn curve_order_minus_one() -> [u8; 32] {
106        let mut bytes = CURVE_ORDER;
107        // Subtract 1 from the last byte.
108        bytes[31] -= 1;
109        bytes
110    }
111
112    /// n + 1: one above the curve order.
113    fn curve_order_plus_one() -> [u8; 32] {
114        let mut bytes = CURVE_ORDER;
115        // Add 1 to the last byte.
116        bytes[31] += 1;
117        bytes
118    }
119
120    // ---------------------------------------------------------------
121    // 6.1 — Private key boundary validation
122    // ---------------------------------------------------------------
123
124    #[test]
125    fn test_zero_key_rejected() {
126        let zero = [0u8; 32];
127        assert!(!is_valid_key(&zero), "zero must not be a valid private key");
128    }
129
130    #[test]
131    fn test_one_key_valid() {
132        let mut one = [0u8; 32];
133        one[31] = 1;
134        assert!(is_valid_key(&one), "scalar 1 must be a valid private key");
135    }
136
137    #[test]
138    fn test_curve_order_minus_one_valid() {
139        let n_minus_1 = curve_order_minus_one();
140        assert!(
141            is_valid_key(&n_minus_1),
142            "n-1 must be a valid private key (maximum scalar)"
143        );
144    }
145
146    #[test]
147    fn test_curve_order_rejected() {
148        assert!(
149            !is_valid_key(&CURVE_ORDER),
150            "the curve order n itself must not be a valid private key"
151        );
152    }
153
154    #[test]
155    fn test_curve_order_plus_one_rejected() {
156        let n_plus_1 = curve_order_plus_one();
157        assert!(
158            !is_valid_key(&n_plus_1),
159            "n+1 must not be a valid private key"
160        );
161    }
162
163    #[test]
164    fn test_all_ff_rejected() {
165        let all_ff = [0xFF; 32];
166        assert!(
167            !is_valid_key(&all_ff),
168            "all 0xFF bytes exceed curve order and must be rejected"
169        );
170    }
171
172    #[test]
173    fn test_valid_midrange_key() {
174        // A known midrange value well within [1, n-1].
175        let mut key = [0u8; 32];
176        key[0] = 0x0A;
177        key[31] = 0x0B;
178        assert!(is_valid_key(&key));
179    }
180
181    // ---------------------------------------------------------------
182    // 6.2 — Deterministic key generation with injectable entropy
183    // ---------------------------------------------------------------
184
185    #[test]
186    fn test_fixed_entropy_produces_expected_key() {
187        let mut key_bytes = [0u8; 32];
188        key_bytes[31] = 0x01; // scalar = 1, valid
189        let entropy = FixedEntropy::new(key_bytes.to_vec());
190
191        let key = generate_with_entropy(&entropy).expect("generation should succeed");
192        assert_eq!(key.as_bytes(), &key_bytes);
193    }
194
195    #[test]
196    fn test_different_entropy_produces_different_keys() {
197        let mut bytes_a = [0u8; 32];
198        bytes_a[31] = 0x01;
199        let mut bytes_b = [0u8; 32];
200        bytes_b[31] = 0x02;
201
202        let key_a = generate_with_entropy(&FixedEntropy::new(bytes_a.to_vec())).unwrap();
203        let key_b = generate_with_entropy(&FixedEntropy::new(bytes_b.to_vec())).unwrap();
204
205        assert_ne!(key_a.as_bytes(), key_b.as_bytes());
206    }
207
208    #[test]
209    fn test_same_entropy_produces_same_key() {
210        let mut key_bytes = [0u8; 32];
211        key_bytes[31] = 0x05;
212
213        let key1 = generate_with_entropy(&FixedEntropy::new(key_bytes.to_vec())).unwrap();
214        let key2 = generate_with_entropy(&FixedEntropy::new(key_bytes.to_vec())).unwrap();
215
216        assert_eq!(key1.as_bytes(), key2.as_bytes());
217    }
218
219    #[test]
220    fn test_invalid_entropy_triggers_retry() {
221        // First 32 bytes: the curve order (invalid).
222        // Next 32 bytes: scalar 1 (valid).
223        let mut data = CURVE_ORDER.to_vec();
224        let mut valid = [0u8; 32];
225        valid[31] = 0x01;
226        data.extend_from_slice(&valid);
227
228        let entropy = FixedEntropy::new(data);
229        let key = generate_with_entropy(&entropy).expect("should succeed after retry");
230        assert_eq!(key.as_bytes(), &valid);
231    }
232
233    #[test]
234    fn test_entropy_failure_propagates() {
235        let result = generate_with_entropy(&FailingEntropy);
236        assert!(result.is_err(), "entropy failure must propagate as error");
237    }
238
239    #[test]
240    fn test_generated_key_converts_to_secret_key() {
241        let mut key_bytes = [0u8; 32];
242        key_bytes[31] = 0x01;
243        let key = generate_with_entropy(&FixedEntropy::new(key_bytes.to_vec())).unwrap();
244
245        // Must not panic — validates the internal invariant.
246        let _sk = key.to_secret_key();
247    }
248}