txgate_crypto/keys.rs
1//! Cryptographic key types with secure memory handling.
2//!
3//! This module provides key types that ensure sensitive key material is:
4//! - Zeroized on drop to prevent memory leaks
5//! - Never exposed in debug output
6//! - Compared in constant time to prevent timing attacks
7//!
8//! # Security
9//!
10//! All key types in this module are designed with defense-in-depth:
11//! - `SecretKey` does not implement `Clone` to prevent accidental duplication
12//! - Debug output is redacted to prevent logging of key material
13//! - Comparison uses constant-time algorithms
14
15use rand::RngCore;
16use subtle::ConstantTimeEq;
17use zeroize::{Zeroize, ZeroizeOnDrop};
18
19/// The length of a secret key in bytes.
20pub const SECRET_KEY_LEN: usize = 32;
21
22/// A 32-byte secret key with automatic zeroization.
23///
24/// # Security
25///
26/// This type ensures that key material is securely erased from memory
27/// when the value is dropped. Key material never appears in debug output.
28///
29/// **Important**: This type intentionally does not implement `Clone` to
30/// prevent accidental duplication of secret key material. Keys must be
31/// moved, not copied.
32///
33/// # Example
34///
35/// ```
36/// use txgate_crypto::keys::SecretKey;
37///
38/// // Generate a new random key
39/// let key = SecretKey::generate();
40///
41/// // Keys are automatically zeroized when dropped
42/// drop(key);
43/// ```
44#[derive(Zeroize, ZeroizeOnDrop)]
45pub struct SecretKey {
46 bytes: [u8; SECRET_KEY_LEN],
47}
48
49impl SecretKey {
50 /// Create a new `SecretKey` from raw bytes.
51 ///
52 /// # Arguments
53 /// * `bytes` - The 32-byte secret key material
54 ///
55 /// # Security
56 /// The input bytes are copied into the `SecretKey`. The caller should
57 /// zeroize the original bytes if they are no longer needed.
58 ///
59 /// # Example
60 ///
61 /// ```
62 /// use txgate_crypto::keys::SecretKey;
63 /// use zeroize::Zeroize;
64 ///
65 /// let mut raw_bytes = [0x42u8; 32];
66 /// let key = SecretKey::new(raw_bytes);
67 ///
68 /// // Zeroize the original bytes for security
69 /// raw_bytes.zeroize();
70 /// ```
71 #[must_use]
72 pub const fn new(bytes: [u8; SECRET_KEY_LEN]) -> Self {
73 Self { bytes }
74 }
75
76 /// Generate a new random `SecretKey` using a cryptographically secure RNG.
77 ///
78 /// This uses the operating system's secure random number generator
79 /// (`OsRng`) to generate the key material.
80 ///
81 /// # Example
82 ///
83 /// ```
84 /// use txgate_crypto::keys::SecretKey;
85 ///
86 /// let key = SecretKey::generate();
87 /// assert_eq!(key.len(), 32);
88 /// ```
89 #[must_use]
90 pub fn generate() -> Self {
91 let mut bytes = [0u8; SECRET_KEY_LEN];
92 rand::rngs::OsRng.fill_bytes(&mut bytes);
93 Self { bytes }
94 }
95
96 /// Expose the raw bytes for cryptographic operations.
97 ///
98 /// # Security
99 ///
100 /// The returned reference must not be stored or copied beyond the
101 /// immediate cryptographic operation. Misuse can lead to key material
102 /// remaining in memory longer than intended.
103 ///
104 /// # Example
105 ///
106 /// ```
107 /// use txgate_crypto::keys::SecretKey;
108 ///
109 /// let key = SecretKey::generate();
110 /// let bytes = key.as_bytes();
111 /// assert_eq!(bytes.len(), 32);
112 /// ```
113 #[must_use]
114 pub const fn as_bytes(&self) -> &[u8; SECRET_KEY_LEN] {
115 &self.bytes
116 }
117
118 /// Get the length of the secret key in bytes.
119 ///
120 /// Always returns 32 for this key type.
121 #[must_use]
122 pub const fn len(&self) -> usize {
123 SECRET_KEY_LEN
124 }
125
126 /// Returns false (`SecretKey` is never empty).
127 ///
128 /// This method exists for API consistency and always returns `false`.
129 #[must_use]
130 pub const fn is_empty(&self) -> bool {
131 false
132 }
133
134 /// Convert this `SecretKey` into a `k256::SecretKey` for secp256k1 operations.
135 ///
136 /// # Security
137 ///
138 /// This method consumes `self` to ensure the key material exists in only
139 /// one place. After calling this method, the original `SecretKey` is
140 /// zeroized and cannot be used.
141 ///
142 /// # Errors
143 ///
144 /// Returns an error if the bytes do not represent a valid secp256k1 scalar
145 /// (e.g., if the value is zero or greater than the curve order).
146 ///
147 /// # Example
148 ///
149 /// ```
150 /// use txgate_crypto::keys::SecretKey;
151 ///
152 /// let key = SecretKey::generate();
153 /// match key.into_k256() {
154 /// Ok(k256_key) => {
155 /// // Use k256_key for signing
156 /// }
157 /// Err(e) => {
158 /// // Handle invalid key (very rare with generated keys)
159 /// }
160 /// }
161 /// ```
162 pub fn into_k256(self) -> Result<k256::SecretKey, SecretKeyError> {
163 k256::SecretKey::from_bytes((&self.bytes).into()).map_err(|_| SecretKeyError::InvalidKey)
164 }
165}
166
167/// Errors related to secret key operations.
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum SecretKeyError {
170 /// The provided bytes do not represent a valid secret key.
171 InvalidKey,
172}
173
174impl std::fmt::Display for SecretKeyError {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 match self {
177 Self::InvalidKey => write!(f, "invalid secret key bytes"),
178 }
179 }
180}
181
182impl std::error::Error for SecretKeyError {}
183
184// Prevent accidental debug printing of secrets
185impl std::fmt::Debug for SecretKey {
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 f.write_str("SecretKey([REDACTED])")
188 }
189}
190
191// Constant-time equality comparison to prevent timing attacks
192impl PartialEq for SecretKey {
193 fn eq(&self, other: &Self) -> bool {
194 self.bytes.ct_eq(&other.bytes).into()
195 }
196}
197
198impl Eq for SecretKey {}
199
200impl From<[u8; SECRET_KEY_LEN]> for SecretKey {
201 fn from(bytes: [u8; SECRET_KEY_LEN]) -> Self {
202 Self::new(bytes)
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 #![allow(clippy::expect_used)]
209 #![allow(clippy::unwrap_used)]
210
211 use super::*;
212
213 #[test]
214 fn test_new_creates_key_with_correct_bytes() {
215 let bytes = [0x42u8; SECRET_KEY_LEN];
216 let key = SecretKey::new(bytes);
217 assert_eq!(key.as_bytes(), &bytes);
218 }
219
220 #[test]
221 fn test_generate_produces_unique_keys() {
222 let key1 = SecretKey::generate();
223 let key2 = SecretKey::generate();
224 // Extremely unlikely to generate the same key twice
225 assert_ne!(key1.as_bytes(), key2.as_bytes());
226 }
227
228 #[test]
229 fn test_len_returns_32() {
230 let key = SecretKey::generate();
231 assert_eq!(key.len(), 32);
232 }
233
234 #[test]
235 fn test_is_empty_returns_false() {
236 let key = SecretKey::generate();
237 assert!(!key.is_empty());
238 }
239
240 #[test]
241 fn test_debug_does_not_expose_key_material() {
242 let key = SecretKey::new([0xABu8; SECRET_KEY_LEN]);
243 let debug_output = format!("{key:?}");
244 assert_eq!(debug_output, "SecretKey([REDACTED])");
245 // Ensure the actual key bytes don't appear in output
246 assert!(!debug_output.contains("ab"));
247 assert!(!debug_output.contains("AB"));
248 assert!(!debug_output.contains("171")); // 0xAB as decimal
249 }
250
251 #[test]
252 fn test_partial_eq_is_constant_time() {
253 // This test verifies the PartialEq implementation works correctly.
254 // Verifying constant-time behavior programmatically is difficult,
255 // but we can at least verify correctness.
256 let key1 = SecretKey::new([0x42u8; SECRET_KEY_LEN]);
257 let key2 = SecretKey::new([0x42u8; SECRET_KEY_LEN]);
258 let key3 = SecretKey::new([0x43u8; SECRET_KEY_LEN]);
259
260 assert_eq!(key1, key2);
261 assert_ne!(key1, key3);
262 }
263
264 #[test]
265 fn test_partial_eq_single_bit_difference() {
266 let mut bytes1 = [0x00u8; SECRET_KEY_LEN];
267 let mut bytes2 = [0x00u8; SECRET_KEY_LEN];
268 bytes2[0] = 0x01; // Single bit difference
269
270 let key1 = SecretKey::new(bytes1);
271 let key2 = SecretKey::new(bytes2);
272
273 assert_ne!(key1, key2);
274
275 // Clean up
276 bytes1.zeroize();
277 bytes2.zeroize();
278 }
279
280 #[test]
281 fn test_from_array() {
282 let bytes = [0x42u8; SECRET_KEY_LEN];
283 let key: SecretKey = bytes.into();
284 assert_eq!(key.as_bytes(), &bytes);
285 }
286
287 #[test]
288 fn test_into_k256_success() {
289 // Generate a key - should almost always be valid
290 let key = SecretKey::generate();
291 let result = key.into_k256();
292 assert!(result.is_ok());
293 }
294
295 #[test]
296 fn test_into_k256_invalid_zero_key() {
297 // Zero is not a valid secp256k1 scalar
298 let key = SecretKey::new([0u8; SECRET_KEY_LEN]);
299 let result = key.into_k256();
300 assert!(result.is_err());
301 assert_eq!(result.unwrap_err(), SecretKeyError::InvalidKey);
302 }
303
304 #[test]
305 fn test_secret_key_error_display() {
306 let err = SecretKeyError::InvalidKey;
307 assert_eq!(format!("{err}"), "invalid secret key bytes");
308 }
309
310 // Compile-time check: SecretKey should NOT implement Clone
311 // If this trait bound compiles, the test fails.
312 // We verify by ensuring Clone is not implemented via a negative test.
313 #[test]
314 fn test_no_clone_implementation() {
315 fn assert_not_clone<T>() {
316 // This function exists purely for documentation.
317 // The actual check is that SecretKey doesn't implement Clone,
318 // which we verify by NOT being able to call .clone() on it.
319 }
320 assert_not_clone::<SecretKey>();
321
322 // The following would fail to compile if uncommented,
323 // proving that Clone is not implemented:
324 // let key = SecretKey::generate();
325 // let _cloned = key.clone(); // ERROR: Clone not implemented
326 }
327
328 // Test that SecretKey is Send and Sync (safe to transfer between threads)
329 #[test]
330 fn test_send_sync() {
331 fn assert_send_sync<T: Send + Sync>() {}
332 assert_send_sync::<SecretKey>();
333 }
334}