sphinx_packet/header/
mod.rs

1// Copyright 2020-2025 Nym Technologies SA
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::constants::HEADER_INTEGRITY_MAC_SIZE;
16use crate::header::delays::Delay;
17use crate::header::filler::Filler;
18use crate::header::keys::KeyMaterial;
19use crate::header::routing::{EncapsulatedRoutingInformation, ENCRYPTED_ROUTING_INFO_SIZE};
20use crate::header::shared_secret::{ExpandSecret, ExpandedSharedSecret};
21use crate::packet::ProcessedPacketData;
22use crate::payload::key::{derive_payload_key, PayloadKey, PayloadKeySeed};
23use crate::payload::Payload;
24use crate::route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier};
25use crate::version::Version;
26use crate::{Error, ErrorKind, ProcessedPacket, Result, SphinxPacket};
27use x25519_dalek::{PublicKey, StaticSecret};
28
29pub mod delays;
30pub mod filler;
31pub mod keys;
32pub mod mac;
33pub mod routing;
34pub mod shared_secret;
35
36// 32 represents size of a MontgomeryPoint on Curve25519
37pub const HEADER_SIZE: usize = 32 + HEADER_INTEGRITY_MAC_SIZE + ENCRYPTED_ROUTING_INFO_SIZE;
38
39#[derive(Debug)]
40#[cfg_attr(test, derive(Clone))]
41pub struct SphinxHeader {
42    /// Alpha element
43    pub shared_secret: PublicKey,
44    pub routing_info: Box<EncapsulatedRoutingInformation>,
45}
46
47pub struct ProcessedHeader {
48    payload_key: PayloadKey,
49    version: Version,
50    data: ProcessedHeaderData,
51}
52
53pub enum ProcessedHeaderData {
54    FinalHop {
55        destination: DestinationAddressBytes,
56        identifier: SURBIdentifier,
57    },
58    ForwardHop {
59        updated_header: SphinxHeader,
60        next_hop_address: NodeAddressBytes,
61        delay: Delay,
62    },
63}
64
65impl ProcessedHeader {
66    pub(crate) fn payload_key(&self) -> &PayloadKey {
67        &self.payload_key
68    }
69
70    pub(crate) fn attach_payload(self, payload: Payload) -> ProcessedPacket {
71        match self.data {
72            ProcessedHeaderData::ForwardHop {
73                updated_header,
74                next_hop_address,
75                delay,
76            } => ProcessedPacket {
77                version: self.version,
78                data: ProcessedPacketData::ForwardHop {
79                    next_hop_packet: SphinxPacket {
80                        header: updated_header,
81                        payload,
82                    },
83                    next_hop_address,
84                    delay,
85                },
86            },
87            ProcessedHeaderData::FinalHop {
88                destination,
89                identifier,
90            } => ProcessedPacket {
91                version: self.version,
92                data: ProcessedPacketData::FinalHop {
93                    destination,
94                    identifier,
95                    payload,
96                },
97            },
98        }
99    }
100}
101
102impl SphinxHeader {
103    #[cfg(test)]
104    pub(crate) fn new_current(
105        initial_secret: &StaticSecret,
106        route: &[Node],
107        delays: &[Delay],
108        destination: &Destination,
109    ) -> BuiltHeader {
110        let key_material = keys::KeyMaterial::derive(route, initial_secret);
111        Self::build_header(
112            key_material,
113            route,
114            delays,
115            destination,
116            crate::version::CURRENT_VERSION,
117        )
118    }
119
120    #[cfg(test)]
121    #[deprecated]
122    #[allow(deprecated)]
123    pub(crate) fn new_legacy(
124        initial_secret: &StaticSecret,
125        route: &[Node],
126        delays: &[Delay],
127        destination: &Destination,
128    ) -> BuiltHeader {
129        let key_material = keys::KeyMaterial::derive_legacy(route, initial_secret);
130        Self::build_header(
131            key_material,
132            route,
133            delays,
134            destination,
135            crate::version::UPDATED_LEGACY_VERSION,
136        )
137    }
138
139    #[allow(deprecated)]
140    pub(crate) fn new_versioned(
141        initial_secret: &StaticSecret,
142        route: &[Node],
143        delays: &[Delay],
144        destination: &Destination,
145        version: Version,
146    ) -> BuiltHeader {
147        let key_material = if version.is_legacy() {
148            keys::KeyMaterial::derive_legacy(route, initial_secret)
149        } else {
150            keys::KeyMaterial::derive(route, initial_secret)
151        };
152        Self::build_header(key_material, route, delays, destination, version)
153    }
154
155    fn build_header(
156        key_material: KeyMaterial,
157        route: &[Node],
158        delays: &[Delay],
159        destination: &Destination,
160        version: Version,
161    ) -> BuiltHeader {
162        let filler_string = Filler::new(&key_material.expanded_shared_secrets[..route.len() - 1]);
163        let routing_info = EncapsulatedRoutingInformation::new(
164            route,
165            destination,
166            delays,
167            &key_material.expanded_shared_secrets,
168            filler_string,
169            version,
170        );
171
172        // encapsulate header.routing information, compute MACs
173        BuiltHeader::new(version, key_material, routing_info)
174    }
175
176    // note: this method is currently removed because there's too many branches to support
177    // with the legacy compatibility requirements.
178    // this will be revisited in the future
179    // /// Processes the header with the provided shared secret
180    // /// It could be useful in the situation where sender is re-using initial secret
181    // /// and we could cache processing results.
182    // ///
183    // /// However, unless you know exactly what you are doing, you should NEVER use this method!
184    // /// Prefer normal [process] instead.
185    // pub fn process_with_cached_secret(
186    //     &self,
187    //     expanded_secret: ExpandedSharedSecret,
188    //     cached_new_shared_secret: PublicKey,
189    // ) -> Result<ProcessedHeader> {
190    //     self.ensure_valid_mac(expanded_secret.header_integrity_hmac_key())?;
191    //
192    //     let unwrapped_routing_information = self
193    //         .routing_info
194    //         .enc_routing_information
195    //         .unwrap(expanded_secret.stream_cipher_key())?;
196    //
197    //     match unwrapped_routing_information.data {
198    //         ParsedRawRoutingInformationData::ForwardHop {
199    //             next_hop_address,
200    //             delay,
201    //             new_routing_information,
202    //         } => {
203    //             if let Some(new_blinded_secret) = cached_new_derived_secret {
204    //                 Ok(ProcessedHeader {
205    //                     payload_key: *expanded_secret.payload_key(),
206    //                     version: unwrapped_routing_information.version,
207    //                     data: ProcessedHeaderData::ForwardHop {
208    //                         updated_header: SphinxHeader {
209    //                             shared_secret: new_blinded_secret,
210    //                             routing_info: new_routing_information,
211    //                         },
212    //                         next_hop_address,
213    //                         delay,
214    //                     },
215    //                 })
216    //             } else {
217    //                 Err(Error::new(
218    //                     ErrorKind::InvalidHeader,
219    //                     "tried to process forward hop without blinded secret",
220    //                 ))
221    //             }
222    //         }
223    //         ParsedRawRoutingInformationData::FinalHop {
224    //             destination,
225    //             identifier,
226    //         } => Ok(ProcessedHeader {
227    //             payload_key: *expanded_secret.payload_key(),
228    //             version: unwrapped_routing_information.version,
229    //             data: ProcessedHeaderData::FinalHop {
230    //                 destination,
231    //                 identifier,
232    //             },
233    //         }),
234    //     }
235    // }
236
237    /// Processes the header with the provided expanded shared secret
238    /// It could be useful in the situation where caller has already derived the value,
239    /// because, for example, he had to obtain the reply tag.
240    #[allow(deprecated)]
241    pub fn process_with_expanded_secret(
242        self,
243        expanded_secret: &ExpandedSharedSecret,
244    ) -> Result<ProcessedHeader> {
245        self.ensure_header_integrity(expanded_secret)?;
246
247        let unwrapped_routing_information = self
248            .routing_info
249            .enc_routing_information
250            .unwrap(expanded_secret.stream_cipher_key())?;
251
252        if unwrapped_routing_information.version.is_legacy() {
253            Ok(unwrapped_routing_information
254                .legacy_into_processed_header(self.shared_secret, expanded_secret))
255        } else {
256            Ok(unwrapped_routing_information
257                .into_processed_header(self.shared_secret, expanded_secret))
258        }
259    }
260
261    #[allow(deprecated)]
262    pub fn process(self, node_secret_key: &StaticSecret) -> Result<ProcessedHeader> {
263        let expanded_secret = self.compute_expanded_shared_secret(node_secret_key);
264        self.process_with_expanded_secret(&expanded_secret)
265    }
266
267    /// Using the provided packet's alpha and node's secret key, expand it into the output of all required random oracles
268    pub fn compute_expanded_shared_secret(
269        &self,
270        node_secret_key: &StaticSecret,
271    ) -> ExpandedSharedSecret {
272        node_secret_key
273            .diffie_hellman(&self.shared_secret)
274            .expand_shared_secret()
275    }
276
277    pub fn ensure_header_integrity(
278        &self,
279        expanded_shared_secret: &ExpandedSharedSecret,
280    ) -> Result<()> {
281        if !self.routing_info.integrity_mac.verify(
282            expanded_shared_secret.header_integrity_hmac_key(),
283            self.routing_info.enc_routing_information.as_ref(),
284        ) {
285            return Err(Error::new(
286                ErrorKind::InvalidHeader,
287                "failed to verify integrity MAC",
288            ));
289        }
290        Ok(())
291    }
292
293    #[deprecated]
294    pub fn unchecked_process_as_current(
295        self,
296        node_secret_key: &StaticSecret,
297    ) -> Result<ProcessedHeader> {
298        let expanded_secret = self.compute_expanded_shared_secret(node_secret_key);
299        self.ensure_header_integrity(&expanded_secret)?;
300
301        let unwrapped_routing_information = self
302            .routing_info
303            .enc_routing_information
304            .unwrap(expanded_secret.stream_cipher_key())?;
305
306        Ok(unwrapped_routing_information
307            .into_processed_header(self.shared_secret, &expanded_secret))
308    }
309
310    #[deprecated]
311    #[allow(deprecated)]
312    pub fn unchecked_process_as_legacy(
313        self,
314        node_secret_key: &StaticSecret,
315    ) -> Result<ProcessedHeader> {
316        let expanded_secret = self.compute_expanded_shared_secret(node_secret_key);
317        self.ensure_header_integrity(&expanded_secret)?;
318
319        let unwrapped_routing_information = self
320            .routing_info
321            .enc_routing_information
322            .unwrap(expanded_secret.stream_cipher_key())?;
323
324        Ok(unwrapped_routing_information
325            .legacy_into_processed_header(self.shared_secret, &expanded_secret))
326    }
327
328    pub fn to_bytes(&self) -> Vec<u8> {
329        self.shared_secret
330            .as_bytes()
331            .iter()
332            .cloned()
333            .chain(self.routing_info.to_bytes())
334            .collect()
335    }
336
337    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
338        if bytes.len() != HEADER_SIZE {
339            return Err(Error::new(
340                ErrorKind::InvalidHeader,
341                format!(
342                    "tried to recover using {} bytes, expected {}",
343                    bytes.len(),
344                    HEADER_SIZE
345                ),
346            ));
347        }
348
349        let mut shared_secret_bytes = [0u8; 32];
350        // first 32 bytes represent the shared secret
351        shared_secret_bytes.copy_from_slice(&bytes[..32]);
352        let shared_secret = PublicKey::from(shared_secret_bytes);
353
354        // the rest are for the encapsulated routing info
355        let routing_info = Box::new(EncapsulatedRoutingInformation::from_bytes(&bytes[32..])?);
356
357        Ok(SphinxHeader {
358            shared_secret,
359            routing_info,
360        })
361    }
362
363    fn blind_the_shared_secret(
364        shared_secret: PublicKey,
365        blinding_factor: StaticSecret,
366    ) -> PublicKey {
367        // shared_secret * blinding_factor
368        let new_shared_secret = blinding_factor.diffie_hellman(&shared_secret);
369        PublicKey::from(new_shared_secret.to_bytes())
370    }
371
372    /// use unreduced multiplication for legacy backwards compatibility
373    #[deprecated]
374    fn legacy_blind_shared_secret(
375        shared_secret: PublicKey,
376        blinding_factor: StaticSecret,
377    ) -> PublicKey {
378        let blinding_factor =
379            curve25519_dalek::scalar::Scalar::from_bytes_mod_order(blinding_factor.to_bytes());
380        let mp = curve25519_dalek::montgomery::MontgomeryPoint(shared_secret.to_bytes());
381        PublicKey::from((blinding_factor * mp).to_bytes())
382    }
383}
384
385pub(crate) struct BuiltHeader {
386    header: SphinxHeader,
387    version: Version,
388    expanded_secrets: Vec<ExpandedSharedSecret>,
389}
390
391impl BuiltHeader {
392    fn new(
393        version: Version,
394        key_material: KeyMaterial,
395        routing_information: EncapsulatedRoutingInformation,
396    ) -> Self {
397        BuiltHeader {
398            header: SphinxHeader {
399                shared_secret: key_material.initial_shared_secret,
400                routing_info: Box::new(routing_information),
401            },
402            version,
403            expanded_secrets: key_material.expanded_shared_secrets,
404        }
405    }
406
407    // depending on the version either use the initial hkdf output as payload keys
408    // or extract the seed and run it through another hkdf
409    pub(crate) fn derive_payload_keys(&self) -> Vec<PayloadKey> {
410        if self.version.expects_legacy_full_payload_keys() {
411            self.legacy_full_payload_keys()
412        } else {
413            self.expanded_secrets
414                .iter()
415                .map(|s| derive_payload_key(s.payload_key_seed()))
416                .collect()
417        }
418    }
419
420    pub(crate) fn legacy_full_payload_keys(&self) -> Vec<PayloadKey> {
421        self.expanded_secrets
422            .iter()
423            .map(|s| *s.legacy_payload_key())
424            .collect()
425    }
426
427    pub(crate) fn payload_key_seeds(&self) -> Vec<PayloadKeySeed> {
428        self.expanded_secrets
429            .iter()
430            .map(|s| *s.payload_key_seed())
431            .collect()
432    }
433
434    pub(crate) fn into_header(self) -> SphinxHeader {
435        self.header
436    }
437}
438
439#[cfg(test)]
440mod create_and_process_sphinx_packet_header {
441    use super::*;
442    use crate::crypto::PrivateKey;
443    use crate::{
444        constants::NODE_ADDRESS_LENGTH,
445        test_utils::fixtures::{destination_fixture, keygen},
446    };
447    use std::time::Duration;
448
449    #[test]
450    fn it_returns_correct_routing_information_at_each_hop_for_route_of_3_mixnodes() {
451        let (node1_sk, node1_pk) = keygen();
452        let node1 = Node {
453            address: NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
454            pub_key: node1_pk,
455        };
456        let (node2_sk, node2_pk) = keygen();
457        let node2 = Node {
458            address: NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]),
459            pub_key: node2_pk,
460        };
461        let (node3_sk, node3_pk) = keygen();
462        let node3 = Node {
463            address: NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
464            pub_key: node3_pk,
465        };
466        let route = [node1, node2, node3];
467        let route_destination = destination_fixture();
468        let initial_secret = StaticSecret::random();
469        let average_delay = 1;
470        let delays =
471            delays::generate_from_average_duration(route.len(), Duration::from_secs(average_delay));
472        let sphinx_header =
473            SphinxHeader::new_current(&initial_secret, &route, &delays, &route_destination)
474                .into_header();
475
476        //let (new_header, next_hop_address, _) = sphinx_header.process(node1_sk).unwrap();
477        let new_header = match sphinx_header.process(&node1_sk).unwrap().data {
478            ProcessedHeaderData::ForwardHop {
479                updated_header,
480                next_hop_address,
481                delay,
482            } => {
483                assert_eq!(
484                    NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]),
485                    next_hop_address
486                );
487                assert_eq!(delays[0].to_nanos(), delay.to_nanos());
488                updated_header
489            }
490            _ => panic!(),
491        };
492
493        let new_header2 = match new_header.process(&node2_sk).unwrap().data {
494            ProcessedHeaderData::ForwardHop {
495                updated_header,
496                next_hop_address,
497                delay,
498            } => {
499                assert_eq!(
500                    NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
501                    next_hop_address
502                );
503                assert_eq!(delays[1].to_nanos(), delay.to_nanos());
504                updated_header
505            }
506            _ => panic!(),
507        };
508        match new_header2.process(&node3_sk).unwrap().data {
509            ProcessedHeaderData::FinalHop {
510                destination,
511                identifier: _,
512            } => {
513                assert_eq!(route_destination.address, destination);
514            }
515            _ => panic!(),
516        };
517    }
518
519    #[test]
520    #[allow(deprecated)]
521    fn it_returns_correct_routing_information_at_each_hop_for_route_of_3_mixnodes_with_legacy_processing(
522    ) {
523        let node1_sk = PrivateKey::from([
524            202, 37, 190, 57, 90, 36, 148, 40, 37, 203, 207, 229, 5, 80, 8, 77, 227, 95, 67, 20,
525            47, 83, 220, 34, 164, 207, 5, 212, 97, 151, 142, 168,
526        ]);
527        let node1_pk = PublicKey::from([
528            105, 91, 210, 146, 245, 155, 27, 169, 192, 123, 75, 121, 19, 204, 59, 187, 190, 150,
529            131, 118, 151, 77, 180, 144, 253, 88, 6, 212, 63, 5, 51, 7,
530        ]);
531
532        let node2_sk = PrivateKey::from([
533            130, 31, 0, 83, 139, 16, 225, 239, 132, 130, 122, 18, 217, 187, 91, 87, 250, 137, 152,
534            220, 254, 153, 246, 249, 252, 43, 153, 191, 152, 48, 154, 170,
535        ]);
536        let node2_pk = PublicKey::from([
537            178, 47, 98, 179, 103, 199, 16, 245, 35, 85, 9, 63, 138, 212, 83, 233, 169, 31, 205,
538            20, 73, 238, 141, 204, 19, 35, 226, 138, 44, 67, 225, 46,
539        ]);
540
541        let node3_sk = PrivateKey::from([
542            116, 204, 108, 186, 75, 233, 232, 22, 79, 66, 65, 176, 196, 246, 253, 30, 133, 153,
543            109, 229, 133, 177, 40, 42, 175, 72, 80, 70, 161, 7, 187, 155,
544        ]);
545        let node3_pk = PublicKey::from([
546            21, 93, 4, 80, 178, 177, 7, 218, 192, 213, 58, 157, 239, 242, 139, 45, 75, 26, 225, 54,
547            174, 21, 159, 25, 62, 87, 187, 46, 92, 246, 136, 81,
548        ]);
549
550        let node1 = Node::new(
551            NodeAddressBytes::from_bytes([1u8; NODE_ADDRESS_LENGTH]),
552            node1_pk,
553        );
554        let node2 = Node::new(
555            NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
556            node2_pk,
557        );
558        let node3 = Node::new(
559            NodeAddressBytes::from_bytes([3u8; NODE_ADDRESS_LENGTH]),
560            node3_pk,
561        );
562        let initial_secret = StaticSecret::from([
563            104, 106, 58, 28, 53, 127, 216, 216, 8, 84, 74, 171, 220, 71, 145, 25, 205, 24, 253,
564            23, 120, 124, 255, 114, 14, 246, 179, 119, 101, 14, 10, 89,
565        ]);
566
567        let route = [node1, node2, node3];
568        let route_destination = destination_fixture();
569        let average_delay = 1;
570        let delays =
571            delays::generate_from_average_duration(route.len(), Duration::from_secs(average_delay));
572        let sphinx_header =
573            SphinxHeader::new_legacy(&initial_secret, &route, &delays, &route_destination)
574                .into_header();
575
576        //let (new_header, next_hop_address, _) = sphinx_header.process(node1_sk).unwrap();
577        let new_header = match sphinx_header
578            .unchecked_process_as_legacy(&node1_sk)
579            .unwrap()
580            .data
581        {
582            ProcessedHeaderData::ForwardHop {
583                updated_header,
584                next_hop_address,
585                delay,
586            } => {
587                assert_eq!(
588                    NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
589                    next_hop_address
590                );
591                assert_eq!(delays[0].to_nanos(), delay.to_nanos());
592                updated_header
593            }
594            _ => panic!(),
595        };
596
597        let new_header2 = match new_header
598            .unchecked_process_as_legacy(&node2_sk)
599            .unwrap()
600            .data
601        {
602            ProcessedHeaderData::ForwardHop {
603                updated_header,
604                next_hop_address,
605                delay,
606            } => {
607                assert_eq!(
608                    NodeAddressBytes::from_bytes([3u8; NODE_ADDRESS_LENGTH]),
609                    next_hop_address
610                );
611                assert_eq!(delays[1].to_nanos(), delay.to_nanos());
612                updated_header
613            }
614            _ => panic!(),
615        };
616        match new_header2
617            .unchecked_process_as_legacy(&node3_sk)
618            .unwrap()
619            .data
620        {
621            ProcessedHeaderData::FinalHop {
622                destination,
623                identifier: _,
624            } => {
625                assert_eq!(route_destination.address, destination);
626            }
627            _ => panic!(),
628        };
629    }
630}
631
632#[cfg(test)]
633mod unwrap_routing_information {
634    use crate::constants::{
635        HEADER_INTEGRITY_MAC_SIZE, NODE_ADDRESS_LENGTH, NODE_META_INFO_SIZE,
636        STREAM_CIPHER_OUTPUT_LENGTH,
637    };
638    use crate::crypto;
639    use crate::header::routing::nodes::{
640        EncryptedRoutingInformation, ParsedRawRoutingInformationData,
641    };
642    use crate::header::routing::{ENCRYPTED_ROUTING_INFO_SIZE, FORWARD_HOP};
643    use crate::utils;
644
645    #[test]
646    fn it_returns_correct_unwrapped_routing_information() {
647        let mut routing_info = [9u8; ENCRYPTED_ROUTING_INFO_SIZE];
648        routing_info[0] = FORWARD_HOP;
649        // reserved 0 byte for version
650        routing_info[1] = 0;
651
652        let stream_cipher_key = [1u8; crypto::STREAM_CIPHER_KEY_SIZE];
653        let pseudorandom_bytes = crypto::generate_pseudorandom_bytes(
654            &stream_cipher_key,
655            &crypto::STREAM_CIPHER_INIT_VECTOR,
656            STREAM_CIPHER_OUTPUT_LENGTH,
657        );
658        let encrypted_routing_info_vec = utils::bytes::xor(
659            &routing_info,
660            &pseudorandom_bytes[..ENCRYPTED_ROUTING_INFO_SIZE],
661        );
662        let mut encrypted_routing_info_array = [0u8; ENCRYPTED_ROUTING_INFO_SIZE];
663        encrypted_routing_info_array.copy_from_slice(&encrypted_routing_info_vec);
664
665        let enc_routing_info =
666            EncryptedRoutingInformation::from_bytes(encrypted_routing_info_array);
667
668        let expected_next_hop_encrypted_routing_information = [
669            routing_info[NODE_META_INFO_SIZE + HEADER_INTEGRITY_MAC_SIZE..].to_vec(),
670            pseudorandom_bytes
671                [NODE_META_INFO_SIZE + HEADER_INTEGRITY_MAC_SIZE + ENCRYPTED_ROUTING_INFO_SIZE..]
672                .to_vec(),
673        ]
674        .concat();
675        let next_hop_encapsulated_routing_info =
676            match enc_routing_info.unwrap(&stream_cipher_key).unwrap().data {
677                ParsedRawRoutingInformationData::ForwardHop {
678                    next_hop_address,
679                    new_routing_information,
680                    ..
681                } => {
682                    assert_eq!(
683                        routing_info[2..2 + NODE_ADDRESS_LENGTH],
684                        next_hop_address.to_bytes()
685                    );
686                    assert_eq!(
687                        routing_info
688                            [NODE_ADDRESS_LENGTH..NODE_ADDRESS_LENGTH + HEADER_INTEGRITY_MAC_SIZE]
689                            .to_vec(),
690                        new_routing_information.integrity_mac.as_bytes().to_vec()
691                    );
692                    new_routing_information
693                }
694                _ => panic!(),
695            };
696
697        let next_hop_encrypted_routing_information = next_hop_encapsulated_routing_info
698            .enc_routing_information
699            .as_ref();
700
701        for i in 0..expected_next_hop_encrypted_routing_information.len() {
702            assert_eq!(
703                expected_next_hop_encrypted_routing_information[i],
704                next_hop_encrypted_routing_information[i]
705            );
706        }
707    }
708}
709
710#[cfg(test)]
711mod unwrapping_using_previously_expanded_shared_secret {
712    use super::*;
713    use crate::constants::NODE_ADDRESS_LENGTH;
714    use crate::test_utils::fixtures::{destination_fixture, keygen};
715    use std::time::Duration;
716
717    #[test]
718    fn produces_same_result_for_forward_hop() {
719        let (node1_sk, node1_pk) = keygen();
720        let node1 = Node {
721            address: NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
722            pub_key: node1_pk,
723        };
724        let (_, node2_pk) = keygen();
725        let node2 = Node {
726            address: NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]),
727            pub_key: node2_pk,
728        };
729        let route = [node1, node2];
730        let destination = destination_fixture();
731        let initial_secret = StaticSecret::random();
732        let average_delay = 1;
733        let delays =
734            delays::generate_from_average_duration(route.len(), Duration::from_secs(average_delay));
735        let sphinx_header =
736            SphinxHeader::new_current(&initial_secret, &route, &delays, &destination).into_header();
737        let initial_secret = sphinx_header.shared_secret;
738
739        let normally_unwrapped = match sphinx_header.clone().process(&node1_sk).unwrap().data {
740            ProcessedHeaderData::ForwardHop { updated_header, .. } => updated_header,
741            _ => unreachable!(),
742        };
743
744        let expanded_secret = node1_sk
745            .diffie_hellman(&initial_secret)
746            .expand_shared_secret();
747
748        let derived_unwrapped = match sphinx_header
749            .process_with_expanded_secret(&expanded_secret)
750            .unwrap()
751            .data
752        {
753            ProcessedHeaderData::ForwardHop { updated_header, .. } => updated_header,
754            _ => unreachable!(),
755        };
756
757        assert_eq!(
758            normally_unwrapped.shared_secret,
759            derived_unwrapped.shared_secret
760        );
761        assert_eq!(
762            normally_unwrapped.routing_info.to_bytes(),
763            derived_unwrapped.routing_info.to_bytes()
764        )
765    }
766
767    #[test]
768    fn produces_same_result_for_final_hop() {
769        let (node1_sk, node1_pk) = keygen();
770        let node1 = Node {
771            address: NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
772            pub_key: node1_pk,
773        };
774        let route = [node1];
775        let destination = destination_fixture();
776        let initial_secret = StaticSecret::random();
777        let average_delay = 1;
778        let delays =
779            delays::generate_from_average_duration(route.len(), Duration::from_secs(average_delay));
780        let sphinx_header =
781            SphinxHeader::new_current(&initial_secret, &route, &delays, &destination).into_header();
782        let initial_secret = sphinx_header.shared_secret;
783
784        let normally_unwrapped = sphinx_header.clone().process(&node1_sk).unwrap();
785        let normally_unwrapped = match normally_unwrapped.data {
786            ProcessedHeaderData::FinalHop {
787                destination,
788                identifier,
789            } => (destination, identifier, normally_unwrapped.payload_key),
790            _ => unreachable!(),
791        };
792
793        let expanded_secret = node1_sk
794            .diffie_hellman(&initial_secret)
795            .expand_shared_secret();
796
797        let derived_unwrapped = sphinx_header
798            .process_with_expanded_secret(&expanded_secret)
799            .unwrap();
800
801        let derived_unwrapped = match derived_unwrapped.data {
802            ProcessedHeaderData::FinalHop {
803                destination,
804                identifier,
805            } => (destination, identifier, derived_unwrapped.payload_key),
806            _ => unreachable!(),
807        };
808
809        assert_eq!(normally_unwrapped.0, derived_unwrapped.0);
810        assert_eq!(normally_unwrapped.1, derived_unwrapped.1);
811        assert_eq!(normally_unwrapped.2.to_vec(), derived_unwrapped.2.to_vec())
812    }
813}
814
815#[cfg(test)]
816mod converting_header_to_bytes {
817    use super::*;
818    use crate::test_utils::fixtures::encapsulated_routing_information_fixture;
819
820    #[test]
821    fn it_is_possible_to_convert_back_and_forth() {
822        let encapsulated_routing_info = Box::new(encapsulated_routing_information_fixture());
823        let header = SphinxHeader {
824            shared_secret: PublicKey::from(&StaticSecret::random()),
825            routing_info: encapsulated_routing_info,
826        };
827
828        let header_bytes = header.to_bytes();
829        let recovered_header = SphinxHeader::from_bytes(&header_bytes).unwrap();
830
831        assert_eq!(
832            header.shared_secret.as_bytes(),
833            recovered_header.shared_secret.as_bytes()
834        );
835        assert_eq!(
836            header.routing_info.to_bytes(),
837            recovered_header.routing_info.to_bytes()
838        );
839    }
840}