Skip to content

Commit d0c0578

Browse files
Merge branch 'main' into add-qm31
2 parents d8bbdb5 + 820c306 commit d0c0578

File tree

4 files changed

+307
-37
lines changed

4 files changed

+307
-37
lines changed

crates/starknet-types-core/Cargo.toml

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,27 @@ arbitrary = { version = "1.3", optional = true }
2121
blake2 = { version = "0.10.6", default-features = false, optional = true }
2222
digest = { version = "0.10.7", optional = true }
2323
serde = { version = "1", optional = true, default-features = false, features = [
24-
"alloc", "derive"
24+
"alloc", "derive"
2525
] }
2626
lambdaworks-crypto = { version = "0.10.0", default-features = false, optional = true }
2727
parity-scale-codec = { version = "3.6", default-features = false, optional = true }
2828
lazy_static = { version = "1.5", default-features = false, optional = true }
2929
zeroize = { version = "1.8.1", default-features = false, optional = true }
30+
subtle = { version = "2.6.1", default-features = false, optional = true }
31+
rand = { version = "0.9.2", default-features = false, optional = true }
3032

3133
[features]
3234
default = ["std", "serde", "curve", "num-traits"]
3335
std = [
34-
"alloc",
35-
"lambdaworks-math/std",
36-
"num-traits/std",
37-
"num-bigint/std",
38-
"num-integer/std",
39-
"serde?/std",
40-
"lambdaworks-crypto?/std",
41-
"zeroize?/std",
36+
"alloc",
37+
"lambdaworks-math/std",
38+
"num-traits/std",
39+
"num-bigint/std",
40+
"num-integer/std",
41+
"serde?/std",
42+
"lambdaworks-crypto?/std",
43+
"zeroize?/std",
44+
"rand?/std"
4245
]
4346
alloc = ["zeroize?/alloc"]
4447
curve = []
@@ -49,18 +52,18 @@ serde = ["alloc", "dep:serde"]
4952
prime-bigint = ["dep:lazy_static"]
5053
num-traits = []
5154
papyrus-serialization = ["std"]
52-
zeroize = ["dep:zeroize"]
55+
secret-felt = ["alloc", "dep:zeroize", "dep:subtle", "subtle/const-generics", "subtle/core_hint_black_box", "dep:rand", "rand/alloc"]
5356

5457
[dev-dependencies]
5558
proptest = { version = "1.5", default-features = false, features = [
56-
"alloc",
57-
"proptest-macro",
59+
"alloc",
60+
"proptest-macro",
5861
] }
5962
regex = "1.11"
6063
serde_test = "1"
6164
criterion = "0.5"
62-
rand_chacha = "0.3"
63-
rand = "0.8"
65+
rand_chacha = "0.9"
66+
rand = "0.9.2"
6467
rstest = "0.24"
6568
lazy_static = { version = "1.5", default-features = false }
6669

crates/starknet-types-core/src/felt/mod.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#[cfg(feature = "alloc")]
2+
pub extern crate alloc;
3+
#[cfg(feature = "alloc")]
24
mod alloc_impls;
35
#[cfg(feature = "arbitrary")]
46
mod arbitrary;
@@ -14,10 +16,10 @@ mod parity_scale_codec;
1416
#[cfg(feature = "prime-bigint")]
1517
mod prime_bigint;
1618
mod primitive_conversions;
19+
#[cfg(feature = "secret-felt")]
20+
pub mod secret_felt;
1721
#[cfg(feature = "serde")]
1822
mod serde;
19-
#[cfg(feature = "zeroize")]
20-
mod zeroize;
2123

2224
use lambdaworks_math::errors::CreationError;
2325
pub use non_zero::{FeltIsZeroError, NonZeroFelt};
@@ -33,9 +35,6 @@ use num_integer::Integer;
3335
use num_traits::{One, Zero};
3436
pub use primitive_conversions::PrimitiveFromFeltError;
3537

36-
#[cfg(feature = "alloc")]
37-
pub extern crate alloc;
38-
3938
use lambdaworks_math::{
4039
field::{
4140
element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField,
@@ -742,7 +741,6 @@ mod arithmetic {
742741
}
743742

744743
mod formatting {
745-
746744
use core::fmt;
747745

748746
use super::*;
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
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

Comments
 (0)