diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index 8b08749512..77016fe43b 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -25,6 +25,7 @@ cipher = { workspace = true, optional = true } x25519-dalek = { workspace = true, features = ["static_secrets"], optional = true } ed25519-dalek = { workspace = true, features = ["rand_core"], optional = true } rand = { workspace = true, optional = true } +rand09 = { workspace = true } serde_bytes = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } sha2 = { workspace = true, optional = true } diff --git a/common/crypto/src/hkdf.rs b/common/crypto/src/hkdf.rs index c343002430..28306f51e8 100644 --- a/common/crypto/src/hkdf.rs +++ b/common/crypto/src/hkdf.rs @@ -109,3 +109,152 @@ impl DerivationMaterial { } } } + +pub mod blake3 { + + //! Key Derivation Functions using Blake3. + + use blake3::Hasher; + + use rand09::{RngCore, rng}; + use zeroize::Zeroize; + + pub fn derive_key_blake3_multi_input( + info: &str, + input_key_material: &[&[u8]], + salt: &[u8], + ) -> [u8; 32] { + let mut hasher = Hasher::new_derive_key(&info); + + for input_key in input_key_material { + hasher.update(*input_key); + } + + hasher.update(salt); + + hasher.finalize().as_bytes().to_owned() + } + + /// Derives a 32-byte key using Blake3's key derivation mode. + /// + /// Uses Blake3's built-in `derive_key` function with domain separation via context string. + /// + /// # Arguments + /// * `info` - Context string for domain separation (e.g., "nym-lp-psk-v1") + /// * `input_key_material` - Input key material (shared secret from ECDH, etc.) + /// * `salt` - Additional salt for freshness (nonce) + /// + /// # Returns + /// 32-byte derived key suitable for use as PSK + /// + /// # Example + /// ```ignore + /// let psk = derive_key_blake3("nym-lp-psk-v1", shared_secret.as_bytes(), &salt); + /// ``` + pub fn derive_key_blake3(info: &str, input_key_material: &[u8], salt: &[u8]) -> [u8; 32] { + derive_key_blake3_multi_input(info, &[input_key_material], salt) + } + + pub fn derive_fresh_key_blake3_multi_input( + info: &str, + input_key_material: &[&[u8]], + ) -> [u8; 32] { + let mut salt = [0u8; 32]; + rng().fill_bytes(&mut salt); + + let derived_key = derive_key_blake3_multi_input(info, input_key_material, &mut salt); + + // Zeroize salt + salt.zeroize(); + + derived_key + } + + /// Derives a fresh 32-byte key using Blake3's key derivation mode. + /// The function calls a random number generator to generate a fresh salt. + /// Uses Blake3's built-in `derive_key` function with domain separation via context string. + /// + /// # Arguments + /// * `info` - Context string for domain separation (e.g., "nym-lp-psk-v1") + /// * `input_key_material` - Input key material (shared secret from ECDH, etc.) + /// + /// # Returns + /// 32-byte derived key suitable for use as PSK + /// + /// # Example + /// ```ignore + /// let psk = derive_fresh_key_blake3("nym-lp-psk-v1", shared_secret.as_bytes()); + /// ``` + pub fn derive_fresh_key_blake3(info: &str, input_key_material: &[u8]) -> [u8; 32] { + derive_fresh_key_blake3_multi_input(info, &[input_key_material]) + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_deterministic_derivation() { + let context = "test-context"; + let key_material = b"shared_secret_12345"; + let salt = b"salt_67890"; + + let key1 = derive_key_blake3(context, key_material, salt); + let key2 = derive_key_blake3(context, key_material, salt); + + assert_eq!(key1, key2, "Same inputs should produce same output"); + } + + #[test] + fn test_different_contexts_produce_different_keys() { + let key_material = b"shared_secret"; + let salt = b"salt"; + + let key1 = derive_key_blake3("context1", key_material, salt); + let key2 = derive_key_blake3("context2", key_material, salt); + + assert_ne!( + key1, key2, + "Different contexts should produce different keys" + ); + } + + #[test] + fn test_different_salts_produce_different_keys() { + let context = "test-context"; + let key_material = b"shared_secret"; + + let key1 = derive_key_blake3(context, key_material, b"salt1"); + let key2 = derive_key_blake3(context, key_material, b"salt2"); + + assert_ne!(key1, key2, "Different salts should produce different keys"); + } + + #[test] + fn test_different_key_material_produces_different_keys() { + let context = "test-context"; + let salt = b"salt"; + + let key1 = derive_key_blake3(context, b"secret1", salt); + let key2 = derive_key_blake3(context, b"secret2", salt); + + assert_ne!( + key1, key2, + "Different key material should produce different keys" + ); + } + + #[test] + fn test_output_length() { + let key = derive_key_blake3("test", b"key", b"salt"); + assert_eq!(key.len(), 32, "Output should be exactly 32 bytes"); + } + + #[test] + fn test_empty_inputs() { + // Should not panic with empty inputs + let key = derive_key_blake3("test", b"", b""); + assert_eq!(key.len(), 32); + } + } +} diff --git a/common/crypto/src/kdf.rs b/common/crypto/src/kdf.rs deleted file mode 100644 index 3edb257299..0000000000 --- a/common/crypto/src/kdf.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -//! Key Derivation Functions using Blake3. - -/// Derives a 32-byte key using Blake3's key derivation mode. -/// -/// Uses Blake3's built-in `derive_key` function with domain separation via context string. -/// -/// # Arguments -/// * `context` - Context string for domain separation (e.g., "nym-lp-psk-v1") -/// * `key_material` - Input key material (shared secret from ECDH, etc.) -/// * `salt` - Additional salt for freshness (timestamp + nonce) -/// -/// # Returns -/// 32-byte derived key suitable for use as PSK -/// -/// # Example -/// ```ignore -/// let psk = derive_key_blake3("nym-lp-psk-v1", shared_secret.as_bytes(), &salt); -/// ``` -pub fn derive_key_blake3(context: &str, key_material: &[u8], salt: &[u8]) -> [u8; 32] { - // Concatenate key_material and salt as input - let input = [key_material, salt].concat(); - - // Use Blake3's derive_key with context for domain separation - // blake3::derive_key returns [u8; 32] directly - blake3::derive_key(context, &input) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_deterministic_derivation() { - let context = "test-context"; - let key_material = b"shared_secret_12345"; - let salt = b"salt_67890"; - - let key1 = derive_key_blake3(context, key_material, salt); - let key2 = derive_key_blake3(context, key_material, salt); - - assert_eq!(key1, key2, "Same inputs should produce same output"); - } - - #[test] - fn test_different_contexts_produce_different_keys() { - let key_material = b"shared_secret"; - let salt = b"salt"; - - let key1 = derive_key_blake3("context1", key_material, salt); - let key2 = derive_key_blake3("context2", key_material, salt); - - assert_ne!( - key1, key2, - "Different contexts should produce different keys" - ); - } - - #[test] - fn test_different_salts_produce_different_keys() { - let context = "test-context"; - let key_material = b"shared_secret"; - - let key1 = derive_key_blake3(context, key_material, b"salt1"); - let key2 = derive_key_blake3(context, key_material, b"salt2"); - - assert_ne!(key1, key2, "Different salts should produce different keys"); - } - - #[test] - fn test_different_key_material_produces_different_keys() { - let context = "test-context"; - let salt = b"salt"; - - let key1 = derive_key_blake3(context, b"secret1", salt); - let key2 = derive_key_blake3(context, b"secret2", salt); - - assert_ne!( - key1, key2, - "Different key material should produce different keys" - ); - } - - #[test] - fn test_output_length() { - let key = derive_key_blake3("test", b"key", b"salt"); - assert_eq!(key.len(), 32, "Output should be exactly 32 bytes"); - } - - #[test] - fn test_empty_inputs() { - // Should not panic with empty inputs - let key = derive_key_blake3("test", b"", b""); - assert_eq!(key.len(), 32); - } -} diff --git a/common/crypto/src/lib.rs b/common/crypto/src/lib.rs index 3875fa7f81..1dff7b82be 100644 --- a/common/crypto/src/lib.rs +++ b/common/crypto/src/lib.rs @@ -10,8 +10,6 @@ pub mod crypto_hash; pub mod hkdf; #[cfg(feature = "hashing")] pub mod hmac; -#[cfg(feature = "hashing")] -pub mod kdf; #[cfg(all(feature = "asymmetric", feature = "hashing", feature = "stream_cipher"))] pub mod shared_key; pub mod symmetric;