|
| 1 | +use crate::felt::{Felt, FromStrError}; |
| 2 | +use rand::{CryptoRng, RngCore}; |
| 3 | +use subtle::ConstantTimeEq; |
| 4 | +use zeroize::{Zeroize, Zeroizing}; |
| 5 | + |
| 6 | +#[cfg(not(feature = "std"))] |
| 7 | +use super::alloc::{boxed::Box, string::String, vec::Vec}; |
| 8 | + |
| 9 | +/// A wrapper for a [Felt] that ensures the value is securely zeroized when dropped. |
| 10 | +/// |
| 11 | +/// This type provides secure handling of sensitive [Felt] values (like private keys) |
| 12 | +/// by ensuring that the memory is properly cleared when the value is no longer needed. |
| 13 | +#[derive(Eq)] |
| 14 | +pub struct SecretFelt(Box<Felt>); |
| 15 | + |
| 16 | +impl zeroize::DefaultIsZeroes for Felt {} |
| 17 | + |
| 18 | +impl Zeroize for SecretFelt { |
| 19 | + fn zeroize(&mut self) { |
| 20 | + self.0.zeroize(); |
| 21 | + } |
| 22 | +} |
| 23 | + |
| 24 | +impl Drop for SecretFelt { |
| 25 | + fn drop(&mut self) { |
| 26 | + self.zeroize(); |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +impl SecretFelt { |
| 31 | + /// Creates a new [SecretFelt] from a [Felt] value and zeroize the original. |
| 32 | + /// |
| 33 | + /// It takes a mutable reference to a [Felt] value, creates a copy, |
| 34 | + /// and then zeroize the original value to ensure it doesn't remain in memory. |
| 35 | + /// |
| 36 | + /// # Warning |
| 37 | + /// |
| 38 | + /// Avoid moving the secret [Felt] in memory and avoid intermediate |
| 39 | + /// operations between the [Felt] creation and the [SecretFelt] initialization |
| 40 | + /// in order to not leave any copies of the value in memory |
| 41 | + /// |
| 42 | + /// # Example |
| 43 | + /// |
| 44 | + /// ``` |
| 45 | + /// use starknet_types_core::felt::{Felt, secret_felt::SecretFelt}; |
| 46 | + /// |
| 47 | + /// let mut private_key = Felt::from_hex_unchecked("0x2d39148a92f479fb077389d"); |
| 48 | + /// let secret_felt = SecretFelt::from_felt(&mut private_key); |
| 49 | + /// // private_key is now zeroized (set to Felt::ZERO) |
| 50 | + /// ``` |
| 51 | + pub fn from_felt(secret_felt: &mut Felt) -> Self { |
| 52 | + let boxed_copy = Box::new(*secret_felt); |
| 53 | + secret_felt.zeroize(); |
| 54 | + Self(boxed_copy) |
| 55 | + } |
| 56 | + |
| 57 | + /// Creates a new [SecretFelt] from a hex String and zeroized the original String. |
| 58 | + /// |
| 59 | + /// |
| 60 | + /// # Warning |
| 61 | + /// Make sure the String is initialized in a secure way. |
| 62 | + /// e.g. read from a file. |
| 63 | + /// |
| 64 | + /// # Example |
| 65 | + /// ``` |
| 66 | + /// use std::fs; |
| 67 | + /// use starknet_types_core::felt::secret_felt::SecretFelt; |
| 68 | + /// use std::str::FromStr; |
| 69 | + /// |
| 70 | + /// let mut private_key = String::from_str("0x2d39148a92f479fb077389d").unwrap(); |
| 71 | + /// let secret_felt = SecretFelt::from_hex_string(&mut private_key).unwrap(); |
| 72 | + /// ``` |
| 73 | + pub fn from_hex_string(hex: &mut String) -> Result<Self, FromStrError> { |
| 74 | + let secret_felt = Felt::from_hex(hex)?; |
| 75 | + hex.zeroize(); |
| 76 | + Ok(Self(Box::new(secret_felt))) |
| 77 | + } |
| 78 | + |
| 79 | + /// Creates a new [SecretFelt] from its big-endian representation in a Vec<u8> of length 32. |
| 80 | + /// Internally it uses [from_bytes_be](Felt::from_bytes_be). |
| 81 | + /// The input will be zeroized after calling this function |
| 82 | + pub fn from_bytes_be(secret: &mut [u8; 32]) -> Self { |
| 83 | + let secret_felt = Self(Box::new(Felt::from_bytes_be(secret))); |
| 84 | + secret.zeroize(); |
| 85 | + secret_felt |
| 86 | + } |
| 87 | + |
| 88 | + /// Creates a new [SecretFelt] from its little-endian representation in a Vec<u8> of length 32. |
| 89 | + /// Internally it uses [from_bytes_le](Felt::from_bytes_le). |
| 90 | + /// The input will be zeroized after calling this function |
| 91 | + pub fn from_bytes_le(secret: &mut [u8; 32]) -> Self { |
| 92 | + let secret_felt = Self(Box::new(Felt::from_bytes_le(secret))); |
| 93 | + secret.zeroize(); |
| 94 | + secret_felt |
| 95 | + } |
| 96 | + |
| 97 | + /// Create a new [SecretFelt] from cryptographically secure PRNG |
| 98 | + /// |
| 99 | + /// # Example |
| 100 | + /// ``` |
| 101 | + /// use starknet_types_core::felt::secret_felt::SecretFelt; |
| 102 | + /// use rand_chacha::ChaCha20Rng; |
| 103 | + /// use rand::SeedableRng; |
| 104 | + /// |
| 105 | + /// let rng = ChaCha20Rng::from_os_rng(); |
| 106 | + /// let secret_key = SecretFelt::from_random(rng); |
| 107 | + /// ``` |
| 108 | + pub fn from_random<T>(mut rng: T) -> Self |
| 109 | + where |
| 110 | + T: RngCore + CryptoRng, |
| 111 | + { |
| 112 | + let mut buffer = [0u8; 32]; |
| 113 | + rng.fill_bytes(&mut buffer); |
| 114 | + |
| 115 | + let secret_felt = Self(Box::new(Felt::from_bytes_be(&buffer))); |
| 116 | + buffer.zeroize(); |
| 117 | + |
| 118 | + secret_felt |
| 119 | + } |
| 120 | + |
| 121 | + /// Returns a safe copy of the inner value. |
| 122 | + /// |
| 123 | + /// # Warning |
| 124 | + /// |
| 125 | + /// Be careful not to copy the value elsewhere, as that would defeat |
| 126 | + /// the security guarantees of this type. |
| 127 | + pub fn inner_value(&self) -> Zeroizing<Felt> { |
| 128 | + Zeroizing::new(*self.0.clone()) |
| 129 | + } |
| 130 | +} |
| 131 | + |
| 132 | +/// Constant time equality check for [SecretFelt] |
| 133 | +impl PartialEq for SecretFelt { |
| 134 | + fn eq(&self, other: &Self) -> bool { |
| 135 | + let mut self_limbs = self.0 .0.representative().limbs; |
| 136 | + let mut other_limbs = other.0 .0.representative().limbs; |
| 137 | + |
| 138 | + let is_eq: bool = self_limbs.ct_eq(&other_limbs).into(); |
| 139 | + |
| 140 | + self_limbs.zeroize(); |
| 141 | + other_limbs.zeroize(); |
| 142 | + |
| 143 | + is_eq |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +#[cfg(test)] |
| 148 | +mod test { |
| 149 | + use crate::felt::{secret_felt::SecretFelt, Felt}; |
| 150 | + use core::mem::size_of; |
| 151 | + use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; |
| 152 | + use std::{ops::Deref, str::FromStr}; |
| 153 | + use zeroize::Zeroize; |
| 154 | + |
| 155 | + #[test] |
| 156 | + fn test_zeroize_secret_felt() { |
| 157 | + let mut signing_key = SecretFelt::from_random(ChaCha20Rng::seed_from_u64(1)); |
| 158 | + signing_key.zeroize(); |
| 159 | + |
| 160 | + // Get a pointer to the inner Felt |
| 161 | + let ptr = signing_key.inner_value().deref() as *const Felt as *const u8; |
| 162 | + let after_zeroize = unsafe { std::slice::from_raw_parts(ptr, size_of::<Felt>()) }; |
| 163 | + |
| 164 | + // Check that the memory is zeroed |
| 165 | + assert_eq!( |
| 166 | + Felt::from_bytes_be_slice(after_zeroize), |
| 167 | + Felt::ZERO, |
| 168 | + "Memory was not properly zeroized" |
| 169 | + ); |
| 170 | + } |
| 171 | + |
| 172 | + #[test] |
| 173 | + fn test_zeroize_original() { |
| 174 | + let mut private_key = Felt::from_hex_unchecked( |
| 175 | + "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", |
| 176 | + ); |
| 177 | + let mut signing_key = SecretFelt::from_felt(&mut private_key); |
| 178 | + signing_key.zeroize(); |
| 179 | + |
| 180 | + // Get a pointer to the original memory |
| 181 | + let ptr = private_key.as_ref() as *const Felt as *const u8; |
| 182 | + let after_zeroize = unsafe { std::slice::from_raw_parts(ptr, size_of::<Felt>()) }; |
| 183 | + |
| 184 | + // Check that original value was zeroized |
| 185 | + assert_eq!( |
| 186 | + Felt::from_bytes_be_slice(after_zeroize), |
| 187 | + Felt::ZERO, |
| 188 | + "Original value was not properly zeroized" |
| 189 | + ); |
| 190 | + } |
| 191 | + |
| 192 | + #[test] |
| 193 | + fn test_zeroize_hex_string() { |
| 194 | + let mut private_key = |
| 195 | + String::from_str("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") |
| 196 | + .unwrap(); |
| 197 | + |
| 198 | + let mut signing_key = SecretFelt::from_hex_string(&mut private_key).unwrap(); |
| 199 | + signing_key.zeroize(); |
| 200 | + |
| 201 | + let ptr = private_key.as_ptr() as *const Felt as *const u8; |
| 202 | + let after_zeroize = unsafe { std::slice::from_raw_parts(ptr, size_of::<Felt>()) }; |
| 203 | + |
| 204 | + assert_eq!( |
| 205 | + Felt::from_bytes_be_slice(after_zeroize), |
| 206 | + Felt::ZERO, |
| 207 | + "Original value was not properly zeroized" |
| 208 | + ); |
| 209 | + } |
| 210 | + |
| 211 | + #[test] |
| 212 | + fn test_zeroize_on_drop() { |
| 213 | + let mut private_key = Felt::from_hex_unchecked( |
| 214 | + "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", |
| 215 | + ); |
| 216 | + |
| 217 | + // make a copy, the initial Felt will be zeroized |
| 218 | + let pk_copy = private_key; |
| 219 | + |
| 220 | + let raw_ptr; |
| 221 | + { |
| 222 | + let signing_key = SecretFelt::from_felt(&mut private_key); |
| 223 | + |
| 224 | + let inner_value = *signing_key.0; |
| 225 | + raw_ptr = &inner_value as *const Felt as *const u8; |
| 226 | + |
| 227 | + // Verify it's not zero before dropping |
| 228 | + let before_drop = unsafe { std::slice::from_raw_parts(raw_ptr, size_of::<Felt>()) }; |
| 229 | + assert!( |
| 230 | + !before_drop.iter().all(|&b| b == 0), |
| 231 | + "Memory should not be zeroed yet" |
| 232 | + ); |
| 233 | + } // At this point, signing_key has been dropped and zeroized |
| 234 | + |
| 235 | + // Check that the memory is zeroed after drop |
| 236 | + let after_drop = unsafe { std::slice::from_raw_parts(raw_ptr, size_of::<Felt>()) }; |
| 237 | + |
| 238 | + let felt_after_drop = Felt::from_bytes_be_slice(after_drop); |
| 239 | + |
| 240 | + // Memory is not zero because the compiler reuse that memory slot |
| 241 | + // but should not be equal to the initial value |
| 242 | + assert_ne!(pk_copy, felt_after_drop); |
| 243 | + } |
| 244 | + |
| 245 | + #[test] |
| 246 | + fn test_inner_value() { |
| 247 | + let mut private_key = Felt::from_hex_unchecked( |
| 248 | + "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", |
| 249 | + ); |
| 250 | + |
| 251 | + // make a copy, the initial Felt will be zeroized |
| 252 | + let pk_copy = private_key; |
| 253 | + |
| 254 | + let raw_ptr; |
| 255 | + { |
| 256 | + let signing_key = SecretFelt::from_felt(&mut private_key); |
| 257 | + |
| 258 | + let inner_felt = signing_key.inner_value(); |
| 259 | + |
| 260 | + assert_eq!(*inner_felt, pk_copy); |
| 261 | + |
| 262 | + raw_ptr = inner_felt.as_ref() as *const Felt as *const u8; |
| 263 | + } // inner_value should be zeroized when is out of scope |
| 264 | + |
| 265 | + let after_drop = unsafe { std::slice::from_raw_parts(raw_ptr, size_of::<Felt>()) }; |
| 266 | + let felt_after_drop = Felt::from_bytes_be_slice(after_drop); |
| 267 | + |
| 268 | + // Memory is not zero because the compiler reuse that memory slot |
| 269 | + // but should not be equal to the initial value |
| 270 | + assert_ne!(pk_copy, felt_after_drop); |
| 271 | + } |
| 272 | + |
| 273 | + #[test] |
| 274 | + fn test_partial_eq() { |
| 275 | + let mut private_key1 = [255u8; 32]; |
| 276 | + let mut private_key2 = [255u8; 32]; |
| 277 | + let mut private_key3 = [254u8; 32]; |
| 278 | + |
| 279 | + let signing_key1 = SecretFelt::from_bytes_be(&mut private_key1); |
| 280 | + let signing_key2 = SecretFelt::from_bytes_be(&mut private_key2); |
| 281 | + let signing_key3 = SecretFelt::from_bytes_be(&mut private_key3); |
| 282 | + |
| 283 | + assert!(signing_key1.eq(&signing_key2)); |
| 284 | + assert!(signing_key1.ne(&signing_key3)); |
| 285 | + } |
| 286 | +} |
0 commit comments