-
Notifications
You must be signed in to change notification settings - Fork 56
Filip/secret felt implementation #144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
FilipLaurentiu
wants to merge
22
commits into
starknet-io:main
Choose a base branch
from
FilipLaurentiu:filip/secret_felt_implementation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
4f3a2b3
Affine coordinate are not correctly exposed from the underlying type.
FilipLaurentiu b53ca90
Merge branch 'main' into main
FilipLaurentiu 9c0676e
Fix the `Add` implementation
FilipLaurentiu 86e617c
Merge branch 'starknet-io:main' into main
FilipLaurentiu ad125a3
Merge branch 'main' into main
FilipLaurentiu f9d898c
Merge branch 'main' into main
FilipLaurentiu 9d49f0b
Merge branch 'starknet-io:main' into main
FilipLaurentiu 733693f
Merge branch 'starknet-io:main' into main
FilipLaurentiu 4a84f6b
Merge branch 'starknet-io:main' into main
FilipLaurentiu bd27423
Implement SecretFelt, a secure wrapper for Felt that will avoid leaki…
FilipLaurentiu 05e2f40
Add warning message to raise attention that good cryptographic hygien…
FilipLaurentiu ddf80c8
Merge branch 'main' into filip/secret_felt_implementation
FilipLaurentiu 161d5a7
Fix fmt
FilipLaurentiu 8d4a2a4
Update code documentation
FilipLaurentiu cad3f8e
Merge branch 'main' into filip/secret_felt_implementation
FilipLaurentiu 9020969
Fix `--no-default-features` build error
FilipLaurentiu ce58b8f
Change condition to use imports from `alloc` only when std is not enable
FilipLaurentiu b7fb59d
Apply `Clippy` suggestions
FilipLaurentiu 46d7af6
Merge branch 'main' into filip/secret_felt_implementation
FilipLaurentiu f6f22fd
- Implement constant time equality check for `SecretFelt` using `subt…
FilipLaurentiu 042b7b9
Merge branch 'main' into filip/secret_felt_implementation
FilipLaurentiu 13a1854
Merge branch 'main' into filip/secret_felt_implementation
FilipLaurentiu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,280 @@ | ||
use crate::felt::{Felt, FromStrError}; | ||
use rand::{CryptoRng, RngCore}; | ||
use subtle::ConstantTimeEq; | ||
use zeroize::{Zeroize, Zeroizing}; | ||
|
||
#[cfg(not(feature = "std"))] | ||
use super::alloc::{boxed::Box, string::String, vec::Vec}; | ||
|
||
/// A wrapper for a [Felt] that ensures the value is securely zeroized when dropped. | ||
/// | ||
/// This type provides secure handling of sensitive [Felt] values (like private keys) | ||
/// by ensuring that the memory is properly cleared when the value is no longer needed. | ||
pub struct SecretFelt(Box<Felt>); | ||
|
||
impl zeroize::DefaultIsZeroes for Felt {} | ||
|
||
impl Zeroize for SecretFelt { | ||
fn zeroize(&mut self) { | ||
self.0.zeroize(); | ||
} | ||
} | ||
|
||
impl Drop for SecretFelt { | ||
fn drop(&mut self) { | ||
self.zeroize(); | ||
} | ||
} | ||
|
||
impl SecretFelt { | ||
/// Creates a new [SecretFelt] from a [Felt] value and zeroize the original. | ||
/// | ||
/// It takes a mutable reference to a [Felt] value, creates a copy, | ||
/// and then zeroize the original value to ensure it doesn't remain in memory. | ||
/// | ||
/// # Warning | ||
/// | ||
/// Avoid moving the secret [Felt] in the memory and initialize the [SecretFelt] | ||
/// as soon as possible in order to not let any copy of the value in memory | ||
/// | ||
/// # Example | ||
/// | ||
/// ``` | ||
/// use starknet_types_core::felt::{Felt, secret_felt::SecretFelt}; | ||
/// | ||
/// let mut private_key = Felt::from_hex_unchecked("0x123..."); | ||
/// let secret_felt = SecretFelt::from_felt(&mut private_key); | ||
/// // private_key is now zeroized (set to Felt::ZERO) | ||
/// ``` | ||
pub fn from_felt(secret_felt: &mut Felt) -> Self { | ||
let boxed_copy = Box::new(*secret_felt); | ||
secret_felt.zeroize(); | ||
Self(boxed_copy) | ||
} | ||
|
||
/// Creates a new [SecretFelt] from a hex String and zeroized the original String. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// use std::fs; | ||
/// use starknet_types_core::felt::secret_felt::SecretFelt; | ||
/// use std::str::FromStr; | ||
/// | ||
/// // make sure the String is initialized in a secure way | ||
/// let mut private_key = fs::read_to_string("path/to/secret_value").unwrap(); | ||
/// let secret_felt = SecretFelt::from_hex_string(&mut private_key); | ||
/// ``` | ||
pub fn from_hex_string(hex: &mut String) -> Result<Self, FromStrError> { | ||
let secret_felt = Felt::from_hex(hex)?; | ||
hex.zeroize(); | ||
Ok(Self(Box::new(secret_felt))) | ||
} | ||
|
||
/// Creates a new [SecretFelt] from its big-endian representation in a Vec<u8> of length 32. | ||
/// Internally it uses [from_bytes_be](Felt::from_bytes_be). | ||
/// The input will be zeroized after calling this function | ||
pub fn from_bytes_be(secret: &mut [u8; 32]) -> Self { | ||
let secret_felt = Self(Box::new(Felt::from_bytes_be(secret))); | ||
secret.zeroize(); | ||
secret_felt | ||
} | ||
|
||
/// Creates a new [SecretFelt] from its little-endian representation in a Vec<u8> of length 32. | ||
/// Internally it uses [from_bytes_le](Felt::from_bytes_le). | ||
tdelabro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// The input will be zeroized after calling this function | ||
pub fn from_bytes_le(secret: &mut [u8; 32]) -> Self { | ||
let secret_felt = Self(Box::new(Felt::from_bytes_le(secret))); | ||
secret.zeroize(); | ||
secret_felt | ||
} | ||
|
||
/// Create a new [SecretFelt] from cryptographically secure PRNG | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// use starknet_types_core::felt::secret_felt::SecretFelt; | ||
/// use rand_chacha::ChaCha20Rng; | ||
/// use rand::SeedableRng; | ||
/// | ||
/// let rng = ChaCha20Rng::from_os_rng(); | ||
/// let secret_key = SecretFelt::from_random(rng); | ||
/// ``` | ||
pub fn from_random<T>(mut rng: T) -> Self | ||
where | ||
T: RngCore + CryptoRng, | ||
{ | ||
let mut buffer = [0u8; 32]; | ||
rng.fill_bytes(&mut buffer); | ||
|
||
let secret_felt = Self(Box::new(Felt::from_bytes_be(&buffer))); | ||
buffer.zeroize(); | ||
|
||
secret_felt | ||
} | ||
|
||
/// Returns a safe copy of the inner value. | ||
/// | ||
/// # Warning | ||
/// | ||
/// Be careful not to copy the value elsewhere, as that would defeat | ||
/// the security guarantees of this type. | ||
pub fn inner_value(&self) -> Zeroizing<Felt> { | ||
Zeroizing::new(*self.0.clone()) | ||
} | ||
} | ||
|
||
tdelabro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// Constant time equality check for [SecretFelt] | ||
impl PartialEq for SecretFelt { | ||
fn eq(&self, other: &Self) -> bool { | ||
let mut self_limbs = self.0 .0.representative().limbs; | ||
let mut other_limbs = other.0 .0.representative().limbs; | ||
|
||
let is_eq: bool = self_limbs.ct_eq(&other_limbs).into(); | ||
|
||
self_limbs.zeroize(); | ||
other_limbs.zeroize(); | ||
|
||
is_eq | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use crate::felt::{secret_felt::SecretFelt, Felt}; | ||
use core::mem::size_of; | ||
use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; | ||
use std::{ops::Deref, str::FromStr}; | ||
use zeroize::Zeroize; | ||
|
||
#[test] | ||
fn test_zeroize_secret_felt() { | ||
let mut signing_key = SecretFelt::from_random(ChaCha20Rng::seed_from_u64(1)); | ||
signing_key.zeroize(); | ||
|
||
// Get a pointer to the inner Felt | ||
let ptr = signing_key.inner_value().deref() as *const Felt as *const u8; | ||
let after_zeroize = unsafe { std::slice::from_raw_parts(ptr, size_of::<Felt>()) }; | ||
|
||
// Check that the memory is zeroed | ||
assert_eq!( | ||
Felt::from_bytes_be_slice(after_zeroize), | ||
Felt::ZERO, | ||
"Memory was not properly zeroized" | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_zeroize_original() { | ||
let mut private_key = Felt::from_hex_unchecked( | ||
"0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", | ||
); | ||
let mut signing_key = SecretFelt::from_felt(&mut private_key); | ||
signing_key.zeroize(); | ||
|
||
// Get a pointer to the original memory | ||
let ptr = private_key.as_ref() as *const Felt as *const u8; | ||
let after_zeroize = unsafe { std::slice::from_raw_parts(ptr, size_of::<Felt>()) }; | ||
|
||
// Check that original value was zeroized | ||
assert_eq!( | ||
Felt::from_bytes_be_slice(after_zeroize), | ||
Felt::ZERO, | ||
"Original value was not properly zeroized" | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_zeroize_hex_string() { | ||
let mut private_key = | ||
String::from_str("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") | ||
.unwrap(); | ||
|
||
let mut signing_key = SecretFelt::from_hex_string(&mut private_key).unwrap(); | ||
signing_key.zeroize(); | ||
|
||
let ptr = private_key.as_ptr() as *const Felt as *const u8; | ||
let after_zeroize = unsafe { std::slice::from_raw_parts(ptr, size_of::<Felt>()) }; | ||
|
||
assert_eq!( | ||
Felt::from_bytes_be_slice(after_zeroize), | ||
Felt::ZERO, | ||
"Original value was not properly zeroized" | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_zeroize_on_drop() { | ||
let mut private_key = Felt::from_hex_unchecked( | ||
"0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", | ||
); | ||
|
||
// make a copy, the initial Felt will be zeroized | ||
let pk_copy = private_key; | ||
|
||
let raw_ptr; | ||
{ | ||
let signing_key = SecretFelt::from_felt(&mut private_key); | ||
|
||
let inner_value = *signing_key.0; | ||
raw_ptr = &inner_value as *const Felt as *const u8; | ||
|
||
// Verify it's not zero before dropping | ||
let before_drop = unsafe { std::slice::from_raw_parts(raw_ptr, size_of::<Felt>()) }; | ||
assert!( | ||
!before_drop.iter().all(|&b| b == 0), | ||
"Memory should not be zeroed yet" | ||
); | ||
} // At this point, signing_key has been dropped and zeroized | ||
|
||
// Check that the memory is zeroed after drop | ||
let after_drop = unsafe { std::slice::from_raw_parts(raw_ptr, size_of::<Felt>()) }; | ||
|
||
let felt_after_drop = Felt::from_bytes_be_slice(after_drop); | ||
|
||
// Memory is not zero because the compiler reuse that memory slot | ||
// but should not be equal to the initial value | ||
assert_ne!(pk_copy, felt_after_drop); | ||
} | ||
|
||
#[test] | ||
fn test_inner_value() { | ||
let mut private_key = Felt::from_hex_unchecked( | ||
"0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", | ||
); | ||
|
||
// make a copy, the initial Felt will be zeroized | ||
let pk_copy = private_key; | ||
|
||
let raw_ptr; | ||
{ | ||
let signing_key = SecretFelt::from_felt(&mut private_key); | ||
|
||
let inner_felt = signing_key.inner_value(); | ||
|
||
assert_eq!(*inner_felt, pk_copy); | ||
|
||
raw_ptr = inner_felt.as_ref() as *const Felt as *const u8; | ||
} // inner_value should be zeroized when is out of scope | ||
|
||
let after_drop = unsafe { std::slice::from_raw_parts(raw_ptr, size_of::<Felt>()) }; | ||
let felt_after_drop = Felt::from_bytes_be_slice(after_drop); | ||
|
||
// Memory is not zero because the compiler reuse that memory slot | ||
// but should not be equal to the initial value | ||
assert_ne!(pk_copy, felt_after_drop); | ||
} | ||
|
||
#[test] | ||
fn test_partial_eq() { | ||
let mut private_key1 = [255u8; 32]; | ||
let mut private_key2 = [255u8; 32]; | ||
let mut private_key3 = [254u8; 32]; | ||
|
||
let signing_key1 = SecretFelt::from_bytes_be(&mut private_key1); | ||
let signing_key2 = SecretFelt::from_bytes_be(&mut private_key2); | ||
let signing_key3 = SecretFelt::from_bytes_be(&mut private_key3); | ||
|
||
assert!(signing_key1.eq(&signing_key2)); | ||
assert!(signing_key1.ne(&signing_key3)); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.