diff --git a/Cargo.toml b/Cargo.toml index 50902d6..ee0dc00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sphinx-packet" -version = "0.4.0" +version = "0.5.0" authors = ["Ania Piotrowska ", "Dave Hrycyszyn ", "Jędrzej Stuczyński "] edition = "2018" license = "Apache-2.0" @@ -16,6 +16,9 @@ ctr = "0.9.2" bs58 = "0.5.1" x25519-dalek = { version = "2.0.1", features = ["static_secrets", "getrandom", "zeroize"] } +# for legacy compat +curve25519-dalek = { version = "4.1.3" } + hmac = "0.12.1" digest = "0.10.7" rand = "0.8.5" @@ -28,11 +31,13 @@ chacha = "0.3.0" blake2 = "0.8.0" # cannot be updated due to outdated dependency inside lioness byteorder = "1.5.0" subtle = "2.4.1" +zeroize = "1.7.0" [dev-dependencies] criterion = "0.5.1" +rand_chacha = "0.3.1" [[bench]] name = "benchmarks" diff --git a/README.md b/README.md index 0303b67..fb8ef39 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,21 @@ of this library added additional public methods on the `Version` +#### v0.3.2 + +added version method to allow constructing SURBs with legacy headers + #### v0.4.0 removed processing and creation of packets with undefined operations +#### v0.5.0 + +- temporarily restored processing and creation of packets with undefined operations as additional breaking changes had + to be added to v0.3.2 release +- removed `RoutingKeys` in favour of `ExpandedSharedSecret` and added `ReplyTag` +- type adjustments + ### Benchmarks To run benchmarks, use: diff --git a/src/constants.rs b/src/constants.rs index add6452..b08effe 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -19,10 +19,16 @@ use sha2::Sha256; pub const SECURITY_PARAMETER: usize = 16; // k in the Sphinx paper. Measured in bytes; 128 bits. pub const MAX_PATH_LENGTH: usize = 5; // r in the Sphinx paper pub const BLINDING_FACTOR_SIZE: usize = 2 * SECURITY_PARAMETER; -pub const ROUTING_KEYS_LENGTH: usize = crypto::STREAM_CIPHER_KEY_SIZE + +/// Output of the h𝜏 hash function / random oracle +pub const REPLAY_TAG_SIZE: usize = 2 * SECURITY_PARAMETER; + +pub const EXPANDED_SHARED_SECRET_LENGTH: usize = crypto::STREAM_CIPHER_KEY_SIZE + INTEGRITY_MAC_KEY_SIZE + PAYLOAD_KEY_SIZE - + BLINDING_FACTOR_SIZE; + + BLINDING_FACTOR_SIZE + + REPLAY_TAG_SIZE; + pub const HKDF_INPUT_SEED: &[u8; 97] = b"Dwste mou enan moxlo arketa makru kai ena upomoxlio gia na ton topothetisw kai tha kinisw thn gh."; pub const STREAM_CIPHER_OUTPUT_LENGTH: usize = (NODE_META_INFO_SIZE + HEADER_INTEGRITY_MAC_SIZE) * (MAX_PATH_LENGTH + 1); diff --git a/src/header/filler.rs b/src/header/filler.rs index 683529c..3db6e19 100644 --- a/src/header/filler.rs +++ b/src/header/filler.rs @@ -14,25 +14,23 @@ use crate::constants::{HEADER_INTEGRITY_MAC_SIZE, MAX_PATH_LENGTH, NODE_META_INFO_SIZE}; use crate::crypto; -use crate::header::keys::RoutingKeys; +use crate::header::shared_secret::ExpandedSharedSecret; use crate::{constants, utils}; pub const FILLER_STEP_SIZE_INCREASE: usize = NODE_META_INFO_SIZE + HEADER_INTEGRITY_MAC_SIZE; #[derive(Debug, PartialEq, Eq)] -pub struct Filler { - value: Vec, -} +pub struct Filler(Vec); impl Filler { - pub fn new(routing_keys: &[RoutingKeys]) -> Self { - assert!(routing_keys.len() <= MAX_PATH_LENGTH); - let filler_value = routing_keys + pub(crate) fn new(expanded_shared_secrets: &[ExpandedSharedSecret]) -> Self { + assert!(expanded_shared_secrets.len() <= MAX_PATH_LENGTH); + let filler_value = expanded_shared_secrets .iter() - .map(|node_routing_keys| node_routing_keys.stream_cipher_key) // we only want the cipher key + .map(|ess| ess.stream_cipher_key()) // we only want the cipher key .map(|cipher_key| { crypto::generate_pseudorandom_bytes( - &cipher_key, + cipher_key, &crypto::STREAM_CIPHER_INIT_VECTOR, constants::STREAM_CIPHER_OUTPUT_LENGTH, ) @@ -45,9 +43,7 @@ impl Filler { Self::filler_step(filler_string_accumulator, i, pseudorandom_bytes) }, ); - Self { - value: filler_value, - } + Self(filler_value) } fn filler_step( @@ -75,41 +71,44 @@ impl Filler { filler_string_accumulator } +} - pub fn get_value(self) -> Vec { - self.value +impl From> for Filler { + fn from(raw_bytes: Vec) -> Self { + Self(raw_bytes) } +} - pub(crate) fn from_raw(raw_value: Vec) -> Self { - Filler { value: raw_value } +impl From for Vec { + fn from(filler: Filler) -> Self { + filler.0 } } #[cfg(test)] mod test_creating_pseudorandom_bytes { - use crate::header::keys; - use super::*; + use crate::header::shared_secret::ExpandSecret; use x25519_dalek::{PublicKey, StaticSecret}; #[test] fn with_no_keys_it_generates_empty_filler_string() { - let routing_keys: Vec = vec![]; - let filler_string = Filler::new(&routing_keys); + let expanded_shared_secret: Vec<_> = vec![]; + let filler_string = Filler::new(&expanded_shared_secret); - assert_eq!(0, filler_string.value.len()); + assert_eq!(0, filler_string.0.len()); } #[test] fn with_1_key_it_generates_filler_of_length_1_times_3_times_security_parameter() { let shared_keys = [PublicKey::from(&StaticSecret::random())]; - let routing_keys: Vec<_> = shared_keys + let expanded_shared_secret: Vec<_> = shared_keys .iter() - .map(|&key| keys::RoutingKeys::derive(key)) + .map(|&key| key.expand_shared_secret()) .collect(); - let filler_string = Filler::new(&routing_keys); + let filler_string = Filler::new(&expanded_shared_secret); - assert_eq!(FILLER_STEP_SIZE_INCREASE, filler_string.value.len()); + assert_eq!(FILLER_STEP_SIZE_INCREASE, filler_string.0.len()); } #[test] @@ -119,12 +118,12 @@ mod test_creating_pseudorandom_bytes { PublicKey::from(&StaticSecret::random()), PublicKey::from(&StaticSecret::random()), ]; - let routing_keys: Vec<_> = shared_keys + let expanded_shared_secret: Vec<_> = shared_keys .iter() - .map(|&key| keys::RoutingKeys::derive(key)) + .map(|&key| key.expand_shared_secret()) .collect(); - let filler_string = Filler::new(&routing_keys); - assert_eq!(3 * FILLER_STEP_SIZE_INCREASE, filler_string.value.len()); + let filler_string = Filler::new(&expanded_shared_secret); + assert_eq!(3 * FILLER_STEP_SIZE_INCREASE, filler_string.0.len()); } #[test] @@ -133,43 +132,43 @@ mod test_creating_pseudorandom_bytes { let shared_keys: Vec<_> = std::iter::repeat_n((), constants::MAX_PATH_LENGTH + 1) .map(|_| PublicKey::from(&StaticSecret::random())) .collect(); - let routing_keys: Vec<_> = shared_keys + let expanded_shared_secrets: Vec<_> = shared_keys .iter() - .map(|&key| keys::RoutingKeys::derive(key)) + .map(|&key| key.expand_shared_secret()) .collect(); - Filler::new(&routing_keys); + Filler::new(&expanded_shared_secrets); } } #[cfg(test)] mod test_new_filler_bytes { use super::*; - use crate::test_utils::fixtures::routing_keys_fixture; + use crate::test_utils::fixtures::expanded_shared_secret_fixture; #[test] - fn it_retusn_filler_bytes_of_correct_length_for_3_routing_keys() { - let routing_key_1 = routing_keys_fixture(); - let routing_key_2 = routing_keys_fixture(); - let routing_key_3 = routing_keys_fixture(); - let routing_keys = [routing_key_1, routing_key_2, routing_key_3]; - let filler = Filler::new(&routing_keys); + fn it_retusn_filler_bytes_of_correct_length_for_3_expanded_shared_secret() { + let routing_key_1 = expanded_shared_secret_fixture(); + let routing_key_2 = expanded_shared_secret_fixture(); + let routing_key_3 = expanded_shared_secret_fixture(); + let expanded_shared_secret = [routing_key_1, routing_key_2, routing_key_3]; + let filler = Filler::new(&expanded_shared_secret); assert_eq!( - FILLER_STEP_SIZE_INCREASE * (routing_keys.len()), - filler.get_value().len() + FILLER_STEP_SIZE_INCREASE * (expanded_shared_secret.len()), + filler.0.len() ) } #[test] - fn it_retusn_filler_bytes_of_correct_length_for_4_routing_keys() { - let routing_key_1 = routing_keys_fixture(); - let routing_key_2 = routing_keys_fixture(); - let routing_key_3 = routing_keys_fixture(); - let routing_key_4 = routing_keys_fixture(); - let routing_keys = [routing_key_1, routing_key_2, routing_key_3, routing_key_4]; - let filler = Filler::new(&routing_keys); + fn it_retusn_filler_bytes_of_correct_length_for_4_expanded_shared_secret() { + let routing_key_1 = expanded_shared_secret_fixture(); + let routing_key_2 = expanded_shared_secret_fixture(); + let routing_key_3 = expanded_shared_secret_fixture(); + let routing_key_4 = expanded_shared_secret_fixture(); + let expanded_shared_secret = [routing_key_1, routing_key_2, routing_key_3, routing_key_4]; + let filler = Filler::new(&expanded_shared_secret); assert_eq!( - FILLER_STEP_SIZE_INCREASE * (routing_keys.len()), - filler.get_value().len() + FILLER_STEP_SIZE_INCREASE * (expanded_shared_secret.len()), + filler.0.len() ) } } diff --git a/src/header/keys.rs b/src/header/keys.rs index 6fc5bb3..afdf033 100644 --- a/src/header/keys.rs +++ b/src/header/keys.rs @@ -12,124 +12,85 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::constants::{ - BLINDING_FACTOR_SIZE, HKDF_INPUT_SEED, INTEGRITY_MAC_KEY_SIZE, PAYLOAD_KEY_SIZE, - ROUTING_KEYS_LENGTH, -}; -use crate::crypto; +use crate::constants::{INTEGRITY_MAC_KEY_SIZE, PAYLOAD_KEY_SIZE}; use crate::crypto::STREAM_CIPHER_KEY_SIZE; +use crate::header::shared_secret::{expand_shared_secret, ExpandedSharedSecret}; use crate::route::Node; -use hkdf::Hkdf; -use sha2::Sha256; -use std::convert::TryInto; -use std::fmt; +use curve25519_dalek::Scalar; use x25519_dalek::{PublicKey, StaticSecret}; pub type StreamCipherKey = [u8; STREAM_CIPHER_KEY_SIZE]; pub type HeaderIntegrityMacKey = [u8; INTEGRITY_MAC_KEY_SIZE]; -// TODO: perhaps change PayloadKey to a Vec considering it's almost 200 bytes long? -// we will lose length assertions but won't need to copy all that data every single function call pub type PayloadKey = [u8; PAYLOAD_KEY_SIZE]; -#[derive(Clone)] -pub struct RoutingKeys { - pub stream_cipher_key: StreamCipherKey, - pub header_integrity_hmac_key: HeaderIntegrityMacKey, - pub payload_key: PayloadKey, - pub blinding_factor: StaticSecret, -} - -impl RoutingKeys { - // or should this be renamed to 'new'? - // Given that everything here except RoutingKeys lives in the `crypto` module, I think - // that this one could potentially move most of its functionality there quite profitably. - pub fn derive(shared_key: PublicKey) -> Self { - let hkdf = Hkdf::::new(None, shared_key.as_bytes()); - - let mut i = 0; - let mut output = [0u8; ROUTING_KEYS_LENGTH]; - // SAFETY: the length of the provided okm is within the allowed range - #[allow(clippy::unwrap_used)] - hkdf.expand(HKDF_INPUT_SEED, &mut output).unwrap(); - - let mut stream_cipher_key: [u8; crypto::STREAM_CIPHER_KEY_SIZE] = Default::default(); - stream_cipher_key.copy_from_slice(&output[i..i + crypto::STREAM_CIPHER_KEY_SIZE]); - i += crypto::STREAM_CIPHER_KEY_SIZE; - - let mut header_integrity_hmac_key: [u8; INTEGRITY_MAC_KEY_SIZE] = Default::default(); - header_integrity_hmac_key.copy_from_slice(&output[i..i + INTEGRITY_MAC_KEY_SIZE]); - i += INTEGRITY_MAC_KEY_SIZE; - - let mut payload_key: [u8; PAYLOAD_KEY_SIZE] = [0u8; PAYLOAD_KEY_SIZE]; - payload_key.copy_from_slice(&output[i..i + PAYLOAD_KEY_SIZE]); - i += PAYLOAD_KEY_SIZE; - - //Safety, converting a slice of size BLINDING_FACTOR_SIZE into an array of type [u8; BLINDING_FACTOR_SIZE], hence unwrap is fine - #[allow(clippy::unwrap_used)] - let blinding_factor_bytes: [u8; BLINDING_FACTOR_SIZE] = - output[i..i + BLINDING_FACTOR_SIZE].try_into().unwrap(); - let blinding_factor = StaticSecret::from(blinding_factor_bytes); - - Self { - stream_cipher_key, - header_integrity_hmac_key, - payload_key, - blinding_factor, - } - } -} - -impl fmt::Debug for RoutingKeys { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("RoutingKeys") - .field("stream_cipher_key", &self.stream_cipher_key) - .field("header_integrity_hmac_key", &self.header_integrity_hmac_key) - .field("payload_key", &self.payload_key) - .field("blinding_factor", self.blinding_factor.as_bytes()) - .finish() - } -} - -impl PartialEq for RoutingKeys { - fn eq(&self, other: &RoutingKeys) -> bool { - self.stream_cipher_key == other.stream_cipher_key - && self.header_integrity_hmac_key == other.header_integrity_hmac_key - && self.payload_key.to_vec() == other.payload_key.to_vec() - } -} - pub struct KeyMaterial { pub initial_shared_secret: PublicKey, - // why this is here? - pub routing_keys: Vec, + pub expanded_shared_secrets: Vec, } impl KeyMaterial { // derive shared keys, group elements, blinding factors pub fn derive(route: &[Node], initial_secret: &StaticSecret) -> Self { let initial_shared_secret = PublicKey::from(initial_secret); - let mut routing_keys = Vec::with_capacity(route.len()); + let mut expanded_shared_secrets = Vec::with_capacity(route.len()); let mut blinding_factors = vec![initial_secret.clone()]; for (i, node) in route.iter().enumerate() { let shared_key = blinding_factors .iter() .fold(node.pub_key, |acc, blinding_factor| { + // a nasty hack to convert `SharedSecret` into `PublicKey`, + // so that we could call `diffie_hellman` repeatedly PublicKey::from(blinding_factor.diffie_hellman(&acc).to_bytes()) }); - let node_routing_keys = RoutingKeys::derive(shared_key); + let expanded_shared_secret = expand_shared_secret(shared_key.as_bytes()); // it's not the last iteration if i != route.len() + 1 { - blinding_factors.push(node_routing_keys.blinding_factor.clone()); + blinding_factors.push(expanded_shared_secret.blinding_factor()); } - routing_keys.push(node_routing_keys); + expanded_shared_secrets.push(expanded_shared_secret); } Self { initial_shared_secret, - routing_keys, + expanded_shared_secrets, + } + } + + #[deprecated] + pub fn derive_legacy(route: &[Node], initial_secret: &StaticSecret) -> Self { + let initial_secret_scalar = Scalar::from_bytes_mod_order(initial_secret.to_bytes()); + + let initial_shared_secret = + curve25519_dalek::MontgomeryPoint::mul_base(&initial_secret_scalar); + + let mut expanded_shared_secrets = Vec::with_capacity(route.len()); + + let mut accumulator = initial_secret_scalar; + for (i, node) in route.iter().enumerate() { + // pub^{a * b * ...} + let pk_mt = curve25519_dalek::MontgomeryPoint(node.pub_key.to_bytes()); + let shared_key = pk_mt * accumulator; + + let expanded_shared_secret = expand_shared_secret(shared_key.as_bytes()); + + // it's not the last iteration + if i != route.len() + 1 { + // convert the blinding factor to a raw scalar and perform multiplication without + // any reduction (UNSAFE since we're not in ristretto) + let blinding_factor_scalar = + &Scalar::from_bytes_mod_order(*expanded_shared_secret.blinding_factor_bytes()); + + accumulator *= blinding_factor_scalar; + } + + expanded_shared_secrets.push(expanded_shared_secret); + } + Self { + initial_shared_secret: PublicKey::from(initial_shared_secret.0), + expanded_shared_secrets, } } } @@ -148,7 +109,7 @@ mod deriving_key_material { let empty_route: Vec = vec![]; let initial_secret = StaticSecret::random(); let key_material = KeyMaterial::derive(&empty_route, &initial_secret); - assert_eq!(0, key_material.routing_keys.len()); + assert_eq!(0, key_material.expanded_shared_secrets.len()); assert_eq!( PublicKey::from(&initial_secret).as_bytes(), key_material.initial_shared_secret.as_bytes() @@ -171,7 +132,7 @@ mod deriving_key_material { #[test] fn it_returns_number_of_shared_keys_equal_to_length_of_the_route() { let (_, _, key_material) = setup(); - assert_eq!(3, key_material.routing_keys.len()); + assert_eq!(3, key_material.expanded_shared_secrets.len()); } #[test] @@ -184,7 +145,7 @@ mod deriving_key_material { } #[test] - fn it_generates_correct_routing_keys() { + fn it_generates_correct_expanded_shared_secret() { let (route, initial_secret, key_material) = setup(); // The accumulator is the key to our blinding factors working. // If the accumulator value isn't incremented correctly, we risk passing an @@ -200,37 +161,14 @@ mod deriving_key_material { PublicKey::from(blinding_factor.diffie_hellman(&acc).to_bytes()) }); - let expected_routing_keys = RoutingKeys::derive(expected_shared_key); + let expected_expanded_ss = expand_shared_secret(expected_shared_key.as_bytes()); - expected_accumulator.push(expected_routing_keys.blinding_factor); - let expected_routing_keys = RoutingKeys::derive(expected_shared_key); - assert_eq!(expected_routing_keys, key_material.routing_keys[i]) + expected_accumulator.push(expected_expanded_ss.blinding_factor()); + assert_eq!( + expected_expanded_ss, + key_material.expanded_shared_secrets[i] + ) } } } } - -#[cfg(test)] -mod key_derivation_function { - use super::*; - - #[test] - fn it_expands_the_seed_key_to_expected_length() { - let initial_secret = StaticSecret::random(); - let shared_key = PublicKey::from(&initial_secret); - let routing_keys = RoutingKeys::derive(shared_key); - assert_eq!( - crypto::STREAM_CIPHER_KEY_SIZE, - routing_keys.stream_cipher_key.len() - ); - } - - #[test] - fn it_returns_the_same_output_for_two_equal_inputs() { - let initial_secret = StaticSecret::random(); - let shared_key = PublicKey::from(&initial_secret); - let routing_keys1 = RoutingKeys::derive(shared_key); - let routing_keys2 = RoutingKeys::derive(shared_key); - assert_eq!(routing_keys1, routing_keys2); - } -} diff --git a/src/header/mac.rs b/src/header/mac.rs index 9d5d8ee..d524bbb 100644 --- a/src/header/mac.rs +++ b/src/header/mac.rs @@ -25,9 +25,9 @@ use subtle::{Choice, ConstantTimeEq}; pub struct HeaderIntegrityMac(GenericArray); impl HeaderIntegrityMac { - pub(crate) fn compute(key: HeaderIntegrityMacKey, header_data: &[u8]) -> Self { + pub(crate) fn compute(key: &HeaderIntegrityMacKey, header_data: &[u8]) -> Self { let routing_info_mac = - crypto::compute_keyed_hmac::(&key, header_data); + crypto::compute_keyed_hmac::(key, header_data); // NOTE: BE EXTREMELY CAREFUL HOW YOU MANAGE THOSE BYTES // YOU CAN'T TREAT THEM AS NORMAL ONES @@ -47,7 +47,7 @@ impl HeaderIntegrityMac { pub fn verify( &self, - integrity_mac_key: HeaderIntegrityMacKey, + integrity_mac_key: &HeaderIntegrityMacKey, enc_routing_info: &[u8], ) -> bool { let recomputed_integrity_mac = Self::compute(integrity_mac_key, enc_routing_info); @@ -83,17 +83,17 @@ mod computing_integrity_mac { fn it_is_possible_to_verify_correct_mac() { let key = [2u8; INTEGRITY_MAC_KEY_SIZE]; let data = vec![3u8; ENCRYPTED_ROUTING_INFO_SIZE]; - let integrity_mac = HeaderIntegrityMac::compute(key, &data); + let integrity_mac = HeaderIntegrityMac::compute(&key, &data); - assert!(integrity_mac.verify(key, &data)); + assert!(integrity_mac.verify(&key, &data)); } #[test] fn it_lets_detecting_flipped_data_bits() { let key = [2u8; INTEGRITY_MAC_KEY_SIZE]; let mut data = vec![3u8; ENCRYPTED_ROUTING_INFO_SIZE]; - let integrity_mac = HeaderIntegrityMac::compute(key, &data); + let integrity_mac = HeaderIntegrityMac::compute(&key, &data); data[10] = !data[10]; - assert!(!integrity_mac.verify(key, &data)); + assert!(!integrity_mac.verify(&key, &data)); } } diff --git a/src/header/mod.rs b/src/header/mod.rs index daac4f5..ac89289 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Nym Technologies SA +// Copyright 2020-2025 Nym Technologies SA // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,14 +16,13 @@ use crate::constants::HEADER_INTEGRITY_MAC_SIZE; use crate::header::delays::Delay; use crate::header::filler::Filler; use crate::header::keys::{KeyMaterial, PayloadKey}; -use crate::header::routing::nodes::ParsedRawRoutingInformationData; use crate::header::routing::{EncapsulatedRoutingInformation, ENCRYPTED_ROUTING_INFO_SIZE}; +use crate::header::shared_secret::{ExpandSecret, ExpandedSharedSecret}; use crate::packet::ProcessedPacketData; use crate::payload::Payload; use crate::route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier}; -use crate::version::{Version, CURRENT_VERSION}; +use crate::version::{Version, CURRENT_VERSION, UPDATED_LEGACY_VERSION}; use crate::{Error, ErrorKind, ProcessedPacket, Result, SphinxPacket}; -use keys::RoutingKeys; use x25519_dalek::{PublicKey, StaticSecret}; pub mod delays; @@ -31,6 +30,7 @@ pub mod filler; pub mod keys; pub mod mac; pub mod routing; +pub mod shared_secret; // 32 represents size of a MontgomeryPoint on Curve25519 pub const HEADER_SIZE: usize = 32 + HEADER_INTEGRITY_MAC_SIZE + ENCRYPTED_ROUTING_INFO_SIZE; @@ -38,6 +38,7 @@ pub const HEADER_SIZE: usize = 32 + HEADER_INTEGRITY_MAC_SIZE + ENCRYPTED_ROUTIN #[derive(Debug)] #[cfg_attr(test, derive(Clone))] pub struct SphinxHeader { + /// Alpha element pub shared_secret: PublicKey, pub routing_info: Box, } @@ -110,6 +111,25 @@ impl SphinxHeader { Self::build_header(key_material, route, delays, destination, CURRENT_VERSION) } + #[deprecated] + #[allow(deprecated)] + pub fn new_legacy( + initial_secret: &StaticSecret, + route: &[Node], + delays: &[Delay], + destination: &Destination, + ) -> (Self, Vec) { + let key_material = keys::KeyMaterial::derive_legacy(route, initial_secret); + Self::build_header( + key_material, + route, + delays, + destination, + UPDATED_LEGACY_VERSION, + ) + } + + #[allow(deprecated)] pub fn new_versioned( initial_secret: &StaticSecret, route: &[Node], @@ -117,7 +137,11 @@ impl SphinxHeader { destination: &Destination, version: Version, ) -> (Self, Vec) { - let key_material = keys::KeyMaterial::derive(route, initial_secret); + let key_material = if version.is_legacy() { + keys::KeyMaterial::derive_legacy(route, initial_secret) + } else { + keys::KeyMaterial::derive(route, initial_secret) + }; Self::build_header(key_material, route, delays, destination, version) } @@ -128,12 +152,12 @@ impl SphinxHeader { destination: &Destination, version: Version, ) -> (Self, Vec) { - let filler_string = Filler::new(&key_material.routing_keys[..route.len() - 1]); + let filler_string = Filler::new(&key_material.expanded_shared_secrets[..route.len() - 1]); let routing_info = Box::new(routing::EncapsulatedRoutingInformation::new( route, destination, delays, - &key_material.routing_keys, + &key_material.expanded_shared_secrets, filler_string, version, )); @@ -145,90 +169,120 @@ impl SphinxHeader { routing_info, }, key_material - .routing_keys + .expanded_shared_secrets .iter() - .map(|routing_key| routing_key.payload_key) + .map(|expanded| *expanded.payload_key()) .collect(), ) } - /// Processes the header with the provided derived keys. - /// It could be useful in the situation where sender is re-using initial secret - /// and we could cache processing results. - /// - /// However, unless you know exactly what you are doing, you should NEVER use this method! - /// Prefer normal [process] instead. - pub fn process_with_derived_keys( + // note: this method is currently removed because there's too many branches to support + // with the legacy compatibility requirements. + // this will be revisited in the future + // /// Processes the header with the provided shared secret + // /// It could be useful in the situation where sender is re-using initial secret + // /// and we could cache processing results. + // /// + // /// However, unless you know exactly what you are doing, you should NEVER use this method! + // /// Prefer normal [process] instead. + // pub fn process_with_cached_secret( + // &self, + // expanded_secret: ExpandedSharedSecret, + // cached_new_shared_secret: PublicKey, + // ) -> Result { + // self.ensure_valid_mac(expanded_secret.header_integrity_hmac_key())?; + // + // let unwrapped_routing_information = self + // .routing_info + // .enc_routing_information + // .unwrap(expanded_secret.stream_cipher_key())?; + // + // match unwrapped_routing_information.data { + // ParsedRawRoutingInformationData::ForwardHop { + // next_hop_address, + // delay, + // new_routing_information, + // } => { + // if let Some(new_blinded_secret) = cached_new_derived_secret { + // Ok(ProcessedHeader { + // payload_key: *expanded_secret.payload_key(), + // version: unwrapped_routing_information.version, + // data: ProcessedHeaderData::ForwardHop { + // updated_header: SphinxHeader { + // shared_secret: new_blinded_secret, + // routing_info: new_routing_information, + // }, + // next_hop_address, + // delay, + // }, + // }) + // } else { + // Err(Error::new( + // ErrorKind::InvalidHeader, + // "tried to process forward hop without blinded secret", + // )) + // } + // } + // ParsedRawRoutingInformationData::FinalHop { + // destination, + // identifier, + // } => Ok(ProcessedHeader { + // payload_key: *expanded_secret.payload_key(), + // version: unwrapped_routing_information.version, + // data: ProcessedHeaderData::FinalHop { + // destination, + // identifier, + // }, + // }), + // } + // } + + /// Processes the header with the provided expanded shared secret + /// It could be useful in the situation where caller has already derived the value, + /// because, for example, he had to obtain the reply tag. + #[allow(deprecated)] + pub fn process_with_expanded_secret( self, - new_blinded_secret: &Option, - routing_keys: &RoutingKeys, + expanded_secret: &ExpandedSharedSecret, ) -> Result { - if !self.routing_info.integrity_mac.verify( - routing_keys.header_integrity_hmac_key, - self.routing_info.enc_routing_information.as_ref(), - ) { - return Err(Error::new( - ErrorKind::InvalidHeader, - "failed to verify integrity MAC", - )); - } + self.ensure_header_integrity(expanded_secret)?; let unwrapped_routing_information = self .routing_info .enc_routing_information - .unwrap(&routing_keys.stream_cipher_key)?; - match unwrapped_routing_information.data { - ParsedRawRoutingInformationData::ForwardHop { - next_hop_address, - delay, - new_routing_information, - } => { - if let Some(new_blinded_secret) = new_blinded_secret { - Ok(ProcessedHeader { - payload_key: routing_keys.payload_key, - version: unwrapped_routing_information.version, - data: ProcessedHeaderData::ForwardHop { - updated_header: SphinxHeader { - shared_secret: *new_blinded_secret, - routing_info: new_routing_information, - }, - next_hop_address, - delay, - }, - }) - } else { - Err(Error::new( - ErrorKind::InvalidHeader, - "tried to process forward hop without blinded secret", - )) - } - } - ParsedRawRoutingInformationData::FinalHop { - destination, - identifier, - } => Ok(ProcessedHeader { - payload_key: routing_keys.payload_key, - version: unwrapped_routing_information.version, - data: ProcessedHeaderData::FinalHop { - destination, - identifier, - }, - }), + .unwrap(expanded_secret.stream_cipher_key())?; + + if unwrapped_routing_information.version.is_legacy() { + Ok(unwrapped_routing_information + .legacy_into_processed_header(self.shared_secret, expanded_secret)) + } else { + Ok(unwrapped_routing_information + .into_processed_header(self.shared_secret, expanded_secret)) } } - /// Using the provided shared_secret and node's secret key, derive all routing keys for this hop. - pub fn compute_routing_keys( - shared_secret: &PublicKey, + #[allow(deprecated)] + pub fn process(self, node_secret_key: &StaticSecret) -> Result { + let expanded_secret = self.compute_expanded_shared_secret(node_secret_key); + self.process_with_expanded_secret(&expanded_secret) + } + + /// Using the provided packet's alpha and node's secret key, expand it into the output of all required random oracles + pub fn compute_expanded_shared_secret( + &self, node_secret_key: &StaticSecret, - ) -> RoutingKeys { - let shared_key = PublicKey::from(node_secret_key.diffie_hellman(shared_secret).to_bytes()); - keys::RoutingKeys::derive(shared_key) + ) -> ExpandedSharedSecret { + node_secret_key + .diffie_hellman(&self.shared_secret) + .expand_shared_secret() } - fn ensure_valid_mac(&self, routing_keys: &RoutingKeys) -> Result<()> { + pub fn ensure_header_integrity( + &self, + expanded_shared_secret: &ExpandedSharedSecret, + ) -> Result<()> { if !self.routing_info.integrity_mac.verify( - routing_keys.header_integrity_hmac_key, + expanded_shared_secret.header_integrity_hmac_key(), self.routing_info.enc_routing_information.as_ref(), ) { return Err(Error::new( @@ -239,16 +293,39 @@ impl SphinxHeader { Ok(()) } - pub fn process(self, node_secret_key: &StaticSecret) -> Result { - let routing_keys = Self::compute_routing_keys(&self.shared_secret, node_secret_key); - self.ensure_valid_mac(&routing_keys)?; + #[deprecated] + pub fn unchecked_process_as_current( + self, + node_secret_key: &StaticSecret, + ) -> Result { + let expanded_secret = self.compute_expanded_shared_secret(node_secret_key); + self.ensure_header_integrity(&expanded_secret)?; let unwrapped_routing_information = self .routing_info .enc_routing_information - .unwrap(&routing_keys.stream_cipher_key)?; + .unwrap(expanded_secret.stream_cipher_key())?; - Ok(unwrapped_routing_information.into_processed_header(self.shared_secret, routing_keys)) + Ok(unwrapped_routing_information + .into_processed_header(self.shared_secret, &expanded_secret)) + } + + #[deprecated] + #[allow(deprecated)] + pub fn unchecked_process_as_legacy( + self, + node_secret_key: &StaticSecret, + ) -> Result { + let expanded_secret = self.compute_expanded_shared_secret(node_secret_key); + self.ensure_header_integrity(&expanded_secret)?; + + let unwrapped_routing_information = self + .routing_info + .enc_routing_information + .unwrap(expanded_secret.stream_cipher_key())?; + + Ok(unwrapped_routing_information + .legacy_into_processed_header(self.shared_secret, &expanded_secret)) } pub fn to_bytes(&self) -> Vec { @@ -298,11 +375,24 @@ impl SphinxHeader { let new_shared_secret = blinding_factor.diffie_hellman(&shared_secret); PublicKey::from(new_shared_secret.to_bytes()) } + + /// use unreduced multiplication for legacy backwards compatibility + #[deprecated] + fn legacy_blind_shared_secret( + shared_secret: PublicKey, + blinding_factor: StaticSecret, + ) -> PublicKey { + let blinding_factor = + curve25519_dalek::scalar::Scalar::from_bytes_mod_order(blinding_factor.to_bytes()); + let mp = curve25519_dalek::montgomery::MontgomeryPoint(shared_secret.to_bytes()); + PublicKey::from((blinding_factor * mp).to_bytes()) + } } #[cfg(test)] mod create_and_process_sphinx_packet_header { use super::*; + use crate::crypto::PrivateKey; use crate::{ constants::NODE_ADDRESS_LENGTH, test_utils::fixtures::{destination_fixture, keygen}, @@ -377,17 +467,129 @@ mod create_and_process_sphinx_packet_header { _ => panic!(), }; } + + #[test] + #[allow(deprecated)] + fn it_returns_correct_routing_information_at_each_hop_for_route_of_3_mixnodes_with_legacy_processing( + ) { + let node1_sk = PrivateKey::from([ + 202, 37, 190, 57, 90, 36, 148, 40, 37, 203, 207, 229, 5, 80, 8, 77, 227, 95, 67, 20, + 47, 83, 220, 34, 164, 207, 5, 212, 97, 151, 142, 168, + ]); + let node1_pk = PublicKey::from([ + 105, 91, 210, 146, 245, 155, 27, 169, 192, 123, 75, 121, 19, 204, 59, 187, 190, 150, + 131, 118, 151, 77, 180, 144, 253, 88, 6, 212, 63, 5, 51, 7, + ]); + + let node2_sk = PrivateKey::from([ + 130, 31, 0, 83, 139, 16, 225, 239, 132, 130, 122, 18, 217, 187, 91, 87, 250, 137, 152, + 220, 254, 153, 246, 249, 252, 43, 153, 191, 152, 48, 154, 170, + ]); + let node2_pk = PublicKey::from([ + 178, 47, 98, 179, 103, 199, 16, 245, 35, 85, 9, 63, 138, 212, 83, 233, 169, 31, 205, + 20, 73, 238, 141, 204, 19, 35, 226, 138, 44, 67, 225, 46, + ]); + + let node3_sk = PrivateKey::from([ + 116, 204, 108, 186, 75, 233, 232, 22, 79, 66, 65, 176, 196, 246, 253, 30, 133, 153, + 109, 229, 133, 177, 40, 42, 175, 72, 80, 70, 161, 7, 187, 155, + ]); + let node3_pk = PublicKey::from([ + 21, 93, 4, 80, 178, 177, 7, 218, 192, 213, 58, 157, 239, 242, 139, 45, 75, 26, 225, 54, + 174, 21, 159, 25, 62, 87, 187, 46, 92, 246, 136, 81, + ]); + + let node1 = Node::new( + NodeAddressBytes::from_bytes([1u8; NODE_ADDRESS_LENGTH]), + node1_pk, + ); + let node2 = Node::new( + NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]), + node2_pk, + ); + let node3 = Node::new( + NodeAddressBytes::from_bytes([3u8; NODE_ADDRESS_LENGTH]), + node3_pk, + ); + let initial_secret = StaticSecret::from([ + 104, 106, 58, 28, 53, 127, 216, 216, 8, 84, 74, 171, 220, 71, 145, 25, 205, 24, 253, + 23, 120, 124, 255, 114, 14, 246, 179, 119, 101, 14, 10, 89, + ]); + + let route = [node1, node2, node3]; + let route_destination = destination_fixture(); + let average_delay = 1; + let delays = + delays::generate_from_average_duration(route.len(), Duration::from_secs(average_delay)); + let (sphinx_header, _) = + SphinxHeader::new_legacy(&initial_secret, &route, &delays, &route_destination); + + //let (new_header, next_hop_address, _) = sphinx_header.process(node1_sk).unwrap(); + let new_header = match sphinx_header + .unchecked_process_as_legacy(&node1_sk) + .unwrap() + .data + { + ProcessedHeaderData::ForwardHop { + updated_header, + next_hop_address, + delay, + } => { + assert_eq!( + NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]), + next_hop_address + ); + assert_eq!(delays[0].to_nanos(), delay.to_nanos()); + updated_header + } + _ => panic!(), + }; + + let new_header2 = match new_header + .unchecked_process_as_legacy(&node2_sk) + .unwrap() + .data + { + ProcessedHeaderData::ForwardHop { + updated_header, + next_hop_address, + delay, + } => { + assert_eq!( + NodeAddressBytes::from_bytes([3u8; NODE_ADDRESS_LENGTH]), + next_hop_address + ); + assert_eq!(delays[1].to_nanos(), delay.to_nanos()); + updated_header + } + _ => panic!(), + }; + match new_header2 + .unchecked_process_as_legacy(&node3_sk) + .unwrap() + .data + { + ProcessedHeaderData::FinalHop { + destination, + identifier: _, + } => { + assert_eq!(route_destination.address, destination); + } + _ => panic!(), + }; + } } #[cfg(test)] mod unwrap_routing_information { - use super::*; use crate::constants::{ HEADER_INTEGRITY_MAC_SIZE, NODE_ADDRESS_LENGTH, NODE_META_INFO_SIZE, STREAM_CIPHER_OUTPUT_LENGTH, }; use crate::crypto; - use crate::header::routing::nodes::EncryptedRoutingInformation; + use crate::header::routing::nodes::{ + EncryptedRoutingInformation, ParsedRawRoutingInformationData, + }; use crate::header::routing::{ENCRYPTED_ROUTING_INFO_SIZE, FORWARD_HOP}; use crate::utils; @@ -457,7 +659,7 @@ mod unwrap_routing_information { } #[cfg(test)] -mod unwrapping_using_previously_derived_keys { +mod unwrapping_using_previously_expanded_shared_secret { use super::*; use crate::constants::NODE_ADDRESS_LENGTH; use crate::test_utils::fixtures::{destination_fixture, keygen}; @@ -489,11 +691,12 @@ mod unwrapping_using_previously_derived_keys { _ => unreachable!(), }; - let new_secret = normally_unwrapped.shared_secret; - let routing_keys = SphinxHeader::compute_routing_keys(&initial_secret, &node1_sk); + let expanded_secret = node1_sk + .diffie_hellman(&initial_secret) + .expand_shared_secret(); let derived_unwrapped = match sphinx_header - .process_with_derived_keys(&Some(new_secret), &routing_keys) + .process_with_expanded_secret(&expanded_secret) .unwrap() .data { @@ -536,10 +739,12 @@ mod unwrapping_using_previously_derived_keys { _ => unreachable!(), }; - let routing_keys = SphinxHeader::compute_routing_keys(&initial_secret, &node1_sk); + let expanded_secret = node1_sk + .diffie_hellman(&initial_secret) + .expand_shared_secret(); let derived_unwrapped = sphinx_header - .process_with_derived_keys(&None, &routing_keys) + .process_with_expanded_secret(&expanded_secret) .unwrap(); let derived_unwrapped = match derived_unwrapped.data { diff --git a/src/header/routing/destination.rs b/src/header/routing/destination.rs index d918238..98acfe1 100644 --- a/src/header/routing/destination.rs +++ b/src/header/routing/destination.rs @@ -96,7 +96,7 @@ pub(super) struct PaddedFinalRoutingInformation { impl PaddedFinalRoutingInformation { pub(super) fn encrypt( self, - key: StreamCipherKey, + key: &StreamCipherKey, route_len: usize, ) -> EncryptedPaddedFinalRoutingInformation { debug_assert_eq!( @@ -105,7 +105,7 @@ impl PaddedFinalRoutingInformation { ); let pseudorandom_bytes = crypto::generate_pseudorandom_bytes( - &key, + key, &STREAM_CIPHER_INIT_VECTOR, STREAM_CIPHER_OUTPUT_LENGTH, ); @@ -133,13 +133,13 @@ impl EncryptedPaddedFinalRoutingInformation { filler: Filler, route_len: usize, ) -> EncryptedRoutingInformation { - let filler_value = filler.get_value(); + let filler_bytes: Vec = filler.into(); debug_assert_eq!( - filler_value.len(), + filler_bytes.len(), FILLER_STEP_SIZE_INCREASE * (route_len - 1) ); - let final_routing_info_vec: Vec = self.value.into_iter().chain(filler_value).collect(); + let final_routing_info_vec: Vec = self.value.into_iter().chain(filler_bytes).collect(); // sanity check assertion, because we're using vectors debug_assert_eq!(final_routing_info_vec.len(), ENCRYPTED_ROUTING_INFO_SIZE); @@ -153,11 +153,12 @@ impl EncryptedPaddedFinalRoutingInformation { #[cfg(test)] mod test_encapsulating_final_routing_information_and_mac { use crate::header::mac::HeaderIntegrityMac; + use crate::test_utils::fixtures::expanded_shared_secret_fixture; use crate::version::Version; use crate::{ header::routing::EncapsulatedRoutingInformation, test_utils::{ - fixtures::{destination_fixture, filler_fixture, routing_keys_fixture}, + fixtures::{destination_fixture, filler_fixture}, random_node, }, }; @@ -166,23 +167,26 @@ mod test_encapsulating_final_routing_information_and_mac { fn it_returns_mac_on_correct_data() { // this test is created to ensure we MAC the encrypted data BEFORE it is truncated let route = [random_node(), random_node(), random_node()]; - let routing_keys = [ - routing_keys_fixture(), - routing_keys_fixture(), - routing_keys_fixture(), + let expanded_shared_secret = [ + expanded_shared_secret_fixture(), + expanded_shared_secret_fixture(), + expanded_shared_secret_fixture(), ]; let filler = filler_fixture(route.len() - 1); let destination = destination_fixture(); let final_routing_info = EncapsulatedRoutingInformation::for_final_hop( &destination, - routing_keys.last().unwrap(), + expanded_shared_secret.last().unwrap(), filler, route.len(), Version::default(), ); let expected_mac = HeaderIntegrityMac::compute( - routing_keys.last().unwrap().header_integrity_hmac_key, + expanded_shared_secret + .last() + .unwrap() + .header_integrity_hmac_key(), final_routing_info.enc_routing_information.as_ref(), ); assert_eq!( @@ -195,12 +199,14 @@ mod test_encapsulating_final_routing_information_and_mac { #[cfg(test)] mod test_encapsulating_final_routing_information { use super::*; - use crate::test_utils::fixtures::{destination_fixture, filler_fixture, routing_keys_fixture}; + use crate::test_utils::fixtures::{ + destination_fixture, expanded_shared_secret_fixture, filler_fixture, + }; #[test] fn it_produces_result_of_length_filler_plus_padded_concatenated_destination_and_identifier_and_flag_for_route_of_length_5( ) { - let final_keys = routing_keys_fixture(); + let final_keys = expanded_shared_secret_fixture(); let route_len = 5; let filler = filler_fixture(route_len - 1); let destination = destination_fixture(); @@ -208,7 +214,7 @@ mod test_encapsulating_final_routing_information { let final_routing_header = FinalRoutingInformation::new(&destination, route_len, Version::default()) .add_padding(route_len) - .encrypt(final_keys.stream_cipher_key, route_len) + .encrypt(final_keys.stream_cipher_key(), route_len) .combine_with_filler(filler, route_len); let expected_final_header_len = ENCRYPTED_ROUTING_INFO_SIZE; @@ -222,7 +228,7 @@ mod test_encapsulating_final_routing_information { #[test] fn it_produces_result_of_length_filler_plus_padded_concatenated_destination_and_identifier_and_flag_for_route_of_length_3( ) { - let final_keys = routing_keys_fixture(); + let final_keys = expanded_shared_secret_fixture(); let route_len = 3; let filler = filler_fixture(route_len - 1); let destination = destination_fixture(); @@ -230,7 +236,7 @@ mod test_encapsulating_final_routing_information { let final_routing_header = FinalRoutingInformation::new(&destination, route_len, Version::default()) .add_padding(route_len) - .encrypt(final_keys.stream_cipher_key, route_len) + .encrypt(final_keys.stream_cipher_key(), route_len) .combine_with_filler(filler, route_len); let expected_final_header_len = ENCRYPTED_ROUTING_INFO_SIZE; @@ -244,7 +250,7 @@ mod test_encapsulating_final_routing_information { #[test] fn it_produces_result_of_length_filler_plus_padded_concatenated_destination_and_identifier_and_flag_for_route_of_length_1( ) { - let final_keys = routing_keys_fixture(); + let final_keys = expanded_shared_secret_fixture(); let route_len = 1; let filler = filler_fixture(route_len - 1); let destination = destination_fixture(); @@ -252,7 +258,7 @@ mod test_encapsulating_final_routing_information { let final_routing_header = FinalRoutingInformation::new(&destination, route_len, Version::default()) .add_padding(route_len) - .encrypt(final_keys.stream_cipher_key, route_len) + .encrypt(final_keys.stream_cipher_key(), route_len) .combine_with_filler(filler, route_len); let expected_final_header_len = ENCRYPTED_ROUTING_INFO_SIZE; @@ -266,14 +272,14 @@ mod test_encapsulating_final_routing_information { #[test] #[should_panic] fn it_panics_if_it_receives_filler_different_than_filler_step_multiplied_with_i() { - let final_keys = routing_keys_fixture(); + let final_keys = expanded_shared_secret_fixture(); let route_len = 3; let filler = filler_fixture(route_len); let destination = destination_fixture(); FinalRoutingInformation::new(&destination, route_len, Version::default()) .add_padding(route_len) - .encrypt(final_keys.stream_cipher_key, route_len) + .encrypt(final_keys.stream_cipher_key(), route_len) .combine_with_filler(filler, route_len); } } diff --git a/src/header/routing/mod.rs b/src/header/routing/mod.rs index 2014e4e..3d7aa1f 100644 --- a/src/header/routing/mod.rs +++ b/src/header/routing/mod.rs @@ -15,10 +15,10 @@ use crate::constants::{HEADER_INTEGRITY_MAC_SIZE, MAX_PATH_LENGTH, NODE_META_INFO_SIZE}; use crate::header::delays::Delay; use crate::header::filler::Filler; -use crate::header::keys::RoutingKeys; use crate::header::mac::HeaderIntegrityMac; use crate::header::routing::destination::FinalRoutingInformation; use crate::header::routing::nodes::{EncryptedRoutingInformation, RoutingInformation}; +use crate::header::shared_secret::ExpandedSharedSecret; use crate::route::{Destination, Node, NodeAddressBytes}; use crate::version::Version; use crate::{Error, ErrorKind, Result}; @@ -43,7 +43,7 @@ pub struct EncapsulatedRoutingInformation { } impl EncapsulatedRoutingInformation { - pub fn encapsulate( + pub(crate) fn encapsulate( enc_routing_information: EncryptedRoutingInformation, integrity_mac: HeaderIntegrityMac, ) -> Self { @@ -53,18 +53,18 @@ impl EncapsulatedRoutingInformation { } } - pub fn new( + pub(crate) fn new( route: &[Node], destination: &Destination, delays: &[Delay], - routing_keys: &[RoutingKeys], + expanded_shared_secrets: &[ExpandedSharedSecret], filler: Filler, version: Version, ) -> Self { - assert_eq!(route.len(), routing_keys.len()); + assert_eq!(route.len(), expanded_shared_secrets.len()); assert_eq!(delays.len(), route.len()); - let final_keys = match routing_keys.last() { + let final_keys = match expanded_shared_secrets.last() { Some(k) => k, None => panic!("empty keys"), }; @@ -76,30 +76,31 @@ impl EncapsulatedRoutingInformation { encapsulated_destination_routing_info, delays, route, - routing_keys, + expanded_shared_secrets, version, ) } fn for_final_hop( dest: &Destination, - routing_keys: &RoutingKeys, + expanded_shared_secret: &ExpandedSharedSecret, filler: Filler, route_len: usize, version: Version, ) -> Self { FinalRoutingInformation::new(dest, route_len, version) .add_padding(route_len) // add padding to obtain correct destination length - .encrypt(routing_keys.stream_cipher_key, route_len) // encrypt with the key of final node (in our case service provider) + .encrypt(expanded_shared_secret.stream_cipher_key(), route_len) // encrypt with the key of final node (in our case service provider) .combine_with_filler(filler, route_len) // add filler to get header of correct length - .encapsulate_with_mac(routing_keys.header_integrity_hmac_key) // combine the previous data with a MAC on the header (also calculated with the SPs key) + .encapsulate_with_mac(expanded_shared_secret.header_integrity_hmac_key()) + // combine the previous data with a MAC on the header (also calculated with the SPs key) } fn for_forward_hops( encapsulated_destination_routing_info: Self, delays: &[Delay], - route: &[Node], // [Mix0, Mix1, Mix2, ..., Mix_{v-1}, Mix_v] - routing_keys: &[RoutingKeys], // [Keys0, Keys1, Keys2, ..., Keys_{v-1}, Keys_v] + route: &[Node], // [Mix0, Mix1, Mix2, ..., Mix_{v-1}, Mix_v] + expanded_shared_secrets: &[ExpandedSharedSecret], // [Keys0, Keys1, Keys2, ..., Keys_{v-1}, Keys_v] version: Version, ) -> Self { route @@ -108,7 +109,9 @@ impl EncapsulatedRoutingInformation { .map(|node| node.address.as_bytes()) // we only care about the address field .zip( // we need both route (i.e. address field) and corresponding keys of the PREVIOUS hop - routing_keys.iter().take(routing_keys.len() - 1), // we don't want last element - it was already used to encrypt the destination + expanded_shared_secrets + .iter() + .take(expanded_shared_secrets.len() - 1), // we don't want last element - it was already used to encrypt the destination ) .zip(delays.iter().take(delays.len() - 1)) // no need for the delay for the final node .rev() // we are working from the 'inside' @@ -119,15 +122,15 @@ impl EncapsulatedRoutingInformation { // (encrypted with Keys_v) encapsulated_destination_routing_info, |next_hop_encapsulated_routing_information, - ((current_node_address, previous_node_routing_keys), delay)| { + ((current_node_address, previous_node), delay)| { RoutingInformation::new( NodeAddressBytes::from_bytes(current_node_address), delay.to_owned(), next_hop_encapsulated_routing_information, version, ) - .encrypt(previous_node_routing_keys.stream_cipher_key) - .encapsulate_with_mac(previous_node_routing_keys.header_integrity_hmac_key) + .encrypt(previous_node.stream_cipher_key()) + .encapsulate_with_mac(previous_node.header_integrity_hmac_key()) }, ) } @@ -179,7 +182,7 @@ impl EncapsulatedRoutingInformation { mod encapsulating_all_routing_information { use super::*; use crate::test_utils::{ - fixtures::{destination_fixture, filler_fixture, routing_keys_fixture}, + fixtures::{destination_fixture, expanded_shared_secret_fixture, filler_fixture}, random_node, }; @@ -193,7 +196,10 @@ mod encapsulating_all_routing_information { Delay::new_from_nanos(20), Delay::new_from_nanos(30), ]; - let keys = [routing_keys_fixture(), routing_keys_fixture()]; + let keys = [ + expanded_shared_secret_fixture(), + expanded_shared_secret_fixture(), + ]; let filler = filler_fixture(route.len() - 1); EncapsulatedRoutingInformation::new( @@ -217,9 +223,9 @@ mod encapsulating_all_routing_information { Delay::new_from_nanos(30), ]; let keys = [ - routing_keys_fixture(), - routing_keys_fixture(), - routing_keys_fixture(), + expanded_shared_secret_fixture(), + expanded_shared_secret_fixture(), + expanded_shared_secret_fixture(), ]; let filler = filler_fixture(route.len() - 1); @@ -244,9 +250,9 @@ mod encapsulating_all_routing_information { Delay::new_from_nanos(30), ]; let keys = [ - routing_keys_fixture(), - routing_keys_fixture(), - routing_keys_fixture(), + expanded_shared_secret_fixture(), + expanded_shared_secret_fixture(), + expanded_shared_secret_fixture(), ]; let filler = filler_fixture(route.len() - 1); @@ -288,7 +294,7 @@ mod encapsulating_all_routing_information { mod encapsulating_forward_routing_information { use super::*; use crate::test_utils::{ - fixtures::{destination_fixture, filler_fixture, routing_keys_fixture}, + fixtures::{destination_fixture, expanded_shared_secret_fixture, filler_fixture}, random_node, }; @@ -302,9 +308,9 @@ mod encapsulating_forward_routing_information { let delay2 = Delay::new_from_nanos(30); let delays = [delay0, delay1, delay2].to_vec(); let routing_keys = [ - routing_keys_fixture(), - routing_keys_fixture(), - routing_keys_fixture(), + expanded_shared_secret_fixture(), + expanded_shared_secret_fixture(), + expanded_shared_secret_fixture(), ]; let filler = filler_fixture(route.len() - 1); let filler_copy = filler_fixture(route.len() - 1); @@ -353,8 +359,8 @@ mod encapsulating_forward_routing_information { destination_routing_info_copy, Version::default(), ) - .encrypt(routing_keys[1].stream_cipher_key) - .encapsulate_with_mac(routing_keys[1].header_integrity_hmac_key); + .encrypt(routing_keys[1].stream_cipher_key()) + .encapsulate_with_mac(routing_keys[1].header_integrity_hmac_key()); // this is what first mix should receive let layer_0_routing = RoutingInformation::new( @@ -363,8 +369,8 @@ mod encapsulating_forward_routing_information { layer_1_routing, Version::default(), ) - .encrypt(routing_keys[0].stream_cipher_key) - .encapsulate_with_mac(routing_keys[0].header_integrity_hmac_key); + .encrypt(routing_keys[0].stream_cipher_key()) + .encapsulate_with_mac(routing_keys[0].header_integrity_hmac_key()); assert_eq!( routing_info.enc_routing_information.as_ref().to_vec(), diff --git a/src/header/routing/nodes.rs b/src/header/routing/nodes.rs index 0dfad3d..09ad9c2 100644 --- a/src/header/routing/nodes.rs +++ b/src/header/routing/nodes.rs @@ -19,12 +19,13 @@ use crate::constants::{ use crate::crypto; use crate::crypto::STREAM_CIPHER_INIT_VECTOR; use crate::header::delays::Delay; -use crate::header::keys::{HeaderIntegrityMacKey, RoutingKeys, StreamCipherKey}; +use crate::header::keys::{HeaderIntegrityMacKey, StreamCipherKey}; use crate::header::mac::HeaderIntegrityMac; use crate::header::routing::{ EncapsulatedRoutingInformation, RoutingFlag, ENCRYPTED_ROUTING_INFO_SIZE, FINAL_HOP, FORWARD_HOP, TRUNCATED_ROUTING_INFO_SIZE, }; +use crate::header::shared_secret::ExpandedSharedSecret; use crate::header::{ProcessedHeader, ProcessedHeaderData, SphinxHeader}; use crate::route::{DestinationAddressBytes, NodeAddressBytes, SURBIdentifier}; use crate::utils; @@ -78,12 +79,12 @@ impl RoutingInformation { .collect() } - pub(super) fn encrypt(self, key: StreamCipherKey) -> EncryptedRoutingInformation { + pub(super) fn encrypt(self, key: &StreamCipherKey) -> EncryptedRoutingInformation { let routing_info_components = self.concatenate_components(); assert_eq!(ENCRYPTED_ROUTING_INFO_SIZE, routing_info_components.len()); let pseudorandom_bytes = crypto::generate_pseudorandom_bytes( - &key, + key, &STREAM_CIPHER_INIT_VECTOR, STREAM_CIPHER_OUTPUT_LENGTH, ); @@ -136,7 +137,7 @@ impl EncryptedRoutingInformation { pub(super) fn encapsulate_with_mac( self, - key: HeaderIntegrityMacKey, + key: &HeaderIntegrityMacKey, ) -> EncapsulatedRoutingInformation { let integrity_mac = HeaderIntegrityMac::compute(key, &self.value); EncapsulatedRoutingInformation { @@ -212,7 +213,7 @@ impl ParsedRawRoutingInformation { pub(crate) fn into_processed_header( self, shared_secret: PublicKey, - routing_keys: RoutingKeys, + expanded_shared_secret: &ExpandedSharedSecret, ) -> ProcessedHeader { match self.data { ParsedRawRoutingInformationData::ForwardHop { @@ -221,13 +222,54 @@ impl ParsedRawRoutingInformation { new_routing_information, } => { // blind the shared_secret in the header - let new_shared_secret = SphinxHeader::blind_the_shared_secret( - shared_secret, - routing_keys.blinding_factor, - ); + let new_shared_secret = expanded_shared_secret.blind_shared_secret(shared_secret); + + ProcessedHeader { + payload_key: *expanded_shared_secret.payload_key(), + version: self.version, + data: ProcessedHeaderData::ForwardHop { + updated_header: SphinxHeader { + shared_secret: new_shared_secret, + routing_info: new_routing_information, + }, + next_hop_address, + delay, + }, + } + } + ParsedRawRoutingInformationData::FinalHop { + destination, + identifier, + } => ProcessedHeader { + payload_key: *expanded_shared_secret.payload_key(), + version: self.version, + data: ProcessedHeaderData::FinalHop { + destination, + identifier, + }, + }, + } + } + + #[deprecated] + #[allow(deprecated)] + pub(crate) fn legacy_into_processed_header( + self, + shared_secret: PublicKey, + expanded_shared_secret: &ExpandedSharedSecret, + ) -> ProcessedHeader { + match self.data { + ParsedRawRoutingInformationData::ForwardHop { + next_hop_address, + delay, + new_routing_information, + } => { + // blind the shared_secret in the header + let new_shared_secret = + expanded_shared_secret.legacy_blind_share_secret(shared_secret); ProcessedHeader { - payload_key: routing_keys.payload_key, + payload_key: *expanded_shared_secret.payload_key(), version: self.version, data: ProcessedHeaderData::ForwardHop { updated_header: SphinxHeader { @@ -243,7 +285,7 @@ impl ParsedRawRoutingInformation { destination, identifier, } => ProcessedHeader { - payload_key: routing_keys.payload_key, + payload_key: *expanded_shared_secret.payload_key(), version: self.version, data: ProcessedHeaderData::FinalHop { destination, @@ -345,11 +387,10 @@ type TruncatedRoutingInformation = [u8; TRUNCATED_ROUTING_INFO_SIZE]; mod preparing_header_layer { use super::*; use crate::constants::HeaderIntegrityHmacAlgorithm; + use crate::test_utils::fixtures::expanded_shared_secret_fixture; use crate::{ constants::HEADER_INTEGRITY_MAC_SIZE, - test_utils::fixtures::{ - encapsulated_routing_information_fixture, node_address_fixture, routing_keys_fixture, - }, + test_utils::fixtures::{encapsulated_routing_information_fixture, node_address_fixture}, }; #[test] @@ -357,7 +398,7 @@ mod preparing_header_layer { { let node_address = node_address_fixture(); let delay = Delay::new_from_nanos(10); - let previous_node_routing_keys = routing_keys_fixture(); + let previous_node_routing_keys = expanded_shared_secret_fixture(); let inner_layer_routing = encapsulated_routing_information_fixture(); let version = Version::default(); @@ -380,7 +421,7 @@ mod preparing_header_layer { .concat(); let pseudorandom_bytes = crypto::generate_pseudorandom_bytes( - &previous_node_routing_keys.stream_cipher_key, + previous_node_routing_keys.stream_cipher_key(), &STREAM_CIPHER_INIT_VECTOR, STREAM_CIPHER_OUTPUT_LENGTH, ); @@ -391,7 +432,7 @@ mod preparing_header_layer { ); let expected_routing_mac = crypto::compute_keyed_hmac::( - &previous_node_routing_keys.header_integrity_hmac_key, + previous_node_routing_keys.header_integrity_hmac_key(), &expected_encrypted_routing_info_vec, ); let mut expected_routing_mac = expected_routing_mac.into_bytes().to_vec(); @@ -399,8 +440,8 @@ mod preparing_header_layer { let next_layer_routing = RoutingInformation::new(node_address, delay, inner_layer_routing, Version::default()) - .encrypt(previous_node_routing_keys.stream_cipher_key) - .encapsulate_with_mac(previous_node_routing_keys.header_integrity_hmac_key); + .encrypt(previous_node_routing_keys.stream_cipher_key()) + .encapsulate_with_mac(previous_node_routing_keys.header_integrity_hmac_key()); assert_eq!( expected_encrypted_routing_info_vec, @@ -450,7 +491,7 @@ mod encrypting_routing_information { next_routing_information: next_routing, }; - let encrypted_data = routing_information.encrypt(key); + let encrypted_data = routing_information.encrypt(&key); let decryption_key_source = crypto::generate_pseudorandom_bytes( &key, &STREAM_CIPHER_INIT_VECTOR, diff --git a/src/header/shared_secret.rs b/src/header/shared_secret.rs new file mode 100644 index 0000000..0ac7eec --- /dev/null +++ b/src/header/shared_secret.rs @@ -0,0 +1,171 @@ +// Copyright 2025 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::constants::{ + BLINDING_FACTOR_SIZE, EXPANDED_SHARED_SECRET_LENGTH, HKDF_INPUT_SEED, INTEGRITY_MAC_KEY_SIZE, + PAYLOAD_KEY_SIZE, REPLAY_TAG_SIZE, +}; +use crate::crypto::STREAM_CIPHER_KEY_SIZE; +use crate::header::SphinxHeader; +use arrayref::array_ref; +use hkdf::Hkdf; +use sha2::Sha256; +use x25519_dalek::{PublicKey, SharedSecret, StaticSecret}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +pub(crate) trait ExpandSecret { + fn expand_shared_secret(&self) -> ExpandedSharedSecret; +} + +impl ExpandSecret for PublicKey { + fn expand_shared_secret(&self) -> ExpandedSharedSecret { + self.as_bytes().expand_shared_secret() + } +} + +impl ExpandSecret for SharedSecret { + fn expand_shared_secret(&self) -> ExpandedSharedSecret { + self.as_bytes().expand_shared_secret() + } +} + +impl ExpandSecret for [u8; 32] { + fn expand_shared_secret(&self) -> ExpandedSharedSecret { + expand_shared_secret(self) + } +} + +#[derive(Zeroize, ZeroizeOnDrop, Clone, PartialEq, Debug)] +pub struct ExpandedSharedSecret([u8; EXPANDED_SHARED_SECRET_LENGTH]); + +impl ExpandedSharedSecret { + // order of bytes is quite important here to preserve backwards compatibility. + // replay tag has not been used before so it **has to** be created last + + /// Output of the hρ random oracle + pub(crate) fn stream_cipher_key(&self) -> &[u8; STREAM_CIPHER_KEY_SIZE] { + array_ref!(&self.0, 0, STREAM_CIPHER_KEY_SIZE) + } + + /// Output of the hμ random oracle + pub(crate) fn header_integrity_hmac_key(&self) -> &[u8; INTEGRITY_MAC_KEY_SIZE] { + array_ref!(&self.0, STREAM_CIPHER_KEY_SIZE, INTEGRITY_MAC_KEY_SIZE) + } + + /// Output of the hπ random oracle + // NOTE: currently we expand it to full PRP key + pub(crate) fn payload_key(&self) -> &[u8; PAYLOAD_KEY_SIZE] { + array_ref!( + &self.0, + STREAM_CIPHER_KEY_SIZE + INTEGRITY_MAC_KEY_SIZE, + PAYLOAD_KEY_SIZE + ) + } + + /// Output of the hb random oracle + pub(crate) fn blinding_factor_bytes(&self) -> &[u8; BLINDING_FACTOR_SIZE] { + array_ref!( + &self.0, + STREAM_CIPHER_KEY_SIZE + INTEGRITY_MAC_KEY_SIZE + PAYLOAD_KEY_SIZE, + BLINDING_FACTOR_SIZE + ) + } + + pub(crate) fn blinding_factor(&self) -> StaticSecret { + StaticSecret::from(*self.blinding_factor_bytes()) + } + + pub(crate) fn blind_shared_secret(&self, shared_secret: PublicKey) -> PublicKey { + SphinxHeader::blind_the_shared_secret(shared_secret, self.blinding_factor()) + } + + #[deprecated] + #[allow(deprecated)] + pub(crate) fn legacy_blind_share_secret(&self, shared_secret: PublicKey) -> PublicKey { + SphinxHeader::legacy_blind_shared_secret(shared_secret, self.blinding_factor()) + } + + /// Output of the h𝜏 random oracle + pub fn replay_tag(&self) -> &[u8; REPLAY_TAG_SIZE] { + array_ref!( + &self.0, + STREAM_CIPHER_KEY_SIZE + + INTEGRITY_MAC_KEY_SIZE + + PAYLOAD_KEY_SIZE + + BLINDING_FACTOR_SIZE, + REPLAY_TAG_SIZE + ) + } +} + +pub(crate) fn expand_shared_secret(shared_secret: &[u8; 32]) -> ExpandedSharedSecret { + let hkdf = Hkdf::::new(None, shared_secret); + + let mut output = [0u8; EXPANDED_SHARED_SECRET_LENGTH]; + // SAFETY: the length of the provided okm is within the allowed range + #[allow(clippy::unwrap_used)] + hkdf.expand(HKDF_INPUT_SEED, &mut output).unwrap(); + + ExpandedSharedSecret(output) +} + +#[cfg(test)] +mod expanding_shared_secret { + use super::*; + use crate::test_utils::fixtures::mock_shared_secret; + use crate::test_utils::{assert_zeroize, assert_zeroize_on_drop, seeded_rng}; + + #[test] + fn expanded_shared_secret_is_zeroized() { + assert_zeroize::(); + assert_zeroize_on_drop::(); + } + + // using old values from legacy `RoutingKeys` + #[test] + fn results_in_same_values_as_old_implementation() { + let mut rng = seeded_rng([1u8; 32]); + let ss = mock_shared_secret(&mut rng); + + let expected_sck = [ + 186, 234, 152, 113, 202, 124, 191, 228, 173, 89, 91, 8, 127, 251, 214, 200, + ]; + let expected_hihk = [ + 28, 222, 17, 227, 46, 180, 170, 7, 34, 52, 177, 142, 150, 137, 142, 222, + ]; + let expected_pk = [ + 210, 209, 81, 241, 254, 123, 36, 81, 155, 85, 115, 40, 101, 210, 97, 8, 196, 104, 61, + 23, 165, 190, 191, 236, 203, 69, 15, 230, 70, 100, 161, 136, 53, 88, 116, 118, 81, 57, + 58, 181, 232, 102, 149, 93, 239, 255, 156, 205, 0, 146, 110, 117, 137, 59, 102, 170, + 87, 250, 175, 207, 193, 107, 112, 154, 247, 220, 110, 135, 32, 106, 20, 152, 14, 132, + 89, 154, 249, 24, 176, 40, 30, 182, 195, 209, 124, 59, 58, 201, 209, 255, 80, 151, 109, + 226, 157, 232, 48, 128, 56, 159, 90, 168, 229, 60, 106, 14, 50, 215, 198, 200, 168, 24, + 159, 224, 240, 119, 23, 242, 61, 129, 54, 36, 140, 245, 127, 159, 230, 5, 52, 142, 254, + 52, 168, 171, 139, 100, 206, 16, 94, 219, 68, 113, 141, 159, 4, 233, 189, 144, 164, + 202, 180, 74, 214, 66, 96, 185, 70, 191, 155, 18, 210, 52, 123, 71, 231, 225, 79, 0, + 196, 25, 217, 231, 133, 191, 96, 119, 103, 182, 200, 36, 215, 62, 203, 149, 214, 139, + 32, 70, 66, 30, 63, 102, + ]; + let expected_bf = [ + 227, 64, 184, 235, 140, 62, 232, 172, 235, 42, 58, 169, 241, 253, 245, 2, 136, 149, 74, + 48, 6, 165, 145, 133, 190, 105, 222, 218, 248, 172, 49, 188, + ]; + + let expanded = expand_shared_secret(ss.as_bytes()); + assert_eq!(expanded.stream_cipher_key(), &expected_sck); + assert_eq!(expanded.header_integrity_hmac_key(), &expected_hihk); + assert_eq!(expanded.payload_key(), &expected_pk); + assert_eq!(expanded.blinding_factor_bytes(), &expected_bf); + } +} diff --git a/src/lib.rs b/src/lib.rs index adf58be..bb3383e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,8 @@ mod utils; // cleaned-up modules + imports here: pub mod error; + +#[cfg(test)] pub mod test_utils; pub mod version; diff --git a/src/packet/mod.rs b/src/packet/mod.rs index eb4aeff..9b50a8f 100644 --- a/src/packet/mod.rs +++ b/src/packet/mod.rs @@ -1,4 +1,4 @@ -use crate::header::keys::RoutingKeys; +use crate::header::shared_secret::ExpandedSharedSecret; use crate::version::Version; use crate::{ header::{self, delays::Delay, HEADER_SIZE}, @@ -67,21 +67,16 @@ impl SphinxPacket { HEADER_SIZE + self.payload.len() } - /// Processes the header with the provided derived keys. - /// It could be useful in the situation where sender is re-using initial secret - /// and we could cache processing results. - /// - /// However, unless you know exactly what you are doing, you should NEVER use this method! - /// Prefer normal [process] instead. - #[deprecated] - pub fn process_with_derived_keys( + /// Processes the packet with the provided expanded secret. + /// It could be useful in the situation where caller has already derived the value, + /// because, for example, he had to obtain the reply tag. + pub fn process_with_expanded_secret( self, - new_blinded_secret: &Option, - routing_keys: &RoutingKeys, + expanded_shared_secret: &ExpandedSharedSecret, ) -> Result { let unwrapped_header = self .header - .process_with_derived_keys(new_blinded_secret, routing_keys)?; + .process_with_expanded_secret(expanded_shared_secret)?; let unwrapped_payload = self.payload.unwrap(unwrapped_header.payload_key())?; Ok(unwrapped_header.attach_payload(unwrapped_payload)) diff --git a/src/surb/mod.rs b/src/surb/mod.rs index 39f544f..06ff6fb 100644 --- a/src/surb/mod.rs +++ b/src/surb/mod.rs @@ -3,6 +3,7 @@ use crate::header::delays::Delay; use crate::header::keys::PayloadKey; use crate::payload::Payload; use crate::route::{Destination, Node, NodeAddressBytes}; +use crate::version::Version; use crate::{header, SphinxPacket}; use crate::{Error, ErrorKind, Result}; use header::{SphinxHeader, HEADER_SIZE}; @@ -33,6 +34,7 @@ pub struct SURBMaterial { surb_route: Vec, surb_delays: Vec, surb_destination: Destination, + version: Version, } impl SURBMaterial { @@ -41,6 +43,7 @@ impl SURBMaterial { surb_route: route, surb_delays: delays, surb_destination: destination, + version: Default::default(), } } @@ -49,6 +52,12 @@ impl SURBMaterial { let surb_initial_secret = StaticSecret::random(); SURB::new(surb_initial_secret, self) } + + #[must_use] + pub fn with_version(mut self, version: Version) -> Self { + self.version = version; + self + } } #[allow(non_snake_case)] @@ -72,11 +81,13 @@ impl SURB { return Err(Error::new(ErrorKind::InvalidSURB, format!("creating SURB for contradictory data: route has len {} while there are {} delays generated", surb_route.len(), surb_delays.len()))); } - let (header, payload_keys) = header::SphinxHeader::new( + #[allow(deprecated)] + let (header, payload_keys) = header::SphinxHeader::new_versioned( &surb_initial_secret, &surb_route, &surb_delays, &surb_destination, + surb_material.version, ); Ok(SURB { diff --git a/src/test_utils.rs b/src/test_utils.rs index 240c84c..54bd548 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -16,20 +16,20 @@ use crate::{ constants::NODE_ADDRESS_LENGTH, route::{Node, NodeAddressBytes}, }; +use rand::SeedableRng; +use rand_chacha::ChaCha20Rng; +use zeroize::{Zeroize, ZeroizeOnDrop}; pub mod fixtures { - - use x25519_dalek::{PublicKey, StaticSecret}; - + use crate::header::shared_secret::{expand_shared_secret, ExpandedSharedSecret}; + use crate::test_utils::test_rng; use crate::{ constants::{ - BLINDING_FACTOR_SIZE, DESTINATION_ADDRESS_LENGTH, HEADER_INTEGRITY_MAC_SIZE, - IDENTIFIER_LENGTH, INTEGRITY_MAC_KEY_SIZE, NODE_ADDRESS_LENGTH, PAYLOAD_KEY_SIZE, + DESTINATION_ADDRESS_LENGTH, HEADER_INTEGRITY_MAC_SIZE, IDENTIFIER_LENGTH, + NODE_ADDRESS_LENGTH, }, - crypto, header::{ filler::{Filler, FILLER_STEP_SIZE_INCREASE}, - keys::RoutingKeys, mac::HeaderIntegrityMac, routing::{ nodes::EncryptedRoutingInformation, EncapsulatedRoutingInformation, @@ -38,6 +38,22 @@ pub mod fixtures { }, route::{Destination, DestinationAddressBytes, NodeAddressBytes, SURBIdentifier}, }; + use rand_chacha::ChaCha20Rng; + use x25519_dalek::{PublicKey, SharedSecret, StaticSecret}; + + pub(crate) fn mock_shared_secret(mut rng: &mut ChaCha20Rng) -> SharedSecret { + let sk1 = StaticSecret::random_from_rng(&mut rng); + let pk1 = PublicKey::from(&sk1); + + let sk2 = StaticSecret::random_from_rng(&mut rng); + sk2.diffie_hellman(&pk1) + } + + pub fn expanded_shared_secret_fixture() -> ExpandedSharedSecret { + let mut rng = test_rng(); + let ss = mock_shared_secret(&mut rng); + expand_shared_secret(ss.as_bytes()) + } pub fn destination_address_fixture() -> DestinationAddressBytes { DestinationAddressBytes::from_bytes([1u8; DESTINATION_ADDRESS_LENGTH]) @@ -58,17 +74,8 @@ pub mod fixtures { } } - pub fn routing_keys_fixture() -> RoutingKeys { - RoutingKeys { - stream_cipher_key: [1u8; crypto::STREAM_CIPHER_KEY_SIZE], - header_integrity_hmac_key: [2u8; INTEGRITY_MAC_KEY_SIZE], - payload_key: [3u8; PAYLOAD_KEY_SIZE], - blinding_factor: [4u8; BLINDING_FACTOR_SIZE].into(), - } - } - pub fn filler_fixture(i: usize) -> Filler { - Filler::from_raw(vec![9u8; FILLER_STEP_SIZE_INCREASE * i]) + Filler::from(vec![9u8; FILLER_STEP_SIZE_INCREASE * i]) } pub fn encrypted_routing_information_fixture() -> EncryptedRoutingInformation { @@ -100,3 +107,17 @@ pub fn random_node() -> Node { pub_key: (&random_private_key).into(), } } + +// make sure output is deterministic +pub(super) fn test_rng() -> ChaCha20Rng { + let dummy_seed = [42u8; 32]; + seeded_rng(dummy_seed) +} + +pub(super) fn seeded_rng(seed: [u8; 32]) -> ChaCha20Rng { + ChaCha20Rng::from_seed(seed) +} + +pub(crate) fn assert_zeroize_on_drop() {} + +pub(crate) fn assert_zeroize() {} diff --git a/tests/integration_test.rs b/tests/integration_test.rs index cd30880..d2fa7f0 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -17,9 +17,16 @@ extern crate sphinx_packet; use sphinx_packet::header::delays; use sphinx_packet::route::{Destination, Node}; use sphinx_packet::SphinxPacket; +use x25519_dalek::{PublicKey, StaticSecret}; // const PAYLOAD_SIZE: usize = 1024; +fn keygen() -> (StaticSecret, PublicKey) { + let private_key = StaticSecret::random(); + let public_key = PublicKey::from(&private_key); + (private_key, public_key) +} + #[cfg(test)] mod create_and_process_sphinx_packet { use super::*; @@ -29,7 +36,6 @@ mod create_and_process_sphinx_packet { }; use sphinx_packet::packet::ProcessedPacketData; use sphinx_packet::route::{DestinationAddressBytes, NodeAddressBytes}; - use sphinx_packet::test_utils::fixtures::keygen; use std::time::Duration; #[test] @@ -114,7 +120,6 @@ mod converting_sphinx_packet_to_and_from_bytes { }; use sphinx_packet::packet::ProcessedPacketData; use sphinx_packet::route::{DestinationAddressBytes, NodeAddressBytes}; - use sphinx_packet::test_utils::fixtures::keygen; use std::time::Duration; #[test] @@ -232,13 +237,13 @@ mod converting_sphinx_packet_to_and_from_bytes { #[cfg(test)] mod create_and_process_surb { use super::*; + use sphinx_packet::constants::{DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH}; use sphinx_packet::packet::ProcessedPacketData; - use sphinx_packet::route::NodeAddressBytes; + use sphinx_packet::route::{DestinationAddressBytes, NodeAddressBytes}; use sphinx_packet::surb::{SURBMaterial, SURB}; use sphinx_packet::{ constants::{NODE_ADDRESS_LENGTH, PAYLOAD_SIZE, SECURITY_PARAMETER}, packet::builder::DEFAULT_PAYLOAD_SIZE, - test_utils::fixtures::{destination_fixture, keygen}, }; use std::time::Duration; use x25519_dalek::StaticSecret; @@ -262,7 +267,10 @@ mod create_and_process_surb { }; let surb_route = vec![node1, node2, node3]; - let surb_destination = destination_fixture(); + let surb_destination = Destination { + address: DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]), + identifier: [4u8; IDENTIFIER_LENGTH], + }; let surb_initial_secret = StaticSecret::random(); let surb_delays = delays::generate_from_average_duration(surb_route.len(), Duration::from_secs(3));