Skip to main content

krusty_kms_common/
secret_felt.rs

1//! A zeroizing wrapper for secret `Felt` values.
2//!
3//! `SecretFelt` wraps a `Felt` and ensures the underlying memory is
4//! overwritten with zeros when the value is dropped, preventing private
5//! key material from lingering in memory.
6
7use starknet_types_core::felt::Felt;
8use zeroize::Zeroize;
9
10/// A `Felt` that holds secret key material and is zeroized on drop.
11///
12/// This wrapper ensures that secret scalar values (private keys, seeds) are
13/// overwritten with zeros when they go out of scope, preventing them from
14/// lingering in memory after use.
15///
16/// # Security
17///
18/// - All access to the inner `Felt` must go through [`expose_secret()`], making
19///   every secret-access point explicit and greppable.
20/// - `Debug` output is redacted to prevent accidental logging of secrets.
21/// - `LowerHex` is delegated to `Felt` for intentional serialization (e.g.,
22///   `private_key_hex()` methods).
23/// - `Drop` uses a volatile write to prevent the compiler from optimizing
24///   away the zeroing.
25// Clone is needed by TongoKeyPair; note that cloning duplicates the secret.
26#[derive(Clone)]
27pub struct SecretFelt(Felt);
28
29impl SecretFelt {
30    /// Create a new `SecretFelt` from a `Felt`.
31    pub fn new(felt: Felt) -> Self {
32        Self(felt)
33    }
34
35    /// Access the secret value. Every call site is explicit and greppable.
36    pub fn expose_secret(&self) -> &Felt {
37        &self.0
38    }
39}
40
41impl From<Felt> for SecretFelt {
42    fn from(felt: Felt) -> Self {
43        Self(felt)
44    }
45}
46
47impl PartialEq for SecretFelt {
48    fn eq(&self, other: &Self) -> bool {
49        self.0 == other.0
50    }
51}
52
53impl PartialEq<Felt> for SecretFelt {
54    fn eq(&self, other: &Felt) -> bool {
55        self.0 == *other
56    }
57}
58
59impl Eq for SecretFelt {}
60
61impl core::fmt::Debug for SecretFelt {
62    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
63        f.write_str("SecretFelt(***)")
64    }
65}
66
67impl core::fmt::LowerHex for SecretFelt {
68    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
69        core::fmt::LowerHex::fmt(&self.0, f)
70    }
71}
72
73impl Zeroize for SecretFelt {
74    fn zeroize(&mut self) {
75        // Use a volatile write to prevent the compiler from optimizing
76        // away the zeroing. This is the same approach used by the `zeroize`
77        // crate for opaque types.
78        unsafe {
79            core::ptr::write_volatile(&mut self.0, Felt::ZERO);
80        }
81        core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
82    }
83}
84
85impl Drop for SecretFelt {
86    fn drop(&mut self) {
87        self.zeroize();
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_secret_felt_expose_secret() {
97        let secret = SecretFelt::new(Felt::from(42u64));
98        assert_eq!(*secret.expose_secret(), Felt::from(42u64));
99    }
100
101    #[test]
102    fn test_secret_felt_partial_eq() {
103        let a = SecretFelt::new(Felt::from(42u64));
104        let b = SecretFelt::new(Felt::from(42u64));
105        assert_eq!(a, b);
106        assert_eq!(a, Felt::from(42u64));
107    }
108
109    #[test]
110    fn test_secret_felt_debug_redacted() {
111        let secret = SecretFelt::new(Felt::from(42u64));
112        let debug = format!("{:?}", secret);
113        assert_eq!(debug, "SecretFelt(***)");
114        assert!(!debug.contains("42"));
115    }
116
117    #[test]
118    fn test_secret_felt_hex_format() {
119        let secret = SecretFelt::new(Felt::from(42u64));
120        let hex = format!("{:#x}", secret);
121        assert_eq!(hex, "0x2a");
122    }
123
124    #[test]
125    fn test_secret_felt_clone() {
126        let a = SecretFelt::new(Felt::from(42u64));
127        let b = a.clone();
128        assert_eq!(a, b);
129    }
130
131    #[test]
132    fn test_secret_felt_from() {
133        let secret: SecretFelt = Felt::from(99u64).into();
134        assert_eq!(*secret.expose_secret(), Felt::from(99u64));
135    }
136
137    #[test]
138    fn test_secret_felt_zeroize() {
139        let mut secret = SecretFelt::new(Felt::from(42u64));
140        secret.zeroize();
141        assert_eq!(*secret.expose_secret(), Felt::ZERO);
142    }
143}