diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index fc1676ab67..eeadec5ea1 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -21,8 +21,8 @@ use nym_crypto::asymmetric::ed25519; use nym_gateway_requests::registration::handshake::client_handshake; use nym_gateway_requests::{ BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt, - SensitiveServerResponse, ServerResponse, SharedGatewayKey, SharedSymmetricKey, - CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION, + GatewayRequestsError, SensitiveServerResponse, ServerResponse, SharedGatewayKey, + SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION, }; use nym_sphinx::forwarding::packet::MixPacket; use nym_statistics_common::clients::connection::ConnectionStatsEvent; @@ -662,6 +662,7 @@ impl GatewayClient { let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv(); let supports_auth_v2 = gw_protocol.supports_authenticate_v2(); + let supports_key_rotation_info = gw_protocol.supports_key_rotation_packet(); if !supports_aes_gcm_siv { warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV"); @@ -669,6 +670,9 @@ impl GatewayClient { if !supports_auth_v2 { warn!("this gateway is on an old version that doesn't support authentication v2") } + if !supports_key_rotation_info { + warn!("this gateway is on an old version that doesn't support key rotation packets") + } if self.authenticated { debug!("Already authenticated"); @@ -849,6 +853,22 @@ impl GatewayClient { } } + fn mix_packet_to_ws_message(&self, packet: MixPacket) -> Result { + // note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should + // be more explicit in the naming? + let req = if self.negotiated_protocol.supports_key_rotation_packet() { + BinaryRequest::ForwardSphinxV2 { packet } + } else { + BinaryRequest::ForwardSphinx { packet } + }; + + req.into_ws_message( + self.shared_key + .as_ref() + .expect("no shared key present even though we're authenticated!"), + ) + } + pub async fn batch_send_mix_packets( &mut self, packets: Vec, @@ -877,13 +897,7 @@ impl GatewayClient { let messages: Result, _> = packets .into_iter() - .map(|mix_packet| { - BinaryRequest::ForwardSphinx { packet: mix_packet }.into_ws_message( - self.shared_key - .as_ref() - .expect("no shared key present even though we're authenticated!"), - ) - }) + .map(|mix_packet| self.mix_packet_to_ws_message(mix_packet)) .collect(); if let Err(err) = self @@ -949,13 +963,8 @@ impl GatewayClient { if !self.connection.is_established() { return Err(GatewayClientError::ConnectionNotEstablished); } - // note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should - // be more explicit in the naming? - let msg = BinaryRequest::ForwardSphinx { packet: mix_packet }.into_ws_message( - self.shared_key - .as_ref() - .expect("no shared key present even though we're authenticated!"), - )?; + + let msg = self.mix_packet_to_ws_message(mix_packet)?; self.send_with_reconnection_on_failure(msg).await } diff --git a/common/client-libs/mixnet-client/src/client.rs b/common/client-libs/mixnet-client/src/client.rs index 8788a0aee7..66e5a4d48c 100644 --- a/common/client-libs/mixnet-client/src/client.rs +++ b/common/client-libs/mixnet-client/src/client.rs @@ -3,11 +3,10 @@ use dashmap::DashMap; use futures::StreamExt; -use nym_sphinx::addressing::nodes::NymNodeRoutingAddress; +use nym_sphinx::forwarding::packet::MixPacket; use nym_sphinx::framing::codec::NymCodec; use nym_sphinx::framing::packet::FramedNymPacket; -use nym_sphinx::params::PacketType; -use nym_sphinx::NymPacket; +use nym_sphinx::params::SphinxKeyRotation; use std::io; use std::net::SocketAddr; use std::ops::Deref; @@ -49,12 +48,7 @@ impl Config { pub trait SendWithoutResponse { // Without response in this context means we will not listen for anything we might get back (not // that we should get anything), including any possible io errors - fn send_without_response( - &self, - address: NymNodeRoutingAddress, - packet: NymPacket, - packet_type: PacketType, - ) -> io::Result<()>; + fn send_without_response(&self, packet: MixPacket) -> io::Result<()>; } pub struct Client { @@ -65,7 +59,7 @@ pub struct Client { #[derive(Default, Clone)] pub struct ActiveConnections { - inner: Arc>, + inner: Arc>, } impl ActiveConnections { @@ -82,7 +76,7 @@ impl ActiveConnections { } impl Deref for ActiveConnections { - type Target = DashMap; + type Target = DashMap; fn deref(&self) -> &Self::Target { &self.inner } @@ -196,7 +190,7 @@ impl Client { } } - fn make_connection(&self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) { + fn make_connection(&self, address: SocketAddr, pending_packet: FramedNymPacket) { let (sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size); // this CAN'T fail because we just created the channel which has a non-zero capacity @@ -233,7 +227,7 @@ impl Client { connections_count.fetch_add(1, Ordering::SeqCst); ManagedConnection::new( - address.into(), + address, receiver, initial_connection_timeout, current_reconnection_attempt, @@ -246,18 +240,14 @@ impl Client { } impl SendWithoutResponse for Client { - fn send_without_response( - &self, - address: NymNodeRoutingAddress, - packet: NymPacket, - packet_type: PacketType, - ) -> io::Result<()> { - trace!("Sending packet to {address:?}"); - let framed_packet = FramedNymPacket::new(packet, packet_type); + fn send_without_response(&self, packet: MixPacket) -> io::Result<()> { + let address = packet.next_hop_address(); + trace!("Sending packet to {address}"); + let framed_packet = FramedNymPacket::from(packet); let Some(sender) = self.active_connections.get_mut(&address) else { // there was never a connection to begin with - debug!("establishing initial connection to {}", address); + debug!("establishing initial connection to {address}"); // it's not a 'big' error, but we did not manage to send the packet, but queue the packet // for sending for as soon as the connection is created self.make_connection(address, framed_packet); diff --git a/common/gateway-requests/src/lib.rs b/common/gateway-requests/src/lib.rs index 0d300bc4a4..bdb9a30a0f 100644 --- a/common/gateway-requests/src/lib.rs +++ b/common/gateway-requests/src/lib.rs @@ -19,7 +19,7 @@ pub use shared_key::{ SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey, }; -pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION; +pub const CURRENT_PROTOCOL_VERSION: u8 = EMBEDDED_KEY_ROTATION_INFO_VERSION; /// Defines the current version of the communication protocol between gateway and clients. /// It has to be incremented for any breaking change. @@ -28,10 +28,12 @@ pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION; // 2 - changes to client credentials structure // 3 - change to AES-GCM-SIV and non-zero IVs // 4 - introduction of v2 authentication protocol to prevent reply attacks +// 5 - add key rotation information to the serialised mix packet pub const INITIAL_PROTOCOL_VERSION: u8 = 1; pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2; pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3; pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4; +pub const EMBEDDED_KEY_ROTATION_INFO_VERSION: u8 = 5; // TODO: could using `Mac` trait here for OutputSize backfire? // Should hmac itself be exposed, imported and used instead? @@ -40,6 +42,7 @@ pub type LegacyGatewayMacSize = bool; fn supports_authenticate_v2(&self) -> bool; + fn supports_key_rotation_packet(&self) -> bool; } impl GatewayProtocolVersionExt for Option { @@ -52,4 +55,9 @@ impl GatewayProtocolVersionExt for Option { let Some(protocol) = *self else { return false }; protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION } + + fn supports_key_rotation_packet(&self) -> bool { + let Some(protocol) = *self else { return false }; + protocol >= EMBEDDED_KEY_ROTATION_INFO_VERSION + } } diff --git a/common/gateway-requests/src/types/binary_request.rs b/common/gateway-requests/src/types/binary_request.rs index 17eab6c9cb..b52ee819cd 100644 --- a/common/gateway-requests/src/types/binary_request.rs +++ b/common/gateway-requests/src/types/binary_request.rs @@ -11,6 +11,9 @@ use tungstenite::Message; #[non_exhaustive] pub enum BinaryRequest { ForwardSphinx { packet: MixPacket }, + + // identical to `ForwardSphinx`, but also contains information about sphinx key rotation used + ForwardSphinxV2 { packet: MixPacket }, } #[repr(u8)] @@ -18,6 +21,9 @@ pub enum BinaryRequest { #[non_exhaustive] pub enum BinaryRequestKind { ForwardSphinx = 1, + + // identical to `ForwardSphinx`, but also contains information about sphinx key rotation used + ForwardSphinxV2 = 2, } // Right now the only valid `BinaryRequest` is a request to forward a sphinx packet. @@ -29,6 +35,7 @@ impl BinaryRequest { pub fn kind(&self) -> BinaryRequestKind { match self { BinaryRequest::ForwardSphinx { .. } => BinaryRequestKind::ForwardSphinx, + BinaryRequest::ForwardSphinxV2 { .. } => BinaryRequestKind::ForwardSphinxV2, } } @@ -38,9 +45,13 @@ impl BinaryRequest { ) -> Result { match kind { BinaryRequestKind::ForwardSphinx => { - let packet = MixPacket::try_from_bytes(plaintext)?; + let packet = MixPacket::try_from_v1_bytes(plaintext)?; Ok(BinaryRequest::ForwardSphinx { packet }) } + BinaryRequestKind::ForwardSphinxV2 => { + let packet = MixPacket::try_from_v2_bytes(plaintext)?; + Ok(BinaryRequest::ForwardSphinxV2 { packet }) + } } } @@ -58,7 +69,8 @@ impl BinaryRequest { let kind = self.kind(); let plaintext = match self { - BinaryRequest::ForwardSphinx { packet } => packet.into_bytes()?, + BinaryRequest::ForwardSphinx { packet } => packet.into_v1_bytes()?, + BinaryRequest::ForwardSphinxV2 { packet } => packet.into_v2_bytes()?, }; BinaryData::make_encrypted_blob(kind as u8, &plaintext, shared_key) @@ -70,7 +82,9 @@ impl BinaryRequest { ) -> Result { // all variants are currently encrypted let blob = match self { - BinaryRequest::ForwardSphinx { .. } => self.into_encrypted_tagged_bytes(shared_key)?, + BinaryRequest::ForwardSphinx { .. } | BinaryRequest::ForwardSphinxV2 { .. } => { + self.into_encrypted_tagged_bytes(shared_key)? + } }; Ok(Message::Binary(blob)) diff --git a/common/nymsphinx/anonymous-replies/src/reply_surb.rs b/common/nymsphinx/anonymous-replies/src/reply_surb.rs index 821c0c022b..2c1dc6bf71 100644 --- a/common/nymsphinx/anonymous-replies/src/reply_surb.rs +++ b/common/nymsphinx/anonymous-replies/src/reply_surb.rs @@ -8,7 +8,7 @@ use nym_sphinx_addressing::nodes::{ NymNodeRoutingAddress, NymNodeRoutingAddressError, MAX_NODE_ADDRESS_UNPADDED_LEN, }; use nym_sphinx_params::packet_sizes::PacketSize; -use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm}; +use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, SphinxKeyRotation}; use nym_sphinx_types::{ NymPacket, SURBMaterial, SphinxError, HEADER_SIZE, NODE_ADDRESS_LENGTH, SURB, X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION, @@ -46,44 +46,7 @@ pub enum ReplySurbError { pub struct ReplySurb { pub(crate) surb: SURB, pub(crate) encryption_key: SurbEncryptionKey, -} - -// Serialize + Deserialize is not really used anymore (it was for a CBOR experiment) -// however, if we decided we needed it again, it's already here -impl Serialize for ReplySurb { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_bytes(&self.to_bytes()) - } -} - -impl<'de> Deserialize<'de> for ReplySurb { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: Deserializer<'de>, - { - struct ReplySurbVisitor; - - impl Visitor<'_> for ReplySurbVisitor { - type Value = ReplySurb; - - fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { - write!(formatter, "A replySURB must contain a valid symmetric encryption key and a correctly formed sphinx header") - } - - fn visit_bytes(self, bytes: &[u8]) -> Result - where - E: SerdeError, - { - ReplySurb::from_bytes(bytes) - .map_err(|_| SerdeError::invalid_length(bytes.len(), &self)) - } - } - - deserializer.deserialize_bytes(ReplySurbVisitor) - } + // pub(crate) used_key_rotation: SphinxKeyRotation, } impl ReplySurb { @@ -123,6 +86,7 @@ impl ReplySurb { Ok(ReplySurb { surb: surb_material.construct_SURB().unwrap(), encryption_key: SurbEncryptionKey::new(rng), + // used_key_rotation: SphinxKeyRotation::from(topology.current_key_rotation()), }) } @@ -198,8 +162,14 @@ impl ReplySurb { .use_surb(message_bytes, packet_size.payload_size()) .expect("this error indicates inconsistent message length checking - it shouldn't have happened!"); - let first_hop_address = NymNodeRoutingAddress::try_from(first_hop).unwrap(); + let first_hop_address = NymNodeRoutingAddress::try_from(first_hop)?; Ok((NymPacket::Sphinx(packet), first_hop_address)) } } + +pub struct AppliedReplySurb { + packet: NymPacket, + first_hop_address: NymNodeRoutingAddress, + key_rotation: SphinxKeyRotation, +} diff --git a/common/nymsphinx/cover/src/lib.rs b/common/nymsphinx/cover/src/lib.rs index 55d71e6c3d..1f2e20b773 100644 --- a/common/nymsphinx/cover/src/lib.rs +++ b/common/nymsphinx/cover/src/lib.rs @@ -146,7 +146,8 @@ where )?, }; - Ok(MixPacket::new(first_hop_address, packet, packet_type)) + todo!() + // Ok(MixPacket::new(first_hop_address, packet, packet_type)) } /// Helper function used to determine if given message represents a loop cover message. diff --git a/common/nymsphinx/forwarding/src/packet.rs b/common/nymsphinx/forwarding/src/packet.rs index 97b1419aa1..226e5a71f3 100644 --- a/common/nymsphinx/forwarding/src/packet.rs +++ b/common/nymsphinx/forwarding/src/packet.rs @@ -2,24 +2,35 @@ // SPDX-License-Identifier: Apache-2.0 use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; -use nym_sphinx_params::{PacketSize, PacketType}; +use nym_sphinx_params::{PacketSize, PacketType, SphinxKeyRotation}; use nym_sphinx_types::{NymPacket, NymPacketError}; -use std::fmt::{self, Debug, Formatter}; +use nym_sphinx_params::key_rotation::InvalidSphinxKeyRotation; +use nym_sphinx_params::packet_sizes::InvalidPacketSize; +use nym_sphinx_params::packet_types::InvalidPacketType; +use std::net::SocketAddr; use thiserror::Error; #[derive(Debug, Error)] pub enum MixPacketFormattingError { #[error("too few bytes provided to recover from bytes")] TooFewBytesProvided, - #[error("provided packet mode is invalid")] - InvalidPacketType, - #[error("received request had invalid size - received {0}")] - InvalidPacketSize(usize), + + #[error("provided packet mode is invalid: {0}")] + InvalidPacketType(#[from] InvalidPacketType), + + #[error("received request had an invalid packet size: {0}")] + InvalidPacketSize(#[from] InvalidPacketSize), + + #[error("provided key rotation is invalid: {0}")] + InvalidKeyRotation(#[from] InvalidSphinxKeyRotation), + #[error("address field was incorrectly encoded")] InvalidAddress, + #[error("received sphinx packet was malformed")] MalformedSphinxPacket, + #[error("Packet: {0}")] Packet(#[from] NymPacketError), } @@ -30,20 +41,12 @@ impl From for MixPacketFormattingError { } } +#[derive(Debug)] pub struct MixPacket { next_hop: NymNodeRoutingAddress, packet: NymPacket, packet_type: PacketType, -} - -impl Debug for MixPacket { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "MixPacket to {:?} with packet_type {:?}. Packet {:?}", - self.next_hop, self.packet_type, self.packet - ) - } + key_rotation: SphinxKeyRotation, } impl MixPacket { @@ -51,11 +54,13 @@ impl MixPacket { next_hop: NymNodeRoutingAddress, packet: NymPacket, packet_type: PacketType, + key_rotation: SphinxKeyRotation, ) -> Self { MixPacket { next_hop, packet, packet_type, + key_rotation, } } @@ -63,6 +68,10 @@ impl MixPacket { self.next_hop } + pub fn next_hop_address(&self) -> SocketAddr { + self.next_hop.into() + } + pub fn packet(&self) -> &NymPacket { &self.packet } @@ -71,45 +80,94 @@ impl MixPacket { self.packet } + pub fn key_rotation(&self) -> SphinxKeyRotation { + self.key_rotation + } + pub fn packet_type(&self) -> PacketType { self.packet_type } // the message is formatted as follows: // packet_type || FIRST_HOP || packet - pub fn try_from_bytes(b: &[u8]) -> Result { - let packet_type = match PacketType::try_from(b[0]) { - Ok(mode) => mode, - Err(_) => return Err(MixPacketFormattingError::InvalidPacketType), - }; + pub fn try_from_v1_bytes(b: &[u8]) -> Result { + // we need at least 1 byte to read packet type and another one to read type of the encoded first hop address + if b.len() < 2 { + return Err(MixPacketFormattingError::TooFewBytesProvided); + } + + let packet_type = PacketType::try_from(b[0])?; let next_hop = NymNodeRoutingAddress::try_from_bytes(&b[1..])?; let addr_offset = next_hop.bytes_min_len(); let packet_data = &b[addr_offset + 1..]; let packet_size = packet_data.len(); - if PacketSize::get_type(packet_size).is_err() { - Err(MixPacketFormattingError::InvalidPacketSize(packet_size)) - } else { - let packet = match packet_type { - PacketType::Outfox => NymPacket::outfox_from_bytes(packet_data)?, - _ => NymPacket::sphinx_from_bytes(packet_data)?, - }; - Ok(MixPacket { - next_hop, - packet, - packet_type, - }) - } + // make sure the received data length corresponds to a valid packet + let _ = PacketSize::get_type(packet_size)?; + + let packet = match packet_type { + PacketType::Mix => NymPacket::sphinx_from_bytes(packet_data)?, + PacketType::Outfox => NymPacket::outfox_from_bytes(packet_data)?, + }; + + Ok(MixPacket { + next_hop, + packet, + packet_type, + key_rotation: SphinxKeyRotation::Unknown, + }) } - pub fn into_bytes(self) -> Result, MixPacketFormattingError> { + pub fn into_v1_bytes(self) -> Result, MixPacketFormattingError> { Ok(std::iter::once(self.packet_type as u8) .chain(self.next_hop.as_bytes()) .chain(self.packet.to_bytes()?) .collect()) } + + // the message is formatted as follows: + // packet_type || KEY_ROTATION || FIRST_HOP || packet + pub fn try_from_v2_bytes(b: &[u8]) -> Result { + // we need at least 1 byte to read packet type, 1 byte to read key rotation + // and finally another one to read type of the encoded first hop address + if b.len() < 3 { + return Err(MixPacketFormattingError::TooFewBytesProvided); + } + + let packet_type = PacketType::try_from(b[0])?; + let key_rotation = SphinxKeyRotation::try_from(b[1])?; + + let next_hop = NymNodeRoutingAddress::try_from_bytes(&b[2..])?; + let addr_offset = next_hop.bytes_min_len(); + + let packet_data = &b[addr_offset + 2..]; + let packet_size = packet_data.len(); + + // make sure the received data length corresponds to a valid packet + let _ = PacketSize::get_type(packet_size)?; + + let packet = match packet_type { + PacketType::Mix => NymPacket::sphinx_from_bytes(packet_data)?, + PacketType::Outfox => NymPacket::outfox_from_bytes(packet_data)?, + }; + + Ok(MixPacket { + next_hop, + packet, + packet_type, + key_rotation, + }) + } + + pub fn into_v2_bytes(self) -> Result, MixPacketFormattingError> { + Ok(std::iter::once(self.packet_type as u8) + .chain(std::iter::once(self.key_rotation as u8)) + .chain(self.next_hop.as_bytes()) + .chain(self.packet.to_bytes()?) + .collect()) + } } // TODO: test for serialization and errors! diff --git a/common/nymsphinx/framing/src/packet.rs b/common/nymsphinx/framing/src/packet.rs index c05eec088a..b487539144 100644 --- a/common/nymsphinx/framing/src/packet.rs +++ b/common/nymsphinx/framing/src/packet.rs @@ -3,6 +3,7 @@ use crate::codec::NymCodecError; use bytes::{BufMut, BytesMut}; +use nym_sphinx_forwarding::packet::MixPacket; use nym_sphinx_params::key_rotation::SphinxKeyRotation; use nym_sphinx_params::packet_sizes::PacketSize; use nym_sphinx_params::packet_version::{PacketVersion, CURRENT_PACKET_VERSION}; @@ -18,6 +19,14 @@ pub struct FramedNymPacket { pub(crate) packet: NymPacket, } +impl From for FramedNymPacket { + fn from(packet: MixPacket) -> Self { + let typ = packet.packet_type(); + let rot = packet.key_rotation(); + FramedNymPacket::new(packet.into_packet(), typ, rot) + } +} + impl FramedNymPacket { pub fn new( packet: NymPacket, @@ -58,6 +67,10 @@ impl FramedNymPacket { &self.packet } + pub fn key_rotation(&self) -> SphinxKeyRotation { + self.header.key_rotation + } + pub fn is_sphinx(&self) -> bool { self.packet.is_sphinx() } diff --git a/common/nymsphinx/framing/src/processing.rs b/common/nymsphinx/framing/src/processing.rs index b896f9fcdf..22903b7c81 100644 --- a/common/nymsphinx/framing/src/processing.rs +++ b/common/nymsphinx/framing/src/processing.rs @@ -5,7 +5,7 @@ use crate::packet::FramedNymPacket; use nym_sphinx_acknowledgements::surb_ack::{SurbAck, SurbAckRecoveryError}; use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; use nym_sphinx_forwarding::packet::MixPacket; -use nym_sphinx_params::{PacketSize, PacketType}; +use nym_sphinx_params::{PacketSize, PacketType, SphinxKeyRotation}; use nym_sphinx_types::header::shared_secret::ExpandedSharedSecret; use nym_sphinx_types::{ Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, NymPacket, NymPacketError, @@ -153,6 +153,7 @@ impl PartiallyUnwrappedPacket { let packet_size = self.received_data.packet_size(); let packet_type = self.received_data.packet_type(); + let key_rotation = self.received_data.header.key_rotation; let packet = self.received_data.into_inner(); // currently partial unwrapping is only implemented for sphinx packets. @@ -167,7 +168,7 @@ impl PartiallyUnwrappedPacket { return Err(PacketProcessingError::PartialOutfoxProcessing); }; let processed_packet = packet.process_with_expanded_secret(&expanded_shared_secret)?; - wrap_processed_sphinx_packet(processed_packet, packet_size, packet_type) + wrap_processed_sphinx_packet(processed_packet, packet_size, packet_type, key_rotation) } pub fn replay_tag(&self) -> Option<&[u8; REPLAY_TAG_SIZE]> { @@ -192,13 +193,14 @@ pub fn process_framed_packet( ) -> Result { let packet_size = received.packet_size(); let packet_type = received.packet_type(); + let key_rotation = received.key_rotation(); // unwrap the sphinx packet let processed_packet = perform_framed_unwrapping(received, sphinx_key)?; // for forward packets, extract next hop and set delay (but do NOT delay here) // for final packets, extract SURBAck - perform_final_processing(processed_packet, packet_size, packet_type) + perform_final_processing(processed_packet, packet_size, packet_type, key_rotation) } fn perform_framed_unwrapping( @@ -223,6 +225,7 @@ fn wrap_processed_sphinx_packet( packet: nym_sphinx_types::ProcessedPacket, packet_size: PacketSize, packet_type: PacketType, + key_rotation: SphinxKeyRotation, ) -> Result { let processing_data = match packet.data { ProcessedPacketData::ForwardHop { @@ -234,6 +237,7 @@ fn wrap_processed_sphinx_packet( next_hop_address, delay, packet_type, + key_rotation, ), // right now there's no use for the surb_id included in the header - probably it should get removed from the // sphinx all together? @@ -246,6 +250,7 @@ fn wrap_processed_sphinx_packet( payload.recover_plaintext()?, packet_size, packet_type, + key_rotation, ), }?; @@ -259,6 +264,7 @@ fn wrap_processed_outfox_packet( packet: OutfoxProcessedPacket, packet_size: PacketSize, packet_type: PacketType, + key_rotation: SphinxKeyRotation, ) -> Result { let next_address = *packet.next_address(); let packet = packet.into_packet(); @@ -268,6 +274,7 @@ fn wrap_processed_outfox_packet( packet.recover_plaintext()?.to_vec(), packet_size, packet_type, + key_rotation, )?; Ok(MixProcessingResult { packet_version: MixPacketVersion::Outfox, @@ -278,6 +285,7 @@ fn wrap_processed_outfox_packet( NymNodeRoutingAddress::try_from_bytes(&next_address)?, NymPacket::Outfox(packet), PacketType::Outfox, + SphinxKeyRotation::Unknown, ); Ok(MixProcessingResult { packet_version: MixPacketVersion::Outfox, @@ -293,13 +301,14 @@ fn perform_final_processing( packet: NymProcessedPacket, packet_size: PacketSize, packet_type: PacketType, + key_rotation: SphinxKeyRotation, ) -> Result { match packet { NymProcessedPacket::Sphinx(packet) => { - wrap_processed_sphinx_packet(packet, packet_size, packet_type) + wrap_processed_sphinx_packet(packet, packet_size, packet_type, key_rotation) } NymProcessedPacket::Outfox(packet) => { - wrap_processed_outfox_packet(packet, packet_size, packet_type) + wrap_processed_outfox_packet(packet, packet_size, packet_type, key_rotation) } } } @@ -309,8 +318,10 @@ fn process_final_hop( payload: Vec, packet_size: PacketSize, packet_type: PacketType, + key_rotation: SphinxKeyRotation, ) -> Result { - let (forward_ack, message) = split_into_ack_and_message(payload, packet_size, packet_type)?; + let (forward_ack, message) = + split_into_ack_and_message(payload, packet_size, packet_type, key_rotation)?; Ok(MixProcessingResultData::FinalHop { final_hop_data: ProcessedFinalHop { @@ -325,6 +336,7 @@ fn split_into_ack_and_message( data: Vec, packet_size: PacketSize, packet_type: PacketType, + key_rotation: SphinxKeyRotation, ) -> Result<(Option, Vec), PacketProcessingError> { match packet_size { PacketSize::AckPacket | PacketSize::OutfoxAckPacket => { @@ -346,7 +358,7 @@ fn split_into_ack_and_message( return Err(err.into()); } }; - let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type); + let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type, key_rotation); Ok((Some(forward_ack), message)) } } @@ -374,10 +386,11 @@ fn process_forward_hop( forward_address: NodeAddressBytes, delay: SphinxDelay, packet_type: PacketType, + key_rotation: SphinxKeyRotation, ) -> Result { let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?; - let packet = MixPacket::new(next_hop_address, packet, packet_type); + let packet = MixPacket::new(next_hop_address, packet, packet_type, key_rotation); Ok(MixProcessingResultData::ForwardHop { packet, delay: Some(delay), diff --git a/common/nymsphinx/params/src/packet_types.rs b/common/nymsphinx/params/src/packet_types.rs index f3ad3d54b9..c597d9fda8 100644 --- a/common/nymsphinx/params/src/packet_types.rs +++ b/common/nymsphinx/params/src/packet_types.rs @@ -11,7 +11,7 @@ use std::fmt; use thiserror::Error; #[derive(Error, Debug)] -#[error("{received} is not a valid packet mode tag")] +#[error("{received} is not a valid packet type tag")] pub struct InvalidPacketType { received: u8, } diff --git a/common/nymsphinx/src/preparer/mod.rs b/common/nymsphinx/src/preparer/mod.rs index e0c35200a8..c4ecd15ebd 100644 --- a/common/nymsphinx/src/preparer/mod.rs +++ b/common/nymsphinx/src/preparer/mod.rs @@ -13,7 +13,7 @@ use nym_sphinx_anonymous_replies::reply_surb::ReplySurb; use nym_sphinx_chunking::fragment::{Fragment, FragmentIdentifier}; use nym_sphinx_forwarding::packet::MixPacket; use nym_sphinx_params::packet_sizes::PacketSize; -use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm}; +use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, SphinxKeyRotation}; use nym_sphinx_types::{Delay, NymPacket}; use nym_topology::{NymRouteProvider, NymTopologyError}; use rand::{CryptoRng, Rng, SeedableRng}; @@ -152,14 +152,16 @@ pub trait FragmentPreparer { .apply_surb(packet_payload, packet_size, packet_type) .unwrap(); - Ok(PreparedFragment { - // the round-trip delay is the sum of delays of all hops on the forward route as - // well as the total delay of the ack packet. - // we don't know the delays inside the reply surbs so we use best-effort estimation from our poisson distribution - total_delay: expected_forward_delay + ack_delay, - mix_packet: MixPacket::new(first_hop_address, sphinx_packet, packet_type), - fragment_identifier, - }) + todo!("somehow get key rotation information here") + + // Ok(PreparedFragment { + // // the round-trip delay is the sum of delays of all hops on the forward route as + // // well as the total delay of the ack packet. + // // we don't know the delays inside the reply surbs so we use best-effort estimation from our poisson distribution + // total_delay: expected_forward_delay + ack_delay, + // mix_packet: MixPacket::new(first_hop_address, sphinx_packet, packet_type, key_rotation), + // fragment_identifier, + // }) } /// Tries to convert this [`Fragment`] into a [`SphinxPacket`] that can be sent through the Nym mix-network, @@ -211,6 +213,9 @@ pub trait FragmentPreparer { let packet_size = PacketSize::get_type_from_plaintext(expected_plaintext, packet_type) .expect("the message has been incorrectly fragmented"); + let rotation_id = topology.current_key_rotation(); + let sphinx_key_rotation = SphinxKeyRotation::from(rotation_id); + let fragment_identifier = fragment.fragment_identifier(); // create an ack @@ -279,7 +284,7 @@ pub trait FragmentPreparer { // well as the total delay of the ack packet. // note that the last hop of the packet is a gateway that does not do any delays total_delay: delays.iter().take(delays.len() - 1).sum::() + ack_delay, - mix_packet: MixPacket::new(first_hop_address, packet, packet_type), + mix_packet: MixPacket::new(first_hop_address, packet, packet_type, sphinx_key_rotation), fragment_identifier, }) } diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index 438b3a521e..261d1de54d 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -129,6 +129,11 @@ impl NymRouteProvider { } } + pub fn current_key_rotation(&self) -> u32 { + todo!() + // self.topology.rewarded_set.epoch_id + } + pub fn new_empty(ignore_egress_epoch_roles: bool) -> NymRouteProvider { let this: Self = NymTopology::default().into(); this.with_ignore_egress_epoch_roles(ignore_egress_epoch_roles) diff --git a/nym-node/src/node/http/router/mod.rs b/nym-node/src/node/http/router/mod.rs index 12348d9c50..c296afcea9 100644 --- a/nym-node/src/node/http/router/mod.rs +++ b/nym-node/src/node/http/router/mod.rs @@ -16,7 +16,6 @@ use nym_node_requests::api::v1::mixnode::models::Mixnode; use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy; use nym_node_requests::api::v1::network_requester::models::NetworkRequester; use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, HostSystem, NodeDescription}; -use nym_node_requests::api::SignedHostInformation; use nym_node_requests::routes; use std::net::SocketAddr; use std::path::Path; diff --git a/nym-node/src/node/key_rotation/active_keys.rs b/nym-node/src/node/key_rotation/active_keys.rs index 322a8233f7..7c956d61a7 100644 --- a/nym-node/src/node/key_rotation/active_keys.rs +++ b/nym-node/src/node/key_rotation/active_keys.rs @@ -28,33 +28,33 @@ impl ActiveSphinxKeys { } } - pub(crate) fn even(&self) -> Option> { + pub(crate) fn even(&self) -> Option { let primary = self.inner.primary_key.load(); if primary.is_even_rotation() { - return Some(primary); + return Some(SphinxKeyGuard::Primary(primary)); } self.secondary() } - pub(crate) fn odd(&self) -> Option> { + pub(crate) fn odd(&self) -> Option { let primary = self.inner.primary_key.load(); if !primary.is_even_rotation() { - return Some(primary); + return Some(SphinxKeyGuard::Primary(primary)); } self.secondary() } - pub(crate) fn primary(&self) -> impl Deref { - self.inner.primary_key.map(|k: &SphinxPrivateKey| k).load() + pub(crate) fn primary(&self) -> SphinxKeyGuard { + SphinxKeyGuard::Primary(self.inner.primary_key.load()) } - pub(crate) fn secondary(&self) -> Option> { + pub(crate) fn secondary(&self) -> Option { let guard = self.inner.secondary_key.load(); if guard.is_none() { return None; } - Some(SecondaryKeyGuard { guard }) + Some(SphinxKeyGuard::Secondary(SecondaryKeyGuard { guard })) } // 1. generate new key @@ -87,6 +87,23 @@ impl ActiveSphinxKeys { } } +pub(crate) enum SphinxKeyGuard { + // Primary(Guard>), + Primary(Guard>), + Secondary(SecondaryKeyGuard), +} + +impl Deref for SphinxKeyGuard { + type Target = SphinxPrivateKey; + + fn deref(&self) -> &Self::Target { + match self { + SphinxKeyGuard::Primary(g) => g.deref(), + SphinxKeyGuard::Secondary(g) => g.deref(), + } + } +} + pub(crate) struct SecondaryKeyGuard { guard: Guard>>, } diff --git a/nym-node/src/node/key_rotation/key.rs b/nym-node/src/node/key_rotation/key.rs index 57d17718e2..ebe8389e90 100644 --- a/nym-node/src/node/key_rotation/key.rs +++ b/nym-node/src/node/key_rotation/key.rs @@ -32,6 +32,10 @@ impl SphinxPrivateKey { self.inner.public_key() } + pub(crate) fn inner(&self) -> &x25519::PrivateKey { + &self.inner + } + pub(crate) fn is_even_rotation(&self) -> bool { self.rotation_id & 1 == 0 } diff --git a/nym-node/src/node/mixnet/handler.rs b/nym-node/src/node/mixnet/handler.rs index 49315619c1..15a3f172c4 100644 --- a/nym-node/src/node/mixnet/handler.rs +++ b/nym-node/src/node/mixnet/handler.rs @@ -223,11 +223,15 @@ impl ConnectionHandler { SphinxKeyRotation::Unknown => { // we have to try both keys, start with the primary as it has higher likelihood of being correct // if let Ok(partially_unwrapped) = PartiallyUnwrappedPacket::new() - match PartiallyUnwrappedPacket::new(packet, self.shared.sphinx_keys.primary()) { + match PartiallyUnwrappedPacket::new( + packet, + self.shared.sphinx_keys.primary().inner().as_ref(), + ) { Ok(unwrapped_packet) => Ok(unwrapped_packet), Err((packet, err)) => { if let Some(secondary) = self.shared.sphinx_keys.secondary() { - PartiallyUnwrappedPacket::new(packet, secondary).map_err(|(_, err)| err) + PartiallyUnwrappedPacket::new(packet, secondary.inner().as_ref()) + .map_err(|(_, err)| err) } else { Err(err) } @@ -238,13 +242,15 @@ impl ConnectionHandler { let Some(odd_key) = self.shared.sphinx_keys.odd() else { return Err(PacketProcessingError::ExpiredKey); }; - PartiallyUnwrappedPacket::new(packet, odd_key).map_err(|(_, err)| err) + PartiallyUnwrappedPacket::new(packet, odd_key.inner().as_ref()) + .map_err(|(_, err)| err) } SphinxKeyRotation::EvenRotation => { let Some(even_key) = self.shared.sphinx_keys.even() else { return Err(PacketProcessingError::ExpiredKey); }; - PartiallyUnwrappedPacket::new(packet, even_key).map_err(|(_, err)| err) + PartiallyUnwrappedPacket::new(packet, even_key.inner().as_ref()) + .map_err(|(_, err)| err) } } } @@ -385,19 +391,19 @@ impl ConnectionHandler { // and by the time we need it, the rotation info should be present) match packet.header().key_rotation { SphinxKeyRotation::Unknown => { - process_framed_packet(packet, self.shared.sphinx_keys.primary()) + process_framed_packet(packet, self.shared.sphinx_keys.primary().inner().as_ref()) } SphinxKeyRotation::OddRotation => { let Some(odd_key) = self.shared.sphinx_keys.odd() else { return Err(PacketProcessingError::ExpiredKey); }; - process_framed_packet(packet, odd_key) + process_framed_packet(packet, odd_key.inner().as_ref()) } SphinxKeyRotation::EvenRotation => { let Some(even_key) = self.shared.sphinx_keys.even() else { return Err(PacketProcessingError::ExpiredKey); }; - process_framed_packet(packet, even_key) + process_framed_packet(packet, even_key.inner().as_ref()) } } } diff --git a/nym-node/src/node/mixnet/packet_forwarding/mod.rs b/nym-node/src/node/mixnet/packet_forwarding/mod.rs index 109398f90c..e397124ee0 100644 --- a/nym-node/src/node/mixnet/packet_forwarding/mod.rs +++ b/nym-node/src/node/mixnet/packet_forwarding/mod.rs @@ -58,32 +58,20 @@ impl PacketForwarder { C: SendWithoutResponse, F: RoutingFilter, { - let next_hop = packet.next_hop(); + let next_hop = packet.next_hop_address(); - let packet_type = packet.packet_type(); - let packet = packet.into_packet(); - - if let Err(err) = self - .mixnet_client - .send_without_response(next_hop, packet, packet_type) - { + if let Err(err) = self.mixnet_client.send_without_response(packet) { if err.kind() == io::ErrorKind::WouldBlock { // we only know for sure if we dropped a packet if our sending queue was full // in any other case the connection might still be re-established (or created for the first time) // and the packet might get sent, but we won't know about it - self.metrics - .mixnet - .egress_dropped_forward_packet(next_hop.into()) + self.metrics.mixnet.egress_dropped_forward_packet(next_hop) } else if err.kind() == io::ErrorKind::NotConnected { // let's give the benefit of the doubt and assume we manage to establish connection - self.metrics - .mixnet - .egress_sent_forward_packet(next_hop.into()) + self.metrics.mixnet.egress_sent_forward_packet(next_hop) } } else { - self.metrics - .mixnet - .egress_sent_forward_packet(next_hop.into()) + self.metrics.mixnet.egress_sent_forward_packet(next_hop) } } diff --git a/nym-node/src/throughput_tester/client.rs b/nym-node/src/throughput_tester/client.rs index f95d275183..02a82b4942 100644 --- a/nym-node/src/throughput_tester/client.rs +++ b/nym-node/src/throughput_tester/client.rs @@ -256,7 +256,8 @@ impl ThroughputTestingClient { packet_bytes.append(&mut payload_bytes); let forward_packet = NymPacket::sphinx_from_bytes(&packet_bytes)?; - Ok(FramedNymPacket::new(forward_packet, Default::default())) + todo!() + // Ok(FramedNymPacket::new(forward_packet, Default::default())) } async fn send_packets(&mut self) -> anyhow::Result<()> {