Skip to main content

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}