1use crate::packet::FramedNymPacket;
5use nym_sphinx_acknowledgements::surb_ack::{SurbAck, SurbAckRecoveryError};
6use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
7use nym_sphinx_forwarding::packet::MixPacket;
8use nym_sphinx_params::{PacketSize, PacketType, SphinxKeyRotation};
9use nym_sphinx_types::header::shared_secret::ExpandedSharedSecret;
10use nym_sphinx_types::{
11 Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, NymPacket, NymPacketError,
12 NymProcessedPacket, OutfoxError, OutfoxProcessedPacket, PrivateKey, ProcessedPacketData,
13 REPLAY_TAG_SIZE, SphinxError, Version as SphinxPacketVersion,
14};
15use std::fmt::Display;
16use thiserror::Error;
17use tracing::{debug, trace};
18
19#[derive(Debug)]
20pub enum MixProcessingResultData {
21 ForwardHop {
23 packet: MixPacket,
24 delay: Option<SphinxDelay>,
25 },
26
27 FinalHop { final_hop_data: ProcessedFinalHop },
29}
30
31#[derive(Debug, Copy, Clone)]
32pub enum MixPacketVersion {
33 Outfox,
34 Sphinx(SphinxPacketVersion),
35}
36
37impl Display for MixPacketVersion {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
39 match self {
40 MixPacketVersion::Outfox => "outfox".fmt(f),
41 MixPacketVersion::Sphinx(sphinx_version) => {
42 write!(f, "sphinx-{}", sphinx_version.value())
43 }
44 }
45 }
46}
47
48#[derive(Debug)]
49pub struct MixProcessingResult {
50 pub packet_version: MixPacketVersion,
51 pub processing_data: MixProcessingResultData,
52}
53
54#[allow(clippy::large_enum_variant)]
55#[derive(Debug)]
56pub enum PartialMixProcessingResult {
57 Sphinx {
58 expanded_shared_secret: ExpandedSharedSecret,
59 },
60 Outfox,
61}
62
63impl PartialMixProcessingResult {
64 pub fn replay_tag(&self) -> Option<&[u8; REPLAY_TAG_SIZE]> {
65 match self {
66 PartialMixProcessingResult::Sphinx {
67 expanded_shared_secret,
68 } => Some(expanded_shared_secret.replay_tag()),
69 PartialMixProcessingResult::Outfox => None,
70 }
71 }
72}
73
74type ForwardAck = MixPacket;
75
76#[derive(Debug)]
77pub struct ProcessedFinalHop {
78 pub destination: DestinationAddressBytes,
79 pub forward_ack: Option<ForwardAck>,
80 pub message: Vec<u8>,
81}
82
83#[derive(Debug, Error)]
84pub enum PacketProcessingError {
85 #[error("failed to process received packet: {0}")]
86 NymPacketProcessingError(#[from] NymPacketError),
87
88 #[error("failed to process received sphinx packet: {0}")]
89 SphinxProcessingError(#[from] SphinxError),
90
91 #[error("the forward hop address was malformed: {0}")]
92 InvalidForwardHopAddress(#[from] NymNodeRoutingAddressError),
93
94 #[error("the final hop did not contain a SURB-Ack")]
95 NoSurbAckInFinalHop,
96
97 #[error("failed to recover the expected SURB-Ack packet: {0}")]
98 MalformedSurbAck(#[from] SurbAckRecoveryError),
99
100 #[error("failed to process received outfox packet: {0}")]
101 OutfoxProcessingError(#[from] OutfoxError),
102
103 #[error("attempted to partially process an outfox packet")]
104 PartialOutfoxProcessing,
105
106 #[error("the key needed for unwrapping this packet has already expired")]
107 ExpiredKey,
108
109 #[error("this packet has already been processed before")]
110 PacketReplay,
111}
112
113pub struct PartialyUnwrappedPacketWithKeyRotation {
114 pub packet: PartiallyUnwrappedPacket,
115 pub used_key_rotation: u32,
116}
117
118pub struct PartiallyUnwrappedPacket {
119 received_data: FramedNymPacket,
120 partial_result: PartialMixProcessingResult,
121}
122
123impl PartiallyUnwrappedPacket {
124 pub fn new(
128 received_data: FramedNymPacket,
129 sphinx_key: &PrivateKey,
130 ) -> Result<Self, (FramedNymPacket, PacketProcessingError)> {
131 let partial_result = match received_data.packet() {
132 NymPacket::Sphinx(packet) => {
133 let expanded_shared_secret =
134 packet.header.compute_expanded_shared_secret(sphinx_key);
135
136 if let Err(err) = packet
138 .header
139 .ensure_header_integrity(&expanded_shared_secret)
140 {
141 return Err((received_data, err.into()));
142 }
143
144 PartialMixProcessingResult::Sphinx {
145 expanded_shared_secret,
146 }
147 }
148
149 NymPacket::Outfox(_) => PartialMixProcessingResult::Outfox,
150 };
151 Ok(PartiallyUnwrappedPacket {
152 received_data,
153 partial_result,
154 })
155 }
156
157 pub fn finalise_unwrapping(self) -> Result<MixProcessingResult, PacketProcessingError> {
158 let packet_size = self.received_data.packet_size();
159 let packet_type = self.received_data.packet_type();
160
161 let key_rotation = self.received_data.header.key_rotation;
162 let packet = self.received_data.into_inner();
163
164 let (
167 NymPacket::Sphinx(packet),
168 PartialMixProcessingResult::Sphinx {
169 expanded_shared_secret,
170 },
171 ) = (packet, self.partial_result)
172 else {
173 return Err(PacketProcessingError::PartialOutfoxProcessing);
174 };
175 let processed_packet = packet.process_with_expanded_secret(&expanded_shared_secret)?;
176 wrap_processed_sphinx_packet(processed_packet, packet_size, packet_type, key_rotation)
177 }
178
179 pub fn replay_tag(&self) -> Option<&[u8; REPLAY_TAG_SIZE]> {
180 self.partial_result.replay_tag()
181 }
182
183 pub fn with_key_rotation(
184 self,
185 used_key_rotation: u32,
186 ) -> PartialyUnwrappedPacketWithKeyRotation {
187 PartialyUnwrappedPacketWithKeyRotation {
188 packet: self,
189 used_key_rotation,
190 }
191 }
192}
193
194impl From<(FramedNymPacket, PartialMixProcessingResult)> for PartiallyUnwrappedPacket {
195 fn from(
196 (received_data, partial_result): (FramedNymPacket, PartialMixProcessingResult),
197 ) -> Self {
198 PartiallyUnwrappedPacket {
199 received_data,
200 partial_result,
201 }
202 }
203}
204
205pub fn process_framed_packet(
206 received: FramedNymPacket,
207 sphinx_key: &PrivateKey,
208) -> Result<MixProcessingResult, PacketProcessingError> {
209 let packet_size = received.packet_size();
210 let packet_type = received.packet_type();
211 let key_rotation = received.key_rotation();
212
213 let processed_packet = perform_framed_unwrapping(received, sphinx_key)?;
215
216 perform_final_processing(processed_packet, packet_size, packet_type, key_rotation)
219}
220
221fn perform_framed_unwrapping(
222 received: FramedNymPacket,
223 sphinx_key: &PrivateKey,
224) -> Result<NymProcessedPacket, PacketProcessingError> {
225 let packet = received.into_inner();
226 perform_framed_packet_processing(packet, sphinx_key)
227}
228
229fn perform_framed_packet_processing(
230 packet: NymPacket,
231 sphinx_key: &PrivateKey,
232) -> Result<NymProcessedPacket, PacketProcessingError> {
233 packet.process(sphinx_key).map_err(|err| {
234 debug!("Failed to unwrap NymPacket packet: {err}");
235 PacketProcessingError::NymPacketProcessingError(err)
236 })
237}
238
239fn wrap_processed_sphinx_packet(
240 packet: nym_sphinx_types::ProcessedPacket,
241 packet_size: PacketSize,
242 packet_type: PacketType,
243 key_rotation: SphinxKeyRotation,
244) -> Result<MixProcessingResult, PacketProcessingError> {
245 let processing_data = match packet.data {
246 ProcessedPacketData::ForwardHop {
247 next_hop_packet,
248 next_hop_address,
249 delay,
250 } => process_forward_hop(
251 NymPacket::Sphinx(next_hop_packet),
252 next_hop_address,
253 delay,
254 packet_type,
255 key_rotation,
256 ),
257 ProcessedPacketData::FinalHop {
260 destination,
261 identifier: _,
262 payload,
263 } => process_final_hop(
264 destination,
265 payload.recover_plaintext()?,
266 packet_size,
267 packet_type,
268 key_rotation,
269 ),
270 }?;
271
272 Ok(MixProcessingResult {
273 packet_version: MixPacketVersion::Sphinx(packet.version),
274 processing_data,
275 })
276}
277
278fn wrap_processed_outfox_packet(
279 packet: OutfoxProcessedPacket,
280 packet_size: PacketSize,
281 packet_type: PacketType,
282 key_rotation: SphinxKeyRotation,
283) -> Result<MixProcessingResult, PacketProcessingError> {
284 let next_address = *packet.next_address();
285 let packet = packet.into_packet();
286 if packet.is_final_hop() {
287 let processing_data = process_final_hop(
288 DestinationAddressBytes::from_bytes(next_address),
289 packet.recover_plaintext()?.to_vec(),
290 packet_size,
291 packet_type,
292 key_rotation,
293 )?;
294 Ok(MixProcessingResult {
295 packet_version: MixPacketVersion::Outfox,
296 processing_data,
297 })
298 } else {
299 let packet = MixPacket::new(
300 NymNodeRoutingAddress::try_from_bytes(&next_address)?,
301 NymPacket::Outfox(packet),
302 PacketType::Outfox,
303 SphinxKeyRotation::Unknown,
304 );
305 Ok(MixProcessingResult {
306 packet_version: MixPacketVersion::Outfox,
307 processing_data: MixProcessingResultData::ForwardHop {
308 packet,
309 delay: None,
310 },
311 })
312 }
313}
314
315fn perform_final_processing(
316 packet: NymProcessedPacket,
317 packet_size: PacketSize,
318 packet_type: PacketType,
319 key_rotation: SphinxKeyRotation,
320) -> Result<MixProcessingResult, PacketProcessingError> {
321 match packet {
322 NymProcessedPacket::Sphinx(packet) => {
323 wrap_processed_sphinx_packet(packet, packet_size, packet_type, key_rotation)
324 }
325 NymProcessedPacket::Outfox(packet) => {
326 wrap_processed_outfox_packet(packet, packet_size, packet_type, key_rotation)
327 }
328 }
329}
330
331fn process_final_hop(
332 destination: DestinationAddressBytes,
333 payload: Vec<u8>,
334 packet_size: PacketSize,
335 packet_type: PacketType,
336 key_rotation: SphinxKeyRotation,
337) -> Result<MixProcessingResultData, PacketProcessingError> {
338 let (forward_ack, message) =
339 split_into_ack_and_message(payload, packet_size, packet_type, key_rotation)?;
340
341 Ok(MixProcessingResultData::FinalHop {
342 final_hop_data: ProcessedFinalHop {
343 destination,
344 forward_ack,
345 message,
346 },
347 })
348}
349
350fn split_into_ack_and_message(
351 data: Vec<u8>,
352 packet_size: PacketSize,
353 packet_type: PacketType,
354 key_rotation: SphinxKeyRotation,
355) -> Result<(Option<MixPacket>, Vec<u8>), PacketProcessingError> {
356 match packet_size {
357 PacketSize::AckPacket | PacketSize::OutfoxAckPacket => {
358 trace!("received an ack packet!");
359 Ok((None, data))
360 }
361 PacketSize::RegularPacket
362 | PacketSize::ExtendedPacket8
363 | PacketSize::ExtendedPacket16
364 | PacketSize::ExtendedPacket32
365 | PacketSize::OutfoxRegularPacket => {
366 trace!("received a normal packet!");
367 cfg_if::cfg_if! {
368 if #[cfg(feature = "no-mix-acks")] {
369 let _ = packet_type;
370 let _ = key_rotation;
371
372 Ok((None, data))
375 } else {
376 let (ack_data, message) = split_hop_data_into_ack_and_message(data, packet_type)?;
377 let (ack_first_hop, ack_packet) =
378 match SurbAck::try_recover_first_hop_packet(&ack_data, packet_type) {
379 Ok((first_hop, packet)) => (first_hop, packet),
380 Err(err) => {
381 tracing::info!("Failed to recover first hop from ack data: {err}");
382 return Err(err.into());
383 }
384 };
385 let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type, key_rotation);
386 Ok((Some(forward_ack), message))
387 }
388 }
389 }
390 }
391}
392
393#[allow(dead_code)]
394fn split_hop_data_into_ack_and_message(
395 mut extracted_data: Vec<u8>,
396 packet_type: PacketType,
397) -> Result<(Vec<u8>, Vec<u8>), PacketProcessingError> {
398 let ack_len = SurbAck::len(Some(packet_type));
399
400 if extracted_data.len() < ack_len {
403 return Err(PacketProcessingError::NoSurbAckInFinalHop);
404 }
405
406 let message = extracted_data.split_off(ack_len);
407 let ack_data = extracted_data;
408 Ok((ack_data, message))
409}
410
411fn process_forward_hop(
412 packet: NymPacket,
413 forward_address: NodeAddressBytes,
414 delay: SphinxDelay,
415 packet_type: PacketType,
416 key_rotation: SphinxKeyRotation,
417) -> Result<MixProcessingResultData, PacketProcessingError> {
418 let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?;
419
420 let packet = MixPacket::new(next_hop_address, packet, packet_type, key_rotation);
421 Ok(MixProcessingResultData::ForwardHop {
422 packet,
423 delay: Some(delay),
424 })
425}
426
427#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[tokio::test]
433 async fn splitting_hop_data_works_for_sufficiently_long_payload() {
434 let short_data = vec![42u8];
435 assert!(split_hop_data_into_ack_and_message(short_data, PacketType::Mix).is_err());
436
437 let sufficient_data = vec![42u8; SurbAck::len(Some(PacketType::Mix))];
438 let (ack, data) =
439 split_hop_data_into_ack_and_message(sufficient_data.clone(), PacketType::Mix).unwrap();
440 assert_eq!(sufficient_data, ack);
441 assert!(data.is_empty());
442
443 let long_data: Vec<u8> = vec![42u8; SurbAck::len(Some(PacketType::Mix)) * 5];
444 let (ack, data) = split_hop_data_into_ack_and_message(long_data, PacketType::Mix).unwrap();
445 assert_eq!(ack.len(), SurbAck::len(Some(PacketType::Mix)));
446 assert_eq!(data.len(), SurbAck::len(Some(PacketType::Mix)) * 4)
447 }
448
449 #[tokio::test]
450 async fn splitting_hop_data_works_for_sufficiently_long_payload_outfox() {
451 let short_data = vec![42u8];
452 assert!(split_hop_data_into_ack_and_message(short_data, PacketType::Outfox).is_err());
453
454 let sufficient_data = vec![42u8; SurbAck::len(Some(PacketType::Outfox))];
455 let (ack, data) =
456 split_hop_data_into_ack_and_message(sufficient_data.clone(), PacketType::Outfox)
457 .unwrap();
458 assert_eq!(sufficient_data, ack);
459 assert!(data.is_empty());
460
461 let long_data = vec![42u8; SurbAck::len(Some(PacketType::Outfox)) * 5];
462 let (ack, data) =
463 split_hop_data_into_ack_and_message(long_data, PacketType::Outfox).unwrap();
464 assert_eq!(ack.len(), SurbAck::len(Some(PacketType::Outfox)));
465 assert_eq!(data.len(), SurbAck::len(Some(PacketType::Outfox)) * 4)
466 }
467
468 #[tokio::test]
469 async fn splitting_into_ack_and_message_returns_whole_data_for_ack() {
470 let data = vec![42u8; SurbAck::len(Some(PacketType::Mix)) + 10];
471 let (ack, message) = split_into_ack_and_message(
472 data.clone(),
473 PacketSize::AckPacket,
474 PacketType::Mix,
475 SphinxKeyRotation::EvenRotation,
476 )
477 .unwrap();
478 assert!(ack.is_none());
479 assert_eq!(data, message)
480 }
481
482 #[tokio::test]
483 async fn splitting_into_ack_and_message_returns_whole_data_for_ack_outfox() {
484 let data = vec![42u8; SurbAck::len(Some(PacketType::Outfox)) + 10];
485 let (ack, message) = split_into_ack_and_message(
486 data.clone(),
487 PacketSize::OutfoxAckPacket,
488 PacketType::Outfox,
489 SphinxKeyRotation::EvenRotation,
490 )
491 .unwrap();
492 assert!(ack.is_none());
493 assert_eq!(data, message)
494 }
495}